Outlook_Addin_LLM/node_modules/tabster/dist/tabster.esm.js

10364 lines
290 KiB
JavaScript

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:
// <div>
// <button>Button1</button>
// <div id="uncontrolled" data-tabster={uncontrolled: {}}>
// <button>Button2</button>
// <button>Button3</button>
// </div>
// <button>Button4</button>
// </div>
//
// 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 <body> 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 <body>.
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 <body> is in hidden <iframe>.
return false;
}
return true;
}
isAccessible(el) {
var _a;
for (let e = el; e; e = dom.getParentElement(e)) {
const tabsterOnElement = getTabsterOnElement(this._tabster, e);
if (this._isHidden(e)) {
return false;
}
const ignoreDisabled = (_a = tabsterOnElement === null || tabsterOnElement === void 0 ? void 0 : tabsterOnElement.focusable) === null || _a === void 0 ? void 0 : _a.ignoreAriaDisabled;
if (!ignoreDisabled && this._isDisabled(e)) {
return false;
}
}
return true;
}
_isDisabled(el) {
return el.hasAttribute("disabled");
}
_isHidden(el) {
var _a;
const attrVal = el.getAttribute("aria-hidden");
if (attrVal && attrVal.toLowerCase() === "true") {
if (!((_a = this._tabster.modalizer) === null || _a === void 0 ? void 0 : _a.isAugmented(el))) {
return true;
}
}
return false;
}
findFirst(options, out) {
return this.findElement({ ...options
}, out);
}
findLast(options, out) {
return this.findElement({
isBackward: true,
...options
}, out);
}
findNext(options, out) {
return this.findElement({ ...options
}, out);
}
findPrev(options, out) {
return this.findElement({ ...options,
isBackward: true
}, out);
}
findDefault(options, out) {
return this.findElement({ ...options,
acceptCondition: el => this.isFocusable(el, options.includeProgrammaticallyFocusable) && !!this.getProps(el).isDefault
}, out) || null;
}
findAll(options) {
return this._findElements(true, options) || [];
}
findElement(options, out) {
const found = this._findElements(false, options, out);
return found ? found[0] : found;
}
_findElements(isFindAll, options, out) {
var _a, _b, _c;
const {
container,
currentElement = null,
includeProgrammaticallyFocusable,
useActiveModalizer,
ignoreAccessibility,
modalizerId,
isBackward,
onElement
} = options;
if (!out) {
out = {};
}
const elements = [];
let {
acceptCondition
} = options;
const hasCustomCondition = !!acceptCondition;
if (!container) {
return null;
}
if (!acceptCondition) {
acceptCondition = el => this.isFocusable(el, includeProgrammaticallyFocusable, false, ignoreAccessibility);
}
const acceptElementState = {
container,
modalizerUserId: modalizerId === undefined && useActiveModalizer ? (_a = this._tabster.modalizer) === null || _a === void 0 ? void 0 : _a.activeId : modalizerId || ((_c = (_b = RootAPI.getTabsterContext(this._tabster, container)) === null || _b === void 0 ? void 0 : _b.modalizer) === null || _c === void 0 ? void 0 : _c.userId),
from: currentElement || container,
isBackward,
isFindAll,
acceptCondition,
hasCustomCondition,
includeProgrammaticallyFocusable,
ignoreAccessibility,
cachedGrouppers: {},
cachedRadioGroups: {}
};
const walker = createElementTreeWalker(container.ownerDocument, container, node => this._acceptElement(node, acceptElementState));
if (!walker) {
return null;
}
const prepareForNextElement = shouldContinueIfNotFound => {
var _a, _b;
const foundElement = (_a = acceptElementState.foundElement) !== null && _a !== void 0 ? _a : acceptElementState.foundBackward;
if (foundElement) {
elements.push(foundElement);
}
if (isFindAll) {
if (foundElement) {
acceptElementState.found = false;
delete acceptElementState.foundElement;
delete acceptElementState.foundBackward;
delete acceptElementState.fromCtx;
acceptElementState.from = foundElement;
if (onElement && !onElement(foundElement)) {
return false;
}
}
return !!(foundElement || shouldContinueIfNotFound);
} else {
if (foundElement && out) {
out.uncontrolled = (_b = RootAPI.getTabsterContext(this._tabster, foundElement)) === null || _b === void 0 ? void 0 : _b.uncontrolled;
}
return !!(shouldContinueIfNotFound && !foundElement);
}
};
if (!currentElement) {
out.outOfDOMOrder = true;
}
if (currentElement && dom.nodeContains(container, currentElement)) {
walker.currentNode = currentElement;
} else if (isBackward) {
const lastChild = getLastChild$2(container);
if (!lastChild) {
return null;
}
if (this._acceptElement(lastChild, acceptElementState) === NodeFilter.FILTER_ACCEPT && !prepareForNextElement(true)) {
if (acceptElementState.skippedFocusable) {
out.outOfDOMOrder = true;
}
return elements;
}
walker.currentNode = lastChild;
}
do {
if (isBackward) {
walker.previousNode();
} else {
walker.nextNode();
}
} while (prepareForNextElement());
if (acceptElementState.skippedFocusable) {
out.outOfDOMOrder = true;
}
return elements.length ? elements : null;
}
_acceptElement(element, state) {
var _a, _b, _c;
if (state.found) {
return NodeFilter.FILTER_ACCEPT;
}
const foundBackward = state.foundBackward;
if (foundBackward && (element === foundBackward || !dom.nodeContains(foundBackward, element))) {
state.found = true;
state.foundElement = foundBackward;
return NodeFilter.FILTER_ACCEPT;
}
const container = state.container;
if (element === container) {
return NodeFilter.FILTER_SKIP;
}
if (!dom.nodeContains(container, element)) {
return NodeFilter.FILTER_REJECT;
}
if (getDummyInputContainer(element)) {
return NodeFilter.FILTER_REJECT;
}
if (dom.nodeContains(state.rejectElementsFrom, element)) {
return NodeFilter.FILTER_REJECT;
}
const ctx = state.currentCtx = RootAPI.getTabsterContext(this._tabster, element); // Tabster is opt in, if it is not managed, don't try and get do anything special
if (!ctx) {
return NodeFilter.FILTER_SKIP;
}
if (shouldIgnoreFocus(element)) {
if (this.isFocusable(element, undefined, true, true)) {
state.skippedFocusable = true;
}
return NodeFilter.FILTER_SKIP;
} // We assume iframes are focusable because native tab behaviour would tab inside.
// But we do it only during the standard search when there is no custom accept
// element condition.
if (!state.hasCustomCondition && (element.tagName === "IFRAME" || element.tagName === "WEBVIEW")) {
if (((_a = ctx.modalizer) === null || _a === void 0 ? void 0 : _a.userId) === ((_b = this._tabster.modalizer) === null || _b === void 0 ? void 0 : _b.activeId)) {
state.found = true;
state.rejectElementsFrom = state.foundElement = element;
return NodeFilter.FILTER_ACCEPT;
} else {
return NodeFilter.FILTER_REJECT;
}
}
if (!state.ignoreAccessibility && !this.isAccessible(element)) {
if (this.isFocusable(element, false, true, true)) {
state.skippedFocusable = true;
}
return NodeFilter.FILTER_REJECT;
}
let result;
let fromCtx = state.fromCtx;
if (!fromCtx) {
fromCtx = state.fromCtx = RootAPI.getTabsterContext(this._tabster, state.from);
}
const fromMover = fromCtx === null || fromCtx === void 0 ? void 0 : fromCtx.mover;
let groupper = ctx.groupper;
let mover = ctx.mover;
result = (_c = this._tabster.modalizer) === null || _c === void 0 ? void 0 : _c.acceptElement(element, state);
if (result !== undefined) {
state.skippedFocusable = true;
}
if (result === undefined && (groupper || mover || fromMover)) {
const groupperElement = groupper === null || groupper === void 0 ? void 0 : groupper.getElement();
const fromMoverElement = fromMover === null || fromMover === void 0 ? void 0 : fromMover.getElement();
let moverElement = mover === null || mover === void 0 ? void 0 : mover.getElement();
if (moverElement && dom.nodeContains(fromMoverElement, moverElement) && dom.nodeContains(container, fromMoverElement) && (!groupperElement || !mover || dom.nodeContains(fromMoverElement, groupperElement))) {
mover = fromMover;
moverElement = fromMoverElement;
}
if (groupperElement && (groupperElement === container || !dom.nodeContains(container, groupperElement))) {
groupper = undefined;
}
if (moverElement && !dom.nodeContains(container, moverElement)) {
mover = undefined;
}
if (groupper && mover) {
if (moverElement && groupperElement && !dom.nodeContains(groupperElement, moverElement)) {
mover = undefined;
} else {
groupper = undefined;
}
}
if (groupper) {
result = groupper.acceptElement(element, state);
}
if (mover) {
result = mover.acceptElement(element, state);
}
}
if (result === undefined) {
result = state.acceptCondition(element) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
if (result === NodeFilter.FILTER_SKIP && this.isFocusable(element, false, true, true)) {
state.skippedFocusable = true;
}
}
if (result === NodeFilter.FILTER_ACCEPT && !state.found) {
if (!state.isFindAll && isRadio(element) && !element.checked) {
// We need to mimic the browser's behaviour to skip unchecked radio buttons.
const radioGroupName = element.name;
let radioGroup = state.cachedRadioGroups[radioGroupName];
if (!radioGroup) {
radioGroup = getRadioButtonGroup(element);
if (radioGroup) {
state.cachedRadioGroups[radioGroupName] = radioGroup;
}
}
if ((radioGroup === null || radioGroup === void 0 ? void 0 : radioGroup.checked) && radioGroup.checked !== element) {
// Currently found element is a radio button in a group that has another radio button checked.
return NodeFilter.FILTER_SKIP;
}
}
if (state.isBackward) {
// When TreeWalker goes backwards, it visits the container first,
// then it goes inside. So, if the container is accepted, we remember it,
// but allowing the TreeWalker to check inside.
state.foundBackward = element;
result = NodeFilter.FILTER_SKIP;
} else {
state.found = true;
state.foundElement = element;
}
}
return result;
}
}
/*!
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
const Keys = {
Tab: "Tab",
Enter: "Enter",
Escape: "Escape",
Space: " ",
PageUp: "PageUp",
PageDown: "PageDown",
End: "End",
Home: "Home",
ArrowLeft: "ArrowLeft",
ArrowUp: "ArrowUp",
ArrowRight: "ArrowRight",
ArrowDown: "ArrowDown"
};
/*!
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
function getUncontrolledCompletelyContainer(tabster, element) {
var _a;
const getParent = tabster.getParent;
let el = element;
do {
const uncontrolledOnElement = (_a = getTabsterOnElement(tabster, el)) === null || _a === void 0 ? void 0 : _a.uncontrolled;
if (uncontrolledOnElement && tabster.uncontrolled.isUncontrolledCompletely(el, !!uncontrolledOnElement.completely)) {
return el;
}
el = getParent(el);
} while (el);
return undefined;
}
const AsyncFocusIntentPriorityBySource = {
[AsyncFocusSources.Restorer]: 0,
[AsyncFocusSources.Deloser]: 1,
[AsyncFocusSources.EscapeGroupper]: 2
};
class FocusedElementState extends Subscribable {
constructor(tabster, getWindow) {
super();
this._init = () => {
const win = this._win();
const doc = win.document; // Add these event listeners as capture - we want Tabster to run before user event handlers
doc.addEventListener(KEYBORG_FOCUSIN, this._onFocusIn, true);
doc.addEventListener(KEYBORG_FOCUSOUT, this._onFocusOut, true);
win.addEventListener("keydown", this._onKeyDown, true);
const activeElement = dom.getActiveElement(doc);
if (activeElement && activeElement !== doc.body) {
this._setFocusedElement(activeElement);
}
this.subscribe(this._onChanged);
};
this._onFocusIn = e => {
const target = e.composedPath()[0];
if (target) {
this._setFocusedElement(target, e.detail.relatedTarget, e.detail.isFocusedProgrammatically);
}
};
this._onFocusOut = e => {
var _a;
this._setFocusedElement(undefined, (_a = e.detail) === null || _a === void 0 ? void 0 : _a.originalEvent.relatedTarget);
}; // eslint-disable-next-line @typescript-eslint/no-unused-vars
this._validateFocusedElement = element => {// TODO: Make sure this is not needed anymore and write tests.
};
this._onKeyDown = event => {
if (event.key !== Keys.Tab || event.ctrlKey) {
return;
}
const currentElement = this.getVal();
if (!currentElement || !currentElement.ownerDocument || currentElement.contentEditable === "true") {
return;
}
const tabster = this._tabster;
const controlTab = tabster.controlTab;
const ctx = RootAPI.getTabsterContext(tabster, currentElement);
if (!ctx || ctx.ignoreKeydown(event)) {
return;
}
const isBackward = event.shiftKey;
const next = FocusedElementState.findNextTabbable(tabster, ctx, undefined, currentElement, undefined, isBackward, true);
const rootElement = ctx.root.getElement();
if (!rootElement) {
return;
}
const nextElement = next === null || next === void 0 ? void 0 : next.element;
const uncontrolledCompletelyContainer = getUncontrolledCompletelyContainer(tabster, currentElement);
if (nextElement) {
const nextUncontrolled = next.uncontrolled;
if (ctx.uncontrolled || dom.nodeContains(nextUncontrolled, currentElement)) {
if (!next.outOfDOMOrder && nextUncontrolled === ctx.uncontrolled || uncontrolledCompletelyContainer && !dom.nodeContains(uncontrolledCompletelyContainer, nextElement)) {
// Nothing to do, everything will be done by the browser or something
// that controls the uncontrolled area.
return;
} // We are in uncontrolled area. We allow whatever controls it to move
// focus, but we add a phantom dummy to make sure the focus is moved
// to the correct place if the uncontrolled area allows default action.
// We only need that in the controlled mode, because in uncontrolled
// mode we have dummy inputs around everything that redirects focus.
DummyInputManager.addPhantomDummyWithTarget(tabster, currentElement, isBackward, nextElement);
return;
}
if (nextUncontrolled || nextElement.tagName === "IFRAME") {
// For iframes and uncontrolled areas we always want to use default action to
// move focus into.
if (rootElement.dispatchEvent(new TabsterMoveFocusEvent({
by: "root",
owner: rootElement,
next: nextElement,
relatedEvent: event
}))) {
DummyInputManager.moveWithPhantomDummy(this._tabster, nextUncontrolled !== null && nextUncontrolled !== void 0 ? nextUncontrolled : nextElement, false, isBackward, event);
}
return;
}
if (controlTab || (next === null || next === void 0 ? void 0 : next.outOfDOMOrder)) {
if (rootElement.dispatchEvent(new TabsterMoveFocusEvent({
by: "root",
owner: rootElement,
next: nextElement,
relatedEvent: event
}))) {
event.preventDefault();
event.stopImmediatePropagation();
nativeFocus(nextElement);
}
}
} else {
if (!uncontrolledCompletelyContainer && rootElement.dispatchEvent(new TabsterMoveFocusEvent({
by: "root",
owner: rootElement,
next: null,
relatedEvent: event
}))) {
ctx.root.moveOutWithDefaultAction(isBackward, event);
}
}
};
this._onChanged = (element, detail) => {
var _a, _b;
if (element) {
element.dispatchEvent(new TabsterFocusInEvent(detail));
} else {
const last = (_a = this._lastVal) === null || _a === void 0 ? void 0 : _a.get();
if (last) {
const d = { ...detail
};
const lastCtx = RootAPI.getTabsterContext(this._tabster, last);
const modalizerId = (_b = lastCtx === null || lastCtx === void 0 ? void 0 : lastCtx.modalizer) === null || _b === void 0 ? void 0 : _b.userId;
if (modalizerId) {
d.modalizerId = modalizerId;
}
last.dispatchEvent(new TabsterFocusOutEvent(d));
}
}
};
this._tabster = tabster;
this._win = getWindow;
tabster.queueInit(this._init);
}
dispose() {
super.dispose();
const win = this._win();
const doc = win.document;
doc.removeEventListener(KEYBORG_FOCUSIN, this._onFocusIn, true);
doc.removeEventListener(KEYBORG_FOCUSOUT, this._onFocusOut, true);
win.removeEventListener("keydown", this._onKeyDown, true);
this.unsubscribe(this._onChanged);
const asyncFocus = this._asyncFocus;
if (asyncFocus) {
win.clearTimeout(asyncFocus.timeout);
delete this._asyncFocus;
}
delete FocusedElementState._lastResetElement;
delete this._nextVal;
delete this._lastVal;
}
static forgetMemorized(instance, parent) {
var _a, _b;
let wel = FocusedElementState._lastResetElement;
let el = wel && wel.get();
if (el && dom.nodeContains(parent, el)) {
delete FocusedElementState._lastResetElement;
}
el = (_b = (_a = instance._nextVal) === null || _a === void 0 ? void 0 : _a.element) === null || _b === void 0 ? void 0 : _b.get();
if (el && dom.nodeContains(parent, el)) {
delete instance._nextVal;
}
wel = instance._lastVal;
el = wel && wel.get();
if (el && dom.nodeContains(parent, el)) {
delete instance._lastVal;
}
}
getFocusedElement() {
return this.getVal();
}
getLastFocusedElement() {
var _a;
let el = (_a = this._lastVal) === null || _a === void 0 ? void 0 : _a.get();
if (!el || el && !documentContains(el.ownerDocument, el)) {
this._lastVal = el = undefined;
}
return el;
}
focus(element, noFocusedProgrammaticallyFlag, noAccessibleCheck) {
if (!this._tabster.focusable.isFocusable(element, noFocusedProgrammaticallyFlag, false, noAccessibleCheck)) {
return false;
}
element.focus();
return true;
}
focusDefault(container) {
const el = this._tabster.focusable.findDefault({
container
});
if (el) {
this._tabster.focusedElement.focus(el);
return true;
}
return false;
}
getFirstOrLastTabbable(isFirst, props) {
var _a;
const {
container,
ignoreAccessibility
} = props;
let toFocus;
if (container) {
const ctx = RootAPI.getTabsterContext(this._tabster, container);
if (ctx) {
toFocus = (_a = FocusedElementState.findNextTabbable(this._tabster, ctx, container, undefined, undefined, !isFirst, ignoreAccessibility)) === null || _a === void 0 ? void 0 : _a.element;
}
}
if (toFocus && !dom.nodeContains(container, toFocus)) {
toFocus = undefined;
}
return toFocus || undefined;
}
_focusFirstOrLast(isFirst, props) {
const toFocus = this.getFirstOrLastTabbable(isFirst, props);
if (toFocus) {
this.focus(toFocus, false, true);
return true;
}
return false;
}
focusFirst(props) {
return this._focusFirstOrLast(true, props);
}
focusLast(props) {
return this._focusFirstOrLast(false, props);
}
resetFocus(container) {
if (!this._tabster.focusable.isVisible(container)) {
return false;
}
if (!this._tabster.focusable.isFocusable(container, true, true, true)) {
const prevTabIndex = container.getAttribute("tabindex");
const prevAriaHidden = container.getAttribute("aria-hidden");
container.tabIndex = -1;
container.setAttribute("aria-hidden", "true");
FocusedElementState._lastResetElement = new WeakHTMLElement(this._win, container);
this.focus(container, true, true);
this._setOrRemoveAttribute(container, "tabindex", prevTabIndex);
this._setOrRemoveAttribute(container, "aria-hidden", prevAriaHidden);
} else {
this.focus(container);
}
return true;
}
requestAsyncFocus(source, callback, delay) {
const win = this._tabster.getWindow();
const currentAsyncFocus = this._asyncFocus;
if (currentAsyncFocus) {
if (AsyncFocusIntentPriorityBySource[source] > AsyncFocusIntentPriorityBySource[currentAsyncFocus.source]) {
// Previously registered intent has higher priority.
return;
} // New intent has higher priority.
win.clearTimeout(currentAsyncFocus.timeout);
}
this._asyncFocus = {
source,
callback,
timeout: win.setTimeout(() => {
this._asyncFocus = undefined;
callback();
}, delay)
};
}
cancelAsyncFocus(source) {
const asyncFocus = this._asyncFocus;
if ((asyncFocus === null || asyncFocus === void 0 ? void 0 : asyncFocus.source) === source) {
this._tabster.getWindow().clearTimeout(asyncFocus.timeout);
this._asyncFocus = undefined;
}
}
_setOrRemoveAttribute(element, name, value) {
if (value === null) {
element.removeAttribute(name);
} else {
element.setAttribute(name, value);
}
}
_setFocusedElement(element, relatedTarget, isFocusedProgrammatically) {
var _a, _b;
if (this._tabster._noop) {
return;
}
const detail = {
relatedTarget
};
if (element) {
const lastResetElement = (_a = FocusedElementState._lastResetElement) === null || _a === void 0 ? void 0 : _a.get();
FocusedElementState._lastResetElement = undefined;
if (lastResetElement === element || shouldIgnoreFocus(element)) {
return;
}
detail.isFocusedProgrammatically = isFocusedProgrammatically;
const ctx = RootAPI.getTabsterContext(this._tabster, element);
const modalizerId = (_b = ctx === null || ctx === void 0 ? void 0 : ctx.modalizer) === null || _b === void 0 ? void 0 : _b.userId;
if (modalizerId) {
detail.modalizerId = modalizerId;
}
}
const nextVal = this._nextVal = {
element: element ? new WeakHTMLElement(this._win, element) : undefined,
detail
};
if (element && element !== this._val) {
this._validateFocusedElement(element);
} // _validateFocusedElement() might cause the refocus which will trigger
// another call to this function. Making sure that the value is correct.
if (this._nextVal === nextVal) {
this.setVal(element, detail);
}
this._nextVal = undefined;
}
setVal(val, detail) {
super.setVal(val, detail);
if (val) {
this._lastVal = new WeakHTMLElement(this._win, val);
}
}
static findNextTabbable(tabster, ctx, container, currentElement, referenceElement, isBackward, ignoreAccessibility) {
const actualContainer = container || ctx.root.getElement();
if (!actualContainer) {
return null;
}
let next = null;
const isTabbingTimer = FocusedElementState._isTabbingTimer;
const win = tabster.getWindow();
if (isTabbingTimer) {
win.clearTimeout(isTabbingTimer);
}
FocusedElementState.isTabbing = true;
FocusedElementState._isTabbingTimer = win.setTimeout(() => {
delete FocusedElementState._isTabbingTimer;
FocusedElementState.isTabbing = false;
}, 0);
const modalizer = ctx.modalizer;
const groupper = ctx.groupper;
const mover = ctx.mover;
const callFindNext = what => {
next = what.findNextTabbable(currentElement, referenceElement, isBackward, ignoreAccessibility);
if (currentElement && !(next === null || next === void 0 ? void 0 : next.element)) {
const parentElement = what !== modalizer && dom.getParentElement(what.getElement());
if (parentElement) {
const parentCtx = RootAPI.getTabsterContext(tabster, currentElement, {
referenceElement: parentElement
});
if (parentCtx) {
const currentScopeElement = what.getElement();
const newCurrent = isBackward ? currentScopeElement : currentScopeElement && getLastChild$2(currentScopeElement) || currentScopeElement;
if (newCurrent) {
next = FocusedElementState.findNextTabbable(tabster, parentCtx, container, newCurrent, parentElement, isBackward, ignoreAccessibility);
if (next) {
next.outOfDOMOrder = true;
}
}
}
}
}
};
if (groupper && mover) {
callFindNext(ctx.groupperBeforeMover ? groupper : mover);
} else if (groupper) {
callFindNext(groupper);
} else if (mover) {
callFindNext(mover);
} else if (modalizer) {
callFindNext(modalizer);
} else {
const findProps = {
container: actualContainer,
currentElement,
referenceElement,
ignoreAccessibility,
useActiveModalizer: true
};
const findPropsOut = {};
const nextElement = tabster.focusable[isBackward ? "findPrev" : "findNext"](findProps, findPropsOut);
next = {
element: nextElement,
outOfDOMOrder: findPropsOut.outOfDOMOrder,
uncontrolled: findPropsOut.uncontrolled
};
}
return next;
}
}
FocusedElementState.isTabbing = false;
/*!
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
class GroupperDummyManager extends DummyInputManager {
constructor(element, groupper, tabster, sys) {
super(tabster, element, DummyInputManagerPriorities.Groupper, sys, true);
this._setHandlers((dummyInput, isBackward, relatedTarget) => {
var _a, _b;
const container = element.get();
const input = dummyInput.input;
if (container && input) {
const ctx = RootAPI.getTabsterContext(tabster, input);
if (ctx) {
let next;
next = (_a = groupper.findNextTabbable(relatedTarget || undefined, undefined, isBackward, true)) === null || _a === void 0 ? void 0 : _a.element;
if (!next) {
next = (_b = FocusedElementState.findNextTabbable(tabster, ctx, undefined, dummyInput.isOutside ? input : getAdjacentElement(container, !isBackward), undefined, isBackward, true)) === null || _b === void 0 ? void 0 : _b.element;
}
if (next) {
nativeFocus(next);
}
}
}
});
}
}
class Groupper extends TabsterPart {
constructor(tabster, element, onDispose, props, sys) {
super(tabster, element, props);
this._shouldTabInside = false;
this.makeTabbable(false);
this._onDispose = onDispose;
if (!tabster.controlTab) {
this.dummyManager = new GroupperDummyManager(this._element, this, tabster, sys);
}
}
dispose() {
var _a;
this._onDispose(this);
const element = this._element.get();
(_a = this.dummyManager) === null || _a === void 0 ? void 0 : _a.dispose();
delete this.dummyManager;
if (element) {
if (process.env.NODE_ENV === 'development') {
_setInformativeStyle$1(this._element, true);
}
}
delete this._first;
}
findNextTabbable(currentElement, referenceElement, isBackward, ignoreAccessibility) {
const groupperElement = this.getElement();
if (!groupperElement) {
return null;
}
const currentIsDummy = getDummyInputContainer(currentElement) === groupperElement;
if (!this._shouldTabInside && currentElement && dom.nodeContains(groupperElement, currentElement) && !currentIsDummy) {
return {
element: undefined,
outOfDOMOrder: true
};
}
const groupperFirstFocusable = this.getFirst(true);
if (!currentElement || !dom.nodeContains(groupperElement, currentElement) || currentIsDummy) {
return {
element: groupperFirstFocusable,
outOfDOMOrder: true
};
}
const tabster = this._tabster;
let next = null;
let outOfDOMOrder = false;
let uncontrolled;
if (this._shouldTabInside && groupperFirstFocusable) {
const findProps = {
container: groupperElement,
currentElement,
referenceElement,
ignoreAccessibility,
useActiveModalizer: true
};
const findPropsOut = {};
next = tabster.focusable[isBackward ? "findPrev" : "findNext"](findProps, findPropsOut);
outOfDOMOrder = !!findPropsOut.outOfDOMOrder;
if (!next && this._props.tabbability === GroupperTabbabilities.LimitedTrapFocus) {
next = tabster.focusable[isBackward ? "findLast" : "findFirst"]({
container: groupperElement,
ignoreAccessibility,
useActiveModalizer: true
}, findPropsOut);
outOfDOMOrder = true;
}
uncontrolled = findPropsOut.uncontrolled;
}
return {
element: next,
uncontrolled,
outOfDOMOrder
};
}
makeTabbable(isTabbable) {
this._shouldTabInside = isTabbable || !this._props.tabbability;
if (process.env.NODE_ENV === 'development') {
_setInformativeStyle$1(this._element, !this._shouldTabInside);
}
}
isActive(noIfFirstIsFocused) {
var _a;
const element = this.getElement() || null;
let isParentActive = true;
for (let e = dom.getParentElement(element); e; e = dom.getParentElement(e)) {
const g = (_a = getTabsterOnElement(this._tabster, e)) === null || _a === void 0 ? void 0 : _a.groupper;
if (g) {
if (!g._shouldTabInside) {
isParentActive = false;
}
}
}
let ret = isParentActive ? this._props.tabbability ? this._shouldTabInside : false : undefined;
if (ret && noIfFirstIsFocused) {
const focused = this._tabster.focusedElement.getFocusedElement();
if (focused) {
ret = focused !== this.getFirst(true);
}
}
return ret;
}
getFirst(orContainer) {
var _a;
const groupperElement = this.getElement();
let first;
if (groupperElement) {
if (orContainer && this._tabster.focusable.isFocusable(groupperElement)) {
return groupperElement;
}
first = (_a = this._first) === null || _a === void 0 ? void 0 : _a.get();
if (!first) {
first = this._tabster.focusable.findFirst({
container: groupperElement,
useActiveModalizer: true
}) || undefined;
if (first) {
this.setFirst(first);
}
}
}
return first;
}
setFirst(element) {
if (element) {
this._first = new WeakHTMLElement(this._tabster.getWindow, element);
} else {
delete this._first;
}
}
acceptElement(element, state) {
const cachedGrouppers = state.cachedGrouppers;
const parentElement = dom.getParentElement(this.getElement());
const parentCtx = parentElement && RootAPI.getTabsterContext(this._tabster, parentElement);
const parentCtxGroupper = parentCtx === null || parentCtx === void 0 ? void 0 : parentCtx.groupper;
const parentGroupper = (parentCtx === null || parentCtx === void 0 ? void 0 : parentCtx.groupperBeforeMover) ? parentCtxGroupper : undefined;
let parentGroupperElement;
const getIsActive = groupper => {
let cached = cachedGrouppers[groupper.id];
let isActive;
if (cached) {
isActive = cached.isActive;
} else {
isActive = this.isActive(true);
cached = cachedGrouppers[groupper.id] = {
isActive
};
}
return isActive;
};
if (parentGroupper) {
parentGroupperElement = parentGroupper.getElement();
if (!getIsActive(parentGroupper) && parentGroupperElement && state.container !== parentGroupperElement && dom.nodeContains(state.container, parentGroupperElement)) {
// Do not fall into a child groupper of inactive parent groupper if it's in the scope of the search.
state.skippedFocusable = true;
return NodeFilter.FILTER_REJECT;
}
}
const isActive = getIsActive(this);
const groupperElement = this.getElement();
if (groupperElement) {
if (isActive !== true) {
if (groupperElement === element && parentCtxGroupper) {
if (!parentGroupperElement) {
parentGroupperElement = parentCtxGroupper.getElement();
}
if (parentGroupperElement && !getIsActive(parentCtxGroupper) && dom.nodeContains(state.container, parentGroupperElement) && parentGroupperElement !== state.container) {
state.skippedFocusable = true;
return NodeFilter.FILTER_REJECT;
}
}
if (groupperElement !== element && dom.nodeContains(groupperElement, element)) {
state.skippedFocusable = true;
return NodeFilter.FILTER_REJECT;
}
const cached = cachedGrouppers[this.id];
let first;
if ("first" in cached) {
first = cached.first;
} else {
first = cached.first = this.getFirst(true);
}
if (first && state.acceptCondition(first)) {
state.rejectElementsFrom = groupperElement;
state.skippedFocusable = true;
if (first !== state.from) {
state.found = true;
state.foundElement = first;
return NodeFilter.FILTER_ACCEPT;
} else {
return NodeFilter.FILTER_REJECT;
}
}
}
}
return undefined;
}
} // eslint-disable-next-line @typescript-eslint/no-unused-vars
class GroupperAPI {
constructor(tabster, getWindow) {
this._current = {};
this._grouppers = {};
this._init = () => {
const win = this._win(); // Making sure groupper's onFocus is called before modalizer's onFocus.
this._tabster.focusedElement.subscribeFirst(this._onFocus);
const doc = win.document;
const activeElement = dom.getActiveElement(doc);
if (activeElement) {
this._onFocus(activeElement);
}
doc.addEventListener("mousedown", this._onMouseDown, true);
win.addEventListener("keydown", this._onKeyDown, true);
win.addEventListener(GroupperMoveFocusEventName, this._onMoveFocus);
};
this._onGroupperDispose = groupper => {
delete this._grouppers[groupper.id];
};
this._onFocus = element => {
if (element) {
this._updateCurrent(element, true, true);
}
};
this._onMouseDown = e => {
if (e.target) {
this._updateCurrent(e.target, true);
}
};
this._onKeyDown = event => {
if (event.key !== Keys.Enter && event.key !== Keys.Escape) {
return;
} // Give a chance to other listeners to handle the event.
if (event.ctrlKey || event.altKey || event.shiftKey || event.metaKey) {
return;
}
const element = this._tabster.focusedElement.getFocusedElement();
if (element) {
this.handleKeyPress(element, event);
}
};
this._onMoveFocus = e => {
var _a;
const element = e.composedPath()[0];
const action = (_a = e.detail) === null || _a === void 0 ? void 0 : _a.action;
if (element && action !== undefined && !e.defaultPrevented) {
if (action === GroupperMoveFocusActions.Enter) {
this._enterGroupper(element);
} else {
this._escapeGroupper(element);
}
e.stopImmediatePropagation();
}
};
this._tabster = tabster;
this._win = getWindow;
tabster.queueInit(this._init);
}
dispose() {
const win = this._win();
this._tabster.focusedElement.cancelAsyncFocus(AsyncFocusSources.EscapeGroupper);
this._current = {};
if (this._updateTimer) {
win.clearTimeout(this._updateTimer);
delete this._updateTimer;
}
this._tabster.focusedElement.unsubscribe(this._onFocus);
win.document.removeEventListener("mousedown", this._onMouseDown, true);
win.removeEventListener("keydown", this._onKeyDown, true);
win.removeEventListener(GroupperMoveFocusEventName, this._onMoveFocus);
Object.keys(this._grouppers).forEach(groupperId => {
if (this._grouppers[groupperId]) {
this._grouppers[groupperId].dispose();
delete this._grouppers[groupperId];
}
});
}
createGroupper(element, props, sys) {
if (process.env.NODE_ENV === 'development') ;
const newGroupper = new Groupper(this._tabster, element, this._onGroupperDispose, props, sys);
this._grouppers[newGroupper.id] = newGroupper;
const focusedElement = this._tabster.focusedElement.getFocusedElement(); // Newly created groupper contains currently focused element, update the state on the next tick (to
// make sure all grouppers are processed).
if (focusedElement && dom.nodeContains(element, focusedElement) && !this._updateTimer) {
this._updateTimer = this._win().setTimeout(() => {
delete this._updateTimer; // Making sure the focused element hasn't changed.
if (focusedElement === this._tabster.focusedElement.getFocusedElement()) {
this._updateCurrent(focusedElement, true, true);
}
}, 0);
}
return newGroupper;
}
forgetCurrentGrouppers() {
this._current = {};
}
_updateCurrent(element, includeTarget, checkTarget) {
var _a;
if (this._updateTimer) {
this._win().clearTimeout(this._updateTimer);
delete this._updateTimer;
}
const newIds = {};
let isTarget = true;
for (let el = element; el; el = dom.getParentElement(el)) {
const groupper = (_a = getTabsterOnElement(this._tabster, el)) === null || _a === void 0 ? void 0 : _a.groupper;
if (groupper) {
newIds[groupper.id] = true;
if (isTarget && checkTarget && el !== element) {
isTarget = false;
}
if (includeTarget || !isTarget) {
this._current[groupper.id] = groupper;
const isTabbable = groupper.isActive() || element !== el && (!groupper.getProps().delegated || groupper.getFirst(false) !== element);
groupper.makeTabbable(isTabbable);
}
isTarget = false;
}
}
for (const id of Object.keys(this._current)) {
const groupper = this._current[id];
if (!(groupper.id in newIds)) {
groupper.makeTabbable(false);
groupper.setFirst(undefined);
delete this._current[id];
}
}
}
_enterGroupper(element, relatedEvent) {
const tabster = this._tabster;
const ctx = RootAPI.getTabsterContext(tabster, element);
const groupper = (ctx === null || ctx === void 0 ? void 0 : ctx.groupper) || (ctx === null || ctx === void 0 ? void 0 : ctx.modalizerInGroupper);
const groupperElement = groupper === null || groupper === void 0 ? void 0 : groupper.getElement();
if (groupper && groupperElement && (element === groupperElement || groupper.getProps().delegated && element === groupper.getFirst(false))) {
const next = tabster.focusable.findNext({
container: groupperElement,
currentElement: element,
useActiveModalizer: true
});
if (next && (!relatedEvent || relatedEvent && groupperElement.dispatchEvent(new TabsterMoveFocusEvent({
by: "groupper",
owner: groupperElement,
next,
relatedEvent
})))) {
if (relatedEvent) {
// When the application hasn't prevented default,
// we consider the event completely handled, hence we
// prevent the initial event's default action and stop
// propagation.
relatedEvent.preventDefault();
relatedEvent.stopImmediatePropagation();
}
next.focus();
return next;
}
}
return null;
}
_escapeGroupper(element, relatedEvent, fromModalizer) {
var _a;
const tabster = this._tabster;
const ctx = RootAPI.getTabsterContext(tabster, element);
const modalizerInGroupper = ctx === null || ctx === void 0 ? void 0 : ctx.modalizerInGroupper;
let groupper = (ctx === null || ctx === void 0 ? void 0 : ctx.groupper) || modalizerInGroupper;
const groupperElement = groupper === null || groupper === void 0 ? void 0 : groupper.getElement();
if (groupper && groupperElement && dom.nodeContains(groupperElement, element)) {
let next;
if (element !== groupperElement || fromModalizer) {
next = groupper.getFirst(true);
} else {
const parentElement = dom.getParentElement(groupperElement);
const parentCtx = parentElement ? RootAPI.getTabsterContext(tabster, parentElement) : undefined;
groupper = parentCtx === null || parentCtx === void 0 ? void 0 : parentCtx.groupper;
next = groupper === null || groupper === void 0 ? void 0 : groupper.getFirst(true);
}
if (next && (!relatedEvent || relatedEvent && groupperElement.dispatchEvent(new TabsterMoveFocusEvent({
by: "groupper",
owner: groupperElement,
next,
relatedEvent
})))) {
if (groupper) {
groupper.makeTabbable(false);
if (modalizerInGroupper) {
(_a = tabster.modalizer) === null || _a === void 0 ? void 0 : _a.setActive(undefined);
}
} // This part happens asynchronously inside setTimeout,
// so no need to prevent default or stop propagation.
next.focus();
return next;
}
}
return null;
}
moveFocus(element, action) {
return action === GroupperMoveFocusActions.Enter ? this._enterGroupper(element) : this._escapeGroupper(element);
}
handleKeyPress(element, event, fromModalizer) {
const tabster = this._tabster;
const ctx = RootAPI.getTabsterContext(tabster, element);
if (ctx && ((ctx === null || ctx === void 0 ? void 0 : ctx.groupper) || (ctx === null || ctx === void 0 ? void 0 : ctx.modalizerInGroupper))) {
tabster.focusedElement.cancelAsyncFocus(AsyncFocusSources.EscapeGroupper);
if (ctx.ignoreKeydown(event)) {
return;
}
if (event.key === Keys.Enter) {
this._enterGroupper(element, event);
} else if (event.key === Keys.Escape) {
// We will handle Esc asynchronously, if something in the application will
// move focus during the keypress handling, we will not interfere.
const focusedElement = tabster.focusedElement.getFocusedElement();
tabster.focusedElement.requestAsyncFocus(AsyncFocusSources.EscapeGroupper, () => {
if (focusedElement !== tabster.focusedElement.getFocusedElement() && ( // A part of Modalizer that has called this handler to escape the active groupper
// might have been removed from DOM, if the focus is on body, we still want to handle Esc.
fromModalizer && !focusedElement || !fromModalizer)) {
// Something else in the application has moved focus, we will not handle Esc.
return;
}
this._escapeGroupper(element, event, fromModalizer);
}, 0);
}
}
}
}
function _setInformativeStyle$1(weakElement, remove) {
if (process.env.NODE_ENV === 'development') {
const element = weakElement.get();
if (element) {
if (remove) {
element.style.removeProperty("--tabster-groupper");
} else {
element.style.setProperty("--tabster-groupper", "unlimited");
}
}
}
}
/*!
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
class KeyboardNavigationState extends Subscribable {
constructor(getWindow) {
super();
this._onChange = isNavigatingWithKeyboard => {
this.setVal(isNavigatingWithKeyboard, undefined);
};
this._keyborg = createKeyborg(getWindow());
this._keyborg.subscribe(this._onChange);
}
dispose() {
super.dispose();
if (this._keyborg) {
this._keyborg.unsubscribe(this._onChange);
disposeKeyborg(this._keyborg);
delete this._keyborg;
}
}
setNavigatingWithKeyboard(isNavigatingWithKeyboard) {
var _a;
(_a = this._keyborg) === null || _a === void 0 ? void 0 : _a.setVal(isNavigatingWithKeyboard);
}
isNavigatingWithKeyboard() {
var _a;
return !!((_a = this._keyborg) === null || _a === void 0 ? void 0 : _a.isNavigatingWithKeyboard());
}
}
/*!
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
let _wasFocusedCounter = 0;
const _ariaHidden = "aria-hidden";
function _setInformativeStyle(weakElement, remove, internalId, userId, isActive, wasFocused) {
if (process.env.NODE_ENV === 'development') {
const element = weakElement.get();
if (element) {
if (remove) {
element.style.removeProperty("--tabster-modalizer");
} else {
element.style.setProperty("--tabster-modalizer", internalId + "," + userId + "," + (isActive ? "active" : "inactive") + "," + "," + (wasFocused ? `focused(${wasFocused})` : "not-focused"));
}
}
}
}
/**
* Manages the dummy inputs for the Modalizer.
*/
class ModalizerDummyManager extends DummyInputManager {
constructor(element, tabster, sys) {
super(tabster, element, DummyInputManagerPriorities.Modalizer, sys);
this._setHandlers((dummyInput, isBackward) => {
var _a, _b;
const el = element.get();
const container = el && ((_a = RootAPI.getRoot(tabster, el)) === null || _a === void 0 ? void 0 : _a.getElement());
const input = dummyInput.input;
let toFocus;
if (container && input) {
const dummyContainer = getDummyInputContainer(input);
const ctx = RootAPI.getTabsterContext(tabster, dummyContainer || input);
if (ctx) {
toFocus = (_b = FocusedElementState.findNextTabbable(tabster, ctx, container, input, undefined, isBackward, true)) === null || _b === void 0 ? void 0 : _b.element;
}
if (toFocus) {
nativeFocus(toFocus);
}
}
});
}
}
class Modalizer extends TabsterPart {
constructor(tabster, element, onDispose, props, sys, activeElements) {
super(tabster, element, props);
this._wasFocused = 0;
this.userId = props.id;
this._onDispose = onDispose;
this._activeElements = activeElements;
if (!tabster.controlTab) {
this.dummyManager = new ModalizerDummyManager(this._element, tabster, sys);
}
if (process.env.NODE_ENV === 'development') {
_setInformativeStyle(this._element, false, this.id, this.userId, this._isActive, this._wasFocused);
}
}
makeActive(isActive) {
if (this._isActive !== isActive) {
this._isActive = isActive;
const element = this.getElement();
if (element) {
const activeElements = this._activeElements;
const index = activeElements.map(e => e.get()).indexOf(element);
if (isActive) {
if (index < 0) {
activeElements.push(new WeakHTMLElement(this._tabster.getWindow, element));
}
} else {
if (index >= 0) {
activeElements.splice(index, 1);
}
}
}
if (process.env.NODE_ENV === 'development') {
_setInformativeStyle(this._element, false, this.id, this.userId, this._isActive, this._wasFocused);
}
this._dispatchEvent(isActive);
}
}
focused(noIncrement) {
if (!noIncrement) {
this._wasFocused = ++_wasFocusedCounter;
}
return this._wasFocused;
}
setProps(props) {
if (props.id) {
this.userId = props.id;
}
this._props = { ...props
};
}
dispose() {
var _a;
this.makeActive(false);
this._onDispose(this);
(_a = this.dummyManager) === null || _a === void 0 ? void 0 : _a.dispose();
delete this.dummyManager;
this._activeElements = [];
this._remove();
}
isActive() {
return !!this._isActive;
}
contains(element) {
return dom.nodeContains(this.getElement(), element);
}
findNextTabbable(currentElement, referenceElement, isBackward, ignoreAccessibility) {
var _a, _b;
const modalizerElement = this.getElement();
if (!modalizerElement) {
return null;
}
const tabster = this._tabster;
let next = null;
let outOfDOMOrder = false;
let uncontrolled;
const container = currentElement && ((_a = RootAPI.getRoot(tabster, currentElement)) === null || _a === void 0 ? void 0 : _a.getElement());
if (container) {
const findProps = {
container,
currentElement,
referenceElement,
ignoreAccessibility,
useActiveModalizer: true
};
const findPropsOut = {};
next = tabster.focusable[isBackward ? "findPrev" : "findNext"](findProps, findPropsOut);
if (!next && this._props.isTrapped && ((_b = tabster.modalizer) === null || _b === void 0 ? void 0 : _b.activeId)) {
next = tabster.focusable[isBackward ? "findLast" : "findFirst"]({
container,
ignoreAccessibility,
useActiveModalizer: true
}, findPropsOut);
outOfDOMOrder = true;
} else {
outOfDOMOrder = !!findPropsOut.outOfDOMOrder;
}
uncontrolled = findPropsOut.uncontrolled;
}
return {
element: next,
uncontrolled,
outOfDOMOrder
};
}
_dispatchEvent(isActive, allElements) {
const element = this.getElement();
let defaultPrevented = false;
if (element) {
const elements = allElements ? this._activeElements.map(e => e.get()) : [element];
for (const el of elements) {
if (el) {
const eventDetail = {
id: this.userId,
element
};
const event = isActive ? new ModalizerActiveEvent(eventDetail) : new ModalizerInactiveEvent(eventDetail);
el.dispatchEvent(event);
if (event.defaultPrevented) {
defaultPrevented = true;
}
}
}
}
return defaultPrevented;
}
_remove() {
if (process.env.NODE_ENV === 'development') {
_setInformativeStyle(this._element, true);
}
}
} // eslint-disable-next-line @typescript-eslint/no-unused-vars
class ModalizerAPI {
constructor(tabster, // @deprecated use accessibleCheck.
alwaysAccessibleSelector, accessibleCheck) {
this._onModalizerDispose = modalizer => {
const id = modalizer.id;
const userId = modalizer.userId;
const part = this._parts[userId];
delete this._modalizers[id];
if (part) {
delete part[id];
if (Object.keys(part).length === 0) {
delete this._parts[userId];
if (this.activeId === userId) {
this.setActive(undefined);
}
}
}
};
this._onKeyDown = event => {
var _a;
if (event.key !== Keys.Escape) {
return;
}
const tabster = this._tabster;
const element = tabster.focusedElement.getFocusedElement();
if (element) {
const ctx = RootAPI.getTabsterContext(tabster, element);
const modalizer = ctx === null || ctx === void 0 ? void 0 : ctx.modalizer;
if (ctx && !ctx.groupper && (modalizer === null || modalizer === void 0 ? void 0 : modalizer.isActive()) && !ctx.ignoreKeydown(event)) {
const activeId = modalizer.userId;
if (activeId) {
const part = this._parts[activeId];
if (part) {
const focusedSince = Object.keys(part).map(id => {
var _a;
const m = part[id];
const el = m.getElement();
let groupper;
if (el) {
groupper = (_a = getTabsterOnElement(this._tabster, el)) === null || _a === void 0 ? void 0 : _a.groupper;
}
return m && el && groupper ? {
el,
focusedSince: m.focused(true)
} : {
focusedSince: 0
};
}).filter(f => f.focusedSince > 0).sort((a, b) => a.focusedSince > b.focusedSince ? -1 : a.focusedSince < b.focusedSince ? 1 : 0);
if (focusedSince.length) {
const groupperElement = focusedSince[0].el;
if (groupperElement) {
(_a = tabster.groupper) === null || _a === void 0 ? void 0 : _a.handleKeyPress(groupperElement, event, true);
}
}
}
}
}
}
};
/**
* Subscribes to the focus state and handles modalizer related focus events
* @param focusedElement - Element that is focused
* @param detail - Additional data about the focus event
*/
this._onFocus = (focusedElement, detail) => {
var _a, _b;
const ctx = focusedElement && RootAPI.getTabsterContext(this._tabster, focusedElement); // Modalizer behaviour is opt in, only apply to elements that have a tabster context
if (!ctx || !focusedElement) {
return;
}
const augmentedMap = this._augMap;
for (let e = focusedElement; e; e = dom.getParentElement(e)) {
// If the newly focused element is inside some of the hidden containers,
// remove aria-hidden from those synchronously for the screen readers
// to be able to read the element. The rest of aria-hiddens, will be removed
// acynchronously for the sake of performance.
if (augmentedMap.has(e)) {
augmentedMap.delete(e);
augmentAttribute(this._tabster, e, _ariaHidden);
}
}
const modalizer = ctx.modalizer; // An inactive groupper with the modalizer on the same node will not give the modalizer
// in the context, yet we still want to track that the modalizer's container was focused.
(_b = modalizer || ((_a = getTabsterOnElement(this._tabster, focusedElement)) === null || _a === void 0 ? void 0 : _a.modalizer)) === null || _b === void 0 ? void 0 : _b.focused();
if ((modalizer === null || modalizer === void 0 ? void 0 : modalizer.userId) === this.activeId) {
this.currentIsOthersAccessible = modalizer === null || modalizer === void 0 ? void 0 : modalizer.getProps().isOthersAccessible;
return;
} // Developers calling `element.focus()` should change/deactivate active modalizer
if (detail.isFocusedProgrammatically || this.currentIsOthersAccessible || (modalizer === null || modalizer === void 0 ? void 0 : modalizer.getProps().isAlwaysAccessible)) {
this.setActive(modalizer);
} else {
// Focused outside of the active modalizer, try pull focus back to current modalizer
const win = this._win();
win.clearTimeout(this._restoreModalizerFocusTimer); // TODO some rendering frameworks (i.e. React) might async rerender the DOM so we need to wait for a duration
// Figure out a better way of doing this rather than a 100ms timeout
this._restoreModalizerFocusTimer = win.setTimeout(() => this._restoreModalizerFocus(focusedElement), 100);
}
};
this._tabster = tabster;
this._win = tabster.getWindow;
this._modalizers = {};
this._parts = {};
this._augMap = new WeakMap();
this._aug = [];
this._alwaysAccessibleSelector = alwaysAccessibleSelector;
this._accessibleCheck = accessibleCheck;
this.activeElements = [];
if (!tabster.controlTab) {
tabster.root.addDummyInputs();
}
const win = this._win();
win.addEventListener("keydown", this._onKeyDown, true);
tabster.queueInit(() => {
this._tabster.focusedElement.subscribe(this._onFocus);
});
}
dispose() {
const win = this._win();
win.removeEventListener("keydown", this._onKeyDown, true); // Dispose all modalizers managed by the API
Object.keys(this._modalizers).forEach(modalizerId => {
if (this._modalizers[modalizerId]) {
this._modalizers[modalizerId].dispose();
delete this._modalizers[modalizerId];
}
});
win.clearTimeout(this._restoreModalizerFocusTimer);
win.clearTimeout(this._hiddenUpdateTimer);
this._parts = {};
delete this.activeId;
this.activeElements = [];
this._augMap = new WeakMap();
this._aug = [];
this._tabster.focusedElement.unsubscribe(this._onFocus);
}
createModalizer(element, props, sys) {
var _a;
if (process.env.NODE_ENV === 'development') ;
const modalizer = new Modalizer(this._tabster, element, this._onModalizerDispose, props, sys, this.activeElements);
const id = modalizer.id;
const userId = props.id;
this._modalizers[id] = modalizer;
let part = this._parts[userId];
if (!part) {
part = this._parts[userId] = {};
}
part[id] = modalizer; // Adding a modalizer which is already focused, activate it
if (dom.nodeContains(element, (_a = this._tabster.focusedElement.getFocusedElement()) !== null && _a !== void 0 ? _a : null)) {
if (userId !== this.activeId) {
this.setActive(modalizer);
} else {
modalizer.makeActive(true);
}
}
return modalizer;
}
isAugmented(element) {
return this._augMap.has(element);
}
hiddenUpdate() {
if (this._hiddenUpdateTimer) {
return;
}
this._hiddenUpdateTimer = this._win().setTimeout(() => {
delete this._hiddenUpdateTimer;
this._hiddenUpdate();
}, 250);
}
setActive(modalizer) {
const userId = modalizer === null || modalizer === void 0 ? void 0 : modalizer.userId;
const activeId = this.activeId;
if (activeId === userId) {
return;
}
this.activeId = userId;
if (activeId) {
const part = this._parts[activeId];
if (part) {
for (const id of Object.keys(part)) {
part[id].makeActive(false);
}
}
}
if (userId) {
const part = this._parts[userId];
if (part) {
for (const id of Object.keys(part)) {
part[id].makeActive(true);
}
}
}
this.currentIsOthersAccessible = modalizer === null || modalizer === void 0 ? void 0 : modalizer.getProps().isOthersAccessible;
this.hiddenUpdate();
}
focus(elementFromModalizer, noFocusFirst, noFocusDefault) {
const ctx = RootAPI.getTabsterContext(this._tabster, elementFromModalizer);
const modalizer = ctx === null || ctx === void 0 ? void 0 : ctx.modalizer;
if (modalizer) {
this.setActive(modalizer);
const props = modalizer.getProps();
const modalizerRoot = modalizer.getElement();
if (modalizerRoot) {
if (noFocusFirst === undefined) {
noFocusFirst = props.isNoFocusFirst;
}
if (!noFocusFirst && this._tabster.keyboardNavigation.isNavigatingWithKeyboard() && this._tabster.focusedElement.focusFirst({
container: modalizerRoot
})) {
return true;
}
if (noFocusDefault === undefined) {
noFocusDefault = props.isNoFocusDefault;
}
if (!noFocusDefault && this._tabster.focusedElement.focusDefault(modalizerRoot)) {
return true;
}
this._tabster.focusedElement.resetFocus(modalizerRoot);
}
} else if (process.env.NODE_ENV === 'development') {
console.error("Element is not in Modalizer.", elementFromModalizer);
}
return false;
}
acceptElement(element, state) {
var _a;
const modalizerUserId = state.modalizerUserId;
const currentModalizer = (_a = state.currentCtx) === null || _a === void 0 ? void 0 : _a.modalizer;
if (modalizerUserId) {
for (const e of this.activeElements) {
const el = e.get();
if (el && (dom.nodeContains(element, el) || el === element)) {
// We have a part of currently active modalizer somewhere deeper in the DOM,
// skipping all other checks.
return NodeFilter.FILTER_SKIP;
}
}
}
const ret = modalizerUserId === (currentModalizer === null || currentModalizer === void 0 ? void 0 : currentModalizer.userId) || !modalizerUserId && (currentModalizer === null || currentModalizer === void 0 ? void 0 : currentModalizer.getProps().isAlwaysAccessible) ? undefined : NodeFilter.FILTER_SKIP;
if (ret !== undefined) {
state.skippedFocusable = true;
}
return ret;
}
_hiddenUpdate() {
var _a;
const tabster = this._tabster;
const body = tabster.getWindow().document.body;
const activeId = this.activeId;
const parts = this._parts;
const visibleElements = [];
const hiddenElements = [];
const alwaysAccessibleSelector = this._alwaysAccessibleSelector;
const alwaysAccessibleElements = alwaysAccessibleSelector ? Array.from(dom.querySelectorAll(body, alwaysAccessibleSelector)) : [];
const activeModalizerElements = [];
for (const userId of Object.keys(parts)) {
const modalizerParts = parts[userId];
for (const id of Object.keys(modalizerParts)) {
const modalizer = modalizerParts[id];
const el = modalizer.getElement();
const props = modalizer.getProps();
const isAlwaysAccessible = props.isAlwaysAccessible;
if (el) {
if (userId === activeId) {
activeModalizerElements.push(el);
if (!this.currentIsOthersAccessible) {
visibleElements.push(el);
}
} else if (isAlwaysAccessible) {
alwaysAccessibleElements.push(el);
} else {
hiddenElements.push(el);
}
}
}
}
const augmentedMap = this._augMap;
const allVisibleElements = visibleElements.length > 0 ? [...visibleElements, ...alwaysAccessibleElements] : undefined;
const newAugmented = [];
const newAugmentedMap = new WeakMap();
const toggle = (element, hide) => {
var _a;
const tagName = element.tagName;
if (tagName === "SCRIPT" || tagName === "STYLE") {
return;
}
let isAugmented = false;
if (augmentedMap.has(element)) {
if (hide) {
isAugmented = true;
} else {
augmentedMap.delete(element);
augmentAttribute(tabster, element, _ariaHidden);
}
} else if (hide && !((_a = this._accessibleCheck) === null || _a === void 0 ? void 0 : _a.call(this, element, activeModalizerElements)) && augmentAttribute(tabster, element, _ariaHidden, "true")) {
augmentedMap.set(element, true);
isAugmented = true;
}
if (isAugmented) {
newAugmented.push(new WeakHTMLElement(tabster.getWindow, element));
newAugmentedMap.set(element, true);
}
};
const walk = element => {
var _a;
for (let el = dom.getFirstElementChild(element); el; el = dom.getNextElementSibling(el)) {
let skip = false;
let containsModalizer = false;
let containedByModalizer = false;
if (allVisibleElements) {
const elParent = tabster.getParent(el);
for (const c of allVisibleElements) {
if (el === c) {
skip = true;
break;
}
if (dom.nodeContains(el, c)) {
containsModalizer = true;
break;
} else if (dom.nodeContains(c, elParent)) {
// tabster.getParent() could be provided by the application to
// handle, for example, virtual parents. Making sure, we are
// not setting aria-hidden on elements which are virtually
// inside modalizer.
containedByModalizer = true;
}
}
if (containsModalizer || ((_a = el.__tabsterElementFlags) === null || _a === void 0 ? void 0 : _a.noDirectAriaHidden)) {
walk(el);
} else if (!skip && !containedByModalizer) {
toggle(el, true);
}
} else {
toggle(el, false);
}
}
};
if (!allVisibleElements) {
alwaysAccessibleElements.forEach(e => toggle(e, false));
}
hiddenElements.forEach(e => toggle(e, true));
if (body) {
walk(body);
}
(_a = this._aug) === null || _a === void 0 ? void 0 : _a.map(e => e.get()).forEach(e => {
if (e && !newAugmentedMap.get(e)) {
toggle(e, false);
}
});
this._aug = newAugmented;
this._augMap = newAugmentedMap;
}
/**
* Called when an element is focused outside of an active modalizer.
* Attempts to pull focus back into the active modalizer
* @param outsideElement - An element being focused outside of the modalizer
*/
_restoreModalizerFocus(outsideElement) {
const ownerDocument = outsideElement === null || outsideElement === void 0 ? void 0 : outsideElement.ownerDocument;
if (!outsideElement || !ownerDocument) {
return;
}
const ctx = RootAPI.getTabsterContext(this._tabster, outsideElement);
const modalizer = ctx === null || ctx === void 0 ? void 0 : ctx.modalizer;
const activeId = this.activeId;
if (!modalizer && !activeId || modalizer && activeId === modalizer.userId) {
return;
}
const container = ctx === null || ctx === void 0 ? void 0 : ctx.root.getElement();
if (container) {
let toFocus = this._tabster.focusable.findFirst({
container,
useActiveModalizer: true
});
if (toFocus) {
if (outsideElement.compareDocumentPosition(toFocus) & document.DOCUMENT_POSITION_PRECEDING) {
toFocus = this._tabster.focusable.findLast({
container,
useActiveModalizer: true
});
if (!toFocus) {
// This only might mean that findFirst/findLast are buggy and inconsistent.
throw new Error("Something went wrong.");
}
}
this._tabster.focusedElement.focus(toFocus);
return;
}
} // Current Modalizer doesn't seem to have focusable elements.
// Blurring the currently focused element which is outside of the current Modalizer.
outsideElement.blur();
}
}
/*!
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
const _inputSelector = /*#__PURE__*/["input", "textarea", "*[contenteditable]"].join(", ");
class MoverDummyManager extends DummyInputManager {
constructor(element, tabster, getMemorized, sys) {
super(tabster, element, DummyInputManagerPriorities.Mover, sys);
this._onFocusDummyInput = dummyInput => {
var _a, _b;
const container = this._element.get();
const input = dummyInput.input;
if (container && input) {
const ctx = RootAPI.getTabsterContext(this._tabster, container);
let toFocus;
if (ctx) {
toFocus = (_a = FocusedElementState.findNextTabbable(this._tabster, ctx, undefined, input, undefined, !dummyInput.isFirst, true)) === null || _a === void 0 ? void 0 : _a.element;
}
const memorized = (_b = this._getMemorized()) === null || _b === void 0 ? void 0 : _b.get();
if (memorized && this._tabster.focusable.isFocusable(memorized)) {
toFocus = memorized;
}
if (toFocus) {
nativeFocus(toFocus);
}
}
};
this._tabster = tabster;
this._getMemorized = getMemorized;
this._setHandlers(this._onFocusDummyInput);
}
} // TypeScript enums produce depressing JavaScript code, so, we're just using
// a few old style constants here.
const _moverUpdateAdd = 1;
const _moverUpdateAttr = 2;
const _moverUpdateRemove = 3;
class Mover extends TabsterPart {
constructor(tabster, element, onDispose, props, sys) {
var _a;
super(tabster, element, props);
this._visible = {};
this._onIntersection = entries => {
for (const entry of entries) {
const el = entry.target;
const id = getElementUId(this._win, el);
let newVisibility;
let fullyVisible = this._fullyVisible;
if (entry.intersectionRatio >= 0.25) {
newVisibility = entry.intersectionRatio >= 0.75 ? Visibilities.Visible : Visibilities.PartiallyVisible;
if (newVisibility === Visibilities.Visible) {
fullyVisible = id;
}
} else {
newVisibility = Visibilities.Invisible;
}
if (this._visible[id] !== newVisibility) {
if (newVisibility === undefined) {
delete this._visible[id];
if (fullyVisible === id) {
delete this._fullyVisible;
}
} else {
this._visible[id] = newVisibility;
this._fullyVisible = fullyVisible;
}
const state = this.getState(el);
if (state) {
el.dispatchEvent(new MoverStateEvent(state));
}
}
}
};
this._win = tabster.getWindow;
this.visibilityTolerance = (_a = props.visibilityTolerance) !== null && _a !== void 0 ? _a : 0.8;
if (this._props.trackState || this._props.visibilityAware) {
this._intersectionObserver = new IntersectionObserver(this._onIntersection, {
threshold: [0, 0.25, 0.5, 0.75, 1]
});
this._observeState();
}
this._onDispose = onDispose;
const getMemorized = () => props.memorizeCurrent ? this._current : undefined;
if (!tabster.controlTab) {
this.dummyManager = new MoverDummyManager(this._element, tabster, getMemorized, sys);
}
}
dispose() {
var _a;
this._onDispose(this);
if (this._intersectionObserver) {
this._intersectionObserver.disconnect();
delete this._intersectionObserver;
}
delete this._current;
delete this._fullyVisible;
delete this._allElements;
delete this._updateQueue;
if (this._unobserve) {
this._unobserve();
delete this._unobserve;
}
const win = this._win();
if (this._setCurrentTimer) {
win.clearTimeout(this._setCurrentTimer);
delete this._setCurrentTimer;
}
if (this._updateTimer) {
win.clearTimeout(this._updateTimer);
delete this._updateTimer;
}
(_a = this.dummyManager) === null || _a === void 0 ? void 0 : _a.dispose();
delete this.dummyManager;
}
setCurrent(element) {
if (element) {
this._current = new WeakHTMLElement(this._win, element);
} else {
this._current = undefined;
}
if ((this._props.trackState || this._props.visibilityAware) && !this._setCurrentTimer) {
this._setCurrentTimer = this._win().setTimeout(() => {
var _a;
delete this._setCurrentTimer;
const changed = [];
if (this._current !== this._prevCurrent) {
changed.push(this._current);
changed.push(this._prevCurrent);
this._prevCurrent = this._current;
}
for (const weak of changed) {
const el = weak === null || weak === void 0 ? void 0 : weak.get();
if (el && ((_a = this._allElements) === null || _a === void 0 ? void 0 : _a.get(el)) === this) {
const props = this._props;
if (el && (props.visibilityAware !== undefined || props.trackState)) {
const state = this.getState(el);
if (state) {
el.dispatchEvent(new MoverStateEvent(state));
}
}
}
}
});
}
}
getCurrent() {
var _a;
return ((_a = this._current) === null || _a === void 0 ? void 0 : _a.get()) || null;
}
findNextTabbable(currentElement, referenceElement, isBackward, ignoreAccessibility) {
const container = this.getElement();
const currentIsDummy = container && getDummyInputContainer(currentElement) === container;
if (!container) {
return null;
}
let next = null;
let outOfDOMOrder = false;
let uncontrolled;
if (this._props.tabbable || currentIsDummy || currentElement && !dom.nodeContains(container, currentElement)) {
const findProps = {
currentElement,
referenceElement,
container,
ignoreAccessibility,
useActiveModalizer: true
};
const findPropsOut = {};
next = this._tabster.focusable[isBackward ? "findPrev" : "findNext"](findProps, findPropsOut);
outOfDOMOrder = !!findPropsOut.outOfDOMOrder;
uncontrolled = findPropsOut.uncontrolled;
}
return {
element: next,
uncontrolled,
outOfDOMOrder
};
}
acceptElement(element, state) {
var _a, _b;
if (!FocusedElementState.isTabbing) {
return ((_a = state.currentCtx) === null || _a === void 0 ? void 0 : _a.excludedFromMover) ? NodeFilter.FILTER_REJECT : undefined;
}
const {
memorizeCurrent,
visibilityAware,
hasDefault = true
} = this._props;
const moverElement = this.getElement();
if (moverElement && (memorizeCurrent || visibilityAware || hasDefault) && (!dom.nodeContains(moverElement, state.from) || getDummyInputContainer(state.from) === moverElement)) {
let found;
if (memorizeCurrent) {
const current = (_b = this._current) === null || _b === void 0 ? void 0 : _b.get();
if (current && state.acceptCondition(current)) {
found = current;
}
}
if (!found && hasDefault) {
found = this._tabster.focusable.findDefault({
container: moverElement,
useActiveModalizer: true
});
}
if (!found && visibilityAware) {
found = this._tabster.focusable.findElement({
container: moverElement,
useActiveModalizer: true,
isBackward: state.isBackward,
acceptCondition: el => {
var _a;
const id = getElementUId(this._win, el);
const visibility = this._visible[id];
return moverElement !== el && !!((_a = this._allElements) === null || _a === void 0 ? void 0 : _a.get(el)) && state.acceptCondition(el) && (visibility === Visibilities.Visible || visibility === Visibilities.PartiallyVisible && (visibilityAware === Visibilities.PartiallyVisible || !this._fullyVisible));
}
});
}
if (found) {
state.found = true;
state.foundElement = found;
state.rejectElementsFrom = moverElement;
state.skippedFocusable = true;
return NodeFilter.FILTER_ACCEPT;
}
}
return undefined;
}
_observeState() {
const element = this.getElement();
if (this._unobserve || !element || typeof MutationObserver === "undefined") {
return;
}
const win = this._win();
const allElements = this._allElements = new WeakMap();
const tabsterFocusable = this._tabster.focusable;
let updateQueue = this._updateQueue = [];
const observer = dom.createMutationObserver(mutations => {
for (const mutation of mutations) {
const target = mutation.target;
const removed = mutation.removedNodes;
const added = mutation.addedNodes;
if (mutation.type === "attributes") {
if (mutation.attributeName === "tabindex") {
updateQueue.push({
element: target,
type: _moverUpdateAttr
});
}
} else {
for (let i = 0; i < removed.length; i++) {
updateQueue.push({
element: removed[i],
type: _moverUpdateRemove
});
}
for (let i = 0; i < added.length; i++) {
updateQueue.push({
element: added[i],
type: _moverUpdateAdd
});
}
}
}
requestUpdate();
});
const setElement = (element, remove) => {
var _a, _b;
const current = allElements.get(element);
if (current && remove) {
(_a = this._intersectionObserver) === null || _a === void 0 ? void 0 : _a.unobserve(element);
allElements.delete(element);
}
if (!current && !remove) {
allElements.set(element, this);
(_b = this._intersectionObserver) === null || _b === void 0 ? void 0 : _b.observe(element);
}
};
const updateElement = element => {
const isFocusable = tabsterFocusable.isFocusable(element);
const current = allElements.get(element);
if (current) {
if (!isFocusable) {
setElement(element, true);
}
} else {
if (isFocusable) {
setElement(element);
}
}
};
const addNewElements = element => {
const {
mover
} = getMoverGroupper(element);
if (mover && mover !== this) {
if (mover.getElement() === element && tabsterFocusable.isFocusable(element)) {
setElement(element);
} else {
return;
}
}
const walker = createElementTreeWalker(win.document, element, node => {
const {
mover,
groupper
} = getMoverGroupper(node);
if (mover && mover !== this) {
return NodeFilter.FILTER_REJECT;
}
const groupperFirstFocusable = groupper === null || groupper === void 0 ? void 0 : groupper.getFirst(true);
if (groupper && groupper.getElement() !== node && groupperFirstFocusable && groupperFirstFocusable !== node) {
return NodeFilter.FILTER_REJECT;
}
if (tabsterFocusable.isFocusable(node)) {
setElement(node);
}
return NodeFilter.FILTER_SKIP;
});
if (walker) {
walker.currentNode = element;
while (walker.nextNode()) {
/* Iterating for the sake of calling processNode() callback. */
}
}
};
const removeWalk = element => {
const current = allElements.get(element);
if (current) {
setElement(element, true);
}
for (let el = dom.getFirstElementChild(element); el; el = dom.getNextElementSibling(el)) {
removeWalk(el);
}
};
const requestUpdate = () => {
if (!this._updateTimer && updateQueue.length) {
this._updateTimer = win.setTimeout(() => {
delete this._updateTimer;
for (const {
element,
type
} of updateQueue) {
switch (type) {
case _moverUpdateAttr:
updateElement(element);
break;
case _moverUpdateAdd:
addNewElements(element);
break;
case _moverUpdateRemove:
removeWalk(element);
break;
}
}
updateQueue = this._updateQueue = [];
}, 0);
}
};
const getMoverGroupper = element => {
const ret = {};
for (let el = element; el; el = dom.getParentElement(el)) {
const toe = getTabsterOnElement(this._tabster, el);
if (toe) {
if (toe.groupper && !ret.groupper) {
ret.groupper = toe.groupper;
}
if (toe.mover) {
ret.mover = toe.mover;
break;
}
}
}
return ret;
};
updateQueue.push({
element,
type: _moverUpdateAdd
});
requestUpdate();
observer.observe(element, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ["tabindex"]
});
this._unobserve = () => {
observer.disconnect();
};
}
getState(element) {
const id = getElementUId(this._win, element);
if (id in this._visible) {
const visibility = this._visible[id] || Visibilities.Invisible;
const isCurrent = this._current ? this._current.get() === element : undefined;
return {
isCurrent,
visibility
};
}
return undefined;
}
} // eslint-disable-next-line @typescript-eslint/no-unused-vars
/**
* Calculates distance between two rectangles.
*
* @param ax1 first rectangle left
* @param ay1 first rectangle top
* @param ax2 first rectangle right
* @param ay2 first rectangle bottom
* @param bx1 second rectangle left
* @param by1 second rectangle top
* @param bx2 second rectangle right
* @param by2 second rectangle bottom
* @returns number, shortest distance between the rectangles.
*/
function getDistance(ax1, ay1, ax2, ay2, bx1, by1, bx2, by2) {
const xDistance = ax2 < bx1 ? bx1 - ax2 : bx2 < ax1 ? ax1 - bx2 : 0;
const yDistance = ay2 < by1 ? by1 - ay2 : by2 < ay1 ? ay1 - by2 : 0;
return xDistance === 0 ? yDistance : yDistance === 0 ? xDistance : Math.sqrt(xDistance * xDistance + yDistance * yDistance);
}
class MoverAPI {
constructor(tabster, getWindow) {
this._init = () => {
const win = this._win();
win.addEventListener("keydown", this._onKeyDown, true);
win.addEventListener(MoverMoveFocusEventName, this._onMoveFocus);
win.addEventListener(MoverMemorizedElementEventName, this._onMemorizedElement);
this._tabster.focusedElement.subscribe(this._onFocus);
};
this._onMoverDispose = mover => {
delete this._movers[mover.id];
};
this._onFocus = element => {
var _a; // When something in the app gets focused, we are making sure that
// the relevant context Mover is aware of it.
// Looking for the relevant context Mover from the currently
// focused element parent, not from the element itself, because the
// Mover element itself cannot be its own current (but might be
// current for its parent Mover).
let currentFocusableElement = element;
let deepestFocusableElement = element;
for (let el = dom.getParentElement(element); el; el = dom.getParentElement(el)) {
// We go through all Movers up from the focused element and
// set their current element to the deepest focusable of that
// Mover.
const mover = (_a = getTabsterOnElement(this._tabster, el)) === null || _a === void 0 ? void 0 : _a.mover;
if (mover) {
mover.setCurrent(deepestFocusableElement);
currentFocusableElement = undefined;
}
if (!currentFocusableElement && this._tabster.focusable.isFocusable(el)) {
currentFocusableElement = deepestFocusableElement = el;
}
}
};
this._onKeyDown = async event => {
var _a;
if (this._ignoredInputTimer) {
this._win().clearTimeout(this._ignoredInputTimer);
delete this._ignoredInputTimer;
}
(_a = this._ignoredInputResolve) === null || _a === void 0 ? void 0 : _a.call(this, false); // Give a chance to other listeners to handle the event (for example,
// to scroll instead of moving focus).
if (event.ctrlKey || event.altKey || event.shiftKey || event.metaKey) {
return;
}
const key = event.key;
let moverKey;
if (key === Keys.ArrowDown) {
moverKey = MoverKeys.ArrowDown;
} else if (key === Keys.ArrowRight) {
moverKey = MoverKeys.ArrowRight;
} else if (key === Keys.ArrowUp) {
moverKey = MoverKeys.ArrowUp;
} else if (key === Keys.ArrowLeft) {
moverKey = MoverKeys.ArrowLeft;
} else if (key === Keys.PageDown) {
moverKey = MoverKeys.PageDown;
} else if (key === Keys.PageUp) {
moverKey = MoverKeys.PageUp;
} else if (key === Keys.Home) {
moverKey = MoverKeys.Home;
} else if (key === Keys.End) {
moverKey = MoverKeys.End;
}
if (!moverKey) {
return;
}
const focused = this._tabster.focusedElement.getFocusedElement();
if (!focused || (await this._isIgnoredInput(focused, key))) {
return;
}
this._moveFocus(focused, moverKey, event);
};
this._onMoveFocus = e => {
var _a;
const element = e.composedPath()[0];
const key = (_a = e.detail) === null || _a === void 0 ? void 0 : _a.key;
if (element && key !== undefined && !e.defaultPrevented) {
this._moveFocus(element, key);
e.stopImmediatePropagation();
}
};
this._onMemorizedElement = e => {
var _a;
const target = e.composedPath()[0];
let memorizedElement = (_a = e.detail) === null || _a === void 0 ? void 0 : _a.memorizedElement;
if (target) {
const ctx = RootAPI.getTabsterContext(this._tabster, target);
const mover = ctx === null || ctx === void 0 ? void 0 : ctx.mover;
if (mover) {
if (memorizedElement && !dom.nodeContains(mover.getElement(), memorizedElement)) {
memorizedElement = undefined;
}
mover.setCurrent(memorizedElement);
e.stopImmediatePropagation();
}
}
};
this._tabster = tabster;
this._win = getWindow;
this._movers = {};
tabster.queueInit(this._init);
}
dispose() {
var _a;
const win = this._win();
this._tabster.focusedElement.unsubscribe(this._onFocus);
(_a = this._ignoredInputResolve) === null || _a === void 0 ? void 0 : _a.call(this, false);
if (this._ignoredInputTimer) {
win.clearTimeout(this._ignoredInputTimer);
delete this._ignoredInputTimer;
}
win.removeEventListener("keydown", this._onKeyDown, true);
win.removeEventListener(MoverMoveFocusEventName, this._onMoveFocus);
win.removeEventListener(MoverMemorizedElementEventName, this._onMemorizedElement);
Object.keys(this._movers).forEach(moverId => {
if (this._movers[moverId]) {
this._movers[moverId].dispose();
delete this._movers[moverId];
}
});
}
createMover(element, props, sys) {
if (process.env.NODE_ENV === 'development') ;
const newMover = new Mover(this._tabster, element, this._onMoverDispose, props, sys);
this._movers[newMover.id] = newMover;
return newMover;
}
moveFocus(fromElement, key) {
return this._moveFocus(fromElement, key);
}
_moveFocus(fromElement, key, relatedEvent) {
var _a, _b;
const tabster = this._tabster;
const ctx = RootAPI.getTabsterContext(tabster, fromElement, {
checkRtl: true
});
if (!ctx || !ctx.mover || ctx.excludedFromMover || relatedEvent && ctx.ignoreKeydown(relatedEvent)) {
return null;
}
const mover = ctx.mover;
const container = mover.getElement();
if (ctx.groupperBeforeMover) {
const groupper = ctx.groupper;
if (groupper && !groupper.isActive(true)) {
// For the cases when we have Mover/Active Groupper/Inactive Groupper, we need to check
// the grouppers between the current element and the current mover.
for (let el = dom.getParentElement(groupper.getElement()); el && el !== container; el = dom.getParentElement(el)) {
if ((_b = (_a = getTabsterOnElement(tabster, el)) === null || _a === void 0 ? void 0 : _a.groupper) === null || _b === void 0 ? void 0 : _b.isActive(true)) {
return null;
}
}
} else {
return null;
}
}
if (!container) {
return null;
}
const focusable = tabster.focusable;
const moverProps = mover.getProps();
const direction = moverProps.direction || MoverDirections.Both;
const isBoth = direction === MoverDirections.Both;
const isVertical = isBoth || direction === MoverDirections.Vertical;
const isHorizontal = isBoth || direction === MoverDirections.Horizontal;
const isGridLinear = direction === MoverDirections.GridLinear;
const isGrid = isGridLinear || direction === MoverDirections.Grid;
const isCyclic = moverProps.cyclic;
let next;
let scrollIntoViewArg;
let focusedElementRect;
let focusedElementX1 = 0;
let focusedElementX2 = 0;
if (isGrid) {
focusedElementRect = fromElement.getBoundingClientRect();
focusedElementX1 = Math.ceil(focusedElementRect.left);
focusedElementX2 = Math.floor(focusedElementRect.right);
}
if (ctx.rtl) {
if (key === MoverKeys.ArrowRight) {
key = MoverKeys.ArrowLeft;
} else if (key === MoverKeys.ArrowLeft) {
key = MoverKeys.ArrowRight;
}
}
if (key === MoverKeys.ArrowDown && isVertical || key === MoverKeys.ArrowRight && (isHorizontal || isGrid)) {
next = focusable.findNext({
currentElement: fromElement,
container,
useActiveModalizer: true
});
if (next && isGrid) {
const nextElementX1 = Math.ceil(next.getBoundingClientRect().left);
if (!isGridLinear && focusedElementX2 > nextElementX1) {
next = undefined;
}
} else if (!next && isCyclic) {
next = focusable.findFirst({
container,
useActiveModalizer: true
});
}
} else if (key === MoverKeys.ArrowUp && isVertical || key === MoverKeys.ArrowLeft && (isHorizontal || isGrid)) {
next = focusable.findPrev({
currentElement: fromElement,
container,
useActiveModalizer: true
});
if (next && isGrid) {
const nextElementX2 = Math.floor(next.getBoundingClientRect().right);
if (!isGridLinear && nextElementX2 > focusedElementX1) {
next = undefined;
}
} else if (!next && isCyclic) {
next = focusable.findLast({
container,
useActiveModalizer: true
});
}
} else if (key === MoverKeys.Home) {
if (isGrid) {
focusable.findElement({
container,
currentElement: fromElement,
useActiveModalizer: true,
isBackward: true,
acceptCondition: el => {
var _a;
if (!focusable.isFocusable(el)) {
return false;
}
const nextElementX1 = Math.ceil((_a = el.getBoundingClientRect().left) !== null && _a !== void 0 ? _a : 0);
if (el !== fromElement && focusedElementX1 <= nextElementX1) {
return true;
}
next = el;
return false;
}
});
} else {
next = focusable.findFirst({
container,
useActiveModalizer: true
});
}
} else if (key === MoverKeys.End) {
if (isGrid) {
focusable.findElement({
container,
currentElement: fromElement,
useActiveModalizer: true,
acceptCondition: el => {
var _a;
if (!focusable.isFocusable(el)) {
return false;
}
const nextElementX1 = Math.ceil((_a = el.getBoundingClientRect().left) !== null && _a !== void 0 ? _a : 0);
if (el !== fromElement && focusedElementX1 >= nextElementX1) {
return true;
}
next = el;
return false;
}
});
} else {
next = focusable.findLast({
container,
useActiveModalizer: true
});
}
} else if (key === MoverKeys.PageUp) {
focusable.findElement({
currentElement: fromElement,
container,
useActiveModalizer: true,
isBackward: true,
acceptCondition: el => {
if (!focusable.isFocusable(el)) {
return false;
}
if (isElementVerticallyVisibleInContainer(this._win, el, mover.visibilityTolerance)) {
next = el;
return false;
}
return true;
}
}); // will be on the first column move forward and preserve previous column
if (isGrid && next) {
const firstColumnX1 = Math.ceil(next.getBoundingClientRect().left);
focusable.findElement({
currentElement: next,
container,
useActiveModalizer: true,
acceptCondition: el => {
if (!focusable.isFocusable(el)) {
return false;
}
const nextElementX1 = Math.ceil(el.getBoundingClientRect().left);
if (focusedElementX1 < nextElementX1 || firstColumnX1 >= nextElementX1) {
return true;
}
next = el;
return false;
}
});
}
scrollIntoViewArg = false;
} else if (key === MoverKeys.PageDown) {
focusable.findElement({
currentElement: fromElement,
container,
useActiveModalizer: true,
acceptCondition: el => {
if (!focusable.isFocusable(el)) {
return false;
}
if (isElementVerticallyVisibleInContainer(this._win, el, mover.visibilityTolerance)) {
next = el;
return false;
}
return true;
}
}); // will be on the last column move backwards and preserve previous column
if (isGrid && next) {
const lastColumnX1 = Math.ceil(next.getBoundingClientRect().left);
focusable.findElement({
currentElement: next,
container,
useActiveModalizer: true,
isBackward: true,
acceptCondition: el => {
if (!focusable.isFocusable(el)) {
return false;
}
const nextElementX1 = Math.ceil(el.getBoundingClientRect().left);
if (focusedElementX1 > nextElementX1 || lastColumnX1 <= nextElementX1) {
return true;
}
next = el;
return false;
}
});
}
scrollIntoViewArg = true;
} else if (isGrid) {
const isBackward = key === MoverKeys.ArrowUp;
const ax1 = focusedElementX1; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const ay1 = Math.ceil(focusedElementRect.top);
const ax2 = focusedElementX2; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const ay2 = Math.floor(focusedElementRect.bottom);
let targetElement;
let lastDistance;
let lastIntersection = 0;
focusable.findAll({
container,
currentElement: fromElement,
isBackward,
onElement: el => {
// Find element which has maximal intersection with the focused element horizontally,
// or the closest one.
const rect = el.getBoundingClientRect();
const bx1 = Math.ceil(rect.left);
const by1 = Math.ceil(rect.top);
const bx2 = Math.floor(rect.right);
const by2 = Math.floor(rect.bottom);
if (isBackward && ay1 < by2 || !isBackward && ay2 > by1) {
// Only consider elements which are below/above curretly focused.
return true;
}
const xIntersectionWidth = Math.ceil(Math.min(ax2, bx2)) - Math.floor(Math.max(ax1, bx1));
const minWidth = Math.ceil(Math.min(ax2 - ax1, bx2 - bx1));
if (xIntersectionWidth > 0 && minWidth >= xIntersectionWidth) {
// Element intersects with the focused element on X axis.
const intersection = xIntersectionWidth / minWidth;
if (intersection > lastIntersection) {
targetElement = el;
lastIntersection = intersection;
}
} else if (lastIntersection === 0) {
// If we didn't have intersection, try just the closest one.
const distance = getDistance(ax1, ay1, ax2, ay2, bx1, by1, bx2, by2);
if (lastDistance === undefined || distance < lastDistance) {
lastDistance = distance;
targetElement = el;
}
} else if (lastIntersection > 0) {
// Element doesn't intersect, but we had intersection already, stop search.
return false;
}
return true;
}
});
next = targetElement;
}
if (next && (!relatedEvent || relatedEvent && container.dispatchEvent(new TabsterMoveFocusEvent({
by: "mover",
owner: container,
next,
relatedEvent
})))) {
if (scrollIntoViewArg !== undefined) {
scrollIntoView(this._win, next, scrollIntoViewArg);
}
if (relatedEvent) {
relatedEvent.preventDefault();
relatedEvent.stopImmediatePropagation();
}
nativeFocus(next);
return next;
}
return null;
}
async _isIgnoredInput(element, key) {
if (element.getAttribute("aria-expanded") === "true" && element.hasAttribute("aria-activedescendant")) {
// It is likely a combobox with expanded options and arrow keys are
// controlled by it.
return true;
}
if (matchesSelector(element, _inputSelector)) {
let selectionStart = 0;
let selectionEnd = 0;
let textLength = 0;
let asyncRet;
if (element.tagName === "INPUT" || element.tagName === "TEXTAREA") {
const type = element.type;
const value = element.value;
textLength = (value || "").length;
if (type === "email" || type === "number") {
// For these types Chromium doesn't provide selectionStart and selectionEnd.
// Hence the ugly workaround to find if the caret position is changed with
// the keypress.
// TODO: Have a look at range, week, time, time, date, datetime-local.
if (textLength) {
const selection = dom.getSelection(element);
if (selection) {
const initialLength = selection.toString().length;
const isBackward = key === Keys.ArrowLeft || key === Keys.ArrowUp;
selection.modify("extend", isBackward ? "backward" : "forward", "character");
if (initialLength !== selection.toString().length) {
// The caret is moved, so, we're not on the edge of the value.
// Restore original selection.
selection.modify("extend", isBackward ? "forward" : "backward", "character");
return true;
} else {
textLength = 0;
}
}
}
} else {
const selStart = element.selectionStart;
if (selStart === null) {
// Do not ignore not text editable inputs like checkboxes and radios (but ignore hidden).
return type === "hidden";
}
selectionStart = selStart || 0;
selectionEnd = element.selectionEnd || 0;
}
} else if (element.contentEditable === "true") {
asyncRet = new (getPromise(this._win))(resolve => {
this._ignoredInputResolve = value => {
delete this._ignoredInputResolve;
resolve(value);
};
const win = this._win();
if (this._ignoredInputTimer) {
win.clearTimeout(this._ignoredInputTimer);
}
const {
anchorNode: prevAnchorNode,
focusNode: prevFocusNode,
anchorOffset: prevAnchorOffset,
focusOffset: prevFocusOffset
} = dom.getSelection(element) || {}; // Get selection gives incorrect value if we call it syncronously onKeyDown.
this._ignoredInputTimer = win.setTimeout(() => {
var _a, _b, _c;
delete this._ignoredInputTimer;
const {
anchorNode,
focusNode,
anchorOffset,
focusOffset
} = dom.getSelection(element) || {};
if (anchorNode !== prevAnchorNode || focusNode !== prevFocusNode || anchorOffset !== prevAnchorOffset || focusOffset !== prevFocusOffset) {
(_a = this._ignoredInputResolve) === null || _a === void 0 ? void 0 : _a.call(this, false);
return;
}
selectionStart = anchorOffset || 0;
selectionEnd = focusOffset || 0;
textLength = ((_b = element.textContent) === null || _b === void 0 ? void 0 : _b.length) || 0;
if (anchorNode && focusNode) {
if (dom.nodeContains(element, anchorNode) && dom.nodeContains(element, focusNode)) {
if (anchorNode !== element) {
let anchorFound = false;
const addOffsets = node => {
if (node === anchorNode) {
anchorFound = true;
} else if (node === focusNode) {
return true;
}
const nodeText = node.textContent;
if (nodeText && !dom.getFirstChild(node)) {
const len = nodeText.length;
if (anchorFound) {
if (focusNode !== anchorNode) {
selectionEnd += len;
}
} else {
selectionStart += len;
selectionEnd += len;
}
}
let stop = false;
for (let e = dom.getFirstChild(node); e && !stop; e = e.nextSibling) {
stop = addOffsets(e);
}
return stop;
};
addOffsets(element);
}
}
}
(_c = this._ignoredInputResolve) === null || _c === void 0 ? void 0 : _c.call(this, true);
}, 0);
});
}
if (asyncRet && !(await asyncRet)) {
return true;
}
if (selectionStart !== selectionEnd) {
return true;
}
if (selectionStart > 0 && (key === Keys.ArrowLeft || key === Keys.ArrowUp || key === Keys.Home)) {
return true;
}
if (selectionStart < textLength && (key === Keys.ArrowRight || key === Keys.ArrowDown || key === Keys.End)) {
return true;
}
}
return false;
}
}
/*!
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
function observeMutations(doc, tabster, updateTabsterByAttribute, syncState) {
if (typeof MutationObserver === "undefined") {
return () => {
/* Noop */
};
}
const getWindow = tabster.getWindow;
let elementByUId;
const onMutation = mutations => {
var _a, _b, _c, _d, _e;
const removedNodes = new Set();
for (const mutation of mutations) {
const target = mutation.target;
const removed = mutation.removedNodes;
const added = mutation.addedNodes;
if (mutation.type === "attributes") {
if (mutation.attributeName === TABSTER_ATTRIBUTE_NAME) {
// removedNodes helps to make sure we are not recreating things
// for the removed elements.
// For some reason, if we do removeChild() and setAttribute() on the
// removed child in the same tick, both the child removal and the attribute
// change will be present in the mutation records. And the attribute change
// will follow the child removal.
// So, we remember the removed nodes and ignore attribute changes for them.
if (!removedNodes.has(target)) {
updateTabsterByAttribute(tabster, target);
}
}
} else {
for (let i = 0; i < removed.length; i++) {
const removedNode = removed[i];
removedNodes.add(removedNode);
updateTabsterElements(removedNode, true);
(_b = (_a = tabster._dummyObserver).domChanged) === null || _b === void 0 ? void 0 : _b.call(_a, target);
}
for (let i = 0; i < added.length; i++) {
updateTabsterElements(added[i]);
(_d = (_c = tabster._dummyObserver).domChanged) === null || _d === void 0 ? void 0 : _d.call(_c, target);
}
}
}
removedNodes.clear();
(_e = tabster.modalizer) === null || _e === void 0 ? void 0 : _e.hiddenUpdate();
};
function updateTabsterElements(node, removed) {
if (!elementByUId) {
elementByUId = getInstanceContext(getWindow).elementByUId;
}
processNode(node, removed);
const walker = createElementTreeWalker(doc, node, element => {
return processNode(element, removed);
});
if (walker) {
while (walker.nextNode()) {
/* Iterating for the sake of calling processNode() callback. */
}
}
}
function processNode(element, removed) {
var _a;
if (!element.getAttribute) {
// It might actually be a text node.
return NodeFilter.FILTER_SKIP;
}
const uid = element.__tabsterElementUID;
if (uid && elementByUId) {
if (removed) {
delete elementByUId[uid];
} else {
(_a = elementByUId[uid]) !== null && _a !== void 0 ? _a : elementByUId[uid] = new WeakHTMLElement(getWindow, element);
}
}
if (getTabsterOnElement(tabster, element) || element.hasAttribute(TABSTER_ATTRIBUTE_NAME)) {
updateTabsterByAttribute(tabster, element, removed);
}
return NodeFilter.FILTER_SKIP;
}
const observer = dom.createMutationObserver(onMutation);
if (syncState) {
updateTabsterElements(getWindow().document.body);
}
observer.observe(doc, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: [TABSTER_ATTRIBUTE_NAME]
});
return () => {
observer.disconnect();
};
}
/*!
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
const _conditionCheckTimeout = 100;
class ObservedElementAPI extends Subscribable {
constructor(tabster) {
super();
this._waiting = {};
this._lastRequestFocusId = 0;
this._observedById = {};
this._observedByName = {};
this._currentRequestTimestamp = 0;
this._onFocus = e => {
if (e) {
const current = this._currentRequest;
if (current) {
const delta = Date.now() - this._currentRequestTimestamp;
const settleTime = 300;
if (delta >= settleTime) {
// Giving some time for the focus to settle before
// automatically cancelling the current request on focus change.
delete this._currentRequest;
current.cancel();
}
}
}
};
this.onObservedElementUpdate = element => {
var _a;
const observed = (_a = getTabsterOnElement(this._tabster, element)) === null || _a === void 0 ? void 0 : _a.observed;
const uid = getElementUId(this._win, element);
let info = this._observedById[uid];
if (observed && documentContains(element.ownerDocument, element)) {
if (!info) {
info = this._observedById[uid] = {
element: new WeakHTMLElement(this._win, element)
};
}
observed.names.sort();
const observedNames = observed.names;
const prevNames = info.prevNames; // prevNames are already sorted
if (this._isObservedNamesUpdated(observedNames, prevNames)) {
if (prevNames) {
prevNames.forEach(prevName => {
const obn = this._observedByName[prevName];
if (obn && obn[uid]) {
if (Object.keys(obn).length > 1) {
delete obn[uid];
} else {
delete this._observedByName[prevName];
}
}
});
}
info.prevNames = observedNames;
}
observedNames.forEach(observedName => {
let obn = this._observedByName[observedName];
if (!obn) {
obn = this._observedByName[observedName] = {};
} // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
obn[uid] = info;
this._waitConditional(observedName);
});
} else if (info) {
const prevNames = info.prevNames;
if (prevNames) {
prevNames.forEach(prevName => {
const obn = this._observedByName[prevName];
if (obn && obn[uid]) {
if (Object.keys(obn).length > 1) {
delete obn[uid];
} else {
delete this._observedByName[prevName];
}
}
});
}
delete this._observedById[uid];
}
};
this._tabster = tabster;
this._win = tabster.getWindow;
tabster.queueInit(() => {
this._tabster.focusedElement.subscribe(this._onFocus);
});
}
dispose() {
this._tabster.focusedElement.unsubscribe(this._onFocus);
for (const key of Object.keys(this._waiting)) {
this._rejectWaiting(key);
}
this._observedById = {};
this._observedByName = {};
}
_rejectWaiting(key, shouldResolve) {
const w = this._waiting[key];
if (w) {
const win = this._win();
if (w.timer) {
win.clearTimeout(w.timer);
}
if (w.conditionTimer) {
win.clearTimeout(w.conditionTimer);
}
if (!shouldResolve && w.reject) {
w.reject();
} else if (shouldResolve && w.resolve) {
w.resolve(null);
}
delete this._waiting[key];
}
}
_isObservedNamesUpdated(cur, prev) {
if (!prev || cur.length !== prev.length) {
return true;
}
for (let i = 0; i < cur.length; ++i) {
if (cur[i] !== prev[i]) {
return true;
}
}
return false;
}
/**
* Returns existing element by observed name
*
* @param observedName An observed name
* @param accessibility Optionally, return only if the element is accessible or focusable
* @returns HTMLElement | null
*/
getElement(observedName, accessibility) {
const o = this._observedByName[observedName];
if (o) {
for (const uid of Object.keys(o)) {
let el = o[uid].element.get() || null;
if (el) {
if (accessibility === ObservedElementAccessibilities.Accessible && !this._tabster.focusable.isAccessible(el) || accessibility === ObservedElementAccessibilities.Focusable && !this._tabster.focusable.isFocusable(el, true)) {
el = null;
}
} else {
delete o[uid];
delete this._observedById[uid];
}
return el;
}
}
return null;
}
/**
* Waits for the element to appear in the DOM and returns it.
*
* @param observedName An observed name
* @param timeout Wait no longer than this timeout
* @param accessibility Optionally, wait for the element to also become accessible or focusable before returning it
* @returns Promise<HTMLElement | null>
*/
waitElement(observedName, timeout, accessibility) {
const el = this.getElement(observedName, accessibility);
if (el) {
return {
result: getPromise(this._win).resolve(el),
cancel: () => {
/**/
}
};
}
let prefix;
if (accessibility === ObservedElementAccessibilities.Accessible) {
prefix = "a";
} else if (accessibility === ObservedElementAccessibilities.Focusable) {
prefix = "f";
} else {
prefix = "_";
}
const key = prefix + observedName;
let w = this._waiting[key];
if (w && w.request) {
return w.request;
}
w = this._waiting[key] = {
timer: this._win().setTimeout(() => {
if (w.conditionTimer) {
this._win().clearTimeout(w.conditionTimer);
}
delete this._waiting[key];
if (w.resolve) {
w.resolve(null);
}
}, timeout)
};
const promise = new (getPromise(this._win))((resolve, reject) => {
w.resolve = resolve;
w.reject = reject;
});
w.request = {
result: promise,
cancel: () => {
this._rejectWaiting(key, true);
}
};
if (accessibility && this.getElement(observedName)) {
// If the observed element is alread in DOM, but not accessible yet,
// we need to run the wait logic.
this._waitConditional(observedName);
}
return w.request;
}
requestFocus(observedName, timeout) {
const requestId = ++this._lastRequestFocusId;
const currentRequestFocus = this._currentRequest;
if (currentRequestFocus) {
currentRequestFocus.cancel();
}
const request = this.waitElement(observedName, timeout, ObservedElementAccessibilities.Focusable);
this._currentRequest = request;
this._currentRequestTimestamp = Date.now();
request.result.finally(() => {
if (this._currentRequest === request) {
delete this._currentRequest;
}
});
return {
result: request.result.then(element => this._lastRequestFocusId === requestId && element ? this._tabster.focusedElement.focus(element, true) : false),
cancel: () => {
request.cancel();
}
};
}
_waitConditional(observedName) {
const waitingElementKey = "_" + observedName;
const waitingAccessibleElementKey = "a" + observedName;
const waitingFocusableElementKey = "f" + observedName;
const waitingElement = this._waiting[waitingElementKey];
const waitingAccessibleElement = this._waiting[waitingAccessibleElementKey];
const waitingFocusableElement = this._waiting[waitingFocusableElementKey];
const win = this._win();
const resolve = (element, key, waiting, accessibility) => {
var _a;
const observed = (_a = getTabsterOnElement(this._tabster, element)) === null || _a === void 0 ? void 0 : _a.observed;
if (!observed || !observed.names.includes(observedName)) {
return;
}
if (waiting.timer) {
win.clearTimeout(waiting.timer);
}
delete this._waiting[key];
if (waiting.resolve) {
waiting.resolve(element);
}
this.trigger(element, {
names: [observedName],
details: observed.details,
accessibility
});
};
if (waitingElement) {
const element = this.getElement(observedName);
if (element && documentContains(element.ownerDocument, element)) {
resolve(element, waitingElementKey, waitingElement, ObservedElementAccessibilities.Any);
}
}
if (waitingAccessibleElement && !waitingAccessibleElement.conditionTimer) {
const resolveAccessible = () => {
const element = this.getElement(observedName);
if (element && documentContains(element.ownerDocument, element) && this._tabster.focusable.isAccessible(element)) {
resolve(element, waitingAccessibleElementKey, waitingAccessibleElement, ObservedElementAccessibilities.Accessible);
} else {
waitingAccessibleElement.conditionTimer = win.setTimeout(resolveAccessible, _conditionCheckTimeout);
}
};
resolveAccessible();
}
if (waitingFocusableElement && !waitingFocusableElement.conditionTimer) {
const resolveFocusable = () => {
const element = this.getElement(observedName);
if (element && documentContains(element.ownerDocument, element) && this._tabster.focusable.isFocusable(element, true)) {
resolve(element, waitingFocusableElementKey, waitingFocusableElement, ObservedElementAccessibilities.Focusable);
} else {
waitingFocusableElement.conditionTimer = win.setTimeout(resolveFocusable, _conditionCheckTimeout);
}
};
resolveFocusable();
}
}
}
/*!
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
const defaultProps = {
areaClass: "tabster-focus-outline-area",
outlineClass: "tabster-focus-outline",
outlineColor: "#ff4500",
outlineWidth: 2,
zIndex: 2147483647
};
let _props = defaultProps;
class OutlinePosition {
constructor(left, top, right, bottom) {
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
equalsTo(other) {
return this.left === other.left && this.top === other.top && this.right === other.right && this.bottom === other.bottom;
}
clone() {
return new OutlinePosition(this.left, this.top, this.right, this.bottom);
}
}
class OutlineAPI {
constructor(tabster) {
this._isVisible = false;
this._allOutlineElements = [];
this._init = () => {
this._tabster.keyboardNavigation.subscribe(this._onKeyboardNavigationStateChanged);
this._tabster.focusedElement.subscribe(this._onFocus);
const win = this._win();
win.addEventListener("scroll", this._onScroll, true); // Capture!
if (this._fullScreenEventName) {
win.document.addEventListener(this._fullScreenEventName, this._onFullScreenChanged);
}
};
this._onFullScreenChanged = e => {
if (!this._fullScreenElementName || !e.target) {
return;
}
const target = e.target.body || e.target;
const outlineElements = this._getDOM(target);
if (target.ownerDocument && outlineElements) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const fsElement = target.ownerDocument[this._fullScreenElementName];
if (fsElement) {
fsElement.appendChild(outlineElements.container);
this._fullScreenElement = fsElement;
} else {
target.ownerDocument.body.appendChild(outlineElements.container);
this._fullScreenElement = undefined;
}
}
};
this._onKeyboardNavigationStateChanged = () => {
this._onFocus(this._tabster.focusedElement.getFocusedElement());
};
this._onFocus = e => {
if (!this._updateElement(e) && this._isVisible) {
this._setVisibility(false);
}
};
this._onScroll = e => {
if (!this._outlinedElement || !OutlineAPI._isParentChild(e.target, this._outlinedElement)) {
return;
}
this._curPos = undefined;
this._setOutlinePosition();
};
this._tabster = tabster;
this._win = tabster.getWindow;
tabster.queueInit(this._init);
if (typeof document !== "undefined") {
if ("onfullscreenchange" in document) {
this._fullScreenEventName = "fullscreenchange";
this._fullScreenElementName = "fullscreenElement";
} else if ("onwebkitfullscreenchange" in document) {
this._fullScreenEventName = "webkitfullscreenchange";
this._fullScreenElementName = "webkitFullscreenElement";
} else if ("onmozfullscreenchange" in document) {
this._fullScreenEventName = "mozfullscreenchange";
this._fullScreenElementName = "mozFullScreenElement";
} else if ("onmsfullscreenchange" in document) {
this._fullScreenEventName = "msfullscreenchange";
this._fullScreenElementName = "msFullscreenElement";
}
}
}
setup(props) {
_props = { ..._props,
...props
};
const win = this._win();
if (!win.__tabsterOutline) {
win.__tabsterOutline = {};
}
if (!win.__tabsterOutline.style) {
win.__tabsterOutline.style = appendStyles(win.document, _props);
}
if (!props || !props.areaClass) {
win.document.body.classList.add(defaultProps.areaClass);
} else {
win.document.body.classList.remove(defaultProps.areaClass);
}
}
dispose() {
const win = this._win();
if (this._updateTimer) {
win.clearTimeout(this._updateTimer);
this._updateTimer = undefined;
}
this._tabster.keyboardNavigation.unsubscribe(this._onKeyboardNavigationStateChanged);
this._tabster.focusedElement.unsubscribe(this._onFocus);
win.removeEventListener("scroll", this._onScroll, true);
if (this._fullScreenEventName) {
win.document.removeEventListener(this._fullScreenEventName, this._onFullScreenChanged);
}
this._allOutlineElements.forEach(outlineElements => this._removeDOM(outlineElements.container));
this._allOutlineElements = [];
delete this._outlinedElement;
delete this._curPos;
delete this._curOutlineElements;
delete this._fullScreenElement;
}
_shouldShowCustomOutline(element) {
const tabsterOnElement = getTabsterOnElement(this._tabster, element);
if (tabsterOnElement && tabsterOnElement.outline && tabsterOnElement.outline.isIgnored) {
return false;
}
for (let i = element; i; i = i.parentElement) {
if (i.classList && i.classList.contains(_props.areaClass)) {
return true;
}
}
return false;
}
_updateElement(e) {
this._outlinedElement = undefined;
if (this._updateTimer) {
this._win().clearTimeout(this._updateTimer);
this._updateTimer = undefined;
}
this._curPos = undefined;
if (!this._tabster.keyboardNavigation.isNavigatingWithKeyboard()) {
return false;
}
if (e) {
// TODO: It's hard (and not necessary) to come up with every possible
// condition when there should be no outline, it's better to add an
// API to customize the ignores.
if (e.tagName === "INPUT") {
const inputType = e.type;
const outlinedInputTypes = {
button: true,
checkbox: true,
file: true,
image: true,
radio: true,
range: true,
reset: true,
submit: true
};
if (!(inputType in outlinedInputTypes)) {
return false;
}
} else if (e.tagName === "TEXTAREA" || e.contentEditable === "true" || e.tagName === "IFRAME") {
return false;
}
if (!this._shouldShowCustomOutline(e)) {
return false;
}
if (this._tabster.keyboardNavigation.isNavigatingWithKeyboard()) {
this._outlinedElement = e;
this._updateOutline();
}
return true;
}
return false;
}
_updateOutline() {
this._setOutlinePosition();
if (this._updateTimer) {
this._win().clearTimeout(this._updateTimer);
this._updateTimer = undefined;
}
if (!this._outlinedElement) {
return;
}
this._updateTimer = this._win().setTimeout(() => {
this._updateTimer = undefined;
this._updateOutline();
}, 30);
}
_setVisibility(visible) {
this._isVisible = visible;
if (this._curOutlineElements) {
if (visible) {
this._curOutlineElements.container.classList.add(`${_props.outlineClass}_visible`);
} else {
this._curOutlineElements.container.classList.remove(`${_props.outlineClass}_visible`);
this._curPos = undefined;
}
}
}
_setOutlinePosition() {
if (!this._outlinedElement) {
return;
}
let boundingRect = getBoundingRect(this._win, this._outlinedElement);
const position = new OutlinePosition(boundingRect.left, boundingRect.top, boundingRect.right, boundingRect.bottom);
if (this._curPos && position.equalsTo(this._curPos)) {
return;
}
const outlineElements = this._getDOM(this._outlinedElement);
const win = this._outlinedElement.ownerDocument && this._outlinedElement.ownerDocument.defaultView;
if (!outlineElements || !win) {
return;
}
if (this._curOutlineElements !== outlineElements) {
this._setVisibility(false);
this._curOutlineElements = outlineElements;
}
this._curPos = position;
const p = position.clone();
let hasAbsolutePositionedParent = false;
let hasFixedPositionedParent = false;
const container = outlineElements.container;
const scrollingElement = container && container.ownerDocument && container.ownerDocument.scrollingElement;
if (!scrollingElement) {
return;
}
for (let parent = this._outlinedElement.parentElement; parent && parent.nodeType === Node.ELEMENT_NODE; parent = parent.parentElement) {
// The element might be partially visible within its scrollable parent,
// reduce the bounding rect if this is the case.
if (parent === this._fullScreenElement) {
break;
}
boundingRect = getBoundingRect(this._win, parent);
const win = parent.ownerDocument && parent.ownerDocument.defaultView;
if (!win) {
return;
}
const computedStyle = win.getComputedStyle(parent);
const position = computedStyle.position;
if (position === "absolute") {
hasAbsolutePositionedParent = true;
} else if (position === "fixed" || position === "sticky") {
hasFixedPositionedParent = true;
}
if (computedStyle.overflow === "visible") {
continue;
}
if (!hasAbsolutePositionedParent && !hasFixedPositionedParent || computedStyle.overflow === "hidden") {
if (boundingRect.left > p.left) {
p.left = boundingRect.left;
}
if (boundingRect.top > p.top) {
p.top = boundingRect.top;
}
if (boundingRect.right < p.right) {
p.right = boundingRect.right;
}
if (boundingRect.bottom < p.bottom) {
p.bottom = boundingRect.bottom;
}
}
}
const allRect = getBoundingRect(this._win, scrollingElement);
const allWidth = allRect.left + allRect.right;
const allHeight = allRect.top + allRect.bottom;
const ow = _props.outlineWidth;
p.left = p.left > ow ? p.left - ow : 0;
p.top = p.top > ow ? p.top - ow : 0;
p.right = p.right < allWidth - ow ? p.right + ow : allWidth;
p.bottom = p.bottom < allHeight - ow ? p.bottom + ow : allHeight;
const width = p.right - p.left;
const height = p.bottom - p.top;
if (width > ow * 2 && height > ow * 2) {
const leftBorderNode = outlineElements.left;
const topBorderNode = outlineElements.top;
const rightBorderNode = outlineElements.right;
const bottomBorderNode = outlineElements.bottom;
const sx = this._fullScreenElement || hasFixedPositionedParent ? 0 : win.pageXOffset;
const sy = this._fullScreenElement || hasFixedPositionedParent ? 0 : win.pageYOffset;
container.style.position = hasFixedPositionedParent ? "fixed" : "absolute";
container.style.background = _props.outlineColor;
leftBorderNode.style.width = rightBorderNode.style.width = topBorderNode.style.height = bottomBorderNode.style.height = _props.outlineWidth + "px";
leftBorderNode.style.left = topBorderNode.style.left = bottomBorderNode.style.left = p.left + sx + "px";
rightBorderNode.style.left = p.left + sx + width - ow + "px";
leftBorderNode.style.top = rightBorderNode.style.top = topBorderNode.style.top = p.top + sy + "px";
bottomBorderNode.style.top = p.top + sy + height - ow + "px";
leftBorderNode.style.height = rightBorderNode.style.height = height + "px";
topBorderNode.style.width = bottomBorderNode.style.width = width + "px";
this._setVisibility(true);
} else {
this._setVisibility(false);
}
}
_getDOM(contextElement) {
const doc = contextElement.ownerDocument;
const win = doc && doc.defaultView;
if (!doc || !win || !win.__tabsterOutline) {
return undefined;
}
if (!win.__tabsterOutline.style) {
win.__tabsterOutline.style = appendStyles(doc, _props);
}
if (!win.__tabsterOutline.elements) {
const outlineElements = {
container: doc.createElement("div"),
left: doc.createElement("div"),
top: doc.createElement("div"),
right: doc.createElement("div"),
bottom: doc.createElement("div")
};
outlineElements.container.className = _props.outlineClass;
outlineElements.left.className = `${_props.outlineClass}__left`;
outlineElements.top.className = `${_props.outlineClass}__top`;
outlineElements.right.className = `${_props.outlineClass}__right`;
outlineElements.bottom.className = `${_props.outlineClass}__bottom`;
outlineElements.container.appendChild(outlineElements.left);
outlineElements.container.appendChild(outlineElements.top);
outlineElements.container.appendChild(outlineElements.right);
outlineElements.container.appendChild(outlineElements.bottom);
doc.body.appendChild(outlineElements.container);
win.__tabsterOutline.elements = outlineElements; // TODO: Make a garbage collector to remove the references
// to the outlines which are nowhere in the DOM anymore.
this._allOutlineElements.push(outlineElements);
}
return win.__tabsterOutline.elements;
}
_removeDOM(contextElement) {
const win = contextElement.ownerDocument && contextElement.ownerDocument.defaultView;
const outline = win && win.__tabsterOutline;
if (!outline) {
return;
}
if (outline.style && outline.style.parentNode) {
outline.style.parentNode.removeChild(outline.style);
delete outline.style;
}
const outlineElements = outline && outline.elements;
if (outlineElements) {
if (outlineElements.container.parentNode) {
outlineElements.container.parentNode.removeChild(outlineElements.container);
}
delete outline.elements;
}
}
static _isParentChild(parent, child) {
return child === parent || // tslint:disable-next-line:no-bitwise
!!(parent.compareDocumentPosition(child) & document.DOCUMENT_POSITION_CONTAINED_BY);
}
}
function appendStyles(document, props) {
const style = document.createElement("style");
style.type = "text/css";
style.appendChild(document.createTextNode(getOutlineStyles(props)));
document.head.appendChild(style);
return style;
}
function getOutlineStyles(props) {
return `
.${props.areaClass} *, .${props.areaClass} *:focus {
outline: none !important;
}
.${props.outlineClass} {
display: none;
position: absolute;
width: 0;
height: 0;
left: 0;
top: 0;
z-index: ${props.zIndex};
}
.${props.outlineClass}.${props.outlineClass}_visible {
display: block;
}
.${props.outlineClass}__left,
.${props.outlineClass}__top,
.${props.outlineClass}__right,
.${props.outlineClass}__bottom {
position: absolute;
background: inherit;
}`;
}
/*!
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
/**
* Allows default or user focus behaviour on the DOM subtree
* i.e. Tabster will not control focus events within an uncontrolled area
*/
class UncontrolledAPI {
constructor(isUncontrolledCompletely) {
this._isUncontrolledCompletely = isUncontrolledCompletely;
}
isUncontrolledCompletely(element, completely) {
var _a;
const isUncontrolledCompletely = (_a = this._isUncontrolledCompletely) === null || _a === void 0 ? void 0 : _a.call(this, element, completely); // If isUncontrolledCompletely callback is not defined or returns undefined, then the default
// behaviour is to return the uncontrolled.completely value from the element.
return isUncontrolledCompletely === undefined ? completely : isUncontrolledCompletely;
}
}
/*!
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
const HISOTRY_DEPTH = 10;
class Restorer extends TabsterPart {
constructor(tabster, element, props) {
var _a;
super(tabster, element, props);
this._hasFocus = false;
this._onFocusOut = e => {
var _a;
const element = (_a = this._element) === null || _a === void 0 ? void 0 : _a.get();
if (element && e.relatedTarget === null) {
element.dispatchEvent(new RestorerRestoreFocusEvent());
}
if (element && !dom.nodeContains(element, e.relatedTarget)) {
this._hasFocus = false;
}
};
this._onFocusIn = () => {
this._hasFocus = true;
};
if (this._props.type === RestorerTypes.Source) {
const element = (_a = this._element) === null || _a === void 0 ? void 0 : _a.get();
element === null || element === void 0 ? void 0 : element.addEventListener("focusout", this._onFocusOut);
element === null || element === void 0 ? void 0 : element.addEventListener("focusin", this._onFocusIn); // set hasFocus when the instance is created, in case focus has already moved within it
this._hasFocus = dom.nodeContains(element, element && dom.getActiveElement(element.ownerDocument));
}
}
dispose() {
var _a;
if (this._props.type === RestorerTypes.Source) {
const element = (_a = this._element) === null || _a === void 0 ? void 0 : _a.get();
element === null || element === void 0 ? void 0 : element.removeEventListener("focusout", this._onFocusOut);
element === null || element === void 0 ? void 0 : element.removeEventListener("focusin", this._onFocusIn);
if (this._hasFocus) {
const doc = this._tabster.getWindow().document;
doc.body.dispatchEvent(new RestorerRestoreFocusEvent());
}
}
}
}
class RestorerAPI {
constructor(tabster) {
this._history = [];
this._onRestoreFocus = e => {
this._focusedElementState.cancelAsyncFocus(AsyncFocusSources.Restorer); // ShadowDOM will have shadowRoot as e.target.
const target = e.composedPath()[0];
if (target) {
this._focusedElementState.requestAsyncFocus(AsyncFocusSources.Restorer, () => this._restoreFocus(target), 0);
}
};
this._onFocusIn = element => {
var _a;
if (!element) {
return;
}
const tabsterAttribute = getTabsterOnElement(this._tabster, element);
if (((_a = tabsterAttribute === null || tabsterAttribute === void 0 ? void 0 : tabsterAttribute.restorer) === null || _a === void 0 ? void 0 : _a.getProps().type) !== RestorerTypes.Target) {
return;
}
this._addToHistory(element);
};
this._restoreFocus = source => {
var _a; // don't restore focus if focus isn't lost to body
const doc = this._getWindow().document;
if (dom.getActiveElement(doc) !== doc.body) {
return;
}
if ( // clicking on any empty space focuses body - this is can be a false positive
!this._keyboardNavState.isNavigatingWithKeyboard() && // Source no longer exists on DOM - always restore focus
dom.nodeContains(doc.body, source)) {
return;
}
let weakElement = this._history.pop();
while (weakElement && !dom.nodeContains(doc.body, dom.getParentElement(weakElement.get()))) {
weakElement = this._history.pop();
}
(_a = weakElement === null || weakElement === void 0 ? void 0 : weakElement.get()) === null || _a === void 0 ? void 0 : _a.focus();
};
this._tabster = tabster;
this._getWindow = tabster.getWindow;
this._getWindow().addEventListener(RestorerRestoreFocusEventName, this._onRestoreFocus);
this._keyboardNavState = tabster.keyboardNavigation;
this._focusedElementState = tabster.focusedElement;
this._focusedElementState.subscribe(this._onFocusIn);
}
dispose() {
const win = this._getWindow();
this._focusedElementState.unsubscribe(this._onFocusIn);
this._focusedElementState.cancelAsyncFocus(AsyncFocusSources.Restorer);
win.removeEventListener(RestorerRestoreFocusEventName, this._onRestoreFocus);
}
_addToHistory(element) {
var _a; // Don't duplicate the top of history
if (((_a = this._history[this._history.length - 1]) === null || _a === void 0 ? void 0 : _a.get()) === element) {
return;
}
if (this._history.length > HISOTRY_DEPTH) {
this._history.shift();
}
this._history.push(new WeakHTMLElement(this._getWindow, element));
}
createRestorer(element, props) {
const restorer = new Restorer(this._tabster, element, props); // Focus might already be on a restorer target when it gets created so the focusin will not do anything
if (props.type === RestorerTypes.Target && dom.getActiveElement(element.ownerDocument) === element) {
this._addToHistory(element);
}
return restorer;
}
}
/*!
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
function getActiveElement(doc) {
var _a;
let activeElement = doc.activeElement;
while ((_a = activeElement === null || activeElement === void 0 ? void 0 : activeElement.shadowRoot) === null || _a === void 0 ? void 0 : _a.activeElement) {
activeElement = activeElement.shadowRoot.activeElement;
}
return activeElement;
}
function nodeContains(node, otherNode) {
var _a, _b;
if (!node || !otherNode) {
return false;
}
let currentNode = otherNode;
while (currentNode) {
if (currentNode === node) {
return true;
}
if (typeof currentNode.assignedElements !== "function" && ((_a = currentNode.assignedSlot) === null || _a === void 0 ? void 0 : _a.parentNode)) {
// Element is slotted
currentNode = (_b = currentNode.assignedSlot) === null || _b === void 0 ? void 0 : _b.parentNode;
} else if (currentNode.nodeType === document.DOCUMENT_FRAGMENT_NODE) {
// Element is in shadow root
currentNode = currentNode.host;
} else {
currentNode = currentNode.parentNode;
}
}
return false;
}
function getParentNode(node) {
if (!node) {
return null;
}
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE && node.host) {
return node.host;
}
return node.parentNode;
}
function getParentElement(element) {
for (let parentNode = getParentNode(element); parentNode; parentNode = getParentNode(parentNode)) {
if (parentNode.nodeType === Node.ELEMENT_NODE) {
return parentNode;
}
}
return null;
}
function getFirstChild(node) {
if (!node) {
return null;
}
if (node.shadowRoot) {
const child = getFirstChild(node.shadowRoot);
if (child) {
return child;
} // If the attached shadowRoot has no children, just try ordinary children,
// that might come after.
}
return node.firstChild;
}
function getLastChild$1(node) {
if (!node) {
return null;
}
if (!node.lastChild && node.shadowRoot) {
return getLastChild$1(node.shadowRoot);
}
return node.lastChild;
}
function getNextSibling(node) {
return (node === null || node === void 0 ? void 0 : node.nextSibling) || null;
}
function getPreviousSibling(node) {
var _a;
if (!node) {
return null;
}
let sibling = node.previousSibling;
if (!sibling && ((_a = node.parentElement) === null || _a === void 0 ? void 0 : _a.shadowRoot)) {
sibling = getLastChild$1(node.parentElement.shadowRoot);
}
return sibling;
}
function getFirstElementChild(element) {
let child = getFirstChild(element);
while (child && child.nodeType !== Node.ELEMENT_NODE) {
child = getNextSibling(child);
}
return child;
}
function getLastElementChild(element) {
let child = getLastChild$1(element);
while (child && child.nodeType !== Node.ELEMENT_NODE) {
child = getPreviousSibling(child);
}
return child;
}
function getNextElementSibling(element) {
let sibling = getNextSibling(element);
while (sibling && sibling.nodeType !== Node.ELEMENT_NODE) {
sibling = getNextSibling(sibling);
}
return sibling;
}
function getPreviousElementSibling(element) {
let sibling = getPreviousSibling(element);
while (sibling && sibling.nodeType !== Node.ELEMENT_NODE) {
sibling = getPreviousSibling(sibling);
}
return sibling;
}
function appendChild(parent, child) {
const shadowRoot = parent.shadowRoot;
return shadowRoot ? shadowRoot.appendChild(child) : parent.appendChild(child);
}
function insertBefore(parent, child, referenceChild) {
const shadowRoot = parent.shadowRoot;
return shadowRoot ? shadowRoot.insertBefore(child, referenceChild) : parent.insertBefore(child, referenceChild);
}
function getSelection(ref) {
var _a;
const win = (_a = ref.ownerDocument) === null || _a === void 0 ? void 0 : _a.defaultView;
if (!win) {
return null;
}
for (let el = ref; el; el = el.parentNode) {
if (el.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
const tmp = el; // ShadowRoot.getSelection() exists only in Chrome.
if (tmp.getSelection) {
return tmp.getSelection() || null;
}
break;
}
}
return win.getSelection() || null;
}
function getElementsByName(referenceElement, name) {
for (let el = referenceElement; el; el = el.parentNode) {
if (el.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
// Shadow root doesn't have getElementsByName()...
return el.querySelectorAll(`[name=${name}]`);
}
}
return referenceElement.ownerDocument.getElementsByName(name);
}
/*!
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
function getLastChild(container) {
let lastChild = null;
for (let i = getLastElementChild(container); i; i = getLastElementChild(i)) {
lastChild = i;
}
return lastChild || undefined;
}
class ShadowTreeWalker {
constructor(doc, root, whatToShow, filter) {
this._walkerStack = [];
this._currentSetFor = new Set();
this._acceptNode = node => {
var _a;
if (node.nodeType === Node.ELEMENT_NODE) {
const shadowRoot = node.shadowRoot;
if (shadowRoot) {
const walker = this._doc.createTreeWalker(shadowRoot, this.whatToShow, {
acceptNode: this._acceptNode
});
this._walkerStack.unshift(walker);
return NodeFilter.FILTER_ACCEPT;
} else {
if (typeof this.filter === "function") {
return this.filter(node);
} else if ((_a = this.filter) === null || _a === void 0 ? void 0 : _a.acceptNode) {
return this.filter.acceptNode(node);
} else if (this.filter === null) {
return NodeFilter.FILTER_ACCEPT;
}
}
}
return NodeFilter.FILTER_SKIP;
};
this._doc = doc;
this.root = root;
this.filter = filter !== null && filter !== void 0 ? filter : null;
this.whatToShow = whatToShow !== null && whatToShow !== void 0 ? whatToShow : NodeFilter.SHOW_ALL;
this._currentNode = root;
this._walkerStack.unshift(doc.createTreeWalker(root, whatToShow, this._acceptNode));
const shadowRoot = root.shadowRoot;
if (shadowRoot) {
const walker = this._doc.createTreeWalker(shadowRoot, this.whatToShow, {
acceptNode: this._acceptNode
});
this._walkerStack.unshift(walker);
}
}
get currentNode() {
return this._currentNode;
}
set currentNode(node) {
if (!nodeContains(this.root, node)) {
throw new Error("Cannot set currentNode to a node that is not contained by the root node.");
}
const walkers = [];
let curNode = node;
let currentWalkerCurrentNode = node;
this._currentNode = node;
while (curNode && curNode !== this.root) {
if (curNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
const shadowRoot = curNode;
const walker = this._doc.createTreeWalker(shadowRoot, this.whatToShow, {
acceptNode: this._acceptNode
});
walkers.push(walker);
walker.currentNode = currentWalkerCurrentNode;
this._currentSetFor.add(walker);
curNode = currentWalkerCurrentNode = shadowRoot.host;
} else {
curNode = curNode.parentNode;
}
}
const walker = this._doc.createTreeWalker(this.root, this.whatToShow, {
acceptNode: this._acceptNode
});
walkers.push(walker);
walker.currentNode = currentWalkerCurrentNode;
this._currentSetFor.add(walker);
this._walkerStack = walkers;
}
firstChild() {
if (process.env.NODE_ENV === 'development') {
throw new Error("Method not implemented.");
}
return null;
}
lastChild() {
if (process.env.NODE_ENV === 'development') {
throw new Error("Method not implemented.");
}
return null;
}
nextNode() {
var _a;
const nextNode = this._walkerStack[0].nextNode();
if (nextNode) {
const shadowRoot = nextNode.shadowRoot;
if (shadowRoot) {
let nodeResult;
if (typeof this.filter === "function") {
nodeResult = this.filter(nextNode);
} else if ((_a = this.filter) === null || _a === void 0 ? void 0 : _a.acceptNode) {
nodeResult = this.filter.acceptNode(nextNode);
}
if (nodeResult === NodeFilter.FILTER_ACCEPT) {
return nextNode;
} // _acceptNode should have added new walker for this shadow,
// go in recursively.
return this.nextNode();
}
return nextNode;
} else {
if (this._walkerStack.length > 1) {
this._walkerStack.shift();
return this.nextNode();
} else {
return null;
}
}
}
previousNode() {
var _a, _b;
const currentWalker = this._walkerStack[0];
if (currentWalker.currentNode === currentWalker.root) {
if (this._currentSetFor.has(currentWalker)) {
this._currentSetFor.delete(currentWalker);
if (this._walkerStack.length > 1) {
this._walkerStack.shift();
return this.previousNode();
} else {
return null;
}
}
const lastChild = getLastChild(currentWalker.root);
if (lastChild) {
currentWalker.currentNode = lastChild;
let nodeResult;
if (typeof this.filter === "function") {
nodeResult = this.filter(lastChild);
} else if ((_a = this.filter) === null || _a === void 0 ? void 0 : _a.acceptNode) {
nodeResult = this.filter.acceptNode(lastChild);
}
if (nodeResult === NodeFilter.FILTER_ACCEPT) {
return lastChild;
}
}
}
const previousNode = currentWalker.previousNode();
if (previousNode) {
const shadowRoot = previousNode.shadowRoot;
if (shadowRoot) {
let nodeResult;
if (typeof this.filter === "function") {
nodeResult = this.filter(previousNode);
} else if ((_b = this.filter) === null || _b === void 0 ? void 0 : _b.acceptNode) {
nodeResult = this.filter.acceptNode(previousNode);
}
if (nodeResult === NodeFilter.FILTER_ACCEPT) {
return previousNode;
} // _acceptNode should have added new walker for this shadow,
// go in recursively.
return this.previousNode();
}
return previousNode;
} else {
if (this._walkerStack.length > 1) {
this._walkerStack.shift();
return this.previousNode();
} else {
return null;
}
}
}
nextSibling() {
if (process.env.NODE_ENV === 'development') {
throw new Error("Method not implemented.");
}
return null;
}
previousSibling() {
if (process.env.NODE_ENV === 'development') {
throw new Error("Method not implemented.");
}
return null;
}
parentNode() {
if (process.env.NODE_ENV === 'development') {
throw new Error("Method not implemented.");
}
return null;
}
}
function createShadowTreeWalker(doc, root, whatToShow, filter) {
return new ShadowTreeWalker(doc, root, whatToShow, filter);
}
/*!
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
class ShadowMutationObserver {
static _overrideAttachShadow(win) {
const origAttachShadow = win.Element.prototype.attachShadow;
if (origAttachShadow.__origAttachShadow) {
return;
}
Element.prototype.attachShadow = function (options) {
const shadowRoot = origAttachShadow.call(this, options);
for (const shadowObserver of ShadowMutationObserver._shadowObservers) {
shadowObserver._addSubObserver(shadowRoot);
}
return shadowRoot;
};
Element.prototype.attachShadow.__origAttachShadow = origAttachShadow;
}
constructor(callback) {
this._isObserving = false;
this._callbackWrapper = (mutations, observer) => {
for (const mutation of mutations) {
if (mutation.type === "childList") {
const removed = mutation.removedNodes;
const added = mutation.addedNodes;
for (let i = 0; i < removed.length; i++) {
this._walkShadows(removed[i], true);
}
for (let i = 0; i < added.length; i++) {
this._walkShadows(added[i]);
}
}
}
this._callback(mutations, observer);
};
this._callback = callback;
this._observer = new MutationObserver(this._callbackWrapper);
this._subObservers = new Map();
}
_addSubObserver(shadowRoot) {
if (!this._options || !this._callback || this._subObservers.has(shadowRoot)) {
return;
}
if (this._options.subtree && nodeContains(this._root, shadowRoot)) {
const subObserver = new MutationObserver(this._callbackWrapper);
this._subObservers.set(shadowRoot, subObserver);
if (this._isObserving) {
subObserver.observe(shadowRoot, this._options);
}
this._walkShadows(shadowRoot);
}
}
disconnect() {
this._isObserving = false;
delete this._options;
ShadowMutationObserver._shadowObservers.delete(this);
for (const subObserver of this._subObservers.values()) {
subObserver.disconnect();
}
this._subObservers.clear();
this._observer.disconnect();
}
observe(target, options) {
const doc = target.nodeType === Node.DOCUMENT_NODE ? target : target.ownerDocument;
const win = doc === null || doc === void 0 ? void 0 : doc.defaultView;
if (!doc || !win) {
return;
}
ShadowMutationObserver._overrideAttachShadow(win);
ShadowMutationObserver._shadowObservers.add(this);
this._root = target;
this._options = options;
this._isObserving = true;
this._observer.observe(target, options);
this._walkShadows(target);
}
_walkShadows(target, remove) {
const doc = target.nodeType === Node.DOCUMENT_NODE ? target : target.ownerDocument;
if (!doc) {
return;
}
if (target === doc) {
target = doc.body;
} else {
const shadowRoot = target.shadowRoot;
if (shadowRoot) {
this._addSubObserver(shadowRoot);
return;
}
}
const walker = doc.createTreeWalker(target, NodeFilter.SHOW_ELEMENT, {
acceptNode: node => {
if (node.nodeType === Node.ELEMENT_NODE) {
if (remove) {
const subObserver = this._subObservers.get(node);
if (subObserver) {
subObserver.disconnect();
this._subObservers.delete(node);
}
} else {
const shadowRoot = node.shadowRoot;
if (shadowRoot) {
this._addSubObserver(shadowRoot);
}
}
}
return NodeFilter.FILTER_SKIP;
}
});
walker.nextNode();
}
takeRecords() {
const records = this._observer.takeRecords();
for (const subObserver of this._subObservers.values()) {
records.push(...subObserver.takeRecords());
}
return records;
}
}
ShadowMutationObserver._shadowObservers = /*#__PURE__*/new Set();
function createShadowMutationObserver(callback) {
return new ShadowMutationObserver(callback);
}
/*!
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
function shadowQuerySelector(node, selector, all) {
// TODO: This is probably slow. Optimize to use each shadowRoot's querySelector/querySelectorAll
// instead of walking the tree.
const elements = [];
walk(node, selector);
return elements;
function walk(from, selector) {
let el = null;
const walker = document.createTreeWalker(from, NodeFilter.SHOW_ELEMENT, {
acceptNode: n => {
if (n.nodeType === Node.ELEMENT_NODE) {
if (n.matches(selector)) {
el = n;
elements.push(el);
return all ? NodeFilter.FILTER_SKIP : NodeFilter.FILTER_ACCEPT;
}
const shadowRoot = n.shadowRoot;
if (shadowRoot) {
walk(shadowRoot, selector);
return !all && elements.length ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
}
}
return NodeFilter.FILTER_SKIP;
}
});
walker.nextNode();
}
}
function querySelectorAll(node, selector) {
return shadowQuerySelector(node, selector, true);
}
function querySelector(node, selector) {
return shadowQuerySelector(node, selector, false)[0] || null;
}
function getElementById(doc, id) {
return querySelector(doc, "#" + id);
}
/*!
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
var shadowDOMAPI = /*#__PURE__*/Object.freeze({
__proto__: null,
createTreeWalker: createShadowTreeWalker,
createMutationObserver: createShadowMutationObserver,
appendChild: appendChild,
getActiveElement: getActiveElement,
getFirstChild: getFirstChild,
getFirstElementChild: getFirstElementChild,
getLastChild: getLastChild$1,
getLastElementChild: getLastElementChild,
getNextElementSibling: getNextElementSibling,
getNextSibling: getNextSibling,
getParentElement: getParentElement,
getParentNode: getParentNode,
getPreviousElementSibling: getPreviousElementSibling,
getPreviousSibling: getPreviousSibling,
getSelection: getSelection,
getElementsByName: getElementsByName,
insertBefore: insertBefore,
nodeContains: nodeContains,
getElementById: getElementById,
querySelector: querySelector,
querySelectorAll: querySelectorAll
});
/*!
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
class Tabster {
constructor(tabster) {
this.keyboardNavigation = tabster.keyboardNavigation;
this.focusedElement = tabster.focusedElement;
this.focusable = tabster.focusable;
this.root = tabster.root;
this.uncontrolled = tabster.uncontrolled;
this.core = tabster;
}
}
/**
* Extends Window to include an internal Tabster instance.
*/
class TabsterCore {
constructor(win, props) {
var _a, _b;
this._forgetMemorizedElements = [];
this._wrappers = new Set();
this._initQueue = [];
this._version = "8.0.1";
this._noop = false;
this.getWindow = () => {
if (!this._win) {
throw new Error("Using disposed Tabster.");
}
return this._win;
};
this._storage = createWeakMap(win);
this._win = win;
const getWindow = this.getWindow;
if (props === null || props === void 0 ? void 0 : props.DOMAPI) {
setDOMAPI({ ...props.DOMAPI
});
}
this.keyboardNavigation = new KeyboardNavigationState(getWindow);
this.focusedElement = new FocusedElementState(this, getWindow);
this.focusable = new FocusableAPI(this);
this.root = new RootAPI(this, props === null || props === void 0 ? void 0 : props.autoRoot);
this.uncontrolled = new UncontrolledAPI( // TODO: Remove checkUncontrolledTrappingFocus in the next major version.
(props === null || props === void 0 ? void 0 : props.checkUncontrolledCompletely) || (props === null || props === void 0 ? void 0 : props.checkUncontrolledTrappingFocus));
this.controlTab = (_a = props === null || props === void 0 ? void 0 : props.controlTab) !== null && _a !== void 0 ? _a : true;
this.rootDummyInputs = !!(props === null || props === void 0 ? void 0 : props.rootDummyInputs);
this._dummyObserver = new DummyInputObserver(getWindow);
this.getParent = (_b = props === null || props === void 0 ? void 0 : props.getParent) !== null && _b !== void 0 ? _b : dom.getParentNode;
this.internal = {
stopObserver: () => {
if (this._unobserve) {
this._unobserve();
delete this._unobserve;
}
},
resumeObserver: syncState => {
if (!this._unobserve) {
const doc = getWindow().document;
this._unobserve = observeMutations(doc, this, updateTabsterByAttribute, syncState);
}
}
};
startFakeWeakRefsCleanup(getWindow); // Gives a tick to the host app to initialize other tabster
// APIs before tabster starts observing attributes.
this.queueInit(() => {
this.internal.resumeObserver(true);
});
}
/**
* Merges external props with the current props. Not all
* props can/should be mergeable, so let's add more as we move on.
* @param props Tabster props
*/
_mergeProps(props) {
var _a;
if (!props) {
return;
}
this.getParent = (_a = props.getParent) !== null && _a !== void 0 ? _a : this.getParent;
}
createTabster(noRefCount, props) {
const wrapper = new Tabster(this);
if (!noRefCount) {
this._wrappers.add(wrapper);
}
this._mergeProps(props);
return wrapper;
}
disposeTabster(wrapper, allInstances) {
if (allInstances) {
this._wrappers.clear();
} else {
this._wrappers.delete(wrapper);
}
if (this._wrappers.size === 0) {
this.dispose();
}
}
dispose() {
var _a, _b, _c, _d, _e, _f, _g, _h;
this.internal.stopObserver();
const win = this._win;
win === null || win === void 0 ? void 0 : win.clearTimeout(this._initTimer);
delete this._initTimer;
this._initQueue = [];
this._forgetMemorizedElements = [];
if (win && this._forgetMemorizedTimer) {
win.clearTimeout(this._forgetMemorizedTimer);
delete this._forgetMemorizedTimer;
}
(_a = this.outline) === null || _a === void 0 ? void 0 : _a.dispose();
(_b = this.crossOrigin) === null || _b === void 0 ? void 0 : _b.dispose();
(_c = this.deloser) === null || _c === void 0 ? void 0 : _c.dispose();
(_d = this.groupper) === null || _d === void 0 ? void 0 : _d.dispose();
(_e = this.mover) === null || _e === void 0 ? void 0 : _e.dispose();
(_f = this.modalizer) === null || _f === void 0 ? void 0 : _f.dispose();
(_g = this.observedElement) === null || _g === void 0 ? void 0 : _g.dispose();
(_h = this.restorer) === null || _h === void 0 ? void 0 : _h.dispose();
this.keyboardNavigation.dispose();
this.focusable.dispose();
this.focusedElement.dispose();
this.root.dispose();
this._dummyObserver.dispose();
stopFakeWeakRefsCleanupAndClearStorage(this.getWindow);
clearElementCache(this.getWindow);
this._storage = new WeakMap();
this._wrappers.clear();
if (win) {
disposeInstanceContext(win);
delete win.__tabsterInstance;
delete this._win;
}
}
storageEntry(element, addremove) {
const storage = this._storage;
let entry = storage.get(element);
if (entry) {
if (addremove === false && Object.keys(entry).length === 0) {
storage.delete(element);
}
} else if (addremove === true) {
entry = {};
storage.set(element, entry);
}
return entry;
}
forceCleanup() {
if (!this._win) {
return;
}
this._forgetMemorizedElements.push(this._win.document.body);
if (this._forgetMemorizedTimer) {
return;
}
this._forgetMemorizedTimer = this._win.setTimeout(() => {
delete this._forgetMemorizedTimer;
for (let el = this._forgetMemorizedElements.shift(); el; el = this._forgetMemorizedElements.shift()) {
clearElementCache(this.getWindow, el);
FocusedElementState.forgetMemorized(this.focusedElement, el);
}
}, 0);
cleanupFakeWeakRefs(this.getWindow, true);
}
queueInit(callback) {
var _a;
if (!this._win) {
return;
}
this._initQueue.push(callback);
if (!this._initTimer) {
this._initTimer = (_a = this._win) === null || _a === void 0 ? void 0 : _a.setTimeout(() => {
delete this._initTimer;
this.drainInitQueue();
}, 0);
}
}
drainInitQueue() {
if (!this._win) {
return;
}
const queue = this._initQueue; // Resetting the queue before calling the callbacks to avoid recursion.
this._initQueue = [];
queue.forEach(callback => callback());
}
}
function forceCleanup(tabster) {
// The only legit case for calling this method is when you've completely removed
// the application DOM and not going to add the new one for a while.
const tabsterCore = tabster.core;
tabsterCore.forceCleanup();
}
/**
* Creates an instance of Tabster, returns the current window instance if it already exists.
*/
function createTabster(win, props) {
let tabster = getCurrentTabster(win);
if (tabster) {
return tabster.createTabster(false, props);
}
tabster = new TabsterCore(win, props);
win.__tabsterInstance = tabster;
return tabster.createTabster();
}
/**
* Returns an instance of Tabster if it was created before or null.
*/
function getTabster(win) {
const tabster = getCurrentTabster(win);
return tabster ? tabster.createTabster(true) : null;
}
function getShadowDOMAPI() {
return shadowDOMAPI;
}
/**
* Creates a new groupper instance or returns an existing one
* @param tabster Tabster instance
*/
function getGroupper(tabster) {
const tabsterCore = tabster.core;
if (!tabsterCore.groupper) {
tabsterCore.groupper = new GroupperAPI(tabsterCore, tabsterCore.getWindow);
}
return tabsterCore.groupper;
}
/**
* Creates a new mover instance or returns an existing one
* @param tabster Tabster instance
*/
function getMover(tabster) {
const tabsterCore = tabster.core;
if (!tabsterCore.mover) {
tabsterCore.mover = new MoverAPI(tabsterCore, tabsterCore.getWindow);
}
return tabsterCore.mover;
}
function getOutline(tabster) {
const tabsterCore = tabster.core;
if (!tabsterCore.outline) {
tabsterCore.outline = new OutlineAPI(tabsterCore);
}
return tabsterCore.outline;
}
/**
* Creates a new new deloser instance or returns an existing one
* @param tabster Tabster instance
* @param props Deloser props
*/
function getDeloser(tabster, props) {
const tabsterCore = tabster.core;
if (!tabsterCore.deloser) {
tabsterCore.deloser = new DeloserAPI(tabsterCore, props);
}
return tabsterCore.deloser;
}
/**
* Creates a new modalizer instance or returns an existing one
* @param tabster Tabster instance
* @param alwaysAccessibleSelector When Modalizer is active, we put
* aria-hidden to everything else to hide it from screen readers. This CSS
* selector allows to exclude some elements from this behaviour. For example,
* this could be used to exclude aria-live region with the application-wide
* status announcements.
* @param accessibleCheck An optional callback that will be called when
* active Modalizer wants to hide an element that doesn't belong to it from
* the screen readers by setting aria-hidden. Similar to alwaysAccessibleSelector
* but allows to address the elements programmatically rather than with a selector.
* If the callback returns true, the element will not receive aria-hidden.
*/
function getModalizer(tabster, // @deprecated use accessibleCheck.
alwaysAccessibleSelector, accessibleCheck) {
const tabsterCore = tabster.core;
if (!tabsterCore.modalizer) {
tabsterCore.modalizer = new ModalizerAPI(tabsterCore, alwaysAccessibleSelector, accessibleCheck);
}
return tabsterCore.modalizer;
}
function getObservedElement(tabster) {
const tabsterCore = tabster.core;
if (!tabsterCore.observedElement) {
tabsterCore.observedElement = new ObservedElementAPI(tabsterCore);
}
return tabsterCore.observedElement;
}
function getCrossOrigin(tabster) {
const tabsterCore = tabster.core;
if (!tabsterCore.crossOrigin) {
getDeloser(tabster);
getModalizer(tabster);
getMover(tabster);
getGroupper(tabster);
getOutline(tabster);
getObservedElement(tabster);
tabsterCore.crossOrigin = new CrossOriginAPI(tabsterCore);
}
return tabsterCore.crossOrigin;
}
function getInternal(tabster) {
const tabsterCore = tabster.core;
return tabsterCore.internal;
}
function getRestorer(tabster) {
const tabsterCore = tabster.core;
if (!tabsterCore.restorer) {
tabsterCore.restorer = new RestorerAPI(tabsterCore);
}
return tabsterCore.restorer;
}
function disposeTabster(tabster, allInstances) {
tabster.core.disposeTabster(tabster, allInstances);
}
/**
* Returns an instance of Tabster if it already exists on the window .
* @param win window instance that could contain an Tabster instance.
*/
function getCurrentTabster(win) {
return win.__tabsterInstance;
}
/**
* Allows to make Tabster non operational. Intended for performance debugging (and other
* kinds of debugging), you can switch Tabster off without changing the application code
* that consumes it.
* @param tabster a reference created by createTabster().
* @param noop true if you want to make Tabster noop, false if you want to turn it back.
*/
function makeNoOp(tabster, noop) {
const core = tabster.core;
if (core._noop !== noop) {
core._noop = noop;
const processNode = element => {
if (!element.getAttribute) {
return NodeFilter.FILTER_SKIP;
}
if (getTabsterOnElement(core, element) || element.hasAttribute(TABSTER_ATTRIBUTE_NAME)) {
updateTabsterByAttribute(core, element);
}
return NodeFilter.FILTER_SKIP;
};
const doc = core.getWindow().document;
const body = doc.body;
processNode(body);
const walker = createElementTreeWalker(doc, body, processNode);
if (walker) {
while (walker.nextNode()) {
/* Iterating for the sake of calling processNode() callback. */
}
}
}
}
function isNoOp(tabster) {
return tabster._noop;
}
/*!
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
var Types = /*#__PURE__*/Object.freeze({
__proto__: null
});
/*!
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
var EventsTypes = /*#__PURE__*/Object.freeze({
__proto__: null
});
/*!
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
/** @deprecated This function is obsolete, use native element.dispatchEvent(new GroupperMoveFocusEvent(...)). */
function dispatchGroupperMoveFocusEvent(target, action) {
return target.dispatchEvent(new GroupperMoveFocusEvent({
action
}));
}
/** @deprecated This function is obsolete, use native element.dispatchEvent(new MoverMoveFocusEvent(...)). */
function dispatchMoverMoveFocusEvent(target, key) {
return target.dispatchEvent(new MoverMoveFocusEvent({
key
}));
}
/** @deprecated This function is obsolete, use native element.dispatchEvent(new MoverMemorizedElementEvent(...)). */
function dispatchMoverMemorizedElementEvent(target, memorizedElement) {
return target.dispatchEvent(new MoverMemorizedElementEvent({
memorizedElement
}));
}
export { AsyncFocusSources, DeloserFocusLostEvent, DeloserFocusLostEventName, DeloserRestoreFocusEvent, DeloserRestoreFocusEventName, DeloserStrategies, EventsTypes, FOCUSABLE_SELECTOR, GroupperMoveFocusActions, GroupperMoveFocusEvent, GroupperMoveFocusEventName, GroupperTabbabilities, ModalizerActiveEvent, ModalizerActiveEventName, ModalizerFocusInEventName, ModalizerFocusOutEventName, ModalizerInactiveEvent, ModalizerInactiveEventName, MoverDirections, MoverKeys, MoverMemorizedElementEvent, MoverMemorizedElementEventName, MoverMoveFocusEvent, MoverMoveFocusEventName, MoverStateEvent, MoverStateEventName, ObservedElementAccessibilities, RestoreFocusOrders, RestorerRestoreFocusEvent, RestorerRestoreFocusEventName, RestorerTypes, RootBlurEvent, RootBlurEventName, RootFocusEvent, RootFocusEventName, SysDummyInputsPositions, TABSTER_ATTRIBUTE_NAME, TABSTER_DUMMY_INPUT_ATTRIBUTE_NAME, TabsterCustomEvent, TabsterFocusInEvent, TabsterFocusInEventName, TabsterFocusOutEvent, TabsterFocusOutEventName, TabsterMoveFocusEvent, TabsterMoveFocusEventName, Types, Visibilities, createTabster, dispatchGroupperMoveFocusEvent, dispatchMoverMemorizedElementEvent, dispatchMoverMoveFocusEvent, disposeTabster, forceCleanup, getCrossOrigin, getDeloser, getDummyInputContainer, getGroupper, getInternal, getModalizer, getMover, getObservedElement, getOutline, getRestorer, getShadowDOMAPI, getTabster, getTabsterAttribute, isNoOp, makeNoOp, mergeTabsterProps, setTabsterAttribute };
//# sourceMappingURL=tabster.esm.js.map