diff --git a/.changeset/beige-meals-repair.md b/.changeset/beige-meals-repair.md new file mode 100644 index 000000000..5b7510660 --- /dev/null +++ b/.changeset/beige-meals-repair.md @@ -0,0 +1,5 @@ +--- +"bits-ui": patch +--- + +fix: make layers global for use with other libs that depend on Bits UI (like `vaul-svelte`) diff --git a/packages/bits-ui/src/lib/app.d.ts b/packages/bits-ui/src/lib/app.d.ts new file mode 100644 index 000000000..af1dcb8c8 --- /dev/null +++ b/packages/bits-ui/src/lib/app.d.ts @@ -0,0 +1,15 @@ +import type { ReadableBox } from "svelte-toolbelt"; +import type { DismissibleLayerState } from "./bits/utilities/dismissible-layer/useDismissibleLayer.svelte.ts"; +import type { InteractOutsideBehaviorType } from "./bits/utilities/dismissible-layer/types.ts"; +import type { EscapeLayerState } from "./bits/utilities/escape-layer/useEscapeLayer.svelte.ts"; +import type { EscapeBehaviorType } from "./bits/utilities/escape-layer/types.ts"; +import type { TextSelectionLayerState } from "./bits/utilities/text-selection-layer/useTextSelectionLayer.svelte.ts"; + +declare global { + // eslint-disable-next-line vars-on-top, no-var + var bitsDismissableLayers: Map>; + // eslint-disable-next-line vars-on-top, no-var + var bitsEscapeLayers: Map>; + // eslint-disable-next-line vars-on-top, no-var + var bitsTextSelectionLayers: Map>; +} diff --git a/packages/bits-ui/src/lib/bits/utilities/dismissible-layer/useDismissibleLayer.svelte.ts b/packages/bits-ui/src/lib/bits/utilities/dismissible-layer/useDismissibleLayer.svelte.ts index fd01ace2d..6b9130e7a 100644 --- a/packages/bits-ui/src/lib/bits/utilities/dismissible-layer/useDismissibleLayer.svelte.ts +++ b/packages/bits-ui/src/lib/bits/utilities/dismissible-layer/useDismissibleLayer.svelte.ts @@ -17,7 +17,10 @@ import { noop } from "$lib/internal/noop.js"; import { getOwnerDocument, isOrContainsTarget } from "$lib/internal/elements.js"; import { isElement } from "$lib/internal/is.js"; -const layers = new Map>(); +globalThis.bitsDismissableLayers ??= new Map< + DismissibleLayerState, + ReadableBox +>(); type DismissibleLayerStateProps = ReadableBoxedValues< Required> @@ -64,7 +67,7 @@ export class DismissibleLayerState { const cleanup = () => { this.#resetState(); - layers.delete(this); + globalThis.bitsDismissableLayers.delete(this); this.#handleInteractOutside.destroy(); unsubEvents(); }; @@ -72,7 +75,7 @@ export class DismissibleLayerState { $effect(() => { if (this.#enabled.current && this.currNode) { afterSleep(1, () => { - layers.set( + globalThis.bitsDismissableLayers.set( this, untrack(() => this.#behaviorType) ); @@ -87,7 +90,7 @@ export class DismissibleLayerState { onDestroyEffect(() => { this.#resetState.destroy(); - layers.delete(this); + globalThis.bitsDismissableLayers.delete(this); this.#handleInteractOutside.destroy(); this.#unsubClickListener(); unsubEvents(); @@ -246,7 +249,7 @@ function getTopMostLayer( } function isResponsibleLayer(node: HTMLElement): boolean { - const layersArr = [...layers]; + const layersArr = [...globalThis.bitsDismissableLayers]; /** * We first check if we can find a top layer with `close` or `ignore`. * If that top layer was found and matches the provided node, then the node is diff --git a/packages/bits-ui/src/lib/bits/utilities/escape-layer/useEscapeLayer.svelte.ts b/packages/bits-ui/src/lib/bits/utilities/escape-layer/useEscapeLayer.svelte.ts index 4fcefb2db..c1286eab2 100644 --- a/packages/bits-ui/src/lib/bits/utilities/escape-layer/useEscapeLayer.svelte.ts +++ b/packages/bits-ui/src/lib/bits/utilities/escape-layer/useEscapeLayer.svelte.ts @@ -6,7 +6,7 @@ import { type EventCallback, addEventListener } from "$lib/internal/events.js"; import { kbd } from "$lib/internal/kbd.js"; import { noop } from "$lib/internal/noop.js"; -const layers = new Map>(); +globalThis.bitsEscapeLayers ??= new Map>(); type EscapeLayerStateProps = ReadableBoxedValues>>; @@ -24,7 +24,7 @@ export class EscapeLayerState { $effect(() => { if (this.#enabled.current) { - layers.set( + globalThis.bitsEscapeLayers.set( this, untrack(() => this.#behaviorType) ); @@ -33,7 +33,7 @@ export class EscapeLayerState { return () => { unsubEvents(); - layers.delete(this); + globalThis.bitsEscapeLayers.delete(this); }; }); } @@ -57,7 +57,7 @@ export function useEscapeLayer(props: EscapeLayerStateProps) { } function isResponsibleEscapeLayer(instance: EscapeLayerState) { - const layersArr = [...layers]; + const layersArr = [...globalThis.bitsEscapeLayers]; /** * We first check if we can find a top layer with `close` or `ignore`. * If that top layer was found and matches the provided node, then the node is diff --git a/packages/bits-ui/src/lib/bits/utilities/text-selection-layer/useTextSelectionLayer.svelte.ts b/packages/bits-ui/src/lib/bits/utilities/text-selection-layer/useTextSelectionLayer.svelte.ts index 31c32973f..318cee308 100644 --- a/packages/bits-ui/src/lib/bits/utilities/text-selection-layer/useTextSelectionLayer.svelte.ts +++ b/packages/bits-ui/src/lib/bits/utilities/text-selection-layer/useTextSelectionLayer.svelte.ts @@ -15,7 +15,7 @@ import { isOrContainsTarget } from "$lib/internal/elements.js"; type StateProps = ReadableBoxedValues>>; -const layers = new Map>(); +globalThis.bitsTextSelectionLayers ??= new Map>(); export class TextSelectionLayerState { #id: StateProps["id"]; @@ -40,7 +40,7 @@ export class TextSelectionLayerState { $effect(() => { if (this.#enabled.current) { - layers.set( + globalThis.bitsTextSelectionLayers.set( this, untrack(() => this.#enabled) ); @@ -49,7 +49,7 @@ export class TextSelectionLayerState { return () => { unsubEvents(); this.#resetSelectionLock(); - layers.delete(this); + globalThis.bitsTextSelectionLayers.delete(this); }; }); } @@ -71,7 +71,7 @@ export class TextSelectionLayerState { if (!isHTMLElement(node) || !isHTMLElement(target) || !this.#enabled.current) return; /** * We only lock user-selection overflow if layer is the top most layer and - * pointerdown occured inside the node. You are still allowed to select text + * pointerdown occurred inside the node. You are still allowed to select text * outside the node provided pointerdown occurs outside the node. */ if (!isHighestLayer(this) || !isOrContainsTarget(node, target)) return; @@ -110,7 +110,7 @@ function setUserSelect(node: HTMLElement, value: string) { } function isHighestLayer(instance: TextSelectionLayerState) { - const layersArr = [...layers]; + const layersArr = [...globalThis.bitsTextSelectionLayers]; if (!layersArr.length) return false; const highestLayer = layersArr.at(-1); if (!highestLayer) return false;