import { nativeFocus, KEYBORG_FOCUSIN, KEYBORG_FOCUSOUT, createKeyborg, disposeKeyborg } from 'keyborg'; /*! * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ const TABSTER_ATTRIBUTE_NAME = "data-tabster"; const TABSTER_DUMMY_INPUT_ATTRIBUTE_NAME = "data-tabster-dummy"; const FOCUSABLE_SELECTOR = /*#__PURE__*/["a[href]", "button:not([disabled])", "input:not([disabled])", "select:not([disabled])", "textarea:not([disabled])", "*[tabindex]", "*[contenteditable]", "details > summary", "audio[controls]", "video[controls]"].join(", "); const AsyncFocusSources = { EscapeGroupper: 1, Restorer: 2, Deloser: 3 }; const ObservedElementAccessibilities = { Any: 0, Accessible: 1, Focusable: 2 }; const RestoreFocusOrders = { History: 0, DeloserDefault: 1, RootDefault: 2, DeloserFirst: 3, RootFirst: 4 }; const DeloserStrategies = { /** * If the focus is lost, the focus will be restored automatically using all available focus history. * This is the default strategy. */ Auto: 0, /** * If the focus is lost from this Deloser instance, the focus will not be restored automatically. * The application might listen to the event and restore the focus manually. * But if it is lost from another Deloser instance, the history of this Deloser could be used finding * the element to focus. */ Manual: 1 }; const Visibilities = { Invisible: 0, PartiallyVisible: 1, Visible: 2 }; const RestorerTypes = { Source: 0, Target: 1 }; const MoverDirections = { Both: 0, Vertical: 1, Horizontal: 2, Grid: 3, GridLinear: 4 // Two-dimentional movement depending on the visual placement. Allows linear movement. }; const MoverKeys = { ArrowUp: 1, ArrowDown: 2, ArrowLeft: 3, ArrowRight: 4, PageUp: 5, PageDown: 6, Home: 7, End: 8 }; const GroupperTabbabilities = { Unlimited: 0, Limited: 1, LimitedTrapFocus: 2 // The focus is limited as above, plus trapped when inside. }; const GroupperMoveFocusActions = { Enter: 1, Escape: 2 }; const SysDummyInputsPositions = { Auto: 0, Inside: 1, Outside: 2 // Tabster will always place dummy inputs outside of the container. }; /*! * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ function getTabsterOnElement(tabster, element) { var _a; return (_a = tabster.storageEntry(element)) === null || _a === void 0 ? void 0 : _a.tabster; } function updateTabsterByAttribute(tabster, element, dispose) { var _a, _b; const newAttrValue = dispose || tabster._noop ? undefined : element.getAttribute(TABSTER_ATTRIBUTE_NAME); let entry = tabster.storageEntry(element); let newAttr; if (newAttrValue) { if (newAttrValue !== ((_a = entry === null || entry === void 0 ? void 0 : entry.attr) === null || _a === void 0 ? void 0 : _a.string)) { try { const newValue = JSON.parse(newAttrValue); if (typeof newValue !== "object") { throw new Error(`Value is not a JSON object, got '${newAttrValue}'.`); } newAttr = { string: newAttrValue, object: newValue }; } catch (e) { if (process.env.NODE_ENV === 'development') { console.error(`data-tabster attribute error: ${e}`, element); } } } else { return; } } else if (!entry) { return; } if (!entry) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion entry = tabster.storageEntry(element, true); } if (!entry.tabster) { entry.tabster = {}; } const tabsterOnElement = entry.tabster || {}; const oldTabsterProps = ((_b = entry.attr) === null || _b === void 0 ? void 0 : _b.object) || {}; const newTabsterProps = (newAttr === null || newAttr === void 0 ? void 0 : newAttr.object) || {}; for (const key of Object.keys(oldTabsterProps)) { if (!newTabsterProps[key]) { if (key === "root") { const root = tabsterOnElement[key]; if (root) { tabster.root.onRoot(root, true); } } switch (key) { case "deloser": case "root": case "groupper": case "modalizer": case "restorer": case "mover": // eslint-disable-next-line no-case-declarations const part = tabsterOnElement[key]; if (part) { part.dispose(); delete tabsterOnElement[key]; } break; case "observed": delete tabsterOnElement[key]; if (tabster.observedElement) { tabster.observedElement.onObservedElementUpdate(element); } break; case "focusable": case "outline": case "uncontrolled": case "sys": delete tabsterOnElement[key]; break; } } } for (const key of Object.keys(newTabsterProps)) { const sys = newTabsterProps.sys; switch (key) { case "deloser": if (tabsterOnElement.deloser) { tabsterOnElement.deloser.setProps(newTabsterProps.deloser); } else { if (tabster.deloser) { tabsterOnElement.deloser = tabster.deloser.createDeloser(element, newTabsterProps.deloser); } else if (process.env.NODE_ENV === 'development') { console.error("Deloser API used before initialization, please call `getDeloser()`"); } } break; case "root": if (tabsterOnElement.root) { tabsterOnElement.root.setProps(newTabsterProps.root); } else { tabsterOnElement.root = tabster.root.createRoot(element, newTabsterProps.root, sys); } tabster.root.onRoot(tabsterOnElement.root); break; case "modalizer": if (tabsterOnElement.modalizer) { tabsterOnElement.modalizer.setProps(newTabsterProps.modalizer); } else { if (tabster.modalizer) { tabsterOnElement.modalizer = tabster.modalizer.createModalizer(element, newTabsterProps.modalizer, sys); } else if (process.env.NODE_ENV === 'development') { console.error("Modalizer API used before initialization, please call `getModalizer()`"); } } break; case "restorer": if (tabsterOnElement.restorer) { tabsterOnElement.restorer.setProps(newTabsterProps.restorer); } else { if (tabster.restorer) { if (newTabsterProps.restorer) { tabsterOnElement.restorer = tabster.restorer.createRestorer(element, newTabsterProps.restorer); } } else if (process.env.NODE_ENV === 'development') { console.error("Restorer API used before initialization, please call `getRestorer()`"); } } break; case "focusable": tabsterOnElement.focusable = newTabsterProps.focusable; break; case "groupper": if (tabsterOnElement.groupper) { tabsterOnElement.groupper.setProps(newTabsterProps.groupper); } else { if (tabster.groupper) { tabsterOnElement.groupper = tabster.groupper.createGroupper(element, newTabsterProps.groupper, sys); } else if (process.env.NODE_ENV === 'development') { console.error("Groupper API used before initialization, please call `getGroupper()`"); } } break; case "mover": if (tabsterOnElement.mover) { tabsterOnElement.mover.setProps(newTabsterProps.mover); } else { if (tabster.mover) { tabsterOnElement.mover = tabster.mover.createMover(element, newTabsterProps.mover, sys); } else if (process.env.NODE_ENV === 'development') { console.error("Mover API used before initialization, please call `getMover()`"); } } break; case "observed": if (tabster.observedElement) { tabsterOnElement.observed = newTabsterProps.observed; tabster.observedElement.onObservedElementUpdate(element); } else if (process.env.NODE_ENV === 'development') { console.error("ObservedElement API used before initialization, please call `getObservedElement()`"); } break; case "uncontrolled": tabsterOnElement.uncontrolled = newTabsterProps.uncontrolled; break; case "outline": if (tabster.outline) { tabsterOnElement.outline = newTabsterProps.outline; } else if (process.env.NODE_ENV === 'development') { console.error("Outline API used before initialization, please call `getOutline()`"); } break; case "sys": tabsterOnElement.sys = newTabsterProps.sys; break; default: console.error(`Unknown key '${key}' in data-tabster attribute value.`); } } if (newAttr) { entry.attr = newAttr; } else { if (Object.keys(tabsterOnElement).length === 0) { delete entry.tabster; delete entry.attr; } tabster.storageEntry(element, false); } } /*! * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ /** * Events sent by Tabster. */ const TabsterFocusInEventName = "tabster:focusin"; const TabsterFocusOutEventName = "tabster:focusout"; // Event is dispatched when Tabster wants to move focus as the result of // handling keyboard event. This allows to preventDefault() if you want to have // some custom logic. const TabsterMoveFocusEventName = "tabster:movefocus"; /** * Events sent by Deloser. */ const DeloserFocusLostEventName = "tabster:deloser:focus-lost"; /** * Events to be sent to Deloser by the application. */ const DeloserRestoreFocusEventName = "tabster:deloser:restore-focus"; /** * Events sent by Modalizer. */ const ModalizerActiveEventName = "tabster:modalizer:active"; const ModalizerInactiveEventName = "tabster:modalizer:inactive"; const ModalizerFocusInEventName = "tabster:modalizer:focusin"; const ModalizerFocusOutEventName = "tabster:modalizer:focusout"; /** * Events sent by Mover. */ const MoverStateEventName = "tabster:mover:state"; /** * Events to be sent to Mover by the application. */ // Event that can be dispatched by the application to programmatically move // focus inside Mover. const MoverMoveFocusEventName = "tabster:mover:movefocus"; // Event that can be dispatched by the application to forget or modify // memorized element in Mover with memorizeCurrent property. const MoverMemorizedElementEventName = "tabster:mover:memorized-element"; /** * Events sent by Groupper. */ /** * Events to be sent to Groupper by the application. */ // Event that can be dispatched by the application to programmatically enter // or escape Groupper. const GroupperMoveFocusEventName = "tabster:groupper:movefocus"; /** * Events sent by Restorer. */ const RestorerRestoreFocusEventName = "tabster:restorer:restore-focus"; /** * Events sent by Root. */ const RootFocusEventName = "tabster:root:focus"; const RootBlurEventName = "tabster:root:blur"; // Node.js environments do not have CustomEvent and it is needed for the events to be // evaluated. It doesn't matter if it works or not in Node.js environment. // So, we just need to make sure that it doesn't throw undefined reference. const CustomEvent_ = typeof CustomEvent !== "undefined" ? CustomEvent : function () { /* no-op */ }; class TabsterCustomEvent extends CustomEvent_ { constructor(type, detail) { super(type, { bubbles: true, cancelable: true, composed: true, detail }); this.details = detail; } } class TabsterFocusInEvent extends TabsterCustomEvent { constructor(detail) { super(TabsterFocusInEventName, detail); } } class TabsterFocusOutEvent extends TabsterCustomEvent { constructor(detail) { super(TabsterFocusOutEventName, detail); } } class TabsterMoveFocusEvent extends TabsterCustomEvent { constructor(detail) { super(TabsterMoveFocusEventName, detail); } } class MoverStateEvent extends TabsterCustomEvent { constructor(detail) { super(MoverStateEventName, detail); } } class MoverMoveFocusEvent extends TabsterCustomEvent { constructor(detail) { super(MoverMoveFocusEventName, detail); } } class MoverMemorizedElementEvent extends TabsterCustomEvent { constructor(detail) { super(MoverMemorizedElementEventName, detail); } } class GroupperMoveFocusEvent extends TabsterCustomEvent { constructor(detail) { super(GroupperMoveFocusEventName, detail); } } class ModalizerActiveEvent extends TabsterCustomEvent { constructor(detail) { super(ModalizerActiveEventName, detail); } } class ModalizerInactiveEvent extends TabsterCustomEvent { constructor(detail) { super(ModalizerInactiveEventName, detail); } } class DeloserFocusLostEvent extends TabsterCustomEvent { constructor(detail) { super(DeloserFocusLostEventName, detail); } } class DeloserRestoreFocusEvent extends TabsterCustomEvent { constructor() { super(DeloserRestoreFocusEventName); } } class RestorerRestoreFocusEvent extends TabsterCustomEvent { constructor() { super(RestorerRestoreFocusEventName); } } class RootFocusEvent extends TabsterCustomEvent { constructor(detail) { super(RootFocusEventName, detail); } } class RootBlurEvent extends TabsterCustomEvent { constructor(detail) { super(RootBlurEventName, detail); } } /*! * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ const _createMutationObserver = callback => new MutationObserver(callback); const _createTreeWalker = (doc, root, whatToShow, filter) => doc.createTreeWalker(root, whatToShow, filter); const _getParentNode = node => node ? node.parentNode : null; const _getParentElement = element => element ? element.parentElement : null; const _nodeContains = (parent, child) => !!(child && (parent === null || parent === void 0 ? void 0 : parent.contains(child))); const _getActiveElement = doc => doc.activeElement; const _querySelector = (element, selector) => element.querySelector(selector); const _querySelectorAll = (element, selector) => Array.prototype.slice.call(element.querySelectorAll(selector), 0); const _getElementById = (doc, id) => doc.getElementById(id); const _getFirstChild = node => (node === null || node === void 0 ? void 0 : node.firstChild) || null; const _getLastChild = node => (node === null || node === void 0 ? void 0 : node.lastChild) || null; const _getNextSibling = node => (node === null || node === void 0 ? void 0 : node.nextSibling) || null; const _getPreviousSibling = node => (node === null || node === void 0 ? void 0 : node.previousSibling) || null; const _getFirstElementChild = element => (element === null || element === void 0 ? void 0 : element.firstElementChild) || null; const _getLastElementChild = element => (element === null || element === void 0 ? void 0 : element.lastElementChild) || null; const _getNextElementSibling = element => (element === null || element === void 0 ? void 0 : element.nextElementSibling) || null; const _getPreviousElementSibling = element => (element === null || element === void 0 ? void 0 : element.previousElementSibling) || null; const _appendChild = (parent, child) => parent.appendChild(child); const _insertBefore = (parent, child, referenceChild) => parent.insertBefore(child, referenceChild); const _getSelection = ref => { var _a; return ((_a = ref.ownerDocument) === null || _a === void 0 ? void 0 : _a.getSelection()) || null; }; const _getElementsByName = (referenceElement, name) => referenceElement.ownerDocument.getElementsByName(name); const dom = { createMutationObserver: _createMutationObserver, createTreeWalker: _createTreeWalker, getParentNode: _getParentNode, getParentElement: _getParentElement, nodeContains: _nodeContains, getActiveElement: _getActiveElement, querySelector: _querySelector, querySelectorAll: _querySelectorAll, getElementById: _getElementById, getFirstChild: _getFirstChild, getLastChild: _getLastChild, getNextSibling: _getNextSibling, getPreviousSibling: _getPreviousSibling, getFirstElementChild: _getFirstElementChild, getLastElementChild: _getLastElementChild, getNextElementSibling: _getNextElementSibling, getPreviousElementSibling: _getPreviousElementSibling, appendChild: _appendChild, insertBefore: _insertBefore, getSelection: _getSelection, getElementsByName: _getElementsByName }; function setDOMAPI(domapi) { for (const key of Object.keys(domapi)) { dom[key] = domapi[key]; } } /*! * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ let _isBrokenIE11; const _DOMRect = typeof DOMRect !== "undefined" ? DOMRect : class { constructor(x, y, width, height) { this.left = x || 0; this.top = y || 0; this.right = (x || 0) + (width || 0); this.bottom = (y || 0) + (height || 0); } }; let _uidCounter = 0; try { // IE11 only accepts `filter` argument as a function (not object with the `acceptNode` // property as the docs define). Also `entityReferenceExpansion` argument is not // optional. And it throws exception when the above arguments aren't there. document.createTreeWalker(document, NodeFilter.SHOW_ELEMENT); _isBrokenIE11 = false; } catch (e) { _isBrokenIE11 = true; } const _updateDummyInputsTimeout = 100; function getInstanceContext(getWindow) { const win = getWindow(); let ctx = win.__tabsterInstanceContext; if (!ctx) { ctx = { elementByUId: {}, basics: { Promise: win.Promise || undefined, WeakRef: win.WeakRef || undefined }, containerBoundingRectCache: {}, lastContainerBoundingRectCacheId: 0, fakeWeakRefs: [], fakeWeakRefsStarted: false }; win.__tabsterInstanceContext = ctx; } return ctx; } function disposeInstanceContext(win) { const ctx = win.__tabsterInstanceContext; if (ctx) { ctx.elementByUId = {}; delete ctx.WeakRef; ctx.containerBoundingRectCache = {}; if (ctx.containerBoundingRectCacheTimer) { win.clearTimeout(ctx.containerBoundingRectCacheTimer); } if (ctx.fakeWeakRefsTimer) { win.clearTimeout(ctx.fakeWeakRefsTimer); } ctx.fakeWeakRefs = []; delete win.__tabsterInstanceContext; } } function createWeakMap(win) { const ctx = win.__tabsterInstanceContext; return new ((ctx === null || ctx === void 0 ? void 0 : ctx.basics.WeakMap) || WeakMap)(); } function hasSubFocusable(element) { return !!element.querySelector(FOCUSABLE_SELECTOR); } class FakeWeakRef { constructor(target) { this._target = target; } deref() { return this._target; } static cleanup(fwr, forceRemove) { if (!fwr._target) { return true; } if (forceRemove || !documentContains(fwr._target.ownerDocument, fwr._target)) { delete fwr._target; return true; } return false; } } class WeakHTMLElement { constructor(getWindow, element, data) { const context = getInstanceContext(getWindow); let ref; if (context.WeakRef) { ref = new context.WeakRef(element); } else { ref = new FakeWeakRef(element); context.fakeWeakRefs.push(ref); } this._ref = ref; this._data = data; } get() { const ref = this._ref; let element; if (ref) { element = ref.deref(); if (!element) { delete this._ref; } } return element; } getData() { return this._data; } } function cleanupFakeWeakRefs(getWindow, forceRemove) { const context = getInstanceContext(getWindow); context.fakeWeakRefs = context.fakeWeakRefs.filter(e => !FakeWeakRef.cleanup(e, forceRemove)); } function startFakeWeakRefsCleanup(getWindow) { const context = getInstanceContext(getWindow); if (!context.fakeWeakRefsStarted) { context.fakeWeakRefsStarted = true; context.WeakRef = getWeakRef(context); } if (!context.fakeWeakRefsTimer) { context.fakeWeakRefsTimer = getWindow().setTimeout(() => { context.fakeWeakRefsTimer = undefined; cleanupFakeWeakRefs(getWindow); startFakeWeakRefsCleanup(getWindow); }, 2 * 60 * 1000); // 2 minutes. } } function stopFakeWeakRefsCleanupAndClearStorage(getWindow) { const context = getInstanceContext(getWindow); context.fakeWeakRefsStarted = false; if (context.fakeWeakRefsTimer) { getWindow().clearTimeout(context.fakeWeakRefsTimer); context.fakeWeakRefsTimer = undefined; context.fakeWeakRefs = []; } } function createElementTreeWalker(doc, root, acceptNode) { // IE11 will throw an exception when the TreeWalker root is not an Element. if (root.nodeType !== Node.ELEMENT_NODE) { return undefined; } // TypeScript isn't aware of IE11 behaving badly. const filter = _isBrokenIE11 ? acceptNode : { acceptNode }; return dom.createTreeWalker(doc, root, NodeFilter.SHOW_ELEMENT, filter, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore: We still don't want to completely break IE11, so, entityReferenceExpansion argument is not optional. false /* Last argument is not optional for IE11! */ ); } function getBoundingRect(getWindow, element) { let cacheId = element.__tabsterCacheId; const context = getInstanceContext(getWindow); const cached = cacheId ? context.containerBoundingRectCache[cacheId] : undefined; if (cached) { return cached.rect; } const scrollingElement = element.ownerDocument && element.ownerDocument.documentElement; if (!scrollingElement) { return new _DOMRect(); } // A bounding rect of the top-level element contains the whole page regardless of the // scrollbar. So, we improvise a little and limiting the final result... let left = 0; let top = 0; let right = scrollingElement.clientWidth; let bottom = scrollingElement.clientHeight; if (element !== scrollingElement) { const r = element.getBoundingClientRect(); left = Math.max(left, r.left); top = Math.max(top, r.top); right = Math.min(right, r.right); bottom = Math.min(bottom, r.bottom); } const rect = new _DOMRect(left < right ? left : -1, top < bottom ? top : -1, left < right ? right - left : 0, top < bottom ? bottom - top : 0); if (!cacheId) { cacheId = "r-" + ++context.lastContainerBoundingRectCacheId; element.__tabsterCacheId = cacheId; } context.containerBoundingRectCache[cacheId] = { rect, element }; if (!context.containerBoundingRectCacheTimer) { context.containerBoundingRectCacheTimer = window.setTimeout(() => { context.containerBoundingRectCacheTimer = undefined; for (const cId of Object.keys(context.containerBoundingRectCache)) { delete context.containerBoundingRectCache[cId].element.__tabsterCacheId; } context.containerBoundingRectCache = {}; }, 50); } return rect; } function isElementVerticallyVisibleInContainer(getWindow, element, tolerance) { const container = getScrollableContainer(element); if (!container) { return false; } const containerRect = getBoundingRect(getWindow, container); const elementRect = element.getBoundingClientRect(); const intersectionTolerance = elementRect.height * (1 - tolerance); const topIntersection = Math.max(0, containerRect.top - elementRect.top); const bottomIntersection = Math.max(0, elementRect.bottom - containerRect.bottom); const totalIntersection = topIntersection + bottomIntersection; return totalIntersection === 0 || totalIntersection <= intersectionTolerance; } function scrollIntoView(getWindow, element, alignToTop) { // Built-in DOM's scrollIntoView() is cool, but when we have nested containers, // it scrolls all of them, not just the deepest one. So, trying to work it around. const container = getScrollableContainer(element); if (container) { const containerRect = getBoundingRect(getWindow, container); const elementRect = element.getBoundingClientRect(); if (alignToTop) { container.scrollTop += elementRect.top - containerRect.top; } else { container.scrollTop += elementRect.bottom - containerRect.bottom; } } } function getScrollableContainer(element) { const doc = element.ownerDocument; if (doc) { for (let el = dom.getParentElement(element); el; el = dom.getParentElement(el)) { if (el.scrollWidth > el.clientWidth || el.scrollHeight > el.clientHeight) { return el; } } return doc.documentElement; } return null; } function makeFocusIgnored(element) { element.__shouldIgnoreFocus = true; } function shouldIgnoreFocus(element) { return !!element.__shouldIgnoreFocus; } function getUId(wnd) { const rnd = new Uint32Array(4); if (wnd.crypto && wnd.crypto.getRandomValues) { wnd.crypto.getRandomValues(rnd); } else if (wnd.msCrypto && wnd.msCrypto.getRandomValues) { wnd.msCrypto.getRandomValues(rnd); } else { for (let i = 0; i < rnd.length; i++) { rnd[i] = 0xffffffff * Math.random(); } } const srnd = []; for (let i = 0; i < rnd.length; i++) { srnd.push(rnd[i].toString(36)); } srnd.push("|"); srnd.push((++_uidCounter).toString(36)); srnd.push("|"); srnd.push(Date.now().toString(36)); return srnd.join(""); } function getElementUId(getWindow, element) { const context = getInstanceContext(getWindow); let uid = element.__tabsterElementUID; if (!uid) { uid = element.__tabsterElementUID = getUId(getWindow()); } if (!context.elementByUId[uid] && documentContains(element.ownerDocument, element)) { context.elementByUId[uid] = new WeakHTMLElement(getWindow, element); } return uid; } function getWindowUId(win) { let uid = win.__tabsterCrossOriginWindowUID; if (!uid) { uid = win.__tabsterCrossOriginWindowUID = getUId(win); } return uid; } function clearElementCache(getWindow, parent) { const context = getInstanceContext(getWindow); for (const key of Object.keys(context.elementByUId)) { const wel = context.elementByUId[key]; const el = wel && wel.get(); if (el && parent) { if (!dom.nodeContains(parent, el)) { continue; } } delete context.elementByUId[key]; } } // IE11 doesn't have document.contains()... function documentContains(doc, element) { return dom.nodeContains(doc === null || doc === void 0 ? void 0 : doc.body, element); } function matchesSelector(element, selector) { const matches = element.matches || element.matchesSelector || element.msMatchesSelector || element.webkitMatchesSelector; return matches && matches.call(element, selector); } function getPromise(getWindow) { const context = getInstanceContext(getWindow); if (context.basics.Promise) { return context.basics.Promise; } throw new Error("No Promise defined."); } function getWeakRef(context) { return context.basics.WeakRef; } let _lastTabsterPartId = 0; class TabsterPart { constructor(tabster, element, props) { const getWindow = tabster.getWindow; this._tabster = tabster; this._element = new WeakHTMLElement(getWindow, element); this._props = { ...props }; this.id = "i" + ++_lastTabsterPartId; } getElement() { return this._element.get(); } getProps() { return this._props; } setProps(props) { this._props = { ...props }; } } /** * Dummy HTML elements that are used as focus sentinels for the DOM enclosed within them */ class DummyInput { constructor(getWindow, isOutside, props, element, fixedTarget) { var _a; this._focusIn = e => { if (this._fixedTarget) { const target = this._fixedTarget.get(); if (target) { nativeFocus(target); } return; } const input = this.input; if (this.onFocusIn && input) { const relatedTarget = e.relatedTarget; this.onFocusIn(this, this._isBackward(true, input, relatedTarget), relatedTarget); } }; this._focusOut = e => { if (this._fixedTarget) { return; } this.useDefaultAction = false; const input = this.input; if (this.onFocusOut && input) { const relatedTarget = e.relatedTarget; this.onFocusOut(this, this._isBackward(false, input, relatedTarget), relatedTarget); } }; const win = getWindow(); const input = win.document.createElement("i"); input.tabIndex = 0; input.setAttribute("role", "none"); input.setAttribute(TABSTER_DUMMY_INPUT_ATTRIBUTE_NAME, ""); input.setAttribute("aria-hidden", "true"); const style = input.style; style.position = "fixed"; style.width = style.height = "1px"; style.opacity = "0.001"; style.zIndex = "-1"; style.setProperty("content-visibility", "hidden"); makeFocusIgnored(input); this.input = input; this.isFirst = props.isFirst; this.isOutside = isOutside; this._isPhantom = (_a = props.isPhantom) !== null && _a !== void 0 ? _a : false; this._fixedTarget = fixedTarget; input.addEventListener("focusin", this._focusIn); input.addEventListener("focusout", this._focusOut); input.__tabsterDummyContainer = element; if (this._isPhantom) { this._disposeTimer = win.setTimeout(() => { delete this._disposeTimer; this.dispose(); }, 0); this._clearDisposeTimeout = () => { if (this._disposeTimer) { win.clearTimeout(this._disposeTimer); delete this._disposeTimer; } delete this._clearDisposeTimeout; }; } } dispose() { var _a; if (this._clearDisposeTimeout) { this._clearDisposeTimeout(); } const input = this.input; if (!input) { return; } delete this._fixedTarget; delete this.onFocusIn; delete this.onFocusOut; delete this.input; input.removeEventListener("focusin", this._focusIn); input.removeEventListener("focusout", this._focusOut); delete input.__tabsterDummyContainer; (_a = dom.getParentNode(input)) === null || _a === void 0 ? void 0 : _a.removeChild(input); } setTopLeft(top, left) { var _a; const style = (_a = this.input) === null || _a === void 0 ? void 0 : _a.style; if (style) { style.top = `${top}px`; style.left = `${left}px`; } } _isBackward(isIn, current, previous) { return isIn && !previous ? !this.isFirst : !!(previous && current.compareDocumentPosition(previous) & Node.DOCUMENT_POSITION_FOLLOWING); } } const DummyInputManagerPriorities = { Root: 1, Modalizer: 2, Mover: 3, Groupper: 4 }; class DummyInputManager { constructor(tabster, element, priority, sys, outsideByDefault, callForDefaultAction) { this._element = element; this._instance = new DummyInputManagerCore(tabster, element, this, priority, sys, outsideByDefault, callForDefaultAction); } _setHandlers(onFocusIn, onFocusOut) { this._onFocusIn = onFocusIn; this._onFocusOut = onFocusOut; } moveOut(backwards) { var _a; (_a = this._instance) === null || _a === void 0 ? void 0 : _a.moveOut(backwards); } moveOutWithDefaultAction(backwards, relatedEvent) { var _a; (_a = this._instance) === null || _a === void 0 ? void 0 : _a.moveOutWithDefaultAction(backwards, relatedEvent); } getHandler(isIn) { return isIn ? this._onFocusIn : this._onFocusOut; } setTabbable(tabbable) { var _a; (_a = this._instance) === null || _a === void 0 ? void 0 : _a.setTabbable(this, tabbable); } dispose() { if (this._instance) { this._instance.dispose(this); delete this._instance; } delete this._onFocusIn; delete this._onFocusOut; } static moveWithPhantomDummy(tabster, element, // The target element to move to or out of. moveOutOfElement, // Whether to move out of the element or into it. isBackward, // Are we tabbing of shift-tabbing? relatedEvent // The event that triggered the move. ) { // Phantom dummy is a hack to use browser's default action to move // focus from a specific point in the application to the next/previous // element. Default action is needed because next focusable element // is not always available to focus directly (for example, next focusable // is inside isolated iframe) or for uncontrolled areas we want to make // sure that something that controls it takes care of the focusing. // It works in a way that during the Tab key handling, we create a dummy // input element, place it to the specific place in the DOM and focus it, // then the default action of the Tab press will move focus from our dummy // input. And we remove it from the DOM right after that. const dummy = new DummyInput(tabster.getWindow, true, { isPhantom: true, isFirst: true }); const input = dummy.input; if (input) { let parent; let insertBefore; // Let's say we have a following DOM structure: //
// //
// // //
// //
// // We pass the "uncontrolled" div as the element to move to or out of. // // When we pass moveOutOfElement=true and isBackward=false, // the phantom dummy input will be inserted before Button4. // // When we pass moveOutOfElement=true and isBackward=true, there are // two cases. If the uncontrolled element is focusable (has tabindex=0), // the phantom dummy input will be inserted after Button1. If the // uncontrolled element is not focusable, the phantom dummy input will be // inserted before Button2. // // When we pass moveOutOfElement=false and isBackward=false, the // phantom dummy input will be inserted after Button1. // // When we pass moveOutOfElement=false and isBackward=true, the phantom // dummy input will be inserted before Button4. // // And we have a corner case for and we make sure that the inserted // dummy is inserted properly when there are existing permanent dummies. if (element.tagName === "BODY") { // We cannot insert elements outside of BODY. parent = element; insertBefore = moveOutOfElement && isBackward || !moveOutOfElement && !isBackward ? dom.getFirstElementChild(element) : null; } else { if (moveOutOfElement && (!isBackward || isBackward && !tabster.focusable.isFocusable(element, false, true, true))) { parent = element; insertBefore = isBackward ? element.firstElementChild : null; } else { parent = dom.getParentElement(element); insertBefore = moveOutOfElement && isBackward || !moveOutOfElement && !isBackward ? element : dom.getNextElementSibling(element); } let potentialDummy; let dummyFor; do { // This is a safety pillow for the cases when someone, combines // groupper with uncontrolled on the same node. Which is technically // not correct, but moving into the container element via its dummy // input would produce a correct behaviour in uncontrolled mode. potentialDummy = moveOutOfElement && isBackward || !moveOutOfElement && !isBackward ? dom.getPreviousElementSibling(insertBefore) : insertBefore; dummyFor = getDummyInputContainer(potentialDummy); if (dummyFor === element) { insertBefore = moveOutOfElement && isBackward || !moveOutOfElement && !isBackward ? potentialDummy : dom.getNextElementSibling(potentialDummy); } else { dummyFor = null; } } while (dummyFor); } if (parent === null || parent === void 0 ? void 0 : parent.dispatchEvent(new TabsterMoveFocusEvent({ by: "root", owner: parent, next: null, relatedEvent }))) { dom.insertBefore(parent, input, insertBefore); nativeFocus(input); } } } static addPhantomDummyWithTarget(tabster, sourceElement, isBackward, targetElement) { const dummy = new DummyInput(tabster.getWindow, true, { isPhantom: true, isFirst: true }, undefined, new WeakHTMLElement(tabster.getWindow, targetElement)); const input = dummy.input; if (input) { let dummyParent; let insertBefore; if (hasSubFocusable(sourceElement) && !isBackward) { dummyParent = sourceElement; insertBefore = dom.getFirstElementChild(sourceElement); } else { dummyParent = dom.getParentElement(sourceElement); insertBefore = isBackward ? sourceElement : dom.getNextElementSibling(sourceElement); } if (dummyParent) { dom.insertBefore(dummyParent, input, insertBefore); } } } } function setDummyInputDebugValue(dummy, wrappers) { var _a; const what = { 1: "Root", 2: "Modalizer", 3: "Mover", 4: "Groupper" }; (_a = dummy.input) === null || _a === void 0 ? void 0 : _a.setAttribute(TABSTER_DUMMY_INPUT_ATTRIBUTE_NAME, [`isFirst=${dummy.isFirst}`, `isOutside=${dummy.isOutside}`, ...wrappers.map(w => `(${what[w.priority]}, tabbable=${w.tabbable})`)].join(", ")); } class DummyInputObserver { constructor(win) { this._updateQueue = new Set(); this._lastUpdateQueueTime = 0; this._changedParents = new WeakSet(); this._dummyElements = []; this._dummyCallbacks = new WeakMap(); this._domChanged = parent => { var _a; if (this._changedParents.has(parent)) { return; } this._changedParents.add(parent); if (this._updateDummyInputsTimer) { return; } this._updateDummyInputsTimer = (_a = this._win) === null || _a === void 0 ? void 0 : _a.call(this).setTimeout(() => { delete this._updateDummyInputsTimer; for (const ref of this._dummyElements) { const dummyElement = ref.get(); if (dummyElement) { const callback = this._dummyCallbacks.get(dummyElement); if (callback) { const dummyParent = dom.getParentNode(dummyElement); if (!dummyParent || this._changedParents.has(dummyParent)) { callback(); } } } } this._changedParents = new WeakSet(); }, _updateDummyInputsTimeout); }; this._win = win; } add(dummy, callback) { if (!this._dummyCallbacks.has(dummy) && this._win) { this._dummyElements.push(new WeakHTMLElement(this._win, dummy)); this._dummyCallbacks.set(dummy, callback); this.domChanged = this._domChanged; } } remove(dummy) { this._dummyElements = this._dummyElements.filter(ref => { const element = ref.get(); return element && element !== dummy; }); this._dummyCallbacks.delete(dummy); if (this._dummyElements.length === 0) { delete this.domChanged; } } dispose() { var _a; const win = (_a = this._win) === null || _a === void 0 ? void 0 : _a.call(this); if (this._updateTimer) { win === null || win === void 0 ? void 0 : win.clearTimeout(this._updateTimer); delete this._updateTimer; } if (this._updateDummyInputsTimer) { win === null || win === void 0 ? void 0 : win.clearTimeout(this._updateDummyInputsTimer); delete this._updateDummyInputsTimer; } this._changedParents = new WeakSet(); this._dummyCallbacks = new WeakMap(); this._dummyElements = []; this._updateQueue.clear(); delete this.domChanged; delete this._win; } updatePositions(compute) { if (!this._win) { // As this is a public method, we make sure that it has no effect when // called after dispose(). return; } this._updateQueue.add(compute); this._lastUpdateQueueTime = Date.now(); this._scheduledUpdatePositions(); } _scheduledUpdatePositions() { var _a; if (this._updateTimer) { return; } this._updateTimer = (_a = this._win) === null || _a === void 0 ? void 0 : _a.call(this).setTimeout(() => { delete this._updateTimer; // updatePositions() might be called quite a lot during the scrolling. // So, instead of clearing the timeout and scheduling a new one, we // check if enough time has passed since the last updatePositions() call // and only schedule a new one if not. // At maximum, we will update dummy inputs positions // _updateDummyInputsTimeout * 2 after the last updatePositions() call. if (this._lastUpdateQueueTime + _updateDummyInputsTimeout <= Date.now()) { // A cache for current bulk of updates to reduce getComputedStyle() calls. const scrollTopLeftCache = new Map(); const setTopLeftCallbacks = []; for (const compute of this._updateQueue) { setTopLeftCallbacks.push(compute(scrollTopLeftCache)); } this._updateQueue.clear(); // We're splitting the computation of offsets and setting them to avoid extra // reflows. for (const setTopLeft of setTopLeftCallbacks) { setTopLeft(); } // Explicitly clear to not hold references till the next garbage collection. scrollTopLeftCache.clear(); } else { this._scheduledUpdatePositions(); } }, _updateDummyInputsTimeout); } } /** * Parent class that encapsulates the behaviour of dummy inputs (focus sentinels) */ class DummyInputManagerCore { constructor(tabster, element, manager, priority, sys, outsideByDefault, callForDefaultAction) { this._wrappers = []; this._isOutside = false; this._transformElements = new Set(); this._onFocusIn = (dummyInput, isBackward, relatedTarget) => { this._onFocus(true, dummyInput, isBackward, relatedTarget); }; this._onFocusOut = (dummyInput, isBackward, relatedTarget) => { this._onFocus(false, dummyInput, isBackward, relatedTarget); }; this.moveOut = backwards => { var _a; const first = this._firstDummy; const last = this._lastDummy; if (first && last) { // For the sake of performance optimization, the dummy input // position in the DOM updates asynchronously from the DOM change. // Calling _ensurePosition() to make sure the position is correct. this._ensurePosition(); const firstInput = first.input; const lastInput = last.input; const element = (_a = this._element) === null || _a === void 0 ? void 0 : _a.get(); if (firstInput && lastInput && element) { let toFocus; if (backwards) { firstInput.tabIndex = 0; toFocus = firstInput; } else { lastInput.tabIndex = 0; toFocus = lastInput; } if (toFocus) { nativeFocus(toFocus); } } } }; /** * Prepares to move focus out of the given element by focusing * one of the dummy inputs and setting the `useDefaultAction` flag * @param backwards focus moving to an element behind the given element */ this.moveOutWithDefaultAction = (backwards, relatedEvent) => { var _a; const first = this._firstDummy; const last = this._lastDummy; if (first && last) { // For the sake of performance optimization, the dummy input // position in the DOM updates asynchronously from the DOM change. // Calling _ensurePosition() to make sure the position is correct. this._ensurePosition(); const firstInput = first.input; const lastInput = last.input; const element = (_a = this._element) === null || _a === void 0 ? void 0 : _a.get(); if (firstInput && lastInput && element) { let toFocus; if (backwards) { if (!first.isOutside && this._tabster.focusable.isFocusable(element, true, true, true)) { toFocus = element; } else { first.useDefaultAction = true; firstInput.tabIndex = 0; toFocus = firstInput; } } else { last.useDefaultAction = true; lastInput.tabIndex = 0; toFocus = lastInput; } if (toFocus && element.dispatchEvent(new TabsterMoveFocusEvent({ by: "root", owner: element, next: null, relatedEvent }))) { nativeFocus(toFocus); } } } }; this.setTabbable = (manager, tabbable) => { var _a, _b; for (const w of this._wrappers) { if (w.manager === manager) { w.tabbable = tabbable; break; } } const wrapper = this._getCurrent(); if (wrapper) { const tabIndex = wrapper.tabbable ? 0 : -1; let input = (_a = this._firstDummy) === null || _a === void 0 ? void 0 : _a.input; if (input) { input.tabIndex = tabIndex; } input = (_b = this._lastDummy) === null || _b === void 0 ? void 0 : _b.input; if (input) { input.tabIndex = tabIndex; } } if (process.env.NODE_ENV === 'development') { this._firstDummy && setDummyInputDebugValue(this._firstDummy, this._wrappers); this._lastDummy && setDummyInputDebugValue(this._lastDummy, this._wrappers); } }; /** * Adds dummy inputs as the first and last child of the given element * Called each time the children under the element is mutated */ this._addDummyInputs = () => { if (this._addTimer) { return; } this._addTimer = this._getWindow().setTimeout(() => { delete this._addTimer; this._ensurePosition(); if (process.env.NODE_ENV === 'development') { this._firstDummy && setDummyInputDebugValue(this._firstDummy, this._wrappers); this._lastDummy && setDummyInputDebugValue(this._lastDummy, this._wrappers); } this._addTransformOffsets(); }, 0); }; this._addTransformOffsets = () => { this._tabster._dummyObserver.updatePositions(this._computeTransformOffsets); }; this._computeTransformOffsets = scrollTopLeftCache => { var _a, _b; const from = ((_a = this._firstDummy) === null || _a === void 0 ? void 0 : _a.input) || ((_b = this._lastDummy) === null || _b === void 0 ? void 0 : _b.input); const transformElements = this._transformElements; const newTransformElements = new Set(); let scrollTop = 0; let scrollLeft = 0; const win = this._getWindow(); for (let element = from; element && element.nodeType === Node.ELEMENT_NODE; element = dom.getParentElement(element)) { let scrollTopLeft = scrollTopLeftCache.get(element); // getComputedStyle() and element.scrollLeft/Top() cause style recalculation, // so we cache the result across all elements in the current bulk. if (scrollTopLeft === undefined) { const transform = win.getComputedStyle(element).transform; if (transform && transform !== "none") { scrollTopLeft = { scrollTop: element.scrollTop, scrollLeft: element.scrollLeft }; } scrollTopLeftCache.set(element, scrollTopLeft || null); } if (scrollTopLeft) { newTransformElements.add(element); if (!transformElements.has(element)) { element.addEventListener("scroll", this._addTransformOffsets); } scrollTop += scrollTopLeft.scrollTop; scrollLeft += scrollTopLeft.scrollLeft; } } for (const el of transformElements) { if (!newTransformElements.has(el)) { el.removeEventListener("scroll", this._addTransformOffsets); } } this._transformElements = newTransformElements; return () => { var _a, _b; (_a = this._firstDummy) === null || _a === void 0 ? void 0 : _a.setTopLeft(scrollTop, scrollLeft); (_b = this._lastDummy) === null || _b === void 0 ? void 0 : _b.setTopLeft(scrollTop, scrollLeft); }; }; const el = element.get(); if (!el) { throw new Error("No element"); } this._tabster = tabster; this._getWindow = tabster.getWindow; this._callForDefaultAction = callForDefaultAction; const instance = el.__tabsterDummy; (instance || this)._wrappers.push({ manager, priority, tabbable: true }); if (instance) { if (process.env.NODE_ENV === 'development') { this._firstDummy && setDummyInputDebugValue(this._firstDummy, instance._wrappers); this._lastDummy && setDummyInputDebugValue(this._lastDummy, instance._wrappers); } return instance; } el.__tabsterDummy = this; // Some elements allow only specific types of direct descendants and we need to // put our dummy inputs inside or outside of the element accordingly. const forcedDummyPosition = sys === null || sys === void 0 ? void 0 : sys.dummyInputsPosition; const tagName = el.tagName; this._isOutside = !forcedDummyPosition ? (outsideByDefault || tagName === "UL" || tagName === "OL" || tagName === "TABLE") && !(tagName === "LI" || tagName === "TD" || tagName === "TH") : forcedDummyPosition === SysDummyInputsPositions.Outside; this._firstDummy = new DummyInput(this._getWindow, this._isOutside, { isFirst: true }, element); this._lastDummy = new DummyInput(this._getWindow, this._isOutside, { isFirst: false }, element); // We will be checking dummy input parents to see if their child list have changed. // So, it is enough to have just one of the inputs observed, because // both dummy inputs always have the same parent. const dummyElement = this._firstDummy.input; dummyElement && tabster._dummyObserver.add(dummyElement, this._addDummyInputs); this._firstDummy.onFocusIn = this._onFocusIn; this._firstDummy.onFocusOut = this._onFocusOut; this._lastDummy.onFocusIn = this._onFocusIn; this._lastDummy.onFocusOut = this._onFocusOut; this._element = element; this._addDummyInputs(); } dispose(manager, force) { var _a, _b, _c, _d; const wrappers = this._wrappers = this._wrappers.filter(w => w.manager !== manager && !force); if (process.env.NODE_ENV === 'development') { this._firstDummy && setDummyInputDebugValue(this._firstDummy, wrappers); this._lastDummy && setDummyInputDebugValue(this._lastDummy, wrappers); } if (wrappers.length === 0) { delete ((_a = this._element) === null || _a === void 0 ? void 0 : _a.get()).__tabsterDummy; for (const el of this._transformElements) { el.removeEventListener("scroll", this._addTransformOffsets); } this._transformElements.clear(); const win = this._getWindow(); if (this._addTimer) { win.clearTimeout(this._addTimer); delete this._addTimer; } const dummyElement = (_b = this._firstDummy) === null || _b === void 0 ? void 0 : _b.input; dummyElement && this._tabster._dummyObserver.remove(dummyElement); (_c = this._firstDummy) === null || _c === void 0 ? void 0 : _c.dispose(); (_d = this._lastDummy) === null || _d === void 0 ? void 0 : _d.dispose(); } } _onFocus(isIn, dummyInput, isBackward, relatedTarget) { var _a; const wrapper = this._getCurrent(); if (wrapper && (!dummyInput.useDefaultAction || this._callForDefaultAction)) { (_a = wrapper.manager.getHandler(isIn)) === null || _a === void 0 ? void 0 : _a(dummyInput, isBackward, relatedTarget); } } _getCurrent() { this._wrappers.sort((a, b) => { if (a.tabbable !== b.tabbable) { return a.tabbable ? -1 : 1; } return a.priority - b.priority; }); return this._wrappers[0]; } _ensurePosition() { var _a, _b, _c; const element = (_a = this._element) === null || _a === void 0 ? void 0 : _a.get(); const firstDummyInput = (_b = this._firstDummy) === null || _b === void 0 ? void 0 : _b.input; const lastDummyInput = (_c = this._lastDummy) === null || _c === void 0 ? void 0 : _c.input; if (!element || !firstDummyInput || !lastDummyInput) { return; } if (this._isOutside) { const elementParent = dom.getParentNode(element); if (elementParent) { const nextSibling = dom.getNextSibling(element); if (nextSibling !== lastDummyInput) { dom.insertBefore(elementParent, lastDummyInput, nextSibling); } if (dom.getPreviousElementSibling(element) !== firstDummyInput) { dom.insertBefore(elementParent, firstDummyInput, element); } } } else { if (dom.getLastElementChild(element) !== lastDummyInput) { dom.appendChild(element, lastDummyInput); } const firstElementChild = dom.getFirstElementChild(element); if (firstElementChild && firstElementChild !== firstDummyInput && firstElementChild.parentNode) { dom.insertBefore(firstElementChild.parentNode, firstDummyInput, firstElementChild); } } } } function getLastChild$2(container) { let lastChild = null; for (let i = dom.getLastElementChild(container); i; i = dom.getLastElementChild(i)) { lastChild = i; } return lastChild || undefined; } function getAdjacentElement(from, prev) { let cur = from; let adjacent = null; while (cur && !adjacent) { adjacent = prev ? dom.getPreviousElementSibling(cur) : dom.getNextElementSibling(cur); cur = dom.getParentElement(cur); } return adjacent || undefined; } function augmentAttribute(tabster, element, name, value // Restore original value when undefined. ) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const entry = tabster.storageEntry(element, true); let ret = false; if (!entry.aug) { if (value === undefined) { return ret; } entry.aug = {}; } if (value === undefined) { if (name in entry.aug) { const origVal = entry.aug[name]; delete entry.aug[name]; if (origVal === null) { element.removeAttribute(name); } else { element.setAttribute(name, origVal); } ret = true; } } else { let origValue; if (!(name in entry.aug)) { origValue = element.getAttribute(name); } if (origValue !== undefined && origValue !== value) { entry.aug[name] = origValue; if (value === null) { element.removeAttribute(name); } else { element.setAttribute(name, value); } ret = true; } } if (value === undefined && Object.keys(entry.aug).length === 0) { delete entry.aug; tabster.storageEntry(element, false); } return ret; } function isDisplayNone(element) { var _a, _b; const elementDocument = element.ownerDocument; const computedStyle = (_a = elementDocument.defaultView) === null || _a === void 0 ? void 0 : _a.getComputedStyle(element); // offsetParent is null for elements with display:none, display:fixed and for . if (element.offsetParent === null && elementDocument.body !== element && (computedStyle === null || computedStyle === void 0 ? void 0 : computedStyle.position) !== "fixed") { return true; } // For our purposes of looking for focusable elements, visibility:hidden has the same // effect as display:none. if ((computedStyle === null || computedStyle === void 0 ? void 0 : computedStyle.visibility) === "hidden") { return true; } // if an element has display: fixed, we need to check if it is also hidden with CSS, // or within a parent hidden with CSS if ((computedStyle === null || computedStyle === void 0 ? void 0 : computedStyle.position) === "fixed") { if (computedStyle.display === "none") { return true; } if (((_b = element.parentElement) === null || _b === void 0 ? void 0 : _b.offsetParent) === null && elementDocument.body !== element.parentElement) { return true; } } return false; } function isRadio(element) { return element.tagName === "INPUT" && !!element.name && element.type === "radio"; } function getRadioButtonGroup(element) { if (!isRadio(element)) { return; } const name = element.name; let radioButtons = Array.from(dom.getElementsByName(element, name)); let checked; radioButtons = radioButtons.filter(el => { if (isRadio(el)) { if (el.checked) { checked = el; } return true; } return false; }); return { name, buttons: new Set(radioButtons), checked }; } /** * If the passed element is Tabster dummy input, returns the container element this dummy input belongs to. * @param element Element to check for being dummy input. * @returns Dummy input container element (if the passed element is a dummy input) or null. */ function getDummyInputContainer(element) { var _a; return ((_a = element === null || element === void 0 ? void 0 : element.__tabsterDummyContainer) === null || _a === void 0 ? void 0 : _a.get()) || null; } /*! * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ function getTabsterAttribute(props, plain) { const attr = JSON.stringify(props); if (plain === true) { return attr; } return { [TABSTER_ATTRIBUTE_NAME]: attr }; } /** * Updates Tabster props object with new props. * @param element an element to set data-tabster attribute on. * @param props current Tabster props to update. * @param newProps new Tabster props to add. * When the value of a property in newProps is undefined, the property * will be removed from the attribute. */ function mergeTabsterProps(props, newProps) { for (const key of Object.keys(newProps)) { const value = newProps[key]; if (value) { // eslint-disable-next-line @typescript-eslint/no-explicit-any props[key] = value; } else { delete props[key]; } } } /** * Sets or updates Tabster attribute of the element. * @param element an element to set data-tabster attribute on. * @param newProps new Tabster props to set. * @param update if true, newProps will be merged with the existing props. * When true and the value of a property in newProps is undefined, the property * will be removed from the attribute. */ function setTabsterAttribute(element, newProps, update) { let props; if (update) { const attr = element.getAttribute(TABSTER_ATTRIBUTE_NAME); if (attr) { try { props = JSON.parse(attr); } catch (e) { if (process.env.NODE_ENV === 'development') { console.error(`data-tabster attribute error: ${e}`, element); } } } } if (!props) { props = {}; } mergeTabsterProps(props, newProps); if (Object.keys(props).length > 0) { element.setAttribute(TABSTER_ATTRIBUTE_NAME, getTabsterAttribute(props, true)); } else { element.removeAttribute(TABSTER_ATTRIBUTE_NAME); } } /*! * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ function _setInformativeStyle$3(weakElement, remove, id) { if (process.env.NODE_ENV === 'development') { const element = weakElement.get(); if (element) { if (remove) { element.style.removeProperty("--tabster-root"); } else { element.style.setProperty("--tabster-root", id + ","); } } } } class RootDummyManager extends DummyInputManager { constructor(tabster, element, setFocused, sys) { super(tabster, element, DummyInputManagerPriorities.Root, sys, undefined, true); this._onDummyInputFocus = dummyInput => { var _a; if (dummyInput.useDefaultAction) { // When we've reached the last focusable element, we want to let the browser // to move the focus outside of the page. In order to do that we're synchronously // calling focus() of the dummy input from the Tab key handler and allowing // the default action to move the focus out. this._setFocused(false); } else { // The only way a dummy input gets focused is during the keyboard navigation. this._tabster.keyboardNavigation.setNavigatingWithKeyboard(true); const element = this._element.get(); if (element) { this._setFocused(true); const toFocus = this._tabster.focusedElement.getFirstOrLastTabbable(dummyInput.isFirst, { container: element, ignoreAccessibility: true }); if (toFocus) { nativeFocus(toFocus); return; } } (_a = dummyInput.input) === null || _a === void 0 ? void 0 : _a.blur(); } }; this._setHandlers(this._onDummyInputFocus); this._tabster = tabster; this._setFocused = setFocused; } } class Root extends TabsterPart { constructor(tabster, element, onDispose, props, sys) { super(tabster, element, props); this._isFocused = false; this._setFocused = hasFocused => { var _a; if (this._setFocusedTimer) { this._tabster.getWindow().clearTimeout(this._setFocusedTimer); delete this._setFocusedTimer; } if (this._isFocused === hasFocused) { return; } const element = this._element.get(); if (element) { if (hasFocused) { this._isFocused = true; (_a = this._dummyManager) === null || _a === void 0 ? void 0 : _a.setTabbable(false); element.dispatchEvent(new RootFocusEvent({ element })); } else { this._setFocusedTimer = this._tabster.getWindow().setTimeout(() => { var _a; delete this._setFocusedTimer; this._isFocused = false; (_a = this._dummyManager) === null || _a === void 0 ? void 0 : _a.setTabbable(true); element.dispatchEvent(new RootBlurEvent({ element })); }, 0); } } }; this._onFocusIn = event => { const getParent = this._tabster.getParent; const rootElement = this._element.get(); let curElement = event.composedPath()[0]; do { if (curElement === rootElement) { this._setFocused(true); return; } curElement = curElement && getParent(curElement); } while (curElement); }; this._onFocusOut = () => { this._setFocused(false); }; this._onDispose = onDispose; const win = tabster.getWindow; this.uid = getElementUId(win, element); this._sys = sys; if (tabster.controlTab || tabster.rootDummyInputs) { this.addDummyInputs(); } const w = win(); const doc = w.document; doc.addEventListener(KEYBORG_FOCUSIN, this._onFocusIn); doc.addEventListener(KEYBORG_FOCUSOUT, this._onFocusOut); this._add(); } addDummyInputs() { if (!this._dummyManager) { this._dummyManager = new RootDummyManager(this._tabster, this._element, this._setFocused, this._sys); } } dispose() { var _a; this._onDispose(this); const win = this._tabster.getWindow(); const doc = win.document; doc.removeEventListener(KEYBORG_FOCUSIN, this._onFocusIn); doc.removeEventListener(KEYBORG_FOCUSOUT, this._onFocusOut); if (this._setFocusedTimer) { win.clearTimeout(this._setFocusedTimer); delete this._setFocusedTimer; } (_a = this._dummyManager) === null || _a === void 0 ? void 0 : _a.dispose(); this._remove(); } moveOutWithDefaultAction(isBackward, relatedEvent) { const dummyManager = this._dummyManager; if (dummyManager) { dummyManager.moveOutWithDefaultAction(isBackward, relatedEvent); } else { const el = this.getElement(); if (el) { RootDummyManager.moveWithPhantomDummy(this._tabster, el, true, isBackward, relatedEvent); } } } _add() { if (process.env.NODE_ENV === 'development') { _setInformativeStyle$3(this._element, false, this.uid); } } _remove() { if (process.env.NODE_ENV === 'development') { _setInformativeStyle$3(this._element, true); } } } // eslint-disable-next-line @typescript-eslint/no-unused-vars class RootAPI { constructor(tabster, autoRoot) { this._autoRootWaiting = false; this._roots = {}; this._forceDummy = false; this.rootById = {}; this._autoRootCreate = () => { var _a; const doc = this._win().document; const body = doc.body; if (body) { this._autoRootUnwait(doc); const props = this._autoRoot; if (props) { setTabsterAttribute(body, { root: props }, true); updateTabsterByAttribute(this._tabster, body); return (_a = getTabsterOnElement(this._tabster, body)) === null || _a === void 0 ? void 0 : _a.root; } } else if (!this._autoRootWaiting) { this._autoRootWaiting = true; doc.addEventListener("readystatechange", this._autoRootCreate); } return undefined; }; this._onRootDispose = root => { delete this._roots[root.id]; }; this._tabster = tabster; this._win = tabster.getWindow; this._autoRoot = autoRoot; tabster.queueInit(() => { if (this._autoRoot) { this._autoRootCreate(); } }); } _autoRootUnwait(doc) { doc.removeEventListener("readystatechange", this._autoRootCreate); this._autoRootWaiting = false; } dispose() { const win = this._win(); this._autoRootUnwait(win.document); delete this._autoRoot; Object.keys(this._roots).forEach(rootId => { if (this._roots[rootId]) { this._roots[rootId].dispose(); delete this._roots[rootId]; } }); this.rootById = {}; } createRoot(element, props, sys) { if (process.env.NODE_ENV === 'development') ; const newRoot = new Root(this._tabster, element, this._onRootDispose, props, sys); this._roots[newRoot.id] = newRoot; if (this._forceDummy) { newRoot.addDummyInputs(); } return newRoot; } addDummyInputs() { this._forceDummy = true; const roots = this._roots; for (const id of Object.keys(roots)) { roots[id].addDummyInputs(); } } static getRootByUId(getWindow, id) { const tabster = getWindow().__tabsterInstance; return tabster && tabster.root.rootById[id]; } /** * Fetches the tabster context for an element walking up its ancestors * * @param tabster Tabster instance * @param element The element the tabster context should represent * @param options Additional options * @returns undefined if the element is not a child of a tabster root, otherwise all applicable tabster behaviours and configurations */ static getTabsterContext(tabster, element, options) { if (options === void 0) { options = {}; } var _a, _b, _c, _d; if (!element.ownerDocument) { return undefined; } const { checkRtl, referenceElement } = options; const getParent = tabster.getParent; // Normally, the initialization starts on the next tick after the tabster // instance creation. However, if the application starts using it before // the next tick, we need to make sure the initialization is done. tabster.drainInitQueue(); let root; let modalizer; let groupper; let mover; let excludedFromMover = false; let groupperBeforeMover; let modalizerInGroupper; let dirRightToLeft; let uncontrolled; let curElement = referenceElement || element; const ignoreKeydown = {}; while (curElement && (!root || checkRtl)) { const tabsterOnElement = getTabsterOnElement(tabster, curElement); if (checkRtl && dirRightToLeft === undefined) { const dir = curElement.dir; if (dir) { dirRightToLeft = dir.toLowerCase() === "rtl"; } } if (!tabsterOnElement) { curElement = getParent(curElement); continue; } const tagName = curElement.tagName; if (tabsterOnElement.uncontrolled || tagName === "IFRAME" || tagName === "WEBVIEW") { uncontrolled = curElement; } if (!mover && ((_a = tabsterOnElement.focusable) === null || _a === void 0 ? void 0 : _a.excludeFromMover) && !groupper) { excludedFromMover = true; } const curModalizer = tabsterOnElement.modalizer; const curGroupper = tabsterOnElement.groupper; const curMover = tabsterOnElement.mover; if (!modalizer && curModalizer) { modalizer = curModalizer; } if (!groupper && curGroupper && (!modalizer || curModalizer)) { if (modalizer) { // Modalizer dominates the groupper when they are on the same node and the groupper is active. if (!curGroupper.isActive() && curGroupper.getProps().tabbability && modalizer.userId !== ((_b = tabster.modalizer) === null || _b === void 0 ? void 0 : _b.activeId)) { modalizer = undefined; groupper = curGroupper; } modalizerInGroupper = curGroupper; } else { groupper = curGroupper; } } if (!mover && curMover && (!modalizer || curModalizer) && (!curGroupper || curElement !== element) && curElement.contains(element) // Mover makes sense only for really inside elements, not for virutal out of the DOM order children. ) { mover = curMover; groupperBeforeMover = !!groupper && groupper !== curGroupper; } if (tabsterOnElement.root) { root = tabsterOnElement.root; } if ((_c = tabsterOnElement.focusable) === null || _c === void 0 ? void 0 : _c.ignoreKeydown) { Object.assign(ignoreKeydown, tabsterOnElement.focusable.ignoreKeydown); } curElement = getParent(curElement); } // No root element could be found, try to get an auto root if (!root) { const rootAPI = tabster.root; const autoRoot = rootAPI._autoRoot; if (autoRoot) { if ((_d = element.ownerDocument) === null || _d === void 0 ? void 0 : _d.body) { root = rootAPI._autoRootCreate(); } } } if (groupper && !mover) { groupperBeforeMover = true; } if (process.env.NODE_ENV === 'development' && !root) { if (modalizer || groupper || mover) { console.error("Tabster Root is required for Mover, Groupper and Modalizer to work."); } } const shouldIgnoreKeydown = event => !!ignoreKeydown[event.key]; return root ? { root, modalizer, groupper, mover, groupperBeforeMover, modalizerInGroupper, rtl: checkRtl ? !!dirRightToLeft : undefined, uncontrolled, excludedFromMover, ignoreKeydown: shouldIgnoreKeydown } : undefined; } static getRoot(tabster, element) { var _a; const getParent = tabster.getParent; for (let el = element; el; el = getParent(el)) { const root = (_a = getTabsterOnElement(tabster, el)) === null || _a === void 0 ? void 0 : _a.root; if (root) { return root; } } return undefined; } onRoot(root, removed) { if (removed) { delete this.rootById[root.uid]; } else { this.rootById[root.uid] = root; } } } /*! * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ const _containerHistoryLength = 10; class DeloserItemBase {} class DeloserItem extends DeloserItemBase { constructor(tabster, deloser) { super(); this.uid = deloser.uid; this._tabster = tabster; this._deloser = deloser; } belongsTo(deloser) { return deloser === this._deloser; } unshift(element) { this._deloser.unshift(element); } async focusAvailable() { const available = this._deloser.findAvailable(); const deloserElement = this._deloser.getElement(); if (available && deloserElement) { if (!deloserElement.dispatchEvent(new TabsterMoveFocusEvent({ by: "deloser", owner: deloserElement, next: available }))) { // Default action is prevented, don't look further. return null; } return this._tabster.focusedElement.focus(available); } return false; } async resetFocus() { const getWindow = this._tabster.getWindow; return getPromise(getWindow).resolve(this._deloser.resetFocus()); } } class DeloserHistoryByRootBase { constructor(tabster, rootUId) { this._history = []; this._tabster = tabster; this.rootUId = rootUId; } getLength() { return this._history.length; } removeDeloser(deloser) { this._history = this._history.filter(c => !c.belongsTo(deloser)); } hasDeloser(deloser) { return this._history.some(d => d.belongsTo(deloser)); } } class DeloserHistoryByRoot extends DeloserHistoryByRootBase { unshiftToDeloser(deloser, element) { let item; for (let i = 0; i < this._history.length; i++) { if (this._history[i].belongsTo(deloser)) { item = this._history[i]; this._history.splice(i, 1); break; } } if (!item) { item = new DeloserItem(this._tabster, deloser); } item.unshift(element); this._history.unshift(item); this._history.splice(_containerHistoryLength, this._history.length - _containerHistoryLength); } async focusAvailable(from) { let skip = !!from; for (const i of this._history) { if (from && i.belongsTo(from)) { skip = false; } if (!skip) { const result = await i.focusAvailable(); // Result is null when the default action is prevented by the application // and we don't need to look further. if (result || result === null) { return result; } } } return false; } async resetFocus(from) { let skip = !!from; const resetQueue = {}; for (const i of this._history) { if (from && i.belongsTo(from)) { skip = false; } if (!skip && !resetQueue[i.uid]) { resetQueue[i.uid] = i; } } // Nothing is found, at least try to reset. for (const id of Object.keys(resetQueue)) { if (await resetQueue[id].resetFocus()) { return true; } } return false; } } class DeloserHistory { constructor(tabster) { // eslint-disable-next-line @typescript-eslint/ban-types this._history = []; this._tabster = tabster; } dispose() { this._history = []; } process(element) { var _a; const ctx = RootAPI.getTabsterContext(this._tabster, element); const rootUId = ctx && ctx.root.uid; const deloser = DeloserAPI.getDeloser(this._tabster, element); if (!rootUId || !deloser) { return undefined; } const historyByRoot = this.make(rootUId, () => new DeloserHistoryByRoot(this._tabster, rootUId)); if (!ctx || !ctx.modalizer || ((_a = ctx.modalizer) === null || _a === void 0 ? void 0 : _a.isActive())) { historyByRoot.unshiftToDeloser(deloser, element); } return deloser; } make(rootUId, createInstance) { let historyByRoot; for (let i = 0; i < this._history.length; i++) { const hbr = this._history[i]; if (hbr.rootUId === rootUId) { historyByRoot = hbr; this._history.splice(i, 1); break; } } if (!historyByRoot) { historyByRoot = createInstance(); } this._history.unshift(historyByRoot); this._history.splice(_containerHistoryLength, this._history.length - _containerHistoryLength); return historyByRoot; } removeDeloser(deloser) { this._history.forEach(i => { i.removeDeloser(deloser); }); this._history = this._history.filter(i => i.getLength() > 0); } async focusAvailable(from) { let skip = !!from; for (const h of this._history) { if (from && h.hasDeloser(from)) { skip = false; } if (!skip) { const result = await h.focusAvailable(from); // Result is null when the default action is prevented by the application // and we don't need to look further. if (result || result === null) { return result; } } } return false; } async resetFocus(from) { let skip = !!from; for (const h of this._history) { if (from && h.hasDeloser(from)) { skip = false; } if (!skip && (await h.resetFocus(from))) { return true; } } return false; } } function _setInformativeStyle$2(weakElement, remove, isActive, snapshotIndex) { if (process.env.NODE_ENV === 'development') { const element = weakElement.get(); if (element) { if (remove) { element.style.removeProperty("--tabster-deloser"); } else { element.style.setProperty("--tabster-deloser", (isActive ? "active" : "inactive") + "," + ("snapshot-" + snapshotIndex)); } } } } function buildElementSelector(element, withClass, withIndex) { const selector = []; const escapeRegExp = /(:|\.|\[|\]|,|=|@)/g; const escapeReplaceValue = "\\$1"; const elementId = element.getAttribute("id"); if (elementId) { selector.push("#" + elementId.replace(escapeRegExp, escapeReplaceValue)); } if (withClass !== false && element.className) { element.className.split(" ").forEach(cls => { cls = cls.trim(); if (cls) { selector.push("." + cls.replace(escapeRegExp, escapeReplaceValue)); } }); } let index = 0; let el; if (withIndex !== false && selector.length === 0) { el = element; while (el) { index++; el = el.previousElementSibling; } selector.unshift(":nth-child(" + index + ")"); } selector.unshift(element.tagName.toLowerCase()); return selector.join(""); } function buildSelector(element) { if (!documentContains(element.ownerDocument, element)) { return undefined; } const selector = [buildElementSelector(element)]; let node = dom.getParentNode(element); while (node && node.nodeType !== Node.DOCUMENT_FRAGMENT_NODE) { // Stop at the shadow root as cross shadow selectors won't work. if (node.nodeType === Node.ELEMENT_NODE) { const isBody = node.tagName === "BODY"; selector.unshift(buildElementSelector(node, false, !isBody)); if (isBody) { break; } } node = dom.getParentNode(node); } return selector.join(" "); } class Deloser extends TabsterPart { constructor(tabster, element, onDispose, props) { super(tabster, element, props); this._isActive = false; this._history = [[]]; this._snapshotIndex = 0; this.isActive = () => { return this._isActive; }; this.setSnapshot = index => { this._snapshotIndex = index; if (this._history.length > index + 1) { this._history.splice(index + 1, this._history.length - index - 1); } if (!this._history[index]) { this._history[index] = []; } if (process.env.NODE_ENV === 'development') { _setInformativeStyle$2(this._element, false, this._isActive, this._snapshotIndex); } }; this.focusFirst = () => { const e = this._element.get(); return !!e && this._tabster.focusedElement.focusFirst({ container: e }); }; this.focusDefault = () => { const e = this._element.get(); return !!e && this._tabster.focusedElement.focusDefault(e); }; this.resetFocus = () => { const e = this._element.get(); return !!e && this._tabster.focusedElement.resetFocus(e); }; this.clearHistory = preserveExisting => { const element = this._element.get(); if (!element) { this._history[this._snapshotIndex] = []; return; } this._history[this._snapshotIndex] = this._history[this._snapshotIndex].filter(we => { const e = we.get(); return e && preserveExisting ? dom.nodeContains(element, e) : false; }); }; this.uid = getElementUId(tabster.getWindow, element); this.strategy = props.strategy || DeloserStrategies.Auto; this._onDispose = onDispose; if (process.env.NODE_ENV === 'development') { _setInformativeStyle$2(this._element, false, this._isActive, this._snapshotIndex); } } dispose() { this._remove(); this._onDispose(this); this._isActive = false; this._snapshotIndex = 0; this._props = {}; this._history = []; } setActive(active) { this._isActive = active; if (process.env.NODE_ENV === 'development') { _setInformativeStyle$2(this._element, false, this._isActive, this._snapshotIndex); } } getActions() { return { focusDefault: this.focusDefault, focusFirst: this.focusFirst, resetFocus: this.resetFocus, clearHistory: this.clearHistory, setSnapshot: this.setSnapshot, isActive: this.isActive }; } unshift(element) { let cur = this._history[this._snapshotIndex]; cur = this._history[this._snapshotIndex] = cur.filter(we => { const e = we.get(); return e && e !== element; }); cur.unshift(new WeakHTMLElement(this._tabster.getWindow, element, buildSelector(element))); while (cur.length > _containerHistoryLength) { cur.pop(); } } findAvailable() { const element = this._element.get(); if (!element || !this._tabster.focusable.isVisible(element)) { return null; } let restoreFocusOrder = this._props.restoreFocusOrder; let available = null; const ctx = RootAPI.getTabsterContext(this._tabster, element); if (!ctx) { return null; } const root = ctx.root; const rootElement = root.getElement(); if (!rootElement) { return null; } if (restoreFocusOrder === undefined) { restoreFocusOrder = root.getProps().restoreFocusOrder; } if (restoreFocusOrder === RestoreFocusOrders.RootDefault) { available = this._tabster.focusable.findDefault({ container: rootElement }); } if (!available && restoreFocusOrder === RestoreFocusOrders.RootFirst) { available = this._findFirst(rootElement); } if (available) { return available; } const availableInHistory = this._findInHistory(); if (availableInHistory && restoreFocusOrder === RestoreFocusOrders.History) { return availableInHistory; } const availableDefault = this._tabster.focusable.findDefault({ container: element }); if (availableDefault && restoreFocusOrder === RestoreFocusOrders.DeloserDefault) { return availableDefault; } const availableFirst = this._findFirst(element); if (availableFirst && restoreFocusOrder === RestoreFocusOrders.DeloserFirst) { return availableFirst; } return availableDefault || availableInHistory || availableFirst || null; } customFocusLostHandler(element) { return element.dispatchEvent(new DeloserFocusLostEvent(this.getActions())); } _findInHistory() { const cur = this._history[this._snapshotIndex].slice(0); this.clearHistory(true); for (let i = 0; i < cur.length; i++) { const we = cur[i]; const e = we.get(); const element = this._element.get(); if (e && element && dom.nodeContains(element, e)) { if (this._tabster.focusable.isFocusable(e)) { return e; } } else if (!this._props.noSelectorCheck) { // Element is not in the DOM, try to locate the node by it's // selector. This might return not exactly the right node, // but it would be easily fixable by having more detailed selectors. const selector = we.getData(); if (selector && element) { let els; try { els = dom.querySelectorAll(element.ownerDocument, selector); } catch (e) { if (process.env.NODE_ENV === 'development') { // This should never happen, unless there is some bug in buildElementSelector(). console.error(`Failed to querySelectorAll('${selector}')`); } continue; } for (let i = 0; i < els.length; i++) { const el = els[i]; if (el && this._tabster.focusable.isFocusable(el)) { return el; } } } } } return null; } _findFirst(element) { if (this._tabster.keyboardNavigation.isNavigatingWithKeyboard()) { const first = this._tabster.focusable.findFirst({ container: element, useActiveModalizer: true }); if (first) { return first; } } return null; } _remove() { if (process.env.NODE_ENV === 'development') { _setInformativeStyle$2(this._element, true); } } } // eslint-disable-next-line @typescript-eslint/no-unused-vars class DeloserAPI { constructor(tabster, props) { /** * Tracks if focus is inside a deloser */ this._inDeloser = false; this._isRestoringFocus = false; this._isPaused = false; this._onRestoreFocus = event => { var _a; const target = event.composedPath()[0]; if (target) { const available = (_a = DeloserAPI.getDeloser(this._tabster, target)) === null || _a === void 0 ? void 0 : _a.findAvailable(); if (available) { this._tabster.focusedElement.focus(available); } event.stopImmediatePropagation(); } }; this._onFocus = e => { if (this._restoreFocusTimer) { this._win().clearTimeout(this._restoreFocusTimer); this._restoreFocusTimer = undefined; } if (!e) { this._scheduleRestoreFocus(); return; } const deloser = this._history.process(e); if (deloser) { this._activate(deloser); } else { this._deactivate(); } }; this._onDeloserDispose = deloser => { this._history.removeDeloser(deloser); if (deloser.isActive()) { this._scheduleRestoreFocus(); } }; this._tabster = tabster; this._win = tabster.getWindow; this._history = new DeloserHistory(tabster); tabster.queueInit(() => { this._tabster.focusedElement.subscribe(this._onFocus); const doc = this._win().document; doc.addEventListener(DeloserRestoreFocusEventName, this._onRestoreFocus); const activeElement = dom.getActiveElement(doc); if (activeElement && activeElement !== doc.body) { // Adding currently focused element to the deloser history. this._onFocus(activeElement); } }); const autoDeloser = props === null || props === void 0 ? void 0 : props.autoDeloser; if (autoDeloser) { this._autoDeloser = autoDeloser; } } dispose() { const win = this._win(); if (this._restoreFocusTimer) { win.clearTimeout(this._restoreFocusTimer); this._restoreFocusTimer = undefined; } if (this._autoDeloserInstance) { this._autoDeloserInstance.dispose(); delete this._autoDeloserInstance; delete this._autoDeloser; } this._tabster.focusedElement.unsubscribe(this._onFocus); win.document.removeEventListener(DeloserRestoreFocusEventName, this._onRestoreFocus); this._history.dispose(); delete this._curDeloser; } createDeloser(element, props) { var _a; if (process.env.NODE_ENV === 'development') ; const deloser = new Deloser(this._tabster, element, this._onDeloserDispose, props); if (dom.nodeContains(element, (_a = this._tabster.focusedElement.getFocusedElement()) !== null && _a !== void 0 ? _a : null)) { this._activate(deloser); } return deloser; } getActions(element) { for (let e = element; e; e = dom.getParentElement(e)) { const tabsterOnElement = getTabsterOnElement(this._tabster, e); if (tabsterOnElement && tabsterOnElement.deloser) { return tabsterOnElement.deloser.getActions(); } } return undefined; } pause() { this._isPaused = true; if (this._restoreFocusTimer) { this._win().clearTimeout(this._restoreFocusTimer); this._restoreFocusTimer = undefined; } } resume(restore) { this._isPaused = false; if (restore) { this._scheduleRestoreFocus(); } } /** * Activates and sets the current deloser */ _activate(deloser) { const curDeloser = this._curDeloser; if (curDeloser !== deloser) { this._inDeloser = true; curDeloser === null || curDeloser === void 0 ? void 0 : curDeloser.setActive(false); deloser.setActive(true); this._curDeloser = deloser; } } /** * Called when focus should no longer be in a deloser */ _deactivate() { var _a; this._inDeloser = false; (_a = this._curDeloser) === null || _a === void 0 ? void 0 : _a.setActive(false); this._curDeloser = undefined; } _scheduleRestoreFocus(force) { if (this._isPaused || this._isRestoringFocus) { return; } const restoreFocus = async () => { this._restoreFocusTimer = undefined; const lastFocused = this._tabster.focusedElement.getLastFocusedElement(); if (!force && (this._isRestoringFocus || !this._inDeloser || lastFocused && !isDisplayNone(lastFocused))) { return; } const curDeloser = this._curDeloser; let isManual = false; if (curDeloser) { if (lastFocused && curDeloser.customFocusLostHandler(lastFocused)) { return; } if (curDeloser.strategy === DeloserStrategies.Manual) { isManual = true; } else { const curDeloserElement = curDeloser.getElement(); const el = curDeloser.findAvailable(); if (el && (!(curDeloserElement === null || curDeloserElement === void 0 ? void 0 : curDeloserElement.dispatchEvent(new TabsterMoveFocusEvent({ by: "deloser", owner: curDeloserElement, next: el }))) || this._tabster.focusedElement.focus(el))) { return; } } } this._deactivate(); if (isManual) { return; } this._isRestoringFocus = true; // focusAvailable returns null when the default action is prevented by the application, false // when nothing was focused and true when something was focused. if ((await this._history.focusAvailable(null)) === false) { await this._history.resetFocus(null); } this._isRestoringFocus = false; }; if (force) { restoreFocus(); } else { this._restoreFocusTimer = this._win().setTimeout(restoreFocus, 100); } } static getDeloser(tabster, element) { var _a; let root; for (let e = element; e; e = dom.getParentElement(e)) { const tabsterOnElement = getTabsterOnElement(tabster, e); if (tabsterOnElement) { if (!root) { root = tabsterOnElement.root; } const deloser = tabsterOnElement.deloser; if (deloser) { return deloser; } } } const deloserAPI = tabster.deloser && tabster.deloser; if (deloserAPI) { if (deloserAPI._autoDeloserInstance) { return deloserAPI._autoDeloserInstance; } const autoDeloserProps = deloserAPI._autoDeloser; if (root && !deloserAPI._autoDeloserInstance && autoDeloserProps) { const body = (_a = element.ownerDocument) === null || _a === void 0 ? void 0 : _a.body; if (body) { deloserAPI._autoDeloserInstance = new Deloser(tabster, body, tabster.deloser._onDeloserDispose, autoDeloserProps); } } return deloserAPI._autoDeloserInstance; } return undefined; } static getHistory(instance) { return instance._history; } static forceRestoreFocus(instance) { instance._scheduleRestoreFocus(true); } } /*! * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ class Subscribable { constructor() { this._callbacks = []; } dispose() { this._callbacks = []; delete this._val; } subscribe(callback) { const callbacks = this._callbacks; const index = callbacks.indexOf(callback); if (index < 0) { callbacks.push(callback); } } subscribeFirst(callback) { const callbacks = this._callbacks; const index = callbacks.indexOf(callback); if (index >= 0) { callbacks.splice(index, 1); } callbacks.unshift(callback); } unsubscribe(callback) { const index = this._callbacks.indexOf(callback); if (index >= 0) { this._callbacks.splice(index, 1); } } setVal(val, detail) { if (this._val === val) { return; } this._val = val; this._callCallbacks(val, detail); } getVal() { return this._val; } trigger(val, detail) { this._callCallbacks(val, detail); } _callCallbacks(val, detail) { this._callbacks.forEach(callback => callback(val, detail)); } } /*! * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ const _transactionTimeout = 1500; const _pingTimeout = 3000; const _targetIdUp = "up"; const CrossOriginTransactionTypes = { Bootstrap: 1, FocusElement: 2, State: 3, GetElement: 4, RestoreFocusInDeloser: 5, Ping: 6 }; class CrossOriginDeloserItem extends DeloserItemBase { constructor(tabster, deloser, trasactions) { super(); this._deloser = deloser; this._transactions = trasactions; } belongsTo(deloser) { return deloser.deloserUId === this._deloser.deloserUId; } async focusAvailable() { const data = { ...this._deloser, reset: false }; return this._transactions.beginTransaction(RestoreFocusInDeloserTransaction, data).then(value => !!value); } async resetFocus() { const data = { ...this._deloser, reset: true }; return this._transactions.beginTransaction(RestoreFocusInDeloserTransaction, data).then(value => !!value); } } class CrossOriginDeloserHistoryByRoot extends DeloserHistoryByRootBase { constructor(tabster, rootUId, transactions) { super(tabster, rootUId); this._transactions = transactions; } unshift(deloser) { let item; for (let i = 0; i < this._history.length; i++) { if (this._history[i].belongsTo(deloser)) { item = this._history[i]; this._history.splice(i, 1); break; } } if (!item) { item = new CrossOriginDeloserItem(this._tabster, deloser, this._transactions); } this._history.unshift(item); this._history.splice(10, this._history.length - 10); } async focusAvailable() { for (const i of this._history) { if (await i.focusAvailable()) { return true; } } return false; } async resetFocus() { for (const i of this._history) { if (await i.resetFocus()) { return true; } } return false; } } class CrossOriginTransaction { constructor(tabster, getOwner, knownTargets, value, timeout, sentTo, targetId, sendUp) { this._inProgress = {}; this._isDone = false; this._isSelfResponding = false; this._sentCount = 0; this.tabster = tabster; this.owner = getOwner; this.ownerId = getWindowUId(getOwner()); this.id = getUId(getOwner()); this.beginData = value; this._knownTargets = knownTargets; this._sentTo = sentTo || { [this.ownerId]: true }; this.targetId = targetId; this.sendUp = sendUp; this.timeout = timeout; this._promise = new (getPromise(getOwner))((resolve, reject) => { this._resolve = resolve; this._reject = reject; }); } getTargets(knownTargets) { return this.targetId === _targetIdUp ? this.sendUp ? { [_targetIdUp]: { send: this.sendUp } } : null : this.targetId ? knownTargets[this.targetId] ? { [this.targetId]: { send: knownTargets[this.targetId].send } } : null : Object.keys(knownTargets).length === 0 && this.sendUp ? { [_targetIdUp]: { send: this.sendUp } } : Object.keys(knownTargets).length > 0 ? knownTargets : null; } begin(selfResponse) { const targets = this.getTargets(this._knownTargets); const sentTo = { ...this._sentTo }; if (targets) { for (const id of Object.keys(targets)) { sentTo[id] = true; } } const data = { transaction: this.id, type: this.type, isResponse: false, timestamp: Date.now(), owner: this.ownerId, sentto: sentTo, timeout: this.timeout, beginData: this.beginData }; if (this.targetId) { data.target = this.targetId; } if (selfResponse) { this._isSelfResponding = true; selfResponse(data).then(value => { this._isSelfResponding = false; if (value !== undefined) { if (!this.endData) { this.endData = value; } } if (this.endData || this._sentCount === 0) { this.end(); } }); } if (targets) { for (const id of Object.keys(targets)) { if (!(id in this._sentTo)) { this._send(targets[id].send, id, data); } } } if (this._sentCount === 0 && !this._isSelfResponding) { this.end(); } return this._promise; } _send(send, targetId, data) { if (this._inProgress[targetId] === undefined) { this._inProgress[targetId] = true; this._sentCount++; send(data); } } end(error) { if (this._isDone) { return; } this._isDone = true; if (this.endData === undefined && error) { if (this._reject) { this._reject(error); } } else if (this._resolve) { this._resolve(this.endData); } } onResponse(data) { const endData = data.endData; if (endData !== undefined && !this.endData) { this.endData = endData; } const inProgressId = data.target === _targetIdUp ? _targetIdUp : data.owner; if (this._inProgress[inProgressId]) { this._inProgress[inProgressId] = false; this._sentCount--; if (this.endData || this._sentCount === 0 && !this._isSelfResponding) { this.end(); } } } } class BootstrapTransaction extends CrossOriginTransaction { constructor() { super(...arguments); this.type = CrossOriginTransactionTypes.Bootstrap; } static shouldForward() { return false; } static async makeResponse(tabster) { return { isNavigatingWithKeyboard: tabster.keyboardNavigation.isNavigatingWithKeyboard() }; } } class FocusElementTransaction extends CrossOriginTransaction { constructor() { super(...arguments); this.type = CrossOriginTransactionTypes.FocusElement; } static shouldSelfRespond() { return true; } static shouldForward(tabster, data, getOwner) { const el = GetElementTransaction.findElement(tabster, getOwner, data.beginData); return !el || !tabster.focusable.isFocusable(el); } static async makeResponse(tabster, data, getOwner, ownerId, transactions, forwardResult) { const el = GetElementTransaction.findElement(tabster, getOwner, data.beginData); return !!el && tabster.focusedElement.focus(el, true) || !!(await forwardResult); } } const CrossOriginStates = { Focused: 1, Blurred: 2, Observed: 3, DeadWindow: 4, KeyboardNavigation: 5, Outline: 6 }; class StateTransaction extends CrossOriginTransaction { constructor() { super(...arguments); this.type = CrossOriginTransactionTypes.State; } static shouldSelfRespond(tabster, data) { return data.state !== CrossOriginStates.DeadWindow && data.state !== CrossOriginStates.KeyboardNavigation; } static async makeResponse(tabster, data, getOwner, ownerId, transactions, forwardResult, isSelfResponse) { const timestamp = data.timestamp; const beginData = data.beginData; if (timestamp && beginData) { switch (beginData.state) { case CrossOriginStates.Focused: return StateTransaction._makeFocusedResponse(tabster, timestamp, beginData, transactions, isSelfResponse); case CrossOriginStates.Blurred: return StateTransaction._makeBlurredResponse(tabster, timestamp, beginData, transactions.ctx); case CrossOriginStates.Observed: return StateTransaction._makeObservedResponse(tabster, beginData); case CrossOriginStates.DeadWindow: return StateTransaction._makeDeadWindowResponse(tabster, beginData, transactions, forwardResult); case CrossOriginStates.KeyboardNavigation: return StateTransaction._makeKeyboardNavigationResponse(tabster, transactions.ctx, beginData.isNavigatingWithKeyboard); case CrossOriginStates.Outline: return StateTransaction._makeOutlineResponse(tabster, transactions.ctx, beginData.outline); } } return true; } static createElement(tabster, beginData) { return beginData.uid ? new CrossOriginElement(tabster, beginData.uid, beginData.ownerUId, beginData.id, beginData.rootUId, beginData.observedName, beginData.observedDetails) : null; } static async _makeFocusedResponse(tabster, timestamp, beginData, transactions, isSelfResponse) { const element = StateTransaction.createElement(tabster, beginData); if (beginData && beginData.ownerUId && element) { transactions.ctx.focusOwner = beginData.ownerUId; transactions.ctx.focusOwnerTimestamp = timestamp; if (!isSelfResponse && beginData.rootUId && beginData.deloserUId) { const deloserAPI = tabster.deloser; if (deloserAPI) { const history = DeloserAPI.getHistory(deloserAPI); const deloser = { ownerUId: beginData.ownerUId, deloserUId: beginData.deloserUId, rootUId: beginData.rootUId }; const historyItem = history.make(beginData.rootUId, () => new CrossOriginDeloserHistoryByRoot(tabster, deloser.rootUId, transactions)); historyItem.unshift(deloser); } } CrossOriginFocusedElementState.setVal( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion tabster.crossOrigin.focusedElement, element, { isFocusedProgrammatically: beginData.isFocusedProgrammatically }); } return true; } static async _makeBlurredResponse(tabster, timestamp, beginData, context) { if (beginData && (beginData.ownerUId === context.focusOwner || beginData.force) && (!context.focusOwnerTimestamp || context.focusOwnerTimestamp < timestamp)) { CrossOriginFocusedElementState.setVal( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion tabster.crossOrigin.focusedElement, undefined, {}); } return true; } static async _makeObservedResponse(tabster, beginData) { const name = beginData.observedName; const element = StateTransaction.createElement(tabster, beginData); if (name && element) { CrossOriginObservedElementState.trigger( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion tabster.crossOrigin.observedElement, element, { names: [name], details: beginData.observedDetails }); } return true; } static async _makeDeadWindowResponse(tabster, beginData, transactions, forwardResult) { const deadUId = beginData && beginData.ownerUId; if (deadUId) { transactions.removeTarget(deadUId); } return forwardResult.then(() => { if (deadUId === transactions.ctx.focusOwner) { const deloserAPI = tabster.deloser; if (deloserAPI) { DeloserAPI.forceRestoreFocus(deloserAPI); } } return true; }); } static async _makeKeyboardNavigationResponse(tabster, context, isNavigatingWithKeyboard) { if (isNavigatingWithKeyboard !== undefined && tabster.keyboardNavigation.isNavigatingWithKeyboard() !== isNavigatingWithKeyboard) { context.ignoreKeyboardNavigationStateUpdate = true; tabster.keyboardNavigation.setNavigatingWithKeyboard(isNavigatingWithKeyboard); context.ignoreKeyboardNavigationStateUpdate = false; } return true; } static async _makeOutlineResponse(tabster, context, props) { if (context.origOutlineSetup) { context.origOutlineSetup.call( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion tabster.outline, props); } return true; } } class GetElementTransaction extends CrossOriginTransaction { constructor() { super(...arguments); this.type = CrossOriginTransactionTypes.GetElement; } static shouldSelfRespond() { return true; } static findElement(tabster, getOwner, data) { let element; if (data && (!data.ownerId || data.ownerId === getWindowUId(getOwner()))) { if (data.id) { element = dom.getElementById(getOwner().document, data.id); if (element && data.rootId) { const ctx = RootAPI.getTabsterContext(tabster, element); if (!ctx || ctx.root.uid !== data.rootId) { return null; } } } else if (data.uid) { const ref = getInstanceContext(getOwner).elementByUId[data.uid]; element = ref && ref.get(); } else if (data.observedName) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion element = tabster.observedElement.getElement(data.observedName, data.accessibility); } } return element || null; } static getElementData(tabster, element, getOwner, context, ownerUId) { const deloser = DeloserAPI.getDeloser(tabster, element); const ctx = RootAPI.getTabsterContext(tabster, element); const tabsterOnElement = getTabsterOnElement(tabster, element); const observed = tabsterOnElement && tabsterOnElement.observed; return { uid: getElementUId(getOwner, element), ownerUId, id: element.id || undefined, rootUId: ctx ? ctx.root.uid : undefined, deloserUId: deloser ? getDeloserUID(getOwner, context, deloser) : undefined, observedName: observed && observed.names && observed.names[0], observedDetails: observed && observed.details }; } static async makeResponse(tabster, data, getOwner, ownerUId, transactions, forwardResult) { const beginData = data.beginData; let element; let dataOut; if (beginData === undefined) { element = tabster.focusedElement.getFocusedElement(); } else if (beginData) { element = GetElementTransaction.findElement(tabster, getOwner, beginData) || undefined; } if (!element && beginData) { const name = beginData.observedName; const timeout = data.timeout; const accessibility = beginData.accessibility; if (name && timeout) { const e = await new (getPromise(getOwner))(resolve => { let isWaitElementResolved = false; let isForwardResolved = false; let isResolved = false; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion tabster.observedElement.waitElement(name, timeout, accessibility).result.then(value => { isWaitElementResolved = true; if (!isResolved && (value || isForwardResolved)) { isResolved = true; resolve({ element: value }); } }); forwardResult.then(value => { isForwardResolved = true; if (!isResolved && (value || isWaitElementResolved)) { isResolved = true; resolve({ crossOrigin: value }); } }); }); if (e.element) { element = e.element; } else if (e.crossOrigin) { dataOut = e.crossOrigin; } } } return element ? GetElementTransaction.getElementData(tabster, element, getOwner, transactions.ctx, ownerUId) : dataOut; } } class RestoreFocusInDeloserTransaction extends CrossOriginTransaction { constructor() { super(...arguments); this.type = CrossOriginTransactionTypes.RestoreFocusInDeloser; } static async makeResponse(tabster, data, getOwner, ownerId, transactions, forwardResult) { const forwardRet = await forwardResult; const begin = !forwardRet && data.beginData; const uid = begin && begin.deloserUId; const deloser = uid && transactions.ctx.deloserByUId[uid]; const deloserAPI = tabster.deloser; if (begin && deloser && deloserAPI) { const history = DeloserAPI.getHistory(deloserAPI); return begin.reset ? history.resetFocus(deloser) : history.focusAvailable(deloser); } return !!forwardRet; } } class PingTransaction extends CrossOriginTransaction { constructor() { super(...arguments); this.type = CrossOriginTransactionTypes.Ping; } static shouldForward() { return false; } static async makeResponse() { return true; } } class CrossOriginTransactions { constructor(tabster, getOwner, context) { this._knownTargets = {}; this._transactions = {}; this._isDefaultSendUp = false; this.isSetUp = false; this._onMessage = e => { if (e.data.owner === this._ownerUId || !this._tabster) { return; } // eslint-disable-next-line @typescript-eslint/no-explicit-any const data = e.data; let transactionId; if (!data || !(transactionId = data.transaction) || !data.type || !data.timestamp || !data.owner || !data.sentto) { return; } let knownTarget = this._knownTargets[data.owner]; if (!knownTarget && e.send && data.owner !== this._ownerUId) { knownTarget = this._knownTargets[data.owner] = { send: e.send }; } if (knownTarget) { knownTarget.last = Date.now(); } if (data.isResponse) { const t = this._transactions[transactionId]; if (t && t.transaction && t.transaction.type === data.type) { t.transaction.onResponse(data); } } else { const Transaction = this._getTransactionClass(data.type); const forwardResult = this.forwardTransaction(data); if (Transaction && e.send) { Transaction.makeResponse(this._tabster, data, this._owner, this._ownerUId, this, forwardResult, false).then(r => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const response = { transaction: data.transaction, type: data.type, isResponse: true, timestamp: Date.now(), owner: this._ownerUId, timeout: data.timeout, sentto: {}, target: data.target === _targetIdUp ? _targetIdUp : data.owner, endData: r }; e.send(response); }); } } }; this._onPageHide = () => { this._dead(); }; this._onBrowserMessage = e => { if (e.source === this._owner()) { return; } // eslint-disable-next-line @typescript-eslint/no-explicit-any const send = data => { if (e.source && e.source.postMessage) { // eslint-disable-next-line @typescript-eslint/ban-types e.source.postMessage(JSON.stringify(data), "*"); } }; try { this._onMessage({ data: JSON.parse(e.data), send }); } catch (e) { /* Ignore */ } }; this._tabster = tabster; this._owner = getOwner; this._ownerUId = getWindowUId(getOwner()); this.ctx = context; } setup(sendUp) { if (this.isSetUp) { if (process.env.NODE_ENV === 'development') { console.error("CrossOrigin is already set up."); } } else { this.isSetUp = true; this.setSendUp(sendUp); this._owner().addEventListener("pagehide", this._onPageHide); this._ping(); } return this._onMessage; } setSendUp(sendUp) { if (!this.isSetUp) { throw new Error("CrossOrigin is not set up."); } this.sendUp = sendUp || undefined; const owner = this._owner(); if (sendUp === undefined) { if (!this._isDefaultSendUp) { if (owner.document) { this._isDefaultSendUp = true; if (owner.parent && owner.parent !== owner && owner.parent.postMessage) { this.sendUp = ( // eslint-disable-next-line @typescript-eslint/no-explicit-any data) => { owner.parent.postMessage(JSON.stringify(data), "*"); }; } owner.addEventListener("message", this._onBrowserMessage); } } } else if (this._isDefaultSendUp) { owner.removeEventListener("message", this._onBrowserMessage); this._isDefaultSendUp = false; } return this._onMessage; } async dispose() { const owner = this._owner(); if (this._pingTimer) { owner.clearTimeout(this._pingTimer); this._pingTimer = undefined; } owner.removeEventListener("message", this._onBrowserMessage); owner.removeEventListener("pagehide", this._onPageHide); await this._dead(); delete this._deadPromise; for (const id of Object.keys(this._transactions)) { const t = this._transactions[id]; if (t.timer) { owner.clearTimeout(t.timer); delete t.timer; } t.transaction.end(); } this._knownTargets = {}; delete this.sendUp; } beginTransaction(Transaction, value, timeout, sentTo, targetId, withReject) { if (!this._owner) { return getPromise(this._owner).reject(); } const transaction = new Transaction(this._tabster, this._owner, this._knownTargets, value, timeout, sentTo, targetId, this.sendUp); let selfResponse; if (Transaction.shouldSelfRespond && Transaction.shouldSelfRespond(this._tabster, value, this._owner, this._ownerUId)) { selfResponse = data => { return Transaction.makeResponse(this._tabster, data, this._owner, this._ownerUId, this, getPromise(this._owner).resolve(undefined), true); }; } return this._beginTransaction(transaction, timeout, selfResponse, withReject); } removeTarget(uid) { delete this._knownTargets[uid]; } _beginTransaction(transaction, timeout, selfResponse, withReject) { const owner = this._owner(); const wrapper = { transaction, timer: owner.setTimeout(() => { delete wrapper.timer; transaction.end("Cross origin transaction timed out."); }, _transactionTimeout + (timeout || 0)) }; this._transactions[transaction.id] = wrapper; const ret = transaction.begin(selfResponse); ret.catch(() => { /**/ }).finally(() => { if (wrapper.timer) { owner.clearTimeout(wrapper.timer); } delete this._transactions[transaction.id]; }); return ret.then(value => value, withReject ? undefined : () => undefined); } forwardTransaction( // eslint-disable-next-line @typescript-eslint/no-explicit-any data // eslint-disable-next-line @typescript-eslint/no-explicit-any ) { const owner = this._owner; let targetId = data.target; if (targetId === this._ownerUId) { return getPromise(owner).resolve(); } const Transaction = this._getTransactionClass(data.type); if (Transaction) { if (Transaction.shouldForward === undefined || Transaction.shouldForward(this._tabster, data, owner, this._ownerUId)) { const sentTo = data.sentto; if (targetId === _targetIdUp) { targetId = undefined; sentTo[this._ownerUId] = true; } delete sentTo[_targetIdUp]; return this._beginTransaction(new Transaction(this._tabster, owner, this._knownTargets, data.beginData, data.timeout, sentTo, targetId, this.sendUp), data.timeout); } else { return getPromise(owner).resolve(); } } return getPromise(owner).reject(`Unknown transaction type ${data.type}`); } _getTransactionClass(type // eslint-disable-next-line @typescript-eslint/no-explicit-any ) { switch (type) { case CrossOriginTransactionTypes.Bootstrap: return BootstrapTransaction; case CrossOriginTransactionTypes.FocusElement: return FocusElementTransaction; case CrossOriginTransactionTypes.State: return StateTransaction; case CrossOriginTransactionTypes.GetElement: return GetElementTransaction; case CrossOriginTransactionTypes.RestoreFocusInDeloser: return RestoreFocusInDeloserTransaction; case CrossOriginTransactionTypes.Ping: return PingTransaction; default: return null; } } async _dead() { if (!this._deadPromise && this.ctx.focusOwner === this._ownerUId) { this._deadPromise = this.beginTransaction(StateTransaction, { ownerUId: this._ownerUId, state: CrossOriginStates.DeadWindow }); } if (this._deadPromise) { await this._deadPromise; } } async _ping() { if (this._pingTimer) { return; } let deadWindows; const now = Date.now(); const targets = Object.keys(this._knownTargets).filter(uid => now - (this._knownTargets[uid].last || 0) > _pingTimeout); if (this.sendUp) { targets.push(_targetIdUp); } if (targets.length) { await getPromise(this._owner).all(targets.map(uid => this.beginTransaction(PingTransaction, undefined, undefined, undefined, uid, true).then(() => true, () => { if (uid !== _targetIdUp) { if (!deadWindows) { deadWindows = {}; } deadWindows[uid] = true; delete this._knownTargets[uid]; } return false; }))); } if (deadWindows) { const focused = await this.beginTransaction(GetElementTransaction, undefined); if (!focused && this.ctx.focusOwner && this.ctx.focusOwner in deadWindows) { await this.beginTransaction(StateTransaction, { ownerUId: this._ownerUId, state: CrossOriginStates.Blurred, force: true }); const deloserAPI = this._tabster.deloser; if (deloserAPI) { DeloserAPI.forceRestoreFocus(deloserAPI); } } } this._pingTimer = this._owner().setTimeout(() => { this._pingTimer = undefined; this._ping(); }, _pingTimeout); } } class CrossOriginElement { constructor(tabster, uid, ownerId, id, rootId, observedName, observedDetails) { this._tabster = tabster; this.uid = uid; this.ownerId = ownerId; this.id = id; this.rootId = rootId; this.observedName = observedName; this.observedDetails = observedDetails; } focus(noFocusedProgrammaticallyFlag, noAccessibleCheck) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return this._tabster.crossOrigin.focusedElement.focus(this, noFocusedProgrammaticallyFlag, noAccessibleCheck); } } class CrossOriginFocusedElementState extends Subscribable { constructor(transactions) { super(); this._transactions = transactions; } async focus(element, noFocusedProgrammaticallyFlag, noAccessibleCheck) { return this._focus({ uid: element.uid, id: element.id, rootId: element.rootId, ownerId: element.ownerId, observedName: element.observedName }, noFocusedProgrammaticallyFlag, noAccessibleCheck); } async focusById(elementId, rootId, noFocusedProgrammaticallyFlag, noAccessibleCheck) { return this._focus({ id: elementId, rootId }, noFocusedProgrammaticallyFlag, noAccessibleCheck); } async focusByObservedName(observedName, timeout, rootId, noFocusedProgrammaticallyFlag, noAccessibleCheck) { return this._focus({ observedName, rootId }, noFocusedProgrammaticallyFlag, noAccessibleCheck, timeout); } async _focus(elementData, noFocusedProgrammaticallyFlag, noAccessibleCheck, timeout) { return this._transactions.beginTransaction(FocusElementTransaction, { ...elementData, noFocusedProgrammaticallyFlag, noAccessibleCheck }, timeout).then(value => !!value); } static setVal(instance, val, detail) { instance.setVal(val, detail); } } class CrossOriginObservedElementState extends Subscribable { constructor(tabster, transactions) { super(); this._lastRequestFocusId = 0; this._tabster = tabster; this._transactions = transactions; } async getElement(observedName, accessibility) { return this.waitElement(observedName, 0, accessibility); } async waitElement(observedName, timeout, accessibility) { return this._transactions.beginTransaction(GetElementTransaction, { observedName, accessibility }, timeout).then(value => value ? StateTransaction.createElement(this._tabster, value) : null); } async requestFocus(observedName, timeout) { const requestId = ++this._lastRequestFocusId; return this.waitElement(observedName, timeout, ObservedElementAccessibilities.Focusable).then(element => this._lastRequestFocusId === requestId && element ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this._tabster.crossOrigin.focusedElement.focus(element, true) : false); } static trigger(instance, element, details) { instance.trigger(element, details); } } class CrossOriginAPI { constructor(tabster) { this._init = () => { const tabster = this._tabster; tabster.keyboardNavigation.subscribe(this._onKeyboardNavigationStateChanged); tabster.focusedElement.subscribe(this._onFocus); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion tabster.observedElement.subscribe(this._onObserved); if (!this._ctx.origOutlineSetup) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this._ctx.origOutlineSetup = tabster.outline.setup; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion tabster.outline.setup = this._outlineSetup; } this._transactions.beginTransaction(BootstrapTransaction, undefined, undefined, undefined, _targetIdUp).then(data => { if (data && this._tabster.keyboardNavigation.isNavigatingWithKeyboard() !== data.isNavigatingWithKeyboard) { this._ctx.ignoreKeyboardNavigationStateUpdate = true; this._tabster.keyboardNavigation.setNavigatingWithKeyboard(data.isNavigatingWithKeyboard); this._ctx.ignoreKeyboardNavigationStateUpdate = false; } }); }; this._onKeyboardNavigationStateChanged = value => { if (!this._ctx.ignoreKeyboardNavigationStateUpdate) { this._transactions.beginTransaction(StateTransaction, { state: CrossOriginStates.KeyboardNavigation, ownerUId: getWindowUId(this._win()), isNavigatingWithKeyboard: value }); } }; this._onFocus = element => { const win = this._win(); const ownerUId = getWindowUId(win); if (this._blurTimer) { win.clearTimeout(this._blurTimer); this._blurTimer = undefined; } if (element) { this._transactions.beginTransaction(StateTransaction, { ...GetElementTransaction.getElementData(this._tabster, element, this._win, this._ctx, ownerUId), state: CrossOriginStates.Focused }); } else { this._blurTimer = win.setTimeout(() => { this._blurTimer = undefined; if (this._ctx.focusOwner && this._ctx.focusOwner === ownerUId) { this._transactions.beginTransaction(GetElementTransaction, undefined).then(value => { if (!value && this._ctx.focusOwner === ownerUId) { this._transactions.beginTransaction(StateTransaction, { ownerUId, state: CrossOriginStates.Blurred, force: false }); } }); } }, 0); } }; this._onObserved = (element, details) => { var _a; const d = GetElementTransaction.getElementData(this._tabster, element, this._win, this._ctx, getWindowUId(this._win())); d.state = CrossOriginStates.Observed; d.observedName = (_a = details.names) === null || _a === void 0 ? void 0 : _a[0]; d.observedDetails = details.details; this._transactions.beginTransaction(StateTransaction, d); }; this._outlineSetup = props => { this._transactions.beginTransaction(StateTransaction, { state: CrossOriginStates.Outline, ownerUId: getWindowUId(this._win()), outline: props }); }; this._tabster = tabster; this._win = tabster.getWindow; this._ctx = { ignoreKeyboardNavigationStateUpdate: false, deloserByUId: {} }; this._transactions = new CrossOriginTransactions(tabster, this._win, this._ctx); this.focusedElement = new CrossOriginFocusedElementState(this._transactions); this.observedElement = new CrossOriginObservedElementState(tabster, this._transactions); } setup(sendUp) { if (this.isSetUp()) { return this._transactions.setSendUp(sendUp); } else { this._tabster.queueInit(this._init); return this._transactions.setup(sendUp); } } isSetUp() { return this._transactions.isSetUp; } dispose() { var _a; const tabster = this._tabster; tabster.keyboardNavigation.unsubscribe(this._onKeyboardNavigationStateChanged); tabster.focusedElement.unsubscribe(this._onFocus); (_a = tabster.observedElement) === null || _a === void 0 ? void 0 : _a.unsubscribe(this._onObserved); this._transactions.dispose(); this.focusedElement.dispose(); this.observedElement.dispose(); this._ctx.deloserByUId = {}; } } function getDeloserUID(getWindow, context, deloser) { const deloserElement = deloser.getElement(); if (deloserElement) { const uid = getElementUId(getWindow, deloserElement); if (!context.deloserByUId[uid]) { context.deloserByUId[uid] = deloser; } return uid; } return undefined; } /*! * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ class FocusableAPI { constructor(tabster) { this._tabster = tabster; } dispose() { /**/ } getProps(element) { const tabsterOnElement = getTabsterOnElement(this._tabster, element); return tabsterOnElement && tabsterOnElement.focusable || {}; } isFocusable(el, includeProgrammaticallyFocusable, noVisibleCheck, noAccessibleCheck) { if (matchesSelector(el, FOCUSABLE_SELECTOR) && (includeProgrammaticallyFocusable || el.tabIndex !== -1)) { return (noVisibleCheck || this.isVisible(el)) && (noAccessibleCheck || this.isAccessible(el)); } return false; } isVisible(el) { if (!el.ownerDocument || el.nodeType !== Node.ELEMENT_NODE) { return false; } if (isDisplayNone(el)) { return false; } const rect = el.ownerDocument.body.getBoundingClientRect(); if (rect.width === 0 && rect.height === 0) { // This might happen, for example, if our is in hidden