Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

♻️ Refactors away complexity #1

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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, { stripDown } from '../src/index.js'

export default mask

export { mask, stripDown, buildUp }
export { mask, stripDown }
149 changes: 27 additions & 122 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,52 @@ 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)
}

let regexes = {
'9': /[0-9]/,
'a': /[a-zA-Z]/,
'*': /[a-zA-Z0-9]/,
};
export function stripDown(template, input) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Is this required any more? I do not believe it is referenced anywhere other than module.js in order to consume in jest testing?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just thinking that, in the interest of good UX, having it also exported could be useful for people that want to strip out the input values themselves.

Like a money one where they just want the numbers as raw ints or something. Heck if I know. Seems valuable in the general sense, not the specific.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough I can re-introduce, I guess my concern is that we are no longer directly consuming the output within the new formatInput implementation that its not really clear what the output represents and its functionality could deviate from future change of formatInput.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you mean. The code is consuming the output from formatInput.

formatInput is a pure function.

let inputToBeStripped = 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;
}

}

// 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;
}

}
}

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;
if (template[i] in regexes) output += input[i]
}

return output
}

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

for (let i = 0; i < template.length; i++) {
if (!['9', 'a', '*', '¤'].includes(template[i])) {
output += template[i]
continue;
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++
}
imark++
} else {
output += char
tmark++
if (char === input[imark]) imark++
}

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 +171,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
12 changes: 6 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 Down Expand Up @@ -43,7 +43,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 +119,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 +146,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