Skip to content

Commit

Permalink
no exit transitions
Browse files Browse the repository at this point in the history
  • Loading branch information
huntabyte committed Apr 20, 2024
1 parent 62dbabd commit 6a23442
Show file tree
Hide file tree
Showing 15 changed files with 91 additions and 23 deletions.
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 @@ -30,7 +30,6 @@ class PopoverRootState {
}

close = () => {
console.log("closing!");
if (!this.open.value) return;
this.open.value = false;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
onInteractOutsideStart = noop,
id,
children,
present,
}: DismissableLayerProps = $props();
useDismissableLayer({
id: readonlyBox(() => id),
behaviorType: readonlyBox(() => behaviorType),
onInteractOutside: readonlyBox(() => onInteractOutside),
onInteractOutsideStart: readonlyBox(() => onInteractOutsideStart),
present: readonlyBox(() => present),
});
</script>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { onDestroy, onMount, untrack } from "svelte";
import type {
DismissableLayerProps,
InteractOutsideBehaviorType,
Expand All @@ -16,6 +17,7 @@ import {
getOwnerDocument,
isElement,
isOrContainsTarget,
noop,
useNodeById,
} from "$lib/internal/index.js";

Expand Down Expand Up @@ -54,31 +56,58 @@ export class DismissableLayerState {
#isResponsibleLayer = false;
node: Box<HTMLElement | null>;
#documentObj = undefined as unknown as Document;
#present: ReadonlyBox<boolean>;

constructor(props: DismissableLayerStateProps) {
this.node = useNodeById(props.id);
this.#behaviorType = props.behaviorType;
this.#interactOutsideStartProp = props.onInteractOutsideStart;
this.#interactOutsideProp = props.onInteractOutside;
this.#present = props.present;

layers.set(this, this.#behaviorType);
$effect(() => {
console.log("present", this.#present.value);
});

$effect(() => {
this.#documentObj = getOwnerDocument(this.node.value);
});

const unsubEvents = this.#addEventListeners();
let unsubEvents = noop;

$effect(() => {
if (this.#present.value) {
layers.set(
this,
untrack(() => this.#behaviorType)
);
unsubEvents = this.#addEventListeners();
}
return () => {
unsubEvents();
this.#resetState.destroy();
this.#resetState();
layers.delete(this);
this.#onInteractOutsideStart.destroy();
this.#onInteractOutside.destroy();
layers.delete(this);
unsubEvents();
};
});

$effect(() => {
return () => {
// onDestroy, cleanup anything leftover
untrack(() => {
this.#resetState.destroy();
layers.delete(this);
this.#onInteractOutsideStart.destroy();
this.#onInteractOutside.destroy();
unsubEvents();
});
};
});
}

#addEventListeners() {
console.log("adding event listeners");
return executeCallbacks(
/**
* CAPTURE INTERACTION START
Expand Down Expand Up @@ -131,15 +160,21 @@ export class DismissableLayerState {
}

#onInteractOutsideStart = debounce((e: InteractOutsideEvent) => {
const node = this.node.value!;
if (!this.#isResponsibleLayer || this.#isAnyEventIntercepted() || !isValidEvent(e, node))
console.log("onInteractOutsideStart");
if (!this.node.value) return;
if (
!this.#isResponsibleLayer ||
this.#isAnyEventIntercepted() ||
!isValidEvent(e, this.node.value)
)
return;
this.#interactOutsideStartProp.value(e);
if (e.defaultPrevented) return;
this.#isPointerDownOutside = true;
}, 10);

#onInteractOutside = debounce((e: InteractOutsideEvent) => {
console.log("onInteractOutside");
if (!this.node.value) return;

const behaviorType = this.#behaviorType.value;
Expand All @@ -156,19 +191,23 @@ export class DismissableLayerState {
}, 10);

#markInterceptedEvent = (e: HTMLElementEventMap[InteractOutsideInterceptEventType]) => {
console.log("markInterceptedEvent", e.type);
this.#interceptedEvents[e.type as InteractOutsideInterceptEventType] = true;
};

#markNonInterceptedEvent = (e: HTMLElementEventMap[InteractOutsideInterceptEventType]) => {
console.log("markNonInterceptedEvent", e.type);
this.#interceptedEvents[e.type as InteractOutsideInterceptEventType] = false;
};

#markResponsibleLayer = () => {
console.log("markResponsibleLayer");
if (!this.node.value) return;
this.#isResponsibleLayer = isResponsibleLayer(this.node.value);
};

#resetState = debounce(() => {
console.log("resetState");
for (const eventType in this.#interceptedEvents) {
this.#interceptedEvents[eventType as InteractOutsideInterceptEventType] = false;
}
Expand All @@ -177,6 +216,7 @@ export class DismissableLayerState {
}, 20);

#isAnyEventIntercepted() {
console.log("isAnyEventIntercepted");
return Object.values(this.#interceptedEvents).some(Boolean);
}
}
Expand All @@ -186,7 +226,7 @@ export function useDismissableLayer(props: DismissableLayerStateProps) {
}

function isResponsibleLayer(node: HTMLElement): boolean {
console.log(layers);
console.log("isResponsibleLayer");
const layersArr = [...layers];
/**
* We first check if we can find a top layer with `close` or `ignore`.
Expand All @@ -203,6 +243,7 @@ function isResponsibleLayer(node: HTMLElement): boolean {
}

function isValidEvent(e: InteractOutsideEvent, node: HTMLElement): boolean {
console.log("isValidEvent");
if ("button" in e && e.button > 0) return false;
const target = e.target;
if (!isElement(target)) return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ export type DismissableLayerProps = {
* @defaultValue `close`
*/
behaviorType?: InteractOutsideBehaviorType;

/**
* Whether the layer is active. Currently, we determine this with the
* `presence` returned from the `presence` layer.
*/
present: boolean;
};

export type InteractOutsideInterceptEventType =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
import { useEscapeLayer } from "./escape-layer.svelte.js";
import { noop, readonlyBox } from "$lib/internal/index.js";
let { behaviorType = "close", onEscape = noop, children }: EscapeLayerProps = $props();
let { behaviorType = "close", onEscape = noop, children, present }: EscapeLayerProps = $props();
useEscapeLayer({
behaviorType: readonlyBox(() => behaviorType),
onEscape: readonlyBox(() => onEscape),
present: readonlyBox(() => present),
});
</script>

Expand Down
6 changes: 6 additions & 0 deletions packages/bits-ui/src/lib/bits/utilities/escape-layer/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,10 @@ export type EscapeLayerProps = {
* @defaultValue `close`
*/
behaviorType?: EscapeBehaviorType;

/**
* Whether the layer is enabled. Currently, we determine this with the
* `presence` returned from the `presence` layer.
*/
present: boolean;
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@
{#snippet presence({ present })}
<FloatingLayer.Content {...restProps}>
{#snippet content({ props })}
<EscapeLayer.Root {...restProps}>
<DismissableLayer.Root {...restProps}>
<PreventTextSelectionOverflowLayer.Root {...restProps}>
<EscapeLayer.Root {...restProps} present={present.value}>
<DismissableLayer.Root {...restProps} present={present.value}>
<PreventTextSelectionOverflowLayer.Root
{...restProps}
present={present.value}
>
{@render popper?.({
props: { ...props, hidden: present.value ? undefined : true },
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
onPointerUp = noop,
id,
children,
present,
}: PreventTextSelectionOverflowLayerProps = $props();
usePreventTextSelectionOverflowLayer({
id: readonlyBox(() => id),
enabled: readonlyBox(() => enabled),
onPointerDown: readonlyBox(() => onPointerDown),
onPointerUp: readonlyBox(() => onPointerUp),
present: readonlyBox(() => present),
});
</script>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,10 @@ export type PreventTextSelectionOverflowLayerProps = {
* @defaultValue `true`
*/
enabled?: boolean;

/**
* Whether the layer is enabled. Currently, we determine this with the
* `presence` returned from the `presence` layer.
*/
present: boolean;
};
4 changes: 3 additions & 1 deletion packages/bits-ui/src/lib/internal/debounce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export function debounce<T extends (...args: any[]) => any>(fn: T, wait = 500) {
timeout = setTimeout(later, wait);
};

debounced.destroy = () => clearTimeout(timeout);
debounced.destroy = () => {
clearTimeout(timeout);
};
return debounced;
}
1 change: 1 addition & 0 deletions packages/bits-ui/src/lib/internal/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export function addEventListener(

// Return a function that removes the event listener from the target element(s).
return () => {
console.log("removing event listeners");
events.forEach((_event) => target.removeEventListener(_event, handler, options));
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import { Info } from "$icons/index.js";
import { Code } from "$lib/components/index.js";
import type { DataAttrSchema } from "$lib/types/index.js";
import { flyAndScale } from "$lib/utils/index.js";
export let attr: DataAttrSchema;
</script>
Expand All @@ -23,8 +22,6 @@
<Popover.Content
side="top"
sideOffset={10}
transition={flyAndScale}
transitionConfig={{ y: 8 }}
class="z-50 max-h-[400px] overflow-auto rounded-input border border-border bg-background p-4 shadow-popover"
>
<Code class="h-auto bg-transparent px-0 tracking-tight text-foreground">
Expand Down
3 changes: 0 additions & 3 deletions sites/docs/src/lib/components/api-ref/prop-copy.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<script lang="ts">
import { Popover } from "bits-ui";
import { Code } from "$lib/components/index.js";
import { flyAndScale } from "$lib/utils/index.js";
export let name: string;
</script>
Expand All @@ -16,8 +15,6 @@
<Popover.Content
side="top"
sideOffset={10}
transition={flyAndScale}
transitionConfig={{ y: 8 }}
class="z-50 max-h-[400px] overflow-auto rounded-input border border-border bg-background p-4 shadow-popover"
>
<button class="bg-transparent"> Copy to clipboard </button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { Info } from "$icons/index.js";
import { Code } from "$lib/components/index.js";
import type { PropType } from "$lib/types/index.js";
import { flyAndScale, parseTypeDef } from "$lib/utils/index.js";
import { parseTypeDef } from "$lib/utils/index.js";
export let type: PropType | string;
</script>
Expand All @@ -23,8 +23,6 @@
<Popover.Content
side="top"
sideOffset={10}
transition={flyAndScale}
transitionConfig={{ y: 8 }}
class="z-50 max-h-[400px] overflow-auto rounded-input border border-border bg-background p-4 shadow-popover"
>
<Code class="h-auto bg-transparent px-0 tracking-tight text-foreground">
Expand Down
7 changes: 7 additions & 0 deletions sites/docs/src/routes/sink/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script lang="ts">
import PopoverDemo from "$lib/components/demos/popover-demo.svelte";
</script>

<div class="flex items-center justify-center">
<PopoverDemo />
</div>

0 comments on commit 6a23442

Please sign in to comment.