@zag-js/interact-outside 0.12.0 → 0.14.0

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/index.js CHANGED
@@ -78,6 +78,15 @@ function isComposedPathFocusable(event) {
78
78
  }
79
79
  return false;
80
80
  }
81
+ var isPointerEvent = (event) => "clientY" in event;
82
+ function isEventPointWithin(node, event) {
83
+ if (!isPointerEvent(event) || !node)
84
+ return false;
85
+ const rect = node.getBoundingClientRect();
86
+ if (rect.width === 0 || rect.height === 0)
87
+ return false;
88
+ return rect.top <= event.clientY && event.clientY <= rect.top + rect.height && rect.left <= event.clientX && event.clientX <= rect.left + rect.width;
89
+ }
81
90
  function trackInteractOutsideImpl(node, options) {
82
91
  const { exclude, onFocusOutside, onPointerDownOutside, onInteractOutside } = options;
83
92
  if (!node)
@@ -87,12 +96,12 @@ function trackInteractOutsideImpl(node, options) {
87
96
  const frames = getWindowFrames(win);
88
97
  function isEventOutside(event) {
89
98
  const target = (0, import_dom_query.getEventTarget)(event);
90
- if (!(0, import_dom_query.isHTMLElement)(target)) {
99
+ if (!(0, import_dom_query.isHTMLElement)(target))
91
100
  return false;
92
- }
93
- if ((0, import_dom_query.contains)(node, target)) {
101
+ if ((0, import_dom_query.contains)(node, target))
102
+ return false;
103
+ if (isEventPointWithin(node, event))
94
104
  return false;
95
- }
96
105
  return !exclude?.(target);
97
106
  }
98
107
  let clickHandler;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/get-window-frames.ts"],"sourcesContent":["import { addDomEvent, fireCustomEvent, isContextMenuEvent } from \"@zag-js/dom-event\"\nimport { contains, getDocument, getEventTarget, getWindow, isHTMLElement, raf } from \"@zag-js/dom-query\"\nimport { isFocusable } from \"@zag-js/tabbable\"\nimport { callAll } from \"@zag-js/utils\"\nimport { getWindowFrames } from \"./get-window-frames\"\n\nexport type InteractOutsideHandlers = {\n onPointerDownOutside?: (event: PointerDownOutsideEvent) => void\n onFocusOutside?: (event: FocusOutsideEvent) => void\n onInteractOutside?: (event: InteractOutsideEvent) => void\n}\n\nexport type InteractOutsideOptions = InteractOutsideHandlers & {\n exclude?: (target: HTMLElement) => boolean\n defer?: boolean\n}\n\nexport type EventDetails<T> = {\n originalEvent: T\n contextmenu: boolean\n focusable: boolean\n}\n\nconst POINTER_OUTSIDE_EVENT = \"pointerdown.outside\"\nconst FOCUS_OUTSIDE_EVENT = \"focus.outside\"\n\nexport type PointerDownOutsideEvent = CustomEvent<EventDetails<PointerEvent>>\n\nexport type FocusOutsideEvent = CustomEvent<EventDetails<FocusEvent>>\n\nexport type InteractOutsideEvent = PointerDownOutsideEvent | FocusOutsideEvent\n\nexport type MaybeElement = HTMLElement | null | undefined\nexport type NodeOrFn = MaybeElement | (() => MaybeElement)\n\nfunction isComposedPathFocusable(event: Event) {\n const composedPath = event.composedPath() ?? [event.target as HTMLElement]\n for (const node of composedPath) {\n if (isHTMLElement(node) && isFocusable(node)) return true\n }\n return false\n}\n\nfunction trackInteractOutsideImpl(node: MaybeElement, options: InteractOutsideOptions) {\n const { exclude, onFocusOutside, onPointerDownOutside, onInteractOutside } = options\n\n if (!node) return\n\n const doc = getDocument(node)\n const win = getWindow(node)\n const frames = getWindowFrames(win)\n\n function isEventOutside(event: Event): boolean {\n const target = getEventTarget(event)\n\n if (!isHTMLElement(target)) {\n return false\n }\n\n if (contains(node, target)) {\n return false\n }\n\n return !exclude?.(target)\n }\n\n let clickHandler: VoidFunction\n\n function onPointerDown(event: PointerEvent) {\n //\n function handler() {\n if (!node || !isEventOutside(event)) return\n\n if (onPointerDownOutside || onInteractOutside) {\n const handler = callAll(onPointerDownOutside, onInteractOutside) as EventListener\n node.addEventListener(POINTER_OUTSIDE_EVENT, handler, { once: true })\n }\n\n fireCustomEvent(node, POINTER_OUTSIDE_EVENT, {\n bubbles: false,\n cancelable: true,\n detail: {\n originalEvent: event,\n contextmenu: isContextMenuEvent(event),\n focusable: isComposedPathFocusable(event),\n },\n })\n }\n\n if (event.pointerType === \"touch\") {\n frames.removeEventListener(\"click\", handler)\n doc.removeEventListener(\"click\", handler)\n\n clickHandler = handler\n\n doc.addEventListener(\"click\", handler, { once: true })\n frames.addEventListener(\"click\", handler, { once: true })\n } else {\n handler()\n }\n }\n const cleanups = new Set<VoidFunction>()\n\n const timer = setTimeout(() => {\n cleanups.add(frames.addEventListener(\"pointerdown\", onPointerDown, true))\n cleanups.add(addDomEvent(doc, \"pointerdown\", onPointerDown, true))\n }, 0)\n\n function onFocusin(event: FocusEvent) {\n //\n if (!node || !isEventOutside(event)) return\n\n if (onFocusOutside || onInteractOutside) {\n const handler = callAll(onFocusOutside, onInteractOutside) as EventListener\n node.addEventListener(FOCUS_OUTSIDE_EVENT, handler, { once: true })\n }\n\n fireCustomEvent(node, FOCUS_OUTSIDE_EVENT, {\n bubbles: false,\n cancelable: true,\n detail: {\n originalEvent: event,\n contextmenu: false,\n focusable: isFocusable(getEventTarget(event)),\n },\n })\n }\n\n cleanups.add(addDomEvent(doc, \"focusin\", onFocusin, true))\n cleanups.add(frames.addEventListener(\"focusin\", onFocusin, true))\n\n return () => {\n clearTimeout(timer)\n if (clickHandler) {\n frames.removeEventListener(\"click\", clickHandler)\n doc.removeEventListener(\"click\", clickHandler)\n }\n cleanups.forEach((fn) => fn())\n }\n}\n\nexport function trackInteractOutside(nodeOrFn: NodeOrFn, options: InteractOutsideOptions) {\n const { defer } = options\n const func = defer ? raf : (v: any) => v()\n const cleanups: (VoidFunction | undefined)[] = []\n cleanups.push(\n func(() => {\n const node = typeof nodeOrFn === \"function\" ? nodeOrFn() : nodeOrFn\n cleanups.push(trackInteractOutsideImpl(node, options))\n }),\n )\n return () => {\n cleanups.forEach((fn) => fn?.())\n }\n}\n","export function getWindowFrames(win: Window) {\n const frames = {\n each(cb: (win: Window) => void) {\n for (let i = 0; i < win.frames?.length; i += 1) {\n const frame = win.frames[i]\n if (frame) cb(frame)\n }\n },\n addEventListener(event: string, listener: any, options?: any) {\n frames.each((frame) => {\n try {\n frame.document.addEventListener(event, listener, options)\n } catch (err) {\n console.warn(err)\n }\n })\n return () => {\n try {\n frames.removeEventListener(event, listener, options)\n } catch (err) {\n console.warn(err)\n }\n }\n },\n removeEventListener(event: string, listener: any, options?: any) {\n frames.each((frame) => {\n try {\n frame.document.removeEventListener(event, listener, options)\n } catch (err) {\n console.warn(err)\n }\n })\n },\n }\n return frames\n}\n"],"mappings":"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","names":["handler"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/get-window-frames.ts"],"sourcesContent":["import { addDomEvent, fireCustomEvent, isContextMenuEvent } from \"@zag-js/dom-event\"\nimport { contains, getDocument, getEventTarget, getWindow, isHTMLElement, raf } from \"@zag-js/dom-query\"\nimport { isFocusable } from \"@zag-js/tabbable\"\nimport { callAll } from \"@zag-js/utils\"\nimport { getWindowFrames } from \"./get-window-frames\"\n\nexport type InteractOutsideHandlers = {\n onPointerDownOutside?: (event: PointerDownOutsideEvent) => void\n onFocusOutside?: (event: FocusOutsideEvent) => void\n onInteractOutside?: (event: InteractOutsideEvent) => void\n}\n\nexport type InteractOutsideOptions = InteractOutsideHandlers & {\n exclude?: (target: HTMLElement) => boolean\n defer?: boolean\n}\n\nexport type EventDetails<T> = {\n originalEvent: T\n contextmenu: boolean\n focusable: boolean\n}\n\nconst POINTER_OUTSIDE_EVENT = \"pointerdown.outside\"\nconst FOCUS_OUTSIDE_EVENT = \"focus.outside\"\n\nexport type PointerDownOutsideEvent = CustomEvent<EventDetails<PointerEvent>>\n\nexport type FocusOutsideEvent = CustomEvent<EventDetails<FocusEvent>>\n\nexport type InteractOutsideEvent = PointerDownOutsideEvent | FocusOutsideEvent\n\nexport type MaybeElement = HTMLElement | null | undefined\nexport type NodeOrFn = MaybeElement | (() => MaybeElement)\n\nfunction isComposedPathFocusable(event: Event) {\n const composedPath = event.composedPath() ?? [event.target as HTMLElement]\n for (const node of composedPath) {\n if (isHTMLElement(node) && isFocusable(node)) return true\n }\n return false\n}\n\nconst isPointerEvent = (event: Event): event is PointerEvent => \"clientY\" in event\n\nfunction isEventPointWithin(node: MaybeElement, event: Event) {\n if (!isPointerEvent(event) || !node) return false\n\n const rect = node.getBoundingClientRect()\n if (rect.width === 0 || rect.height === 0) return false\n\n return (\n rect.top <= event.clientY &&\n event.clientY <= rect.top + rect.height &&\n rect.left <= event.clientX &&\n event.clientX <= rect.left + rect.width\n )\n}\n\nfunction trackInteractOutsideImpl(node: MaybeElement, options: InteractOutsideOptions) {\n const { exclude, onFocusOutside, onPointerDownOutside, onInteractOutside } = options\n\n if (!node) return\n\n const doc = getDocument(node)\n const win = getWindow(node)\n const frames = getWindowFrames(win)\n\n function isEventOutside(event: Event): boolean {\n const target = getEventTarget(event)\n if (!isHTMLElement(target)) return false\n if (contains(node, target)) return false\n if (isEventPointWithin(node, event)) return false\n return !exclude?.(target)\n }\n\n let clickHandler: VoidFunction\n\n function onPointerDown(event: PointerEvent) {\n //\n function handler() {\n if (!node || !isEventOutside(event)) return\n\n if (onPointerDownOutside || onInteractOutside) {\n const handler = callAll(onPointerDownOutside, onInteractOutside) as EventListener\n node.addEventListener(POINTER_OUTSIDE_EVENT, handler, { once: true })\n }\n\n fireCustomEvent(node, POINTER_OUTSIDE_EVENT, {\n bubbles: false,\n cancelable: true,\n detail: {\n originalEvent: event,\n contextmenu: isContextMenuEvent(event),\n focusable: isComposedPathFocusable(event),\n },\n })\n }\n\n if (event.pointerType === \"touch\") {\n frames.removeEventListener(\"click\", handler)\n doc.removeEventListener(\"click\", handler)\n\n clickHandler = handler\n\n doc.addEventListener(\"click\", handler, { once: true })\n frames.addEventListener(\"click\", handler, { once: true })\n } else {\n handler()\n }\n }\n const cleanups = new Set<VoidFunction>()\n\n const timer = setTimeout(() => {\n cleanups.add(frames.addEventListener(\"pointerdown\", onPointerDown, true))\n cleanups.add(addDomEvent(doc, \"pointerdown\", onPointerDown, true))\n }, 0)\n\n function onFocusin(event: FocusEvent) {\n //\n if (!node || !isEventOutside(event)) return\n\n if (onFocusOutside || onInteractOutside) {\n const handler = callAll(onFocusOutside, onInteractOutside) as EventListener\n node.addEventListener(FOCUS_OUTSIDE_EVENT, handler, { once: true })\n }\n\n fireCustomEvent(node, FOCUS_OUTSIDE_EVENT, {\n bubbles: false,\n cancelable: true,\n detail: {\n originalEvent: event,\n contextmenu: false,\n focusable: isFocusable(getEventTarget(event)),\n },\n })\n }\n\n cleanups.add(addDomEvent(doc, \"focusin\", onFocusin, true))\n cleanups.add(frames.addEventListener(\"focusin\", onFocusin, true))\n\n return () => {\n clearTimeout(timer)\n if (clickHandler) {\n frames.removeEventListener(\"click\", clickHandler)\n doc.removeEventListener(\"click\", clickHandler)\n }\n cleanups.forEach((fn) => fn())\n }\n}\n\nexport function trackInteractOutside(nodeOrFn: NodeOrFn, options: InteractOutsideOptions) {\n const { defer } = options\n const func = defer ? raf : (v: any) => v()\n const cleanups: (VoidFunction | undefined)[] = []\n cleanups.push(\n func(() => {\n const node = typeof nodeOrFn === \"function\" ? nodeOrFn() : nodeOrFn\n cleanups.push(trackInteractOutsideImpl(node, options))\n }),\n )\n return () => {\n cleanups.forEach((fn) => fn?.())\n }\n}\n","export function getWindowFrames(win: Window) {\n const frames = {\n each(cb: (win: Window) => void) {\n for (let i = 0; i < win.frames?.length; i += 1) {\n const frame = win.frames[i]\n if (frame) cb(frame)\n }\n },\n addEventListener(event: string, listener: any, options?: any) {\n frames.each((frame) => {\n try {\n frame.document.addEventListener(event, listener, options)\n } catch (err) {\n console.warn(err)\n }\n })\n return () => {\n try {\n frames.removeEventListener(event, listener, options)\n } catch (err) {\n console.warn(err)\n }\n }\n },\n removeEventListener(event: string, listener: any, options?: any) {\n frames.each((frame) => {\n try {\n frame.document.removeEventListener(event, listener, options)\n } catch (err) {\n console.warn(err)\n }\n })\n },\n }\n return frames\n}\n"],"mappings":"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","names":["handler"]}
package/dist/index.mjs CHANGED
@@ -54,6 +54,15 @@ function isComposedPathFocusable(event) {
54
54
  }
55
55
  return false;
56
56
  }
57
+ var isPointerEvent = (event) => "clientY" in event;
58
+ function isEventPointWithin(node, event) {
59
+ if (!isPointerEvent(event) || !node)
60
+ return false;
61
+ const rect = node.getBoundingClientRect();
62
+ if (rect.width === 0 || rect.height === 0)
63
+ return false;
64
+ return rect.top <= event.clientY && event.clientY <= rect.top + rect.height && rect.left <= event.clientX && event.clientX <= rect.left + rect.width;
65
+ }
57
66
  function trackInteractOutsideImpl(node, options) {
58
67
  const { exclude, onFocusOutside, onPointerDownOutside, onInteractOutside } = options;
59
68
  if (!node)
@@ -63,12 +72,12 @@ function trackInteractOutsideImpl(node, options) {
63
72
  const frames = getWindowFrames(win);
64
73
  function isEventOutside(event) {
65
74
  const target = getEventTarget(event);
66
- if (!isHTMLElement(target)) {
75
+ if (!isHTMLElement(target))
67
76
  return false;
68
- }
69
- if (contains(node, target)) {
77
+ if (contains(node, target))
78
+ return false;
79
+ if (isEventPointWithin(node, event))
70
80
  return false;
71
- }
72
81
  return !exclude?.(target);
73
82
  }
74
83
  let clickHandler;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/get-window-frames.ts"],"sourcesContent":["import { addDomEvent, fireCustomEvent, isContextMenuEvent } from \"@zag-js/dom-event\"\nimport { contains, getDocument, getEventTarget, getWindow, isHTMLElement, raf } from \"@zag-js/dom-query\"\nimport { isFocusable } from \"@zag-js/tabbable\"\nimport { callAll } from \"@zag-js/utils\"\nimport { getWindowFrames } from \"./get-window-frames\"\n\nexport type InteractOutsideHandlers = {\n onPointerDownOutside?: (event: PointerDownOutsideEvent) => void\n onFocusOutside?: (event: FocusOutsideEvent) => void\n onInteractOutside?: (event: InteractOutsideEvent) => void\n}\n\nexport type InteractOutsideOptions = InteractOutsideHandlers & {\n exclude?: (target: HTMLElement) => boolean\n defer?: boolean\n}\n\nexport type EventDetails<T> = {\n originalEvent: T\n contextmenu: boolean\n focusable: boolean\n}\n\nconst POINTER_OUTSIDE_EVENT = \"pointerdown.outside\"\nconst FOCUS_OUTSIDE_EVENT = \"focus.outside\"\n\nexport type PointerDownOutsideEvent = CustomEvent<EventDetails<PointerEvent>>\n\nexport type FocusOutsideEvent = CustomEvent<EventDetails<FocusEvent>>\n\nexport type InteractOutsideEvent = PointerDownOutsideEvent | FocusOutsideEvent\n\nexport type MaybeElement = HTMLElement | null | undefined\nexport type NodeOrFn = MaybeElement | (() => MaybeElement)\n\nfunction isComposedPathFocusable(event: Event) {\n const composedPath = event.composedPath() ?? [event.target as HTMLElement]\n for (const node of composedPath) {\n if (isHTMLElement(node) && isFocusable(node)) return true\n }\n return false\n}\n\nfunction trackInteractOutsideImpl(node: MaybeElement, options: InteractOutsideOptions) {\n const { exclude, onFocusOutside, onPointerDownOutside, onInteractOutside } = options\n\n if (!node) return\n\n const doc = getDocument(node)\n const win = getWindow(node)\n const frames = getWindowFrames(win)\n\n function isEventOutside(event: Event): boolean {\n const target = getEventTarget(event)\n\n if (!isHTMLElement(target)) {\n return false\n }\n\n if (contains(node, target)) {\n return false\n }\n\n return !exclude?.(target)\n }\n\n let clickHandler: VoidFunction\n\n function onPointerDown(event: PointerEvent) {\n //\n function handler() {\n if (!node || !isEventOutside(event)) return\n\n if (onPointerDownOutside || onInteractOutside) {\n const handler = callAll(onPointerDownOutside, onInteractOutside) as EventListener\n node.addEventListener(POINTER_OUTSIDE_EVENT, handler, { once: true })\n }\n\n fireCustomEvent(node, POINTER_OUTSIDE_EVENT, {\n bubbles: false,\n cancelable: true,\n detail: {\n originalEvent: event,\n contextmenu: isContextMenuEvent(event),\n focusable: isComposedPathFocusable(event),\n },\n })\n }\n\n if (event.pointerType === \"touch\") {\n frames.removeEventListener(\"click\", handler)\n doc.removeEventListener(\"click\", handler)\n\n clickHandler = handler\n\n doc.addEventListener(\"click\", handler, { once: true })\n frames.addEventListener(\"click\", handler, { once: true })\n } else {\n handler()\n }\n }\n const cleanups = new Set<VoidFunction>()\n\n const timer = setTimeout(() => {\n cleanups.add(frames.addEventListener(\"pointerdown\", onPointerDown, true))\n cleanups.add(addDomEvent(doc, \"pointerdown\", onPointerDown, true))\n }, 0)\n\n function onFocusin(event: FocusEvent) {\n //\n if (!node || !isEventOutside(event)) return\n\n if (onFocusOutside || onInteractOutside) {\n const handler = callAll(onFocusOutside, onInteractOutside) as EventListener\n node.addEventListener(FOCUS_OUTSIDE_EVENT, handler, { once: true })\n }\n\n fireCustomEvent(node, FOCUS_OUTSIDE_EVENT, {\n bubbles: false,\n cancelable: true,\n detail: {\n originalEvent: event,\n contextmenu: false,\n focusable: isFocusable(getEventTarget(event)),\n },\n })\n }\n\n cleanups.add(addDomEvent(doc, \"focusin\", onFocusin, true))\n cleanups.add(frames.addEventListener(\"focusin\", onFocusin, true))\n\n return () => {\n clearTimeout(timer)\n if (clickHandler) {\n frames.removeEventListener(\"click\", clickHandler)\n doc.removeEventListener(\"click\", clickHandler)\n }\n cleanups.forEach((fn) => fn())\n }\n}\n\nexport function trackInteractOutside(nodeOrFn: NodeOrFn, options: InteractOutsideOptions) {\n const { defer } = options\n const func = defer ? raf : (v: any) => v()\n const cleanups: (VoidFunction | undefined)[] = []\n cleanups.push(\n func(() => {\n const node = typeof nodeOrFn === \"function\" ? nodeOrFn() : nodeOrFn\n cleanups.push(trackInteractOutsideImpl(node, options))\n }),\n )\n return () => {\n cleanups.forEach((fn) => fn?.())\n }\n}\n","export function getWindowFrames(win: Window) {\n const frames = {\n each(cb: (win: Window) => void) {\n for (let i = 0; i < win.frames?.length; i += 1) {\n const frame = win.frames[i]\n if (frame) cb(frame)\n }\n },\n addEventListener(event: string, listener: any, options?: any) {\n frames.each((frame) => {\n try {\n frame.document.addEventListener(event, listener, options)\n } catch (err) {\n console.warn(err)\n }\n })\n return () => {\n try {\n frames.removeEventListener(event, listener, options)\n } catch (err) {\n console.warn(err)\n }\n }\n },\n removeEventListener(event: string, listener: any, options?: any) {\n frames.each((frame) => {\n try {\n frame.document.removeEventListener(event, listener, options)\n } catch (err) {\n console.warn(err)\n }\n })\n },\n }\n return frames\n}\n"],"mappings":"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","names":["handler"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/get-window-frames.ts"],"sourcesContent":["import { addDomEvent, fireCustomEvent, isContextMenuEvent } from \"@zag-js/dom-event\"\nimport { contains, getDocument, getEventTarget, getWindow, isHTMLElement, raf } from \"@zag-js/dom-query\"\nimport { isFocusable } from \"@zag-js/tabbable\"\nimport { callAll } from \"@zag-js/utils\"\nimport { getWindowFrames } from \"./get-window-frames\"\n\nexport type InteractOutsideHandlers = {\n onPointerDownOutside?: (event: PointerDownOutsideEvent) => void\n onFocusOutside?: (event: FocusOutsideEvent) => void\n onInteractOutside?: (event: InteractOutsideEvent) => void\n}\n\nexport type InteractOutsideOptions = InteractOutsideHandlers & {\n exclude?: (target: HTMLElement) => boolean\n defer?: boolean\n}\n\nexport type EventDetails<T> = {\n originalEvent: T\n contextmenu: boolean\n focusable: boolean\n}\n\nconst POINTER_OUTSIDE_EVENT = \"pointerdown.outside\"\nconst FOCUS_OUTSIDE_EVENT = \"focus.outside\"\n\nexport type PointerDownOutsideEvent = CustomEvent<EventDetails<PointerEvent>>\n\nexport type FocusOutsideEvent = CustomEvent<EventDetails<FocusEvent>>\n\nexport type InteractOutsideEvent = PointerDownOutsideEvent | FocusOutsideEvent\n\nexport type MaybeElement = HTMLElement | null | undefined\nexport type NodeOrFn = MaybeElement | (() => MaybeElement)\n\nfunction isComposedPathFocusable(event: Event) {\n const composedPath = event.composedPath() ?? [event.target as HTMLElement]\n for (const node of composedPath) {\n if (isHTMLElement(node) && isFocusable(node)) return true\n }\n return false\n}\n\nconst isPointerEvent = (event: Event): event is PointerEvent => \"clientY\" in event\n\nfunction isEventPointWithin(node: MaybeElement, event: Event) {\n if (!isPointerEvent(event) || !node) return false\n\n const rect = node.getBoundingClientRect()\n if (rect.width === 0 || rect.height === 0) return false\n\n return (\n rect.top <= event.clientY &&\n event.clientY <= rect.top + rect.height &&\n rect.left <= event.clientX &&\n event.clientX <= rect.left + rect.width\n )\n}\n\nfunction trackInteractOutsideImpl(node: MaybeElement, options: InteractOutsideOptions) {\n const { exclude, onFocusOutside, onPointerDownOutside, onInteractOutside } = options\n\n if (!node) return\n\n const doc = getDocument(node)\n const win = getWindow(node)\n const frames = getWindowFrames(win)\n\n function isEventOutside(event: Event): boolean {\n const target = getEventTarget(event)\n if (!isHTMLElement(target)) return false\n if (contains(node, target)) return false\n if (isEventPointWithin(node, event)) return false\n return !exclude?.(target)\n }\n\n let clickHandler: VoidFunction\n\n function onPointerDown(event: PointerEvent) {\n //\n function handler() {\n if (!node || !isEventOutside(event)) return\n\n if (onPointerDownOutside || onInteractOutside) {\n const handler = callAll(onPointerDownOutside, onInteractOutside) as EventListener\n node.addEventListener(POINTER_OUTSIDE_EVENT, handler, { once: true })\n }\n\n fireCustomEvent(node, POINTER_OUTSIDE_EVENT, {\n bubbles: false,\n cancelable: true,\n detail: {\n originalEvent: event,\n contextmenu: isContextMenuEvent(event),\n focusable: isComposedPathFocusable(event),\n },\n })\n }\n\n if (event.pointerType === \"touch\") {\n frames.removeEventListener(\"click\", handler)\n doc.removeEventListener(\"click\", handler)\n\n clickHandler = handler\n\n doc.addEventListener(\"click\", handler, { once: true })\n frames.addEventListener(\"click\", handler, { once: true })\n } else {\n handler()\n }\n }\n const cleanups = new Set<VoidFunction>()\n\n const timer = setTimeout(() => {\n cleanups.add(frames.addEventListener(\"pointerdown\", onPointerDown, true))\n cleanups.add(addDomEvent(doc, \"pointerdown\", onPointerDown, true))\n }, 0)\n\n function onFocusin(event: FocusEvent) {\n //\n if (!node || !isEventOutside(event)) return\n\n if (onFocusOutside || onInteractOutside) {\n const handler = callAll(onFocusOutside, onInteractOutside) as EventListener\n node.addEventListener(FOCUS_OUTSIDE_EVENT, handler, { once: true })\n }\n\n fireCustomEvent(node, FOCUS_OUTSIDE_EVENT, {\n bubbles: false,\n cancelable: true,\n detail: {\n originalEvent: event,\n contextmenu: false,\n focusable: isFocusable(getEventTarget(event)),\n },\n })\n }\n\n cleanups.add(addDomEvent(doc, \"focusin\", onFocusin, true))\n cleanups.add(frames.addEventListener(\"focusin\", onFocusin, true))\n\n return () => {\n clearTimeout(timer)\n if (clickHandler) {\n frames.removeEventListener(\"click\", clickHandler)\n doc.removeEventListener(\"click\", clickHandler)\n }\n cleanups.forEach((fn) => fn())\n }\n}\n\nexport function trackInteractOutside(nodeOrFn: NodeOrFn, options: InteractOutsideOptions) {\n const { defer } = options\n const func = defer ? raf : (v: any) => v()\n const cleanups: (VoidFunction | undefined)[] = []\n cleanups.push(\n func(() => {\n const node = typeof nodeOrFn === \"function\" ? nodeOrFn() : nodeOrFn\n cleanups.push(trackInteractOutsideImpl(node, options))\n }),\n )\n return () => {\n cleanups.forEach((fn) => fn?.())\n }\n}\n","export function getWindowFrames(win: Window) {\n const frames = {\n each(cb: (win: Window) => void) {\n for (let i = 0; i < win.frames?.length; i += 1) {\n const frame = win.frames[i]\n if (frame) cb(frame)\n }\n },\n addEventListener(event: string, listener: any, options?: any) {\n frames.each((frame) => {\n try {\n frame.document.addEventListener(event, listener, options)\n } catch (err) {\n console.warn(err)\n }\n })\n return () => {\n try {\n frames.removeEventListener(event, listener, options)\n } catch (err) {\n console.warn(err)\n }\n }\n },\n removeEventListener(event: string, listener: any, options?: any) {\n frames.each((frame) => {\n try {\n frame.document.removeEventListener(event, listener, options)\n } catch (err) {\n console.warn(err)\n }\n })\n },\n }\n return frames\n}\n"],"mappings":"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","names":["handler"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zag-js/interact-outside",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "Track interations or focus outside an element",
5
5
  "keywords": [
6
6
  "js",
@@ -17,10 +17,10 @@
17
17
  "src"
18
18
  ],
19
19
  "dependencies": {
20
- "@zag-js/dom-query": "0.12.0",
21
- "@zag-js/dom-event": "0.12.0",
22
- "@zag-js/tabbable": "0.12.0",
23
- "@zag-js/utils": "0.12.0"
20
+ "@zag-js/dom-query": "0.14.0",
21
+ "@zag-js/dom-event": "0.14.0",
22
+ "@zag-js/tabbable": "0.14.0",
23
+ "@zag-js/utils": "0.14.0"
24
24
  },
25
25
  "devDependencies": {
26
26
  "clean-package": "2.2.0"
package/src/index.ts CHANGED
@@ -41,6 +41,22 @@ function isComposedPathFocusable(event: Event) {
41
41
  return false
42
42
  }
43
43
 
44
+ const isPointerEvent = (event: Event): event is PointerEvent => "clientY" in event
45
+
46
+ function isEventPointWithin(node: MaybeElement, event: Event) {
47
+ if (!isPointerEvent(event) || !node) return false
48
+
49
+ const rect = node.getBoundingClientRect()
50
+ if (rect.width === 0 || rect.height === 0) return false
51
+
52
+ return (
53
+ rect.top <= event.clientY &&
54
+ event.clientY <= rect.top + rect.height &&
55
+ rect.left <= event.clientX &&
56
+ event.clientX <= rect.left + rect.width
57
+ )
58
+ }
59
+
44
60
  function trackInteractOutsideImpl(node: MaybeElement, options: InteractOutsideOptions) {
45
61
  const { exclude, onFocusOutside, onPointerDownOutside, onInteractOutside } = options
46
62
 
@@ -52,15 +68,9 @@ function trackInteractOutsideImpl(node: MaybeElement, options: InteractOutsideOp
52
68
 
53
69
  function isEventOutside(event: Event): boolean {
54
70
  const target = getEventTarget(event)
55
-
56
- if (!isHTMLElement(target)) {
57
- return false
58
- }
59
-
60
- if (contains(node, target)) {
61
- return false
62
- }
63
-
71
+ if (!isHTMLElement(target)) return false
72
+ if (contains(node, target)) return false
73
+ if (isEventPointWithin(node, event)) return false
64
74
  return !exclude?.(target)
65
75
  }
66
76