0.2.0 - Mid migration

This commit is contained in:
Daniel Mason 2022-04-25 14:47:15 +12:00
parent 139e6a915e
commit 7e38fdbd7d
42393 changed files with 5358157 additions and 62 deletions

View file

@ -0,0 +1,15 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.blur = blur;
var _utils = require("./utils");
function blur(element) {
if (!(0, _utils.isFocusable)(element)) return;
const wasActive = (0, _utils.getActiveElement)(element.ownerDocument) === element;
if (!wasActive) return;
(0, _utils.eventWrapper)(() => element.blur());
}

View file

@ -0,0 +1,35 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.clear = clear;
var _type = require("./type");
function clear(element) {
if (element.tagName !== 'INPUT' && element.tagName !== 'TEXTAREA') {
// TODO: support contenteditable
throw new Error('clear currently only supports input and textarea elements.');
}
if (element.disabled) return; // TODO: track the selection range ourselves so we don't have to do this input "type" trickery
// just like cypress does: https://github.com/cypress-io/cypress/blob/8d7f1a0bedc3c45a2ebf1ff50324b34129fdc683/packages/driver/src/dom/selection.ts#L16-L37
const elementType = element.type; // type is a readonly property on textarea, so check if element is an input before trying to modify it
if (element.tagName === 'INPUT') {
// setSelectionRange is not supported on certain types of inputs, e.g. "number" or "email"
element.type = 'text';
}
(0, _type.type)(element, '{selectall}{del}', {
delay: 0,
initialSelectionStart: element.selectionStart,
initialSelectionEnd: element.selectionEnd
});
if (element.tagName === 'INPUT') {
element.type = elementType;
}
}

View file

@ -0,0 +1,154 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.click = click;
exports.dblClick = dblClick;
var _dom = require("@testing-library/dom");
var _utils = require("./utils");
var _hover = require("./hover");
var _blur = require("./blur");
var _focus = require("./focus");
function getPreviouslyFocusedElement(element) {
const focusedElement = element.ownerDocument.activeElement;
const wasAnotherElementFocused = focusedElement && focusedElement !== element.ownerDocument.body && focusedElement !== element;
return wasAnotherElementFocused ? focusedElement : null;
}
function clickLabel(label, init, {
clickCount
}) {
if ((0, _utils.isLabelWithInternallyDisabledControl)(label)) return;
_dom.fireEvent.pointerDown(label, init);
_dom.fireEvent.mouseDown(label, (0, _utils.getMouseEventOptions)('mousedown', init, clickCount));
_dom.fireEvent.pointerUp(label, init);
_dom.fireEvent.mouseUp(label, (0, _utils.getMouseEventOptions)('mouseup', init, clickCount));
_dom.fireEvent.click(label, (0, _utils.getMouseEventOptions)('click', init, clickCount)); // clicking the label will trigger a click of the label.control
// however, it will not focus the label.control so we have to do it
// ourselves.
if (label.control) (0, _focus.focus)(label.control);
}
function clickBooleanElement(element, init, clickCount) {
_dom.fireEvent.pointerDown(element, init);
if (!element.disabled) {
_dom.fireEvent.mouseDown(element, (0, _utils.getMouseEventOptions)('mousedown', init, clickCount));
}
(0, _focus.focus)(element, init);
_dom.fireEvent.pointerUp(element, init);
if (!element.disabled) {
_dom.fireEvent.mouseUp(element, (0, _utils.getMouseEventOptions)('mouseup', init, clickCount));
_dom.fireEvent.click(element, (0, _utils.getMouseEventOptions)('click', init, clickCount));
}
}
function clickElement(element, init, {
clickCount
}) {
const previousElement = getPreviouslyFocusedElement(element);
_dom.fireEvent.pointerDown(element, init);
if (!element.disabled) {
const continueDefaultHandling = _dom.fireEvent.mouseDown(element, (0, _utils.getMouseEventOptions)('mousedown', init, clickCount));
if (continueDefaultHandling) {
const closestFocusable = findClosest(element, _utils.isFocusable);
if (previousElement && !closestFocusable) {
(0, _blur.blur)(previousElement, init);
} else if (closestFocusable) {
(0, _focus.focus)(closestFocusable, init);
}
}
}
_dom.fireEvent.pointerUp(element, init);
if (!element.disabled) {
_dom.fireEvent.mouseUp(element, (0, _utils.getMouseEventOptions)('mouseup', init, clickCount));
_dom.fireEvent.click(element, (0, _utils.getMouseEventOptions)('click', init, clickCount));
const parentLabel = element.closest('label');
if (parentLabel != null && parentLabel.control) (0, _focus.focus)(parentLabel.control, init);
}
}
function findClosest(el, callback) {
do {
if (callback(el)) {
return el;
}
el = el.parentElement;
} while (el && el !== document.body);
return undefined;
}
function click(element, init, {
skipHover = false,
clickCount = 0
} = {}) {
if (!skipHover) (0, _hover.hover)(element, init);
switch (element.tagName) {
case 'LABEL':
clickLabel(element, init, {
clickCount
});
break;
case 'INPUT':
if (element.type === 'checkbox' || element.type === 'radio') {
clickBooleanElement(element, init, {
clickCount
});
} else {
clickElement(element, init, {
clickCount
});
}
break;
default:
clickElement(element, init, {
clickCount
});
}
}
function dblClick(element, init) {
(0, _hover.hover)(element, init);
click(element, init, {
skipHover: true,
clickCount: 0
});
click(element, init, {
skipHover: true,
clickCount: 1
});
_dom.fireEvent.dblClick(element, (0, _utils.getMouseEventOptions)('dblclick', init, 2));
}

View file

@ -0,0 +1,15 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.focus = focus;
var _utils = require("./utils");
function focus(element) {
if (!(0, _utils.isFocusable)(element)) return;
const isAlreadyActive = (0, _utils.getActiveElement)(element.ownerDocument) === element;
if (isAlreadyActive) return;
(0, _utils.eventWrapper)(() => element.focus());
}

View file

@ -0,0 +1,73 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.hover = hover;
exports.unhover = unhover;
var _dom = require("@testing-library/dom");
var _utils = require("./utils");
// includes `element`
function getParentElements(element) {
const parentElements = [element];
let currentElement = element;
while ((currentElement = currentElement.parentElement) != null) {
parentElements.push(currentElement);
}
return parentElements;
}
function hover(element, init) {
if ((0, _utils.isLabelWithInternallyDisabledControl)(element)) return;
const parentElements = getParentElements(element).reverse();
_dom.fireEvent.pointerOver(element, init);
for (const el of parentElements) {
_dom.fireEvent.pointerEnter(el, init);
}
if (!element.disabled) {
_dom.fireEvent.mouseOver(element, (0, _utils.getMouseEventOptions)('mouseover', init));
for (const el of parentElements) {
_dom.fireEvent.mouseEnter(el, (0, _utils.getMouseEventOptions)('mouseenter', init));
}
}
_dom.fireEvent.pointerMove(element, init);
if (!element.disabled) {
_dom.fireEvent.mouseMove(element, (0, _utils.getMouseEventOptions)('mousemove', init));
}
}
function unhover(element, init) {
if ((0, _utils.isLabelWithInternallyDisabledControl)(element)) return;
const parentElements = getParentElements(element);
_dom.fireEvent.pointerMove(element, init);
if (!element.disabled) {
_dom.fireEvent.mouseMove(element, (0, _utils.getMouseEventOptions)('mousemove', init));
}
_dom.fireEvent.pointerOut(element, init);
for (const el of parentElements) {
_dom.fireEvent.pointerLeave(el, init);
}
if (!element.disabled) {
_dom.fireEvent.mouseOut(element, (0, _utils.getMouseEventOptions)('mouseout', init));
for (const el of parentElements) {
_dom.fireEvent.mouseLeave(el, (0, _utils.getMouseEventOptions)('mouseleave', init));
}
}
}

View file

@ -0,0 +1,44 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "specialChars", {
enumerable: true,
get: function () {
return _type.specialCharMap;
}
});
exports.default = void 0;
var _click = require("./click");
var _type = require("./type");
var _clear = require("./clear");
var _tab = require("./tab");
var _hover = require("./hover");
var _upload = require("./upload");
var _selectOptions = require("./select-options");
var _paste = require("./paste");
const userEvent = {
click: _click.click,
dblClick: _click.dblClick,
type: _type.type,
clear: _clear.clear,
tab: _tab.tab,
hover: _hover.hover,
unhover: _hover.unhover,
upload: _upload.upload,
selectOptions: _selectOptions.selectOptions,
deselectOptions: _selectOptions.deselectOptions,
paste: _paste.paste
};
var _default = userEvent;
exports.default = _default;

View file

@ -0,0 +1,75 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.navigationKey = navigationKey;
var _dom = require("@testing-library/dom");
var _utils = require("../utils");
const keys = {
Home: {
keyCode: 36
},
End: {
keyCode: 35
},
ArrowLeft: {
keyCode: 37
},
ArrowRight: {
keyCode: 39
}
};
function getSelectionRange(currentElement, key) {
const {
selectionStart,
selectionEnd
} = currentElement();
if (key === 'Home') {
return {
selectionStart: 0,
selectionEnd: 0
};
}
if (key === 'End') {
return {
selectionStart: selectionEnd + 1,
selectionEnd: selectionEnd + 1
};
}
const cursorChange = Number(key in keys) * (key === 'ArrowLeft' ? -1 : 1);
return {
selectionStart: selectionStart + cursorChange,
selectionEnd: selectionEnd + cursorChange
};
}
function navigationKey(key) {
const event = {
key,
keyCode: keys[key].keyCode,
which: keys[key].keyCode
};
return ({
currentElement,
eventOverrides
}) => {
_dom.fireEvent.keyDown(currentElement(), { ...event,
...eventOverrides
});
const range = getSelectionRange(currentElement, key);
(0, _utils.setSelectionRangeIfNecessary)(currentElement(), range.selectionStart, range.selectionEnd);
_dom.fireEvent.keyUp(currentElement(), { ...event,
...eventOverrides
});
};
}

View file

@ -0,0 +1,55 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.paste = paste;
var _dom = require("@testing-library/dom");
var _utils = require("./utils");
function paste(element, text, init, {
initialSelectionStart,
initialSelectionEnd
} = {}) {
if (element.disabled) return;
if (typeof element.value === 'undefined') {
throw new TypeError(`the current element is of type ${element.tagName} and doesn't have a valid value`);
}
(0, _utils.eventWrapper)(() => element.focus()); // by default, a new element has it's selection start and end at 0
// but most of the time when people call "paste", they expect it to paste
// at the end of the current input value. So, if the selection start
// and end are both the default of 0, then we'll go ahead and change
// them to the length of the current value.
// the only time it would make sense to pass the initialSelectionStart or
// initialSelectionEnd is if you have an input with a value and want to
// explicitely start typing with the cursor at 0. Not super common.
if (element.selectionStart === 0 && element.selectionEnd === 0) {
(0, _utils.setSelectionRangeIfNecessary)(element, initialSelectionStart != null ? initialSelectionStart : element.value.length, initialSelectionEnd != null ? initialSelectionEnd : element.value.length);
}
_dom.fireEvent.paste(element, init);
if (!element.readOnly) {
const {
newValue,
newSelectionStart
} = (0, _utils.calculateNewValue)(text, element);
_dom.fireEvent.input(element, {
inputType: 'insertFromPaste',
target: {
value: newValue
}
});
(0, _utils.setSelectionRangeIfNecessary)(element, {
newSelectionStart,
newSelectionEnd: newSelectionStart
});
}
}

View file

@ -0,0 +1,118 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.deselectOptions = exports.selectOptions = void 0;
var _dom = require("@testing-library/dom");
var _utils = require("./utils");
var _click = require("./click");
var _focus = require("./focus");
var _hover = require("./hover");
function selectOptionsBase(newValue, select, values, init) {
if (!newValue && !select.multiple) {
throw (0, _dom.getConfig)().getElementError(`Unable to deselect an option in a non-multiple select. Use selectOptions to change the selection instead.`, select);
}
const valArray = Array.isArray(values) ? values : [values];
const allOptions = Array.from(select.querySelectorAll('option, [role="option"]'));
const selectedOptions = valArray.map(val => {
if (allOptions.includes(val)) {
return val;
} else {
const matchingOption = allOptions.find(o => o.value === val || o.innerHTML === val);
if (matchingOption) {
return matchingOption;
} else {
throw (0, _dom.getConfig)().getElementError(`Value "${val}" not found in options`, select);
}
}
}).filter(option => !option.disabled);
if (select.disabled || !selectedOptions.length) return;
if ((0, _utils.isInstanceOfElement)(select, 'HTMLSelectElement')) {
if (select.multiple) {
for (const option of selectedOptions) {
// events fired for multiple select are weird. Can't use hover...
_dom.fireEvent.pointerOver(option, init);
_dom.fireEvent.pointerEnter(select, init);
_dom.fireEvent.mouseOver(option);
_dom.fireEvent.mouseEnter(select);
_dom.fireEvent.pointerMove(option, init);
_dom.fireEvent.mouseMove(option, init);
_dom.fireEvent.pointerDown(option, init);
_dom.fireEvent.mouseDown(option, init);
(0, _focus.focus)(select, init);
_dom.fireEvent.pointerUp(option, init);
_dom.fireEvent.mouseUp(option, init);
selectOption(option);
_dom.fireEvent.click(option, init);
}
} else if (selectedOptions.length === 1) {
// the click to open the select options
(0, _click.click)(select, init);
selectOption(selectedOptions[0]); // the browser triggers another click event on the select for the click on the option
// this second click has no 'down' phase
_dom.fireEvent.pointerOver(select, init);
_dom.fireEvent.pointerEnter(select, init);
_dom.fireEvent.mouseOver(select);
_dom.fireEvent.mouseEnter(select);
_dom.fireEvent.pointerUp(select, init);
_dom.fireEvent.mouseUp(select, init);
_dom.fireEvent.click(select, init);
} else {
throw (0, _dom.getConfig)().getElementError(`Cannot select multiple options on a non-multiple select`, select);
}
} else if (select.getAttribute('role') === 'listbox') {
selectedOptions.forEach(option => {
(0, _hover.hover)(option, init);
(0, _click.click)(option, init);
(0, _hover.unhover)(option, init);
});
} else {
throw (0, _dom.getConfig)().getElementError(`Cannot select options on elements that are neither select nor listbox elements`, select);
}
function selectOption(option) {
option.selected = newValue;
(0, _dom.fireEvent)(select, (0, _dom.createEvent)('input', select, {
bubbles: true,
cancelable: false,
composed: true,
...init
}));
_dom.fireEvent.change(select, init);
}
}
const selectOptions = selectOptionsBase.bind(null, true);
exports.selectOptions = selectOptions;
const deselectOptions = selectOptionsBase.bind(null, false);
exports.deselectOptions = deselectOptions;

View file

@ -0,0 +1,138 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.tab = tab;
var _dom = require("@testing-library/dom");
var _utils = require("./utils");
var _focus = require("./focus");
var _blur = require("./blur");
function getNextElement(currentIndex, shift, elements, focusTrap) {
if (focusTrap === document && currentIndex === 0 && shift) {
return document.body;
} else if (focusTrap === document && currentIndex === elements.length - 1 && !shift) {
return document.body;
} else {
const nextIndex = shift ? currentIndex - 1 : currentIndex + 1;
const defaultIndex = shift ? elements.length - 1 : 0;
return elements[nextIndex] || elements[defaultIndex];
}
}
function tab({
shift = false,
focusTrap
} = {}) {
var _focusTrap$ownerDocum, _focusTrap;
const previousElement = (0, _utils.getActiveElement)((_focusTrap$ownerDocum = (_focusTrap = focusTrap) == null ? void 0 : _focusTrap.ownerDocument) != null ? _focusTrap$ownerDocum : document);
if (!focusTrap) {
focusTrap = document;
}
const focusableElements = focusTrap.querySelectorAll(_utils.FOCUSABLE_SELECTOR);
const enabledElements = [...focusableElements].filter(el => el === previousElement || el.getAttribute('tabindex') !== '-1' && !el.disabled && // Hidden elements are not tabable
(0, _utils.isVisible)(el));
if (enabledElements.length === 0) return;
const orderedElements = enabledElements.map((el, idx) => ({
el,
idx
})).sort((a, b) => {
// tabindex has no effect if the active element has tabindex="-1"
if (previousElement && previousElement.getAttribute('tabindex') === '-1') {
return a.idx - b.idx;
}
const tabIndexA = a.el.getAttribute('tabindex');
const tabIndexB = b.el.getAttribute('tabindex');
const diff = tabIndexA - tabIndexB;
return diff === 0 ? a.idx - b.idx : diff;
}).map(({
el
}) => el);
const checkedRadio = {};
let prunedElements = [];
orderedElements.forEach(el => {
// 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 (el.type === 'radio' && el.name) {
// If the active element is part of the group, add only that
if (previousElement && previousElement.type === el.type && previousElement.name === el.name) {
if (el === previousElement) {
prunedElements.push(el);
}
return;
} // If we stumble upon a checked radio, remove the others
if (el.checked) {
prunedElements = prunedElements.filter(e => e.type !== el.type || e.name !== el.name);
prunedElements.push(el);
checkedRadio[el.name] = el;
return;
} // If we already found the checked one, skip
if (checkedRadio[el.name]) {
return;
}
}
prunedElements.push(el);
});
const index = prunedElements.findIndex(el => el === previousElement);
const nextElement = getNextElement(index, shift, prunedElements, focusTrap);
const shiftKeyInit = {
key: 'Shift',
keyCode: 16,
shiftKey: true
};
const tabKeyInit = {
key: 'Tab',
keyCode: 9,
shiftKey: shift
};
let continueToTab = true; // not sure how to make it so there's no previous element...
// istanbul ignore else
if (previousElement) {
// preventDefault on the shift key makes no difference
if (shift) _dom.fireEvent.keyDown(previousElement, { ...shiftKeyInit
});
continueToTab = _dom.fireEvent.keyDown(previousElement, { ...tabKeyInit
});
}
const keyUpTarget = !continueToTab && previousElement ? previousElement : nextElement;
if (continueToTab) {
if (nextElement === document.body) {
(0, _blur.blur)(previousElement);
} else {
(0, _focus.focus)(nextElement);
}
}
_dom.fireEvent.keyUp(keyUpTarget, { ...tabKeyInit
});
if (shift) {
_dom.fireEvent.keyUp(keyUpTarget, { ...shiftKeyInit,
shiftKey: false
});
}
}
/*
eslint
complexity: "off",
max-statements: "off",
*/

View file

@ -0,0 +1,867 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.type = type;
exports.specialCharMap = void 0;
var _dom = require("@testing-library/dom");
var _utils = require("./utils");
var _click = require("./click");
var _navigationKey = require("./keys/navigation-key");
// TODO: wrap in asyncWrapper
const modifierCallbackMap = { ...createModifierCallbackEntries({
name: 'shift',
key: 'Shift',
keyCode: 16,
modifierProperty: 'shiftKey'
}),
...createModifierCallbackEntries({
name: 'ctrl',
key: 'Control',
keyCode: 17,
modifierProperty: 'ctrlKey'
}),
...createModifierCallbackEntries({
name: 'alt',
key: 'Alt',
keyCode: 18,
modifierProperty: 'altKey'
}),
...createModifierCallbackEntries({
name: 'meta',
key: 'Meta',
keyCode: 93,
modifierProperty: 'metaKey'
}),
// capslock is inline because of the need to fire both keydown and keyup on use, while preserving the modifier state.
'{capslock}': function capslockOn({
currentElement,
eventOverrides
}) {
const newEventOverrides = {
modifierCapsLock: true
};
_dom.fireEvent.keyDown(currentElement(), {
key: 'CapsLock',
keyCode: 20,
which: 20,
...eventOverrides,
...newEventOverrides
});
_dom.fireEvent.keyUp(currentElement(), {
key: 'CapsLock',
keyCode: 20,
which: 20,
...eventOverrides,
...newEventOverrides
});
return {
eventOverrides: newEventOverrides
};
},
'{/capslock}': function capslockOff({
currentElement,
eventOverrides
}) {
const newEventOverrides = {
modifierCapsLock: false
};
_dom.fireEvent.keyDown(currentElement(), {
key: 'CapsLock',
keyCode: 20,
which: 20,
...eventOverrides,
...newEventOverrides
});
_dom.fireEvent.keyUp(currentElement(), {
key: 'CapsLock',
keyCode: 20,
which: 20,
...eventOverrides,
...newEventOverrides
});
return {
eventOverrides: newEventOverrides
};
}
};
const specialCharMap = {
arrowLeft: '{arrowleft}',
arrowRight: '{arrowright}',
arrowDown: '{arrowdown}',
arrowUp: '{arrowup}',
enter: '{enter}',
escape: '{esc}',
delete: '{del}',
backspace: '{backspace}',
home: '{home}',
end: '{end}',
selectAll: '{selectall}',
space: '{space}',
whitespace: ' '
};
exports.specialCharMap = specialCharMap;
const specialCharCallbackMap = {
[specialCharMap.arrowLeft]: (0, _navigationKey.navigationKey)('ArrowLeft'),
[specialCharMap.arrowRight]: (0, _navigationKey.navigationKey)('ArrowRight'),
[specialCharMap.arrowDown]: handleArrowDown,
[specialCharMap.arrowUp]: handleArrowUp,
[specialCharMap.home]: (0, _navigationKey.navigationKey)('Home'),
[specialCharMap.end]: (0, _navigationKey.navigationKey)('End'),
[specialCharMap.enter]: handleEnter,
[specialCharMap.escape]: handleEsc,
[specialCharMap.delete]: handleDel,
[specialCharMap.backspace]: handleBackspace,
[specialCharMap.selectAll]: handleSelectall,
[specialCharMap.space]: handleSpace,
[specialCharMap.whitespace]: handleSpace
};
function wait(time) {
return new Promise(resolve => setTimeout(() => resolve(), time));
} // this needs to be wrapped in the event/asyncWrapper for React's act and angular's change detection
// depending on whether it will be async.
async function type(element, text, {
delay = 0,
...options
} = {}) {
// we do not want to wrap in the asyncWrapper if we're not
// going to actually be doing anything async, so we only wrap
// if the delay is greater than 0
let result;
if (delay > 0) {
await (0, _dom.getConfig)().asyncWrapper(async () => {
result = await typeImpl(element, text, {
delay,
...options
});
});
} else {
result = typeImpl(element, text, {
delay,
...options
});
}
return result;
}
async function typeImpl(element, text, {
delay,
skipClick = false,
skipAutoClose = false,
initialSelectionStart,
initialSelectionEnd
}) {
if (element.disabled) return;
if (!skipClick) (0, _click.click)(element);
if ((0, _utils.isContentEditable)(element) && document.getSelection().rangeCount === 0) {
const range = document.createRange();
range.setStart(element, 0);
range.setEnd(element, 0);
document.getSelection().addRange(range);
} // The focused element could change between each event, so get the currently active element each time
const currentElement = () => (0, _utils.getActiveElement)(element.ownerDocument); // by default, a new element has it's selection start and end at 0
// but most of the time when people call "type", they expect it to type
// at the end of the current input value. So, if the selection start
// and end are both the default of 0, then we'll go ahead and change
// them to the length of the current value.
// the only time it would make sense to pass the initialSelectionStart or
// initialSelectionEnd is if you have an input with a value and want to
// explicitely start typing with the cursor at 0. Not super common.
const value = (0, _utils.getValue)(currentElement());
const {
selectionStart,
selectionEnd
} = (0, _utils.getSelectionRange)(element);
if (value != null && selectionStart === 0 && selectionEnd === 0) {
(0, _utils.setSelectionRangeIfNecessary)(currentElement(), initialSelectionStart != null ? initialSelectionStart : value.length, initialSelectionEnd != null ? initialSelectionEnd : value.length);
}
const eventCallbacks = queueCallbacks();
await runCallbacks(eventCallbacks);
function queueCallbacks() {
const callbacks = [];
let remainingString = text;
while (remainingString) {
const {
callback,
remainingString: newRemainingString
} = getNextCallback(remainingString, skipAutoClose);
callbacks.push(callback);
remainingString = newRemainingString;
}
return callbacks;
}
async function runCallbacks(callbacks) {
const eventOverrides = {};
let prevWasMinus, prevWasPeriod, prevValue, typedValue;
for (const callback of callbacks) {
if (delay > 0) await wait(delay);
if (!currentElement().disabled) {
const returnValue = callback({
currentElement,
prevWasMinus,
prevWasPeriod,
prevValue,
eventOverrides,
typedValue
});
Object.assign(eventOverrides, returnValue == null ? void 0 : returnValue.eventOverrides);
prevWasMinus = returnValue == null ? void 0 : returnValue.prevWasMinus;
prevWasPeriod = returnValue == null ? void 0 : returnValue.prevWasPeriod;
prevValue = returnValue == null ? void 0 : returnValue.prevValue;
typedValue = returnValue == null ? void 0 : returnValue.typedValue;
}
}
}
}
function getNextCallback(remainingString, skipAutoClose) {
const modifierCallback = getModifierCallback(remainingString, skipAutoClose);
if (modifierCallback) {
return modifierCallback;
}
const specialCharCallback = getSpecialCharCallback(remainingString);
if (specialCharCallback) {
return specialCharCallback;
}
return getTypeCallback(remainingString);
}
function getModifierCallback(remainingString, skipAutoClose) {
const modifierKey = Object.keys(modifierCallbackMap).find(key => remainingString.startsWith(key));
if (!modifierKey) {
return null;
}
const callback = modifierCallbackMap[modifierKey]; // if this modifier has an associated "close" callback and the developer
// doesn't close it themselves, then we close it for them automatically
// Effectively if they send in: '{alt}a' then we type: '{alt}a{/alt}'
if (!skipAutoClose && callback.closeName && !remainingString.includes(callback.closeName)) {
remainingString += callback.closeName;
}
remainingString = remainingString.slice(modifierKey.length);
return {
callback,
remainingString
};
}
function getSpecialCharCallback(remainingString) {
const specialChar = Object.keys(specialCharCallbackMap).find(key => remainingString.startsWith(key));
if (!specialChar) {
return null;
}
return {
callback: specialCharCallbackMap[specialChar],
remainingString: remainingString.slice(specialChar.length)
};
}
function getTypeCallback(remainingString) {
const character = remainingString[0];
const callback = context => typeCharacter(character, context);
return {
callback,
remainingString: remainingString.slice(1)
};
}
function setSelectionRange({
currentElement,
newValue,
newSelectionStart
}) {
// if we *can* change the selection start, then we will if the new value
// is the same as the current value (so it wasn't programatically changed
// when the fireEvent.input was triggered).
// The reason we have to do this at all is because it actually *is*
// programmatically changed by fireEvent.input, so we have to simulate the
// browser's default behavior
const value = (0, _utils.getValue)(currentElement());
if (value === newValue) {
(0, _utils.setSelectionRangeIfNecessary)(currentElement(), newSelectionStart, newSelectionStart);
} else {
// If the currentValue is different than the expected newValue and we *can*
// change the selection range, than we should set it to the length of the
// currentValue to ensure that the browser behavior is mimicked.
(0, _utils.setSelectionRangeIfNecessary)(currentElement(), value.length, value.length);
}
}
function fireInputEventIfNeeded({
currentElement,
newValue,
newSelectionStart,
eventOverrides
}) {
const prevValue = (0, _utils.getValue)(currentElement());
if (!currentElement().readOnly && !(0, _utils.isClickableInput)(currentElement()) && newValue !== prevValue) {
if ((0, _utils.isContentEditable)(currentElement())) {
_dom.fireEvent.input(currentElement(), {
target: {
textContent: newValue
},
...eventOverrides
});
} else {
_dom.fireEvent.input(currentElement(), {
target: {
value: newValue
},
...eventOverrides
});
}
setSelectionRange({
currentElement,
newValue,
newSelectionStart
});
}
return {
prevValue
};
}
function typeCharacter(char, {
currentElement,
prevWasMinus = false,
prevWasPeriod = false,
prevValue = '',
typedValue = '',
eventOverrides
}) {
const key = char; // TODO: check if this also valid for characters with diacritic markers e.g. úé etc
const keyCode = char.charCodeAt(0);
let nextPrevWasMinus, nextPrevWasPeriod;
const textToBeTyped = typedValue + char;
const keyDownDefaultNotPrevented = _dom.fireEvent.keyDown(currentElement(), {
key,
keyCode,
which: keyCode,
...eventOverrides
});
if (keyDownDefaultNotPrevented) {
const keyPressDefaultNotPrevented = _dom.fireEvent.keyPress(currentElement(), {
key,
keyCode,
charCode: keyCode,
...eventOverrides
});
if ((0, _utils.getValue)(currentElement()) != null && keyPressDefaultNotPrevented) {
let newEntry = char;
if (prevWasMinus) {
newEntry = `-${char}`;
} else if (prevWasPeriod) {
newEntry = `${prevValue}.${char}`;
}
if ((0, _utils.isValidDateValue)(currentElement(), textToBeTyped)) {
newEntry = textToBeTyped;
}
const timeNewEntry = (0, _utils.buildTimeValue)(textToBeTyped);
if ((0, _utils.isValidInputTimeValue)(currentElement(), timeNewEntry)) {
newEntry = timeNewEntry;
}
const inputEvent = fireInputEventIfNeeded({ ...(0, _utils.calculateNewValue)(newEntry, currentElement()),
eventOverrides: {
data: key,
inputType: 'insertText',
...eventOverrides
},
currentElement
});
prevValue = inputEvent.prevValue;
if ((0, _utils.isValidDateValue)(currentElement(), textToBeTyped)) {
_dom.fireEvent.change(currentElement(), {
target: {
value: textToBeTyped
}
});
}
fireChangeForInputTimeIfValid(currentElement, prevValue, timeNewEntry); // typing "-" into a number input will not actually update the value
// so for the next character we type, the value should be set to
// `-${newEntry}`
// we also preserve the prevWasMinus when the value is unchanged due
// to typing an invalid character (typing "-a3" results in "-3")
// same applies for the decimal character.
if (currentElement().type === 'number') {
const newValue = (0, _utils.getValue)(currentElement());
if (newValue === prevValue && newEntry !== '-') {
nextPrevWasMinus = prevWasMinus;
} else {
nextPrevWasMinus = newEntry === '-';
}
if (newValue === prevValue && newEntry !== '.') {
nextPrevWasPeriod = prevWasPeriod;
} else {
nextPrevWasPeriod = newEntry === '.';
}
}
}
}
_dom.fireEvent.keyUp(currentElement(), {
key,
keyCode,
which: keyCode,
...eventOverrides
});
return {
prevWasMinus: nextPrevWasMinus,
prevWasPeriod: nextPrevWasPeriod,
prevValue,
typedValue: textToBeTyped
};
}
function fireChangeForInputTimeIfValid(currentElement, prevValue, timeNewEntry) {
if ((0, _utils.isValidInputTimeValue)(currentElement(), timeNewEntry) && prevValue !== timeNewEntry) {
_dom.fireEvent.change(currentElement(), {
target: {
value: timeNewEntry
}
});
}
} // yes, calculateNewBackspaceValue and calculateNewValue look extremely similar
// and you may be tempted to create a shared abstraction.
// If you, brave soul, decide to so endevor, please increment this count
// when you inevitably fail: 1
function calculateNewBackspaceValue(element) {
const {
selectionStart,
selectionEnd
} = (0, _utils.getSelectionRange)(element);
const value = (0, _utils.getValue)(element);
let newValue, newSelectionStart;
if (selectionStart === null) {
// at the end of an input type that does not support selection ranges
// https://github.com/testing-library/user-event/issues/316#issuecomment-639744793
newValue = value.slice(0, value.length - 1);
newSelectionStart = selectionStart - 1;
} else if (selectionStart === selectionEnd) {
if (selectionStart === 0) {
// at the beginning of the input
newValue = value;
newSelectionStart = selectionStart;
} else if (selectionStart === value.length) {
// at the end of the input
newValue = value.slice(0, value.length - 1);
newSelectionStart = selectionStart - 1;
} else {
// in the middle of the input
newValue = value.slice(0, selectionStart - 1) + value.slice(selectionEnd);
newSelectionStart = selectionStart - 1;
}
} else {
// we have something selected
const firstPart = value.slice(0, selectionStart);
newValue = firstPart + value.slice(selectionEnd);
newSelectionStart = firstPart.length;
}
return {
newValue,
newSelectionStart
};
}
function calculateNewDeleteValue(element) {
const {
selectionStart,
selectionEnd
} = (0, _utils.getSelectionRange)(element);
const value = (0, _utils.getValue)(element);
let newValue;
if (selectionStart === null) {
// at the end of an input type that does not support selection ranges
// https://github.com/testing-library/user-event/issues/316#issuecomment-639744793
newValue = value;
} else if (selectionStart === selectionEnd) {
if (selectionStart === 0) {
// at the beginning of the input
newValue = value.slice(1);
} else if (selectionStart === value.length) {
// at the end of the input
newValue = value;
} else {
// in the middle of the input
newValue = value.slice(0, selectionStart) + value.slice(selectionEnd + 1);
}
} else {
// we have something selected
const firstPart = value.slice(0, selectionStart);
newValue = firstPart + value.slice(selectionEnd);
}
return {
newValue,
newSelectionStart: selectionStart
};
}
function createModifierCallbackEntries({
name,
key,
keyCode,
modifierProperty
}) {
const openName = `{${name}}`;
const closeName = `{/${name}}`;
function open({
currentElement,
eventOverrides
}) {
const newEventOverrides = {
[modifierProperty]: true
};
_dom.fireEvent.keyDown(currentElement(), {
key,
keyCode,
which: keyCode,
...eventOverrides,
...newEventOverrides
});
return {
eventOverrides: newEventOverrides
};
}
open.closeName = closeName;
function close({
currentElement,
eventOverrides
}) {
const newEventOverrides = {
[modifierProperty]: false
};
_dom.fireEvent.keyUp(currentElement(), {
key,
keyCode,
which: keyCode,
...eventOverrides,
...newEventOverrides
});
return {
eventOverrides: newEventOverrides
};
}
return {
[openName]: open,
[closeName]: close
};
}
function handleEnter({
currentElement,
eventOverrides
}) {
const key = 'Enter';
const keyCode = 13;
const keyDownDefaultNotPrevented = _dom.fireEvent.keyDown(currentElement(), {
key,
keyCode,
which: keyCode,
...eventOverrides
});
if (keyDownDefaultNotPrevented) {
const keyPressDefaultNotPrevented = _dom.fireEvent.keyPress(currentElement(), {
key,
keyCode,
charCode: keyCode,
...eventOverrides
});
if (keyPressDefaultNotPrevented) {
if ((0, _utils.isClickableInput)(currentElement()) || // Links with href defined should handle Enter the same as a click
(0, _utils.isInstanceOfElement)(currentElement(), 'HTMLAnchorElement') && currentElement().href) {
_dom.fireEvent.click(currentElement(), { ...eventOverrides
});
}
if (currentElement().tagName === 'TEXTAREA') {
const {
newValue,
newSelectionStart
} = (0, _utils.calculateNewValue)('\n', currentElement());
_dom.fireEvent.input(currentElement(), {
target: {
value: newValue
},
inputType: 'insertLineBreak',
...eventOverrides
});
setSelectionRange({
currentElement,
newValue,
newSelectionStart
});
}
if (currentElement().tagName === 'INPUT' && currentElement().form && (currentElement().form.querySelectorAll('input').length === 1 || currentElement().form.querySelector('input[type="submit"]') || currentElement().form.querySelector('button[type="submit"]'))) {
_dom.fireEvent.submit(currentElement().form);
}
}
}
_dom.fireEvent.keyUp(currentElement(), {
key,
keyCode,
which: keyCode,
...eventOverrides
});
}
function handleEsc({
currentElement,
eventOverrides
}) {
const key = 'Escape';
const keyCode = 27;
_dom.fireEvent.keyDown(currentElement(), {
key,
keyCode,
which: keyCode,
...eventOverrides
}); // NOTE: Browsers do not fire a keypress on meta key presses
_dom.fireEvent.keyUp(currentElement(), {
key,
keyCode,
which: keyCode,
...eventOverrides
});
}
function handleDel({
currentElement,
eventOverrides
}) {
const key = 'Delete';
const keyCode = 46;
const keyPressDefaultNotPrevented = _dom.fireEvent.keyDown(currentElement(), {
key,
keyCode,
which: keyCode,
...eventOverrides
});
if (keyPressDefaultNotPrevented) {
fireInputEventIfNeeded({ ...calculateNewDeleteValue(currentElement()),
eventOverrides: {
inputType: 'deleteContentForward',
...eventOverrides
},
currentElement
});
}
_dom.fireEvent.keyUp(currentElement(), {
key,
keyCode,
which: keyCode,
...eventOverrides
});
}
function handleBackspace({
currentElement,
eventOverrides
}) {
const key = 'Backspace';
const keyCode = 8;
const keyPressDefaultNotPrevented = _dom.fireEvent.keyDown(currentElement(), {
key,
keyCode,
which: keyCode,
...eventOverrides
});
if (keyPressDefaultNotPrevented) {
fireInputEventIfNeeded({ ...calculateNewBackspaceValue(currentElement()),
eventOverrides: {
inputType: 'deleteContentBackward',
...eventOverrides
},
currentElement
});
}
_dom.fireEvent.keyUp(currentElement(), {
key,
keyCode,
which: keyCode,
...eventOverrides
});
}
function handleSelectall({
currentElement
}) {
currentElement().setSelectionRange(0, (0, _utils.getValue)(currentElement()).length);
}
function handleSpace(context) {
if ((0, _utils.isClickableInput)(context.currentElement())) {
handleSpaceOnClickable(context);
return;
}
typeCharacter(' ', context);
}
function handleSpaceOnClickable({
currentElement,
eventOverrides
}) {
const key = ' ';
const keyCode = 32;
const keyDownDefaultNotPrevented = _dom.fireEvent.keyDown(currentElement(), {
key,
keyCode,
which: keyCode,
...eventOverrides
});
if (keyDownDefaultNotPrevented) {
_dom.fireEvent.keyPress(currentElement(), {
key,
keyCode,
charCode: keyCode,
...eventOverrides
});
}
const keyUpDefaultNotPrevented = _dom.fireEvent.keyUp(currentElement(), {
key,
keyCode,
which: keyCode,
...eventOverrides
});
if (keyDownDefaultNotPrevented && keyUpDefaultNotPrevented) {
_dom.fireEvent.click(currentElement(), { ...eventOverrides
});
}
}
function handleArrowDown({
currentElement,
eventOverrides
}) {
const key = 'ArrowDown';
const keyCode = 40;
_dom.fireEvent.keyDown(currentElement(), {
key,
keyCode,
which: keyCode,
...eventOverrides
});
_dom.fireEvent.keyUp(currentElement(), {
key,
keyCode,
which: keyCode,
...eventOverrides
});
}
function handleArrowUp({
currentElement,
eventOverrides
}) {
const key = 'ArrowUp';
const keyCode = 38;
_dom.fireEvent.keyDown(currentElement(), {
key,
keyCode,
which: keyCode,
...eventOverrides
});
_dom.fireEvent.keyUp(currentElement(), {
key,
keyCode,
which: keyCode,
...eventOverrides
});
}

View file

@ -0,0 +1,74 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.upload = upload;
var _dom = require("@testing-library/dom");
var _click = require("./click");
var _blur = require("./blur");
var _focus = require("./focus");
function upload(element, fileOrFiles, init, {
applyAccept = false
} = {}) {
if (element.disabled) return;
(0, _click.click)(element, init);
const input = element.tagName === 'LABEL' ? element.control : element;
const files = (Array.isArray(fileOrFiles) ? fileOrFiles : [fileOrFiles]).filter(file => !applyAccept || isAcceptableFile(file, element.accept)).slice(0, input.multiple ? undefined : 1); // blur fires when the file selector pops up
(0, _blur.blur)(element, init); // focus fires when they make their selection
(0, _focus.focus)(element, init); // do not fire an input event if the file selection does not change
if (files.length === input.files.length && files.every((f, i) => f === input.files.item(i))) {
return;
} // the event fired in the browser isn't actually an "input" or "change" event
// but a new Event with a type set to "input" and "change"
// Kinda odd...
const inputFiles = {
length: files.length,
item: index => files[index],
...files
};
(0, _dom.fireEvent)(input, (0, _dom.createEvent)('input', input, {
target: {
files: inputFiles
},
bubbles: true,
cancelable: false,
composed: true,
...init
}));
_dom.fireEvent.change(input, {
target: {
files: inputFiles
},
...init
});
}
function isAcceptableFile(file, accept) {
if (!accept) {
return true;
}
const wildcards = ['audio/*', 'image/*', 'video/*'];
return accept.split(',').some(acceptToken => {
if (acceptToken[0] === '.') {
// tokens starting with a dot represent a file extension
return file.name.endsWith(acceptToken);
} else if (wildcards.includes(acceptToken)) {
return file.type.startsWith(acceptToken.substr(0, acceptToken.length - 1));
}
return file.type === acceptToken;
});
}

View file

@ -0,0 +1,354 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.isFocusable = isFocusable;
exports.isClickableInput = isClickableInput;
exports.getMouseEventOptions = getMouseEventOptions;
exports.isLabelWithInternallyDisabledControl = isLabelWithInternallyDisabledControl;
exports.getActiveElement = getActiveElement;
exports.calculateNewValue = calculateNewValue;
exports.setSelectionRangeIfNecessary = setSelectionRangeIfNecessary;
exports.eventWrapper = eventWrapper;
exports.isValidDateValue = isValidDateValue;
exports.isValidInputTimeValue = isValidInputTimeValue;
exports.buildTimeValue = buildTimeValue;
exports.getValue = getValue;
exports.getSelectionRange = getSelectionRange;
exports.isContentEditable = isContentEditable;
exports.isInstanceOfElement = isInstanceOfElement;
exports.isVisible = isVisible;
exports.FOCUSABLE_SELECTOR = void 0;
var _dom = require("@testing-library/dom");
var _helpers = require("@testing-library/dom/dist/helpers");
// isInstanceOfElement can be removed once the peerDependency for @testing-library/dom is bumped to a version that includes https://github.com/testing-library/dom-testing-library/pull/885
/**
* Check if an element is of a given type.
*
* @param {Element} element The element to test
* @param {string} elementType Constructor name. E.g. 'HTMLSelectElement'
*/
function isInstanceOfElement(element, elementType) {
try {
const window = (0, _helpers.getWindowFromNode)(element); // Window usually has the element constructors as properties but is not required to do so per specs
if (typeof window[elementType] === 'function') {
return element instanceof window[elementType];
}
} catch (e) {// The document might not be associated with a window
} // Fall back to the constructor name as workaround for test environments that
// a) not associate the document with a window
// b) not provide the constructor as property of window
if (/^HTML(\w+)Element$/.test(element.constructor.name)) {
return element.constructor.name === elementType;
} // The user passed some node that is not created in a browser-like environment
throw new Error(`Unable to verify if element is instance of ${elementType}. Please file an issue describing your test environment: https://github.com/testing-library/dom-testing-library/issues/new`);
}
function isMousePressEvent(event) {
return event === 'mousedown' || event === 'mouseup' || event === 'click' || event === 'dblclick';
}
function invert(map) {
const res = {};
for (const key of Object.keys(map)) {
res[map[key]] = key;
}
return res;
} // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
const BUTTONS_TO_NAMES = {
0: 'none',
1: 'primary',
2: 'secondary',
4: 'auxiliary'
};
const NAMES_TO_BUTTONS = invert(BUTTONS_TO_NAMES); // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
const BUTTON_TO_NAMES = {
0: 'primary',
1: 'auxiliary',
2: 'secondary'
};
const NAMES_TO_BUTTON = invert(BUTTON_TO_NAMES);
function convertMouseButtons(event, init, property, mapping) {
if (!isMousePressEvent(event)) {
return 0;
}
if (init[property] != null) {
return init[property];
}
if (init.buttons != null) {
// not sure how to test this. Feel free to try and add a test if you want.
// istanbul ignore next
return mapping[BUTTONS_TO_NAMES[init.buttons]] || 0;
}
if (init.button != null) {
// not sure how to test this. Feel free to try and add a test if you want.
// istanbul ignore next
return mapping[BUTTON_TO_NAMES[init.button]] || 0;
}
return property != 'button' && isMousePressEvent(event) ? 1 : 0;
}
function getMouseEventOptions(event, init, clickCount = 0) {
init = init || {};
return { ...init,
// https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail
detail: event === 'mousedown' || event === 'mouseup' || event === 'click' ? 1 + clickCount : clickCount,
buttons: convertMouseButtons(event, init, 'buttons', NAMES_TO_BUTTONS),
button: convertMouseButtons(event, init, 'button', NAMES_TO_BUTTON)
};
} // Absolutely NO events fire on label elements that contain their control
// if that control is disabled. NUTS!
// no joke. There are NO events for: <label><input disabled /><label>
function isLabelWithInternallyDisabledControl(element) {
var _element$control;
return element.tagName === 'LABEL' && ((_element$control = element.control) == null ? void 0 : _element$control.disabled) && element.contains(element.control);
}
function getActiveElement(document) {
const activeElement = document.activeElement;
if (activeElement != null && activeElement.shadowRoot) {
return getActiveElement(activeElement.shadowRoot);
} else {
return activeElement;
}
}
function supportsMaxLength(element) {
if (element.tagName === 'TEXTAREA') return true;
if (element.tagName === 'INPUT') {
const type = element.getAttribute('type'); // Missing value default is "text"
if (!type) return true; // https://html.spec.whatwg.org/multipage/input.html#concept-input-apply
if (type.match(/email|password|search|telephone|text|url/)) return true;
}
return false;
}
function getSelectionRange(element) {
if (isContentEditable(element)) {
const range = element.ownerDocument.getSelection().getRangeAt(0);
return {
selectionStart: range.startOffset,
selectionEnd: range.endOffset
};
}
return {
selectionStart: element.selectionStart,
selectionEnd: element.selectionEnd
};
} //jsdom is not supporting isContentEditable
function isContentEditable(element) {
return element.hasAttribute('contenteditable') && (element.getAttribute('contenteditable') == 'true' || element.getAttribute('contenteditable') == '');
}
function getValue(element) {
if (isContentEditable(element)) {
return element.textContent;
}
return element.value;
}
function calculateNewValue(newEntry, element) {
var _element$getAttribute;
const {
selectionStart,
selectionEnd
} = getSelectionRange(element);
const value = getValue(element); // can't use .maxLength property because of a jsdom bug:
// https://github.com/jsdom/jsdom/issues/2927
const maxLength = Number((_element$getAttribute = element.getAttribute('maxlength')) != null ? _element$getAttribute : -1);
let newValue, newSelectionStart;
if (selectionStart === null) {
// at the end of an input type that does not support selection ranges
// https://github.com/testing-library/user-event/issues/316#issuecomment-639744793
newValue = value + newEntry;
} else if (selectionStart === selectionEnd) {
if (selectionStart === 0) {
// at the beginning of the input
newValue = newEntry + value;
} else if (selectionStart === value.length) {
// at the end of the input
newValue = value + newEntry;
} else {
// in the middle of the input
newValue = value.slice(0, selectionStart) + newEntry + value.slice(selectionEnd);
}
newSelectionStart = selectionStart + newEntry.length;
} else {
// we have something selected
const firstPart = value.slice(0, selectionStart) + newEntry;
newValue = firstPart + value.slice(selectionEnd);
newSelectionStart = firstPart.length;
}
if (element.type === 'date' && !isValidDateValue(element, newValue)) {
newValue = value;
}
if (element.type === 'time' && !isValidInputTimeValue(element, newValue)) {
if (isValidInputTimeValue(element, newEntry)) {
newValue = newEntry;
} else {
newValue = value;
}
}
if (!supportsMaxLength(element) || maxLength < 0) {
return {
newValue,
newSelectionStart
};
} else {
return {
newValue: newValue.slice(0, maxLength),
newSelectionStart: newSelectionStart > maxLength ? maxLength : newSelectionStart
};
}
}
function setSelectionRangeIfNecessary(element, newSelectionStart, newSelectionEnd) {
const {
selectionStart,
selectionEnd
} = getSelectionRange(element);
if (!isContentEditable(element) && (!element.setSelectionRange || selectionStart === null)) {
// cannot set selection
return;
}
if (selectionStart !== newSelectionStart || selectionEnd !== newSelectionStart) {
if (isContentEditable(element)) {
const range = element.ownerDocument.createRange();
range.selectNodeContents(element);
range.setStart(element.firstChild, newSelectionStart);
range.setEnd(element.firstChild, newSelectionEnd);
element.ownerDocument.getSelection().removeAllRanges();
element.ownerDocument.getSelection().addRange(range);
} else {
element.setSelectionRange(newSelectionStart, newSelectionEnd);
}
}
}
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(', ');
exports.FOCUSABLE_SELECTOR = FOCUSABLE_SELECTOR;
function isFocusable(element) {
return !isLabelWithInternallyDisabledControl(element) && (element == null ? void 0 : element.matches(FOCUSABLE_SELECTOR));
}
const CLICKABLE_INPUT_TYPES = ['button', 'color', 'file', 'image', 'reset', 'submit'];
function isClickableInput(element) {
return element.tagName === 'BUTTON' || isInstanceOfElement(element, 'HTMLInputElement') && CLICKABLE_INPUT_TYPES.includes(element.type);
}
function isVisible(element) {
const getComputedStyle = (0, _helpers.getWindowFromNode)(element).getComputedStyle;
for (; element && element.ownerDocument; element = element.parentNode) {
const display = getComputedStyle(element).display;
if (display === 'none') {
return false;
}
}
return true;
}
function eventWrapper(cb) {
let result;
(0, _dom.getConfig)().eventWrapper(() => {
result = cb();
});
return result;
}
function isValidDateValue(element, value) {
if (element.type !== 'date') return false;
const clone = element.cloneNode();
clone.value = value;
return clone.value === value;
}
function buildTimeValue(value) {
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')}`;
}
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 isValidInputTimeValue(element, timeValue) {
if (element.type !== 'time') return false;
const clone = element.cloneNode();
clone.value = timeValue;
return clone.value === timeValue;
}