@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.
- package/README.md +5 -1
- package/dist/components/composite/confirm-dialog.js +56 -35
- package/dist/components/composite/confirm-dialog.js.map +1 -1
- package/dist/components/composite/search-filter-bar-constants.d.ts +3 -0
- package/dist/components/composite/search-filter-bar-constants.js.map +1 -1
- package/dist/components/composite/table-pagination-constants.d.ts +3 -1
- package/dist/components/composite/table-pagination-constants.js.map +1 -1
- package/dist/components/composite/table-pagination.js +10 -2
- package/dist/components/composite/table-pagination.js.map +1 -1
- package/dist/components/composite/text-utils.d.ts +4 -23
- package/dist/components/composite/text-utils.js +1 -11
- package/dist/components/composite/text-utils.js.map +1 -1
- package/dist/components/composite/toast.d.ts +2 -1
- package/dist/components/composite/toast.js +3 -9
- package/dist/components/composite/toast.js.map +1 -1
- package/dist/components/ui/action-bar.js +45 -1
- package/dist/components/ui/action-bar.js.map +1 -1
- package/dist/components/ui/banner.d.ts +8 -0
- package/dist/components/ui/banner.js +149 -35
- package/dist/components/ui/banner.js.map +1 -1
- package/dist/components/ui/circular-progress.js +10 -2
- package/dist/components/ui/circular-progress.js.map +1 -1
- package/dist/components/ui/kanban.js +9 -3
- package/dist/components/ui/kanban.js.map +1 -1
- package/dist/components/ui/key-value.js +70 -76
- package/dist/components/ui/key-value.js.map +1 -1
- package/dist/components/ui/mask-input-constants.d.ts +34 -2
- package/dist/components/ui/mask-input-constants.js +87 -76
- package/dist/components/ui/mask-input-constants.js.map +1 -1
- package/dist/components/ui/mask-input.d.ts +6 -5
- package/dist/components/ui/mask-input.js +23 -21
- package/dist/components/ui/mask-input.js.map +1 -1
- package/dist/components/ui/mention.d.ts +14 -2
- package/dist/components/ui/mention.js +33 -6
- package/dist/components/ui/mention.js.map +1 -1
- package/dist/components/ui/phone-input.d.ts +18 -8
- package/dist/components/ui/phone-input.js +125 -35
- package/dist/components/ui/phone-input.js.map +1 -1
- package/dist/components/ui/rating.js +4 -2
- package/dist/components/ui/rating.js.map +1 -1
- package/dist/components/ui/scroll-spy.js +46 -31
- package/dist/components/ui/scroll-spy.js.map +1 -1
- package/dist/components/ui/slider.d.ts +1 -1
- package/dist/components/ui/slider.js +15 -1
- package/dist/components/ui/slider.js.map +1 -1
- package/dist/components/ui/sonner.js +2 -0
- package/dist/components/ui/sonner.js.map +1 -1
- package/dist/components/ui/stepper.js +26 -3
- package/dist/components/ui/stepper.js.map +1 -1
- package/dist/components/ui/time-picker.d.ts +24 -4
- package/dist/components/ui/time-picker.js +425 -162
- package/dist/components/ui/time-picker.js.map +1 -1
- package/dist/components/ui/timeline.js +45 -9
- package/dist/components/ui/timeline.js.map +1 -1
- package/dist/components/ui/toggle-group.js +12 -1
- package/dist/components/ui/toggle-group.js.map +1 -1
- package/dist/components/ui/tour.js +3 -2
- package/dist/components/ui/tour.js.map +1 -1
- package/dist/data-grid/data-grid-cell-variants.js +4 -4
- package/dist/data-grid/data-grid-cell-variants.js.map +1 -1
- package/dist/data-grid/data-grid-cell-wrapper.d.ts +1 -1
- package/dist/data-grid/data-grid-cell-wrapper.js +6 -0
- package/dist/data-grid/data-grid-cell-wrapper.js.map +1 -1
- package/dist/data-grid/data-grid-row.js +1 -1
- package/dist/data-grid/data-grid-row.js.map +1 -1
- package/dist/data-grid/data-grid-select-column.js +6 -2
- package/dist/data-grid/data-grid-select-column.js.map +1 -1
- package/dist/data-grid/data-grid-skeleton.d.ts +5 -0
- package/dist/data-grid/data-grid-skeleton.js +1 -4
- package/dist/data-grid/data-grid-skeleton.js.map +1 -1
- package/dist/data-grid/data-grid.d.ts +1 -1
- package/dist/data-grid/data-grid.js +3 -2
- package/dist/data-grid/data-grid.js.map +1 -1
- package/dist/data-grid/filters.js +3 -2
- package/dist/data-grid/filters.js.map +1 -1
- package/dist/data-grid/types.d.ts +7 -0
- package/dist/data-grid/use-data-grid.d.ts +1 -0
- package/dist/data-grid/use-data-grid.js +70 -24
- package/dist/data-grid/use-data-grid.js.map +1 -1
- package/dist/data-grid/utils.d.ts +7 -0
- package/dist/data-grid/utils.js +3 -1
- package/dist/data-grid/utils.js.map +1 -1
- package/dist/i18n/locale-provider.d.ts +18 -0
- package/dist/i18n/locale-provider.js.map +1 -1
- package/dist/i18n/namespaces/rating.js +2 -1
- package/dist/i18n/namespaces/rating.js.map +1 -1
- package/dist/i18n/namespaces/search-filter-bar.js +8 -6
- package/dist/i18n/namespaces/search-filter-bar.js.map +1 -1
- package/dist/i18n/namespaces/shell.d.ts +4 -0
- package/dist/i18n/namespaces/shell.js +1 -1
- package/dist/i18n/namespaces/shell.js.map +1 -1
- package/dist/i18n/namespaces/table-pagination.js +6 -4
- package/dist/i18n/namespaces/table-pagination.js.map +1 -1
- package/dist/i18n/namespaces/toast.d.ts +3 -2
- package/dist/i18n/namespaces/toast.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/lib/collect-item-elements.d.ts +47 -0
- package/dist/lib/collect-item-elements.js +22 -0
- package/dist/lib/collect-item-elements.js.map +1 -0
- package/dist/lib/text-search.d.ts +32 -0
- package/dist/lib/text-search.js +17 -0
- package/dist/lib/text-search.js.map +1 -0
- package/dist/lib/toast-language.d.ts +8 -0
- package/dist/lib/toast-language.js +19 -0
- package/dist/lib/toast-language.js.map +1 -0
- package/dist/shell/app-shell.d.ts +1 -0
- package/dist/shell/index.d.ts +2 -1
- package/dist/shell/shell-context.d.ts +1 -0
- package/dist/shell/utils.d.ts +2 -1
- package/package.json +1 -1
|
@@ -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":"AAmBA,MAAM,mBAAmB;AACzB,MAAM,qBAAqB;AAQ3B,MAAM,mBAAmB;AACzB,MAAM,iBAAiB;AAEvB,MAAM,wBAAwB;AAC9B,MAAM,8BAA8B;AAQpC,MAAM,kBAAkB,oBAAI,IAA+B;AAC3D,MAAM,qBAAqB,oBAAI,IAAqB;AACpD,MAAM,uBAAuB,oBAAI,IAA6B;AAC9D,MAAM,sBAAsB,oBAAI,IAAoB;AACpD,MAAM,mBAAmB,CAAC,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,EAAE;AAExE,MAAM,cAAc;AAAA,EAClB,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,kBAAkB;AAAA,EAClB,aAAa;AAAA,EACb,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,OAAO;AAAA,EACP,KAAK;AAAA,EACL,SAAS;AAAA,EACT,iBAAiB;AAAA,EACjB,MAAM;AAAA,EACN,KAAK;AAAA,EACL,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,kBAAkB;AAAA,EAClB,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,oBAAoB;AAAA,EACpB,aAAa;AACf;AAEA,SAAS,mBAAmB,QAA4B,MAAmD;AACzG,QAAM,EAAE,UAAU,wBAAwB,GAAG,wBAAwB,EAAE,IAAI;AAE3E,QAAM,MAAM,GAAG,MAAM,IAAI,QAAQ,IAAI,qBAAqB,IAAI,qBAAqB;AAEnF,MAAI,CAAC,gBAAgB,IAAI,GAAG,GAAG;AAC7B,QAAI;AACF,sBAAgB;AAAA,QACd;AAAA,QACA,IAAI,KAAK,aAAa,QAAQ;AAAA,UAC5B,OAAO;AAAA,UACP;AAAA,UACA,GAAG;AAAA,QACL,CAAC;AAAA,MACH;AAAA,IACF,QAAQ;AACN,sBAAgB;AAAA,QACd;AAAA,QACA,IAAI,KAAK,aAAa,gBAAgB;AAAA,UACpC,OAAO;AAAA,UACP,UAAU;AAAA,UACV,GAAG;AAAA,QACL,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAEA,QAAM,YAAY,gBAAgB,IAAI,GAAG;AACzC,MAAI,CAAC,WAAW;AACd,UAAM,IAAI,MAAM,gGAA2C,GAAG,EAAE;AAAA,EAClE;AACA,SAAO;AACT;AAEA,SAAS,yBAAyB,MAAyC;AACzE,QAAM,EAAE,QAAQ,SAAS,IAAI;AAE7B,QAAM,MAAM,GAAG,MAAM,IAAI,QAAQ;AACjC,QAAM,SAAS,qBAAqB,IAAI,GAAG;AAC3C,MAAI,QAAQ;AACV,WAAO;AAAA,EACT;AAEA,MAAI,iBAAiB;AACrB,MAAI,mBAAmB;AACvB,MAAI,iBAAiB;AAErB,MAAI;AACF,UAAM,YAAY,mBAAmB,QAAQ;AAAA,MAC3C;AAAA,MACA,uBAAuB;AAAA,MACvB,uBAAuB;AAAA,IACzB,CAAC;AACD,UAAM,QAAQ,UAAU,cAAc,MAAM;AAC5C,UAAM,eAAe,MAAM,KAAK,CAAC,SAAS,KAAK,SAAS,UAAU;AAClE,UAAM,cAAc,MAAM,KAAK,CAAC,SAAS,KAAK,SAAS,SAAS;AAChE,UAAM,YAAY,MAAM,KAAK,CAAC,SAAS,KAAK,SAAS,OAAO;AAE5D,QAAI,aAAc,kBAAiB,aAAa;AAChD,QAAI,YAAa,oBAAmB,YAAY;AAChD,QAAI,UAAW,kBAAiB,UAAU;AAAA,EAC5C,QAAQ;AAAA,EAER;AAEA,QAAM,UAA2B;AAAA,IAC/B,UAAU;AAAA,IACV,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AACA,uBAAqB,IAAI,KAAK,OAAO;AACrC,SAAO;AACT;AAgBA,SAAS,0BAA0B,MAAgC;AACjE,QAAM,EAAE,QAAQ,SAAS,IAAI;AAE7B,QAAM,MAAM,GAAG,MAAM,IAAI,QAAQ;AACjC,QAAM,SAAS,oBAAoB,IAAI,GAAG;AAC1C,MAAI,WAAW,QAAW;AACxB,WAAO;AAAA,EACT;AAEA,MAAI,SAAS;AACb,MAAI;AACF,UAAM,WAAW,IAAI,KAAK,aAAa,QAAQ,EAAE,OAAO,YAAY,SAAS,CAAC,EAAE,gBAAgB;AAChG,aAAS,KAAK,IAAI,GAAG,SAAS,yBAAyB,CAAC;AAAA,EAC1D,QAAQ;AAAA,EAER;AAEA,sBAAoB,IAAI,KAAK,MAAM;AACnC,SAAO;AACT;AAEA,SAAS,gBAAgB,MAAiC;AACxD,QAAM,EAAE,QAAQ,SAAS,IAAI;AAE7B,QAAM,MAAM,GAAG,MAAM,IAAI,QAAQ;AACjC,QAAM,SAAS,mBAAmB,IAAI,GAAG;AACzC,MAAI,WAAW,QAAW;AACxB,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,YAAY,mBAAmB,QAAQ;AAAA,MAC3C;AAAA,MACA,uBAAuB;AAAA,MACvB,uBAAuB;AAAA,IACzB,CAAC;AACD,UAAM,SAAS,UAAU,OAAO,GAAG;AACnC,UAAM,SAAS,YAAY,cAAc,KAAK,MAAM;AACpD,uBAAmB,IAAI,KAAK,MAAM;AAClC,WAAO;AAAA,EACT,QAAQ;AACN,uBAAmB,IAAI,KAAK,KAAK;AACjC,WAAO;AAAA,EACT;AACF;AAEA,SAAS,eAAe,MAAqF;AAC3G,QAAM,EAAE,MAAM,QAAQ,IAAI;AAE1B,SAAO,SAAS,cAAc,QAAQ,YAAY,QAAQ,SAAS,GAAG,KAAK,QAAQ,SAAS,QAAG,EAAE;AACnG;AAwCA,MAAM,gBAAqD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOzD,OAAO;AAAA,IACL,SAAS;AAAA,IACT,WAAW,CAAC,UAAU,MAAM,QAAQ,YAAY,WAAW,EAAE;AAAA,IAC7D,UAAU,CAAC,UAAU,YAAY,MAAM,KAAK,MAAM,QAAQ,YAAY,WAAW,EAAE,CAAC;AAAA,EACtF;AAAA,EACA,KAAK;AAAA,IACH,SAAS;AAAA,IACT,WAAW,CAAC,UAAU,MAAM,QAAQ,YAAY,WAAW,EAAE;AAAA,IAC7D,UAAU,CAAC,UAAU,YAAY,IAAI,KAAK,MAAM,QAAQ,YAAY,WAAW,EAAE,CAAC;AAAA,EACpF;AAAA;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM;AAAA,IACJ,SAAS;AAAA,IACT,WAAW,CAAC,UAAU,MAAM,QAAQ,YAAY,WAAW,EAAE;AAAA,IAC7D,UAAU,CAAC,UAAU;AACnB,YAAM,UAAU,MAAM,QAAQ,YAAY,WAAW,EAAE;AACvD,UAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,YAAM,QAAQ,OAAO,SAAS,QAAQ,UAAU,GAAG,CAAC,GAAG,EAAE;AACzD,YAAM,MAAM,OAAO,SAAS,QAAQ,UAAU,GAAG,CAAC,GAAG,EAAE;AACvD,YAAM,OAAO,OAAO,SAAS,QAAQ,UAAU,GAAG,CAAC,GAAG,EAAE;AAExD,YAAM,eAAc,oBAAI,KAAK,GAAE,YAAY;AAC3C,YAAM,UAAU,cAAc;AAC9B,YAAM,UAAU,cAAc;AAC9B,UAAI,QAAQ,KAAK,QAAQ,MAAM,MAAM,KAAK,OAAO,WAAW,OAAO,QAAS,QAAO;AAEnF,YAAM,UACJ,UAAU,MAAO,OAAO,MAAM,KAAK,OAAO,QAAQ,KAAM,OAAO,QAAQ,KACnE,KACC,iBAAiB,QAAQ,CAAC,KAAK;AAEtC,aAAO,OAAO;AAAA,IAChB;AAAA,EACF;AAAA,EACA,MAAM;AAAA,IACJ,SAAS;AAAA,IACT,WAAW,CAAC,UAAU,MAAM,QAAQ,YAAY,WAAW,EAAE;AAAA,IAC7D,UAAU,CAAC,UAAU;AACnB,YAAM,UAAU,MAAM,QAAQ,YAAY,WAAW,EAAE;AACvD,UAAI,CAAC,YAAY,KAAK,KAAK,OAAO,EAAG,QAAO;AAC5C,YAAM,QAAQ,OAAO,SAAS,QAAQ,UAAU,GAAG,CAAC,GAAG,EAAE;AACzD,YAAM,UAAU,OAAO,SAAS,QAAQ,UAAU,GAAG,CAAC,GAAG,EAAE;AAC3D,aAAO,SAAS,MAAM,WAAW;AAAA,IACnC;AAAA,EACF;AAAA,EACA,YAAY;AAAA,IACV,SAAS;AAAA,IACT,WAAW,CAAC,UAAU,MAAM,QAAQ,YAAY,WAAW,EAAE;AAAA,IAC7D,UAAU,CAAC,UAAU;AACnB,YAAM,UAAU,MAAM,QAAQ,YAAY,WAAW,EAAE;AACvD,UAAI,CAAC,YAAY,WAAW,KAAK,OAAO,EAAG,QAAO;AAElD,UAAI,MAAM;AACV,UAAI,SAAS;AACb,eAAS,IAAI,QAAQ,SAAS,GAAG,KAAK,GAAG,KAAK;AAC5C,cAAM,YAAY,QAAQ,CAAC;AAC3B,YAAI,CAAC,UAAW;AAChB,YAAI,QAAQ,OAAO,SAAS,WAAW,EAAE;AACzC,YAAI,QAAQ;AACV,mBAAS;AACT,cAAI,QAAQ,GAAG;AACb,qBAAS;AAAA,UACX;AAAA,QACF;AACA,eAAO;AACP,iBAAS,CAAC;AAAA,MACZ;AACA,aAAO,MAAM,OAAO;AAAA,IACtB;AAAA,EACF;AAAA,EACA,kBAAkB;AAAA,IAChB,SAAS;AAAA,IACT,WAAW,CAAC,UAAU,MAAM,QAAQ,YAAY,WAAW,EAAE;AAAA,IAC7D,UAAU,CAAC,UAAU;AACnB,YAAM,UAAU,MAAM,QAAQ,YAAY,WAAW,EAAE;AACvD,UAAI,CAAC,YAAY,iBAAiB,KAAK,OAAO,EAAG,QAAO;AAExD,YAAM,QAAQ,OAAO,SAAS,QAAQ,UAAU,GAAG,CAAC,GAAG,EAAE;AACzD,YAAM,OAAO,OAAO,SAAS,QAAQ,UAAU,GAAG,CAAC,GAAG,EAAE;AAExD,UAAI,QAAQ,KAAK,QAAQ,GAAI,QAAO;AAEpC,YAAM,MAAM,oBAAI,KAAK;AACrB,YAAM,cAAc,IAAI,YAAY;AACpC,YAAM,eAAe,IAAI,SAAS,IAAI;AAEtC,YAAM,WAAW,QAAQ,KAAK,MAAO,OAAO,OAAO;AAEnD,UAAI,WAAW,eAAgB,aAAa,eAAe,QAAQ,cAAe;AAChF,eAAO;AAAA,MACT;AAEA,YAAM,UAAU,cAAc;AAC9B,UAAI,WAAW,SAAS;AACtB,eAAO;AAAA,MACT;AAEA,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EACA,SAAS;AAAA,IACP,SAAS;AAAA,IACT,WAAW,CAAC,UAAU,MAAM,QAAQ,YAAY,WAAW,EAAE;AAAA,IAC7D,UAAU,CAAC,UAAU,YAAY,QAAQ,KAAK,MAAM,QAAQ,YAAY,WAAW,EAAE,CAAC;AAAA,EACxF;AAAA,EACA,iBAAiB;AAAA,IACf,SAAS;AAAA,IACT,WAAW,CAAC,UAAU,MAAM,QAAQ,YAAY,WAAW,EAAE;AAAA,IAC7D,UAAU,CAAC,UAAU,YAAY,gBAAgB,KAAK,MAAM,QAAQ,YAAY,WAAW,EAAE,CAAC;AAAA,EAChG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAU;AAAA,IACR,SAAS;AAAA,IACT,WAAW,CAAC,OAAO,EAAE,WAAW,kBAAkB,SAAS,eAAe,IAAI,CAAC,MAAM;AAGnF,UAAI,0BAA0B,EAAE,UAAU,OAAO,CAAC,MAAM,GAAG;AACzD,eAAO,MAAM,QAAQ,YAAY,WAAW,EAAE;AAAA,MAChD;AAEA,UAAI,yBAAyB;AAE7B,UAAI;AACF,cAAM,YAAY,mBAAmB,QAAQ;AAAA,UAC3C;AAAA,UACA,uBAAuB;AAAA,UACvB,uBAAuB;AAAA,QACzB,CAAC;AACD,cAAM,QAAQ,UAAU,cAAc,MAAM;AAC5C,cAAM,cAAc,MAAM,KAAK,CAAC,SAAS,KAAK,SAAS,SAAS;AAEhE,YAAI,YAAa,0BAAyB,YAAY;AAAA,MACxD,QAAQ;AAAA,MAER;AAEA,YAAM,UAAU,MAAM,QAAQ,YAAY,kBAAkB,EAAE;AAE9D,YAAM,WAAW,QAAQ,QAAQ,GAAG;AACpC,YAAM,aAAa,QAAQ,QAAQ,GAAG;AAEtC,UAAI,sBAAsB;AAC1B,UAAI,eAAe;AAEnB,UAAI,2BAA2B,KAAK;AAClC,cAAM,iBAAiB,QAAQ,YAAY,GAAG;AAC9C,YAAI,mBAAmB,IAAI;AACzB,gBAAM,aAAa,QAAQ,UAAU,iBAAiB,CAAC;AACvD,cAAI,WAAW,UAAU,KAAK,QAAQ,KAAK,UAAU,GAAG;AACtD,kCAAsB;AACtB,2BAAe;AAAA,UACjB;AAAA,QACF;AAEA,YAAI,CAAC,uBAAuB,aAAa,IAAI;AAC3C,gBAAM,WAAW,QAAQ,UAAU,WAAW,CAAC;AAC/C,cAAI,SAAS,UAAU,KAAK,QAAQ,KAAK,QAAQ,GAAG;AAClD,kCAAsB;AACtB,2BAAe;AAAA,UACjB;AAAA,QACF;AAEA,YAAI,CAAC,uBAAuB,QAAQ,UAAU,GAAG;AAC/C,gBAAM,QAAQ,QAAQ,MAAM,2BAA2B;AACvD,cAAI,OAAO;AACT,kBAAM,CAAC,EAAE,WAAW,eAAe,WAAW,IAAI;AAClD,kBAAM,eAAe,aAAa,OAAO,iBAAiB;AAC1D,kBAAM,SAAS,GAAG,WAAW,IAAI,WAAW;AAC5C,mBAAO;AAAA,UACT;AAAA,QACF;AAAA,MACF,OAAO;AACL,cAAM,eAAe,QAAQ,YAAY,GAAG;AAC5C,YAAI,iBAAiB,IAAI;AACvB,gBAAM,WAAW,QAAQ,UAAU,eAAe,CAAC;AACnD,cAAI,SAAS,UAAU,KAAK,QAAQ,KAAK,QAAQ,GAAG;AAClD,kCAAsB;AACtB,2BAAe;AAAA,UACjB;AAAA,QACF;AAEA,YAAI,CAAC,uBAAuB,eAAe,IAAI;AAC7C,gBAAM,aAAa,QAAQ,UAAU,aAAa,CAAC;AACnD,gBAAM,qBAAqB,cAAc,KAAK,WAAW,UAAU;AACnE,cAAI,CAAC,sBAAsB,WAAW,UAAU,KAAK,QAAQ,KAAK,UAAU,GAAG;AAC7E,kCAAsB;AACtB,2BAAe;AAAA,UACjB;AAAA,QACF;AAAA,MACF;AAEA,UAAI,uBAAuB,iBAAiB,IAAI;AAC9C,cAAM,gBAAgB,QAAQ,UAAU,GAAG,YAAY,EAAE,QAAQ,SAAS,EAAE;AAC5E,cAAM,eAAe,QAAQ,UAAU,eAAe,CAAC,EAAE,QAAQ,SAAS,EAAE;AAE5E,YAAI,iBAAiB,IAAI;AACvB,gBAAMA,UAAS,GAAG,aAAa;AAC/B,iBAAOA;AAAA,QACT;AAEA,cAAM,SAAS,GAAG,aAAa,IAAI,aAAa,UAAU,GAAG,CAAC,CAAC;AAC/D,eAAO;AAAA,MACT;AAEA,YAAM,aAAa,QAAQ,QAAQ,SAAS,EAAE;AAC9C,aAAO;AAAA,IACT;AAAA,IACA,UAAU,CAAC,UAAU;AACnB,UAAI,CAAC,YAAY,mBAAmB,KAAK,KAAK,EAAG,QAAO;AACxD,YAAM,MAAM,OAAO,WAAW,KAAK;AACnC,aAAO,CAAC,OAAO,MAAM,GAAG,KAAK,OAAO;AAAA,IACtC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAIA,YAAY;AAAA,IACV,SAAS;AAAA,IACT,WAAW,CAAC,UAAU;AACpB,YAAM,UAAU,MAAM,QAAQ,YAAY,iBAAiB,EAAE;AAC7D,YAAM,QAAQ,QAAQ,MAAM,GAAG;AAC/B,UAAI,MAAM,SAAS,GAAG;AACpB,eAAO,GAAG,MAAM,CAAC,CAAC,IAAI,MAAM,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;AAAA,MAC/C;AACA,UAAI,MAAM,CAAC,KAAK,MAAM,CAAC,EAAE,SAAS,GAAG;AACnC,eAAO,GAAG,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,EAAE,UAAU,GAAG,CAAC,CAAC;AAAA,MAChD;AACA,aAAO;AAAA,IACT;AAAA,IACA,UAAU,CAAC,OAAO,OAAO,CAAC,MAAM;AAC9B,YAAM,MAAM,OAAO,WAAW,KAAK;AACnC,YAAM,MAAM,KAAK,OAAO;AACxB,YAAM,MAAM,KAAK,OAAO;AACxB,aAAO,CAAC,OAAO,MAAM,GAAG,KAAK,OAAO,OAAO,OAAO;AAAA,IACpD;AAAA,EACF;AAAA,EACA,cAAc;AAAA,IACZ,SAAS;AAAA,IACT,WAAW,CAAC,UAAU,MAAM,QAAQ,YAAY,iBAAiB,EAAE,EAAE,YAAY;AAAA,IACjF,UAAU,CAAC,UAAU,YAAY,aAAa,KAAK,KAAK;AAAA,EAC1D;AAAA,EACA,MAAM;AAAA,IACJ,SAAS;AAAA,IACT,WAAW,CAAC,UAAU,MAAM,QAAQ,YAAY,eAAe,EAAE;AAAA,IACjE,UAAU,CAAC,UAAU;AACnB,UAAI,MAAM,SAAS,GAAG,GAAG;AACvB,cAAM,WAAW,MAAM,MAAM,GAAG;AAChC,YAAI,SAAS,SAAS,EAAG,QAAO;AAEhC,eAAO,SAAS,MAAM,CAAC,YAAY;AACjC,cAAI,YAAY,GAAI,QAAO;AAC3B,cAAI,CAAC,YAAY,YAAY,KAAK,OAAO,EAAG,QAAO;AACnD,gBAAM,MAAM,OAAO,SAAS,SAAS,EAAE;AACvC,iBAAO,OAAO;AAAA,QAChB,CAAC;AAAA,MACH;AAEA,UAAI,CAAC,YAAY,WAAW,KAAK,KAAK,EAAG,QAAO;AAChD,UAAI,MAAM,SAAS,GAAI,QAAO;AAE9B,YAAM,SAAS,CAAC;AAChB,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,GAAG;AACxC,eAAO,KAAK,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC;AAAA,MACvC;AAEA,UAAI,OAAO,SAAS,EAAG,QAAO;AAE9B,aAAO,OAAO,MAAM,CAAC,UAAU;AAC7B,cAAM,MAAM,OAAO,SAAS,OAAO,EAAE;AACrC,eAAO,OAAO,KAAK,OAAO;AAAA,MAC5B,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EACA,YAAY;AAAA,IACV,SAAS;AAAA,IACT,WAAW,CAAC,UAAU,MAAM,QAAQ,YAAY,iBAAiB,EAAE,EAAE,YAAY;AAAA,IACjF,UAAU,CAAC,UAAU,YAAY,WAAW,KAAK,KAAK;AAAA,EACxD;AAAA,EACA,MAAM;AAAA,IACJ,SAAS;AAAA,IACT,WAAW,CAAC,UAAU,MAAM,QAAQ,YAAY,WAAW,EAAE;AAAA,IAC7D,UAAU,CAAC,UAAU,YAAY,KAAK,KAAK,MAAM,QAAQ,YAAY,WAAW,EAAE,CAAC;AAAA,EACrF;AAAA,EACA,KAAK;AAAA,IACH,SAAS;AAAA,IACT,WAAW,CAAC,UAAU,MAAM,QAAQ,YAAY,WAAW,EAAE;AAAA,IAC7D,UAAU,CAAC,UAAU,YAAY,IAAI,KAAK,MAAM,QAAQ,YAAY,WAAW,EAAE,CAAC;AAAA,EACpF;AACF;AAEA,SAAS,UAAU,MAMR;AACT,QAAM,EAAE,OAAO,SAAS,UAAU,QAAQ,KAAK,IAAI;AAEnD,QAAM,aAAa;AAEnB,MAAI,QAAQ,SAAS,GAAG,KAAK,QAAQ,SAAS,QAAG,KAAK,SAAS,YAAY;AACzE,WAAO,kBAAkB;AAAA,MACvB,OAAO;AAAA,MACP,UAAU,YAAY;AAAA,MACtB,QAAQ,UAAU;AAAA,IACpB,CAAC;AAAA,EACH;AAEA,MAAI,QAAQ,SAAS,GAAG,GAAG;AACzB,WAAO,oBAAoB,UAAU;AAAA,EACvC;AAEA,MAAI,SAAS,QAAQ;AACnB,WAAO;AAAA,EACT;AAEA,QAAM,cAAwB,CAAC;AAC/B,MAAI,aAAa;AAEjB,WAAS,IAAI,GAAG,IAAI,QAAQ,UAAU,aAAa,WAAW,QAAQ,KAAK;AACzE,UAAM,cAAc,QAAQ,CAAC;AAC7B,UAAM,YAAY,WAAW,UAAU;AAEvC,QAAI,gBAAgB,OAAO,WAAW;AACpC,kBAAY,KAAK,SAAS;AAC1B;AAAA,IACF,WAAW,aAAa;AACtB,kBAAY,KAAK,WAAW;AAAA,IAC9B;AAAA,EACF;AAEA,SAAO,YAAY,KAAK,EAAE;AAC5B;AAEA,SAAS,kBAAkB,MAAqE;AAC9F,QAAM,EAAE,OAAO,WAAW,kBAAkB,SAAS,eAAe,IAAI;AAExE,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM;AAAA,IACJ,UAAU;AAAA,IACV,SAAS;AAAA,IACT,OAAO;AAAA,EACT,IAAI,yBAAyB,EAAE,QAAQ,SAAS,CAAC;AAEjD,QAAM,kBAAkB,MACrB,QAAQ,IAAI,OAAO,KAAK,eAAe,QAAQ,uBAAuB,MAAM,CAAC,IAAI,GAAG,GAAG,EAAE,EACzF,QAAQ,kBAAkB,GAAG;AAEhC,QAAM,QAAQ,gBAAgB,MAAM,GAAG;AACvC,QAAM,cAAc,MAAM,CAAC,KAAK;AAChC,QAAM,iBAAiB,MAAM,CAAC,KAAK;AAEnC,MAAI,CAAC,eAAe,CAAC,eAAgB,QAAO;AAI5C,QAAM,iBAAiB,0BAA0B,EAAE,UAAU,OAAO,CAAC;AAErE,QAAM,WAAW,eAAe;AAChC,QAAM,YAAY,eAAe,MAAM,GAAG,cAAc;AAExD,QAAM,MAAM,OAAO,GAAG,QAAQ,IAAI,aAAa,EAAE,EAAE;AAEnD,MAAI,OAAO,MAAM,GAAG,GAAG;AACrB,UAAM,gBAAgB,MAAM,QAAQ,UAAU,EAAE;AAChD,QAAI,CAAC,cAAe,QAAO;AAC3B,WAAO,GAAG,cAAc,GAAG,aAAa;AAAA,EAC1C;AAEA,QAAM,qBAAqB,iBAAiB,MAAM,MAAM,SAAS,GAAG,KAAK,MAAM,SAAS,gBAAgB;AAExG,MAAI;AACF,UAAM,YAAY,mBAAmB,QAAQ;AAAA,MAC3C;AAAA,MACA,uBAAuB,YAAY,UAAU,SAAS;AAAA,MACtD,uBAAuB;AAAA,IACzB,CAAC;AACD,UAAM,SAAS,UAAU,OAAO,GAAG;AAEnC,QAAI,sBAAsB,CAAC,WAAW;AACpC,UAAI,OAAO,MAAM,WAAW,GAAG;AAC7B,cAAMC,eAAc,OAAO,QAAQ,SAAS,KAAK,gBAAgB,EAAE;AACnE,eAAOA;AAAA,MACT;AAEA,YAAM,cAAc,OAAO,QAAQ,wBAAwB,KAAK,gBAAgB,MAAM;AACtF,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT,QAAQ;AACN,UAAM,eAAe,SAAS,QAAQ,yBAAyB,cAAc;AAC7E,QAAI,SAAS,GAAG,cAAc,GAAG,YAAY;AAC7C,QAAI,oBAAoB;AACtB,gBAAU,GAAG,gBAAgB,GAAG,SAAS;AAAA,IAC3C;AAEA,WAAO;AAAA,EACT;AACF;AAEA,SAAS,oBAAoB,OAAuB;AAClD,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,MAAI,SAAS,MAAM,CAAC,KAAK;AAEzB,MAAI,MAAM,SAAS,GAAG,GAAG;AACvB,cAAU,KAAK,MAAM,CAAC,KAAK,IAAI,UAAU,GAAG,CAAC,CAAC;AAAA,EAChD;AAEA,SAAO,GAAG,MAAM;AAClB;AAEA,SAAS,iBAAiB,MAKf;AACT,QAAM,EAAE,OAAO,WAAW,UAAU,OAAO,IAAI;AAE/C,SAAO,YAAY,UAAU,OAAO,EAAE,UAAU,OAAO,CAAC,IAAI,MAAM,QAAQ,YAAY,WAAW,EAAE;AACrG;AAEA,SAAS,gBAAgB,MAAkE;AACzF,QAAM,EAAE,QAAQ,SAAS,MAAM,IAAI;AAEnC,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,SAAS,IAAI,OAAO,UAAU,IAAI,QAAQ,QAAQ,KAAK;AACzE,QAAI,QAAQ,CAAC,MAAM,KAAK;AACtB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,kBAAkB,MAA0E;AACnG,QAAM,EAAE,QAAQ,SAAS,cAAc,IAAI;AAE3C,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,OAAO,UAAU,IAAI,QAAQ,QAAQ,KAAK;AAC5D,QAAI,QAAQ,CAAC,MAAM,KAAK;AACtB;AACA,UAAI,SAAS,eAAe;AAC1B,eAAO,IAAI;AAAA,MACb;AAAA,IACF;AAAA,EACF;AAEA,SAAO,OAAO;AAChB;AAEA,SAAS,yBAAyB,MAOvB;AACT,QAAM,EAAE,UAAU,MAAM,eAAe,mBAAmB,UAAU,iBAAiB,IAAI;AAEzF,MAAI,sBAAsB,UAAa,YAAY,qBAAqB,QAAW;AACjF,QAAI,oBAAoB,SAAS,QAAQ;AACvC,YAAM,qBAAqB,SAAS,UAAU,GAAG,iBAAiB,EAAE,QAAQ,OAAO,EAAE,EAAE;AAEvF,UAAI,aAAa;AACjB,eAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,YAAI,KAAK,KAAK,SAAS,CAAC,KAAK,EAAE,GAAG;AAChC;AACA,cAAI,eAAe,oBAAoB;AACrC,mBAAO,IAAI;AAAA,UACb;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,SAAS,YAAY;AACvB,UAAM,gBAAgB,gBAAgB,aAAa;AACnD,QAAI,eAAe;AACjB,YAAM,QAAQ,SAAS,MAAM,oBAAoB;AACjD,UAAI,QAAQ,CAAC,GAAG;AACd,eAAO,SAAS,YAAY,MAAM,CAAC,CAAC,IAAI;AAAA,MAC1C;AACA,aAAO,SAAS;AAAA,IAClB;AACA,WAAO,SAAS;AAAA,EAClB;AACA,SAAO,SAAS;AAClB;AAEA,SAAS,wBAAwB,MAOtB;AACT,QAAM,EAAE,UAAU,aAAa,iBAAiB,mBAAmB,UAAU,iBAAiB,IAAI;AAClG,MAAI,WAAW;AACf,MAAI,gBAAgB;AAEpB,MAAI,sBAAsB,UAAa,YAAY,qBAAqB,QAAW;AACjF,UAAM,mBAAmB,gBAAgB;AAAA,MACvC,QAAQ;AAAA,MACR,SAAS,YAAY;AAAA,MACrB,OAAO;AAAA,IACT,CAAC;AAED,QAAI,oBAAoB,SAAS,QAAQ;AACvC,YAAM,sBAAsB,KAAK,IAAI,kBAAkB,gBAAgB,MAAM;AAE7E,eAAS,IAAI,GAAG,IAAI,YAAY,QAAQ,UAAU,IAAI,SAAS,QAAQ,KAAK;AAC1E,YAAI,YAAY,QAAQ,CAAC,MAAM,KAAK;AAClC;AACA,cAAI,iBAAiB,qBAAqB;AACxC,uBAAW,IAAI;AAAA,UACjB;AAAA,QACF;AAAA,MACF;AAEA,aAAO;AAAA,IACT;AAAA,EACF;AAEA,WAAS,IAAI,GAAG,IAAI,YAAY,QAAQ,UAAU,IAAI,SAAS,QAAQ,KAAK;AAC1E,QAAI,YAAY,QAAQ,CAAC,MAAM,KAAK;AAClC;AACA,UAAI,iBAAiB,gBAAgB,QAAQ;AAC3C,mBAAW,IAAI;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;","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 decimalSeparatorCache = new Map<string, string>();\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 percentageCommaChars: /[^\\d.,]/g,\n decimalMark: /[.,]/,\n decimalMarks: /[.,]/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\n/**\n * `percentage` đọc và vẽ dấu `,` ở `locale` này — tức locale dùng `,` làm dấu thập phân (vi, de,\n * fr, es, pt-BR, ru…). Locale dùng `.` (en, en-GB, zh, ja…), hay một dấu khác hẳn (ar `٫` — nhánh\n * tiền tệ cũng chưa đọc được nó), giữ đúng hành vi thượng nguồn.\n *\n * ⚠ Khác thượng nguồn (3.1.0) — hàm MỚI, cho `percentage`.\n */\nfunction usesDecimalComma(locale: string): boolean {\n let decimal = decimalSeparatorCache.get(locale);\n if (decimal === undefined) {\n decimal = \".\";\n try {\n decimal = new Intl.NumberFormat(locale).formatToParts(1.5).find((part) => part.type === \"decimal\")?.value ?? \".\";\n } catch {\n // Locale không hợp lệ → `.` như thượng nguồn.\n }\n decimalSeparatorCache.set(locale, decimal);\n }\n return decimal === \",\";\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 `##.##%`, tối đa 2 chữ số lẻ. Giá trị thô (vế thứ hai của `onValueChange`, và thứ\n * `validate` nhận) LUÔN dùng `.`: `\"12.5\"`.\n *\n * Dấu thập phân theo `locale` (`3.1.0`): ở locale dùng `,` (vi, de, fr…) ô đọc CẢ `,` lẫn `.` là\n * 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à\n * hiện `12,5%`. Locale dùng `.` (en…): như thượng nguồn, `,` bị bỏ qua (`12,5` → `125%`).\n */\n percentage: {\n pattern: \"##.##%\",\n transform: (value, { locale = DEFAULT_LOCALE } = {}) => {\n /*\n * ⚠ Khác thượng nguồn (3.1.0): thượng nguồn chỉ hiểu `.`, nên ở `vi` gõ `12,5` ra `125%` — gấp\n * mười lần, và chỉ phép kiểm khoảng (`max`) bắt được. Ở locale dùng `,`, dấu `,` hay `.` ĐẦU\n * TIÊN là dấu thập phân, mọi dấu sau bị bỏ — đúng luật \"dấu chấm đầu tiên thắng\" của nhánh\n * thượng nguồn bên dưới. Dạng chuẩn app trả lại qua `value` (`\"12.5\"`) vì thế đọc thẳng ra\n * chính nó, không cần luật riêng như `isCanonicalAmount` của tiền tệ: phần trăm không có dấu\n * nhóm. Cái giá: dán một số CÓ nhóm nghìn (`1.234,5`) đọc sai — phần trăm hiếm khi tới đó.\n */\n if (usesDecimalComma(locale)) {\n const cleaned = value.replace(REGEX_CACHE.percentageCommaChars, \"\");\n const markIndex = cleaned.search(REGEX_CACHE.decimalMark);\n if (markIndex === -1) return cleaned;\n const fractionPart = cleaned\n .slice(markIndex + 1)\n .replace(REGEX_CACHE.decimalMarks, \"\")\n .slice(0, 2);\n return `${cleaned.slice(0, markIndex)}.${fractionPart}`;\n }\n\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 // ⚠ Khác thượng nguồn (3.1.0): dấu thập phân theo `locale` — xem `isLocalizedPercentage`.\n return applyPercentageMask(cleanValue, { locale: isLocalizedPercentage(mask) ? locale : undefined });\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\n/**\n * Giá trị thô dạng chuẩn (`\"12.5\"`) → chuỗi hiển thị (`\"12.5%\"`).\n *\n * ⚠ 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` →\n * `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\n * 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.\n */\nfunction applyPercentageMask(value: string, opts: { locale?: string } = {}): string {\n if (!value) return \"\";\n\n const decimalSeparator = usesDecimalComma(opts.locale ?? DEFAULT_LOCALE) ? \",\" : \".\";\n const parts = value.split(\".\");\n let result = parts[0] ?? \"0\";\n\n if (value.includes(\".\")) {\n result += `${decimalSeparator}${(parts[1] ?? \"\").substring(0, 2)}`;\n }\n\n return `${result}%`;\n}\n\n/**\n * Ô phần trăm này vẽ dấu thập phân theo `locale` — khi và chỉ khi chuỗi nó hiện ra sẽ được chính\n * `transform` của `percentage` đọc lại: mẫu `\"percentage\"`, hoặc một `MaskPattern` tự viết dùng lại\n * `transform` đó (`{ ...MASK_PATTERNS.percentage, validate }`). Mẫu `%` tự viết với `transform` riêng\n * giữ `.` như thượng nguồn: vẽ `12,5%` cho một `transform` chỉ hiểu `.` là mời nó đọc thành 125.\n *\n * ⚠ Khác thượng nguồn (3.1.0) — hàm MỚI.\n */\nfunction isLocalizedPercentage(mask: MaskPatternKey | MaskPattern | undefined): boolean {\n return mask === \"percentage\" || (typeof mask === \"object\" && mask.transform === MASK_PATTERNS.percentage.transform);\n}\n\n/** Ô phần trăm đang đọc và vẽ dấu `,` — `mask-input.tsx` dùng để chọn cách đặt con trỏ. */\nfunction isDecimalCommaPercentage(opts: { mask: MaskPatternKey | MaskPattern | undefined; locale?: string }): boolean {\n return isLocalizedPercentage(opts.mask) && usesDecimalComma(opts.locale ?? DEFAULT_LOCALE);\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\n/**\n * Con trỏ sau khi ô phần trăm ở chế độ dấu phẩy (`isDecimalCommaPercentage`) vẽ lại chuỗi.\n *\n * ⚠ Khác thượng nguồn (3.1.0) — hàm MỚI. `getCurrencyCaretPosition` chỉ đếm CHỮ SỐ trước con trỏ,\n * 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ố\n * 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\n * `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.\n * Ở đâ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ó.\n * Chế độ dấu chấm (en…) vẫn đi `getCurrencyCaretPosition` như cũ.\n */\nfunction getPercentageCaretPosition(opts: { newValue: string; oldValue: string; oldCursorPosition: number }): number {\n const { newValue, oldValue, oldCursorPosition } = opts;\n\n const markIndex = oldValue.search(REGEX_CACHE.decimalMark);\n const target =\n oldValue.slice(0, oldCursorPosition).replace(REGEX_CACHE.nonDigits, \"\").length +\n (markIndex !== -1 && markIndex < oldCursorPosition ? 1 : 0);\n if (target === 0) return 0;\n\n let seen = 0;\n for (let i = 0; i < newValue.length; i++) {\n if (/[\\d.,]/.test(newValue.charAt(i))) {\n seen++;\n if (seen === target) return i + 1;\n }\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 getPercentageCaretPosition,\n getUnmaskedValue,\n isCurrencyAtEnd,\n isCurrencyMask,\n isDecimalCommaPercentage,\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":["cleaned","finalResult"]}
|
|
@@ -22,12 +22,13 @@ interface MaskInputProps extends React.ComponentProps<"input"> {
|
|
|
22
22
|
*/
|
|
23
23
|
currency?: string;
|
|
24
24
|
/**
|
|
25
|
-
* Locale BCP 47 cho `mask="currency"` — quyết định dấu nhóm, dấu thập phân và vị trí ký hiệu
|
|
26
|
-
*
|
|
27
|
-
*
|
|
25
|
+
* Locale BCP 47 cho `mask="currency"` — quyết định dấu nhóm, dấu thập phân và vị trí ký hiệu — và
|
|
26
|
+
* cho `mask="percentage"` — dấu thập phân (từ `3.1.0`: `"vi"` đọc `12,5` là 12,5 và hiện `12,5%`;
|
|
27
|
+
* giá trị thô vẫn là `"12.5"`). Không truyền thì theo `locale` của `ComitorLocaleProvider` (không
|
|
28
|
+
* có provider → `"en"` → `$1,234,567`; `"vi"` kèm `currency="VND"` → `1.234.567 ₫`).
|
|
28
29
|
*
|
|
29
|
-
* ⚠ Khác thượng nguồn: thượng nguồn mặc định cứng `"en-US"
|
|
30
|
-
* `"vi-VN"`.
|
|
30
|
+
* ⚠ Khác thượng nguồn: thượng nguồn mặc định cứng `"en-US"`, và `percentage` không đọc `locale`.
|
|
31
|
+
* Trước `3.0.0` mặc định cứng là `"vi-VN"`.
|
|
31
32
|
*/
|
|
32
33
|
locale?: string;
|
|
33
34
|
asChild?: boolean;
|
|
@@ -12,9 +12,11 @@ import {
|
|
|
12
12
|
fromUnmaskedIndex,
|
|
13
13
|
getCurrencyCaretPosition,
|
|
14
14
|
getPatternCaretPosition,
|
|
15
|
+
getPercentageCaretPosition,
|
|
15
16
|
getUnmaskedValue,
|
|
16
17
|
isCurrencyAtEnd,
|
|
17
18
|
isCurrencyMask,
|
|
19
|
+
isDecimalCommaPercentage,
|
|
18
20
|
MASK_PATTERNS,
|
|
19
21
|
NUMERIC_MASK_PATTERNS,
|
|
20
22
|
REGEX_CACHE,
|
|
@@ -194,7 +196,9 @@ function MaskInput(props) {
|
|
|
194
196
|
transform: maskPattern.transform,
|
|
195
197
|
...transformOpts
|
|
196
198
|
});
|
|
197
|
-
if (
|
|
199
|
+
if (isDecimalCommaPercentage({ mask, locale: transformOpts.locale })) {
|
|
200
|
+
newCursorPosition = getPercentageCaretPosition({ newValue, oldValue: inputValue, oldCursorPosition });
|
|
201
|
+
} else if (CURRENCY_PERCENTAGE_SYMBOLS.test(maskPattern.pattern)) {
|
|
198
202
|
newCursorPosition = getCurrencyCaretPosition({
|
|
199
203
|
newValue,
|
|
200
204
|
mask,
|
|
@@ -359,31 +363,29 @@ function MaskInput(props) {
|
|
|
359
363
|
mask
|
|
360
364
|
});
|
|
361
365
|
target.value = newMaskedValue;
|
|
366
|
+
let newCursorPosition = newMaskedValue.length;
|
|
362
367
|
if (isCurrencyMask({ mask, pattern: maskPattern.pattern })) {
|
|
363
368
|
const currencyAtEnd = isCurrencyAtEnd(transformOpts);
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
count++;
|
|
380
|
-
if (count <= unmaskedCount) {
|
|
381
|
-
position = i + 1;
|
|
369
|
+
const symbolStart = newMaskedValue.search(/\s*[^\d\s]+$/);
|
|
370
|
+
newCursorPosition = currencyAtEnd && symbolStart !== -1 ? symbolStart : newMaskedValue.length;
|
|
371
|
+
} else if (maskPattern.pattern.includes("%")) {
|
|
372
|
+
newCursorPosition = Math.max(0, newMaskedValue.length - 1);
|
|
373
|
+
} else {
|
|
374
|
+
try {
|
|
375
|
+
const unmaskedCount = unmasked.length;
|
|
376
|
+
let position = 0;
|
|
377
|
+
let count = 0;
|
|
378
|
+
for (let i = 0; i < maskPattern.pattern.length && i < newMaskedValue.length; i++) {
|
|
379
|
+
if (maskPattern.pattern[i] === "#") {
|
|
380
|
+
count++;
|
|
381
|
+
if (count <= unmaskedCount) {
|
|
382
|
+
position = i + 1;
|
|
383
|
+
}
|
|
382
384
|
}
|
|
383
385
|
}
|
|
386
|
+
newCursorPosition = position;
|
|
387
|
+
} catch {
|
|
384
388
|
}
|
|
385
|
-
newCursorPosition = position;
|
|
386
|
-
} catch {
|
|
387
389
|
}
|
|
388
390
|
target.setSelectionRange(newCursorPosition, newCursorPosition);
|
|
389
391
|
if (!isControlled) setInternalValue(newMaskedValue);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/ui/mask-input.tsx"],"sourcesContent":["\"use client\";\n\n/*\n * Port từ Dice UI (https://diceui.com/docs/components/mask-input) — MIT, © 2024 Sadman Sakib.\n * Giữ nguyên cấu trúc thượng nguồn để còn đối chiếu ngược được; mọi chỗ đổi đều có chú thích.\n */\n\n/*\n * ⚠ Khác thượng nguồn: thượng nguồn là MỘT file. Ở đây nửa trên (bảng `MASK_PATTERNS` + mọi hàm\n * thuần) nằm ở `mask-input-constants.ts` — hằng dữ liệu export ra không được sống trong file\n * `\"use client\"` (Server Component đọc ra `undefined`, không throw). File này chỉ còn component,\n * và chỉ `import` từ đó, KHÔNG `export` lại (re-export vòng qua file client là vô hiệu).\n */\n\nimport { Slot } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { useComitorLocale } from \"../../i18n/locale-provider.js\";\nimport { cn } from \"../../lib/cn.js\";\nimport { useComposedRefs } from \"../../lib/compose-refs.js\";\nimport {\n applyMask,\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 toUnmaskedIndex\n} from \"./mask-input-constants.js\";\n\ntype InputElement = React.ComponentRef<\"input\">;\n\ninterface MaskInputProps extends React.ComponentProps<\"input\"> {\n value?: string;\n defaultValue?: string;\n onValueChange?: (maskedValue: string, unmaskedValue: string) => void;\n onValidate?: (isValid: boolean, unmaskedValue: string) => void;\n validationMode?: \"onChange\" | \"onBlur\" | \"onSubmit\" | \"onTouched\" | \"all\";\n /**\n * Tên một mẫu dựng sẵn (`\"currency\"`, `\"percentage\"`, `\"time\"`, `\"creditCard\"`…) hoặc một\n * `MaskPattern` tự viết. ⚠ `\"phone\"`, `\"date\"`, `\"ssn\"`, `\"zipCode\"`, `\"ein\"` là định dạng Hoa Kỳ\n * — số điện thoại ở app Comitor dùng `PhoneInput`.\n */\n mask?: MaskPatternKey | MaskPattern;\n maskPlaceholder?: string;\n /**\n * Mã tiền tệ ISO 4217 cho `mask=\"currency\"`. Mặc định `\"USD\"`, như thượng nguồn.\n *\n * Tiền tệ là thiết lập VÙNG / nghiệp vụ, không suy từ ngôn ngữ: app Comitor truyền từ cài đặt\n * workspace (vd `\"VND\"`). Trước `3.0.0` mặc định cứng là `\"VND\"`.\n */\n currency?: string;\n /**\n * Locale BCP 47 cho `mask=\"currency\"` — quyết định dấu nhóm, dấu thập phân và vị trí ký hiệu.\n * Không truyền thì theo `locale` của `ComitorLocaleProvider` (không có provider → `\"en\"` →\n * `$1,234,567`; `\"vi\"` kèm `currency=\"VND\"` → `1.234.567 ₫`).\n *\n * ⚠ Khác thượng nguồn: thượng nguồn mặc định cứng `\"en-US\"`. Trước `3.0.0` mặc định cứng là\n * `\"vi-VN\"`.\n */\n locale?: string;\n asChild?: boolean;\n invalid?: boolean;\n withoutMask?: boolean;\n}\n\nfunction MaskInput(props: MaskInputProps) {\n const {\n value: valueProp,\n defaultValue,\n onValueChange: onValueChangeProp,\n onValidate,\n onBlur: onBlurProp,\n onFocus: onFocusProp,\n onKeyDown: onKeyDownProp,\n onPaste: onPasteProp,\n onCompositionStart: onCompositionStartProp,\n onCompositionEnd: onCompositionEndProp,\n validationMode = \"onChange\",\n mask,\n maskPlaceholder,\n currency = DEFAULT_CURRENCY,\n locale: localeProp,\n placeholder,\n inputMode,\n min,\n max,\n maxLength,\n asChild = false,\n disabled = false,\n invalid = false,\n readOnly = false,\n required = false,\n withoutMask = false,\n className,\n ref,\n ...inputProps\n } = props;\n\n const { locale: providerLocale } = useComitorLocale();\n const locale = localeProp ?? providerLocale;\n const [internalValue, setInternalValue] = React.useState(defaultValue ?? \"\");\n const [focused, setFocused] = React.useState(false);\n const [composing, setComposing] = React.useState(false);\n const [touched, setTouched] = React.useState(false);\n const inputRef = React.useRef<HTMLInputElement>(null);\n const composedRef = useComposedRefs(ref, inputRef);\n\n const isControlled = valueProp !== undefined;\n const value = isControlled ? valueProp : internalValue;\n\n const maskPattern = React.useMemo(() => {\n if (typeof mask === \"string\") {\n return MASK_PATTERNS[mask];\n }\n return mask;\n }, [mask]);\n\n const transformOpts = React.useMemo(\n () => ({\n currency,\n locale\n }),\n [currency, locale]\n );\n\n const placeholderValue = React.useMemo(() => {\n if (withoutMask) return placeholder;\n\n if (placeholder && maskPlaceholder) {\n return focused ? maskPlaceholder : placeholder;\n }\n\n if (maskPlaceholder) {\n return focused ? maskPlaceholder : undefined;\n }\n\n return placeholder;\n }, [placeholder, maskPlaceholder, focused, withoutMask]);\n\n const displayValue = React.useMemo(() => {\n if (withoutMask || !maskPattern || !value) return value ?? \"\";\n const unmasked = getUnmaskedValue({\n value,\n transform: maskPattern.transform,\n ...transformOpts\n });\n return applyMask({\n value: unmasked,\n pattern: maskPattern.pattern,\n ...transformOpts,\n mask\n });\n }, [value, maskPattern, withoutMask, transformOpts, mask]);\n\n const tokenCount = React.useMemo(() => {\n if (!maskPattern || CURRENCY_PERCENTAGE_SYMBOLS.test(maskPattern.pattern)) return undefined;\n return maskPattern.pattern.match(REGEX_CACHE.hashPattern)?.length ?? 0;\n }, [maskPattern]);\n\n const calculatedMaxLength = tokenCount ? maskPattern?.pattern.length : maxLength;\n\n const calculatedInputMode = React.useMemo(() => {\n if (inputMode) return inputMode;\n if (!maskPattern) return undefined;\n\n if (mask === \"currency\" || mask === \"percentage\" || mask === \"ipv4\") {\n return \"decimal\";\n }\n\n if (typeof mask === \"string\" && NUMERIC_MASK_PATTERNS.test(mask)) {\n return \"numeric\";\n }\n return undefined;\n }, [maskPattern, mask, inputMode]);\n\n const shouldValidate = React.useCallback(\n (trigger: \"change\" | \"blur\") => {\n if (!onValidate || !maskPattern?.validate) return false;\n\n switch (validationMode) {\n case \"onChange\":\n return trigger === \"change\";\n case \"onBlur\":\n return trigger === \"blur\";\n case \"onSubmit\":\n return false;\n case \"onTouched\":\n return touched ? trigger === \"change\" : trigger === \"blur\";\n case \"all\":\n return true;\n default:\n return trigger === \"change\";\n }\n },\n [onValidate, maskPattern, validationMode, touched]\n );\n\n const validationOpts = React.useMemo(\n () => ({\n min: typeof min === \"string\" ? Number.parseFloat(min) : min,\n max: typeof max === \"string\" ? Number.parseFloat(max) : max\n }),\n [min, max]\n );\n\n const onInputValidate = React.useCallback(\n (unmaskedValue: string) => {\n if (onValidate && maskPattern?.validate) {\n const isValid = maskPattern.validate(unmaskedValue, validationOpts);\n onValidate(isValid, unmaskedValue);\n }\n },\n [onValidate, maskPattern?.validate, validationOpts]\n );\n\n const onValueChange = React.useCallback(\n (event: React.ChangeEvent<InputElement>) => {\n const inputValue = event.target.value;\n let newValue = inputValue;\n let unmaskedValue = inputValue;\n\n if (composing) {\n if (!isControlled) setInternalValue(inputValue);\n return;\n }\n\n if (withoutMask || !maskPattern) {\n if (!isControlled) setInternalValue(inputValue);\n if (shouldValidate(\"change\")) onValidate?.(true, inputValue);\n onValueChangeProp?.(inputValue, inputValue);\n return;\n }\n\n if (maskPattern) {\n unmaskedValue = getUnmaskedValue({\n value: inputValue,\n transform: maskPattern.transform,\n ...transformOpts\n });\n newValue = applyMask({\n value: unmaskedValue,\n pattern: maskPattern.pattern,\n ...transformOpts,\n mask\n });\n\n if (inputRef.current && newValue !== inputValue) {\n const inputElement = inputRef.current;\n if (!(inputElement instanceof HTMLInputElement)) return;\n\n const oldCursorPosition = inputElement.selectionStart ?? 0;\n\n inputElement.value = newValue;\n\n const currentUnmasked = getUnmaskedValue({\n value: newValue,\n transform: maskPattern.transform,\n ...transformOpts\n });\n\n let newCursorPosition: number;\n\n const previousUnmasked = getUnmaskedValue({\n value,\n transform: maskPattern.transform,\n ...transformOpts\n });\n\n if (CURRENCY_PERCENTAGE_SYMBOLS.test(maskPattern.pattern)) {\n newCursorPosition = getCurrencyCaretPosition({\n newValue,\n mask,\n transformOpts,\n oldCursorPosition,\n oldValue: inputValue,\n previousUnmasked\n });\n } else {\n newCursorPosition = getPatternCaretPosition({\n newValue,\n maskPattern,\n currentUnmasked,\n oldCursorPosition,\n oldValue: inputValue,\n previousUnmasked\n });\n }\n\n if (isCurrencyMask({ mask, pattern: maskPattern.pattern })) {\n if (mask === \"currency\") {\n const currencyAtEnd = isCurrencyAtEnd(transformOpts);\n if (!currencyAtEnd) {\n newCursorPosition = Math.max(1, newCursorPosition);\n }\n } else {\n newCursorPosition = Math.max(1, newCursorPosition);\n }\n } else if (maskPattern.pattern.includes(\"%\")) {\n newCursorPosition = Math.min(newValue.length - 1, newCursorPosition);\n }\n\n newCursorPosition = Math.min(newCursorPosition, newValue.length);\n\n inputElement.setSelectionRange(newCursorPosition, newCursorPosition);\n }\n }\n\n if (!isControlled) {\n setInternalValue(newValue);\n }\n\n if (shouldValidate(\"change\")) {\n onInputValidate(unmaskedValue);\n }\n\n onValueChangeProp?.(newValue, unmaskedValue);\n },\n [\n maskPattern,\n isControlled,\n onValueChangeProp,\n onValidate,\n onInputValidate,\n composing,\n shouldValidate,\n withoutMask,\n transformOpts,\n mask,\n value\n ]\n );\n\n const onFocus = React.useCallback(\n (event: React.FocusEvent<InputElement>) => {\n onFocusProp?.(event);\n if (event.defaultPrevented) return;\n\n setFocused(true);\n },\n [onFocusProp]\n );\n\n const onBlur = React.useCallback(\n (event: React.FocusEvent<InputElement>) => {\n onBlurProp?.(event);\n if (event.defaultPrevented) return;\n\n setFocused(false);\n\n if (!touched) {\n setTouched(true);\n }\n\n if (shouldValidate(\"blur\")) {\n const currentValue = event.target.value;\n const unmaskedValue = maskPattern\n ? getUnmaskedValue({\n value: currentValue,\n transform: maskPattern.transform,\n ...transformOpts\n })\n : currentValue;\n onInputValidate(unmaskedValue);\n }\n },\n [onBlurProp, touched, shouldValidate, onInputValidate, maskPattern, transformOpts]\n );\n\n const onCompositionStart = React.useCallback(\n (event: React.CompositionEvent<InputElement>) => {\n onCompositionStartProp?.(event);\n if (event.defaultPrevented) return;\n\n setComposing(true);\n },\n [onCompositionStartProp]\n );\n\n const onCompositionEnd = React.useCallback(\n (event: React.CompositionEvent<InputElement>) => {\n onCompositionEndProp?.(event);\n if (event.defaultPrevented) return;\n\n setComposing(false);\n\n const inputElement = inputRef.current;\n if (!inputElement) return;\n if (!(inputElement instanceof HTMLInputElement)) return;\n const inputValue = inputElement.value;\n\n if (!maskPattern || withoutMask) {\n if (!isControlled) setInternalValue(inputValue);\n if (shouldValidate(\"change\")) onValidate?.(true, inputValue);\n onValueChangeProp?.(inputValue, inputValue);\n return;\n }\n\n const unmasked = getUnmaskedValue({\n value: inputValue,\n transform: maskPattern.transform,\n ...transformOpts\n });\n const masked = applyMask({\n value: unmasked,\n pattern: maskPattern.pattern,\n ...transformOpts,\n mask\n });\n\n if (!isControlled) setInternalValue(masked);\n if (shouldValidate(\"change\")) onInputValidate(unmasked);\n onValueChangeProp?.(masked, unmasked);\n },\n [\n onCompositionEndProp,\n maskPattern,\n withoutMask,\n isControlled,\n shouldValidate,\n onValidate,\n onValueChangeProp,\n transformOpts,\n mask,\n onInputValidate\n ]\n );\n\n const onPaste = React.useCallback(\n (event: React.ClipboardEvent<InputElement>) => {\n onPasteProp?.(event);\n if (event.defaultPrevented) return;\n\n if (withoutMask || !maskPattern) return;\n\n if (mask === \"ipv4\") return;\n\n const target = event.target as InputElement;\n if (!(target instanceof HTMLInputElement)) return;\n\n const pastedData = event.clipboardData.getData(\"text\");\n if (!pastedData) return;\n\n event.preventDefault();\n\n const currentValue = target.value;\n const selectionStart = target.selectionStart ?? 0;\n const selectionEnd = target.selectionEnd ?? 0;\n\n const beforeSelection = currentValue.slice(0, selectionStart);\n const afterSelection = currentValue.slice(selectionEnd);\n const newInputValue = beforeSelection + pastedData + afterSelection;\n\n const unmasked = getUnmaskedValue({\n value: newInputValue,\n transform: maskPattern.transform,\n ...transformOpts\n });\n const newMaskedValue = applyMask({\n value: unmasked,\n pattern: maskPattern.pattern,\n ...transformOpts,\n mask\n });\n\n target.value = newMaskedValue;\n\n if (isCurrencyMask({ mask, pattern: maskPattern.pattern })) {\n const currencyAtEnd = isCurrencyAtEnd(transformOpts);\n const caret = currencyAtEnd ? newMaskedValue.search(/\\s*[^\\d\\s]+$/) : newMaskedValue.length;\n target.setSelectionRange(caret, caret);\n return;\n }\n\n if (maskPattern.pattern.includes(\"%\")) {\n target.setSelectionRange(newMaskedValue.length - 1, newMaskedValue.length - 1);\n return;\n }\n\n let newCursorPosition = newMaskedValue.length;\n try {\n const unmaskedCount = unmasked.length;\n let position = 0;\n let count = 0;\n\n for (let i = 0; i < maskPattern.pattern.length && i < newMaskedValue.length; i++) {\n if (maskPattern.pattern[i] === \"#\") {\n count++;\n if (count <= unmaskedCount) {\n position = i + 1;\n }\n }\n }\n newCursorPosition = position;\n } catch {\n // Lỗi thì để con trỏ ở cuối.\n }\n\n target.setSelectionRange(newCursorPosition, newCursorPosition);\n\n if (!isControlled) setInternalValue(newMaskedValue);\n if (shouldValidate(\"change\")) onInputValidate(unmasked);\n onValueChangeProp?.(newMaskedValue, unmasked);\n },\n [\n onPasteProp,\n withoutMask,\n maskPattern,\n mask,\n transformOpts,\n isControlled,\n shouldValidate,\n onInputValidate,\n onValueChangeProp\n ]\n );\n\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent<InputElement>) => {\n onKeyDownProp?.(event);\n if (event.defaultPrevented) return;\n\n if (withoutMask || !maskPattern) return;\n\n if (mask === \"ipv4\") return;\n\n if (event.key === \"Backspace\") {\n const target = event.target as InputElement;\n if (!(target instanceof HTMLInputElement)) return;\n const cursorPosition = target.selectionStart ?? 0;\n const selectionEnd = target.selectionEnd ?? 0;\n const currentValue = target.value;\n\n if (\n mask === \"currency\" ||\n mask === \"percentage\" ||\n maskPattern.pattern.includes(\"$\") ||\n maskPattern.pattern.includes(\"€\") ||\n maskPattern.pattern.includes(\"%\")\n ) {\n return;\n }\n\n if (cursorPosition !== selectionEnd) {\n return;\n }\n\n if (cursorPosition > 0) {\n const charBeforeCursor = currentValue[cursorPosition - 1];\n\n if (charBeforeCursor) {\n event.preventDefault();\n\n const unmaskedIndex = toUnmaskedIndex({\n masked: currentValue,\n pattern: maskPattern.pattern,\n caret: cursorPosition\n });\n\n if (unmaskedIndex > 0) {\n const currentUnmasked = getUnmaskedValue({\n value: currentValue,\n transform: maskPattern.transform,\n ...transformOpts\n });\n const nextUnmasked = currentUnmasked.slice(0, unmaskedIndex - 1) + currentUnmasked.slice(unmaskedIndex);\n const nextMasked = applyMask({\n value: nextUnmasked,\n pattern: maskPattern.pattern,\n ...transformOpts,\n mask\n });\n\n target.value = nextMasked;\n const nextCaret = fromUnmaskedIndex({\n masked: nextMasked,\n pattern: maskPattern.pattern,\n unmaskedIndex: unmaskedIndex - 1\n });\n\n target.setSelectionRange(nextCaret, nextCaret);\n\n onValueChangeProp?.(nextMasked, nextUnmasked);\n }\n return;\n }\n }\n }\n\n if (event.key === \"Delete\") {\n const target = event.target as InputElement;\n if (!(target instanceof HTMLInputElement)) return;\n const cursorPosition = target.selectionStart ?? 0;\n const selectionEnd = target.selectionEnd ?? 0;\n const currentValue = target.value;\n\n if (\n mask === \"currency\" ||\n mask === \"percentage\" ||\n maskPattern.pattern.includes(\"$\") ||\n maskPattern.pattern.includes(\"€\") ||\n maskPattern.pattern.includes(\"%\")\n ) {\n return;\n }\n\n if (cursorPosition !== selectionEnd) {\n return;\n }\n\n if (cursorPosition < currentValue.length) {\n const charAtCursor = currentValue[cursorPosition];\n\n if (charAtCursor) {\n event.preventDefault();\n\n const unmaskedIndex = toUnmaskedIndex({\n masked: currentValue,\n pattern: maskPattern.pattern,\n caret: cursorPosition\n });\n\n const currentUnmasked = getUnmaskedValue({\n value: currentValue,\n transform: maskPattern.transform,\n ...transformOpts\n });\n\n if (unmaskedIndex < currentUnmasked.length) {\n const nextUnmasked = currentUnmasked.slice(0, unmaskedIndex) + currentUnmasked.slice(unmaskedIndex + 1);\n const nextMasked = applyMask({\n value: nextUnmasked,\n pattern: maskPattern.pattern,\n ...transformOpts,\n mask\n });\n\n target.value = nextMasked;\n const nextCaret = fromUnmaskedIndex({\n masked: nextMasked,\n pattern: maskPattern.pattern,\n unmaskedIndex: unmaskedIndex\n });\n\n target.setSelectionRange(nextCaret, nextCaret);\n\n onValueChangeProp?.(nextMasked, nextUnmasked);\n }\n return;\n }\n }\n }\n },\n [maskPattern, onKeyDownProp, onValueChangeProp, transformOpts, mask, withoutMask]\n );\n\n const InputPrimitive = asChild ? Slot : \"input\";\n\n return (\n <InputPrimitive\n // ⚠ Khác thượng nguồn: chỉ phát thuộc tính khi THẬT SỰ lỗi — `aria-invalid=\"false\"` hợp lệ\n // nhưng thừa, và cùng quy ước với `Input`/`Combobox` của gói. Đặt TRƯỚC spread để\n // `aria-invalid` do `<FormControl>` dán vào vẫn thắng.\n aria-invalid={invalid || undefined}\n data-disabled={disabled ? \"\" : undefined}\n data-invalid={invalid ? \"\" : undefined}\n data-readonly={readOnly ? \"\" : undefined}\n data-required={required ? \"\" : undefined}\n data-slot=\"mask-input\"\n {...inputProps}\n className={cn(\n /*\n * ⚠ Khác thượng nguồn: chuỗi lớp của `Input` Comitor thay cho của shadcn, để ô có mặt nạ\n * đứng cạnh ô nhập thường trong cùng một form không lệch nhau (cao, nền, viền, cỡ chữ,\n * focus). Số đo của từng vai trò ở chú thích đầu `input.tsx`:\n * viền nghỉ `border-input` · hover `--control-edge-strong` · focus `--primary-ink`;\n * lỗi `border-destructive-ink` (không phải `border-destructive` — token TÔ NỀN).\n * Bỏ `dark:bg-input/30` (viết thẳng thang màu — bảng tương phản cao không đổi được) và\n * `shadow-xs` (không có trong bộ bóng của gói).\n */\n \"w-full h-9 px-3 rounded-md border bg-background text-sm text-foreground placeholder:text-muted-foreground\",\n \"transition-colors duration-150 ease-out\",\n \"focus:outline-none focus:ring-2 focus:ring-ring/50 focus:border-primary-ink\",\n \"disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-muted\",\n \"border-input hover:border-control-edge-strong\",\n \"aria-invalid:border-destructive-ink aria-invalid:hover:border-destructive-ink aria-invalid:focus:border-destructive-ink aria-invalid:focus:ring-destructive/30\",\n className\n )}\n placeholder={placeholderValue}\n ref={composedRef}\n value={displayValue}\n disabled={disabled}\n maxLength={calculatedMaxLength}\n readOnly={readOnly}\n required={required}\n inputMode={calculatedInputMode}\n min={min}\n max={max}\n onFocus={onFocus}\n onBlur={onBlur}\n onKeyDown={onKeyDown}\n onPaste={onPaste}\n onChange={onValueChange}\n onCompositionStart={onCompositionStart}\n onCompositionEnd={onCompositionEnd}\n />\n );\n}\n\nexport { MaskInput, type MaskInputProps };\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/mask-input.tsx"],"sourcesContent":["\"use client\";\n\n/*\n * Port từ Dice UI (https://diceui.com/docs/components/mask-input) — MIT, © 2024 Sadman Sakib.\n * Giữ nguyên cấu trúc thượng nguồn để còn đối chiếu ngược được; mọi chỗ đổi đều có chú thích.\n */\n\n/*\n * ⚠ Khác thượng nguồn: thượng nguồn là MỘT file. Ở đây nửa trên (bảng `MASK_PATTERNS` + mọi hàm\n * thuần) nằm ở `mask-input-constants.ts` — hằng dữ liệu export ra không được sống trong file\n * `\"use client\"` (Server Component đọc ra `undefined`, không throw). File này chỉ còn component,\n * và chỉ `import` từ đó, KHÔNG `export` lại (re-export vòng qua file client là vô hiệu).\n */\n\nimport { Slot } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { useComitorLocale } from \"../../i18n/locale-provider.js\";\nimport { cn } from \"../../lib/cn.js\";\nimport { useComposedRefs } from \"../../lib/compose-refs.js\";\nimport {\n applyMask,\n CURRENCY_PERCENTAGE_SYMBOLS,\n DEFAULT_CURRENCY,\n fromUnmaskedIndex,\n getCurrencyCaretPosition,\n getPatternCaretPosition,\n getPercentageCaretPosition,\n getUnmaskedValue,\n isCurrencyAtEnd,\n isCurrencyMask,\n isDecimalCommaPercentage,\n MASK_PATTERNS,\n type MaskPattern,\n type MaskPatternKey,\n NUMERIC_MASK_PATTERNS,\n REGEX_CACHE,\n toUnmaskedIndex\n} from \"./mask-input-constants.js\";\n\ntype InputElement = React.ComponentRef<\"input\">;\n\ninterface MaskInputProps extends React.ComponentProps<\"input\"> {\n value?: string;\n defaultValue?: string;\n onValueChange?: (maskedValue: string, unmaskedValue: string) => void;\n onValidate?: (isValid: boolean, unmaskedValue: string) => void;\n validationMode?: \"onChange\" | \"onBlur\" | \"onSubmit\" | \"onTouched\" | \"all\";\n /**\n * Tên một mẫu dựng sẵn (`\"currency\"`, `\"percentage\"`, `\"time\"`, `\"creditCard\"`…) hoặc một\n * `MaskPattern` tự viết. ⚠ `\"phone\"`, `\"date\"`, `\"ssn\"`, `\"zipCode\"`, `\"ein\"` là định dạng Hoa Kỳ\n * — số điện thoại ở app Comitor dùng `PhoneInput`.\n */\n mask?: MaskPatternKey | MaskPattern;\n maskPlaceholder?: string;\n /**\n * Mã tiền tệ ISO 4217 cho `mask=\"currency\"`. Mặc định `\"USD\"`, như thượng nguồn.\n *\n * Tiền tệ là thiết lập VÙNG / nghiệp vụ, không suy từ ngôn ngữ: app Comitor truyền từ cài đặt\n * workspace (vd `\"VND\"`). Trước `3.0.0` mặc định cứng là `\"VND\"`.\n */\n currency?: string;\n /**\n * Locale BCP 47 cho `mask=\"currency\"` — quyết định dấu nhóm, dấu thập phân và vị trí ký hiệu — và\n * cho `mask=\"percentage\"` — dấu thập phân (từ `3.1.0`: `\"vi\"` đọc `12,5` là 12,5 và hiện `12,5%`;\n * giá trị thô vẫn là `\"12.5\"`). Không truyền thì theo `locale` của `ComitorLocaleProvider` (không\n * có provider → `\"en\"` → `$1,234,567`; `\"vi\"` kèm `currency=\"VND\"` → `1.234.567 ₫`).\n *\n * ⚠ Khác thượng nguồn: thượng nguồn mặc định cứng `\"en-US\"`, và `percentage` không đọc `locale`.\n * Trước `3.0.0` mặc định cứng là `\"vi-VN\"`.\n */\n locale?: string;\n asChild?: boolean;\n invalid?: boolean;\n withoutMask?: boolean;\n}\n\nfunction MaskInput(props: MaskInputProps) {\n const {\n value: valueProp,\n defaultValue,\n onValueChange: onValueChangeProp,\n onValidate,\n onBlur: onBlurProp,\n onFocus: onFocusProp,\n onKeyDown: onKeyDownProp,\n onPaste: onPasteProp,\n onCompositionStart: onCompositionStartProp,\n onCompositionEnd: onCompositionEndProp,\n validationMode = \"onChange\",\n mask,\n maskPlaceholder,\n currency = DEFAULT_CURRENCY,\n locale: localeProp,\n placeholder,\n inputMode,\n min,\n max,\n maxLength,\n asChild = false,\n disabled = false,\n invalid = false,\n readOnly = false,\n required = false,\n withoutMask = false,\n className,\n ref,\n ...inputProps\n } = props;\n\n const { locale: providerLocale } = useComitorLocale();\n const locale = localeProp ?? providerLocale;\n const [internalValue, setInternalValue] = React.useState(defaultValue ?? \"\");\n const [focused, setFocused] = React.useState(false);\n const [composing, setComposing] = React.useState(false);\n const [touched, setTouched] = React.useState(false);\n const inputRef = React.useRef<HTMLInputElement>(null);\n const composedRef = useComposedRefs(ref, inputRef);\n\n const isControlled = valueProp !== undefined;\n const value = isControlled ? valueProp : internalValue;\n\n const maskPattern = React.useMemo(() => {\n if (typeof mask === \"string\") {\n return MASK_PATTERNS[mask];\n }\n return mask;\n }, [mask]);\n\n const transformOpts = React.useMemo(\n () => ({\n currency,\n locale\n }),\n [currency, locale]\n );\n\n const placeholderValue = React.useMemo(() => {\n if (withoutMask) return placeholder;\n\n if (placeholder && maskPlaceholder) {\n return focused ? maskPlaceholder : placeholder;\n }\n\n if (maskPlaceholder) {\n return focused ? maskPlaceholder : undefined;\n }\n\n return placeholder;\n }, [placeholder, maskPlaceholder, focused, withoutMask]);\n\n const displayValue = React.useMemo(() => {\n if (withoutMask || !maskPattern || !value) return value ?? \"\";\n const unmasked = getUnmaskedValue({\n value,\n transform: maskPattern.transform,\n ...transformOpts\n });\n return applyMask({\n value: unmasked,\n pattern: maskPattern.pattern,\n ...transformOpts,\n mask\n });\n }, [value, maskPattern, withoutMask, transformOpts, mask]);\n\n const tokenCount = React.useMemo(() => {\n if (!maskPattern || CURRENCY_PERCENTAGE_SYMBOLS.test(maskPattern.pattern)) return undefined;\n return maskPattern.pattern.match(REGEX_CACHE.hashPattern)?.length ?? 0;\n }, [maskPattern]);\n\n const calculatedMaxLength = tokenCount ? maskPattern?.pattern.length : maxLength;\n\n const calculatedInputMode = React.useMemo(() => {\n if (inputMode) return inputMode;\n if (!maskPattern) return undefined;\n\n if (mask === \"currency\" || mask === \"percentage\" || mask === \"ipv4\") {\n return \"decimal\";\n }\n\n if (typeof mask === \"string\" && NUMERIC_MASK_PATTERNS.test(mask)) {\n return \"numeric\";\n }\n return undefined;\n }, [maskPattern, mask, inputMode]);\n\n const shouldValidate = React.useCallback(\n (trigger: \"change\" | \"blur\") => {\n if (!onValidate || !maskPattern?.validate) return false;\n\n switch (validationMode) {\n case \"onChange\":\n return trigger === \"change\";\n case \"onBlur\":\n return trigger === \"blur\";\n case \"onSubmit\":\n return false;\n case \"onTouched\":\n return touched ? trigger === \"change\" : trigger === \"blur\";\n case \"all\":\n return true;\n default:\n return trigger === \"change\";\n }\n },\n [onValidate, maskPattern, validationMode, touched]\n );\n\n const validationOpts = React.useMemo(\n () => ({\n min: typeof min === \"string\" ? Number.parseFloat(min) : min,\n max: typeof max === \"string\" ? Number.parseFloat(max) : max\n }),\n [min, max]\n );\n\n const onInputValidate = React.useCallback(\n (unmaskedValue: string) => {\n if (onValidate && maskPattern?.validate) {\n const isValid = maskPattern.validate(unmaskedValue, validationOpts);\n onValidate(isValid, unmaskedValue);\n }\n },\n [onValidate, maskPattern?.validate, validationOpts]\n );\n\n const onValueChange = React.useCallback(\n (event: React.ChangeEvent<InputElement>) => {\n const inputValue = event.target.value;\n let newValue = inputValue;\n let unmaskedValue = inputValue;\n\n if (composing) {\n if (!isControlled) setInternalValue(inputValue);\n return;\n }\n\n if (withoutMask || !maskPattern) {\n if (!isControlled) setInternalValue(inputValue);\n if (shouldValidate(\"change\")) onValidate?.(true, inputValue);\n onValueChangeProp?.(inputValue, inputValue);\n return;\n }\n\n if (maskPattern) {\n unmaskedValue = getUnmaskedValue({\n value: inputValue,\n transform: maskPattern.transform,\n ...transformOpts\n });\n newValue = applyMask({\n value: unmaskedValue,\n pattern: maskPattern.pattern,\n ...transformOpts,\n mask\n });\n\n if (inputRef.current && newValue !== inputValue) {\n const inputElement = inputRef.current;\n if (!(inputElement instanceof HTMLInputElement)) return;\n\n const oldCursorPosition = inputElement.selectionStart ?? 0;\n\n inputElement.value = newValue;\n\n const currentUnmasked = getUnmaskedValue({\n value: newValue,\n transform: maskPattern.transform,\n ...transformOpts\n });\n\n let newCursorPosition: number;\n\n const previousUnmasked = getUnmaskedValue({\n value,\n transform: maskPattern.transform,\n ...transformOpts\n });\n\n if (isDecimalCommaPercentage({ mask, locale: transformOpts.locale })) {\n // ⚠ Khác thượng nguồn (3.1.0): mặt nạ đổi cả dấu thập phân (`.` → `,`) — lý do ở hàm.\n newCursorPosition = getPercentageCaretPosition({ newValue, oldValue: inputValue, oldCursorPosition });\n } else if (CURRENCY_PERCENTAGE_SYMBOLS.test(maskPattern.pattern)) {\n newCursorPosition = getCurrencyCaretPosition({\n newValue,\n mask,\n transformOpts,\n oldCursorPosition,\n oldValue: inputValue,\n previousUnmasked\n });\n } else {\n newCursorPosition = getPatternCaretPosition({\n newValue,\n maskPattern,\n currentUnmasked,\n oldCursorPosition,\n oldValue: inputValue,\n previousUnmasked\n });\n }\n\n if (isCurrencyMask({ mask, pattern: maskPattern.pattern })) {\n if (mask === \"currency\") {\n const currencyAtEnd = isCurrencyAtEnd(transformOpts);\n if (!currencyAtEnd) {\n newCursorPosition = Math.max(1, newCursorPosition);\n }\n } else {\n newCursorPosition = Math.max(1, newCursorPosition);\n }\n } else if (maskPattern.pattern.includes(\"%\")) {\n newCursorPosition = Math.min(newValue.length - 1, newCursorPosition);\n }\n\n newCursorPosition = Math.min(newCursorPosition, newValue.length);\n\n inputElement.setSelectionRange(newCursorPosition, newCursorPosition);\n }\n }\n\n if (!isControlled) {\n setInternalValue(newValue);\n }\n\n if (shouldValidate(\"change\")) {\n onInputValidate(unmaskedValue);\n }\n\n onValueChangeProp?.(newValue, unmaskedValue);\n },\n [\n maskPattern,\n isControlled,\n onValueChangeProp,\n onValidate,\n onInputValidate,\n composing,\n shouldValidate,\n withoutMask,\n transformOpts,\n mask,\n value\n ]\n );\n\n const onFocus = React.useCallback(\n (event: React.FocusEvent<InputElement>) => {\n onFocusProp?.(event);\n if (event.defaultPrevented) return;\n\n setFocused(true);\n },\n [onFocusProp]\n );\n\n const onBlur = React.useCallback(\n (event: React.FocusEvent<InputElement>) => {\n onBlurProp?.(event);\n if (event.defaultPrevented) return;\n\n setFocused(false);\n\n if (!touched) {\n setTouched(true);\n }\n\n if (shouldValidate(\"blur\")) {\n const currentValue = event.target.value;\n const unmaskedValue = maskPattern\n ? getUnmaskedValue({\n value: currentValue,\n transform: maskPattern.transform,\n ...transformOpts\n })\n : currentValue;\n onInputValidate(unmaskedValue);\n }\n },\n [onBlurProp, touched, shouldValidate, onInputValidate, maskPattern, transformOpts]\n );\n\n const onCompositionStart = React.useCallback(\n (event: React.CompositionEvent<InputElement>) => {\n onCompositionStartProp?.(event);\n if (event.defaultPrevented) return;\n\n setComposing(true);\n },\n [onCompositionStartProp]\n );\n\n const onCompositionEnd = React.useCallback(\n (event: React.CompositionEvent<InputElement>) => {\n onCompositionEndProp?.(event);\n if (event.defaultPrevented) return;\n\n setComposing(false);\n\n const inputElement = inputRef.current;\n if (!inputElement) return;\n if (!(inputElement instanceof HTMLInputElement)) return;\n const inputValue = inputElement.value;\n\n if (!maskPattern || withoutMask) {\n if (!isControlled) setInternalValue(inputValue);\n if (shouldValidate(\"change\")) onValidate?.(true, inputValue);\n onValueChangeProp?.(inputValue, inputValue);\n return;\n }\n\n const unmasked = getUnmaskedValue({\n value: inputValue,\n transform: maskPattern.transform,\n ...transformOpts\n });\n const masked = applyMask({\n value: unmasked,\n pattern: maskPattern.pattern,\n ...transformOpts,\n mask\n });\n\n if (!isControlled) setInternalValue(masked);\n if (shouldValidate(\"change\")) onInputValidate(unmasked);\n onValueChangeProp?.(masked, unmasked);\n },\n [\n onCompositionEndProp,\n maskPattern,\n withoutMask,\n isControlled,\n shouldValidate,\n onValidate,\n onValueChangeProp,\n transformOpts,\n mask,\n onInputValidate\n ]\n );\n\n const onPaste = React.useCallback(\n (event: React.ClipboardEvent<InputElement>) => {\n onPasteProp?.(event);\n if (event.defaultPrevented) return;\n\n if (withoutMask || !maskPattern) return;\n\n if (mask === \"ipv4\") return;\n\n const target = event.target as InputElement;\n if (!(target instanceof HTMLInputElement)) return;\n\n const pastedData = event.clipboardData.getData(\"text\");\n if (!pastedData) return;\n\n event.preventDefault();\n\n const currentValue = target.value;\n const selectionStart = target.selectionStart ?? 0;\n const selectionEnd = target.selectionEnd ?? 0;\n\n const beforeSelection = currentValue.slice(0, selectionStart);\n const afterSelection = currentValue.slice(selectionEnd);\n const newInputValue = beforeSelection + pastedData + afterSelection;\n\n const unmasked = getUnmaskedValue({\n value: newInputValue,\n transform: maskPattern.transform,\n ...transformOpts\n });\n const newMaskedValue = applyMask({\n value: unmasked,\n pattern: maskPattern.pattern,\n ...transformOpts,\n mask\n });\n\n target.value = newMaskedValue;\n\n /*\n * ⚠ Khác thượng nguồn (3.0.1): nhánh tiền tệ và phần trăm chỉ đặt con trỏ rồi đi TIẾP xuống\n * phần báo giá trị. Bản gốc `return` ngay trong hai nhánh này, nên dán vào ô tiền/phần trăm\n * chỉ đổi DOM: `onValueChange` không chạy, state nội bộ không đổi, và lần render kế tiếp trả\n * ô về số cũ — đo trên comitor-ds: dán \"777\" hiện `777 US$`, app vẫn giữ 12345.56.\n */\n let newCursorPosition = newMaskedValue.length;\n if (isCurrencyMask({ mask, pattern: maskPattern.pattern })) {\n const currencyAtEnd = isCurrencyAtEnd(transformOpts);\n const symbolStart = newMaskedValue.search(/\\s*[^\\d\\s]+$/);\n newCursorPosition = currencyAtEnd && symbolStart !== -1 ? symbolStart : newMaskedValue.length;\n } else if (maskPattern.pattern.includes(\"%\")) {\n newCursorPosition = Math.max(0, newMaskedValue.length - 1);\n } else {\n try {\n const unmaskedCount = unmasked.length;\n let position = 0;\n let count = 0;\n\n for (let i = 0; i < maskPattern.pattern.length && i < newMaskedValue.length; i++) {\n if (maskPattern.pattern[i] === \"#\") {\n count++;\n if (count <= unmaskedCount) {\n position = i + 1;\n }\n }\n }\n newCursorPosition = position;\n } catch {\n // Lỗi thì để con trỏ ở cuối.\n }\n }\n\n target.setSelectionRange(newCursorPosition, newCursorPosition);\n\n if (!isControlled) setInternalValue(newMaskedValue);\n if (shouldValidate(\"change\")) onInputValidate(unmasked);\n onValueChangeProp?.(newMaskedValue, unmasked);\n },\n [\n onPasteProp,\n withoutMask,\n maskPattern,\n mask,\n transformOpts,\n isControlled,\n shouldValidate,\n onInputValidate,\n onValueChangeProp\n ]\n );\n\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent<InputElement>) => {\n onKeyDownProp?.(event);\n if (event.defaultPrevented) return;\n\n if (withoutMask || !maskPattern) return;\n\n if (mask === \"ipv4\") return;\n\n if (event.key === \"Backspace\") {\n const target = event.target as InputElement;\n if (!(target instanceof HTMLInputElement)) return;\n const cursorPosition = target.selectionStart ?? 0;\n const selectionEnd = target.selectionEnd ?? 0;\n const currentValue = target.value;\n\n if (\n mask === \"currency\" ||\n mask === \"percentage\" ||\n maskPattern.pattern.includes(\"$\") ||\n maskPattern.pattern.includes(\"€\") ||\n maskPattern.pattern.includes(\"%\")\n ) {\n return;\n }\n\n if (cursorPosition !== selectionEnd) {\n return;\n }\n\n if (cursorPosition > 0) {\n const charBeforeCursor = currentValue[cursorPosition - 1];\n\n if (charBeforeCursor) {\n event.preventDefault();\n\n const unmaskedIndex = toUnmaskedIndex({\n masked: currentValue,\n pattern: maskPattern.pattern,\n caret: cursorPosition\n });\n\n if (unmaskedIndex > 0) {\n const currentUnmasked = getUnmaskedValue({\n value: currentValue,\n transform: maskPattern.transform,\n ...transformOpts\n });\n const nextUnmasked = currentUnmasked.slice(0, unmaskedIndex - 1) + currentUnmasked.slice(unmaskedIndex);\n const nextMasked = applyMask({\n value: nextUnmasked,\n pattern: maskPattern.pattern,\n ...transformOpts,\n mask\n });\n\n target.value = nextMasked;\n const nextCaret = fromUnmaskedIndex({\n masked: nextMasked,\n pattern: maskPattern.pattern,\n unmaskedIndex: unmaskedIndex - 1\n });\n\n target.setSelectionRange(nextCaret, nextCaret);\n\n onValueChangeProp?.(nextMasked, nextUnmasked);\n }\n return;\n }\n }\n }\n\n if (event.key === \"Delete\") {\n const target = event.target as InputElement;\n if (!(target instanceof HTMLInputElement)) return;\n const cursorPosition = target.selectionStart ?? 0;\n const selectionEnd = target.selectionEnd ?? 0;\n const currentValue = target.value;\n\n if (\n mask === \"currency\" ||\n mask === \"percentage\" ||\n maskPattern.pattern.includes(\"$\") ||\n maskPattern.pattern.includes(\"€\") ||\n maskPattern.pattern.includes(\"%\")\n ) {\n return;\n }\n\n if (cursorPosition !== selectionEnd) {\n return;\n }\n\n if (cursorPosition < currentValue.length) {\n const charAtCursor = currentValue[cursorPosition];\n\n if (charAtCursor) {\n event.preventDefault();\n\n const unmaskedIndex = toUnmaskedIndex({\n masked: currentValue,\n pattern: maskPattern.pattern,\n caret: cursorPosition\n });\n\n const currentUnmasked = getUnmaskedValue({\n value: currentValue,\n transform: maskPattern.transform,\n ...transformOpts\n });\n\n if (unmaskedIndex < currentUnmasked.length) {\n const nextUnmasked = currentUnmasked.slice(0, unmaskedIndex) + currentUnmasked.slice(unmaskedIndex + 1);\n const nextMasked = applyMask({\n value: nextUnmasked,\n pattern: maskPattern.pattern,\n ...transformOpts,\n mask\n });\n\n target.value = nextMasked;\n const nextCaret = fromUnmaskedIndex({\n masked: nextMasked,\n pattern: maskPattern.pattern,\n unmaskedIndex: unmaskedIndex\n });\n\n target.setSelectionRange(nextCaret, nextCaret);\n\n onValueChangeProp?.(nextMasked, nextUnmasked);\n }\n return;\n }\n }\n }\n },\n [maskPattern, onKeyDownProp, onValueChangeProp, transformOpts, mask, withoutMask]\n );\n\n const InputPrimitive = asChild ? Slot : \"input\";\n\n return (\n <InputPrimitive\n // ⚠ Khác thượng nguồn: chỉ phát thuộc tính khi THẬT SỰ lỗi — `aria-invalid=\"false\"` hợp lệ\n // nhưng thừa, và cùng quy ước với `Input`/`Combobox` của gói. Đặt TRƯỚC spread để\n // `aria-invalid` do `<FormControl>` dán vào vẫn thắng.\n aria-invalid={invalid || undefined}\n data-disabled={disabled ? \"\" : undefined}\n data-invalid={invalid ? \"\" : undefined}\n data-readonly={readOnly ? \"\" : undefined}\n data-required={required ? \"\" : undefined}\n data-slot=\"mask-input\"\n {...inputProps}\n className={cn(\n /*\n * ⚠ Khác thượng nguồn: chuỗi lớp của `Input` Comitor thay cho của shadcn, để ô có mặt nạ\n * đứng cạnh ô nhập thường trong cùng một form không lệch nhau (cao, nền, viền, cỡ chữ,\n * focus). Số đo của từng vai trò ở chú thích đầu `input.tsx`:\n * viền nghỉ `border-input` · hover `--control-edge-strong` · focus `--primary-ink`;\n * lỗi `border-destructive-ink` (không phải `border-destructive` — token TÔ NỀN).\n * Bỏ `dark:bg-input/30` (viết thẳng thang màu — bảng tương phản cao không đổi được) và\n * `shadow-xs` (không có trong bộ bóng của gói).\n */\n \"w-full h-9 px-3 rounded-md border bg-background text-sm text-foreground placeholder:text-muted-foreground\",\n \"transition-colors duration-150 ease-out\",\n \"focus:outline-none focus:ring-2 focus:ring-ring/50 focus:border-primary-ink\",\n \"disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-muted\",\n \"border-input hover:border-control-edge-strong\",\n \"aria-invalid:border-destructive-ink aria-invalid:hover:border-destructive-ink aria-invalid:focus:border-destructive-ink aria-invalid:focus:ring-destructive/30\",\n className\n )}\n placeholder={placeholderValue}\n ref={composedRef}\n value={displayValue}\n disabled={disabled}\n maxLength={calculatedMaxLength}\n readOnly={readOnly}\n required={required}\n inputMode={calculatedInputMode}\n min={min}\n max={max}\n onFocus={onFocus}\n onBlur={onBlur}\n onKeyDown={onKeyDown}\n onPaste={onPaste}\n onChange={onValueChange}\n onCompositionStart={onCompositionStart}\n onCompositionEnd={onCompositionEnd}\n />\n );\n}\n\nexport { MaskInput, type MaskInputProps };\n"],"mappings":"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","names":[]}
|
|
@@ -8,9 +8,17 @@ import * as React from 'react';
|
|
|
8
8
|
*
|
|
9
9
|
* Phần lõi (dò ký tự kích hoạt, lọc, điều hướng bàn phím, định vị danh sách theo con trỏ) là gói
|
|
10
10
|
* headless `@diceui/mention`; file này chỉ phủ style theo token của Comitor.
|
|
11
|
+
*
|
|
12
|
+
* Lọc: không truyền `onFilter` thì so với NHÃN của mục (`label`, thiếu thì `value` — đúng chữ được
|
|
13
|
+
* chèn sau `@`), không dấu, không phân biệt hoa thường. `onFilter` của app vẫn thắng. `exactMatch`
|
|
14
|
+
* không còn tác dụng: lõi bỏ qua nó khi có `onFilter`, mà nay luôn có.
|
|
15
|
+
*
|
|
16
|
+
* Chữ gõ dừng ở dấu cách ĐẦU TIÊN — luật cứng trong `MentionInput` của lõi (chuỗi sau ký tự kích
|
|
17
|
+
* hoạt có dấu cách thì không còn là mention, danh sách đóng), không prop nào mở được. Tên nhiều chữ
|
|
18
|
+
* vì thế gõ LIỀN: "@nguyenvana" tới "Nguyễn Văn An" (bộ lọc mặc định so cả nhãn đã bỏ khoảng trắng).
|
|
11
19
|
*/
|
|
12
20
|
type MentionProps = React.ComponentProps<typeof MentionPrimitive.Root>;
|
|
13
|
-
declare function Mention({ className, dir: dirProp, ...props }: MentionProps): React.JSX.Element;
|
|
21
|
+
declare function Mention({ className, dir: dirProp, onFilter: onFilterProp, ...props }: MentionProps): React.JSX.Element;
|
|
14
22
|
type MentionLabelProps = React.ComponentProps<typeof MentionPrimitive.Label>;
|
|
15
23
|
declare function MentionLabel({ className, ...props }: MentionLabelProps): React.JSX.Element;
|
|
16
24
|
type MentionInputProps = React.ComponentProps<typeof MentionPrimitive.Input>;
|
|
@@ -27,6 +35,10 @@ type MentionContentProps = React.ComponentProps<typeof MentionPrimitive.Content>
|
|
|
27
35
|
*/
|
|
28
36
|
declare function MentionContent({ className, children, "aria-label": ariaLabelProp, ...props }: MentionContentProps): React.JSX.Element;
|
|
29
37
|
type MentionItemProps = React.ComponentProps<typeof MentionPrimitive.Item>;
|
|
30
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Một gợi ý. `value` là thứ app nhận lại (id), `label` là chữ chèn sau `@` — và là chữ bộ lọc mặc
|
|
40
|
+
* định của `Mention` so với chữ gõ (thiếu `label` thì dùng `value`).
|
|
41
|
+
*/
|
|
42
|
+
declare function MentionItem({ className, children, value, label, ...props }: MentionItemProps): React.JSX.Element;
|
|
31
43
|
|
|
32
44
|
export { Mention, MentionContent, type MentionContentProps, MentionInput, type MentionInputProps, MentionItem, type MentionItemProps, MentionLabel, type MentionLabelProps, type MentionProps };
|
|
@@ -5,15 +5,31 @@ import { useDirection } from "@radix-ui/react-direction";
|
|
|
5
5
|
import * as React from "react";
|
|
6
6
|
import { useComitorMessages } from "../../i18n/locale-provider.js";
|
|
7
7
|
import { cn } from "../../lib/cn.js";
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
import { matchesSearch } from "../../lib/text-search.js";
|
|
9
|
+
import { useIsomorphicLayoutEffect } from "../../lib/use-isomorphic-layout-effect.js";
|
|
10
|
+
import { useLazyRef } from "../../lib/use-lazy-ref.js";
|
|
11
|
+
const MentionWrapperContext = React.createContext(null);
|
|
12
|
+
function matchesMentionLabel(label, term) {
|
|
13
|
+
return matchesSearch(label, term) || matchesSearch(label.replace(/\s+/g, ""), term);
|
|
14
|
+
}
|
|
15
|
+
function Mention({ className, dir: dirProp, onFilter: onFilterProp, ...props }) {
|
|
10
16
|
const dir = useDirection(dirProp);
|
|
11
17
|
const listId = React.useId();
|
|
12
|
-
|
|
18
|
+
const itemLabels = useLazyRef(() => /* @__PURE__ */ new Map()).current;
|
|
19
|
+
const defaultOnFilter = React.useCallback(
|
|
20
|
+
(values, term) => values.filter((value) => matchesMentionLabel(itemLabels.get(value) ?? value, term)),
|
|
21
|
+
[itemLabels]
|
|
22
|
+
);
|
|
23
|
+
const wrapperContext = React.useMemo(
|
|
24
|
+
() => ({ listId, itemLabels }),
|
|
25
|
+
[listId, itemLabels]
|
|
26
|
+
);
|
|
27
|
+
return /* @__PURE__ */ jsx(MentionWrapperContext.Provider, { value: wrapperContext, children: /* @__PURE__ */ jsx(
|
|
13
28
|
MentionPrimitive.Root,
|
|
14
29
|
{
|
|
15
30
|
"data-slot": "mention",
|
|
16
31
|
dir,
|
|
32
|
+
onFilter: onFilterProp ?? defaultOnFilter,
|
|
17
33
|
className: cn(
|
|
18
34
|
/*
|
|
19
35
|
* Khối `@Tên` đã chèn. Gói vẽ nó bằng một lớp phủ (`MentionHighlighter`) định vị TUYỆT
|
|
@@ -48,7 +64,7 @@ function MentionLabel({ className, ...props }) {
|
|
|
48
64
|
);
|
|
49
65
|
}
|
|
50
66
|
function MentionInput({ className, ...props }) {
|
|
51
|
-
const listId = React.useContext(
|
|
67
|
+
const listId = React.useContext(MentionWrapperContext)?.listId;
|
|
52
68
|
return /* @__PURE__ */ jsx(
|
|
53
69
|
MentionPrimitive.Input,
|
|
54
70
|
{
|
|
@@ -71,7 +87,7 @@ function MentionInput({ className, ...props }) {
|
|
|
71
87
|
);
|
|
72
88
|
}
|
|
73
89
|
function MentionContent({ className, children, "aria-label": ariaLabelProp, ...props }) {
|
|
74
|
-
const listId = React.useContext(
|
|
90
|
+
const listId = React.useContext(MentionWrapperContext)?.listId;
|
|
75
91
|
const t = useComitorMessages("mention");
|
|
76
92
|
const ariaLabel = ariaLabelProp ?? t.listLabel;
|
|
77
93
|
return /* @__PURE__ */ jsx(MentionPrimitive.Portal, { children: /* @__PURE__ */ jsx(
|
|
@@ -96,11 +112,22 @@ function MentionContent({ className, children, "aria-label": ariaLabelProp, ...p
|
|
|
96
112
|
}
|
|
97
113
|
) });
|
|
98
114
|
}
|
|
99
|
-
function MentionItem({ className, children, ...props }) {
|
|
115
|
+
function MentionItem({ className, children, value, label, ...props }) {
|
|
116
|
+
const itemLabels = React.useContext(MentionWrapperContext)?.itemLabels;
|
|
117
|
+
const text = label ?? value;
|
|
118
|
+
useIsomorphicLayoutEffect(() => {
|
|
119
|
+
if (!itemLabels) return;
|
|
120
|
+
itemLabels.set(value, text);
|
|
121
|
+
return () => {
|
|
122
|
+
if (itemLabels.get(value) === text) itemLabels.delete(value);
|
|
123
|
+
};
|
|
124
|
+
}, [itemLabels, value, text]);
|
|
100
125
|
return /* @__PURE__ */ jsx(
|
|
101
126
|
MentionPrimitive.Item,
|
|
102
127
|
{
|
|
103
128
|
"data-slot": "mention-item",
|
|
129
|
+
value,
|
|
130
|
+
label,
|
|
104
131
|
className: cn(
|
|
105
132
|
/*
|
|
106
133
|
* ⚠ Khác thượng nguồn: bộ lớp của `CommandItem` (mục đang sáng `bg-accent`/`text-accent-
|