@exsto/ds 0.0.1-rc.2 → 0.0.1-rc.22

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 (346) hide show
  1. package/README.md +171 -28
  2. package/dist/date-picker.js +2 -0
  3. package/dist/ds/src/date-picker.d.ts +9 -0
  4. package/dist/ds/src/file-input.d.ts +9 -0
  5. package/dist/ds/src/index.d.ts +2 -3
  6. package/dist/ds/src/rich-text-editor.d.ts +8 -0
  7. package/dist/ds/src/time-field.d.ts +9 -0
  8. package/dist/file-input.js +2 -0
  9. package/dist/index.js +79 -2
  10. package/dist/rich-text-editor.js +4 -0
  11. package/dist/source-hash.txt +1 -0
  12. package/dist/styles.css +1201 -404
  13. package/dist/theme.css +125 -7
  14. package/dist/time-field.js +2 -0
  15. package/dist/ui/src/components/edyn/animated-number/animated-number.d.ts +30 -0
  16. package/dist/ui/src/components/edyn/animated-number/animated-number.js +108 -0
  17. package/dist/ui/src/components/edyn/animated-number/animated-number.js.map +1 -0
  18. package/dist/ui/src/components/edyn/animated-number/animated-number.test.d.ts +9 -0
  19. package/dist/ui/src/components/edyn/animated-number/index.d.ts +1 -0
  20. package/dist/ui/src/components/edyn/audio-player/audio-player.d.ts +3 -2
  21. package/dist/ui/src/components/edyn/audio-player/audio-player.js +86 -0
  22. package/dist/ui/src/components/edyn/audio-player/audio-player.js.map +1 -0
  23. package/dist/ui/src/components/edyn/date-picker/date-picker.d.ts +86 -0
  24. package/dist/ui/src/components/edyn/date-picker/date-picker.js +302 -0
  25. package/dist/ui/src/components/edyn/date-picker/date-picker.js.map +1 -0
  26. package/dist/ui/src/components/edyn/date-picker/date-picker.state.test.d.ts +9 -0
  27. package/dist/ui/src/components/edyn/date-picker/date-text.d.ts +40 -0
  28. package/dist/ui/src/components/edyn/date-picker/date-text.js +105 -0
  29. package/dist/ui/src/components/edyn/date-picker/date-text.js.map +1 -0
  30. package/dist/ui/src/components/edyn/date-picker/index.d.ts +2 -0
  31. package/dist/ui/src/components/edyn/date-range-picker/date-range-picker.d.ts +28 -0
  32. package/dist/ui/src/components/edyn/date-range-picker/date-range-picker.js +70 -0
  33. package/dist/ui/src/components/edyn/date-range-picker/date-range-picker.js.map +1 -0
  34. package/dist/ui/src/components/edyn/date-range-picker/date-range-picker.test.d.ts +9 -0
  35. package/dist/ui/src/components/edyn/date-range-picker/index.d.ts +1 -0
  36. package/dist/ui/src/components/edyn/file-input/file-input.d.ts +90 -0
  37. package/dist/ui/src/components/edyn/file-input/file-input.js +261 -0
  38. package/dist/ui/src/components/edyn/file-input/file-input.js.map +1 -0
  39. package/dist/ui/src/components/edyn/file-input/file-input.test.d.ts +8 -0
  40. package/dist/ui/src/components/edyn/file-input/index.d.ts +1 -0
  41. package/dist/ui/src/components/edyn/initials-avatar/index.d.ts +1 -0
  42. package/dist/ui/src/components/edyn/initials-avatar/initials-avatar.d.ts +54 -0
  43. package/dist/ui/src/components/edyn/initials-avatar/initials-avatar.js +86 -0
  44. package/dist/ui/src/components/edyn/initials-avatar/initials-avatar.js.map +1 -0
  45. package/dist/ui/src/components/edyn/initials-avatar/initials-avatar.test.d.ts +10 -0
  46. package/dist/ui/src/components/edyn/multi-select/index.d.ts +1 -0
  47. package/dist/ui/src/components/edyn/multi-select/multi-select.d.ts +117 -0
  48. package/dist/ui/src/components/edyn/multi-select/multi-select.js +342 -0
  49. package/dist/ui/src/components/edyn/multi-select/multi-select.js.map +1 -0
  50. package/dist/ui/src/components/edyn/multi-select/multi-select.test.d.ts +9 -0
  51. package/dist/ui/src/components/edyn/presence/index.d.ts +1 -0
  52. package/dist/ui/src/components/edyn/presence/presence.d.ts +15 -0
  53. package/dist/ui/src/components/edyn/presence/presence.js +58 -0
  54. package/dist/ui/src/components/edyn/presence/presence.js.map +1 -0
  55. package/dist/ui/src/components/edyn/presence/presence.test.d.ts +8 -0
  56. package/dist/ui/src/components/edyn/progress-line/index.d.ts +1 -0
  57. package/dist/ui/src/components/edyn/progress-line/progress-line.d.ts +23 -0
  58. package/dist/ui/src/components/edyn/progress-line/progress-line.js +100 -0
  59. package/dist/ui/src/components/edyn/progress-line/progress-line.js.map +1 -0
  60. package/dist/ui/src/components/edyn/progress-line/progress-line.test.d.ts +9 -0
  61. package/dist/ui/src/components/edyn/rich-text-content/allow-list.d.ts +11 -0
  62. package/dist/ui/src/components/edyn/rich-text-content/allow-list.js +37 -0
  63. package/dist/ui/src/components/edyn/rich-text-content/allow-list.js.map +1 -0
  64. package/dist/ui/src/components/edyn/rich-text-content/index.d.ts +2 -0
  65. package/dist/ui/src/components/edyn/rich-text-content/rich-text-content.d.ts +22 -0
  66. package/dist/ui/src/components/edyn/rich-text-content/rich-text-content.js +124 -0
  67. package/dist/ui/src/components/edyn/rich-text-content/rich-text-content.js.map +1 -0
  68. package/dist/ui/src/components/edyn/rich-text-content/rich-text-content.test.d.ts +8 -0
  69. package/dist/ui/src/components/edyn/rich-text-editor/extensions.d.ts +18 -0
  70. package/dist/ui/src/components/edyn/rich-text-editor/extensions.js +124 -0
  71. package/dist/ui/src/components/edyn/rich-text-editor/extensions.js.map +1 -0
  72. package/dist/ui/src/components/edyn/rich-text-editor/index.d.ts +3 -0
  73. package/dist/ui/src/components/edyn/rich-text-editor/mention-list.d.ts +17 -0
  74. package/dist/ui/src/components/edyn/rich-text-editor/mention-list.js +232 -0
  75. package/dist/ui/src/components/edyn/rich-text-editor/mention-list.js.map +1 -0
  76. package/dist/ui/src/components/edyn/rich-text-editor/rich-text-editor.d.ts +53 -0
  77. package/dist/ui/src/components/edyn/rich-text-editor/rich-text-editor.js +244 -0
  78. package/dist/ui/src/components/edyn/rich-text-editor/rich-text-editor.js.map +1 -0
  79. package/dist/ui/src/components/edyn/rich-text-editor/rich-text-editor.test.d.ts +6 -0
  80. package/dist/ui/src/components/edyn/rich-text-editor/serialize.d.ts +13 -0
  81. package/dist/ui/src/components/edyn/rich-text-editor/serialize.js +55 -0
  82. package/dist/ui/src/components/edyn/rich-text-editor/serialize.js.map +1 -0
  83. package/dist/ui/src/components/edyn/rich-text-editor/sync.d.ts +19 -0
  84. package/dist/ui/src/components/edyn/rich-text-editor/sync.js +34 -0
  85. package/dist/ui/src/components/edyn/rich-text-editor/sync.js.map +1 -0
  86. package/dist/ui/src/components/edyn/rich-text-editor/sync.test.d.ts +11 -0
  87. package/dist/ui/src/components/edyn/rich-text-editor/toolbar.d.ts +20 -0
  88. package/dist/ui/src/components/edyn/rich-text-editor/toolbar.js +235 -0
  89. package/dist/ui/src/components/edyn/rich-text-editor/toolbar.js.map +1 -0
  90. package/dist/ui/src/components/edyn/rich-text-editor/types.d.ts +68 -0
  91. package/dist/ui/src/components/edyn/rich-text-editor/types.js +26 -0
  92. package/dist/ui/src/components/edyn/rich-text-editor/types.js.map +1 -0
  93. package/dist/ui/src/components/edyn/time-field/index.d.ts +1 -0
  94. package/dist/ui/src/components/edyn/time-field/time-field.d.ts +85 -0
  95. package/dist/ui/src/components/edyn/time-field/time-field.js +337 -0
  96. package/dist/ui/src/components/edyn/time-field/time-field.js.map +1 -0
  97. package/dist/ui/src/components/edyn/time-field/time-field.test.d.ts +8 -0
  98. package/dist/ui/src/components/edyn/video-player/video-player.d.ts +2 -2
  99. package/dist/ui/src/components/edyn/video-player/video-player.js +132 -0
  100. package/dist/ui/src/components/edyn/video-player/video-player.js.map +1 -0
  101. package/dist/ui/src/components/forwarded-props.test.d.ts +8 -0
  102. package/dist/ui/src/components/ui/accordion/accordion.d.ts +9 -8
  103. package/dist/ui/src/components/ui/accordion/accordion.js +83 -0
  104. package/dist/ui/src/components/ui/accordion/accordion.js.map +1 -0
  105. package/dist/ui/src/components/ui/alert/alert.js +47 -0
  106. package/dist/ui/src/components/ui/alert/alert.js.map +1 -0
  107. package/dist/ui/src/components/ui/alert-dialog/alert-dialog.js +97 -0
  108. package/dist/ui/src/components/ui/alert-dialog/alert-dialog.js.map +1 -0
  109. package/dist/ui/src/components/ui/aspect-ratio/aspect-ratio.js +15 -0
  110. package/dist/ui/src/components/ui/aspect-ratio/aspect-ratio.js.map +1 -0
  111. package/dist/ui/src/components/ui/attachment/attachment.d.ts +1 -1
  112. package/dist/ui/src/components/ui/attachment/attachment.js +105 -0
  113. package/dist/ui/src/components/ui/attachment/attachment.js.map +1 -0
  114. package/dist/ui/src/components/ui/avatar/avatar.js +51 -0
  115. package/dist/ui/src/components/ui/avatar/avatar.js.map +1 -0
  116. package/dist/ui/src/components/ui/badge/badge.d.ts +1 -1
  117. package/dist/ui/src/components/ui/badge/badge.js +47 -0
  118. package/dist/ui/src/components/ui/badge/badge.js.map +1 -0
  119. package/dist/ui/src/components/ui/breadcrumb/breadcrumb.js +73 -0
  120. package/dist/ui/src/components/ui/breadcrumb/breadcrumb.js.map +1 -0
  121. package/dist/ui/src/components/ui/bubble/bubble.js +74 -0
  122. package/dist/ui/src/components/ui/bubble/bubble.js.map +1 -0
  123. package/dist/ui/src/components/ui/button/button.behavior.test.d.ts +8 -0
  124. package/dist/ui/src/components/ui/button/button.d.ts +1 -1
  125. package/dist/ui/src/components/ui/button/button.js +68 -0
  126. package/dist/ui/src/components/ui/button/button.js.map +1 -0
  127. package/dist/ui/src/components/ui/button-group/button-group.js +37 -0
  128. package/dist/ui/src/components/ui/button-group/button-group.js.map +1 -0
  129. package/dist/ui/src/components/ui/calendar/calendar.behavior.test.d.ts +9 -0
  130. package/dist/ui/src/components/ui/calendar/calendar.d.ts +1 -1
  131. package/dist/ui/src/components/ui/calendar/calendar.js +141 -0
  132. package/dist/ui/src/components/ui/calendar/calendar.js.map +1 -0
  133. package/dist/ui/src/components/ui/card/card.js +57 -0
  134. package/dist/ui/src/components/ui/card/card.js.map +1 -0
  135. package/dist/ui/src/components/ui/carousel/carousel.js +136 -0
  136. package/dist/ui/src/components/ui/carousel/carousel.js.map +1 -0
  137. package/dist/ui/src/components/ui/chart/chart.js +168 -0
  138. package/dist/ui/src/components/ui/chart/chart.js.map +1 -0
  139. package/dist/ui/src/components/ui/checkbox/checkbox.js +21 -0
  140. package/dist/ui/src/components/ui/checkbox/checkbox.js.map +1 -0
  141. package/dist/ui/src/components/ui/collapsible/collapsible.js +26 -0
  142. package/dist/ui/src/components/ui/collapsible/collapsible.js.map +1 -0
  143. package/dist/ui/src/components/ui/combobox/combobox.d.ts +15 -1
  144. package/dist/ui/src/components/ui/combobox/combobox.js +111 -0
  145. package/dist/ui/src/components/ui/combobox/combobox.js.map +1 -0
  146. package/dist/ui/src/components/ui/command/command.d.ts +17 -1
  147. package/dist/ui/src/components/ui/command/command.js +118 -0
  148. package/dist/ui/src/components/ui/command/command.js.map +1 -0
  149. package/dist/ui/src/components/ui/context-menu/context-menu.d.ts +1 -1
  150. package/dist/ui/src/components/ui/context-menu/context-menu.js +145 -0
  151. package/dist/ui/src/components/ui/context-menu/context-menu.js.map +1 -0
  152. package/dist/ui/src/components/ui/dialog/dialog.js +94 -0
  153. package/dist/ui/src/components/ui/dialog/dialog.js.map +1 -0
  154. package/dist/ui/src/components/ui/direction/direction.js +2 -0
  155. package/dist/ui/src/components/ui/drawer/drawer.js +88 -0
  156. package/dist/ui/src/components/ui/drawer/drawer.js.map +1 -0
  157. package/dist/ui/src/components/ui/dropdown-menu/dropdown-menu.d.ts +12 -1
  158. package/dist/ui/src/components/ui/dropdown-menu/dropdown-menu.js +159 -0
  159. package/dist/ui/src/components/ui/dropdown-menu/dropdown-menu.js.map +1 -0
  160. package/dist/ui/src/components/ui/empty/empty.d.ts +5 -1
  161. package/dist/ui/src/components/ui/empty/empty.js +72 -0
  162. package/dist/ui/src/components/ui/empty/empty.js.map +1 -0
  163. package/dist/ui/src/components/ui/field/field.js +36 -0
  164. package/dist/ui/src/components/ui/field/field.js.map +1 -0
  165. package/dist/ui/src/components/ui/hover-card/hover-card.js +39 -0
  166. package/dist/ui/src/components/ui/hover-card/hover-card.js.map +1 -0
  167. package/dist/ui/src/components/ui/indicator/indicator.js +79 -0
  168. package/dist/ui/src/components/ui/indicator/indicator.js.map +1 -0
  169. package/dist/ui/src/components/ui/input/input.js +16 -0
  170. package/dist/ui/src/components/ui/input/input.js.map +1 -0
  171. package/dist/ui/src/components/ui/input-group/input-group.d.ts +1 -1
  172. package/dist/ui/src/components/ui/input-group/input-group.js +77 -0
  173. package/dist/ui/src/components/ui/input-group/input-group.js.map +1 -0
  174. package/dist/ui/src/components/ui/input-otp/input-otp.js +48 -0
  175. package/dist/ui/src/components/ui/input-otp/input-otp.js.map +1 -0
  176. package/dist/ui/src/components/ui/item/item.d.ts +1 -1
  177. package/dist/ui/src/components/ui/item/item.js +122 -0
  178. package/dist/ui/src/components/ui/item/item.js.map +1 -0
  179. package/dist/ui/src/components/ui/kbd/kbd.js +21 -0
  180. package/dist/ui/src/components/ui/kbd/kbd.js.map +1 -0
  181. package/dist/ui/src/components/ui/label/label.js +14 -0
  182. package/dist/ui/src/components/ui/label/label.js.map +1 -0
  183. package/dist/ui/src/components/ui/marker/marker.js +44 -0
  184. package/dist/ui/src/components/ui/marker/marker.js.map +1 -0
  185. package/dist/ui/src/components/ui/menubar/menubar.js +140 -0
  186. package/dist/ui/src/components/ui/menubar/menubar.js.map +1 -0
  187. package/dist/ui/src/components/ui/message/message.js +50 -0
  188. package/dist/ui/src/components/ui/message/message.js.map +1 -0
  189. package/dist/ui/src/components/ui/message-scroller/message-scroller.js +61 -0
  190. package/dist/ui/src/components/ui/message-scroller/message-scroller.js.map +1 -0
  191. package/dist/ui/src/components/ui/native-select/native-select.js +39 -0
  192. package/dist/ui/src/components/ui/native-select/native-select.js.map +1 -0
  193. package/dist/ui/src/components/ui/navigation-menu/navigation-menu.js +87 -0
  194. package/dist/ui/src/components/ui/navigation-menu/navigation-menu.js.map +1 -0
  195. package/dist/ui/src/components/ui/pagination/pagination.d.ts +3 -2
  196. package/dist/ui/src/components/ui/pagination/pagination.js +104 -0
  197. package/dist/ui/src/components/ui/pagination/pagination.js.map +1 -0
  198. package/dist/ui/src/components/ui/popover/popover.d.ts +1 -1
  199. package/dist/ui/src/components/ui/popover/popover.js +59 -0
  200. package/dist/ui/src/components/ui/popover/popover.js.map +1 -0
  201. package/dist/ui/src/components/ui/progress/progress.behavior.test.d.ts +8 -0
  202. package/dist/ui/src/components/ui/progress/progress.d.ts +43 -1
  203. package/dist/ui/src/components/ui/progress/progress.js +70 -0
  204. package/dist/ui/src/components/ui/progress/progress.js.map +1 -0
  205. package/dist/ui/src/components/ui/questionnaire/questionnaire.js +171 -0
  206. package/dist/ui/src/components/ui/questionnaire/questionnaire.js.map +1 -0
  207. package/dist/ui/src/components/ui/radio-group/radio-group.js +29 -0
  208. package/dist/ui/src/components/ui/radio-group/radio-group.js.map +1 -0
  209. package/dist/ui/src/components/ui/resizable/resizable.js +29 -0
  210. package/dist/ui/src/components/ui/resizable/resizable.js.map +1 -0
  211. package/dist/ui/src/components/ui/scroll-area/index.d.ts +1 -0
  212. package/dist/ui/src/components/ui/scroll-area/popup-list-scroll.d.ts +13 -0
  213. package/dist/ui/src/components/ui/scroll-area/popup-list-scroll.js +69 -0
  214. package/dist/ui/src/components/ui/scroll-area/popup-list-scroll.js.map +1 -0
  215. package/dist/ui/src/components/ui/scroll-area/scroll-area.behavior.test.d.ts +12 -0
  216. package/dist/ui/src/components/ui/scroll-area/scroll-area.d.ts +57 -6
  217. package/dist/ui/src/components/ui/scroll-area/scroll-area.js +125 -0
  218. package/dist/ui/src/components/ui/scroll-area/scroll-area.js.map +1 -0
  219. package/dist/ui/src/components/ui/select/select.js +97 -0
  220. package/dist/ui/src/components/ui/select/select.js.map +1 -0
  221. package/dist/ui/src/components/ui/separator/separator.behavior.test.d.ts +14 -0
  222. package/dist/ui/src/components/ui/separator/separator.d.ts +14 -0
  223. package/dist/ui/src/components/ui/separator/separator.js +30 -0
  224. package/dist/ui/src/components/ui/separator/separator.js.map +1 -0
  225. package/dist/ui/src/components/ui/sheet/sheet.d.ts +8 -0
  226. package/dist/ui/src/components/ui/sheet/sheet.js +99 -0
  227. package/dist/ui/src/components/ui/sheet/sheet.js.map +1 -0
  228. package/dist/ui/src/components/ui/sidebar/sidebar.js +383 -0
  229. package/dist/ui/src/components/ui/sidebar/sidebar.js.map +1 -0
  230. package/dist/ui/src/components/ui/skeleton/skeleton.js +14 -0
  231. package/dist/ui/src/components/ui/skeleton/skeleton.js.map +1 -0
  232. package/dist/ui/src/components/ui/slider/slider.js +49 -0
  233. package/dist/ui/src/components/ui/slider/slider.js.map +1 -0
  234. package/dist/ui/src/components/ui/spinner/spinner.js +28 -0
  235. package/dist/ui/src/components/ui/spinner/spinner.js.map +1 -0
  236. package/dist/ui/src/components/ui/switch/switch.js +20 -0
  237. package/dist/ui/src/components/ui/switch/switch.js.map +1 -0
  238. package/dist/ui/src/components/ui/table/table.behavior.test.d.ts +4 -5
  239. package/dist/ui/src/components/ui/table/table.d.ts +15 -1
  240. package/dist/ui/src/components/ui/table/table.js +81 -0
  241. package/dist/ui/src/components/ui/table/table.js.map +1 -0
  242. package/dist/ui/src/components/ui/tabs/tabs.d.ts +14 -8
  243. package/dist/ui/src/components/ui/tabs/tabs.js +115 -0
  244. package/dist/ui/src/components/ui/tabs/tabs.js.map +1 -0
  245. package/dist/ui/src/components/ui/textarea/textarea.js +14 -0
  246. package/dist/ui/src/components/ui/textarea/textarea.js.map +1 -0
  247. package/dist/ui/src/components/ui/toast/toast.js +124 -0
  248. package/dist/ui/src/components/ui/toast/toast.js.map +1 -0
  249. package/dist/ui/src/components/ui/toggle/toggle.js +37 -0
  250. package/dist/ui/src/components/ui/toggle/toggle.js.map +1 -0
  251. package/dist/ui/src/components/ui/toggle-group/toggle-group.js +56 -0
  252. package/dist/ui/src/components/ui/toggle-group/toggle-group.js.map +1 -0
  253. package/dist/ui/src/components/ui/tooltip/tooltip.js +43 -0
  254. package/dist/ui/src/components/ui/tooltip/tooltip.js.map +1 -0
  255. package/dist/ui/src/hooks/use-holds-focusable.d.ts +3 -0
  256. package/dist/ui/src/hooks/use-holds-focusable.js +33 -0
  257. package/dist/ui/src/hooks/use-holds-focusable.js.map +1 -0
  258. package/dist/ui/src/hooks/use-media-player.js +140 -0
  259. package/dist/ui/src/hooks/use-media-player.js.map +1 -0
  260. package/dist/ui/src/hooks/use-merged-refs.js +18 -0
  261. package/dist/ui/src/hooks/use-merged-refs.js.map +1 -0
  262. package/dist/ui/src/hooks/use-mobile.js +20 -0
  263. package/dist/ui/src/hooks/use-mobile.js.map +1 -0
  264. package/dist/ui/src/index.d.ts +8 -0
  265. package/dist/ui/src/lib/first-weekday.d.ts +6 -0
  266. package/dist/ui/src/lib/first-weekday.js +34 -0
  267. package/dist/ui/src/lib/first-weekday.js.map +1 -0
  268. package/dist/ui/src/lib/focus-guards.d.ts +2 -0
  269. package/dist/ui/src/lib/focus-guards.js +44 -0
  270. package/dist/ui/src/lib/focus-guards.js.map +1 -0
  271. package/dist/ui/src/lib/focus-ring.test.d.ts +8 -0
  272. package/dist/ui/src/lib/popup-list.d.ts +75 -0
  273. package/dist/ui/src/lib/popup-list.js +48 -0
  274. package/dist/ui/src/lib/popup-list.js.map +1 -0
  275. package/dist/ui/src/lib/popup-list.test.d.ts +10 -0
  276. package/dist/ui/src/lib/utils.js +21 -0
  277. package/dist/ui/src/lib/utils.js.map +1 -0
  278. package/package.json +36 -8
  279. package/dist/chunks/toggle-group-ByNq8g7H.js +0 -4263
  280. package/dist/chunks/toggle-group-ByNq8g7H.js.map +0 -1
  281. package/dist/dom/src/context.d.ts +0 -34
  282. package/dist/dom/src/define-element.d.ts +0 -11
  283. package/dist/dom/src/field-control.d.ts +0 -5
  284. package/dist/dom/src/index.d.ts +0 -6
  285. package/dist/dom/src/mode.d.ts +0 -8
  286. package/dist/dom/src/styles.d.ts +0 -6
  287. package/dist/dom/src/theme-state.d.ts +0 -1
  288. package/dist/dom/src/types.d.ts +0 -65
  289. package/dist/ds/src/elements.d.ts +0 -12
  290. package/dist/elements/src/edx-accordion.d.ts +0 -14
  291. package/dist/elements/src/edx-accordion.test.d.ts +0 -1
  292. package/dist/elements/src/edx-alert.d.ts +0 -14
  293. package/dist/elements/src/edx-audio-player.d.ts +0 -11
  294. package/dist/elements/src/edx-avatar.d.ts +0 -10
  295. package/dist/elements/src/edx-badge.d.ts +0 -12
  296. package/dist/elements/src/edx-breadcrumb.d.ts +0 -11
  297. package/dist/elements/src/edx-button-group.d.ts +0 -7
  298. package/dist/elements/src/edx-button.d.ts +0 -20
  299. package/dist/elements/src/edx-calendar.d.ts +0 -21
  300. package/dist/elements/src/edx-card.d.ts +0 -8
  301. package/dist/elements/src/edx-checkbox.d.ts +0 -18
  302. package/dist/elements/src/edx-combobox.d.ts +0 -28
  303. package/dist/elements/src/edx-content-components.d.ts +0 -2
  304. package/dist/elements/src/edx-dialog.d.ts +0 -20
  305. package/dist/elements/src/edx-drawer.d.ts +0 -21
  306. package/dist/elements/src/edx-dropdown-menu.d.ts +0 -29
  307. package/dist/elements/src/edx-field.d.ts +0 -9
  308. package/dist/elements/src/edx-field.test.d.ts +0 -1
  309. package/dist/elements/src/edx-indicator.d.ts +0 -14
  310. package/dist/elements/src/edx-input-group.d.ts +0 -32
  311. package/dist/elements/src/edx-input-otp.d.ts +0 -19
  312. package/dist/elements/src/edx-input.d.ts +0 -31
  313. package/dist/elements/src/edx-interactive-components.d.ts +0 -2
  314. package/dist/elements/src/edx-label.d.ts +0 -6
  315. package/dist/elements/src/edx-pagination.d.ts +0 -8
  316. package/dist/elements/src/edx-popover.d.ts +0 -24
  317. package/dist/elements/src/edx-progress.d.ts +0 -10
  318. package/dist/elements/src/edx-radio-group.d.ts +0 -22
  319. package/dist/elements/src/edx-radio-group.test.d.ts +0 -1
  320. package/dist/elements/src/edx-scroll-area.d.ts +0 -15
  321. package/dist/elements/src/edx-scroll-area.test.d.ts +0 -1
  322. package/dist/elements/src/edx-select.d.ts +0 -34
  323. package/dist/elements/src/edx-separator.d.ts +0 -10
  324. package/dist/elements/src/edx-skeleton.d.ts +0 -8
  325. package/dist/elements/src/edx-slider.d.ts +0 -17
  326. package/dist/elements/src/edx-slider.test.d.ts +0 -1
  327. package/dist/elements/src/edx-switch.d.ts +0 -18
  328. package/dist/elements/src/edx-table.d.ts +0 -13
  329. package/dist/elements/src/edx-table.test.d.ts +0 -1
  330. package/dist/elements/src/edx-tabs.d.ts +0 -15
  331. package/dist/elements/src/edx-tabs.test.d.ts +0 -1
  332. package/dist/elements/src/edx-textarea.d.ts +0 -25
  333. package/dist/elements/src/edx-toggle-group.d.ts +0 -27
  334. package/dist/elements/src/edx-toggle-group.test.d.ts +0 -1
  335. package/dist/elements/src/edx-toggle.d.ts +0 -18
  336. package/dist/elements/src/edx-tooltip.d.ts +0 -28
  337. package/dist/elements/src/edx-video-player.d.ts +0 -16
  338. package/dist/elements/src/field-control-bridge.d.ts +0 -7
  339. package/dist/elements/src/index.d.ts +0 -14
  340. package/dist/elements/src/projected-content.test.d.ts +0 -1
  341. package/dist/elements/src/registry.d.ts +0 -39
  342. package/dist/elements/src/registry.test.d.ts +0 -1
  343. package/dist/elements.js +0 -4020
  344. package/dist/elements.js.map +0 -1
  345. /package/dist/{dom/src/define-element.test.d.ts → ui/src/components/edyn/date-picker/date-text.test.d.ts} +0 -0
  346. /package/dist/{dom/src/native-slot.test.d.ts → ui/src/lib/first-weekday.test.d.ts} +0 -0
@@ -0,0 +1,9 @@
1
+ /**
2
+ * DateRangePicker: the day arithmetic and the trigger's text.
3
+ *
4
+ * PATH TAKEN: converting a calendar day to a Date and back, and how the ends are
5
+ * written, are plain logic and run in happy-dom. Opening the calendar, picking two
6
+ * days and the one-month layout on a phone need a real browser and layout, so
7
+ * e2e/date-range-picker.spec.ts covers those.
8
+ */
9
+ export {};
@@ -0,0 +1 @@
1
+ export * from './date-range-picker';
@@ -0,0 +1,90 @@
1
+ import { ComponentPropsWithoutRef, ReactNode } from 'react';
2
+ export type TFileRejectReason = 'type' | 'size' | 'count';
3
+ export interface IFileRejection {
4
+ file: File;
5
+ reason: TFileRejectReason;
6
+ }
7
+ /** The words a FileInput says and announces. Pass the ones to change. */
8
+ export interface IFileInputLabels {
9
+ /** The prompt in the drop area when it takes one file, before the "browse" link. */
10
+ dropOne: string;
11
+ /** The same, when it takes several. */
12
+ dropMany: string;
13
+ browse: string;
14
+ /** The accessible name of the file chooser itself, when the caller gives it no name of its own. */
15
+ chooseOne: string;
16
+ chooseMany: string;
17
+ /** The accessible name of the list of chosen files. */
18
+ list: string;
19
+ remove: (name: string) => string;
20
+ /** Announced when files are added. */
21
+ added: (count: number) => string;
22
+ /** Announced when a file is taken out. */
23
+ removed: (name: string) => string;
24
+ /** Said, and shown, for a file that was refused. */
25
+ rejection: (name: string, reason: TFileRejectReason, limit: string) => string;
26
+ }
27
+ export declare const DEFAULT_FILE_INPUT_LABELS: IFileInputLabels;
28
+ /** 1,536 bytes as "1.5 KB": the size a person reads next to a file's name. */
29
+ export declare const formatBytes: (bytes: number) => string;
30
+ /** Whether a file is one `accept` allows: an extension (`.pdf`), a type (`application/pdf`) or a family of types (`image/*`). No `accept` allows all. */
31
+ export declare const matchesAccept: (file: {
32
+ name: string;
33
+ type: string;
34
+ }, accept: string | undefined) => boolean;
35
+ export interface ISortFilesInput {
36
+ incoming: readonly File[];
37
+ current: readonly File[];
38
+ multiple: boolean;
39
+ accept?: string;
40
+ maxSize?: number;
41
+ maxFiles?: number;
42
+ }
43
+ /**
44
+ * Which of the files just offered are taken, and which are refused and why. A file already chosen is skipped without a word. With one
45
+ * file allowed, the first acceptable one replaces what was chosen and the rest are refused as too many.
46
+ */
47
+ export declare const sortFiles: ({ incoming, current, multiple, accept, maxSize, maxFiles, }: ISortFilesInput) => {
48
+ accepted: File[];
49
+ rejected: IFileRejection[];
50
+ };
51
+ export interface IFileInputProps extends Omit<ComponentPropsWithoutRef<'div'>, 'defaultValue' | 'onChange' | 'children' | 'id'> {
52
+ /** The chosen files. Leave it out and the field keeps them itself. */
53
+ value?: readonly File[];
54
+ defaultValue?: readonly File[];
55
+ onValueChange?: (files: File[]) => void;
56
+ /** What the picker offers, and what a drop is held to: extensions (`.pdf`), types (`application/pdf`) or families (`image/*`). */
57
+ accept?: string;
58
+ /** Several files, added to each other, instead of one that is replaced. */
59
+ multiple?: boolean;
60
+ /** The most a file may weigh, in bytes. */
61
+ maxSize?: number;
62
+ /** The most files that may be chosen. Several-file fields only. */
63
+ maxFiles?: number;
64
+ /** Called with the files that were refused, each with the reason. They are also listed under the field. */
65
+ onReject?: (rejections: IFileRejection[]) => void;
66
+ disabled?: boolean;
67
+ invalid?: boolean;
68
+ required?: boolean;
69
+ /** Submits the chosen files with a form, under this name. */
70
+ name?: string;
71
+ /** For a `<Label htmlFor>` to name the field. */
72
+ id?: string;
73
+ /** What the field accepts, in words ("PDF or PNG, up to 5 MB"), read with the field. */
74
+ hint?: ReactNode;
75
+ labels?: Partial<IFileInputLabels>;
76
+ 'aria-label'?: string;
77
+ 'aria-labelledby'?: string;
78
+ }
79
+ /**
80
+ * Chooses files by picking them or by dropping them on the field, in the kit's look. It exists so an app does not fall back to
81
+ * `<input type="file">`, which cannot be styled and has no list of what was chosen.
82
+ *
83
+ * The control is a real file input laid over the whole drop area, so Tab, Enter and Space, the system's file dialog, a drop and a
84
+ * screen reader's name for it all come from the browser. What the browser lacks is added around it: the list of chosen files with a
85
+ * button to take each out, the checks against `accept`, `maxSize` and `maxFiles` (a drop skips the dialog's own filter), and a
86
+ * live region that says what was added, removed or refused.
87
+ *
88
+ * Name the field with `aria-label`, `aria-labelledby` or a `<Label htmlFor={id}>`; with none of those it is named "Choose files".
89
+ */
90
+ export declare const FileInput: ({ value, defaultValue, onValueChange, accept, multiple, maxSize, maxFiles, onReject, disabled, invalid, required, name, id, hint, labels: labelOverrides, className, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, ...rest }: IFileInputProps) => import('react').JSX.Element;
@@ -0,0 +1,261 @@
1
+ import { cn } from "../../../lib/utils.js";
2
+ import { Button } from "../../ui/button/button.js";
3
+ import { useEffect, useMemo, useRef, useState } from "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { IconFile, IconUpload, IconX } from "@tabler/icons-react";
6
+ //#region ../ui/src/components/edyn/file-input/file-input.tsx
7
+ var DEFAULT_FILE_INPUT_LABELS = {
8
+ dropOne: "Drag a file here or",
9
+ dropMany: "Drag files here or",
10
+ browse: "browse",
11
+ chooseOne: "Choose a file",
12
+ chooseMany: "Choose files",
13
+ list: "Chosen files",
14
+ remove: (name) => `Remove ${name}`,
15
+ added: (count) => count === 1 ? "1 file added" : `${count} files added`,
16
+ removed: (name) => `${name} removed`,
17
+ rejection: (name, reason, limit) => {
18
+ if (reason === "type") return `${name} was not added: this type is not accepted`;
19
+ if (reason === "size") return `${name} was not added: it is larger than ${limit}`;
20
+ return `${name} was not added: ${limit === "1" ? "1 file is" : `${limit} files are`} the most`;
21
+ }
22
+ };
23
+ var UNITS = [
24
+ "B",
25
+ "KB",
26
+ "MB",
27
+ "GB"
28
+ ];
29
+ /** 1,536 bytes as "1.5 KB": the size a person reads next to a file's name. */
30
+ var formatBytes = (bytes) => {
31
+ let size = bytes;
32
+ let unit = 0;
33
+ while (size >= 1024 && unit < UNITS.length - 1) {
34
+ size /= 1024;
35
+ unit += 1;
36
+ }
37
+ return `${unit === 0 || size >= 10 ? String(Math.round(size)) : size.toFixed(1).replace(/\.0$/, "")} ${UNITS[unit]}`;
38
+ };
39
+ /** Whether a file is one `accept` allows: an extension (`.pdf`), a type (`application/pdf`) or a family of types (`image/*`). No `accept` allows all. */
40
+ var matchesAccept = (file, accept) => {
41
+ const tokens = (accept ?? "").split(",").map((token) => token.trim().toLowerCase()).filter((token) => token !== "");
42
+ if (tokens.length === 0) return true;
43
+ const name = file.name.toLowerCase();
44
+ const type = file.type.toLowerCase();
45
+ return tokens.some((token) => {
46
+ if (token.startsWith(".")) return name.endsWith(token);
47
+ if (token.endsWith("/*")) return type.startsWith(token.slice(0, -1));
48
+ return type === token;
49
+ });
50
+ };
51
+ var sameFile = (a, b) => a.name === b.name && a.size === b.size && a.lastModified === b.lastModified;
52
+ /**
53
+ * Which of the files just offered are taken, and which are refused and why. A file already chosen is skipped without a word. With one
54
+ * file allowed, the first acceptable one replaces what was chosen and the rest are refused as too many.
55
+ */
56
+ var sortFiles = ({ incoming, current, multiple, accept, maxSize, maxFiles }) => {
57
+ const accepted = [];
58
+ const rejected = [];
59
+ const limit = multiple ? maxFiles : 1;
60
+ for (const file of incoming) if (!matchesAccept(file, accept)) rejected.push({
61
+ file,
62
+ reason: "type"
63
+ });
64
+ else if (maxSize !== void 0 && file.size > maxSize) rejected.push({
65
+ file,
66
+ reason: "size"
67
+ });
68
+ else if (current.some((chosen) => sameFile(chosen, file)) || accepted.some((taken) => sameFile(taken, file))) {} else if (limit !== void 0 && (multiple ? current.length : 0) + accepted.length >= limit) rejected.push({
69
+ file,
70
+ reason: "count"
71
+ });
72
+ else accepted.push(file);
73
+ return {
74
+ accepted,
75
+ rejected
76
+ };
77
+ };
78
+ var limitText = (reason, maxSize, maxFiles, multiple) => {
79
+ if (reason === "size") return formatBytes(maxSize ?? 0);
80
+ return String(multiple ? maxFiles ?? 0 : 1);
81
+ };
82
+ /**
83
+ * Chooses files by picking them or by dropping them on the field, in the kit's look. It exists so an app does not fall back to
84
+ * `<input type="file">`, which cannot be styled and has no list of what was chosen.
85
+ *
86
+ * The control is a real file input laid over the whole drop area, so Tab, Enter and Space, the system's file dialog, a drop and a
87
+ * screen reader's name for it all come from the browser. What the browser lacks is added around it: the list of chosen files with a
88
+ * button to take each out, the checks against `accept`, `maxSize` and `maxFiles` (a drop skips the dialog's own filter), and a
89
+ * live region that says what was added, removed or refused.
90
+ *
91
+ * Name the field with `aria-label`, `aria-labelledby` or a `<Label htmlFor={id}>`; with none of those it is named "Choose files".
92
+ */
93
+ var FileInput = ({ value, defaultValue = [], onValueChange, accept, multiple = false, maxSize, maxFiles, onReject, disabled = false, invalid = false, required = false, name, id, hint, labels: labelOverrides, className, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, ...rest }) => {
94
+ const labels = useMemo(() => ({
95
+ ...DEFAULT_FILE_INPUT_LABELS,
96
+ ...labelOverrides
97
+ }), [labelOverrides]);
98
+ const [inner, setInner] = useState(defaultValue);
99
+ const files = value ?? inner;
100
+ const [dragging, setDragging] = useState(false);
101
+ const [rejections, setRejections] = useState([]);
102
+ const [announcement, setAnnouncement] = useState("");
103
+ const refInput = useRef(null);
104
+ const hintId = `${id ?? "file-input"}-hint`;
105
+ useEffect(() => {
106
+ const input = refInput.current;
107
+ if (input === null || typeof DataTransfer === "undefined") return;
108
+ const transfer = new DataTransfer();
109
+ for (const file of files) transfer.items.add(file);
110
+ input.files = transfer.files;
111
+ }, [files]);
112
+ const commit = (next) => {
113
+ setInner(next);
114
+ onValueChange?.(next);
115
+ };
116
+ const take = (incoming) => {
117
+ if (incoming.length === 0) return;
118
+ const { accepted, rejected } = sortFiles({
119
+ incoming,
120
+ current: files,
121
+ multiple,
122
+ accept,
123
+ maxSize,
124
+ maxFiles
125
+ });
126
+ setRejections(rejected);
127
+ if (rejected.length > 0) onReject?.(rejected);
128
+ if (accepted.length > 0) {
129
+ commit(multiple ? [...files, ...accepted] : accepted);
130
+ setAnnouncement(labels.added(accepted.length));
131
+ } else setAnnouncement("");
132
+ };
133
+ const remove = (index) => {
134
+ const gone = files[index];
135
+ if (gone === void 0) return;
136
+ setRejections([]);
137
+ commit(files.filter((_, at) => at !== index));
138
+ setAnnouncement(labels.removed(gone.name));
139
+ refInput.current?.focus();
140
+ };
141
+ const onChange = (event) => {
142
+ take([...event.target.files ?? []]);
143
+ };
144
+ const carriesFiles = (event) => event.dataTransfer.types.includes("Files");
145
+ const onDrag = (event) => {
146
+ if (disabled || !carriesFiles(event)) return;
147
+ event.preventDefault();
148
+ setDragging(true);
149
+ };
150
+ const onDrop = (event) => {
151
+ if (disabled || !carriesFiles(event)) return;
152
+ event.preventDefault();
153
+ setDragging(false);
154
+ take([...event.dataTransfer.files]);
155
+ };
156
+ const named = ariaLabel !== void 0 || ariaLabelledBy !== void 0 || id !== void 0;
157
+ return /* @__PURE__ */ jsxs("div", {
158
+ "data-slot": "file-input",
159
+ className: cn("flex w-full flex-col gap-2", className),
160
+ ...rest,
161
+ children: [
162
+ /* @__PURE__ */ jsxs("div", {
163
+ "data-slot": "file-input-zone",
164
+ role: "presentation",
165
+ "data-dragging": dragging || void 0,
166
+ className: cn("relative flex flex-col items-center justify-center gap-1 rounded-lg border border-dashed border-control px-4 py-6 text-center text-sm transition-colors dark:bg-input/30", "hover:bg-foreground/4 has-[input:focus-visible]:outline-solid", "data-dragging:border-ring data-dragging:bg-foreground/6", invalid && "border-destructive ring-3 ring-destructive/20 dark:border-destructive/50 dark:ring-destructive/40", disabled && "pointer-events-none cursor-not-allowed border-border"),
167
+ onDragEnter: onDrag,
168
+ onDragOver: onDrag,
169
+ onDragLeave: (event) => {
170
+ if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
171
+ },
172
+ onDrop,
173
+ children: [
174
+ /* @__PURE__ */ jsx(IconUpload, {
175
+ "aria-hidden": "true",
176
+ className: "size-5 text-muted-foreground"
177
+ }),
178
+ /* @__PURE__ */ jsxs("p", {
179
+ className: disabled ? "text-muted-foreground" : "text-foreground",
180
+ children: [
181
+ multiple ? labels.dropMany : labels.dropOne,
182
+ " ",
183
+ /* @__PURE__ */ jsx("span", {
184
+ className: "font-medium underline underline-offset-3",
185
+ children: labels.browse
186
+ })
187
+ ]
188
+ }),
189
+ hint !== void 0 && /* @__PURE__ */ jsx("p", {
190
+ id: hintId,
191
+ className: "text-xs text-muted-foreground",
192
+ children: hint
193
+ }),
194
+ /* @__PURE__ */ jsx("input", {
195
+ ref: refInput,
196
+ id,
197
+ type: "file",
198
+ name,
199
+ accept,
200
+ multiple,
201
+ disabled,
202
+ required: required && files.length === 0,
203
+ "aria-label": named ? ariaLabel : multiple ? labels.chooseMany : labels.chooseOne,
204
+ "aria-labelledby": ariaLabelledBy,
205
+ "aria-describedby": hint === void 0 ? void 0 : hintId,
206
+ "aria-invalid": invalid || void 0,
207
+ className: "absolute inset-0 size-full cursor-pointer opacity-0 disabled:cursor-not-allowed",
208
+ onChange
209
+ })
210
+ ]
211
+ }),
212
+ files.length > 0 && /* @__PURE__ */ jsx("ul", {
213
+ "data-slot": "file-input-list",
214
+ "aria-label": labels.list,
215
+ className: "flex flex-col gap-1",
216
+ children: files.map((file, index) => /* @__PURE__ */ jsxs("li", {
217
+ className: "flex items-center gap-2 rounded-lg border border-control py-1 pr-1 pl-2 text-sm",
218
+ children: [
219
+ /* @__PURE__ */ jsx(IconFile, {
220
+ "aria-hidden": "true",
221
+ className: "size-4 shrink-0 text-muted-foreground"
222
+ }),
223
+ /* @__PURE__ */ jsx("span", {
224
+ className: "min-w-0 flex-1 truncate",
225
+ children: file.name
226
+ }),
227
+ /* @__PURE__ */ jsx("span", {
228
+ className: "shrink-0 text-xs text-muted-foreground tabular-nums",
229
+ children: formatBytes(file.size)
230
+ }),
231
+ /* @__PURE__ */ jsx(Button, {
232
+ type: "button",
233
+ variant: "ghost",
234
+ size: "icon-xs",
235
+ disabled,
236
+ "aria-label": labels.remove(file.name),
237
+ onClick: () => remove(index),
238
+ children: /* @__PURE__ */ jsx(IconX, { "aria-hidden": "true" })
239
+ })
240
+ ]
241
+ }, `${file.name}-${file.size}-${file.lastModified}`))
242
+ }),
243
+ /* @__PURE__ */ jsxs("div", {
244
+ role: "status",
245
+ "data-slot": "file-input-status",
246
+ className: "text-sm empty:hidden",
247
+ children: [rejections.map(({ file, reason }) => /* @__PURE__ */ jsx("p", {
248
+ className: "text-destructive",
249
+ children: labels.rejection(file.name, reason, limitText(reason, maxSize, maxFiles, multiple))
250
+ }, `${file.name}-${reason}`)), announcement !== "" && /* @__PURE__ */ jsx("span", {
251
+ className: "sr-only",
252
+ children: announcement
253
+ })]
254
+ })
255
+ ]
256
+ });
257
+ };
258
+ //#endregion
259
+ export { DEFAULT_FILE_INPUT_LABELS, FileInput, formatBytes, matchesAccept, sortFiles };
260
+
261
+ //# sourceMappingURL=file-input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-input.js","names":[],"sources":["../../../../../../../ui/src/components/edyn/file-input/file-input.tsx"],"sourcesContent":["import { Button } from '@edyn/ui/components/ui/button';\nimport { cn } from '@edyn/ui/lib/utils';\nimport { IconFile, IconUpload, IconX } from '@tabler/icons-react';\nimport { type ChangeEvent, type ComponentPropsWithoutRef, type DragEvent, type ReactNode, useEffect, useMemo, useRef, useState } from 'react';\n\nexport type TFileRejectReason = 'type' | 'size' | 'count';\n\nexport interface IFileRejection {\n file: File;\n reason: TFileRejectReason;\n}\n\n/** The words a FileInput says and announces. Pass the ones to change. */\nexport interface IFileInputLabels {\n /** The prompt in the drop area when it takes one file, before the \"browse\" link. */\n dropOne: string;\n /** The same, when it takes several. */\n dropMany: string;\n browse: string;\n /** The accessible name of the file chooser itself, when the caller gives it no name of its own. */\n chooseOne: string;\n chooseMany: string;\n /** The accessible name of the list of chosen files. */\n list: string;\n remove: (name: string) => string;\n /** Announced when files are added. */\n added: (count: number) => string;\n /** Announced when a file is taken out. */\n removed: (name: string) => string;\n /** Said, and shown, for a file that was refused. */\n rejection: (name: string, reason: TFileRejectReason, limit: string) => string;\n}\n\nexport const DEFAULT_FILE_INPUT_LABELS: IFileInputLabels = {\n dropOne: 'Drag a file here or',\n dropMany: 'Drag files here or',\n browse: 'browse',\n chooseOne: 'Choose a file',\n chooseMany: 'Choose files',\n list: 'Chosen files',\n remove: (name) => `Remove ${name}`,\n added: (count) => (count === 1 ? '1 file added' : `${count} files added`),\n removed: (name) => `${name} removed`,\n rejection: (name, reason, limit) => {\n if (reason === 'type') {\n return `${name} was not added: this type is not accepted`;\n }\n if (reason === 'size') {\n return `${name} was not added: it is larger than ${limit}`;\n }\n return `${name} was not added: ${limit === '1' ? '1 file is' : `${limit} files are`} the most`;\n },\n};\n\nconst UNITS = ['B', 'KB', 'MB', 'GB'] as const;\n\n/** 1,536 bytes as \"1.5 KB\": the size a person reads next to a file's name. */\nexport const formatBytes = (bytes: number): string => {\n let size = bytes;\n let unit = 0;\n while (size >= 1024 && unit < UNITS.length - 1) {\n size /= 1024;\n unit += 1;\n }\n const text = unit === 0 || size >= 10 ? String(Math.round(size)) : size.toFixed(1).replace(/\\.0$/, '');\n return `${text} ${UNITS[unit]}`;\n};\n\n/** Whether a file is one `accept` allows: an extension (`.pdf`), a type (`application/pdf`) or a family of types (`image/*`). No `accept` allows all. */\nexport const matchesAccept = (file: { name: string; type: string }, accept: string | undefined): boolean => {\n const tokens = (accept ?? '')\n .split(',')\n .map((token) => token.trim().toLowerCase())\n .filter((token) => token !== '');\n if (tokens.length === 0) {\n return true;\n }\n const name = file.name.toLowerCase();\n const type = file.type.toLowerCase();\n return tokens.some((token) => {\n if (token.startsWith('.')) {\n return name.endsWith(token);\n }\n if (token.endsWith('/*')) {\n return type.startsWith(token.slice(0, -1));\n }\n return type === token;\n });\n};\n\nconst sameFile = (a: File, b: File): boolean => a.name === b.name && a.size === b.size && a.lastModified === b.lastModified;\n\nexport interface ISortFilesInput {\n incoming: readonly File[];\n current: readonly File[];\n multiple: boolean;\n accept?: string;\n maxSize?: number;\n maxFiles?: number;\n}\n\n/**\n * Which of the files just offered are taken, and which are refused and why. A file already chosen is skipped without a word. With one\n * file allowed, the first acceptable one replaces what was chosen and the rest are refused as too many.\n */\nexport const sortFiles = ({\n incoming,\n current,\n multiple,\n accept,\n maxSize,\n maxFiles,\n}: ISortFilesInput): { accepted: File[]; rejected: IFileRejection[] } => {\n const accepted: File[] = [];\n const rejected: IFileRejection[] = [];\n const limit = multiple ? maxFiles : 1;\n for (const file of incoming) {\n if (!matchesAccept(file, accept)) {\n rejected.push({ file, reason: 'type' });\n } else if (maxSize !== undefined && file.size > maxSize) {\n rejected.push({ file, reason: 'size' });\n } else if (current.some((chosen) => sameFile(chosen, file)) || accepted.some((taken) => sameFile(taken, file))) {\n } else if (limit !== undefined && (multiple ? current.length : 0) + accepted.length >= limit) {\n rejected.push({ file, reason: 'count' });\n } else {\n accepted.push(file);\n }\n }\n return { accepted, rejected };\n};\n\nexport interface IFileInputProps extends Omit<ComponentPropsWithoutRef<'div'>, 'defaultValue' | 'onChange' | 'children' | 'id'> {\n /** The chosen files. Leave it out and the field keeps them itself. */\n value?: readonly File[];\n defaultValue?: readonly File[];\n onValueChange?: (files: File[]) => void;\n /** What the picker offers, and what a drop is held to: extensions (`.pdf`), types (`application/pdf`) or families (`image/*`). */\n accept?: string;\n /** Several files, added to each other, instead of one that is replaced. */\n multiple?: boolean;\n /** The most a file may weigh, in bytes. */\n maxSize?: number;\n /** The most files that may be chosen. Several-file fields only. */\n maxFiles?: number;\n /** Called with the files that were refused, each with the reason. They are also listed under the field. */\n onReject?: (rejections: IFileRejection[]) => void;\n disabled?: boolean;\n invalid?: boolean;\n required?: boolean;\n /** Submits the chosen files with a form, under this name. */\n name?: string;\n /** For a `<Label htmlFor>` to name the field. */\n id?: string;\n /** What the field accepts, in words (\"PDF or PNG, up to 5 MB\"), read with the field. */\n hint?: ReactNode;\n labels?: Partial<IFileInputLabels>;\n 'aria-label'?: string;\n 'aria-labelledby'?: string;\n}\n\nconst limitText = (reason: TFileRejectReason, maxSize: number | undefined, maxFiles: number | undefined, multiple: boolean): string => {\n if (reason === 'size') {\n return formatBytes(maxSize ?? 0);\n }\n return String(multiple ? (maxFiles ?? 0) : 1);\n};\n\n/**\n * Chooses files by picking them or by dropping them on the field, in the kit's look. It exists so an app does not fall back to\n * `<input type=\"file\">`, which cannot be styled and has no list of what was chosen.\n *\n * The control is a real file input laid over the whole drop area, so Tab, Enter and Space, the system's file dialog, a drop and a\n * screen reader's name for it all come from the browser. What the browser lacks is added around it: the list of chosen files with a\n * button to take each out, the checks against `accept`, `maxSize` and `maxFiles` (a drop skips the dialog's own filter), and a\n * live region that says what was added, removed or refused.\n *\n * Name the field with `aria-label`, `aria-labelledby` or a `<Label htmlFor={id}>`; with none of those it is named \"Choose files\".\n */\nexport const FileInput = ({\n value,\n defaultValue = [],\n onValueChange,\n accept,\n multiple = false,\n maxSize,\n maxFiles,\n onReject,\n disabled = false,\n invalid = false,\n required = false,\n name,\n id,\n hint,\n labels: labelOverrides,\n className,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n ...rest\n}: IFileInputProps) => {\n const labels = useMemo(() => ({ ...DEFAULT_FILE_INPUT_LABELS, ...labelOverrides }), [labelOverrides]);\n const [inner, setInner] = useState<readonly File[]>(defaultValue);\n const files = value ?? inner;\n const [dragging, setDragging] = useState(false);\n const [rejections, setRejections] = useState<IFileRejection[]>([]);\n const [announcement, setAnnouncement] = useState('');\n const refInput = useRef<HTMLInputElement | null>(null);\n const hintId = `${id ?? 'file-input'}-hint`;\n\n // The input's own list follows the chosen files, so a form submits exactly what the list shows.\n useEffect(() => {\n const input = refInput.current;\n if (input === null || typeof DataTransfer === 'undefined') {\n return;\n }\n const transfer = new DataTransfer();\n for (const file of files) {\n transfer.items.add(file);\n }\n input.files = transfer.files;\n }, [files]);\n\n const commit = (next: File[]): void => {\n setInner(next);\n onValueChange?.(next);\n };\n\n const take = (incoming: File[]): void => {\n if (incoming.length === 0) {\n return;\n }\n const { accepted, rejected } = sortFiles({ incoming, current: files, multiple, accept, maxSize, maxFiles });\n setRejections(rejected);\n if (rejected.length > 0) {\n onReject?.(rejected);\n }\n if (accepted.length > 0) {\n commit(multiple ? [...files, ...accepted] : accepted);\n setAnnouncement(labels.added(accepted.length));\n } else {\n setAnnouncement('');\n }\n };\n\n const remove = (index: number): void => {\n const gone = files[index];\n if (gone === undefined) {\n return;\n }\n setRejections([]);\n commit(files.filter((_, at) => at !== index));\n setAnnouncement(labels.removed(gone.name));\n refInput.current?.focus();\n };\n\n const onChange = (event: ChangeEvent<HTMLInputElement>): void => {\n take([...(event.target.files ?? [])]);\n };\n\n const carriesFiles = (event: DragEvent): boolean => event.dataTransfer.types.includes('Files');\n\n const onDrag = (event: DragEvent<HTMLDivElement>): void => {\n if (disabled || !carriesFiles(event)) {\n return;\n }\n event.preventDefault();\n setDragging(true);\n };\n\n const onDrop = (event: DragEvent<HTMLDivElement>): void => {\n if (disabled || !carriesFiles(event)) {\n return;\n }\n // The drop is handled here, once, and not again by the input underneath.\n event.preventDefault();\n setDragging(false);\n take([...event.dataTransfer.files]);\n };\n\n const named = ariaLabel !== undefined || ariaLabelledBy !== undefined || id !== undefined;\n\n return (\n <div data-slot=\"file-input\" className={cn('flex w-full flex-col gap-2', className)} {...rest}>\n {/* biome-ignore lint/a11y/noStaticElementInteractions: a drop is only the pointer's way onto the file input laid over this area, which stays the keyboard and screen-reader way. */}\n <div\n data-slot=\"file-input-zone\"\n role=\"presentation\"\n data-dragging={dragging || undefined}\n className={cn(\n 'relative flex flex-col items-center justify-center gap-1 rounded-lg border border-dashed border-control px-4 py-6 text-center text-sm transition-colors dark:bg-input/30',\n 'hover:bg-foreground/4 has-[input:focus-visible]:outline-solid',\n 'data-dragging:border-ring data-dragging:bg-foreground/6',\n invalid && 'border-destructive ring-3 ring-destructive/20 dark:border-destructive/50 dark:ring-destructive/40',\n // The prompt stays readable (4.5:1) the way a disabled Button's text does: no opacity on the text, a muted colour instead.\n disabled && 'pointer-events-none cursor-not-allowed border-border',\n )}\n onDragEnter={onDrag}\n onDragOver={onDrag}\n onDragLeave={(event) => {\n if (!event.currentTarget.contains(event.relatedTarget as Node | null)) {\n setDragging(false);\n }\n }}\n onDrop={onDrop}\n >\n <IconUpload aria-hidden=\"true\" className=\"size-5 text-muted-foreground\" />\n <p className={disabled ? 'text-muted-foreground' : 'text-foreground'}>\n {multiple ? labels.dropMany : labels.dropOne} <span className=\"font-medium underline underline-offset-3\">{labels.browse}</span>\n </p>\n {hint !== undefined && (\n <p id={hintId} className=\"text-xs text-muted-foreground\">\n {hint}\n </p>\n )}\n <input\n ref={refInput}\n id={id}\n type=\"file\"\n name={name}\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n required={required && files.length === 0}\n aria-label={named ? ariaLabel : multiple ? labels.chooseMany : labels.chooseOne}\n aria-labelledby={ariaLabelledBy}\n aria-describedby={hint === undefined ? undefined : hintId}\n aria-invalid={invalid || undefined}\n className=\"absolute inset-0 size-full cursor-pointer opacity-0 disabled:cursor-not-allowed\"\n onChange={onChange}\n />\n </div>\n {files.length > 0 && (\n <ul data-slot=\"file-input-list\" aria-label={labels.list} className=\"flex flex-col gap-1\">\n {files.map((file, index) => (\n <li\n key={`${file.name}-${file.size}-${file.lastModified}`}\n className=\"flex items-center gap-2 rounded-lg border border-control py-1 pr-1 pl-2 text-sm\"\n >\n <IconFile aria-hidden=\"true\" className=\"size-4 shrink-0 text-muted-foreground\" />\n <span className=\"min-w-0 flex-1 truncate\">{file.name}</span>\n <span className=\"shrink-0 text-xs text-muted-foreground tabular-nums\">{formatBytes(file.size)}</span>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n disabled={disabled}\n aria-label={labels.remove(file.name)}\n onClick={() => remove(index)}\n >\n <IconX aria-hidden=\"true\" />\n </Button>\n </li>\n ))}\n </ul>\n )}\n {/* One live region: it stays mounted and only its words change, which is what makes a screen reader say them. The refused files are\n the visible part; what was added or removed is for the ear. */}\n <div role=\"status\" data-slot=\"file-input-status\" className=\"text-sm empty:hidden\">\n {rejections.map(({ file, reason }) => (\n <p key={`${file.name}-${reason}`} className=\"text-destructive\">\n {labels.rejection(file.name, reason, limitText(reason, maxSize, maxFiles, multiple))}\n </p>\n ))}\n {announcement !== '' && <span className=\"sr-only\">{announcement}</span>}\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;AAiCA,IAAa,4BAA8C;CACzD,SAAS;CACT,UAAU;CACV,QAAQ;CACR,WAAW;CACX,YAAY;CACZ,MAAM;CACN,SAAS,SAAS,UAAU;CAC5B,QAAQ,UAAW,UAAU,IAAI,iBAAiB,GAAG,MAAM;CAC3D,UAAU,SAAS,GAAG,KAAK;CAC3B,YAAY,MAAM,QAAQ,UAAU;EAClC,IAAI,WAAW,QACb,OAAO,GAAG,KAAK;EAEjB,IAAI,WAAW,QACb,OAAO,GAAG,KAAK,oCAAoC;EAErD,OAAO,GAAG,KAAK,kBAAkB,UAAU,MAAM,cAAc,GAAG,MAAM,YAAY;CACtF;AACF;AAEA,IAAM,QAAQ;CAAC;CAAK;CAAM;CAAM;AAAI;;AAGpC,IAAa,eAAe,UAA0B;CACpD,IAAI,OAAO;CACX,IAAI,OAAO;CACX,OAAO,QAAQ,QAAQ,OAAO,MAAM,SAAS,GAAG;EAC9C,QAAQ;EACR,QAAQ;CACV;CAEA,OAAO,GADM,SAAS,KAAK,QAAQ,KAAK,OAAO,KAAK,MAAM,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,QAAQ,EAAE,EACtF,GAAG,MAAM;AAC1B;;AAGA,IAAa,iBAAiB,MAAsC,WAAwC;CAC1G,MAAM,UAAU,UAAU,GAAA,CACvB,MAAM,GAAG,CAAC,CACV,KAAK,UAAU,MAAM,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAC1C,QAAQ,UAAU,UAAU,EAAE;CACjC,IAAI,OAAO,WAAW,GACpB,OAAO;CAET,MAAM,OAAO,KAAK,KAAK,YAAY;CACnC,MAAM,OAAO,KAAK,KAAK,YAAY;CACnC,OAAO,OAAO,MAAM,UAAU;EAC5B,IAAI,MAAM,WAAW,GAAG,GACtB,OAAO,KAAK,SAAS,KAAK;EAE5B,IAAI,MAAM,SAAS,IAAI,GACrB,OAAO,KAAK,WAAW,MAAM,MAAM,GAAG,EAAE,CAAC;EAE3C,OAAO,SAAS;CAClB,CAAC;AACH;AAEA,IAAM,YAAY,GAAS,MAAqB,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,iBAAiB,EAAE;;;;;AAe/G,IAAa,aAAa,EACxB,UACA,SACA,UACA,QACA,SACA,eACuE;CACvE,MAAM,WAAmB,CAAC;CAC1B,MAAM,WAA6B,CAAC;CACpC,MAAM,QAAQ,WAAW,WAAW;CACpC,KAAK,MAAM,QAAQ,UACjB,IAAI,CAAC,cAAc,MAAM,MAAM,GAC7B,SAAS,KAAK;EAAE;EAAM,QAAQ;CAAO,CAAC;MACjC,IAAI,YAAY,KAAA,KAAa,KAAK,OAAO,SAC9C,SAAS,KAAK;EAAE;EAAM,QAAQ;CAAO,CAAC;MACjC,IAAI,QAAQ,MAAM,WAAW,SAAS,QAAQ,IAAI,CAAC,KAAK,SAAS,MAAM,UAAU,SAAS,OAAO,IAAI,CAAC,GAAG,CAChH,OAAO,IAAI,UAAU,KAAA,MAAc,WAAW,QAAQ,SAAS,KAAK,SAAS,UAAU,OACrF,SAAS,KAAK;EAAE;EAAM,QAAQ;CAAQ,CAAC;MAEvC,SAAS,KAAK,IAAI;CAGtB,OAAO;EAAE;EAAU;CAAS;AAC9B;AA+BA,IAAM,aAAa,QAA2B,SAA6B,UAA8B,aAA8B;CACrI,IAAI,WAAW,QACb,OAAO,YAAY,WAAW,CAAC;CAEjC,OAAO,OAAO,WAAY,YAAY,IAAK,CAAC;AAC9C;;;;;;;;;;;;AAaA,IAAa,aAAa,EACxB,OACA,eAAe,CAAC,GAChB,eACA,QACA,WAAW,OACX,SACA,UACA,UACA,WAAW,OACX,UAAU,OACV,WAAW,OACX,MACA,IACA,MACA,QAAQ,gBACR,WACA,cAAc,WACd,mBAAmB,gBACnB,GAAG,WACkB;CACrB,MAAM,SAAS,eAAe;EAAE,GAAG;EAA2B,GAAG;CAAe,IAAI,CAAC,cAAc,CAAC;CACpG,MAAM,CAAC,OAAO,YAAY,SAA0B,YAAY;CAChE,MAAM,QAAQ,SAAS;CACvB,MAAM,CAAC,UAAU,eAAe,SAAS,KAAK;CAC9C,MAAM,CAAC,YAAY,iBAAiB,SAA2B,CAAC,CAAC;CACjE,MAAM,CAAC,cAAc,mBAAmB,SAAS,EAAE;CACnD,MAAM,WAAW,OAAgC,IAAI;CACrD,MAAM,SAAS,GAAG,MAAM,aAAa;CAGrC,gBAAgB;EACd,MAAM,QAAQ,SAAS;EACvB,IAAI,UAAU,QAAQ,OAAO,iBAAiB,aAC5C;EAEF,MAAM,WAAW,IAAI,aAAa;EAClC,KAAK,MAAM,QAAQ,OACjB,SAAS,MAAM,IAAI,IAAI;EAEzB,MAAM,QAAQ,SAAS;CACzB,GAAG,CAAC,KAAK,CAAC;CAEV,MAAM,UAAU,SAAuB;EACrC,SAAS,IAAI;EACb,gBAAgB,IAAI;CACtB;CAEA,MAAM,QAAQ,aAA2B;EACvC,IAAI,SAAS,WAAW,GACtB;EAEF,MAAM,EAAE,UAAU,aAAa,UAAU;GAAE;GAAU,SAAS;GAAO;GAAU;GAAQ;GAAS;EAAS,CAAC;EAC1G,cAAc,QAAQ;EACtB,IAAI,SAAS,SAAS,GACpB,WAAW,QAAQ;EAErB,IAAI,SAAS,SAAS,GAAG;GACvB,OAAO,WAAW,CAAC,GAAG,OAAO,GAAG,QAAQ,IAAI,QAAQ;GACpD,gBAAgB,OAAO,MAAM,SAAS,MAAM,CAAC;EAC/C,OACE,gBAAgB,EAAE;CAEtB;CAEA,MAAM,UAAU,UAAwB;EACtC,MAAM,OAAO,MAAM;EACnB,IAAI,SAAS,KAAA,GACX;EAEF,cAAc,CAAC,CAAC;EAChB,OAAO,MAAM,QAAQ,GAAG,OAAO,OAAO,KAAK,CAAC;EAC5C,gBAAgB,OAAO,QAAQ,KAAK,IAAI,CAAC;EACzC,SAAS,SAAS,MAAM;CAC1B;CAEA,MAAM,YAAY,UAA+C;EAC/D,KAAK,CAAC,GAAI,MAAM,OAAO,SAAS,CAAC,CAAE,CAAC;CACtC;CAEA,MAAM,gBAAgB,UAA8B,MAAM,aAAa,MAAM,SAAS,OAAO;CAE7F,MAAM,UAAU,UAA2C;EACzD,IAAI,YAAY,CAAC,aAAa,KAAK,GACjC;EAEF,MAAM,eAAe;EACrB,YAAY,IAAI;CAClB;CAEA,MAAM,UAAU,UAA2C;EACzD,IAAI,YAAY,CAAC,aAAa,KAAK,GACjC;EAGF,MAAM,eAAe;EACrB,YAAY,KAAK;EACjB,KAAK,CAAC,GAAG,MAAM,aAAa,KAAK,CAAC;CACpC;CAEA,MAAM,QAAQ,cAAc,KAAA,KAAa,mBAAmB,KAAA,KAAa,OAAO,KAAA;CAEhF,OACE,qBAAC,OAAD;EAAK,aAAU;EAAa,WAAW,GAAG,8BAA8B,SAAS;EAAG,GAAI;EAAxF,UAAA;GAEE,qBAAC,OAAD;IACE,aAAU;IACV,MAAK;IACL,iBAAe,YAAY,KAAA;IAC3B,WAAW,GACT,4KACA,iEACA,2DACA,WAAW,qGAEX,YAAY,sDACd;IACA,aAAa;IACb,YAAY;IACZ,cAAc,UAAU;KACtB,IAAI,CAAC,MAAM,cAAc,SAAS,MAAM,aAA4B,GAClE,YAAY,KAAK;IAErB;IACQ;IAnBV,UAAA;KAqBE,oBAAC,YAAD;MAAY,eAAY;MAAO,WAAU;KAAgC,CAAA;KACzE,qBAAC,KAAD;MAAG,WAAW,WAAW,0BAA0B;MAAnD,UAAA;OACG,WAAW,OAAO,WAAW,OAAO;OAAQ;OAAC,oBAAC,QAAD;QAAM,WAAU;QAA4C,UAAA,OAAO;OAAa,CAAA;MAC7H;;KACF,SAAS,KAAA,KACR,oBAAC,KAAD;MAAG,IAAI;MAAQ,WAAU;MACtB,UAAA;KACA,CAAA;KAEL,oBAAC,SAAD;MACE,KAAK;MACD;MACJ,MAAK;MACC;MACE;MACE;MACA;MACV,UAAU,YAAY,MAAM,WAAW;MACvC,cAAY,QAAQ,YAAY,WAAW,OAAO,aAAa,OAAO;MACtE,mBAAiB;MACjB,oBAAkB,SAAS,KAAA,IAAY,KAAA,IAAY;MACnD,gBAAc,WAAW,KAAA;MACzB,WAAU;MACA;KACX,CAAA;IACE;;GACJ,MAAM,SAAS,KACd,oBAAC,MAAD;IAAI,aAAU;IAAkB,cAAY,OAAO;IAAM,WAAU;IAChE,UAAA,MAAM,KAAK,MAAM,UAChB,qBAAC,MAAD;KAEE,WAAU;KAFZ,UAAA;MAIE,oBAAC,UAAD;OAAU,eAAY;OAAO,WAAU;MAAyC,CAAA;MAChF,oBAAC,QAAD;OAAM,WAAU;OAA2B,UAAA,KAAK;MAAW,CAAA;MAC3D,oBAAC,QAAD;OAAM,WAAU;OAAuD,UAAA,YAAY,KAAK,IAAI;MAAQ,CAAA;MACpG,oBAAC,QAAD;OACE,MAAK;OACL,SAAQ;OACR,MAAK;OACK;OACV,cAAY,OAAO,OAAO,KAAK,IAAI;OACnC,eAAe,OAAO,KAAK;OAE3B,UAAA,oBAAC,OAAD,EAAO,eAAY,OAAQ,CAAA;MACrB,CAAA;KACN;IAhBG,GAAA,GAAG,KAAK,KAAK,GAAG,KAAK,KAAK,GAAG,KAAK,cAgBrC,CACL;GACC,CAAA;GAIN,qBAAC,OAAD;IAAK,MAAK;IAAS,aAAU;IAAoB,WAAU;IAA3D,UAAA,CACG,WAAW,KAAK,EAAE,MAAM,aACvB,oBAAC,KAAD;KAAkC,WAAU;KACzC,UAAA,OAAO,UAAU,KAAK,MAAM,QAAQ,UAAU,QAAQ,SAAS,UAAU,QAAQ,CAAC;IAClF,GAFK,GAAG,KAAK,KAAK,GAAG,QAErB,CACJ,GACA,iBAAiB,MAAM,oBAAC,QAAD;KAAM,WAAU;KAAW,UAAA;IAAmB,CAAA,CACnE;;EACF;;AAET"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * FileInput: which files are taken, what they weigh in words, and the list on a mounted field.
3
+ *
4
+ * PATH TAKEN: the rules (accept, size, count, duplicates) and the labels are plain functions and run in happy-dom, and so does the list
5
+ * of chosen files with its remove buttons. The system's file dialog, a real drop and the focus ring are a browser's, covered by
6
+ * e2e/file-input.spec.ts.
7
+ */
8
+ export {};
@@ -0,0 +1 @@
1
+ export * from './file-input';
@@ -0,0 +1 @@
1
+ export * from './initials-avatar';
@@ -0,0 +1,54 @@
1
+ import { ComponentPropsWithoutRef } from 'react';
2
+ /** How many colour pairs the theme defines (--avatar-1-bg, --avatar-1-fg ... in @edyn/theme tokens.css). */
3
+ export declare const AVATAR_TONES = 12;
4
+ /**
5
+ * The palette entry the text gets, from 1: the same text always gets the same one.
6
+ * FNV-1a, because a plain multiply-and-add hash puts two letters on a few entries
7
+ * only (it reduces to 7 x first + second, modulo 12).
8
+ */
9
+ export declare const avatarTone: (text: string) => number;
10
+ /** Up to two initials, upper-cased: the first letter of the first two words. */
11
+ export declare const initialsOf: (name: string) => string;
12
+ /**
13
+ * The circle and its letters together, so the initials always fit. The font size
14
+ * is inline rather than a class on purpose: 9px and 15px are not on Tailwind's
15
+ * type scale, a custom `text-*` token would be read by tailwind-merge as a colour
16
+ * and could drop the wrong class, and an inline size cannot be fought by the
17
+ * fallback's own `text-sm`.
18
+ */
19
+ declare const SIZES: {
20
+ readonly xs: {
21
+ readonly box: "size-5";
22
+ readonly fontSize: 9;
23
+ };
24
+ readonly sm: {
25
+ readonly box: "size-6";
26
+ readonly fontSize: 10;
27
+ };
28
+ readonly md: {
29
+ readonly box: "size-8";
30
+ readonly fontSize: 12;
31
+ };
32
+ readonly lg: {
33
+ readonly box: "size-10";
34
+ readonly fontSize: 15;
35
+ };
36
+ };
37
+ export type TInitialsAvatarSize = keyof typeof SIZES;
38
+ export interface IInitialsAvatarProps extends Omit<ComponentPropsWithoutRef<'span'>, 'children' | 'className' | 'role'> {
39
+ name: string;
40
+ photoUrl?: string | null;
41
+ /** xs 20px, sm 24px in table rows, md 32px in a sidebar, lg 40px in a menu's header. */
42
+ size?: TInitialsAvatarSize;
43
+ className?: string;
44
+ /** Colour the initials from the palette (on by default); off, they take the muted look. */
45
+ autoColor?: boolean;
46
+ /**
47
+ * An accessible name. Left out, the avatar is decoration, which is right beside
48
+ * the person's name; give one when it stands alone.
49
+ */
50
+ label?: string;
51
+ }
52
+ /** A person's photo, or their initials on a circle in the palette colour they pick; both themes switch by themselves. */
53
+ export declare const InitialsAvatar: ({ name, photoUrl, size, className, autoColor, label, "aria-label": ariaLabel, ...props }: IInitialsAvatarProps) => import('react').JSX.Element;
54
+ export {};
@@ -0,0 +1,86 @@
1
+ import { cn } from "../../../lib/utils.js";
2
+ import { Avatar, AvatarFallback, AvatarImage } from "../../ui/avatar/avatar.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ //#region ../ui/src/components/edyn/initials-avatar/initials-avatar.tsx
5
+ /** How many colour pairs the theme defines (--avatar-1-bg, --avatar-1-fg ... in @edyn/theme tokens.css). */
6
+ var AVATAR_TONES = 12;
7
+ /**
8
+ * The palette entry the text gets, from 1: the same text always gets the same one.
9
+ * FNV-1a, because a plain multiply-and-add hash puts two letters on a few entries
10
+ * only (it reduces to 7 x first + second, modulo 12).
11
+ */
12
+ var avatarTone = (text) => {
13
+ let hash = 2166136261;
14
+ for (const character of text) {
15
+ hash ^= character.codePointAt(0) ?? 0;
16
+ hash = Math.imul(hash, 16777619);
17
+ }
18
+ hash ^= hash >>> 16;
19
+ hash = Math.imul(hash, 2246822507);
20
+ hash ^= hash >>> 13;
21
+ hash = Math.imul(hash, 3266489909);
22
+ hash ^= hash >>> 16;
23
+ return (hash >>> 0) % 12 + 1;
24
+ };
25
+ /** Up to two initials, upper-cased: the first letter of the first two words. */
26
+ var initialsOf = (name) => name.split(/\s+/).filter((part) => part !== "").slice(0, 2).map((part) => Array.from(part)[0]?.toUpperCase() ?? "").join("");
27
+ /**
28
+ * The circle and its letters together, so the initials always fit. The font size
29
+ * is inline rather than a class on purpose: 9px and 15px are not on Tailwind's
30
+ * type scale, a custom `text-*` token would be read by tailwind-merge as a colour
31
+ * and could drop the wrong class, and an inline size cannot be fought by the
32
+ * fallback's own `text-sm`.
33
+ */
34
+ var SIZES = {
35
+ xs: {
36
+ box: "size-5",
37
+ fontSize: 9
38
+ },
39
+ sm: {
40
+ box: "size-6",
41
+ fontSize: 10
42
+ },
43
+ md: {
44
+ box: "size-8",
45
+ fontSize: 12
46
+ },
47
+ lg: {
48
+ box: "size-10",
49
+ fontSize: 15
50
+ }
51
+ };
52
+ /** A person's photo, or their initials on a circle in the palette colour they pick; both themes switch by themselves. */
53
+ var InitialsAvatar = ({ name, photoUrl, size = "md", className, autoColor = true, label, "aria-label": ariaLabel, ...props }) => {
54
+ const initials = initialsOf(name);
55
+ const tone = avatarTone(initials);
56
+ const { box, fontSize } = SIZES[size];
57
+ const style = autoColor ? {
58
+ fontSize,
59
+ backgroundColor: `var(--avatar-${tone}-bg)`,
60
+ color: `var(--avatar-${tone}-fg)`
61
+ } : { fontSize };
62
+ const accessibleName = label ?? ariaLabel;
63
+ const a11y = accessibleName === void 0 ? { "aria-hidden": true } : {
64
+ role: "img",
65
+ "aria-label": accessibleName
66
+ };
67
+ return /* @__PURE__ */ jsxs(Avatar, {
68
+ "data-slot": "initials-avatar",
69
+ ...props,
70
+ "data-tone": autoColor ? tone : void 0,
71
+ className: cn(box, className),
72
+ ...a11y,
73
+ children: [photoUrl != null && /* @__PURE__ */ jsx(AvatarImage, {
74
+ src: photoUrl,
75
+ alt: ""
76
+ }), /* @__PURE__ */ jsx(AvatarFallback, {
77
+ style,
78
+ className: "leading-none font-semibold tracking-tight",
79
+ children: initials
80
+ })]
81
+ });
82
+ };
83
+ //#endregion
84
+ export { AVATAR_TONES, InitialsAvatar, avatarTone, initialsOf };
85
+
86
+ //# sourceMappingURL=initials-avatar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"initials-avatar.js","names":[],"sources":["../../../../../../../ui/src/components/edyn/initials-avatar/initials-avatar.tsx"],"sourcesContent":["import { Avatar, AvatarFallback, AvatarImage } from '@edyn/ui/components/ui/avatar';\nimport { cn } from '@edyn/ui/lib/utils';\nimport type { ComponentPropsWithoutRef, CSSProperties } from 'react';\n\n/** How many colour pairs the theme defines (--avatar-1-bg, --avatar-1-fg ... in @edyn/theme tokens.css). */\nexport const AVATAR_TONES = 12;\n\n/**\n * The palette entry the text gets, from 1: the same text always gets the same one.\n * FNV-1a, because a plain multiply-and-add hash puts two letters on a few entries\n * only (it reduces to 7 x first + second, modulo 12).\n */\nexport const avatarTone = (text: string): number => {\n let hash = 2166136261;\n for (const character of text) {\n hash ^= character.codePointAt(0) ?? 0;\n hash = Math.imul(hash, 16777619);\n }\n // murmur3's finaliser mixes every bit into the low ones the modulo keeps; without\n // it AB, CD, EF share an entry.\n hash ^= hash >>> 16;\n hash = Math.imul(hash, 0x85ebca6b);\n hash ^= hash >>> 13;\n hash = Math.imul(hash, 0xc2b2ae35);\n hash ^= hash >>> 16;\n return ((hash >>> 0) % AVATAR_TONES) + 1;\n};\n\n/** Up to two initials, upper-cased: the first letter of the first two words. */\nexport const initialsOf = (name: string): string =>\n name\n .split(/\\s+/)\n .filter((part) => part !== '')\n .slice(0, 2)\n .map((part) => Array.from(part)[0]?.toUpperCase() ?? '')\n .join('');\n\n/**\n * The circle and its letters together, so the initials always fit. The font size\n * is inline rather than a class on purpose: 9px and 15px are not on Tailwind's\n * type scale, a custom `text-*` token would be read by tailwind-merge as a colour\n * and could drop the wrong class, and an inline size cannot be fought by the\n * fallback's own `text-sm`.\n */\nconst SIZES = {\n xs: { box: 'size-5', fontSize: 9 },\n sm: { box: 'size-6', fontSize: 10 },\n md: { box: 'size-8', fontSize: 12 },\n lg: { box: 'size-10', fontSize: 15 },\n} as const;\n\nexport type TInitialsAvatarSize = keyof typeof SIZES;\n\nexport interface IInitialsAvatarProps extends Omit<ComponentPropsWithoutRef<'span'>, 'children' | 'className' | 'role'> {\n name: string;\n photoUrl?: string | null;\n /** xs 20px, sm 24px in table rows, md 32px in a sidebar, lg 40px in a menu's header. */\n size?: TInitialsAvatarSize;\n className?: string;\n /** Colour the initials from the palette (on by default); off, they take the muted look. */\n autoColor?: boolean;\n /**\n * An accessible name. Left out, the avatar is decoration, which is right beside\n * the person's name; give one when it stands alone.\n */\n label?: string;\n}\n\n/** A person's photo, or their initials on a circle in the palette colour they pick; both themes switch by themselves. */\nexport const InitialsAvatar = ({\n name,\n photoUrl,\n size = 'md',\n className,\n autoColor = true,\n label,\n 'aria-label': ariaLabel,\n ...props\n}: IInitialsAvatarProps) => {\n const initials = initialsOf(name);\n const tone = avatarTone(initials);\n const { box, fontSize } = SIZES[size];\n const style: CSSProperties = autoColor\n ? { fontSize, backgroundColor: `var(--avatar-${tone}-bg)`, color: `var(--avatar-${tone}-fg)` }\n : { fontSize };\n const accessibleName = label ?? ariaLabel;\n const a11y = accessibleName === undefined ? ({ 'aria-hidden': true } as const) : ({ role: 'img', 'aria-label': accessibleName } as const);\n return (\n <Avatar data-slot=\"initials-avatar\" {...props} data-tone={autoColor ? tone : undefined} className={cn(box, className)} {...a11y}>\n {photoUrl != null && <AvatarImage src={photoUrl} alt=\"\" />}\n <AvatarFallback style={style} className=\"leading-none font-semibold tracking-tight\">\n {initials}\n </AvatarFallback>\n </Avatar>\n );\n};\n"],"mappings":";;;;;AAKA,IAAa,eAAe;;;;;;AAO5B,IAAa,cAAc,SAAyB;CAClD,IAAI,OAAO;CACX,KAAK,MAAM,aAAa,MAAM;EAC5B,QAAQ,UAAU,YAAY,CAAC,KAAK;EACpC,OAAO,KAAK,KAAK,MAAM,QAAQ;CACjC;CAGA,QAAQ,SAAS;CACjB,OAAO,KAAK,KAAK,MAAM,UAAU;CACjC,QAAQ,SAAS;CACjB,OAAO,KAAK,KAAK,MAAM,UAAU;CACjC,QAAQ,SAAS;CACjB,QAAS,SAAS,KAAA,KAAqB;AACzC;;AAGA,IAAa,cAAc,SACzB,KACG,MAAM,KAAK,CAAC,CACZ,QAAQ,SAAS,SAAS,EAAE,CAAC,CAC7B,MAAM,GAAG,CAAC,CAAC,CACX,KAAK,SAAS,MAAM,KAAK,IAAI,CAAC,CAAC,EAAE,EAAE,YAAY,KAAK,EAAE,CAAC,CACvD,KAAK,EAAE;;;;;;;;AASZ,IAAM,QAAQ;CACZ,IAAI;EAAE,KAAK;EAAU,UAAU;CAAE;CACjC,IAAI;EAAE,KAAK;EAAU,UAAU;CAAG;CAClC,IAAI;EAAE,KAAK;EAAU,UAAU;CAAG;CAClC,IAAI;EAAE,KAAK;EAAW,UAAU;CAAG;AACrC;;AAoBA,IAAa,kBAAkB,EAC7B,MACA,UACA,OAAO,MACP,WACA,YAAY,MACZ,OACA,cAAc,WACd,GAAG,YACuB;CAC1B,MAAM,WAAW,WAAW,IAAI;CAChC,MAAM,OAAO,WAAW,QAAQ;CAChC,MAAM,EAAE,KAAK,aAAa,MAAM;CAChC,MAAM,QAAuB,YACzB;EAAE;EAAU,iBAAiB,gBAAgB,KAAK;EAAO,OAAO,gBAAgB,KAAK;CAAM,IAC3F,EAAE,SAAS;CACf,MAAM,iBAAiB,SAAS;CAChC,MAAM,OAAO,mBAAmB,KAAA,IAAa,EAAE,eAAe,KAAK,IAAe;EAAE,MAAM;EAAO,cAAc;CAAe;CAC9H,OACE,qBAAC,QAAD;EAAQ,aAAU;EAAkB,GAAI;EAAO,aAAW,YAAY,OAAO,KAAA;EAAW,WAAW,GAAG,KAAK,SAAS;EAAG,GAAI;EAA3H,UAAA,CACG,YAAY,QAAQ,oBAAC,aAAD;GAAa,KAAK;GAAU,KAAI;EAAI,CAAA,GACzD,oBAAC,gBAAD;GAAuB;GAAO,WAAU;GACrC,UAAA;EACa,CAAA,CACV;;AAEZ"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * InitialsAvatar: a stable colour per person, initials that fit, and a palette
3
+ * that stays readable.
4
+ *
5
+ * PATH TAKEN: the hash, the initials and the markup are plain logic, so happy-dom
6
+ * covers them. The palette claim ("every pair is readable") is checked against
7
+ * the real tokens.css, converting OKLCH to sRGB and measuring WCAG contrast, so
8
+ * the numbers cannot drift from the file.
9
+ */
10
+ export {};
@@ -0,0 +1 @@
1
+ export * from './multi-select';