@comitor/ui 1.11.0 → 2.1.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/LICENSE +35 -0
- package/README.md +23 -7
- package/dist/components/ui/action-bar-constants.d.ts +31 -0
- package/dist/components/ui/action-bar-constants.js +9 -0
- package/dist/components/ui/action-bar-constants.js.map +1 -0
- package/dist/components/ui/action-bar.d.ts +67 -0
- package/dist/components/ui/action-bar.js +522 -0
- package/dist/components/ui/action-bar.js.map +1 -0
- package/dist/components/ui/badge-overflow.d.ts +30 -0
- package/dist/components/ui/badge-overflow.js +179 -0
- package/dist/components/ui/badge-overflow.js.map +1 -0
- package/dist/components/ui/banner.d.ts +79 -0
- package/dist/components/ui/banner.js +518 -0
- package/dist/components/ui/banner.js.map +1 -0
- package/dist/components/ui/circular-progress.d.ts +50 -0
- package/dist/components/ui/circular-progress.js +266 -0
- package/dist/components/ui/circular-progress.js.map +1 -0
- package/dist/components/ui/cropper-constants.d.ts +34 -0
- package/dist/components/ui/cropper-constants.js +10 -0
- package/dist/components/ui/cropper-constants.js.map +1 -0
- package/dist/components/ui/cropper.d.ts +106 -0
- package/dist/components/ui/cropper.js +1269 -0
- package/dist/components/ui/cropper.js.map +1 -0
- package/dist/components/ui/drawer.d.ts +1 -1
- package/dist/components/ui/drawer.js +2 -2
- package/dist/components/ui/drawer.js.map +1 -1
- package/dist/components/ui/kanban-constants.d.ts +61 -0
- package/dist/components/ui/kanban-constants.js +21 -0
- package/dist/components/ui/kanban-constants.js.map +1 -0
- package/dist/components/ui/kanban.d.ts +72 -0
- package/dist/components/ui/kanban.js +787 -0
- package/dist/components/ui/kanban.js.map +1 -0
- package/dist/components/ui/key-value-constants.d.ts +33 -0
- package/dist/components/ui/key-value-constants.js +11 -0
- package/dist/components/ui/key-value-constants.js.map +1 -0
- package/dist/components/ui/key-value.d.ts +96 -0
- package/dist/components/ui/key-value.js +641 -0
- package/dist/components/ui/key-value.js.map +1 -0
- package/dist/components/ui/mask-input-constants.d.ts +99 -0
- package/dist/components/ui/mask-input-constants.js +601 -0
- package/dist/components/ui/mask-input-constants.js.map +1 -0
- package/dist/components/ui/mask-input.d.ts +36 -0
- package/dist/components/ui/mask-input.js +558 -0
- package/dist/components/ui/mask-input.js.map +1 -0
- package/dist/components/ui/mention.d.ts +31 -0
- package/dist/components/ui/mention.js +128 -0
- package/dist/components/ui/mention.js.map +1 -0
- package/dist/components/ui/phone-input-constants.d.ts +54 -0
- package/dist/components/ui/phone-input-constants.js +9 -0
- package/dist/components/ui/phone-input-constants.js.map +1 -0
- package/dist/components/ui/phone-input.d.ts +85 -0
- package/dist/components/ui/phone-input.js +743 -0
- package/dist/components/ui/phone-input.js.map +1 -0
- package/dist/components/ui/rating.d.ts +56 -0
- package/dist/components/ui/rating.js +646 -0
- package/dist/components/ui/rating.js.map +1 -0
- package/dist/components/ui/responsive-dialog.d.ts +35 -0
- package/dist/components/ui/responsive-dialog.js +193 -0
- package/dist/components/ui/responsive-dialog.js.map +1 -0
- package/dist/components/ui/scroll-spy.d.ts +42 -0
- package/dist/components/ui/scroll-spy.js +334 -0
- package/dist/components/ui/scroll-spy.js.map +1 -0
- package/dist/components/ui/stepper-constants.d.ts +29 -0
- package/dist/components/ui/stepper-constants.js +9 -0
- package/dist/components/ui/stepper-constants.js.map +1 -0
- package/dist/components/ui/stepper.d.ts +80 -0
- package/dist/components/ui/stepper.js +902 -0
- package/dist/components/ui/stepper.js.map +1 -0
- package/dist/components/ui/tags-input.d.ts +41 -0
- package/dist/components/ui/tags-input.js +127 -0
- package/dist/components/ui/tags-input.js.map +1 -0
- package/dist/components/ui/time-picker-constants.d.ts +48 -0
- package/dist/components/ui/time-picker-constants.js +14 -0
- package/dist/components/ui/time-picker-constants.js.map +1 -0
- package/dist/components/ui/time-picker.d.ts +115 -0
- package/dist/components/ui/time-picker.js +1620 -0
- package/dist/components/ui/time-picker.js.map +1 -0
- package/dist/components/ui/timeline.d.ts +32 -0
- package/dist/components/ui/timeline.js +559 -0
- package/dist/components/ui/timeline.js.map +1 -0
- package/dist/components/ui/tour-constants.d.ts +39 -0
- package/dist/components/ui/tour-constants.js +12 -0
- package/dist/components/ui/tour-constants.js.map +1 -0
- package/dist/components/ui/tour.d.ts +124 -0
- package/dist/components/ui/tour.js +1207 -0
- package/dist/components/ui/tour.js.map +1 -0
- package/dist/data-grid/data-grid-cell-variants.js +10 -6
- package/dist/data-grid/data-grid-cell-variants.js.map +1 -1
- package/dist/data-grid/data-grid-cell-wrapper.js +1 -1
- package/dist/data-grid/data-grid-cell-wrapper.js.map +1 -1
- package/dist/data-grid/data-grid-column-header.js +4 -2
- package/dist/data-grid/data-grid-column-header.js.map +1 -1
- package/dist/data-grid/data-grid-context-menu.js +5 -4
- package/dist/data-grid/data-grid-context-menu.js.map +1 -1
- package/dist/data-grid/data-grid-filter-menu.d.ts +12 -1
- package/dist/data-grid/data-grid-filter-menu.js +28 -17
- package/dist/data-grid/data-grid-filter-menu.js.map +1 -1
- package/dist/data-grid/data-grid-keyboard-shortcuts.d.ts +12 -1
- package/dist/data-grid/data-grid-keyboard-shortcuts.js +7 -5
- package/dist/data-grid/data-grid-keyboard-shortcuts.js.map +1 -1
- package/dist/data-grid/data-grid-locale.d.ts +24 -0
- package/dist/data-grid/data-grid-locale.js +25 -0
- package/dist/data-grid/data-grid-locale.js.map +1 -0
- package/dist/data-grid/data-grid-paste-dialog.js +3 -2
- package/dist/data-grid/data-grid-paste-dialog.js.map +1 -1
- package/dist/data-grid/data-grid-row-height-menu.d.ts +11 -1
- package/dist/data-grid/data-grid-row-height-menu.js +25 -28
- package/dist/data-grid/data-grid-row-height-menu.js.map +1 -1
- package/dist/data-grid/data-grid-row.js +1 -1
- package/dist/data-grid/data-grid-row.js.map +1 -1
- package/dist/data-grid/data-grid-search.js +6 -5
- package/dist/data-grid/data-grid-search.js.map +1 -1
- package/dist/data-grid/data-grid-select-column.js +3 -1
- package/dist/data-grid/data-grid-select-column.js.map +1 -1
- package/dist/data-grid/data-grid-sort-menu.d.ts +12 -1
- package/dist/data-grid/data-grid-sort-menu.js +25 -13
- package/dist/data-grid/data-grid-sort-menu.js.map +1 -1
- package/dist/data-grid/data-grid-view-menu.d.ts +12 -1
- package/dist/data-grid/data-grid-view-menu.js +12 -4
- package/dist/data-grid/data-grid-view-menu.js.map +1 -1
- package/dist/data-grid/data-grid.d.ts +14 -2
- package/dist/data-grid/data-grid.js +7 -4
- package/dist/data-grid/data-grid.js.map +1 -1
- package/dist/data-grid/filters.d.ts +17 -21
- package/dist/data-grid/filters.js +73 -52
- package/dist/data-grid/filters.js.map +1 -1
- package/dist/data-grid/index.d.ts +4 -1
- package/dist/data-grid/index.js +20 -2
- package/dist/data-grid/index.js.map +1 -1
- package/dist/data-grid/messages-en.d.ts +294 -0
- package/dist/data-grid/messages-en.js +279 -0
- package/dist/data-grid/messages-en.js.map +1 -0
- package/dist/data-grid/messages-vi.d.ts +12 -0
- package/dist/data-grid/messages-vi.js +279 -0
- package/dist/data-grid/messages-vi.js.map +1 -0
- package/dist/data-grid/messages.d.ts +549 -262
- package/dist/data-grid/messages.js +16 -275
- package/dist/data-grid/messages.js.map +1 -1
- package/dist/data-grid/sortable.js +1 -1
- package/dist/data-grid/sortable.js.map +1 -1
- package/dist/data-grid/use-data-grid-undo-redo.d.ts +13 -1
- package/dist/data-grid/use-data-grid-undo-redo.js +13 -10
- package/dist/data-grid/use-data-grid-undo-redo.js.map +1 -1
- package/dist/data-grid/use-data-grid.d.ts +281 -1
- package/dist/data-grid/use-data-grid.js +24 -19
- package/dist/data-grid/use-data-grid.js.map +1 -1
- package/dist/data-grid/utils.d.ts +9 -1
- package/dist/data-grid/utils.js +3 -2
- package/dist/data-grid/utils.js.map +1 -1
- package/dist/index.d.ts +31 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/compose-refs.js.map +1 -0
- package/dist/lib/use-as-ref.js.map +1 -0
- package/dist/lib/use-is-mobile.d.ts +10 -0
- package/dist/lib/use-is-mobile.js +21 -0
- package/dist/lib/use-is-mobile.js.map +1 -0
- package/dist/lib/use-isomorphic-layout-effect.js.map +1 -0
- package/dist/lib/use-lazy-ref.js.map +1 -0
- package/dist/lib/visually-hidden-input.d.ts +12 -0
- package/dist/lib/visually-hidden-input.js +105 -0
- package/dist/lib/visually-hidden-input.js.map +1 -0
- package/package.json +5 -1
- package/styles.css +19 -0
- package/dist/data-grid/compose-refs.js.map +0 -1
- package/dist/data-grid/use-as-ref.js.map +0 -1
- package/dist/data-grid/use-isomorphic-layout-effect.js.map +0 -1
- package/dist/data-grid/use-lazy-ref.js.map +0 -1
- /package/dist/{data-grid → lib}/compose-refs.d.ts +0 -0
- /package/dist/{data-grid → lib}/compose-refs.js +0 -0
- /package/dist/{data-grid → lib}/use-as-ref.d.ts +0 -0
- /package/dist/{data-grid → lib}/use-as-ref.js +0 -0
- /package/dist/{data-grid → lib}/use-isomorphic-layout-effect.d.ts +0 -0
- /package/dist/{data-grid → lib}/use-isomorphic-layout-effect.js +0 -0
- /package/dist/{data-grid → lib}/use-lazy-ref.d.ts +0 -0
- /package/dist/{data-grid → lib}/use-lazy-ref.js +0 -0
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { cn } from "../../lib/cn.js";
|
|
6
|
+
import { useComposedRefs } from "../../lib/compose-refs.js";
|
|
7
|
+
import {
|
|
8
|
+
applyMask,
|
|
9
|
+
CURRENCY_PERCENTAGE_SYMBOLS,
|
|
10
|
+
DEFAULT_CURRENCY,
|
|
11
|
+
DEFAULT_LOCALE,
|
|
12
|
+
fromUnmaskedIndex,
|
|
13
|
+
getCurrencyCaretPosition,
|
|
14
|
+
getPatternCaretPosition,
|
|
15
|
+
getUnmaskedValue,
|
|
16
|
+
isCurrencyAtEnd,
|
|
17
|
+
isCurrencyMask,
|
|
18
|
+
MASK_PATTERNS,
|
|
19
|
+
NUMERIC_MASK_PATTERNS,
|
|
20
|
+
REGEX_CACHE,
|
|
21
|
+
toUnmaskedIndex
|
|
22
|
+
} from "./mask-input-constants.js";
|
|
23
|
+
function MaskInput(props) {
|
|
24
|
+
const {
|
|
25
|
+
value: valueProp,
|
|
26
|
+
defaultValue,
|
|
27
|
+
onValueChange: onValueChangeProp,
|
|
28
|
+
onValidate,
|
|
29
|
+
onBlur: onBlurProp,
|
|
30
|
+
onFocus: onFocusProp,
|
|
31
|
+
onKeyDown: onKeyDownProp,
|
|
32
|
+
onPaste: onPasteProp,
|
|
33
|
+
onCompositionStart: onCompositionStartProp,
|
|
34
|
+
onCompositionEnd: onCompositionEndProp,
|
|
35
|
+
validationMode = "onChange",
|
|
36
|
+
mask,
|
|
37
|
+
maskPlaceholder,
|
|
38
|
+
currency = DEFAULT_CURRENCY,
|
|
39
|
+
locale = DEFAULT_LOCALE,
|
|
40
|
+
placeholder,
|
|
41
|
+
inputMode,
|
|
42
|
+
min,
|
|
43
|
+
max,
|
|
44
|
+
maxLength,
|
|
45
|
+
asChild = false,
|
|
46
|
+
disabled = false,
|
|
47
|
+
invalid = false,
|
|
48
|
+
readOnly = false,
|
|
49
|
+
required = false,
|
|
50
|
+
withoutMask = false,
|
|
51
|
+
className,
|
|
52
|
+
ref,
|
|
53
|
+
...inputProps
|
|
54
|
+
} = props;
|
|
55
|
+
const [internalValue, setInternalValue] = React.useState(defaultValue ?? "");
|
|
56
|
+
const [focused, setFocused] = React.useState(false);
|
|
57
|
+
const [composing, setComposing] = React.useState(false);
|
|
58
|
+
const [touched, setTouched] = React.useState(false);
|
|
59
|
+
const inputRef = React.useRef(null);
|
|
60
|
+
const composedRef = useComposedRefs(ref, inputRef);
|
|
61
|
+
const isControlled = valueProp !== void 0;
|
|
62
|
+
const value = isControlled ? valueProp : internalValue;
|
|
63
|
+
const maskPattern = React.useMemo(() => {
|
|
64
|
+
if (typeof mask === "string") {
|
|
65
|
+
return MASK_PATTERNS[mask];
|
|
66
|
+
}
|
|
67
|
+
return mask;
|
|
68
|
+
}, [mask]);
|
|
69
|
+
const transformOpts = React.useMemo(
|
|
70
|
+
() => ({
|
|
71
|
+
currency,
|
|
72
|
+
locale
|
|
73
|
+
}),
|
|
74
|
+
[currency, locale]
|
|
75
|
+
);
|
|
76
|
+
const placeholderValue = React.useMemo(() => {
|
|
77
|
+
if (withoutMask) return placeholder;
|
|
78
|
+
if (placeholder && maskPlaceholder) {
|
|
79
|
+
return focused ? maskPlaceholder : placeholder;
|
|
80
|
+
}
|
|
81
|
+
if (maskPlaceholder) {
|
|
82
|
+
return focused ? maskPlaceholder : void 0;
|
|
83
|
+
}
|
|
84
|
+
return placeholder;
|
|
85
|
+
}, [placeholder, maskPlaceholder, focused, withoutMask]);
|
|
86
|
+
const displayValue = React.useMemo(() => {
|
|
87
|
+
if (withoutMask || !maskPattern || !value) return value ?? "";
|
|
88
|
+
const unmasked = getUnmaskedValue({
|
|
89
|
+
value,
|
|
90
|
+
transform: maskPattern.transform,
|
|
91
|
+
...transformOpts
|
|
92
|
+
});
|
|
93
|
+
return applyMask({
|
|
94
|
+
value: unmasked,
|
|
95
|
+
pattern: maskPattern.pattern,
|
|
96
|
+
...transformOpts,
|
|
97
|
+
mask
|
|
98
|
+
});
|
|
99
|
+
}, [value, maskPattern, withoutMask, transformOpts, mask]);
|
|
100
|
+
const tokenCount = React.useMemo(() => {
|
|
101
|
+
if (!maskPattern || CURRENCY_PERCENTAGE_SYMBOLS.test(maskPattern.pattern)) return void 0;
|
|
102
|
+
return maskPattern.pattern.match(REGEX_CACHE.hashPattern)?.length ?? 0;
|
|
103
|
+
}, [maskPattern]);
|
|
104
|
+
const calculatedMaxLength = tokenCount ? maskPattern?.pattern.length : maxLength;
|
|
105
|
+
const calculatedInputMode = React.useMemo(() => {
|
|
106
|
+
if (inputMode) return inputMode;
|
|
107
|
+
if (!maskPattern) return void 0;
|
|
108
|
+
if (mask === "currency" || mask === "percentage" || mask === "ipv4") {
|
|
109
|
+
return "decimal";
|
|
110
|
+
}
|
|
111
|
+
if (typeof mask === "string" && NUMERIC_MASK_PATTERNS.test(mask)) {
|
|
112
|
+
return "numeric";
|
|
113
|
+
}
|
|
114
|
+
return void 0;
|
|
115
|
+
}, [maskPattern, mask, inputMode]);
|
|
116
|
+
const shouldValidate = React.useCallback(
|
|
117
|
+
(trigger) => {
|
|
118
|
+
if (!onValidate || !maskPattern?.validate) return false;
|
|
119
|
+
switch (validationMode) {
|
|
120
|
+
case "onChange":
|
|
121
|
+
return trigger === "change";
|
|
122
|
+
case "onBlur":
|
|
123
|
+
return trigger === "blur";
|
|
124
|
+
case "onSubmit":
|
|
125
|
+
return false;
|
|
126
|
+
case "onTouched":
|
|
127
|
+
return touched ? trigger === "change" : trigger === "blur";
|
|
128
|
+
case "all":
|
|
129
|
+
return true;
|
|
130
|
+
default:
|
|
131
|
+
return trigger === "change";
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
[onValidate, maskPattern, validationMode, touched]
|
|
135
|
+
);
|
|
136
|
+
const validationOpts = React.useMemo(
|
|
137
|
+
() => ({
|
|
138
|
+
min: typeof min === "string" ? Number.parseFloat(min) : min,
|
|
139
|
+
max: typeof max === "string" ? Number.parseFloat(max) : max
|
|
140
|
+
}),
|
|
141
|
+
[min, max]
|
|
142
|
+
);
|
|
143
|
+
const onInputValidate = React.useCallback(
|
|
144
|
+
(unmaskedValue) => {
|
|
145
|
+
if (onValidate && maskPattern?.validate) {
|
|
146
|
+
const isValid = maskPattern.validate(unmaskedValue, validationOpts);
|
|
147
|
+
onValidate(isValid, unmaskedValue);
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
[onValidate, maskPattern?.validate, validationOpts]
|
|
151
|
+
);
|
|
152
|
+
const onValueChange = React.useCallback(
|
|
153
|
+
(event) => {
|
|
154
|
+
const inputValue = event.target.value;
|
|
155
|
+
let newValue = inputValue;
|
|
156
|
+
let unmaskedValue = inputValue;
|
|
157
|
+
if (composing) {
|
|
158
|
+
if (!isControlled) setInternalValue(inputValue);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (withoutMask || !maskPattern) {
|
|
162
|
+
if (!isControlled) setInternalValue(inputValue);
|
|
163
|
+
if (shouldValidate("change")) onValidate?.(true, inputValue);
|
|
164
|
+
onValueChangeProp?.(inputValue, inputValue);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (maskPattern) {
|
|
168
|
+
unmaskedValue = getUnmaskedValue({
|
|
169
|
+
value: inputValue,
|
|
170
|
+
transform: maskPattern.transform,
|
|
171
|
+
...transformOpts
|
|
172
|
+
});
|
|
173
|
+
newValue = applyMask({
|
|
174
|
+
value: unmaskedValue,
|
|
175
|
+
pattern: maskPattern.pattern,
|
|
176
|
+
...transformOpts,
|
|
177
|
+
mask
|
|
178
|
+
});
|
|
179
|
+
if (inputRef.current && newValue !== inputValue) {
|
|
180
|
+
const inputElement = inputRef.current;
|
|
181
|
+
if (!(inputElement instanceof HTMLInputElement)) return;
|
|
182
|
+
const oldCursorPosition = inputElement.selectionStart ?? 0;
|
|
183
|
+
inputElement.value = newValue;
|
|
184
|
+
const currentUnmasked = getUnmaskedValue({
|
|
185
|
+
value: newValue,
|
|
186
|
+
transform: maskPattern.transform,
|
|
187
|
+
...transformOpts
|
|
188
|
+
});
|
|
189
|
+
let newCursorPosition;
|
|
190
|
+
const previousUnmasked = getUnmaskedValue({
|
|
191
|
+
value,
|
|
192
|
+
transform: maskPattern.transform,
|
|
193
|
+
...transformOpts
|
|
194
|
+
});
|
|
195
|
+
if (CURRENCY_PERCENTAGE_SYMBOLS.test(maskPattern.pattern)) {
|
|
196
|
+
newCursorPosition = getCurrencyCaretPosition({
|
|
197
|
+
newValue,
|
|
198
|
+
mask,
|
|
199
|
+
transformOpts,
|
|
200
|
+
oldCursorPosition,
|
|
201
|
+
oldValue: inputValue,
|
|
202
|
+
previousUnmasked
|
|
203
|
+
});
|
|
204
|
+
} else {
|
|
205
|
+
newCursorPosition = getPatternCaretPosition({
|
|
206
|
+
newValue,
|
|
207
|
+
maskPattern,
|
|
208
|
+
currentUnmasked,
|
|
209
|
+
oldCursorPosition,
|
|
210
|
+
oldValue: inputValue,
|
|
211
|
+
previousUnmasked
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
if (isCurrencyMask({ mask, pattern: maskPattern.pattern })) {
|
|
215
|
+
if (mask === "currency") {
|
|
216
|
+
const currencyAtEnd = isCurrencyAtEnd(transformOpts);
|
|
217
|
+
if (!currencyAtEnd) {
|
|
218
|
+
newCursorPosition = Math.max(1, newCursorPosition);
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
newCursorPosition = Math.max(1, newCursorPosition);
|
|
222
|
+
}
|
|
223
|
+
} else if (maskPattern.pattern.includes("%")) {
|
|
224
|
+
newCursorPosition = Math.min(newValue.length - 1, newCursorPosition);
|
|
225
|
+
}
|
|
226
|
+
newCursorPosition = Math.min(newCursorPosition, newValue.length);
|
|
227
|
+
inputElement.setSelectionRange(newCursorPosition, newCursorPosition);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (!isControlled) {
|
|
231
|
+
setInternalValue(newValue);
|
|
232
|
+
}
|
|
233
|
+
if (shouldValidate("change")) {
|
|
234
|
+
onInputValidate(unmaskedValue);
|
|
235
|
+
}
|
|
236
|
+
onValueChangeProp?.(newValue, unmaskedValue);
|
|
237
|
+
},
|
|
238
|
+
[
|
|
239
|
+
maskPattern,
|
|
240
|
+
isControlled,
|
|
241
|
+
onValueChangeProp,
|
|
242
|
+
onValidate,
|
|
243
|
+
onInputValidate,
|
|
244
|
+
composing,
|
|
245
|
+
shouldValidate,
|
|
246
|
+
withoutMask,
|
|
247
|
+
transformOpts,
|
|
248
|
+
mask,
|
|
249
|
+
value
|
|
250
|
+
]
|
|
251
|
+
);
|
|
252
|
+
const onFocus = React.useCallback(
|
|
253
|
+
(event) => {
|
|
254
|
+
onFocusProp?.(event);
|
|
255
|
+
if (event.defaultPrevented) return;
|
|
256
|
+
setFocused(true);
|
|
257
|
+
},
|
|
258
|
+
[onFocusProp]
|
|
259
|
+
);
|
|
260
|
+
const onBlur = React.useCallback(
|
|
261
|
+
(event) => {
|
|
262
|
+
onBlurProp?.(event);
|
|
263
|
+
if (event.defaultPrevented) return;
|
|
264
|
+
setFocused(false);
|
|
265
|
+
if (!touched) {
|
|
266
|
+
setTouched(true);
|
|
267
|
+
}
|
|
268
|
+
if (shouldValidate("blur")) {
|
|
269
|
+
const currentValue = event.target.value;
|
|
270
|
+
const unmaskedValue = maskPattern ? getUnmaskedValue({
|
|
271
|
+
value: currentValue,
|
|
272
|
+
transform: maskPattern.transform,
|
|
273
|
+
...transformOpts
|
|
274
|
+
}) : currentValue;
|
|
275
|
+
onInputValidate(unmaskedValue);
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
[onBlurProp, touched, shouldValidate, onInputValidate, maskPattern, transformOpts]
|
|
279
|
+
);
|
|
280
|
+
const onCompositionStart = React.useCallback(
|
|
281
|
+
(event) => {
|
|
282
|
+
onCompositionStartProp?.(event);
|
|
283
|
+
if (event.defaultPrevented) return;
|
|
284
|
+
setComposing(true);
|
|
285
|
+
},
|
|
286
|
+
[onCompositionStartProp]
|
|
287
|
+
);
|
|
288
|
+
const onCompositionEnd = React.useCallback(
|
|
289
|
+
(event) => {
|
|
290
|
+
onCompositionEndProp?.(event);
|
|
291
|
+
if (event.defaultPrevented) return;
|
|
292
|
+
setComposing(false);
|
|
293
|
+
const inputElement = inputRef.current;
|
|
294
|
+
if (!inputElement) return;
|
|
295
|
+
if (!(inputElement instanceof HTMLInputElement)) return;
|
|
296
|
+
const inputValue = inputElement.value;
|
|
297
|
+
if (!maskPattern || withoutMask) {
|
|
298
|
+
if (!isControlled) setInternalValue(inputValue);
|
|
299
|
+
if (shouldValidate("change")) onValidate?.(true, inputValue);
|
|
300
|
+
onValueChangeProp?.(inputValue, inputValue);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const unmasked = getUnmaskedValue({
|
|
304
|
+
value: inputValue,
|
|
305
|
+
transform: maskPattern.transform,
|
|
306
|
+
...transformOpts
|
|
307
|
+
});
|
|
308
|
+
const masked = applyMask({
|
|
309
|
+
value: unmasked,
|
|
310
|
+
pattern: maskPattern.pattern,
|
|
311
|
+
...transformOpts,
|
|
312
|
+
mask
|
|
313
|
+
});
|
|
314
|
+
if (!isControlled) setInternalValue(masked);
|
|
315
|
+
if (shouldValidate("change")) onInputValidate(unmasked);
|
|
316
|
+
onValueChangeProp?.(masked, unmasked);
|
|
317
|
+
},
|
|
318
|
+
[
|
|
319
|
+
onCompositionEndProp,
|
|
320
|
+
maskPattern,
|
|
321
|
+
withoutMask,
|
|
322
|
+
isControlled,
|
|
323
|
+
shouldValidate,
|
|
324
|
+
onValidate,
|
|
325
|
+
onValueChangeProp,
|
|
326
|
+
transformOpts,
|
|
327
|
+
mask,
|
|
328
|
+
onInputValidate
|
|
329
|
+
]
|
|
330
|
+
);
|
|
331
|
+
const onPaste = React.useCallback(
|
|
332
|
+
(event) => {
|
|
333
|
+
onPasteProp?.(event);
|
|
334
|
+
if (event.defaultPrevented) return;
|
|
335
|
+
if (withoutMask || !maskPattern) return;
|
|
336
|
+
if (mask === "ipv4") return;
|
|
337
|
+
const target = event.target;
|
|
338
|
+
if (!(target instanceof HTMLInputElement)) return;
|
|
339
|
+
const pastedData = event.clipboardData.getData("text");
|
|
340
|
+
if (!pastedData) return;
|
|
341
|
+
event.preventDefault();
|
|
342
|
+
const currentValue = target.value;
|
|
343
|
+
const selectionStart = target.selectionStart ?? 0;
|
|
344
|
+
const selectionEnd = target.selectionEnd ?? 0;
|
|
345
|
+
const beforeSelection = currentValue.slice(0, selectionStart);
|
|
346
|
+
const afterSelection = currentValue.slice(selectionEnd);
|
|
347
|
+
const newInputValue = beforeSelection + pastedData + afterSelection;
|
|
348
|
+
const unmasked = getUnmaskedValue({
|
|
349
|
+
value: newInputValue,
|
|
350
|
+
transform: maskPattern.transform,
|
|
351
|
+
...transformOpts
|
|
352
|
+
});
|
|
353
|
+
const newMaskedValue = applyMask({
|
|
354
|
+
value: unmasked,
|
|
355
|
+
pattern: maskPattern.pattern,
|
|
356
|
+
...transformOpts,
|
|
357
|
+
mask
|
|
358
|
+
});
|
|
359
|
+
target.value = newMaskedValue;
|
|
360
|
+
if (isCurrencyMask({ mask, pattern: maskPattern.pattern })) {
|
|
361
|
+
const currencyAtEnd = isCurrencyAtEnd(transformOpts);
|
|
362
|
+
const caret = currencyAtEnd ? newMaskedValue.search(/\s*[^\d\s]+$/) : newMaskedValue.length;
|
|
363
|
+
target.setSelectionRange(caret, caret);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (maskPattern.pattern.includes("%")) {
|
|
367
|
+
target.setSelectionRange(newMaskedValue.length - 1, newMaskedValue.length - 1);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
let newCursorPosition = newMaskedValue.length;
|
|
371
|
+
try {
|
|
372
|
+
const unmaskedCount = unmasked.length;
|
|
373
|
+
let position = 0;
|
|
374
|
+
let count = 0;
|
|
375
|
+
for (let i = 0; i < maskPattern.pattern.length && i < newMaskedValue.length; i++) {
|
|
376
|
+
if (maskPattern.pattern[i] === "#") {
|
|
377
|
+
count++;
|
|
378
|
+
if (count <= unmaskedCount) {
|
|
379
|
+
position = i + 1;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
newCursorPosition = position;
|
|
384
|
+
} catch {
|
|
385
|
+
}
|
|
386
|
+
target.setSelectionRange(newCursorPosition, newCursorPosition);
|
|
387
|
+
if (!isControlled) setInternalValue(newMaskedValue);
|
|
388
|
+
if (shouldValidate("change")) onInputValidate(unmasked);
|
|
389
|
+
onValueChangeProp?.(newMaskedValue, unmasked);
|
|
390
|
+
},
|
|
391
|
+
[
|
|
392
|
+
onPasteProp,
|
|
393
|
+
withoutMask,
|
|
394
|
+
maskPattern,
|
|
395
|
+
mask,
|
|
396
|
+
transformOpts,
|
|
397
|
+
isControlled,
|
|
398
|
+
shouldValidate,
|
|
399
|
+
onInputValidate,
|
|
400
|
+
onValueChangeProp
|
|
401
|
+
]
|
|
402
|
+
);
|
|
403
|
+
const onKeyDown = React.useCallback(
|
|
404
|
+
(event) => {
|
|
405
|
+
onKeyDownProp?.(event);
|
|
406
|
+
if (event.defaultPrevented) return;
|
|
407
|
+
if (withoutMask || !maskPattern) return;
|
|
408
|
+
if (mask === "ipv4") return;
|
|
409
|
+
if (event.key === "Backspace") {
|
|
410
|
+
const target = event.target;
|
|
411
|
+
if (!(target instanceof HTMLInputElement)) return;
|
|
412
|
+
const cursorPosition = target.selectionStart ?? 0;
|
|
413
|
+
const selectionEnd = target.selectionEnd ?? 0;
|
|
414
|
+
const currentValue = target.value;
|
|
415
|
+
if (mask === "currency" || mask === "percentage" || maskPattern.pattern.includes("$") || maskPattern.pattern.includes("\u20AC") || maskPattern.pattern.includes("%")) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
if (cursorPosition !== selectionEnd) {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
if (cursorPosition > 0) {
|
|
422
|
+
const charBeforeCursor = currentValue[cursorPosition - 1];
|
|
423
|
+
if (charBeforeCursor) {
|
|
424
|
+
event.preventDefault();
|
|
425
|
+
const unmaskedIndex = toUnmaskedIndex({
|
|
426
|
+
masked: currentValue,
|
|
427
|
+
pattern: maskPattern.pattern,
|
|
428
|
+
caret: cursorPosition
|
|
429
|
+
});
|
|
430
|
+
if (unmaskedIndex > 0) {
|
|
431
|
+
const currentUnmasked = getUnmaskedValue({
|
|
432
|
+
value: currentValue,
|
|
433
|
+
transform: maskPattern.transform,
|
|
434
|
+
...transformOpts
|
|
435
|
+
});
|
|
436
|
+
const nextUnmasked = currentUnmasked.slice(0, unmaskedIndex - 1) + currentUnmasked.slice(unmaskedIndex);
|
|
437
|
+
const nextMasked = applyMask({
|
|
438
|
+
value: nextUnmasked,
|
|
439
|
+
pattern: maskPattern.pattern,
|
|
440
|
+
...transformOpts,
|
|
441
|
+
mask
|
|
442
|
+
});
|
|
443
|
+
target.value = nextMasked;
|
|
444
|
+
const nextCaret = fromUnmaskedIndex({
|
|
445
|
+
masked: nextMasked,
|
|
446
|
+
pattern: maskPattern.pattern,
|
|
447
|
+
unmaskedIndex: unmaskedIndex - 1
|
|
448
|
+
});
|
|
449
|
+
target.setSelectionRange(nextCaret, nextCaret);
|
|
450
|
+
onValueChangeProp?.(nextMasked, nextUnmasked);
|
|
451
|
+
}
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (event.key === "Delete") {
|
|
457
|
+
const target = event.target;
|
|
458
|
+
if (!(target instanceof HTMLInputElement)) return;
|
|
459
|
+
const cursorPosition = target.selectionStart ?? 0;
|
|
460
|
+
const selectionEnd = target.selectionEnd ?? 0;
|
|
461
|
+
const currentValue = target.value;
|
|
462
|
+
if (mask === "currency" || mask === "percentage" || maskPattern.pattern.includes("$") || maskPattern.pattern.includes("\u20AC") || maskPattern.pattern.includes("%")) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (cursorPosition !== selectionEnd) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
if (cursorPosition < currentValue.length) {
|
|
469
|
+
const charAtCursor = currentValue[cursorPosition];
|
|
470
|
+
if (charAtCursor) {
|
|
471
|
+
event.preventDefault();
|
|
472
|
+
const unmaskedIndex = toUnmaskedIndex({
|
|
473
|
+
masked: currentValue,
|
|
474
|
+
pattern: maskPattern.pattern,
|
|
475
|
+
caret: cursorPosition
|
|
476
|
+
});
|
|
477
|
+
const currentUnmasked = getUnmaskedValue({
|
|
478
|
+
value: currentValue,
|
|
479
|
+
transform: maskPattern.transform,
|
|
480
|
+
...transformOpts
|
|
481
|
+
});
|
|
482
|
+
if (unmaskedIndex < currentUnmasked.length) {
|
|
483
|
+
const nextUnmasked = currentUnmasked.slice(0, unmaskedIndex) + currentUnmasked.slice(unmaskedIndex + 1);
|
|
484
|
+
const nextMasked = applyMask({
|
|
485
|
+
value: nextUnmasked,
|
|
486
|
+
pattern: maskPattern.pattern,
|
|
487
|
+
...transformOpts,
|
|
488
|
+
mask
|
|
489
|
+
});
|
|
490
|
+
target.value = nextMasked;
|
|
491
|
+
const nextCaret = fromUnmaskedIndex({
|
|
492
|
+
masked: nextMasked,
|
|
493
|
+
pattern: maskPattern.pattern,
|
|
494
|
+
unmaskedIndex
|
|
495
|
+
});
|
|
496
|
+
target.setSelectionRange(nextCaret, nextCaret);
|
|
497
|
+
onValueChangeProp?.(nextMasked, nextUnmasked);
|
|
498
|
+
}
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
[maskPattern, onKeyDownProp, onValueChangeProp, transformOpts, mask, withoutMask]
|
|
505
|
+
);
|
|
506
|
+
const InputPrimitive = asChild ? Slot : "input";
|
|
507
|
+
return /* @__PURE__ */ jsx(
|
|
508
|
+
InputPrimitive,
|
|
509
|
+
{
|
|
510
|
+
"aria-invalid": invalid || void 0,
|
|
511
|
+
"data-disabled": disabled ? "" : void 0,
|
|
512
|
+
"data-invalid": invalid ? "" : void 0,
|
|
513
|
+
"data-readonly": readOnly ? "" : void 0,
|
|
514
|
+
"data-required": required ? "" : void 0,
|
|
515
|
+
"data-slot": "mask-input",
|
|
516
|
+
...inputProps,
|
|
517
|
+
className: cn(
|
|
518
|
+
/*
|
|
519
|
+
* ⚠ Khác thượng nguồn: chuỗi lớp của `Input` Comitor thay cho của shadcn, để ô có mặt nạ
|
|
520
|
+
* đứng cạnh ô nhập thường trong cùng một form không lệch nhau (cao, nền, viền, cỡ chữ,
|
|
521
|
+
* focus). Số đo của từng vai trò ở chú thích đầu `input.tsx`:
|
|
522
|
+
* viền nghỉ `border-input` · hover `--control-edge-strong` · focus `--primary-ink`;
|
|
523
|
+
* lỗi `border-destructive-ink` (không phải `border-destructive` — token TÔ NỀN).
|
|
524
|
+
* Bỏ `dark:bg-input/30` (viết thẳng thang màu — bảng tương phản cao không đổi được) và
|
|
525
|
+
* `shadow-xs` (không có trong bộ bóng của gói).
|
|
526
|
+
*/
|
|
527
|
+
"w-full h-9 px-3 rounded-md border bg-background text-sm text-foreground placeholder:text-muted-foreground",
|
|
528
|
+
"transition-colors duration-150 ease-out",
|
|
529
|
+
"focus:outline-none focus:ring-2 focus:ring-ring/50 focus:border-primary-ink",
|
|
530
|
+
"disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-muted",
|
|
531
|
+
"border-input hover:border-control-edge-strong",
|
|
532
|
+
"aria-invalid:border-destructive-ink aria-invalid:hover:border-destructive-ink aria-invalid:focus:border-destructive-ink aria-invalid:focus:ring-destructive/30",
|
|
533
|
+
className
|
|
534
|
+
),
|
|
535
|
+
placeholder: placeholderValue,
|
|
536
|
+
ref: composedRef,
|
|
537
|
+
value: displayValue,
|
|
538
|
+
disabled,
|
|
539
|
+
maxLength: calculatedMaxLength,
|
|
540
|
+
readOnly,
|
|
541
|
+
required,
|
|
542
|
+
inputMode: calculatedInputMode,
|
|
543
|
+
min,
|
|
544
|
+
max,
|
|
545
|
+
onFocus,
|
|
546
|
+
onBlur,
|
|
547
|
+
onKeyDown,
|
|
548
|
+
onPaste,
|
|
549
|
+
onChange: onValueChange,
|
|
550
|
+
onCompositionStart,
|
|
551
|
+
onCompositionEnd
|
|
552
|
+
}
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
export {
|
|
556
|
+
MaskInput
|
|
557
|
+
};
|
|
558
|
+
//# sourceMappingURL=mask-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/mask-input.tsx"],"sourcesContent":["\"use client\";\n\n/*\n * Port từ Dice UI (https://diceui.com/docs/components/mask-input) — MIT, © 2024 Sadman Sakib.\n * Giữ nguyên cấu trúc thượng nguồn để còn đối chiếu ngược được; mọi chỗ đổi đều có chú thích.\n */\n\n/*\n * ⚠ Khác thượng nguồn: thượng nguồn là MỘT file. Ở đây nửa trên (bảng `MASK_PATTERNS` + mọi hàm\n * thuần) nằm ở `mask-input-constants.ts` — hằng dữ liệu export ra không được sống trong file\n * `\"use client\"` (Server Component đọc ra `undefined`, không throw). File này chỉ còn component,\n * và chỉ `import` từ đó, KHÔNG `export` lại (re-export vòng qua file client là vô hiệu).\n */\n\nimport { Slot } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { useComposedRefs } from \"../../lib/compose-refs.js\";\nimport {\n applyMask,\n CURRENCY_PERCENTAGE_SYMBOLS,\n DEFAULT_CURRENCY,\n DEFAULT_LOCALE,\n fromUnmaskedIndex,\n getCurrencyCaretPosition,\n getPatternCaretPosition,\n getUnmaskedValue,\n isCurrencyAtEnd,\n isCurrencyMask,\n MASK_PATTERNS,\n type MaskPattern,\n type MaskPatternKey,\n NUMERIC_MASK_PATTERNS,\n REGEX_CACHE,\n toUnmaskedIndex\n} from \"./mask-input-constants.js\";\n\ntype InputElement = React.ComponentRef<\"input\">;\n\ninterface MaskInputProps extends React.ComponentProps<\"input\"> {\n value?: string;\n defaultValue?: string;\n onValueChange?: (maskedValue: string, unmaskedValue: string) => void;\n onValidate?: (isValid: boolean, unmaskedValue: string) => void;\n validationMode?: \"onChange\" | \"onBlur\" | \"onSubmit\" | \"onTouched\" | \"all\";\n /**\n * Tên một mẫu dựng sẵn (`\"currency\"`, `\"percentage\"`, `\"time\"`, `\"creditCard\"`…) hoặc một\n * `MaskPattern` tự viết. ⚠ `\"phone\"`, `\"date\"`, `\"ssn\"`, `\"zipCode\"`, `\"ein\"` là định dạng Hoa Kỳ\n * — số điện thoại ở app Comitor dùng `PhoneInput`.\n */\n mask?: MaskPatternKey | MaskPattern;\n maskPlaceholder?: string;\n /**\n * Mã tiền tệ ISO 4217 cho `mask=\"currency\"`. Mặc định `\"VND\"`.\n *\n * ⚠ Khác thượng nguồn: thượng nguồn mặc định `\"USD\"`.\n */\n currency?: string;\n /**\n * Locale BCP 47 cho `mask=\"currency\"` — quyết định dấu nhóm, dấu thập phân và vị trí ký hiệu.\n * Mặc định `\"vi-VN\"` (`1.234.567 ₫`).\n *\n * ⚠ Khác thượng nguồn: thượng nguồn mặc định `\"en-US\"`.\n */\n locale?: string;\n asChild?: boolean;\n invalid?: boolean;\n withoutMask?: boolean;\n}\n\nfunction MaskInput(props: MaskInputProps) {\n const {\n value: valueProp,\n defaultValue,\n onValueChange: onValueChangeProp,\n onValidate,\n onBlur: onBlurProp,\n onFocus: onFocusProp,\n onKeyDown: onKeyDownProp,\n onPaste: onPasteProp,\n onCompositionStart: onCompositionStartProp,\n onCompositionEnd: onCompositionEndProp,\n validationMode = \"onChange\",\n mask,\n maskPlaceholder,\n currency = DEFAULT_CURRENCY,\n locale = DEFAULT_LOCALE,\n placeholder,\n inputMode,\n min,\n max,\n maxLength,\n asChild = false,\n disabled = false,\n invalid = false,\n readOnly = false,\n required = false,\n withoutMask = false,\n className,\n ref,\n ...inputProps\n } = props;\n\n const [internalValue, setInternalValue] = React.useState(defaultValue ?? \"\");\n const [focused, setFocused] = React.useState(false);\n const [composing, setComposing] = React.useState(false);\n const [touched, setTouched] = React.useState(false);\n const inputRef = React.useRef<HTMLInputElement>(null);\n const composedRef = useComposedRefs(ref, inputRef);\n\n const isControlled = valueProp !== undefined;\n const value = isControlled ? valueProp : internalValue;\n\n const maskPattern = React.useMemo(() => {\n if (typeof mask === \"string\") {\n return MASK_PATTERNS[mask];\n }\n return mask;\n }, [mask]);\n\n const transformOpts = React.useMemo(\n () => ({\n currency,\n locale\n }),\n [currency, locale]\n );\n\n const placeholderValue = React.useMemo(() => {\n if (withoutMask) return placeholder;\n\n if (placeholder && maskPlaceholder) {\n return focused ? maskPlaceholder : placeholder;\n }\n\n if (maskPlaceholder) {\n return focused ? maskPlaceholder : undefined;\n }\n\n return placeholder;\n }, [placeholder, maskPlaceholder, focused, withoutMask]);\n\n const displayValue = React.useMemo(() => {\n if (withoutMask || !maskPattern || !value) return value ?? \"\";\n const unmasked = getUnmaskedValue({\n value,\n transform: maskPattern.transform,\n ...transformOpts\n });\n return applyMask({\n value: unmasked,\n pattern: maskPattern.pattern,\n ...transformOpts,\n mask\n });\n }, [value, maskPattern, withoutMask, transformOpts, mask]);\n\n const tokenCount = React.useMemo(() => {\n if (!maskPattern || CURRENCY_PERCENTAGE_SYMBOLS.test(maskPattern.pattern)) return undefined;\n return maskPattern.pattern.match(REGEX_CACHE.hashPattern)?.length ?? 0;\n }, [maskPattern]);\n\n const calculatedMaxLength = tokenCount ? maskPattern?.pattern.length : maxLength;\n\n const calculatedInputMode = React.useMemo(() => {\n if (inputMode) return inputMode;\n if (!maskPattern) return undefined;\n\n if (mask === \"currency\" || mask === \"percentage\" || mask === \"ipv4\") {\n return \"decimal\";\n }\n\n if (typeof mask === \"string\" && NUMERIC_MASK_PATTERNS.test(mask)) {\n return \"numeric\";\n }\n return undefined;\n }, [maskPattern, mask, inputMode]);\n\n const shouldValidate = React.useCallback(\n (trigger: \"change\" | \"blur\") => {\n if (!onValidate || !maskPattern?.validate) return false;\n\n switch (validationMode) {\n case \"onChange\":\n return trigger === \"change\";\n case \"onBlur\":\n return trigger === \"blur\";\n case \"onSubmit\":\n return false;\n case \"onTouched\":\n return touched ? trigger === \"change\" : trigger === \"blur\";\n case \"all\":\n return true;\n default:\n return trigger === \"change\";\n }\n },\n [onValidate, maskPattern, validationMode, touched]\n );\n\n const validationOpts = React.useMemo(\n () => ({\n min: typeof min === \"string\" ? Number.parseFloat(min) : min,\n max: typeof max === \"string\" ? Number.parseFloat(max) : max\n }),\n [min, max]\n );\n\n const onInputValidate = React.useCallback(\n (unmaskedValue: string) => {\n if (onValidate && maskPattern?.validate) {\n const isValid = maskPattern.validate(unmaskedValue, validationOpts);\n onValidate(isValid, unmaskedValue);\n }\n },\n [onValidate, maskPattern?.validate, validationOpts]\n );\n\n const onValueChange = React.useCallback(\n (event: React.ChangeEvent<InputElement>) => {\n const inputValue = event.target.value;\n let newValue = inputValue;\n let unmaskedValue = inputValue;\n\n if (composing) {\n if (!isControlled) setInternalValue(inputValue);\n return;\n }\n\n if (withoutMask || !maskPattern) {\n if (!isControlled) setInternalValue(inputValue);\n if (shouldValidate(\"change\")) onValidate?.(true, inputValue);\n onValueChangeProp?.(inputValue, inputValue);\n return;\n }\n\n if (maskPattern) {\n unmaskedValue = getUnmaskedValue({\n value: inputValue,\n transform: maskPattern.transform,\n ...transformOpts\n });\n newValue = applyMask({\n value: unmaskedValue,\n pattern: maskPattern.pattern,\n ...transformOpts,\n mask\n });\n\n if (inputRef.current && newValue !== inputValue) {\n const inputElement = inputRef.current;\n if (!(inputElement instanceof HTMLInputElement)) return;\n\n const oldCursorPosition = inputElement.selectionStart ?? 0;\n\n inputElement.value = newValue;\n\n const currentUnmasked = getUnmaskedValue({\n value: newValue,\n transform: maskPattern.transform,\n ...transformOpts\n });\n\n let newCursorPosition: number;\n\n const previousUnmasked = getUnmaskedValue({\n value,\n transform: maskPattern.transform,\n ...transformOpts\n });\n\n if (CURRENCY_PERCENTAGE_SYMBOLS.test(maskPattern.pattern)) {\n newCursorPosition = getCurrencyCaretPosition({\n newValue,\n mask,\n transformOpts,\n oldCursorPosition,\n oldValue: inputValue,\n previousUnmasked\n });\n } else {\n newCursorPosition = getPatternCaretPosition({\n newValue,\n maskPattern,\n currentUnmasked,\n oldCursorPosition,\n oldValue: inputValue,\n previousUnmasked\n });\n }\n\n if (isCurrencyMask({ mask, pattern: maskPattern.pattern })) {\n if (mask === \"currency\") {\n const currencyAtEnd = isCurrencyAtEnd(transformOpts);\n if (!currencyAtEnd) {\n newCursorPosition = Math.max(1, newCursorPosition);\n }\n } else {\n newCursorPosition = Math.max(1, newCursorPosition);\n }\n } else if (maskPattern.pattern.includes(\"%\")) {\n newCursorPosition = Math.min(newValue.length - 1, newCursorPosition);\n }\n\n newCursorPosition = Math.min(newCursorPosition, newValue.length);\n\n inputElement.setSelectionRange(newCursorPosition, newCursorPosition);\n }\n }\n\n if (!isControlled) {\n setInternalValue(newValue);\n }\n\n if (shouldValidate(\"change\")) {\n onInputValidate(unmaskedValue);\n }\n\n onValueChangeProp?.(newValue, unmaskedValue);\n },\n [\n maskPattern,\n isControlled,\n onValueChangeProp,\n onValidate,\n onInputValidate,\n composing,\n shouldValidate,\n withoutMask,\n transformOpts,\n mask,\n value\n ]\n );\n\n const onFocus = React.useCallback(\n (event: React.FocusEvent<InputElement>) => {\n onFocusProp?.(event);\n if (event.defaultPrevented) return;\n\n setFocused(true);\n },\n [onFocusProp]\n );\n\n const onBlur = React.useCallback(\n (event: React.FocusEvent<InputElement>) => {\n onBlurProp?.(event);\n if (event.defaultPrevented) return;\n\n setFocused(false);\n\n if (!touched) {\n setTouched(true);\n }\n\n if (shouldValidate(\"blur\")) {\n const currentValue = event.target.value;\n const unmaskedValue = maskPattern\n ? getUnmaskedValue({\n value: currentValue,\n transform: maskPattern.transform,\n ...transformOpts\n })\n : currentValue;\n onInputValidate(unmaskedValue);\n }\n },\n [onBlurProp, touched, shouldValidate, onInputValidate, maskPattern, transformOpts]\n );\n\n const onCompositionStart = React.useCallback(\n (event: React.CompositionEvent<InputElement>) => {\n onCompositionStartProp?.(event);\n if (event.defaultPrevented) return;\n\n setComposing(true);\n },\n [onCompositionStartProp]\n );\n\n const onCompositionEnd = React.useCallback(\n (event: React.CompositionEvent<InputElement>) => {\n onCompositionEndProp?.(event);\n if (event.defaultPrevented) return;\n\n setComposing(false);\n\n const inputElement = inputRef.current;\n if (!inputElement) return;\n if (!(inputElement instanceof HTMLInputElement)) return;\n const inputValue = inputElement.value;\n\n if (!maskPattern || withoutMask) {\n if (!isControlled) setInternalValue(inputValue);\n if (shouldValidate(\"change\")) onValidate?.(true, inputValue);\n onValueChangeProp?.(inputValue, inputValue);\n return;\n }\n\n const unmasked = getUnmaskedValue({\n value: inputValue,\n transform: maskPattern.transform,\n ...transformOpts\n });\n const masked = applyMask({\n value: unmasked,\n pattern: maskPattern.pattern,\n ...transformOpts,\n mask\n });\n\n if (!isControlled) setInternalValue(masked);\n if (shouldValidate(\"change\")) onInputValidate(unmasked);\n onValueChangeProp?.(masked, unmasked);\n },\n [\n onCompositionEndProp,\n maskPattern,\n withoutMask,\n isControlled,\n shouldValidate,\n onValidate,\n onValueChangeProp,\n transformOpts,\n mask,\n onInputValidate\n ]\n );\n\n const onPaste = React.useCallback(\n (event: React.ClipboardEvent<InputElement>) => {\n onPasteProp?.(event);\n if (event.defaultPrevented) return;\n\n if (withoutMask || !maskPattern) return;\n\n if (mask === \"ipv4\") return;\n\n const target = event.target as InputElement;\n if (!(target instanceof HTMLInputElement)) return;\n\n const pastedData = event.clipboardData.getData(\"text\");\n if (!pastedData) return;\n\n event.preventDefault();\n\n const currentValue = target.value;\n const selectionStart = target.selectionStart ?? 0;\n const selectionEnd = target.selectionEnd ?? 0;\n\n const beforeSelection = currentValue.slice(0, selectionStart);\n const afterSelection = currentValue.slice(selectionEnd);\n const newInputValue = beforeSelection + pastedData + afterSelection;\n\n const unmasked = getUnmaskedValue({\n value: newInputValue,\n transform: maskPattern.transform,\n ...transformOpts\n });\n const newMaskedValue = applyMask({\n value: unmasked,\n pattern: maskPattern.pattern,\n ...transformOpts,\n mask\n });\n\n target.value = newMaskedValue;\n\n if (isCurrencyMask({ mask, pattern: maskPattern.pattern })) {\n const currencyAtEnd = isCurrencyAtEnd(transformOpts);\n const caret = currencyAtEnd ? newMaskedValue.search(/\\s*[^\\d\\s]+$/) : newMaskedValue.length;\n target.setSelectionRange(caret, caret);\n return;\n }\n\n if (maskPattern.pattern.includes(\"%\")) {\n target.setSelectionRange(newMaskedValue.length - 1, newMaskedValue.length - 1);\n return;\n }\n\n let newCursorPosition = newMaskedValue.length;\n try {\n const unmaskedCount = unmasked.length;\n let position = 0;\n let count = 0;\n\n for (let i = 0; i < maskPattern.pattern.length && i < newMaskedValue.length; i++) {\n if (maskPattern.pattern[i] === \"#\") {\n count++;\n if (count <= unmaskedCount) {\n position = i + 1;\n }\n }\n }\n newCursorPosition = position;\n } catch {\n // Lỗi thì để con trỏ ở cuối.\n }\n\n target.setSelectionRange(newCursorPosition, newCursorPosition);\n\n if (!isControlled) setInternalValue(newMaskedValue);\n if (shouldValidate(\"change\")) onInputValidate(unmasked);\n onValueChangeProp?.(newMaskedValue, unmasked);\n },\n [\n onPasteProp,\n withoutMask,\n maskPattern,\n mask,\n transformOpts,\n isControlled,\n shouldValidate,\n onInputValidate,\n onValueChangeProp\n ]\n );\n\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent<InputElement>) => {\n onKeyDownProp?.(event);\n if (event.defaultPrevented) return;\n\n if (withoutMask || !maskPattern) return;\n\n if (mask === \"ipv4\") return;\n\n if (event.key === \"Backspace\") {\n const target = event.target as InputElement;\n if (!(target instanceof HTMLInputElement)) return;\n const cursorPosition = target.selectionStart ?? 0;\n const selectionEnd = target.selectionEnd ?? 0;\n const currentValue = target.value;\n\n if (\n mask === \"currency\" ||\n mask === \"percentage\" ||\n maskPattern.pattern.includes(\"$\") ||\n maskPattern.pattern.includes(\"€\") ||\n maskPattern.pattern.includes(\"%\")\n ) {\n return;\n }\n\n if (cursorPosition !== selectionEnd) {\n return;\n }\n\n if (cursorPosition > 0) {\n const charBeforeCursor = currentValue[cursorPosition - 1];\n\n if (charBeforeCursor) {\n event.preventDefault();\n\n const unmaskedIndex = toUnmaskedIndex({\n masked: currentValue,\n pattern: maskPattern.pattern,\n caret: cursorPosition\n });\n\n if (unmaskedIndex > 0) {\n const currentUnmasked = getUnmaskedValue({\n value: currentValue,\n transform: maskPattern.transform,\n ...transformOpts\n });\n const nextUnmasked = currentUnmasked.slice(0, unmaskedIndex - 1) + currentUnmasked.slice(unmaskedIndex);\n const nextMasked = applyMask({\n value: nextUnmasked,\n pattern: maskPattern.pattern,\n ...transformOpts,\n mask\n });\n\n target.value = nextMasked;\n const nextCaret = fromUnmaskedIndex({\n masked: nextMasked,\n pattern: maskPattern.pattern,\n unmaskedIndex: unmaskedIndex - 1\n });\n\n target.setSelectionRange(nextCaret, nextCaret);\n\n onValueChangeProp?.(nextMasked, nextUnmasked);\n }\n return;\n }\n }\n }\n\n if (event.key === \"Delete\") {\n const target = event.target as InputElement;\n if (!(target instanceof HTMLInputElement)) return;\n const cursorPosition = target.selectionStart ?? 0;\n const selectionEnd = target.selectionEnd ?? 0;\n const currentValue = target.value;\n\n if (\n mask === \"currency\" ||\n mask === \"percentage\" ||\n maskPattern.pattern.includes(\"$\") ||\n maskPattern.pattern.includes(\"€\") ||\n maskPattern.pattern.includes(\"%\")\n ) {\n return;\n }\n\n if (cursorPosition !== selectionEnd) {\n return;\n }\n\n if (cursorPosition < currentValue.length) {\n const charAtCursor = currentValue[cursorPosition];\n\n if (charAtCursor) {\n event.preventDefault();\n\n const unmaskedIndex = toUnmaskedIndex({\n masked: currentValue,\n pattern: maskPattern.pattern,\n caret: cursorPosition\n });\n\n const currentUnmasked = getUnmaskedValue({\n value: currentValue,\n transform: maskPattern.transform,\n ...transformOpts\n });\n\n if (unmaskedIndex < currentUnmasked.length) {\n const nextUnmasked = currentUnmasked.slice(0, unmaskedIndex) + currentUnmasked.slice(unmaskedIndex + 1);\n const nextMasked = applyMask({\n value: nextUnmasked,\n pattern: maskPattern.pattern,\n ...transformOpts,\n mask\n });\n\n target.value = nextMasked;\n const nextCaret = fromUnmaskedIndex({\n masked: nextMasked,\n pattern: maskPattern.pattern,\n unmaskedIndex: unmaskedIndex\n });\n\n target.setSelectionRange(nextCaret, nextCaret);\n\n onValueChangeProp?.(nextMasked, nextUnmasked);\n }\n return;\n }\n }\n }\n },\n [maskPattern, onKeyDownProp, onValueChangeProp, transformOpts, mask, withoutMask]\n );\n\n const InputPrimitive = asChild ? Slot : \"input\";\n\n return (\n <InputPrimitive\n // ⚠ Khác thượng nguồn: chỉ phát thuộc tính khi THẬT SỰ lỗi — `aria-invalid=\"false\"` hợp lệ\n // nhưng thừa, và cùng quy ước với `Input`/`Combobox` của gói. Đặt TRƯỚC spread để\n // `aria-invalid` do `<FormControl>` dán vào vẫn thắng.\n aria-invalid={invalid || undefined}\n data-disabled={disabled ? \"\" : undefined}\n data-invalid={invalid ? \"\" : undefined}\n data-readonly={readOnly ? \"\" : undefined}\n data-required={required ? \"\" : undefined}\n data-slot=\"mask-input\"\n {...inputProps}\n className={cn(\n /*\n * ⚠ Khác thượng nguồn: chuỗi lớp của `Input` Comitor thay cho của shadcn, để ô có mặt nạ\n * đứng cạnh ô nhập thường trong cùng một form không lệch nhau (cao, nền, viền, cỡ chữ,\n * focus). Số đo của từng vai trò ở chú thích đầu `input.tsx`:\n * viền nghỉ `border-input` · hover `--control-edge-strong` · focus `--primary-ink`;\n * lỗi `border-destructive-ink` (không phải `border-destructive` — token TÔ NỀN).\n * Bỏ `dark:bg-input/30` (viết thẳng thang màu — bảng tương phản cao không đổi được) và\n * `shadow-xs` (không có trong bộ bóng của gói).\n */\n \"w-full h-9 px-3 rounded-md border bg-background text-sm text-foreground placeholder:text-muted-foreground\",\n \"transition-colors duration-150 ease-out\",\n \"focus:outline-none focus:ring-2 focus:ring-ring/50 focus:border-primary-ink\",\n \"disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-muted\",\n \"border-input hover:border-control-edge-strong\",\n \"aria-invalid:border-destructive-ink aria-invalid:hover:border-destructive-ink aria-invalid:focus:border-destructive-ink aria-invalid:focus:ring-destructive/30\",\n className\n )}\n placeholder={placeholderValue}\n ref={composedRef}\n value={displayValue}\n disabled={disabled}\n maxLength={calculatedMaxLength}\n readOnly={readOnly}\n required={required}\n inputMode={calculatedInputMode}\n min={min}\n max={max}\n onFocus={onFocus}\n onBlur={onBlur}\n onKeyDown={onKeyDown}\n onPaste={onPaste}\n onChange={onValueChange}\n onCompositionStart={onCompositionStart}\n onCompositionEnd={onCompositionEnd}\n />\n );\n}\n\nexport { MaskInput, type MaskInputProps };\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as MentionPrimitive from '@diceui/mention';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Mention — ô nhập có gợi ý `@nhắc tên`: gõ ký tự kích hoạt (mặc định `@`) là mở danh sách, chọn
|
|
6
|
+
* một mục thì chèn `@Tên` vào ô và đánh dấu nó thành một khối (xoá là xoá cả khối).
|
|
7
|
+
* Dùng cho ô soạn tin của Chat và bình luận trong task.
|
|
8
|
+
*
|
|
9
|
+
* Phần lõi (dò ký tự kích hoạt, lọc, điều hướng bàn phím, định vị danh sách theo con trỏ) là gói
|
|
10
|
+
* headless `@diceui/mention`; file này chỉ phủ style theo token của Comitor.
|
|
11
|
+
*/
|
|
12
|
+
type MentionProps = React.ComponentProps<typeof MentionPrimitive.Root>;
|
|
13
|
+
declare function Mention({ className, dir: dirProp, ...props }: MentionProps): React.JSX.Element;
|
|
14
|
+
type MentionLabelProps = React.ComponentProps<typeof MentionPrimitive.Label>;
|
|
15
|
+
declare function MentionLabel({ className, ...props }: MentionLabelProps): React.JSX.Element;
|
|
16
|
+
type MentionInputProps = React.ComponentProps<typeof MentionPrimitive.Input>;
|
|
17
|
+
/**
|
|
18
|
+
* Ô nhập của `Mention`. Muốn nhiều dòng (ô soạn tin) thì `asChild` với một `<textarea />`.
|
|
19
|
+
* Không có `MentionLabel` thì PHẢI truyền `aria-label`.
|
|
20
|
+
*/
|
|
21
|
+
declare function MentionInput({ className, ...props }: MentionInputProps): React.JSX.Element;
|
|
22
|
+
type MentionContentProps = React.ComponentProps<typeof MentionPrimitive.Content>;
|
|
23
|
+
/**
|
|
24
|
+
* Danh sách gợi ý (`role="listbox"`), render qua portal.
|
|
25
|
+
* Tên cho trình đọc màn hình mặc định "Danh sách gợi ý" — đổi bằng `aria-label`.
|
|
26
|
+
*/
|
|
27
|
+
declare function MentionContent({ className, children, "aria-label": ariaLabel, ...props }: MentionContentProps): React.JSX.Element;
|
|
28
|
+
type MentionItemProps = React.ComponentProps<typeof MentionPrimitive.Item>;
|
|
29
|
+
declare function MentionItem({ className, children, ...props }: MentionItemProps): React.JSX.Element;
|
|
30
|
+
|
|
31
|
+
export { Mention, MentionContent, type MentionContentProps, MentionInput, type MentionInputProps, MentionItem, type MentionItemProps, MentionLabel, type MentionLabelProps, type MentionProps };
|