@zag-js/interact-outside 0.1.0 → 0.1.1
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.d.ts +14 -3
- package/dist/index.js +86 -20
- package/dist/index.mjs +86 -22
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
|
-
export declare type
|
|
1
|
+
export declare type InteractOutsideHandlers = {
|
|
2
|
+
onPointerDownOutside?: (event: PointerDownOutsideEvent) => void;
|
|
3
|
+
onFocusOutside?: (event: FocusOutsideEvent) => void;
|
|
4
|
+
};
|
|
5
|
+
export declare type InteractOutsideOptions = InteractOutsideHandlers & {
|
|
2
6
|
exclude?: (target: HTMLElement) => boolean;
|
|
3
|
-
onPointerDownOutside?: (event: Event) => void;
|
|
4
|
-
onFocusOutside?: (event: Event) => void;
|
|
5
7
|
};
|
|
8
|
+
declare type EventDetails<T> = {
|
|
9
|
+
originalEvent: T;
|
|
10
|
+
contextmenu: boolean;
|
|
11
|
+
focusable: boolean;
|
|
12
|
+
};
|
|
13
|
+
export declare type PointerDownOutsideEvent = CustomEvent<EventDetails<PointerEvent>>;
|
|
14
|
+
export declare type FocusOutsideEvent = CustomEvent<EventDetails<FocusEvent>>;
|
|
15
|
+
export declare type InteractOutsideEvent = PointerDownOutsideEvent | FocusOutsideEvent;
|
|
6
16
|
export declare function trackInteractOutside(node: HTMLElement | null, options: InteractOutsideOptions): () => void;
|
|
17
|
+
export {};
|
package/dist/index.js
CHANGED
|
@@ -25,29 +25,30 @@ __export(src_exports, {
|
|
|
25
25
|
module.exports = __toCommonJS(src_exports);
|
|
26
26
|
|
|
27
27
|
// ../dom/dist/index.mjs
|
|
28
|
-
var hasProp = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
|
|
29
28
|
var runIfFn = (v, ...a) => {
|
|
30
29
|
const res = typeof v === "function" ? v(...a) : v;
|
|
31
30
|
return res != null ? res : void 0;
|
|
32
31
|
};
|
|
33
|
-
var
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
};
|
|
32
|
+
var hasProp = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
|
|
33
|
+
var isDom = () => typeof window !== "undefined";
|
|
34
|
+
function getPlatform() {
|
|
35
|
+
var _a;
|
|
36
|
+
const agent = navigator.userAgentData;
|
|
37
|
+
return (_a = agent == null ? void 0 : agent.platform) != null ? _a : navigator.platform;
|
|
40
38
|
}
|
|
39
|
+
var pt = (v) => isDom() && v.test(getPlatform());
|
|
40
|
+
var isTouchDevice = () => isDom() && !!navigator.maxTouchPoints;
|
|
41
|
+
var isMac = () => pt(/^Mac/) && !isTouchDevice;
|
|
41
42
|
function isWindow(value) {
|
|
42
43
|
return (value == null ? void 0 : value.toString()) === "[object Window]";
|
|
43
44
|
}
|
|
44
|
-
function
|
|
45
|
+
function getDocument(el) {
|
|
45
46
|
var _a;
|
|
46
47
|
if (isWindow(el))
|
|
47
48
|
return el.document;
|
|
48
49
|
return (_a = el == null ? void 0 : el.ownerDocument) != null ? _a : document;
|
|
49
50
|
}
|
|
50
|
-
function
|
|
51
|
+
function getWindow(el) {
|
|
51
52
|
var _a;
|
|
52
53
|
return (_a = el == null ? void 0 : el.ownerDocument.defaultView) != null ? _a : window;
|
|
53
54
|
}
|
|
@@ -63,21 +64,52 @@ function contains(parent, child) {
|
|
|
63
64
|
function isHTMLElement(v) {
|
|
64
65
|
return typeof v === "object" && (v == null ? void 0 : v.nodeType) === Node.ELEMENT_NODE && typeof (v == null ? void 0 : v.nodeName) === "string";
|
|
65
66
|
}
|
|
67
|
+
function isVisible(el) {
|
|
68
|
+
if (!isHTMLElement(el))
|
|
69
|
+
return false;
|
|
70
|
+
return el.offsetWidth > 0 || el.offsetHeight > 0 || el.getClientRects().length > 0;
|
|
71
|
+
}
|
|
72
|
+
var isContextMenuEvent = (e) => {
|
|
73
|
+
return e.button === 2 || isCtrlKey(e) && e.button === 0;
|
|
74
|
+
};
|
|
75
|
+
var isCtrlKey = (v) => isMac() ? v.metaKey && !v.ctrlKey : v.ctrlKey && !v.metaKey;
|
|
76
|
+
function fireCustomEvent(el, type, init) {
|
|
77
|
+
if (!el)
|
|
78
|
+
return;
|
|
79
|
+
const win = getWindow(el);
|
|
80
|
+
const event = new win.CustomEvent(type, init);
|
|
81
|
+
return el.dispatchEvent(event);
|
|
82
|
+
}
|
|
83
|
+
var focusableSelector = "input:not([type='hidden']):not([disabled]), select:not([disabled]), textarea:not([disabled]), a[href], button:not([disabled]), [tabindex], iframe, object, embed, area[href], audio[controls], video[controls], [contenteditable]:not([contenteditable='false']), details > summary:first-of-type";
|
|
84
|
+
function isFocusable(element) {
|
|
85
|
+
if (!element)
|
|
86
|
+
return false;
|
|
87
|
+
return element.matches(focusableSelector) && isVisible(element);
|
|
88
|
+
}
|
|
89
|
+
var isRef = (v) => hasProp(v, "current");
|
|
90
|
+
function addDomEvent(target, eventName, handler, options) {
|
|
91
|
+
const node = isRef(target) ? target.current : runIfFn(target);
|
|
92
|
+
node == null ? void 0 : node.addEventListener(eventName, handler, options);
|
|
93
|
+
return () => {
|
|
94
|
+
node == null ? void 0 : node.removeEventListener(eventName, handler, options);
|
|
95
|
+
};
|
|
96
|
+
}
|
|
66
97
|
|
|
67
98
|
// src/index.ts
|
|
99
|
+
var POINTER_OUTSIDE_EVENT = "pointerdown.outside";
|
|
100
|
+
var FOCUS_OUTSIDE_EVENT = "focus.outside";
|
|
68
101
|
function trackInteractOutside(node, options) {
|
|
69
|
-
const { exclude,
|
|
102
|
+
const { exclude, onFocusOutside, onPointerDownOutside } = options;
|
|
70
103
|
if (!node)
|
|
71
104
|
return;
|
|
72
|
-
const doc =
|
|
73
|
-
const win =
|
|
105
|
+
const doc = getDocument(node);
|
|
106
|
+
const win = getWindow(node);
|
|
74
107
|
function isEventOutside(event) {
|
|
75
108
|
const target = getEventTarget(event);
|
|
76
109
|
if (!(target instanceof win.HTMLElement)) {
|
|
77
110
|
return false;
|
|
78
111
|
}
|
|
79
|
-
|
|
80
|
-
if (!contains(doc2.documentElement, target)) {
|
|
112
|
+
if (!contains(doc.documentElement, target)) {
|
|
81
113
|
return false;
|
|
82
114
|
}
|
|
83
115
|
if (contains(node, target)) {
|
|
@@ -85,23 +117,57 @@ function trackInteractOutside(node, options) {
|
|
|
85
117
|
}
|
|
86
118
|
return !(exclude == null ? void 0 : exclude(target));
|
|
87
119
|
}
|
|
120
|
+
let clickHandler;
|
|
88
121
|
function onPointerDown(event) {
|
|
89
|
-
|
|
90
|
-
|
|
122
|
+
function handler() {
|
|
123
|
+
if (!node || !isEventOutside(event))
|
|
124
|
+
return;
|
|
125
|
+
if (onPointerDownOutside) {
|
|
126
|
+
node.addEventListener(POINTER_OUTSIDE_EVENT, onPointerDownOutside, { once: true });
|
|
127
|
+
}
|
|
128
|
+
fireCustomEvent(node, POINTER_OUTSIDE_EVENT, {
|
|
129
|
+
bubbles: false,
|
|
130
|
+
cancelable: true,
|
|
131
|
+
detail: {
|
|
132
|
+
originalEvent: event,
|
|
133
|
+
contextmenu: isContextMenuEvent(event),
|
|
134
|
+
focusable: isFocusable(getEventTarget(event))
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (event.pointerType === "touch") {
|
|
139
|
+
doc.removeEventListener("click", handler);
|
|
140
|
+
clickHandler = handler;
|
|
141
|
+
doc.addEventListener("click", handler, { once: true });
|
|
142
|
+
} else {
|
|
143
|
+
handler();
|
|
91
144
|
}
|
|
92
145
|
}
|
|
93
146
|
const cleanups = /* @__PURE__ */ new Set();
|
|
94
147
|
const timer = setTimeout(() => {
|
|
95
148
|
cleanups.add(addDomEvent(doc, "pointerdown", onPointerDown, true));
|
|
96
|
-
});
|
|
149
|
+
}, 0);
|
|
97
150
|
function onFocusin(event) {
|
|
98
|
-
if (isEventOutside(event))
|
|
99
|
-
|
|
151
|
+
if (!node || !isEventOutside(event))
|
|
152
|
+
return;
|
|
153
|
+
if (onFocusOutside) {
|
|
154
|
+
node.addEventListener(FOCUS_OUTSIDE_EVENT, onFocusOutside, { once: true });
|
|
100
155
|
}
|
|
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
|
+
});
|
|
101
165
|
}
|
|
102
166
|
cleanups.add(addDomEvent(doc, "focusin", onFocusin, true));
|
|
103
167
|
return () => {
|
|
104
168
|
clearTimeout(timer);
|
|
169
|
+
if (clickHandler)
|
|
170
|
+
doc.removeEventListener("click", clickHandler);
|
|
105
171
|
cleanups.forEach((fn) => fn());
|
|
106
172
|
};
|
|
107
173
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -1,29 +1,28 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
1
|
// ../dom/dist/index.mjs
|
|
4
|
-
var hasProp = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
|
|
5
2
|
var runIfFn = (v, ...a) => {
|
|
6
3
|
const res = typeof v === "function" ? v(...a) : v;
|
|
7
4
|
return res != null ? res : void 0;
|
|
8
5
|
};
|
|
9
|
-
var
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
};
|
|
6
|
+
var hasProp = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
|
|
7
|
+
var isDom = () => typeof window !== "undefined";
|
|
8
|
+
function getPlatform() {
|
|
9
|
+
var _a;
|
|
10
|
+
const agent = navigator.userAgentData;
|
|
11
|
+
return (_a = agent == null ? void 0 : agent.platform) != null ? _a : navigator.platform;
|
|
16
12
|
}
|
|
13
|
+
var pt = (v) => isDom() && v.test(getPlatform());
|
|
14
|
+
var isTouchDevice = () => isDom() && !!navigator.maxTouchPoints;
|
|
15
|
+
var isMac = () => pt(/^Mac/) && !isTouchDevice;
|
|
17
16
|
function isWindow(value) {
|
|
18
17
|
return (value == null ? void 0 : value.toString()) === "[object Window]";
|
|
19
18
|
}
|
|
20
|
-
function
|
|
19
|
+
function getDocument(el) {
|
|
21
20
|
var _a;
|
|
22
21
|
if (isWindow(el))
|
|
23
22
|
return el.document;
|
|
24
23
|
return (_a = el == null ? void 0 : el.ownerDocument) != null ? _a : document;
|
|
25
24
|
}
|
|
26
|
-
function
|
|
25
|
+
function getWindow(el) {
|
|
27
26
|
var _a;
|
|
28
27
|
return (_a = el == null ? void 0 : el.ownerDocument.defaultView) != null ? _a : window;
|
|
29
28
|
}
|
|
@@ -39,21 +38,52 @@ function contains(parent, child) {
|
|
|
39
38
|
function isHTMLElement(v) {
|
|
40
39
|
return typeof v === "object" && (v == null ? void 0 : v.nodeType) === Node.ELEMENT_NODE && typeof (v == null ? void 0 : v.nodeName) === "string";
|
|
41
40
|
}
|
|
41
|
+
function isVisible(el) {
|
|
42
|
+
if (!isHTMLElement(el))
|
|
43
|
+
return false;
|
|
44
|
+
return el.offsetWidth > 0 || el.offsetHeight > 0 || el.getClientRects().length > 0;
|
|
45
|
+
}
|
|
46
|
+
var isContextMenuEvent = (e) => {
|
|
47
|
+
return e.button === 2 || isCtrlKey(e) && e.button === 0;
|
|
48
|
+
};
|
|
49
|
+
var isCtrlKey = (v) => isMac() ? v.metaKey && !v.ctrlKey : v.ctrlKey && !v.metaKey;
|
|
50
|
+
function fireCustomEvent(el, type, init) {
|
|
51
|
+
if (!el)
|
|
52
|
+
return;
|
|
53
|
+
const win = getWindow(el);
|
|
54
|
+
const event = new win.CustomEvent(type, init);
|
|
55
|
+
return el.dispatchEvent(event);
|
|
56
|
+
}
|
|
57
|
+
var focusableSelector = "input:not([type='hidden']):not([disabled]), select:not([disabled]), textarea:not([disabled]), a[href], button:not([disabled]), [tabindex], iframe, object, embed, area[href], audio[controls], video[controls], [contenteditable]:not([contenteditable='false']), details > summary:first-of-type";
|
|
58
|
+
function isFocusable(element) {
|
|
59
|
+
if (!element)
|
|
60
|
+
return false;
|
|
61
|
+
return element.matches(focusableSelector) && isVisible(element);
|
|
62
|
+
}
|
|
63
|
+
var isRef = (v) => hasProp(v, "current");
|
|
64
|
+
function addDomEvent(target, eventName, handler, options) {
|
|
65
|
+
const node = isRef(target) ? target.current : runIfFn(target);
|
|
66
|
+
node == null ? void 0 : node.addEventListener(eventName, handler, options);
|
|
67
|
+
return () => {
|
|
68
|
+
node == null ? void 0 : node.removeEventListener(eventName, handler, options);
|
|
69
|
+
};
|
|
70
|
+
}
|
|
42
71
|
|
|
43
72
|
// src/index.ts
|
|
73
|
+
var POINTER_OUTSIDE_EVENT = "pointerdown.outside";
|
|
74
|
+
var FOCUS_OUTSIDE_EVENT = "focus.outside";
|
|
44
75
|
function trackInteractOutside(node, options) {
|
|
45
|
-
const { exclude,
|
|
76
|
+
const { exclude, onFocusOutside, onPointerDownOutside } = options;
|
|
46
77
|
if (!node)
|
|
47
78
|
return;
|
|
48
|
-
const doc =
|
|
49
|
-
const win =
|
|
79
|
+
const doc = getDocument(node);
|
|
80
|
+
const win = getWindow(node);
|
|
50
81
|
function isEventOutside(event) {
|
|
51
82
|
const target = getEventTarget(event);
|
|
52
83
|
if (!(target instanceof win.HTMLElement)) {
|
|
53
84
|
return false;
|
|
54
85
|
}
|
|
55
|
-
|
|
56
|
-
if (!contains(doc2.documentElement, target)) {
|
|
86
|
+
if (!contains(doc.documentElement, target)) {
|
|
57
87
|
return false;
|
|
58
88
|
}
|
|
59
89
|
if (contains(node, target)) {
|
|
@@ -61,23 +91,57 @@ function trackInteractOutside(node, options) {
|
|
|
61
91
|
}
|
|
62
92
|
return !(exclude == null ? void 0 : exclude(target));
|
|
63
93
|
}
|
|
94
|
+
let clickHandler;
|
|
64
95
|
function onPointerDown(event) {
|
|
65
|
-
|
|
66
|
-
|
|
96
|
+
function handler() {
|
|
97
|
+
if (!node || !isEventOutside(event))
|
|
98
|
+
return;
|
|
99
|
+
if (onPointerDownOutside) {
|
|
100
|
+
node.addEventListener(POINTER_OUTSIDE_EVENT, onPointerDownOutside, { once: true });
|
|
101
|
+
}
|
|
102
|
+
fireCustomEvent(node, POINTER_OUTSIDE_EVENT, {
|
|
103
|
+
bubbles: false,
|
|
104
|
+
cancelable: true,
|
|
105
|
+
detail: {
|
|
106
|
+
originalEvent: event,
|
|
107
|
+
contextmenu: isContextMenuEvent(event),
|
|
108
|
+
focusable: isFocusable(getEventTarget(event))
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
if (event.pointerType === "touch") {
|
|
113
|
+
doc.removeEventListener("click", handler);
|
|
114
|
+
clickHandler = handler;
|
|
115
|
+
doc.addEventListener("click", handler, { once: true });
|
|
116
|
+
} else {
|
|
117
|
+
handler();
|
|
67
118
|
}
|
|
68
119
|
}
|
|
69
120
|
const cleanups = /* @__PURE__ */ new Set();
|
|
70
121
|
const timer = setTimeout(() => {
|
|
71
122
|
cleanups.add(addDomEvent(doc, "pointerdown", onPointerDown, true));
|
|
72
|
-
});
|
|
123
|
+
}, 0);
|
|
73
124
|
function onFocusin(event) {
|
|
74
|
-
if (isEventOutside(event))
|
|
75
|
-
|
|
125
|
+
if (!node || !isEventOutside(event))
|
|
126
|
+
return;
|
|
127
|
+
if (onFocusOutside) {
|
|
128
|
+
node.addEventListener(FOCUS_OUTSIDE_EVENT, onFocusOutside, { once: true });
|
|
76
129
|
}
|
|
130
|
+
fireCustomEvent(node, FOCUS_OUTSIDE_EVENT, {
|
|
131
|
+
bubbles: false,
|
|
132
|
+
cancelable: true,
|
|
133
|
+
detail: {
|
|
134
|
+
originalEvent: event,
|
|
135
|
+
contextmenu: false,
|
|
136
|
+
focusable: isFocusable(getEventTarget(event))
|
|
137
|
+
}
|
|
138
|
+
});
|
|
77
139
|
}
|
|
78
140
|
cleanups.add(addDomEvent(doc, "focusin", onFocusin, true));
|
|
79
141
|
return () => {
|
|
80
142
|
clearTimeout(timer);
|
|
143
|
+
if (clickHandler)
|
|
144
|
+
doc.removeEventListener("click", clickHandler);
|
|
81
145
|
cleanups.forEach((fn) => fn());
|
|
82
146
|
};
|
|
83
147
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zag-js/interact-outside",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Track interations or focus outside an element",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"js",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"test:watch": "yarn test --watchAll"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@zag-js/dom-utils": "0.1.
|
|
31
|
+
"@zag-js/dom-utils": "0.1.6"
|
|
32
32
|
},
|
|
33
33
|
"publishConfig": {
|
|
34
34
|
"access": "public"
|