base-ui-vue 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/button/ToolbarButton.cjs +6 -0
- package/dist/button/ToolbarButton.js +1 -1
- package/dist/content/ScrollAreaContent.cjs +168 -0
- package/dist/content/ScrollAreaContent.cjs.map +1 -0
- package/dist/content/ScrollAreaContent.js +133 -0
- package/dist/content/ScrollAreaContent.js.map +1 -0
- package/dist/control/SliderControl.js +2 -2
- package/dist/corner/ScrollAreaCorner.cjs +77 -0
- package/dist/corner/ScrollAreaCorner.cjs.map +1 -0
- package/dist/corner/ScrollAreaCorner.js +72 -0
- package/dist/corner/ScrollAreaCorner.js.map +1 -0
- package/dist/decrement/NumberFieldDecrement.cjs +861 -0
- package/dist/decrement/NumberFieldDecrement.cjs.map +1 -0
- package/dist/decrement/NumberFieldDecrement.js +700 -0
- package/dist/decrement/NumberFieldDecrement.js.map +1 -0
- package/dist/fallback/AvatarFallback.cjs +2 -46
- package/dist/fallback/AvatarFallback.cjs.map +1 -1
- package/dist/fallback/AvatarFallback.js +3 -41
- package/dist/fallback/AvatarFallback.js.map +1 -1
- package/dist/group/NumberFieldGroup.cjs +72 -0
- package/dist/group/NumberFieldGroup.cjs.map +1 -0
- package/dist/group/NumberFieldGroup.js +67 -0
- package/dist/group/NumberFieldGroup.js.map +1 -0
- package/dist/increment/NumberFieldIncrement.cjs +112 -0
- package/dist/increment/NumberFieldIncrement.cjs.map +1 -0
- package/dist/increment/NumberFieldIncrement.js +107 -0
- package/dist/increment/NumberFieldIncrement.js.map +1 -0
- package/dist/index.cjs +52 -0
- package/dist/index.d.cts +1761 -430
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +1761 -430
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -2
- package/dist/index2.cjs +4065 -60
- package/dist/index2.cjs.map +1 -1
- package/dist/index2.js +3955 -184
- package/dist/index2.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +6 -0
- package/src/input/Input.vue +37 -0
- package/src/input/InputDataAttributes.ts +30 -0
- package/src/input/index.ts +4 -0
- package/src/meter/index.ts +16 -0
- package/src/meter/indicator/MeterIndicator.vue +65 -0
- package/src/meter/label/MeterLabel.vue +63 -0
- package/src/meter/root/MeterRoot.vue +131 -0
- package/src/meter/root/MeterRootContext.ts +41 -0
- package/src/meter/track/MeterTrack.vue +46 -0
- package/src/meter/value/MeterValue.vue +85 -0
- package/src/number-field/decrement/NumberFieldDecrement.vue +109 -0
- package/src/number-field/group/NumberFieldGroup.vue +47 -0
- package/src/number-field/increment/NumberFieldIncrement.vue +109 -0
- package/src/number-field/index.ts +42 -0
- package/src/number-field/input/NumberFieldInput.vue +455 -0
- package/src/number-field/root/NumberFieldRoot.vue +626 -0
- package/src/number-field/root/NumberFieldRootContext.ts +94 -0
- package/src/number-field/root/useNumberFieldButton.ts +171 -0
- package/src/number-field/scrub-area/NumberFieldScrubArea.vue +359 -0
- package/src/number-field/scrub-area/NumberFieldScrubAreaContext.ts +26 -0
- package/src/number-field/scrub-area-cursor/NumberFieldScrubAreaCursor.vue +75 -0
- package/src/number-field/utils/constants.ts +4 -0
- package/src/number-field/utils/getViewportRect.ts +34 -0
- package/src/number-field/utils/parse.ts +248 -0
- package/src/number-field/utils/stateAttributesMapping.ts +9 -0
- package/src/number-field/utils/subscribeToVisualViewportResize.ts +27 -0
- package/src/number-field/utils/types.ts +24 -0
- package/src/number-field/utils/validate.ts +120 -0
- package/src/otp-field/index.ts +22 -0
- package/src/otp-field/input/OtpFieldInput.vue +336 -0
- package/src/otp-field/root/OtpFieldRoot.vue +583 -0
- package/src/otp-field/root/OtpFieldRootContext.ts +81 -0
- package/src/otp-field/utils/otp.ts +135 -0
- package/src/otp-field/utils/stateAttributesMapping.ts +16 -0
- package/src/progress/index.ts +23 -0
- package/src/progress/indicator/ProgressIndicator.vue +74 -0
- package/src/progress/label/ProgressLabel.vue +63 -0
- package/src/progress/root/ProgressRoot.vue +160 -0
- package/src/progress/root/ProgressRootContext.ts +51 -0
- package/src/progress/root/ProgressRootDataAttributes.ts +14 -0
- package/src/progress/root/stateAttributesMapping.ts +18 -0
- package/src/progress/track/ProgressTrack.vue +48 -0
- package/src/progress/value/ProgressValue.vue +92 -0
- package/src/scroll-area/constants.ts +2 -0
- package/src/scroll-area/content/ScrollAreaContent.vue +87 -0
- package/src/scroll-area/corner/ScrollAreaCorner.vue +64 -0
- package/src/scroll-area/index.ts +25 -0
- package/src/scroll-area/root/ScrollAreaRoot.vue +297 -0
- package/src/scroll-area/root/ScrollAreaRootContext.ts +89 -0
- package/src/scroll-area/root/ScrollAreaRootCssVars.ts +4 -0
- package/src/scroll-area/root/ScrollAreaRootDataAttributes.ts +9 -0
- package/src/scroll-area/root/stateAttributes.ts +14 -0
- package/src/scroll-area/scrollbar/ScrollAreaScrollbar.vue +263 -0
- package/src/scroll-area/scrollbar/ScrollAreaScrollbarContext.ts +20 -0
- package/src/scroll-area/scrollbar/ScrollAreaScrollbarCssVars.ts +4 -0
- package/src/scroll-area/scrollbar/ScrollAreaScrollbarDataAttributes.ts +11 -0
- package/src/scroll-area/thumb/ScrollAreaThumb.vue +120 -0
- package/src/scroll-area/thumb/ScrollAreaThumbDataAttributes.ts +3 -0
- package/src/scroll-area/utils/getOffset.ts +34 -0
- package/src/scroll-area/viewport/ScrollAreaViewport.vue +379 -0
- package/src/scroll-area/viewport/ScrollAreaViewportContext.ts +20 -0
- package/src/scroll-area/viewport/ScrollAreaViewportCssVars.ts +6 -0
- package/src/scroll-area/viewport/ScrollAreaViewportDataAttributes.ts +9 -0
- package/src/utils/detectBrowser.ts +15 -0
- package/src/utils/formatNumber.ts +60 -2
- package/src/utils/scrollEdges.ts +33 -0
- package/src/utils/styles.ts +28 -0
- package/src/utils/useInterval.ts +45 -0
- package/src/utils/usePressAndHold.ts +260 -0
- package/src/utils/useValueChanged.ts +21 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { getFormatter } from '../../utils/formatNumber'
|
|
2
|
+
|
|
3
|
+
export const HAN_NUMERALS = ['零', '〇', '一', '二', '三', '四', '五', '六', '七', '八', '九']
|
|
4
|
+
// Map Han numeral characters to ASCII digits. Includes both forms of zero.
|
|
5
|
+
export const HAN_NUMERAL_TO_DIGIT: Record<string, string> = {
|
|
6
|
+
零: '0',
|
|
7
|
+
〇: '0',
|
|
8
|
+
一: '1',
|
|
9
|
+
二: '2',
|
|
10
|
+
三: '3',
|
|
11
|
+
四: '4',
|
|
12
|
+
五: '5',
|
|
13
|
+
六: '6',
|
|
14
|
+
七: '7',
|
|
15
|
+
八: '8',
|
|
16
|
+
九: '9',
|
|
17
|
+
}
|
|
18
|
+
export const ARABIC_NUMERALS = ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩']
|
|
19
|
+
export const PERSIAN_NUMERALS = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']
|
|
20
|
+
export const FULLWIDTH_NUMERALS = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
|
|
21
|
+
|
|
22
|
+
export const PERCENTAGES = ['%', '٪', '%', '﹪']
|
|
23
|
+
export const PERMILLE = ['‰', '؉']
|
|
24
|
+
|
|
25
|
+
export const UNICODE_MINUS_SIGNS = ['−', '-', '‒', '–', '—', '﹣']
|
|
26
|
+
export const UNICODE_PLUS_SIGNS = ['+', '﹢']
|
|
27
|
+
|
|
28
|
+
// Fullwidth punctuation common in CJK inputs
|
|
29
|
+
export const FULLWIDTH_DECIMAL = '.' // U+FF0E
|
|
30
|
+
export const FULLWIDTH_GROUP = ',' // U+FF0C
|
|
31
|
+
|
|
32
|
+
export const ARABIC_RE = new RegExp(`[${ARABIC_NUMERALS.join('')}]`, 'g')
|
|
33
|
+
export const PERSIAN_RE = new RegExp(`[${PERSIAN_NUMERALS.join('')}]`, 'g')
|
|
34
|
+
export const FULLWIDTH_RE = new RegExp(`[${FULLWIDTH_NUMERALS.join('')}]`, 'g')
|
|
35
|
+
export const HAN_RE = new RegExp(`[${HAN_NUMERALS.join('')}]`, 'g')
|
|
36
|
+
export const PERCENT_RE = new RegExp(`[${PERCENTAGES.join('')}]`)
|
|
37
|
+
export const PERMILLE_RE = new RegExp(`[${PERMILLE.join('')}]`)
|
|
38
|
+
const PERCENT_GLOBAL_RE = new RegExp(PERCENT_RE.source, 'g')
|
|
39
|
+
const PERMILLE_GLOBAL_RE = new RegExp(PERMILLE_RE.source, 'g')
|
|
40
|
+
|
|
41
|
+
// Detection regexes (non-global to avoid lastIndex side effects)
|
|
42
|
+
export const ARABIC_DETECT_RE = /[٠١٢٣٤٥٦٧٨٩]/
|
|
43
|
+
export const PERSIAN_DETECT_RE = /[۰۱۲۳۴۵۶۷۸۹]/
|
|
44
|
+
export const HAN_DETECT_RE = /[零〇一二三四五六七八九]/
|
|
45
|
+
export const FULLWIDTH_DETECT_RE = new RegExp(`[${FULLWIDTH_NUMERALS.join('')}]`)
|
|
46
|
+
|
|
47
|
+
export const BASE_NON_NUMERIC_SYMBOLS = [
|
|
48
|
+
'.',
|
|
49
|
+
',',
|
|
50
|
+
FULLWIDTH_DECIMAL,
|
|
51
|
+
FULLWIDTH_GROUP,
|
|
52
|
+
'٫',
|
|
53
|
+
'٬',
|
|
54
|
+
] as const
|
|
55
|
+
export const SPACE_SEPARATOR_RE = /\p{Zs}/u
|
|
56
|
+
export const PLUS_SIGNS_WITH_ASCII = ['+', ...UNICODE_PLUS_SIGNS]
|
|
57
|
+
export const MINUS_SIGNS_WITH_ASCII = ['-', ...UNICODE_MINUS_SIGNS]
|
|
58
|
+
|
|
59
|
+
function escapeRegExp(s: string) {
|
|
60
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
61
|
+
}
|
|
62
|
+
function escapeClassChar(s: string) {
|
|
63
|
+
return s.replace(/[-\\\]^]/g, m => `\\${m}`) // escape for use inside [...]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function shiftDecimal(value: number, exponentDelta: number) {
|
|
67
|
+
const [coefficient, exponent = '0'] = String(value).split('e')
|
|
68
|
+
return Number(`${coefficient}e${Number(exponent) + exponentDelta}`)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function charClassFrom(chars: string[]) {
|
|
72
|
+
return `[${chars.map(escapeClassChar).join('')}]`
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const ANY_MINUS_CLASS = charClassFrom(['-'].concat(UNICODE_MINUS_SIGNS))
|
|
76
|
+
const ANY_PLUS_CLASS = charClassFrom(['+'].concat(UNICODE_PLUS_SIGNS))
|
|
77
|
+
|
|
78
|
+
export const ANY_MINUS_RE = new RegExp(ANY_MINUS_CLASS, 'gu')
|
|
79
|
+
export const ANY_PLUS_RE = new RegExp(ANY_PLUS_CLASS, 'gu')
|
|
80
|
+
export const ANY_MINUS_DETECT_RE = new RegExp(ANY_MINUS_CLASS)
|
|
81
|
+
export const ANY_PLUS_DETECT_RE = new RegExp(ANY_PLUS_CLASS)
|
|
82
|
+
|
|
83
|
+
export function getNumberLocaleDetails(
|
|
84
|
+
locale?: Intl.LocalesArgument,
|
|
85
|
+
options?: Intl.NumberFormatOptions,
|
|
86
|
+
) {
|
|
87
|
+
const parts = getFormatter(locale, options).formatToParts(11111.1)
|
|
88
|
+
const result: Partial<Record<Intl.NumberFormatPartTypes, string | undefined>> = {}
|
|
89
|
+
|
|
90
|
+
parts.forEach((part) => {
|
|
91
|
+
result[part.type] = part.value
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
// The formatting options may result in not returning a decimal.
|
|
95
|
+
getFormatter(locale)
|
|
96
|
+
.formatToParts(0.1)
|
|
97
|
+
.forEach((part) => {
|
|
98
|
+
if (part.type === 'decimal') {
|
|
99
|
+
result[part.type] = part.value
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
return result
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function parseNumber(
|
|
107
|
+
formattedNumber: string,
|
|
108
|
+
locale?: Intl.LocalesArgument,
|
|
109
|
+
options?: Intl.NumberFormatOptions,
|
|
110
|
+
) {
|
|
111
|
+
if (formattedNumber == null) {
|
|
112
|
+
return null
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Normalize control characters and whitespace; remove bidi/format controls
|
|
116
|
+
let input = String(formattedNumber)
|
|
117
|
+
.replace(/\p{Cf}/gu, '')
|
|
118
|
+
.trim()
|
|
119
|
+
|
|
120
|
+
// Normalize unicode minus/plus to ASCII, handle leading/trailing signs
|
|
121
|
+
input = input.replace(ANY_MINUS_RE, '-').replace(ANY_PLUS_RE, '+')
|
|
122
|
+
|
|
123
|
+
let isNegative = false
|
|
124
|
+
|
|
125
|
+
// Trailing sign, e.g. "1234-" / "1234+"
|
|
126
|
+
const trailing = input.match(/([+-])\s*$/)
|
|
127
|
+
if (trailing) {
|
|
128
|
+
if (trailing[1] === '-') {
|
|
129
|
+
isNegative = true
|
|
130
|
+
}
|
|
131
|
+
input = input.replace(/([+-])\s*$/, '')
|
|
132
|
+
}
|
|
133
|
+
// Leading sign
|
|
134
|
+
const leading = input.match(/^\s*([+-])/)
|
|
135
|
+
if (leading) {
|
|
136
|
+
if (leading[1] === '-') {
|
|
137
|
+
isNegative = true
|
|
138
|
+
}
|
|
139
|
+
input = input.replace(/^\s*[+-]/, '')
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Heuristic locale detection
|
|
143
|
+
let computedLocale = locale
|
|
144
|
+
if (computedLocale === undefined) {
|
|
145
|
+
if (ARABIC_DETECT_RE.test(input) || PERSIAN_DETECT_RE.test(input)) {
|
|
146
|
+
computedLocale = 'ar'
|
|
147
|
+
}
|
|
148
|
+
else if (HAN_DETECT_RE.test(input)) {
|
|
149
|
+
computedLocale = 'zh'
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const { group, decimal, currency, exponentSeparator } = getNumberLocaleDetails(
|
|
154
|
+
computedLocale,
|
|
155
|
+
options,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
// Build robust unit regex from all unit parts (such as "km/h")
|
|
159
|
+
const unitParts = getFormatter(computedLocale, options)
|
|
160
|
+
.formatToParts(1)
|
|
161
|
+
.filter(p => p.type === 'unit')
|
|
162
|
+
.map(p => escapeRegExp(p.value))
|
|
163
|
+
const unitRegex = unitParts.length ? new RegExp(unitParts.join('|'), 'g') : null
|
|
164
|
+
|
|
165
|
+
let groupRegex: RegExp | null = null
|
|
166
|
+
if (group) {
|
|
167
|
+
const isSpaceGroup = /\p{Zs}/u.test(group)
|
|
168
|
+
const isApostropheGroup = group === '\'' || group === '’'
|
|
169
|
+
|
|
170
|
+
// Check if the group separator is a space-like character.
|
|
171
|
+
// If so, we'll replace all such characters with an empty string.
|
|
172
|
+
if (isSpaceGroup) {
|
|
173
|
+
groupRegex = /\p{Zs}/gu
|
|
174
|
+
}
|
|
175
|
+
else if (isApostropheGroup) {
|
|
176
|
+
// Some environments format numbers with ASCII apostrophe and others with a curly apostrophe.
|
|
177
|
+
groupRegex = /['’]/g
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
groupRegex = new RegExp(escapeRegExp(group), 'g')
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const replacements: Array<{
|
|
185
|
+
regex: RegExp | null
|
|
186
|
+
replacement: string | ((m: string) => string)
|
|
187
|
+
}> = [
|
|
188
|
+
{ regex: group ? groupRegex : null, replacement: '' },
|
|
189
|
+
{ regex: decimal ? new RegExp(escapeRegExp(decimal), 'g') : null, replacement: '.' },
|
|
190
|
+
// Fullwidth punctuation
|
|
191
|
+
{ regex: /./g, replacement: '.' }, // FULLWIDTH_DECIMAL
|
|
192
|
+
{ regex: /,/g, replacement: '' }, // FULLWIDTH_GROUP
|
|
193
|
+
// Arabic punctuation
|
|
194
|
+
{ regex: /٫/g, replacement: '.' }, // ARABIC DECIMAL SEPARATOR (U+066B)
|
|
195
|
+
{ regex: /٬/g, replacement: '' }, // ARABIC THOUSANDS SEPARATOR (U+066C)
|
|
196
|
+
// Currency & unit labels
|
|
197
|
+
{ regex: currency ? new RegExp(escapeRegExp(currency), 'g') : null, replacement: '' },
|
|
198
|
+
{ regex: unitRegex, replacement: '' },
|
|
199
|
+
{ regex: PERCENT_GLOBAL_RE, replacement: '' },
|
|
200
|
+
{ regex: PERMILLE_GLOBAL_RE, replacement: '' },
|
|
201
|
+
{
|
|
202
|
+
regex: exponentSeparator ? new RegExp(escapeRegExp(exponentSeparator), 'g') : null,
|
|
203
|
+
replacement: 'e',
|
|
204
|
+
},
|
|
205
|
+
// Numeral systems to ASCII digits
|
|
206
|
+
{ regex: ARABIC_RE, replacement: ch => String(ARABIC_NUMERALS.indexOf(ch)) },
|
|
207
|
+
{ regex: PERSIAN_RE, replacement: ch => String(PERSIAN_NUMERALS.indexOf(ch)) },
|
|
208
|
+
{ regex: FULLWIDTH_RE, replacement: ch => String(FULLWIDTH_NUMERALS.indexOf(ch)) },
|
|
209
|
+
{ regex: HAN_RE, replacement: ch => HAN_NUMERAL_TO_DIGIT[ch] },
|
|
210
|
+
]
|
|
211
|
+
|
|
212
|
+
let unformatted = replacements.reduce((acc, { regex, replacement }) => {
|
|
213
|
+
return regex ? acc.replace(regex, replacement as any) : acc
|
|
214
|
+
}, input)
|
|
215
|
+
|
|
216
|
+
// Mixed-locale safety: keep only the last '.' as decimal
|
|
217
|
+
const lastDot = unformatted.lastIndexOf('.')
|
|
218
|
+
if (lastDot !== -1) {
|
|
219
|
+
unformatted = `${unformatted.slice(0, lastDot).replace(/\./g, '')}.${unformatted.slice(lastDot + 1).replace(/\./g, '')}`
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Guard against Infinity inputs (ASCII and symbol)
|
|
223
|
+
if (/^[+-]?Infinity$/i.test(input) || /∞/.test(input)) {
|
|
224
|
+
return null
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const parseTarget = (isNegative ? '-' : '') + unformatted
|
|
228
|
+
|
|
229
|
+
let num = Number.parseFloat(parseTarget)
|
|
230
|
+
|
|
231
|
+
const style = options?.style
|
|
232
|
+
const isUnitPercent = style === 'unit' && options?.unit === 'percent'
|
|
233
|
+
const hasPercentSymbol = PERCENT_RE.test(formattedNumber) || style === 'percent'
|
|
234
|
+
const hasPermilleSymbol = PERMILLE_RE.test(formattedNumber)
|
|
235
|
+
|
|
236
|
+
if (hasPermilleSymbol) {
|
|
237
|
+
num = shiftDecimal(num, -3)
|
|
238
|
+
}
|
|
239
|
+
else if (!isUnitPercent && hasPercentSymbol) {
|
|
240
|
+
num = shiftDecimal(num, -2)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (!Number.isFinite(num)) {
|
|
244
|
+
return null
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return num
|
|
248
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { StateAttributesMapping } from '../../utils/getStateAttributesProps'
|
|
2
|
+
import type { NumberFieldRootState } from '../root/NumberFieldRoot.vue'
|
|
3
|
+
import { fieldValidityMapping } from '../../field/utils/constants'
|
|
4
|
+
|
|
5
|
+
export const stateAttributesMapping: StateAttributesMapping<NumberFieldRootState> = {
|
|
6
|
+
inputValue: () => null,
|
|
7
|
+
value: () => null,
|
|
8
|
+
...fieldValidityMapping,
|
|
9
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Ref } from 'vue'
|
|
2
|
+
import { ownerWindow } from '../../utils/owner'
|
|
3
|
+
|
|
4
|
+
// This lets us invert the scale of the cursor to match the OS scale, in which the cursor doesn't
|
|
5
|
+
// scale with the content on pinch-zoom.
|
|
6
|
+
export function subscribeToVisualViewportResize(
|
|
7
|
+
element: Element,
|
|
8
|
+
visualScaleRef: Ref<number>,
|
|
9
|
+
) {
|
|
10
|
+
const vV = ownerWindow(element).visualViewport
|
|
11
|
+
|
|
12
|
+
if (!vV) {
|
|
13
|
+
return () => {}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function handleVisualResize() {
|
|
17
|
+
if (vV) {
|
|
18
|
+
visualScaleRef.value = vV.scale
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
handleVisualResize()
|
|
23
|
+
vV.addEventListener('resize', handleVisualResize)
|
|
24
|
+
return () => {
|
|
25
|
+
vV.removeEventListener('resize', handleVisualResize)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type Direction = -1 | 1
|
|
2
|
+
|
|
3
|
+
export type DirectionalChangeReason
|
|
4
|
+
= | 'increment-press'
|
|
5
|
+
| 'decrement-press'
|
|
6
|
+
| 'wheel'
|
|
7
|
+
| 'scrub'
|
|
8
|
+
| 'keyboard'
|
|
9
|
+
|
|
10
|
+
export interface ChangeEventCustomProperties {
|
|
11
|
+
direction?: Direction | undefined
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface IncrementValueParameters {
|
|
15
|
+
direction: Direction
|
|
16
|
+
event?: Event | undefined
|
|
17
|
+
reason: DirectionalChangeReason
|
|
18
|
+
currentValue?: number | null | undefined
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface EventWithOptionalKeyState {
|
|
22
|
+
altKey?: boolean | undefined
|
|
23
|
+
shiftKey?: boolean | undefined
|
|
24
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { clamp } from '../../utils/clamp'
|
|
2
|
+
import { getFormatter } from '../../utils/formatNumber'
|
|
3
|
+
import { parseNumber } from './parse'
|
|
4
|
+
|
|
5
|
+
const STEP_EPSILON_FACTOR = 1e-10
|
|
6
|
+
// Matches Intl.NumberFormat's decimal maximumFractionDigits default.
|
|
7
|
+
const DEFAULT_DIGITS = 3
|
|
8
|
+
|
|
9
|
+
// The repo compiles against es2022 Intl types, so model NumberFormat v3 options locally.
|
|
10
|
+
// Delete this once tsconfig includes es2023.
|
|
11
|
+
type NumberFormatOptionsWithRounding = Intl.NumberFormatOptions & {
|
|
12
|
+
roundingIncrement?: number | undefined
|
|
13
|
+
roundingMode?: string | undefined
|
|
14
|
+
roundingPriority?: string | undefined
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function hasNumberFormatRoundingOptions(
|
|
18
|
+
format?: NumberFormatOptionsWithRounding,
|
|
19
|
+
): format is NumberFormatOptionsWithRounding {
|
|
20
|
+
return (
|
|
21
|
+
format?.maximumFractionDigits != null
|
|
22
|
+
|| format?.minimumFractionDigits != null
|
|
23
|
+
|| format?.maximumSignificantDigits != null
|
|
24
|
+
|| format?.minimumSignificantDigits != null
|
|
25
|
+
|| format?.roundingIncrement != null
|
|
26
|
+
|| format?.roundingMode != null
|
|
27
|
+
|| format?.roundingPriority != null
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function removeFloatingPointErrors(value: number, format?: NumberFormatOptionsWithRounding) {
|
|
32
|
+
if (!Number.isFinite(value)) {
|
|
33
|
+
return value
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!hasNumberFormatRoundingOptions(format)) {
|
|
37
|
+
return Number(value.toFixed(DEFAULT_DIGITS))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const formatter = getFormatter('en-US', {
|
|
41
|
+
...format,
|
|
42
|
+
// These options alter only display decoration, not numeric rounding.
|
|
43
|
+
signDisplay: 'auto',
|
|
44
|
+
currencySign: 'standard',
|
|
45
|
+
notation: format.notation === 'compact' ? 'standard' : format.notation,
|
|
46
|
+
useGrouping: false,
|
|
47
|
+
} as NumberFormatOptionsWithRounding)
|
|
48
|
+
const roundedText = formatter.format(value)
|
|
49
|
+
const roundedValue = parseNumber(roundedText, 'en-US', format)
|
|
50
|
+
|
|
51
|
+
if (roundedValue === null) {
|
|
52
|
+
return value
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return formatter.format(roundedValue) === roundedText ? roundedValue : value
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function snapToStep(
|
|
59
|
+
value: number,
|
|
60
|
+
base: number,
|
|
61
|
+
step: number,
|
|
62
|
+
mode: 'directional' | 'nearest' = 'directional',
|
|
63
|
+
) {
|
|
64
|
+
const stepSize = Math.abs(step)
|
|
65
|
+
const direction = Math.sign(step)
|
|
66
|
+
const tolerance = stepSize * STEP_EPSILON_FACTOR * direction
|
|
67
|
+
const rawSteps = value - base + tolerance
|
|
68
|
+
|
|
69
|
+
if (mode === 'nearest') {
|
|
70
|
+
return base + Math.round(rawSteps / step) * step
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const snappedSteps
|
|
74
|
+
= direction > 0 ? Math.floor(rawSteps / stepSize) : Math.ceil(rawSteps / stepSize)
|
|
75
|
+
return base + snappedSteps * stepSize
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function toValidatedNumber(
|
|
79
|
+
value: number | null,
|
|
80
|
+
{
|
|
81
|
+
step,
|
|
82
|
+
minWithDefault,
|
|
83
|
+
maxWithDefault,
|
|
84
|
+
minWithZeroDefault,
|
|
85
|
+
format,
|
|
86
|
+
snapOnStep,
|
|
87
|
+
small,
|
|
88
|
+
clamp: shouldClamp,
|
|
89
|
+
}: {
|
|
90
|
+
step: number | undefined
|
|
91
|
+
minWithDefault: number
|
|
92
|
+
maxWithDefault: number
|
|
93
|
+
minWithZeroDefault: number
|
|
94
|
+
format: NumberFormatOptionsWithRounding | undefined
|
|
95
|
+
snapOnStep: boolean
|
|
96
|
+
small: boolean
|
|
97
|
+
clamp: boolean
|
|
98
|
+
},
|
|
99
|
+
) {
|
|
100
|
+
if (value === null) {
|
|
101
|
+
return value
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let nextValue = value
|
|
105
|
+
|
|
106
|
+
if (step != null && snapOnStep && step !== 0) {
|
|
107
|
+
const base
|
|
108
|
+
= small || minWithDefault === Number.MIN_SAFE_INTEGER ? minWithZeroDefault : minWithDefault
|
|
109
|
+
|
|
110
|
+
// Snap before clamping so non-step-aligned boundaries stay reachable.
|
|
111
|
+
nextValue = snapToStep(nextValue, base, step, small ? 'nearest' : 'directional')
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (shouldClamp) {
|
|
115
|
+
nextValue = clamp(nextValue, minWithDefault, maxWithDefault)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const roundedValue = removeFloatingPointErrors(nextValue, format)
|
|
119
|
+
return shouldClamp ? clamp(roundedValue, minWithDefault, maxWithDefault) : roundedValue
|
|
120
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export { default as OtpFieldInput } from './input/OtpFieldInput.vue'
|
|
2
|
+
export type { OtpFieldInputProps, OtpFieldInputState } from './input/OtpFieldInput.vue'
|
|
3
|
+
|
|
4
|
+
export { default as OtpFieldRoot } from './root/OtpFieldRoot.vue'
|
|
5
|
+
export type { OtpFieldRootProps, OtpFieldRootState } from './root/OtpFieldRoot.vue'
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
getOtpFieldInputState,
|
|
9
|
+
otpFieldRootContextKey,
|
|
10
|
+
useOtpFieldRootContext,
|
|
11
|
+
} from './root/OtpFieldRootContext'
|
|
12
|
+
export type {
|
|
13
|
+
OtpFieldRootChangeEventDetails,
|
|
14
|
+
OtpFieldRootChangeEventReason,
|
|
15
|
+
OtpFieldRootCompleteEventDetails,
|
|
16
|
+
OtpFieldRootCompleteEventReason,
|
|
17
|
+
OtpFieldRootContext,
|
|
18
|
+
OtpFieldRootInvalidEventDetails,
|
|
19
|
+
OtpFieldRootInvalidEventReason,
|
|
20
|
+
} from './root/OtpFieldRootContext'
|
|
21
|
+
|
|
22
|
+
export type { OtpValidationType } from './utils/otp'
|