@zag-js/interact-outside 0.0.0-dev-20220627213111 → 0.0.0-dev-20220703123907

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 CHANGED
@@ -1,6 +1,17 @@
1
- export declare type InteractOutsideOptions = {
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
@@ -41,13 +41,13 @@ function addDomEvent(target, eventName, handler, options) {
41
41
  function isWindow(value) {
42
42
  return (value == null ? void 0 : value.toString()) === "[object Window]";
43
43
  }
44
- function getOwnerDocument(el) {
44
+ function getDocument(el) {
45
45
  var _a;
46
46
  if (isWindow(el))
47
47
  return el.document;
48
48
  return (_a = el == null ? void 0 : el.ownerDocument) != null ? _a : document;
49
49
  }
50
- function getOwnerWindow(el) {
50
+ function getWindow(el) {
51
51
  var _a;
52
52
  return (_a = el == null ? void 0 : el.ownerDocument.defaultView) != null ? _a : window;
53
53
  }
@@ -63,21 +63,54 @@ function contains(parent, child) {
63
63
  function isHTMLElement(v) {
64
64
  return typeof v === "object" && (v == null ? void 0 : v.nodeType) === Node.ELEMENT_NODE && typeof (v == null ? void 0 : v.nodeName) === "string";
65
65
  }
66
+ function isVisible(el) {
67
+ if (!isHTMLElement(el))
68
+ return false;
69
+ return el.offsetWidth > 0 || el.offsetHeight > 0 || el.getClientRects().length > 0;
70
+ }
71
+ function fireCustomEvent(el, type, init) {
72
+ if (!el)
73
+ return;
74
+ const event = new CustomEvent(type, init);
75
+ return el.dispatchEvent(event);
76
+ }
77
+ 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";
78
+ function isFocusable(element) {
79
+ if (!element)
80
+ return false;
81
+ return element.matches(focusableSelector) && isVisible(element);
82
+ }
83
+
84
+ // ../core/dist/index.mjs
85
+ var isDom = () => typeof window !== "undefined";
86
+ function getPlatform() {
87
+ var _a;
88
+ const agent = navigator.userAgentData;
89
+ return (_a = agent == null ? void 0 : agent.platform) != null ? _a : navigator.platform;
90
+ }
91
+ var pt = (v) => isDom() && v.test(getPlatform());
92
+ var isTouchDevice = () => isDom() && !!navigator.maxTouchPoints;
93
+ var isMac = () => pt(/^Mac/) && !isTouchDevice;
94
+ var isContextMenuEvent = (e) => {
95
+ return e.button === 2 || isCtrlKey(e) && e.button === 0;
96
+ };
97
+ var isCtrlKey = (v) => isMac() ? v.metaKey && !v.ctrlKey : v.ctrlKey && !v.metaKey;
66
98
 
67
99
  // src/index.ts
100
+ var POINTER_OUTSIDE_EVENT = "pointerdown.outside";
101
+ var FOCUS_OUTSIDE_EVENT = "focus.outside";
68
102
  function trackInteractOutside(node, options) {
69
- const { exclude, onPointerDownOutside, onFocusOutside } = options;
103
+ const { exclude, onFocusOutside, onPointerDownOutside } = options;
70
104
  if (!node)
71
105
  return;
72
- const doc = getOwnerDocument(node);
73
- const win = getOwnerWindow(node);
106
+ const doc = getDocument(node);
107
+ const win = getWindow(node);
74
108
  function isEventOutside(event) {
75
109
  const target = getEventTarget(event);
76
110
  if (!(target instanceof win.HTMLElement)) {
77
111
  return false;
78
112
  }
79
- const doc2 = target.ownerDocument;
80
- if (!contains(doc2.documentElement, target)) {
113
+ if (!contains(doc.documentElement, target)) {
81
114
  return false;
82
115
  }
83
116
  if (contains(node, target)) {
@@ -85,23 +118,57 @@ function trackInteractOutside(node, options) {
85
118
  }
86
119
  return !(exclude == null ? void 0 : exclude(target));
87
120
  }
121
+ let clickHandler;
88
122
  function onPointerDown(event) {
89
- if (isEventOutside(event)) {
90
- onPointerDownOutside == null ? void 0 : onPointerDownOutside(event);
123
+ function handler() {
124
+ if (!node || !isEventOutside(event))
125
+ return;
126
+ if (onPointerDownOutside) {
127
+ node.addEventListener(POINTER_OUTSIDE_EVENT, onPointerDownOutside, { once: true });
128
+ }
129
+ fireCustomEvent(node, POINTER_OUTSIDE_EVENT, {
130
+ bubbles: false,
131
+ cancelable: true,
132
+ detail: {
133
+ originalEvent: event,
134
+ contextmenu: isContextMenuEvent(event),
135
+ focusable: isFocusable(getEventTarget(event))
136
+ }
137
+ });
138
+ }
139
+ if (event.pointerType === "touch") {
140
+ doc.removeEventListener("click", handler);
141
+ clickHandler = handler;
142
+ doc.addEventListener("click", handler, { once: true });
143
+ } else {
144
+ handler();
91
145
  }
92
146
  }
93
147
  const cleanups = /* @__PURE__ */ new Set();
94
148
  const timer = setTimeout(() => {
95
149
  cleanups.add(addDomEvent(doc, "pointerdown", onPointerDown, true));
96
- });
150
+ }, 0);
97
151
  function onFocusin(event) {
98
- if (isEventOutside(event)) {
99
- onFocusOutside == null ? void 0 : onFocusOutside(event);
152
+ if (!node || !isEventOutside(event))
153
+ return;
154
+ if (onFocusOutside) {
155
+ node.addEventListener(FOCUS_OUTSIDE_EVENT, onFocusOutside, { once: true });
100
156
  }
157
+ fireCustomEvent(node, FOCUS_OUTSIDE_EVENT, {
158
+ bubbles: false,
159
+ cancelable: true,
160
+ detail: {
161
+ originalEvent: event,
162
+ contextmenu: false,
163
+ focusable: isFocusable(getEventTarget(event))
164
+ }
165
+ });
101
166
  }
102
167
  cleanups.add(addDomEvent(doc, "focusin", onFocusin, true));
103
168
  return () => {
104
169
  clearTimeout(timer);
170
+ if (clickHandler)
171
+ doc.removeEventListener("click", clickHandler);
105
172
  cleanups.forEach((fn) => fn());
106
173
  };
107
174
  }
package/dist/index.mjs CHANGED
@@ -17,13 +17,13 @@ function addDomEvent(target, eventName, handler, options) {
17
17
  function isWindow(value) {
18
18
  return (value == null ? void 0 : value.toString()) === "[object Window]";
19
19
  }
20
- function getOwnerDocument(el) {
20
+ function getDocument(el) {
21
21
  var _a;
22
22
  if (isWindow(el))
23
23
  return el.document;
24
24
  return (_a = el == null ? void 0 : el.ownerDocument) != null ? _a : document;
25
25
  }
26
- function getOwnerWindow(el) {
26
+ function getWindow(el) {
27
27
  var _a;
28
28
  return (_a = el == null ? void 0 : el.ownerDocument.defaultView) != null ? _a : window;
29
29
  }
@@ -39,21 +39,54 @@ function contains(parent, child) {
39
39
  function isHTMLElement(v) {
40
40
  return typeof v === "object" && (v == null ? void 0 : v.nodeType) === Node.ELEMENT_NODE && typeof (v == null ? void 0 : v.nodeName) === "string";
41
41
  }
42
+ function isVisible(el) {
43
+ if (!isHTMLElement(el))
44
+ return false;
45
+ return el.offsetWidth > 0 || el.offsetHeight > 0 || el.getClientRects().length > 0;
46
+ }
47
+ function fireCustomEvent(el, type, init) {
48
+ if (!el)
49
+ return;
50
+ const event = new CustomEvent(type, init);
51
+ return el.dispatchEvent(event);
52
+ }
53
+ 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";
54
+ function isFocusable(element) {
55
+ if (!element)
56
+ return false;
57
+ return element.matches(focusableSelector) && isVisible(element);
58
+ }
59
+
60
+ // ../core/dist/index.mjs
61
+ var isDom = () => typeof window !== "undefined";
62
+ function getPlatform() {
63
+ var _a;
64
+ const agent = navigator.userAgentData;
65
+ return (_a = agent == null ? void 0 : agent.platform) != null ? _a : navigator.platform;
66
+ }
67
+ var pt = (v) => isDom() && v.test(getPlatform());
68
+ var isTouchDevice = () => isDom() && !!navigator.maxTouchPoints;
69
+ var isMac = () => pt(/^Mac/) && !isTouchDevice;
70
+ var isContextMenuEvent = (e) => {
71
+ return e.button === 2 || isCtrlKey(e) && e.button === 0;
72
+ };
73
+ var isCtrlKey = (v) => isMac() ? v.metaKey && !v.ctrlKey : v.ctrlKey && !v.metaKey;
42
74
 
43
75
  // src/index.ts
76
+ var POINTER_OUTSIDE_EVENT = "pointerdown.outside";
77
+ var FOCUS_OUTSIDE_EVENT = "focus.outside";
44
78
  function trackInteractOutside(node, options) {
45
- const { exclude, onPointerDownOutside, onFocusOutside } = options;
79
+ const { exclude, onFocusOutside, onPointerDownOutside } = options;
46
80
  if (!node)
47
81
  return;
48
- const doc = getOwnerDocument(node);
49
- const win = getOwnerWindow(node);
82
+ const doc = getDocument(node);
83
+ const win = getWindow(node);
50
84
  function isEventOutside(event) {
51
85
  const target = getEventTarget(event);
52
86
  if (!(target instanceof win.HTMLElement)) {
53
87
  return false;
54
88
  }
55
- const doc2 = target.ownerDocument;
56
- if (!contains(doc2.documentElement, target)) {
89
+ if (!contains(doc.documentElement, target)) {
57
90
  return false;
58
91
  }
59
92
  if (contains(node, target)) {
@@ -61,23 +94,57 @@ function trackInteractOutside(node, options) {
61
94
  }
62
95
  return !(exclude == null ? void 0 : exclude(target));
63
96
  }
97
+ let clickHandler;
64
98
  function onPointerDown(event) {
65
- if (isEventOutside(event)) {
66
- onPointerDownOutside == null ? void 0 : onPointerDownOutside(event);
99
+ function handler() {
100
+ if (!node || !isEventOutside(event))
101
+ return;
102
+ if (onPointerDownOutside) {
103
+ node.addEventListener(POINTER_OUTSIDE_EVENT, onPointerDownOutside, { once: true });
104
+ }
105
+ fireCustomEvent(node, POINTER_OUTSIDE_EVENT, {
106
+ bubbles: false,
107
+ cancelable: true,
108
+ detail: {
109
+ originalEvent: event,
110
+ contextmenu: isContextMenuEvent(event),
111
+ focusable: isFocusable(getEventTarget(event))
112
+ }
113
+ });
114
+ }
115
+ if (event.pointerType === "touch") {
116
+ doc.removeEventListener("click", handler);
117
+ clickHandler = handler;
118
+ doc.addEventListener("click", handler, { once: true });
119
+ } else {
120
+ handler();
67
121
  }
68
122
  }
69
123
  const cleanups = /* @__PURE__ */ new Set();
70
124
  const timer = setTimeout(() => {
71
125
  cleanups.add(addDomEvent(doc, "pointerdown", onPointerDown, true));
72
- });
126
+ }, 0);
73
127
  function onFocusin(event) {
74
- if (isEventOutside(event)) {
75
- onFocusOutside == null ? void 0 : onFocusOutside(event);
128
+ if (!node || !isEventOutside(event))
129
+ return;
130
+ if (onFocusOutside) {
131
+ node.addEventListener(FOCUS_OUTSIDE_EVENT, onFocusOutside, { once: true });
76
132
  }
133
+ fireCustomEvent(node, FOCUS_OUTSIDE_EVENT, {
134
+ bubbles: false,
135
+ cancelable: true,
136
+ detail: {
137
+ originalEvent: event,
138
+ contextmenu: false,
139
+ focusable: isFocusable(getEventTarget(event))
140
+ }
141
+ });
77
142
  }
78
143
  cleanups.add(addDomEvent(doc, "focusin", onFocusin, true));
79
144
  return () => {
80
145
  clearTimeout(timer);
146
+ if (clickHandler)
147
+ doc.removeEventListener("click", clickHandler);
81
148
  cleanups.forEach((fn) => fn());
82
149
  };
83
150
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zag-js/interact-outside",
3
- "version": "0.0.0-dev-20220627213111",
3
+ "version": "0.0.0-dev-20220703123907",
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.0.0-dev-20220627213111"
31
+ "@zag-js/dom-utils": "0.0.0-dev-20220703123907"
32
32
  },
33
33
  "publishConfig": {
34
34
  "access": "public"