@comitor/ui 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +5 -1
  2. package/dist/components/composite/confirm-dialog.js +56 -35
  3. package/dist/components/composite/confirm-dialog.js.map +1 -1
  4. package/dist/components/composite/search-filter-bar-constants.d.ts +3 -0
  5. package/dist/components/composite/search-filter-bar-constants.js.map +1 -1
  6. package/dist/components/composite/table-pagination-constants.d.ts +3 -1
  7. package/dist/components/composite/table-pagination-constants.js.map +1 -1
  8. package/dist/components/composite/table-pagination.js +10 -2
  9. package/dist/components/composite/table-pagination.js.map +1 -1
  10. package/dist/components/composite/text-utils.d.ts +4 -23
  11. package/dist/components/composite/text-utils.js +1 -11
  12. package/dist/components/composite/text-utils.js.map +1 -1
  13. package/dist/components/composite/toast.d.ts +2 -1
  14. package/dist/components/composite/toast.js +3 -9
  15. package/dist/components/composite/toast.js.map +1 -1
  16. package/dist/components/ui/action-bar.js +45 -1
  17. package/dist/components/ui/action-bar.js.map +1 -1
  18. package/dist/components/ui/banner.d.ts +8 -0
  19. package/dist/components/ui/banner.js +149 -35
  20. package/dist/components/ui/banner.js.map +1 -1
  21. package/dist/components/ui/circular-progress.js +10 -2
  22. package/dist/components/ui/circular-progress.js.map +1 -1
  23. package/dist/components/ui/kanban.js +9 -3
  24. package/dist/components/ui/kanban.js.map +1 -1
  25. package/dist/components/ui/key-value.js +70 -76
  26. package/dist/components/ui/key-value.js.map +1 -1
  27. package/dist/components/ui/mask-input-constants.d.ts +34 -2
  28. package/dist/components/ui/mask-input-constants.js +87 -76
  29. package/dist/components/ui/mask-input-constants.js.map +1 -1
  30. package/dist/components/ui/mask-input.d.ts +6 -5
  31. package/dist/components/ui/mask-input.js +23 -21
  32. package/dist/components/ui/mask-input.js.map +1 -1
  33. package/dist/components/ui/mention.d.ts +14 -2
  34. package/dist/components/ui/mention.js +33 -6
  35. package/dist/components/ui/mention.js.map +1 -1
  36. package/dist/components/ui/phone-input.d.ts +18 -8
  37. package/dist/components/ui/phone-input.js +125 -35
  38. package/dist/components/ui/phone-input.js.map +1 -1
  39. package/dist/components/ui/rating.js +4 -2
  40. package/dist/components/ui/rating.js.map +1 -1
  41. package/dist/components/ui/scroll-spy.js +46 -31
  42. package/dist/components/ui/scroll-spy.js.map +1 -1
  43. package/dist/components/ui/slider.d.ts +1 -1
  44. package/dist/components/ui/slider.js +15 -1
  45. package/dist/components/ui/slider.js.map +1 -1
  46. package/dist/components/ui/sonner.js +2 -0
  47. package/dist/components/ui/sonner.js.map +1 -1
  48. package/dist/components/ui/stepper.js +26 -3
  49. package/dist/components/ui/stepper.js.map +1 -1
  50. package/dist/components/ui/time-picker.d.ts +24 -4
  51. package/dist/components/ui/time-picker.js +425 -162
  52. package/dist/components/ui/time-picker.js.map +1 -1
  53. package/dist/components/ui/timeline.js +45 -9
  54. package/dist/components/ui/timeline.js.map +1 -1
  55. package/dist/components/ui/toggle-group.js +12 -1
  56. package/dist/components/ui/toggle-group.js.map +1 -1
  57. package/dist/components/ui/tour.js +3 -2
  58. package/dist/components/ui/tour.js.map +1 -1
  59. package/dist/data-grid/data-grid-cell-variants.js +4 -4
  60. package/dist/data-grid/data-grid-cell-variants.js.map +1 -1
  61. package/dist/data-grid/data-grid-cell-wrapper.d.ts +1 -1
  62. package/dist/data-grid/data-grid-cell-wrapper.js +6 -0
  63. package/dist/data-grid/data-grid-cell-wrapper.js.map +1 -1
  64. package/dist/data-grid/data-grid-row.js +1 -1
  65. package/dist/data-grid/data-grid-row.js.map +1 -1
  66. package/dist/data-grid/data-grid-select-column.js +6 -2
  67. package/dist/data-grid/data-grid-select-column.js.map +1 -1
  68. package/dist/data-grid/data-grid-skeleton.d.ts +5 -0
  69. package/dist/data-grid/data-grid-skeleton.js +1 -4
  70. package/dist/data-grid/data-grid-skeleton.js.map +1 -1
  71. package/dist/data-grid/data-grid.d.ts +1 -1
  72. package/dist/data-grid/data-grid.js +3 -2
  73. package/dist/data-grid/data-grid.js.map +1 -1
  74. package/dist/data-grid/filters.js +3 -2
  75. package/dist/data-grid/filters.js.map +1 -1
  76. package/dist/data-grid/types.d.ts +7 -0
  77. package/dist/data-grid/use-data-grid.d.ts +1 -0
  78. package/dist/data-grid/use-data-grid.js +70 -24
  79. package/dist/data-grid/use-data-grid.js.map +1 -1
  80. package/dist/data-grid/utils.d.ts +7 -0
  81. package/dist/data-grid/utils.js +3 -1
  82. package/dist/data-grid/utils.js.map +1 -1
  83. package/dist/i18n/locale-provider.d.ts +18 -0
  84. package/dist/i18n/locale-provider.js.map +1 -1
  85. package/dist/i18n/namespaces/rating.js +2 -1
  86. package/dist/i18n/namespaces/rating.js.map +1 -1
  87. package/dist/i18n/namespaces/search-filter-bar.js +8 -6
  88. package/dist/i18n/namespaces/search-filter-bar.js.map +1 -1
  89. package/dist/i18n/namespaces/shell.d.ts +4 -0
  90. package/dist/i18n/namespaces/shell.js +1 -1
  91. package/dist/i18n/namespaces/shell.js.map +1 -1
  92. package/dist/i18n/namespaces/table-pagination.js +6 -4
  93. package/dist/i18n/namespaces/table-pagination.js.map +1 -1
  94. package/dist/i18n/namespaces/toast.d.ts +3 -2
  95. package/dist/i18n/namespaces/toast.js.map +1 -1
  96. package/dist/index.d.ts +2 -1
  97. package/dist/lib/collect-item-elements.d.ts +47 -0
  98. package/dist/lib/collect-item-elements.js +22 -0
  99. package/dist/lib/collect-item-elements.js.map +1 -0
  100. package/dist/lib/text-search.d.ts +32 -0
  101. package/dist/lib/text-search.js +17 -0
  102. package/dist/lib/text-search.js.map +1 -0
  103. package/dist/lib/toast-language.d.ts +8 -0
  104. package/dist/lib/toast-language.js +19 -0
  105. package/dist/lib/toast-language.js.map +1 -0
  106. package/dist/shell/app-shell.d.ts +1 -0
  107. package/dist/shell/index.d.ts +2 -1
  108. package/dist/shell/shell-context.d.ts +1 -0
  109. package/dist/shell/utils.d.ts +2 -1
  110. 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":[]}
@@ -10,6 +10,9 @@ declare const REGEX_CACHE: {
10
10
  readonly hashPattern: RegExp;
11
11
  readonly currencyAtEnd: RegExp;
12
12
  readonly percentageChars: RegExp;
13
+ readonly percentageCommaChars: RegExp;
14
+ readonly decimalMark: RegExp;
15
+ readonly decimalMarks: RegExp;
13
16
  readonly phone: RegExp;
14
17
  readonly ssn: RegExp;
15
18
  readonly zipCode: RegExp;
@@ -61,7 +64,21 @@ declare function applyCurrencyMask(opts: {
61
64
  currency?: string;
62
65
  locale?: string;
63
66
  }): string;
64
- declare function applyPercentageMask(value: string): string;
67
+ /**
68
+ * Giá trị thô dạng chuẩn (`"12.5"`) → chuỗi hiển thị (`"12.5%"`).
69
+ *
70
+ * ⚠ Khác thượng nguồn (3.1.0): nhận thêm `locale` (tuỳ chọn) và vẽ dấu thập phân của nó — `vi` →
71
+ * `12,5%`. Không truyền thì `.` như thượng nguồn. Chỉ đổi DẤU: không nhóm nghìn, và `%` luôn dính
72
+ * ngay sau số (`Intl` của de-DE viết `12,5 %`) — để con trỏ và nút xoá chỉ phải biết một dáng chuỗi.
73
+ */
74
+ declare function applyPercentageMask(value: string, opts?: {
75
+ locale?: string;
76
+ }): string;
77
+ /** Ô phần trăm đang đọc và vẽ dấu `,` — `mask-input.tsx` dùng để chọn cách đặt con trỏ. */
78
+ declare function isDecimalCommaPercentage(opts: {
79
+ mask: MaskPatternKey | MaskPattern | undefined;
80
+ locale?: string;
81
+ }): boolean;
65
82
  declare function getUnmaskedValue(opts: {
66
83
  value: string;
67
84
  currency?: string;
@@ -86,6 +103,21 @@ declare function getCurrencyCaretPosition(opts: {
86
103
  oldValue?: string;
87
104
  previousUnmasked?: string;
88
105
  }): number;
106
+ /**
107
+ * Con trỏ sau khi ô phần trăm ở chế độ dấu phẩy (`isDecimalCommaPercentage`) vẽ lại chuỗi.
108
+ *
109
+ * ⚠ Khác thượng nguồn (3.1.0) — hàm MỚI. `getCurrencyCaretPosition` chỉ đếm CHỮ SỐ trước con trỏ,
110
+ * mà ở đây mặt nạ còn đổi chính dấu thập phân (phím `.` của bàn phím số hiện thành `,`): đếm chữ số
111
+ * thì con trỏ rơi về TRƯỚC dấu vừa gõ, và chữ số kế tiếp chen vào phần nguyên — `12` `.` `5` ra
112
+ * `125,%`. Đây đúng là cái bẫy làm cách chữa tạm "`transform` đổi `,` thành `.`" không dùng được.
113
+ * Ở đây đếm cả dấu thập phân ĐƯỢC GIỮ (dấu đầu tiên — luật của `transform`), nên con trỏ đứng sau nó.
114
+ * Chế độ dấu chấm (en…) vẫn đi `getCurrencyCaretPosition` như cũ.
115
+ */
116
+ declare function getPercentageCaretPosition(opts: {
117
+ newValue: string;
118
+ oldValue: string;
119
+ oldCursorPosition: number;
120
+ }): number;
89
121
  declare function getPatternCaretPosition(opts: {
90
122
  newValue: string;
91
123
  maskPattern: MaskPattern;
@@ -95,4 +127,4 @@ declare function getPatternCaretPosition(opts: {
95
127
  previousUnmasked?: string;
96
128
  }): number;
97
129
 
98
- export { CURRENCY_PERCENTAGE_SYMBOLS, DEFAULT_CURRENCY, MASK_PATTERNS, type MaskPattern, type MaskPatternKey, NUMERIC_MASK_PATTERNS, REGEX_CACHE, type TransformOptions, applyCurrencyMask, applyMask, applyPercentageMask, fromUnmaskedIndex, getCurrencyCaretPosition, getPatternCaretPosition, getUnmaskedValue, isCurrencyAtEnd, isCurrencyMask, toUnmaskedIndex };
130
+ export { CURRENCY_PERCENTAGE_SYMBOLS, DEFAULT_CURRENCY, MASK_PATTERNS, type MaskPattern, type MaskPatternKey, NUMERIC_MASK_PATTERNS, REGEX_CACHE, type TransformOptions, applyCurrencyMask, applyMask, applyPercentageMask, fromUnmaskedIndex, getCurrencyCaretPosition, getPatternCaretPosition, getPercentageCaretPosition, getUnmaskedValue, isCurrencyAtEnd, isCurrencyMask, isDecimalCommaPercentage, toUnmaskedIndex };
@@ -8,6 +8,7 @@ const formattersCache = /* @__PURE__ */ new Map();
8
8
  const currencyAtEndCache = /* @__PURE__ */ new Map();
9
9
  const currencySymbolsCache = /* @__PURE__ */ new Map();
10
10
  const fractionDigitsCache = /* @__PURE__ */ new Map();
11
+ const decimalSeparatorCache = /* @__PURE__ */ new Map();
11
12
  const daysInMonthCache = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
12
13
  const REGEX_CACHE = {
13
14
  digitsOnly: /^\d+$/,
@@ -18,6 +19,9 @@ const REGEX_CACHE = {
18
19
  hashPattern: /#/g,
19
20
  currencyAtEnd: /\d\s*[^\d\s]+$/,
20
21
  percentageChars: /[^\d.]/g,
22
+ percentageCommaChars: /[^\d.,]/g,
23
+ decimalMark: /[.,]/,
24
+ decimalMarks: /[.,]/g,
21
25
  phone: /^\d{10}$/,
22
26
  ssn: /^\d{9}$/,
23
27
  zipCode: /^\d{5}$/,
@@ -111,6 +115,39 @@ function getCurrencyFractionDigits(opts) {
111
115
  fractionDigitsCache.set(key, digits);
112
116
  return digits;
113
117
  }
118
+ function findCurrencyDecimalIndex(cleaned, decimal, group) {
119
+ const lastDot = cleaned.lastIndexOf(".");
120
+ const lastComma = cleaned.lastIndexOf(",");
121
+ if (lastDot !== -1 && lastComma !== -1) return Math.max(lastDot, lastComma);
122
+ const separator = lastDot !== -1 ? "." : lastComma !== -1 ? "," : null;
123
+ if (!separator) return -1;
124
+ const last = cleaned.lastIndexOf(separator);
125
+ if (separator === decimal) return last;
126
+ if (separator === group) {
127
+ return separator === "." && decimal === "," && last === cleaned.length - 1 ? last : -1;
128
+ }
129
+ const occurrences = cleaned.split(separator).length - 1;
130
+ const digitsAfter = cleaned.length - last - 1;
131
+ return occurrences === 1 && digitsAfter !== 3 ? last : -1;
132
+ }
133
+ function isCanonicalAmount(value, fractionDigits) {
134
+ const dot = value.indexOf(".");
135
+ const integerPart = dot === -1 ? value : value.slice(0, dot);
136
+ const fractionPart = dot === -1 ? "" : value.slice(dot + 1);
137
+ return /^\d*$/.test(integerPart) && /^\d*$/.test(fractionPart) && fractionPart.length <= fractionDigits;
138
+ }
139
+ function usesDecimalComma(locale) {
140
+ let decimal = decimalSeparatorCache.get(locale);
141
+ if (decimal === void 0) {
142
+ decimal = ".";
143
+ try {
144
+ decimal = new Intl.NumberFormat(locale).formatToParts(1.5).find((part) => part.type === "decimal")?.value ?? ".";
145
+ } catch {
146
+ }
147
+ decimalSeparatorCache.set(locale, decimal);
148
+ }
149
+ return decimal === ",";
150
+ }
114
151
  function isCurrencyAtEnd(opts) {
115
152
  const { locale, currency } = opts;
116
153
  const key = `${locale}|${currency}`;
@@ -251,81 +288,20 @@ const MASK_PATTERNS = {
251
288
  currency: {
252
289
  pattern: "$###,###.##",
253
290
  transform: (value, { currency = DEFAULT_CURRENCY, locale = DEFAULT_LOCALE } = {}) => {
254
- if (getCurrencyFractionDigits({ currency, locale }) === 0) {
291
+ const fractionDigits = getCurrencyFractionDigits({ currency, locale });
292
+ if (fractionDigits === 0) {
255
293
  return value.replace(REGEX_CACHE.nonDigits, "");
256
294
  }
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
- }
295
+ if (isCanonicalAmount(value, fractionDigits)) return value;
296
+ const { decimal, group } = getCachedCurrencySymbols({ currency, locale });
269
297
  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
- }
298
+ const decimalIndex = findCurrencyDecimalIndex(cleaned, decimal, group);
299
+ if (decimalIndex === -1) {
300
+ return cleaned.replace(/[.,]/g, "");
316
301
  }
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;
326
- }
327
- const digitsOnly = cleaned.replace(/[.,]/g, "");
328
- return digitsOnly;
302
+ const integerPart = cleaned.substring(0, decimalIndex).replace(/[.,]/g, "");
303
+ const fractionPart = cleaned.substring(decimalIndex + 1).replace(/[.,]/g, "").substring(0, fractionDigits);
304
+ return `${integerPart}.${fractionPart}`;
329
305
  },
330
306
  validate: (value) => {
331
307
  if (!REGEX_CACHE.currencyValidation.test(value)) return false;
@@ -334,11 +310,23 @@ const MASK_PATTERNS = {
334
310
  }
335
311
  },
336
312
  /**
337
- * Phần trăm `##.##%` — dấu thập phân là `.` cố định, KHÔNG theo `locale` (dấu `,` bị bỏ qua).
313
+ * Phần trăm `##.##%`, tối đa 2 chữ số lẻ. Giá trị thô (vế thứ hai của `onValueChange`, và thứ
314
+ * `validate` nhận) LUÔN dùng `.`: `"12.5"`.
315
+ *
316
+ * Dấu thập phân theo `locale` (`3.1.0`): ở locale dùng `,` (vi, de, fr…) ô đọc CẢ `,` lẫn `.` là
317
+ * dấu thập phân — `.` vì bàn phím số của điện thoại vẽ dấu theo ngôn ngữ MÁY, không theo app — và
318
+ * hiện `12,5%`. Locale dùng `.` (en…): như thượng nguồn, `,` bị bỏ qua (`12,5` → `125%`).
338
319
  */
339
320
  percentage: {
340
321
  pattern: "##.##%",
341
- transform: (value) => {
322
+ transform: (value, { locale = DEFAULT_LOCALE } = {}) => {
323
+ if (usesDecimalComma(locale)) {
324
+ const cleaned2 = value.replace(REGEX_CACHE.percentageCommaChars, "");
325
+ const markIndex = cleaned2.search(REGEX_CACHE.decimalMark);
326
+ if (markIndex === -1) return cleaned2;
327
+ const fractionPart = cleaned2.slice(markIndex + 1).replace(REGEX_CACHE.decimalMarks, "").slice(0, 2);
328
+ return `${cleaned2.slice(0, markIndex)}.${fractionPart}`;
329
+ }
342
330
  const cleaned = value.replace(REGEX_CACHE.percentageChars, "");
343
331
  const parts = cleaned.split(".");
344
332
  if (parts.length > 2) {
@@ -415,7 +403,7 @@ function applyMask(opts) {
415
403
  });
416
404
  }
417
405
  if (pattern.includes("%")) {
418
- return applyPercentageMask(cleanValue);
406
+ return applyPercentageMask(cleanValue, { locale: isLocalizedPercentage(mask) ? locale : void 0 });
419
407
  }
420
408
  if (mask === "ipv4") {
421
409
  return cleanValue;
@@ -482,15 +470,22 @@ function applyCurrencyMask(opts) {
482
470
  return result;
483
471
  }
484
472
  }
485
- function applyPercentageMask(value) {
473
+ function applyPercentageMask(value, opts = {}) {
486
474
  if (!value) return "";
475
+ const decimalSeparator = usesDecimalComma(opts.locale ?? DEFAULT_LOCALE) ? "," : ".";
487
476
  const parts = value.split(".");
488
477
  let result = parts[0] ?? "0";
489
478
  if (value.includes(".")) {
490
- result += `.${(parts[1] ?? "").substring(0, 2)}`;
479
+ result += `${decimalSeparator}${(parts[1] ?? "").substring(0, 2)}`;
491
480
  }
492
481
  return `${result}%`;
493
482
  }
483
+ function isLocalizedPercentage(mask) {
484
+ return mask === "percentage" || typeof mask === "object" && mask.transform === MASK_PATTERNS.percentage.transform;
485
+ }
486
+ function isDecimalCommaPercentage(opts) {
487
+ return isLocalizedPercentage(opts.mask) && usesDecimalComma(opts.locale ?? DEFAULT_LOCALE);
488
+ }
494
489
  function getUnmaskedValue(opts) {
495
490
  const { value, transform, currency, locale } = opts;
496
491
  return transform ? transform(value, { currency, locale }) : value.replace(REGEX_CACHE.nonDigits, "");
@@ -547,6 +542,20 @@ function getCurrencyCaretPosition(opts) {
547
542
  }
548
543
  return newValue.length;
549
544
  }
545
+ function getPercentageCaretPosition(opts) {
546
+ const { newValue, oldValue, oldCursorPosition } = opts;
547
+ const markIndex = oldValue.search(REGEX_CACHE.decimalMark);
548
+ const target = oldValue.slice(0, oldCursorPosition).replace(REGEX_CACHE.nonDigits, "").length + (markIndex !== -1 && markIndex < oldCursorPosition ? 1 : 0);
549
+ if (target === 0) return 0;
550
+ let seen = 0;
551
+ for (let i = 0; i < newValue.length; i++) {
552
+ if (/[\d.,]/.test(newValue.charAt(i))) {
553
+ seen++;
554
+ if (seen === target) return i + 1;
555
+ }
556
+ }
557
+ return newValue.length;
558
+ }
550
559
  function getPatternCaretPosition(opts) {
551
560
  const { newValue, maskPattern, currentUnmasked, oldCursorPosition, oldValue, previousUnmasked } = opts;
552
561
  let position = 0;
@@ -592,9 +601,11 @@ export {
592
601
  fromUnmaskedIndex,
593
602
  getCurrencyCaretPosition,
594
603
  getPatternCaretPosition,
604
+ getPercentageCaretPosition,
595
605
  getUnmaskedValue,
596
606
  isCurrencyAtEnd,
597
607
  isCurrencyMask,
608
+ isDecimalCommaPercentage,
598
609
  toUnmaskedIndex
599
610
  };
600
611
  //# sourceMappingURL=mask-input-constants.js.map