Skip to content

Commit

Permalink
messy but she works
Browse files Browse the repository at this point in the history
  • Loading branch information
huntabyte committed Apr 15, 2024
1 parent 00d0a60 commit f96dd2c
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 43 deletions.
65 changes: 38 additions & 27 deletions packages/bits-ui/src/lib/bits/accordion/accordion.svelte.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getContext, onMount, setContext, untrack } from "svelte";
import { getContext, onMount, setContext, tick, untrack } from "svelte";
import {
type Box,
type BoxedValues,
Expand Down Expand Up @@ -253,17 +253,21 @@ class AccordionTriggerState {

type AccordionContentStateProps = BoxedValues<{
presentEl: HTMLElement | undefined;
}>;
}> &
ReadonlyBoxedValues<{
forceMount: boolean;
}>;

class AccordionContentState {
item = undefined as unknown as AccordionItemState;
currentStyle = boxedState<{ transitionDuration: string; animationName: string } | undefined>(
originalStyles = boxedState<{ transitionDuration: string; animationName: string } | undefined>(
undefined
);
isMountAnimationPrevented = $state(false);
width = boxedState(0);
height = boxedState(0);
presentEl: Box<HTMLElement | undefined> = boxedState<HTMLElement | undefined>(undefined);
forceMount = undefined as unknown as ReadonlyBox<boolean>;
present = $derived(this.item.isSelected);
#attrs: Record<string, unknown> = $derived({
"data-state": getDataOpenClosed(this.item.isSelected),
Expand All @@ -279,41 +283,48 @@ class AccordionContentState {

constructor(props: AccordionContentStateProps, item: AccordionItemState) {
this.item = item;
this.forceMount = props.forceMount;
this.isMountAnimationPrevented = this.item.isSelected;
this.presentEl = props.presentEl;

$effect.root(() => {
requestAnimationFrame(() => {
$effect.pre(() => {
const rAF = requestAnimationFrame(() => {
this.isMountAnimationPrevented = false;
});

return () => {
cancelAnimationFrame(rAF);
};
});

$effect.pre(() => {
$effect(() => {
// eslint-disable-next-line no-unused-expressions
this.item.isSelected;
const node = this.presentEl.value;
const node = untrack(() => this.presentEl.value);
if (!node) return;

this.currentStyle.value = this.currentStyle.value || {
transitionDuration: node.style.transitionDuration,
animationName: node.style.animationName,
};

// block any animations/transitions so the element renders at full dimensions
node.style.transitionDuration = "0s";
node.style.animationName = "none";

// get the dimensions of the element
const rect = node.getBoundingClientRect();
this.height.value = rect.height;
this.width.value = rect.width;

// unblock any animations/transitions that were originally set if not the initial render
if (!untrack(() => this.isMountAnimationPrevented)) {
const { animationName, transitionDuration } = this.currentStyle.value;
node.style.transitionDuration = transitionDuration;
node.style.animationName = animationName;
}
tick().then(() => {
// get the dimensions of the element
this.originalStyles.value = this.originalStyles.value || {
transitionDuration: node.style.transitionDuration,
animationName: node.style.animationName,
};

// block any animations/transitions so the element renders at full dimensions
node.style.transitionDuration = "0s";
node.style.animationName = "none";

const rect = node.getBoundingClientRect();
this.height.value = rect.height;
this.width.value = rect.width;

// unblock any animations/transitions that were originally set if not the initial render
if (!untrack(() => this.isMountAnimationPrevented)) {
const { animationName, transitionDuration } = this.originalStyles.value;
node.style.transitionDuration = transitionDuration;
node.style.animationName = animationName;
}
});
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
import { getAccordionContentState } from "../accordion.svelte.js";
import type { AccordionContentProps } from "../types.js";
import Presence from "$lib/bits/utilities/presence.svelte";
import { box } from "$lib/internal/box.svelte.js";
import { box, readonlyBox } from "$lib/internal/box.svelte.js";
import { styleToString } from "$lib/internal/style.js";
let {
child,
asChild,
el: elProp = $bindable(),
forceMount = false,
forceMount: forceMountProp = false,
children,
style: styleProp = {},
...restProps
Expand All @@ -20,10 +20,11 @@
(v) => (elProp = v)
);
const content = getAccordionContentState({ presentEl: el });
const forceMount = readonlyBox(() => forceMountProp);
const content = getAccordionContentState({ presentEl: el, forceMount });
</script>

<Presence forceMount={true} present={forceMount || content.item.isSelected} bind:el={el.value}>
<Presence forceMount={true} present={content.present} bind:el={el.value}>
{#snippet presence({ node, present })}
{@const mergedProps = {
...restProps,
Expand Down
5 changes: 1 addition & 4 deletions packages/bits-ui/src/lib/bits/utilities/presence.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,14 @@
el = $bindable(),
}: Props = $props();
const present = box(
() => presentProp,
(v) => (presentProp = v)
);
const forceMount = readonlyBox(() => forceMountProp);
const node = box(
() => el,
(v) => (el = v)
);
const present = readonlyBox(() => presentProp);
const isPresent = usePresence(present, node);
</script>

Expand Down
6 changes: 5 additions & 1 deletion packages/bits-ui/src/lib/internal/box.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ export class Box<T> {
}
}

export function watch<T>(box: Box<T>, callback: WatcherCallback<T>, options: WatchOptions = {}) {
export function watch<T>(
box: ReadonlyBox<T> | Box<T>,
callback: WatcherCallback<T>,
options: WatchOptions = {}
) {
let prev = $state(box.value);
let ranOnce = false;

Expand Down
13 changes: 6 additions & 7 deletions packages/bits-ui/src/lib/internal/use-presence.svelte.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { onDestroy, tick } from "svelte";
import { type Box, box, boxedState, watch } from "./box.svelte.js";
import { type Box, type ReadonlyBox, box, boxedState, watch } from "./box.svelte.js";
import { useStateMachine } from "$lib/internal/index.js";

export function usePresence(present: Box<boolean>, node: Box<HTMLElement | undefined>) {
export function usePresence(present: ReadonlyBox<boolean>, node: Box<HTMLElement | undefined>) {
const styles = boxedState({}) as unknown as Box<CSSStyleDeclaration>;
const prevAnimationNameState = boxedState("none");
const initialState = present.value ? "mounted" : "unmounted";
Expand Down Expand Up @@ -75,6 +75,10 @@ export function usePresence(present: Box<boolean>, node: Box<HTMLElement | undef
prevAnimationNameState.value = getAnimationName(node.value);
}
}
const stateWatcher = watch(state, () => {
const currAnimationName = getAnimationName(node.value);
prevAnimationNameState.value = state.value === "mounted" ? currAnimationName : "none";
});

const watcher = watch(
node,
Expand All @@ -94,11 +98,6 @@ export function usePresence(present: Box<boolean>, node: Box<HTMLElement | undef
{ immediate: true }
);

const stateWatcher = watch(state, () => {
const currAnimationName = getAnimationName(node.value);
prevAnimationNameState.value = state.value === "mounted" ? currAnimationName : "none";
});

onDestroy(() => {
watcher();
stateWatcher();
Expand Down

0 comments on commit f96dd2c

Please sign in to comment.