@teamofsilicons/silicon-ui 0.0.0-stage → 0.2.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 (354) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +81 -2
  3. package/check.mjs +236 -0
  4. package/components/accordion/accordion.d.ts +13 -0
  5. package/components/accordion/accordion.js +35 -0
  6. package/components/accordion/accordion.module.css +15 -0
  7. package/components/action-button/action-button.d.ts +11 -0
  8. package/components/action-button/action-button.js +57 -0
  9. package/components/action-button/action-button.module.css +15 -0
  10. package/components/activity-heatmap/activity-heatmap.d.ts +37 -0
  11. package/components/activity-heatmap/activity-heatmap.js +303 -0
  12. package/components/activity-heatmap/activity-heatmap.module.css +83 -0
  13. package/components/alert/alert.d.ts +13 -0
  14. package/components/alert/alert.js +79 -0
  15. package/components/alert/alert.module.css +15 -0
  16. package/components/animated-counter/animated-counter.d.ts +13 -0
  17. package/components/animated-counter/animated-counter.js +73 -0
  18. package/components/animated-counter/animated-counter.module.css +12 -0
  19. package/components/announcement-bar/announcement-bar.d.ts +53 -0
  20. package/components/announcement-bar/announcement-bar.js +187 -0
  21. package/components/announcement-bar/announcement-bar.module.css +60 -0
  22. package/components/avatar/avatar.d.ts +8 -0
  23. package/components/avatar/avatar.js +18 -0
  24. package/components/avatar/avatar.module.css +60 -0
  25. package/components/avatar-group/avatar-group.d.ts +13 -0
  26. package/components/avatar-group/avatar-group.js +24 -0
  27. package/components/avatar-group/avatar-group.module.css +27 -0
  28. package/components/badge/badge.d.ts +10 -0
  29. package/components/badge/badge.js +66 -0
  30. package/components/badge/badge.module.css +52 -0
  31. package/components/bar-chart/bar-chart.d.ts +32 -0
  32. package/components/bar-chart/bar-chart.js +327 -0
  33. package/components/bar-chart/bar-chart.module.css +49 -0
  34. package/components/billing-toggle/billing-toggle.d.ts +35 -0
  35. package/components/billing-toggle/billing-toggle.js +83 -0
  36. package/components/billing-toggle/billing-toggle.module.css +139 -0
  37. package/components/bottom-sheet/bottom-sheet.d.ts +27 -0
  38. package/components/bottom-sheet/bottom-sheet.js +386 -0
  39. package/components/bottom-sheet/bottom-sheet.module.css +69 -0
  40. package/components/breadcrumb/breadcrumb.d.ts +13 -0
  41. package/components/breadcrumb/breadcrumb.js +19 -0
  42. package/components/breadcrumb/breadcrumb.module.css +12 -0
  43. package/components/brush-chart/brush-chart.d.ts +45 -0
  44. package/components/brush-chart/brush-chart.js +426 -0
  45. package/components/brush-chart/brush-chart.module.css +69 -0
  46. package/components/button/button.d.ts +10 -0
  47. package/components/button/button.js +97 -0
  48. package/components/button/button.module.css +39 -0
  49. package/components/button-group/button-group.d.ts +59 -0
  50. package/components/button-group/button-group.js +280 -0
  51. package/components/button-group/button-group.module.css +87 -0
  52. package/components/button-link/button-link.d.ts +24 -0
  53. package/components/button-link/button-link.js +11 -0
  54. package/components/button-link/button-link.module.css +7 -0
  55. package/components/calendar/calendar.d.ts +22 -0
  56. package/components/calendar/calendar.js +253 -0
  57. package/components/calendar/calendar.module.css +51 -0
  58. package/components/card/card.d.ts +19 -0
  59. package/components/card/card.js +108 -0
  60. package/components/card/card.module.css +51 -0
  61. package/components/card-stack/card-stack.d.ts +34 -0
  62. package/components/card-stack/card-stack.js +291 -0
  63. package/components/card-stack/card-stack.module.css +63 -0
  64. package/components/carousel/carousel.d.ts +28 -0
  65. package/components/carousel/carousel.js +332 -0
  66. package/components/carousel/carousel.module.css +42 -0
  67. package/components/chat-thread/chat-thread.d.ts +135 -0
  68. package/components/chat-thread/chat-thread.js +406 -0
  69. package/components/chat-thread/chat-thread.module.css +242 -0
  70. package/components/checkbox/checkbox.d.ts +7 -0
  71. package/components/checkbox/checkbox.js +23 -0
  72. package/components/checkbox/checkbox.module.css +15 -0
  73. package/components/chip-group/chip-group.d.ts +24 -0
  74. package/components/chip-group/chip-group.js +196 -0
  75. package/components/chip-group/chip-group.module.css +38 -0
  76. package/components/code-block/code-block.d.ts +11 -0
  77. package/components/code-block/code-block.js +133 -0
  78. package/components/code-block/code-block.module.css +43 -0
  79. package/components/color-picker/color-picker.d.ts +40 -0
  80. package/components/color-picker/color-picker.js +449 -0
  81. package/components/color-picker/color-picker.module.css +111 -0
  82. package/components/combobox/combobox.d.ts +19 -0
  83. package/components/combobox/combobox.js +167 -0
  84. package/components/combobox/combobox.module.css +28 -0
  85. package/components/command-palette/command-palette.d.ts +20 -0
  86. package/components/command-palette/command-palette.js +151 -0
  87. package/components/command-palette/command-palette.module.css +94 -0
  88. package/components/comment-thread/comment-thread.d.ts +120 -0
  89. package/components/comment-thread/comment-thread.js +343 -0
  90. package/components/comment-thread/comment-thread.module.css +195 -0
  91. package/components/confirm-morph/confirm-morph.d.ts +65 -0
  92. package/components/confirm-morph/confirm-morph.js +355 -0
  93. package/components/confirm-morph/confirm-morph.module.css +146 -0
  94. package/components/context-menu/context-menu.d.ts +18 -0
  95. package/components/context-menu/context-menu.js +148 -0
  96. package/components/context-menu/context-menu.module.css +17 -0
  97. package/components/copy-button/copy-button.d.ts +10 -0
  98. package/components/copy-button/copy-button.js +69 -0
  99. package/components/copy-button/copy-button.module.css +21 -0
  100. package/components/countdown/countdown.d.ts +31 -0
  101. package/components/countdown/countdown.js +177 -0
  102. package/components/countdown/countdown.module.css +49 -0
  103. package/components/date-picker/date-picker.d.ts +17 -0
  104. package/components/date-picker/date-picker.js +100 -0
  105. package/components/date-picker/date-picker.module.css +34 -0
  106. package/components/date-range-picker/date-range-picker.d.ts +44 -0
  107. package/components/date-range-picker/date-range-picker.js +534 -0
  108. package/components/date-range-picker/date-range-picker.module.css +214 -0
  109. package/components/dialog/dialog.d.ts +11 -0
  110. package/components/dialog/dialog.js +52 -0
  111. package/components/dialog/dialog.module.css +36 -0
  112. package/components/donut-chart/donut-chart.d.ts +46 -0
  113. package/components/donut-chart/donut-chart.js +382 -0
  114. package/components/donut-chart/donut-chart.module.css +48 -0
  115. package/components/drawer/drawer.d.ts +20 -0
  116. package/components/drawer/drawer.js +111 -0
  117. package/components/drawer/drawer.module.css +97 -0
  118. package/components/dropdown-menu/dropdown-menu.d.ts +16 -0
  119. package/components/dropdown-menu/dropdown-menu.js +39 -0
  120. package/components/dropdown-menu/dropdown-menu.module.css +38 -0
  121. package/components/elastic-slider/elastic-slider.d.ts +39 -0
  122. package/components/elastic-slider/elastic-slider.js +261 -0
  123. package/components/elastic-slider/elastic-slider.module.css +40 -0
  124. package/components/empty-state/empty-state.d.ts +12 -0
  125. package/components/empty-state/empty-state.js +69 -0
  126. package/components/empty-state/empty-state.module.css +13 -0
  127. package/components/expandable-card/expandable-card.d.ts +13 -0
  128. package/components/expandable-card/expandable-card.js +89 -0
  129. package/components/expandable-card/expandable-card.module.css +22 -0
  130. package/components/expanding-button-group/expanding-button-group.d.ts +41 -0
  131. package/components/expanding-button-group/expanding-button-group.js +258 -0
  132. package/components/expanding-button-group/expanding-button-group.module.css +44 -0
  133. package/components/expanding-search/expanding-search.d.ts +38 -0
  134. package/components/expanding-search/expanding-search.js +242 -0
  135. package/components/expanding-search/expanding-search.module.css +52 -0
  136. package/components/file-dropzone/file-dropzone.d.ts +44 -0
  137. package/components/file-dropzone/file-dropzone.js +379 -0
  138. package/components/file-dropzone/file-dropzone.module.css +115 -0
  139. package/components/file-upload/file-upload.d.ts +22 -0
  140. package/components/file-upload/file-upload.js +192 -0
  141. package/components/file-upload/file-upload.module.css +49 -0
  142. package/components/filter-toolbar/filter-toolbar.d.ts +48 -0
  143. package/components/filter-toolbar/filter-toolbar.js +446 -0
  144. package/components/filter-toolbar/filter-toolbar.module.css +70 -0
  145. package/components/floating-button-group/floating-button-group.d.ts +51 -0
  146. package/components/floating-button-group/floating-button-group.js +215 -0
  147. package/components/floating-button-group/floating-button-group.module.css +74 -0
  148. package/components/gauge/gauge.d.ts +18 -0
  149. package/components/gauge/gauge.js +101 -0
  150. package/components/gauge/gauge.module.css +22 -0
  151. package/components/hold-to-confirm/hold-to-confirm.d.ts +25 -0
  152. package/components/hold-to-confirm/hold-to-confirm.js +165 -0
  153. package/components/hold-to-confirm/hold-to-confirm.module.css +20 -0
  154. package/components/hover-card/hover-card.d.ts +38 -0
  155. package/components/hover-card/hover-card.js +145 -0
  156. package/components/hover-card/hover-card.module.css +20 -0
  157. package/components/image-compare/image-compare.d.ts +27 -0
  158. package/components/image-compare/image-compare.js +267 -0
  159. package/components/image-compare/image-compare.module.css +25 -0
  160. package/components/in-view-title/in-view-title.d.ts +17 -0
  161. package/components/in-view-title/in-view-title.js +70 -0
  162. package/components/in-view-title/in-view-title.module.css +13 -0
  163. package/components/inline-edit/inline-edit.d.ts +27 -0
  164. package/components/inline-edit/inline-edit.js +315 -0
  165. package/components/inline-edit/inline-edit.module.css +73 -0
  166. package/components/input/input.d.ts +7 -0
  167. package/components/input/input.js +60 -0
  168. package/components/input/input.module.css +22 -0
  169. package/components/json-viewer/json-viewer.d.ts +48 -0
  170. package/components/json-viewer/json-viewer.js +463 -0
  171. package/components/json-viewer/json-viewer.module.css +92 -0
  172. package/components/line-chart/line-chart.d.ts +58 -0
  173. package/components/line-chart/line-chart.js +383 -0
  174. package/components/line-chart/line-chart.module.css +60 -0
  175. package/components/mention-input/mention-input.d.ts +72 -0
  176. package/components/mention-input/mention-input.js +417 -0
  177. package/components/mention-input/mention-input.module.css +134 -0
  178. package/components/metric-card/metric-card.d.ts +8 -0
  179. package/components/metric-card/metric-card.js +44 -0
  180. package/components/metric-card/metric-card.module.css +18 -0
  181. package/components/money-input/money-input.d.ts +48 -0
  182. package/components/money-input/money-input.js +503 -0
  183. package/components/money-input/money-input.module.css +77 -0
  184. package/components/morph-select/morph-select.d.ts +50 -0
  185. package/components/morph-select/morph-select.js +395 -0
  186. package/components/morph-select/morph-select.module.css +88 -0
  187. package/components/multi-select/multi-select.d.ts +18 -0
  188. package/components/multi-select/multi-select.js +105 -0
  189. package/components/multi-select/multi-select.module.css +37 -0
  190. package/components/notification-center/notification-center.d.ts +24 -0
  191. package/components/notification-center/notification-center.js +146 -0
  192. package/components/notification-center/notification-center.module.css +79 -0
  193. package/components/number-field/number-field.d.ts +41 -0
  194. package/components/number-field/number-field.js +455 -0
  195. package/components/number-field/number-field.module.css +71 -0
  196. package/components/otp-input/otp-input.d.ts +13 -0
  197. package/components/otp-input/otp-input.js +129 -0
  198. package/components/otp-input/otp-input.module.css +28 -0
  199. package/components/pagination/pagination.d.ts +7 -0
  200. package/components/pagination/pagination.js +60 -0
  201. package/components/pagination/pagination.module.css +16 -0
  202. package/components/password-field/password-field.d.ts +14 -0
  203. package/components/password-field/password-field.js +51 -0
  204. package/components/password-field/password-field.module.css +27 -0
  205. package/components/password-strength/password-strength.d.ts +107 -0
  206. package/components/password-strength/password-strength.js +161 -0
  207. package/components/password-strength/password-strength.module.css +57 -0
  208. package/components/phone-input/phone-input.d.ts +73 -0
  209. package/components/phone-input/phone-input.js +612 -0
  210. package/components/phone-input/phone-input.module.css +98 -0
  211. package/components/popover/popover.d.ts +6 -0
  212. package/components/popover/popover.js +19 -0
  213. package/components/popover/popover.module.css +32 -0
  214. package/components/progress/progress.d.ts +8 -0
  215. package/components/progress/progress.js +42 -0
  216. package/components/progress/progress.module.css +1 -0
  217. package/components/radio-cards/radio-cards.d.ts +35 -0
  218. package/components/radio-cards/radio-cards.js +110 -0
  219. package/components/radio-cards/radio-cards.module.css +84 -0
  220. package/components/radio-group/radio-group.d.ts +13 -0
  221. package/components/radio-group/radio-group.js +55 -0
  222. package/components/radio-group/radio-group.module.css +30 -0
  223. package/components/resizable-panels/resizable-panels.d.ts +37 -0
  224. package/components/resizable-panels/resizable-panels.js +338 -0
  225. package/components/resizable-panels/resizable-panels.module.css +40 -0
  226. package/components/rich-text-editor/rich-text-editor.d.ts +55 -0
  227. package/components/rich-text-editor/rich-text-editor.js +1733 -0
  228. package/components/rich-text-editor/rich-text-editor.module.css +201 -0
  229. package/components/ridgeline/ridgeline.d.ts +37 -0
  230. package/components/ridgeline/ridgeline.js +346 -0
  231. package/components/ridgeline/ridgeline.module.css +58 -0
  232. package/components/scroll-area/scroll-area.d.ts +40 -0
  233. package/components/scroll-area/scroll-area.js +153 -0
  234. package/components/scroll-area/scroll-area.module.css +79 -0
  235. package/components/search-field/search-field.d.ts +7 -0
  236. package/components/search-field/search-field.js +20 -0
  237. package/components/search-field/search-field.module.css +17 -0
  238. package/components/segmented-control/segmented-control.d.ts +16 -0
  239. package/components/segmented-control/segmented-control.js +56 -0
  240. package/components/segmented-control/segmented-control.module.css +15 -0
  241. package/components/select/select.d.ts +15 -0
  242. package/components/select/select.js +36 -0
  243. package/components/select/select.module.css +35 -0
  244. package/components/shortcut-recorder/shortcut-recorder.d.ts +108 -0
  245. package/components/shortcut-recorder/shortcut-recorder.js +380 -0
  246. package/components/shortcut-recorder/shortcut-recorder.module.css +85 -0
  247. package/components/signature-pad/signature-pad.d.ts +72 -0
  248. package/components/signature-pad/signature-pad.js +450 -0
  249. package/components/signature-pad/signature-pad.module.css +84 -0
  250. package/components/skeleton/skeleton.d.ts +13 -0
  251. package/components/skeleton/skeleton.js +53 -0
  252. package/components/skeleton/skeleton.module.css +11 -0
  253. package/components/slider/slider.d.ts +45 -0
  254. package/components/slider/slider.js +347 -0
  255. package/components/slider/slider.module.css +45 -0
  256. package/components/slope-chart/slope-chart.d.ts +38 -0
  257. package/components/slope-chart/slope-chart.js +183 -0
  258. package/components/slope-chart/slope-chart.module.css +51 -0
  259. package/components/slot-text/slot-text.d.ts +26 -0
  260. package/components/slot-text/slot-text.js +119 -0
  261. package/components/slot-text/slot-text.module.css +19 -0
  262. package/components/sortable-data-table/sortable-data-table.d.ts +38 -0
  263. package/components/sortable-data-table/sortable-data-table.js +240 -0
  264. package/components/sortable-data-table/sortable-data-table.module.css +116 -0
  265. package/components/sparkline/sparkline.d.ts +19 -0
  266. package/components/sparkline/sparkline.js +239 -0
  267. package/components/sparkline/sparkline.module.css +26 -0
  268. package/components/split-button/split-button.d.ts +19 -0
  269. package/components/split-button/split-button.js +33 -0
  270. package/components/split-button/split-button.module.css +51 -0
  271. package/components/stepper/stepper.d.ts +36 -0
  272. package/components/stepper/stepper.js +108 -0
  273. package/components/stepper/stepper.module.css +74 -0
  274. package/components/streamgraph/streamgraph.d.ts +51 -0
  275. package/components/streamgraph/streamgraph.js +376 -0
  276. package/components/streamgraph/streamgraph.module.css +55 -0
  277. package/components/swipe-actions/swipe-actions.d.ts +35 -0
  278. package/components/swipe-actions/swipe-actions.js +330 -0
  279. package/components/swipe-actions/swipe-actions.module.css +64 -0
  280. package/components/switch/switch.d.ts +7 -0
  281. package/components/switch/switch.js +45 -0
  282. package/components/switch/switch.module.css +12 -0
  283. package/components/tabs/tabs.d.ts +8 -0
  284. package/components/tabs/tabs.js +138 -0
  285. package/components/tabs/tabs.module.css +27 -0
  286. package/components/tag-input/tag-input.d.ts +15 -0
  287. package/components/tag-input/tag-input.js +150 -0
  288. package/components/tag-input/tag-input.module.css +34 -0
  289. package/components/text-morph/text-morph.d.ts +13 -0
  290. package/components/text-morph/text-morph.js +69 -0
  291. package/components/text-morph/text-morph.module.css +8 -0
  292. package/components/text-reveal/text-reveal.d.ts +14 -0
  293. package/components/text-reveal/text-reveal.js +20 -0
  294. package/components/text-reveal/text-reveal.module.css +13 -0
  295. package/components/text-shimmer/text-shimmer.d.ts +18 -0
  296. package/components/text-shimmer/text-shimmer.js +90 -0
  297. package/components/text-shimmer/text-shimmer.module.css +9 -0
  298. package/components/textarea/textarea.d.ts +7 -0
  299. package/components/textarea/textarea.js +56 -0
  300. package/components/textarea/textarea.module.css +22 -0
  301. package/components/theme-switch/theme-switch.d.ts +11 -0
  302. package/components/theme-switch/theme-switch.js +37 -0
  303. package/components/theme-switch/theme-switch.module.css +31 -0
  304. package/components/time-picker/time-picker.d.ts +13 -0
  305. package/components/time-picker/time-picker.js +95 -0
  306. package/components/time-picker/time-picker.module.css +23 -0
  307. package/components/timeline/timeline.d.ts +47 -0
  308. package/components/timeline/timeline.js +171 -0
  309. package/components/timeline/timeline.module.css +51 -0
  310. package/components/toast/toast.d.ts +8 -0
  311. package/components/toast/toast.js +118 -0
  312. package/components/toast/toast.module.css +100 -0
  313. package/components/toast-stack/toast-stack.d.ts +57 -0
  314. package/components/toast-stack/toast-stack.js +399 -0
  315. package/components/toast-stack/toast-stack.module.css +61 -0
  316. package/components/tooltip/tooltip.d.ts +8 -0
  317. package/components/tooltip/tooltip.js +62 -0
  318. package/components/tooltip/tooltip.module.css +18 -0
  319. package/components/tree-view/tree-view.d.ts +17 -0
  320. package/components/tree-view/tree-view.js +147 -0
  321. package/components/tree-view/tree-view.module.css +22 -0
  322. package/components/treemap/treemap.d.ts +35 -0
  323. package/components/treemap/treemap.js +375 -0
  324. package/components/treemap/treemap.module.css +59 -0
  325. package/components/usage-meter/usage-meter.d.ts +24 -0
  326. package/components/usage-meter/usage-meter.js +296 -0
  327. package/components/usage-meter/usage-meter.module.css +61 -0
  328. package/components/user-menu/user-menu.d.ts +62 -0
  329. package/components/user-menu/user-menu.js +316 -0
  330. package/components/user-menu/user-menu.module.css +97 -0
  331. package/components/waffle-chart/waffle-chart.d.ts +39 -0
  332. package/components/waffle-chart/waffle-chart.js +244 -0
  333. package/components/waffle-chart/waffle-chart.module.css +52 -0
  334. package/components.json +1330 -0
  335. package/eslint.js +53 -0
  336. package/lib/escape.d.ts +19 -0
  337. package/lib/escape.js +50 -0
  338. package/lib/media.d.ts +353 -0
  339. package/lib/media.js +65 -0
  340. package/lib/menu-highlight.d.ts +28 -0
  341. package/lib/menu-highlight.js +43 -0
  342. package/lib/motion-tokens.d.ts +60 -0
  343. package/lib/motion-tokens.js +25 -0
  344. package/lib/return-focus.d.ts +9 -0
  345. package/lib/return-focus.js +53 -0
  346. package/lib/silicon-provider.d.ts +60 -0
  347. package/lib/silicon-provider.js +47 -0
  348. package/lib/use-copy-feedback.d.ts +8 -0
  349. package/lib/use-copy-feedback.js +36 -0
  350. package/lib/use-today.d.ts +2 -0
  351. package/lib/use-today.js +29 -0
  352. package/package.json +76 -4
  353. package/styles.css +218 -0
  354. package/styles.css.d.ts +1 -0
@@ -0,0 +1,612 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState, useSyncExternalStore } from "react";
4
+ import { AnimatePresence, animate, motion, useMotionValue, useReducedMotion } from "motion/react";
5
+ import { Check, ChevronDown, Search, X } from "lucide-react";
6
+ import { motionTokens } from "../../lib/motion-tokens.js";
7
+ import styles from "./phone-input.module.css";
8
+ const country = (iso, name, dial, patterns, example, trunk = "0", areaCodes) => ({ iso, name, dial, patterns: Array.isArray(patterns) ? patterns : [patterns], example, trunk, areaCodes });
9
+ const CA_AREA_CODES = "204 226 236 249 250 263 289 306 343 354 365 367 368 382 387 403 416 418 428 431 437 438 450 468 474 506 514 519 548 579 581 584 587 604 613 639 647 672 683 705 709 742 753 778 780 782 807 819 825 867 873 879 902 905".split(" ");
10
+ export const PHONE_COUNTRIES = [
11
+ country("US", "United States", "1", "(###) ###-####", "2015550123", "1"),
12
+ country("CA", "Canada", "1", "(###) ###-####", "5065550123", "1", CA_AREA_CODES),
13
+ country("MX", "Mexico", "52", "## #### ####", "2221234567", ""),
14
+ country("BR", "Brazil", "55", ["(##) ####-####", "(##) #####-####"], "11961234567"),
15
+ country("AR", "Argentina", "54", "## ####-####", "1123456789"),
16
+ country("CO", "Colombia", "57", "### ### ####", "3211234567", ""),
17
+ country("CL", "Chile", "56", "# #### ####", "221234567", ""),
18
+ country("PE", "Peru", "51", "### ### ###", "912345678"),
19
+ country("GB", "United Kingdom", "44", "#### ######", "7400123456"),
20
+ country("IE", "Ireland", "353", "## ### ####", "850123456"),
21
+ country("FR", "France", "33", "# ## ## ## ##", "612345678"),
22
+ country("DE", "Germany", "49", ["### #######", "### ########"], "15123456789"),
23
+ country("ES", "Spain", "34", "### ## ## ##", "612345678", ""),
24
+ country("IT", "Italy", "39", ["### ### ###", "### ### ####"], "3123456789", ""),
25
+ country("PT", "Portugal", "351", "### ### ###", "912345678", ""),
26
+ country("NL", "Netherlands", "31", "# ########", "612345678"),
27
+ country("BE", "Belgium", "32", "### ## ## ##", "470123456"),
28
+ country("CH", "Switzerland", "41", "## ### ## ##", "781234567"),
29
+ country("AT", "Austria", "43", ["### #######", "### ########"], "6641234567"),
30
+ country("SE", "Sweden", "46", "## ### ## ##", "701234567"),
31
+ country("NO", "Norway", "47", "### ## ###", "40612345", ""),
32
+ country("DK", "Denmark", "45", "## ## ## ##", "32123456", ""),
33
+ country("FI", "Finland", "358", ["## ### ####", "## ### #####"], "412345678"),
34
+ country("PL", "Poland", "48", "### ### ###", "512345678", ""),
35
+ country("CZ", "Czechia", "420", "### ### ###", "601123456", ""),
36
+ country("GR", "Greece", "30", "### ### ####", "6912345678", ""),
37
+ country("UA", "Ukraine", "380", "## ### ## ##", "501234567"),
38
+ country("TR", "Turkey", "90", "### ### ## ##", "5012345678"),
39
+ country("IL", "Israel", "972", "##-###-####", "502345678"),
40
+ country("AE", "United Arab Emirates", "971", "## ### ####", "501234567"),
41
+ country("SA", "Saudi Arabia", "966", "## ### ####", "512345678"),
42
+ country("EG", "Egypt", "20", "### ### ####", "1001234567"),
43
+ country("NG", "Nigeria", "234", "### ### ####", "8021234567"),
44
+ country("KE", "Kenya", "254", "### ######", "712123456"),
45
+ country("ZA", "South Africa", "27", "## ### ####", "711234567"),
46
+ country("IN", "India", "91", "#####-#####", "8123456789"),
47
+ country("PK", "Pakistan", "92", "### #######", "3012345678"),
48
+ country("CN", "China", "86", "### #### ####", "13123456789"),
49
+ country("JP", "Japan", "81", "##-####-####", "9012345678"),
50
+ country("KR", "South Korea", "82", ["##-###-####", "##-####-####"], "1020000000"),
51
+ country("TW", "Taiwan", "886", "### ### ###", "912345678"),
52
+ country("HK", "Hong Kong", "852", "#### ####", "51234567", ""),
53
+ country("SG", "Singapore", "65", "#### ####", "81234567", ""),
54
+ country("PH", "Philippines", "63", "### ### ####", "9051234567"),
55
+ country("ID", "Indonesia", "62", ["###-###-####", "###-####-####", "###-####-#####"], "81234567890"),
56
+ country("TH", "Thailand", "66", "## ### ####", "812345678"),
57
+ country("VN", "Vietnam", "84", "## ### ## ##", "912345678"),
58
+ country("AU", "Australia", "61", "### ### ###", "412345678"),
59
+ country("NZ", "New Zealand", "64", ["# ### ####", "## ### ####", "## #### ####"], "211234567"),
60
+ ];
61
+ const BY_ISO = new Map(PHONE_COUNTRIES.map(entry => [entry.iso, entry]));
62
+ const capacity = (pattern) => pattern.split("#").length - 1;
63
+ const lengthsOf = (entry) => entry.patterns.map(capacity);
64
+ /** A typed trunk prefix earns its own room; without one the longest valid length is the cap. */
65
+ const capDigits = (entry, digits) => digits.slice(0, splitTrunk(entry, digits)[0].length + Math.max(...lengthsOf(entry)));
66
+ const onlyDigits = (text) => text.replace(/\D/g, "");
67
+ /** Regional indicator letters. Platforms without flag glyphs show the two letters, which still read. */
68
+ export const flagOf = (iso) => String.fromCodePoint(...[...iso.toUpperCase()].map(char => 0x1f1e6 + char.charCodeAt(0) - 65));
69
+ function splitTrunk(entry, digits) {
70
+ return entry.trunk && digits.startsWith(entry.trunk) ? [entry.trunk, digits.slice(entry.trunk.length)] : ["", digits];
71
+ }
72
+ /** Separators only print while digits follow, so a half typed number never ends in a dangling ") " or "-". */
73
+ function applyPattern(digits, pattern) {
74
+ let out = "", index = 0;
75
+ for (const char of pattern) {
76
+ if (index >= digits.length)
77
+ break;
78
+ out += char === "#" ? digits[index++] : char;
79
+ }
80
+ return out + digits.slice(index);
81
+ }
82
+ function patternFor(entry, length) {
83
+ return entry.patterns.find(pattern => capacity(pattern) >= length) ?? entry.patterns[entry.patterns.length - 1];
84
+ }
85
+ /** Formats national digits as typed, keeping a typed trunk prefix: "07911123456" in the UK reads "07911 123456". */
86
+ export function formatNational(entry, digits) {
87
+ const [trunk, rest] = splitTrunk(entry, digits);
88
+ const pattern = patternFor(entry, rest.length);
89
+ const body = applyPattern(rest, pattern);
90
+ if (!trunk)
91
+ return body;
92
+ if (!rest)
93
+ return trunk;
94
+ return trunk + (/^#/.test(pattern) ? "" : " ") + body;
95
+ }
96
+ function statusOf(entry, digits) {
97
+ const length = splitTrunk(entry, digits)[1].length;
98
+ const lengths = lengthsOf(entry);
99
+ if (!length)
100
+ return "empty";
101
+ if (lengths.includes(length))
102
+ return "valid";
103
+ return length > Math.max(...lengths) ? "too-long" : "incomplete";
104
+ }
105
+ const toE164 = (entry, digits) => { const rest = splitTrunk(entry, digits)[1]; return rest ? `+${entry.dial}${rest}` : ""; };
106
+ /** Reads "+44 (0)7911 123456", "0044 7911…", or "+14165550123" into a country and national digits. Longest calling code wins. */
107
+ export function parsePhoneNumber(input, pool = PHONE_COUNTRIES) {
108
+ const trimmed = input.trim();
109
+ let digits = onlyDigits(trimmed);
110
+ if (!trimmed.startsWith("+")) {
111
+ if (!trimmed.startsWith("00"))
112
+ return null;
113
+ digits = digits.slice(2);
114
+ }
115
+ for (const size of [3, 2, 1]) {
116
+ const dial = digits.slice(0, size);
117
+ const matches = pool.filter(entry => entry.dial === dial);
118
+ if (!matches.length)
119
+ continue;
120
+ let rest = digits.slice(size);
121
+ const entry = matches.find(item => item.areaCodes?.some(code => rest.startsWith(code))) ?? matches.find(item => !item.areaCodes) ?? matches[0];
122
+ // "+44 (0)7911…" carries a trunk zero it should not; drop it when the rest is still a full number without it.
123
+ if (entry.trunk && rest.startsWith(entry.trunk) && rest.length - entry.trunk.length >= Math.min(...lengthsOf(entry)))
124
+ rest = rest.slice(entry.trunk.length);
125
+ return { country: entry, national: capDigits(entry, rest) };
126
+ }
127
+ return null;
128
+ }
129
+ /** Formats an E.164 number for display, such as "+44 7400 123456". Returns the input when it cannot be read. */
130
+ export function formatPhoneNumber(e164) {
131
+ const parsed = parsePhoneNumber(e164);
132
+ return parsed ? `+${parsed.country.dial} ${formatNational(parsed.country, parsed.national)}` : e164;
133
+ }
134
+ const enterEase = [...motionTokens.ease.enter];
135
+ const standardEase = [...motionTokens.ease.standard];
136
+ const { blur } = motionTokens;
137
+ /** Duration springs restated as stiffness and damping, so a retarget mid flight keeps the velocity it already has. */
138
+ const physical = (visualDuration, bounce) => {
139
+ const root = (2 * Math.PI) / (visualDuration * 1.2);
140
+ return { type: "spring", stiffness: root * root, damping: 2 * (1 - bounce) * root, mass: 1 };
141
+ };
142
+ const GROW = physical(.4, .12), SHRINK = physical(.32, 0), GLIDE = physical(.28, .08), WIDTH = physical(.42, .16);
143
+ const PANEL_MAX = 340, CLOSED_RADIUS = 17, OPEN_RADIUS = 22, PAGE = 8;
144
+ const subscribe = () => () => { };
145
+ function useReducedFlag() {
146
+ const hydrated = useSyncExternalStore(subscribe, () => true, () => false);
147
+ return !!useReducedMotion() && hydrated;
148
+ }
149
+ /** The flag and calling code roll in the direction of the list: a country further down rises from below. */
150
+ const layerVariants = {
151
+ enter: (dir) => ({ opacity: 0, y: `${dir * .6}em`, filter: `blur(${blur.soft}px)` }),
152
+ rest: { opacity: 1, y: 0, filter: "blur(0px)", transitionEnd: { filter: "none" } },
153
+ exit: (dir) => ({ opacity: 0, y: `${dir * -.5}em`, filter: `blur(${blur.subtle}px)`, transition: { duration: .12, ease: standardEase } }),
154
+ };
155
+ const layerFade = { enter: { opacity: 0 }, rest: { opacity: 1, y: 0, filter: "none" }, exit: { opacity: 0, transition: { duration: .08 } } };
156
+ function matchesQuery(entry, needle) {
157
+ if (!needle)
158
+ return true;
159
+ const digits = onlyDigits(needle);
160
+ if (digits && /^[+\d\s()-]+$/.test(needle))
161
+ return entry.dial.startsWith(digits) || digits.startsWith(entry.dial);
162
+ if (needle.replace("+", "") === "")
163
+ return true;
164
+ return entry.name.toLowerCase().includes(needle) || entry.iso.toLowerCase() === needle;
165
+ }
166
+ /** Position in the formatted text just after the nth digit. */
167
+ /**
168
+ * A search that is really a whole international number: "+" and more digits than any calling code and area code take
169
+ * ("+1 202 555 0142" typed key by key after "+" opened the list). It belongs in the number field, under its country.
170
+ */
171
+ function numberInSearch(query, pool) {
172
+ const text = query.trim();
173
+ if (!/^\+[\d\s().-]+$/.test(text))
174
+ return null;
175
+ const digits = onlyDigits(text);
176
+ if (digits.length < 5)
177
+ return null;
178
+ const parsed = parsePhoneNumber(`+${digits}`, pool);
179
+ return parsed && parsed.national ? parsed : null;
180
+ }
181
+ function caretAfterDigits(text, count) {
182
+ if (count <= 0) {
183
+ const first = text.search(/\d/);
184
+ return first < 0 ? text.length : Math.min(first, text.length);
185
+ }
186
+ let seen = 0;
187
+ for (let index = 0; index < text.length; index++)
188
+ if (/\d/.test(text[index]) && ++seen === count)
189
+ return index + 1;
190
+ return text.length;
191
+ }
192
+ function MessageRow({ id, text, tone }) {
193
+ const reduced = useReducedFlag();
194
+ return _jsx(motion.span, { className: styles.messageSlot, initial: reduced ? false : { height: 0, opacity: 0 }, animate: { height: "auto", opacity: 1 }, exit: { height: 0, opacity: 0, transition: reduced ? { duration: 0 } : { height: motionTokens.spring.smooth, opacity: { duration: motionTokens.duration.instant } } }, transition: reduced ? { duration: 0 } : { height: motionTokens.spring.smooth, opacity: { duration: motionTokens.duration.fast } }, children: _jsx(AnimatePresence, { initial: false, mode: "popLayout", children: _jsx(motion.span, { id: id, className: tone === "error" ? styles.error : styles.hint, role: tone === "error" ? "alert" : undefined, initial: reduced ? { opacity: 0 } : { opacity: 0, y: "0.35em", filter: `blur(${blur.soft}px)` }, animate: { opacity: 1, y: 0, filter: "blur(0px)" }, exit: { opacity: 0, transition: { duration: .1 } }, transition: { duration: reduced ? .12 : motionTokens.duration.standard, ease: enterEase }, children: text }, text) }) });
195
+ }
196
+ /**
197
+ * A phone number field with a country picker. The country button grows into a searchable list, the number formats as it is
198
+ * typed with a faint guide showing the rest of the expected shape, pasted or autofilled international numbers pick their own
199
+ * country, and the value comes out in E.164. Typing "+" in the number jumps into the picker with a calling code search.
200
+ */
201
+ export const PhoneInput = forwardRef(function PhoneInput({ label, hideLabel = false, value, defaultValue, onValueChange, country: countryProp, defaultCountry = "US", onCountryChange, countries, preferredCountries = ["US", "CA", "GB"], description, error, validate = true, disabled = false, required, name, id, className, onBlur, }, forwardedRef) {
202
+ const reduced = useReducedFlag();
203
+ const uid = useId();
204
+ const inputId = id ?? `${uid}-number`;
205
+ const listId = `${uid}-list`;
206
+ const hintId = `${inputId}-hint`, errorId = `${inputId}-error`;
207
+ const optionId = (key) => `${uid}-opt-${key}`;
208
+ const pool = useMemo(() => countries?.length ? PHONE_COUNTRIES.filter(entry => countries.includes(entry.iso)) : PHONE_COUNTRIES, [countries]);
209
+ const fallback = BY_ISO.get(defaultCountry) ?? PHONE_COUNTRIES[0];
210
+ const [state, setState] = useState(() => {
211
+ const parsed = parsePhoneNumber(value ?? defaultValue ?? "", pool);
212
+ return parsed ? { iso: parsed.country.iso, digits: parsed.national } : { iso: fallback.iso, digits: "" };
213
+ });
214
+ const iso = countryProp ?? state.iso;
215
+ const current = BY_ISO.get(iso) ?? fallback;
216
+ const digits = state.digits;
217
+ const e164 = toE164(current, digits);
218
+ // A controlled value that differs from what the field last produced is read back in.
219
+ const [seenValue, setSeenValue] = useState(value);
220
+ if (value !== seenValue) {
221
+ setSeenValue(value);
222
+ if (value !== undefined && value !== e164) {
223
+ const parsed = parsePhoneNumber(value, pool);
224
+ setState(parsed ? { iso: parsed.country.iso, digits: parsed.national } : { iso, digits: "" });
225
+ }
226
+ }
227
+ const formatted = formatNational(current, digits);
228
+ const status = statusOf(current, digits);
229
+ const [touched, setTouched] = useState(false);
230
+ const lengths = lengthsOf(current);
231
+ const lengthCopy = lengths.length === 1 ? `${lengths[0]}` : `${lengths.slice(0, -1).join(", ")} or ${lengths[lengths.length - 1]}`;
232
+ const builtIn = validate && touched && (status === "incomplete" || status === "too-long") ? `Numbers in ${current.name} have ${lengthCopy} digits` : undefined;
233
+ const message = error ?? builtIn;
234
+ /* The guide: the rest of the example number, in the shape this country expects, drawn faintly after what was typed. */
235
+ const guide = useMemo(() => {
236
+ const [trunk, rest] = splitTrunk(current, digits);
237
+ if (rest.length >= current.example.length)
238
+ return "";
239
+ const full = formatNational(current, trunk + rest + current.example.slice(rest.length));
240
+ return full.startsWith(formatted) ? full.slice(formatted.length) : "";
241
+ }, [current, digits, formatted]);
242
+ const inputRef = useRef(null);
243
+ useImperativeHandle(forwardedRef, () => inputRef.current);
244
+ const pendingCaret = useRef(null);
245
+ const [, rerender] = useReducer((count) => count + 1, 0);
246
+ const emit = useCallback((entry, nextDigits) => {
247
+ const nextStatus = statusOf(entry, nextDigits);
248
+ onValueChange?.(toE164(entry, nextDigits), { country: entry, formatted: formatNational(entry, nextDigits), status: nextStatus, valid: nextStatus === "valid" });
249
+ }, [onValueChange]);
250
+ const setNumber = (nextDigits, caretDigits, entry = current) => {
251
+ const capped = capDigits(entry, nextDigits);
252
+ pendingCaret.current = caretDigits === null ? null : Math.min(caretDigits, capped.length);
253
+ if (entry.iso !== current.iso)
254
+ onCountryChange?.(entry.iso);
255
+ // Nothing changed (a refused digit): React restores the old text after this handler, so the caret is placed on the next render.
256
+ if (capped === digits && entry.iso === current.iso) {
257
+ rerender();
258
+ return;
259
+ }
260
+ setState({ iso: entry.iso, digits: capped });
261
+ emit(entry, capped);
262
+ };
263
+ function placeCaret() {
264
+ const input = inputRef.current, count = pendingCaret.current;
265
+ pendingCaret.current = null;
266
+ if (!input || count === null || document.activeElement !== input)
267
+ return;
268
+ const position = caretAfterDigits(input.value, count);
269
+ input.setSelectionRange(position, position);
270
+ }
271
+ useLayoutEffect(placeCaret);
272
+ const [announcement, setAnnouncement] = useState("");
273
+ /* ---------------------------------------------- Number entry ---------------------------------------------- */
274
+ function onNumberChange(input) {
275
+ const raw = input.value;
276
+ // Autofill and dropped text arrive as a change: an international number chooses its own country.
277
+ if (raw.includes("+") || (/^\s*00/.test(raw) && onlyDigits(raw).length > 6)) {
278
+ const parsed = parsePhoneNumber(raw.slice(Math.max(0, raw.indexOf("+"))), pool);
279
+ if (parsed && parsed.national) {
280
+ if (parsed.country.iso !== current.iso)
281
+ setAnnouncement(`Country set to ${parsed.country.name}`);
282
+ setNumber(parsed.national, parsed.national.length, parsed.country);
283
+ return;
284
+ }
285
+ }
286
+ const caret = input.selectionStart ?? raw.length;
287
+ setNumber(onlyDigits(raw), onlyDigits(raw.slice(0, caret)).length);
288
+ }
289
+ function onNumberKeyDown(event) {
290
+ const input = event.currentTarget;
291
+ if (event.key === "+" && !event.metaKey && !event.ctrlKey) {
292
+ event.preventDefault();
293
+ openList("+");
294
+ return;
295
+ }
296
+ const start = input.selectionStart ?? 0, end = input.selectionEnd ?? 0;
297
+ if (start !== end || event.metaKey || event.ctrlKey || event.altKey)
298
+ return;
299
+ // Deleting a separator deletes the digit beside it instead of doing nothing.
300
+ if (event.key === "Backspace" && start > 0 && !/\d/.test(input.value[start - 1])) {
301
+ event.preventDefault();
302
+ const index = onlyDigits(input.value.slice(0, start)).length;
303
+ if (index > 0)
304
+ setNumber(digits.slice(0, index - 1) + digits.slice(index), index - 1);
305
+ }
306
+ if (event.key === "Delete" && start < input.value.length && !/\d/.test(input.value[start])) {
307
+ event.preventDefault();
308
+ const index = onlyDigits(input.value.slice(0, start)).length;
309
+ setNumber(digits.slice(0, index) + digits.slice(index + 1), index);
310
+ }
311
+ }
312
+ function onPaste(event) {
313
+ const text = event.clipboardData.getData("text");
314
+ if (!text)
315
+ return;
316
+ event.preventDefault();
317
+ const input = event.currentTarget;
318
+ const parsed = /^\s*(\+|00)/.test(text) ? parsePhoneNumber(text, pool) : null;
319
+ if (parsed) {
320
+ if (parsed.country.iso !== current.iso)
321
+ setAnnouncement(`Country set to ${parsed.country.name}`);
322
+ setNumber(parsed.national, parsed.national.length, parsed.country);
323
+ return;
324
+ }
325
+ let pasted = onlyDigits(text);
326
+ // "1 415 555 0132" pasted into a US field: the leading calling code is dropped when the number would not fit otherwise.
327
+ if (pasted.startsWith(current.dial) && pasted.length > capDigits(current, pasted).length)
328
+ pasted = pasted.slice(current.dial.length);
329
+ // A whole number replaces what was there; a fragment goes in at the caret.
330
+ if (pasted.length >= Math.min(...lengthsOf(current))) {
331
+ setNumber(pasted, pasted.length);
332
+ return;
333
+ }
334
+ const a = onlyDigits(input.value.slice(0, input.selectionStart ?? 0)).length;
335
+ const b = onlyDigits(input.value.slice(0, input.selectionEnd ?? 0)).length;
336
+ setNumber(digits.slice(0, a) + pasted + digits.slice(b), a + pasted.length);
337
+ }
338
+ /* ---------------------------------------------- Country picker ---------------------------------------------- */
339
+ const [open, setOpen] = useState(false);
340
+ const [query, setQuery] = useState("");
341
+ const [active, setActive] = useState(null);
342
+ const [roll, setRoll] = useState(1);
343
+ const needle = query.trim().toLowerCase();
344
+ const sections = useMemo(() => {
345
+ const sorted = [...pool].sort((a, b) => a.name.localeCompare(b.name));
346
+ if (needle && needle !== "+") {
347
+ const hits = sorted.filter(entry => matchesQuery(entry, needle));
348
+ const digitsOnly = onlyDigits(needle);
349
+ if (digitsOnly)
350
+ hits.sort((a, b) => Number(b.dial === digitsOnly) - Number(a.dial === digitsOnly) || a.dial.length - b.dial.length);
351
+ return [{ key: "results", rows: hits.map(entry => ({ key: `r-${entry.iso}`, entry })) }];
352
+ }
353
+ const preferred = preferredCountries.map(code => pool.find(entry => entry.iso === code)).filter((entry) => !!entry);
354
+ const all = { key: "all", label: preferred.length ? "All countries" : undefined, rows: sorted.map(entry => ({ key: `a-${entry.iso}`, entry })) };
355
+ return preferred.length ? [{ key: "preferred", label: "Suggested", rows: preferred.map(entry => ({ key: `p-${entry.iso}`, entry })) }, all] : [all];
356
+ }, [needle, pool, preferredCountries]);
357
+ const rows = useMemo(() => sections.flatMap(section => section.rows), [sections]);
358
+ const activeKey = active !== null && rows.some(row => row.key === active) ? active : rows[0]?.key ?? null;
359
+ const orderOf = useMemo(() => new Map([...pool].sort((a, b) => a.name.localeCompare(b.name)).map((entry, index) => [entry.iso, index])), [pool]);
360
+ const rootRef = useRef(null);
361
+ const controlRef = useRef(null);
362
+ const measureRef = useRef(null);
363
+ const listFaceRef = useRef(null);
364
+ const scrollRef = useRef(null);
365
+ const triggerRef = useRef(null);
366
+ const searchRef = useRef(null);
367
+ const rowRefs = useRef(new Map());
368
+ /* One surface whose width, height, and corners spring between the country button and the open list. */
369
+ const anchorW = useMotionValue("auto");
370
+ const shapeW = useMotionValue("100%"), shapeH = useMotionValue("100%"), radius = useMotionValue(CLOSED_RADIUS);
371
+ const sizes = useRef({ trigger: 0, lid: 0, panel: 0, list: 0 });
372
+ const live = useRef({ open: false, reduced: false, measured: false });
373
+ useLayoutEffect(() => { live.current.reduced = reduced; }, [reduced]);
374
+ const place = useCallback((animated) => {
375
+ const { trigger, lid, panel, list } = sizes.current;
376
+ if (!trigger || !lid)
377
+ return;
378
+ const isOpen = live.current.open;
379
+ const next = isOpen ? { w: Math.max(trigger, panel), h: lid + 2 + list, r: OPEN_RADIUS } : { w: trigger, h: lid, r: CLOSED_RADIUS };
380
+ if (!animated || live.current.reduced || !live.current.measured) {
381
+ anchorW.jump(trigger);
382
+ shapeW.jump(next.w);
383
+ shapeH.jump(next.h);
384
+ radius.jump(next.r);
385
+ live.current.measured = true;
386
+ return;
387
+ }
388
+ const growing = isOpen;
389
+ animate(anchorW, trigger, WIDTH);
390
+ animate(shapeW, next.w, growing ? GROW : SHRINK);
391
+ animate(shapeH, next.h, growing ? GROW : SHRINK);
392
+ animate(radius, next.r, growing ? GROW : SHRINK);
393
+ }, [anchorW, radius, shapeH, shapeW]);
394
+ const read = useCallback(() => {
395
+ const measure = measureRef.current, control = controlRef.current, face = listFaceRef.current, root = rootRef.current;
396
+ if (!measure || !control || !face || !root)
397
+ return false;
398
+ const panel = Math.min(PANEL_MAX, control.offsetWidth);
399
+ root.style.setProperty("--pi-panel-w", `${panel}px`);
400
+ const previous = sizes.current;
401
+ const next = { trigger: measure.offsetWidth, lid: control.clientHeight, panel, list: face.offsetHeight };
402
+ sizes.current = next;
403
+ return next.trigger !== previous.trigger || next.lid !== previous.lid || next.panel !== previous.panel || (live.current.open && next.list !== previous.list);
404
+ }, []);
405
+ useLayoutEffect(() => {
406
+ const measure = measureRef.current, control = controlRef.current, face = listFaceRef.current;
407
+ if (read())
408
+ place(live.current.measured);
409
+ if (!measure || !control || !face || typeof ResizeObserver === "undefined")
410
+ return;
411
+ const observer = new ResizeObserver(() => { if (read())
412
+ place(live.current.measured); });
413
+ observer.observe(measure);
414
+ observer.observe(control);
415
+ observer.observe(face);
416
+ return () => observer.disconnect();
417
+ }, [place, read]);
418
+ const pendingFocus = useRef(null);
419
+ useLayoutEffect(() => {
420
+ if (live.current.open !== open) {
421
+ live.current.open = open;
422
+ read();
423
+ place(true);
424
+ }
425
+ const target = pendingFocus.current;
426
+ pendingFocus.current = null;
427
+ if (target === "search")
428
+ searchRef.current?.focus({ preventScroll: true });
429
+ if (target === "trigger")
430
+ triggerRef.current?.focus({ preventScroll: true });
431
+ if (target === "number") {
432
+ const input = inputRef.current;
433
+ input?.focus({ preventScroll: true });
434
+ input?.setSelectionRange(input.value.length, input.value.length);
435
+ }
436
+ }, [open, place, read]);
437
+ /* The highlight glides between rows on its own spring. */
438
+ const hy = useMotionValue(0), hh = useMotionValue(0), ho = useMotionValue(0);
439
+ const scrollIntent = useRef(false);
440
+ useLayoutEffect(() => {
441
+ const node = open && activeKey ? rowRefs.current.get(activeKey) : undefined;
442
+ if (!node) {
443
+ animate(ho, 0, { duration: reduced || !open ? 0 : .12, ease: standardEase });
444
+ return;
445
+ }
446
+ const top = node.offsetTop, height = node.offsetHeight;
447
+ if (ho.get() < .05 || reduced) {
448
+ hy.jump(top);
449
+ hh.jump(height);
450
+ }
451
+ else {
452
+ animate(hy, top, GLIDE);
453
+ animate(hh, height, GLIDE);
454
+ }
455
+ animate(ho, 1, { duration: reduced ? 0 : .12, ease: enterEase });
456
+ const scroller = scrollRef.current;
457
+ if (scroller && scrollIntent.current) {
458
+ scrollIntent.current = false;
459
+ const pad = 6;
460
+ if (top < scroller.scrollTop + pad)
461
+ scroller.scrollTop = top - pad;
462
+ else if (top + height > scroller.scrollTop + scroller.clientHeight - pad)
463
+ scroller.scrollTop = top + height - scroller.clientHeight + pad;
464
+ }
465
+ }, [activeKey, hh, ho, hy, open, reduced, sections]);
466
+ function openList(seed = "") {
467
+ if (disabled || open)
468
+ return;
469
+ setQuery(seed);
470
+ const selectedRow = seed ? null : sections.flatMap(section => section.rows).find(row => row.entry.iso === current.iso && !row.key.startsWith("p-")) ?? null;
471
+ setActive(selectedRow?.key ?? null);
472
+ scrollIntent.current = true;
473
+ pendingFocus.current = "search";
474
+ setAnnouncement("");
475
+ setOpen(true);
476
+ }
477
+ const close = useCallback((focus) => {
478
+ pendingFocus.current = focus;
479
+ setOpen(false);
480
+ }, [setOpen]);
481
+ function pick(entry) {
482
+ if (!entry)
483
+ return;
484
+ if (entry.iso !== current.iso) {
485
+ setRoll(Math.sign((orderOf.get(entry.iso) ?? 0) - (orderOf.get(current.iso) ?? 0)) || 1);
486
+ const nextDigits = capDigits(entry, digits);
487
+ if (countryProp === undefined)
488
+ setState({ iso: entry.iso, digits: nextDigits });
489
+ else
490
+ setState(last => ({ ...last, digits: nextDigits }));
491
+ onCountryChange?.(entry.iso);
492
+ emit(entry, nextDigits);
493
+ setAnnouncement(`${entry.name}, +${entry.dial}`);
494
+ }
495
+ close("number");
496
+ }
497
+ function move(key) {
498
+ if (!key)
499
+ return;
500
+ scrollIntent.current = true;
501
+ setActive(key);
502
+ }
503
+ function onSearchKeyDown(event) {
504
+ const at = rows.findIndex(row => row.key === activeKey);
505
+ switch (event.key) {
506
+ case "ArrowDown":
507
+ event.preventDefault();
508
+ move(rows[Math.min(rows.length - 1, at + 1)]?.key);
509
+ return;
510
+ case "ArrowUp":
511
+ event.preventDefault();
512
+ move(rows[Math.max(0, at - 1)]?.key);
513
+ return;
514
+ case "PageDown":
515
+ event.preventDefault();
516
+ move(rows[Math.min(rows.length - 1, at + PAGE)]?.key);
517
+ return;
518
+ case "PageUp":
519
+ event.preventDefault();
520
+ move(rows[Math.max(0, at - PAGE)]?.key);
521
+ return;
522
+ case "Home":
523
+ if (query)
524
+ return;
525
+ event.preventDefault();
526
+ move(rows[0]?.key);
527
+ return;
528
+ case "End":
529
+ if (query)
530
+ return;
531
+ event.preventDefault();
532
+ move(rows[rows.length - 1]?.key);
533
+ return;
534
+ case "Enter":
535
+ event.preventDefault();
536
+ pick(rows[at]?.entry);
537
+ return;
538
+ case "Escape":
539
+ event.preventDefault();
540
+ event.stopPropagation();
541
+ close("trigger");
542
+ return;
543
+ case "Tab":
544
+ close(null);
545
+ return;
546
+ }
547
+ }
548
+ function onTriggerKeyDown(event) {
549
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
550
+ event.preventDefault();
551
+ openList();
552
+ return;
553
+ }
554
+ if (event.key.length === 1 && event.key !== " " && !event.metaKey && !event.ctrlKey && !event.altKey) {
555
+ event.preventDefault();
556
+ openList(event.key);
557
+ }
558
+ }
559
+ function onQuery(next) {
560
+ // Typing a whole number after "+" (the habit of writing "+1 202 555 0142") moves it into the number field as soon
561
+ // as it is more than a calling code, under its country; the rest of it goes on in the number field.
562
+ const typed = numberInSearch(next, pool);
563
+ if (typed) {
564
+ if (typed.country.iso !== current.iso)
565
+ setAnnouncement(`Country set to ${typed.country.name}`);
566
+ setNumber(typed.national, typed.national.length, typed.country);
567
+ setQuery("");
568
+ close("number");
569
+ return;
570
+ }
571
+ setQuery(next);
572
+ setActive(null);
573
+ if (scrollRef.current)
574
+ scrollRef.current.scrollTop = 0;
575
+ const text = next.trim().toLowerCase();
576
+ const count = text && text !== "+" ? pool.filter(entry => matchesQuery(entry, text)).length : pool.length;
577
+ setAnnouncement(text ? count ? `${count} ${count === 1 ? "country" : "countries"}` : "No matches" : "");
578
+ }
579
+ useEffect(() => {
580
+ if (!open)
581
+ return;
582
+ const down = (event) => { if (!rootRef.current?.contains(event.target))
583
+ close(null); };
584
+ document.addEventListener("pointerdown", down);
585
+ return () => document.removeEventListener("pointerdown", down);
586
+ }, [close, open]);
587
+ const onRootBlur = (event) => {
588
+ const next = event.relatedTarget;
589
+ if (open && next && !event.currentTarget.contains(next))
590
+ close(null);
591
+ };
592
+ const showCheck = status === "valid";
593
+ const invalid = !!message;
594
+ const describedBy = [description ? hintId : null, message ? errorId : null].filter(Boolean).join(" ") || undefined;
595
+ const rootStyle = { "--pi-closed-r": `${CLOSED_RADIUS}px` };
596
+ const face = (entry) => _jsxs(_Fragment, { children: [_jsx("span", { className: styles.flag, "aria-hidden": "true", children: flagOf(entry.iso) }), _jsxs("span", { className: styles.dial, children: ["+", entry.dial] })] });
597
+ return _jsxs("div", { ref: rootRef, className: [styles.field, className].filter(Boolean).join(" "), style: rootStyle, "data-open": open || undefined, "data-disabled": disabled || undefined, onBlur: onRootBlur, children: [_jsx("label", { htmlFor: inputId, className: hideLabel ? styles.srOnly : styles.label, children: label }), _jsxs("div", { ref: controlRef, className: styles.control, "data-invalid": invalid || undefined, children: [_jsxs(motion.div, { className: styles.anchor, style: { width: anchorW }, children: [_jsx("span", { ref: measureRef, className: `${styles.trigger} ${styles.measure}`, "aria-hidden": "true", children: face(current) }), _jsxs(motion.div, { className: styles.shape, style: { width: shapeW, height: shapeH, borderRadius: radius }, children: [_jsxs("div", { className: styles.lid, children: [_jsx("button", { ref: triggerRef, type: "button", className: styles.trigger, disabled: disabled, inert: open || undefined, tabIndex: open ? -1 : 0, "aria-haspopup": "listbox", "aria-expanded": open, "aria-controls": listId, "aria-label": `Country, ${current.name} +${current.dial}`, onClick: () => (open ? close("trigger") : openList()), onKeyDown: onTriggerKeyDown, children: _jsx("span", { className: styles.slot, "data-hidden": open || undefined, children: _jsx(AnimatePresence, { initial: false, custom: roll, children: _jsx(motion.span, { className: styles.layer, custom: roll, variants: reduced ? layerFade : layerVariants, initial: "enter", animate: "rest", exit: "exit", transition: reduced ? { duration: .12 } : { y: GLIDE, opacity: { duration: .2, ease: enterEase }, filter: { duration: .22, ease: enterEase } }, children: face(current) }, current.iso) }) }) }), _jsxs(motion.div, { className: styles.searchRow, inert: !open || undefined, initial: false, animate: open ? { opacity: 1, filter: "blur(0px)" } : { opacity: 0, filter: reduced ? "blur(0px)" : `blur(${blur.subtle}px)` }, transition: open ? { duration: .18, ease: enterEase, delay: reduced ? 0 : .05 } : { duration: .1, ease: standardEase }, children: [_jsx(Search, { className: styles.searchIcon, size: 16, strokeWidth: 1.75, "aria-hidden": "true" }), _jsx("input", { ref: searchRef, className: styles.search, type: "text", role: "combobox", "aria-label": "Search countries or calling codes", "aria-expanded": open, "aria-controls": listId, "aria-autocomplete": "list", "aria-activedescendant": open && activeKey ? optionId(activeKey) : undefined, placeholder: "Country or code", value: query, autoComplete: "off", spellCheck: false, onChange: event => onQuery(event.target.value), onKeyDown: onSearchKeyDown }), _jsx(AnimatePresence, { initial: false, children: query && _jsx(motion.button, { type: "button", className: styles.clear, "aria-label": "Clear search", onPointerDown: event => event.preventDefault(), onClick: () => { onQuery(""); searchRef.current?.focus(); }, initial: { opacity: 0, scale: .6 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: .6, transition: { duration: .1 } }, transition: reduced ? { duration: 0 } : GLIDE, children: _jsx(X, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }) }, "clear") }), _jsx("button", { type: "button", className: styles.closeHit, "aria-label": "Close country list", onClick: () => close("trigger") })] }), _jsx(motion.span, { className: styles.chevron, "aria-hidden": "true", initial: false, animate: { rotate: open ? 180 : 0 }, transition: reduced ? { duration: 0 } : GLIDE, children: _jsx(ChevronDown, { size: 16, strokeWidth: 1.75 }) })] }), _jsx(motion.div, { ref: listFaceRef, className: styles.listFace, inert: !open || undefined, "aria-hidden": !open || undefined, initial: false, animate: open ? { opacity: 1, y: 0, filter: "blur(0px)" } : { opacity: 0, y: reduced ? 0 : -6, filter: reduced ? "blur(0px)" : `blur(${blur.subtle}px)` }, transition: open ? { y: GROW, opacity: { duration: .2, ease: enterEase, delay: reduced ? 0 : .04 }, filter: { duration: .22, ease: enterEase, delay: .04 } } : { duration: .1, ease: standardEase }, children: _jsx("div", { ref: scrollRef, className: styles.scroll, children: _jsxs("div", { id: listId, className: styles.options, role: "listbox", "aria-label": "Countries", children: [_jsx(motion.span, { className: styles.highlight, style: { y: hy, height: hh, opacity: ho }, "aria-hidden": "true" }), sections.map(section => {
598
+ const body = section.rows.map(row => {
599
+ const selected = row.entry.iso === current.iso;
600
+ return _jsxs("div", { id: optionId(row.key), role: "option", "aria-selected": selected, className: styles.option, "data-active": row.key === activeKey || undefined, ref: node => { if (node)
601
+ rowRefs.current.set(row.key, node);
602
+ else
603
+ rowRefs.current.delete(row.key); }, onPointerMove: event => { if (event.pointerType === "mouse" && row.key !== activeKey)
604
+ setActive(row.key); }, onPointerDown: event => event.preventDefault(), onClick: () => pick(row.entry), children: [_jsx("span", { className: styles.flag, "aria-hidden": "true", children: flagOf(row.entry.iso) }), _jsx("span", { className: styles.name, children: row.entry.name }), _jsxs("span", { className: styles.meta, children: ["+", row.entry.dial] }), _jsx("span", { className: styles.check, "data-on": selected || undefined, "aria-hidden": "true", children: _jsx(Check, { size: 16, strokeWidth: 1.75 }) })] }, row.key);
605
+ });
606
+ return section.label
607
+ ? _jsxs("div", { role: "group", "aria-labelledby": `${uid}-${section.key}`, className: styles.group, children: [_jsx("div", { id: `${uid}-${section.key}`, className: styles.groupLabel, role: "presentation", children: section.label }), body] }, section.key)
608
+ : _jsx("div", { role: "presentation", className: styles.group, children: body }, section.key);
609
+ }), rows.length === 0 && _jsxs("p", { className: styles.empty, children: ["No countries match \u201C", query.trim(), "\u201D"] })] }) }) })] })] }), _jsxs("div", { className: styles.numberWrap, children: [_jsxs("span", { className: styles.guide, "aria-hidden": "true", children: [_jsx("span", { className: styles.guideTyped, children: formatted }), guide] }), _jsx("input", { ref: inputRef, id: inputId, className: styles.number, type: "tel", inputMode: "tel", autoComplete: "tel", value: formatted, disabled: disabled, required: required, "aria-invalid": invalid || undefined, "aria-describedby": describedBy, "aria-label": hideLabel ? label : undefined, onChange: event => onNumberChange(event.currentTarget), onKeyDown: onNumberKeyDown, onPaste: onPaste, onBlur: event => { setTouched(digits.length > 0); onBlur?.(event); } }), _jsx(AnimatePresence, { initial: false, children: showCheck && _jsx(motion.span, { className: styles.valid, "aria-hidden": "true", initial: reduced ? { opacity: 0 } : { opacity: 0, scale: .6, filter: `blur(${blur.subtle}px)` }, animate: { opacity: 1, scale: 1, filter: "blur(0px)" }, exit: { opacity: 0, scale: reduced ? 1 : .8, transition: { duration: .1 } }, transition: reduced ? { duration: .12 } : motionTokens.spring.snappy, children: _jsx(Check, { size: 16, strokeWidth: 2 }) }, "ok") })] })] }), _jsx(AnimatePresence, { initial: false, children: description && !message && _jsx(MessageRow, { id: hintId, text: description, tone: "hint" }, "hint") }), _jsx(AnimatePresence, { initial: false, children: message && _jsx(MessageRow, { id: errorId, text: message, tone: "error" }, "error") }), name && _jsx("input", { type: "hidden", name: name, value: e164 }), _jsx("span", { className: styles.srOnly, role: "status", "aria-live": "polite", children: announcement || (showCheck ? `Valid ${current.name} number` : "") })] });
610
+ });
611
+ PhoneInput.displayName = "PhoneInput";
612
+ export default PhoneInput;