@zag-js/interact-outside 0.39.0 → 0.41.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 +35 -29
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +35 -29
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
- package/src/index.ts +37 -31
package/dist/index.js
CHANGED
|
@@ -67,8 +67,7 @@ function getWindowFrames(win) {
|
|
|
67
67
|
// src/index.ts
|
|
68
68
|
var POINTER_OUTSIDE_EVENT = "pointerdown.outside";
|
|
69
69
|
var FOCUS_OUTSIDE_EVENT = "focus.outside";
|
|
70
|
-
function isComposedPathFocusable(
|
|
71
|
-
const composedPath = event.composedPath() ?? [event.target];
|
|
70
|
+
function isComposedPathFocusable(composedPath) {
|
|
72
71
|
for (const node of composedPath) {
|
|
73
72
|
if ((0, import_dom_query.isHTMLElement)(node) && (0, import_tabbable.isFocusable)(node))
|
|
74
73
|
return true;
|
|
@@ -95,7 +94,7 @@ function isEventWithinScrollbar(event) {
|
|
|
95
94
|
return onScrollbarY || onScrollbarX;
|
|
96
95
|
}
|
|
97
96
|
function trackInteractOutsideImpl(node, options) {
|
|
98
|
-
const { exclude, onFocusOutside, onPointerDownOutside, onInteractOutside } = options;
|
|
97
|
+
const { exclude, onFocusOutside, onPointerDownOutside, onInteractOutside, defer } = options;
|
|
99
98
|
if (!node)
|
|
100
99
|
return;
|
|
101
100
|
const doc = (0, import_dom_query.getDocument)(node);
|
|
@@ -116,20 +115,24 @@ function trackInteractOutsideImpl(node, options) {
|
|
|
116
115
|
let clickHandler;
|
|
117
116
|
function onPointerDown(event) {
|
|
118
117
|
function handler() {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
cancelable: true,
|
|
128
|
-
detail: {
|
|
129
|
-
originalEvent: event,
|
|
130
|
-
contextmenu: (0, import_dom_event.isContextMenuEvent)(event),
|
|
131
|
-
focusable: isComposedPathFocusable(event)
|
|
118
|
+
const func = defer ? import_dom_query.raf : (v) => v();
|
|
119
|
+
const composedPath = [...event.composedPath()];
|
|
120
|
+
func(() => {
|
|
121
|
+
if (!node || !isEventOutside(event))
|
|
122
|
+
return;
|
|
123
|
+
if (onPointerDownOutside || onInteractOutside) {
|
|
124
|
+
const handler2 = (0, import_utils.callAll)(onPointerDownOutside, onInteractOutside);
|
|
125
|
+
node.addEventListener(POINTER_OUTSIDE_EVENT, handler2, { once: true });
|
|
132
126
|
}
|
|
127
|
+
(0, import_dom_event.fireCustomEvent)(node, POINTER_OUTSIDE_EVENT, {
|
|
128
|
+
bubbles: false,
|
|
129
|
+
cancelable: true,
|
|
130
|
+
detail: {
|
|
131
|
+
originalEvent: event,
|
|
132
|
+
contextmenu: (0, import_dom_event.isContextMenuEvent)(event),
|
|
133
|
+
focusable: isComposedPathFocusable(composedPath)
|
|
134
|
+
}
|
|
135
|
+
});
|
|
133
136
|
});
|
|
134
137
|
}
|
|
135
138
|
if (event.pointerType === "touch") {
|
|
@@ -148,20 +151,23 @@ function trackInteractOutsideImpl(node, options) {
|
|
|
148
151
|
cleanups.add((0, import_dom_event.addDomEvent)(doc, "pointerdown", onPointerDown, true));
|
|
149
152
|
}, 0);
|
|
150
153
|
function onFocusin(event) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
bubbles: false,
|
|
159
|
-
cancelable: true,
|
|
160
|
-
detail: {
|
|
161
|
-
originalEvent: event,
|
|
162
|
-
contextmenu: false,
|
|
163
|
-
focusable: (0, import_tabbable.isFocusable)((0, import_dom_query.getEventTarget)(event))
|
|
154
|
+
const func = defer ? import_dom_query.raf : (v) => v();
|
|
155
|
+
func(() => {
|
|
156
|
+
if (!node || !isEventOutside(event))
|
|
157
|
+
return;
|
|
158
|
+
if (onFocusOutside || onInteractOutside) {
|
|
159
|
+
const handler = (0, import_utils.callAll)(onFocusOutside, onInteractOutside);
|
|
160
|
+
node.addEventListener(FOCUS_OUTSIDE_EVENT, handler, { once: true });
|
|
164
161
|
}
|
|
162
|
+
(0, import_dom_event.fireCustomEvent)(node, FOCUS_OUTSIDE_EVENT, {
|
|
163
|
+
bubbles: false,
|
|
164
|
+
cancelable: true,
|
|
165
|
+
detail: {
|
|
166
|
+
originalEvent: event,
|
|
167
|
+
contextmenu: false,
|
|
168
|
+
focusable: (0, import_tabbable.isFocusable)((0, import_dom_query.getEventTarget)(event))
|
|
169
|
+
}
|
|
170
|
+
});
|
|
165
171
|
});
|
|
166
172
|
}
|
|
167
173
|
cleanups.add((0, import_dom_event.addDomEvent)(doc, "focusin", onFocusin, true));
|
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(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 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 } = 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 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 {}\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()] ?? [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"]}
|
package/dist/index.mjs
CHANGED
|
@@ -43,8 +43,7 @@ function getWindowFrames(win) {
|
|
|
43
43
|
// src/index.ts
|
|
44
44
|
var POINTER_OUTSIDE_EVENT = "pointerdown.outside";
|
|
45
45
|
var FOCUS_OUTSIDE_EVENT = "focus.outside";
|
|
46
|
-
function isComposedPathFocusable(
|
|
47
|
-
const composedPath = event.composedPath() ?? [event.target];
|
|
46
|
+
function isComposedPathFocusable(composedPath) {
|
|
48
47
|
for (const node of composedPath) {
|
|
49
48
|
if (isHTMLElement(node) && isFocusable(node))
|
|
50
49
|
return true;
|
|
@@ -71,7 +70,7 @@ function isEventWithinScrollbar(event) {
|
|
|
71
70
|
return onScrollbarY || onScrollbarX;
|
|
72
71
|
}
|
|
73
72
|
function trackInteractOutsideImpl(node, options) {
|
|
74
|
-
const { exclude, onFocusOutside, onPointerDownOutside, onInteractOutside } = options;
|
|
73
|
+
const { exclude, onFocusOutside, onPointerDownOutside, onInteractOutside, defer } = options;
|
|
75
74
|
if (!node)
|
|
76
75
|
return;
|
|
77
76
|
const doc = getDocument(node);
|
|
@@ -92,20 +91,24 @@ function trackInteractOutsideImpl(node, options) {
|
|
|
92
91
|
let clickHandler;
|
|
93
92
|
function onPointerDown(event) {
|
|
94
93
|
function handler() {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
cancelable: true,
|
|
104
|
-
detail: {
|
|
105
|
-
originalEvent: event,
|
|
106
|
-
contextmenu: isContextMenuEvent(event),
|
|
107
|
-
focusable: isComposedPathFocusable(event)
|
|
94
|
+
const func = defer ? raf : (v) => v();
|
|
95
|
+
const composedPath = [...event.composedPath()];
|
|
96
|
+
func(() => {
|
|
97
|
+
if (!node || !isEventOutside(event))
|
|
98
|
+
return;
|
|
99
|
+
if (onPointerDownOutside || onInteractOutside) {
|
|
100
|
+
const handler2 = callAll(onPointerDownOutside, onInteractOutside);
|
|
101
|
+
node.addEventListener(POINTER_OUTSIDE_EVENT, handler2, { once: true });
|
|
108
102
|
}
|
|
103
|
+
fireCustomEvent(node, POINTER_OUTSIDE_EVENT, {
|
|
104
|
+
bubbles: false,
|
|
105
|
+
cancelable: true,
|
|
106
|
+
detail: {
|
|
107
|
+
originalEvent: event,
|
|
108
|
+
contextmenu: isContextMenuEvent(event),
|
|
109
|
+
focusable: isComposedPathFocusable(composedPath)
|
|
110
|
+
}
|
|
111
|
+
});
|
|
109
112
|
});
|
|
110
113
|
}
|
|
111
114
|
if (event.pointerType === "touch") {
|
|
@@ -124,20 +127,23 @@ function trackInteractOutsideImpl(node, options) {
|
|
|
124
127
|
cleanups.add(addDomEvent(doc, "pointerdown", onPointerDown, true));
|
|
125
128
|
}, 0);
|
|
126
129
|
function onFocusin(event) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
bubbles: false,
|
|
135
|
-
cancelable: true,
|
|
136
|
-
detail: {
|
|
137
|
-
originalEvent: event,
|
|
138
|
-
contextmenu: false,
|
|
139
|
-
focusable: isFocusable(getEventTarget(event))
|
|
130
|
+
const func = defer ? raf : (v) => v();
|
|
131
|
+
func(() => {
|
|
132
|
+
if (!node || !isEventOutside(event))
|
|
133
|
+
return;
|
|
134
|
+
if (onFocusOutside || onInteractOutside) {
|
|
135
|
+
const handler = callAll(onFocusOutside, onInteractOutside);
|
|
136
|
+
node.addEventListener(FOCUS_OUTSIDE_EVENT, handler, { once: true });
|
|
140
137
|
}
|
|
138
|
+
fireCustomEvent(node, FOCUS_OUTSIDE_EVENT, {
|
|
139
|
+
bubbles: false,
|
|
140
|
+
cancelable: true,
|
|
141
|
+
detail: {
|
|
142
|
+
originalEvent: event,
|
|
143
|
+
contextmenu: false,
|
|
144
|
+
focusable: isFocusable(getEventTarget(event))
|
|
145
|
+
}
|
|
146
|
+
});
|
|
141
147
|
});
|
|
142
148
|
}
|
|
143
149
|
cleanups.add(addDomEvent(doc, "focusin", onFocusin, true));
|
package/dist/index.mjs.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(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 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 } = 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 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 {}\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()] ?? [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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zag-js/interact-outside",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.41.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.
|
|
21
|
-
"@zag-js/dom-event": "0.
|
|
22
|
-
"@zag-js/tabbable": "0.
|
|
23
|
-
"@zag-js/utils": "0.
|
|
20
|
+
"@zag-js/dom-query": "0.41.0",
|
|
21
|
+
"@zag-js/dom-event": "0.41.0",
|
|
22
|
+
"@zag-js/tabbable": "0.41.0",
|
|
23
|
+
"@zag-js/utils": "0.41.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"clean-package": "2.2.0"
|
package/src/index.ts
CHANGED
|
@@ -42,8 +42,7 @@ export type InteractOutsideEvent = PointerDownOutsideEvent | FocusOutsideEvent
|
|
|
42
42
|
export type MaybeElement = HTMLElement | null | undefined
|
|
43
43
|
export type NodeOrFn = MaybeElement | (() => MaybeElement)
|
|
44
44
|
|
|
45
|
-
function isComposedPathFocusable(
|
|
46
|
-
const composedPath = event.composedPath() ?? [event.target as HTMLElement]
|
|
45
|
+
function isComposedPathFocusable(composedPath: EventTarget[]) {
|
|
47
46
|
for (const node of composedPath) {
|
|
48
47
|
if (isHTMLElement(node) && isFocusable(node)) return true
|
|
49
48
|
}
|
|
@@ -80,7 +79,7 @@ function isEventWithinScrollbar(event: Event): boolean {
|
|
|
80
79
|
}
|
|
81
80
|
|
|
82
81
|
function trackInteractOutsideImpl(node: MaybeElement, options: InteractOutsideOptions) {
|
|
83
|
-
const { exclude, onFocusOutside, onPointerDownOutside, onInteractOutside } = options
|
|
82
|
+
const { exclude, onFocusOutside, onPointerDownOutside, onInteractOutside, defer } = options
|
|
84
83
|
|
|
85
84
|
if (!node) return
|
|
86
85
|
|
|
@@ -102,21 +101,25 @@ function trackInteractOutsideImpl(node: MaybeElement, options: InteractOutsideOp
|
|
|
102
101
|
function onPointerDown(event: PointerEvent) {
|
|
103
102
|
//
|
|
104
103
|
function handler() {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
104
|
+
const func = defer ? raf : (v: any) => v()
|
|
105
|
+
const composedPath = [...event.composedPath()] ?? [getEventTarget(event)]
|
|
106
|
+
func(() => {
|
|
107
|
+
if (!node || !isEventOutside(event)) return
|
|
108
|
+
|
|
109
|
+
if (onPointerDownOutside || onInteractOutside) {
|
|
110
|
+
const handler = callAll(onPointerDownOutside, onInteractOutside) as EventListener
|
|
111
|
+
node.addEventListener(POINTER_OUTSIDE_EVENT, handler, { once: true })
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
fireCustomEvent(node, POINTER_OUTSIDE_EVENT, {
|
|
115
|
+
bubbles: false,
|
|
116
|
+
cancelable: true,
|
|
117
|
+
detail: {
|
|
118
|
+
originalEvent: event,
|
|
119
|
+
contextmenu: isContextMenuEvent(event),
|
|
120
|
+
focusable: isComposedPathFocusable(composedPath),
|
|
121
|
+
},
|
|
122
|
+
})
|
|
120
123
|
})
|
|
121
124
|
}
|
|
122
125
|
|
|
@@ -141,21 +144,24 @@ function trackInteractOutsideImpl(node: MaybeElement, options: InteractOutsideOp
|
|
|
141
144
|
|
|
142
145
|
function onFocusin(event: FocusEvent) {
|
|
143
146
|
//
|
|
144
|
-
|
|
147
|
+
const func = defer ? raf : (v: any) => v()
|
|
148
|
+
func(() => {
|
|
149
|
+
if (!node || !isEventOutside(event)) return
|
|
145
150
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
151
|
+
if (onFocusOutside || onInteractOutside) {
|
|
152
|
+
const handler = callAll(onFocusOutside, onInteractOutside) as EventListener
|
|
153
|
+
node.addEventListener(FOCUS_OUTSIDE_EVENT, handler, { once: true })
|
|
154
|
+
}
|
|
150
155
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
fireCustomEvent(node, FOCUS_OUTSIDE_EVENT, {
|
|
157
|
+
bubbles: false,
|
|
158
|
+
cancelable: true,
|
|
159
|
+
detail: {
|
|
160
|
+
originalEvent: event,
|
|
161
|
+
contextmenu: false,
|
|
162
|
+
focusable: isFocusable(getEventTarget(event)),
|
|
163
|
+
},
|
|
164
|
+
})
|
|
159
165
|
})
|
|
160
166
|
}
|
|
161
167
|
|