@dogsbay/primitives 0.2.0-beta.115 → 0.2.0-beta.116

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.
@@ -5,6 +5,7 @@ function globalEscapeHandler(e) {
5
5
  if (dismissStack.length === 0) return;
6
6
  const top = dismissStack[dismissStack.length - 1];
7
7
  if (top.escapePrevented) return;
8
+ e.preventDefault();
8
9
  e.stopPropagation();
9
10
  top.opts.onDismiss("escape-key");
10
11
  }
@@ -92,4 +93,4 @@ var DismissController = class {
92
93
  export {
93
94
  DismissController
94
95
  };
95
- //# sourceMappingURL=chunk-4CMGFJKB.js.map
96
+ //# sourceMappingURL=chunk-CS5YCYCO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dismiss.ts"],"sourcesContent":["export type DismissReason = \"escape-key\" | \"outside-press\";\n\nexport interface DismissOptions {\n /** Called when the user dismisses the element. */\n onDismiss: (reason: DismissReason) => void;\n /** Whether pressing Escape dismisses. Defaults to true. */\n escapeKey?: boolean;\n /** Whether clicking outside dismisses. Can be a predicate. Defaults to true. */\n outsidePress?: boolean | ((event: PointerEvent) => boolean);\n}\n\n/**\n * Stack of active dismiss controllers. Only the topmost controller\n * handles Escape — this prevents nested dialogs from all closing\n * on a single Escape press.\n */\nconst dismissStack: DismissController[] = [];\n\nfunction globalEscapeHandler(e: KeyboardEvent) {\n if (e.key !== \"Escape\") return;\n if (dismissStack.length === 0) return;\n\n // Only the topmost controller handles Escape\n const top = dismissStack[dismissStack.length - 1];\n if (top.escapePrevented) return;\n\n // preventDefault as well: inside a modal <dialog>, an uncancelled Escape is also\n // the dialog's close request, which would close the dialog with the popup.\n e.preventDefault();\n e.stopPropagation();\n top.opts.onDismiss(\"escape-key\");\n}\n\n// Single global listener, registered once\nlet globalEscapeRegistered = false;\n\nfunction ensureGlobalEscapeListener() {\n if (globalEscapeRegistered) return;\n globalEscapeRegistered = true;\n document.addEventListener(\"keydown\", globalEscapeHandler);\n}\n\n/**\n * Handles dismissal of floating/overlay elements via Escape key\n * and outside pointer press.\n *\n * Uses a global stack so that only the topmost (most recently opened)\n * controller handles Escape. This prevents nested dialogs from all\n * closing on a single keypress.\n *\n * Delays the outside-press listener by one animation frame to prevent\n * the opening click from immediately triggering a dismiss.\n */\nexport class DismissController {\n private root: HTMLElement;\n /** @internal */ opts: {\n onDismiss: (reason: DismissReason) => void;\n escapeKey: boolean;\n outsidePress: boolean | ((event: PointerEvent) => boolean);\n };\n private outsideHandler: ((e: PointerEvent) => void) | null = null;\n private active = false;\n private frameId: number | null = null;\n /** @internal */ escapePrevented = false;\n\n constructor(root: HTMLElement, opts: DismissOptions) {\n this.root = root;\n this.opts = {\n onDismiss: opts.onDismiss,\n escapeKey: opts.escapeKey ?? true,\n outsidePress: opts.outsidePress ?? true,\n };\n }\n\n activate() {\n if (this.active) return;\n this.active = true;\n\n if (this.opts.escapeKey) {\n ensureGlobalEscapeListener();\n dismissStack.push(this);\n }\n\n if (this.opts.outsidePress) {\n this.outsideHandler = (e: PointerEvent) => {\n // Only the topmost controller handles outside press.\n // This prevents a nested dialog's close button click from\n // being seen as an \"outside press\" by the parent dialog.\n if (\n dismissStack.length > 0 &&\n dismissStack[dismissStack.length - 1] !== this\n ) {\n return;\n }\n\n const target = e.target as Node;\n if (this.root.contains(target)) return;\n\n // Don't dismiss if clicking the trigger that owns this element\n const controlsId = this.root.id;\n if (controlsId) {\n const trigger = document.querySelector(\n `[aria-controls=\"${controlsId}\"]`,\n );\n if (trigger?.contains(target)) return;\n }\n const triggerId = this.root.getAttribute(\"aria-labelledby\");\n if (triggerId) {\n const trigger = document.getElementById(triggerId);\n if (trigger?.contains(target)) return;\n }\n\n if (typeof this.opts.outsidePress === \"function\") {\n if (!this.opts.outsidePress(e)) return;\n }\n\n this.opts.onDismiss(\"outside-press\");\n };\n\n // Delay to avoid the opening click triggering immediate dismiss\n this.frameId = requestAnimationFrame(() => {\n this.frameId = null;\n if (this.outsideHandler) {\n document.addEventListener(\"pointerdown\", this.outsideHandler);\n }\n });\n }\n }\n\n deactivate() {\n if (!this.active) return;\n this.active = false;\n\n // Remove from stack\n const idx = dismissStack.indexOf(this);\n if (idx !== -1) dismissStack.splice(idx, 1);\n\n if (this.frameId !== null) {\n cancelAnimationFrame(this.frameId);\n this.frameId = null;\n }\n\n if (this.outsideHandler) {\n document.removeEventListener(\"pointerdown\", this.outsideHandler);\n this.outsideHandler = null;\n }\n }\n\n isActive() {\n return this.active;\n }\n}\n"],"mappings":";AAgBA,IAAM,eAAoC,CAAC;AAE3C,SAAS,oBAAoB,GAAkB;AAC7C,MAAI,EAAE,QAAQ,SAAU;AACxB,MAAI,aAAa,WAAW,EAAG;AAG/B,QAAM,MAAM,aAAa,aAAa,SAAS,CAAC;AAChD,MAAI,IAAI,gBAAiB;AAIzB,IAAE,eAAe;AACjB,IAAE,gBAAgB;AAClB,MAAI,KAAK,UAAU,YAAY;AACjC;AAGA,IAAI,yBAAyB;AAE7B,SAAS,6BAA6B;AACpC,MAAI,uBAAwB;AAC5B,2BAAyB;AACzB,WAAS,iBAAiB,WAAW,mBAAmB;AAC1D;AAaO,IAAM,oBAAN,MAAwB;AAAA,EACrB;AAAA;AAAA,EACS;AAAA,EAKT,iBAAqD;AAAA,EACrD,SAAS;AAAA,EACT,UAAyB;AAAA;AAAA,EAChB,kBAAkB;AAAA,EAEnC,YAAY,MAAmB,MAAsB;AACnD,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,MACV,WAAW,KAAK;AAAA,MAChB,WAAW,KAAK,aAAa;AAAA,MAC7B,cAAc,KAAK,gBAAgB;AAAA,IACrC;AAAA,EACF;AAAA,EAEA,WAAW;AACT,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AAEd,QAAI,KAAK,KAAK,WAAW;AACvB,iCAA2B;AAC3B,mBAAa,KAAK,IAAI;AAAA,IACxB;AAEA,QAAI,KAAK,KAAK,cAAc;AAC1B,WAAK,iBAAiB,CAAC,MAAoB;AAIzC,YACE,aAAa,SAAS,KACtB,aAAa,aAAa,SAAS,CAAC,MAAM,MAC1C;AACA;AAAA,QACF;AAEA,cAAM,SAAS,EAAE;AACjB,YAAI,KAAK,KAAK,SAAS,MAAM,EAAG;AAGhC,cAAM,aAAa,KAAK,KAAK;AAC7B,YAAI,YAAY;AACd,gBAAM,UAAU,SAAS;AAAA,YACvB,mBAAmB,UAAU;AAAA,UAC/B;AACA,cAAI,SAAS,SAAS,MAAM,EAAG;AAAA,QACjC;AACA,cAAM,YAAY,KAAK,KAAK,aAAa,iBAAiB;AAC1D,YAAI,WAAW;AACb,gBAAM,UAAU,SAAS,eAAe,SAAS;AACjD,cAAI,SAAS,SAAS,MAAM,EAAG;AAAA,QACjC;AAEA,YAAI,OAAO,KAAK,KAAK,iBAAiB,YAAY;AAChD,cAAI,CAAC,KAAK,KAAK,aAAa,CAAC,EAAG;AAAA,QAClC;AAEA,aAAK,KAAK,UAAU,eAAe;AAAA,MACrC;AAGA,WAAK,UAAU,sBAAsB,MAAM;AACzC,aAAK,UAAU;AACf,YAAI,KAAK,gBAAgB;AACvB,mBAAS,iBAAiB,eAAe,KAAK,cAAc;AAAA,QAC9D;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,aAAa;AACX,QAAI,CAAC,KAAK,OAAQ;AAClB,SAAK,SAAS;AAGd,UAAM,MAAM,aAAa,QAAQ,IAAI;AACrC,QAAI,QAAQ,GAAI,cAAa,OAAO,KAAK,CAAC;AAE1C,QAAI,KAAK,YAAY,MAAM;AACzB,2BAAqB,KAAK,OAAO;AACjC,WAAK,UAAU;AAAA,IACjB;AAEA,QAAI,KAAK,gBAAgB;AACvB,eAAS,oBAAoB,eAAe,KAAK,cAAc;AAC/D,WAAK,iBAAiB;AAAA,IACxB;AAAA,EACF;AAAA,EAEA,WAAW;AACT,WAAO,KAAK;AAAA,EACd;AACF;","names":[]}
package/dist/dismiss.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  DismissController
3
- } from "./chunk-4CMGFJKB.js";
3
+ } from "./chunk-CS5YCYCO.js";
4
4
  export {
5
5
  DismissController
6
6
  };
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  } from "./chunk-LN2MVY72.js";
13
13
  import {
14
14
  DismissController
15
- } from "./chunk-4CMGFJKB.js";
15
+ } from "./chunk-CS5YCYCO.js";
16
16
  import {
17
17
  setupFloating
18
18
  } from "./chunk-Q2HX4RL6.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dogsbay/primitives",
3
- "version": "0.2.0-beta.115",
3
+ "version": "0.2.0-beta.116",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/dismiss.ts"],"sourcesContent":["export type DismissReason = \"escape-key\" | \"outside-press\";\n\nexport interface DismissOptions {\n /** Called when the user dismisses the element. */\n onDismiss: (reason: DismissReason) => void;\n /** Whether pressing Escape dismisses. Defaults to true. */\n escapeKey?: boolean;\n /** Whether clicking outside dismisses. Can be a predicate. Defaults to true. */\n outsidePress?: boolean | ((event: PointerEvent) => boolean);\n}\n\n/**\n * Stack of active dismiss controllers. Only the topmost controller\n * handles Escape — this prevents nested dialogs from all closing\n * on a single Escape press.\n */\nconst dismissStack: DismissController[] = [];\n\nfunction globalEscapeHandler(e: KeyboardEvent) {\n if (e.key !== \"Escape\") return;\n if (dismissStack.length === 0) return;\n\n // Only the topmost controller handles Escape\n const top = dismissStack[dismissStack.length - 1];\n if (top.escapePrevented) return;\n\n e.stopPropagation();\n top.opts.onDismiss(\"escape-key\");\n}\n\n// Single global listener, registered once\nlet globalEscapeRegistered = false;\n\nfunction ensureGlobalEscapeListener() {\n if (globalEscapeRegistered) return;\n globalEscapeRegistered = true;\n document.addEventListener(\"keydown\", globalEscapeHandler);\n}\n\n/**\n * Handles dismissal of floating/overlay elements via Escape key\n * and outside pointer press.\n *\n * Uses a global stack so that only the topmost (most recently opened)\n * controller handles Escape. This prevents nested dialogs from all\n * closing on a single keypress.\n *\n * Delays the outside-press listener by one animation frame to prevent\n * the opening click from immediately triggering a dismiss.\n */\nexport class DismissController {\n private root: HTMLElement;\n /** @internal */ opts: {\n onDismiss: (reason: DismissReason) => void;\n escapeKey: boolean;\n outsidePress: boolean | ((event: PointerEvent) => boolean);\n };\n private outsideHandler: ((e: PointerEvent) => void) | null = null;\n private active = false;\n private frameId: number | null = null;\n /** @internal */ escapePrevented = false;\n\n constructor(root: HTMLElement, opts: DismissOptions) {\n this.root = root;\n this.opts = {\n onDismiss: opts.onDismiss,\n escapeKey: opts.escapeKey ?? true,\n outsidePress: opts.outsidePress ?? true,\n };\n }\n\n activate() {\n if (this.active) return;\n this.active = true;\n\n if (this.opts.escapeKey) {\n ensureGlobalEscapeListener();\n dismissStack.push(this);\n }\n\n if (this.opts.outsidePress) {\n this.outsideHandler = (e: PointerEvent) => {\n // Only the topmost controller handles outside press.\n // This prevents a nested dialog's close button click from\n // being seen as an \"outside press\" by the parent dialog.\n if (\n dismissStack.length > 0 &&\n dismissStack[dismissStack.length - 1] !== this\n ) {\n return;\n }\n\n const target = e.target as Node;\n if (this.root.contains(target)) return;\n\n // Don't dismiss if clicking the trigger that owns this element\n const controlsId = this.root.id;\n if (controlsId) {\n const trigger = document.querySelector(\n `[aria-controls=\"${controlsId}\"]`,\n );\n if (trigger?.contains(target)) return;\n }\n const triggerId = this.root.getAttribute(\"aria-labelledby\");\n if (triggerId) {\n const trigger = document.getElementById(triggerId);\n if (trigger?.contains(target)) return;\n }\n\n if (typeof this.opts.outsidePress === \"function\") {\n if (!this.opts.outsidePress(e)) return;\n }\n\n this.opts.onDismiss(\"outside-press\");\n };\n\n // Delay to avoid the opening click triggering immediate dismiss\n this.frameId = requestAnimationFrame(() => {\n this.frameId = null;\n if (this.outsideHandler) {\n document.addEventListener(\"pointerdown\", this.outsideHandler);\n }\n });\n }\n }\n\n deactivate() {\n if (!this.active) return;\n this.active = false;\n\n // Remove from stack\n const idx = dismissStack.indexOf(this);\n if (idx !== -1) dismissStack.splice(idx, 1);\n\n if (this.frameId !== null) {\n cancelAnimationFrame(this.frameId);\n this.frameId = null;\n }\n\n if (this.outsideHandler) {\n document.removeEventListener(\"pointerdown\", this.outsideHandler);\n this.outsideHandler = null;\n }\n }\n\n isActive() {\n return this.active;\n }\n}\n"],"mappings":";AAgBA,IAAM,eAAoC,CAAC;AAE3C,SAAS,oBAAoB,GAAkB;AAC7C,MAAI,EAAE,QAAQ,SAAU;AACxB,MAAI,aAAa,WAAW,EAAG;AAG/B,QAAM,MAAM,aAAa,aAAa,SAAS,CAAC;AAChD,MAAI,IAAI,gBAAiB;AAEzB,IAAE,gBAAgB;AAClB,MAAI,KAAK,UAAU,YAAY;AACjC;AAGA,IAAI,yBAAyB;AAE7B,SAAS,6BAA6B;AACpC,MAAI,uBAAwB;AAC5B,2BAAyB;AACzB,WAAS,iBAAiB,WAAW,mBAAmB;AAC1D;AAaO,IAAM,oBAAN,MAAwB;AAAA,EACrB;AAAA;AAAA,EACS;AAAA,EAKT,iBAAqD;AAAA,EACrD,SAAS;AAAA,EACT,UAAyB;AAAA;AAAA,EAChB,kBAAkB;AAAA,EAEnC,YAAY,MAAmB,MAAsB;AACnD,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,MACV,WAAW,KAAK;AAAA,MAChB,WAAW,KAAK,aAAa;AAAA,MAC7B,cAAc,KAAK,gBAAgB;AAAA,IACrC;AAAA,EACF;AAAA,EAEA,WAAW;AACT,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AAEd,QAAI,KAAK,KAAK,WAAW;AACvB,iCAA2B;AAC3B,mBAAa,KAAK,IAAI;AAAA,IACxB;AAEA,QAAI,KAAK,KAAK,cAAc;AAC1B,WAAK,iBAAiB,CAAC,MAAoB;AAIzC,YACE,aAAa,SAAS,KACtB,aAAa,aAAa,SAAS,CAAC,MAAM,MAC1C;AACA;AAAA,QACF;AAEA,cAAM,SAAS,EAAE;AACjB,YAAI,KAAK,KAAK,SAAS,MAAM,EAAG;AAGhC,cAAM,aAAa,KAAK,KAAK;AAC7B,YAAI,YAAY;AACd,gBAAM,UAAU,SAAS;AAAA,YACvB,mBAAmB,UAAU;AAAA,UAC/B;AACA,cAAI,SAAS,SAAS,MAAM,EAAG;AAAA,QACjC;AACA,cAAM,YAAY,KAAK,KAAK,aAAa,iBAAiB;AAC1D,YAAI,WAAW;AACb,gBAAM,UAAU,SAAS,eAAe,SAAS;AACjD,cAAI,SAAS,SAAS,MAAM,EAAG;AAAA,QACjC;AAEA,YAAI,OAAO,KAAK,KAAK,iBAAiB,YAAY;AAChD,cAAI,CAAC,KAAK,KAAK,aAAa,CAAC,EAAG;AAAA,QAClC;AAEA,aAAK,KAAK,UAAU,eAAe;AAAA,MACrC;AAGA,WAAK,UAAU,sBAAsB,MAAM;AACzC,aAAK,UAAU;AACf,YAAI,KAAK,gBAAgB;AACvB,mBAAS,iBAAiB,eAAe,KAAK,cAAc;AAAA,QAC9D;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,aAAa;AACX,QAAI,CAAC,KAAK,OAAQ;AAClB,SAAK,SAAS;AAGd,UAAM,MAAM,aAAa,QAAQ,IAAI;AACrC,QAAI,QAAQ,GAAI,cAAa,OAAO,KAAK,CAAC;AAE1C,QAAI,KAAK,YAAY,MAAM;AACzB,2BAAqB,KAAK,OAAO;AACjC,WAAK,UAAU;AAAA,IACjB;AAEA,QAAI,KAAK,gBAAgB;AACvB,eAAS,oBAAoB,eAAe,KAAK,cAAc;AAC/D,WAAK,iBAAiB;AAAA,IACxB;AAAA,EACF;AAAA,EAEA,WAAW;AACT,WAAO,KAAK;AAAA,EACd;AACF;","names":[]}