@vimeo/iris
Version:
Vimeo Design System
1,392 lines (1,349 loc) • 150 kB
JavaScript
import { n as named$1 } from './react.esm-edf204b5.js';
function _mergeNamespaces(n, m) {
m.forEach(function (e) {
e && typeof e !== 'string' && !Array.isArray(e) && Object.keys(e).forEach(function (k) {
if (k !== 'default' && !(k in n)) {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
});
return Object.freeze(n);
}
function isElementType(element, tag, props) {
if (element.namespaceURI && element.namespaceURI !== 'http://www.w3.org/1999/xhtml') {
return false;
}
tag = Array.isArray(tag) ? tag : [tag];
// tagName is uppercase in HTMLDocument and lowercase in XMLDocument
if (!tag.includes(element.tagName.toLowerCase())) {
return false;
}
if (props) {
return Object.entries(props).every(([k, v]) => element[k] === v);
}
return true;
}
var clickableInputTypes;
(function (clickableInputTypes) {
clickableInputTypes['button'] = 'button';
clickableInputTypes['color'] = 'color';
clickableInputTypes['file'] = 'file';
clickableInputTypes['image'] = 'image';
clickableInputTypes['reset'] = 'reset';
clickableInputTypes['submit'] = 'submit';
clickableInputTypes['checkbox'] = 'checkbox';
clickableInputTypes['radio'] = 'radio';
})(clickableInputTypes || (clickableInputTypes = {}));
function isClickableInput(element) {
return isElementType(element, 'button') || isElementType(element, 'input') && element.type in clickableInputTypes;
}
var helpers = {};
Object.defineProperty(helpers, "__esModule", {
value: true
});
var TEXT_NODE_1 = helpers.TEXT_NODE = void 0;
var checkContainerType_1 = helpers.checkContainerType = checkContainerType;
var getDocument_1 = helpers.getDocument = getDocument$1;
var getWindowFromNode_1 = helpers.getWindowFromNode = getWindowFromNode$1;
var jestFakeTimersAreEnabled_1 = helpers.jestFakeTimersAreEnabled = jestFakeTimersAreEnabled;
// Constant node.nodeType for text nodes, see:
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType#Node_type_constants
const TEXT_NODE = 3;
TEXT_NODE_1 = helpers.TEXT_NODE = TEXT_NODE;
function jestFakeTimersAreEnabled() {
/* istanbul ignore else */
if (typeof jest !== 'undefined' && jest !== null) {
return (
// legacy timers
setTimeout._isMockFunction === true ||
// modern timers
Object.prototype.hasOwnProperty.call(setTimeout, 'clock')
);
} // istanbul ignore next
return false;
}
function getDocument$1() {
/* istanbul ignore if */
if (typeof window === 'undefined') {
throw new Error('Could not find default container');
}
return window.document;
}
function getWindowFromNode$1(node) {
if (node.defaultView) {
// node is document
return node.defaultView;
} else if (node.ownerDocument && node.ownerDocument.defaultView) {
// node is a DOM node
return node.ownerDocument.defaultView;
} else if (node.window) {
// node is window
return node.window;
} else if (node.ownerDocument && node.ownerDocument.defaultView === null) {
throw new Error(`It looks like the window object is not available for the provided node.`);
} else if (node.then instanceof Function) {
throw new Error(`It looks like you passed a Promise object instead of a DOM node. Did you do something like \`fireEvent.click(screen.findBy...\` when you meant to use a \`getBy\` query \`fireEvent.click(screen.getBy...\`, or await the findBy query \`fireEvent.click(await screen.findBy...\`?`);
} else if (Array.isArray(node)) {
throw new Error(`It looks like you passed an Array instead of a DOM node. Did you do something like \`fireEvent.click(screen.getAllBy...\` when you meant to use a \`getBy\` query \`fireEvent.click(screen.getBy...\`?`);
} else if (typeof node.debug === 'function' && typeof node.logTestingPlaygroundURL === 'function') {
throw new Error(`It looks like you passed a \`screen\` object. Did you do something like \`fireEvent.click(screen, ...\` when you meant to use a query, e.g. \`fireEvent.click(screen.getBy..., \`?`);
} else {
// The user passed something unusual to a calling function
throw new Error(`The given node is not an Element, the node type is: ${typeof node}.`);
}
}
function checkContainerType(container) {
if (!container || !(typeof container.querySelector === 'function') || !(typeof container.querySelectorAll === 'function')) {
throw new TypeError(`Expected container to be an Element, a Document or a DocumentFragment but got ${getTypeName(container)}.`);
}
function getTypeName(object) {
if (typeof object === 'object') {
return object === null ? 'null' : object.constructor.name;
}
return typeof object;
}
}
var named = /*#__PURE__*/_mergeNamespaces({
__proto__: null,
get TEXT_NODE () { return TEXT_NODE_1; },
checkContainerType: checkContainerType_1,
getDocument: getDocument_1,
getWindowFromNode: getWindowFromNode_1,
jestFakeTimersAreEnabled: jestFakeTimersAreEnabled_1,
'default': helpers
}, [helpers]);
const {
getWindowFromNode
} = named;
function getWindow(node) {
return getWindowFromNode(node);
}
// jsdom does not implement Blob.text()
function readBlobText(blob, FileReader) {
return new Promise((res, rej) => {
const fr = new FileReader();
fr.onerror = rej;
fr.onabort = rej;
fr.onload = () => {
res(String(fr.result));
};
fr.readAsText(blob);
});
}
// FileList can not be created per constructor.
function createFileList(window, files) {
const list = {
...files,
length: files.length,
item: index => list[index],
[Symbol.iterator]: function* nextFile() {
for (let i = 0; i < list.length; i++) {
yield list[i];
}
}
};
list.constructor = window.FileList;
// guard for environments without FileList
/* istanbul ignore else */
if (window.FileList) {
Object.setPrototypeOf(list, window.FileList.prototype);
}
Object.freeze(list);
return list;
}
function _define_property$8(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
// DataTransfer is not implemented in jsdom.
// DataTransfer with FileList is being created by the browser on certain events.
class DataTransferItemStub {
getAsFile() {
return this.file;
}
getAsString(callback) {
if (typeof this.data === 'string') {
callback(this.data);
}
}
/* istanbul ignore next */
webkitGetAsEntry() {
throw new Error('not implemented');
}
constructor(dataOrFile, type) {
_define_property$8(this, "kind", void 0);
_define_property$8(this, "type", void 0);
_define_property$8(this, "file", null);
_define_property$8(this, "data", undefined);
if (typeof dataOrFile === 'string') {
this.kind = 'string';
this.type = String(type);
this.data = dataOrFile;
} else {
this.kind = 'file';
this.type = dataOrFile.type;
this.file = dataOrFile;
}
}
}
class DataTransferItemListStub extends Array {
add(...args) {
const item = new DataTransferItemStub(args[0], args[1]);
this.push(item);
return item;
}
clear() {
this.splice(0, this.length);
}
remove(index) {
this.splice(index, 1);
}
}
function getTypeMatcher(type, exact) {
const [group, sub] = type.split('/');
const isGroup = !sub || sub === '*';
return item => {
return exact ? item.type === (isGroup ? group : type) : isGroup ? item.type.startsWith(`${group}/`) : item.type === group;
};
}
function createDataTransferStub(window) {
return new class DataTransferStub {
getData(format) {
var _this_items_find;
const match = (_this_items_find = this.items.find(getTypeMatcher(format, true))) !== null && _this_items_find !== void 0 ? _this_items_find : this.items.find(getTypeMatcher(format, false));
let text = '';
match === null || match === void 0 ? void 0 : match.getAsString(t => {
text = t;
});
return text;
}
setData(format, data) {
const matchIndex = this.items.findIndex(getTypeMatcher(format, true));
const item = new DataTransferItemStub(data, format);
if (matchIndex >= 0) {
this.items.splice(matchIndex, 1, item);
} else {
this.items.push(item);
}
}
clearData(format) {
if (format) {
const matchIndex = this.items.findIndex(getTypeMatcher(format, true));
if (matchIndex >= 0) {
this.items.remove(matchIndex);
}
} else {
this.items.clear();
}
}
get types() {
const t = [];
if (this.files.length) {
t.push('Files');
}
this.items.forEach(i => t.push(i.type));
Object.freeze(t);
return t;
}
/* istanbul ignore next */
setDragImage() {}
constructor() {
_define_property$8(this, "dropEffect", 'none');
_define_property$8(this, "effectAllowed", 'uninitialized');
_define_property$8(this, "items", new DataTransferItemListStub());
_define_property$8(this, "files", createFileList(window, []));
}
}();
}
function createDataTransfer(window, files = []) {
// Use real DataTransfer if available
const dt = typeof window.DataTransfer === 'undefined' ? createDataTransferStub(window) : /* istanbul ignore next */new window.DataTransfer();
Object.defineProperty(dt, 'files', {
get: () => createFileList(window, files)
});
return dt;
}
function getBlobFromDataTransferItem(window, item) {
if (item.kind === 'file') {
return item.getAsFile();
}
let data = '';
item.getAsString(s => {
data = s;
});
return new window.Blob([data], {
type: item.type
});
}
// Clipboard is not available in jsdom
function _define_property$7(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
// MDN lists string|Blob|Promise<Blob|string> as possible types in ClipboardItemData
// lib.dom.d.ts lists only Promise<Blob|string>
// https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem/ClipboardItem#syntax
function createClipboardItem(window, ...blobs) {
const dataMap = Object.fromEntries(blobs.map(b => [typeof b === 'string' ? 'text/plain' : b.type, Promise.resolve(b)]));
// use real ClipboardItem if available
/* istanbul ignore if */
if (typeof window.ClipboardItem !== 'undefined') {
return new window.ClipboardItem(dataMap);
}
return new class ClipboardItem {
get types() {
return Array.from(Object.keys(this.data));
}
async getType(type) {
const value = await this.data[type];
if (!value) {
throw new Error(`${type} is not one of the available MIME types on this item.`);
}
return value instanceof window.Blob ? value : new window.Blob([value], {
type
});
}
constructor(d) {
_define_property$7(this, "data", void 0);
this.data = d;
}
}(dataMap);
}
const ClipboardStubControl = Symbol('Manage ClipboardSub');
function createClipboardStub(window, control) {
return Object.assign(new class Clipboard extends window.EventTarget {
async read() {
return Array.from(this.items);
}
async readText() {
let text = '';
for (const item of this.items) {
const type = item.types.includes('text/plain') ? 'text/plain' : item.types.find(t => t.startsWith('text/'));
if (type) {
text += await item.getType(type).then(b => readBlobText(b, window.FileReader));
}
}
return text;
}
async write(data) {
this.items = data;
}
async writeText(text) {
this.items = [createClipboardItem(window, text)];
}
constructor(...args) {
super(...args);
_define_property$7(this, "items", []);
}
}(), {
[ClipboardStubControl]: control
});
}
function isClipboardStub(clipboard) {
return !!(clipboard === null || clipboard === void 0 ? void 0 : clipboard[ClipboardStubControl]);
}
function attachClipboardStubToView(window) {
if (isClipboardStub(window.navigator.clipboard)) {
return window.navigator.clipboard[ClipboardStubControl];
}
const realClipboard = Object.getOwnPropertyDescriptor(window.navigator, 'clipboard');
let stub;
const control = {
resetClipboardStub: () => {
stub = createClipboardStub(window, control);
},
detachClipboardStub: () => {
/* istanbul ignore if */if (realClipboard) {
Object.defineProperty(window.navigator, 'clipboard', realClipboard);
} else {
Object.defineProperty(window.navigator, 'clipboard', {
value: undefined,
configurable: true
});
}
}
};
stub = createClipboardStub(window, control);
Object.defineProperty(window.navigator, 'clipboard', {
get: () => stub,
configurable: true
});
return stub[ClipboardStubControl];
}
function resetClipboardStubOnView(window) {
if (isClipboardStub(window.navigator.clipboard)) {
window.navigator.clipboard[ClipboardStubControl].resetClipboardStub();
}
}
function detachClipboardStubFromView(window) {
if (isClipboardStub(window.navigator.clipboard)) {
window.navigator.clipboard[ClipboardStubControl].detachClipboardStub();
}
}
async function readDataTransferFromClipboard(document) {
const window = document.defaultView;
const clipboard = window === null || window === void 0 ? void 0 : window.navigator.clipboard;
const items = clipboard && (await clipboard.read());
if (!items) {
throw new Error('The Clipboard API is unavailable.');
}
const dt = createDataTransfer(window);
for (const item of items) {
for (const type of item.types) {
dt.setData(type, await item.getType(type).then(b => readBlobText(b, window.FileReader)));
}
}
return dt;
}
async function writeDataTransferToClipboard(document, clipboardData) {
const window = getWindow(document);
const clipboard = window.navigator.clipboard;
const items = [];
for (let i = 0; i < clipboardData.items.length; i++) {
const dtItem = clipboardData.items[i];
const blob = getBlobFromDataTransferItem(window, dtItem);
items.push(createClipboardItem(window, blob));
}
const written = clipboard && (await clipboard.write(items).then(() => true,
// Can happen with other implementations that e.g. require permissions
/* istanbul ignore next */
() => false));
if (!written) {
throw new Error('The Clipboard API is unavailable.');
}
}
const g = globalThis;
/* istanbul ignore else */
if (typeof g.afterEach === 'function') {
g.afterEach(() => resetClipboardStubOnView(globalThis.window));
}
/* istanbul ignore else */
if (typeof g.afterAll === 'function') {
g.afterAll(() => detachClipboardStubFromView(globalThis.window));
}
//jsdom is not supporting isContentEditable
function isContentEditable(element) {
return element.hasAttribute('contenteditable') && (element.getAttribute('contenteditable') == 'true' || element.getAttribute('contenteditable') == '');
}
/**
* If a node is a contenteditable or inside one, return that element.
*/
function getContentEditable(node) {
const element = getElement$1(node);
return element && (element.closest('[contenteditable=""]') || element.closest('[contenteditable="true"]'));
}
function getElement$1(node) {
return node.nodeType === 1 ? node : node.parentElement;
}
function isEditable(element) {
return isEditableInputOrTextArea(element) && !element.readOnly || isContentEditable(element);
}
var editableInputTypes;
(function (editableInputTypes) {
editableInputTypes['text'] = 'text';
editableInputTypes['date'] = 'date';
editableInputTypes['datetime-local'] = 'datetime-local';
editableInputTypes['email'] = 'email';
editableInputTypes['month'] = 'month';
editableInputTypes['number'] = 'number';
editableInputTypes['password'] = 'password';
editableInputTypes['search'] = 'search';
editableInputTypes['tel'] = 'tel';
editableInputTypes['time'] = 'time';
editableInputTypes['url'] = 'url';
editableInputTypes['week'] = 'week';
})(editableInputTypes || (editableInputTypes = {}));
function isEditableInputOrTextArea(element) {
return isElementType(element, 'textarea') || isElementType(element, 'input') && element.type in editableInputTypes;
}
var maxLengthSupportedTypes;
(function (maxLengthSupportedTypes) {
maxLengthSupportedTypes['email'] = 'email';
maxLengthSupportedTypes['password'] = 'password';
maxLengthSupportedTypes['search'] = 'search';
maxLengthSupportedTypes['telephone'] = 'telephone';
maxLengthSupportedTypes['text'] = 'text';
maxLengthSupportedTypes['url'] = 'url';
})(maxLengthSupportedTypes || (maxLengthSupportedTypes = {}));
// can't use .maxLength property because of a jsdom bug:
// https://github.com/jsdom/jsdom/issues/2927
function getMaxLength(element) {
var _element_getAttribute;
const attr = (_element_getAttribute = element.getAttribute('maxlength')) !== null && _element_getAttribute !== void 0 ? _element_getAttribute : '';
return /^\d+$/.test(attr) && Number(attr) >= 0 ? Number(attr) : undefined;
}
function supportsMaxLength(element) {
return isElementType(element, 'textarea') || isElementType(element, 'input') && element.type in maxLengthSupportedTypes;
}
const FOCUSABLE_SELECTOR = ['input:not([type=hidden]):not([disabled])', 'button:not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])', '[contenteditable=""]', '[contenteditable="true"]', 'a[href]', '[tabindex]:not([disabled])'].join(', ');
function isFocusable(element) {
return element.matches(FOCUSABLE_SELECTOR);
}
var bracketDict;
(function (bracketDict) {
bracketDict['{'] = '}';
bracketDict['['] = ']';
})(bracketDict || (bracketDict = {}));
/**
* Read the next key definition from user input
*
* Describe key per `{descriptor}` or `[descriptor]`.
* Everything else will be interpreted as a single character as descriptor - e.g. `a`.
* Brackets `{` and `[` can be escaped by doubling - e.g. `foo[[bar` translates to `foo[bar`.
* A previously pressed key can be released per `{/descriptor}`.
* Keeping the key pressed can be written as `{descriptor>}`.
* When keeping the key pressed you can choose how long the key is pressed `{descriptor>3}`.
* You can then release the key per `{descriptor>3/}` or keep it pressed and continue with the next key.
*/
function readNextDescriptor(text, context) {
let pos = 0;
const startBracket = text[pos] in bracketDict ? text[pos] : '';
pos += startBracket.length;
const isEscapedChar = new RegExp(`^\\${startBracket}{2}`).test(text);
const type = isEscapedChar ? '' : startBracket;
return {
type,
...(type === '' ? readPrintableChar(text, pos, context) : readTag(text, pos, type, context))
};
}
function readPrintableChar(text, pos, context) {
const descriptor = text[pos];
assertDescriptor(descriptor, text, pos, context);
pos += descriptor.length;
return {
consumedLength: pos,
descriptor,
releasePrevious: false,
releaseSelf: true,
repeat: 1
};
}
function readTag(text, pos, startBracket, context) {
var _text_slice_match, _text_slice_match1;
const releasePreviousModifier = text[pos] === '/' ? '/' : '';
pos += releasePreviousModifier.length;
const escapedDescriptor = startBracket === '{' && text[pos] === '\\';
pos += Number(escapedDescriptor);
const descriptor = escapedDescriptor ? text[pos] : (_text_slice_match = text.slice(pos).match(startBracket === '{' ? /^\w+|^[^}>/]/ : /^\w+/)) === null || _text_slice_match === void 0 ? void 0 : _text_slice_match[0];
assertDescriptor(descriptor, text, pos, context);
pos += descriptor.length;
var _text_slice_match_;
const repeatModifier = (_text_slice_match_ = (_text_slice_match1 = text.slice(pos).match(/^>\d+/)) === null || _text_slice_match1 === void 0 ? void 0 : _text_slice_match1[0]) !== null && _text_slice_match_ !== void 0 ? _text_slice_match_ : '';
pos += repeatModifier.length;
const releaseSelfModifier = text[pos] === '/' || !repeatModifier && text[pos] === '>' ? text[pos] : '';
pos += releaseSelfModifier.length;
const expectedEndBracket = bracketDict[startBracket];
const endBracket = text[pos] === expectedEndBracket ? expectedEndBracket : '';
if (!endBracket) {
throw new Error(getErrorMessage([!repeatModifier && 'repeat modifier', !releaseSelfModifier && 'release modifier', `"${expectedEndBracket}"`].filter(Boolean).join(' or '), text[pos], text, context));
}
pos += endBracket.length;
return {
consumedLength: pos,
descriptor,
releasePrevious: !!releasePreviousModifier,
repeat: repeatModifier ? Math.max(Number(repeatModifier.substr(1)), 1) : 1,
releaseSelf: hasReleaseSelf(releaseSelfModifier, repeatModifier)
};
}
function assertDescriptor(descriptor, text, pos, context) {
if (!descriptor) {
throw new Error(getErrorMessage('key descriptor', text[pos], text, context));
}
}
function hasReleaseSelf(releaseSelfModifier, repeatModifier) {
if (releaseSelfModifier) {
return releaseSelfModifier === '/';
}
if (repeatModifier) {
return false;
}
}
function getErrorMessage(expected, found, text, context) {
return `Expected ${expected} but found "${found !== null && found !== void 0 ? found : ''}" in "${text}"
See ${context === 'pointer' ? `https://testing-library.com/docs/user-event/pointer#pressing-a-button-or-touching-the-screen` : `https://testing-library.com/docs/user-event/keyboard`}
for more information about how userEvent parses your input.`;
}
function cloneEvent(event) {
return new event.constructor(event.type, event);
}
var ApiLevel;
(function (ApiLevel) {
ApiLevel[ApiLevel["Trigger"] = 2] = "Trigger";
ApiLevel[ApiLevel["Call"] = 1] = "Call";
})(ApiLevel || (ApiLevel = {}));
function setLevelRef(instance, level) {
instance.levelRefs[level] = {};
}
function getLevelRef(instance, level) {
return instance.levelRefs[level];
}
var PointerEventsCheckLevel;
(function (PointerEventsCheckLevel) {
PointerEventsCheckLevel[PointerEventsCheckLevel[
/**
* Check pointer events on every user interaction that triggers a bunch of events.
* E.g. once for releasing a mouse button even though this triggers `pointerup`, `mouseup`, `click`, etc...
*/
"EachTrigger"] = 4] = "EachTrigger";
PointerEventsCheckLevel[PointerEventsCheckLevel[/** Check each target once per call to pointer (related) API */"EachApiCall"] = 2] = "EachApiCall";
PointerEventsCheckLevel[PointerEventsCheckLevel[/** Check each event target once */"EachTarget"] = 1] = "EachTarget";
PointerEventsCheckLevel[PointerEventsCheckLevel[/** No pointer events check */"Never"] = 0] = "Never";
})(PointerEventsCheckLevel || (PointerEventsCheckLevel = {}));
// This should probably just rely on the :disabled pseudo-class, but JSDOM doesn't implement it properly.
function isDisabled(element) {
for (let el = element; el; el = el.parentElement) {
if (isElementType(el, ['button', 'input', 'select', 'textarea', 'optgroup', 'option'])) {
if (el.hasAttribute('disabled')) {
return true;
}
} else if (isElementType(el, 'fieldset')) {
var _el_querySelector;
if (el.hasAttribute('disabled') && !((_el_querySelector = el.querySelector(':scope > legend')) === null || _el_querySelector === void 0 ? void 0 : _el_querySelector.contains(element))) {
return true;
}
} else if (el.tagName.includes('-')) {
if (el.constructor.formAssociated && el.hasAttribute('disabled')) {
return true;
}
}
}
return false;
}
function getActiveElement(document) {
const activeElement = document.activeElement;
if (activeElement === null || activeElement === void 0 ? void 0 : activeElement.shadowRoot) {
return getActiveElement(activeElement.shadowRoot);
} else {
// Browser does not yield disabled elements as document.activeElement - jsdom does
if (isDisabled(activeElement)) {
return document.ownerDocument ? /* istanbul ignore next */document.ownerDocument.body : document.body;
}
return activeElement;
}
}
function getActiveElementOrBody(document) {
var _getActiveElement;
return (_getActiveElement = getActiveElement(document)) !== null && _getActiveElement !== void 0 ? _getActiveElement : /* istanbul ignore next */document.body;
}
function findClosest(element, callback) {
let el = element;
do {
if (callback(el)) {
return el;
}
el = el.parentElement;
} while (el && el !== element.ownerDocument.body);
return undefined;
}
/**
* Determine if the element has its own selection implementation
* and does not interact with the Document Selection API.
*/
function hasOwnSelection(node) {
return isElement$1(node) && isEditableInputOrTextArea(node);
}
function hasNoSelection(node) {
return isElement$1(node) && isClickableInput(node);
}
function isElement$1(node) {
return node.nodeType === 1;
}
/**
* Reset the Document Selection when moving focus into an element
* with own selection implementation.
*/
function updateSelectionOnFocus(element) {
const selection = element.ownerDocument.getSelection();
/* istanbul ignore if */
if (!(selection === null || selection === void 0 ? void 0 : selection.focusNode)) {
return;
}
// If the focus moves inside an element with own selection implementation,
// the document selection will be this element.
// But if the focused element is inside a contenteditable,
// 1) a collapsed selection will be retained.
// 2) other selections will be replaced by a cursor
// 2.a) at the start of the first child if it is a text node
// 2.b) at the start of the contenteditable.
if (hasOwnSelection(element)) {
const contenteditable = getContentEditable(selection.focusNode);
if (contenteditable) {
if (!selection.isCollapsed) {
var _contenteditable_firstChild;
const focusNode = ((_contenteditable_firstChild = contenteditable.firstChild) === null || _contenteditable_firstChild === void 0 ? void 0 : _contenteditable_firstChild.nodeType) === 3 ? contenteditable.firstChild : contenteditable;
selection.setBaseAndExtent(focusNode, 0, focusNode, 0);
}
} else {
selection.setBaseAndExtent(element, 0, element, 0);
}
}
}
const {
getConfig: getConfig$2
} = named$1;
function wrapEvent(cb, _element) {
return getConfig$2().eventWrapper(cb);
}
/**
* Focus closest focusable element.
*/
function focusElement(element) {
const target = findClosest(element, isFocusable);
const activeElement = getActiveElement(element.ownerDocument);
if ((target !== null && target !== void 0 ? target : element.ownerDocument.body) === activeElement) {
return;
} else if (target) {
wrapEvent(() => target.focus());
} else {
wrapEvent(() => activeElement === null || activeElement === void 0 ? void 0 : activeElement.blur());
}
updateSelectionOnFocus(target !== null && target !== void 0 ? target : element.ownerDocument.body);
}
function blurElement(element) {
if (!isFocusable(element)) return;
const wasActive = getActiveElement(element.ownerDocument) === element;
if (!wasActive) return;
wrapEvent(() => element.blur());
}
const behavior = {};
behavior.click = (event, target, instance) => {
const context = target.closest('button,input,label,select,textarea');
const control = context && isElementType(context, 'label') && context.control;
if (control) {
return () => {
if (isFocusable(control)) {
focusElement(control);
}
instance.dispatchEvent(control, cloneEvent(event));
};
} else if (isElementType(target, 'input', {
type: 'file'
})) {
return () => {
// blur fires when the file selector pops up
blurElement(target);
target.dispatchEvent(new (getWindow(target).Event)('fileDialog'));
// focus fires after the file selector has been closed
focusElement(target);
};
}
};
const UIValue = Symbol('Displayed value in UI');
const UISelection = Symbol('Displayed selection in UI');
const InitialValue = Symbol('Initial value to compare on blur');
function isUIValue(value) {
return typeof value === 'object' && UIValue in value;
}
function isUISelectionStart(start) {
return !!start && typeof start === 'object' && UISelection in start;
}
function setUIValue(element, value) {
if (element[InitialValue] === undefined) {
element[InitialValue] = element.value;
}
element[UIValue] = value;
// eslint-disable-next-line no-new-wrappers
element.value = Object.assign(new String(value), {
[UIValue]: true
});
}
function getUIValue(element) {
return element[UIValue] === undefined ? element.value : String(element[UIValue]);
}
/** Flag the IDL value as clean. This does not change the value.*/
function setUIValueClean(element) {
element[UIValue] = undefined;
}
function clearInitialValue(element) {
element[InitialValue] = undefined;
}
function getInitialValue(element) {
return element[InitialValue];
}
function setUISelectionRaw(element, selection) {
element[UISelection] = selection;
}
function setUISelection(element, {
focusOffset: focusOffsetParam,
anchorOffset: anchorOffsetParam = focusOffsetParam
}, mode = 'replace') {
const valueLength = getUIValue(element).length;
const sanitizeOffset = o => Math.max(0, Math.min(valueLength, o));
const anchorOffset = mode === 'replace' || element[UISelection] === undefined ? sanitizeOffset(anchorOffsetParam) : element[UISelection].anchorOffset;
const focusOffset = sanitizeOffset(focusOffsetParam);
const startOffset = Math.min(anchorOffset, focusOffset);
const endOffset = Math.max(anchorOffset, focusOffset);
element[UISelection] = {
anchorOffset,
focusOffset
};
if (element.selectionStart === startOffset && element.selectionEnd === endOffset) {
return;
}
// eslint-disable-next-line no-new-wrappers
const startObj = Object.assign(new Number(startOffset), {
[UISelection]: true
});
try {
element.setSelectionRange(startObj, endOffset);
} catch {
// DOMException for invalid state is expected when calling this
// on an element without support for setSelectionRange
}
}
function getUISelection(element) {
var _element_selectionStart, _element_selectionEnd, _element_UISelection;
const sel = (_element_UISelection = element[UISelection]) !== null && _element_UISelection !== void 0 ? _element_UISelection : {
anchorOffset: (_element_selectionStart = element.selectionStart) !== null && _element_selectionStart !== void 0 ? _element_selectionStart : 0,
focusOffset: (_element_selectionEnd = element.selectionEnd) !== null && _element_selectionEnd !== void 0 ? _element_selectionEnd : 0
};
return {
...sel,
startOffset: Math.min(sel.anchorOffset, sel.focusOffset),
endOffset: Math.max(sel.anchorOffset, sel.focusOffset)
};
}
function hasUISelection(element) {
return !!element[UISelection];
}
/** Flag the IDL selection as clean. This does not change the selection. */
function setUISelectionClean(element) {
element[UISelection] = undefined;
}
const parseInt = globalThis.parseInt;
function buildTimeValue(value) {
const onlyDigitsValue = value.replace(/\D/g, '');
if (onlyDigitsValue.length < 2) {
return value;
}
const firstDigit = parseInt(onlyDigitsValue[0], 10);
const secondDigit = parseInt(onlyDigitsValue[1], 10);
if (firstDigit >= 3 || firstDigit === 2 && secondDigit >= 4) {
let index;
if (firstDigit >= 3) {
index = 1;
} else {
index = 2;
}
return build(onlyDigitsValue, index);
}
if (value.length === 2) {
return value;
}
return build(onlyDigitsValue, 2);
}
function build(onlyDigitsValue, index) {
const hours = onlyDigitsValue.slice(0, index);
const validHours = Math.min(parseInt(hours, 10), 23);
const minuteCharacters = onlyDigitsValue.slice(index);
const parsedMinutes = parseInt(minuteCharacters, 10);
const validMinutes = Math.min(parsedMinutes, 59);
return `${validHours.toString().padStart(2, '0')}:${validMinutes.toString().padStart(2, '0')}`;
}
function isValidDateOrTimeValue(element, value) {
const clone = element.cloneNode();
clone.value = value;
return clone.value === value;
}
function getNextCursorPosition(node, offset, direction, inputType) {
// The behavior at text node zero offset is inconsistent.
// When walking backwards:
// Firefox always moves to zero offset and jumps over last offset.
// Chrome jumps over zero offset per default but over last offset when Shift is pressed.
// The cursor always moves to zero offset if the focus area (contenteditable or body) ends there.
// When walking foward both ignore zero offset.
// When walking over input elements the cursor moves before or after that element.
// When walking over line breaks the cursor moves inside any following text node.
if (isTextNode(node) && offset + direction >= 0 && offset + direction <= node.nodeValue.length) {
return {
node,
offset: offset + direction
};
}
const nextNode = getNextCharacterContentNode(node, offset, direction);
if (nextNode) {
if (isTextNode(nextNode)) {
return {
node: nextNode,
offset: direction > 0 ? Math.min(1, nextNode.nodeValue.length) : Math.max(nextNode.nodeValue.length - 1, 0)
};
} else if (isElementType(nextNode, 'br')) {
const nextPlusOne = getNextCharacterContentNode(nextNode, undefined, direction);
if (!nextPlusOne) {
// The behavior when there is no possible cursor position beyond the line break is inconsistent.
// In Chrome outside of contenteditable moving before a leading line break is possible.
// A leading line break can still be removed per deleteContentBackward.
// A trailing line break on the other hand is not removed by deleteContentForward.
if (direction < 0 && inputType === 'deleteContentBackward') {
return {
node: nextNode.parentNode,
offset: getOffset(nextNode)
};
}
return undefined;
} else if (isTextNode(nextPlusOne)) {
return {
node: nextPlusOne,
offset: direction > 0 ? 0 : nextPlusOne.nodeValue.length
};
} else if (direction < 0 && isElementType(nextPlusOne, 'br')) {
return {
node: nextNode.parentNode,
offset: getOffset(nextNode)
};
} else {
return {
node: nextPlusOne.parentNode,
offset: getOffset(nextPlusOne) + (direction > 0 ? 0 : 1)
};
}
} else {
return {
node: nextNode.parentNode,
offset: getOffset(nextNode) + (direction > 0 ? 1 : 0)
};
}
}
}
function getNextCharacterContentNode(node, offset, direction) {
const nextOffset = Number(offset) + (direction < 0 ? -1 : 0);
if (offset !== undefined && isElement(node) && nextOffset >= 0 && nextOffset < node.children.length) {
node = node.children[nextOffset];
}
return walkNodes(node, direction === 1 ? 'next' : 'previous', isTreatedAsCharacterContent);
}
function isTreatedAsCharacterContent(node) {
if (isTextNode(node)) {
return true;
}
if (isElement(node)) {
if (isElementType(node, ['input', 'textarea'])) {
return node.type !== 'hidden';
} else if (isElementType(node, 'br')) {
return true;
}
}
return false;
}
function getOffset(node) {
let i = 0;
while (node.previousSibling) {
i++;
node = node.previousSibling;
}
return i;
}
function isElement(node) {
return node.nodeType === 1;
}
function isTextNode(node) {
return node.nodeType === 3;
}
function walkNodes(node, direction, callback) {
for (;;) {
var _node_ownerDocument;
const sibling = node[`${direction}Sibling`];
if (sibling) {
node = getDescendant(sibling, direction === 'next' ? 'first' : 'last');
if (callback(node)) {
return node;
}
} else if (node.parentNode && (!isElement(node.parentNode) || !isContentEditable(node.parentNode) && node.parentNode !== ((_node_ownerDocument = node.ownerDocument) === null || _node_ownerDocument === void 0 ? void 0 : _node_ownerDocument.body))) {
node = node.parentNode;
} else {
break;
}
}
}
function getDescendant(node, direction) {
while (node.hasChildNodes()) {
node = node[`${direction}Child`];
}
return node;
}
const TrackChanges = Symbol('Track programmatic changes for React workaround');
// When the input event happens in the browser, React executes all event handlers
// and if they change state of a controlled value, nothing happens.
// But when we trigger the event handlers in test environment with React@17,
// the changes are rolled back before the state update is applied.
// This results in a reset cursor.
// There might be a better way to work around if we figure out
// why the batched update is executed differently in our test environment.
function isReact17Element(element) {
return Object.getOwnPropertyNames(element).some(k => k.startsWith('__react')) && getWindow(element).REACT_VERSION === 17;
}
function startTrackValue(element) {
if (!isReact17Element(element)) {
return;
}
element[TrackChanges] = {
previousValue: String(element.value),
tracked: []
};
}
function trackOrSetValue(element, v) {
var _element_TrackChanges_tracked, _element_TrackChanges;
(_element_TrackChanges = element[TrackChanges]) === null || _element_TrackChanges === void 0 ? void 0 : (_element_TrackChanges_tracked = _element_TrackChanges.tracked) === null || _element_TrackChanges_tracked === void 0 ? void 0 : _element_TrackChanges_tracked.push(v);
if (!element[TrackChanges]) {
setUIValueClean(element);
setUISelection(element, {
focusOffset: v.length
});
}
}
function commitValueAfterInput(element, cursorOffset) {
var _changes_tracked;
const changes = element[TrackChanges];
element[TrackChanges] = undefined;
if (!(changes === null || changes === void 0 ? void 0 : (_changes_tracked = changes.tracked) === null || _changes_tracked === void 0 ? void 0 : _changes_tracked.length)) {
return;
}
const isJustReactStateUpdate = changes.tracked.length === 2 && changes.tracked[0] === changes.previousValue && changes.tracked[1] === element.value;
if (!isJustReactStateUpdate) {
setUIValueClean(element);
}
if (hasUISelection(element)) {
setUISelection(element, {
focusOffset: isJustReactStateUpdate ? cursorOffset : element.value.length
});
}
}
/**
* Determine which selection logic and selection ranges to consider.
*/
function getTargetTypeAndSelection(node) {
const element = getElement(node);
if (element && hasOwnSelection(element)) {
return {
type: 'input',
selection: getUISelection(element)
};
}
const selection = element === null || element === void 0 ? void 0 : element.ownerDocument.getSelection();
// It is possible to extend a single-range selection into a contenteditable.
// This results in the range acting like a range outside of contenteditable.
const isCE = getContentEditable(node) && (selection === null || selection === void 0 ? void 0 : selection.anchorNode) && getContentEditable(selection.anchorNode);
return {
type: isCE ? 'contenteditable' : 'default',
selection
};
}
function getElement(node) {
return node.nodeType === 1 ? node : node.parentElement;
}
/**
* Get the range that would be overwritten by input.
*/
function getInputRange(focusNode) {
const typeAndSelection = getTargetTypeAndSelection(focusNode);
if (typeAndSelection.type === 'input') {
return typeAndSelection.selection;
} else if (typeAndSelection.type === 'contenteditable') {
var _typeAndSelection_selection;
// Multi-range on contenteditable edits the first selection instead of the last
return (_typeAndSelection_selection = typeAndSelection.selection) === null || _typeAndSelection_selection === void 0 ? void 0 : _typeAndSelection_selection.getRangeAt(0);
}
}
/**
* Set the selection
*/
function setSelection({
focusNode,
focusOffset,
anchorNode = focusNode,
anchorOffset = focusOffset
}) {
var _anchorNode_ownerDocument_getSelection, _anchorNode_ownerDocument;
const typeAndSelection = getTargetTypeAndSelection(focusNode);
if (typeAndSelection.type === 'input') {
return setUISelection(focusNode, {
anchorOffset,
focusOffset
});
}
(_anchorNode_ownerDocument = anchorNode.ownerDocument) === null || _anchorNode_ownerDocument === void 0 ? void 0 : (_anchorNode_ownerDocument_getSelection = _anchorNode_ownerDocument.getSelection()) === null || _anchorNode_ownerDocument_getSelection === void 0 ? void 0 : _anchorNode_ownerDocument_getSelection.setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset);
}
function isDateOrTime(element) {
return isElementType(element, 'input') && ['date', 'time'].includes(element.type);
}
function input(instance, element, data, inputType = 'insertText') {
const inputRange = getInputRange(element);
/* istanbul ignore if */
if (!inputRange) {
return;
}
// There is no `beforeinput` event on `date` and `time` input
if (!isDateOrTime(element)) {
const unprevented = instance.dispatchUIEvent(element, 'beforeinput', {
inputType,
data
});
if (!unprevented) {
return;
}
}
if ('startContainer' in inputRange) {
editContenteditable(instance, element, inputRange, data, inputType);
} else {
editInputElement(instance, element, inputRange, data, inputType);
}
}
function editContenteditable(instance, element, inputRange, data, inputType) {
let del = false;
if (!inputRange.collapsed) {
del = true;
inputRange.deleteContents();
} else if (['deleteContentBackward', 'deleteContentForward'].includes(inputType)) {
const nextPosition = getNextCursorPosition(inputRange.startContainer, inputRange.startOffset, inputType === 'deleteContentBackward' ? -1 : 1, inputType);
if (nextPosition) {
del = true;
const delRange = inputRange.cloneRange();
if (delRange.comparePoint(nextPosition.node, nextPosition.offset) < 0) {
delRange.setStart(nextPosition.node, nextPosition.offset);
} else {
delRange.setEnd(nextPosition.node, nextPosition.offset);
}
delRange.deleteContents();
}
}
if (data) {
if (inputRange.endContainer.nodeType === 3) {
const offset = inputRange.endOffset;
inputRange.endContainer.insertData(offset, data);
inputRange.setStart(inputRange.endContainer, offset + data.length);
inputRange.setEnd(inputRange.endContainer, offset + data.length);
} else {
const text = element.ownerDocument.createTextNode(data);
inputRange.insertNode(text);
inputRange.setStart(text, data.length);
inputRange.setEnd(text, data.length);
}
}
if (del || data) {
instance.dispatchUIEvent(element, 'input', {
inputType
});
}
}
function editInputElement(instance, element, inputRange, data, inputType) {
let dataToInsert = data;
if (supportsMaxLength(element)) {
const maxLength = getMaxLength(element);
if (maxLength !== undefined && data.length > 0) {
const spaceUntilMaxLength = maxLength - element.value.length;
if (spaceUntilMaxLength > 0) {
dataToInsert = data.substring(0, spaceUntilMaxLength);
} else {
return;
}
}
}
const {
newValue,
newOffset,
oldValue
} = calculateNewValue(dataToInsert, element, inputRange, inputType);
if (newValue === oldValue && newOffset === inputRange.startOffset && newOffset === inputRange.endOffset) {
return;
}
if (isElementType(element, 'input', {
type: 'number'
}) && !isValidNumberInput(newValue)) {
return;
}
setUIValue(element, newValue);
setSelection({
focusNode: element,
anchorOffset: newOffset,
focusOffset: newOffset
});
if (isDateOrTime(element)) {
if (isValidDateOrTimeValue(element, newValue)) {
commitInput(instance, element, newOffset, {});
instance.dispatchUIEvent(element, 'change');
clearInitialValue(element);
}
} else {
commitInput(instance, element, newOffset, {
data,
inputType
});
}
}
function calculateNewValue(inputData, node, {
startOffset,
endOffset
}, inputType) {
const value = getUIValue(node);
const prologEnd = Math.max(0, startOffset === endOffset && inputType === 'deleteContentBackward' ? startOffset - 1 : startOffset);
const prolog = value.substring(0, prologEnd);
const epilogStart = Math.min(value.length, startOffset === endOffset && inputType === 'deleteContentForward' ? startOffset + 1 : endOffset);
const epilog = value.substring(epilogStart, value.length);
let newValue = `${prolog}${inputData}${epilog}`;
let newOffset = prologEnd + inputData.length;
if (isElementType(node, 'input', {
type: 'time'
})) {
const builtValue = buildTimeValue(newValue);
if (builtValue !== '' && isValidDateOrTimeValue(node, builtValue)) {
newValue = builtValue;
newOffset = builtValue.length;
}
}
return {
oldValue: value,
newValue,
newOffset
};
}
function commitInput(instance, element, newOffset, inputInit) {
instance.dispatchUIEvent(element, 'input', inputInit);
commitValueAfterInput(element, newOffset);
}
function isValidNumberInput(value) {
var _value_match, _value_match1;
// the browser allows some invalid input but not others
// it allows up to two '-' at any place before any 'e' or one directly following 'e'
// it allows one '.' at any place before e
const valueParts = value.split('e', 2);
return !(/[^\d.\-e]/.test(value) || Number((_value_match = value.match(/-/g)) === null || _value_match === void 0 ? void 0 : _value_match.length) > 2 || Number((_value_match1 = value.match(/\./g)) === null || _value_match1 === void 0 ? void 0 : _value_match1.length) > 1 || valueParts[1] && !/^-?\d*$/.test(valueParts[1]));
}
behavior.cut = (event, target, instance) => {
return () => {
if (isEditable(target)) {
input(instance, target, '', 'deleteByCut');
}
};
};
function getValueOrTextContent(element) {
// istanbul ignore if
if (!element) {
return null;
}
if (isContentEditable(element)) {
return element.textContent;
}
return getUIValue(element);
}
function isVisible(element) {
const window = getWindow(element);
for (let el = element; el === null || el === void 0 ? void 0 : el.ownerDocument; el = el.parentElement) {
const {
display,
visibility
} = window.getComputedStyle(el);
if (display === 'none') {
return false;
}
if (visibility === 'hidden') {
return false;
}
}
return true;
}
function getTabDestination(activeElement, shift) {
const document = activeElement.ownerDocument;
const focusableElements = document.querySelectorAll(FOCUSABLE_SELECTOR);
const enabledElements = Array.from(focusableElements).filter(el => el === activeElement || !(Number(el.getAttribute('tabindex')) < 0 || isDisabled(el)));
// tabindex has no effect if the active element has negative tabindex
if (Number(activeElement.getAttribute('tabindex')) >= 0) {
enabledElements.sort((a, b) => {
const i = Number(a.getAttribute('tabindex'));
const j = Number(b.getAttribute('tabindex'));
if (i === j) {
return 0;
} else if (i === 0) {
return 1;
} else if (j === 0) {
return -1;
}
return i - j;
});
}
const checkedRadio = {};
let prunedElements = [document.body];
const activeRadioGroup = isElementType(activeElement, 'input', {
type: 'radio'
}) ? activeElement.name : undefined;
enabledElements.forEach(currentElement => {
const el = currentElement;
// For radio groups keep only the active radio
// If there is no active radio, keep only the checked radio
// If there is no checked radio, treat like everything else
if (isElementType(el, 'input', {
type: 'radio'
}) && el.name) {
// If the active element is part of the group, add only that
if (el === activeElement) {
prunedElements.push(el);
return;
} else if (el.name === activeRadioGroup) {
return;
}
// If we stumble upon a checked radio, remove the others
if (el.checked) {
prunedElements = prunedElements.filter(e => !isElementType(e, 'input', {
type: 'radio',
name: el.name
}));
prunedElements.push(el);
checkedRadio[el.name] = el;
return;
}
// If we already found the checked one, skip
if (typeof checkedRadio[el.name] !== 'undefined') {
return;
}
}
prunedElements.push(el);
});
for (let index = prunedElements.findIndex(el => el === activeElement);;) {
index += shift ? -1 : 1;
// loop at overflow
if (index === prunedElements.length) {
index = 0;
} else if (index === -1) {
index = prunedElements.length - 1;
}
if (prunedElements[index] === activeElement || prunedElements[index] === document.body || isVisible(prunedElements[index])) {
return prunedElements[index];
}
}
}
/**
* Move the selection
*/
function moveSelection(node, direction) {
// TODO: implement shift
if (hasOwnSelection(node)) {
const selection = getUISelection(node);
setSelection({
focusNode: node,
focusOffset: selection.startOffset === selection.endOffset ? selection.focusOffset + direction : direction < 0 ? selection.startOffset : selection.endOffset
});
} else {
const selection = node.ownerDocument.getSelection();
if (