Skip to content

Commit 491a6a6

Browse files
committed
chore: wip
1 parent e4fa64b commit 491a6a6

28 files changed

+1801
-5
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ For casual chit-chat with others using this package:
7272

7373
Our address: Stacks.js, 12665 Village Ln #2306, Playa Vista, CA 90094, United States 🌎
7474

75+
## Credits
76+
77+
- [cleave-zen](https://github.com/nosir/cleave-zen)
78+
- [Chris Breuer](https://github.com/chrisbbreuer)
79+
- [All Contributors](https://github.com/stacksjs/clarity/contributors)
80+
7581
## Sponsors
7682

7783
We would like to extend our thanks to the following sponsors for funding Stacks development. If you are interested in becoming a sponsor, please reach out to us.

bun.lock

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@
4949
"@types/bun": "^1.2.9",
5050
"bumpp": "^10.1.0",
5151
"bun-plugin-dtsx": "^0.21.9",
52-
"bunfig": "^0.8.2",
5352
"changelogen": "^0.6.1",
5453
"lint-staged": "^15.5.1",
5554
"simple-git-hooks": "^2.12.1",

src/common/BaseInput.vue

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<script setup lang="ts">
2+
import type { CursorTrackerDestructor } from '../cursor-tracker'
3+
import { onBeforeUnmount, onMounted, ref } from 'vue'
4+
import { registerCursorTracker } from '../cursor-tracker'
5+
6+
interface Props {
7+
modelValue: string
8+
className?: string
9+
placeholder?: string
10+
disabled?: boolean
11+
}
12+
13+
const props = withDefaults(defineProps<Props>(), {
14+
className: '',
15+
placeholder: '',
16+
disabled: false,
17+
})
18+
19+
const emit = defineEmits<{
20+
(e: 'update:modelValue', value: string): void
21+
(e: 'blur', event: Event): void
22+
}>()
23+
24+
const inputRef = ref<HTMLInputElement | null>(null)
25+
const cursorTrackerRef = ref<CursorTrackerDestructor | null>(null)
26+
27+
function setInputRef(el: HTMLInputElement | null) {
28+
inputRef.value = el
29+
}
30+
31+
function handleInput(event: Event) {
32+
const target = event.target as HTMLInputElement
33+
emit('update:modelValue', target.value)
34+
}
35+
36+
function onBlur(event: Event) {
37+
emit('blur', event)
38+
}
39+
40+
onMounted(() => {
41+
if (inputRef.value) {
42+
cursorTrackerRef.value = registerCursorTracker({
43+
input: inputRef.value,
44+
value: props.modelValue,
45+
onChange: value => emit('update:modelValue', value),
46+
})
47+
}
48+
})
49+
50+
onBeforeUnmount(() => {
51+
if (cursorTrackerRef.value) {
52+
cursorTrackerRef.value()
53+
}
54+
})
55+
</script>
56+
57+
<template>
58+
<input
59+
:ref="setInputRef"
60+
:value="modelValue"
61+
class="base-input" :class="[className]"
62+
:placeholder="placeholder"
63+
:disabled="disabled"
64+
v-bind="$attrs"
65+
@input="handleInput"
66+
@blur="onBlur"
67+
>
68+
</template>

src/common/types.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export type RequireExactlyOne<T, Keys extends keyof T = keyof T> = {
2+
[K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>
3+
}[Keys]
4+
5+
export type DelimiterType = string
6+
export type BlocksType = number[]
7+
8+
export interface StripDelimitersProps {
9+
value: string
10+
delimiters: DelimiterType[]
11+
}
12+
13+
export interface GetFormattedValueProps {
14+
value: string
15+
blocks: BlocksType
16+
delimiter?: DelimiterType
17+
delimiters?: DelimiterType[]
18+
delimiterLazyShow?: boolean
19+
}

src/common/utils.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import type {
2+
BlocksType,
3+
DelimiterType,
4+
GetFormattedValueProps,
5+
StripDelimitersProps,
6+
} from './types'
7+
8+
// const test = (): string => {
9+
// return 'test-eslint'
10+
// }
11+
12+
export function isString(value: any): value is string {
13+
return typeof value === 'string'
14+
}
15+
16+
export function stripNonNumeric(value: string): string {
17+
return value.replace(/\D/g, '')
18+
}
19+
20+
export function getMaxLength(blocks: BlocksType): number {
21+
return blocks.reduce((previous: number, current: number) => previous + current, 0)
22+
}
23+
24+
export function headStr(str: string, length: number): string {
25+
return str.slice(0, length)
26+
}
27+
28+
export function getDelimiterRegexByDelimiter(delimiter: string): RegExp {
29+
return new RegExp(delimiter.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'), 'g')
30+
}
31+
32+
export function stripDelimiters({
33+
value,
34+
delimiters,
35+
}: StripDelimitersProps): string {
36+
delimiters.forEach((current: DelimiterType) => {
37+
current.split('').forEach((letter) => {
38+
value = value.replace(getDelimiterRegexByDelimiter(letter), '')
39+
})
40+
})
41+
42+
return value
43+
}
44+
45+
export function getFormattedValue({
46+
value,
47+
blocks,
48+
delimiter = '',
49+
delimiters = [],
50+
delimiterLazyShow = false,
51+
}: GetFormattedValueProps): string {
52+
let result = ''
53+
let valueRemaining = value
54+
let currentDelimiter = ''
55+
56+
blocks.forEach((length: number, index: number) => {
57+
if (valueRemaining.length > 0) {
58+
const sub = valueRemaining.slice(0, length)
59+
const rest = valueRemaining.slice(length)
60+
61+
if (delimiters.length > 0) {
62+
currentDelimiter
63+
= delimiters[delimiterLazyShow ? index - 1 : index] ?? currentDelimiter
64+
}
65+
else {
66+
currentDelimiter = delimiter
67+
}
68+
69+
if (delimiterLazyShow) {
70+
if (index > 0) {
71+
result += currentDelimiter
72+
}
73+
74+
result += sub
75+
}
76+
else {
77+
result += sub
78+
79+
if (sub.length === length && index < blocks.length - 1) {
80+
result += currentDelimiter
81+
}
82+
}
83+
84+
// update remaining string
85+
valueRemaining = rest
86+
}
87+
})
88+
89+
return result
90+
}

src/components/CreditCardInput.vue

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<script setup lang="ts">
2+
import type { CreditCardType, FormatCreditCardOptions } from '../credit-card'
3+
import { computed, watch } from 'vue'
4+
import BaseInput from '../common/BaseInput.vue'
5+
import { DefaultCreditCardDelimiter, formatCreditCard, getCreditCardType } from '../credit-card'
6+
7+
interface Props {
8+
modelValue: string
9+
delimiter?: string
10+
className?: string
11+
placeholder?: string
12+
options?: Omit<FormatCreditCardOptions, 'delimiter'>
13+
}
14+
15+
const props = withDefaults(defineProps<Props>(), {
16+
delimiter: DefaultCreditCardDelimiter,
17+
className: '',
18+
options: () => ({}),
19+
})
20+
21+
const emit = defineEmits<{
22+
(e: 'update:modelValue', value: string): void
23+
(e: 'cardTypeChange', type: CreditCardType): void
24+
}>()
25+
26+
const formattedValue = computed({
27+
get: () => props.modelValue,
28+
set: (value: string) => {
29+
const formatted = formatCreditCard(value, {
30+
delimiter: props.delimiter,
31+
...props.options,
32+
})
33+
emit('update:modelValue', formatted)
34+
},
35+
})
36+
37+
watch(formattedValue, (value) => {
38+
const cardType = getCreditCardType(value)
39+
emit('cardTypeChange', cardType)
40+
}, { immediate: true })
41+
</script>
42+
43+
<template>
44+
<BaseInput
45+
v-bind="$attrs"
46+
v-model="formattedValue"
47+
:placeholder="placeholder"
48+
:maxlength="19"
49+
:class="className"
50+
/>
51+
</template>

src/components/DateInput.vue

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<script setup lang="ts">
2+
import type { DatePatternType, FormatDateOptions } from '../date'
3+
import { computed } from 'vue'
4+
import BaseInput from '../common/BaseInput.vue'
5+
import { DefaultDateDelimiter, formatDate } from '../date'
6+
7+
interface Props {
8+
modelValue: string
9+
pattern?: DatePatternType
10+
delimiter?: string
11+
className?: string
12+
options?: Omit<FormatDateOptions, 'pattern' | 'delimiter'>
13+
}
14+
15+
const props = withDefaults(defineProps<Props>(), {
16+
pattern: ['d', 'm', 'Y'],
17+
delimiter: DefaultDateDelimiter,
18+
className: '',
19+
options: () => ({}),
20+
})
21+
22+
const emit = defineEmits<{
23+
(e: 'update:modelValue', value: string): void
24+
}>()
25+
26+
const formattedValue = computed({
27+
get: () => props.modelValue,
28+
set: (value: string) => {
29+
const formatted = formatDate(value, {
30+
pattern: props.pattern,
31+
delimiter: props.delimiter,
32+
...props.options,
33+
})
34+
emit('update:modelValue', formatted)
35+
},
36+
})
37+
</script>
38+
39+
<template>
40+
<BaseInput
41+
v-bind="$attrs"
42+
v-model="formattedValue"
43+
:placeholder="pattern"
44+
:class="className"
45+
/>
46+
</template>

src/components/NumeralInput.vue

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<script setup lang="ts">
2+
import type { FormatNumeralOptions } from '../numeral/types'
3+
import { computed } from 'vue'
4+
import BaseInput from '../common/BaseInput.vue'
5+
import { DefaultNumeralDelimiter, formatNumeral, NumeralThousandGroupStyles } from '../numeral'
6+
7+
interface Props {
8+
modelValue: string
9+
delimiter?: string
10+
thousandGroupStyle?: NumeralThousandGroupStyles
11+
className?: string
12+
options?: Omit<FormatNumeralOptions, 'delimiter' | 'thousandGroupStyle'>
13+
}
14+
15+
const props = withDefaults(defineProps<Props>(), {
16+
delimiter: DefaultNumeralDelimiter,
17+
thousandGroupStyle: NumeralThousandGroupStyles.THOUSAND,
18+
className: '',
19+
options: () => ({}),
20+
})
21+
22+
const emit = defineEmits<{
23+
(e: 'update:modelValue', value: string): void
24+
}>()
25+
26+
const formattedValue = computed({
27+
get: () => props.modelValue,
28+
set: (value: string) => {
29+
const formatted = formatNumeral(value, {
30+
delimiter: props.delimiter,
31+
thousandGroupStyle: props.thousandGroupStyle,
32+
...props.options,
33+
})
34+
emit('update:modelValue', formatted)
35+
},
36+
})
37+
</script>
38+
39+
<template>
40+
<BaseInput
41+
v-bind="$attrs"
42+
v-model="formattedValue"
43+
placeholder="1,234,567.89"
44+
:class="className"
45+
/>
46+
</template>

0 commit comments

Comments
 (0)