@eifi1/ui-kit 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (410) hide show
  1. package/LICENSE +32 -0
  2. package/README.md +498 -57
  3. package/dist/chart.d.ts +10 -0
  4. package/dist/chart.js +10 -0
  5. package/dist/chart.js.map +1 -0
  6. package/dist/components/account-settings.d.ts +11 -0
  7. package/dist/components/account-settings.js +19 -12
  8. package/dist/components/account-settings.js.map +1 -1
  9. package/dist/components/alert-banner.d.ts +10 -6
  10. package/dist/components/alert-banner.js +20 -16
  11. package/dist/components/alert-banner.js.map +1 -1
  12. package/dist/components/amount-input.d.ts +30 -4
  13. package/dist/components/amount-input.js +36 -14
  14. package/dist/components/amount-input.js.map +1 -1
  15. package/dist/components/calculator.d.ts +24 -7
  16. package/dist/components/calculator.js +37 -20
  17. package/dist/components/calculator.js.map +1 -1
  18. package/dist/components/chart-zoom.d.ts +156 -0
  19. package/dist/components/chart-zoom.js +262 -0
  20. package/dist/components/chart-zoom.js.map +1 -0
  21. package/dist/components/chart.d.ts +28 -3
  22. package/dist/components/chart.js +71 -32
  23. package/dist/components/chart.js.map +1 -1
  24. package/dist/components/checkbox.d.ts +51 -0
  25. package/dist/components/checkbox.js +123 -0
  26. package/dist/components/checkbox.js.map +1 -0
  27. package/dist/components/chip.d.ts +128 -0
  28. package/dist/components/chip.js +357 -0
  29. package/dist/components/chip.js.map +1 -0
  30. package/dist/components/choice-parts.d.ts +24 -0
  31. package/dist/components/choice-parts.js +18 -0
  32. package/dist/components/choice-parts.js.map +1 -0
  33. package/dist/components/combobox-core.d.ts +52 -9
  34. package/dist/components/combobox-core.js +89 -19
  35. package/dist/components/combobox-core.js.map +1 -1
  36. package/dist/components/combobox.d.ts +43 -33
  37. package/dist/components/combobox.js +409 -340
  38. package/dist/components/combobox.js.map +1 -1
  39. package/dist/components/currency-select.d.ts +48 -8
  40. package/dist/components/currency-select.js +107 -65
  41. package/dist/components/currency-select.js.map +1 -1
  42. package/dist/components/data-table-filter-popover.d.ts +15 -3
  43. package/dist/components/data-table-filter-popover.js +23 -17
  44. package/dist/components/data-table-filter-popover.js.map +1 -1
  45. package/dist/components/data-table-filters.d.ts +1 -1
  46. package/dist/components/data-table-filters.js +3 -2
  47. package/dist/components/data-table-filters.js.map +1 -1
  48. package/dist/components/data-table-labels.d.ts +53 -2
  49. package/dist/components/data-table-labels.js +31 -2
  50. package/dist/components/data-table-labels.js.map +1 -1
  51. package/dist/components/data-table-pagination.d.ts +3 -1
  52. package/dist/components/data-table-pagination.js +16 -10
  53. package/dist/components/data-table-pagination.js.map +1 -1
  54. package/dist/components/data-table-sort.js +1 -0
  55. package/dist/components/data-table-sort.js.map +1 -1
  56. package/dist/components/data-table.d.ts +1 -1
  57. package/dist/components/data-table.js +201 -85
  58. package/dist/components/data-table.js.map +1 -1
  59. package/dist/components/date-picker.d.ts +25 -9
  60. package/dist/components/date-picker.js +232 -94
  61. package/dist/components/date-picker.js.map +1 -1
  62. package/dist/components/dropdown.d.ts +74 -19
  63. package/dist/components/dropdown.js +72 -22
  64. package/dist/components/dropdown.js.map +1 -1
  65. package/dist/components/entity-combobox.d.ts +5 -4
  66. package/dist/components/entity-combobox.js +107 -76
  67. package/dist/components/entity-combobox.js.map +1 -1
  68. package/dist/components/facing-pair.d.ts +36 -0
  69. package/dist/components/facing-pair.js +40 -0
  70. package/dist/components/facing-pair.js.map +1 -0
  71. package/dist/components/field-sync.d.ts +164 -0
  72. package/dist/components/field-sync.js +264 -0
  73. package/dist/components/field-sync.js.map +1 -0
  74. package/dist/components/file-dropzone.d.ts +21 -7
  75. package/dist/components/file-dropzone.js +28 -7
  76. package/dist/components/file-dropzone.js.map +1 -1
  77. package/dist/components/full-bleed-dialog.d.ts +37 -26
  78. package/dist/components/full-bleed-dialog.js +20 -5
  79. package/dist/components/full-bleed-dialog.js.map +1 -1
  80. package/dist/components/grouped-picker.d.ts +21 -10
  81. package/dist/components/grouped-picker.js +66 -52
  82. package/dist/components/grouped-picker.js.map +1 -1
  83. package/dist/components/hover-menu.d.ts +20 -4
  84. package/dist/components/hover-menu.js +15 -4
  85. package/dist/components/hover-menu.js.map +1 -1
  86. package/dist/components/mini-calendar.d.ts +102 -14
  87. package/dist/components/mini-calendar.js +256 -70
  88. package/dist/components/mini-calendar.js.map +1 -1
  89. package/dist/components/modal.d.ts +9 -3
  90. package/dist/components/modal.js +34 -45
  91. package/dist/components/modal.js.map +1 -1
  92. package/dist/components/month-picker.d.ts +79 -0
  93. package/dist/components/month-picker.js +342 -0
  94. package/dist/components/month-picker.js.map +1 -0
  95. package/dist/components/multi-entity-combobox.d.ts +7 -6
  96. package/dist/components/multi-entity-combobox.js +112 -72
  97. package/dist/components/multi-entity-combobox.js.map +1 -1
  98. package/dist/components/multi-select.d.ts +13 -11
  99. package/dist/components/multi-select.js +161 -84
  100. package/dist/components/multi-select.js.map +1 -1
  101. package/dist/components/number-field.d.ts +92 -0
  102. package/dist/components/number-field.js +111 -0
  103. package/dist/components/number-field.js.map +1 -0
  104. package/dist/components/number-input.d.ts +21 -2
  105. package/dist/components/number-input.js +2 -1
  106. package/dist/components/number-input.js.map +1 -1
  107. package/dist/components/numpad-sheet.d.ts +25 -4
  108. package/dist/components/numpad-sheet.js +30 -13
  109. package/dist/components/numpad-sheet.js.map +1 -1
  110. package/dist/components/page-contents.d.ts +90 -0
  111. package/dist/components/page-contents.js +170 -0
  112. package/dist/components/page-contents.js.map +1 -0
  113. package/dist/components/password-strength.d.ts +106 -0
  114. package/dist/components/password-strength.js +132 -0
  115. package/dist/components/password-strength.js.map +1 -0
  116. package/dist/components/picker-sheet.d.ts +23 -15
  117. package/dist/components/picker-sheet.js +34 -14
  118. package/dist/components/picker-sheet.js.map +1 -1
  119. package/dist/components/popover.d.ts +62 -4
  120. package/dist/components/popover.js +52 -2
  121. package/dist/components/popover.js.map +1 -1
  122. package/dist/components/search-field.d.ts +25 -7
  123. package/dist/components/search-field.js +7 -5
  124. package/dist/components/search-field.js.map +1 -1
  125. package/dist/components/series-chart-labels.d.ts +14 -0
  126. package/dist/components/series-chart-labels.js +11 -0
  127. package/dist/components/series-chart-labels.js.map +1 -0
  128. package/dist/components/series-chart.d.ts +207 -0
  129. package/dist/components/series-chart.js +223 -0
  130. package/dist/components/series-chart.js.map +1 -0
  131. package/dist/components/settings-fields.d.ts +14 -4
  132. package/dist/components/settings-fields.js +19 -8
  133. package/dist/components/settings-fields.js.map +1 -1
  134. package/dist/components/signature-pad.d.ts +119 -0
  135. package/dist/components/signature-pad.js +405 -0
  136. package/dist/components/signature-pad.js.map +1 -0
  137. package/dist/components/slider.d.ts +80 -0
  138. package/dist/components/slider.js +155 -0
  139. package/dist/components/slider.js.map +1 -0
  140. package/dist/components/sparkline.d.ts +93 -0
  141. package/dist/components/sparkline.js +205 -0
  142. package/dist/components/sparkline.js.map +1 -0
  143. package/dist/components/stat-tile.d.ts +151 -0
  144. package/dist/components/stat-tile.js +236 -0
  145. package/dist/components/stat-tile.js.map +1 -0
  146. package/dist/components/swipeable-row.d.ts +25 -2
  147. package/dist/components/swipeable-row.js +31 -0
  148. package/dist/components/swipeable-row.js.map +1 -1
  149. package/dist/components/switch.d.ts +60 -0
  150. package/dist/components/switch.js +81 -0
  151. package/dist/components/switch.js.map +1 -0
  152. package/dist/components/time-input.d.ts +58 -0
  153. package/dist/components/time-input.js +50 -0
  154. package/dist/components/time-input.js.map +1 -0
  155. package/dist/components/toggle-group.d.ts +18 -3
  156. package/dist/components/toggle-group.js +10 -6
  157. package/dist/components/toggle-group.js.map +1 -1
  158. package/dist/components/toggle-legend.d.ts +115 -0
  159. package/dist/components/toggle-legend.js +114 -0
  160. package/dist/components/toggle-legend.js.map +1 -0
  161. package/dist/components/tooltip.d.ts +40 -11
  162. package/dist/components/tooltip.js +77 -20
  163. package/dist/components/tooltip.js.map +1 -1
  164. package/dist/components/treemap.d.ts +132 -0
  165. package/dist/components/treemap.js +179 -0
  166. package/dist/components/treemap.js.map +1 -0
  167. package/dist/components/ui.d.ts +159 -46
  168. package/dist/components/ui.js +220 -115
  169. package/dist/components/ui.js.map +1 -1
  170. package/dist/components/use-mobile-reveal.js +1 -0
  171. package/dist/components/use-mobile-reveal.js.map +1 -1
  172. package/dist/components/use-table-state.d.ts +1 -1
  173. package/dist/components/use-table-state.js +38 -17
  174. package/dist/components/use-table-state.js.map +1 -1
  175. package/dist/components/user-avatar.d.ts +5 -3
  176. package/dist/components/user-avatar.js +4 -2
  177. package/dist/components/user-avatar.js.map +1 -1
  178. package/dist/components/wizard-stepper.js +3 -2
  179. package/dist/components/wizard-stepper.js.map +1 -1
  180. package/dist/{data-table-filters-aXyf0Xub.d.ts → data-table-filters-DM1auUfH.d.ts} +27 -5
  181. package/dist/data-table.d.ts +7 -0
  182. package/dist/data-table.js +12 -0
  183. package/dist/data-table.js.map +1 -0
  184. package/dist/feedback/feedback-attachment.js +12 -11
  185. package/dist/feedback/feedback-attachment.js.map +1 -1
  186. package/dist/feedback/feedback-dialog.js +2 -1
  187. package/dist/feedback/feedback-dialog.js.map +1 -1
  188. package/dist/feedback/feedback-inbox.d.ts +33 -7
  189. package/dist/feedback/feedback-inbox.js +49 -37
  190. package/dist/feedback/feedback-inbox.js.map +1 -1
  191. package/dist/feedback.d.ts +5 -0
  192. package/dist/feedback.js +5 -0
  193. package/dist/feedback.js.map +1 -0
  194. package/dist/hooks/use-anchored-panel.js +1 -0
  195. package/dist/hooks/use-anchored-panel.js.map +1 -1
  196. package/dist/hooks/use-anchored-rect.js +1 -0
  197. package/dist/hooks/use-anchored-rect.js.map +1 -1
  198. package/dist/hooks/use-announce.d.ts +46 -0
  199. package/dist/hooks/use-announce.js +37 -0
  200. package/dist/hooks/use-announce.js.map +1 -0
  201. package/dist/hooks/use-body-scroll-lock.js +1 -0
  202. package/dist/hooks/use-body-scroll-lock.js.map +1 -1
  203. package/dist/hooks/use-close-transition.js +1 -0
  204. package/dist/hooks/use-close-transition.js.map +1 -1
  205. package/dist/hooks/use-dismiss.js +1 -0
  206. package/dist/hooks/use-dismiss.js.map +1 -1
  207. package/dist/hooks/use-focus-trap.d.ts +16 -0
  208. package/dist/hooks/use-focus-trap.js +67 -0
  209. package/dist/hooks/use-focus-trap.js.map +1 -0
  210. package/dist/hooks/use-media-query.js +1 -0
  211. package/dist/hooks/use-media-query.js.map +1 -1
  212. package/dist/hooks/use-overlay-history.js +27 -1
  213. package/dist/hooks/use-overlay-history.js.map +1 -1
  214. package/dist/hooks/use-row-swipe.js +1 -0
  215. package/dist/hooks/use-row-swipe.js.map +1 -1
  216. package/dist/i18n/defaults.d.ts +31 -0
  217. package/dist/i18n/defaults.js +63 -0
  218. package/dist/i18n/defaults.js.map +1 -0
  219. package/dist/i18n/kit-labels.d.ts +250 -0
  220. package/dist/i18n/kit-labels.js +184 -0
  221. package/dist/i18n/kit-labels.js.map +1 -0
  222. package/dist/index.d.ts +47 -22
  223. package/dist/index.js +28 -0
  224. package/dist/index.js.map +1 -1
  225. package/dist/lib/calc.js +1 -0
  226. package/dist/lib/calc.js.map +1 -1
  227. package/dist/lib/cn.js +1 -0
  228. package/dist/lib/cn.js.map +1 -1
  229. package/dist/lib/dates.js +1 -0
  230. package/dist/lib/dates.js.map +1 -1
  231. package/dist/lib/logger.js +1 -0
  232. package/dist/lib/logger.js.map +1 -1
  233. package/dist/lib/safe-storage.js +1 -0
  234. package/dist/lib/safe-storage.js.map +1 -1
  235. package/dist/search/command-palette.d.ts +11 -1
  236. package/dist/search/command-palette.js +21 -15
  237. package/dist/search/command-palette.js.map +1 -1
  238. package/dist/search.d.ts +2 -0
  239. package/dist/search.js +3 -0
  240. package/dist/search.js.map +1 -0
  241. package/dist/shell/app-shell.d.ts +33 -4
  242. package/dist/shell/app-shell.js +275 -78
  243. package/dist/shell/app-shell.js.map +1 -1
  244. package/dist/shell/option-switcher-menu.js +3 -2
  245. package/dist/shell/option-switcher-menu.js.map +1 -1
  246. package/dist/shell/role-switcher.d.ts +6 -0
  247. package/dist/shell/role-switcher.js +7 -4
  248. package/dist/shell/role-switcher.js.map +1 -1
  249. package/dist/shell/top-bar.d.ts +15 -7
  250. package/dist/shell/top-bar.js +3 -5
  251. package/dist/shell/top-bar.js.map +1 -1
  252. package/dist/shell/topbar-action-menu.js +3 -2
  253. package/dist/shell/topbar-action-menu.js.map +1 -1
  254. package/dist/shell/topbar-controls.d.ts +5 -2
  255. package/dist/shell/topbar-controls.js +20 -15
  256. package/dist/shell/topbar-controls.js.map +1 -1
  257. package/dist/shell.d.ts +13 -0
  258. package/dist/shell.js +8 -0
  259. package/dist/shell.js.map +1 -0
  260. package/dist/theme/chart-palette.d.ts +19 -12
  261. package/dist/theme/chart-palette.js +24 -30
  262. package/dist/theme/chart-palette.js.map +1 -1
  263. package/dist/theme/color.d.ts +78 -0
  264. package/dist/theme/color.js +148 -0
  265. package/dist/theme/color.js.map +1 -0
  266. package/dist/theme/palette-derive.d.ts +140 -0
  267. package/dist/theme/palette-derive.js +376 -0
  268. package/dist/theme/palette-derive.js.map +1 -0
  269. package/dist/theme/palette-presets.d.ts +14 -1
  270. package/dist/theme/palette-presets.js +231 -3
  271. package/dist/theme/palette-presets.js.map +1 -1
  272. package/dist/theme/palette-store.js +1 -0
  273. package/dist/theme/palette-store.js.map +1 -1
  274. package/dist/theme/theme-store.js +1 -0
  275. package/dist/theme/theme-store.js.map +1 -1
  276. package/dist/tour/tour.d.ts +2 -1
  277. package/dist/tour/tour.js +15 -9
  278. package/dist/tour/tour.js.map +1 -1
  279. package/dist/tour.d.ts +2 -0
  280. package/dist/tour.js +3 -0
  281. package/dist/tour.js.map +1 -0
  282. package/dist/wizard/stepper-nav.js +10 -8
  283. package/dist/wizard/stepper-nav.js.map +1 -1
  284. package/dist/wizard/types.d.ts +18 -2
  285. package/dist/wizard/types.js +4 -1
  286. package/dist/wizard/types.js.map +1 -1
  287. package/dist/wizard/use-wizard.js +12 -4
  288. package/dist/wizard/use-wizard.js.map +1 -1
  289. package/dist/wizard/validation.js +1 -0
  290. package/dist/wizard/validation.js.map +1 -1
  291. package/dist/wizard/wizard-context.js +1 -0
  292. package/dist/wizard/wizard-context.js.map +1 -1
  293. package/dist/wizard/wizard-step.js +1 -0
  294. package/dist/wizard/wizard-step.js.map +1 -1
  295. package/dist/wizard/wizard-summary.js +5 -3
  296. package/dist/wizard/wizard-summary.js.map +1 -1
  297. package/dist/wizard.d.ts +8 -0
  298. package/dist/wizard.js +9 -0
  299. package/dist/wizard.js.map +1 -0
  300. package/package.json +66 -13
  301. package/src/chart.ts +22 -0
  302. package/src/components/account-settings.tsx +275 -0
  303. package/src/components/alert-banner.tsx +75 -0
  304. package/src/components/amount-input.tsx +471 -0
  305. package/src/components/calculator.tsx +252 -0
  306. package/src/components/chart-zoom.tsx +499 -0
  307. package/src/components/chart.tsx +363 -0
  308. package/src/components/checkbox.tsx +222 -0
  309. package/src/components/chip.tsx +570 -0
  310. package/src/components/choice-parts.ts +33 -0
  311. package/src/components/combobox-core.tsx +540 -0
  312. package/src/components/combobox.tsx +1026 -0
  313. package/src/components/currency-select.tsx +269 -0
  314. package/src/components/data-table-filter-popover.tsx +252 -0
  315. package/src/components/data-table-filters.ts +176 -0
  316. package/src/components/data-table-labels.ts +190 -0
  317. package/src/components/data-table-pagination.tsx +126 -0
  318. package/src/components/data-table-sort.ts +70 -0
  319. package/src/components/data-table.tsx +1740 -0
  320. package/src/components/date-picker.tsx +631 -0
  321. package/src/components/dropdown.tsx +341 -0
  322. package/src/components/entity-combobox.tsx +215 -0
  323. package/src/components/facing-pair.ts +86 -0
  324. package/src/components/field-sync.tsx +472 -0
  325. package/src/components/file-dropzone.tsx +181 -0
  326. package/src/components/full-bleed-dialog.tsx +179 -0
  327. package/src/components/grouped-picker.tsx +157 -0
  328. package/src/components/hover-menu.tsx +183 -0
  329. package/src/components/mini-calendar.tsx +554 -0
  330. package/src/components/modal.tsx +287 -0
  331. package/src/components/month-picker.tsx +549 -0
  332. package/src/components/multi-entity-combobox.tsx +226 -0
  333. package/src/components/multi-select.tsx +303 -0
  334. package/src/components/number-field.tsx +225 -0
  335. package/src/components/number-input.tsx +211 -0
  336. package/src/components/numpad-sheet.tsx +264 -0
  337. package/src/components/page-contents.tsx +289 -0
  338. package/src/components/password-strength.tsx +258 -0
  339. package/src/components/picker-sheet.tsx +251 -0
  340. package/src/components/popover.tsx +195 -0
  341. package/src/components/search-field.tsx +146 -0
  342. package/src/components/series-chart-labels.ts +26 -0
  343. package/src/components/series-chart.tsx +481 -0
  344. package/src/components/settings-fields.tsx +68 -0
  345. package/src/components/signature-pad.tsx +630 -0
  346. package/src/components/slider.tsx +313 -0
  347. package/src/components/sparkline.tsx +329 -0
  348. package/src/components/stat-tile.tsx +448 -0
  349. package/src/components/swipeable-row.tsx +271 -0
  350. package/src/components/switch.tsx +170 -0
  351. package/src/components/time-input.tsx +107 -0
  352. package/src/components/toggle-group.tsx +153 -0
  353. package/src/components/toggle-legend.tsx +229 -0
  354. package/src/components/tooltip.tsx +477 -0
  355. package/src/components/treemap.tsx +390 -0
  356. package/src/components/ui.tsx +1284 -0
  357. package/src/components/use-mobile-reveal.ts +54 -0
  358. package/src/components/use-table-state.ts +295 -0
  359. package/src/components/user-avatar.tsx +61 -0
  360. package/src/components/wizard-stepper.tsx +37 -0
  361. package/src/data-table.ts +19 -0
  362. package/src/feedback/feedback-attachment.tsx +246 -0
  363. package/src/feedback/feedback-dialog.tsx +209 -0
  364. package/src/feedback/feedback-inbox.tsx +615 -0
  365. package/src/feedback.ts +8 -0
  366. package/src/hooks/use-anchored-panel.ts +169 -0
  367. package/src/hooks/use-anchored-rect.ts +78 -0
  368. package/src/hooks/use-announce.ts +85 -0
  369. package/src/hooks/use-body-scroll-lock.ts +94 -0
  370. package/src/hooks/use-close-transition.ts +99 -0
  371. package/src/hooks/use-dismiss.ts +77 -0
  372. package/src/hooks/use-focus-trap.ts +160 -0
  373. package/src/hooks/use-media-query.ts +23 -0
  374. package/src/hooks/use-overlay-history.ts +345 -0
  375. package/src/hooks/use-row-swipe.ts +212 -0
  376. package/src/i18n/defaults.ts +68 -0
  377. package/src/i18n/kit-labels.tsx +447 -0
  378. package/src/index.ts +169 -0
  379. package/src/lib/calc.ts +309 -0
  380. package/src/lib/cn.ts +5 -0
  381. package/src/lib/dates.ts +187 -0
  382. package/src/lib/logger.ts +73 -0
  383. package/src/lib/safe-storage.ts +37 -0
  384. package/src/search/command-palette.tsx +342 -0
  385. package/src/search.ts +3 -0
  386. package/src/shell/app-shell.tsx +772 -0
  387. package/src/shell/option-switcher-menu.tsx +72 -0
  388. package/src/shell/role-switcher.tsx +46 -0
  389. package/src/shell/top-bar.tsx +39 -0
  390. package/src/shell/topbar-action-menu.tsx +91 -0
  391. package/src/shell/topbar-controls.tsx +229 -0
  392. package/src/shell.ts +10 -0
  393. package/src/theme/chart-palette.ts +145 -0
  394. package/src/theme/color.ts +266 -0
  395. package/src/theme/palette-derive.ts +681 -0
  396. package/src/theme/palette-presets.ts +760 -0
  397. package/src/theme/palette-store.ts +94 -0
  398. package/src/theme/theme-store.ts +145 -0
  399. package/src/tour/tour.tsx +586 -0
  400. package/src/tour.ts +3 -0
  401. package/src/vite-env.d.ts +26 -0
  402. package/src/wizard/stepper-nav.tsx +221 -0
  403. package/src/wizard/types.ts +178 -0
  404. package/src/wizard/use-wizard.ts +345 -0
  405. package/src/wizard/validation.ts +37 -0
  406. package/src/wizard/wizard-context.tsx +29 -0
  407. package/src/wizard/wizard-step.tsx +14 -0
  408. package/src/wizard/wizard-summary.tsx +69 -0
  409. package/src/wizard.ts +14 -0
  410. package/tokens.css +222 -20
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Colour maths for the palette system: sRGB ↔ OKLab ↔ OKLCH, WCAG contrast, gamut
3
+ * clipping, and colour-vision-deficiency simulation.
4
+ *
5
+ * Dependency-free on purpose. This package already ships 48MB of runtime dependencies
6
+ * for a 1.4MB library, and a colour-space conversion is forty lines of arithmetic that
7
+ * will not change — importing culori or chroma-js to get it would be the largest
8
+ * dependency in the kit, paid for by every consumer, to avoid writing down formulas
9
+ * that were published once and are now fixed.
10
+ *
11
+ * **Why OKLCH rather than HSL.** HSL's "lightness" is not lightness: `hsl(60 100% 50%)`
12
+ * (yellow) and `hsl(240 100% 50%)` (blue) claim the same 50% and differ by a factor of
13
+ * about twelve in perceived brightness. Any scheme that derives a palette by holding HSL
14
+ * lightness constant therefore produces a set that looks wildly uneven, and any scheme
15
+ * that solves for contrast in HSL has to solve separately per hue. OKLab is built so a
16
+ * given L means the same apparent lightness at every hue, which is what makes "same
17
+ * lightness, different hue" a usable rule and what makes a contrast solve converge.
18
+ *
19
+ * Reference: Björn Ottosson, "A perceptual color space for image processing" (2020).
20
+ */
21
+
22
+ export interface Rgb {
23
+ /** 0–255. */
24
+ r: number;
25
+ g: number;
26
+ b: number;
27
+ }
28
+
29
+ export interface Oklch {
30
+ /** Perceptual lightness, 0–1. */
31
+ l: number;
32
+ /** Chroma, 0–~0.4 in sRGB. */
33
+ c: number;
34
+ /** Hue angle in degrees, 0–360. */
35
+ h: number;
36
+ }
37
+
38
+ /* ── sRGB ─────────────────────────────────────────────────────────────────── */
39
+
40
+ /** `#rgb` or `#rrggbb` → components, or null when it is neither. */
41
+ export function parseHex(hex: string): Rgb | null {
42
+ const h = hex.trim().replace(/^#/, "");
43
+ const full =
44
+ h.length === 3
45
+ ? h
46
+ .split("")
47
+ .map((c) => c + c)
48
+ .join("")
49
+ : h;
50
+ if (!/^[0-9a-fA-F]{6}$/.test(full)) return null;
51
+ return {
52
+ r: parseInt(full.slice(0, 2), 16),
53
+ g: parseInt(full.slice(2, 4), 16),
54
+ b: parseInt(full.slice(4, 6), 16),
55
+ };
56
+ }
57
+
58
+ export function toHex({ r, g, b }: Rgb): string {
59
+ const part = (v: number) =>
60
+ Math.max(0, Math.min(255, Math.round(v)))
61
+ .toString(16)
62
+ .padStart(2, "0");
63
+ return `#${part(r)}${part(g)}${part(b)}`;
64
+ }
65
+
66
+ /** sRGB 0–255 → linear-light 0–1. The transfer function, not a gamma of 2.2. */
67
+ function toLinear(channel: number): number {
68
+ const c = channel / 255;
69
+ return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
70
+ }
71
+
72
+ function fromLinear(channel: number): number {
73
+ const c = channel <= 0.0031308 ? 12.92 * channel : 1.055 * channel ** (1 / 2.4) - 0.055;
74
+ return c * 255;
75
+ }
76
+
77
+ /* ── OKLab / OKLCH ────────────────────────────────────────────────────────── */
78
+
79
+ export function rgbToOklch(rgb: Rgb): Oklch {
80
+ const r = toLinear(rgb.r);
81
+ const g = toLinear(rgb.g);
82
+ const b = toLinear(rgb.b);
83
+
84
+ const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
85
+ const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
86
+ const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
87
+
88
+ const L = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s;
89
+ const A = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s;
90
+ const B = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s;
91
+
92
+ const c = Math.sqrt(A * A + B * B);
93
+ // Hue is meaningless at zero chroma; 0 keeps it stable rather than NaN-adjacent.
94
+ const h = c < 1e-6 ? 0 : ((Math.atan2(B, A) * 180) / Math.PI + 360) % 360;
95
+ return { l: L, c, h };
96
+ }
97
+
98
+ /** OKLCH → sRGB, WITHOUT gamut clipping. May return components outside 0–255. */
99
+ function oklchToRgbRaw({ l, c, h }: Oklch): Rgb {
100
+ const rad = (h * Math.PI) / 180;
101
+ const A = c * Math.cos(rad);
102
+ const B = c * Math.sin(rad);
103
+
104
+ const l_ = (l + 0.3963377774 * A + 0.2158037573 * B) ** 3;
105
+ const m_ = (l - 0.1055613458 * A - 0.0638541728 * B) ** 3;
106
+ const s_ = (l - 0.0894841775 * A - 1.291485548 * B) ** 3;
107
+
108
+ return {
109
+ r: fromLinear(4.0767416621 * l_ - 3.3077115913 * m_ + 0.2309699292 * s_),
110
+ g: fromLinear(-1.2684380046 * l_ + 2.6097574011 * m_ - 0.3413193965 * s_),
111
+ b: fromLinear(-0.0041960863 * l_ - 0.7034186147 * m_ + 1.707614701 * s_),
112
+ };
113
+ }
114
+
115
+ function inGamut({ r, g, b }: Rgb): boolean {
116
+ const ok = (v: number) => v >= -0.5 && v <= 255.5;
117
+ return ok(r) && ok(g) && ok(b);
118
+ }
119
+
120
+ /**
121
+ * OKLCH → sRGB, reducing CHROMA until the colour fits.
122
+ *
123
+ * Clamping the channels instead — the obvious approach — shifts hue and lightness
124
+ * together and unpredictably: a saturated blue that overflows clips to a different blue
125
+ * that is also lighter. Holding L and h and giving up chroma keeps the colour
126
+ * recognisably the one that was asked for, which matters when the whole point is to
127
+ * derive a family at a fixed lightness.
128
+ */
129
+ export function oklchToRgb(target: Oklch): Rgb {
130
+ const direct = oklchToRgbRaw(target);
131
+ if (inGamut(direct)) return direct;
132
+
133
+ let lo = 0;
134
+ let hi = target.c;
135
+ // 20 halvings resolves chroma to ~4e-7, far below a perceptible step.
136
+ for (let i = 0; i < 20; i++) {
137
+ const mid = (lo + hi) / 2;
138
+ if (inGamut(oklchToRgbRaw({ ...target, c: mid }))) lo = mid;
139
+ else hi = mid;
140
+ }
141
+ return oklchToRgbRaw({ ...target, c: lo });
142
+ }
143
+
144
+ export function hexToOklch(hex: string): Oklch | null {
145
+ const rgb = parseHex(hex);
146
+ return rgb ? rgbToOklch(rgb) : null;
147
+ }
148
+
149
+ export function oklchToHex(lch: Oklch): string {
150
+ return toHex(oklchToRgb(lch));
151
+ }
152
+
153
+ /* ── WCAG contrast ────────────────────────────────────────────────────────── */
154
+
155
+ /** WCAG 2.x relative luminance. */
156
+ export function luminance(rgb: Rgb): number {
157
+ return 0.2126 * toLinear(rgb.r) + 0.7152 * toLinear(rgb.g) + 0.0722 * toLinear(rgb.b);
158
+ }
159
+
160
+ /** WCAG 2.x contrast ratio, 1–21. Order-independent. */
161
+ export function contrast(a: Rgb | string, b: Rgb | string): number {
162
+ const ra = typeof a === "string" ? parseHex(a) : a;
163
+ const rb = typeof b === "string" ? parseHex(b) : b;
164
+ if (!ra || !rb) return 1;
165
+ const la = luminance(ra);
166
+ const lb = luminance(rb);
167
+ return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
168
+ }
169
+
170
+ /**
171
+ * The lightness at hue `h`, chroma `c` that hits `target` contrast against `against`.
172
+ *
173
+ * Binary search rather than algebra: OKLab lightness does not map to WCAG luminance in
174
+ * closed form, and it is not even monotonic in the general case once gamut clipping
175
+ * starts reducing chroma. It IS monotonic over the half-range we search — away from the
176
+ * background — which is why the direction is chosen first.
177
+ *
178
+ * Returns the best lightness found. If the target is unreachable at this hue and chroma
179
+ * (a saturated yellow cannot make 7:1 against white at any lightness without going grey)
180
+ * it returns the extreme end, and the caller should check the contrast it actually got
181
+ * rather than assuming success — `deriveTokenSet` does exactly that.
182
+ */
183
+ export function solveLightness(
184
+ target: number,
185
+ against: Rgb,
186
+ h: number,
187
+ c: number,
188
+ direction: "lighter" | "darker",
189
+ ): number {
190
+ // The half-range on the far side of the background from where we are heading.
191
+ // Going darker, that is [0, backgroundL]; going lighter, [backgroundL, 1]. Contrast is
192
+ // monotonic across each of those halves, which is what makes a binary search valid —
193
+ // it is NOT monotonic across the whole 0..1 range, because it turns around at the
194
+ // background's own lightness.
195
+ const bgL = rgbToOklch(against).l;
196
+ let lo = direction === "darker" ? 0 : bgL;
197
+ let hi = direction === "darker" ? bgL : 1;
198
+
199
+ for (let i = 0; i < 24; i++) {
200
+ const mid = (lo + hi) / 2;
201
+ const got = contrast(oklchToRgb({ l: mid, c, h }), against);
202
+ // Moving AWAY from the background raises contrast; which way that is depends on
203
+ // whether we are going lighter or darker.
204
+ if (got < target) {
205
+ if (direction === "darker") hi = mid;
206
+ else lo = mid;
207
+ } else if (direction === "darker") lo = mid;
208
+ else hi = mid;
209
+ }
210
+ return direction === "darker" ? lo : hi;
211
+ }
212
+
213
+ /* ── Colour-vision deficiency ─────────────────────────────────────────────── */
214
+
215
+ export type CvdType = "protanopia" | "deuteranopia" | "tritanopia";
216
+
217
+ /**
218
+ * Brettel/Viénot-style dichromat simulation in linear RGB.
219
+ *
220
+ * An approximation, and a well-established one. It is here so the kit can FAIL a
221
+ * categorical ramp that two of its series become indistinguishable under, rather than
222
+ * asserting CVD-safety in a comment the way `chart-palette.ts` did — the audit found
223
+ * four of the nine default light chart hues below the 3:1 they were claimed to hold.
224
+ */
225
+ const CVD_MATRIX: Record<CvdType, number[][]> = {
226
+ protanopia: [
227
+ [0.1705, 0.8295, 0],
228
+ [0.1705, 0.8295, 0],
229
+ [-0.0045, 0.0045, 1],
230
+ ],
231
+ deuteranopia: [
232
+ [0.33066, 0.66934, 0],
233
+ [0.33066, 0.66934, 0],
234
+ [-0.02785, 0.02785, 1],
235
+ ],
236
+ tritanopia: [
237
+ [1, 0.1273, -0.1273],
238
+ [0, 0.8739, 0.1261],
239
+ [0, 0.8739, 0.1261],
240
+ ],
241
+ };
242
+
243
+ export function simulateCvd(rgb: Rgb, type: CvdType): Rgb {
244
+ const m = CVD_MATRIX[type];
245
+ const [r, g, b] = [toLinear(rgb.r), toLinear(rgb.g), toLinear(rgb.b)];
246
+ return {
247
+ r: fromLinear(m[0][0] * r + m[0][1] * g + m[0][2] * b),
248
+ g: fromLinear(m[1][0] * r + m[1][1] * g + m[1][2] * b),
249
+ b: fromLinear(m[2][0] * r + m[2][1] * g + m[2][2] * b),
250
+ };
251
+ }
252
+
253
+ /**
254
+ * Perceptual distance in OKLab, which is what that space is FOR: a Euclidean distance
255
+ * in OKLab corresponds far more closely to "how different do these look" than one in
256
+ * sRGB, where the same numeric gap is obvious among greens and invisible among blues.
257
+ */
258
+ export function deltaE(a: Rgb, b: Rgb): number {
259
+ const A = rgbToOklch(a);
260
+ const B = rgbToOklch(b);
261
+ const aA = A.c * Math.cos((A.h * Math.PI) / 180);
262
+ const aB = A.c * Math.sin((A.h * Math.PI) / 180);
263
+ const bA = B.c * Math.cos((B.h * Math.PI) / 180);
264
+ const bB = B.c * Math.sin((B.h * Math.PI) / 180);
265
+ return Math.hypot(A.l - B.l, aA - bA, aB - bB);
266
+ }