@maskito/core
Advanced tools
Comparing version 1.9.0 to 2.0.0
1253
index.esm.js
@@ -1,340 +0,284 @@ | ||
const MASKITO_DEFAULT_ELEMENT_PREDICATE = e => e.querySelector('input,textarea') || e; | ||
const MASKITO_DEFAULT_ELEMENT_PREDICATE = e => e.querySelector('input,textarea') || | ||
e; | ||
const MASKITO_DEFAULT_OPTIONS = { | ||
mask: /^.*$/, | ||
preprocessors: [], | ||
postprocessors: [], | ||
plugins: [], | ||
overwriteMode: 'shift' | ||
mask: /^.*$/, | ||
preprocessors: [], | ||
postprocessors: [], | ||
plugins: [], | ||
overwriteMode: 'shift', | ||
}; | ||
class MaskHistory { | ||
constructor() { | ||
this.now = null; | ||
this.past = []; | ||
this.future = []; | ||
} | ||
undo() { | ||
const state = this.past.pop(); | ||
if (state && this.now) { | ||
this.future.push(this.now); | ||
this.updateElement(state, 'historyUndo'); | ||
constructor() { | ||
this.now = null; | ||
this.past = []; | ||
this.future = []; | ||
} | ||
} | ||
redo() { | ||
const state = this.future.pop(); | ||
if (state && this.now) { | ||
this.past.push(this.now); | ||
this.updateElement(state, 'historyRedo'); | ||
undo() { | ||
const state = this.past.pop(); | ||
if (state && this.now) { | ||
this.future.push(this.now); | ||
this.updateElement(state, 'historyUndo'); | ||
} | ||
} | ||
} | ||
updateHistory(state) { | ||
if (!this.now) { | ||
this.now = state; | ||
return; | ||
redo() { | ||
const state = this.future.pop(); | ||
if (state && this.now) { | ||
this.past.push(this.now); | ||
this.updateElement(state, 'historyRedo'); | ||
} | ||
} | ||
const isValueChanged = this.now.value !== state.value; | ||
const isSelectionChanged = this.now.selection.some((item, index) => item !== state.selection[index]); | ||
if (!isValueChanged && !isSelectionChanged) { | ||
return; | ||
updateHistory(state) { | ||
if (!this.now) { | ||
this.now = state; | ||
return; | ||
} | ||
const isValueChanged = this.now.value !== state.value; | ||
const isSelectionChanged = this.now.selection.some((item, index) => item !== state.selection[index]); | ||
if (!isValueChanged && !isSelectionChanged) { | ||
return; | ||
} | ||
if (isValueChanged) { | ||
this.past.push(this.now); | ||
this.future = []; | ||
} | ||
this.now = state; | ||
} | ||
if (isValueChanged) { | ||
this.past.push(this.now); | ||
this.future = []; | ||
updateElement(state, inputType) { | ||
this.now = state; | ||
this.updateElementState(state, { inputType, data: null }); | ||
} | ||
this.now = state; | ||
} | ||
updateElement(state, inputType) { | ||
this.now = state; | ||
this.updateElementState(state, { | ||
inputType, | ||
data: null | ||
}); | ||
} | ||
} | ||
function areElementValuesEqual(sampleState, ...states) { | ||
return states.every(({ | ||
value | ||
}) => value === sampleState.value); | ||
return states.every(({ value }) => value === sampleState.value); | ||
} | ||
function areElementStatesEqual(sampleState, ...states) { | ||
return states.every(({ | ||
value, | ||
selection | ||
}) => value === sampleState.value && selection[0] === sampleState.selection[0] && selection[1] === sampleState.selection[1]); | ||
return states.every(({ value, selection }) => value === sampleState.value && | ||
selection[0] === sampleState.selection[0] && | ||
selection[1] === sampleState.selection[1]); | ||
} | ||
function applyOverwriteMode({ | ||
value, | ||
selection | ||
}, newCharacters, mode) { | ||
const [from, to] = selection; | ||
const computedMode = typeof mode === 'function' ? mode({ | ||
value, | ||
selection | ||
}) : mode; | ||
return { | ||
value, | ||
selection: computedMode === 'replace' ? [from, from + newCharacters.length] : [from, to] | ||
}; | ||
function applyOverwriteMode({ value, selection }, newCharacters, mode) { | ||
const [from, to] = selection; | ||
const computedMode = typeof mode === 'function' ? mode({ value, selection }) : mode; | ||
return { | ||
value, | ||
selection: computedMode === 'replace' ? [from, from + newCharacters.length] : [from, to], | ||
}; | ||
} | ||
function isFixedCharacter(char) { | ||
return typeof char === 'string'; | ||
return typeof char === 'string'; | ||
} | ||
function getLeadingFixedCharacters(mask, validatedValuePart, newCharacter, initialElementState) { | ||
let leadingFixedCharacters = ''; | ||
for (let i = validatedValuePart.length; i < mask.length; i++) { | ||
const charConstraint = mask[i]; | ||
const isInitiallyExisted = (initialElementState === null || initialElementState === void 0 ? void 0 : initialElementState.value[i]) === charConstraint; | ||
if (!isFixedCharacter(charConstraint) || charConstraint === newCharacter && !isInitiallyExisted) { | ||
return leadingFixedCharacters; | ||
let leadingFixedCharacters = ''; | ||
for (let i = validatedValuePart.length; i < mask.length; i++) { | ||
const charConstraint = mask[i]; | ||
const isInitiallyExisted = (initialElementState === null || initialElementState === void 0 ? void 0 : initialElementState.value[i]) === charConstraint; | ||
if (!isFixedCharacter(charConstraint) || | ||
(charConstraint === newCharacter && !isInitiallyExisted)) { | ||
return leadingFixedCharacters; | ||
} | ||
leadingFixedCharacters += charConstraint; | ||
} | ||
leadingFixedCharacters += charConstraint; | ||
} | ||
return leadingFixedCharacters; | ||
return leadingFixedCharacters; | ||
} | ||
function validateValueWithMask(value, maskExpression) { | ||
if (Array.isArray(maskExpression)) { | ||
return value.length === maskExpression.length && Array.from(value).every((char, i) => { | ||
const charConstraint = maskExpression[i]; | ||
return isFixedCharacter(charConstraint) ? char === charConstraint : char.match(charConstraint); | ||
}); | ||
} | ||
return maskExpression.test(value); | ||
if (Array.isArray(maskExpression)) { | ||
return (value.length === maskExpression.length && | ||
Array.from(value).every((char, i) => { | ||
const charConstraint = maskExpression[i]; | ||
return isFixedCharacter(charConstraint) | ||
? char === charConstraint | ||
: char.match(charConstraint); | ||
})); | ||
} | ||
return maskExpression.test(value); | ||
} | ||
function guessValidValueByPattern(elementState, mask, initialElementState) { | ||
let maskedFrom = null; | ||
let maskedTo = null; | ||
const maskedValue = Array.from(elementState.value).reduce((validatedCharacters, char, charIndex) => { | ||
const leadingCharacters = getLeadingFixedCharacters(mask, validatedCharacters, char, initialElementState); | ||
const newValidatedChars = validatedCharacters + leadingCharacters; | ||
const charConstraint = mask[newValidatedChars.length]; | ||
if (isFixedCharacter(charConstraint)) { | ||
return newValidatedChars + charConstraint; | ||
} | ||
if (!char.match(charConstraint)) { | ||
return newValidatedChars; | ||
} | ||
if (maskedFrom === null && charIndex >= elementState.selection[0]) { | ||
maskedFrom = newValidatedChars.length; | ||
} | ||
if (maskedTo === null && charIndex >= elementState.selection[1]) { | ||
maskedTo = newValidatedChars.length; | ||
} | ||
return newValidatedChars + char; | ||
}, ''); | ||
const trailingFixedCharacters = getLeadingFixedCharacters(mask, maskedValue, '', initialElementState); | ||
return { | ||
value: validateValueWithMask(maskedValue + trailingFixedCharacters, mask) ? maskedValue + trailingFixedCharacters : maskedValue, | ||
selection: [maskedFrom !== null && maskedFrom !== void 0 ? maskedFrom : maskedValue.length, maskedTo !== null && maskedTo !== void 0 ? maskedTo : maskedValue.length] | ||
}; | ||
let maskedFrom = null; | ||
let maskedTo = null; | ||
const maskedValue = Array.from(elementState.value).reduce((validatedCharacters, char, charIndex) => { | ||
const leadingCharacters = getLeadingFixedCharacters(mask, validatedCharacters, char, initialElementState); | ||
const newValidatedChars = validatedCharacters + leadingCharacters; | ||
const charConstraint = mask[newValidatedChars.length]; | ||
if (isFixedCharacter(charConstraint)) { | ||
return newValidatedChars + charConstraint; | ||
} | ||
if (!char.match(charConstraint)) { | ||
return newValidatedChars; | ||
} | ||
if (maskedFrom === null && charIndex >= elementState.selection[0]) { | ||
maskedFrom = newValidatedChars.length; | ||
} | ||
if (maskedTo === null && charIndex >= elementState.selection[1]) { | ||
maskedTo = newValidatedChars.length; | ||
} | ||
return newValidatedChars + char; | ||
}, ''); | ||
const trailingFixedCharacters = getLeadingFixedCharacters(mask, maskedValue, '', initialElementState); | ||
return { | ||
value: validateValueWithMask(maskedValue + trailingFixedCharacters, mask) | ||
? maskedValue + trailingFixedCharacters | ||
: maskedValue, | ||
selection: [maskedFrom !== null && maskedFrom !== void 0 ? maskedFrom : maskedValue.length, maskedTo !== null && maskedTo !== void 0 ? maskedTo : maskedValue.length], | ||
}; | ||
} | ||
function guessValidValueByRegExp({ | ||
value, | ||
selection | ||
}, maskRegExp) { | ||
const [from, to] = selection; | ||
let newFrom = from; | ||
let newTo = to; | ||
const validatedValue = Array.from(value).reduce((validatedValuePart, char, i) => { | ||
const newPossibleValue = validatedValuePart + char; | ||
if (from === i) { | ||
newFrom = validatedValuePart.length; | ||
} | ||
if (to === i) { | ||
newTo = validatedValuePart.length; | ||
} | ||
return newPossibleValue.match(maskRegExp) ? newPossibleValue : validatedValuePart; | ||
}, ''); | ||
return { | ||
value: validatedValue, | ||
selection: [newFrom, newTo] | ||
}; | ||
function guessValidValueByRegExp({ value, selection }, maskRegExp) { | ||
const [from, to] = selection; | ||
let newFrom = from; | ||
let newTo = to; | ||
const validatedValue = Array.from(value).reduce((validatedValuePart, char, i) => { | ||
const newPossibleValue = validatedValuePart + char; | ||
if (from === i) { | ||
newFrom = validatedValuePart.length; | ||
} | ||
if (to === i) { | ||
newTo = validatedValuePart.length; | ||
} | ||
return newPossibleValue.match(maskRegExp) ? newPossibleValue : validatedValuePart; | ||
}, ''); | ||
return { value: validatedValue, selection: [newFrom, newTo] }; | ||
} | ||
function calibrateValueByMask(elementState, mask, initialElementState = null) { | ||
if (validateValueWithMask(elementState.value, mask)) { | ||
return elementState; | ||
} | ||
const { | ||
value, | ||
selection | ||
} = Array.isArray(mask) ? guessValidValueByPattern(elementState, mask, initialElementState) : guessValidValueByRegExp(elementState, mask); | ||
return { | ||
selection, | ||
value: Array.isArray(mask) ? value.slice(0, mask.length) : value | ||
}; | ||
if (validateValueWithMask(elementState.value, mask)) { | ||
return elementState; | ||
} | ||
const { value, selection } = Array.isArray(mask) | ||
? guessValidValueByPattern(elementState, mask, initialElementState) | ||
: guessValidValueByRegExp(elementState, mask); | ||
return { | ||
selection, | ||
value: Array.isArray(mask) ? value.slice(0, mask.length) : value, | ||
}; | ||
} | ||
function removeFixedMaskCharacters(initialElementState, mask) { | ||
if (!Array.isArray(mask)) { | ||
return initialElementState; | ||
} | ||
const [from, to] = initialElementState.selection; | ||
const selection = []; | ||
const unmaskedValue = Array.from(initialElementState.value).reduce((rawValue, char, i) => { | ||
const charConstraint = mask[i]; | ||
if (i === from) { | ||
selection.push(rawValue.length); | ||
if (!Array.isArray(mask)) { | ||
return initialElementState; | ||
} | ||
if (i === to) { | ||
selection.push(rawValue.length); | ||
const [from, to] = initialElementState.selection; | ||
const selection = []; | ||
const unmaskedValue = Array.from(initialElementState.value).reduce((rawValue, char, i) => { | ||
const charConstraint = mask[i]; | ||
if (i === from) { | ||
selection.push(rawValue.length); | ||
} | ||
if (i === to) { | ||
selection.push(rawValue.length); | ||
} | ||
return isFixedCharacter(charConstraint) && charConstraint === char | ||
? rawValue | ||
: rawValue + char; | ||
}, ''); | ||
if (selection.length < 2) { | ||
selection.push(...new Array(2 - selection.length).fill(unmaskedValue.length)); | ||
} | ||
return isFixedCharacter(charConstraint) && charConstraint === char ? rawValue : rawValue + char; | ||
}, ''); | ||
if (selection.length < 2) { | ||
selection.push(...new Array(2 - selection.length).fill(unmaskedValue.length)); | ||
} | ||
return { | ||
value: unmaskedValue, | ||
selection: [selection[0], selection[1]] | ||
}; | ||
return { | ||
value: unmaskedValue, | ||
selection: [selection[0], selection[1]], | ||
}; | ||
} | ||
class MaskModel { | ||
constructor(initialElementState, maskOptions) { | ||
this.initialElementState = initialElementState; | ||
this.maskOptions = maskOptions; | ||
this.value = ''; | ||
this.selection = [0, 0]; | ||
const { | ||
value, | ||
selection | ||
} = calibrateValueByMask(initialElementState, this.getMaskExpression(initialElementState)); | ||
this.value = value; | ||
this.selection = selection; | ||
} | ||
addCharacters([from, to], newCharacters) { | ||
const { | ||
value | ||
} = this; | ||
const maskExpression = this.getMaskExpression({ | ||
value: value.slice(0, from) + newCharacters + value.slice(to), | ||
selection: [from + newCharacters.length, from + newCharacters.length] | ||
}); | ||
const initialElementState = { | ||
value, | ||
selection: [from, to] | ||
}; | ||
const unmaskedElementState = removeFixedMaskCharacters(initialElementState, maskExpression); | ||
const [unmaskedFrom, unmaskedTo] = applyOverwriteMode(unmaskedElementState, newCharacters, this.maskOptions.overwriteMode).selection; | ||
const newUnmaskedLeadingValuePart = unmaskedElementState.value.slice(0, unmaskedFrom) + newCharacters; | ||
const newCaretIndex = newUnmaskedLeadingValuePart.length; | ||
const maskedElementState = calibrateValueByMask({ | ||
value: newUnmaskedLeadingValuePart + unmaskedElementState.value.slice(unmaskedTo), | ||
selection: [newCaretIndex, newCaretIndex] | ||
}, maskExpression, initialElementState); | ||
const isInvalidCharsInsertion = // eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with | ||
value.slice(0, unmaskedFrom) === calibrateValueByMask({ | ||
value: newUnmaskedLeadingValuePart, | ||
selection: [newCaretIndex, newCaretIndex] | ||
}, maskExpression, initialElementState).value; | ||
if (isInvalidCharsInsertion || areElementStatesEqual(this, maskedElementState) // If typing new characters does not change value | ||
) { | ||
throw new Error('Invalid mask value'); | ||
constructor(initialElementState, maskOptions) { | ||
this.initialElementState = initialElementState; | ||
this.maskOptions = maskOptions; | ||
this.value = ''; | ||
this.selection = [0, 0]; | ||
const { value, selection } = calibrateValueByMask(initialElementState, this.getMaskExpression(initialElementState)); | ||
this.value = value; | ||
this.selection = selection; | ||
} | ||
this.value = maskedElementState.value; | ||
this.selection = maskedElementState.selection; | ||
} | ||
deleteCharacters([from, to]) { | ||
if (from === to || !to) { | ||
return; | ||
addCharacters([from, to], newCharacters) { | ||
const { value } = this; | ||
const maskExpression = this.getMaskExpression({ | ||
value: value.slice(0, from) + newCharacters + value.slice(to), | ||
selection: [from + newCharacters.length, from + newCharacters.length], | ||
}); | ||
const initialElementState = { value, selection: [from, to] }; | ||
const unmaskedElementState = removeFixedMaskCharacters(initialElementState, maskExpression); | ||
const [unmaskedFrom, unmaskedTo] = applyOverwriteMode(unmaskedElementState, newCharacters, this.maskOptions.overwriteMode).selection; | ||
const newUnmaskedLeadingValuePart = unmaskedElementState.value.slice(0, unmaskedFrom) + newCharacters; | ||
const newCaretIndex = newUnmaskedLeadingValuePart.length; | ||
const maskedElementState = calibrateValueByMask({ | ||
value: newUnmaskedLeadingValuePart + | ||
unmaskedElementState.value.slice(unmaskedTo), | ||
selection: [newCaretIndex, newCaretIndex], | ||
}, maskExpression, initialElementState); | ||
const isInvalidCharsInsertion = | ||
// eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with | ||
value.slice(0, unmaskedFrom) === | ||
calibrateValueByMask({ | ||
value: newUnmaskedLeadingValuePart, | ||
selection: [newCaretIndex, newCaretIndex], | ||
}, maskExpression, initialElementState).value; | ||
if (isInvalidCharsInsertion || | ||
areElementStatesEqual(this, maskedElementState) // If typing new characters does not change value | ||
) { | ||
throw new Error('Invalid mask value'); | ||
} | ||
this.value = maskedElementState.value; | ||
this.selection = maskedElementState.selection; | ||
} | ||
const { | ||
value | ||
} = this; | ||
const maskExpression = this.getMaskExpression({ | ||
value: value.slice(0, from) + value.slice(to), | ||
selection: [from, from] | ||
}); | ||
const initialElementState = { | ||
value, | ||
selection: [from, to] | ||
}; | ||
const unmaskedElementState = removeFixedMaskCharacters(initialElementState, maskExpression); | ||
const [unmaskedFrom, unmaskedTo] = unmaskedElementState.selection; | ||
const newUnmaskedValue = unmaskedElementState.value.slice(0, unmaskedFrom) + unmaskedElementState.value.slice(unmaskedTo); | ||
const maskedElementState = calibrateValueByMask({ | ||
value: newUnmaskedValue, | ||
selection: [unmaskedFrom, unmaskedFrom] | ||
}, maskExpression, initialElementState); | ||
this.value = maskedElementState.value; | ||
this.selection = maskedElementState.selection; | ||
} | ||
getMaskExpression(elementState) { | ||
const { | ||
mask | ||
} = this.maskOptions; | ||
return typeof mask === 'function' ? mask(elementState) : mask; | ||
} | ||
deleteCharacters([from, to]) { | ||
if (from === to || !to) { | ||
return; | ||
} | ||
const { value } = this; | ||
const maskExpression = this.getMaskExpression({ | ||
value: value.slice(0, from) + value.slice(to), | ||
selection: [from, from], | ||
}); | ||
const initialElementState = { value, selection: [from, to] }; | ||
const unmaskedElementState = removeFixedMaskCharacters(initialElementState, maskExpression); | ||
const [unmaskedFrom, unmaskedTo] = unmaskedElementState.selection; | ||
const newUnmaskedValue = unmaskedElementState.value.slice(0, unmaskedFrom) + | ||
unmaskedElementState.value.slice(unmaskedTo); | ||
const maskedElementState = calibrateValueByMask({ value: newUnmaskedValue, selection: [unmaskedFrom, unmaskedFrom] }, maskExpression, initialElementState); | ||
this.value = maskedElementState.value; | ||
this.selection = maskedElementState.selection; | ||
} | ||
getMaskExpression(elementState) { | ||
const { mask } = this.maskOptions; | ||
return typeof mask === 'function' ? mask(elementState) : mask; | ||
} | ||
} | ||
class EventListener { | ||
constructor(element) { | ||
this.element = element; | ||
this.listeners = []; | ||
} | ||
listen(eventType, fn, options) { | ||
const untypedFn = fn; | ||
this.element.addEventListener(eventType, untypedFn, options); | ||
this.listeners.push(() => this.element.removeEventListener(eventType, untypedFn)); | ||
} | ||
destroy() { | ||
this.listeners.forEach(stopListen => stopListen()); | ||
} | ||
constructor(element) { | ||
this.element = element; | ||
this.listeners = []; | ||
} | ||
listen(eventType, fn, options) { | ||
const untypedFn = fn; | ||
this.element.addEventListener(eventType, untypedFn, options); | ||
this.listeners.push(() => this.element.removeEventListener(eventType, untypedFn)); | ||
} | ||
destroy() { | ||
this.listeners.forEach(stopListen => stopListen()); | ||
} | ||
} | ||
const HotkeyModifier = { | ||
CTRL: 1 << 0, | ||
ALT: 1 << 1, | ||
SHIFT: 1 << 2, | ||
META: 1 << 3, | ||
}; | ||
// TODO add variants that can be processed correctly | ||
const HotkeyCode = { | ||
Y: 89, | ||
Z: 90, | ||
}; | ||
/** | ||
* Checks if the passed keyboard event match the required hotkey. | ||
* | ||
* We intentionally use legacy {@link KeyboardEvent#keyCode `keyCode`} property. It is more | ||
* "keyboard-layout"-independent than {@link KeyboardEvent#key `key`} or {@link KeyboardEvent#code `code`} properties. | ||
* | ||
* @example | ||
@@ -347,4 +291,2 @@ * input.addEventListener('keydown', (event) => { | ||
* | ||
* @see {@link https://github.com/taiga-family/maskito/issues/315 `KeyboardEvent#code` issue} | ||
* | ||
* @return will return `true` only if the {@link HotkeyCode} matches and only the necessary | ||
@@ -354,136 +296,118 @@ * {@link HotkeyModifier modifiers} have been pressed | ||
function isHotkey(event, modifiers, hotkeyCode) { | ||
return event.ctrlKey === !!(modifiers & 1 | ||
/* CTRL */ | ||
) && event.altKey === !!(modifiers & 2 | ||
/* ALT */ | ||
) && event.shiftKey === !!(modifiers & 4 | ||
/* SHIFT */ | ||
) && event.metaKey === !!(modifiers & 8 | ||
/* META */ | ||
) && event.keyCode === hotkeyCode; | ||
return (event.ctrlKey === !!(modifiers & HotkeyModifier.CTRL) && | ||
event.altKey === !!(modifiers & HotkeyModifier.ALT) && | ||
event.shiftKey === !!(modifiers & HotkeyModifier.SHIFT) && | ||
event.metaKey === !!(modifiers & HotkeyModifier.META) && | ||
/** | ||
* We intentionally use legacy {@link KeyboardEvent#keyCode `keyCode`} property. It is more | ||
* "keyboard-layout"-independent than {@link KeyboardEvent#key `key`} or {@link KeyboardEvent#code `code`} properties. | ||
* @see {@link https://github.com/taiga-family/maskito/issues/315 `KeyboardEvent#code` issue} | ||
*/ | ||
// eslint-disable-next-line sonar/deprecation | ||
event.keyCode === hotkeyCode); | ||
} | ||
function isRedo(event) { | ||
return isHotkey(event, 1 | ||
/* CTRL */ | ||
, 89 | ||
/* Y */ | ||
) || // Windows | ||
isHotkey(event, 1 | ||
/* CTRL */ | ||
| 4 | ||
/* SHIFT */ | ||
, 90 | ||
/* Z */ | ||
) || // Windows & Android | ||
isHotkey(event, 8 | ||
/* META */ | ||
| 4 | ||
/* SHIFT */ | ||
, 90 | ||
/* Z */ | ||
) // macOS & iOS | ||
; | ||
return (isHotkey(event, HotkeyModifier.CTRL, HotkeyCode.Y) || // Windows | ||
isHotkey(event, HotkeyModifier.CTRL | HotkeyModifier.SHIFT, HotkeyCode.Z) || // Windows & Android | ||
isHotkey(event, HotkeyModifier.META | HotkeyModifier.SHIFT, HotkeyCode.Z) // macOS & iOS | ||
); | ||
} | ||
function isUndo(event) { | ||
return isHotkey(event, 1 | ||
/* CTRL */ | ||
, 90 | ||
/* Z */ | ||
) || // Windows & Android | ||
isHotkey(event, 8 | ||
/* META */ | ||
, 90 | ||
/* Z */ | ||
) // macOS & iOS | ||
; | ||
return (isHotkey(event, HotkeyModifier.CTRL, HotkeyCode.Z) || // Windows & Android | ||
isHotkey(event, HotkeyModifier.META, HotkeyCode.Z) // macOS & iOS | ||
); | ||
} | ||
/** | ||
* "beforeinput" is more appropriate event for preprocessing of the input masking (than `keydown`): | ||
* - `keydown` is not triggered by predictive text from native mobile keyboards. | ||
* - `keydown` is triggered by system key combinations (we don't need them, and they should be manually filtered). | ||
* - Dropping text inside input triggers `beforeinput` (but not `keydown`). | ||
* ___ | ||
* "beforeinput" is not supported by Chrome 49+ (only from 60+) and by Firefox 52+ (only from 87+). | ||
* Sets value to element, and dispatches input event | ||
* if you passed ELementState, it also sets selection range | ||
* | ||
* @see https://caniuse.com/?search=beforeinput | ||
* @see https://taiga-ui.dev/browser-support | ||
* @example | ||
* maskitoUpdateElement(input, newValue); | ||
* maskitoUpdateElement(input, elementState); | ||
* | ||
* @see {@link https://github.com/taiga-family/maskito/issues/804 issue} | ||
* | ||
* @return void | ||
*/ | ||
function isBeforeInputEventSupported(element) { | ||
return 'onbeforeinput' in element; | ||
function maskitoUpdateElement(element, valueOrElementState) { | ||
var _a; | ||
if (typeof valueOrElementState === 'string') { | ||
element.value = valueOrElementState; | ||
} | ||
else { | ||
const [from, to] = valueOrElementState.selection; | ||
element.value = valueOrElementState.value; | ||
(_a = element.setSelectionRange) === null || _a === void 0 ? void 0 : _a.call(element, from, to); | ||
} | ||
element.dispatchEvent(new Event('input', | ||
/** | ||
* React handles this event only on bubbling phase | ||
* | ||
* here is the list of events that are processed in the capture stage, others are processed in the bubbling stage | ||
* https://github.com/facebook/react/blob/cb2439624f43c510007f65aea5c50a8bb97917e4/packages/react-dom-bindings/src/events/DOMPluginEventSystem.js#L222 | ||
*/ | ||
{ bubbles: true })); | ||
} | ||
function isEventProducingCharacter({ | ||
key, | ||
ctrlKey, | ||
metaKey, | ||
altKey | ||
}) { | ||
const isSystemKeyCombinations = ctrlKey || metaKey || altKey; | ||
const isSingleUnicodeChar = /^.$/u.test(key); // 4-byte characters case (e.g. smile) | ||
return !isSystemKeyCombinations && key !== 'Backspace' && isSingleUnicodeChar; | ||
function getLineSelection({ value, selection }, isForward) { | ||
const [from, to] = selection; | ||
if (from !== to) { | ||
return [from, to]; | ||
} | ||
const nearestBreak = isForward | ||
? value.slice(from).indexOf('\n') + 1 || value.length | ||
: value.slice(0, to).lastIndexOf('\n') + 1; | ||
const selectFrom = isForward ? from : nearestBreak; | ||
const selectTo = isForward ? nearestBreak : to; | ||
return [selectFrom, selectTo]; | ||
} | ||
function getLineSelection({ | ||
value, | ||
selection | ||
}, isForward) { | ||
const [from, to] = selection; | ||
if (from !== to) { | ||
return [from, to]; | ||
} | ||
const nearestBreak = isForward ? value.slice(from).indexOf('\n') + 1 || value.length : value.slice(0, to).lastIndexOf('\n') + 1; | ||
const selectFrom = isForward ? from : nearestBreak; | ||
const selectTo = isForward ? nearestBreak : to; | ||
return [selectFrom, selectTo]; | ||
function getNotEmptySelection({ value, selection }, isForward) { | ||
const [from, to] = selection; | ||
if (from !== to) { | ||
return [from, to]; | ||
} | ||
const notEmptySelection = isForward ? [from, to + 1] : [from - 1, to]; | ||
return notEmptySelection.map(x => Math.min(Math.max(x, 0), value.length)); | ||
} | ||
function getNotEmptySelection({ | ||
value, | ||
selection | ||
}, isForward) { | ||
const [from, to] = selection; | ||
if (from !== to) { | ||
return [from, to]; | ||
} | ||
const notEmptySelection = isForward ? [from, to + 1] : [from - 1, to]; | ||
return notEmptySelection.map(x => Math.min(Math.max(x, 0), value.length)); | ||
} | ||
const TRAILING_SPACES_REG = /\s+$/g; | ||
const LEADING_SPACES_REG = /^\s+/g; | ||
const SPACE_REG = /\s/; | ||
function getWordSelection({ | ||
value, | ||
selection | ||
}, isForward) { | ||
const [from, to] = selection; | ||
if (from !== to) { | ||
return [from, to]; | ||
} | ||
if (isForward) { | ||
const valueAfterSelectionStart = value.slice(from); | ||
const [leadingSpaces] = valueAfterSelectionStart.match(LEADING_SPACES_REG) || ['']; | ||
const nearestWordEndIndex = valueAfterSelectionStart.replace(LEADING_SPACES_REG, '') // TODO replace with `String.trimStart` after bumping Firefox to 61+ | ||
.search(SPACE_REG); | ||
return [from, nearestWordEndIndex !== -1 ? from + leadingSpaces.length + nearestWordEndIndex : value.length]; | ||
} | ||
const valueBeforeSelectionEnd = value.slice(0, to); | ||
const [trailingSpaces] = valueBeforeSelectionEnd.match(TRAILING_SPACES_REG) || ['']; | ||
const selectedWordLength = valueBeforeSelectionEnd.replace(TRAILING_SPACES_REG, '') // TODO replace with `String.trimEnd` after bumping Firefox to 61+ | ||
.split('').reverse().findIndex(char => char.match(SPACE_REG)); | ||
return [selectedWordLength !== -1 ? to - trailingSpaces.length - selectedWordLength : 0, to]; | ||
function getWordSelection({ value, selection }, isForward) { | ||
const [from, to] = selection; | ||
if (from !== to) { | ||
return [from, to]; | ||
} | ||
if (isForward) { | ||
const valueAfterSelectionStart = value.slice(from); | ||
const [leadingSpaces] = valueAfterSelectionStart.match(LEADING_SPACES_REG) || [ | ||
'', | ||
]; | ||
const nearestWordEndIndex = valueAfterSelectionStart | ||
.trimStart() | ||
.search(SPACE_REG); | ||
return [ | ||
from, | ||
nearestWordEndIndex !== -1 | ||
? from + leadingSpaces.length + nearestWordEndIndex | ||
: value.length, | ||
]; | ||
} | ||
const valueBeforeSelectionEnd = value.slice(0, to); | ||
const [trailingSpaces] = valueBeforeSelectionEnd.match(TRAILING_SPACES_REG) || ['']; | ||
const selectedWordLength = valueBeforeSelectionEnd | ||
.trimEnd() | ||
.split('') | ||
.reverse() | ||
.findIndex(char => char.match(SPACE_REG)); | ||
return [ | ||
selectedWordLength !== -1 ? to - trailingSpaces.length - selectedWordLength : 0, | ||
to, | ||
]; | ||
} | ||
/* eslint-disable @typescript-eslint/ban-types */ | ||
/** | ||
@@ -493,324 +417,249 @@ * @internal | ||
function maskitoPipe(processors = []) { | ||
return (initialData, ...readonlyArgs) => processors.reduce((data, fn) => Object.assign(Object.assign({}, data), fn(data, ...readonlyArgs)), initialData); | ||
return (initialData, ...readonlyArgs) => processors.reduce((data, fn) => (Object.assign(Object.assign({}, data), fn(data, ...readonlyArgs))), initialData); | ||
} | ||
function maskitoTransform(valueOrState, maskitoOptions) { | ||
const options = Object.assign(Object.assign({}, MASKITO_DEFAULT_OPTIONS), maskitoOptions); | ||
const preprocessor = maskitoPipe(options.preprocessors); | ||
const postprocessor = maskitoPipe(options.postprocessors); | ||
const initialElementState = typeof valueOrState === 'string' ? { | ||
value: valueOrState, | ||
selection: [0, 0] | ||
} : valueOrState; | ||
const { | ||
elementState | ||
} = preprocessor({ | ||
elementState: initialElementState, | ||
data: '' | ||
}, 'validation'); | ||
const maskModel = new MaskModel(elementState, options); | ||
const { | ||
value, | ||
selection | ||
} = postprocessor(maskModel, initialElementState); | ||
return typeof valueOrState === 'string' ? value : { | ||
value, | ||
selection | ||
}; | ||
const options = Object.assign(Object.assign({}, MASKITO_DEFAULT_OPTIONS), maskitoOptions); | ||
const preprocessor = maskitoPipe(options.preprocessors); | ||
const postprocessor = maskitoPipe(options.postprocessors); | ||
const initialElementState = typeof valueOrState === 'string' | ||
? { value: valueOrState, selection: [0, 0] } | ||
: valueOrState; | ||
const { elementState } = preprocessor({ elementState: initialElementState, data: '' }, 'validation'); | ||
const maskModel = new MaskModel(elementState, options); | ||
const { value, selection } = postprocessor(maskModel, initialElementState); | ||
return typeof valueOrState === 'string' ? value : { value, selection }; | ||
} | ||
class Maskito extends MaskHistory { | ||
constructor(element, maskitoOptions) { | ||
super(); | ||
this.element = element; | ||
this.maskitoOptions = maskitoOptions; | ||
this.isTextArea = this.element.nodeName === 'TEXTAREA'; | ||
this.eventListener = new EventListener(this.element); | ||
this.options = Object.assign(Object.assign({}, MASKITO_DEFAULT_OPTIONS), this.maskitoOptions); | ||
this.preprocessor = maskitoPipe(this.options.preprocessors); | ||
this.postprocessor = maskitoPipe(this.options.postprocessors); | ||
this.teardowns = this.options.plugins.map(plugin => plugin(this.element, this.options)); | ||
this.ensureValueFitsMask(); | ||
this.updateHistory(this.elementState); | ||
this.eventListener.listen('keydown', event => { | ||
if (isRedo(event)) { | ||
event.preventDefault(); | ||
return this.redo(); | ||
} | ||
function maskitoInitialCalibrationPlugin(customOptions) { | ||
return (element, options) => { | ||
const from = element.selectionStart || 0; | ||
const to = element.selectionEnd || 0; | ||
maskitoUpdateElement(element, { | ||
value: maskitoTransform(element.value, customOptions || options), | ||
selection: [from, to], | ||
}); | ||
}; | ||
} | ||
if (isUndo(event)) { | ||
event.preventDefault(); | ||
return this.undo(); | ||
} | ||
}); | ||
function maskitoStrictCompositionPlugin() { | ||
return (element, maskitoOptions) => { | ||
const listener = (event) => { | ||
if (event.inputType !== 'insertCompositionText') { | ||
return; | ||
} | ||
const selection = [ | ||
element.selectionStart || 0, | ||
element.selectionEnd || 0, | ||
]; | ||
const elementState = { | ||
selection, | ||
value: element.value, | ||
}; | ||
const validatedState = maskitoTransform(elementState, maskitoOptions); | ||
if (!areElementStatesEqual(elementState, validatedState)) { | ||
event.preventDefault(); | ||
maskitoUpdateElement(element, validatedState); | ||
} | ||
}; | ||
element.addEventListener('input', listener); | ||
return () => element.removeEventListener('input', listener); | ||
}; | ||
} | ||
if (isBeforeInputEventSupported(element)) { | ||
this.eventListener.listen('beforeinput', event => { | ||
const isForward = event.inputType.includes('Forward'); | ||
class Maskito extends MaskHistory { | ||
constructor(element, maskitoOptions) { | ||
super(); | ||
this.element = element; | ||
this.maskitoOptions = maskitoOptions; | ||
this.isTextArea = this.element.nodeName === 'TEXTAREA'; | ||
this.eventListener = new EventListener(this.element); | ||
this.options = Object.assign(Object.assign({}, MASKITO_DEFAULT_OPTIONS), this.maskitoOptions); | ||
this.preprocessor = maskitoPipe(this.options.preprocessors); | ||
this.postprocessor = maskitoPipe(this.options.postprocessors); | ||
this.teardowns = this.options.plugins.map(plugin => plugin(this.element, this.options)); | ||
this.updateHistory(this.elementState); | ||
switch (event.inputType) { | ||
// historyUndo/historyRedo will not be triggered if value was modified programmatically | ||
case 'historyUndo': | ||
event.preventDefault(); | ||
return this.undo(); | ||
case 'historyRedo': | ||
event.preventDefault(); | ||
return this.redo(); | ||
case 'deleteByCut': | ||
case 'deleteContentBackward': | ||
case 'deleteContentForward': | ||
return this.handleDelete({ | ||
event, | ||
isForward, | ||
selection: getNotEmptySelection(this.elementState, isForward) | ||
}); | ||
case 'deleteWordForward': | ||
case 'deleteWordBackward': | ||
return this.handleDelete({ | ||
event, | ||
isForward, | ||
selection: getWordSelection(this.elementState, isForward), | ||
force: true | ||
}); | ||
case 'deleteSoftLineBackward': | ||
case 'deleteSoftLineForward': | ||
case 'deleteHardLineBackward': | ||
case 'deleteHardLineForward': | ||
return this.handleDelete({ | ||
event, | ||
isForward, | ||
selection: getLineSelection(this.elementState, isForward), | ||
force: true | ||
}); | ||
case 'insertCompositionText': | ||
return; | ||
// will be handled inside `compositionend` event | ||
case 'insertLineBreak': | ||
return this.handleEnter(event); | ||
case 'insertFromPaste': | ||
case 'insertText': | ||
case 'insertFromDrop': | ||
default: | ||
return this.handleInsert(event, event.data || ''); | ||
} | ||
}); | ||
} else { | ||
/** TODO: drop it after browser support bump (Firefox 87+) | ||
* Also, replace union types `Event | TypedInputEvent` with `TypedInputEvent` inside: | ||
*** {@link handleDelete} | ||
*** {@link handleInsert} | ||
*/ | ||
this.eventListener.listen('keydown', event => this.handleKeydown(event)); | ||
this.eventListener.listen('paste', event => { | ||
var _a; | ||
return this.handleInsert(event, ((_a = event.clipboardData) === null || _a === void 0 ? void 0 : _a.getData('text/plain')) || ''); | ||
}); | ||
this.eventListener.listen('keydown', event => { | ||
if (isRedo(event)) { | ||
event.preventDefault(); | ||
return this.redo(); | ||
} | ||
if (isUndo(event)) { | ||
event.preventDefault(); | ||
return this.undo(); | ||
} | ||
}); | ||
this.eventListener.listen('beforeinput', event => { | ||
const isForward = event.inputType.includes('Forward'); | ||
this.updateHistory(this.elementState); | ||
switch (event.inputType) { | ||
// historyUndo/historyRedo will not be triggered if value was modified programmatically | ||
case 'historyUndo': | ||
event.preventDefault(); | ||
return this.undo(); | ||
case 'historyRedo': | ||
event.preventDefault(); | ||
return this.redo(); | ||
case 'deleteByCut': | ||
case 'deleteContentBackward': | ||
case 'deleteContentForward': | ||
return this.handleDelete({ | ||
event, | ||
isForward, | ||
selection: getNotEmptySelection(this.elementState, isForward), | ||
}); | ||
case 'deleteWordForward': | ||
case 'deleteWordBackward': | ||
return this.handleDelete({ | ||
event, | ||
isForward, | ||
selection: getWordSelection(this.elementState, isForward), | ||
force: true, | ||
}); | ||
case 'deleteSoftLineBackward': | ||
case 'deleteSoftLineForward': | ||
case 'deleteHardLineBackward': | ||
case 'deleteHardLineForward': | ||
return this.handleDelete({ | ||
event, | ||
isForward, | ||
selection: getLineSelection(this.elementState, isForward), | ||
force: true, | ||
}); | ||
case 'insertCompositionText': | ||
return; // will be handled inside `compositionend` event | ||
case 'insertLineBreak': | ||
return this.handleEnter(event); | ||
case 'insertFromPaste': | ||
case 'insertText': | ||
case 'insertFromDrop': | ||
default: | ||
return this.handleInsert(event, event.data || ''); | ||
} | ||
}); | ||
this.eventListener.listen('input', ({ inputType }) => { | ||
if (inputType === 'insertCompositionText') { | ||
return; // will be handled inside `compositionend` event | ||
} | ||
this.ensureValueFitsMask(); | ||
this.updateHistory(this.elementState); | ||
}); | ||
this.eventListener.listen('compositionend', () => { | ||
this.ensureValueFitsMask(); | ||
this.updateHistory(this.elementState); | ||
}); | ||
} | ||
this.eventListener.listen('input', ({ | ||
inputType | ||
}) => { | ||
if (inputType === 'insertCompositionText') { | ||
return; // will be handled inside `compositionend` event | ||
} | ||
this.ensureValueFitsMask(); | ||
this.updateHistory(this.elementState); | ||
}); | ||
this.eventListener.listen('compositionend', () => { | ||
this.ensureValueFitsMask(); | ||
this.updateHistory(this.elementState); | ||
}); | ||
} | ||
get elementState() { | ||
const { | ||
value, | ||
selectionStart, | ||
selectionEnd | ||
} = this.element; | ||
return { | ||
value, | ||
selection: [selectionStart || 0, selectionEnd || 0] | ||
}; | ||
} | ||
get maxLength() { | ||
const { | ||
maxLength | ||
} = this.element; | ||
return maxLength === -1 ? Infinity : maxLength; | ||
} | ||
destroy() { | ||
this.eventListener.destroy(); | ||
this.teardowns.forEach(teardown => teardown === null || teardown === void 0 ? void 0 : teardown()); | ||
} | ||
updateElementState({ | ||
value, | ||
selection | ||
}, eventInit = { | ||
inputType: 'insertText', | ||
data: null | ||
}) { | ||
const initialValue = this.elementState.value; | ||
this.updateValue(value); | ||
this.updateSelectionRange(selection); | ||
if (initialValue !== value) { | ||
this.dispatchInputEvent(eventInit); | ||
get elementState() { | ||
const { value, selectionStart, selectionEnd } = this.element; | ||
return { | ||
value, | ||
selection: [selectionStart || 0, selectionEnd || 0], | ||
}; | ||
} | ||
} | ||
updateSelectionRange([from, to]) { | ||
var _a, _b; | ||
if (this.element.selectionStart !== from || this.element.selectionEnd !== to) { | ||
(_b = (_a = this.element).setSelectionRange) === null || _b === void 0 ? void 0 : _b.call(_a, from, to); | ||
get maxLength() { | ||
const { maxLength } = this.element; | ||
return maxLength === -1 ? Infinity : maxLength; | ||
} | ||
} | ||
updateValue(value) { | ||
this.element.value = value; | ||
} | ||
ensureValueFitsMask() { | ||
this.updateElementState(maskitoTransform(this.elementState, this.options)); | ||
} | ||
dispatchInputEvent(eventInit = { | ||
inputType: 'insertText', | ||
data: null | ||
}) { | ||
const globalObject = typeof window !== 'undefined' ? window : globalThis; // TODO: replace `globalObject` with `globalThis` after bumping Firefox to 65+ | ||
// @see https://caniuse.com/?search=globalThis | ||
if (globalObject === null || globalObject === void 0 ? void 0 : globalObject.InputEvent) { | ||
this.element.dispatchEvent(new InputEvent('input', Object.assign(Object.assign({}, eventInit), { | ||
bubbles: true, | ||
cancelable: false | ||
}))); | ||
destroy() { | ||
this.eventListener.destroy(); | ||
this.teardowns.forEach(teardown => teardown === null || teardown === void 0 ? void 0 : teardown()); | ||
} | ||
} | ||
handleKeydown(event) { | ||
const pressedKey = event.key; | ||
const isForward = pressedKey === 'Delete'; | ||
switch (pressedKey) { | ||
case 'Backspace': | ||
case 'Delete': | ||
return this.handleDelete({ | ||
event, | ||
isForward, | ||
selection: getNotEmptySelection(this.elementState, isForward) | ||
}); | ||
case 'Enter': | ||
return this.handleEnter(event); | ||
updateElementState({ value, selection }, eventInit = { | ||
inputType: 'insertText', | ||
data: null, | ||
}) { | ||
const initialValue = this.elementState.value; | ||
this.updateValue(value); | ||
this.updateSelectionRange(selection); | ||
if (initialValue !== value) { | ||
this.dispatchInputEvent(eventInit); | ||
} | ||
} | ||
if (!isEventProducingCharacter(event)) { | ||
return; | ||
updateSelectionRange([from, to]) { | ||
var _a, _b; | ||
if (this.element.selectionStart !== from || this.element.selectionEnd !== to) { | ||
(_b = (_a = this.element).setSelectionRange) === null || _b === void 0 ? void 0 : _b.call(_a, from, to); | ||
} | ||
} | ||
this.handleInsert(event, pressedKey); | ||
} | ||
handleDelete({ | ||
event, | ||
selection, | ||
isForward, | ||
force = false | ||
}) { | ||
const initialState = { | ||
value: this.elementState.value, | ||
selection | ||
}; | ||
const [initialFrom, initialTo] = initialState.selection; | ||
const { | ||
elementState | ||
} = this.preprocessor({ | ||
elementState: initialState, | ||
data: '' | ||
}, isForward ? 'deleteForward' : 'deleteBackward'); | ||
const maskModel = new MaskModel(elementState, this.options); | ||
const [from, to] = elementState.selection; | ||
maskModel.deleteCharacters([from, to]); | ||
const newElementState = this.postprocessor(maskModel, initialState); | ||
const newPossibleValue = initialState.value.slice(0, initialFrom) + initialState.value.slice(initialTo); | ||
if (newPossibleValue === newElementState.value && !force) { | ||
return; | ||
updateValue(value) { | ||
this.element.value = value; | ||
} | ||
event.preventDefault(); | ||
if (areElementValuesEqual(initialState, elementState, maskModel, newElementState)) { | ||
// User presses Backspace/Delete for the fixed value | ||
return this.updateSelectionRange(isForward ? [to, to] : [from, from]); | ||
} // TODO: drop it when `event: Event | TypedInputEvent` => `event: TypedInputEvent` | ||
const inputTypeFallback = isForward ? 'deleteContentForward' : 'deleteContentBackward'; | ||
this.updateElementState(newElementState, { | ||
inputType: 'inputType' in event ? event.inputType : inputTypeFallback, | ||
data: null | ||
}); | ||
this.updateHistory(newElementState); | ||
} | ||
handleInsert(event, data) { | ||
const initialElementState = this.elementState; | ||
const { | ||
elementState, | ||
data: insertedText = data | ||
} = this.preprocessor({ | ||
data, | ||
elementState: initialElementState | ||
}, 'insert'); | ||
const maskModel = new MaskModel(elementState, this.options); | ||
try { | ||
maskModel.addCharacters(elementState.selection, insertedText); | ||
} catch (_a) { | ||
return event.preventDefault(); | ||
ensureValueFitsMask() { | ||
this.updateElementState(maskitoTransform(this.elementState, this.options)); | ||
} | ||
const [from, to] = elementState.selection; | ||
const newPossibleValue = elementState.value.slice(0, from) + data + elementState.value.slice(to); | ||
const newElementState = this.postprocessor(maskModel, initialElementState); | ||
if (newElementState.value.length > this.maxLength) { | ||
return event.preventDefault(); | ||
dispatchInputEvent(eventInit = { | ||
inputType: 'insertText', | ||
data: null, | ||
}) { | ||
if (globalThis.InputEvent) { | ||
this.element.dispatchEvent(new InputEvent('input', Object.assign(Object.assign({}, eventInit), { bubbles: true, cancelable: false }))); | ||
} | ||
} | ||
if (newPossibleValue !== newElementState.value) { | ||
event.preventDefault(); | ||
this.updateElementState(newElementState, { | ||
data, | ||
inputType: 'inputType' in event ? event.inputType : 'insertText' | ||
}); | ||
this.updateHistory(newElementState); | ||
handleDelete({ event, selection, isForward, force = false, }) { | ||
const initialState = { | ||
value: this.elementState.value, | ||
selection, | ||
}; | ||
const [initialFrom, initialTo] = initialState.selection; | ||
const { elementState } = this.preprocessor({ | ||
elementState: initialState, | ||
data: '', | ||
}, isForward ? 'deleteForward' : 'deleteBackward'); | ||
const maskModel = new MaskModel(elementState, this.options); | ||
const [from, to] = elementState.selection; | ||
maskModel.deleteCharacters([from, to]); | ||
const newElementState = this.postprocessor(maskModel, initialState); | ||
const newPossibleValue = initialState.value.slice(0, initialFrom) + | ||
initialState.value.slice(initialTo); | ||
if (newPossibleValue === newElementState.value && !force) { | ||
return; | ||
} | ||
event.preventDefault(); | ||
if (areElementValuesEqual(initialState, elementState, maskModel, newElementState)) { | ||
// User presses Backspace/Delete for the fixed value | ||
return this.updateSelectionRange(isForward ? [to, to] : [from, from]); | ||
} | ||
this.updateElementState(newElementState, { | ||
inputType: event.inputType, | ||
data: null, | ||
}); | ||
this.updateHistory(newElementState); | ||
} | ||
} | ||
handleEnter(event) { | ||
if (this.isTextArea) { | ||
this.handleInsert(event, '\n'); | ||
handleInsert(event, data) { | ||
const initialElementState = this.elementState; | ||
const { elementState, data: insertedText = data } = this.preprocessor({ | ||
data, | ||
elementState: initialElementState, | ||
}, 'insert'); | ||
const maskModel = new MaskModel(elementState, this.options); | ||
try { | ||
maskModel.addCharacters(elementState.selection, insertedText); | ||
} | ||
catch (_a) { | ||
return event.preventDefault(); | ||
} | ||
const [from, to] = elementState.selection; | ||
const newPossibleValue = initialElementState.value.slice(0, from) + | ||
data + | ||
initialElementState.value.slice(to); | ||
const newElementState = this.postprocessor(maskModel, initialElementState); | ||
if (newElementState.value.length > this.maxLength) { | ||
return event.preventDefault(); | ||
} | ||
if (newPossibleValue !== newElementState.value) { | ||
event.preventDefault(); | ||
this.updateElementState(newElementState, { | ||
data, | ||
inputType: event.inputType, | ||
}); | ||
this.updateHistory(newElementState); | ||
} | ||
} | ||
} | ||
handleEnter(event) { | ||
if (this.isTextArea) { | ||
this.handleInsert(event, '\n'); | ||
} | ||
} | ||
} | ||
export { MASKITO_DEFAULT_ELEMENT_PREDICATE, MASKITO_DEFAULT_OPTIONS, Maskito, maskitoPipe, maskitoTransform }; | ||
export { MASKITO_DEFAULT_ELEMENT_PREDICATE, MASKITO_DEFAULT_OPTIONS, Maskito, maskitoInitialCalibrationPlugin, maskitoPipe, maskitoStrictCompositionPlugin, maskitoTransform, maskitoUpdateElement }; |
{ | ||
"name": "@maskito/core", | ||
"version": "1.9.0", | ||
"version": "2.0.0", | ||
"description": "The main zero-dependency and framework-agnostic Maskito's package to create an input mask", | ||
@@ -32,5 +32,4 @@ "keywords": [ | ||
], | ||
"main": "./index.umd.js", | ||
"module": "./index.esm.js", | ||
"typings": "./index.d.ts" | ||
"main": "./index.cjs.js" | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
81
82087
1485
1