bits-ui 1.0.0-next.40 → 1.0.0-next.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/app.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ import type { ReadableBox } from "svelte-toolbelt";
2
+ import type { DismissibleLayerState } from "./bits/utilities/dismissible-layer/useDismissibleLayer.svelte.ts";
3
+ import type { InteractOutsideBehaviorType } from "./bits/utilities/dismissible-layer/types.ts";
4
+ import type { EscapeLayerState } from "./bits/utilities/escape-layer/useEscapeLayer.svelte.ts";
5
+ import type { EscapeBehaviorType } from "./bits/utilities/escape-layer/types.ts";
6
+ import type { TextSelectionLayerState } from "./bits/utilities/text-selection-layer/useTextSelectionLayer.svelte.ts";
7
+
8
+ declare global {
9
+ // eslint-disable-next-line vars-on-top, no-var
10
+ var bitsDismissableLayers: Map<DismissibleLayerState, ReadableBox<InteractOutsideBehaviorType>>;
11
+ // eslint-disable-next-line vars-on-top, no-var
12
+ var bitsEscapeLayers: Map<EscapeLayerState, ReadableBox<EscapeBehaviorType>>;
13
+ // eslint-disable-next-line vars-on-top, no-var
14
+ var bitsTextSelectionLayers: Map<TextSelectionLayerState, ReadableBox<boolean>>;
15
+ }
@@ -16,6 +16,7 @@
16
16
  onEscapeKeydown = noop,
17
17
  children,
18
18
  child,
19
+ preventScroll = false,
19
20
  ...restProps
20
21
  }: SelectContentProps = $props();
21
22
 
@@ -56,7 +57,7 @@
56
57
  onCloseAutoFocus={(e) => e.preventDefault()}
57
58
  trapFocus={false}
58
59
  loop={false}
59
- preventScroll={false}
60
+ {preventScroll}
60
61
  onPlaced={() => (contentState.isPositioned = true)}
61
62
  forceMount={true}
62
63
  >
@@ -83,7 +84,7 @@
83
84
  onCloseAutoFocus={(e) => e.preventDefault()}
84
85
  trapFocus={false}
85
86
  loop={false}
86
- preventScroll={false}
87
+ {preventScroll}
87
88
  onPlaced={() => (contentState.isPositioned = true)}
88
89
  forceMount={false}
89
90
  >
@@ -5,7 +5,7 @@ import { debounce } from "../../../internal/debounce.js";
5
5
  import { noop } from "../../../internal/noop.js";
6
6
  import { getOwnerDocument, isOrContainsTarget } from "../../../internal/elements.js";
7
7
  import { isElement } from "../../../internal/is.js";
8
- const layers = new Map();
8
+ globalThis.bitsDismissableLayers ??= new Map();
9
9
  export class DismissibleLayerState {
10
10
  #interactOutsideProp;
11
11
  #behaviorType;
@@ -41,14 +41,16 @@ export class DismissibleLayerState {
41
41
  let unsubEvents = noop;
42
42
  const cleanup = () => {
43
43
  this.#resetState();
44
- layers.delete(this);
44
+ globalThis.bitsDismissableLayers.delete(this);
45
45
  this.#handleInteractOutside.destroy();
46
46
  unsubEvents();
47
47
  };
48
48
  $effect(() => {
49
49
  if (this.#enabled.current && this.currNode) {
50
50
  afterSleep(1, () => {
51
- layers.set(this, untrack(() => this.#behaviorType));
51
+ if (!this.currNode)
52
+ return;
53
+ globalThis.bitsDismissableLayers.set(this, untrack(() => this.#behaviorType));
52
54
  unsubEvents();
53
55
  unsubEvents = this.#addEventListeners();
54
56
  });
@@ -59,7 +61,7 @@ export class DismissibleLayerState {
59
61
  });
60
62
  onDestroyEffect(() => {
61
63
  this.#resetState.destroy();
62
- layers.delete(this);
64
+ globalThis.bitsDismissableLayers.delete(this);
63
65
  this.#handleInteractOutside.destroy();
64
66
  this.#unsubClickListener();
65
67
  unsubEvents();
@@ -178,7 +180,7 @@ function getTopMostLayer(layersArr) {
178
180
  return layersArr.findLast(([_, { current: behaviorType }]) => behaviorType === "close" || behaviorType === "ignore");
179
181
  }
180
182
  function isResponsibleLayer(node) {
181
- const layersArr = [...layers];
183
+ const layersArr = [...globalThis.bitsDismissableLayers];
182
184
  /**
183
185
  * We first check if we can find a top layer with `close` or `ignore`.
184
186
  * If that top layer was found and matches the provided node, then the node is
@@ -2,7 +2,7 @@ import { untrack } from "svelte";
2
2
  import { addEventListener } from "../../../internal/events.js";
3
3
  import { kbd } from "../../../internal/kbd.js";
4
4
  import { noop } from "../../../internal/noop.js";
5
- const layers = new Map();
5
+ globalThis.bitsEscapeLayers ??= new Map();
6
6
  export class EscapeLayerState {
7
7
  #onEscapeProp;
8
8
  #behaviorType;
@@ -14,12 +14,12 @@ export class EscapeLayerState {
14
14
  let unsubEvents = noop;
15
15
  $effect(() => {
16
16
  if (this.#enabled.current) {
17
- layers.set(this, untrack(() => this.#behaviorType));
17
+ globalThis.bitsEscapeLayers.set(this, untrack(() => this.#behaviorType));
18
18
  unsubEvents = this.#addEventListener();
19
19
  }
20
20
  return () => {
21
21
  unsubEvents();
22
- layers.delete(this);
22
+ globalThis.bitsEscapeLayers.delete(this);
23
23
  };
24
24
  });
25
25
  }
@@ -41,7 +41,7 @@ export function useEscapeLayer(props) {
41
41
  return new EscapeLayerState(props);
42
42
  }
43
43
  function isResponsibleEscapeLayer(instance) {
44
- const layersArr = [...layers];
44
+ const layersArr = [...globalThis.bitsEscapeLayers];
45
45
  /**
46
46
  * We first check if we can find a top layer with `close` or `ignore`.
47
47
  * If that top layer was found and matches the provided node, then the node is
@@ -1,11 +1,13 @@
1
1
  import { afterTick } from "svelte-toolbelt";
2
+ import { Previous } from "runed";
3
+ import { untrack } from "svelte";
2
4
  import { useStateMachine } from "../../../internal/use-state-machine.svelte.js";
3
- import { watch } from "../../../internal/box.svelte.js";
4
5
  export function usePresence(present, id) {
5
6
  let styles = $state({});
6
7
  let prevAnimationNameState = $state("none");
7
8
  const initialState = present.current ? "mounted" : "unmounted";
8
9
  let node = $state(null);
10
+ const prevPresent = new Previous(() => present.current);
9
11
  $effect(() => {
10
12
  if (!id.current)
11
13
  return;
@@ -28,40 +30,43 @@ export function usePresence(present, id) {
28
30
  MOUNT: "mounted",
29
31
  },
30
32
  });
31
- watch(present, (currPresent, prevPresent) => {
32
- if (!node) {
33
- node = document.getElementById(id.current);
34
- }
35
- if (!node)
36
- return;
37
- const hasPresentChanged = currPresent !== prevPresent;
38
- if (!hasPresentChanged)
39
- return;
40
- const prevAnimationName = prevAnimationNameState;
41
- const currAnimationName = getAnimationName(node);
42
- if (currPresent) {
43
- dispatch("MOUNT");
44
- }
45
- else if (currAnimationName === "none" || styles.display === "none") {
46
- // If there is no exit animation or the element is hidden, animations won't run
47
- // so we unmount instantly
48
- dispatch("UNMOUNT");
49
- }
50
- else {
51
- /**
52
- * When `present` changes to `false`, we check changes to animation-name to
53
- * determine whether an animation has started. We chose this approach (reading
54
- * computed styles) because there is no `animationrun` event and `animationstart`
55
- * fires after `animation-delay` has expired which would be too late.
56
- */
57
- const isAnimating = prevAnimationName !== currAnimationName;
58
- if (prevPresent && isAnimating) {
59
- dispatch("ANIMATION_OUT");
33
+ $effect(() => {
34
+ const currPresent = present.current;
35
+ untrack(() => {
36
+ if (!node) {
37
+ node = document.getElementById(id.current);
60
38
  }
61
- else {
39
+ if (!node)
40
+ return;
41
+ const hasPresentChanged = currPresent !== prevPresent.current;
42
+ if (!hasPresentChanged)
43
+ return;
44
+ const prevAnimationName = prevAnimationNameState;
45
+ const currAnimationName = getAnimationName(node);
46
+ if (currPresent) {
47
+ dispatch("MOUNT");
48
+ }
49
+ else if (currAnimationName === "none" || styles.display === "none") {
50
+ // If there is no exit animation or the element is hidden, animations won't run
51
+ // so we unmount instantly
62
52
  dispatch("UNMOUNT");
63
53
  }
64
- }
54
+ else {
55
+ /**
56
+ * When `present` changes to `false`, we check changes to animation-name to
57
+ * determine whether an animation has started. We chose this approach (reading
58
+ * computed styles) because there is no `animationrun` event and `animationstart`
59
+ * fires after `animation-delay` has expired which would be too late.
60
+ */
61
+ const isAnimating = prevAnimationName !== currAnimationName;
62
+ if (prevPresent && isAnimating) {
63
+ dispatch("ANIMATION_OUT");
64
+ }
65
+ else {
66
+ dispatch("UNMOUNT");
67
+ }
68
+ }
69
+ });
65
70
  });
66
71
  /**
67
72
  * Triggering an ANIMATION_OUT during an ANIMATION_IN will fire an `animationcancel`
@@ -90,14 +95,17 @@ export function usePresence(present, id) {
90
95
  prevAnimationNameState = getAnimationName(node);
91
96
  }
92
97
  }
93
- watch(state, () => {
94
- if (!node) {
95
- node = document.getElementById(id.current);
96
- }
97
- if (!node)
98
- return;
99
- const currAnimationName = getAnimationName(node);
100
- prevAnimationNameState = state.current === "mounted" ? currAnimationName : "none";
98
+ $effect(() => {
99
+ state.current;
100
+ untrack(() => {
101
+ if (!node) {
102
+ node = document.getElementById(id.current);
103
+ }
104
+ if (!node)
105
+ return;
106
+ const currAnimationName = getAnimationName(node);
107
+ prevAnimationNameState = state.current === "mounted" ? currAnimationName : "none";
108
+ });
101
109
  });
102
110
  $effect(() => {
103
111
  if (!node)
@@ -4,7 +4,7 @@ import { addEventListener } from "../../../internal/events.js";
4
4
  import { noop } from "../../../internal/noop.js";
5
5
  import { isHTMLElement } from "../../../internal/is.js";
6
6
  import { isOrContainsTarget } from "../../../internal/elements.js";
7
- const layers = new Map();
7
+ globalThis.bitsTextSelectionLayers ??= new Map();
8
8
  export class TextSelectionLayerState {
9
9
  #id;
10
10
  #onPointerDownProp;
@@ -25,13 +25,13 @@ export class TextSelectionLayerState {
25
25
  let unsubEvents = noop;
26
26
  $effect(() => {
27
27
  if (this.#enabled.current) {
28
- layers.set(this, untrack(() => this.#enabled));
28
+ globalThis.bitsTextSelectionLayers.set(this, untrack(() => this.#enabled));
29
29
  unsubEvents = this.#addEventListeners();
30
30
  }
31
31
  return () => {
32
32
  unsubEvents();
33
33
  this.#resetSelectionLock();
34
- layers.delete(this);
34
+ globalThis.bitsTextSelectionLayers.delete(this);
35
35
  };
36
36
  });
37
37
  }
@@ -45,7 +45,7 @@ export class TextSelectionLayerState {
45
45
  return;
46
46
  /**
47
47
  * We only lock user-selection overflow if layer is the top most layer and
48
- * pointerdown occured inside the node. You are still allowed to select text
48
+ * pointerdown occurred inside the node. You are still allowed to select text
49
49
  * outside the node provided pointerdown occurs outside the node.
50
50
  */
51
51
  if (!isHighestLayer(this) || !isOrContainsTarget(node, target))
@@ -80,7 +80,7 @@ function setUserSelect(node, value) {
80
80
  node.style.webkitUserSelect = value;
81
81
  }
82
82
  function isHighestLayer(instance) {
83
- const layersArr = [...layers];
83
+ const layersArr = [...globalThis.bitsTextSelectionLayers];
84
84
  if (!layersArr.length)
85
85
  return false;
86
86
  const highestLayer = layersArr.at(-1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bits-ui",
3
- "version": "1.0.0-next.40",
3
+ "version": "1.0.0-next.42",
4
4
  "license": "MIT",
5
5
  "repository": "github:huntabyte/bits-ui",
6
6
  "funding": "https://github.com/sponsors/huntabyte",