UNPKG

@vuetify/nightly

Version:

Vue Material Component Framework

180 lines (178 loc) • 7.13 kB
import { mergeProps as _mergeProps, createVNode as _createVNode } from "vue"; // Components import { makeVTextFieldProps, VTextField } from "../../components/VTextField/VTextField.js"; // Composables import { forwardRefs } from "../../composables/forwardRefs.js"; import { isMaskDelimiter, makeMaskProps, useMask } from "../../composables/mask/index.js"; import { useProxiedModel } from "../../composables/proxiedModel.js"; // Utilities import { computed, nextTick, onBeforeMount, ref, shallowRef, toRef } from 'vue'; import { genericComponent, propsFactory, useRender } from "../../util/index.js"; // Types export const makeVMaskInputProps = propsFactory({ returnMaskedValue: Boolean, ...makeVTextFieldProps(), ...makeMaskProps() }, 'VMaskInput'); export const VMaskInput = genericComponent()({ name: 'VMaskInput', props: makeVMaskInputProps(), emits: { 'update:modelValue': val => true }, setup(props, _ref) { let { slots, emit } = _ref; const vTextFieldRef = ref(); const inputAction = shallowRef(); const caretPosition = shallowRef(0); const mask = useMask(props); const returnMaskedValue = computed(() => props.mask && props.returnMaskedValue); const model = useProxiedModel(props, 'modelValue', undefined, // Always display masked value in input when mask is applied val => props.mask ? mask.mask(mask.unmask(val)) : val, val => { if (props.mask) { const valueWithoutDelimiters = val ? removeMaskDelimiters(val) : ''; // E.g. mask is #-# and the input value is '2-23' // model-value should be enforced to '2-2' const newMaskedValue = mask.mask(valueWithoutDelimiters); const newUnmaskedValue = mask.unmask(newMaskedValue); const newCaretPosition = getNewCaretPosition({ oldValue: model.value, newValue: newMaskedValue, oldCaret: caretPosition.value }); vTextFieldRef.value.value = newMaskedValue; vTextFieldRef.value.setSelectionRange(newCaretPosition, newCaretPosition); return returnMaskedValue.value ? mask.mask(newUnmaskedValue) : newUnmaskedValue; } return val; }); const validationValue = toRef(() => returnMaskedValue.value ? model.value : mask.unmask(model.value)); function removeMaskDelimiters(val) { return val.split('').filter(ch => !isMaskDelimiter(ch)).join(''); } function getNewCaretPosition(_ref2) { let { oldValue, newValue, oldCaret } = _ref2; if (!newValue) return 0; if (!oldValue) return newValue.length; let newCaret; if (inputAction.value === 'Backspace') { newCaret = oldCaret - 1; while (newCaret > 0 && isMaskDelimiter(newValue[newCaret - 1])) newCaret--; } else if (inputAction.value === 'Delete') { newCaret = oldCaret; } else { // insertion newCaret = oldCaret + 1; while (isMaskDelimiter(newValue[newCaret])) newCaret++; if (isMaskDelimiter(newValue[oldCaret])) newCaret++; } return newCaret; } onBeforeMount(() => { if (props.returnMaskedValue) { emit('update:modelValue', model.value); } }); function onKeyDown(e) { if (e.metaKey) return; const inputElement = e.target; caretPosition.value = inputElement.selectionStart || 0; inputAction.value = e.key; const hasSelection = inputElement.selectionStart !== inputElement.selectionEnd; if (e.key === 'Backspace' && hasSelection) { e.preventDefault(); deleteSelection(e); } } async function onCut(e) { e.preventDefault(); copySelectionToClipboard(e); deleteSelection(e); } async function onPaste(e) { e.preventDefault(); const inputElement = e.target; const pastedString = removeMaskDelimiters(e.clipboardData?.getData('text') || ''); if (!pastedString) return; const pastedCharacters = [...pastedString]; const hasSelection = inputElement.selectionStart !== inputElement.selectionEnd; if (hasSelection) { replaceSelection(inputElement, pastedCharacters); } else { insertCharacters(inputElement, pastedCharacters); } } function copySelectionToClipboard(e) { const inputElement = e.target; const start = inputElement.selectionStart || 0; const end = inputElement.selectionEnd || 0; const selectedText = inputElement.value.substring(start, end); navigator.clipboard.writeText(selectedText); } async function deleteSelection(e) { const inputElement = e.target; const curStart = inputElement.selectionStart || 0; caretPosition.value = inputElement.selectionEnd || 0; while (caretPosition.value > curStart) { const success = await simulateBackspace(inputElement); if (!success) break; } } async function simulateBackspace(inputElement) { inputAction.value = 'Backspace'; model.value = inputElement.value.slice(0, caretPosition.value - 1) + inputElement.value.slice(caretPosition.value); inputAction.value = ''; if (caretPosition.value === inputElement.selectionEnd) return false; caretPosition.value = inputElement.selectionEnd || 0; await nextTick(); return true; } async function insertCharacters(inputElement, pastedCharacters) { for (let i = 0; i < pastedCharacters.length; i++) { await insertCharacter(inputElement, pastedCharacters[i]); } } async function insertCharacter(inputElement, character) { caretPosition.value = inputElement.selectionEnd || 0; model.value = inputElement.value.slice(0, caretPosition.value) + character + inputElement.value.slice(caretPosition.value); await nextTick(); } async function replaceSelection(inputElement, pastedCharacters) { caretPosition.value = inputElement.selectionStart || 0; for (let i = 0; i < pastedCharacters.length; i++) { await replaceCharacter(caretPosition.value, pastedCharacters[i]); caretPosition.value++; } } async function replaceCharacter(index, character) { let targetIndex = index; // Find next non-delimiter position while (targetIndex < model.value.length && isMaskDelimiter(model.value[targetIndex])) targetIndex++; model.value = model.value.slice(0, targetIndex) + character + model.value.slice(targetIndex + 1); await nextTick(); } useRender(() => { const textFieldProps = VTextField.filterProps(props); return _createVNode(VTextField, _mergeProps(textFieldProps, { "modelValue": model.value, "onUpdate:modelValue": $event => model.value = $event, "ref": vTextFieldRef, "class": props.class, "style": props.style, "validationValue": validationValue.value, "onCut": onCut, "onPaste": onPaste, "onKeydown": onKeyDown }), { ...slots }); }); return forwardRefs({}, vTextFieldRef); } }); //# sourceMappingURL=VMaskInput.js.map