@zag-js/interact-outside
Advanced tools
Comparing version 0.0.0-dev-20240723090825 to 0.0.0-v1-beta-20250220125322
@@ -5,15 +5,15 @@ interface InteractOutsideHandlers { | ||
*/ | ||
onPointerDownOutside?: (event: PointerDownOutsideEvent) => void; | ||
onPointerDownOutside?: ((event: PointerDownOutsideEvent) => void) | undefined; | ||
/** | ||
* Function called when the focus is moved outside the component | ||
*/ | ||
onFocusOutside?: (event: FocusOutsideEvent) => void; | ||
onFocusOutside?: ((event: FocusOutsideEvent) => void) | undefined; | ||
/** | ||
* Function called when an interaction happens outside the component | ||
*/ | ||
onInteractOutside?: (event: InteractOutsideEvent) => void; | ||
onInteractOutside?: ((event: InteractOutsideEvent) => void) | undefined; | ||
} | ||
interface InteractOutsideOptions extends InteractOutsideHandlers { | ||
exclude?: (target: HTMLElement) => boolean; | ||
defer?: boolean; | ||
exclude?: ((target: HTMLElement) => boolean) | undefined; | ||
defer?: boolean | undefined; | ||
} | ||
@@ -20,0 +20,0 @@ interface EventDetails<T> { |
@@ -1,32 +0,9 @@ | ||
"use strict"; | ||
var __defProp = Object.defineProperty; | ||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
var __getOwnPropNames = Object.getOwnPropertyNames; | ||
var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
var __export = (target, all) => { | ||
for (var name in all) | ||
__defProp(target, name, { get: all[name], enumerable: true }); | ||
}; | ||
var __copyProps = (to, from, except, desc) => { | ||
if (from && typeof from === "object" || typeof from === "function") { | ||
for (let key of __getOwnPropNames(from)) | ||
if (!__hasOwnProp.call(to, key) && key !== except) | ||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
} | ||
return to; | ||
}; | ||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | ||
'use strict'; | ||
var domQuery = require('@zag-js/dom-query'); | ||
var utils = require('@zag-js/utils'); | ||
// src/index.ts | ||
var src_exports = {}; | ||
__export(src_exports, { | ||
trackInteractOutside: () => trackInteractOutside | ||
}); | ||
module.exports = __toCommonJS(src_exports); | ||
var import_dom_event2 = require("@zag-js/dom-event"); | ||
var import_dom_query = require("@zag-js/dom-query"); | ||
var import_utils = require("@zag-js/utils"); | ||
// src/get-window-frames.ts | ||
var import_dom_event = require("@zag-js/dom-event"); | ||
// src/frame-utils.ts | ||
function getWindowFrames(win) { | ||
@@ -40,17 +17,2 @@ const frames = { | ||
}, | ||
queueBeforeEvent(event, listener) { | ||
const cleanup = /* @__PURE__ */ new Set(); | ||
frames.each((frame) => { | ||
try { | ||
cleanup.add((0, import_dom_event.queueBeforeEvent)(frame.document, event, listener)); | ||
} catch { | ||
} | ||
}); | ||
return () => { | ||
try { | ||
cleanup.forEach((fn) => fn()); | ||
} catch { | ||
} | ||
}; | ||
}, | ||
addEventListener(event, listener, options) { | ||
@@ -81,2 +43,25 @@ frames.each((frame) => { | ||
} | ||
function getParentWindow(win) { | ||
const parent = win.frameElement != null ? win.parent : null; | ||
return { | ||
addEventListener: (event, listener, options) => { | ||
try { | ||
parent?.addEventListener(event, listener, options); | ||
} catch { | ||
} | ||
return () => { | ||
try { | ||
parent?.removeEventListener(event, listener, options); | ||
} catch { | ||
} | ||
}; | ||
}, | ||
removeEventListener: (event, listener, options) => { | ||
try { | ||
parent?.removeEventListener(event, listener, options); | ||
} catch { | ||
} | ||
} | ||
}; | ||
} | ||
@@ -88,3 +73,3 @@ // src/index.ts | ||
for (const node of composedPath) { | ||
if ((0, import_dom_query.isHTMLElement)(node) && (0, import_dom_query.isFocusable)(node)) return true; | ||
if (domQuery.isHTMLElement(node) && domQuery.isFocusable(node)) return true; | ||
} | ||
@@ -100,9 +85,22 @@ return false; | ||
} | ||
function isEventWithinScrollbar(event) { | ||
const target = (0, import_dom_query.getEventTarget)(event); | ||
if (!target || !isPointerEvent(event)) return false; | ||
const isScrollableY = target.scrollHeight > target.clientHeight; | ||
const onScrollbarY = isScrollableY && event.clientX > target.clientWidth; | ||
const isScrollableX = target.scrollWidth > target.clientWidth; | ||
const onScrollbarX = isScrollableX && event.clientY > target.clientHeight; | ||
function isPointInRect(rect, point) { | ||
return rect.y <= point.y && point.y <= rect.y + rect.height && rect.x <= point.x && point.x <= rect.x + rect.width; | ||
} | ||
function isEventWithinScrollbar(event, ancestor) { | ||
if (!ancestor || !isPointerEvent(event)) return false; | ||
const isScrollableY = ancestor.scrollHeight > ancestor.clientHeight; | ||
const onScrollbarY = isScrollableY && event.clientX > ancestor.offsetLeft + ancestor.clientWidth; | ||
const isScrollableX = ancestor.scrollWidth > ancestor.clientWidth; | ||
const onScrollbarX = isScrollableX && event.clientY > ancestor.offsetTop + ancestor.clientHeight; | ||
const rect = { | ||
x: ancestor.offsetLeft, | ||
y: ancestor.offsetTop, | ||
width: ancestor.clientWidth + (isScrollableY ? 16 : 0), | ||
height: ancestor.clientHeight + (isScrollableX ? 16 : 0) | ||
}; | ||
const point = { | ||
x: event.clientX, | ||
y: event.clientY | ||
}; | ||
if (!isPointInRect(rect, point)) return false; | ||
return onScrollbarY || onScrollbarX; | ||
@@ -113,11 +111,19 @@ } | ||
if (!node) return; | ||
const doc = (0, import_dom_query.getDocument)(node); | ||
const win = (0, import_dom_query.getWindow)(node); | ||
const doc = domQuery.getDocument(node); | ||
const win = domQuery.getWindow(node); | ||
const frames = getWindowFrames(win); | ||
const parentWin = getParentWindow(win); | ||
function isEventOutside(event) { | ||
const target = (0, import_dom_query.getEventTarget)(event); | ||
if (!(0, import_dom_query.isHTMLElement)(target)) return false; | ||
if ((0, import_dom_query.contains)(node, target)) return false; | ||
const target = domQuery.getEventTarget(event); | ||
if (!domQuery.isHTMLElement(target)) return false; | ||
if (!target.isConnected) return false; | ||
if (domQuery.contains(node, target)) return false; | ||
if (isEventPointWithin(node, event)) return false; | ||
if (isEventWithinScrollbar(event)) return false; | ||
const triggerEl = doc.querySelector(`[aria-controls="${node.id}"]`); | ||
if (triggerEl) { | ||
const triggerAncestor = domQuery.getNearestOverflowAncestor(triggerEl); | ||
if (isEventWithinScrollbar(event, triggerAncestor)) return false; | ||
} | ||
const nodeAncestor = domQuery.getNearestOverflowAncestor(node); | ||
if (isEventWithinScrollbar(event, nodeAncestor)) return false; | ||
return !exclude?.(target); | ||
@@ -128,3 +134,3 @@ } | ||
function handler() { | ||
const func = defer ? import_dom_query.raf : (v) => v(); | ||
const func = defer ? domQuery.raf : (v) => v(); | ||
const composedPath = event.composedPath?.() ?? [event.target]; | ||
@@ -134,6 +140,6 @@ func(() => { | ||
if (onPointerDownOutside || onInteractOutside) { | ||
const handler2 = (0, import_utils.callAll)(onPointerDownOutside, onInteractOutside); | ||
const handler2 = utils.callAll(onPointerDownOutside, onInteractOutside); | ||
node.addEventListener(POINTER_OUTSIDE_EVENT, handler2, { once: true }); | ||
} | ||
(0, import_dom_event2.fireCustomEvent)(node, POINTER_OUTSIDE_EVENT, { | ||
fireCustomEvent(node, POINTER_OUTSIDE_EVENT, { | ||
bubbles: false, | ||
@@ -143,3 +149,3 @@ cancelable: true, | ||
originalEvent: event, | ||
contextmenu: (0, import_dom_event2.isContextMenuEvent)(event), | ||
contextmenu: domQuery.isContextMenuEvent(event), | ||
focusable: isComposedPathFocusable(composedPath) | ||
@@ -152,4 +158,5 @@ } | ||
pointerdownCleanups.forEach((fn) => fn()); | ||
pointerdownCleanups.add((0, import_dom_event2.queueBeforeEvent)(doc, "pointerup", handler)); | ||
pointerdownCleanups.add(frames.queueBeforeEvent("pointerup", handler)); | ||
pointerdownCleanups.add(domQuery.addDomEvent(doc, "click", handler, { once: true })); | ||
pointerdownCleanups.add(parentWin.addEventListener("click", handler, { once: true })); | ||
pointerdownCleanups.add(frames.addEventListener("click", handler, { once: true })); | ||
} else { | ||
@@ -161,14 +168,15 @@ handler(); | ||
const timer = setTimeout(() => { | ||
cleanups.add(domQuery.addDomEvent(doc, "pointerdown", onPointerDown, true)); | ||
cleanups.add(parentWin.addEventListener("pointerdown", onPointerDown, true)); | ||
cleanups.add(frames.addEventListener("pointerdown", onPointerDown, true)); | ||
cleanups.add((0, import_dom_event2.addDomEvent)(doc, "pointerdown", onPointerDown, true)); | ||
}, 0); | ||
function onFocusin(event) { | ||
const func = defer ? import_dom_query.raf : (v) => v(); | ||
const func = defer ? domQuery.raf : (v) => v(); | ||
func(() => { | ||
if (!node || !isEventOutside(event)) return; | ||
if (onFocusOutside || onInteractOutside) { | ||
const handler = (0, import_utils.callAll)(onFocusOutside, onInteractOutside); | ||
const handler = utils.callAll(onFocusOutside, onInteractOutside); | ||
node.addEventListener(FOCUS_OUTSIDE_EVENT, handler, { once: true }); | ||
} | ||
(0, import_dom_event2.fireCustomEvent)(node, FOCUS_OUTSIDE_EVENT, { | ||
fireCustomEvent(node, FOCUS_OUTSIDE_EVENT, { | ||
bubbles: false, | ||
@@ -179,3 +187,3 @@ cancelable: true, | ||
contextmenu: false, | ||
focusable: (0, import_dom_query.isFocusable)((0, import_dom_query.getEventTarget)(event)) | ||
focusable: domQuery.isFocusable(domQuery.getEventTarget(event)) | ||
} | ||
@@ -185,3 +193,4 @@ }); | ||
} | ||
cleanups.add((0, import_dom_event2.addDomEvent)(doc, "focusin", onFocusin, true)); | ||
cleanups.add(domQuery.addDomEvent(doc, "focusin", onFocusin, true)); | ||
cleanups.add(parentWin.addEventListener("focusin", onFocusin, true)); | ||
cleanups.add(frames.addEventListener("focusin", onFocusin, true)); | ||
@@ -196,3 +205,3 @@ return () => { | ||
const { defer } = options; | ||
const func = defer ? import_dom_query.raf : (v) => v(); | ||
const func = defer ? domQuery.raf : (v) => v(); | ||
const cleanups = []; | ||
@@ -209,6 +218,8 @@ cleanups.push( | ||
} | ||
// Annotate the CommonJS export names for ESM import in node: | ||
0 && (module.exports = { | ||
trackInteractOutside | ||
}); | ||
//# sourceMappingURL=index.js.map | ||
function fireCustomEvent(el, type, init) { | ||
const win = el.ownerDocument.defaultView || window; | ||
const event = new win.CustomEvent(type, init); | ||
return el.dispatchEvent(event); | ||
} | ||
exports.trackInteractOutside = trackInteractOutside; |
{ | ||
"name": "@zag-js/interact-outside", | ||
"version": "0.0.0-dev-20240723090825", | ||
"description": "Track interations or focus outside an element", | ||
"version": "0.0.0-v1-beta-20250220125322", | ||
"description": "Track interactions or focus outside an element", | ||
"keywords": [ | ||
@@ -16,9 +16,7 @@ "js", | ||
"files": [ | ||
"dist", | ||
"src" | ||
"dist" | ||
], | ||
"dependencies": { | ||
"@zag-js/dom-query": "0.0.0-dev-20240723090825", | ||
"@zag-js/dom-event": "0.0.0-dev-20240723090825", | ||
"@zag-js/utils": "0.0.0-dev-20240723090825" | ||
"@zag-js/dom-query": "0.0.0-v1-beta-20250220125322", | ||
"@zag-js/utils": "0.0.0-v1-beta-20250220125322" | ||
}, | ||
@@ -48,4 +46,4 @@ "devDependencies": { | ||
"build": "tsup", | ||
"test": "jest --config ../../../jest.config.js --rootDir tests", | ||
"lint": "eslint src --ext .ts,.tsx", | ||
"test": "vitest", | ||
"lint": "eslint src", | ||
"test-ci": "pnpm test --ci --runInBand -u", | ||
@@ -52,0 +50,0 @@ "test-watch": "pnpm test --watchAll" |
# @zag-js/interact-outside | ||
Track interations or focus outside an element | ||
Track interactions or focus outside an element | ||
@@ -5,0 +5,0 @@ ## Installation |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
2
20505
7
441
+ Added@zag-js/dom-query@0.0.0-v1-beta-20250220125322(transitive)
+ Added@zag-js/types@0.0.0-v1-beta-20250220125322(transitive)
+ Added@zag-js/utils@0.0.0-v1-beta-20250220125322(transitive)
- Removed@zag-js/dom-event@0.0.0-dev-20240723090825(transitive)
- Removed@zag-js/dom-query@0.0.0-dev-20240723090825(transitive)
- Removed@zag-js/text-selection@0.0.0-dev-20240723090825(transitive)
- Removed@zag-js/types@0.0.0-dev-20240723090825(transitive)
- Removed@zag-js/utils@0.0.0-dev-20240723090825(transitive)