Skip to content

Commit

Permalink
remove dependency on bind:this for presence
Browse files Browse the repository at this point in the history
  • Loading branch information
huntabyte committed Apr 16, 2024
1 parent 3853dbf commit d696b96
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 22 deletions.
18 changes: 14 additions & 4 deletions packages/bits-ui/src/lib/bits/accordion/accordion.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,18 +249,21 @@ type AccordionContentStateProps = BoxedValues<{
}> &
ReadonlyBoxedValues<{
forceMount: boolean;
id: string;
}>;

class AccordionContentState {
item = undefined as unknown as AccordionItemState;
id = undefined as unknown as ReadonlyBox<string>;
node = boxedState<HTMLElement | null>(null);
originalStyles: { transitionDuration: string; animationName: string } | undefined = undefined;
isMountAnimationPrevented = false;
width = boxedState(0);
height = boxedState(0);
presentEl = boxedState<HTMLElement | undefined>(undefined);
forceMount = undefined as unknown as ReadonlyBox<boolean>;
present = $derived(this.item.isSelected);

#attrs = $derived({
id: this.id.value,
"data-state": getDataOpenClosed(this.item.isSelected),
"data-disabled": getDataDisabled(this.item.isDisabled),
"data-value": this.item.value,
Expand All @@ -276,7 +279,13 @@ class AccordionContentState {
this.item = item;
this.forceMount = props.forceMount;
this.isMountAnimationPrevented = this.item.isSelected;
this.presentEl = props.presentEl;
this.id = props.id;

$effect.root(() => {
tick().then(() => {
this.node.value = document.getElementById(this.id.value);
});
});

$effect.pre(() => {
const rAF = requestAnimationFrame(() => {
Expand All @@ -291,10 +300,11 @@ class AccordionContentState {
$effect(() => {
// eslint-disable-next-line no-unused-expressions
this.item.isSelected;
const node = untrack(() => this.presentEl.value);
const node = this.node.value;
if (!node) return;

tick().then(() => {
if (!this.node) return;
// get the dimensions of the element
this.originalStyles = this.originalStyles || {
transitionDuration: node.style.transitionDuration,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,30 @@
import Presence from "$lib/bits/utilities/presence.svelte";
import { box, readonlyBox } from "$lib/internal/box.svelte.js";
import { styleToString } from "$lib/internal/style.js";
import { generateId } from "$lib/internal/id.js";
let {
child,
asChild,
el: elProp = $bindable(),
id: idProp = generateId(),
forceMount: forceMountProp = false,
children,
style: styleProp = {},
...restProps
}: AccordionContentProps & { forceMount?: boolean } = $props();
}: AccordionContentProps = $props();
const el = box(
() => elProp,
(v) => (elProp = v)
);
const id = readonlyBox(() => idProp);
const forceMount = readonlyBox(() => forceMountProp);
const content = getAccordionContentState({ presentEl: el, forceMount });
const content = getAccordionContentState({ presentEl: el, forceMount, id });
</script>

<Presence forceMount={true} present={content.present} bind:el={el.value}>
{#snippet presence({ node, present })}
<Presence forceMount={true} present={content.item.isSelected} node={content.node}>
{#snippet presence({ present })}
{@const mergedProps = {
...restProps,
...content.props,
Expand All @@ -37,7 +39,7 @@
{#if asChild}
{@render child?.({ props: mergedProps })}
{:else}
<div {...mergedProps} bind:this={node.value} hidden={present.value ? undefined : true}>
<div {...mergedProps} bind:this={el.value} hidden={present.value ? undefined : true}>
{@render children?.()}
</div>
{/if}
Expand Down
4 changes: 3 additions & 1 deletion packages/bits-ui/src/lib/bits/accordion/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,15 @@ export type AccordionContentPropsWithoutHTML<
inTransitionConfig?: TransitionParams<In>;
outTransition?: Out;
outTransitionConfig?: TransitionParams<Out>;
forceMount?: boolean;
id?: string;
}>;

export type AccordionContentProps<
T extends Transition = Transition,
In extends Transition = Transition,
Out extends Transition = Transition,
> = AccordionContentPropsWithoutHTML<T, In, Out> & PrimitiveDivAttributes;
> = AccordionContentPropsWithoutHTML<T, In, Out> & Omit<PrimitiveDivAttributes, "id">;

export type AccordionHeaderPropsWithoutHTML = WithAsChild<{
asChild?: boolean;
Expand Down
15 changes: 5 additions & 10 deletions packages/bits-ui/src/lib/bits/utilities/presence.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import type { Snippet } from "svelte";
import { Box, box, readonlyBox } from "$lib/internal/box.svelte.js";
import { Box, readonlyBox } from "$lib/internal/box.svelte.js";
import { usePresence } from "$lib/internal/use-presence.svelte.js";
type Props = {
Expand All @@ -14,29 +14,24 @@
*/
forceMount?: boolean;
presence?: Snippet<[{ present: { value: boolean }; node: Box<HTMLElement | undefined> }]>;
presence?: Snippet<[{ present: { value: boolean } }]>;
el?: HTMLElement;
node: Box<HTMLElement | null>;
};
let {
present: presentProp,
forceMount: forceMountProp = false,
presence,
el = $bindable(),
node,
}: Props = $props();
const forceMount = readonlyBox(() => forceMountProp);
const node = box(
() => el,
(v) => (el = v)
);
const present = readonlyBox(() => presentProp);
const isPresent = usePresence(present, node);
</script>

{#if forceMount.value || present.value || isPresent.value}
{@render presence?.({ present: isPresent, node })}
{@render presence?.({ present: isPresent })}
{/if}
13 changes: 12 additions & 1 deletion packages/bits-ui/src/lib/internal/use-presence.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@ import { onDestroy, tick } from "svelte";
import { type Box, type ReadonlyBox, box, boxedState, watch } from "./box.svelte.js";
import { useStateMachine } from "$lib/internal/index.js";

export function usePresence(present: ReadonlyBox<boolean>, node: Box<HTMLElement | undefined>) {
export function usePresence(present: ReadonlyBox<boolean>, node: Box<HTMLElement | null>) {
const styles = boxedState({}) as unknown as Box<CSSStyleDeclaration>;
const prevAnimationNameState = boxedState("none");
const initialState = present.value ? "mounted" : "unmounted";

$effect.pre(() => {
// tick().then(() => {
// const el = document.getElementById(id.value);
// console.log(el);
// });
});

const { state, dispatch } = useStateMachine(initialState, {
mounted: {
UNMOUNT: "unmounted",
Expand All @@ -24,6 +31,7 @@ export function usePresence(present: ReadonlyBox<boolean>, node: Box<HTMLElement
watch(
present,
async (currPresent, prevPresent) => {
if (!node.value) return;
const hasPresentChanged = currPresent !== prevPresent;
if (!hasPresentChanged) return;

Expand Down Expand Up @@ -61,6 +69,7 @@ export function usePresence(present: ReadonlyBox<boolean>, node: Box<HTMLElement
*/

function handleAnimationEnd(event: AnimationEvent) {
if (!node.value) return;
const currAnimationName = getAnimationName(node.value);
const isCurrentAnimation =
currAnimationName.includes(event.animationName) || currAnimationName === "none";
Expand All @@ -71,11 +80,13 @@ export function usePresence(present: ReadonlyBox<boolean>, node: Box<HTMLElement
}

function handleAnimationStart(event: AnimationEvent) {
if (!node.value) return;
if (event.target === node.value) {
prevAnimationNameState.value = getAnimationName(node.value);
}
}
const stateWatcher = watch(state, () => {
if (!node.value) return;
const currAnimationName = getAnimationName(node.value);
prevAnimationNameState.value = state.value === "mounted" ? currAnimationName : "none";
});
Expand Down

0 comments on commit d696b96

Please sign in to comment.