@seekalfred/primitives 0.5.1 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (446) hide show
  1. package/README.md +682 -19
  2. package/dist/accordion/accordion.js +10 -4
  3. package/dist/accordion/accordion.js.map +1 -1
  4. package/dist/action-button/action-button.d.ts +50 -0
  5. package/dist/action-button/action-button.js +159 -0
  6. package/dist/action-button/action-button.js.map +1 -0
  7. package/dist/action-button/index.d.ts +2 -0
  8. package/dist/action-button/index.js +2 -0
  9. package/dist/activity-heatmap/activity-heatmap.d.ts +92 -0
  10. package/dist/activity-heatmap/activity-heatmap.js +699 -0
  11. package/dist/activity-heatmap/activity-heatmap.js.map +1 -0
  12. package/dist/activity-heatmap/index.d.ts +2 -0
  13. package/dist/activity-heatmap/index.js +2 -0
  14. package/dist/alert/alert.d.ts +19 -1
  15. package/dist/alert/alert.js +180 -23
  16. package/dist/alert/alert.js.map +1 -1
  17. package/dist/alert-dialog/alert-dialog.d.ts +45 -18
  18. package/dist/alert-dialog/alert-dialog.js +128 -9
  19. package/dist/alert-dialog/alert-dialog.js.map +1 -1
  20. package/dist/alert-dialog/index.d.ts +2 -2
  21. package/dist/alert-dialog/index.js +2 -2
  22. package/dist/animated-counter/animated-counter.d.ts +38 -0
  23. package/dist/animated-counter/animated-counter.js +230 -0
  24. package/dist/animated-counter/animated-counter.js.map +1 -0
  25. package/dist/animated-counter/index.d.ts +2 -0
  26. package/dist/animated-counter/index.js +2 -0
  27. package/dist/announcement-bar/announcement-bar.d.ts +67 -0
  28. package/dist/announcement-bar/announcement-bar.js +364 -0
  29. package/dist/announcement-bar/announcement-bar.js.map +1 -0
  30. package/dist/announcement-bar/index.d.ts +2 -0
  31. package/dist/announcement-bar/index.js +2 -0
  32. package/dist/avatar/avatar.d.ts +6 -0
  33. package/dist/avatar/avatar.js +47 -16
  34. package/dist/avatar/avatar.js.map +1 -1
  35. package/dist/badge/badge.d.ts +10 -3
  36. package/dist/badge/badge.js +35 -5
  37. package/dist/badge/badge.js.map +1 -1
  38. package/dist/bar-chart/bar-chart.d.ts +51 -0
  39. package/dist/bar-chart/bar-chart.js +565 -0
  40. package/dist/bar-chart/bar-chart.js.map +1 -0
  41. package/dist/bar-chart/index.d.ts +2 -0
  42. package/dist/bar-chart/index.js +2 -0
  43. package/dist/breadcrumb/breadcrumb.d.ts +2 -29
  44. package/dist/breadcrumb/breadcrumb.js +32 -9
  45. package/dist/breadcrumb/breadcrumb.js.map +1 -1
  46. package/dist/brush-chart/brush-chart.d.ts +64 -0
  47. package/dist/brush-chart/brush-chart.js +1056 -0
  48. package/dist/brush-chart/brush-chart.js.map +1 -0
  49. package/dist/brush-chart/index.d.ts +2 -0
  50. package/dist/brush-chart/index.js +2 -0
  51. package/dist/button/button.d.ts +36 -23
  52. package/dist/button/button.js +198 -45
  53. package/dist/button/button.js.map +1 -1
  54. package/dist/button-group/button-group.d.ts +76 -0
  55. package/dist/button-group/button-group.js +402 -0
  56. package/dist/button-group/button-group.js.map +1 -0
  57. package/dist/button-group/index.d.ts +2 -0
  58. package/dist/button-group/index.js +2 -0
  59. package/dist/calendar/calendar.d.ts +1 -1
  60. package/dist/calendar/calendar.js +257 -30
  61. package/dist/calendar/calendar.js.map +1 -1
  62. package/dist/card/card.d.ts +9 -2
  63. package/dist/card/card.js +16 -5
  64. package/dist/card/card.js.map +1 -1
  65. package/dist/card-stack/card-stack.d.ts +54 -0
  66. package/dist/card-stack/card-stack.js +492 -0
  67. package/dist/card-stack/card-stack.js.map +1 -0
  68. package/dist/card-stack/index.d.ts +2 -0
  69. package/dist/card-stack/index.js +2 -0
  70. package/dist/carousel/carousel.d.ts +56 -0
  71. package/dist/carousel/carousel.js +389 -0
  72. package/dist/carousel/carousel.js.map +1 -0
  73. package/dist/carousel/index.d.ts +2 -0
  74. package/dist/carousel/index.js +2 -0
  75. package/dist/checkbox/checkbox.d.ts +0 -7
  76. package/dist/checkbox/checkbox.js +26 -29
  77. package/dist/checkbox/checkbox.js.map +1 -1
  78. package/dist/chip-group/chip-group.d.ts +37 -0
  79. package/dist/chip-group/chip-group.js +148 -0
  80. package/dist/chip-group/chip-group.js.map +1 -0
  81. package/dist/chip-group/index.d.ts +2 -0
  82. package/dist/chip-group/index.js +2 -0
  83. package/dist/confirm-morph/confirm-morph.d.ts +85 -0
  84. package/dist/confirm-morph/confirm-morph.js +652 -0
  85. package/dist/confirm-morph/confirm-morph.js.map +1 -0
  86. package/dist/confirm-morph/index.d.ts +2 -0
  87. package/dist/confirm-morph/index.js +2 -0
  88. package/dist/copy-button/copy-button.d.ts +32 -0
  89. package/dist/copy-button/copy-button.js +134 -0
  90. package/dist/copy-button/copy-button.js.map +1 -0
  91. package/dist/copy-button/index.d.ts +2 -0
  92. package/dist/copy-button/index.js +2 -0
  93. package/dist/countdown/countdown.d.ts +45 -0
  94. package/dist/countdown/countdown.js +298 -0
  95. package/dist/countdown/countdown.js.map +1 -0
  96. package/dist/countdown/index.d.ts +2 -0
  97. package/dist/countdown/index.js +2 -0
  98. package/dist/date-picker/date-picker.d.ts +51 -0
  99. package/dist/date-picker/date-picker.js +241 -0
  100. package/dist/date-picker/date-picker.js.map +1 -0
  101. package/dist/date-picker/date-range-picker.d.ts +78 -0
  102. package/dist/date-picker/date-range-picker.js +1015 -0
  103. package/dist/date-picker/date-range-picker.js.map +1 -0
  104. package/dist/date-picker/index.d.ts +4 -0
  105. package/dist/date-picker/index.js +4 -0
  106. package/dist/date-picker/picker-parts.d.ts +46 -0
  107. package/dist/date-picker/picker-parts.js +135 -0
  108. package/dist/date-picker/picker-parts.js.map +1 -0
  109. package/dist/dialog/dialog.d.ts +10 -14
  110. package/dist/dialog/dialog.js +40 -12
  111. package/dist/dialog/dialog.js.map +1 -1
  112. package/dist/donut-chart/donut-chart.d.ts +64 -0
  113. package/dist/donut-chart/donut-chart.js +576 -0
  114. package/dist/donut-chart/donut-chart.js.map +1 -0
  115. package/dist/donut-chart/index.d.ts +2 -0
  116. package/dist/donut-chart/index.js +2 -0
  117. package/dist/empty-state/empty-state.d.ts +55 -0
  118. package/dist/empty-state/empty-state.js +89 -0
  119. package/dist/empty-state/empty-state.js.map +1 -0
  120. package/dist/empty-state/index.d.ts +2 -0
  121. package/dist/empty-state/index.js +2 -0
  122. package/dist/expanding-button-group/expanding-button-group.d.ts +54 -0
  123. package/dist/expanding-button-group/expanding-button-group.js +350 -0
  124. package/dist/expanding-button-group/expanding-button-group.js.map +1 -0
  125. package/dist/expanding-button-group/index.d.ts +2 -0
  126. package/dist/expanding-button-group/index.js +2 -0
  127. package/dist/file-drop-zone/file-drop-zone.d.ts +71 -0
  128. package/dist/file-drop-zone/file-drop-zone.js +116 -0
  129. package/dist/file-drop-zone/file-drop-zone.js.map +1 -0
  130. package/dist/file-drop-zone/index.d.ts +2 -0
  131. package/dist/file-drop-zone/index.js +2 -0
  132. package/dist/filter-bar/filter-bar.d.ts +142 -0
  133. package/dist/filter-bar/filter-bar.js +416 -0
  134. package/dist/filter-bar/filter-bar.js.map +1 -0
  135. package/dist/filter-bar/index.d.ts +2 -0
  136. package/dist/filter-bar/index.js +2 -0
  137. package/dist/floating-button-group/floating-button-group.d.ts +61 -0
  138. package/dist/floating-button-group/floating-button-group.js +340 -0
  139. package/dist/floating-button-group/floating-button-group.js.map +1 -0
  140. package/dist/floating-button-group/index.d.ts +2 -0
  141. package/dist/floating-button-group/index.js +2 -0
  142. package/dist/gauge/gauge.d.ts +45 -0
  143. package/dist/gauge/gauge.js +219 -0
  144. package/dist/gauge/gauge.js.map +1 -0
  145. package/dist/gauge/index.d.ts +2 -0
  146. package/dist/gauge/index.js +2 -0
  147. package/dist/icon-morph/icon-morph.d.ts +55 -0
  148. package/dist/icon-morph/icon-morph.js +331 -0
  149. package/dist/icon-morph/icon-morph.js.map +1 -0
  150. package/dist/icon-morph/index.d.ts +2 -0
  151. package/dist/icon-morph/index.js +2 -0
  152. package/dist/in-view-title/in-view-title.d.ts +33 -0
  153. package/dist/in-view-title/in-view-title.js +119 -0
  154. package/dist/in-view-title/in-view-title.js.map +1 -0
  155. package/dist/in-view-title/index.d.ts +2 -0
  156. package/dist/in-view-title/index.js +2 -0
  157. package/dist/index.d.ts +71 -6
  158. package/dist/index.js +73 -11
  159. package/dist/input/input.d.ts +22 -0
  160. package/dist/input/input.js +36 -12
  161. package/dist/input/input.js.map +1 -1
  162. package/dist/lib/cn.js +11 -1
  163. package/dist/lib/cn.js.map +1 -1
  164. package/dist/lib/count-to.js +113 -0
  165. package/dist/lib/count-to.js.map +1 -0
  166. package/dist/lib/eye-morph.js +62 -0
  167. package/dist/lib/eye-morph.js.map +1 -0
  168. package/dist/lib/field-message.js +354 -0
  169. package/dist/lib/field-message.js.map +1 -0
  170. package/dist/lib/field-shell.js +81 -0
  171. package/dist/lib/field-shell.js.map +1 -0
  172. package/dist/lib/field-surface.js +11 -8
  173. package/dist/lib/field-surface.js.map +1 -1
  174. package/dist/lib/flag-emoji.js +50 -0
  175. package/dist/lib/flag-emoji.js.map +1 -0
  176. package/dist/lib/flip.js +88 -0
  177. package/dist/lib/flip.js.map +1 -0
  178. package/dist/lib/height-follow.js +59 -0
  179. package/dist/lib/height-follow.js.map +1 -0
  180. package/dist/lib/icon-swap.js +47 -0
  181. package/dist/lib/icon-swap.js.map +1 -0
  182. package/dist/lib/leaving-list.js +57 -0
  183. package/dist/lib/leaving-list.js.map +1 -0
  184. package/dist/lib/menu-highlight.js +64 -0
  185. package/dist/lib/menu-highlight.js.map +1 -0
  186. package/dist/lib/merge-refs.js +3 -3
  187. package/dist/lib/merge-refs.js.map +1 -1
  188. package/dist/lib/morph-text.js +128 -0
  189. package/dist/lib/morph-text.js.map +1 -0
  190. package/dist/lib/motion.js +169 -0
  191. package/dist/lib/motion.js.map +1 -0
  192. package/dist/lib/pausable-timeout.js +67 -0
  193. package/dist/lib/pausable-timeout.js.map +1 -0
  194. package/dist/lib/rolling-value.js +31 -0
  195. package/dist/lib/rolling-value.js.map +1 -0
  196. package/dist/lib/spring-progress.js +114 -0
  197. package/dist/lib/spring-progress.js.map +1 -0
  198. package/dist/lib/spring-tween.js +500 -0
  199. package/dist/lib/spring-tween.js.map +1 -0
  200. package/dist/lib/svg-id.js +10 -0
  201. package/dist/lib/svg-id.js.map +1 -0
  202. package/dist/lib/swap-text.js +79 -0
  203. package/dist/lib/swap-text.js.map +1 -0
  204. package/dist/lib/use-copy-feedback.js +36 -0
  205. package/dist/lib/use-copy-feedback.js.map +1 -0
  206. package/dist/line-chart/index.d.ts +2 -0
  207. package/dist/line-chart/index.js +2 -0
  208. package/dist/line-chart/line-chart.d.ts +76 -0
  209. package/dist/line-chart/line-chart.js +611 -0
  210. package/dist/line-chart/line-chart.js.map +1 -0
  211. package/dist/mention-input/index.d.ts +2 -0
  212. package/dist/mention-input/index.js +2 -0
  213. package/dist/mention-input/mention-input.d.ts +98 -0
  214. package/dist/mention-input/mention-input.js +576 -0
  215. package/dist/mention-input/mention-input.js.map +1 -0
  216. package/dist/menu/menu.d.ts +2 -0
  217. package/dist/menu/menu.js +23 -12
  218. package/dist/menu/menu.js.map +1 -1
  219. package/dist/money-input/index.d.ts +2 -0
  220. package/dist/money-input/index.js +2 -0
  221. package/dist/money-input/money-input.d.ts +65 -0
  222. package/dist/money-input/money-input.js +607 -0
  223. package/dist/money-input/money-input.js.map +1 -0
  224. package/dist/morph-select/index.d.ts +2 -0
  225. package/dist/morph-select/index.js +2 -0
  226. package/dist/morph-select/morph-select.d.ts +71 -0
  227. package/dist/morph-select/morph-select.js +693 -0
  228. package/dist/morph-select/morph-select.js.map +1 -0
  229. package/dist/multi-select/index.d.ts +2 -0
  230. package/dist/multi-select/index.js +2 -0
  231. package/dist/multi-select/multi-select.d.ts +36 -0
  232. package/dist/multi-select/multi-select.js +152 -0
  233. package/dist/multi-select/multi-select.js.map +1 -0
  234. package/dist/number-field/index.d.ts +2 -0
  235. package/dist/number-field/index.js +2 -0
  236. package/dist/number-field/number-field.d.ts +42 -0
  237. package/dist/number-field/number-field.js +199 -0
  238. package/dist/number-field/number-field.js.map +1 -0
  239. package/dist/onboarding-checklist/index.d.ts +2 -0
  240. package/dist/onboarding-checklist/index.js +2 -0
  241. package/dist/onboarding-checklist/onboarding-checklist.d.ts +55 -0
  242. package/dist/onboarding-checklist/onboarding-checklist.js +577 -0
  243. package/dist/onboarding-checklist/onboarding-checklist.js.map +1 -0
  244. package/dist/page-header/index.d.ts +2 -0
  245. package/dist/page-header/index.js +2 -0
  246. package/dist/page-header/page-header.d.ts +75 -0
  247. package/dist/page-header/page-header.js +83 -0
  248. package/dist/page-header/page-header.js.map +1 -0
  249. package/dist/password-field/index.d.ts +2 -0
  250. package/dist/password-field/index.js +2 -0
  251. package/dist/password-field/password-field.d.ts +37 -0
  252. package/dist/password-field/password-field.js +101 -0
  253. package/dist/password-field/password-field.js.map +1 -0
  254. package/dist/password-strength/index.d.ts +2 -0
  255. package/dist/password-strength/index.js +2 -0
  256. package/dist/password-strength/password-strength.d.ts +127 -0
  257. package/dist/password-strength/password-strength.js +455 -0
  258. package/dist/password-strength/password-strength.js.map +1 -0
  259. package/dist/phone-input/index.d.ts +3 -0
  260. package/dist/phone-input/index.js +3 -0
  261. package/dist/phone-input/phone-countries.d.ts +29 -0
  262. package/dist/phone-input/phone-countries.js +170 -0
  263. package/dist/phone-input/phone-countries.js.map +1 -0
  264. package/dist/phone-input/phone-input.d.ts +66 -0
  265. package/dist/phone-input/phone-input.js +440 -0
  266. package/dist/phone-input/phone-input.js.map +1 -0
  267. package/dist/popover/popover.d.ts +20 -0
  268. package/dist/popover/popover.js +33 -9
  269. package/dist/popover/popover.js.map +1 -1
  270. package/dist/progress/progress.js +46 -5
  271. package/dist/progress/progress.js.map +1 -1
  272. package/dist/radio-group/radio-group.js +8 -7
  273. package/dist/radio-group/radio-group.js.map +1 -1
  274. package/dist/reactions/index.d.ts +2 -0
  275. package/dist/reactions/index.js +2 -0
  276. package/dist/reactions/reactions.d.ts +50 -0
  277. package/dist/reactions/reactions.js +315 -0
  278. package/dist/reactions/reactions.js.map +1 -0
  279. package/dist/rich-text-editor/index.d.ts +3 -0
  280. package/dist/rich-text-editor/index.js +2 -0
  281. package/dist/rich-text-editor/rich-text-editor.d.ts +70 -0
  282. package/dist/rich-text-editor/rich-text-editor.js +909 -0
  283. package/dist/rich-text-editor/rich-text-editor.js.map +1 -0
  284. package/dist/rich-text-editor/rich-text-engine.d.ts +16 -0
  285. package/dist/rich-text-editor/rich-text-engine.js +366 -0
  286. package/dist/rich-text-editor/rich-text-engine.js.map +1 -0
  287. package/dist/rich-text-editor/rich-text-html.js +174 -0
  288. package/dist/rich-text-editor/rich-text-html.js.map +1 -0
  289. package/dist/rich-text-editor/rich-text-icons.js +43 -0
  290. package/dist/rich-text-editor/rich-text-icons.js.map +1 -0
  291. package/dist/ridgeline/index.d.ts +2 -0
  292. package/dist/ridgeline/index.js +2 -0
  293. package/dist/ridgeline/ridgeline.d.ts +55 -0
  294. package/dist/ridgeline/ridgeline.js +701 -0
  295. package/dist/ridgeline/ridgeline.js.map +1 -0
  296. package/dist/scroll-area/index.d.ts +2 -2
  297. package/dist/scroll-area/index.js +2 -2
  298. package/dist/scroll-area/scroll-area.d.ts +75 -28
  299. package/dist/scroll-area/scroll-area.js +212 -41
  300. package/dist/scroll-area/scroll-area.js.map +1 -1
  301. package/dist/scrub-input/evaluate.d.ts +14 -0
  302. package/dist/scrub-input/evaluate.js +72 -0
  303. package/dist/scrub-input/evaluate.js.map +1 -0
  304. package/dist/scrub-input/index.d.ts +3 -0
  305. package/dist/scrub-input/index.js +3 -0
  306. package/dist/scrub-input/scrub-input.d.ts +74 -0
  307. package/dist/scrub-input/scrub-input.js +486 -0
  308. package/dist/scrub-input/scrub-input.js.map +1 -0
  309. package/dist/search-field/index.d.ts +2 -0
  310. package/dist/search-field/index.js +2 -0
  311. package/dist/search-field/search-field.d.ts +27 -0
  312. package/dist/search-field/search-field.js +155 -0
  313. package/dist/search-field/search-field.js.map +1 -0
  314. package/dist/select/select.d.ts +3 -1
  315. package/dist/select/select.js +95 -25
  316. package/dist/select/select.js.map +1 -1
  317. package/dist/separator/separator.d.ts +1 -1
  318. package/dist/sheet/sheet.js +4 -2
  319. package/dist/sheet/sheet.js.map +1 -1
  320. package/dist/shortcut-recorder/index.d.ts +3 -0
  321. package/dist/shortcut-recorder/index.js +3 -0
  322. package/dist/shortcut-recorder/shortcut-model.d.ts +27 -0
  323. package/dist/shortcut-recorder/shortcut-model.js +206 -0
  324. package/dist/shortcut-recorder/shortcut-model.js.map +1 -0
  325. package/dist/shortcut-recorder/shortcut-recorder.d.ts +93 -0
  326. package/dist/shortcut-recorder/shortcut-recorder.js +489 -0
  327. package/dist/shortcut-recorder/shortcut-recorder.js.map +1 -0
  328. package/dist/side-nav/index.d.ts +2 -0
  329. package/dist/side-nav/index.js +2 -0
  330. package/dist/side-nav/side-nav.d.ts +72 -0
  331. package/dist/side-nav/side-nav.js +142 -0
  332. package/dist/side-nav/side-nav.js.map +1 -0
  333. package/dist/skeleton/skeleton.d.ts +8 -2
  334. package/dist/skeleton/skeleton.js +6 -2
  335. package/dist/skeleton/skeleton.js.map +1 -1
  336. package/dist/slider/index.d.ts +2 -2
  337. package/dist/slider/slider.d.ts +46 -14
  338. package/dist/slider/slider.js +188 -50
  339. package/dist/slider/slider.js.map +1 -1
  340. package/dist/slope-chart/index.d.ts +2 -0
  341. package/dist/slope-chart/index.js +2 -0
  342. package/dist/slope-chart/slope-chart.d.ts +54 -0
  343. package/dist/slope-chart/slope-chart.js +568 -0
  344. package/dist/slope-chart/slope-chart.js.map +1 -0
  345. package/dist/sparkline/index.d.ts +2 -0
  346. package/dist/sparkline/index.js +2 -0
  347. package/dist/sparkline/sparkline.d.ts +46 -0
  348. package/dist/sparkline/sparkline.js +390 -0
  349. package/dist/sparkline/sparkline.js.map +1 -0
  350. package/dist/split-bar/index.d.ts +2 -0
  351. package/dist/split-bar/index.js +2 -0
  352. package/dist/split-bar/split-bar.d.ts +62 -0
  353. package/dist/split-bar/split-bar.js +513 -0
  354. package/dist/split-bar/split-bar.js.map +1 -0
  355. package/dist/split-button/index.d.ts +2 -0
  356. package/dist/split-button/index.js +2 -0
  357. package/dist/split-button/split-button.d.ts +46 -0
  358. package/dist/split-button/split-button.js +123 -0
  359. package/dist/split-button/split-button.js.map +1 -0
  360. package/dist/stat-card/index.d.ts +2 -0
  361. package/dist/stat-card/index.js +2 -0
  362. package/dist/stat-card/stat-card.d.ts +101 -0
  363. package/dist/stat-card/stat-card.js +249 -0
  364. package/dist/stat-card/stat-card.js.map +1 -0
  365. package/dist/stepper/index.d.ts +2 -0
  366. package/dist/stepper/index.js +2 -0
  367. package/dist/stepper/stepper.d.ts +51 -0
  368. package/dist/stepper/stepper.js +215 -0
  369. package/dist/stepper/stepper.js.map +1 -0
  370. package/dist/streamgraph/index.d.ts +2 -0
  371. package/dist/streamgraph/index.js +2 -0
  372. package/dist/streamgraph/streamgraph.d.ts +70 -0
  373. package/dist/streamgraph/streamgraph.js +613 -0
  374. package/dist/streamgraph/streamgraph.js.map +1 -0
  375. package/dist/styles.css +1 -1
  376. package/dist/swipe-actions/index.d.ts +2 -0
  377. package/dist/swipe-actions/index.js +2 -0
  378. package/dist/swipe-actions/swipe-actions.d.ts +67 -0
  379. package/dist/swipe-actions/swipe-actions.js +552 -0
  380. package/dist/swipe-actions/swipe-actions.js.map +1 -0
  381. package/dist/switch/switch.js +10 -9
  382. package/dist/switch/switch.js.map +1 -1
  383. package/dist/table/index.d.ts +2 -2
  384. package/dist/table/index.js +2 -2
  385. package/dist/table/table.d.ts +87 -4
  386. package/dist/table/table.js +441 -165
  387. package/dist/table/table.js.map +1 -1
  388. package/dist/tabs/tabs.js +143 -7
  389. package/dist/tabs/tabs.js.map +1 -1
  390. package/dist/tag-input/index.d.ts +2 -0
  391. package/dist/tag-input/index.js +2 -0
  392. package/dist/tag-input/tag-input.d.ts +40 -0
  393. package/dist/tag-input/tag-input.js +269 -0
  394. package/dist/tag-input/tag-input.js.map +1 -0
  395. package/dist/text-morph/index.d.ts +2 -0
  396. package/dist/text-morph/index.js +2 -0
  397. package/dist/text-morph/text-morph.d.ts +23 -0
  398. package/dist/text-morph/text-morph.js +21 -0
  399. package/dist/text-morph/text-morph.js.map +1 -0
  400. package/dist/text-reveal/index.d.ts +2 -0
  401. package/dist/text-reveal/index.js +2 -0
  402. package/dist/text-reveal/text-reveal.d.ts +27 -0
  403. package/dist/text-reveal/text-reveal.js +51 -0
  404. package/dist/text-reveal/text-reveal.js.map +1 -0
  405. package/dist/text-shimmer/index.d.ts +2 -0
  406. package/dist/text-shimmer/index.js +2 -0
  407. package/dist/text-shimmer/text-shimmer.d.ts +29 -0
  408. package/dist/text-shimmer/text-shimmer.js +29 -0
  409. package/dist/text-shimmer/text-shimmer.js.map +1 -0
  410. package/dist/textarea/textarea.d.ts +23 -0
  411. package/dist/textarea/textarea.js +24 -2
  412. package/dist/textarea/textarea.js.map +1 -1
  413. package/dist/theme-switch/index.d.ts +2 -0
  414. package/dist/theme-switch/index.js +2 -0
  415. package/dist/theme-switch/theme-switch.d.ts +37 -0
  416. package/dist/theme-switch/theme-switch.js +123 -0
  417. package/dist/theme-switch/theme-switch.js.map +1 -0
  418. package/dist/time-picker/index.d.ts +2 -0
  419. package/dist/time-picker/index.js +2 -0
  420. package/dist/time-picker/time-picker.d.ts +54 -0
  421. package/dist/time-picker/time-picker.js +201 -0
  422. package/dist/time-picker/time-picker.js.map +1 -0
  423. package/dist/toast/index.d.ts +2 -2
  424. package/dist/toast/index.js +2 -2
  425. package/dist/toast/toast.d.ts +6 -0
  426. package/dist/toast/toast.js +98 -36
  427. package/dist/toast/toast.js.map +1 -1
  428. package/dist/tooltip/tooltip.d.ts +10 -3
  429. package/dist/tooltip/tooltip.js +67 -18
  430. package/dist/tooltip/tooltip.js.map +1 -1
  431. package/dist/treemap/index.d.ts +2 -0
  432. package/dist/treemap/index.js +2 -0
  433. package/dist/treemap/treemap.d.ts +53 -0
  434. package/dist/treemap/treemap.js +699 -0
  435. package/dist/treemap/treemap.js.map +1 -0
  436. package/dist/usage-meter/index.d.ts +2 -0
  437. package/dist/usage-meter/index.js +2 -0
  438. package/dist/usage-meter/usage-meter.d.ts +50 -0
  439. package/dist/usage-meter/usage-meter.js +440 -0
  440. package/dist/usage-meter/usage-meter.js.map +1 -0
  441. package/dist/waffle-chart/index.d.ts +2 -0
  442. package/dist/waffle-chart/index.js +2 -0
  443. package/dist/waffle-chart/waffle-chart.d.ts +57 -0
  444. package/dist/waffle-chart/waffle-chart.js +426 -0
  445. package/dist/waffle-chart/waffle-chart.js.map +1 -0
  446. package/package.json +245 -3
@@ -0,0 +1,88 @@
1
+ import { prefersReducedMotion, useIsomorphicLayoutEffect } from "./motion.js";
2
+ import { useRef } from "react";
3
+ //#region src/lib/flip.ts
4
+ /**
5
+ * Siblings that shift because something next to them changed size (a wider month
6
+ * name pushes the day and year along) slide to their new place instead of
7
+ * jumping: measure where each `[data-flip]` element was, let the new layout
8
+ * commit, put each one back where it was with a translate, then release it on a
9
+ * transition. CSS cannot do this alone, because the layout move is not a style
10
+ * change.
11
+ *
12
+ * `key` says when to look. Positions are remembered after every look and
13
+ * refreshed when the container resizes, so a move that is not a change of `key`
14
+ * (a window resize) does not become the next flip. Reduced motion does not flip.
15
+ * Elements inside a leaving copy (`[data-leaving]`) are ignored.
16
+ *
17
+ * @param duration CSS time of the slide, e.g. `var(--duration-standard)`.
18
+ * @param easing CSS easing of the slide, e.g. `var(--ease-enter)`.
19
+ * @param relative Measure positions from the container's own corner instead of the
20
+ * viewport's, so scrolling the page between two looks is not mistaken for the elements
21
+ * moving. For lists whose container stays put (rows of a table); content that also moves
22
+ * because its container does (centred text) wants the default.
23
+ */
24
+ function useFlip(container, key, duration = "var(--duration-standard)", easing = "var(--ease-enter)", relative = false) {
25
+ const remembered = useRef(/* @__PURE__ */ new Map());
26
+ const measure = () => {
27
+ const root = container.current;
28
+ const next = /* @__PURE__ */ new Map();
29
+ if (!root) return next;
30
+ const origin = relative ? root.getBoundingClientRect() : {
31
+ left: 0,
32
+ top: 0
33
+ };
34
+ for (const el of root.querySelectorAll("[data-flip]")) {
35
+ if (el.closest("[data-leaving]")) continue;
36
+ const box = el.getBoundingClientRect();
37
+ next.set(el, {
38
+ left: box.left - origin.left,
39
+ top: box.top - origin.top
40
+ });
41
+ }
42
+ return next;
43
+ };
44
+ useIsomorphicLayoutEffect(() => {
45
+ const root = container.current;
46
+ if (!root) return;
47
+ const before = remembered.current;
48
+ const after = measure();
49
+ if (before.size > 0 && !prefersReducedMotion()) {
50
+ const moved = [];
51
+ for (const [el, now] of after) {
52
+ const was = before.get(el);
53
+ if (!was) continue;
54
+ const dx = was.left - now.left;
55
+ const dy = was.top - now.top;
56
+ if (Math.abs(dx) < .5 && Math.abs(dy) < .5) continue;
57
+ const node = el;
58
+ node.style.transition = "none";
59
+ node.style.translate = `${dx}px ${dy}px`;
60
+ moved.push(node);
61
+ }
62
+ if (moved.length > 0) {
63
+ root.offsetWidth;
64
+ for (const node of moved) {
65
+ node.style.transition = `translate ${duration} ${easing}`;
66
+ node.style.translate = "";
67
+ node.addEventListener("transitionend", () => {
68
+ node.style.transition = "";
69
+ }, { once: true });
70
+ }
71
+ }
72
+ }
73
+ remembered.current = after;
74
+ }, [key]);
75
+ useIsomorphicLayoutEffect(() => {
76
+ const root = container.current;
77
+ if (!root || typeof ResizeObserver === "undefined") return;
78
+ const observer = new ResizeObserver(() => {
79
+ remembered.current = measure();
80
+ });
81
+ observer.observe(root);
82
+ return () => observer.disconnect();
83
+ }, []);
84
+ }
85
+ //#endregion
86
+ export { useFlip };
87
+
88
+ //# sourceMappingURL=flip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flip.js","names":[],"sources":["../../src/lib/flip.ts"],"sourcesContent":["import { useRef, type RefObject } from \"react\";\n\nimport { prefersReducedMotion, useIsomorphicLayoutEffect } from \"./motion.js\";\n\n/**\n * Siblings that shift because something next to them changed size (a wider month\n * name pushes the day and year along) slide to their new place instead of\n * jumping: measure where each `[data-flip]` element was, let the new layout\n * commit, put each one back where it was with a translate, then release it on a\n * transition. CSS cannot do this alone, because the layout move is not a style\n * change.\n *\n * `key` says when to look. Positions are remembered after every look and\n * refreshed when the container resizes, so a move that is not a change of `key`\n * (a window resize) does not become the next flip. Reduced motion does not flip.\n * Elements inside a leaving copy (`[data-leaving]`) are ignored.\n *\n * @param duration CSS time of the slide, e.g. `var(--duration-standard)`.\n * @param easing CSS easing of the slide, e.g. `var(--ease-enter)`.\n * @param relative Measure positions from the container's own corner instead of the\n * viewport's, so scrolling the page between two looks is not mistaken for the elements\n * moving. For lists whose container stays put (rows of a table); content that also moves\n * because its container does (centred text) wants the default.\n */\nexport function useFlip(\n container: RefObject<HTMLElement | null>,\n key: unknown,\n duration = \"var(--duration-standard)\",\n easing = \"var(--ease-enter)\",\n relative = false,\n) {\n const remembered = useRef(new Map<Element, { left: number; top: number }>());\n\n const measure = () => {\n const root = container.current;\n const next = new Map<Element, { left: number; top: number }>();\n if (!root) return next;\n const origin = relative ? root.getBoundingClientRect() : { left: 0, top: 0 };\n for (const el of root.querySelectorAll<HTMLElement>(\"[data-flip]\")) {\n if (el.closest(\"[data-leaving]\")) continue;\n const box = el.getBoundingClientRect();\n next.set(el, { left: box.left - origin.left, top: box.top - origin.top });\n }\n return next;\n };\n\n useIsomorphicLayoutEffect(() => {\n const root = container.current;\n if (!root) return;\n const before = remembered.current;\n const after = measure();\n if (before.size > 0 && !prefersReducedMotion()) {\n const moved: HTMLElement[] = [];\n for (const [el, now] of after) {\n const was = before.get(el);\n if (!was) continue;\n const dx = was.left - now.left;\n const dy = was.top - now.top;\n if (Math.abs(dx) < 0.5 && Math.abs(dy) < 0.5) continue;\n const node = el as HTMLElement;\n node.style.transition = \"none\";\n node.style.translate = `${dx}px ${dy}px`;\n moved.push(node);\n }\n if (moved.length > 0) {\n void root.offsetWidth;\n for (const node of moved) {\n node.style.transition = `translate ${duration} ${easing}`;\n node.style.translate = \"\";\n node.addEventListener(\n \"transitionend\",\n () => {\n node.style.transition = \"\";\n },\n { once: true },\n );\n }\n }\n }\n remembered.current = after;\n // Positions are read once per change of `key`; a resize refreshes them below.\n }, [key]);\n\n useIsomorphicLayoutEffect(() => {\n const root = container.current;\n if (!root || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(() => {\n remembered.current = measure();\n });\n observer.observe(root);\n return () => observer.disconnect();\n }, []);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,QACd,WACA,KACA,WAAW,4BACX,SAAS,qBACT,WAAW,OACX;CACA,MAAM,aAAa,uBAAO,IAAI,IAA4C,CAAC;CAE3E,MAAM,gBAAgB;EACpB,MAAM,OAAO,UAAU;EACvB,MAAM,uBAAO,IAAI,IAA4C;EAC7D,IAAI,CAAC,MAAM,OAAO;EAClB,MAAM,SAAS,WAAW,KAAK,sBAAsB,IAAI;GAAE,MAAM;GAAG,KAAK;EAAE;EAC3E,KAAK,MAAM,MAAM,KAAK,iBAA8B,aAAa,GAAG;GAClE,IAAI,GAAG,QAAQ,gBAAgB,GAAG;GAClC,MAAM,MAAM,GAAG,sBAAsB;GACrC,KAAK,IAAI,IAAI;IAAE,MAAM,IAAI,OAAO,OAAO;IAAM,KAAK,IAAI,MAAM,OAAO;GAAI,CAAC;EAC1E;EACA,OAAO;CACT;CAEA,gCAAgC;EAC9B,MAAM,OAAO,UAAU;EACvB,IAAI,CAAC,MAAM;EACX,MAAM,SAAS,WAAW;EAC1B,MAAM,QAAQ,QAAQ;EACtB,IAAI,OAAO,OAAO,KAAK,CAAC,qBAAqB,GAAG;GAC9C,MAAM,QAAuB,CAAC;GAC9B,KAAK,MAAM,CAAC,IAAI,QAAQ,OAAO;IAC7B,MAAM,MAAM,OAAO,IAAI,EAAE;IACzB,IAAI,CAAC,KAAK;IACV,MAAM,KAAK,IAAI,OAAO,IAAI;IAC1B,MAAM,KAAK,IAAI,MAAM,IAAI;IACzB,IAAI,KAAK,IAAI,EAAE,IAAI,MAAO,KAAK,IAAI,EAAE,IAAI,IAAK;IAC9C,MAAM,OAAO;IACb,KAAK,MAAM,aAAa;IACxB,KAAK,MAAM,YAAY,GAAG,GAAG,KAAK,GAAG;IACrC,MAAM,KAAK,IAAI;GACjB;GACA,IAAI,MAAM,SAAS,GAAG;IACpB,KAAU;IACV,KAAK,MAAM,QAAQ,OAAO;KACxB,KAAK,MAAM,aAAa,aAAa,SAAS,GAAG;KACjD,KAAK,MAAM,YAAY;KACvB,KAAK,iBACH,uBACM;MACJ,KAAK,MAAM,aAAa;KAC1B,GACA,EAAE,MAAM,KAAK,CACf;IACF;GACF;EACF;EACA,WAAW,UAAU;CAEvB,GAAG,CAAC,GAAG,CAAC;CAER,gCAAgC;EAC9B,MAAM,OAAO,UAAU;EACvB,IAAI,CAAC,QAAQ,OAAO,mBAAmB,aAAa;EACpD,MAAM,WAAW,IAAI,qBAAqB;GACxC,WAAW,UAAU,QAAQ;EAC/B,CAAC;EACD,SAAS,QAAQ,IAAI;EACrB,aAAa,SAAS,WAAW;CACnC,GAAG,CAAC,CAAC;AACP"}
@@ -0,0 +1,59 @@
1
+ import { prefersReducedMotion, useIsomorphicLayoutEffect } from "./motion.js";
2
+ import { useEffect, useRef } from "react";
3
+ //#region src/lib/height-follow.ts
4
+ /**
5
+ * Keeps `frame` as tall as `content`, and lets it spring when the content changes
6
+ * on purpose. Height stays `auto` at rest, so a passive reflow (a resize, a font
7
+ * swap) follows at once. When `key` changes and the content's height changes within
8
+ * 120ms of it, the old height is pinned before that frame paints, then released to
9
+ * the new one on a transition (the frame's own `data-following` turns it on) and
10
+ * returned to `auto` when it ends. Reduced motion never animates.
11
+ *
12
+ * `frame` clips only while it moves (`overflow: clip` under `data-following`), so a
13
+ * focus ring is never cut at rest.
14
+ */
15
+ function useHeightFollow(frame, content, key) {
16
+ const changedAt = useRef(0);
17
+ useIsomorphicLayoutEffect(() => {
18
+ changedAt.current = performance.now();
19
+ }, [key]);
20
+ useEffect(() => {
21
+ const box = frame.current;
22
+ const node = content.current;
23
+ if (!box || !node || typeof ResizeObserver === "undefined") return;
24
+ let last;
25
+ const settle = () => {
26
+ delete box.dataset.following;
27
+ box.style.height = "";
28
+ };
29
+ const onEnd = (event) => {
30
+ if (event.target === box && event.propertyName === "height") settle();
31
+ };
32
+ box.addEventListener("transitionend", onEnd);
33
+ const observer = new ResizeObserver(([entry]) => {
34
+ if (!entry) return;
35
+ const next = entry.borderBoxSize?.[0]?.blockSize ?? node.offsetHeight;
36
+ const from = last;
37
+ last = next;
38
+ if (prefersReducedMotion() || from === void 0 || from === next || performance.now() - changedAt.current > 120) {
39
+ settle();
40
+ return;
41
+ }
42
+ box.style.transition = "none";
43
+ box.style.height = `${from}px`;
44
+ box.offsetHeight;
45
+ box.style.transition = "";
46
+ box.dataset.following = "";
47
+ box.style.height = `${next}px`;
48
+ });
49
+ observer.observe(node);
50
+ return () => {
51
+ observer.disconnect();
52
+ box.removeEventListener("transitionend", onEnd);
53
+ };
54
+ }, [frame, content]);
55
+ }
56
+ //#endregion
57
+ export { useHeightFollow };
58
+
59
+ //# sourceMappingURL=height-follow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"height-follow.js","names":[],"sources":["../../src/lib/height-follow.ts"],"sourcesContent":["import { useEffect, useRef, type RefObject } from \"react\";\n\nimport { prefersReducedMotion, useIsomorphicLayoutEffect } from \"./motion.js\";\n\n/**\n * Keeps `frame` as tall as `content`, and lets it spring when the content changes\n * on purpose. Height stays `auto` at rest, so a passive reflow (a resize, a font\n * swap) follows at once. When `key` changes and the content's height changes within\n * 120ms of it, the old height is pinned before that frame paints, then released to\n * the new one on a transition (the frame's own `data-following` turns it on) and\n * returned to `auto` when it ends. Reduced motion never animates.\n *\n * `frame` clips only while it moves (`overflow: clip` under `data-following`), so a\n * focus ring is never cut at rest.\n */\nexport function useHeightFollow(\n frame: RefObject<HTMLElement | null>,\n content: RefObject<HTMLElement | null>,\n key: string,\n) {\n const changedAt = useRef(0);\n useIsomorphicLayoutEffect(() => {\n changedAt.current = performance.now();\n }, [key]);\n useEffect(() => {\n const box = frame.current;\n const node = content.current;\n if (!box || !node || typeof ResizeObserver === \"undefined\") return;\n let last: number | undefined;\n const settle = () => {\n delete box.dataset.following;\n box.style.height = \"\";\n };\n const onEnd = (event: TransitionEvent) => {\n if (event.target === box && event.propertyName === \"height\") settle();\n };\n box.addEventListener(\"transitionend\", onEnd);\n const observer = new ResizeObserver(([entry]) => {\n if (!entry) return;\n const next = entry.borderBoxSize?.[0]?.blockSize ?? node.offsetHeight;\n const from = last;\n last = next;\n if (\n prefersReducedMotion() ||\n from === undefined ||\n from === next ||\n performance.now() - changedAt.current > 120\n ) {\n settle();\n return;\n }\n // Pin the old height before this frame paints, then release to the new one.\n box.style.transition = \"none\";\n box.style.height = `${from}px`;\n void box.offsetHeight;\n box.style.transition = \"\";\n box.dataset.following = \"\";\n box.style.height = `${next}px`;\n });\n observer.observe(node);\n return () => {\n observer.disconnect();\n box.removeEventListener(\"transitionend\", onEnd);\n };\n }, [frame, content]);\n}\n"],"mappings":";;;;;;;;;;;;;;AAeA,SAAgB,gBACd,OACA,SACA,KACA;CACA,MAAM,YAAY,OAAO,CAAC;CAC1B,gCAAgC;EAC9B,UAAU,UAAU,YAAY,IAAI;CACtC,GAAG,CAAC,GAAG,CAAC;CACR,gBAAgB;EACd,MAAM,MAAM,MAAM;EAClB,MAAM,OAAO,QAAQ;EACrB,IAAI,CAAC,OAAO,CAAC,QAAQ,OAAO,mBAAmB,aAAa;EAC5D,IAAI;EACJ,MAAM,eAAe;GACnB,OAAO,IAAI,QAAQ;GACnB,IAAI,MAAM,SAAS;EACrB;EACA,MAAM,SAAS,UAA2B;GACxC,IAAI,MAAM,WAAW,OAAO,MAAM,iBAAiB,UAAU,OAAO;EACtE;EACA,IAAI,iBAAiB,iBAAiB,KAAK;EAC3C,MAAM,WAAW,IAAI,gBAAgB,CAAC,WAAW;GAC/C,IAAI,CAAC,OAAO;GACZ,MAAM,OAAO,MAAM,gBAAgB,EAAE,EAAE,aAAa,KAAK;GACzD,MAAM,OAAO;GACb,OAAO;GACP,IACE,qBAAqB,KACrB,SAAS,KAAA,KACT,SAAS,QACT,YAAY,IAAI,IAAI,UAAU,UAAU,KACxC;IACA,OAAO;IACP;GACF;GAEA,IAAI,MAAM,aAAa;GACvB,IAAI,MAAM,SAAS,GAAG,KAAK;GAC3B,IAAS;GACT,IAAI,MAAM,aAAa;GACvB,IAAI,QAAQ,YAAY;GACxB,IAAI,MAAM,SAAS,GAAG,KAAK;EAC7B,CAAC;EACD,SAAS,QAAQ,IAAI;EACrB,aAAa;GACX,SAAS,WAAW;GACpB,IAAI,oBAAoB,iBAAiB,KAAK;EAChD;CACF,GAAG,CAAC,OAAO,OAAO,CAAC;AACrB"}
@@ -0,0 +1,47 @@
1
+ import { cn } from "./cn.js";
2
+ import { useExitBackstop, useLeavingItems } from "./motion.js";
3
+ import { useCallback, useRef } from "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/lib/icon-swap.tsx
6
+ /**
7
+ * Swaps one icon for another: the old one scales away with a little blur while
8
+ * the new one scales in on the snappy spring. `iconKey` says when it is a
9
+ * different icon (a re-render of the same one stays still), and nothing animates
10
+ * on first mount. The icons share one grid cell, so a leaving one needs no
11
+ * position: it stays under the new one.
12
+ */
13
+ function IconSwap({ iconKey, children, className, calm = false, tight = false, fill = false }) {
14
+ const phaseRef = useRef(null);
15
+ const { leaving, changed, remove } = useLeavingItems(iconKey, children, () => phaseRef.current);
16
+ return /* @__PURE__ */ jsxs("span", {
17
+ className: cn("inline-grid shrink-0 place-items-center", className),
18
+ children: [leaving.map((item) => /* @__PURE__ */ jsx(LeavingIcon, {
19
+ id: item.id,
20
+ onDone: remove,
21
+ calm,
22
+ tight,
23
+ fill,
24
+ children: item.value
25
+ }, item.id)), /* @__PURE__ */ jsx("span", {
26
+ ref: phaseRef,
27
+ className: cn("col-start-1 row-start-1 inline-flex items-center justify-center" + (fill ? " size-full" : ""), changed && (calm ? "motion-safe:animate-glyph-in-calm motion-reduce:animate-fade-in" : tight ? "motion-safe:animate-icon-in-tight motion-reduce:animate-fade-in" : "motion-safe:animate-glyph-in motion-reduce:animate-fade-in")),
28
+ children
29
+ }, iconKey)]
30
+ });
31
+ }
32
+ function LeavingIcon({ id, onDone, calm, tight, fill, children }) {
33
+ const remove = useCallback(() => onDone(id), [onDone, id]);
34
+ useExitBackstop(true, remove);
35
+ return /* @__PURE__ */ jsx("span", {
36
+ "aria-hidden": "true",
37
+ onAnimationEnd: (event) => {
38
+ if (event.target === event.currentTarget) remove();
39
+ },
40
+ className: cn("col-start-1 row-start-1 inline-flex items-center justify-center" + (fill ? " size-full" : ""), calm ? "motion-safe:animate-glyph-out-calm motion-reduce:animate-fade-out" : tight ? "motion-safe:animate-icon-out-tight motion-reduce:animate-fade-out" : "motion-safe:animate-glyph-out motion-reduce:animate-fade-out"),
41
+ children
42
+ });
43
+ }
44
+ //#endregion
45
+ export { IconSwap };
46
+
47
+ //# sourceMappingURL=icon-swap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icon-swap.js","names":[],"sources":["../../src/lib/icon-swap.tsx"],"sourcesContent":["import { useCallback, useRef, type ReactNode } from \"react\";\n\nimport { cn } from \"./cn.js\";\nimport { useExitBackstop, useLeavingItems } from \"./motion.js\";\n\n/**\n * Swaps one icon for another: the old one scales away with a little blur while\n * the new one scales in on the snappy spring. `iconKey` says when it is a\n * different icon (a re-render of the same one stays still), and nothing animates\n * on first mount. The icons share one grid cell, so a leaving one needs no\n * position: it stays under the new one.\n */\nexport function IconSwap({\n iconKey,\n children,\n className,\n calm = false,\n tight = false,\n fill = false,\n}: {\n iconKey: string;\n children: ReactNode;\n className?: string;\n /** Slower, softer motion, for feedback that should never read as busy. */\n calm?: boolean;\n /** Scales from 0.55 rather than 0.6, for a small icon in a small box. */\n tight?: boolean;\n /** The icons fill the box they sit in, so an svg at width 100% has something to be 100% of. */\n fill?: boolean;\n}) {\n const phaseRef = useRef<HTMLSpanElement>(null);\n const { leaving, changed, remove } = useLeavingItems(iconKey, children, () => phaseRef.current);\n return (\n <span className={cn(\"inline-grid shrink-0 place-items-center\", className)}>\n {leaving.map((item) => (\n <LeavingIcon\n key={item.id}\n id={item.id}\n onDone={remove}\n calm={calm}\n tight={tight}\n fill={fill}\n >\n {item.value}\n </LeavingIcon>\n ))}\n <span\n key={iconKey}\n ref={phaseRef}\n className={cn(\n \"col-start-1 row-start-1 inline-flex items-center justify-center\" +\n (fill ? \" size-full\" : \"\"),\n changed &&\n (calm\n ? \"motion-safe:animate-glyph-in-calm motion-reduce:animate-fade-in\"\n : tight\n ? \"motion-safe:animate-icon-in-tight motion-reduce:animate-fade-in\"\n : \"motion-safe:animate-glyph-in motion-reduce:animate-fade-in\"),\n )}\n >\n {children}\n </span>\n </span>\n );\n}\n\nfunction LeavingIcon({\n id,\n onDone,\n calm,\n tight,\n fill,\n children,\n}: {\n id: number;\n onDone: (id: number) => void;\n calm: boolean;\n tight: boolean;\n fill: boolean;\n children: ReactNode;\n}) {\n const remove = useCallback(() => onDone(id), [onDone, id]);\n useExitBackstop(true, remove);\n return (\n <span\n aria-hidden=\"true\"\n onAnimationEnd={(event) => {\n if (event.target === event.currentTarget) remove();\n }}\n className={cn(\n \"col-start-1 row-start-1 inline-flex items-center justify-center\" +\n (fill ? \" size-full\" : \"\"),\n calm\n ? \"motion-safe:animate-glyph-out-calm motion-reduce:animate-fade-out\"\n : tight\n ? \"motion-safe:animate-icon-out-tight motion-reduce:animate-fade-out\"\n : \"motion-safe:animate-glyph-out motion-reduce:animate-fade-out\",\n )}\n >\n {children}\n </span>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAYA,SAAgB,SAAS,EACvB,SACA,UACA,WACA,OAAO,OACP,QAAQ,OACR,OAAO,SAWN;CACD,MAAM,WAAW,OAAwB,IAAI;CAC7C,MAAM,EAAE,SAAS,SAAS,WAAW,gBAAgB,SAAS,gBAAgB,SAAS,OAAO;CAC9F,OACE,qBAAC,QAAD;EAAM,WAAW,GAAG,2CAA2C,SAAS;EAAxE,UAAA,CACG,QAAQ,KAAK,SACZ,oBAAC,aAAD;GAEE,IAAI,KAAK;GACT,QAAQ;GACF;GACC;GACD;GAEL,UAAA,KAAK;EACK,GARN,KAAK,EAQC,CACd,GACD,oBAAC,QAAD;GAEE,KAAK;GACL,WAAW,GACT,qEACG,OAAO,eAAe,KACzB,YACG,OACG,oEACA,QACE,oEACA,6DACV;GAEC;EACG,GAdC,OAcD,CACF;;AAEV;AAEA,SAAS,YAAY,EACnB,IACA,QACA,MACA,OACA,MACA,YAQC;CACD,MAAM,SAAS,kBAAkB,OAAO,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC;CACzD,gBAAgB,MAAM,MAAM;CAC5B,OACE,oBAAC,QAAD;EACE,eAAY;EACZ,iBAAiB,UAAU;GACzB,IAAI,MAAM,WAAW,MAAM,eAAe,OAAO;EACnD;EACA,WAAW,GACT,qEACG,OAAO,eAAe,KACzB,OACI,sEACA,QACE,sEACA,8DACR;EAEC;CACG,CAAA;AAEV"}
@@ -0,0 +1,57 @@
1
+ import { useCallback, useState } from "react";
2
+ //#region src/lib/leaving-list.ts
3
+ /**
4
+ * A list whose removed items stay on screen while they leave. Pass the current
5
+ * items (each with a stable `id`); the result is what to render, in order: every
6
+ * current item, with each removed one kept where it stood, flagged `leaving`.
7
+ * The caller plays the exit and calls `done(id)` when it ends. An item that comes
8
+ * back stops leaving. Nothing is kept on first render.
9
+ */
10
+ function useLeavingList(items) {
11
+ const key = items.map((item) => item.id).join("\0");
12
+ const [seen, setSeen] = useState({
13
+ key,
14
+ items
15
+ });
16
+ const [gone, setGone] = useState([]);
17
+ if (seen.key !== key) {
18
+ const now = new Set(items.map((item) => item.id));
19
+ const added = seen.items.map((item, index) => now.has(item.id) ? null : {
20
+ item,
21
+ after: index > 0 ? seen.items[index - 1].id : null
22
+ }).filter((entry) => entry !== null);
23
+ setSeen({
24
+ key,
25
+ items
26
+ });
27
+ setGone((list) => [...list.filter((entry) => !now.has(entry.item.id) && !added.some((a) => a.item.id === entry.item.id)), ...added]);
28
+ }
29
+ const done = useCallback((id) => setGone((list) => list.filter((entry) => entry.item.id !== id)), []);
30
+ const out = [];
31
+ const place = (after) => {
32
+ for (const entry of gone) if (entry.after === after) out.push({
33
+ item: entry.item,
34
+ leaving: true
35
+ });
36
+ };
37
+ place(null);
38
+ for (const item of items) {
39
+ out.push({
40
+ item,
41
+ leaving: false
42
+ });
43
+ place(item.id);
44
+ }
45
+ for (const entry of gone) if (!out.some((o) => o.item.id === entry.item.id)) out.push({
46
+ item: entry.item,
47
+ leaving: true
48
+ });
49
+ return {
50
+ list: out,
51
+ done
52
+ };
53
+ }
54
+ //#endregion
55
+ export { useLeavingList };
56
+
57
+ //# sourceMappingURL=leaving-list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"leaving-list.js","names":[],"sources":["../../src/lib/leaving-list.ts"],"sourcesContent":["import { useCallback, useState } from \"react\";\n\ninterface Gone<T> {\n item: T;\n /** The id of the item that stood before it, so it leaves where it was. `null`: it was first. */\n after: string | null;\n}\n\n/**\n * A list whose removed items stay on screen while they leave. Pass the current\n * items (each with a stable `id`); the result is what to render, in order: every\n * current item, with each removed one kept where it stood, flagged `leaving`.\n * The caller plays the exit and calls `done(id)` when it ends. An item that comes\n * back stops leaving. Nothing is kept on first render.\n */\nexport function useLeavingList<T extends { id: string }>(items: readonly T[]) {\n const key = items.map((item) => item.id).join(\"\\u0000\");\n const [seen, setSeen] = useState({ key, items });\n const [gone, setGone] = useState<Gone<T>[]>([]);\n if (seen.key !== key) {\n const now = new Set(items.map((item) => item.id));\n const added = seen.items\n .map((item, index): Gone<T> | null =>\n now.has(item.id) ? null : { item, after: index > 0 ? seen.items[index - 1]!.id : null },\n )\n .filter((entry): entry is Gone<T> => entry !== null);\n setSeen({ key, items });\n setGone((list) => [\n ...list.filter((entry) => !now.has(entry.item.id) && !added.some((a) => a.item.id === entry.item.id)),\n ...added,\n ]);\n }\n const done = useCallback(\n (id: string) => setGone((list) => list.filter((entry) => entry.item.id !== id)),\n [],\n );\n\n const out: { item: T; leaving: boolean }[] = [];\n const place = (after: string | null) => {\n for (const entry of gone) if (entry.after === after) out.push({ item: entry.item, leaving: true });\n };\n place(null);\n for (const item of items) {\n out.push({ item, leaving: false });\n place(item.id);\n }\n // A leaver whose neighbour is gone too has nowhere to stand: it goes at the end.\n for (const entry of gone) {\n if (!out.some((o) => o.item.id === entry.item.id)) out.push({ item: entry.item, leaving: true });\n }\n return { list: out, done };\n}\n"],"mappings":";;;;;;;;;AAeA,SAAgB,eAAyC,OAAqB;CAC5E,MAAM,MAAM,MAAM,KAAK,SAAS,KAAK,EAAE,CAAC,CAAC,KAAK,IAAQ;CACtD,MAAM,CAAC,MAAM,WAAW,SAAS;EAAE;EAAK;CAAM,CAAC;CAC/C,MAAM,CAAC,MAAM,WAAW,SAAoB,CAAC,CAAC;CAC9C,IAAI,KAAK,QAAQ,KAAK;EACpB,MAAM,MAAM,IAAI,IAAI,MAAM,KAAK,SAAS,KAAK,EAAE,CAAC;EAChD,MAAM,QAAQ,KAAK,MAChB,KAAK,MAAM,UACV,IAAI,IAAI,KAAK,EAAE,IAAI,OAAO;GAAE;GAAM,OAAO,QAAQ,IAAI,KAAK,MAAM,QAAQ,EAAE,CAAE,KAAK;EAAK,CACxF,CAAC,CACA,QAAQ,UAA4B,UAAU,IAAI;EACrD,QAAQ;GAAE;GAAK;EAAM,CAAC;EACtB,SAAS,SAAS,CAChB,GAAG,KAAK,QAAQ,UAAU,CAAC,IAAI,IAAI,MAAM,KAAK,EAAE,KAAK,CAAC,MAAM,MAAM,MAAM,EAAE,KAAK,OAAO,MAAM,KAAK,EAAE,CAAC,GACpG,GAAG,KACL,CAAC;CACH;CACA,MAAM,OAAO,aACV,OAAe,SAAS,SAAS,KAAK,QAAQ,UAAU,MAAM,KAAK,OAAO,EAAE,CAAC,GAC9E,CAAC,CACH;CAEA,MAAM,MAAuC,CAAC;CAC9C,MAAM,SAAS,UAAyB;EACtC,KAAK,MAAM,SAAS,MAAM,IAAI,MAAM,UAAU,OAAO,IAAI,KAAK;GAAE,MAAM,MAAM;GAAM,SAAS;EAAK,CAAC;CACnG;CACA,MAAM,IAAI;CACV,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,KAAK;GAAE;GAAM,SAAS;EAAM,CAAC;EACjC,MAAM,KAAK,EAAE;CACf;CAEA,KAAK,MAAM,SAAS,MAClB,IAAI,CAAC,IAAI,MAAM,MAAM,EAAE,KAAK,OAAO,MAAM,KAAK,EAAE,GAAG,IAAI,KAAK;EAAE,MAAM,MAAM;EAAM,SAAS;CAAK,CAAC;CAEjG,OAAO;EAAE,MAAM;EAAK;CAAK;AAC3B"}
@@ -0,0 +1,64 @@
1
+ import { useCallback, useRef } from "react";
2
+ //#region src/lib/menu-highlight.ts
3
+ const ROWS = "[role=\"menuitem\"], [role=\"menuitemradio\"], [role=\"menuitemcheckbox\"]";
4
+ /**
5
+ * One highlight for a whole menu, drawn by the menu's ::before (the
6
+ * `menu-highlight` utility) and moved from here: it appears in place on the
7
+ * first row, glides between rows after that (pointer and arrow keys alike,
8
+ * because React Aria focuses the row either way), and fades out shortly after
9
+ * focus leaves the rows. A row marked `data-destructive` tints it red. Returns
10
+ * the cleanup.
11
+ */
12
+ function bindMenuHighlight(menu) {
13
+ let shown = false;
14
+ let timer = 0;
15
+ const hide = () => {
16
+ menu.removeAttribute("data-hl");
17
+ menu.removeAttribute("data-glide");
18
+ shown = false;
19
+ };
20
+ const onFocusIn = (event) => {
21
+ window.clearTimeout(timer);
22
+ const row = event.target.closest(ROWS);
23
+ if (!row || !menu.contains(row) || row.hasAttribute("data-disabled")) {
24
+ timer = window.setTimeout(hide, 70);
25
+ return;
26
+ }
27
+ if (shown) menu.setAttribute("data-glide", "");
28
+ else menu.removeAttribute("data-glide");
29
+ menu.style.setProperty("--hl-y", row.offsetTop + "px");
30
+ menu.style.setProperty("--hl-h", row.offsetHeight + "px");
31
+ if (row.hasAttribute("data-destructive")) menu.setAttribute("data-hl-tone", "destructive");
32
+ else menu.removeAttribute("data-hl-tone");
33
+ menu.setAttribute("data-hl", "");
34
+ shown = true;
35
+ };
36
+ const onFocusOut = (event) => {
37
+ if (menu.contains(event.relatedTarget)) return;
38
+ window.clearTimeout(timer);
39
+ hide();
40
+ };
41
+ menu.addEventListener("focusin", onFocusIn);
42
+ menu.addEventListener("focusout", onFocusOut);
43
+ return () => {
44
+ window.clearTimeout(timer);
45
+ menu.removeEventListener("focusin", onFocusIn);
46
+ menu.removeEventListener("focusout", onFocusOut);
47
+ };
48
+ }
49
+ /**
50
+ * A ref callback for the element that carries `menu-highlight`. A menu that
51
+ * mounts only while open (a popover) binds when it appears and unbinds when it
52
+ * goes, so this is a callback ref rather than an effect.
53
+ */
54
+ function useMenuHighlight() {
55
+ const unbind = useRef(void 0);
56
+ return useCallback((node) => {
57
+ unbind.current?.();
58
+ unbind.current = node ? bindMenuHighlight(node) : void 0;
59
+ }, []);
60
+ }
61
+ //#endregion
62
+ export { useMenuHighlight };
63
+
64
+ //# sourceMappingURL=menu-highlight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menu-highlight.js","names":[],"sources":["../../src/lib/menu-highlight.ts"],"sourcesContent":["import { useCallback, useRef } from \"react\";\n\nconst ROWS = '[role=\"menuitem\"], [role=\"menuitemradio\"], [role=\"menuitemcheckbox\"]';\n\n/**\n * One highlight for a whole menu, drawn by the menu's ::before (the\n * `menu-highlight` utility) and moved from here: it appears in place on the\n * first row, glides between rows after that (pointer and arrow keys alike,\n * because React Aria focuses the row either way), and fades out shortly after\n * focus leaves the rows. A row marked `data-destructive` tints it red. Returns\n * the cleanup.\n */\nfunction bindMenuHighlight(menu: HTMLElement) {\n let shown = false;\n let timer = 0;\n const hide = () => {\n menu.removeAttribute(\"data-hl\");\n menu.removeAttribute(\"data-glide\");\n shown = false;\n };\n const onFocusIn = (event: FocusEvent) => {\n window.clearTimeout(timer);\n const row = (event.target as Element).closest<HTMLElement>(ROWS);\n // Between rows (a gap, the separator, the menu itself) it waits a moment, so it can glide across.\n if (!row || !menu.contains(row) || row.hasAttribute(\"data-disabled\")) {\n timer = window.setTimeout(hide, 70);\n return;\n }\n if (shown) menu.setAttribute(\"data-glide\", \"\");\n else menu.removeAttribute(\"data-glide\");\n menu.style.setProperty(\"--hl-y\", row.offsetTop + \"px\");\n menu.style.setProperty(\"--hl-h\", row.offsetHeight + \"px\");\n if (row.hasAttribute(\"data-destructive\")) menu.setAttribute(\"data-hl-tone\", \"destructive\");\n else menu.removeAttribute(\"data-hl-tone\");\n menu.setAttribute(\"data-hl\", \"\");\n shown = true;\n };\n const onFocusOut = (event: FocusEvent) => {\n if (menu.contains(event.relatedTarget as Node | null)) return;\n window.clearTimeout(timer);\n hide();\n };\n menu.addEventListener(\"focusin\", onFocusIn);\n menu.addEventListener(\"focusout\", onFocusOut);\n return () => {\n window.clearTimeout(timer);\n menu.removeEventListener(\"focusin\", onFocusIn);\n menu.removeEventListener(\"focusout\", onFocusOut);\n };\n}\n\n/**\n * A ref callback for the element that carries `menu-highlight`. A menu that\n * mounts only while open (a popover) binds when it appears and unbinds when it\n * goes, so this is a callback ref rather than an effect.\n */\nexport function useMenuHighlight() {\n const unbind = useRef<(() => void) | undefined>(undefined);\n return useCallback((node: HTMLElement | null) => {\n unbind.current?.();\n unbind.current = node ? bindMenuHighlight(node) : undefined;\n }, []);\n}\n"],"mappings":";;AAEA,MAAM,OAAO;;;;;;;;;AAUb,SAAS,kBAAkB,MAAmB;CAC5C,IAAI,QAAQ;CACZ,IAAI,QAAQ;CACZ,MAAM,aAAa;EACjB,KAAK,gBAAgB,SAAS;EAC9B,KAAK,gBAAgB,YAAY;EACjC,QAAQ;CACV;CACA,MAAM,aAAa,UAAsB;EACvC,OAAO,aAAa,KAAK;EACzB,MAAM,MAAO,MAAM,OAAmB,QAAqB,IAAI;EAE/D,IAAI,CAAC,OAAO,CAAC,KAAK,SAAS,GAAG,KAAK,IAAI,aAAa,eAAe,GAAG;GACpE,QAAQ,OAAO,WAAW,MAAM,EAAE;GAClC;EACF;EACA,IAAI,OAAO,KAAK,aAAa,cAAc,EAAE;OACxC,KAAK,gBAAgB,YAAY;EACtC,KAAK,MAAM,YAAY,UAAU,IAAI,YAAY,IAAI;EACrD,KAAK,MAAM,YAAY,UAAU,IAAI,eAAe,IAAI;EACxD,IAAI,IAAI,aAAa,kBAAkB,GAAG,KAAK,aAAa,gBAAgB,aAAa;OACpF,KAAK,gBAAgB,cAAc;EACxC,KAAK,aAAa,WAAW,EAAE;EAC/B,QAAQ;CACV;CACA,MAAM,cAAc,UAAsB;EACxC,IAAI,KAAK,SAAS,MAAM,aAA4B,GAAG;EACvD,OAAO,aAAa,KAAK;EACzB,KAAK;CACP;CACA,KAAK,iBAAiB,WAAW,SAAS;CAC1C,KAAK,iBAAiB,YAAY,UAAU;CAC5C,aAAa;EACX,OAAO,aAAa,KAAK;EACzB,KAAK,oBAAoB,WAAW,SAAS;EAC7C,KAAK,oBAAoB,YAAY,UAAU;CACjD;AACF;;;;;;AAOA,SAAgB,mBAAmB;CACjC,MAAM,SAAS,OAAiC,KAAA,CAAS;CACzD,OAAO,aAAa,SAA6B;EAC/C,OAAO,UAAU;EACjB,OAAO,UAAU,OAAO,kBAAkB,IAAI,IAAI,KAAA;CACpD,GAAG,CAAC,CAAC;AACP"}
@@ -3,9 +3,9 @@
3
3
  * Combines a caller-supplied ref with a ref this component already needs for
4
4
  * its own purposes, so both receive the same node.
5
5
  *
6
- * Only two call sites need this -- Avatar's `measure` (image decode-state)
7
- * and ScrollArea's `region` (scrollTop reset) -- both already attached to the
8
- * same node the newly forwarded ref now also targets. A shared five-line
6
+ * Three call sites need this -- Avatar's `measure` (image decode-state),
7
+ * ScrollArea's `region` (scrollTop reset) and TabList's overflow watch --
8
+ * each already attached to the same node the forwarded ref also targets. A shared five-line
9
9
  * helper for two call sites is the Rule of Two; anything more general (a
10
10
  * `useMergedRef` hook, React 19 ref-cleanup support) is speculative until a
11
11
  * third caller needs it.
@@ -1 +1 @@
1
- {"version":3,"file":"merge-refs.js","names":[],"sources":["../../src/lib/merge-refs.ts"],"sourcesContent":["import type { Ref, RefCallback } from \"react\";\n\n/**\n * Combines a caller-supplied ref with a ref this component already needs for\n * its own purposes, so both receive the same node.\n *\n * Only two call sites need this -- Avatar's `measure` (image decode-state)\n * and ScrollArea's `region` (scrollTop reset) -- both already attached to the\n * same node the newly forwarded ref now also targets. A shared five-line\n * helper for two call sites is the Rule of Two; anything more general (a\n * `useMergedRef` hook, React 19 ref-cleanup support) is speculative until a\n * third caller needs it.\n */\nexport function mergeRefs<T>(...refs: (Ref<T> | undefined)[]): RefCallback<T> {\n return (node) => {\n for (const ref of refs) {\n if (typeof ref === \"function\") ref(node);\n else if (ref) (ref as { current: T | null }).current = node;\n }\n };\n}\n"],"mappings":";;;;;;;;;;;;AAaA,SAAgB,UAAa,GAAG,MAA8C;CAC5E,QAAQ,SAAS;EACf,KAAK,MAAM,OAAO,MAChB,IAAI,OAAO,QAAQ,YAAY,IAAI,IAAI;OAClC,IAAI,KAAK,IAA+B,UAAU;CAE3D;AACF"}
1
+ {"version":3,"file":"merge-refs.js","names":[],"sources":["../../src/lib/merge-refs.ts"],"sourcesContent":["import type { Ref, RefCallback } from \"react\";\n\n/**\n * Combines a caller-supplied ref with a ref this component already needs for\n * its own purposes, so both receive the same node.\n *\n * Three call sites need this -- Avatar's `measure` (image decode-state),\n * ScrollArea's `region` (scrollTop reset) and TabList's overflow watch --\n * each already attached to the same node the forwarded ref also targets. A shared five-line\n * helper for two call sites is the Rule of Two; anything more general (a\n * `useMergedRef` hook, React 19 ref-cleanup support) is speculative until a\n * third caller needs it.\n */\nexport function mergeRefs<T>(...refs: (Ref<T> | undefined)[]): RefCallback<T> {\n return (node) => {\n for (const ref of refs) {\n if (typeof ref === \"function\") ref(node);\n else if (ref) (ref as { current: T | null }).current = node;\n }\n };\n}\n"],"mappings":";;;;;;;;;;;;AAaA,SAAgB,UAAa,GAAG,MAA8C;CAC5E,QAAQ,SAAS;EACf,KAAK,MAAM,OAAO,MAChB,IAAI,OAAO,QAAQ,YAAY,IAAI,IAAI;OAClC,IAAI,KAAK,IAA+B,UAAU;CAE3D;AACF"}
@@ -0,0 +1,128 @@
1
+ import { cn } from "./cn.js";
2
+ import { prefersReducedMotion, useExitBackstop, useIsomorphicLayoutEffect, useMorphWidth } from "./motion.js";
3
+ import { useCallback, useRef, useState } from "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/lib/morph-text.tsx
6
+ const toGlyphs = (chars, seq) => chars.map((char, order) => ({
7
+ id: seq + ":" + order,
8
+ char,
9
+ order,
10
+ seq
11
+ }));
12
+ /** Shared leading and trailing letters keep their identity, so only the changed run of text is replaced. */
13
+ function useGlyphs(text) {
14
+ const [state, setState] = useState(() => ({
15
+ text,
16
+ seq: 0,
17
+ glyphs: toGlyphs([...text], 0)
18
+ }));
19
+ if (state.text === text) return state.glyphs;
20
+ const prev = [...state.text];
21
+ const next = [...text];
22
+ let start = 0;
23
+ let end = 0;
24
+ while (start < prev.length && start < next.length && prev[start] === next[start]) start++;
25
+ while (end < prev.length - start && end < next.length - start && prev[prev.length - 1 - end] === next[next.length - 1 - end]) end++;
26
+ if (start < 2) start = 0;
27
+ if (end < 2) end = 0;
28
+ const seq = state.seq + 1;
29
+ const glyphs = [
30
+ ...state.glyphs.slice(0, start),
31
+ ...toGlyphs(next.slice(start, next.length - end), seq),
32
+ ...state.glyphs.slice(state.glyphs.length - end)
33
+ ];
34
+ setState({
35
+ text,
36
+ seq,
37
+ glyphs
38
+ });
39
+ return glyphs;
40
+ }
41
+ function LeavingLetter({ glyph, onDone, calm }) {
42
+ const { id } = glyph;
43
+ const remove = useCallback(() => onDone(id), [onDone, id]);
44
+ useExitBackstop(true, remove);
45
+ return /* @__PURE__ */ jsx("span", {
46
+ "aria-hidden": "true",
47
+ onAnimationEnd: (event) => {
48
+ if (event.target === event.currentTarget) remove();
49
+ },
50
+ style: glyph.rect,
51
+ className: calm ? "absolute inline-block motion-safe:animate-letter-out-calm motion-reduce:animate-fade-out" : "absolute inline-block motion-safe:animate-letter-out motion-reduce:animate-fade-out",
52
+ children: glyph.char
53
+ });
54
+ }
55
+ /**
56
+ * One label morphing into the next: letters both labels share glide into their
57
+ * new place, new ones rise in from a soft blur, removed ones lift away, and the
58
+ * width follows on a spring. The glide is a FLIP: the letter is moved back to
59
+ * where it was, then transitioned to where it is.
60
+ */
61
+ function MorphText({ text, morphWidth = true, calm = false }) {
62
+ const glyphs = useGlyphs(text);
63
+ const rowRef = useRef(null);
64
+ const before = useRef(/* @__PURE__ */ new Map());
65
+ const [leaving, setLeaving] = useState([]);
66
+ useMorphWidth(rowRef, text, morphWidth);
67
+ useIsomorphicLayoutEffect(() => {
68
+ const row = rowRef.current;
69
+ if (!row) return;
70
+ const now = /* @__PURE__ */ new Map();
71
+ const reduced = prefersReducedMotion();
72
+ for (const node of row.querySelectorAll("[data-glyph]")) {
73
+ const id = node.dataset.glyph;
74
+ const rect = {
75
+ left: node.offsetLeft,
76
+ top: node.offsetTop,
77
+ width: node.offsetWidth,
78
+ height: node.offsetHeight
79
+ };
80
+ const was = before.current.get(id);
81
+ now.set(id, {
82
+ rect,
83
+ char: node.textContent ?? ""
84
+ });
85
+ if (was && !reduced) {
86
+ const dx = was.rect.left - rect.left;
87
+ if (Math.abs(dx) > .5) {
88
+ node.style.transition = "none";
89
+ node.style.translate = dx + "px 0";
90
+ node.offsetWidth;
91
+ node.style.transition = calm ? "translate var(--duration-calm) var(--ease-calm)" : "translate var(--duration-morph) var(--ease-morph)";
92
+ node.style.translate = "0 0";
93
+ }
94
+ }
95
+ }
96
+ const gone = [];
97
+ for (const [id, was] of before.current) if (!now.has(id)) gone.push({
98
+ id,
99
+ char: was.char,
100
+ rect: was.rect
101
+ });
102
+ before.current = now;
103
+ if (gone.length) setLeaving((list) => [...list, ...gone]);
104
+ });
105
+ const finish = useCallback((id) => setLeaving((list) => list.filter((glyph) => glyph.id !== id)), []);
106
+ return /* @__PURE__ */ jsx("span", {
107
+ className: morphWidth ? "button-slot [--morph-clip:inset(-.6em_0_-.6em_-.3em)]" : "inline-flex min-w-0",
108
+ "aria-hidden": "true",
109
+ children: /* @__PURE__ */ jsxs("span", {
110
+ ref: rowRef,
111
+ className: "relative inline-flex flex-none whitespace-pre",
112
+ children: [leaving.map((glyph) => /* @__PURE__ */ jsx(LeavingLetter, {
113
+ glyph,
114
+ onDone: finish,
115
+ calm
116
+ }, glyph.id)), glyphs.map((glyph) => /* @__PURE__ */ jsx("span", {
117
+ "data-glyph": glyph.id,
118
+ style: { "--letter-delay": (calm ? Math.min(glyph.order * 20, 120) : Math.min(glyph.order * 16, 100)) + "ms" },
119
+ className: cn("inline-block", glyph.seq > 0 && (calm ? "motion-safe:animate-letter-in-calm motion-safe:[animation-delay:var(--letter-delay)] motion-reduce:animate-fade-in" : "motion-safe:animate-letter-in motion-safe:[animation-delay:var(--letter-delay)] motion-reduce:animate-fade-in")),
120
+ children: glyph.char
121
+ }, glyph.id))]
122
+ })
123
+ });
124
+ }
125
+ //#endregion
126
+ export { MorphText };
127
+
128
+ //# sourceMappingURL=morph-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"morph-text.js","names":[],"sources":["../../src/lib/morph-text.tsx"],"sourcesContent":["import { useCallback, useRef, useState, type CSSProperties } from \"react\";\n\nimport { cn } from \"./cn.js\";\nimport {\n prefersReducedMotion,\n useExitBackstop,\n useIsomorphicLayoutEffect,\n useMorphWidth,\n type Rect,\n} from \"./motion.js\";\n\ninterface Glyph {\n id: string;\n char: string;\n /** Position within the replaced run, which staggers its entrance. */\n order: number;\n /** 0 for the letters the component mounted with, which do not animate in. */\n seq: number;\n}\n\nconst toGlyphs = (chars: string[], seq: number): Glyph[] =>\n chars.map((char, order) => ({ id: seq + \":\" + order, char, order, seq }));\n\n/** Shared leading and trailing letters keep their identity, so only the changed run of text is replaced. */\nfunction useGlyphs(text: string) {\n const [state, setState] = useState(() => ({ text, seq: 0, glyphs: toGlyphs([...text], 0) }));\n if (state.text === text) return state.glyphs;\n const prev = [...state.text];\n const next = [...text];\n let start = 0;\n let end = 0;\n while (start < prev.length && start < next.length && prev[start] === next[start]) start++;\n while (\n end < prev.length - start &&\n end < next.length - start &&\n prev[prev.length - 1 - end] === next[next.length - 1 - end]\n )\n end++;\n if (start < 2) start = 0;\n if (end < 2) end = 0;\n const seq = state.seq + 1;\n const glyphs = [\n ...state.glyphs.slice(0, start),\n ...toGlyphs(next.slice(start, next.length - end), seq),\n ...state.glyphs.slice(state.glyphs.length - end),\n ];\n setState({ text, seq, glyphs });\n return glyphs;\n}\n\ninterface LeavingGlyph {\n id: string;\n char: string;\n rect: Rect;\n}\n\nfunction LeavingLetter({\n glyph,\n onDone,\n calm,\n}: {\n glyph: LeavingGlyph;\n onDone: (id: string) => void;\n calm: boolean;\n}) {\n const { id } = glyph;\n const remove = useCallback(() => onDone(id), [onDone, id]);\n useExitBackstop(true, remove);\n return (\n <span\n aria-hidden=\"true\"\n onAnimationEnd={(event) => {\n if (event.target === event.currentTarget) remove();\n }}\n style={glyph.rect}\n className={\n calm\n ? \"absolute inline-block motion-safe:animate-letter-out-calm motion-reduce:animate-fade-out\"\n : \"absolute inline-block motion-safe:animate-letter-out motion-reduce:animate-fade-out\"\n }\n >\n {glyph.char}\n </span>\n );\n}\n\n/**\n * One label morphing into the next: letters both labels share glide into their\n * new place, new ones rise in from a soft blur, removed ones lift away, and the\n * width follows on a spring. The glide is a FLIP: the letter is moved back to\n * where it was, then transitioned to where it is.\n */\nexport function MorphText({\n text,\n morphWidth = true,\n calm = false,\n}: {\n text: string;\n /** Spring the width to the new text. Off when the caller reserves the widest text or springs something wider. */\n morphWidth?: boolean;\n /** Slower, softer motion, for feedback that should never read as busy. */\n calm?: boolean;\n}) {\n const glyphs = useGlyphs(text);\n const rowRef = useRef<HTMLSpanElement>(null);\n const before = useRef(new Map<string, { rect: Rect; char: string }>());\n const [leaving, setLeaving] = useState<LeavingGlyph[]>([]);\n\n // The width slot is optional: a caller that wraps this in something wider (an icon beside it) springs that instead.\n useMorphWidth(rowRef, text, morphWidth);\n\n useIsomorphicLayoutEffect(() => {\n const row = rowRef.current;\n if (!row) return;\n const now = new Map<string, { rect: Rect; char: string }>();\n const reduced = prefersReducedMotion();\n for (const node of row.querySelectorAll<HTMLElement>(\"[data-glyph]\")) {\n const id = node.dataset.glyph!;\n const rect = {\n left: node.offsetLeft,\n top: node.offsetTop,\n width: node.offsetWidth,\n height: node.offsetHeight,\n };\n const was = before.current.get(id);\n now.set(id, { rect, char: node.textContent ?? \"\" });\n if (was && !reduced) {\n const dx = was.rect.left - rect.left;\n if (Math.abs(dx) > 0.5) {\n node.style.transition = \"none\";\n node.style.translate = dx + \"px 0\";\n void node.offsetWidth;\n node.style.transition = calm\n ? \"translate var(--duration-calm) var(--ease-calm)\"\n : \"translate var(--duration-morph) var(--ease-morph)\";\n node.style.translate = \"0 0\";\n }\n }\n }\n const gone: LeavingGlyph[] = [];\n for (const [id, was] of before.current)\n if (!now.has(id)) gone.push({ id, char: was.char, rect: was.rect });\n before.current = now;\n if (gone.length) setLeaving((list) => [...list, ...gone]);\n });\n\n const finish = useCallback(\n (id: string) => setLeaving((list) => list.filter((glyph) => glyph.id !== id)),\n [],\n );\n\n return (\n <span\n className={\n morphWidth ? \"button-slot [--morph-clip:inset(-.6em_0_-.6em_-.3em)]\" : \"inline-flex min-w-0\"\n }\n aria-hidden=\"true\"\n >\n <span ref={rowRef} className=\"relative inline-flex flex-none whitespace-pre\">\n {leaving.map((glyph) => (\n <LeavingLetter key={glyph.id} glyph={glyph} onDone={finish} calm={calm} />\n ))}\n {glyphs.map((glyph) => (\n <span\n key={glyph.id}\n data-glyph={glyph.id}\n style={\n {\n \"--letter-delay\":\n (calm ? Math.min(glyph.order * 20, 120) : Math.min(glyph.order * 16, 100)) + \"ms\",\n } as CSSProperties\n }\n className={cn(\n \"inline-block\",\n glyph.seq > 0 &&\n (calm\n ? \"motion-safe:animate-letter-in-calm motion-safe:[animation-delay:var(--letter-delay)] motion-reduce:animate-fade-in\"\n : \"motion-safe:animate-letter-in motion-safe:[animation-delay:var(--letter-delay)] motion-reduce:animate-fade-in\"),\n )}\n >\n {glyph.char}\n </span>\n ))}\n </span>\n </span>\n );\n}\n"],"mappings":";;;;;AAoBA,MAAM,YAAY,OAAiB,QACjC,MAAM,KAAK,MAAM,WAAW;CAAE,IAAI,MAAM,MAAM;CAAO;CAAM;CAAO;AAAI,EAAE;;AAG1E,SAAS,UAAU,MAAc;CAC/B,MAAM,CAAC,OAAO,YAAY,gBAAgB;EAAE;EAAM,KAAK;EAAG,QAAQ,SAAS,CAAC,GAAG,IAAI,GAAG,CAAC;CAAE,EAAE;CAC3F,IAAI,MAAM,SAAS,MAAM,OAAO,MAAM;CACtC,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI;CAC3B,MAAM,OAAO,CAAC,GAAG,IAAI;CACrB,IAAI,QAAQ;CACZ,IAAI,MAAM;CACV,OAAO,QAAQ,KAAK,UAAU,QAAQ,KAAK,UAAU,KAAK,WAAW,KAAK,QAAQ;CAClF,OACE,MAAM,KAAK,SAAS,SACpB,MAAM,KAAK,SAAS,SACpB,KAAK,KAAK,SAAS,IAAI,SAAS,KAAK,KAAK,SAAS,IAAI,MAEvD;CACF,IAAI,QAAQ,GAAG,QAAQ;CACvB,IAAI,MAAM,GAAG,MAAM;CACnB,MAAM,MAAM,MAAM,MAAM;CACxB,MAAM,SAAS;EACb,GAAG,MAAM,OAAO,MAAM,GAAG,KAAK;EAC9B,GAAG,SAAS,KAAK,MAAM,OAAO,KAAK,SAAS,GAAG,GAAG,GAAG;EACrD,GAAG,MAAM,OAAO,MAAM,MAAM,OAAO,SAAS,GAAG;CACjD;CACA,SAAS;EAAE;EAAM;EAAK;CAAO,CAAC;CAC9B,OAAO;AACT;AAQA,SAAS,cAAc,EACrB,OACA,QACA,QAKC;CACD,MAAM,EAAE,OAAO;CACf,MAAM,SAAS,kBAAkB,OAAO,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC;CACzD,gBAAgB,MAAM,MAAM;CAC5B,OACE,oBAAC,QAAD;EACE,eAAY;EACZ,iBAAiB,UAAU;GACzB,IAAI,MAAM,WAAW,MAAM,eAAe,OAAO;EACnD;EACA,OAAO,MAAM;EACb,WACE,OACI,6FACA;EAGL,UAAA,MAAM;CACH,CAAA;AAEV;;;;;;;AAQA,SAAgB,UAAU,EACxB,MACA,aAAa,MACb,OAAO,SAON;CACD,MAAM,SAAS,UAAU,IAAI;CAC7B,MAAM,SAAS,OAAwB,IAAI;CAC3C,MAAM,SAAS,uBAAO,IAAI,IAA0C,CAAC;CACrE,MAAM,CAAC,SAAS,cAAc,SAAyB,CAAC,CAAC;CAGzD,cAAc,QAAQ,MAAM,UAAU;CAEtC,gCAAgC;EAC9B,MAAM,MAAM,OAAO;EACnB,IAAI,CAAC,KAAK;EACV,MAAM,sBAAM,IAAI,IAA0C;EAC1D,MAAM,UAAU,qBAAqB;EACrC,KAAK,MAAM,QAAQ,IAAI,iBAA8B,cAAc,GAAG;GACpE,MAAM,KAAK,KAAK,QAAQ;GACxB,MAAM,OAAO;IACX,MAAM,KAAK;IACX,KAAK,KAAK;IACV,OAAO,KAAK;IACZ,QAAQ,KAAK;GACf;GACA,MAAM,MAAM,OAAO,QAAQ,IAAI,EAAE;GACjC,IAAI,IAAI,IAAI;IAAE;IAAM,MAAM,KAAK,eAAe;GAAG,CAAC;GAClD,IAAI,OAAO,CAAC,SAAS;IACnB,MAAM,KAAK,IAAI,KAAK,OAAO,KAAK;IAChC,IAAI,KAAK,IAAI,EAAE,IAAI,IAAK;KACtB,KAAK,MAAM,aAAa;KACxB,KAAK,MAAM,YAAY,KAAK;KAC5B,KAAU;KACV,KAAK,MAAM,aAAa,OACpB,oDACA;KACJ,KAAK,MAAM,YAAY;IACzB;GACF;EACF;EACA,MAAM,OAAuB,CAAC;EAC9B,KAAK,MAAM,CAAC,IAAI,QAAQ,OAAO,SAC7B,IAAI,CAAC,IAAI,IAAI,EAAE,GAAG,KAAK,KAAK;GAAE;GAAI,MAAM,IAAI;GAAM,MAAM,IAAI;EAAK,CAAC;EACpE,OAAO,UAAU;EACjB,IAAI,KAAK,QAAQ,YAAY,SAAS,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC;CAC1D,CAAC;CAED,MAAM,SAAS,aACZ,OAAe,YAAY,SAAS,KAAK,QAAQ,UAAU,MAAM,OAAO,EAAE,CAAC,GAC5E,CAAC,CACH;CAEA,OACE,oBAAC,QAAD;EACE,WACE,aAAa,0DAA0D;EAEzE,eAAY;EAEZ,UAAA,qBAAC,QAAD;GAAM,KAAK;GAAQ,WAAU;GAA7B,UAAA,CACG,QAAQ,KAAK,UACZ,oBAAC,eAAD;IAAqC;IAAO,QAAQ;IAAc;GAAO,GAArD,MAAM,EAA+C,CAC1E,GACA,OAAO,KAAK,UACX,oBAAC,QAAD;IAEE,cAAY,MAAM;IAClB,OACE,EACE,mBACG,OAAO,KAAK,IAAI,MAAM,QAAQ,IAAI,GAAG,IAAI,KAAK,IAAI,MAAM,QAAQ,IAAI,GAAG,KAAK,KACjF;IAEF,WAAW,GACT,gBACA,MAAM,MAAM,MACT,OACG,uHACA,gHACR;IAEC,UAAA,MAAM;GACH,GAjBC,MAAM,EAiBP,CACP,CACG;;CACF,CAAA;AAEV"}