vira
Version:
A simple and highly versatile design system using element-vir.
113 lines (112 loc) • 3.48 kB
JavaScript
import { check, checkWrap } from '@augment-vir/assert';
import { extractEventTarget } from '@augment-vir/web';
function doesMatch({ input, matcher }) {
if (!input || !matcher) {
return true;
}
else if (input.length > 1) {
return input.split('').every((singleInput) => doesMatch({
input: singleInput,
matcher,
}));
}
else if (matcher instanceof RegExp) {
return !!input.match(matcher);
}
else {
return matcher.includes(input);
}
}
function isAllowed({ value: rawValue, allowed, blocked }) {
const value = String(rawValue);
const isAllowedCharacter = allowed
? doesMatch({
input: value,
matcher: allowed,
})
: true;
const isBlockedCharacter = blocked
? doesMatch({
input: value,
matcher: blocked,
})
: false;
return isAllowedCharacter && !isBlockedCharacter;
}
/**
* Filters out blocked text from an input element's value.
*
* @category Internal
*/
export function filterTextInputValue(inputs) {
const value = String(inputs.value);
if (!inputs.value) {
return {
filtered: value,
blocked: '',
};
}
const { filtered, blocked } = value.split('').reduce((accum, letter) => {
const allowed = isAllowed({
...inputs,
value: letter,
});
if (allowed) {
accum.filtered.push(letter);
}
else {
accum.blocked.push(letter);
}
return accum;
}, {
filtered: [],
blocked: [],
});
return {
filtered: filtered.join(''),
blocked: blocked.join(''),
};
}
/**
* A function to be called when an input element's value changes.
*
* @category Internal
*/
export function textInputListener({ inputs, previousValue, event, elementConstructor, inputBlockedCallback, newValueCallback, }) {
const inputElement = extractEventTarget(event, elementConstructor || HTMLInputElement);
/**
* This is usually a single character, but can be a bunch of characters in some circumstances.
* For example, when a bunch of characters are pasted, this will be the entire pasted contents.
*
* When a password manager auto fills the password, at least for Safari + iCloud Keychain, it'll
* fire a `CustomEvent` (rather than the typical `InputEvent`) and `event.data` won't be
* populated.
*/
const changedText = (check.hasKey(event, 'data') && checkWrap.isString(event.data)) || '';
/**
* When changedText is falsy, that means an operation other than inserting characters happened.
* Such as: deleting, cutting the text, etc.
*/
if (changedText) {
const { blocked } = filterTextInputValue({
value: changedText,
allowed: inputs.allowedInputs,
blocked: inputs.blockedInputs,
});
if (blocked.length) {
inputBlockedCallback(blocked);
}
}
const finalValue = filterTextInputValue({
value: inputElement.value,
allowed: inputs.allowedInputs,
blocked: inputs.blockedInputs,
}).filtered;
if (inputElement.value !== finalValue) {
// this prevents blocked inputs by simply overwriting them
inputElement.value = finalValue;
}
if (previousValue !== finalValue) {
newValueCallback(finalValue);
}
}