/** * @internal */ const CONTROLLER = '__FUIDT_CONTROLLER__'; /** * @internal */ const ELEMENT_METADATA = '__FUIDT_ELEMENT_METADATA__'; /** * @internal */ const HTML_ELEMENT_REFERENCE = '__FUIDT_HTML_ELEMENT_REFERENCE__'; /** * @internal */ const SERIALIZED_DATA_CHANGE = '__FUIDT_SERIALIZED_DATA_CHANGE__'; /** * Verifies if a given node is an HTMLElement, * this method works seamlessly with frames and elements from different documents * * This is preferred over simply using `instanceof`. * Since `instanceof` might be problematic while operating with [multiple realms](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof#instanceof_and_multiple_realms) * * @example * ```ts * isHTMLElement(event.target) && event.target.focus() * isHTMLElement(event.target, {constructorName: 'HTMLInputElement'}) && event.target.value // some value * ``` * */ function isHTMLElement(element, options) { var _typedElement$ownerDo, _options$constructorN; const typedElement = element; return Boolean((typedElement == null || (_typedElement$ownerDo = typedElement.ownerDocument) == null ? void 0 : _typedElement$ownerDo.defaultView) && typedElement instanceof typedElement.ownerDocument.defaultView[(_options$constructorN = options == null ? void 0 : options.constructorName) != null ? _options$constructorN : 'HTMLElement']); } /** * @internal */ const isHTMLElementWithMetadata = element => Boolean(isHTMLElement(element) && ELEMENT_METADATA in element && element.parentElement !== null); const createController = defaultView => { let selectedElement = null; const observer = new MutationObserver(mutations => { if (!selectedElement) { return; } for (const mutation of mutations) { if (mutation.type === 'childList' && Array.from(mutation.removedNodes).includes(selectedElement)) { controller.withdraw(); } } }); const controller = { get selectedElement() { return selectedElement; }, select: nextSelectedElement => { if (isHTMLElementWithMetadata(nextSelectedElement)) { selectedElement = nextSelectedElement; observer.observe(nextSelectedElement.parentElement, { childList: true, subtree: false }); } if (selectedElement && nextSelectedElement) { const metadata = selectedElement[ELEMENT_METADATA]; if (metadata.references.has(nextSelectedElement)) { return selectedElement; } } controller.withdraw(); return selectedElement; }, withdraw: () => { selectedElement = null; observer.disconnect(); defaultView.postMessage(SERIALIZED_DATA_CHANGE); } }; return controller; }; const injectController = _ref => { let { defaultView } = _ref; if (!defaultView) { return; } if (!defaultView[CONTROLLER]) { defaultView[CONTROLLER] = createController(defaultView); } }; const getController = targetDocument => { var _targetDocument$defau, _targetDocument$defau2; injectController(targetDocument); return (_targetDocument$defau = (_targetDocument$defau2 = targetDocument.defaultView) == null ? void 0 : _targetDocument$defau2[CONTROLLER]) != null ? _targetDocument$defau : null; }; const serialize = (data, references) => { const serializedData = JSON.parse(JSON.stringify(data, (_, value) => { if (isHTMLElement(value)) return references.add(value); if (typeof value === 'object' && value && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== Array.prototype) { if ('toString' in value) { return value.toString(); } return undefined; } return value; })); return serializedData; }; let counter = 0; const generateReferenceId = () => HTML_ELEMENT_REFERENCE + ":" + counter++; const createReferences = () => { const map = new Map(); const weakMap = new WeakMap(); const references = { add: element => { if (weakMap.has(element)) { // biome-ignore lint/style/noNonNullAssertion: weakMap.has(element) ensures this is not undefined return weakMap.get(element); } const id = generateReferenceId(); map.set(id, element); weakMap.set(element, id); return id; }, get: id => { const element = map.get(id); if (element && weakMap.has(element)) { return element; } }, has: element => { return weakMap.has(element); } }; return references; }; /** * devtools middleware * @public */ const devtools = function (targetDocument, middlewareDataCallback) { if (targetDocument === void 0) { targetDocument = document; } if (middlewareDataCallback === void 0) { middlewareDataCallback = floatingUIMiddlewareDataCallback; } return { name: '@floating-ui/devtools', fn: state => { const { [ELEMENT_METADATA]: metadata } = isHTMLElementWithMetadata(state.elements.floating) ? state.elements.floating : Object.assign(state.elements.floating, { [ELEMENT_METADATA]: { references: createReferences(), serializedData: [] } }); const serializedData = serialize(middlewareDataCallback(state), metadata.references); metadata.serializedData.unshift(serializedData); const controller = getController(targetDocument); if (metadata.serializedData.length > 1 && state.elements.floating === (controller == null ? void 0 : controller.selectedElement)) { var _targetDocument$defau; (_targetDocument$defau = targetDocument.defaultView) == null || _targetDocument$defau.postMessage(SERIALIZED_DATA_CHANGE); } return {}; } }; }; const floatingUIMiddlewareDataCallback = state => ({ ...state, type: 'FloatingUIMiddleware' }); export { devtools, devtools as middleware };