@comitor/ui 1.11.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/LICENSE +35 -0
  2. package/README.md +23 -7
  3. package/dist/components/ui/action-bar-constants.d.ts +31 -0
  4. package/dist/components/ui/action-bar-constants.js +9 -0
  5. package/dist/components/ui/action-bar-constants.js.map +1 -0
  6. package/dist/components/ui/action-bar.d.ts +67 -0
  7. package/dist/components/ui/action-bar.js +522 -0
  8. package/dist/components/ui/action-bar.js.map +1 -0
  9. package/dist/components/ui/badge-overflow.d.ts +30 -0
  10. package/dist/components/ui/badge-overflow.js +179 -0
  11. package/dist/components/ui/badge-overflow.js.map +1 -0
  12. package/dist/components/ui/banner.d.ts +79 -0
  13. package/dist/components/ui/banner.js +518 -0
  14. package/dist/components/ui/banner.js.map +1 -0
  15. package/dist/components/ui/circular-progress.d.ts +50 -0
  16. package/dist/components/ui/circular-progress.js +266 -0
  17. package/dist/components/ui/circular-progress.js.map +1 -0
  18. package/dist/components/ui/cropper-constants.d.ts +34 -0
  19. package/dist/components/ui/cropper-constants.js +10 -0
  20. package/dist/components/ui/cropper-constants.js.map +1 -0
  21. package/dist/components/ui/cropper.d.ts +106 -0
  22. package/dist/components/ui/cropper.js +1269 -0
  23. package/dist/components/ui/cropper.js.map +1 -0
  24. package/dist/components/ui/drawer.d.ts +1 -1
  25. package/dist/components/ui/drawer.js +2 -2
  26. package/dist/components/ui/drawer.js.map +1 -1
  27. package/dist/components/ui/kanban-constants.d.ts +61 -0
  28. package/dist/components/ui/kanban-constants.js +21 -0
  29. package/dist/components/ui/kanban-constants.js.map +1 -0
  30. package/dist/components/ui/kanban.d.ts +72 -0
  31. package/dist/components/ui/kanban.js +787 -0
  32. package/dist/components/ui/kanban.js.map +1 -0
  33. package/dist/components/ui/key-value-constants.d.ts +33 -0
  34. package/dist/components/ui/key-value-constants.js +11 -0
  35. package/dist/components/ui/key-value-constants.js.map +1 -0
  36. package/dist/components/ui/key-value.d.ts +96 -0
  37. package/dist/components/ui/key-value.js +641 -0
  38. package/dist/components/ui/key-value.js.map +1 -0
  39. package/dist/components/ui/mask-input-constants.d.ts +99 -0
  40. package/dist/components/ui/mask-input-constants.js +601 -0
  41. package/dist/components/ui/mask-input-constants.js.map +1 -0
  42. package/dist/components/ui/mask-input.d.ts +36 -0
  43. package/dist/components/ui/mask-input.js +558 -0
  44. package/dist/components/ui/mask-input.js.map +1 -0
  45. package/dist/components/ui/mention.d.ts +31 -0
  46. package/dist/components/ui/mention.js +128 -0
  47. package/dist/components/ui/mention.js.map +1 -0
  48. package/dist/components/ui/phone-input-constants.d.ts +54 -0
  49. package/dist/components/ui/phone-input-constants.js +9 -0
  50. package/dist/components/ui/phone-input-constants.js.map +1 -0
  51. package/dist/components/ui/phone-input.d.ts +85 -0
  52. package/dist/components/ui/phone-input.js +743 -0
  53. package/dist/components/ui/phone-input.js.map +1 -0
  54. package/dist/components/ui/rating.d.ts +56 -0
  55. package/dist/components/ui/rating.js +646 -0
  56. package/dist/components/ui/rating.js.map +1 -0
  57. package/dist/components/ui/responsive-dialog.d.ts +35 -0
  58. package/dist/components/ui/responsive-dialog.js +193 -0
  59. package/dist/components/ui/responsive-dialog.js.map +1 -0
  60. package/dist/components/ui/scroll-spy.d.ts +42 -0
  61. package/dist/components/ui/scroll-spy.js +334 -0
  62. package/dist/components/ui/scroll-spy.js.map +1 -0
  63. package/dist/components/ui/stepper-constants.d.ts +29 -0
  64. package/dist/components/ui/stepper-constants.js +9 -0
  65. package/dist/components/ui/stepper-constants.js.map +1 -0
  66. package/dist/components/ui/stepper.d.ts +80 -0
  67. package/dist/components/ui/stepper.js +902 -0
  68. package/dist/components/ui/stepper.js.map +1 -0
  69. package/dist/components/ui/tags-input.d.ts +41 -0
  70. package/dist/components/ui/tags-input.js +127 -0
  71. package/dist/components/ui/tags-input.js.map +1 -0
  72. package/dist/components/ui/time-picker-constants.d.ts +48 -0
  73. package/dist/components/ui/time-picker-constants.js +14 -0
  74. package/dist/components/ui/time-picker-constants.js.map +1 -0
  75. package/dist/components/ui/time-picker.d.ts +115 -0
  76. package/dist/components/ui/time-picker.js +1620 -0
  77. package/dist/components/ui/time-picker.js.map +1 -0
  78. package/dist/components/ui/timeline.d.ts +32 -0
  79. package/dist/components/ui/timeline.js +559 -0
  80. package/dist/components/ui/timeline.js.map +1 -0
  81. package/dist/components/ui/tour-constants.d.ts +39 -0
  82. package/dist/components/ui/tour-constants.js +12 -0
  83. package/dist/components/ui/tour-constants.js.map +1 -0
  84. package/dist/components/ui/tour.d.ts +124 -0
  85. package/dist/components/ui/tour.js +1207 -0
  86. package/dist/components/ui/tour.js.map +1 -0
  87. package/dist/data-grid/data-grid-cell-variants.js +10 -6
  88. package/dist/data-grid/data-grid-cell-variants.js.map +1 -1
  89. package/dist/data-grid/data-grid-cell-wrapper.js +1 -1
  90. package/dist/data-grid/data-grid-cell-wrapper.js.map +1 -1
  91. package/dist/data-grid/data-grid-column-header.js +4 -2
  92. package/dist/data-grid/data-grid-column-header.js.map +1 -1
  93. package/dist/data-grid/data-grid-context-menu.js +5 -4
  94. package/dist/data-grid/data-grid-context-menu.js.map +1 -1
  95. package/dist/data-grid/data-grid-filter-menu.d.ts +12 -1
  96. package/dist/data-grid/data-grid-filter-menu.js +28 -17
  97. package/dist/data-grid/data-grid-filter-menu.js.map +1 -1
  98. package/dist/data-grid/data-grid-keyboard-shortcuts.d.ts +12 -1
  99. package/dist/data-grid/data-grid-keyboard-shortcuts.js +7 -5
  100. package/dist/data-grid/data-grid-keyboard-shortcuts.js.map +1 -1
  101. package/dist/data-grid/data-grid-locale.d.ts +24 -0
  102. package/dist/data-grid/data-grid-locale.js +25 -0
  103. package/dist/data-grid/data-grid-locale.js.map +1 -0
  104. package/dist/data-grid/data-grid-paste-dialog.js +3 -2
  105. package/dist/data-grid/data-grid-paste-dialog.js.map +1 -1
  106. package/dist/data-grid/data-grid-row-height-menu.d.ts +11 -1
  107. package/dist/data-grid/data-grid-row-height-menu.js +25 -28
  108. package/dist/data-grid/data-grid-row-height-menu.js.map +1 -1
  109. package/dist/data-grid/data-grid-row.js +1 -1
  110. package/dist/data-grid/data-grid-row.js.map +1 -1
  111. package/dist/data-grid/data-grid-search.js +6 -5
  112. package/dist/data-grid/data-grid-search.js.map +1 -1
  113. package/dist/data-grid/data-grid-select-column.js +3 -1
  114. package/dist/data-grid/data-grid-select-column.js.map +1 -1
  115. package/dist/data-grid/data-grid-sort-menu.d.ts +12 -1
  116. package/dist/data-grid/data-grid-sort-menu.js +25 -13
  117. package/dist/data-grid/data-grid-sort-menu.js.map +1 -1
  118. package/dist/data-grid/data-grid-view-menu.d.ts +12 -1
  119. package/dist/data-grid/data-grid-view-menu.js +12 -4
  120. package/dist/data-grid/data-grid-view-menu.js.map +1 -1
  121. package/dist/data-grid/data-grid.d.ts +14 -2
  122. package/dist/data-grid/data-grid.js +7 -4
  123. package/dist/data-grid/data-grid.js.map +1 -1
  124. package/dist/data-grid/filters.d.ts +17 -21
  125. package/dist/data-grid/filters.js +73 -52
  126. package/dist/data-grid/filters.js.map +1 -1
  127. package/dist/data-grid/index.d.ts +4 -1
  128. package/dist/data-grid/index.js +20 -2
  129. package/dist/data-grid/index.js.map +1 -1
  130. package/dist/data-grid/messages-en.d.ts +294 -0
  131. package/dist/data-grid/messages-en.js +279 -0
  132. package/dist/data-grid/messages-en.js.map +1 -0
  133. package/dist/data-grid/messages-vi.d.ts +12 -0
  134. package/dist/data-grid/messages-vi.js +279 -0
  135. package/dist/data-grid/messages-vi.js.map +1 -0
  136. package/dist/data-grid/messages.d.ts +549 -262
  137. package/dist/data-grid/messages.js +16 -275
  138. package/dist/data-grid/messages.js.map +1 -1
  139. package/dist/data-grid/sortable.js +1 -1
  140. package/dist/data-grid/sortable.js.map +1 -1
  141. package/dist/data-grid/use-data-grid-undo-redo.d.ts +13 -1
  142. package/dist/data-grid/use-data-grid-undo-redo.js +13 -10
  143. package/dist/data-grid/use-data-grid-undo-redo.js.map +1 -1
  144. package/dist/data-grid/use-data-grid.d.ts +281 -1
  145. package/dist/data-grid/use-data-grid.js +24 -19
  146. package/dist/data-grid/use-data-grid.js.map +1 -1
  147. package/dist/data-grid/utils.d.ts +9 -1
  148. package/dist/data-grid/utils.js +3 -2
  149. package/dist/data-grid/utils.js.map +1 -1
  150. package/dist/index.d.ts +31 -0
  151. package/dist/index.js +42 -0
  152. package/dist/index.js.map +1 -1
  153. package/dist/lib/compose-refs.js.map +1 -0
  154. package/dist/lib/use-as-ref.js.map +1 -0
  155. package/dist/lib/use-is-mobile.d.ts +10 -0
  156. package/dist/lib/use-is-mobile.js +21 -0
  157. package/dist/lib/use-is-mobile.js.map +1 -0
  158. package/dist/lib/use-isomorphic-layout-effect.js.map +1 -0
  159. package/dist/lib/use-lazy-ref.js.map +1 -0
  160. package/dist/lib/visually-hidden-input.d.ts +12 -0
  161. package/dist/lib/visually-hidden-input.js +105 -0
  162. package/dist/lib/visually-hidden-input.js.map +1 -0
  163. package/package.json +5 -1
  164. package/styles.css +19 -0
  165. package/dist/data-grid/compose-refs.js.map +0 -1
  166. package/dist/data-grid/use-as-ref.js.map +0 -1
  167. package/dist/data-grid/use-isomorphic-layout-effect.js.map +0 -1
  168. package/dist/data-grid/use-lazy-ref.js.map +0 -1
  169. /package/dist/{data-grid → lib}/compose-refs.d.ts +0 -0
  170. /package/dist/{data-grid → lib}/compose-refs.js +0 -0
  171. /package/dist/{data-grid → lib}/use-as-ref.d.ts +0 -0
  172. /package/dist/{data-grid → lib}/use-as-ref.js +0 -0
  173. /package/dist/{data-grid → lib}/use-isomorphic-layout-effect.d.ts +0 -0
  174. /package/dist/{data-grid → lib}/use-isomorphic-layout-effect.js +0 -0
  175. /package/dist/{data-grid → lib}/use-lazy-ref.d.ts +0 -0
  176. /package/dist/{data-grid → lib}/use-lazy-ref.js +0 -0
@@ -0,0 +1,743 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Slot } from "@radix-ui/react-slot";
4
+ import { defaultFilter } from "cmdk";
5
+ import { Check, ChevronDown, Globe } from "lucide-react";
6
+ import * as React from "react";
7
+ import { cn } from "../../lib/cn.js";
8
+ import { useComposedRefs } from "../../lib/compose-refs.js";
9
+ import { useAsRef } from "../../lib/use-as-ref.js";
10
+ import { useIsomorphicLayoutEffect } from "../../lib/use-isomorphic-layout-effect.js";
11
+ import { useLazyRef } from "../../lib/use-lazy-ref.js";
12
+ import { VisuallyHiddenInput } from "../../lib/visually-hidden-input.js";
13
+ import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "./command.js";
14
+ import { Input } from "./input.js";
15
+ import {
16
+ DEFAULT_PHONE_INPUT_LABELS
17
+ } from "./phone-input-constants.js";
18
+ import { Popover, PopoverContent, PopoverTrigger } from "./popover.js";
19
+ const ROOT_NAME = "PhoneInput";
20
+ const COUNTRY_SELECT_NAME = "PhoneInputCountrySelect";
21
+ const FIELD_NAME = "PhoneInputField";
22
+ const DEFAULT_COUNTRY = "VN";
23
+ const DEFAULT_DISPLAY_LOCALE = "vi";
24
+ const COUNTRY_DATA = [
25
+ ["af", "93"],
26
+ ["ax", "358"],
27
+ ["al", "355"],
28
+ ["dz", "213"],
29
+ ["as", "1684"],
30
+ ["ad", "376"],
31
+ ["ao", "244"],
32
+ ["ai", "1264"],
33
+ ["ag", "1268"],
34
+ ["ar", "54"],
35
+ ["am", "374"],
36
+ ["aw", "297"],
37
+ ["au", "61"],
38
+ ["at", "43"],
39
+ ["az", "994"],
40
+ ["bs", "1242"],
41
+ ["bh", "973"],
42
+ ["bd", "880"],
43
+ ["bb", "1246"],
44
+ ["by", "375"],
45
+ ["be", "32"],
46
+ ["bz", "501"],
47
+ ["bj", "229"],
48
+ ["bm", "1441"],
49
+ ["bt", "975"],
50
+ ["bo", "591"],
51
+ ["ba", "387"],
52
+ ["bw", "267"],
53
+ ["br", "55"],
54
+ ["io", "246"],
55
+ ["vg", "1284"],
56
+ ["bn", "673"],
57
+ ["bg", "359"],
58
+ ["bf", "226"],
59
+ ["bi", "257"],
60
+ ["kh", "855"],
61
+ ["cm", "237"],
62
+ ["ca", "1"],
63
+ ["cv", "238"],
64
+ ["bq", "599"],
65
+ ["ky", "1345"],
66
+ ["cf", "236"],
67
+ ["td", "235"],
68
+ ["cl", "56"],
69
+ ["cn", "86"],
70
+ ["co", "57"],
71
+ ["km", "269"],
72
+ ["cd", "243"],
73
+ ["cg", "242"],
74
+ ["ck", "682"],
75
+ ["cr", "506"],
76
+ ["ci", "225"],
77
+ ["hr", "385"],
78
+ ["cu", "53"],
79
+ ["cw", "599"],
80
+ ["cy", "357"],
81
+ ["cz", "420"],
82
+ ["dk", "45"],
83
+ ["dj", "253"],
84
+ ["dm", "1767"],
85
+ ["do", "1"],
86
+ ["ec", "593"],
87
+ ["eg", "20"],
88
+ ["sv", "503"],
89
+ ["gq", "240"],
90
+ ["er", "291"],
91
+ ["ee", "372"],
92
+ ["et", "251"],
93
+ ["fk", "500"],
94
+ ["fo", "298"],
95
+ ["fj", "679"],
96
+ ["fi", "358"],
97
+ ["fr", "33"],
98
+ ["gf", "594"],
99
+ ["pf", "689"],
100
+ ["ga", "241"],
101
+ ["gm", "220"],
102
+ ["ge", "995"],
103
+ ["de", "49"],
104
+ ["gh", "233"],
105
+ ["gi", "350"],
106
+ ["gr", "30"],
107
+ ["gl", "299"],
108
+ ["gd", "1473"],
109
+ ["gp", "590"],
110
+ ["gu", "1671"],
111
+ ["gt", "502"],
112
+ ["gg", "44"],
113
+ ["gn", "224"],
114
+ ["gw", "245"],
115
+ ["gy", "592"],
116
+ ["ht", "509"],
117
+ ["hn", "504"],
118
+ ["hk", "852"],
119
+ ["hu", "36"],
120
+ ["is", "354"],
121
+ ["in", "91"],
122
+ ["id", "62"],
123
+ ["ir", "98"],
124
+ ["iq", "964"],
125
+ ["ie", "353"],
126
+ ["im", "44"],
127
+ ["il", "972"],
128
+ ["it", "39"],
129
+ ["jm", "1876"],
130
+ ["jp", "81"],
131
+ ["je", "44"],
132
+ ["jo", "962"],
133
+ ["kz", "7"],
134
+ ["ke", "254"],
135
+ ["ki", "686"],
136
+ ["xk", "383"],
137
+ ["kw", "965"],
138
+ ["kg", "996"],
139
+ ["la", "856"],
140
+ ["lv", "371"],
141
+ ["lb", "961"],
142
+ ["ls", "266"],
143
+ ["lr", "231"],
144
+ ["ly", "218"],
145
+ ["li", "423"],
146
+ ["lt", "370"],
147
+ ["lu", "352"],
148
+ ["mo", "853"],
149
+ ["mk", "389"],
150
+ ["mg", "261"],
151
+ ["mw", "265"],
152
+ ["my", "60"],
153
+ ["mv", "960"],
154
+ ["ml", "223"],
155
+ ["mt", "356"],
156
+ ["mh", "692"],
157
+ ["mq", "596"],
158
+ ["mr", "222"],
159
+ ["mu", "230"],
160
+ ["mx", "52"],
161
+ ["fm", "691"],
162
+ ["md", "373"],
163
+ ["mc", "377"],
164
+ ["mn", "976"],
165
+ ["me", "382"],
166
+ ["ms", "1664"],
167
+ ["ma", "212"],
168
+ ["mz", "258"],
169
+ ["mm", "95"],
170
+ ["na", "264"],
171
+ ["nr", "674"],
172
+ ["np", "977"],
173
+ ["nl", "31"],
174
+ ["nc", "687"],
175
+ ["nz", "64"],
176
+ ["ni", "505"],
177
+ ["ne", "227"],
178
+ ["ng", "234"],
179
+ ["nu", "683"],
180
+ ["nf", "672"],
181
+ ["kp", "850"],
182
+ ["mp", "1670"],
183
+ ["no", "47"],
184
+ ["om", "968"],
185
+ ["pk", "92"],
186
+ ["pw", "680"],
187
+ ["ps", "970"],
188
+ ["pa", "507"],
189
+ ["pg", "675"],
190
+ ["py", "595"],
191
+ ["pe", "51"],
192
+ ["ph", "63"],
193
+ ["pl", "48"],
194
+ ["pt", "351"],
195
+ ["pr", "1"],
196
+ ["qa", "974"],
197
+ ["re", "262"],
198
+ ["ro", "40"],
199
+ ["ru", "7"],
200
+ ["rw", "250"],
201
+ ["bl", "590"],
202
+ ["sh", "290"],
203
+ ["kn", "1869"],
204
+ ["lc", "1758"],
205
+ ["mf", "590"],
206
+ ["pm", "508"],
207
+ ["vc", "1784"],
208
+ ["ws", "685"],
209
+ ["sm", "378"],
210
+ ["st", "239"],
211
+ ["sa", "966"],
212
+ ["sn", "221"],
213
+ ["rs", "381"],
214
+ ["sc", "248"],
215
+ ["sl", "232"],
216
+ ["sg", "65"],
217
+ ["sx", "1721"],
218
+ ["sk", "421"],
219
+ ["si", "386"],
220
+ ["sb", "677"],
221
+ ["so", "252"],
222
+ ["za", "27"],
223
+ ["kr", "82"],
224
+ ["ss", "211"],
225
+ ["es", "34"],
226
+ ["lk", "94"],
227
+ ["sd", "249"],
228
+ ["sr", "597"],
229
+ ["sz", "268"],
230
+ ["se", "46"],
231
+ ["ch", "41"],
232
+ ["sy", "963"],
233
+ ["tw", "886"],
234
+ ["tj", "992"],
235
+ ["tz", "255"],
236
+ ["th", "66"],
237
+ ["tl", "670"],
238
+ ["tg", "228"],
239
+ ["tk", "690"],
240
+ ["to", "676"],
241
+ ["tt", "1868"],
242
+ ["tn", "216"],
243
+ ["tr", "90"],
244
+ ["tm", "993"],
245
+ ["tc", "1649"],
246
+ ["tv", "688"],
247
+ ["vi", "1340"],
248
+ ["ug", "256"],
249
+ ["ua", "380"],
250
+ ["ae", "971"],
251
+ ["gb", "44"],
252
+ ["us", "1"],
253
+ ["uy", "598"],
254
+ ["uz", "998"],
255
+ ["vu", "678"],
256
+ ["va", "39"],
257
+ ["ve", "58"],
258
+ ["vn", "84"],
259
+ ["wf", "681"],
260
+ ["eh", "212"],
261
+ ["ye", "967"],
262
+ ["zm", "260"],
263
+ ["zw", "263"]
264
+ ];
265
+ function getCountryName(countryCode, regionNames) {
266
+ try {
267
+ return regionNames?.of(countryCode) ?? countryCode;
268
+ } catch {
269
+ return countryCode;
270
+ }
271
+ }
272
+ function getFlagEmoji(countryCode) {
273
+ const codePoints = countryCode.toUpperCase().split("").map((char) => 127397 + char.charCodeAt(0));
274
+ return String.fromCodePoint(...codePoints);
275
+ }
276
+ function getCountries(locale = DEFAULT_DISPLAY_LOCALE) {
277
+ let regionNames;
278
+ let collator;
279
+ try {
280
+ regionNames = new Intl.DisplayNames([locale], { type: "region" });
281
+ collator = new Intl.Collator(locale);
282
+ } catch {
283
+ }
284
+ return COUNTRY_DATA.map(([iso2, dialCode]) => {
285
+ const code = iso2.toUpperCase();
286
+ return {
287
+ code,
288
+ name: getCountryName(code, regionNames),
289
+ dialCode: `+${dialCode}`,
290
+ flag: getFlagEmoji(code)
291
+ };
292
+ }).sort((a, b) => collator ? collator.compare(a.name, b.name) : a.name.localeCompare(b.name));
293
+ }
294
+ function removeDiacritics(text) {
295
+ return text.normalize("NFD").replace(/\p{Diacritic}/gu, "").replace(/đ/g, "d").replace(/Đ/g, "D");
296
+ }
297
+ function filterCountries(value, search, keywords) {
298
+ return defaultFilter(removeDiacritics(value), removeDiacritics(search), keywords?.map(removeDiacritics));
299
+ }
300
+ const PRIMARY_COUNTRY_BY_DIAL_CODE = {
301
+ "+1": "US",
302
+ "+7": "RU",
303
+ "+39": "IT",
304
+ "+44": "GB",
305
+ "+212": "MA",
306
+ "+358": "FI",
307
+ "+590": "GP",
308
+ "+599": "CW"
309
+ };
310
+ function detectCountryFromNumber(value, countries, currentCode) {
311
+ if (!value?.startsWith("+")) return void 0;
312
+ const digits = value.slice(1).replace(/\D/g, "");
313
+ if (!digits) return void 0;
314
+ const sorted = [...countries].sort((a, b) => b.dialCode.length - a.dialCode.length);
315
+ const matches = [];
316
+ for (const country of sorted) {
317
+ const dialCode = country.dialCode.slice(1);
318
+ if (digits.startsWith(dialCode)) {
319
+ matches.push(country);
320
+ }
321
+ }
322
+ const best = matches[0];
323
+ if (!best) return void 0;
324
+ const tied = matches.filter((c) => c.dialCode === best.dialCode);
325
+ if (tied.length > 1) {
326
+ const current = tied.find((c) => c.code === currentCode);
327
+ if (current) return current;
328
+ const primary = tied.find((c) => c.code === PRIMARY_COUNTRY_BY_DIAL_CODE[best.dialCode]);
329
+ if (primary) return primary;
330
+ }
331
+ return best;
332
+ }
333
+ function formatPhoneNumber(value, countries) {
334
+ if (!value) return "";
335
+ const normalized = value.startsWith("+") ? value : `+${value}`;
336
+ const digits = normalized.slice(1).replace(/\D/g, "");
337
+ if (!digits) return "+";
338
+ const detected = detectCountryFromNumber(`+${digits}`, countries);
339
+ const dialCodeLength = detected ? detected.dialCode.slice(1).length : Math.min(digits.length, 3);
340
+ const countryCode = digits.slice(0, dialCodeLength);
341
+ const rest = digits.slice(dialCodeLength);
342
+ let formatted = `+${countryCode}`;
343
+ if (rest) {
344
+ formatted += " ";
345
+ for (let i = 0; i < rest.length; i++) {
346
+ if (i > 0 && i % 3 === 0) {
347
+ formatted += " ";
348
+ }
349
+ formatted += rest[i];
350
+ }
351
+ }
352
+ return formatted;
353
+ }
354
+ const NO_TRUNK_PREFIX_COUNTRIES = /* @__PURE__ */ new Set(["IT", "SM", "VA"]);
355
+ const NATIONAL_INPUT_DISALLOWED = /[^\d\s().-]/g;
356
+ function nationalToE164(nationalInput, country) {
357
+ let digits = nationalInput.replace(/\D/g, "");
358
+ if (digits.startsWith("0") && !NO_TRUNK_PREFIX_COUNTRIES.has(country.code)) {
359
+ digits = digits.slice(1);
360
+ }
361
+ return digits ? `${country.dialCode}${digits}` : "";
362
+ }
363
+ function getNationalInput(state, countries) {
364
+ const { nationalInput, country, value } = state;
365
+ if (nationalInput === null) return null;
366
+ const selected = countries.find((c) => c.code === country);
367
+ if (!selected || nationalToE164(nationalInput, selected) !== value) return null;
368
+ return nationalInput;
369
+ }
370
+ const StoreContext = React.createContext(null);
371
+ function useStoreContext(consumerName) {
372
+ const context = React.useContext(StoreContext);
373
+ if (!context) {
374
+ throw new Error(`\`${consumerName}\` ph\u1EA3i n\u1EB1m b\xEAn trong \`${ROOT_NAME}\``);
375
+ }
376
+ return context;
377
+ }
378
+ function useStore(selector, ogStore) {
379
+ const contextStore = React.useContext(StoreContext);
380
+ const store = ogStore ?? contextStore;
381
+ if (!store) {
382
+ throw new Error(`\`usePhoneInput\` ph\u1EA3i \u0111\u01B0\u1EE3c g\u1ECDi b\xEAn trong \`${ROOT_NAME}\``);
383
+ }
384
+ const getSnapshot = React.useCallback(() => selector(store.getState()), [store, selector]);
385
+ return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);
386
+ }
387
+ const PhoneInputContext = React.createContext(null);
388
+ function usePhoneInputContext(consumerName) {
389
+ const context = React.useContext(PhoneInputContext);
390
+ if (!context) {
391
+ throw new Error(`\`${consumerName}\` ph\u1EA3i n\u1EB1m b\xEAn trong \`${ROOT_NAME}\``);
392
+ }
393
+ return context;
394
+ }
395
+ function PhoneInput(props) {
396
+ const {
397
+ value: valueProp,
398
+ defaultValue,
399
+ defaultCountry = DEFAULT_COUNTRY,
400
+ country: countryProp,
401
+ onValueChange,
402
+ onCountryChange,
403
+ countries: countriesProp,
404
+ name,
405
+ placeholder = "Nh\u1EADp s\u1ED1 \u0111i\u1EC7n tho\u1EA1i",
406
+ locale = DEFAULT_DISPLAY_LOCALE,
407
+ labels: labelsProp,
408
+ asChild,
409
+ disabled,
410
+ required,
411
+ readOnly,
412
+ invalid,
413
+ showFlag = true,
414
+ className,
415
+ id,
416
+ ref,
417
+ ...rootProps
418
+ } = props;
419
+ const instanceId = React.useId();
420
+ const rootId = id ?? instanceId;
421
+ const inputRef = React.useRef(null);
422
+ const countries = React.useMemo(() => countriesProp ?? getCountries(locale), [countriesProp, locale]);
423
+ const labels = React.useMemo(() => ({ ...DEFAULT_PHONE_INPUT_LABELS, ...labelsProp }), [labelsProp]);
424
+ const [formTrigger, setFormTrigger] = React.useState(null);
425
+ const composedRef = useComposedRefs(ref, (node) => setFormTrigger(node));
426
+ const isFormControl = formTrigger ? !!formTrigger.closest("form") : true;
427
+ const listenersRef = useLazyRef(() => /* @__PURE__ */ new Set());
428
+ const stateRef = useLazyRef(() => {
429
+ const initialValue = valueProp ?? defaultValue ?? "";
430
+ const initialCountry = countryProp ?? defaultCountry ?? "";
431
+ return {
432
+ value: initialValue,
433
+ country: initialCountry,
434
+ open: false,
435
+ startsWithPlus: initialValue.startsWith("+"),
436
+ nationalInput: null
437
+ };
438
+ });
439
+ const propsRef = useAsRef({
440
+ onValueChange,
441
+ onCountryChange,
442
+ countries
443
+ });
444
+ const store = React.useMemo(() => {
445
+ return {
446
+ subscribe: (cb) => {
447
+ listenersRef.current.add(cb);
448
+ return () => listenersRef.current.delete(cb);
449
+ },
450
+ getState: () => stateRef.current,
451
+ setState: (key, value2) => {
452
+ if (Object.is(stateRef.current[key], value2)) return;
453
+ if (key === "value" && typeof value2 === "string") {
454
+ stateRef.current.value = value2;
455
+ propsRef.current.onValueChange?.(value2);
456
+ } else if (key === "country" && typeof value2 === "string") {
457
+ const nationalInput2 = getNationalInput(stateRef.current, propsRef.current.countries);
458
+ stateRef.current.country = value2;
459
+ propsRef.current.onCountryChange?.(value2);
460
+ if (nationalInput2 !== null) {
461
+ const selected = propsRef.current.countries.find((c) => c.code === value2);
462
+ if (selected) {
463
+ const nextValue = nationalToE164(nationalInput2, selected);
464
+ if (nextValue !== stateRef.current.value) {
465
+ stateRef.current.value = nextValue;
466
+ propsRef.current.onValueChange?.(nextValue);
467
+ }
468
+ } else {
469
+ stateRef.current.nationalInput = null;
470
+ }
471
+ }
472
+ } else {
473
+ stateRef.current[key] = value2;
474
+ }
475
+ store.notify();
476
+ },
477
+ notify: () => {
478
+ for (const cb of listenersRef.current) {
479
+ cb();
480
+ }
481
+ }
482
+ };
483
+ }, [listenersRef, stateRef, propsRef]);
484
+ const value = useStore((state) => state.value, store);
485
+ const country = useStore((state) => state.country, store);
486
+ useIsomorphicLayoutEffect(() => {
487
+ if (valueProp !== void 0) {
488
+ store.setState("value", valueProp);
489
+ }
490
+ }, [valueProp]);
491
+ useIsomorphicLayoutEffect(() => {
492
+ if (countryProp !== void 0) {
493
+ store.setState("country", countryProp);
494
+ }
495
+ }, [countryProp]);
496
+ const startsWithPlus = useStore((state) => state.startsWithPlus, store);
497
+ const nationalInput = useStore((state) => getNationalInput(state, countries), store);
498
+ React.useEffect(() => {
499
+ if (!value || nationalInput !== null) return;
500
+ const digits = value.slice(1).replace(/\D/g, "");
501
+ const shouldDetect = startsWithPlus || digits.length >= 10;
502
+ if (!shouldDetect) return;
503
+ const detected = detectCountryFromNumber(value, countries, country);
504
+ if (detected && detected.code !== country) {
505
+ store.setState("country", detected.code);
506
+ }
507
+ }, [value, countries, country, store, startsWithPlus, nationalInput]);
508
+ const contextValue = React.useMemo(
509
+ () => ({
510
+ rootId,
511
+ countries,
512
+ placeholder,
513
+ labels,
514
+ disabled,
515
+ readOnly,
516
+ required,
517
+ invalid,
518
+ showFlag,
519
+ inputRef
520
+ }),
521
+ [rootId, countries, placeholder, labels, disabled, required, readOnly, invalid, showFlag]
522
+ );
523
+ const RootPrimitive = asChild ? Slot : "div";
524
+ return /* @__PURE__ */ jsx(StoreContext.Provider, { value: store, children: /* @__PURE__ */ jsxs(PhoneInputContext.Provider, { value: contextValue, children: [
525
+ /* @__PURE__ */ jsx(
526
+ RootPrimitive,
527
+ {
528
+ role: "group",
529
+ "data-slot": "phone-input",
530
+ "data-disabled": disabled ? "" : void 0,
531
+ "data-invalid": invalid ? "" : void 0,
532
+ "data-readonly": readOnly ? "" : void 0,
533
+ id: rootId,
534
+ ...rootProps,
535
+ ref: composedRef,
536
+ className: cn(
537
+ /*
538
+ * ⚠ Khác thượng nguồn: khung mượn đúng vai trò màu của `Input` Comitor để đứng cạnh ô
539
+ * nhập thường trong một form không lệch (cao `h-9` thay vì `h-10`, nền `bg-background`
540
+ * thay vì `dark:bg-input/30` — viết thẳng thang màu thì bảng tương phản cao không đổi
541
+ * được). Số đo từng vai trò: chú thích đầu `input.tsx`.
542
+ *
543
+ * Focus: thượng nguồn soi `[data-slot=input-group-control]` — slot KHÔNG tồn tại trong
544
+ * component này, nên khung không bao giờ sáng. Ở đây soi đúng slot của ô số.
545
+ *
546
+ * Lỗi: HAI đường như `InputGroup` — `aria-invalid` trên ô số (prop `invalid`), hoặc
547
+ * trên CHÍNH gốc khi `<FormControl>` của `@comitor/ui/form` dán nó vào con trực tiếp.
548
+ * Viền lỗi là `--destructive-ink` (không phải `--destructive` — token TÔ NỀN).
549
+ *
550
+ * Vô hiệu: thượng nguồn làm mờ CẢ gốc (`data-disabled:opacity-50`) trong khi nút và ô
551
+ * số bên trong cũng tự mờ theo `:disabled` → nội dung còn 25%. Ở đây gốc KHÔNG mờ (như
552
+ * `InputGroup`); hai con tự mờ bằng `:disabled` gốc của HTML, gốc chỉ đổi nền theo ô số.
553
+ * `data-disabled` còn không được cổng tương phản coi là trạng thái vô hiệu — nó không
554
+ * chứng minh phần tử đã inert — còn `:disabled` thì có.
555
+ */
556
+ "relative flex h-9 w-full items-center rounded-md border border-input bg-background transition-colors duration-150 ease-out hover:border-control-edge-strong",
557
+ "has-[[data-slot=phone-input-field]:focus-visible]:border-primary-ink has-[[data-slot=phone-input-field]:focus-visible]:ring-2 has-[[data-slot=phone-input-field]:focus-visible]:ring-ring/50",
558
+ "has-[[data-slot][aria-invalid=true]]:border-destructive-ink has-[[data-slot][aria-invalid=true]]:ring-destructive/30",
559
+ "aria-invalid:border-destructive-ink aria-invalid:hover:border-destructive-ink aria-invalid:ring-destructive/30",
560
+ "data-disabled:cursor-not-allowed has-[[data-slot=phone-input-field]:disabled]:bg-muted",
561
+ className
562
+ )
563
+ }
564
+ ),
565
+ isFormControl && /* @__PURE__ */ jsx(
566
+ VisuallyHiddenInput,
567
+ {
568
+ type: "hidden",
569
+ control: formTrigger,
570
+ name,
571
+ value,
572
+ disabled,
573
+ readOnly,
574
+ required
575
+ }
576
+ )
577
+ ] }) });
578
+ }
579
+ function PhoneInputCountrySelect(props) {
580
+ const { disabled: disabledProp, className, children, onOpenChange: onOpenChangeProp, ...popoverProps } = props;
581
+ const { countries, inputRef, disabled, showFlag, labels } = usePhoneInputContext(COUNTRY_SELECT_NAME);
582
+ const store = useStoreContext(COUNTRY_SELECT_NAME);
583
+ const country = useStore((state) => state.country);
584
+ const open = useStore((state) => state.open);
585
+ const onOpenChangeRef = useAsRef(onOpenChangeProp);
586
+ const isDisabled = disabledProp || disabled;
587
+ const countryContext = countries.find((c) => c.code === country);
588
+ const onOpenChange = React.useCallback(
589
+ (open2) => {
590
+ store.setState("open", open2);
591
+ onOpenChangeRef.current?.(open2);
592
+ },
593
+ [store, onOpenChangeRef]
594
+ );
595
+ return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange, ...popoverProps, children: [
596
+ /* @__PURE__ */ jsxs(
597
+ PopoverTrigger,
598
+ {
599
+ "data-slot": "phone-input-country-select",
600
+ "aria-label": labels.countrySelect(countryContext),
601
+ disabled: isDisabled,
602
+ className: cn(
603
+ /*
604
+ * Hover `bg-muted` như nút `ghost` của gói. Viền focus cùng khuôn `Button`
605
+ * (`outline-ring`), nhưng THỤT VÀO trong (`-outline-offset-2`) vì nút nằm lọt trong khung
606
+ * của ô — viền đẩy ra ngoài sẽ đè lên khung và lên ô số bên cạnh.
607
+ */
608
+ "flex h-full shrink-0 items-center gap-2 rounded-l-md border-r border-input bg-transparent px-3 text-sm transition-colors duration-150 ease-out hover:bg-muted focus-visible:outline-2 focus-visible:outline-ring focus-visible:-outline-offset-2 disabled:pointer-events-none disabled:opacity-50",
609
+ className
610
+ ),
611
+ children: [
612
+ !countryContext ? (
613
+ /*
614
+ * ⚠ Khác thượng nguồn: bản gốc vẽ một ô `bg-muted/50` trống — 1,00:1 trên nền hover
615
+ * `--muted`, tức biến mất đúng lúc người dùng rê chuột vào (cổng tương phản chặn). Quả
616
+ * địa cầu nói được "chưa chọn quốc gia"; giữ bề rộng `w-6` của lá cờ để chọn xong không
617
+ * xô lệch.
618
+ */
619
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "flex w-6 justify-center", children: /* @__PURE__ */ jsx(Globe, { className: "size-4 text-muted-foreground" }) })
620
+ ) : showFlag && countryContext.flag && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "w-6 text-lg leading-none", children: countryContext.flag }),
621
+ /* @__PURE__ */ jsx(ChevronDown, { "aria-hidden": "true", className: "size-4 text-muted-foreground" })
622
+ ]
623
+ }
624
+ ),
625
+ /* @__PURE__ */ jsx(PopoverContent, { className: "w-[300px] p-0", align: "start", children: /* @__PURE__ */ jsxs(Command, { filter: filterCountries, children: [
626
+ /* @__PURE__ */ jsx(CommandInput, { placeholder: labels.searchPlaceholder }),
627
+ /* @__PURE__ */ jsxs(CommandList, { children: [
628
+ /* @__PURE__ */ jsx(CommandEmpty, { children: labels.empty }),
629
+ /* @__PURE__ */ jsx(CommandGroup, { children: countries.map((c) => /* @__PURE__ */ jsxs(
630
+ CommandItem,
631
+ {
632
+ value: `${c.name} ${c.dialCode} ${c.code}`,
633
+ onSelect: () => {
634
+ store.setState("country", c.code);
635
+ store.setState("open", false);
636
+ requestAnimationFrame(() => {
637
+ inputRef.current?.focus();
638
+ });
639
+ },
640
+ children: [
641
+ showFlag && c.flag && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "text-lg", children: c.flag }),
642
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: c.name }),
643
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: c.dialCode }),
644
+ /* @__PURE__ */ jsx(
645
+ Check,
646
+ {
647
+ "aria-hidden": "true",
648
+ className: cn("size-4", country === c.code ? "opacity-100" : "opacity-0")
649
+ }
650
+ )
651
+ ]
652
+ },
653
+ c.code
654
+ )) })
655
+ ] })
656
+ ] }) })
657
+ ] });
658
+ }
659
+ function PhoneInputField(props) {
660
+ const {
661
+ onChange: onChangeProp,
662
+ className,
663
+ disabled: disabledProp,
664
+ readOnly: readOnlyProp,
665
+ required: requiredProp,
666
+ placeholder: placeholderProp,
667
+ ref,
668
+ ...inputProps
669
+ } = props;
670
+ const { inputRef, disabled, invalid, readOnly, required, placeholder, countries } = usePhoneInputContext(FIELD_NAME);
671
+ const store = useStoreContext(FIELD_NAME);
672
+ const value = useStore((state) => state.value);
673
+ const nationalInput = useStore((state) => getNationalInput(state, countries));
674
+ const composedRef = useComposedRefs(ref, inputRef);
675
+ const onChangeRef = useAsRef(onChangeProp);
676
+ const isDisabled = disabledProp || disabled;
677
+ const isReadOnly = readOnlyProp || readOnly;
678
+ const isRequired = requiredProp || required;
679
+ const onChange = React.useCallback(
680
+ (event) => {
681
+ if (isDisabled || isReadOnly) return;
682
+ onChangeRef.current?.(event);
683
+ if (event.defaultPrevented) return;
684
+ const inputValue = event.target.value;
685
+ const startsWithPlus = inputValue.trimStart().startsWith("+");
686
+ const selectedCountry = startsWithPlus ? void 0 : countries.find((c) => c.code === store.getState().country);
687
+ if (selectedCountry) {
688
+ const nextNationalInput = inputValue.replace(NATIONAL_INPUT_DISALLOWED, "");
689
+ if (nextNationalInput !== inputValue) {
690
+ const target = event.target;
691
+ const caret = target.selectionStart ?? inputValue.length;
692
+ const nextCaret = inputValue.slice(0, caret).replace(NATIONAL_INPUT_DISALLOWED, "").length;
693
+ target.value = nextNationalInput;
694
+ target.setSelectionRange(nextCaret, nextCaret);
695
+ }
696
+ store.setState("startsWithPlus", false);
697
+ store.setState("nationalInput", nextNationalInput);
698
+ store.setState("value", nationalToE164(nextNationalInput, selectedCountry));
699
+ return;
700
+ }
701
+ const digits = inputValue.replace(/\D/g, "");
702
+ const newValue = digits ? `+${digits}` : startsWithPlus ? "+" : "";
703
+ store.setState("startsWithPlus", startsWithPlus);
704
+ store.setState("nationalInput", null);
705
+ store.setState("value", newValue);
706
+ },
707
+ [store, countries, onChangeRef, isDisabled, isReadOnly]
708
+ );
709
+ const displayValue = React.useMemo(() => {
710
+ return nationalInput ?? formatPhoneNumber(value, countries);
711
+ }, [nationalInput, value, countries]);
712
+ return /* @__PURE__ */ jsx(
713
+ Input,
714
+ {
715
+ type: "tel",
716
+ inputMode: "tel",
717
+ "aria-required": isRequired,
718
+ "aria-invalid": invalid,
719
+ "data-slot": "phone-input-field",
720
+ disabled: isDisabled,
721
+ readOnly: isReadOnly,
722
+ required: isRequired,
723
+ ...inputProps,
724
+ ref: composedRef,
725
+ className: cn(
726
+ // Viền, quầng focus và nền (kể cả nền `--muted` lúc vô hiệu) đều do khung gốc vẽ; ô số
727
+ // trong suốt và phẳng. `focus:ring-0` chứ không `focus-visible:` — `Input` vẽ quầng bằng `focus:`.
728
+ "h-full flex-1 rounded-l-none rounded-r-md border-0 bg-transparent focus:ring-0 disabled:bg-transparent",
729
+ className
730
+ ),
731
+ placeholder: placeholderProp ?? placeholder,
732
+ value: displayValue,
733
+ onChange
734
+ }
735
+ );
736
+ }
737
+ export {
738
+ PhoneInput,
739
+ PhoneInputCountrySelect,
740
+ PhoneInputField,
741
+ useStore as usePhoneInput
742
+ };
743
+ //# sourceMappingURL=phone-input.js.map