Skip to content

Commit

Permalink
fix: useMask makes input dirty and add decimal char option (epicmaxco…
Browse files Browse the repository at this point in the history
…#4399)

* fix(epicmaxco#4397): prevent input from being dirty on mount when mask is used

* fix(epicmaxco#4372): allow different decimal chars in numeralMask

* chore: update useInputMask stories
  • Loading branch information
m0ksem authored Oct 15, 2024
1 parent e9084f6 commit 6aecd46
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 10 deletions.
32 changes: 22 additions & 10 deletions packages/ui/src/composables/useInputMask/masks/numeral.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,41 @@
import { StringWithAutocomplete } from '../../../utils/types/prop-type'
import { Mask, MaskToken } from '../mask'
import { createRegexMask, RegexToken } from './regex'

const DELIMITER = ' '
const DECIMAL = '.'

type NumeralToken = RegexToken & { isDecimal?: boolean}

export const createNumeralMask = (): Mask<NumeralToken> => {
export const createNumeralMask = (options: {
decimal?: boolean,
decimalChar?: StringWithAutocomplete<'.' | ','>,
} = {}): Mask<NumeralToken> => {
const intMask = createRegexMask(/(\d{3} )*(\d{3})/, { reverse: true })

const { decimal = true, decimalChar = '.' } = options

const decimalRegex = new RegExp(`[.|,|${decimalChar}]`, 'g')

if (!decimal) {
return intMask
}

const decimalMask = createRegexMask(/(\d{3} )*(\d{3})/, { reverse: false })

return {
format: (text: string) => {
const hasDecimal = text.includes(DECIMAL)
const foundDecimal = text.match(decimalRegex)

if (!hasDecimal) {
if (!foundDecimal) {
return intMask.format(text)
}

const [int = '', decimal = '', ...rest] = text.split(DECIMAL)
const [int = '', decimal = '', ...rest] = text.split(foundDecimal[0])

const intResult = intMask.format(int)
const decimalResult = decimalMask.format(decimal + rest.join(''))

return {
text: intResult.text + DECIMAL + decimalResult.text,
tokens: [...intResult.tokens, { type: 'char', static: false, expect: DECIMAL, isDecimal: true }, ...decimalResult.tokens] as NumeralToken[],
text: intResult.text + decimalChar + decimalResult.text,
tokens: [...intResult.tokens, { type: 'char', static: false, expect: decimalChar, isDecimal: true }, ...decimalResult.tokens] as NumeralToken[],
}
},
handleCursor (selectionStart, selectionEnd, oldTokens, newTokens, data) {
Expand All @@ -42,7 +52,9 @@ export const createNumeralMask = (): Mask<NumeralToken> => {
}
},
unformat: (text: string, tokens: MaskToken[]) => {
return parseFloat(text.replace(/ /g, '')).toString()
const [int = 0, decimal = 0] = text.replace(/ /g, '').split(decimalChar)

return parseFloat(int + '.' + decimal).toString()
},
}
}
72 changes: 72 additions & 0 deletions packages/ui/src/composables/useInputMask/useInputMask.stories.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { computed, ref } from 'vue'
import { defineStory } from '../../../.storybook/types'
import { useInputMask } from './useInputMask'
import { VaInput } from '../../components'
import TokensRenderer from './tests/Tokens.vue'
import PossibleTokens from './tests/PossibleTokens.vue'
import { createRegexMask } from './masks/regex'
Expand Down Expand Up @@ -259,6 +260,50 @@ export const NumeralWithDecimal = defineStory({
}),
})

export const NumeralWithDecimalCustomChar = defineStory({
story: () => ({
setup () {
const value = ref('123456')
const input = ref()

const numeralRegex = createNumeralMask({
decimalChar: ',',
})
const { masked, unmasked } = useInputMask(numeralRegex, input)

return { value, input, masked, unmasked }
},
template: `
<input v-model="value" ref="input" class="bg-gray-200 p-2" placeholder="Numeral" />
<p>masked: {{ masked }}</p>
<p>unmasked: {{ unmasked }}</p>
`,
}),
})

export const NumeralMaskWithoutDecimal = defineStory({
story: () => ({
setup () {
const value = ref('123456')
const input = ref()

const numeralRegex = createNumeralMask({
decimal: false,
})
const { masked, unmasked } = useInputMask(numeralRegex, input)

return { value, input, masked, unmasked }
},
template: `
<input v-model="value" ref="input" class="bg-gray-200 p-2" placeholder="Numeral" />
<p>masked: {{ masked }}</p>
<p>unmasked: {{ unmasked }}</p>
`,
}),
})

export const CustomMask = defineStory({
story: () => ({
setup () {
Expand Down Expand Up @@ -310,3 +355,30 @@ export const CustomMask = defineStory({
`,
}),
})

export const WithVueComponent = defineStory({
story: () => ({
components: { VaInput },
setup () {
const value = ref('123456')
const input = ref()

const numeralRegex = createNumeralMask({
decimal: false,
})
const { masked, unmasked } = useInputMask(numeralRegex, input)

return { value, input, masked, unmasked }
},
template: `
<VaInput
v-model="value"
ref="input"
:rules="[(v) => !!v || 'Required']"
/>
<p>masked: {{ masked }}</p>
<p>unmasked: {{ unmasked }}</p>
`,
}),
})
3 changes: 3 additions & 0 deletions packages/ui/src/composables/useInputMask/useInputMask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export const useInputMask = <Token extends MaskToken>(mask: MaybeRef<Mask<Token>
const input = computed(() => extractInput(el.value))

const setInputValue = (value: string, options?: InputEventInit) => {
if (input.value!.value === value) {
return
}
input.value!.value = value
input.value!.dispatchEvent(new InputEvent('input', options))
}
Expand Down

0 comments on commit 6aecd46

Please sign in to comment.