@comitor/ui 3.0.0 → 3.0.1

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.
Files changed (44) hide show
  1. package/README.md +5 -1
  2. package/dist/components/composite/toast.d.ts +2 -1
  3. package/dist/components/composite/toast.js +3 -9
  4. package/dist/components/composite/toast.js.map +1 -1
  5. package/dist/components/ui/action-bar.js +45 -1
  6. package/dist/components/ui/action-bar.js.map +1 -1
  7. package/dist/components/ui/circular-progress.js +10 -2
  8. package/dist/components/ui/circular-progress.js.map +1 -1
  9. package/dist/components/ui/kanban.js +9 -3
  10. package/dist/components/ui/kanban.js.map +1 -1
  11. package/dist/components/ui/key-value.js +70 -76
  12. package/dist/components/ui/key-value.js.map +1 -1
  13. package/dist/components/ui/mask-input-constants.js +31 -71
  14. package/dist/components/ui/mask-input-constants.js.map +1 -1
  15. package/dist/components/ui/mask-input.js +18 -20
  16. package/dist/components/ui/mask-input.js.map +1 -1
  17. package/dist/components/ui/phone-input.js +5 -2
  18. package/dist/components/ui/phone-input.js.map +1 -1
  19. package/dist/components/ui/rating.js +4 -2
  20. package/dist/components/ui/rating.js.map +1 -1
  21. package/dist/components/ui/slider.d.ts +1 -1
  22. package/dist/components/ui/slider.js +15 -1
  23. package/dist/components/ui/slider.js.map +1 -1
  24. package/dist/components/ui/sonner.js +2 -0
  25. package/dist/components/ui/sonner.js.map +1 -1
  26. package/dist/components/ui/toggle-group.js +12 -1
  27. package/dist/components/ui/toggle-group.js.map +1 -1
  28. package/dist/components/ui/tour.js +3 -2
  29. package/dist/components/ui/tour.js.map +1 -1
  30. package/dist/data-grid/use-data-grid.d.ts +2 -2
  31. package/dist/i18n/locale-provider.d.ts +18 -0
  32. package/dist/i18n/locale-provider.js.map +1 -1
  33. package/dist/i18n/namespaces/rating.js +2 -1
  34. package/dist/i18n/namespaces/rating.js.map +1 -1
  35. package/dist/i18n/namespaces/shell.d.ts +4 -0
  36. package/dist/i18n/namespaces/shell.js +1 -1
  37. package/dist/i18n/namespaces/shell.js.map +1 -1
  38. package/dist/i18n/namespaces/toast.d.ts +3 -2
  39. package/dist/i18n/namespaces/toast.js.map +1 -1
  40. package/dist/lib/toast-language.d.ts +8 -0
  41. package/dist/lib/toast-language.js +19 -0
  42. package/dist/lib/toast-language.js.map +1 -0
  43. package/dist/shell/display-axis-provider.d.ts +1 -1
  44. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/ui/key-value.tsx"],"sourcesContent":["\"use client\";\n\n/*\n * Port từ Dice UI (https://diceui.com/docs/components/key-value) — 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\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { Plus, X } from \"lucide-react\";\nimport * as React from \"react\";\nimport { useComitorLabels } from \"../../i18n/locale-provider.js\";\nimport { cn } from \"../../lib/cn.js\";\nimport { useComposedRefs } from \"../../lib/compose-refs.js\";\nimport { useAsRef } from \"../../lib/use-as-ref.js\";\nimport { useIsomorphicLayoutEffect } from \"../../lib/use-isomorphic-layout-effect.js\";\nimport { useLazyRef } from \"../../lib/use-lazy-ref.js\";\nimport { VisuallyHiddenInput } from \"../../lib/visually-hidden-input.js\";\nimport { Button } from \"./button.js\";\nimport { Input } from \"./input.js\";\nimport type { KeyValueLabels } from \"./key-value-constants.js\";\nimport { Textarea } from \"./textarea.js\";\n\nconst ROOT_NAME = \"KeyValue\";\nconst LIST_NAME = \"KeyValueList\";\nconst ITEM_NAME = \"KeyValueItem\";\nconst KEY_INPUT_NAME = \"KeyValueKeyInput\";\nconst VALUE_INPUT_NAME = \"KeyValueValueInput\";\nconst REMOVE_NAME = \"KeyValueRemove\";\nconst ADD_NAME = \"KeyValueAdd\";\nconst ERROR_NAME = \"KeyValueError\";\n\ntype Orientation = \"vertical\" | \"horizontal\";\ntype Field = \"key\" | \"value\";\n\ninterface DivProps extends React.ComponentProps<\"div\"> {\n asChild?: boolean;\n}\n\ntype RootElement = React.ComponentRef<typeof KeyValue>;\ntype KeyInputElement = React.ComponentRef<typeof KeyValueKeyInput>;\ntype RemoveElement = React.ComponentRef<typeof KeyValueRemove>;\ntype AddElement = React.ComponentRef<typeof KeyValueAdd>;\n\nfunction getErrorId(rootId: string, itemId: string, field: Field) {\n return `${rootId}-${itemId}-${field}-error`;\n}\n\n/*\n * ⚠ Khác thượng nguồn: bản gốc gọi thẳng `crypto.randomUUID()`. Hàm đó chỉ tồn tại trong NGỮ CẢNH\n * AN TOÀN (HTTPS hoặc localhost) — mở trang quản trị qua `http://` bằng IP nội bộ là `TypeError`\n * ngay lúc render dòng đầu tiên. Id ở đây chỉ làm key của React và nối vào id của thông báo lỗi,\n * không cần ngẫu nhiên mật mã, nên có đường lui.\n */\nlet fallbackIdCounter = 0;\n\nfunction createItemId(): string {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n return crypto.randomUUID();\n }\n fallbackIdCounter += 1;\n return `kv-${Date.now().toString(36)}-${fallbackIdCounter}`;\n}\n\nfunction removeQuotes(string: string, shouldStrip: boolean): string {\n if (!shouldStrip) return string;\n\n const trimmed = string.trim();\n if ((trimmed.startsWith('\"') && trimmed.endsWith('\"')) || (trimmed.startsWith(\"'\") && trimmed.endsWith(\"'\"))) {\n return trimmed.slice(1, -1);\n }\n return trimmed;\n}\n\ninterface Store {\n subscribe: (callback: () => void) => () => void;\n getState: () => KeyValueState;\n setState: <K extends keyof KeyValueState>(key: K, value: KeyValueState[K]) => void;\n notify: () => void;\n}\n\nfunction useStore<T>(selector: (state: KeyValueState) => T, ogStore?: Store | null): T {\n const contextStore = React.useContext(StoreContext);\n\n const store = ogStore ?? contextStore;\n\n if (!store) {\n throw new Error(`\\`useKeyValueStore\\` phải được gọi bên trong \\`${ROOT_NAME}\\``);\n }\n\n const getSnapshot = React.useCallback(() => selector(store.getState()), [store, selector]);\n\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\ninterface ItemData {\n id: string;\n key: string;\n value: string;\n}\n\ninterface KeyValueState {\n value: ItemData[];\n focusedId: string | null;\n errors: Record<string, { key?: string; value?: string }>;\n}\n\nconst StoreContext = React.createContext<Store | null>(null);\n\nfunction useStoreContext(consumerName: string) {\n const context = React.useContext(StoreContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\ninterface KeyValueContextValue {\n onPaste?: (event: ClipboardEvent, items: ItemData[]) => void;\n onAdd?: (value: ItemData) => void;\n onRemove?: (value: ItemData) => void;\n onKeyValidate?: (key: string, value: ItemData[]) => string | undefined;\n onValueValidate?: (value: string, key: string, items: ItemData[]) => string | undefined;\n rootId: string;\n maxItems?: number;\n minItems: number;\n keyPlaceholder: string;\n valuePlaceholder: string;\n labels: KeyValueLabels;\n allowDuplicateKeys: boolean;\n enablePaste: boolean;\n trim: boolean;\n stripQuotes: boolean;\n disabled: boolean;\n readOnly: boolean;\n required: boolean;\n}\n\nconst KeyValueContext = React.createContext<KeyValueContextValue | null>(null);\n\nfunction useKeyValueContext(consumerName: string) {\n const context = React.useContext(KeyValueContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\ninterface KeyValueProps extends Omit<DivProps, \"onPaste\" | \"defaultValue\"> {\n id?: string;\n defaultValue?: ItemData[];\n value?: ItemData[];\n onValueChange?: (value: ItemData[]) => void;\n maxItems?: number;\n minItems?: number;\n /** Placeholder của ô khoá — thắng `labels.keyPlaceholder` (mặc định \"Key\"). */\n keyPlaceholder?: string;\n /** Placeholder của ô giá trị — thắng `labels.valuePlaceholder` (mặc định \"Value\"). */\n valuePlaceholder?: string;\n /**\n * Chuỗi hiển thị và chuỗi cho trình đọc màn hình — truyền MỘT PHẦN là đủ, phần còn lại theo\n * namespace `keyValue` của `ComitorLocaleProvider` (mặc định tiếng Anh). ⚠ Thêm so với thượng\n * nguồn (bản gốc chôn cứng \"Add\" / \"Duplicate key\" và để nút xoá không có tên).\n */\n labels?: Partial<KeyValueLabels>;\n name?: string;\n allowDuplicateKeys?: boolean;\n enablePaste?: boolean;\n trim?: boolean;\n stripQuotes?: boolean;\n disabled?: boolean;\n readOnly?: boolean;\n required?: boolean;\n onPaste?: (event: ClipboardEvent, items: ItemData[]) => void;\n onAdd?: (value: ItemData) => void;\n onRemove?: (value: ItemData) => void;\n onKeyValidate?: (key: string, value: ItemData[]) => string | undefined;\n onValueValidate?: (value: string, key: string, items: ItemData[]) => string | undefined;\n}\n\n/**\n * KeyValue — danh sách cặp khoá/giá trị sửa được: trường tuỳ biến của bản ghi CRM, header /\n * metadata của webhook ở trang quản trị. Dán nhiều dòng `KEY=value` (hoặc `KEY: value`, hoặc\n * cách bằng tab) vào ô khoá là tách thành nhiều cặp.\n */\nfunction KeyValue(props: KeyValueProps) {\n const {\n value: valueProp,\n defaultValue,\n onValueChange,\n onPaste,\n onAdd,\n onRemove,\n onKeyValidate,\n onValueValidate,\n maxItems,\n minItems = 0,\n labels: labelsProp,\n keyPlaceholder: keyPlaceholderProp,\n valuePlaceholder: valuePlaceholderProp,\n allowDuplicateKeys = false,\n asChild,\n enablePaste = true,\n trim = true,\n stripQuotes = true,\n disabled = false,\n readOnly = false,\n required = false,\n className,\n id,\n name,\n ref,\n ...rootProps\n } = props;\n\n const instanceId = React.useId();\n const rootId = id ?? instanceId;\n\n const labels = useComitorLabels(\"keyValue\", labelsProp);\n const keyPlaceholder = keyPlaceholderProp ?? labels.keyPlaceholder;\n const valuePlaceholder = valuePlaceholderProp ?? labels.valuePlaceholder;\n\n const [formTrigger, setFormTrigger] = React.useState<RootElement | null>(null);\n const composedRef = useComposedRefs(ref, (node) => setFormTrigger(node));\n const isFormControl = formTrigger ? !!formTrigger.closest(\"form\") : true;\n\n const listenersRef = useLazyRef(() => new Set<() => void>());\n const stateRef = useLazyRef<KeyValueState>(() => ({\n value: valueProp ?? defaultValue ?? [{ id: createItemId(), key: \"\", value: \"\" }],\n focusedId: null,\n errors: {}\n }));\n const propsRef = useAsRef({ onValueChange });\n\n const store = React.useMemo<Store>(() => {\n return {\n subscribe: (cb) => {\n listenersRef.current.add(cb);\n return () => listenersRef.current.delete(cb);\n },\n getState: () => stateRef.current,\n setState: (key, val) => {\n if (Object.is(stateRef.current[key], val)) return;\n\n if (key === \"value\" && Array.isArray(val)) {\n stateRef.current.value = val as ItemData[];\n propsRef.current.onValueChange?.(val as ItemData[]);\n } else {\n stateRef.current[key] = val;\n }\n\n store.notify();\n },\n notify: () => {\n for (const cb of listenersRef.current) {\n cb();\n }\n }\n };\n }, [listenersRef, stateRef, propsRef]);\n\n const value = useStore((state) => state.value, store);\n const errors = useStore((state) => state.errors, store);\n const isInvalid = Object.keys(errors).length > 0;\n\n useIsomorphicLayoutEffect(() => {\n if (valueProp !== undefined) {\n store.setState(\"value\", valueProp);\n }\n }, [valueProp]);\n\n const contextValue = React.useMemo<KeyValueContextValue>(\n () => ({\n onPaste,\n onAdd,\n onRemove,\n onKeyValidate,\n onValueValidate,\n rootId,\n maxItems,\n minItems,\n keyPlaceholder,\n valuePlaceholder,\n labels,\n allowDuplicateKeys,\n enablePaste,\n trim,\n stripQuotes,\n disabled,\n readOnly,\n required\n }),\n [\n onPaste,\n onAdd,\n onRemove,\n onKeyValidate,\n onValueValidate,\n rootId,\n disabled,\n readOnly,\n required,\n maxItems,\n minItems,\n keyPlaceholder,\n valuePlaceholder,\n labels,\n allowDuplicateKeys,\n enablePaste,\n trim,\n stripQuotes\n ]\n );\n\n const RootPrimitive = asChild ? Slot : \"div\";\n\n return (\n <StoreContext.Provider value={store}>\n <KeyValueContext.Provider value={contextValue}>\n <RootPrimitive\n id={id}\n data-slot=\"key-value\"\n data-disabled={disabled ? \"\" : undefined}\n data-invalid={isInvalid ? \"\" : undefined}\n data-readonly={readOnly ? \"\" : undefined}\n {...rootProps}\n ref={composedRef}\n className={cn(\"flex flex-col gap-2\", className)}\n />\n {isFormControl && (\n <VisuallyHiddenInput\n type=\"hidden\"\n control={formTrigger}\n name={name}\n value={value}\n disabled={disabled}\n readOnly={readOnly}\n required={required}\n />\n )}\n </KeyValueContext.Provider>\n </StoreContext.Provider>\n );\n}\n\ninterface KeyValueListProps extends DivProps {\n orientation?: Orientation;\n}\n\nfunction KeyValueList(props: KeyValueListProps) {\n const { orientation = \"vertical\", asChild, className, ...listProps } = props;\n\n const value = useStore((state) => state.value);\n\n const ListPrimitive = asChild ? Slot : \"div\";\n\n return (\n <ListPrimitive\n role=\"list\"\n /*\n * ⚠ Khác thượng nguồn (a11y): bỏ `aria-orientation`. Thuộc tính đó KHÔNG được phép trên\n * `role=\"list\"` (ARIA 1.2 chỉ cho nó ở listbox, menu, tablist, toolbar, radiogroup, slider…)\n * — axe báo `aria-allowed-attr`. Hướng vẫn còn ở `data-orientation` cho CSS.\n */\n data-slot=\"key-value-list\"\n data-orientation={orientation}\n {...listProps}\n className={cn(\"flex\", orientation === \"vertical\" ? \"flex-col gap-2\" : \"flex-row gap-2\", className)}\n >\n {value.map((item) => {\n const children = React.Children.toArray(props.children);\n\n return (\n <KeyValueItemContext.Provider key={item.id} value={item}>\n {children}\n </KeyValueItemContext.Provider>\n );\n })}\n </ListPrimitive>\n );\n}\n\nconst KeyValueItemContext = React.createContext<ItemData | null>(null);\n\nfunction useKeyValueItemContext(consumerName: string) {\n const context = React.useContext(KeyValueItemContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${LIST_NAME}\\``);\n }\n return context;\n}\n\ninterface KeyValueItemProps extends React.ComponentProps<\"div\"> {\n asChild?: boolean;\n}\n\nfunction KeyValueItem(props: KeyValueItemProps) {\n const { asChild, className, ...itemProps } = props;\n const itemData = useKeyValueItemContext(ITEM_NAME);\n\n const focusedId = useStore((state) => state.focusedId);\n\n const ItemPrimitive = asChild ? Slot : \"div\";\n\n return (\n <ItemPrimitive\n role=\"listitem\"\n data-slot=\"key-value-item\"\n data-highlighted={focusedId === itemData.id ? \"\" : undefined}\n {...itemProps}\n className={cn(\"flex items-start gap-2\", className)}\n />\n );\n}\n\ninterface KeyValueKeyInputProps extends React.ComponentProps<\"input\"> {\n asChild?: boolean;\n}\n\nfunction KeyValueKeyInput(props: KeyValueKeyInputProps) {\n const {\n onChange: onChangeProp,\n onPaste: onPasteProp,\n asChild,\n disabled,\n readOnly,\n required,\n ref,\n ...keyInputProps\n } = props;\n\n const context = useKeyValueContext(KEY_INPUT_NAME);\n const itemData = useKeyValueItemContext(KEY_INPUT_NAME);\n const store = useStoreContext(KEY_INPUT_NAME);\n const errors = useStore((state) => state.errors);\n const focusedId = useStore((state) => state.focusedId);\n\n const inputRef = React.useRef<KeyInputElement>(null);\n const composedRef = useComposedRefs(ref, inputRef);\n\n /*\n * ⚠ Khác thượng nguồn (a11y): `KeyValueAdd` ghi id dòng mới vào `focusedId` nhưng bản gốc không\n * bao giờ dùng nó để đưa focus — bấm \"Thêm\" xong focus vẫn đứng ở nút, người dùng bàn phím phải\n * tự Tab ngược lên dòng vừa thêm. Ở đây ô KHOÁ của dòng mới tự nhận focus. Chỉ chạy khi\n * `focusedId` ĐỔI sang dòng này (mà chỉ `KeyValueAdd` ghi nó), nên dòng có sẵn lúc mount và dòng\n * sinh ra do dán không bị cướp focus.\n */\n React.useEffect(() => {\n if (focusedId === itemData.id) {\n inputRef.current?.focus();\n }\n }, [focusedId, itemData.id]);\n\n const propsRef = useAsRef({\n onChange: onChangeProp,\n onPaste: onPasteProp\n });\n\n const isDisabled = disabled || context.disabled;\n const isReadOnly = readOnly || context.readOnly;\n const isRequired = required || context.required;\n const isInvalid = errors[itemData.id]?.key !== undefined;\n\n const onChange = React.useCallback(\n (event: React.ChangeEvent<KeyInputElement>) => {\n const state = store.getState();\n const newValue = state.value.map((item) => {\n if (item.id !== itemData.id) return item;\n const updated = { ...item, key: event.target.value };\n if (context.trim) updated.key = updated.key.trim();\n return updated;\n });\n\n store.setState(\"value\", newValue);\n\n const updatedItemData = newValue.find((item) => item.id === itemData.id);\n if (updatedItemData) {\n const errors: { key?: string; value?: string } = {};\n\n if (context.onKeyValidate) {\n const keyError = context.onKeyValidate(updatedItemData.key, newValue);\n if (keyError) errors.key = keyError;\n }\n\n if (!context.allowDuplicateKeys) {\n const duplicateKey = newValue.find(\n (item) => item.id !== updatedItemData.id && item.key === updatedItemData.key && updatedItemData.key !== \"\"\n );\n if (duplicateKey) {\n errors.key = context.labels.duplicateKey;\n }\n }\n\n if (context.onValueValidate) {\n const valueError = context.onValueValidate(updatedItemData.value, updatedItemData.key, newValue);\n if (valueError) errors.value = valueError;\n }\n\n const newErrorsState = { ...state.errors };\n if (Object.keys(errors).length > 0) {\n newErrorsState[itemData.id] = errors;\n } else {\n delete newErrorsState[itemData.id];\n }\n store.setState(\"errors\", newErrorsState);\n }\n\n propsRef.current.onChange?.(event);\n },\n [store, itemData.id, context, propsRef]\n );\n\n const onPaste = React.useCallback(\n (event: React.ClipboardEvent<KeyInputElement>) => {\n if (!context.enablePaste) return;\n\n propsRef.current.onPaste?.(event);\n if (event.defaultPrevented) return;\n\n const content = event.clipboardData.getData(\"text\");\n const lines = content.split(/\\r?\\n/).filter((line) => line.trim());\n\n if (lines.length > 1) {\n event.preventDefault();\n\n const parsed: ItemData[] = [];\n\n for (const line of lines) {\n let key = \"\";\n let value = \"\";\n\n if (line.includes(\"=\")) {\n const parts = line.split(\"=\");\n key = parts[0]?.trim() ?? \"\";\n value = removeQuotes(parts.slice(1).join(\"=\").trim(), context.stripQuotes);\n } else if (line.includes(\":\")) {\n const parts = line.split(\":\");\n key = parts[0]?.trim() ?? \"\";\n value = removeQuotes(parts.slice(1).join(\":\").trim(), context.stripQuotes);\n } else if (/\\s{2,}|\\t/.test(line)) {\n const parts = line.split(/\\s{2,}|\\t/);\n key = parts[0]?.trim() ?? \"\";\n value = removeQuotes(parts.slice(1).join(\" \").trim(), context.stripQuotes);\n }\n\n if (key) {\n parsed.push({ id: createItemId(), key, value });\n }\n }\n\n if (parsed.length > 0) {\n const state = store.getState();\n const currentIndex = state.value.findIndex((item) => item.id === itemData.id);\n\n let newValue: ItemData[];\n if (itemData.key === \"\" && itemData.value === \"\") {\n newValue = [...state.value.slice(0, currentIndex), ...parsed, ...state.value.slice(currentIndex + 1)];\n } else {\n newValue = [...state.value.slice(0, currentIndex + 1), ...parsed, ...state.value.slice(currentIndex + 1)];\n }\n\n if (context.maxItems !== undefined) {\n newValue = newValue.slice(0, context.maxItems);\n }\n\n store.setState(\"value\", newValue);\n\n if (context.onPaste) {\n context.onPaste(event.nativeEvent as unknown as ClipboardEvent, parsed);\n }\n }\n }\n },\n [context, store, itemData, propsRef]\n );\n\n const KeyInputPrimitive = asChild ? Slot : Input;\n\n return (\n <KeyInputPrimitive\n aria-invalid={isInvalid}\n aria-describedby={isInvalid ? getErrorId(context.rootId, itemData.id, \"key\") : undefined}\n data-slot=\"key-value-key-input\"\n autoCapitalize=\"off\"\n autoComplete=\"off\"\n autoCorrect=\"off\"\n spellCheck=\"false\"\n disabled={isDisabled}\n readOnly={isReadOnly}\n required={isRequired}\n placeholder={context.keyPlaceholder}\n {...keyInputProps}\n ref={composedRef}\n value={itemData.key}\n onChange={onChange}\n onPaste={onPaste}\n />\n );\n}\n\ninterface KeyValueValueInputProps extends Omit<React.ComponentProps<\"textarea\">, \"rows\"> {\n maxRows?: number;\n asChild?: boolean;\n}\n\nfunction KeyValueValueInput(props: KeyValueValueInputProps) {\n const {\n onChange: onChangeProp,\n asChild,\n disabled,\n readOnly,\n required,\n className,\n maxRows,\n style,\n ...valueInputProps\n } = props;\n\n const context = useKeyValueContext(VALUE_INPUT_NAME);\n const itemData = useKeyValueItemContext(VALUE_INPUT_NAME);\n const store = useStoreContext(VALUE_INPUT_NAME);\n const errors = useStore((state) => state.errors);\n\n const propsRef = useAsRef({\n onChange: onChangeProp\n });\n\n const isDisabled = disabled || context.disabled;\n const isReadOnly = readOnly || context.readOnly;\n const isRequired = required || context.required;\n const isInvalid = errors[itemData.id]?.value !== undefined;\n const maxHeight = maxRows ? `calc(${maxRows} * 1.5em + 1rem)` : undefined;\n\n const onChange = React.useCallback(\n (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n propsRef.current.onChange?.(event);\n\n const state = store.getState();\n const newValue = state.value.map((item) => {\n if (item.id !== itemData.id) return item;\n const updated = { ...item, value: event.target.value };\n if (context.trim) updated.value = updated.value.trim();\n return updated;\n });\n\n store.setState(\"value\", newValue);\n\n const updatedItemData = newValue.find((item) => item.id === itemData.id);\n if (updatedItemData) {\n const errors: { key?: string; value?: string } = {};\n\n if (context.onKeyValidate) {\n const keyError = context.onKeyValidate(updatedItemData.key, newValue);\n if (keyError) errors.key = keyError;\n }\n\n if (!context.allowDuplicateKeys) {\n const duplicateKey = newValue.find(\n (item) => item.id !== updatedItemData.id && item.key === updatedItemData.key && updatedItemData.key !== \"\"\n );\n if (duplicateKey) {\n errors.key = context.labels.duplicateKey;\n }\n }\n\n if (context.onValueValidate) {\n const valueError = context.onValueValidate(updatedItemData.value, updatedItemData.key, newValue);\n if (valueError) errors.value = valueError;\n }\n\n const newErrorsState = { ...state.errors };\n if (Object.keys(errors).length > 0) {\n newErrorsState[itemData.id] = errors;\n } else {\n delete newErrorsState[itemData.id];\n }\n store.setState(\"errors\", newErrorsState);\n }\n },\n [store, itemData.id, context, propsRef]\n );\n\n const ValueInputPrimitive = asChild ? Slot : Textarea;\n\n return (\n <ValueInputPrimitive\n aria-invalid={isInvalid}\n aria-describedby={isInvalid ? getErrorId(context.rootId, itemData.id, \"value\") : undefined}\n data-slot=\"key-value-value-input\"\n autoCapitalize=\"off\"\n autoComplete=\"off\"\n autoCorrect=\"off\"\n spellCheck=\"false\"\n disabled={isDisabled}\n readOnly={isReadOnly}\n required={isRequired}\n placeholder={context.valuePlaceholder}\n {...valueInputProps}\n className={cn(\n /*\n * ⚠ Khác thượng nguồn: ô giá trị phải trông như ô khoá (`Input`) đứng ngay cạnh khi còn MỘT\n * dòng. `Textarea` của gói theo comitor-ds: `text-base md:text-sm` + `py-2` (cao 38px ở\n * desktop, 42px và chữ 16px ở mobile), nền `bg-transparent dark:bg-input-fill`, có\n * `shadow-xs`; còn `Input` là `h-9 text-sm bg-background`, không bóng. Ghi đè ở ĐÂY chứ\n * không sửa hai primitive: `text-sm` ở mọi cỡ màn, `min-h-9` + `py-1.5` (dòng chữ 20px cách\n * mép trên 7px — đúng chỗ `Input` căn giữa dòng chữ trong 36px), `bg-background` ở cả hai\n * theme, bỏ bóng. Nhiều dòng thì `field-sizing-content` vẫn cho ô cao dần như cũ.\n */\n \"field-sizing-content min-h-9 resize-none py-1.5 text-sm md:text-sm bg-background dark:bg-background shadow-none\",\n maxRows && \"overflow-y-auto\",\n className\n )}\n style={{\n ...style,\n ...(maxHeight && { maxHeight })\n }}\n value={itemData.value}\n onChange={onChange}\n />\n );\n}\n\ninterface KeyValueRemoveProps extends React.ComponentProps<typeof Button> {}\n\nfunction KeyValueRemove(props: KeyValueRemoveProps) {\n const { onClick: onClickProp, children, className, ...removeProps } = props;\n\n const context = useKeyValueContext(REMOVE_NAME);\n const itemData = useKeyValueItemContext(REMOVE_NAME);\n const store = useStoreContext(REMOVE_NAME);\n\n const propsRef = useAsRef({\n onClick: onClickProp\n });\n const value = useStore((state) => state.value);\n const isDisabled = context.disabled || value.length <= context.minItems;\n const itemIndex = value.findIndex((item) => item.id === itemData.id);\n\n const onClick = React.useCallback(\n (event: React.MouseEvent<RemoveElement>) => {\n propsRef.current.onClick?.(event);\n\n const state = store.getState();\n if (state.value.length <= context.minItems) return;\n\n const itemToRemove = state.value.find((item) => item.id === itemData.id);\n if (!itemToRemove) return;\n\n /*\n * ⚠ Khác thượng nguồn (a11y): nút đang giữ focus biến mất cùng dòng của nó, và focus rơi về\n * `<body>` — người dùng bàn phím phải Tab lại từ đầu trang (WCAG 2.4.3). Nếu nút có focus lúc\n * bấm thì sau khi xoá, đưa focus sang nút xoá của dòng kế (hoặc dòng trước nếu vừa xoá dòng\n * cuối), không còn nút nào dùng được thì sang nút thêm.\n */\n const button = event.currentTarget;\n const hadFocus = button.ownerDocument.activeElement === button;\n const root = button.closest<HTMLElement>('[data-slot=\"key-value\"]');\n const removedIndex = state.value.indexOf(itemToRemove);\n\n const newValue = state.value.filter((item) => item.id !== itemData.id);\n const newErrors = { ...state.errors };\n delete newErrors[itemData.id];\n\n store.setState(\"value\", newValue);\n store.setState(\"errors\", newErrors);\n\n context.onRemove?.(itemToRemove);\n\n if (hadFocus && root) {\n requestAnimationFrame(() => {\n const removeButtons = root.querySelectorAll<HTMLElement>('[data-slot=\"key-value-remove\"]:not(:disabled)');\n const nextTarget =\n removeButtons[Math.min(removedIndex, removeButtons.length - 1)] ??\n root.querySelector<HTMLElement>('[data-slot=\"key-value-add\"]:not(:disabled)');\n nextTarget?.focus();\n });\n }\n },\n [store, context, itemData.id, propsRef]\n );\n\n return (\n <Button\n type=\"button\"\n data-slot=\"key-value-remove\"\n variant=\"outline\"\n size=\"icon\"\n /*\n * ⚠ Khác thượng nguồn (a11y): nút chỉ có icon ✕ và bản gốc không đặt tên nào — trình đọc màn\n * hình đọc \"nút\" trơn, lặp lại ở mọi dòng. Tên mặc định nói RÕ dòng nào: \"Remove API_KEY\",\n * hoặc \"Remove row 3\" khi khoá còn trống. Đổi qua `labels.remove` của `KeyValue`, hoặc `aria-label`.\n */\n aria-label={context.labels.remove(itemData.key, itemIndex + 1)}\n disabled={isDisabled}\n {...removeProps}\n // ⚠ Khác thượng nguồn: `shrink-0` — nút là mục flex cạnh hai ô `w-full`; thiếu nó thì flex\n // bóp nút còn ~18×36px. `size=\"icon\"` của `Button` đã là ô vuông 36×36 (`h-9 w-9`).\n className={cn(\"shrink-0\", className)}\n onClick={onClick}\n >\n {/* `size-4`: `Button` của gói không tự đặt cỡ cho svg con như shadcn — thiếu là icon 24px. */}\n {children ?? <X aria-hidden=\"true\" className=\"size-4\" />}\n </Button>\n );\n}\n\ntype KeyValueAddProps = React.ComponentProps<typeof Button>;\n\nfunction KeyValueAdd(props: KeyValueAddProps) {\n const { onClick: onClickProp, children, ...addProps } = props;\n\n const context = useKeyValueContext(ADD_NAME);\n const store = useStoreContext(ADD_NAME);\n\n const propsRef = useAsRef({\n onClick: onClickProp\n });\n const value = useStore((state) => state.value);\n const isDisabled = context.disabled || (context.maxItems !== undefined && value.length >= context.maxItems);\n\n const onClick = React.useCallback(\n (event: React.MouseEvent<AddElement>) => {\n propsRef.current.onClick?.(event);\n\n const state = store.getState();\n if (context.maxItems !== undefined && state.value.length >= context.maxItems) {\n return;\n }\n\n const newItem: ItemData = {\n id: createItemId(),\n key: \"\",\n value: \"\"\n };\n\n const newValue = [...state.value, newItem];\n store.setState(\"value\", newValue);\n store.setState(\"focusedId\", newItem.id);\n\n context.onAdd?.(newItem);\n },\n [store, context, propsRef]\n );\n\n return (\n <Button\n type=\"button\"\n data-slot=\"key-value-add\"\n variant=\"outline\"\n disabled={isDisabled}\n {...addProps}\n onClick={onClick}\n >\n {children ?? (\n <>\n <Plus aria-hidden=\"true\" className=\"size-4\" />\n {context.labels.add}\n </>\n )}\n </Button>\n );\n}\n\ninterface KeyValueErrorProps extends DivProps {\n field: Field;\n}\n\nfunction KeyValueError(props: KeyValueErrorProps) {\n const { field, asChild, className, ...errorProps } = props;\n\n const context = useKeyValueContext(ERROR_NAME);\n const itemData = useKeyValueItemContext(ERROR_NAME);\n\n const errors = useStore((state) => state.errors);\n const error = errors[itemData.id]?.[field];\n\n if (!error) return null;\n\n const ErrorPrimitive = asChild ? Slot : \"span\";\n\n return (\n <ErrorPrimitive\n id={getErrorId(context.rootId, itemData.id, field)}\n role=\"alert\"\n {...errorProps}\n className={cn(\n // ⚠ Khác thượng nguồn: `text-destructive` (bậc TÔ NỀN) → `text-destructive-ink`, bậc dành\n // cho chữ trên nền trang — cùng token với chữ lỗi của `Input`.\n \"text-sm font-medium text-destructive-ink\",\n className\n )}\n >\n {error}\n </ErrorPrimitive>\n );\n}\n\nexport {\n type ItemData as KeyValueItemData,\n KeyValue,\n KeyValueAdd,\n type KeyValueAddProps,\n KeyValueError,\n type KeyValueErrorProps,\n KeyValueItem,\n type KeyValueItemProps,\n KeyValueKeyInput,\n type KeyValueKeyInputProps,\n KeyValueList,\n type KeyValueListProps,\n type KeyValueProps,\n KeyValueRemove,\n type KeyValueRemoveProps,\n KeyValueValueInput,\n type KeyValueValueInputProps,\n useStore as useKeyValueStore\n};\n"],"mappings":"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","names":["errors"]}
1
+ {"version":3,"sources":["../../../src/components/ui/key-value.tsx"],"sourcesContent":["\"use client\";\n\n/*\n * Port từ Dice UI (https://diceui.com/docs/components/key-value) — 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\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { Plus, X } from \"lucide-react\";\nimport * as React from \"react\";\nimport { useComitorLabels } from \"../../i18n/locale-provider.js\";\nimport { cn } from \"../../lib/cn.js\";\nimport { useComposedRefs } from \"../../lib/compose-refs.js\";\nimport { useAsRef } from \"../../lib/use-as-ref.js\";\nimport { useIsomorphicLayoutEffect } from \"../../lib/use-isomorphic-layout-effect.js\";\nimport { useLazyRef } from \"../../lib/use-lazy-ref.js\";\nimport { VisuallyHiddenInput } from \"../../lib/visually-hidden-input.js\";\nimport { Button } from \"./button.js\";\nimport { Input } from \"./input.js\";\nimport type { KeyValueLabels } from \"./key-value-constants.js\";\nimport { Textarea } from \"./textarea.js\";\n\nconst ROOT_NAME = \"KeyValue\";\nconst LIST_NAME = \"KeyValueList\";\nconst ITEM_NAME = \"KeyValueItem\";\nconst KEY_INPUT_NAME = \"KeyValueKeyInput\";\nconst VALUE_INPUT_NAME = \"KeyValueValueInput\";\nconst REMOVE_NAME = \"KeyValueRemove\";\nconst ADD_NAME = \"KeyValueAdd\";\nconst ERROR_NAME = \"KeyValueError\";\n\ntype Orientation = \"vertical\" | \"horizontal\";\ntype Field = \"key\" | \"value\";\n\ninterface DivProps extends React.ComponentProps<\"div\"> {\n asChild?: boolean;\n}\n\ntype RootElement = React.ComponentRef<typeof KeyValue>;\ntype KeyInputElement = React.ComponentRef<typeof KeyValueKeyInput>;\ntype RemoveElement = React.ComponentRef<typeof KeyValueRemove>;\ntype AddElement = React.ComponentRef<typeof KeyValueAdd>;\n\nfunction getErrorId(rootId: string, itemId: string, field: Field) {\n return `${rootId}-${itemId}-${field}-error`;\n}\n\n/*\n * ⚠ Khác thượng nguồn: bản gốc gọi thẳng `crypto.randomUUID()`. Hàm đó chỉ tồn tại trong NGỮ CẢNH\n * AN TOÀN (HTTPS hoặc localhost) — mở trang quản trị qua `http://` bằng IP nội bộ là `TypeError`\n * ngay lúc render dòng đầu tiên. Id ở đây chỉ làm key của React và nối vào id của thông báo lỗi,\n * không cần ngẫu nhiên mật mã, nên có đường lui.\n */\nlet fallbackIdCounter = 0;\n\nfunction createItemId(): string {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n return crypto.randomUUID();\n }\n fallbackIdCounter += 1;\n return `kv-${Date.now().toString(36)}-${fallbackIdCounter}`;\n}\n\nfunction removeQuotes(string: string, shouldStrip: boolean): string {\n if (!shouldStrip) return string;\n\n const trimmed = string.trim();\n if ((trimmed.startsWith('\"') && trimmed.endsWith('\"')) || (trimmed.startsWith(\"'\") && trimmed.endsWith(\"'\"))) {\n return trimmed.slice(1, -1);\n }\n return trimmed;\n}\n\ninterface Store {\n subscribe: (callback: () => void) => () => void;\n getState: () => KeyValueState;\n setState: <K extends keyof KeyValueState>(key: K, value: KeyValueState[K]) => void;\n notify: () => void;\n}\n\nfunction useStore<T>(selector: (state: KeyValueState) => T, ogStore?: Store | null): T {\n const contextStore = React.useContext(StoreContext);\n\n const store = ogStore ?? contextStore;\n\n if (!store) {\n throw new Error(`\\`useKeyValueStore\\` phải được gọi bên trong \\`${ROOT_NAME}\\``);\n }\n\n const getSnapshot = React.useCallback(() => selector(store.getState()), [store, selector]);\n\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\ninterface ItemData {\n id: string;\n key: string;\n value: string;\n}\n\ninterface KeyValueState {\n value: ItemData[];\n focusedId: string | null;\n errors: Record<string, { key?: string; value?: string }>;\n}\n\nconst StoreContext = React.createContext<Store | null>(null);\n\nfunction useStoreContext(consumerName: string) {\n const context = React.useContext(StoreContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\ninterface KeyValueContextValue {\n onPaste?: (event: ClipboardEvent, items: ItemData[]) => void;\n onAdd?: (value: ItemData) => void;\n onRemove?: (value: ItemData) => void;\n onKeyValidate?: (key: string, value: ItemData[]) => string | undefined;\n onValueValidate?: (value: string, key: string, items: ItemData[]) => string | undefined;\n rootId: string;\n maxItems?: number;\n minItems: number;\n keyPlaceholder: string;\n valuePlaceholder: string;\n labels: KeyValueLabels;\n allowDuplicateKeys: boolean;\n enablePaste: boolean;\n trim: boolean;\n stripQuotes: boolean;\n disabled: boolean;\n readOnly: boolean;\n required: boolean;\n}\n\nconst KeyValueContext = React.createContext<KeyValueContextValue | null>(null);\n\nfunction useKeyValueContext(consumerName: string) {\n const context = React.useContext(KeyValueContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\n/**\n * Ghi MỘT ô (khoá hoặc giá trị) của một dòng vào store rồi kiểm lỗi lại dòng đó. Dùng chung cho\n * hai ô nhập — trước `3.0.1` khối này chép nguyên hai lần, mỗi ô một bản.\n */\nfunction commitItemField(store: Store, context: KeyValueContextValue, itemId: string, field: Field, text: string) {\n const state = store.getState();\n const newValue = state.value.map((item) => (item.id === itemId ? { ...item, [field]: text } : item));\n\n store.setState(\"value\", newValue);\n\n const updatedItemData = newValue.find((item) => item.id === itemId);\n if (!updatedItemData) return;\n\n const errors: { key?: string; value?: string } = {};\n\n if (context.onKeyValidate) {\n const keyError = context.onKeyValidate(updatedItemData.key, newValue);\n if (keyError) errors.key = keyError;\n }\n\n if (!context.allowDuplicateKeys) {\n const duplicateKey = newValue.find(\n (item) => item.id !== updatedItemData.id && item.key === updatedItemData.key && updatedItemData.key !== \"\"\n );\n if (duplicateKey) {\n errors.key = context.labels.duplicateKey;\n }\n }\n\n if (context.onValueValidate) {\n const valueError = context.onValueValidate(updatedItemData.value, updatedItemData.key, newValue);\n if (valueError) errors.value = valueError;\n }\n\n const newErrorsState = { ...state.errors };\n if (Object.keys(errors).length > 0) {\n newErrorsState[itemId] = errors;\n } else {\n delete newErrorsState[itemId];\n }\n store.setState(\"errors\", newErrorsState);\n}\n\n/*\n * ⚠ Khác thượng nguồn (3.0.1): với `trim`, Ô NHẬP hiện đúng chữ đang gõ, còn STORE nhận bản đã cắt.\n * Bản gốc cắt ngay trong `onChange` rồi đưa chính chuỗi đã cắt về lại ô, nên dấu cách vừa gõ ở cuối\n * biến mất trước khi chữ kế tiếp tới — với `trim` mặc định `true`, không gõ được \"Contract term\" hay\n * \"Bearer token\" theo cách bình thường (ra \"Contractterm\").\n * Mọi thứ app nhận (`onValueChange`, ô ẩn trong form, kiểm lỗi, trùng khoá) vẫn LUÔN là bản đã cắt,\n * đúng như bản gốc — kể cả khi Enter gửi form ngay sau một dấu cách. Chỉ phần hiển thị giữ khoảng\n * trắng, cho tới khi rời ô.\n */\nfunction useTrimDraft(committed: string) {\n const [draft, setDraft] = React.useState<string | null>(null);\n // Bản nháp chỉ được hiện khi nó còn khớp store: store đổi từ nơi khác (app đặt `value`, dán nhiều\n // dòng) thì hiện giá trị mới.\n const shown = draft !== null && draft.trim() === committed ? draft : committed;\n return [shown, setDraft] as const;\n}\n\ninterface KeyValueProps extends Omit<DivProps, \"onPaste\" | \"defaultValue\"> {\n id?: string;\n defaultValue?: ItemData[];\n value?: ItemData[];\n onValueChange?: (value: ItemData[]) => void;\n maxItems?: number;\n minItems?: number;\n /** Placeholder của ô khoá — thắng `labels.keyPlaceholder` (mặc định \"Key\"). */\n keyPlaceholder?: string;\n /** Placeholder của ô giá trị — thắng `labels.valuePlaceholder` (mặc định \"Value\"). */\n valuePlaceholder?: string;\n /**\n * Chuỗi hiển thị và chuỗi cho trình đọc màn hình — truyền MỘT PHẦN là đủ, phần còn lại theo\n * namespace `keyValue` của `ComitorLocaleProvider` (mặc định tiếng Anh). ⚠ Thêm so với thượng\n * nguồn (bản gốc chôn cứng \"Add\" / \"Duplicate key\" và để nút xoá không có tên).\n */\n labels?: Partial<KeyValueLabels>;\n name?: string;\n allowDuplicateKeys?: boolean;\n enablePaste?: boolean;\n trim?: boolean;\n stripQuotes?: boolean;\n disabled?: boolean;\n readOnly?: boolean;\n required?: boolean;\n onPaste?: (event: ClipboardEvent, items: ItemData[]) => void;\n onAdd?: (value: ItemData) => void;\n onRemove?: (value: ItemData) => void;\n onKeyValidate?: (key: string, value: ItemData[]) => string | undefined;\n onValueValidate?: (value: string, key: string, items: ItemData[]) => string | undefined;\n}\n\n/**\n * KeyValue — danh sách cặp khoá/giá trị sửa được: trường tuỳ biến của bản ghi CRM, header /\n * metadata của webhook ở trang quản trị. Dán nhiều dòng `KEY=value` (hoặc `KEY: value`, hoặc\n * cách bằng tab) vào ô khoá là tách thành nhiều cặp.\n */\nfunction KeyValue(props: KeyValueProps) {\n const {\n value: valueProp,\n defaultValue,\n onValueChange,\n onPaste,\n onAdd,\n onRemove,\n onKeyValidate,\n onValueValidate,\n maxItems,\n minItems = 0,\n labels: labelsProp,\n keyPlaceholder: keyPlaceholderProp,\n valuePlaceholder: valuePlaceholderProp,\n allowDuplicateKeys = false,\n asChild,\n enablePaste = true,\n trim = true,\n stripQuotes = true,\n disabled = false,\n readOnly = false,\n required = false,\n className,\n id,\n name,\n ref,\n ...rootProps\n } = props;\n\n const instanceId = React.useId();\n const rootId = id ?? instanceId;\n\n const labels = useComitorLabels(\"keyValue\", labelsProp);\n const keyPlaceholder = keyPlaceholderProp ?? labels.keyPlaceholder;\n const valuePlaceholder = valuePlaceholderProp ?? labels.valuePlaceholder;\n\n const [formTrigger, setFormTrigger] = React.useState<RootElement | null>(null);\n const composedRef = useComposedRefs(ref, (node) => setFormTrigger(node));\n const isFormControl = formTrigger ? !!formTrigger.closest(\"form\") : true;\n\n const listenersRef = useLazyRef(() => new Set<() => void>());\n const stateRef = useLazyRef<KeyValueState>(() => ({\n value: valueProp ?? defaultValue ?? [{ id: createItemId(), key: \"\", value: \"\" }],\n focusedId: null,\n errors: {}\n }));\n const propsRef = useAsRef({ onValueChange });\n\n const store = React.useMemo<Store>(() => {\n return {\n subscribe: (cb) => {\n listenersRef.current.add(cb);\n return () => listenersRef.current.delete(cb);\n },\n getState: () => stateRef.current,\n setState: (key, val) => {\n if (Object.is(stateRef.current[key], val)) return;\n\n if (key === \"value\" && Array.isArray(val)) {\n stateRef.current.value = val as ItemData[];\n propsRef.current.onValueChange?.(val as ItemData[]);\n } else {\n stateRef.current[key] = val;\n }\n\n store.notify();\n },\n notify: () => {\n for (const cb of listenersRef.current) {\n cb();\n }\n }\n };\n }, [listenersRef, stateRef, propsRef]);\n\n const value = useStore((state) => state.value, store);\n const errors = useStore((state) => state.errors, store);\n const isInvalid = Object.keys(errors).length > 0;\n\n useIsomorphicLayoutEffect(() => {\n if (valueProp !== undefined) {\n store.setState(\"value\", valueProp);\n }\n }, [valueProp]);\n\n const contextValue = React.useMemo<KeyValueContextValue>(\n () => ({\n onPaste,\n onAdd,\n onRemove,\n onKeyValidate,\n onValueValidate,\n rootId,\n maxItems,\n minItems,\n keyPlaceholder,\n valuePlaceholder,\n labels,\n allowDuplicateKeys,\n enablePaste,\n trim,\n stripQuotes,\n disabled,\n readOnly,\n required\n }),\n [\n onPaste,\n onAdd,\n onRemove,\n onKeyValidate,\n onValueValidate,\n rootId,\n disabled,\n readOnly,\n required,\n maxItems,\n minItems,\n keyPlaceholder,\n valuePlaceholder,\n labels,\n allowDuplicateKeys,\n enablePaste,\n trim,\n stripQuotes\n ]\n );\n\n const RootPrimitive = asChild ? Slot : \"div\";\n\n return (\n <StoreContext.Provider value={store}>\n <KeyValueContext.Provider value={contextValue}>\n <RootPrimitive\n id={id}\n data-slot=\"key-value\"\n data-disabled={disabled ? \"\" : undefined}\n data-invalid={isInvalid ? \"\" : undefined}\n data-readonly={readOnly ? \"\" : undefined}\n {...rootProps}\n ref={composedRef}\n className={cn(\"flex flex-col gap-2\", className)}\n />\n {isFormControl && (\n <VisuallyHiddenInput\n type=\"hidden\"\n control={formTrigger}\n name={name}\n value={value}\n disabled={disabled}\n readOnly={readOnly}\n required={required}\n />\n )}\n </KeyValueContext.Provider>\n </StoreContext.Provider>\n );\n}\n\ninterface KeyValueListProps extends DivProps {\n orientation?: Orientation;\n}\n\nfunction KeyValueList(props: KeyValueListProps) {\n const { orientation = \"vertical\", asChild, className, ...listProps } = props;\n\n const value = useStore((state) => state.value);\n\n const ListPrimitive = asChild ? Slot : \"div\";\n\n return (\n <ListPrimitive\n role=\"list\"\n /*\n * ⚠ Khác thượng nguồn (a11y): bỏ `aria-orientation`. Thuộc tính đó KHÔNG được phép trên\n * `role=\"list\"` (ARIA 1.2 chỉ cho nó ở listbox, menu, tablist, toolbar, radiogroup, slider…)\n * — axe báo `aria-allowed-attr`. Hướng vẫn còn ở `data-orientation` cho CSS.\n */\n data-slot=\"key-value-list\"\n data-orientation={orientation}\n {...listProps}\n className={cn(\"flex\", orientation === \"vertical\" ? \"flex-col gap-2\" : \"flex-row gap-2\", className)}\n >\n {value.map((item) => {\n const children = React.Children.toArray(props.children);\n\n return (\n <KeyValueItemContext.Provider key={item.id} value={item}>\n {children}\n </KeyValueItemContext.Provider>\n );\n })}\n </ListPrimitive>\n );\n}\n\nconst KeyValueItemContext = React.createContext<ItemData | null>(null);\n\nfunction useKeyValueItemContext(consumerName: string) {\n const context = React.useContext(KeyValueItemContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${LIST_NAME}\\``);\n }\n return context;\n}\n\ninterface KeyValueItemProps extends React.ComponentProps<\"div\"> {\n asChild?: boolean;\n}\n\nfunction KeyValueItem(props: KeyValueItemProps) {\n const { asChild, className, ...itemProps } = props;\n const itemData = useKeyValueItemContext(ITEM_NAME);\n\n const focusedId = useStore((state) => state.focusedId);\n\n const ItemPrimitive = asChild ? Slot : \"div\";\n\n return (\n <ItemPrimitive\n role=\"listitem\"\n data-slot=\"key-value-item\"\n data-highlighted={focusedId === itemData.id ? \"\" : undefined}\n {...itemProps}\n className={cn(\"flex items-start gap-2\", className)}\n />\n );\n}\n\ninterface KeyValueKeyInputProps extends React.ComponentProps<\"input\"> {\n asChild?: boolean;\n}\n\nfunction KeyValueKeyInput(props: KeyValueKeyInputProps) {\n const {\n onChange: onChangeProp,\n onBlur: onBlurProp,\n onPaste: onPasteProp,\n asChild,\n disabled,\n readOnly,\n required,\n ref,\n ...keyInputProps\n } = props;\n\n const context = useKeyValueContext(KEY_INPUT_NAME);\n const itemData = useKeyValueItemContext(KEY_INPUT_NAME);\n const store = useStoreContext(KEY_INPUT_NAME);\n const errors = useStore((state) => state.errors);\n const focusedId = useStore((state) => state.focusedId);\n\n const inputRef = React.useRef<KeyInputElement>(null);\n const composedRef = useComposedRefs(ref, inputRef);\n\n /*\n * ⚠ Khác thượng nguồn (a11y): `KeyValueAdd` ghi id dòng mới vào `focusedId` nhưng bản gốc không\n * bao giờ dùng nó để đưa focus — bấm \"Thêm\" xong focus vẫn đứng ở nút, người dùng bàn phím phải\n * tự Tab ngược lên dòng vừa thêm. Ở đây ô KHOÁ của dòng mới tự nhận focus. Chỉ chạy khi\n * `focusedId` ĐỔI sang dòng này (mà chỉ `KeyValueAdd` ghi nó), nên dòng có sẵn lúc mount và dòng\n * sinh ra do dán không bị cướp focus.\n */\n React.useEffect(() => {\n if (focusedId === itemData.id) {\n inputRef.current?.focus();\n }\n }, [focusedId, itemData.id]);\n\n const propsRef = useAsRef({\n onChange: onChangeProp,\n onBlur: onBlurProp,\n onPaste: onPasteProp\n });\n\n const isDisabled = disabled || context.disabled;\n const isReadOnly = readOnly || context.readOnly;\n const isRequired = required || context.required;\n const isInvalid = errors[itemData.id]?.key !== undefined;\n const [shownKey, setKeyDraft] = useTrimDraft(itemData.key);\n\n const onChange = React.useCallback(\n (event: React.ChangeEvent<KeyInputElement>) => {\n const text = event.target.value;\n if (context.trim) setKeyDraft(text);\n commitItemField(store, context, itemData.id, \"key\", context.trim ? text.trim() : text);\n propsRef.current.onChange?.(event);\n },\n [store, itemData.id, context, propsRef, setKeyDraft]\n );\n\n const onBlur = React.useCallback(\n (event: React.FocusEvent<KeyInputElement>) => {\n propsRef.current.onBlur?.(event);\n setKeyDraft(null);\n },\n [propsRef, setKeyDraft]\n );\n\n const onPaste = React.useCallback(\n (event: React.ClipboardEvent<KeyInputElement>) => {\n if (!context.enablePaste) return;\n\n propsRef.current.onPaste?.(event);\n if (event.defaultPrevented) return;\n\n const content = event.clipboardData.getData(\"text\");\n const lines = content.split(/\\r?\\n/).filter((line) => line.trim());\n\n if (lines.length > 1) {\n event.preventDefault();\n\n const parsed: ItemData[] = [];\n\n for (const line of lines) {\n let key = \"\";\n let value = \"\";\n\n if (line.includes(\"=\")) {\n const parts = line.split(\"=\");\n key = parts[0]?.trim() ?? \"\";\n value = removeQuotes(parts.slice(1).join(\"=\").trim(), context.stripQuotes);\n } else if (line.includes(\":\")) {\n const parts = line.split(\":\");\n key = parts[0]?.trim() ?? \"\";\n value = removeQuotes(parts.slice(1).join(\":\").trim(), context.stripQuotes);\n } else if (/\\s{2,}|\\t/.test(line)) {\n const parts = line.split(/\\s{2,}|\\t/);\n key = parts[0]?.trim() ?? \"\";\n value = removeQuotes(parts.slice(1).join(\" \").trim(), context.stripQuotes);\n }\n\n if (key) {\n parsed.push({ id: createItemId(), key, value });\n }\n }\n\n if (parsed.length > 0) {\n const state = store.getState();\n const currentIndex = state.value.findIndex((item) => item.id === itemData.id);\n\n let newValue: ItemData[];\n if (itemData.key === \"\" && itemData.value === \"\") {\n newValue = [...state.value.slice(0, currentIndex), ...parsed, ...state.value.slice(currentIndex + 1)];\n } else {\n newValue = [...state.value.slice(0, currentIndex + 1), ...parsed, ...state.value.slice(currentIndex + 1)];\n }\n\n if (context.maxItems !== undefined) {\n newValue = newValue.slice(0, context.maxItems);\n }\n\n store.setState(\"value\", newValue);\n\n if (context.onPaste) {\n context.onPaste(event.nativeEvent as unknown as ClipboardEvent, parsed);\n }\n }\n }\n },\n [context, store, itemData, propsRef]\n );\n\n const KeyInputPrimitive = asChild ? Slot : Input;\n\n return (\n <KeyInputPrimitive\n aria-invalid={isInvalid}\n aria-describedby={isInvalid ? getErrorId(context.rootId, itemData.id, \"key\") : undefined}\n data-slot=\"key-value-key-input\"\n autoCapitalize=\"off\"\n autoComplete=\"off\"\n autoCorrect=\"off\"\n spellCheck=\"false\"\n disabled={isDisabled}\n readOnly={isReadOnly}\n required={isRequired}\n placeholder={context.keyPlaceholder}\n {...keyInputProps}\n ref={composedRef}\n value={shownKey}\n onChange={onChange}\n onBlur={onBlur}\n onPaste={onPaste}\n />\n );\n}\n\ninterface KeyValueValueInputProps extends Omit<React.ComponentProps<\"textarea\">, \"rows\"> {\n maxRows?: number;\n asChild?: boolean;\n}\n\nfunction KeyValueValueInput(props: KeyValueValueInputProps) {\n const {\n onChange: onChangeProp,\n onBlur: onBlurProp,\n asChild,\n disabled,\n readOnly,\n required,\n className,\n maxRows,\n style,\n ...valueInputProps\n } = props;\n\n const context = useKeyValueContext(VALUE_INPUT_NAME);\n const itemData = useKeyValueItemContext(VALUE_INPUT_NAME);\n const store = useStoreContext(VALUE_INPUT_NAME);\n const errors = useStore((state) => state.errors);\n\n const propsRef = useAsRef({\n onChange: onChangeProp,\n onBlur: onBlurProp\n });\n\n const isDisabled = disabled || context.disabled;\n const isReadOnly = readOnly || context.readOnly;\n const isRequired = required || context.required;\n const isInvalid = errors[itemData.id]?.value !== undefined;\n const maxHeight = maxRows ? `calc(${maxRows} * 1.5em + 1rem)` : undefined;\n const [shownValue, setValueDraft] = useTrimDraft(itemData.value);\n\n const onChange = React.useCallback(\n (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n propsRef.current.onChange?.(event);\n const text = event.target.value;\n if (context.trim) setValueDraft(text);\n commitItemField(store, context, itemData.id, \"value\", context.trim ? text.trim() : text);\n },\n [store, itemData.id, context, propsRef, setValueDraft]\n );\n\n const onBlur = React.useCallback(\n (event: React.FocusEvent<HTMLTextAreaElement>) => {\n propsRef.current.onBlur?.(event);\n setValueDraft(null);\n },\n [propsRef, setValueDraft]\n );\n\n const ValueInputPrimitive = asChild ? Slot : Textarea;\n\n return (\n <ValueInputPrimitive\n aria-invalid={isInvalid}\n aria-describedby={isInvalid ? getErrorId(context.rootId, itemData.id, \"value\") : undefined}\n data-slot=\"key-value-value-input\"\n autoCapitalize=\"off\"\n autoComplete=\"off\"\n autoCorrect=\"off\"\n spellCheck=\"false\"\n disabled={isDisabled}\n readOnly={isReadOnly}\n required={isRequired}\n placeholder={context.valuePlaceholder}\n {...valueInputProps}\n className={cn(\n /*\n * ⚠ Khác thượng nguồn: ô giá trị phải trông như ô khoá (`Input`) đứng ngay cạnh khi còn MỘT\n * dòng. `Textarea` của gói theo comitor-ds: `text-base md:text-sm` + `py-2` (cao 38px ở\n * desktop, 42px và chữ 16px ở mobile), nền `bg-transparent dark:bg-input-fill`, có\n * `shadow-xs`; còn `Input` là `h-9 text-sm bg-background`, không bóng. Ghi đè ở ĐÂY chứ\n * không sửa hai primitive: `text-sm` ở mọi cỡ màn, `min-h-9` + `py-1.5` (dòng chữ 20px cách\n * mép trên 7px — đúng chỗ `Input` căn giữa dòng chữ trong 36px), `bg-background` ở cả hai\n * theme, bỏ bóng. Nhiều dòng thì `field-sizing-content` vẫn cho ô cao dần như cũ.\n */\n \"field-sizing-content min-h-9 resize-none py-1.5 text-sm md:text-sm bg-background dark:bg-background shadow-none\",\n maxRows && \"overflow-y-auto\",\n className\n )}\n style={{\n ...style,\n ...(maxHeight && { maxHeight })\n }}\n value={shownValue}\n onChange={onChange}\n onBlur={onBlur}\n />\n );\n}\n\ninterface KeyValueRemoveProps extends React.ComponentProps<typeof Button> {}\n\nfunction KeyValueRemove(props: KeyValueRemoveProps) {\n const { onClick: onClickProp, children, className, ...removeProps } = props;\n\n const context = useKeyValueContext(REMOVE_NAME);\n const itemData = useKeyValueItemContext(REMOVE_NAME);\n const store = useStoreContext(REMOVE_NAME);\n\n const propsRef = useAsRef({\n onClick: onClickProp\n });\n const value = useStore((state) => state.value);\n const isDisabled = context.disabled || value.length <= context.minItems;\n const itemIndex = value.findIndex((item) => item.id === itemData.id);\n\n const onClick = React.useCallback(\n (event: React.MouseEvent<RemoveElement>) => {\n propsRef.current.onClick?.(event);\n\n const state = store.getState();\n if (state.value.length <= context.minItems) return;\n\n const itemToRemove = state.value.find((item) => item.id === itemData.id);\n if (!itemToRemove) return;\n\n /*\n * ⚠ Khác thượng nguồn (a11y): nút đang giữ focus biến mất cùng dòng của nó, và focus rơi về\n * `<body>` — người dùng bàn phím phải Tab lại từ đầu trang (WCAG 2.4.3). Nếu nút có focus lúc\n * bấm thì sau khi xoá, đưa focus sang nút xoá của dòng kế (hoặc dòng trước nếu vừa xoá dòng\n * cuối), không còn nút nào dùng được thì sang nút thêm.\n */\n const button = event.currentTarget;\n const hadFocus = button.ownerDocument.activeElement === button;\n const root = button.closest<HTMLElement>('[data-slot=\"key-value\"]');\n const removedIndex = state.value.indexOf(itemToRemove);\n\n const newValue = state.value.filter((item) => item.id !== itemData.id);\n const newErrors = { ...state.errors };\n delete newErrors[itemData.id];\n\n store.setState(\"value\", newValue);\n store.setState(\"errors\", newErrors);\n\n context.onRemove?.(itemToRemove);\n\n if (hadFocus && root) {\n requestAnimationFrame(() => {\n const removeButtons = root.querySelectorAll<HTMLElement>('[data-slot=\"key-value-remove\"]:not(:disabled)');\n const nextTarget =\n removeButtons[Math.min(removedIndex, removeButtons.length - 1)] ??\n root.querySelector<HTMLElement>('[data-slot=\"key-value-add\"]:not(:disabled)');\n nextTarget?.focus();\n });\n }\n },\n [store, context, itemData.id, propsRef]\n );\n\n return (\n <Button\n type=\"button\"\n data-slot=\"key-value-remove\"\n variant=\"outline\"\n size=\"icon\"\n /*\n * ⚠ Khác thượng nguồn (a11y): nút chỉ có icon ✕ và bản gốc không đặt tên nào — trình đọc màn\n * hình đọc \"nút\" trơn, lặp lại ở mọi dòng. Tên mặc định nói RÕ dòng nào: \"Remove API_KEY\",\n * hoặc \"Remove row 3\" khi khoá còn trống. Đổi qua `labels.remove` của `KeyValue`, hoặc `aria-label`.\n */\n aria-label={context.labels.remove(itemData.key, itemIndex + 1)}\n disabled={isDisabled}\n {...removeProps}\n // ⚠ Khác thượng nguồn: `shrink-0` — nút là mục flex cạnh hai ô `w-full`; thiếu nó thì flex\n // bóp nút còn ~18×36px. `size=\"icon\"` của `Button` đã là ô vuông 36×36 (`h-9 w-9`).\n className={cn(\"shrink-0\", className)}\n onClick={onClick}\n >\n {/* `size-4`: `Button` của gói không tự đặt cỡ cho svg con như shadcn — thiếu là icon 24px. */}\n {children ?? <X aria-hidden=\"true\" className=\"size-4\" />}\n </Button>\n );\n}\n\ntype KeyValueAddProps = React.ComponentProps<typeof Button>;\n\nfunction KeyValueAdd(props: KeyValueAddProps) {\n const { onClick: onClickProp, children, ...addProps } = props;\n\n const context = useKeyValueContext(ADD_NAME);\n const store = useStoreContext(ADD_NAME);\n\n const propsRef = useAsRef({\n onClick: onClickProp\n });\n const value = useStore((state) => state.value);\n const isDisabled = context.disabled || (context.maxItems !== undefined && value.length >= context.maxItems);\n\n const onClick = React.useCallback(\n (event: React.MouseEvent<AddElement>) => {\n propsRef.current.onClick?.(event);\n\n const state = store.getState();\n if (context.maxItems !== undefined && state.value.length >= context.maxItems) {\n return;\n }\n\n const newItem: ItemData = {\n id: createItemId(),\n key: \"\",\n value: \"\"\n };\n\n const newValue = [...state.value, newItem];\n store.setState(\"value\", newValue);\n store.setState(\"focusedId\", newItem.id);\n\n context.onAdd?.(newItem);\n },\n [store, context, propsRef]\n );\n\n return (\n <Button\n type=\"button\"\n data-slot=\"key-value-add\"\n variant=\"outline\"\n disabled={isDisabled}\n {...addProps}\n onClick={onClick}\n >\n {children ?? (\n <>\n <Plus aria-hidden=\"true\" className=\"size-4\" />\n {context.labels.add}\n </>\n )}\n </Button>\n );\n}\n\ninterface KeyValueErrorProps extends DivProps {\n field: Field;\n}\n\nfunction KeyValueError(props: KeyValueErrorProps) {\n const { field, asChild, className, ...errorProps } = props;\n\n const context = useKeyValueContext(ERROR_NAME);\n const itemData = useKeyValueItemContext(ERROR_NAME);\n\n const errors = useStore((state) => state.errors);\n const error = errors[itemData.id]?.[field];\n\n if (!error) return null;\n\n const ErrorPrimitive = asChild ? Slot : \"span\";\n\n return (\n <ErrorPrimitive\n id={getErrorId(context.rootId, itemData.id, field)}\n role=\"alert\"\n {...errorProps}\n className={cn(\n // ⚠ Khác thượng nguồn: `text-destructive` (bậc TÔ NỀN) → `text-destructive-ink`, bậc dành\n // cho chữ trên nền trang — cùng token với chữ lỗi của `Input`.\n \"text-sm font-medium text-destructive-ink\",\n className\n )}\n >\n {error}\n </ErrorPrimitive>\n );\n}\n\nexport {\n type ItemData as KeyValueItemData,\n KeyValue,\n KeyValueAdd,\n type KeyValueAddProps,\n KeyValueError,\n type KeyValueErrorProps,\n KeyValueItem,\n type KeyValueItemProps,\n KeyValueKeyInput,\n type KeyValueKeyInputProps,\n KeyValueList,\n type KeyValueListProps,\n type KeyValueProps,\n KeyValueRemove,\n type KeyValueRemoveProps,\n KeyValueValueInput,\n type KeyValueValueInputProps,\n useStore as useKeyValueStore\n};\n"],"mappings":"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","names":[]}
@@ -111,6 +111,27 @@ function getCurrencyFractionDigits(opts) {
111
111
  fractionDigitsCache.set(key, digits);
112
112
  return digits;
113
113
  }
114
+ function findCurrencyDecimalIndex(cleaned, decimal, group) {
115
+ const lastDot = cleaned.lastIndexOf(".");
116
+ const lastComma = cleaned.lastIndexOf(",");
117
+ if (lastDot !== -1 && lastComma !== -1) return Math.max(lastDot, lastComma);
118
+ const separator = lastDot !== -1 ? "." : lastComma !== -1 ? "," : null;
119
+ if (!separator) return -1;
120
+ const last = cleaned.lastIndexOf(separator);
121
+ if (separator === decimal) return last;
122
+ if (separator === group) {
123
+ return separator === "." && decimal === "," && last === cleaned.length - 1 ? last : -1;
124
+ }
125
+ const occurrences = cleaned.split(separator).length - 1;
126
+ const digitsAfter = cleaned.length - last - 1;
127
+ return occurrences === 1 && digitsAfter !== 3 ? last : -1;
128
+ }
129
+ function isCanonicalAmount(value, fractionDigits) {
130
+ const dot = value.indexOf(".");
131
+ const integerPart = dot === -1 ? value : value.slice(0, dot);
132
+ const fractionPart = dot === -1 ? "" : value.slice(dot + 1);
133
+ return /^\d*$/.test(integerPart) && /^\d*$/.test(fractionPart) && fractionPart.length <= fractionDigits;
134
+ }
114
135
  function isCurrencyAtEnd(opts) {
115
136
  const { locale, currency } = opts;
116
137
  const key = `${locale}|${currency}`;
@@ -251,81 +272,20 @@ const MASK_PATTERNS = {
251
272
  currency: {
252
273
  pattern: "$###,###.##",
253
274
  transform: (value, { currency = DEFAULT_CURRENCY, locale = DEFAULT_LOCALE } = {}) => {
254
- if (getCurrencyFractionDigits({ currency, locale }) === 0) {
275
+ const fractionDigits = getCurrencyFractionDigits({ currency, locale });
276
+ if (fractionDigits === 0) {
255
277
  return value.replace(REGEX_CACHE.nonDigits, "");
256
278
  }
257
- let localeDecimalSeparator = ".";
258
- try {
259
- const formatter = getCachedFormatter(locale, {
260
- currency,
261
- minimumFractionDigits: 0,
262
- maximumFractionDigits: 2
263
- });
264
- const parts = formatter.formatToParts(1234.5);
265
- const decimalPart = parts.find((part) => part.type === "decimal");
266
- if (decimalPart) localeDecimalSeparator = decimalPart.value;
267
- } catch {
268
- }
279
+ if (isCanonicalAmount(value, fractionDigits)) return value;
280
+ const { decimal, group } = getCachedCurrencySymbols({ currency, locale });
269
281
  const cleaned = value.replace(REGEX_CACHE.nonCurrencyChars, "");
270
- const dotIndex = cleaned.indexOf(".");
271
- const commaIndex = cleaned.indexOf(",");
272
- let hasDecimalSeparator = false;
273
- let decimalIndex = -1;
274
- if (localeDecimalSeparator === ",") {
275
- const lastCommaIndex = cleaned.lastIndexOf(",");
276
- if (lastCommaIndex !== -1) {
277
- const afterComma = cleaned.substring(lastCommaIndex + 1);
278
- if (afterComma.length <= 2 && /^\d*$/.test(afterComma)) {
279
- hasDecimalSeparator = true;
280
- decimalIndex = lastCommaIndex;
281
- }
282
- }
283
- if (!hasDecimalSeparator && dotIndex !== -1) {
284
- const afterDot = cleaned.substring(dotIndex + 1);
285
- if (afterDot.length <= 2 && /^\d*$/.test(afterDot)) {
286
- hasDecimalSeparator = true;
287
- decimalIndex = dotIndex;
288
- }
289
- }
290
- if (!hasDecimalSeparator && cleaned.length >= 4) {
291
- const match = cleaned.match(/^(\d+)\.(\d{3})(\d{1,2})$/);
292
- if (match) {
293
- const [, beforeDot, thousandsPart, decimalPart] = match;
294
- const integerPart = (beforeDot ?? "") + (thousandsPart ?? "");
295
- const result = `${integerPart}.${decimalPart}`;
296
- return result;
297
- }
298
- }
299
- } else {
300
- const lastDotIndex = cleaned.lastIndexOf(".");
301
- if (lastDotIndex !== -1) {
302
- const afterDot = cleaned.substring(lastDotIndex + 1);
303
- if (afterDot.length <= 2 && /^\d*$/.test(afterDot)) {
304
- hasDecimalSeparator = true;
305
- decimalIndex = lastDotIndex;
306
- }
307
- }
308
- if (!hasDecimalSeparator && commaIndex !== -1) {
309
- const afterComma = cleaned.substring(commaIndex + 1);
310
- const looksLikeThousands = commaIndex <= 3 && afterComma.length >= 3;
311
- if (!looksLikeThousands && afterComma.length <= 2 && /^\d*$/.test(afterComma)) {
312
- hasDecimalSeparator = true;
313
- decimalIndex = commaIndex;
314
- }
315
- }
316
- }
317
- if (hasDecimalSeparator && decimalIndex !== -1) {
318
- const beforeDecimal = cleaned.substring(0, decimalIndex).replace(/[.,]/g, "");
319
- const afterDecimal = cleaned.substring(decimalIndex + 1).replace(/[.,]/g, "");
320
- if (afterDecimal === "") {
321
- const result2 = `${beforeDecimal}.`;
322
- return result2;
323
- }
324
- const result = `${beforeDecimal}.${afterDecimal.substring(0, 2)}`;
325
- return result;
282
+ const decimalIndex = findCurrencyDecimalIndex(cleaned, decimal, group);
283
+ if (decimalIndex === -1) {
284
+ return cleaned.replace(/[.,]/g, "");
326
285
  }
327
- const digitsOnly = cleaned.replace(/[.,]/g, "");
328
- return digitsOnly;
286
+ const integerPart = cleaned.substring(0, decimalIndex).replace(/[.,]/g, "");
287
+ const fractionPart = cleaned.substring(decimalIndex + 1).replace(/[.,]/g, "").substring(0, fractionDigits);
288
+ return `${integerPart}.${fractionPart}`;
329
289
  },
330
290
  validate: (value) => {
331
291
  if (!REGEX_CACHE.currencyValidation.test(value)) return false;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/ui/mask-input-constants.ts"],"sourcesContent":["/*\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 * Lõi THUẦN của `MaskInput`: bảng mẫu `MASK_PATTERNS` và các hàm định dạng/bóc mặt nạ.\n *\n * File này là NỬA TRÊN của `mask-input.tsx` thượng nguồn (mọi thứ đứng trước component), tách ra\n * nguyên khối, đúng thứ tự, để còn diff ngược được. Nó **CỐ Ý không có `\"use client\"`**: mọi\n * export của một file `\"use client\"` tới Server Component là client-reference proxy — `MASK_PATTERNS`\n * sẽ đọc ra `undefined` mà không throw — và `applyCurrencyMask` là thứ trang server hay cần nhất\n * (in số tiền ra bảng, email, PDF) nên phải chạy được ở cả hai phía.\n *\n * ⚠ Barrel `src/index.ts` xuất module này TRỰC TIẾP, và CHỌN LỌC (không `export *`): ngoài các tên\n * công khai của thượng nguồn, file còn export vài helper NỘI BỘ cho riêng `mask-input.tsx`\n * (liệt kê ở khối export cuối file). `mask-input.tsx` chỉ `import`, KHÔNG `export` lại.\n */\n\nconst PAST_YEARS_LIMIT = 120;\nconst FUTURE_YEARS_LIMIT = 10;\n/*\n * \"USD\" / \"en-US\" như thượng nguồn — mặc định của các hàm THUẦN ở đây (gọi thẳng ở server, email,\n * PDF). Tiền tệ là thiết lập của VÙNG / nghiệp vụ, không phải của ngôn ngữ (xem\n * `src/i18n/locale.ts`): app Comitor truyền `currency` từ cài đặt workspace. Component `MaskInput`\n * dùng `DEFAULT_CURRENCY` nhưng lấy `locale` mặc định từ `ComitorLocaleProvider`, không từ hằng\n * `DEFAULT_LOCALE`. Trước `3.0.0` hai hằng là \"VND\" / \"vi-VN\".\n */\nconst DEFAULT_CURRENCY = \"USD\";\nconst DEFAULT_LOCALE = \"en-US\";\n\nconst NUMERIC_MASK_PATTERNS = /^(phone|zipCode|zipCodeExtended|ssn|ein|time|date|creditCard|creditCardExpiry)$/;\nconst CURRENCY_PERCENTAGE_SYMBOLS = /[€$%]/;\n\ninterface CurrencySymbols {\n currency: string;\n decimal: string;\n group: string;\n}\n\nconst formattersCache = new Map<string, Intl.NumberFormat>();\nconst currencyAtEndCache = new Map<string, boolean>();\nconst currencySymbolsCache = new Map<string, CurrencySymbols>();\nconst fractionDigitsCache = new Map<string, number>();\nconst daysInMonthCache = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] as const;\n\nconst REGEX_CACHE = {\n digitsOnly: /^\\d+$/,\n nonDigits: /\\D/g,\n nonAlphaNumeric: /[^A-Z0-9]/gi,\n nonNumericDot: /[^0-9.]/g,\n nonCurrencyChars: /[^\\d.,]/g,\n hashPattern: /#/g,\n currencyAtEnd: /\\d\\s*[^\\d\\s]+$/,\n percentageChars: /[^\\d.]/g,\n phone: /^\\d{10}$/,\n ssn: /^\\d{9}$/,\n zipCode: /^\\d{5}$/,\n zipCodeExtended: /^\\d{9}$/,\n isbn: /^\\d{13}$/,\n ein: /^\\d{9}$/,\n time: /^\\d{4}$/,\n creditCard: /^\\d{13,19}$/,\n creditCardExpiry: /^\\d{4}$/,\n licensePlate: /^[A-Z0-9]{6}$/,\n macAddress: /^[A-F0-9]{12}$/,\n currencyValidation: /^\\d+(\\.\\d{1,2})?$/,\n ipv4Segment: /^\\d{1,3}$/\n} as const;\n\nfunction getCachedFormatter(locale: string | undefined, opts: Intl.NumberFormatOptions): Intl.NumberFormat {\n const { currency, minimumFractionDigits = 0, maximumFractionDigits = 2 } = opts;\n\n const key = `${locale}|${currency}|${minimumFractionDigits}|${maximumFractionDigits}`;\n\n if (!formattersCache.has(key)) {\n try {\n formattersCache.set(\n key,\n new Intl.NumberFormat(locale, {\n style: \"currency\",\n currency,\n ...opts\n })\n );\n } catch {\n formattersCache.set(\n key,\n new Intl.NumberFormat(DEFAULT_LOCALE, {\n style: \"currency\",\n currency: DEFAULT_CURRENCY,\n ...opts\n })\n );\n }\n }\n\n const formatter = formattersCache.get(key);\n if (!formatter) {\n throw new Error(`Không tạo được bộ định dạng tiền tệ cho ${key}`);\n }\n return formatter;\n}\n\nfunction getCachedCurrencySymbols(opts: TransformOptions): CurrencySymbols {\n const { locale, currency } = opts;\n\n const key = `${locale}|${currency}`;\n const cached = currencySymbolsCache.get(key);\n if (cached) {\n return cached;\n }\n\n let currencySymbol = \"$\";\n let decimalSeparator = \".\";\n let groupSeparator = \",\";\n\n try {\n const formatter = getCachedFormatter(locale, {\n currency,\n minimumFractionDigits: 0,\n maximumFractionDigits: 2\n });\n const parts = formatter.formatToParts(1234.5);\n const currencyPart = parts.find((part) => part.type === \"currency\");\n const decimalPart = parts.find((part) => part.type === \"decimal\");\n const groupPart = parts.find((part) => part.type === \"group\");\n\n if (currencyPart) currencySymbol = currencyPart.value;\n if (decimalPart) decimalSeparator = decimalPart.value;\n if (groupPart) groupSeparator = groupPart.value;\n } catch {\n // Giữ giá trị mặc định.\n }\n\n const symbols: CurrencySymbols = {\n currency: currencySymbol,\n decimal: decimalSeparator,\n group: groupSeparator\n };\n currencySymbolsCache.set(key, symbols);\n return symbols;\n}\n\n/**\n * Số chữ số thập phân TỰ NHIÊN của đồng tiền theo ISO 4217 / CLDR: VND 0, JPY 0, USD 2, EUR 2.\n * Chặn trần ở 2 vì phần còn lại của logic (validate, cắt phần lẻ) chỉ hiểu tối đa 2 chữ số.\n *\n * ⚠ Khác thượng nguồn — hàm MỚI. Thượng nguồn ghim cứng `maximumFractionDigits: 2` cho mọi đồng\n * tiền, và với locale dùng `,` làm dấu thập phân (de-DE, vi-VN) thì `transform` còn đoán \"chữ số\n * gõ sau một nhóm nghìn là phần lẻ\". Với VND, đo trên chính mã thượng nguồn, gõ lần lượt\n * `1234567` ra:\n *\n * 1 ₫ → 12 ₫ → 123 ₫ → 1.234 ₫ → 1.234,5 ₫ → 1.234,56 ₫ → 1.234.567 ₫\n *\n * tức chữ số thứ 5 và 6 biến thành HÀO, và giá trị thô báo ra `onValueChange` là \"1234.5\" trong\n * khi người dùng đang gõ 12.345 ₫. Đồng tiền không có phần lẻ thì mọi `.`/`,` đều là dấu nhóm.\n */\nfunction getCurrencyFractionDigits(opts: TransformOptions): number {\n const { locale, currency } = opts;\n\n const key = `${locale}|${currency}`;\n const cached = fractionDigitsCache.get(key);\n if (cached !== undefined) {\n return cached;\n }\n\n let digits = 2;\n try {\n const resolved = new Intl.NumberFormat(locale, { style: \"currency\", currency }).resolvedOptions();\n digits = Math.min(2, resolved.maximumFractionDigits ?? 2);\n } catch {\n // Đồng tiền/locale không hợp lệ → giữ 2 như thượng nguồn.\n }\n\n fractionDigitsCache.set(key, digits);\n return digits;\n}\n\nfunction isCurrencyAtEnd(opts: TransformOptions): boolean {\n const { locale, currency } = opts;\n\n const key = `${locale}|${currency}`;\n const cached = currencyAtEndCache.get(key);\n if (cached !== undefined) {\n return cached;\n }\n\n try {\n const formatter = getCachedFormatter(locale, {\n currency,\n minimumFractionDigits: 0,\n maximumFractionDigits: 0\n });\n const sample = formatter.format(123);\n const result = REGEX_CACHE.currencyAtEnd.test(sample);\n currencyAtEndCache.set(key, result);\n return result;\n } catch {\n currencyAtEndCache.set(key, false);\n return false;\n }\n}\n\nfunction isCurrencyMask(opts: { mask: MaskPatternKey | MaskPattern | undefined; pattern?: string }): boolean {\n const { mask, pattern } = opts;\n\n return mask === \"currency\" || Boolean(pattern && (pattern.includes(\"$\") || pattern.includes(\"€\")));\n}\n\ninterface TransformOptions {\n currency?: string;\n locale?: string;\n}\n\ninterface ValidateOptions {\n min?: number;\n max?: number;\n}\n\ninterface MaskPattern {\n pattern: string;\n transform?: (value: string, opts?: TransformOptions) => string;\n validate?: (value: string, opts?: ValidateOptions) => boolean;\n}\n\ntype MaskPatternKey =\n | \"phone\"\n | \"ssn\"\n | \"date\"\n | \"time\"\n | \"creditCard\"\n | \"creditCardExpiry\"\n | \"zipCode\"\n | \"zipCodeExtended\"\n | \"currency\"\n | \"percentage\"\n | \"licensePlate\"\n | \"ipv4\"\n | \"macAddress\"\n | \"isbn\"\n | \"ein\";\n\n/**\n * Các mẫu dựng sẵn — giữ NGUYÊN bộ của thượng nguồn, không thêm không bớt. Nhiều mẫu là định dạng\n * Hoa Kỳ (`phone`, `ssn`, `date`, `zipCode`, `zipCodeExtended`, `ein`); app Comitor cần định dạng\n * khác thì truyền một `MaskPattern` riêng vào prop `mask`.\n */\nconst MASK_PATTERNS: Record<MaskPatternKey, MaskPattern> = {\n /**\n * Số điện thoại **Hoa Kỳ** `(###) ###-####`, đúng 10 chữ số.\n *\n * ⚠ Không dùng cho số điện thoại ở app Comitor: số Việt Nam (và mọi số quốc tế) đi qua\n * `PhoneInput` — có chọn quốc gia, tự nhận mã vùng và lưu dạng `+84…`.\n */\n phone: {\n pattern: \"(###) ###-####\",\n transform: (value) => value.replace(REGEX_CACHE.nonDigits, \"\"),\n validate: (value) => REGEX_CACHE.phone.test(value.replace(REGEX_CACHE.nonDigits, \"\"))\n },\n ssn: {\n pattern: \"###-##-####\",\n transform: (value) => value.replace(REGEX_CACHE.nonDigits, \"\"),\n validate: (value) => REGEX_CACHE.ssn.test(value.replace(REGEX_CACHE.nonDigits, \"\"))\n },\n /**\n * Ngày kiểu **Hoa Kỳ** `MM/DD/YYYY` — `validate` đọc 2 chữ số ĐẦU là THÁNG. Ngày kiểu Việt Nam\n * (`dd/MM/yyyy`) thì dùng `DatePicker` của gói, hoặc một `MaskPattern` riêng.\n */\n date: {\n pattern: \"##/##/####\",\n transform: (value) => value.replace(REGEX_CACHE.nonDigits, \"\"),\n validate: (value) => {\n const cleaned = value.replace(REGEX_CACHE.nonDigits, \"\");\n if (cleaned.length !== 8) return false;\n const month = Number.parseInt(cleaned.substring(0, 2), 10);\n const day = Number.parseInt(cleaned.substring(2, 4), 10);\n const year = Number.parseInt(cleaned.substring(4, 8), 10);\n\n const currentYear = new Date().getFullYear();\n const minYear = currentYear - PAST_YEARS_LIMIT;\n const maxYear = currentYear + FUTURE_YEARS_LIMIT;\n if (month < 1 || month > 12 || day < 1 || year < minYear || year > maxYear) return false;\n\n const maxDays =\n month === 2 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0)\n ? 29\n : (daysInMonthCache[month - 1] ?? 31);\n\n return day <= maxDays;\n }\n },\n time: {\n pattern: \"##:##\",\n transform: (value) => value.replace(REGEX_CACHE.nonDigits, \"\"),\n validate: (value) => {\n const cleaned = value.replace(REGEX_CACHE.nonDigits, \"\");\n if (!REGEX_CACHE.time.test(cleaned)) return false;\n const hours = Number.parseInt(cleaned.substring(0, 2), 10);\n const minutes = Number.parseInt(cleaned.substring(2, 4), 10);\n return hours <= 23 && minutes <= 59;\n }\n },\n creditCard: {\n pattern: \"#### #### #### ####\",\n transform: (value) => value.replace(REGEX_CACHE.nonDigits, \"\"),\n validate: (value) => {\n const cleaned = value.replace(REGEX_CACHE.nonDigits, \"\");\n if (!REGEX_CACHE.creditCard.test(cleaned)) return false;\n\n let sum = 0;\n let isEven = false;\n for (let i = cleaned.length - 1; i >= 0; i--) {\n const digitChar = cleaned[i];\n if (!digitChar) continue;\n let digit = Number.parseInt(digitChar, 10);\n if (isEven) {\n digit *= 2;\n if (digit > 9) {\n digit -= 9;\n }\n }\n sum += digit;\n isEven = !isEven;\n }\n return sum % 10 === 0;\n }\n },\n creditCardExpiry: {\n pattern: \"##/##\",\n transform: (value) => value.replace(REGEX_CACHE.nonDigits, \"\"),\n validate: (value) => {\n const cleaned = value.replace(REGEX_CACHE.nonDigits, \"\");\n if (!REGEX_CACHE.creditCardExpiry.test(cleaned)) return false;\n\n const month = Number.parseInt(cleaned.substring(0, 2), 10);\n const year = Number.parseInt(cleaned.substring(2, 4), 10);\n\n if (month < 1 || month > 12) return false;\n\n const now = new Date();\n const currentYear = now.getFullYear();\n const currentMonth = now.getMonth() + 1;\n\n const fullYear = year <= 75 ? 2000 + year : 1900 + year;\n\n if (fullYear < currentYear || (fullYear === currentYear && month < currentMonth)) {\n return false;\n }\n\n const maxYear = currentYear + 50;\n if (fullYear > maxYear) {\n return false;\n }\n\n return true;\n }\n },\n zipCode: {\n pattern: \"#####\",\n transform: (value) => value.replace(REGEX_CACHE.nonDigits, \"\"),\n validate: (value) => REGEX_CACHE.zipCode.test(value.replace(REGEX_CACHE.nonDigits, \"\"))\n },\n zipCodeExtended: {\n pattern: \"#####-####\",\n transform: (value) => value.replace(REGEX_CACHE.nonDigits, \"\"),\n validate: (value) => REGEX_CACHE.zipCodeExtended.test(value.replace(REGEX_CACHE.nonDigits, \"\"))\n },\n /**\n * Tiền tệ — định dạng thật đi qua `Intl.NumberFormat` theo `currency`/`locale` (mặc định USD /\n * en-US → `$1,234,567`; `VND` / `vi-VN` → `1.234.567 ₫`). Chuỗi `pattern` ở đây chỉ là DẤU NHẬN\n * DẠNG cho nhánh tiền tệ, không phải khuôn chữ số.\n */\n currency: {\n pattern: \"$###,###.##\",\n transform: (value, { currency = DEFAULT_CURRENCY, locale = DEFAULT_LOCALE } = {}) => {\n // ⚠ Khác thượng nguồn: đồng tiền không có phần lẻ (VND, JPY) → mọi `.`/`,` là dấu nhóm,\n // giá trị thô chỉ còn chữ số. Lý do: xem `getCurrencyFractionDigits`.\n if (getCurrencyFractionDigits({ currency, locale }) === 0) {\n return value.replace(REGEX_CACHE.nonDigits, \"\");\n }\n\n let localeDecimalSeparator = \".\";\n\n try {\n const formatter = getCachedFormatter(locale, {\n currency,\n minimumFractionDigits: 0,\n maximumFractionDigits: 2\n });\n const parts = formatter.formatToParts(1234.5);\n const decimalPart = parts.find((part) => part.type === \"decimal\");\n\n if (decimalPart) localeDecimalSeparator = decimalPart.value;\n } catch {\n // Giữ giá trị mặc định.\n }\n\n const cleaned = value.replace(REGEX_CACHE.nonCurrencyChars, \"\");\n\n const dotIndex = cleaned.indexOf(\".\");\n const commaIndex = cleaned.indexOf(\",\");\n\n let hasDecimalSeparator = false;\n let decimalIndex = -1;\n\n if (localeDecimalSeparator === \",\") {\n const lastCommaIndex = cleaned.lastIndexOf(\",\");\n if (lastCommaIndex !== -1) {\n const afterComma = cleaned.substring(lastCommaIndex + 1);\n if (afterComma.length <= 2 && /^\\d*$/.test(afterComma)) {\n hasDecimalSeparator = true;\n decimalIndex = lastCommaIndex;\n }\n }\n\n if (!hasDecimalSeparator && dotIndex !== -1) {\n const afterDot = cleaned.substring(dotIndex + 1);\n if (afterDot.length <= 2 && /^\\d*$/.test(afterDot)) {\n hasDecimalSeparator = true;\n decimalIndex = dotIndex;\n }\n }\n\n if (!hasDecimalSeparator && cleaned.length >= 4) {\n const match = cleaned.match(/^(\\d+)\\.(\\d{3})(\\d{1,2})$/);\n if (match) {\n const [, beforeDot, thousandsPart, decimalPart] = match;\n const integerPart = (beforeDot ?? \"\") + (thousandsPart ?? \"\");\n const result = `${integerPart}.${decimalPart}`;\n return result;\n }\n }\n } else {\n const lastDotIndex = cleaned.lastIndexOf(\".\");\n if (lastDotIndex !== -1) {\n const afterDot = cleaned.substring(lastDotIndex + 1);\n if (afterDot.length <= 2 && /^\\d*$/.test(afterDot)) {\n hasDecimalSeparator = true;\n decimalIndex = lastDotIndex;\n }\n }\n\n if (!hasDecimalSeparator && commaIndex !== -1) {\n const afterComma = cleaned.substring(commaIndex + 1);\n const looksLikeThousands = commaIndex <= 3 && afterComma.length >= 3;\n if (!looksLikeThousands && afterComma.length <= 2 && /^\\d*$/.test(afterComma)) {\n hasDecimalSeparator = true;\n decimalIndex = commaIndex;\n }\n }\n }\n\n if (hasDecimalSeparator && decimalIndex !== -1) {\n const beforeDecimal = cleaned.substring(0, decimalIndex).replace(/[.,]/g, \"\");\n const afterDecimal = cleaned.substring(decimalIndex + 1).replace(/[.,]/g, \"\");\n\n if (afterDecimal === \"\") {\n const result = `${beforeDecimal}.`;\n return result;\n }\n\n const result = `${beforeDecimal}.${afterDecimal.substring(0, 2)}`;\n return result;\n }\n\n const digitsOnly = cleaned.replace(/[.,]/g, \"\");\n return digitsOnly;\n },\n validate: (value) => {\n if (!REGEX_CACHE.currencyValidation.test(value)) return false;\n const num = Number.parseFloat(value);\n return !Number.isNaN(num) && num >= 0;\n }\n },\n /**\n * Phần trăm `##.##%` — dấu thập phân là `.` cố định, KHÔNG theo `locale` (dấu `,` bị bỏ qua).\n */\n percentage: {\n pattern: \"##.##%\",\n transform: (value) => {\n const cleaned = value.replace(REGEX_CACHE.percentageChars, \"\");\n const parts = cleaned.split(\".\");\n if (parts.length > 2) {\n return `${parts[0]}.${parts.slice(1).join(\"\")}`;\n }\n if (parts[1] && parts[1].length > 2) {\n return `${parts[0]}.${parts[1].substring(0, 2)}`;\n }\n return cleaned;\n },\n validate: (value, opts = {}) => {\n const num = Number.parseFloat(value);\n const min = opts.min ?? 0;\n const max = opts.max ?? 100;\n return !Number.isNaN(num) && num >= min && num <= max;\n }\n },\n licensePlate: {\n pattern: \"###-###\",\n transform: (value) => value.replace(REGEX_CACHE.nonAlphaNumeric, \"\").toUpperCase(),\n validate: (value) => REGEX_CACHE.licensePlate.test(value)\n },\n ipv4: {\n pattern: \"###.###.###.###\",\n transform: (value) => value.replace(REGEX_CACHE.nonNumericDot, \"\"),\n validate: (value) => {\n if (value.includes(\".\")) {\n const segments = value.split(\".\");\n if (segments.length > 4) return false;\n\n return segments.every((segment) => {\n if (segment === \"\") return true;\n if (!REGEX_CACHE.ipv4Segment.test(segment)) return false;\n const num = Number.parseInt(segment, 10);\n return num <= 255;\n });\n }\n\n if (!REGEX_CACHE.digitsOnly.test(value)) return false;\n if (value.length > 12) return false;\n\n const chunks = [];\n for (let i = 0; i < value.length; i += 3) {\n chunks.push(value.substring(i, i + 3));\n }\n\n if (chunks.length > 4) return false;\n\n return chunks.every((chunk) => {\n const num = Number.parseInt(chunk, 10);\n return num >= 0 && num <= 255;\n });\n }\n },\n macAddress: {\n pattern: \"##:##:##:##:##:##\",\n transform: (value) => value.replace(REGEX_CACHE.nonAlphaNumeric, \"\").toUpperCase(),\n validate: (value) => REGEX_CACHE.macAddress.test(value)\n },\n isbn: {\n pattern: \"###-#-###-#####-#\",\n transform: (value) => value.replace(REGEX_CACHE.nonDigits, \"\"),\n validate: (value) => REGEX_CACHE.isbn.test(value.replace(REGEX_CACHE.nonDigits, \"\"))\n },\n ein: {\n pattern: \"##-#######\",\n transform: (value) => value.replace(REGEX_CACHE.nonDigits, \"\"),\n validate: (value) => REGEX_CACHE.ein.test(value.replace(REGEX_CACHE.nonDigits, \"\"))\n }\n};\n\nfunction applyMask(opts: {\n value: string;\n pattern: string;\n currency?: string;\n locale?: string;\n mask?: MaskPatternKey | MaskPattern;\n}): string {\n const { value, pattern, currency, locale, mask } = opts;\n\n const cleanValue = value;\n\n if (pattern.includes(\"$\") || pattern.includes(\"€\") || mask === \"currency\") {\n return applyCurrencyMask({\n value: cleanValue,\n currency: currency ?? DEFAULT_CURRENCY,\n locale: locale ?? DEFAULT_LOCALE\n });\n }\n\n if (pattern.includes(\"%\")) {\n return applyPercentageMask(cleanValue);\n }\n\n if (mask === \"ipv4\") {\n return cleanValue;\n }\n\n const maskedChars: string[] = [];\n let valueIndex = 0;\n\n for (let i = 0; i < pattern.length && valueIndex < cleanValue.length; i++) {\n const patternChar = pattern[i];\n const valueChar = cleanValue[valueIndex];\n\n if (patternChar === \"#\" && valueChar) {\n maskedChars.push(valueChar);\n valueIndex++;\n } else if (patternChar) {\n maskedChars.push(patternChar);\n }\n }\n\n return maskedChars.join(\"\");\n}\n\nfunction applyCurrencyMask(opts: { value: string; currency?: string; locale?: string }): string {\n const { value, currency = DEFAULT_CURRENCY, locale = DEFAULT_LOCALE } = opts;\n\n if (!value) return \"\";\n\n const {\n currency: currencySymbol,\n decimal: decimalSeparator,\n group: groupSeparator\n } = getCachedCurrencySymbols({ locale, currency });\n\n const normalizedValue = value\n .replace(new RegExp(`\\\\${groupSeparator.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}`, \"g\"), \"\")\n .replace(decimalSeparator, \".\");\n\n const parts = normalizedValue.split(\".\");\n const integerPart = parts[0] ?? \"\";\n const fractionalPart = parts[1] ?? \"\";\n\n if (!integerPart && !fractionalPart) return \"\";\n\n // ⚠ Khác thượng nguồn: cắt phần lẻ theo số chữ số TỰ NHIÊN của đồng tiền (VND 0) thay vì ghim\n // cứng 2, và đồng tiền không có phần lẻ thì không bao giờ vẽ dấu thập phân treo (\"1.234, ₫\").\n const fractionDigits = getCurrencyFractionDigits({ currency, locale });\n\n const intValue = integerPart ?? \"0\";\n const fracValue = fractionalPart.slice(0, fractionDigits);\n\n const num = Number(`${intValue}.${fracValue ?? \"\"}`);\n\n if (Number.isNaN(num)) {\n const cleanedDigits = value.replace(/[^\\d]/g, \"\");\n if (!cleanedDigits) return \"\";\n return `${currencySymbol}${cleanedDigits}`;\n }\n\n const hasExplicitDecimal = fractionDigits > 0 && (value.includes(\".\") || value.includes(decimalSeparator));\n\n try {\n const formatter = getCachedFormatter(locale, {\n currency,\n minimumFractionDigits: fracValue ? fracValue.length : 0,\n maximumFractionDigits: 2\n });\n const result = formatter.format(num);\n\n if (hasExplicitDecimal && !fracValue) {\n if (result.match(/^[^\\d\\s]+/)) {\n const finalResult = result.replace(/(\\d)$/, `$1${decimalSeparator}`);\n return finalResult;\n }\n\n const finalResult = result.replace(/(\\d)(\\s*)([^\\d\\s]+)$/, `$1${decimalSeparator}$2$3`);\n return finalResult;\n }\n\n return result;\n } catch {\n const formattedInt = intValue.replace(/\\B(?=(\\d{3})+(?!\\d))/g, groupSeparator);\n let result = `${currencySymbol}${formattedInt}`;\n if (hasExplicitDecimal) {\n result += `${decimalSeparator}${fracValue}`;\n }\n\n return result;\n }\n}\n\nfunction applyPercentageMask(value: string): string {\n if (!value) return \"\";\n\n const parts = value.split(\".\");\n let result = parts[0] ?? \"0\";\n\n if (value.includes(\".\")) {\n result += `.${(parts[1] ?? \"\").substring(0, 2)}`;\n }\n\n return `${result}%`;\n}\n\nfunction getUnmaskedValue(opts: {\n value: string;\n currency?: string;\n locale?: string;\n transform?: (value: string, opts?: TransformOptions) => string;\n}): string {\n const { value, transform, currency, locale } = opts;\n\n return transform ? transform(value, { currency, locale }) : value.replace(REGEX_CACHE.nonDigits, \"\");\n}\n\nfunction toUnmaskedIndex(opts: { masked: string; pattern: string; caret: number }): number {\n const { masked, pattern, caret } = opts;\n\n let idx = 0;\n for (let i = 0; i < caret && i < masked.length && i < pattern.length; i++) {\n if (pattern[i] === \"#\") {\n idx++;\n }\n }\n\n return idx;\n}\n\nfunction fromUnmaskedIndex(opts: { masked: string; pattern: string; unmaskedIndex: number }): number {\n const { masked, pattern, unmaskedIndex } = opts;\n\n let seen = 0;\n for (let i = 0; i < masked.length && i < pattern.length; i++) {\n if (pattern[i] === \"#\") {\n seen++;\n if (seen === unmaskedIndex) {\n return i + 1;\n }\n }\n }\n\n return masked.length;\n}\n\nfunction getCurrencyCaretPosition(opts: {\n newValue: string;\n mask: MaskPatternKey | MaskPattern | undefined;\n transformOpts: TransformOptions;\n oldCursorPosition?: number;\n oldValue?: string;\n previousUnmasked?: string;\n}): number {\n const { newValue, mask, transformOpts, oldCursorPosition, oldValue, previousUnmasked } = opts;\n\n if (oldCursorPosition !== undefined && oldValue && previousUnmasked !== undefined) {\n if (oldCursorPosition < oldValue.length) {\n const digitsBeforeCursor = oldValue.substring(0, oldCursorPosition).replace(/\\D/g, \"\").length;\n\n let digitCount = 0;\n for (let i = 0; i < newValue.length; i++) {\n if (/\\d/.test(newValue[i] ?? \"\")) {\n digitCount++;\n if (digitCount === digitsBeforeCursor) {\n return i + 1;\n }\n }\n }\n }\n }\n\n if (mask === \"currency\") {\n const currencyAtEnd = isCurrencyAtEnd(transformOpts);\n if (currencyAtEnd) {\n const match = newValue.match(/(\\d)\\s*([^\\d\\s]+)$/);\n if (match?.[1]) {\n return newValue.lastIndexOf(match[1]) + 1;\n }\n return newValue.length;\n }\n return newValue.length;\n }\n return newValue.length;\n}\n\nfunction getPatternCaretPosition(opts: {\n newValue: string;\n maskPattern: MaskPattern;\n currentUnmasked: string;\n oldCursorPosition?: number;\n oldValue?: string;\n previousUnmasked?: string;\n}): number {\n const { newValue, maskPattern, currentUnmasked, oldCursorPosition, oldValue, previousUnmasked } = opts;\n let position = 0;\n let unmaskedCount = 0;\n\n if (oldCursorPosition !== undefined && oldValue && previousUnmasked !== undefined) {\n const oldUnmaskedIndex = toUnmaskedIndex({\n masked: oldValue,\n pattern: maskPattern.pattern,\n caret: oldCursorPosition\n });\n\n if (oldCursorPosition < oldValue.length) {\n const targetUnmaskedIndex = Math.min(oldUnmaskedIndex, currentUnmasked.length);\n\n for (let i = 0; i < maskPattern.pattern.length && i < newValue.length; i++) {\n if (maskPattern.pattern[i] === \"#\") {\n unmaskedCount++;\n if (unmaskedCount <= targetUnmaskedIndex) {\n position = i + 1;\n }\n }\n }\n\n return position;\n }\n }\n\n for (let i = 0; i < maskPattern.pattern.length && i < newValue.length; i++) {\n if (maskPattern.pattern[i] === \"#\") {\n unmaskedCount++;\n if (unmaskedCount <= currentUnmasked.length) {\n position = i + 1;\n }\n }\n }\n\n return position;\n}\n\n/*\n * ⚠ Khối export này trộn HAI nhóm (biome gộp chúng lại theo a-z):\n * - CÔNG KHAI — đúng các tên thượng nguồn xuất ra: `applyCurrencyMask`, `applyMask`,\n * `applyPercentageMask`, `fromUnmaskedIndex`, `getUnmaskedValue`, `MASK_PATTERNS`,\n * `MaskPattern`, `toUnmaskedIndex`. Barrel `src/index.ts` liệt kê TƯỜNG MINH đúng các tên này.\n * - NỘI BỘ — mọi tên còn lại, chỉ cho `mask-input.tsx`. Đừng đổi dòng barrel thành `export *`:\n * `DEFAULT_CURRENCY`, `REGEX_CACHE`… lọt ra entry chính là mời đụng tên và khoá cứng API.\n */\nexport {\n applyCurrencyMask,\n applyMask,\n applyPercentageMask,\n CURRENCY_PERCENTAGE_SYMBOLS,\n DEFAULT_CURRENCY,\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 type TransformOptions,\n toUnmaskedIndex\n};\n"],"mappings":"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","names":["result","finalResult"]}
1
+ {"version":3,"sources":["../../../src/components/ui/mask-input-constants.ts"],"sourcesContent":["/*\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 * Lõi THUẦN của `MaskInput`: bảng mẫu `MASK_PATTERNS` và các hàm định dạng/bóc mặt nạ.\n *\n * File này là NỬA TRÊN của `mask-input.tsx` thượng nguồn (mọi thứ đứng trước component), tách ra\n * nguyên khối, đúng thứ tự, để còn diff ngược được. Nó **CỐ Ý không có `\"use client\"`**: mọi\n * export của một file `\"use client\"` tới Server Component là client-reference proxy — `MASK_PATTERNS`\n * sẽ đọc ra `undefined` mà không throw — và `applyCurrencyMask` là thứ trang server hay cần nhất\n * (in số tiền ra bảng, email, PDF) nên phải chạy được ở cả hai phía.\n *\n * ⚠ Barrel `src/index.ts` xuất module này TRỰC TIẾP, và CHỌN LỌC (không `export *`): ngoài các tên\n * công khai của thượng nguồn, file còn export vài helper NỘI BỘ cho riêng `mask-input.tsx`\n * (liệt kê ở khối export cuối file). `mask-input.tsx` chỉ `import`, KHÔNG `export` lại.\n */\n\nconst PAST_YEARS_LIMIT = 120;\nconst FUTURE_YEARS_LIMIT = 10;\n/*\n * \"USD\" / \"en-US\" như thượng nguồn — mặc định của các hàm THUẦN ở đây (gọi thẳng ở server, email,\n * PDF). Tiền tệ là thiết lập của VÙNG / nghiệp vụ, không phải của ngôn ngữ (xem\n * `src/i18n/locale.ts`): app Comitor truyền `currency` từ cài đặt workspace. Component `MaskInput`\n * dùng `DEFAULT_CURRENCY` nhưng lấy `locale` mặc định từ `ComitorLocaleProvider`, không từ hằng\n * `DEFAULT_LOCALE`. Trước `3.0.0` hai hằng là \"VND\" / \"vi-VN\".\n */\nconst DEFAULT_CURRENCY = \"USD\";\nconst DEFAULT_LOCALE = \"en-US\";\n\nconst NUMERIC_MASK_PATTERNS = /^(phone|zipCode|zipCodeExtended|ssn|ein|time|date|creditCard|creditCardExpiry)$/;\nconst CURRENCY_PERCENTAGE_SYMBOLS = /[€$%]/;\n\ninterface CurrencySymbols {\n currency: string;\n decimal: string;\n group: string;\n}\n\nconst formattersCache = new Map<string, Intl.NumberFormat>();\nconst currencyAtEndCache = new Map<string, boolean>();\nconst currencySymbolsCache = new Map<string, CurrencySymbols>();\nconst fractionDigitsCache = new Map<string, number>();\nconst daysInMonthCache = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] as const;\n\nconst REGEX_CACHE = {\n digitsOnly: /^\\d+$/,\n nonDigits: /\\D/g,\n nonAlphaNumeric: /[^A-Z0-9]/gi,\n nonNumericDot: /[^0-9.]/g,\n nonCurrencyChars: /[^\\d.,]/g,\n hashPattern: /#/g,\n currencyAtEnd: /\\d\\s*[^\\d\\s]+$/,\n percentageChars: /[^\\d.]/g,\n phone: /^\\d{10}$/,\n ssn: /^\\d{9}$/,\n zipCode: /^\\d{5}$/,\n zipCodeExtended: /^\\d{9}$/,\n isbn: /^\\d{13}$/,\n ein: /^\\d{9}$/,\n time: /^\\d{4}$/,\n creditCard: /^\\d{13,19}$/,\n creditCardExpiry: /^\\d{4}$/,\n licensePlate: /^[A-Z0-9]{6}$/,\n macAddress: /^[A-F0-9]{12}$/,\n currencyValidation: /^\\d+(\\.\\d{1,2})?$/,\n ipv4Segment: /^\\d{1,3}$/\n} as const;\n\nfunction getCachedFormatter(locale: string | undefined, opts: Intl.NumberFormatOptions): Intl.NumberFormat {\n const { currency, minimumFractionDigits = 0, maximumFractionDigits = 2 } = opts;\n\n const key = `${locale}|${currency}|${minimumFractionDigits}|${maximumFractionDigits}`;\n\n if (!formattersCache.has(key)) {\n try {\n formattersCache.set(\n key,\n new Intl.NumberFormat(locale, {\n style: \"currency\",\n currency,\n ...opts\n })\n );\n } catch {\n formattersCache.set(\n key,\n new Intl.NumberFormat(DEFAULT_LOCALE, {\n style: \"currency\",\n currency: DEFAULT_CURRENCY,\n ...opts\n })\n );\n }\n }\n\n const formatter = formattersCache.get(key);\n if (!formatter) {\n throw new Error(`Không tạo được bộ định dạng tiền tệ cho ${key}`);\n }\n return formatter;\n}\n\nfunction getCachedCurrencySymbols(opts: TransformOptions): CurrencySymbols {\n const { locale, currency } = opts;\n\n const key = `${locale}|${currency}`;\n const cached = currencySymbolsCache.get(key);\n if (cached) {\n return cached;\n }\n\n let currencySymbol = \"$\";\n let decimalSeparator = \".\";\n let groupSeparator = \",\";\n\n try {\n const formatter = getCachedFormatter(locale, {\n currency,\n minimumFractionDigits: 0,\n maximumFractionDigits: 2\n });\n const parts = formatter.formatToParts(1234.5);\n const currencyPart = parts.find((part) => part.type === \"currency\");\n const decimalPart = parts.find((part) => part.type === \"decimal\");\n const groupPart = parts.find((part) => part.type === \"group\");\n\n if (currencyPart) currencySymbol = currencyPart.value;\n if (decimalPart) decimalSeparator = decimalPart.value;\n if (groupPart) groupSeparator = groupPart.value;\n } catch {\n // Giữ giá trị mặc định.\n }\n\n const symbols: CurrencySymbols = {\n currency: currencySymbol,\n decimal: decimalSeparator,\n group: groupSeparator\n };\n currencySymbolsCache.set(key, symbols);\n return symbols;\n}\n\n/**\n * Số chữ số thập phân TỰ NHIÊN của đồng tiền theo ISO 4217 / CLDR: VND 0, JPY 0, USD 2, EUR 2.\n * Chặn trần ở 2 vì phần còn lại của logic (validate, cắt phần lẻ) chỉ hiểu tối đa 2 chữ số.\n *\n * ⚠ Khác thượng nguồn — hàm MỚI. Thượng nguồn ghim cứng `maximumFractionDigits: 2` cho mọi đồng\n * tiền, và với locale dùng `,` làm dấu thập phân (de-DE, vi-VN) thì `transform` còn đoán \"chữ số\n * gõ sau một nhóm nghìn là phần lẻ\". Với VND, đo trên chính mã thượng nguồn, gõ lần lượt\n * `1234567` ra:\n *\n * 1 ₫ → 12 ₫ → 123 ₫ → 1.234 ₫ → 1.234,5 ₫ → 1.234,56 ₫ → 1.234.567 ₫\n *\n * tức chữ số thứ 5 và 6 biến thành HÀO, và giá trị thô báo ra `onValueChange` là \"1234.5\" trong\n * khi người dùng đang gõ 12.345 ₫. Đồng tiền không có phần lẻ thì mọi `.`/`,` đều là dấu nhóm.\n */\nfunction getCurrencyFractionDigits(opts: TransformOptions): number {\n const { locale, currency } = opts;\n\n const key = `${locale}|${currency}`;\n const cached = fractionDigitsCache.get(key);\n if (cached !== undefined) {\n return cached;\n }\n\n let digits = 2;\n try {\n const resolved = new Intl.NumberFormat(locale, { style: \"currency\", currency }).resolvedOptions();\n digits = Math.min(2, resolved.maximumFractionDigits ?? 2);\n } catch {\n // Đồng tiền/locale không hợp lệ → giữ 2 như thượng nguồn.\n }\n\n fractionDigitsCache.set(key, digits);\n return digits;\n}\n\n/**\n * Vị trí dấu THẬP PHÂN trong chuỗi đã lọc (chỉ còn chữ số, `.` và `,`), hoặc `-1` nếu không có.\n *\n * Chuỗi tới đây là một trong hai thứ: chuỗi mặt nạ tự định dạng cộng MỘT lần sửa (gõ / xoá một ký\n * tự), hoặc chữ người dùng dán vào. Luật, theo thứ tự:\n * 1. Có cả `.` lẫn `,` → dấu xuất hiện SAU CÙNG là thập phân. Dán `1,234.56` hay `1.234,56` vào ô\n * nào cũng ra 1234.56; chuỗi tự định dạng `1.234,5` (vi-VN) cũng khớp luật này.\n * 2. Chỉ có dấu thập phân của locale → lần xuất hiện cuối.\n * 3. Chỉ có dấu NHÓM của locale → là dấu nhóm, KHÔNG BAO GIỜ là phần lẻ. Đây là luật sửa lỗi:\n * sau một lần xoá, `$123,45` vẫn là 12345 chứ không phải 123,45.\n * Ngoại lệ duy nhất: dấu `.` đứng CUỐI ở locale dùng `,` làm thập phân. Chuỗi tự định dạng không\n * bao giờ kết thúc bằng dấu nhóm, nên `.` ở cuối chỉ có thể là người dùng vừa bấm phím `.` của\n * bàn phím số — ý họ là dấu thập phân. Ở locale dùng `.` làm thập phân thì `,` ở cuối vẫn là\n * người gõ tay dấu nhóm, không đoán.\n * 4. Dấu KHÔNG thuộc locale (vd `.` ở fr-FR, nơi dấu nhóm là khoảng trắng hẹp): xuất hiện một lần\n * và không có đúng 3 chữ số theo sau → thập phân; còn lại (`1.234`, `1.234.567`) → nhóm.\n *\n * ⚠ Cái giá có chủ ý: dán `1234,5` (kiểu Việt) vào ô en-US ra 12.345 — `,` là dấu nhóm của en-US.\n * Kết quả HIỆN RA ngay trong ô nên người dùng thấy và sửa được; còn đoán theo kiểu cũ thì chính lúc\n * sửa số (thao tác thường gặp nhất) lại âm thầm đổi giá trị. Chiều ngược lại (`1234.5` vào ô vi-VN)\n * thì đúng — đó là dạng chuẩn, xem `isCanonicalAmount`.\n */\nfunction findCurrencyDecimalIndex(cleaned: string, decimal: string, group: string): number {\n const lastDot = cleaned.lastIndexOf(\".\");\n const lastComma = cleaned.lastIndexOf(\",\");\n\n if (lastDot !== -1 && lastComma !== -1) return Math.max(lastDot, lastComma);\n\n const separator = lastDot !== -1 ? \".\" : lastComma !== -1 ? \",\" : null;\n if (!separator) return -1;\n\n const last = cleaned.lastIndexOf(separator);\n\n if (separator === decimal) return last;\n\n if (separator === group) {\n return separator === \".\" && decimal === \",\" && last === cleaned.length - 1 ? last : -1;\n }\n\n const occurrences = cleaned.split(separator).length - 1;\n const digitsAfter = cleaned.length - last - 1;\n return occurrences === 1 && digitsAfter !== 3 ? last : -1;\n}\n\n/**\n * Chuỗi đã ở DẠNG CHUẨN — đúng dạng vế thứ hai của `onValueChange` (`\"12345.56\"`, `\"12.\"`,\n * `\"12345\"`): chỉ chữ số, nhiều nhất một `.`, phần sau `.` không dài hơn số lẻ của đồng tiền.\n *\n * Vì sao cần (3.0.1): `transform` đọc cả chuỗi HIỂN THỊ lẫn giá trị app trả về qua `value`. App\n * controlled thường lưu đúng số thô rồi trả lại (`value={amount}`) — và ở locale dùng `.` làm dấu\n * NHÓM (vi-VN, de-DE…), luật 3 của `findCurrencyDecimalIndex` đọc \"12345.56\" thành 1.234.556.\n * Dạng chuẩn không nhập nhằng với chuỗi hiển thị: chuỗi hiển thị luôn mang ký hiệu tiền tệ, còn\n * chuỗi trần có `.` rồi ĐÚNG 3 chữ số (\"12.345\" — một nhóm nghìn) thì dài hơn phần lẻ (tối đa 2\n * chữ số, xem `getCurrencyFractionDigits`) nên không lọt vào đây.\n */\nfunction isCanonicalAmount(value: string, fractionDigits: number): boolean {\n const dot = value.indexOf(\".\");\n const integerPart = dot === -1 ? value : value.slice(0, dot);\n const fractionPart = dot === -1 ? \"\" : value.slice(dot + 1);\n return /^\\d*$/.test(integerPart) && /^\\d*$/.test(fractionPart) && fractionPart.length <= fractionDigits;\n}\n\nfunction isCurrencyAtEnd(opts: TransformOptions): boolean {\n const { locale, currency } = opts;\n\n const key = `${locale}|${currency}`;\n const cached = currencyAtEndCache.get(key);\n if (cached !== undefined) {\n return cached;\n }\n\n try {\n const formatter = getCachedFormatter(locale, {\n currency,\n minimumFractionDigits: 0,\n maximumFractionDigits: 0\n });\n const sample = formatter.format(123);\n const result = REGEX_CACHE.currencyAtEnd.test(sample);\n currencyAtEndCache.set(key, result);\n return result;\n } catch {\n currencyAtEndCache.set(key, false);\n return false;\n }\n}\n\nfunction isCurrencyMask(opts: { mask: MaskPatternKey | MaskPattern | undefined; pattern?: string }): boolean {\n const { mask, pattern } = opts;\n\n return mask === \"currency\" || Boolean(pattern && (pattern.includes(\"$\") || pattern.includes(\"€\")));\n}\n\ninterface TransformOptions {\n currency?: string;\n locale?: string;\n}\n\ninterface ValidateOptions {\n min?: number;\n max?: number;\n}\n\ninterface MaskPattern {\n pattern: string;\n transform?: (value: string, opts?: TransformOptions) => string;\n validate?: (value: string, opts?: ValidateOptions) => boolean;\n}\n\ntype MaskPatternKey =\n | \"phone\"\n | \"ssn\"\n | \"date\"\n | \"time\"\n | \"creditCard\"\n | \"creditCardExpiry\"\n | \"zipCode\"\n | \"zipCodeExtended\"\n | \"currency\"\n | \"percentage\"\n | \"licensePlate\"\n | \"ipv4\"\n | \"macAddress\"\n | \"isbn\"\n | \"ein\";\n\n/**\n * Các mẫu dựng sẵn — giữ NGUYÊN bộ của thượng nguồn, không thêm không bớt. Nhiều mẫu là định dạng\n * Hoa Kỳ (`phone`, `ssn`, `date`, `zipCode`, `zipCodeExtended`, `ein`); app Comitor cần định dạng\n * khác thì truyền một `MaskPattern` riêng vào prop `mask`.\n */\nconst MASK_PATTERNS: Record<MaskPatternKey, MaskPattern> = {\n /**\n * Số điện thoại **Hoa Kỳ** `(###) ###-####`, đúng 10 chữ số.\n *\n * ⚠ Không dùng cho số điện thoại ở app Comitor: số Việt Nam (và mọi số quốc tế) đi qua\n * `PhoneInput` — có chọn quốc gia, tự nhận mã vùng và lưu dạng `+84…`.\n */\n phone: {\n pattern: \"(###) ###-####\",\n transform: (value) => value.replace(REGEX_CACHE.nonDigits, \"\"),\n validate: (value) => REGEX_CACHE.phone.test(value.replace(REGEX_CACHE.nonDigits, \"\"))\n },\n ssn: {\n pattern: \"###-##-####\",\n transform: (value) => value.replace(REGEX_CACHE.nonDigits, \"\"),\n validate: (value) => REGEX_CACHE.ssn.test(value.replace(REGEX_CACHE.nonDigits, \"\"))\n },\n /**\n * Ngày kiểu **Hoa Kỳ** `MM/DD/YYYY` — `validate` đọc 2 chữ số ĐẦU là THÁNG. Ngày kiểu Việt Nam\n * (`dd/MM/yyyy`) thì dùng `DatePicker` của gói, hoặc một `MaskPattern` riêng.\n */\n date: {\n pattern: \"##/##/####\",\n transform: (value) => value.replace(REGEX_CACHE.nonDigits, \"\"),\n validate: (value) => {\n const cleaned = value.replace(REGEX_CACHE.nonDigits, \"\");\n if (cleaned.length !== 8) return false;\n const month = Number.parseInt(cleaned.substring(0, 2), 10);\n const day = Number.parseInt(cleaned.substring(2, 4), 10);\n const year = Number.parseInt(cleaned.substring(4, 8), 10);\n\n const currentYear = new Date().getFullYear();\n const minYear = currentYear - PAST_YEARS_LIMIT;\n const maxYear = currentYear + FUTURE_YEARS_LIMIT;\n if (month < 1 || month > 12 || day < 1 || year < minYear || year > maxYear) return false;\n\n const maxDays =\n month === 2 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0)\n ? 29\n : (daysInMonthCache[month - 1] ?? 31);\n\n return day <= maxDays;\n }\n },\n time: {\n pattern: \"##:##\",\n transform: (value) => value.replace(REGEX_CACHE.nonDigits, \"\"),\n validate: (value) => {\n const cleaned = value.replace(REGEX_CACHE.nonDigits, \"\");\n if (!REGEX_CACHE.time.test(cleaned)) return false;\n const hours = Number.parseInt(cleaned.substring(0, 2), 10);\n const minutes = Number.parseInt(cleaned.substring(2, 4), 10);\n return hours <= 23 && minutes <= 59;\n }\n },\n creditCard: {\n pattern: \"#### #### #### ####\",\n transform: (value) => value.replace(REGEX_CACHE.nonDigits, \"\"),\n validate: (value) => {\n const cleaned = value.replace(REGEX_CACHE.nonDigits, \"\");\n if (!REGEX_CACHE.creditCard.test(cleaned)) return false;\n\n let sum = 0;\n let isEven = false;\n for (let i = cleaned.length - 1; i >= 0; i--) {\n const digitChar = cleaned[i];\n if (!digitChar) continue;\n let digit = Number.parseInt(digitChar, 10);\n if (isEven) {\n digit *= 2;\n if (digit > 9) {\n digit -= 9;\n }\n }\n sum += digit;\n isEven = !isEven;\n }\n return sum % 10 === 0;\n }\n },\n creditCardExpiry: {\n pattern: \"##/##\",\n transform: (value) => value.replace(REGEX_CACHE.nonDigits, \"\"),\n validate: (value) => {\n const cleaned = value.replace(REGEX_CACHE.nonDigits, \"\");\n if (!REGEX_CACHE.creditCardExpiry.test(cleaned)) return false;\n\n const month = Number.parseInt(cleaned.substring(0, 2), 10);\n const year = Number.parseInt(cleaned.substring(2, 4), 10);\n\n if (month < 1 || month > 12) return false;\n\n const now = new Date();\n const currentYear = now.getFullYear();\n const currentMonth = now.getMonth() + 1;\n\n const fullYear = year <= 75 ? 2000 + year : 1900 + year;\n\n if (fullYear < currentYear || (fullYear === currentYear && month < currentMonth)) {\n return false;\n }\n\n const maxYear = currentYear + 50;\n if (fullYear > maxYear) {\n return false;\n }\n\n return true;\n }\n },\n zipCode: {\n pattern: \"#####\",\n transform: (value) => value.replace(REGEX_CACHE.nonDigits, \"\"),\n validate: (value) => REGEX_CACHE.zipCode.test(value.replace(REGEX_CACHE.nonDigits, \"\"))\n },\n zipCodeExtended: {\n pattern: \"#####-####\",\n transform: (value) => value.replace(REGEX_CACHE.nonDigits, \"\"),\n validate: (value) => REGEX_CACHE.zipCodeExtended.test(value.replace(REGEX_CACHE.nonDigits, \"\"))\n },\n /**\n * Tiền tệ — định dạng thật đi qua `Intl.NumberFormat` theo `currency`/`locale` (mặc định USD /\n * en-US → `$1,234,567`; `VND` / `vi-VN` → `1.234.567 ₫`). Chuỗi `pattern` ở đây chỉ là DẤU NHẬN\n * DẠNG cho nhánh tiền tệ, không phải khuôn chữ số.\n */\n currency: {\n pattern: \"$###,###.##\",\n transform: (value, { currency = DEFAULT_CURRENCY, locale = DEFAULT_LOCALE } = {}) => {\n // ⚠ Khác thượng nguồn: đồng tiền không có phần lẻ (VND, JPY) → mọi `.`/`,` là dấu nhóm,\n // giá trị thô chỉ còn chữ số. Lý do: xem `getCurrencyFractionDigits`.\n const fractionDigits = getCurrencyFractionDigits({ currency, locale });\n if (fractionDigits === 0) {\n return value.replace(REGEX_CACHE.nonDigits, \"\");\n }\n\n /*\n * ⚠ Khác thượng nguồn (3.0.1): thay cả khối đoán \"dấu có ≤ 2 chữ số theo sau là phần lẻ\" bằng\n * `findCurrencyDecimalIndex`. Khối đó không phân biệt được dấu NHÓM do chính mặt nạ chèn vào\n * với dấu thập phân người dùng gõ, và hỏng ở cả hai phía — đo bằng chính helper này:\n * · en-US (mặc định của gói): xoá một chữ số khỏi `$123,456` → `$123.45` (123,45 đô) thay vì\n * `$12,345`, vì `,` còn lại có đúng 2 chữ số theo sau.\n * · vi-VN / de-DE với USD, EUR: gõ thẳng `12345` → `1.234,5` (giá trị thô \"1234.5\"), gõ thêm\n * thì nhảy về `1.234.567` — số người dùng thấy đổi nghĩa giữa chừng.\n * Bản `2.1.0` chỉ vá được nhánh không phần lẻ (VND) ở trên; nhánh có phần lẻ vẫn đoán.\n */\n if (isCanonicalAmount(value, fractionDigits)) return value;\n\n const { decimal, group } = getCachedCurrencySymbols({ currency, locale });\n const cleaned = value.replace(REGEX_CACHE.nonCurrencyChars, \"\");\n const decimalIndex = findCurrencyDecimalIndex(cleaned, decimal, group);\n\n if (decimalIndex === -1) {\n return cleaned.replace(/[.,]/g, \"\");\n }\n\n const integerPart = cleaned.substring(0, decimalIndex).replace(/[.,]/g, \"\");\n const fractionPart = cleaned\n .substring(decimalIndex + 1)\n .replace(/[.,]/g, \"\")\n .substring(0, fractionDigits);\n\n // Phần lẻ rỗng vẫn giữ dấu chấm (\"12.\") để ô còn hiện dấu thập phân vừa gõ.\n return `${integerPart}.${fractionPart}`;\n },\n validate: (value) => {\n if (!REGEX_CACHE.currencyValidation.test(value)) return false;\n const num = Number.parseFloat(value);\n return !Number.isNaN(num) && num >= 0;\n }\n },\n /**\n * Phần trăm `##.##%` — dấu thập phân là `.` cố định, KHÔNG theo `locale` (dấu `,` bị bỏ qua).\n */\n percentage: {\n pattern: \"##.##%\",\n transform: (value) => {\n const cleaned = value.replace(REGEX_CACHE.percentageChars, \"\");\n const parts = cleaned.split(\".\");\n if (parts.length > 2) {\n return `${parts[0]}.${parts.slice(1).join(\"\")}`;\n }\n if (parts[1] && parts[1].length > 2) {\n return `${parts[0]}.${parts[1].substring(0, 2)}`;\n }\n return cleaned;\n },\n validate: (value, opts = {}) => {\n const num = Number.parseFloat(value);\n const min = opts.min ?? 0;\n const max = opts.max ?? 100;\n return !Number.isNaN(num) && num >= min && num <= max;\n }\n },\n licensePlate: {\n pattern: \"###-###\",\n transform: (value) => value.replace(REGEX_CACHE.nonAlphaNumeric, \"\").toUpperCase(),\n validate: (value) => REGEX_CACHE.licensePlate.test(value)\n },\n ipv4: {\n pattern: \"###.###.###.###\",\n transform: (value) => value.replace(REGEX_CACHE.nonNumericDot, \"\"),\n validate: (value) => {\n if (value.includes(\".\")) {\n const segments = value.split(\".\");\n if (segments.length > 4) return false;\n\n return segments.every((segment) => {\n if (segment === \"\") return true;\n if (!REGEX_CACHE.ipv4Segment.test(segment)) return false;\n const num = Number.parseInt(segment, 10);\n return num <= 255;\n });\n }\n\n if (!REGEX_CACHE.digitsOnly.test(value)) return false;\n if (value.length > 12) return false;\n\n const chunks = [];\n for (let i = 0; i < value.length; i += 3) {\n chunks.push(value.substring(i, i + 3));\n }\n\n if (chunks.length > 4) return false;\n\n return chunks.every((chunk) => {\n const num = Number.parseInt(chunk, 10);\n return num >= 0 && num <= 255;\n });\n }\n },\n macAddress: {\n pattern: \"##:##:##:##:##:##\",\n transform: (value) => value.replace(REGEX_CACHE.nonAlphaNumeric, \"\").toUpperCase(),\n validate: (value) => REGEX_CACHE.macAddress.test(value)\n },\n isbn: {\n pattern: \"###-#-###-#####-#\",\n transform: (value) => value.replace(REGEX_CACHE.nonDigits, \"\"),\n validate: (value) => REGEX_CACHE.isbn.test(value.replace(REGEX_CACHE.nonDigits, \"\"))\n },\n ein: {\n pattern: \"##-#######\",\n transform: (value) => value.replace(REGEX_CACHE.nonDigits, \"\"),\n validate: (value) => REGEX_CACHE.ein.test(value.replace(REGEX_CACHE.nonDigits, \"\"))\n }\n};\n\nfunction applyMask(opts: {\n value: string;\n pattern: string;\n currency?: string;\n locale?: string;\n mask?: MaskPatternKey | MaskPattern;\n}): string {\n const { value, pattern, currency, locale, mask } = opts;\n\n const cleanValue = value;\n\n if (pattern.includes(\"$\") || pattern.includes(\"€\") || mask === \"currency\") {\n return applyCurrencyMask({\n value: cleanValue,\n currency: currency ?? DEFAULT_CURRENCY,\n locale: locale ?? DEFAULT_LOCALE\n });\n }\n\n if (pattern.includes(\"%\")) {\n return applyPercentageMask(cleanValue);\n }\n\n if (mask === \"ipv4\") {\n return cleanValue;\n }\n\n const maskedChars: string[] = [];\n let valueIndex = 0;\n\n for (let i = 0; i < pattern.length && valueIndex < cleanValue.length; i++) {\n const patternChar = pattern[i];\n const valueChar = cleanValue[valueIndex];\n\n if (patternChar === \"#\" && valueChar) {\n maskedChars.push(valueChar);\n valueIndex++;\n } else if (patternChar) {\n maskedChars.push(patternChar);\n }\n }\n\n return maskedChars.join(\"\");\n}\n\nfunction applyCurrencyMask(opts: { value: string; currency?: string; locale?: string }): string {\n const { value, currency = DEFAULT_CURRENCY, locale = DEFAULT_LOCALE } = opts;\n\n if (!value) return \"\";\n\n const {\n currency: currencySymbol,\n decimal: decimalSeparator,\n group: groupSeparator\n } = getCachedCurrencySymbols({ locale, currency });\n\n const normalizedValue = value\n .replace(new RegExp(`\\\\${groupSeparator.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}`, \"g\"), \"\")\n .replace(decimalSeparator, \".\");\n\n const parts = normalizedValue.split(\".\");\n const integerPart = parts[0] ?? \"\";\n const fractionalPart = parts[1] ?? \"\";\n\n if (!integerPart && !fractionalPart) return \"\";\n\n // ⚠ Khác thượng nguồn: cắt phần lẻ theo số chữ số TỰ NHIÊN của đồng tiền (VND 0) thay vì ghim\n // cứng 2, và đồng tiền không có phần lẻ thì không bao giờ vẽ dấu thập phân treo (\"1.234, ₫\").\n const fractionDigits = getCurrencyFractionDigits({ currency, locale });\n\n const intValue = integerPart ?? \"0\";\n const fracValue = fractionalPart.slice(0, fractionDigits);\n\n const num = Number(`${intValue}.${fracValue ?? \"\"}`);\n\n if (Number.isNaN(num)) {\n const cleanedDigits = value.replace(/[^\\d]/g, \"\");\n if (!cleanedDigits) return \"\";\n return `${currencySymbol}${cleanedDigits}`;\n }\n\n const hasExplicitDecimal = fractionDigits > 0 && (value.includes(\".\") || value.includes(decimalSeparator));\n\n try {\n const formatter = getCachedFormatter(locale, {\n currency,\n minimumFractionDigits: fracValue ? fracValue.length : 0,\n maximumFractionDigits: 2\n });\n const result = formatter.format(num);\n\n if (hasExplicitDecimal && !fracValue) {\n if (result.match(/^[^\\d\\s]+/)) {\n const finalResult = result.replace(/(\\d)$/, `$1${decimalSeparator}`);\n return finalResult;\n }\n\n const finalResult = result.replace(/(\\d)(\\s*)([^\\d\\s]+)$/, `$1${decimalSeparator}$2$3`);\n return finalResult;\n }\n\n return result;\n } catch {\n const formattedInt = intValue.replace(/\\B(?=(\\d{3})+(?!\\d))/g, groupSeparator);\n let result = `${currencySymbol}${formattedInt}`;\n if (hasExplicitDecimal) {\n result += `${decimalSeparator}${fracValue}`;\n }\n\n return result;\n }\n}\n\nfunction applyPercentageMask(value: string): string {\n if (!value) return \"\";\n\n const parts = value.split(\".\");\n let result = parts[0] ?? \"0\";\n\n if (value.includes(\".\")) {\n result += `.${(parts[1] ?? \"\").substring(0, 2)}`;\n }\n\n return `${result}%`;\n}\n\nfunction getUnmaskedValue(opts: {\n value: string;\n currency?: string;\n locale?: string;\n transform?: (value: string, opts?: TransformOptions) => string;\n}): string {\n const { value, transform, currency, locale } = opts;\n\n return transform ? transform(value, { currency, locale }) : value.replace(REGEX_CACHE.nonDigits, \"\");\n}\n\nfunction toUnmaskedIndex(opts: { masked: string; pattern: string; caret: number }): number {\n const { masked, pattern, caret } = opts;\n\n let idx = 0;\n for (let i = 0; i < caret && i < masked.length && i < pattern.length; i++) {\n if (pattern[i] === \"#\") {\n idx++;\n }\n }\n\n return idx;\n}\n\nfunction fromUnmaskedIndex(opts: { masked: string; pattern: string; unmaskedIndex: number }): number {\n const { masked, pattern, unmaskedIndex } = opts;\n\n let seen = 0;\n for (let i = 0; i < masked.length && i < pattern.length; i++) {\n if (pattern[i] === \"#\") {\n seen++;\n if (seen === unmaskedIndex) {\n return i + 1;\n }\n }\n }\n\n return masked.length;\n}\n\nfunction getCurrencyCaretPosition(opts: {\n newValue: string;\n mask: MaskPatternKey | MaskPattern | undefined;\n transformOpts: TransformOptions;\n oldCursorPosition?: number;\n oldValue?: string;\n previousUnmasked?: string;\n}): number {\n const { newValue, mask, transformOpts, oldCursorPosition, oldValue, previousUnmasked } = opts;\n\n if (oldCursorPosition !== undefined && oldValue && previousUnmasked !== undefined) {\n if (oldCursorPosition < oldValue.length) {\n const digitsBeforeCursor = oldValue.substring(0, oldCursorPosition).replace(/\\D/g, \"\").length;\n\n let digitCount = 0;\n for (let i = 0; i < newValue.length; i++) {\n if (/\\d/.test(newValue[i] ?? \"\")) {\n digitCount++;\n if (digitCount === digitsBeforeCursor) {\n return i + 1;\n }\n }\n }\n }\n }\n\n if (mask === \"currency\") {\n const currencyAtEnd = isCurrencyAtEnd(transformOpts);\n if (currencyAtEnd) {\n const match = newValue.match(/(\\d)\\s*([^\\d\\s]+)$/);\n if (match?.[1]) {\n return newValue.lastIndexOf(match[1]) + 1;\n }\n return newValue.length;\n }\n return newValue.length;\n }\n return newValue.length;\n}\n\nfunction getPatternCaretPosition(opts: {\n newValue: string;\n maskPattern: MaskPattern;\n currentUnmasked: string;\n oldCursorPosition?: number;\n oldValue?: string;\n previousUnmasked?: string;\n}): number {\n const { newValue, maskPattern, currentUnmasked, oldCursorPosition, oldValue, previousUnmasked } = opts;\n let position = 0;\n let unmaskedCount = 0;\n\n if (oldCursorPosition !== undefined && oldValue && previousUnmasked !== undefined) {\n const oldUnmaskedIndex = toUnmaskedIndex({\n masked: oldValue,\n pattern: maskPattern.pattern,\n caret: oldCursorPosition\n });\n\n if (oldCursorPosition < oldValue.length) {\n const targetUnmaskedIndex = Math.min(oldUnmaskedIndex, currentUnmasked.length);\n\n for (let i = 0; i < maskPattern.pattern.length && i < newValue.length; i++) {\n if (maskPattern.pattern[i] === \"#\") {\n unmaskedCount++;\n if (unmaskedCount <= targetUnmaskedIndex) {\n position = i + 1;\n }\n }\n }\n\n return position;\n }\n }\n\n for (let i = 0; i < maskPattern.pattern.length && i < newValue.length; i++) {\n if (maskPattern.pattern[i] === \"#\") {\n unmaskedCount++;\n if (unmaskedCount <= currentUnmasked.length) {\n position = i + 1;\n }\n }\n }\n\n return position;\n}\n\n/*\n * ⚠ Khối export này trộn HAI nhóm (biome gộp chúng lại theo a-z):\n * - CÔNG KHAI — đúng các tên thượng nguồn xuất ra: `applyCurrencyMask`, `applyMask`,\n * `applyPercentageMask`, `fromUnmaskedIndex`, `getUnmaskedValue`, `MASK_PATTERNS`,\n * `MaskPattern`, `toUnmaskedIndex`. Barrel `src/index.ts` liệt kê TƯỜNG MINH đúng các tên này.\n * - NỘI BỘ — mọi tên còn lại, chỉ cho `mask-input.tsx`. Đừng đổi dòng barrel thành `export *`:\n * `DEFAULT_CURRENCY`, `REGEX_CACHE`… lọt ra entry chính là mời đụng tên và khoá cứng API.\n */\nexport {\n applyCurrencyMask,\n applyMask,\n applyPercentageMask,\n CURRENCY_PERCENTAGE_SYMBOLS,\n DEFAULT_CURRENCY,\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 type TransformOptions,\n toUnmaskedIndex\n};\n"],"mappings":"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","names":["finalResult"]}
@@ -359,31 +359,29 @@ function MaskInput(props) {
359
359
  mask
360
360
  });
361
361
  target.value = newMaskedValue;
362
+ let newCursorPosition = newMaskedValue.length;
362
363
  if (isCurrencyMask({ mask, pattern: maskPattern.pattern })) {
363
364
  const currencyAtEnd = isCurrencyAtEnd(transformOpts);
364
- const caret = currencyAtEnd ? newMaskedValue.search(/\s*[^\d\s]+$/) : newMaskedValue.length;
365
- target.setSelectionRange(caret, caret);
366
- return;
367
- }
368
- if (maskPattern.pattern.includes("%")) {
369
- target.setSelectionRange(newMaskedValue.length - 1, newMaskedValue.length - 1);
370
- return;
371
- }
372
- let newCursorPosition = newMaskedValue.length;
373
- try {
374
- const unmaskedCount = unmasked.length;
375
- let position = 0;
376
- let count = 0;
377
- for (let i = 0; i < maskPattern.pattern.length && i < newMaskedValue.length; i++) {
378
- if (maskPattern.pattern[i] === "#") {
379
- count++;
380
- if (count <= unmaskedCount) {
381
- position = i + 1;
365
+ const symbolStart = newMaskedValue.search(/\s*[^\d\s]+$/);
366
+ newCursorPosition = currencyAtEnd && symbolStart !== -1 ? symbolStart : newMaskedValue.length;
367
+ } else if (maskPattern.pattern.includes("%")) {
368
+ newCursorPosition = Math.max(0, newMaskedValue.length - 1);
369
+ } else {
370
+ try {
371
+ const unmaskedCount = unmasked.length;
372
+ let position = 0;
373
+ let count = 0;
374
+ for (let i = 0; i < maskPattern.pattern.length && i < newMaskedValue.length; i++) {
375
+ if (maskPattern.pattern[i] === "#") {
376
+ count++;
377
+ if (count <= unmaskedCount) {
378
+ position = i + 1;
379
+ }
382
380
  }
383
381
  }
382
+ newCursorPosition = position;
383
+ } catch {
384
384
  }
385
- newCursorPosition = position;
386
- } catch {
387
385
  }
388
386
  target.setSelectionRange(newCursorPosition, newCursorPosition);
389
387
  if (!isControlled) setInternalValue(newMaskedValue);