Skip to content

Commit

Permalink
presence and portal updates
Browse files Browse the repository at this point in the history
  • Loading branch information
huntabyte committed Apr 23, 2024
1 parent e332589 commit 20799a8
Show file tree
Hide file tree
Showing 11 changed files with 173 additions and 140 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import type { ContentProps } from "../index.js";
import { setPopoverContentState } from "../popover.svelte.js";
import { PopperLayer } from "$lib/bits/utilities/popper-layer/index.js";
import { readonlyBox, useId } from "$lib/internal/index.js";
import { mergeProps, readonlyBox, useId } from "$lib/internal/index.js";
import Portal from "$lib/bits/utilities/portal/portal.svelte";
let {
asChild,
Expand All @@ -12,6 +13,8 @@
style = {},
id = useId(),
forceMount = false,
// eslint-disable-next-line ts/no-unused-vars, unused-imports/no-unused-vars
hidden: hiddenProp = undefined,
...restProps
}: ContentProps = $props();
Expand All @@ -20,27 +23,25 @@
});
</script>

<PopperLayer
{...restProps}
forceMount={true}
present={state.root.open.value || forceMount}
{id}
{style}
onInteractOutside={state.root.close}
onEscape={state.root.close}
>
{#snippet popper({ props })}
{@const mergedProps = {
...restProps,
...state.props,
...props,
}}
{#if asChild}
{@render child?.({ props: mergedProps })}
{:else}
<div {...mergedProps} bind:this={el}>
{@render children?.()}
</div>
{/if}
{/snippet}
</PopperLayer>
<Portal>
<PopperLayer
{...restProps}
present={state.root.open.value || forceMount}
{id}
{style}
onInteractOutside={state.root.close}
onEscape={state.root.close}
>
{#snippet popper({ props })}
{@const mergedProps = mergeProps(restProps, state.props, props)}
{mergedProps.hidden}
{#if asChild}
{@render child?.({ props: mergedProps })}
{:else}
<div {...mergedProps} bind:this={el}>
{@render children?.()}
</div>
{/if}
{/snippet}
</PopperLayer>
</Portal>
1 change: 0 additions & 1 deletion packages/bits-ui/src/lib/bits/popover/popover.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ class PopoverContentState {
props = $derived({
id: this.id.value,
tabindex: -1,
hidden: getHiddenAttr(!this.root.open.value),
"data-state": getDataOpenClosed(this.root.open.value),
"data-popover-content": "",
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script lang="ts">
import { setFloatingContentState } from "../floating-layer.svelte.js";
import type { ContentProps } from "./index.js";
import { readonlyBox, useId } from "$lib/internal/index.js";
import { mergeProps, readonlyBox, useId } from "$lib/internal/index.js";
let {
content,
Expand Down Expand Up @@ -45,8 +45,10 @@
present: readonlyBox(() => present),
wrapperId: readonlyBox(() => wrapperId),
});
const mergedProps = $derived(mergeProps(state.wrapperProps));
</script>

<div {...state.wrapperProps}>
<div {...mergedProps}>
{@render content?.({ props: state.props })}
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ class FloatingContentState {
wrapperProps = $derived({
id: this.wrapperId.value,
"data-bits-floating-content-wrapper": "",
style: styleToString({
style: {
...this.floating.floatingStyles,
// keep off page when measuring
transform: this.floating.isPositioned
Expand All @@ -198,7 +198,7 @@ class FloatingContentState {
visibility: "hidden",
"pointer-events": "none",
}),
}),
},
// Floating UI calculates logical alignment based the `dir` attribute
dir: this.dir.value,
} as const);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,26 @@
import { EscapeLayer } from "$lib/bits/utilities/escape-layer/index.js";
import { DismissableLayer } from "$lib/bits/utilities/dismissable-layer/index.js";
import { TextSelectionLayer } from "$lib/bits/utilities/text-selection-layer/index.js";
import { Portal } from "$lib/bits/utilities/portal/index.js";
import { PresenceLayer } from "$lib/bits/utilities/presence-layer/index.js";
import { mergeProps } from "$lib/internal/merge-props.js";
let { popper, ...restProps }: PopperLayerProps = $props();
</script>

<Portal forceMount={true}>
{#snippet portal({ portalProps })}
<PresenceLayer {...restProps}>
{#snippet presence({ present })}
<FloatingLayer.Content
{...restProps}
wrapperId={portalProps.id}
present={present.value}
>
{#snippet content({ props })}
<EscapeLayer {...restProps} present={present.value}>
<DismissableLayer {...restProps} present={present.value}>
<TextSelectionLayer {...restProps} present={present.value}>
{@render popper?.({
props: mergeProps(props, { hidden: !present.value }),
})}
</TextSelectionLayer>
</DismissableLayer>
</EscapeLayer>
{/snippet}
</FloatingLayer.Content>
<PresenceLayer {...restProps}>
{#snippet presence({ present })}
<FloatingLayer.Content {...restProps} present={present.value}>
{#snippet content({ props })}
<EscapeLayer {...restProps} present={present.value}>
<DismissableLayer {...restProps} present={present.value}>
<TextSelectionLayer {...restProps} present={present.value}>
{@render popper?.({
props: mergeProps(props),
})}
</TextSelectionLayer>
</DismissableLayer>
</EscapeLayer>
{/snippet}
</PresenceLayer>
</FloatingLayer.Content>
{/snippet}
</Portal>
</PresenceLayer>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script lang="ts">
import type { Snippet } from "svelte";
const { children }: { children?: Snippet } = $props();
</script>

{@render children?.()}
52 changes: 41 additions & 11 deletions packages/bits-ui/src/lib/bits/utilities/portal/portal.svelte
Original file line number Diff line number Diff line change
@@ -1,16 +1,46 @@
<script lang="ts">
import { usePortal } from "./use-portal.svelte.js";
import { getAllContexts, mount, unmount } from "svelte";
import PortalConsumer from "./portal-consumer.svelte";
import type { PortalProps } from "./types.js";
import { readonlyBox, useId } from "$lib/internal/index.js";
import { isBrowser } from "$lib/internal/is.js";
let { id = useId(), to = "body", forceMount, portal }: PortalProps = $props();
let { to = "body", children }: PortalProps = $props();
const state = usePortal(
readonlyBox(() => id),
readonlyBox(() => to)
);
</script>
const context = getAllContexts();
let target = $derived(getTarget());
function getTarget() {
if (!isBrowser) return null;
let target: HTMLElement | null;
if (typeof to === "string") {
target = document.querySelector(to);
if (target === null) {
throw new Error(`Target element "${to}" not found.`);
}
} else if (to instanceof HTMLElement) {
target = to;
} else {
throw new TypeError(
`Unknown portal target type: ${
to === null ? "null" : typeof to
}. Allowed types: string (CSS selector) or HTMLElement.`
);
}
return target;
}
{#if forceMount}
{@render portal?.({ portalProps: state.props })}
{/if}
let instance: any;
$effect(() => {
if (!target) {
return unmount(instance);
}
instance = mount(PortalConsumer, { target, props: { children }, context });
return () => {
unmount(instance);
};
});
</script>
16 changes: 1 addition & 15 deletions packages/bits-ui/src/lib/bits/utilities/portal/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,5 @@ export type PortalProps = {
*/
disabled?: boolean;

/**
* Whether to force mount the portal content for more
* advanced animation control
*
* @defaultValue false
*
*/
forceMount?: boolean;

/**
* The id of the portal content
*/
id?: string;

portal?: Snippet<[{ portalProps: { id: string; "data-portal": string } }]>;
children?: Snippet;
};
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,20 @@
import { usePresence } from "$lib/internal/use-presence.svelte.js";
let {
present: presentProp,
present,
forceMount: forceMountProp = false,
presence,
id,
}: PresenceLayerProps = $props();
const forceMount = readonlyBox(() => forceMountProp);
const present = readonlyBox(() => presentProp);
const isPresent = usePresence(
present,
readonlyBox(() => present),
readonlyBox(() => id)
);
</script>

{#if forceMount.value || present.value || isPresent.value}
{#if forceMount.value || present || isPresent.value}
{@render presence?.({ present: isPresent })}
{/if}
Loading

0 comments on commit 20799a8

Please sign in to comment.