/
vshmidt
/
label-studio
Обзор
Документация
Войти
/
vshmidt
/
label-studio
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
web/libs/editor/src/utils/InputMask.ts
293 строки
9 KB
Raul Martin
refactor: echo 27: Introducing biome (#5759)
29 апр 2024, 11:39
Не верифицирован
29 апр 2024, 11:39
2660ec9
Код
Авторство
О чём код?
export class MaskUtil { input: HTMLInputElement; maskPattern: string; proxyChar: string; numValidate: any; stringValidate: any; mask: any; validators: any; placeholder: string; regExp: string; onChange: (value: string) => void; /** * Construct a new MaskUtil instance * @param {HTMLInputElement} input - The input to be masked * @param {string} pattern - The pattern to validate against * @param {string} proxyChar - The placeholder string */ constructor(input: HTMLInputElement, pattern: string, onChange: (value: string) => void, proxyChar = "_") { this.input = input; this.maskPattern = pattern; this.proxyChar = proxyChar; this.onChange = onChange; /** Validation patterns */ this.numValidate = /^\d$/; this.stringValidate = /^[a-zA-Z]$/; /** * Construct the mask object * @property { string } char - The validation character || required string * @property { RegExp } validator - The regular expression to validate against */ this.mask = pattern.split("").map((char: any) => { let validator; if (char === "A") { validator = this.stringValidate; } else if (char === "1") { validator = this.numValidate; } return { char, validator }; }); /** Only mask implementations that include validators */ this.validators = this.mask.filter((charData: any) => charData.validator); /** Construct a default placeholder for use by component if one is not supplied */ this.placeholder = this.mask .map((char: any) => { if (char.validator) { return this.proxyChar; } return char.char; }) .join(""); /** Characters that need to be escaped */ const escape = "\\^$*+?.()|{}[]".split(""); /** Construct a string to be used as a pattern setting in an input component */ const regExp = this.mask .map((entry: any) => { const { validator, char } = entry; if (validator) { return validator === this.numValidate ? "\\d" : "[a-zA-Z]"; } if (escape.includes(char)) { return `\\${char}`; } return char; }) .join(""); /** Provide the regular expression */ this.regExp = regExp; input.pattern = regExp; input.placeholder = input.placeholder || this.placeholder; input.addEventListener("keydown", this.__inputKeydownMask.bind(this)); input.addEventListener("paste", this.__inputPaste.bind(this)); input.addEventListener("focus", this.__inputFocus.bind(this)); input.addEventListener("blur", this.__inputBlur.bind(this)); } /** * Take some raw data and return the masked version * @param {string} data - The raw string to parse into the proper format * @return { false | string } - If valid, return the parsed string, otherwise false */ parseRaw(data: any) { data = data || ""; const filteredData = data.replace(/\W/g, ""); if (filteredData.length === this.validators.length) { const isValid = filteredData .split("") .map((char: any, index: any) => !!char.match(this.validators[index].validator)) .reduce((accumulator: any, currentValue: any) => { if (currentValue === false) { return false; } return accumulator; }); if (!isValid) { return false; } let pointer = -1; return this.mask .map((maskObj: any) => { if (maskObj.validator) { pointer += 1; return filteredData[pointer]; } return maskObj.char; }) .join(""); } } /** * Remove event listeners */ disconnect() { this.input.addEventListener("keydown", this.__inputKeydownMask.bind(this)); this.input.addEventListener("paste", this.__inputPaste.bind(this)); this.input.addEventListener("focus", this.__inputFocus.bind(this)); this.input.addEventListener("blur", this.__inputBlur.bind(this)); } /** Simple masked value getter */ get value() { return this.parseRaw(this.input.value); } /** * Parse an incomplete stringa and splice in placeholders * @param {string} data - A partial string to mask * @return {string} - A masked string with the additional placeholders */ parsePartial(data = "") { data = data || ""; const filteredData = data.replace(/\W/g, ""); let pointer = -1; return ( this.mask .map((maskObj: any) => { if (maskObj.validator) { pointer += 1; return filteredData[pointer] || this.proxyChar; } return maskObj.char || this.proxyChar; }) .join("") || this.placeholder ); } /** * Splice characters into a base string and return the result * @param {string} string - The base string * @param {number} index - The index at which to splice * @param {string} chars - The characters to splice into the base string * @return { string } - A newly-spliced string */ splice(string: string, index: number, chars: string) { return string.slice(0, index) + chars + string.slice(index + 1); } /** * On input blur, remove the element's value * if it matches the placeholder * @param {Event} event - A blur event */ __inputBlur(event: any) { if (event.target.value === this.placeholder) { this.onChange(""); } } /** * On input focus, set the value to the placeholder * for the mask and set the selection at 0. * @param {Event} event - A focus event */ __inputFocus(event: any) { const value = event.target.value; if (!value) { this.onChange(this.placeholder); } } /** * On input keydown, set manage the input's value * @param {Event} event - A keydown event */ __inputKeydownMask(event: any) { const { selectionStart, selectionEnd } = event.target; const key = event.key; let index = selectionStart > this.mask.length - 1 ? this.mask.length - 1 : selectionStart; let mask = this.mask[index]; /** Set up which keys to ignore */ const ignored = ["Tab", "Enter", "Escape", "ArrowLeft", "ArrowRight", "Shift"]; if (ignored.includes(key) || event.metaKey) { return; } /** If the value isn't a replacement of multiple characters */ if (selectionStart === selectionEnd) { event.preventDefault(); let _removingKey = null; if (key === "Backspace") _removingKey = 1; else if (key === "Delete") _removingKey = 0; if (_removingKey !== null) { /** If this is a delete event, replace the deleted element with the placeholder */ const previous = this.mask[selectionStart - _removingKey]; if (previous) { const replacement = previous.validator ? this.proxyChar : previous.char; this.onChange(this.splice(event.target.value, selectionStart - _removingKey, replacement)); event.target.setSelectionRange(selectionStart - _removingKey, selectionStart - _removingKey); } return; } /** While the input doesn't have a validator, splice character in */ while (mask && !mask.validator && key !== mask.char) { this.onChange(this.splice(event.target.value, index, mask.char)); event.target.setSelectionRange(index + 1, index + 1); mask = this.mask[index + 1]; index += 1; } /** If we have a validator for the key */ if (mask && mask.validator) { const match = !!key.match(mask.validator); /** Don't allow non-matches */ if (!match) { event.preventDefault(); return false; } } /** Splice in the added data */ this.onChange(this.splice(event.target.value, index, key)); setTimeout((target) => target.setSelectionRange(index + 1, index + 1), 0, event.target); } else { /** If this input replaces multiple items, check its validity and format if possible */ setTimeout(() => { let partialValue = event.target.value; const newKey = key === "Backspace" || key === "Delete" ? this.proxyChar : key; const selectionPosition = key === "Backspace" || key === "Delete" ? selectionStart : selectionStart + 1; for (let i = selectionStart; i < selectionEnd; i++) { if (partialValue[i] !== ":") { partialValue = `${partialValue.substring(0, i)}${ i === selectionStart ? newKey : this.proxyChar }${partialValue.substring(i + 1, partialValue.length)}`; } } this.onChange(partialValue); this.input.setSelectionRange(selectionPosition, selectionPosition); }); } } /** * On input paste, validate the new data * @param {Event} event - Paste event */ __inputPaste(event: any) { const data = event.clipboardData.getData("text/plain"); const maskedData = this.parseRaw(data); if (maskedData !== false) { // this.setCustomValidity(''); setTimeout(() => { this.onChange(maskedData); }); } } }