@v4fire/client
Version:
V4Fire client core library
155 lines (124 loc) • 3.86 kB
text/typescript
/*!
* V4Fire Client Core
* https://github.com/V4Fire/Client
*
* Released under the MIT license
* https://github.com/V4Fire/Client/blob/master/LICENSE
*/
import type iInputText from 'super/i-input-text/i-input-text';
import { fitForText, convertCursorPositionToRaw } from 'super/i-input-text/modules/mask/helpers';
import type { SyncMaskWithTextOptions } from 'super/i-input-text/modules/mask/interface';
/**
* Synchronizes the component mask with the specified text value.
*
* The component can already have some masked text as a value (by default, the value is taken from the DOM node,
* but you can specify it directly). So we need to apply a new text to the old value with or without limiting bounds to
* update. If only a part of the mask is modified, the rest symbols from the old masked text will be preserved.
*
* The resulting text is saved to the input. The cursor position is updated too.
*
* @param component
* @param text - text to synchronize or a list of Unicode symbols
* @param [opts] - additional options
*/
export function syncWithText<C extends iInputText>(
component: C,
text: CanArray<string>,
opts: SyncMaskWithTextOptions = {}
): void {
const {
unsafe,
unsafe: {maskPlaceholder}
} = component;
const
originalMask = unsafe.compiledMask;
if (originalMask == null) {
return;
}
const
originalText = opts.inputText ?? originalMask.text,
originalTextChunks = Object.isArray(originalText) ? originalText.slice() : [...originalText.letters()];
const
from = opts.from ?? 0,
to = opts.to ?? originalMask.symbols.length;
text = originalTextChunks
.slice(0, from)
.concat(text, originalTextChunks.slice(to + 1))
.join('');
const mask = opts.fitMask !== false ?
fitForText(component, text) :
originalMask;
if (mask == null) {
return;
}
const
textChunks = Object.isArray(text) ? text.slice() : [...text.letters()];
if (!Object.isArray(textChunks)) {
return;
}
const
{symbols: maskSymbols} = mask;
const
isFocused = unsafe.mods.focused === 'true',
isEmptyText = !Object.isTruly(opts.from) && textChunks.length === 0;
let
newMaskedText = '',
cursorPos = opts.cursorPos ?? from;
if (isEmptyText) {
newMaskedText = mask.placeholder;
} else {
for (let i = 0; i < maskSymbols.length; i++) {
const
maskEl = maskSymbols[i];
if (Object.isRegExp(maskEl)) {
if (textChunks.length > 0) {
// Skip all symbols that don't match the non-terminal grammar
while (
textChunks.length > 0 && (
(!opts.preservePlaceholders || textChunks[0] !== maskPlaceholder) &&
!maskEl.test(textChunks[0])
)
) {
textChunks.shift();
}
if (textChunks.length > 0) {
newMaskedText += textChunks[0];
if (!opts.preserveCursor) {
cursorPos++;
}
}
}
// There are no symbols from the raw input that match the non-terminal grammar
if (textChunks.length === 0) {
newMaskedText += maskPlaceholder;
} else {
textChunks.shift();
}
// This is a static symbol from the mask
} else {
newMaskedText += maskEl;
if (!opts.preserveCursor && textChunks.length > 0) {
cursorPos++;
}
}
}
}
mask.text = newMaskedText;
unsafe.updateTextStore(newMaskedText);
// If the component is focused, we need to correct the cursor position
if (isFocused) {
const needRewindCursorPos =
!opts.preserveCursor &&
newMaskedText[cursorPos] !== mask.placeholder &&
cursorPos < (opts.to ?? maskSymbols.length) - 1;
if (needRewindCursorPos) {
do {
cursorPos++;
} while (cursorPos < to && !Object.isRegExp(maskSymbols[cursorPos]));
}
mask.selectionStart = cursorPos;
mask.selectionEnd = cursorPos;
cursorPos = convertCursorPositionToRaw(component, cursorPos);
unsafe.$refs.input.setSelectionRange(cursorPos, cursorPos);
}
}