Graduation
/
ui
/node_modules
/@melt-ui
/svelte
/dist
/internal
/actions
/interact-outside
/action.js
import { addEventListener, isElement, executeCallbacks, noop, } from '../../helpers/index.js'; | |
export function useInteractOutside(node, config) { | |
let unsub = noop; | |
let isPointerDown = false; | |
let isPointerDownInside = false; | |
let ignoreEmulatedMouseEvents = false; | |
function update(config) { | |
unsub(); | |
const { onInteractOutside, onInteractOutsideStart, enabled } = config; | |
if (!enabled) | |
return; | |
function onPointerDown(e) { | |
if (onInteractOutside && isValidEvent(e, node)) { | |
onInteractOutsideStart?.(e); | |
} | |
const target = e.target; | |
if (isElement(target) && isOrContainsTarget(node, target)) { | |
isPointerDownInside = true; | |
} | |
isPointerDown = true; | |
} | |
function triggerInteractOutside(e) { | |
onInteractOutside?.(e); | |
} | |
const documentObj = getOwnerDocument(node); | |
// Use pointer events if available, otherwise use mouse/touch events | |
if (typeof PointerEvent !== 'undefined') { | |
const onPointerUp = (e) => { | |
if (shouldTriggerInteractOutside(e)) { | |
triggerInteractOutside(e); | |
} | |
resetPointerState(); | |
}; | |
unsub = executeCallbacks(addEventListener(documentObj, 'pointerdown', onPointerDown, true), addEventListener(documentObj, 'pointerup', onPointerUp, true)); | |
} | |
else { | |
const onMouseUp = (e) => { | |
if (ignoreEmulatedMouseEvents) { | |
ignoreEmulatedMouseEvents = false; | |
} | |
else if (shouldTriggerInteractOutside(e)) { | |
triggerInteractOutside(e); | |
} | |
resetPointerState(); | |
}; | |
const onTouchEnd = (e) => { | |
ignoreEmulatedMouseEvents = true; | |
if (shouldTriggerInteractOutside(e)) { | |
triggerInteractOutside(e); | |
} | |
resetPointerState(); | |
}; | |
unsub = executeCallbacks(addEventListener(documentObj, 'mousedown', onPointerDown, true), addEventListener(documentObj, 'mouseup', onMouseUp, true), addEventListener(documentObj, 'touchstart', onPointerDown, true), addEventListener(documentObj, 'touchend', onTouchEnd, true)); | |
} | |
} | |
function shouldTriggerInteractOutside(e) { | |
if (isPointerDown && !isPointerDownInside && isValidEvent(e, node)) { | |
return true; | |
} | |
return false; | |
} | |
function resetPointerState() { | |
isPointerDown = false; | |
isPointerDownInside = false; | |
} | |
update(config); | |
return { | |
update, | |
destroy: unsub, | |
}; | |
} | |
function isValidEvent(e, node) { | |
if ('button' in e && e.button > 0) | |
return false; | |
const target = e.target; | |
if (!isElement(target)) | |
return false; | |
// if the target is no longer in the document (e.g. it was removed) ignore it | |
const ownerDocument = target.ownerDocument; | |
if (!ownerDocument || !ownerDocument.documentElement.contains(target)) { | |
return false; | |
} | |
return node && !isOrContainsTarget(node, target); | |
} | |
function isOrContainsTarget(node, target) { | |
return node === target || node.contains(target); | |
} | |
function getOwnerDocument(el) { | |
return el?.ownerDocument ?? document; | |
} | |