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