@zag-js/dismissable 2.0.0-next.1 → 2.0.0-next.2

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.
@@ -1,5 +1,5 @@
1
1
  import { InteractOutsideHandlers } from '@zag-js/interact-outside';
2
- import { MaybeFunction } from '@zag-js/utils';
2
+ import { MaybeFunction, TeardownReason } from '@zag-js/utils';
3
3
  import { LayerDismissEvent, LayerSnapshot, LayerType } from './layer-stack.mjs';
4
4
 
5
5
  type MaybeElement = HTMLElement | null;
@@ -35,7 +35,7 @@ interface DismissableElementOptions extends DismissableElementHandlers, Persiste
35
35
  /**
36
36
  * Whether to block pointer events outside the dismissable element
37
37
  */
38
- pointerBlocking?: boolean | undefined;
38
+ pointerBlocking?: boolean | (() => boolean) | undefined;
39
39
  /**
40
40
  * Function called when the dismissable element is dismissed
41
41
  */
@@ -57,7 +57,7 @@ interface DismissableElementOptions extends DismissableElementHandlers, Persiste
57
57
  */
58
58
  type?: LayerType | undefined;
59
59
  }
60
- declare function trackDismissableElement(nodeOrFn: NodeOrFn, options: DismissableElementOptions): () => void;
60
+ declare function trackDismissableElement(nodeOrFn: NodeOrFn, options: DismissableElementOptions): (args_0?: TeardownReason | undefined) => void;
61
61
  declare function trackDismissableBranch(nodeOrFn: NodeOrFn, options?: {
62
62
  defer?: boolean | undefined;
63
63
  }): () => void;
@@ -1,5 +1,5 @@
1
1
  import { InteractOutsideHandlers } from '@zag-js/interact-outside';
2
- import { MaybeFunction } from '@zag-js/utils';
2
+ import { MaybeFunction, TeardownReason } from '@zag-js/utils';
3
3
  import { LayerDismissEvent, LayerSnapshot, LayerType } from './layer-stack.js';
4
4
 
5
5
  type MaybeElement = HTMLElement | null;
@@ -35,7 +35,7 @@ interface DismissableElementOptions extends DismissableElementHandlers, Persiste
35
35
  /**
36
36
  * Whether to block pointer events outside the dismissable element
37
37
  */
38
- pointerBlocking?: boolean | undefined;
38
+ pointerBlocking?: boolean | (() => boolean) | undefined;
39
39
  /**
40
40
  * Function called when the dismissable element is dismissed
41
41
  */
@@ -57,7 +57,7 @@ interface DismissableElementOptions extends DismissableElementHandlers, Persiste
57
57
  */
58
58
  type?: LayerType | undefined;
59
59
  }
60
- declare function trackDismissableElement(nodeOrFn: NodeOrFn, options: DismissableElementOptions): () => void;
60
+ declare function trackDismissableElement(nodeOrFn: NodeOrFn, options: DismissableElementOptions): (args_0?: TeardownReason | undefined) => void;
61
61
  declare function trackDismissableBranch(nodeOrFn: NodeOrFn, options?: {
62
62
  defer?: boolean | undefined;
63
63
  }): () => void;
@@ -31,14 +31,6 @@ var import_escape_keydown = require("./escape-keydown.js");
31
31
  var import_layer_stack = require("./layer-stack.js");
32
32
  var import_pointer_event_outside = require("./pointer-event-outside.js");
33
33
  function trackDismissableElementImpl(node, options) {
34
- const { warnOnMissingNode = true } = options;
35
- if (warnOnMissingNode && !node) {
36
- (0, import_utils.warn)("[@zag-js/dismissable] node is `null` or `undefined`");
37
- return;
38
- }
39
- if (!node) {
40
- return;
41
- }
42
34
  const {
43
35
  onDismiss,
44
36
  onRequestDismiss,
@@ -88,7 +80,6 @@ function trackDismissableElementImpl(node, options) {
88
80
  }
89
81
  }
90
82
  function exclude(target) {
91
- if (!node) return false;
92
83
  const containers = typeof excludeContainers === "function" ? excludeContainers() : excludeContainers;
93
84
  const _containers = Array.isArray(containers) ? containers : [containers];
94
85
  const persistentElements = options.persistentElements?.map((fn) => fn()).filter(import_dom_query.isHTMLElement);
@@ -96,49 +87,36 @@ function trackDismissableElementImpl(node, options) {
96
87
  return _containers.some((node2) => (0, import_dom_query.contains)(node2, target)) || import_layer_stack.layerStack.isInNestedLayer(node, target);
97
88
  }
98
89
  const cleanups = [
99
- pointerBlocking ? (0, import_pointer_event_outside.disablePointerEventsOutside)(node, options.persistentElements) : void 0,
90
+ (0, import_layer_stack.isPointerBlocking)(layer) ? (0, import_pointer_event_outside.disablePointerEventsOutside)(node, options.persistentElements) : void 0,
100
91
  (0, import_escape_keydown.trackEscapeKeydown)(node, onEscapeKeyDown),
101
92
  (0, import_interact_outside.trackInteractOutside)(node, { exclude, onFocusOutside, onPointerDownOutside, defer: options.defer })
102
93
  ];
103
- return () => {
104
- import_layer_stack.layerStack.remove(node);
94
+ return (reason) => {
95
+ import_layer_stack.layerStack.remove(node, { reattach: reason === "restart" });
105
96
  cleanups.forEach((fn) => fn?.());
106
97
  };
107
98
  }
108
99
  function trackDismissableElement(nodeOrFn, options) {
109
- const { defer } = options;
110
- const func = defer ? import_dom_query.raf : (v) => v();
111
- const cleanups = [];
112
- cleanups.push(
113
- func(() => {
114
- const node = (0, import_utils.isFunction)(nodeOrFn) ? nodeOrFn() : nodeOrFn;
115
- cleanups.push(trackDismissableElementImpl(node, options));
116
- })
117
- );
118
- return () => {
119
- cleanups.forEach((fn) => fn?.());
120
- };
100
+ const { warnOnMissingNode = true } = options;
101
+ return (0, import_dom_query.whenNode)(nodeOrFn, (node) => trackDismissableElementImpl(node, options), {
102
+ defer: options.defer,
103
+ onMissing: warnOnMissingNode ? () => (0, import_utils.warn)("[@zag-js/dismissable] node is `null` or `undefined`") : void 0
104
+ });
121
105
  }
122
106
  function trackDismissableBranch(nodeOrFn, options = {}) {
123
- const { defer } = options;
124
- const func = defer ? import_dom_query.raf : (v) => v();
125
- const cleanups = [];
126
- cleanups.push(
127
- func(() => {
128
- const node = (0, import_utils.isFunction)(nodeOrFn) ? nodeOrFn() : nodeOrFn;
129
- if (!node) {
130
- (0, import_utils.warn)("[@zag-js/dismissable] branch node is `null` or `undefined`");
131
- return;
132
- }
107
+ return (0, import_dom_query.whenNode)(
108
+ nodeOrFn,
109
+ (node) => {
133
110
  import_layer_stack.layerStack.addBranch(node);
134
- cleanups.push(() => {
111
+ return () => {
135
112
  import_layer_stack.layerStack.removeBranch(node);
136
- });
137
- })
113
+ };
114
+ },
115
+ {
116
+ defer: options.defer,
117
+ onMissing: () => (0, import_utils.warn)("[@zag-js/dismissable] branch node is `null` or `undefined`")
118
+ }
138
119
  );
139
- return () => {
140
- cleanups.forEach((fn) => fn?.());
141
- };
142
120
  }
143
121
  // Annotate the CommonJS export names for ESM import in node:
144
122
  0 && (module.exports = {
@@ -1,21 +1,16 @@
1
1
  // src/dismissable-layer.ts
2
- import { contains, getEventTarget, isHTMLElement, raf } from "@zag-js/dom-query";
2
+ import { contains, getEventTarget, isHTMLElement, whenNode } from "@zag-js/dom-query";
3
3
  import {
4
4
  trackInteractOutside
5
5
  } from "@zag-js/interact-outside";
6
- import { isFunction, warn } from "@zag-js/utils";
6
+ import { warn } from "@zag-js/utils";
7
7
  import { trackEscapeKeydown } from "./escape-keydown.mjs";
8
- import { layerStack } from "./layer-stack.mjs";
8
+ import {
9
+ isPointerBlocking,
10
+ layerStack
11
+ } from "./layer-stack.mjs";
9
12
  import { disablePointerEventsOutside } from "./pointer-event-outside.mjs";
10
13
  function trackDismissableElementImpl(node, options) {
11
- const { warnOnMissingNode = true } = options;
12
- if (warnOnMissingNode && !node) {
13
- warn("[@zag-js/dismissable] node is `null` or `undefined`");
14
- return;
15
- }
16
- if (!node) {
17
- return;
18
- }
19
14
  const {
20
15
  onDismiss,
21
16
  onRequestDismiss,
@@ -65,7 +60,6 @@ function trackDismissableElementImpl(node, options) {
65
60
  }
66
61
  }
67
62
  function exclude(target) {
68
- if (!node) return false;
69
63
  const containers = typeof excludeContainers === "function" ? excludeContainers() : excludeContainers;
70
64
  const _containers = Array.isArray(containers) ? containers : [containers];
71
65
  const persistentElements = options.persistentElements?.map((fn) => fn()).filter(isHTMLElement);
@@ -73,49 +67,36 @@ function trackDismissableElementImpl(node, options) {
73
67
  return _containers.some((node2) => contains(node2, target)) || layerStack.isInNestedLayer(node, target);
74
68
  }
75
69
  const cleanups = [
76
- pointerBlocking ? disablePointerEventsOutside(node, options.persistentElements) : void 0,
70
+ isPointerBlocking(layer) ? disablePointerEventsOutside(node, options.persistentElements) : void 0,
77
71
  trackEscapeKeydown(node, onEscapeKeyDown),
78
72
  trackInteractOutside(node, { exclude, onFocusOutside, onPointerDownOutside, defer: options.defer })
79
73
  ];
80
- return () => {
81
- layerStack.remove(node);
74
+ return (reason) => {
75
+ layerStack.remove(node, { reattach: reason === "restart" });
82
76
  cleanups.forEach((fn) => fn?.());
83
77
  };
84
78
  }
85
79
  function trackDismissableElement(nodeOrFn, options) {
86
- const { defer } = options;
87
- const func = defer ? raf : (v) => v();
88
- const cleanups = [];
89
- cleanups.push(
90
- func(() => {
91
- const node = isFunction(nodeOrFn) ? nodeOrFn() : nodeOrFn;
92
- cleanups.push(trackDismissableElementImpl(node, options));
93
- })
94
- );
95
- return () => {
96
- cleanups.forEach((fn) => fn?.());
97
- };
80
+ const { warnOnMissingNode = true } = options;
81
+ return whenNode(nodeOrFn, (node) => trackDismissableElementImpl(node, options), {
82
+ defer: options.defer,
83
+ onMissing: warnOnMissingNode ? () => warn("[@zag-js/dismissable] node is `null` or `undefined`") : void 0
84
+ });
98
85
  }
99
86
  function trackDismissableBranch(nodeOrFn, options = {}) {
100
- const { defer } = options;
101
- const func = defer ? raf : (v) => v();
102
- const cleanups = [];
103
- cleanups.push(
104
- func(() => {
105
- const node = isFunction(nodeOrFn) ? nodeOrFn() : nodeOrFn;
106
- if (!node) {
107
- warn("[@zag-js/dismissable] branch node is `null` or `undefined`");
108
- return;
109
- }
87
+ return whenNode(
88
+ nodeOrFn,
89
+ (node) => {
110
90
  layerStack.addBranch(node);
111
- cleanups.push(() => {
91
+ return () => {
112
92
  layerStack.removeBranch(node);
113
- });
114
- })
93
+ };
94
+ },
95
+ {
96
+ defer: options.defer,
97
+ onMissing: () => warn("[@zag-js/dismissable] branch node is `null` or `undefined`")
98
+ }
115
99
  );
116
- return () => {
117
- cleanups.forEach((fn) => fn?.());
118
- };
119
100
  }
120
101
  export {
121
102
  trackDismissableBranch,
package/dist/index.d.mts CHANGED
@@ -2,4 +2,5 @@ export { FocusOutsideEvent, InteractOutsideEvent, InteractOutsideHandlers, Point
2
2
  export { DismissableElementHandlers, DismissableElementOptions, PersistentElementOptions, trackDismissableBranch, trackDismissableElement } from './dismissable-layer.mjs';
3
3
  export { DismissableLayerStyle, DismissableLayerStyleOptions, getDismissableLayerAttrs, getDismissableLayerStyle } from './layer-props.mjs';
4
4
  export { LayerSnapshot, LayerType } from './layer-stack.mjs';
5
+ export { syncPointerEvents } from './pointer-event-outside.mjs';
5
6
  import '@zag-js/utils';
package/dist/index.d.ts CHANGED
@@ -2,4 +2,5 @@ export { FocusOutsideEvent, InteractOutsideEvent, InteractOutsideHandlers, Point
2
2
  export { DismissableElementHandlers, DismissableElementOptions, PersistentElementOptions, trackDismissableBranch, trackDismissableElement } from './dismissable-layer.js';
3
3
  export { DismissableLayerStyle, DismissableLayerStyleOptions, getDismissableLayerAttrs, getDismissableLayerStyle } from './layer-props.js';
4
4
  export { LayerSnapshot, LayerType } from './layer-stack.js';
5
+ export { syncPointerEvents } from './pointer-event-outside.js';
5
6
  import '@zag-js/utils';
package/dist/index.js CHANGED
@@ -3,6 +3,10 @@ var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
6
10
  var __copyProps = (to, from, except, desc) => {
7
11
  if (from && typeof from === "object" || typeof from === "function") {
8
12
  for (let key of __getOwnPropNames(from))
@@ -16,11 +20,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
16
20
 
17
21
  // src/index.ts
18
22
  var index_exports = {};
23
+ __export(index_exports, {
24
+ syncPointerEvents: () => import_pointer_event_outside.syncPointerEvents
25
+ });
19
26
  module.exports = __toCommonJS(index_exports);
20
27
  __reExport(index_exports, require("./dismissable-layer.js"), module.exports);
21
28
  __reExport(index_exports, require("./layer-props.js"), module.exports);
29
+ var import_pointer_event_outside = require("./pointer-event-outside.js");
22
30
  // Annotate the CommonJS export names for ESM import in node:
23
31
  0 && (module.exports = {
32
+ syncPointerEvents,
24
33
  ...require("./dismissable-layer.js"),
25
34
  ...require("./layer-props.js")
26
35
  });
package/dist/index.mjs CHANGED
@@ -1,3 +1,7 @@
1
1
  // src/index.ts
2
2
  export * from "./dismissable-layer.mjs";
3
3
  export * from "./layer-props.mjs";
4
+ import { syncPointerEvents } from "./pointer-event-outside.mjs";
5
+ export {
6
+ syncPointerEvents
7
+ };
@@ -26,18 +26,19 @@ __export(layer_props_exports, {
26
26
  module.exports = __toCommonJS(layer_props_exports);
27
27
  function getDismissableLayerAttrs(layer) {
28
28
  return {
29
- "data-nested": layer?.nested ? layer.type : void 0,
30
- "data-has-nested": layer?.hasNested ? layer.type : void 0
29
+ "data-nested": layer?.active && layer.nested ? layer.type : void 0,
30
+ "data-has-nested": layer?.active && layer.hasNested ? layer.type : void 0
31
31
  };
32
32
  }
33
33
  function getDismissableLayerStyle(layer, options = {}) {
34
+ const active = layer?.active === true;
34
35
  const style = {
35
- "--layer-index": layer ? `${layer.index}` : void 0,
36
- "--nested-layer-count": layer ? `${layer.nestedCount}` : void 0,
37
- "--z-index": options.zIndex && layer ? "var(--layer-index)" : void 0
36
+ "--layer-index": active ? `${layer.index}` : void 0,
37
+ "--nested-layer-count": active ? `${layer.nestedCount}` : void 0,
38
+ "--z-index": options.zIndex && active ? "var(--layer-index)" : void 0
38
39
  };
39
40
  if (options.pointerEvents) {
40
- style.pointerEvents = layer?.active ? layer.blocked ? "none" : "auto" : void 0;
41
+ style.pointerEvents = active ? layer.blocked ? "none" : "auto" : void 0;
41
42
  }
42
43
  return style;
43
44
  }
@@ -1,18 +1,19 @@
1
1
  // src/layer-props.ts
2
2
  function getDismissableLayerAttrs(layer) {
3
3
  return {
4
- "data-nested": layer?.nested ? layer.type : void 0,
5
- "data-has-nested": layer?.hasNested ? layer.type : void 0
4
+ "data-nested": layer?.active && layer.nested ? layer.type : void 0,
5
+ "data-has-nested": layer?.active && layer.hasNested ? layer.type : void 0
6
6
  };
7
7
  }
8
8
  function getDismissableLayerStyle(layer, options = {}) {
9
+ const active = layer?.active === true;
9
10
  const style = {
10
- "--layer-index": layer ? `${layer.index}` : void 0,
11
- "--nested-layer-count": layer ? `${layer.nestedCount}` : void 0,
12
- "--z-index": options.zIndex && layer ? "var(--layer-index)" : void 0
11
+ "--layer-index": active ? `${layer.index}` : void 0,
12
+ "--nested-layer-count": active ? `${layer.nestedCount}` : void 0,
13
+ "--z-index": options.zIndex && active ? "var(--layer-index)" : void 0
13
14
  };
14
15
  if (options.pointerEvents) {
15
- style.pointerEvents = layer?.active ? layer.blocked ? "none" : "auto" : void 0;
16
+ style.pointerEvents = active ? layer.blocked ? "none" : "auto" : void 0;
16
17
  }
17
18
  return style;
18
19
  }
@@ -16,24 +16,39 @@ interface LayerSnapshot {
16
16
  blocked: boolean;
17
17
  }
18
18
  interface Layer {
19
+ /** Assigned on first registration. */
20
+ id?: string | undefined;
21
+ /** The layer this one opened inside. Captured once and kept across re-registration. */
22
+ parentId?: string | undefined;
19
23
  dismiss: VoidFunction;
20
24
  node: HTMLElement;
21
25
  type: LayerType;
22
- pointerBlocking?: boolean | undefined;
26
+ pointerBlocking?: boolean | (() => boolean) | undefined;
23
27
  requestDismiss?: ((event: LayerDismissEvent) => void) | undefined;
24
28
  onLayerChange?: ((snapshot: LayerSnapshot) => void) | undefined;
25
29
  snapshot?: LayerSnapshot | undefined;
26
30
  }
31
+ /** Resolved on read, so a layer's blocking can change without re-registering it. */
32
+ declare function isPointerBlocking(layer: Pick<Layer, "pointerBlocking">): boolean;
27
33
  declare const layerStack: {
28
34
  layers: Layer[];
29
35
  branches: HTMLElement[];
30
36
  recentlyRemoved: Set<HTMLElement>;
37
+ pendingReattach: Map<HTMLElement, {
38
+ id: string | undefined;
39
+ parentId: string | undefined;
40
+ index: number;
41
+ }>;
31
42
  count(): number;
32
43
  pointerBlockingLayers(): Layer[];
33
44
  topMostPointerBlockingLayer(): Layer | undefined;
34
45
  hasPointerBlockingLayer(): boolean;
35
46
  isBelowPointerBlockingLayer(node: HTMLElement): boolean;
36
47
  isTopMost(node: HTMLElement | null): boolean;
48
+ layerFor(node: HTMLElement | null): Layer | undefined;
49
+ isDescendantOf(layer: Layer, ancestorId: string | undefined): boolean;
50
+ depthOf(layer: Layer): number;
51
+ /** Descendants, shallowest first, so a cascade dismisses parents before their own children. */
37
52
  getNestedLayers(node: HTMLElement): Layer[];
38
53
  getLayersByType(type: LayerType): Layer[];
39
54
  getNestedLayersByType(node: HTMLElement, type: LayerType): Layer[];
@@ -43,7 +58,9 @@ declare const layerStack: {
43
58
  isInBranch(target: HTMLElement | EventTarget | null): boolean;
44
59
  add(layer: Layer): void;
45
60
  addBranch(node: HTMLElement): void;
46
- remove(node: HTMLElement): void;
61
+ remove(node: HTMLElement, options?: {
62
+ reattach?: boolean;
63
+ }): void;
47
64
  removeBranch(node: HTMLElement): void;
48
65
  syncLayers(): void;
49
66
  indexOf(node: HTMLElement | undefined): number;
@@ -51,4 +68,4 @@ declare const layerStack: {
51
68
  clear(): void;
52
69
  };
53
70
 
54
- export { type Layer, type LayerDismissEvent, type LayerDismissEventDetail, type LayerSnapshot, type LayerType, layerStack };
71
+ export { type Layer, type LayerDismissEvent, type LayerDismissEventDetail, type LayerSnapshot, type LayerType, isPointerBlocking, layerStack };
@@ -16,24 +16,39 @@ interface LayerSnapshot {
16
16
  blocked: boolean;
17
17
  }
18
18
  interface Layer {
19
+ /** Assigned on first registration. */
20
+ id?: string | undefined;
21
+ /** The layer this one opened inside. Captured once and kept across re-registration. */
22
+ parentId?: string | undefined;
19
23
  dismiss: VoidFunction;
20
24
  node: HTMLElement;
21
25
  type: LayerType;
22
- pointerBlocking?: boolean | undefined;
26
+ pointerBlocking?: boolean | (() => boolean) | undefined;
23
27
  requestDismiss?: ((event: LayerDismissEvent) => void) | undefined;
24
28
  onLayerChange?: ((snapshot: LayerSnapshot) => void) | undefined;
25
29
  snapshot?: LayerSnapshot | undefined;
26
30
  }
31
+ /** Resolved on read, so a layer's blocking can change without re-registering it. */
32
+ declare function isPointerBlocking(layer: Pick<Layer, "pointerBlocking">): boolean;
27
33
  declare const layerStack: {
28
34
  layers: Layer[];
29
35
  branches: HTMLElement[];
30
36
  recentlyRemoved: Set<HTMLElement>;
37
+ pendingReattach: Map<HTMLElement, {
38
+ id: string | undefined;
39
+ parentId: string | undefined;
40
+ index: number;
41
+ }>;
31
42
  count(): number;
32
43
  pointerBlockingLayers(): Layer[];
33
44
  topMostPointerBlockingLayer(): Layer | undefined;
34
45
  hasPointerBlockingLayer(): boolean;
35
46
  isBelowPointerBlockingLayer(node: HTMLElement): boolean;
36
47
  isTopMost(node: HTMLElement | null): boolean;
48
+ layerFor(node: HTMLElement | null): Layer | undefined;
49
+ isDescendantOf(layer: Layer, ancestorId: string | undefined): boolean;
50
+ depthOf(layer: Layer): number;
51
+ /** Descendants, shallowest first, so a cascade dismisses parents before their own children. */
37
52
  getNestedLayers(node: HTMLElement): Layer[];
38
53
  getLayersByType(type: LayerType): Layer[];
39
54
  getNestedLayersByType(node: HTMLElement, type: LayerType): Layer[];
@@ -43,7 +58,9 @@ declare const layerStack: {
43
58
  isInBranch(target: HTMLElement | EventTarget | null): boolean;
44
59
  add(layer: Layer): void;
45
60
  addBranch(node: HTMLElement): void;
46
- remove(node: HTMLElement): void;
61
+ remove(node: HTMLElement, options?: {
62
+ reattach?: boolean;
63
+ }): void;
47
64
  removeBranch(node: HTMLElement): void;
48
65
  syncLayers(): void;
49
66
  indexOf(node: HTMLElement | undefined): number;
@@ -51,4 +68,4 @@ declare const layerStack: {
51
68
  clear(): void;
52
69
  };
53
70
 
54
- export { type Layer, type LayerDismissEvent, type LayerDismissEventDetail, type LayerSnapshot, type LayerType, layerStack };
71
+ export { type Layer, type LayerDismissEvent, type LayerDismissEventDetail, type LayerSnapshot, type LayerType, isPointerBlocking, layerStack };
@@ -20,21 +20,28 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/layer-stack.ts
21
21
  var layer_stack_exports = {};
22
22
  __export(layer_stack_exports, {
23
+ isPointerBlocking: () => isPointerBlocking,
23
24
  layerStack: () => layerStack
24
25
  });
25
26
  module.exports = __toCommonJS(layer_stack_exports);
26
27
  var import_dom_query = require("@zag-js/dom-query");
27
28
  var import_utils = require("@zag-js/utils");
28
29
  var LAYER_REQUEST_DISMISS_EVENT = "layer:request-dismiss";
30
+ var layerId = 0;
31
+ function isPointerBlocking(layer) {
32
+ const value = layer.pointerBlocking;
33
+ return typeof value === "function" ? value() : !!value;
34
+ }
29
35
  var layerStack = {
30
36
  layers: [],
31
37
  branches: [],
32
38
  recentlyRemoved: /* @__PURE__ */ new Set(),
39
+ pendingReattach: /* @__PURE__ */ new Map(),
33
40
  count() {
34
41
  return this.layers.length;
35
42
  },
36
43
  pointerBlockingLayers() {
37
- return this.layers.filter((layer) => layer.pointerBlocking);
44
+ return this.layers.filter(isPointerBlocking);
38
45
  },
39
46
  topMostPointerBlockingLayer() {
40
47
  return [...this.pointerBlockingLayers()].slice(-1)[0];
@@ -51,21 +58,54 @@ var layerStack = {
51
58
  const layer = this.layers[this.count() - 1];
52
59
  return layer?.node === node;
53
60
  },
61
+ layerFor(node) {
62
+ return this.layers.find((layer) => layer.node === node);
63
+ },
64
+ isDescendantOf(layer, ancestorId) {
65
+ if (!ancestorId) return false;
66
+ const seen = /* @__PURE__ */ new Set();
67
+ let parentId = layer.parentId;
68
+ while (parentId && !seen.has(parentId)) {
69
+ if (parentId === ancestorId) return true;
70
+ seen.add(parentId);
71
+ parentId = this.layers.find((l) => l.id === parentId)?.parentId;
72
+ }
73
+ return false;
74
+ },
75
+ depthOf(layer) {
76
+ const seen = /* @__PURE__ */ new Set();
77
+ let depth = 0;
78
+ let parentId = layer.parentId;
79
+ while (parentId && !seen.has(parentId)) {
80
+ depth++;
81
+ seen.add(parentId);
82
+ parentId = this.layers.find((l) => l.id === parentId)?.parentId;
83
+ }
84
+ return depth;
85
+ },
86
+ /** Descendants, shallowest first, so a cascade dismisses parents before their own children. */
54
87
  getNestedLayers(node) {
55
- return Array.from(this.layers).slice(this.indexOf(node) + 1);
88
+ const id = this.layerFor(node)?.id;
89
+ if (!id) return [];
90
+ return this.layers.filter((layer) => this.isDescendantOf(layer, id)).sort((a, b) => this.depthOf(a) - this.depthOf(b));
56
91
  },
57
92
  getLayersByType(type) {
58
93
  return this.layers.filter((layer) => layer.type === type);
59
94
  },
60
95
  getNestedLayersByType(node, type) {
61
- const index = this.indexOf(node);
62
- if (index === -1) return [];
63
- return this.layers.slice(index + 1).filter((layer) => layer.type === type);
96
+ return this.getNestedLayers(node).filter((layer) => layer.type === type);
64
97
  },
65
98
  getParentLayerOfType(node, type) {
66
- const index = this.indexOf(node);
67
- if (index <= 0) return void 0;
68
- return this.layers.slice(0, index).reverse().find((layer) => layer.type === type);
99
+ const seen = /* @__PURE__ */ new Set();
100
+ let parentId = this.layerFor(node)?.parentId;
101
+ while (parentId && !seen.has(parentId)) {
102
+ const parent = this.layers.find((l) => l.id === parentId);
103
+ if (!parent) return void 0;
104
+ if (parent.type === type) return parent;
105
+ seen.add(parentId);
106
+ parentId = parent.parentId;
107
+ }
108
+ return void 0;
69
109
  },
70
110
  countNestedLayersOfType(node, type) {
71
111
  return this.getNestedLayersByType(node, type).length;
@@ -81,29 +121,47 @@ var layerStack = {
81
121
  },
82
122
  add(layer) {
83
123
  const existingIndex = this.indexOf(layer.node);
124
+ const existing = existingIndex !== -1 ? this.layers[existingIndex] : void 0;
125
+ const pending = this.pendingReattach.get(layer.node);
126
+ if (existing) {
127
+ layer.id ?? (layer.id = existing.id);
128
+ layer.parentId = existing.parentId;
129
+ } else if (pending) {
130
+ layer.id ?? (layer.id = pending.id);
131
+ layer.parentId = pending.parentId;
132
+ } else {
133
+ layer.id ?? (layer.id = `layer-${++layerId}`);
134
+ layer.parentId ?? (layer.parentId = this.layers[this.count() - 1]?.id);
135
+ }
84
136
  if (existingIndex !== -1) {
85
137
  this.layers.splice(existingIndex, 1);
86
138
  }
87
- this.layers.push(layer);
139
+ if (pending) {
140
+ this.pendingReattach.delete(layer.node);
141
+ this.layers.splice(Math.min(pending.index, this.count()), 0, layer);
142
+ } else {
143
+ this.layers.push(layer);
144
+ }
88
145
  this.syncLayers();
89
146
  },
90
147
  addBranch(node) {
91
148
  this.branches.push(node);
92
149
  },
93
- remove(node) {
150
+ remove(node, options) {
94
151
  const index = this.indexOf(node);
95
152
  if (index < 0) return;
96
153
  const layer = this.layers[index];
154
+ if (options?.reattach) {
155
+ this.pendingReattach.set(node, { id: layer.id, parentId: layer.parentId, index });
156
+ } else {
157
+ this.recentlyRemoved.add(node);
158
+ (0, import_dom_query.nextTick)(() => this.recentlyRemoved.delete(node));
159
+ this.getNestedLayers(node).forEach((nested) => layerStack.dismiss(nested.node, node));
160
+ }
161
+ this.layers.splice(index, 1);
97
162
  if (layer.snapshot) {
98
163
  publishSnapshot(layer, { ...layer.snapshot, active: false, blocked: false });
99
164
  }
100
- this.recentlyRemoved.add(node);
101
- (0, import_dom_query.nextTick)(() => this.recentlyRemoved.delete(node));
102
- if (index < this.count() - 1) {
103
- const _layers = this.getNestedLayers(node);
104
- _layers.forEach((layer2) => layerStack.dismiss(layer2.node, node));
105
- }
106
- this.layers.splice(index, 1);
107
165
  this.syncLayers();
108
166
  },
109
167
  removeBranch(node) {
@@ -167,5 +225,6 @@ function addListenerOnce(el, type, callback) {
167
225
  }
168
226
  // Annotate the CommonJS export names for ESM import in node:
169
227
  0 && (module.exports = {
228
+ isPointerBlocking,
170
229
  layerStack
171
230
  });
@@ -2,15 +2,21 @@
2
2
  import { contains, nextTick } from "@zag-js/dom-query";
3
3
  import { isEqual } from "@zag-js/utils";
4
4
  var LAYER_REQUEST_DISMISS_EVENT = "layer:request-dismiss";
5
+ var layerId = 0;
6
+ function isPointerBlocking(layer) {
7
+ const value = layer.pointerBlocking;
8
+ return typeof value === "function" ? value() : !!value;
9
+ }
5
10
  var layerStack = {
6
11
  layers: [],
7
12
  branches: [],
8
13
  recentlyRemoved: /* @__PURE__ */ new Set(),
14
+ pendingReattach: /* @__PURE__ */ new Map(),
9
15
  count() {
10
16
  return this.layers.length;
11
17
  },
12
18
  pointerBlockingLayers() {
13
- return this.layers.filter((layer) => layer.pointerBlocking);
19
+ return this.layers.filter(isPointerBlocking);
14
20
  },
15
21
  topMostPointerBlockingLayer() {
16
22
  return [...this.pointerBlockingLayers()].slice(-1)[0];
@@ -27,21 +33,54 @@ var layerStack = {
27
33
  const layer = this.layers[this.count() - 1];
28
34
  return layer?.node === node;
29
35
  },
36
+ layerFor(node) {
37
+ return this.layers.find((layer) => layer.node === node);
38
+ },
39
+ isDescendantOf(layer, ancestorId) {
40
+ if (!ancestorId) return false;
41
+ const seen = /* @__PURE__ */ new Set();
42
+ let parentId = layer.parentId;
43
+ while (parentId && !seen.has(parentId)) {
44
+ if (parentId === ancestorId) return true;
45
+ seen.add(parentId);
46
+ parentId = this.layers.find((l) => l.id === parentId)?.parentId;
47
+ }
48
+ return false;
49
+ },
50
+ depthOf(layer) {
51
+ const seen = /* @__PURE__ */ new Set();
52
+ let depth = 0;
53
+ let parentId = layer.parentId;
54
+ while (parentId && !seen.has(parentId)) {
55
+ depth++;
56
+ seen.add(parentId);
57
+ parentId = this.layers.find((l) => l.id === parentId)?.parentId;
58
+ }
59
+ return depth;
60
+ },
61
+ /** Descendants, shallowest first, so a cascade dismisses parents before their own children. */
30
62
  getNestedLayers(node) {
31
- return Array.from(this.layers).slice(this.indexOf(node) + 1);
63
+ const id = this.layerFor(node)?.id;
64
+ if (!id) return [];
65
+ return this.layers.filter((layer) => this.isDescendantOf(layer, id)).sort((a, b) => this.depthOf(a) - this.depthOf(b));
32
66
  },
33
67
  getLayersByType(type) {
34
68
  return this.layers.filter((layer) => layer.type === type);
35
69
  },
36
70
  getNestedLayersByType(node, type) {
37
- const index = this.indexOf(node);
38
- if (index === -1) return [];
39
- return this.layers.slice(index + 1).filter((layer) => layer.type === type);
71
+ return this.getNestedLayers(node).filter((layer) => layer.type === type);
40
72
  },
41
73
  getParentLayerOfType(node, type) {
42
- const index = this.indexOf(node);
43
- if (index <= 0) return void 0;
44
- return this.layers.slice(0, index).reverse().find((layer) => layer.type === type);
74
+ const seen = /* @__PURE__ */ new Set();
75
+ let parentId = this.layerFor(node)?.parentId;
76
+ while (parentId && !seen.has(parentId)) {
77
+ const parent = this.layers.find((l) => l.id === parentId);
78
+ if (!parent) return void 0;
79
+ if (parent.type === type) return parent;
80
+ seen.add(parentId);
81
+ parentId = parent.parentId;
82
+ }
83
+ return void 0;
45
84
  },
46
85
  countNestedLayersOfType(node, type) {
47
86
  return this.getNestedLayersByType(node, type).length;
@@ -57,29 +96,47 @@ var layerStack = {
57
96
  },
58
97
  add(layer) {
59
98
  const existingIndex = this.indexOf(layer.node);
99
+ const existing = existingIndex !== -1 ? this.layers[existingIndex] : void 0;
100
+ const pending = this.pendingReattach.get(layer.node);
101
+ if (existing) {
102
+ layer.id ?? (layer.id = existing.id);
103
+ layer.parentId = existing.parentId;
104
+ } else if (pending) {
105
+ layer.id ?? (layer.id = pending.id);
106
+ layer.parentId = pending.parentId;
107
+ } else {
108
+ layer.id ?? (layer.id = `layer-${++layerId}`);
109
+ layer.parentId ?? (layer.parentId = this.layers[this.count() - 1]?.id);
110
+ }
60
111
  if (existingIndex !== -1) {
61
112
  this.layers.splice(existingIndex, 1);
62
113
  }
63
- this.layers.push(layer);
114
+ if (pending) {
115
+ this.pendingReattach.delete(layer.node);
116
+ this.layers.splice(Math.min(pending.index, this.count()), 0, layer);
117
+ } else {
118
+ this.layers.push(layer);
119
+ }
64
120
  this.syncLayers();
65
121
  },
66
122
  addBranch(node) {
67
123
  this.branches.push(node);
68
124
  },
69
- remove(node) {
125
+ remove(node, options) {
70
126
  const index = this.indexOf(node);
71
127
  if (index < 0) return;
72
128
  const layer = this.layers[index];
129
+ if (options?.reattach) {
130
+ this.pendingReattach.set(node, { id: layer.id, parentId: layer.parentId, index });
131
+ } else {
132
+ this.recentlyRemoved.add(node);
133
+ nextTick(() => this.recentlyRemoved.delete(node));
134
+ this.getNestedLayers(node).forEach((nested) => layerStack.dismiss(nested.node, node));
135
+ }
136
+ this.layers.splice(index, 1);
73
137
  if (layer.snapshot) {
74
138
  publishSnapshot(layer, { ...layer.snapshot, active: false, blocked: false });
75
139
  }
76
- this.recentlyRemoved.add(node);
77
- nextTick(() => this.recentlyRemoved.delete(node));
78
- if (index < this.count() - 1) {
79
- const _layers = this.getNestedLayers(node);
80
- _layers.forEach((layer2) => layerStack.dismiss(layer2.node, node));
81
- }
82
- this.layers.splice(index, 1);
83
140
  this.syncLayers();
84
141
  },
85
142
  removeBranch(node) {
@@ -142,5 +199,6 @@ function addListenerOnce(el, type, callback) {
142
199
  el.addEventListener(type, callback, { once: true });
143
200
  }
144
201
  export {
202
+ isPointerBlocking,
145
203
  layerStack
146
204
  };
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Re-derives pointer blocking from the stack. Needed after a layer's `pointerBlocking` getter
3
+ * changes value, since nothing was added or removed to trigger the usual sync.
4
+ */
5
+ declare function syncPointerEvents(node: HTMLElement): void;
1
6
  declare function disablePointerEventsOutside(node: HTMLElement, persistentElements?: Array<() => Element | null>): () => void;
2
7
 
3
- export { disablePointerEventsOutside };
8
+ export { disablePointerEventsOutside, syncPointerEvents };
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Re-derives pointer blocking from the stack. Needed after a layer's `pointerBlocking` getter
3
+ * changes value, since nothing was added or removed to trigger the usual sync.
4
+ */
5
+ declare function syncPointerEvents(node: HTMLElement): void;
1
6
  declare function disablePointerEventsOutside(node: HTMLElement, persistentElements?: Array<() => Element | null>): () => void;
2
7
 
3
- export { disablePointerEventsOutside };
8
+ export { disablePointerEventsOutside, syncPointerEvents };
@@ -20,24 +20,45 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/pointer-event-outside.ts
21
21
  var pointer_event_outside_exports = {};
22
22
  __export(pointer_event_outside_exports, {
23
- disablePointerEventsOutside: () => disablePointerEventsOutside
23
+ disablePointerEventsOutside: () => disablePointerEventsOutside,
24
+ syncPointerEvents: () => syncPointerEvents
24
25
  });
25
26
  module.exports = __toCommonJS(pointer_event_outside_exports);
26
27
  var import_dom_query = require("@zag-js/dom-query");
27
28
  var import_layer_stack = require("./layer-stack.js");
28
29
  var originalBodyPointerEvents = /* @__PURE__ */ new WeakMap();
29
- function disablePointerEventsOutside(node, persistentElements) {
30
- const doc = (0, import_dom_query.getDocument)(node);
31
- const cleanups = [];
32
- if (import_layer_stack.layerStack.hasPointerBlockingLayer() && !doc.body.hasAttribute("data-inert")) {
33
- originalBodyPointerEvents.set(doc.body, doc.body.style.pointerEvents);
30
+ function syncBodyPointerEvents(doc) {
31
+ const body = doc.body;
32
+ if (!body) return;
33
+ const shouldBlock = import_layer_stack.layerStack.hasPointerBlockingLayer();
34
+ if (shouldBlock === body.hasAttribute("data-inert")) return;
35
+ if (shouldBlock) {
36
+ originalBodyPointerEvents.set(body, body.style.pointerEvents);
34
37
  queueMicrotask(() => {
35
- const body = doc.body;
36
- if (!body) return;
38
+ if (!import_layer_stack.layerStack.hasPointerBlockingLayer()) return;
37
39
  body.style.pointerEvents = "none";
38
40
  body.setAttribute("data-inert", "");
39
41
  });
42
+ return;
40
43
  }
44
+ queueMicrotask(() => {
45
+ if (import_layer_stack.layerStack.hasPointerBlockingLayer()) return;
46
+ const original = originalBodyPointerEvents.get(body);
47
+ if (original !== void 0) {
48
+ body.style.pointerEvents = original;
49
+ originalBodyPointerEvents.delete(body);
50
+ }
51
+ body.removeAttribute("data-inert");
52
+ if (body.style.length === 0) body.removeAttribute("style");
53
+ });
54
+ }
55
+ function syncPointerEvents(node) {
56
+ syncBodyPointerEvents((0, import_dom_query.getDocument)(node));
57
+ }
58
+ function disablePointerEventsOutside(node, persistentElements) {
59
+ const doc = (0, import_dom_query.getDocument)(node);
60
+ const cleanups = [];
61
+ syncBodyPointerEvents(doc);
41
62
  persistentElements?.forEach((el) => {
42
63
  const [promise, abort] = (0, import_dom_query.waitForElement)(
43
64
  () => {
@@ -50,22 +71,12 @@ function disablePointerEventsOutside(node, persistentElements) {
50
71
  cleanups.push(abort);
51
72
  });
52
73
  return () => {
53
- if (import_layer_stack.layerStack.hasPointerBlockingLayer()) return;
54
- queueMicrotask(() => {
55
- const body = doc.body;
56
- if (!body) return;
57
- const original = originalBodyPointerEvents.get(body);
58
- if (original !== void 0) {
59
- body.style.pointerEvents = original;
60
- originalBodyPointerEvents.delete(body);
61
- }
62
- body.removeAttribute("data-inert");
63
- if (body.style.length === 0) body.removeAttribute("style");
64
- });
74
+ syncBodyPointerEvents(doc);
65
75
  cleanups.forEach((fn) => fn());
66
76
  };
67
77
  }
68
78
  // Annotate the CommonJS export names for ESM import in node:
69
79
  0 && (module.exports = {
70
- disablePointerEventsOutside
80
+ disablePointerEventsOutside,
81
+ syncPointerEvents
71
82
  });
@@ -2,18 +2,38 @@
2
2
  import { getDocument, isHTMLElement, setStyle, waitForElement } from "@zag-js/dom-query";
3
3
  import { layerStack } from "./layer-stack.mjs";
4
4
  var originalBodyPointerEvents = /* @__PURE__ */ new WeakMap();
5
- function disablePointerEventsOutside(node, persistentElements) {
6
- const doc = getDocument(node);
7
- const cleanups = [];
8
- if (layerStack.hasPointerBlockingLayer() && !doc.body.hasAttribute("data-inert")) {
9
- originalBodyPointerEvents.set(doc.body, doc.body.style.pointerEvents);
5
+ function syncBodyPointerEvents(doc) {
6
+ const body = doc.body;
7
+ if (!body) return;
8
+ const shouldBlock = layerStack.hasPointerBlockingLayer();
9
+ if (shouldBlock === body.hasAttribute("data-inert")) return;
10
+ if (shouldBlock) {
11
+ originalBodyPointerEvents.set(body, body.style.pointerEvents);
10
12
  queueMicrotask(() => {
11
- const body = doc.body;
12
- if (!body) return;
13
+ if (!layerStack.hasPointerBlockingLayer()) return;
13
14
  body.style.pointerEvents = "none";
14
15
  body.setAttribute("data-inert", "");
15
16
  });
17
+ return;
16
18
  }
19
+ queueMicrotask(() => {
20
+ if (layerStack.hasPointerBlockingLayer()) return;
21
+ const original = originalBodyPointerEvents.get(body);
22
+ if (original !== void 0) {
23
+ body.style.pointerEvents = original;
24
+ originalBodyPointerEvents.delete(body);
25
+ }
26
+ body.removeAttribute("data-inert");
27
+ if (body.style.length === 0) body.removeAttribute("style");
28
+ });
29
+ }
30
+ function syncPointerEvents(node) {
31
+ syncBodyPointerEvents(getDocument(node));
32
+ }
33
+ function disablePointerEventsOutside(node, persistentElements) {
34
+ const doc = getDocument(node);
35
+ const cleanups = [];
36
+ syncBodyPointerEvents(doc);
17
37
  persistentElements?.forEach((el) => {
18
38
  const [promise, abort] = waitForElement(
19
39
  () => {
@@ -26,21 +46,11 @@ function disablePointerEventsOutside(node, persistentElements) {
26
46
  cleanups.push(abort);
27
47
  });
28
48
  return () => {
29
- if (layerStack.hasPointerBlockingLayer()) return;
30
- queueMicrotask(() => {
31
- const body = doc.body;
32
- if (!body) return;
33
- const original = originalBodyPointerEvents.get(body);
34
- if (original !== void 0) {
35
- body.style.pointerEvents = original;
36
- originalBodyPointerEvents.delete(body);
37
- }
38
- body.removeAttribute("data-inert");
39
- if (body.style.length === 0) body.removeAttribute("style");
40
- });
49
+ syncBodyPointerEvents(doc);
41
50
  cleanups.forEach((fn) => fn());
42
51
  };
43
52
  }
44
53
  export {
45
- disablePointerEventsOutside
54
+ disablePointerEventsOutside,
55
+ syncPointerEvents
46
56
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zag-js/dismissable",
3
- "version": "2.0.0-next.1",
3
+ "version": "2.0.0-next.2",
4
4
  "description": "Dismissable layer utilities for the DOM",
5
5
  "keywords": [
6
6
  "js",
@@ -26,9 +26,9 @@
26
26
  "access": "public"
27
27
  },
28
28
  "dependencies": {
29
- "@zag-js/dom-query": "2.0.0-next.1",
30
- "@zag-js/utils": "2.0.0-next.1",
31
- "@zag-js/interact-outside": "2.0.0-next.1"
29
+ "@zag-js/interact-outside": "2.0.0-next.2",
30
+ "@zag-js/utils": "2.0.0-next.2",
31
+ "@zag-js/dom-query": "2.0.0-next.2"
32
32
  },
33
33
  "devDependencies": {
34
34
  "clean-package": "2.2.0"
@@ -56,7 +56,7 @@
56
56
  "scripts": {
57
57
  "build": "tsup",
58
58
  "test": "vitest",
59
- "lint": "eslint src",
59
+ "lint": "oxlint src",
60
60
  "test-ci": "pnpm test --ci --runInBand -u",
61
61
  "test-watch": "pnpm test --watchAll"
62
62
  }