Skip to content

Commit

Permalink
next: global layers (#873)
Browse files Browse the repository at this point in the history
  • Loading branch information
huntabyte authored Nov 4, 2024
1 parent bd8f255 commit 22bfba7
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 14 deletions.
5 changes: 5 additions & 0 deletions .changeset/beige-meals-repair.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"bits-ui": patch
---

fix: make layers global for use with other libs that depend on Bits UI (like `vaul-svelte`)
15 changes: 15 additions & 0 deletions packages/bits-ui/src/lib/app.d.ts
Original file line number Diff line number Diff line change
@@ -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<DismissibleLayerState, ReadableBox<InteractOutsideBehaviorType>>;
// eslint-disable-next-line vars-on-top, no-var
var bitsEscapeLayers: Map<EscapeLayerState, ReadableBox<EscapeBehaviorType>>;
// eslint-disable-next-line vars-on-top, no-var
var bitsTextSelectionLayers: Map<TextSelectionLayerState, ReadableBox<boolean>>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<DismissibleLayerState, ReadableBox<InteractOutsideBehaviorType>>();
globalThis.bitsDismissableLayers ??= new Map<
DismissibleLayerState,
ReadableBox<InteractOutsideBehaviorType>
>();

type DismissibleLayerStateProps = ReadableBoxedValues<
Required<Omit<DismissibleLayerImplProps, "children">>
Expand Down Expand Up @@ -64,15 +67,15 @@ export class DismissibleLayerState {

const cleanup = () => {
this.#resetState();
layers.delete(this);
globalThis.bitsDismissableLayers.delete(this);
this.#handleInteractOutside.destroy();
unsubEvents();
};

$effect(() => {
if (this.#enabled.current && this.currNode) {
afterSleep(1, () => {
layers.set(
globalThis.bitsDismissableLayers.set(
this,
untrack(() => this.#behaviorType)
);
Expand All @@ -87,7 +90,7 @@ export class DismissibleLayerState {

onDestroyEffect(() => {
this.#resetState.destroy();
layers.delete(this);
globalThis.bitsDismissableLayers.delete(this);
this.#handleInteractOutside.destroy();
this.#unsubClickListener();
unsubEvents();
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<EscapeLayerState, ReadableBox<EscapeBehaviorType>>();
globalThis.bitsEscapeLayers ??= new Map<EscapeLayerState, ReadableBox<EscapeBehaviorType>>();

type EscapeLayerStateProps = ReadableBoxedValues<Required<Omit<EscapeLayerImplProps, "children">>>;

Expand All @@ -24,7 +24,7 @@ export class EscapeLayerState {

$effect(() => {
if (this.#enabled.current) {
layers.set(
globalThis.bitsEscapeLayers.set(
this,
untrack(() => this.#behaviorType)
);
Expand All @@ -33,7 +33,7 @@ export class EscapeLayerState {

return () => {
unsubEvents();
layers.delete(this);
globalThis.bitsEscapeLayers.delete(this);
};
});
}
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { isOrContainsTarget } from "$lib/internal/elements.js";

type StateProps = ReadableBoxedValues<Required<Omit<TextSelectionLayerImplProps, "children">>>;

const layers = new Map<TextSelectionLayerState, ReadableBox<boolean>>();
globalThis.bitsTextSelectionLayers ??= new Map<TextSelectionLayerState, ReadableBox<boolean>>();

export class TextSelectionLayerState {
#id: StateProps["id"];
Expand All @@ -40,7 +40,7 @@ export class TextSelectionLayerState {

$effect(() => {
if (this.#enabled.current) {
layers.set(
globalThis.bitsTextSelectionLayers.set(
this,
untrack(() => this.#enabled)
);
Expand All @@ -49,7 +49,7 @@ export class TextSelectionLayerState {
return () => {
unsubEvents();
this.#resetSelectionLock();
layers.delete(this);
globalThis.bitsTextSelectionLayers.delete(this);
};
});
}
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 22bfba7

Please sign in to comment.