Skip to content

Commit

Permalink
Refactor mask functionality and update tests
Browse files Browse the repository at this point in the history
  • Loading branch information
robertmarney committed Jun 11, 2024
2 parents 82e3e4f + ed461b3 commit cf15d91
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 239 deletions.
4 changes: 2 additions & 2 deletions packages/mask/builds/module.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import mask, { stripDown, buildUp } from '../src/index.js'
import mask, { formatInput } from '../src/index.js'

export default mask

export { mask, stripDown, buildUp }
export { mask, formatInput }
154 changes: 25 additions & 129 deletions packages/mask/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export default function (Alpine) {
}

let setInput = () => {
lastInputValue = el.value = formatInput(input, template)
lastInputValue = el.value = formatInput(template,input)
}

if (shouldRestoreCursor) {
Expand All @@ -88,16 +88,6 @@ export default function (Alpine) {
setInput()
}
}

function formatInput(input, template) {
// Let empty inputs be empty inputs.
if (input === '') return ''

let strippedDownInput = stripDown(template, input)
let rebuiltInput = buildUp(template, strippedDownInput)

return rebuiltInput
}
}).before('model')
}

Expand All @@ -109,137 +99,43 @@ export function restoreCursorPosition(el, template, callback) {

let beforeLeftOfCursorBeforeFormatting = unformattedValue.slice(0, cursorPosition)

let newPosition = buildUp(
template, stripDown(
let newPosition = formatInput(
template, beforeLeftOfCursorBeforeFormatting
)
).length

el.setSelectionRange(newPosition, newPosition)
}

export function stripDown(template, input) {
let inputToBeStripped = input
let regexes = {
'9': /[0-9]/,
'a': /[a-zA-Z]/,
'*': /[a-zA-Z0-9]/,
};
export function formatInput (template, input) {
let output = ''
let regexes = {
'9': /[0-9]/,
'¤': /[0-9]/,
'a': /[a-zA-Z]/,
'*': /[a-zA-Z0-9]/,
}
let wildcardTemplate = "";

// Check for money format and skip over autocomplete cases (as sufficiently handled by formatMoney)
if (!template.includes('¤')) {

// Compile wildcard template
for (let i = 0; i < template.length; i++) {
if (["9", "a", "*"].includes(template[i])) {
wildcardTemplate += template[i];
}
}

//Case 1: template and input are the same length (Autocomplete case 1)
if (template.length === input.length) {
for (let i = 0; i < template.length; i++) {

if (["9", "a", "*"].includes(template[i]) && regexes[template[i]].test(input[i])) {

output += input[i];
}
}

if(output.length === wildcardTemplate.length) {
return output;
let imark = 0
let tmark = 0
while (tmark < template.length && imark < input.length) {
const char = template[tmark]
const ichar = input[imark]
if (char in regexes) {
if (regexes[char].test(ichar)) {
output += ichar
tmark++
}

}

// Case 2: We have a match on the wildcard template length to the input length (Autocomplete case 2)

if (wildcardTemplate.length === input.length) {

for (let i = 0; i < wildcardTemplate.length; i++) {

if (regexes[wildcardTemplate[i]].test(input[i])) {

output += input[i];

continue;

}

// If any character does not match, the autocomplete does not match the template
break;
}

if(output.length === wildcardTemplate.length) {
return output;
}

imark++
} else {
output += char
tmark++
if (char === input[imark]) imark++
}
}

wildcardTemplate = ''
output = ''


// Strip away non wildcard template characters.
for (let i = 0; i < template.length; i++) {
if (['9', 'a', '*', '¤'].includes(template[i])) {
wildcardTemplate += template[i]
continue;
}

for (let j = 0; j < inputToBeStripped.length; j++) {
if (inputToBeStripped[j] === template[i]) {
inputToBeStripped = inputToBeStripped.slice(0, j) + inputToBeStripped.slice(j+1)

break;
}
}
}

for (let i = 0; i < wildcardTemplate.length; i++) {
let found = false

for (let j = 0; j < inputToBeStripped.length; j++) {
if (regexes[wildcardTemplate[i]].test(inputToBeStripped[j])) {
output += inputToBeStripped[j]
inputToBeStripped = inputToBeStripped.slice(j+1)

found = true
break;
}
}

if (! found) break;
}

return output
}

export function buildUp(template, input) {
let clean = Array.from(input)
let output = ''

for (let i = 0; i < template.length; i++) {
if (!['9', 'a', '*', '¤'].includes(template[i])) {
output += template[i]
continue;
}

if (clean.length === 0) break;

output += clean.shift()
}

return output
}

export function formatMoney(input, delimiter = '.', thousands, precision = 2) {
if (input === '-') return '-'
if (/^\D+$/.test(input)) return '¤'
if (/^\D+$/.test(input)) return '9'

if (thousands === null || thousands === undefined) {
thousands = delimiter === "," ? "." : ","
Expand All @@ -266,12 +162,12 @@ export function formatMoney(input, delimiter = '.', thousands, precision = 2) {

let minus = input.startsWith('-') ? '-' : ''
let strippedInput = input.replaceAll(new RegExp(`[^0-9\\${delimiter}]`, 'g'), '')
let template = Array.from({length: strippedInput.split(delimiter)[0].length}).fill('¤').join('')
let template = Array.from({length: strippedInput.split(delimiter)[0].length}).fill('9').join('')

template = `${minus}${addThousands(template, thousands)}`

if (precision > 0 && input.includes(delimiter))
template += `${delimiter}` + '¤'.repeat(precision)
template += `${delimiter}` + '9'.repeat(precision)

queueMicrotask(() => {
if (this.el.value.endsWith(delimiter)) return
Expand Down
36 changes: 30 additions & 6 deletions tests/cypress/integration/plugins/mask.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ test('x-mask',
({ get }) => {
// Type a phone number:
get('input').type('12').should(haveValue('(12'))
get('input').type('3').should(haveValue('(123) '))
get('input').type('3 ').should(haveValue('(123) '))
get('input').type('4567890').should(haveValue('(123) 456-7890'))
// Clear it & paste formatted version in:
get('input').type('{selectAll}{backspace}')
Expand All @@ -32,6 +32,30 @@ test('x-mask',
},
)

test('x-mask autocomplete',
[html`<input x-data x-mask="+1 (999) 999-9999">`],
({ get }) => {
// Type a phone number:
get('input').type('21').should(haveValue('+1 (21'))
get('input').type('3 ').should(haveValue('+1 (213) '))
get('input').type('4567890').should(haveValue('+1 (213) 456-7890'))
// Clear it & paste formatted version in:
get('input').type('{selectAll}{backspace}')
get('input').invoke('val', '+1 (213) 456-7890').trigger('blur')
get('input').should(haveValue('+1 (213) 456-7890'))
// Clear it & paste un-formatted version in:
get('input').type('{selectAll}{backspace}')
get('input').invoke('val', '2134567890').trigger('blur')
get('input').should(haveValue('+1 (213) 456-7890'))
// Clear it and start with an area code starting with 1:
get('input').type('{selectAll}{backspace}')
get('input').type('1 ').should(haveValue('+1 '))
get('input').type('2').should(haveValue('+1 (2'))
get('input').type('13 ').should(haveValue('+1 (213) '))
get('input').type('456 78-90').should(haveValue('+1 (213) 456-7890'))
},
)

test('x-mask with x-model',
[html`
<div x-data="{ value: '' }">
Expand All @@ -43,7 +67,7 @@ test('x-mask with x-model',
// Type a phone number:
get('#1').type('12').should(haveValue('(12'))
get('#2').should(haveValue('(12'))
get('#1').type('3').should(haveValue('(123) '))
get('#1').type('3 ').should(haveValue('(123) '))
get('#2').should(haveValue('(123) '))
get('#1').type('4567890').should(haveValue('(123) 456-7890'))
get('#2').should(haveValue('(123) 456-7890'))
Expand Down Expand Up @@ -119,15 +143,15 @@ test('x-mask with non wildcard alpha-numeric characters (b)',
get('input').type('a').should(haveValue('ba'))
get('input').type('a').should(haveValue('ba'))
get('input').type('3').should(haveValue('ba3'))
get('input').type('z').should(haveValue('ba3zb'))
get('input').type('{backspace}{backspace}4').should(haveValue('ba34b'))
get('input').type('z ').should(haveValue('ba3zb'))
get('input').type('{backspace}{backspace}4 ').should(haveValue('ba34b'))
}
)

test('x-mask:dynamic',
[html`<input x-data x-mask:dynamic="'(999)'">`],
({ get }) => {
get('input').type('123').should(haveValue('(123)'))
get('input').type('123 ').should(haveValue('(123)'))
}
)

Expand All @@ -146,7 +170,7 @@ test('$money',
get('input').type('{leftArrow}7').should(haveValue('1,234,567.87'))
get('input').type('{leftArrow}{leftArrow}{leftArrow}89').should(haveValue('123,456,789.87'))
get('input').type('{leftArrow}{leftArrow}{leftArrow}{leftArrow}12').should(haveValue('12,345,612,789.87'))
get('input').type('{leftArrow}3').should(haveValue('123,456,123,789.87'))
get('input').type('3').should(haveValue('123,456,123,789.87'))
// Clear it & paste formatted version in:
get('input').type('{selectAll}{backspace}')
get('input').invoke('val', '123,456,132,789.87').trigger('blur')
Expand Down
Loading

0 comments on commit cf15d91

Please sign in to comment.