158 lines
7.8 KiB
JavaScript
158 lines
7.8 KiB
JavaScript
|
"use strict";
|
||
|
Object.defineProperty(exports, "__esModule", {
|
||
|
value: true
|
||
|
});
|
||
|
Object.defineProperty(exports, "useOnClickOutside", {
|
||
|
enumerable: true,
|
||
|
get: function() {
|
||
|
return useOnClickOutside;
|
||
|
}
|
||
|
});
|
||
|
const _interop_require_wildcard = require("@swc/helpers/_/_interop_require_wildcard");
|
||
|
const _react = /*#__PURE__*/ _interop_require_wildcard._(require("react"));
|
||
|
const _useEventCallback = require("./useEventCallback");
|
||
|
const _reactsharedcontexts = require("@fluentui/react-shared-contexts");
|
||
|
const DEFAULT_CONTAINS = (parent, child)=>!!(parent === null || parent === void 0 ? void 0 : parent.contains(child));
|
||
|
const useOnClickOutside = (options)=>{
|
||
|
const { targetDocument } = (0, _reactsharedcontexts.useFluent_unstable)();
|
||
|
const win = targetDocument === null || targetDocument === void 0 ? void 0 : targetDocument.defaultView;
|
||
|
const { refs, callback, element, disabled, disabledFocusOnIframe, contains = DEFAULT_CONTAINS } = options;
|
||
|
const timeoutId = _react.useRef(undefined);
|
||
|
useIFrameFocus({
|
||
|
element,
|
||
|
disabled: disabledFocusOnIframe || disabled,
|
||
|
callback,
|
||
|
refs,
|
||
|
contains
|
||
|
});
|
||
|
const isMouseDownInsideRef = _react.useRef(false);
|
||
|
const listener = (0, _useEventCallback.useEventCallback)((ev)=>{
|
||
|
if (isMouseDownInsideRef.current) {
|
||
|
isMouseDownInsideRef.current = false;
|
||
|
return;
|
||
|
}
|
||
|
const target = ev.composedPath()[0];
|
||
|
const isOutside = refs.every((ref)=>!contains(ref.current || null, target));
|
||
|
if (isOutside && !disabled) {
|
||
|
callback(ev);
|
||
|
}
|
||
|
});
|
||
|
const handleMouseDown = (0, _useEventCallback.useEventCallback)((ev)=>{
|
||
|
// Selecting text from inside to outside will rigger click event.
|
||
|
// In this case click event target is outside but mouse down event target is inside.
|
||
|
// And this click event should be considered as inside click.
|
||
|
isMouseDownInsideRef.current = refs.some((ref)=>contains(ref.current || null, ev.target));
|
||
|
});
|
||
|
_react.useEffect(()=>{
|
||
|
if (disabled) {
|
||
|
return;
|
||
|
}
|
||
|
// Store the current event to avoid triggering handlers immediately
|
||
|
// Note this depends on a deprecated but extremely well supported quirk of the web platform
|
||
|
// https://github.com/facebook/react/issues/20074
|
||
|
let currentEvent = getWindowEvent(win);
|
||
|
const conditionalHandler = (event)=>{
|
||
|
// Skip if this event is the same as the one running when we added the handlers
|
||
|
if (event === currentEvent) {
|
||
|
currentEvent = undefined;
|
||
|
return;
|
||
|
}
|
||
|
listener(event);
|
||
|
};
|
||
|
// use capture phase because React can update DOM before the event bubbles to the document
|
||
|
element === null || element === void 0 ? void 0 : element.addEventListener('click', conditionalHandler, true);
|
||
|
element === null || element === void 0 ? void 0 : element.addEventListener('touchstart', conditionalHandler, true);
|
||
|
element === null || element === void 0 ? void 0 : element.addEventListener('contextmenu', conditionalHandler, true);
|
||
|
element === null || element === void 0 ? void 0 : element.addEventListener('mousedown', handleMouseDown, true);
|
||
|
// Garbage collect this event after it's no longer useful to avoid memory leaks
|
||
|
timeoutId.current = win === null || win === void 0 ? void 0 : win.setTimeout(()=>{
|
||
|
currentEvent = undefined;
|
||
|
}, 1);
|
||
|
return ()=>{
|
||
|
element === null || element === void 0 ? void 0 : element.removeEventListener('click', conditionalHandler, true);
|
||
|
element === null || element === void 0 ? void 0 : element.removeEventListener('touchstart', conditionalHandler, true);
|
||
|
element === null || element === void 0 ? void 0 : element.removeEventListener('contextmenu', conditionalHandler, true);
|
||
|
element === null || element === void 0 ? void 0 : element.removeEventListener('mousedown', handleMouseDown, true);
|
||
|
win === null || win === void 0 ? void 0 : win.clearTimeout(timeoutId.current);
|
||
|
currentEvent = undefined;
|
||
|
};
|
||
|
}, [
|
||
|
listener,
|
||
|
element,
|
||
|
disabled,
|
||
|
handleMouseDown,
|
||
|
win
|
||
|
]);
|
||
|
};
|
||
|
const getWindowEvent = (target)=>{
|
||
|
if (target) {
|
||
|
var _target_ownerDocument_defaultView, _target_ownerDocument;
|
||
|
if (typeof target.window === 'object' && target.window === target) {
|
||
|
// eslint-disable-next-line deprecation/deprecation
|
||
|
return target.event;
|
||
|
}
|
||
|
var _target_ownerDocument_defaultView_event;
|
||
|
// eslint-disable-next-line deprecation/deprecation
|
||
|
return (_target_ownerDocument_defaultView_event = (_target_ownerDocument = target.ownerDocument) === null || _target_ownerDocument === void 0 ? void 0 : (_target_ownerDocument_defaultView = _target_ownerDocument.defaultView) === null || _target_ownerDocument_defaultView === void 0 ? void 0 : _target_ownerDocument_defaultView.event) !== null && _target_ownerDocument_defaultView_event !== void 0 ? _target_ownerDocument_defaultView_event : undefined;
|
||
|
}
|
||
|
return undefined;
|
||
|
};
|
||
|
const FUI_FRAME_EVENT = 'fuiframefocus';
|
||
|
/**
|
||
|
* Since click events do not propagate past iframes, we use focus to detect if a
|
||
|
* click has happened inside an iframe, since the only ways of focusing inside an
|
||
|
* iframe are:
|
||
|
* - clicking inside
|
||
|
* - tabbing inside
|
||
|
*
|
||
|
* Polls the value of `document.activeElement`. If it is an iframe, then dispatch
|
||
|
* a custom DOM event. When the custom event is received call the provided callback
|
||
|
*/ const useIFrameFocus = (options)=>{
|
||
|
const { disabled, element: targetDocument, callback, contains = DEFAULT_CONTAINS, pollDuration = 1000, refs } = options;
|
||
|
const timeoutRef = _react.useRef();
|
||
|
const listener = (0, _useEventCallback.useEventCallback)((e)=>{
|
||
|
const isOutside = refs.every((ref)=>!contains(ref.current || null, e.target));
|
||
|
if (isOutside && !disabled) {
|
||
|
callback(e);
|
||
|
}
|
||
|
});
|
||
|
// Adds listener to the custom iframe focus event
|
||
|
_react.useEffect(()=>{
|
||
|
if (disabled) {
|
||
|
return;
|
||
|
}
|
||
|
targetDocument === null || targetDocument === void 0 ? void 0 : targetDocument.addEventListener(FUI_FRAME_EVENT, listener, true);
|
||
|
return ()=>{
|
||
|
targetDocument === null || targetDocument === void 0 ? void 0 : targetDocument.removeEventListener(FUI_FRAME_EVENT, listener, true);
|
||
|
};
|
||
|
}, [
|
||
|
targetDocument,
|
||
|
disabled,
|
||
|
listener
|
||
|
]);
|
||
|
// Starts polling for the active element
|
||
|
_react.useEffect(()=>{
|
||
|
var _targetDocument_defaultView;
|
||
|
if (disabled) {
|
||
|
return;
|
||
|
}
|
||
|
timeoutRef.current = targetDocument === null || targetDocument === void 0 ? void 0 : (_targetDocument_defaultView = targetDocument.defaultView) === null || _targetDocument_defaultView === void 0 ? void 0 : _targetDocument_defaultView.setInterval(()=>{
|
||
|
const activeElement = targetDocument === null || targetDocument === void 0 ? void 0 : targetDocument.activeElement;
|
||
|
if ((activeElement === null || activeElement === void 0 ? void 0 : activeElement.tagName) === 'IFRAME' || (activeElement === null || activeElement === void 0 ? void 0 : activeElement.tagName) === 'WEBVIEW') {
|
||
|
const event = new CustomEvent(FUI_FRAME_EVENT, {
|
||
|
bubbles: true
|
||
|
});
|
||
|
activeElement.dispatchEvent(event);
|
||
|
}
|
||
|
}, pollDuration);
|
||
|
return ()=>{
|
||
|
var _targetDocument_defaultView;
|
||
|
targetDocument === null || targetDocument === void 0 ? void 0 : (_targetDocument_defaultView = targetDocument.defaultView) === null || _targetDocument_defaultView === void 0 ? void 0 : _targetDocument_defaultView.clearTimeout(timeoutRef.current);
|
||
|
};
|
||
|
}, [
|
||
|
targetDocument,
|
||
|
disabled,
|
||
|
pollDuration
|
||
|
]);
|
||
|
};
|