@zag-js/interact-outside 0.42.0 → 0.44.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
@@ -116,7 +116,7 @@ function trackInteractOutsideImpl(node, options) {
116
116
  function onPointerDown(event) {
117
117
  function handler() {
118
118
  const func = defer ? import_dom_query.raf : (v) => v();
119
- const composedPath = [...event.composedPath()];
119
+ const composedPath = event.composedPath?.() ?? [event.target];
120
120
  func(() => {
121
121
  if (!node || !isEventOutside(event))
122
122
  return;
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 interface InteractOutsideHandlers {\n /**\n * Function called when the pointer is pressed down outside the component\n */\n onPointerDownOutside?: (event: PointerDownOutsideEvent) => void\n /**\n * Function called when the focus is moved outside the component\n */\n onFocusOutside?: (event: FocusOutsideEvent) => void\n /**\n * Function called when an interaction happens outside the component\n */\n onInteractOutside?: (event: InteractOutsideEvent) => void\n}\n\nexport interface InteractOutsideOptions extends InteractOutsideHandlers {\n exclude?: (target: HTMLElement) => boolean\n defer?: boolean\n}\n\nexport interface 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(composedPath: EventTarget[]) {\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 isEventWithinScrollbar(event: Event): boolean {\n const target = getEventTarget<HTMLElement>(event)\n if (!target || !isPointerEvent(event)) return false\n\n const isScrollableY = target.scrollHeight > target.clientHeight\n const onScrollbarY = isScrollableY && event.clientX > target.clientWidth\n\n const isScrollableX = target.scrollWidth > target.clientWidth\n const onScrollbarX = isScrollableX && event.clientY > target.clientHeight\n\n return onScrollbarY || onScrollbarX\n}\n\nfunction trackInteractOutsideImpl(node: MaybeElement, options: InteractOutsideOptions) {\n const { exclude, onFocusOutside, onPointerDownOutside, onInteractOutside, defer } = 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 if (isEventWithinScrollbar(event)) return false\n return !exclude?.(target)\n }\n\n let clickHandler: VoidFunction\n\n function onPointerDown(event: PointerEvent) {\n //\n function handler() {\n const func = defer ? raf : (v: any) => v()\n const composedPath = [...event.composedPath()] ?? [getEventTarget(event)]\n func(() => {\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(composedPath),\n },\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 const func = defer ? raf : (v: any) => v()\n func(() => {\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\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 {}\n })\n return () => {\n try {\n frames.removeEventListener(event, listener, options)\n } catch {}\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 {}\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 interface InteractOutsideHandlers {\n /**\n * Function called when the pointer is pressed down outside the component\n */\n onPointerDownOutside?: (event: PointerDownOutsideEvent) => void\n /**\n * Function called when the focus is moved outside the component\n */\n onFocusOutside?: (event: FocusOutsideEvent) => void\n /**\n * Function called when an interaction happens outside the component\n */\n onInteractOutside?: (event: InteractOutsideEvent) => void\n}\n\nexport interface InteractOutsideOptions extends InteractOutsideHandlers {\n exclude?: (target: HTMLElement) => boolean\n defer?: boolean\n}\n\nexport interface 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(composedPath: EventTarget[]) {\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 isEventWithinScrollbar(event: Event): boolean {\n const target = getEventTarget<HTMLElement>(event)\n if (!target || !isPointerEvent(event)) return false\n\n const isScrollableY = target.scrollHeight > target.clientHeight\n const onScrollbarY = isScrollableY && event.clientX > target.clientWidth\n\n const isScrollableX = target.scrollWidth > target.clientWidth\n const onScrollbarX = isScrollableX && event.clientY > target.clientHeight\n\n return onScrollbarY || onScrollbarX\n}\n\nfunction trackInteractOutsideImpl(node: MaybeElement, options: InteractOutsideOptions) {\n const { exclude, onFocusOutside, onPointerDownOutside, onInteractOutside, defer } = 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 if (isEventWithinScrollbar(event)) return false\n return !exclude?.(target)\n }\n\n let clickHandler: VoidFunction\n\n function onPointerDown(event: PointerEvent) {\n //\n function handler() {\n const func = defer ? raf : (v: any) => v()\n const composedPath = event.composedPath?.() ?? [event.target]\n func(() => {\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(composedPath),\n },\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 const func = defer ? raf : (v: any) => v()\n func(() => {\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\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 {}\n })\n return () => {\n try {\n frames.removeEventListener(event, listener, options)\n } catch {}\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 {}\n })\n },\n }\n return frames\n}\n"],"mappings":"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","names":["handler"]}
package/dist/index.mjs CHANGED
@@ -92,7 +92,7 @@ function trackInteractOutsideImpl(node, options) {
92
92
  function onPointerDown(event) {
93
93
  function handler() {
94
94
  const func = defer ? raf : (v) => v();
95
- const composedPath = [...event.composedPath()];
95
+ const composedPath = event.composedPath?.() ?? [event.target];
96
96
  func(() => {
97
97
  if (!node || !isEventOutside(event))
98
98
  return;
@@ -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 interface InteractOutsideHandlers {\n /**\n * Function called when the pointer is pressed down outside the component\n */\n onPointerDownOutside?: (event: PointerDownOutsideEvent) => void\n /**\n * Function called when the focus is moved outside the component\n */\n onFocusOutside?: (event: FocusOutsideEvent) => void\n /**\n * Function called when an interaction happens outside the component\n */\n onInteractOutside?: (event: InteractOutsideEvent) => void\n}\n\nexport interface InteractOutsideOptions extends InteractOutsideHandlers {\n exclude?: (target: HTMLElement) => boolean\n defer?: boolean\n}\n\nexport interface 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(composedPath: EventTarget[]) {\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 isEventWithinScrollbar(event: Event): boolean {\n const target = getEventTarget<HTMLElement>(event)\n if (!target || !isPointerEvent(event)) return false\n\n const isScrollableY = target.scrollHeight > target.clientHeight\n const onScrollbarY = isScrollableY && event.clientX > target.clientWidth\n\n const isScrollableX = target.scrollWidth > target.clientWidth\n const onScrollbarX = isScrollableX && event.clientY > target.clientHeight\n\n return onScrollbarY || onScrollbarX\n}\n\nfunction trackInteractOutsideImpl(node: MaybeElement, options: InteractOutsideOptions) {\n const { exclude, onFocusOutside, onPointerDownOutside, onInteractOutside, defer } = 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 if (isEventWithinScrollbar(event)) return false\n return !exclude?.(target)\n }\n\n let clickHandler: VoidFunction\n\n function onPointerDown(event: PointerEvent) {\n //\n function handler() {\n const func = defer ? raf : (v: any) => v()\n const composedPath = [...event.composedPath()] ?? [getEventTarget(event)]\n func(() => {\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(composedPath),\n },\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 const func = defer ? raf : (v: any) => v()\n func(() => {\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\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 {}\n })\n return () => {\n try {\n frames.removeEventListener(event, listener, options)\n } catch {}\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 {}\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 interface InteractOutsideHandlers {\n /**\n * Function called when the pointer is pressed down outside the component\n */\n onPointerDownOutside?: (event: PointerDownOutsideEvent) => void\n /**\n * Function called when the focus is moved outside the component\n */\n onFocusOutside?: (event: FocusOutsideEvent) => void\n /**\n * Function called when an interaction happens outside the component\n */\n onInteractOutside?: (event: InteractOutsideEvent) => void\n}\n\nexport interface InteractOutsideOptions extends InteractOutsideHandlers {\n exclude?: (target: HTMLElement) => boolean\n defer?: boolean\n}\n\nexport interface 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(composedPath: EventTarget[]) {\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 isEventWithinScrollbar(event: Event): boolean {\n const target = getEventTarget<HTMLElement>(event)\n if (!target || !isPointerEvent(event)) return false\n\n const isScrollableY = target.scrollHeight > target.clientHeight\n const onScrollbarY = isScrollableY && event.clientX > target.clientWidth\n\n const isScrollableX = target.scrollWidth > target.clientWidth\n const onScrollbarX = isScrollableX && event.clientY > target.clientHeight\n\n return onScrollbarY || onScrollbarX\n}\n\nfunction trackInteractOutsideImpl(node: MaybeElement, options: InteractOutsideOptions) {\n const { exclude, onFocusOutside, onPointerDownOutside, onInteractOutside, defer } = 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 if (isEventWithinScrollbar(event)) return false\n return !exclude?.(target)\n }\n\n let clickHandler: VoidFunction\n\n function onPointerDown(event: PointerEvent) {\n //\n function handler() {\n const func = defer ? raf : (v: any) => v()\n const composedPath = event.composedPath?.() ?? [event.target]\n func(() => {\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(composedPath),\n },\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 const func = defer ? raf : (v: any) => v()\n func(() => {\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\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 {}\n })\n return () => {\n try {\n frames.removeEventListener(event, listener, options)\n } catch {}\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 {}\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.42.0",
3
+ "version": "0.44.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.42.0",
21
- "@zag-js/dom-event": "0.42.0",
22
- "@zag-js/tabbable": "0.42.0",
23
- "@zag-js/utils": "0.42.0"
20
+ "@zag-js/dom-query": "0.44.0",
21
+ "@zag-js/dom-event": "0.44.0",
22
+ "@zag-js/tabbable": "0.44.0",
23
+ "@zag-js/utils": "0.44.0"
24
24
  },
25
25
  "devDependencies": {
26
26
  "clean-package": "2.2.0"
package/src/index.ts CHANGED
@@ -102,7 +102,7 @@ function trackInteractOutsideImpl(node: MaybeElement, options: InteractOutsideOp
102
102
  //
103
103
  function handler() {
104
104
  const func = defer ? raf : (v: any) => v()
105
- const composedPath = [...event.composedPath()] ?? [getEventTarget(event)]
105
+ const composedPath = event.composedPath?.() ?? [event.target]
106
106
  func(() => {
107
107
  if (!node || !isEventOutside(event)) return
108
108