@cube-dev/ui-kit 0.163.0 → 0.164.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 (636) hide show
  1. package/README.md +1 -1
  2. package/dist/CHANGELOG.md +377 -0
  3. package/dist/README.md +1 -1
  4. package/dist/_internal/hooks/use-chained-callback.js +1 -1
  5. package/dist/_internal/hooks/use-debounced-value.js +1 -1
  6. package/dist/_internal/hooks/use-deprecation-warning.js +1 -1
  7. package/dist/_internal/hooks/use-event.js +1 -1
  8. package/dist/_internal/hooks/use-is-first-render.js +1 -1
  9. package/dist/_internal/hooks/use-sync-ref.js +1 -1
  10. package/dist/_internal/hooks/use-timer/timer.js +1 -1
  11. package/dist/_internal/hooks/use-timer/use-timer.js +1 -1
  12. package/dist/_internal/hooks/use-warn.js +1 -1
  13. package/dist/components/Block.js +1 -1
  14. package/dist/components/CollectionItem.js +1 -1
  15. package/dist/components/GlobalStyles.js +1 -1
  16. package/dist/components/GridProvider.js +1 -1
  17. package/dist/components/HiddenInput.js +1 -1
  18. package/dist/components/Root.d.ts +3 -2
  19. package/dist/components/Root.js +1 -1
  20. package/dist/components/Root.js.map +1 -1
  21. package/dist/components/actions/Action/Action.js +1 -1
  22. package/dist/components/actions/Banner/Banner.js +2 -5
  23. package/dist/components/actions/Banner/Banner.js.map +1 -1
  24. package/dist/components/actions/Button/Button.d.ts +3 -2
  25. package/dist/components/actions/Button/Button.js +8 -5
  26. package/dist/components/actions/Button/Button.js.map +1 -1
  27. package/dist/components/actions/ButtonGroup/ButtonGroup.js +1 -1
  28. package/dist/components/actions/ButtonSplit/ButtonSplit.js +1 -1
  29. package/dist/components/actions/ButtonSplit/context.js +1 -1
  30. package/dist/components/actions/CommandMenu/CommandMenu.js +1 -1
  31. package/dist/components/actions/CommandMenu/styled.js +1 -1
  32. package/dist/components/actions/ItemAction/ItemAction.d.ts +2 -2
  33. package/dist/components/actions/ItemAction/ItemAction.js +15 -6
  34. package/dist/components/actions/ItemAction/ItemAction.js.map +1 -1
  35. package/dist/components/actions/ItemActionContext.d.ts +14 -0
  36. package/dist/components/actions/ItemActionContext.js +2 -2
  37. package/dist/components/actions/ItemActionContext.js.map +1 -1
  38. package/dist/components/actions/ItemButton/ItemButton.js +56 -46
  39. package/dist/components/actions/ItemButton/ItemButton.js.map +1 -1
  40. package/dist/components/actions/Link/Link.js +1 -1
  41. package/dist/components/actions/Menu/Menu.js +1 -1
  42. package/dist/components/actions/Menu/MenuItem.js +1 -1
  43. package/dist/components/actions/Menu/MenuSection.js +1 -1
  44. package/dist/components/actions/Menu/MenuTrigger.d.ts +13 -0
  45. package/dist/components/actions/Menu/MenuTrigger.js +27 -15
  46. package/dist/components/actions/Menu/MenuTrigger.js.map +1 -1
  47. package/dist/components/actions/Menu/SubMenuTrigger.js +1 -1
  48. package/dist/components/actions/Menu/SubmenuTriggerContext.js +1 -1
  49. package/dist/components/actions/Menu/context.js +1 -1
  50. package/dist/components/actions/Menu/styled.js +1 -1
  51. package/dist/components/actions/index.js +1 -1
  52. package/dist/components/actions/use-action.js +1 -1
  53. package/dist/components/actions/use-anchored-menu.js +1 -1
  54. package/dist/components/actions/use-context-menu.js +1 -1
  55. package/dist/components/content/ActiveZone/ActiveZone.js +1 -1
  56. package/dist/components/content/Alert/Alert.js +1 -1
  57. package/dist/components/content/Alert/use-alert.js +1 -1
  58. package/dist/components/content/Avatar/Avatar.js +1 -1
  59. package/dist/components/content/Badge/Badge.js +1 -1
  60. package/dist/components/content/Card/Card.js +1 -1
  61. package/dist/components/content/Content.js +1 -1
  62. package/dist/components/content/CopyPasteBlock/CopyPasteBlock.js +1 -1
  63. package/dist/components/content/CopySnippet/CopySnippet.d.ts +10 -0
  64. package/dist/components/content/CopySnippet/CopySnippet.js +7 -4
  65. package/dist/components/content/CopySnippet/CopySnippet.js.map +1 -1
  66. package/dist/components/content/Disclosure/Disclosure.js +1 -1
  67. package/dist/components/content/Divider.js +1 -1
  68. package/dist/components/content/Footer.js +1 -1
  69. package/dist/components/content/Header.js +1 -1
  70. package/dist/components/content/HotKeys/HotKeys.js +1 -1
  71. package/dist/components/content/InfoBadge/InfoBadge.js +1 -1
  72. package/dist/components/content/InlineInput/InlineInput.js +1 -1
  73. package/dist/components/content/InlineInput/InlineInput.js.map +1 -1
  74. package/dist/components/content/Item/Item.d.ts +2 -2
  75. package/dist/components/content/Item/Item.js +13 -58
  76. package/dist/components/content/Item/Item.js.map +1 -1
  77. package/dist/components/content/ItemBadge/ItemBadge.d.ts +1 -1
  78. package/dist/components/content/ItemBadge/ItemBadge.js +7 -4
  79. package/dist/components/content/ItemBadge/ItemBadge.js.map +1 -1
  80. package/dist/components/content/ItemCard/ItemCard.js +1 -1
  81. package/dist/components/content/Layout/GridLayout.js +1 -1
  82. package/dist/components/content/Layout/Layout.js +1 -1
  83. package/dist/components/content/Layout/LayoutBlock.js +1 -1
  84. package/dist/components/content/Layout/LayoutCenter.js +1 -1
  85. package/dist/components/content/Layout/LayoutContainer.js +1 -1
  86. package/dist/components/content/Layout/LayoutContent.js +1 -1
  87. package/dist/components/content/Layout/LayoutContext.js +1 -1
  88. package/dist/components/content/Layout/LayoutFlex.js +1 -1
  89. package/dist/components/content/Layout/LayoutFooter.js +1 -1
  90. package/dist/components/content/Layout/LayoutGrid.js +1 -1
  91. package/dist/components/content/Layout/LayoutHeader.js +1 -1
  92. package/dist/components/content/Layout/LayoutPane.js +1 -1
  93. package/dist/components/content/Layout/LayoutPanel.js +1 -1
  94. package/dist/components/content/Layout/LayoutPanelHeader.js +1 -1
  95. package/dist/components/content/Layout/LayoutToolbar.js +1 -1
  96. package/dist/components/content/Layout/hooks/useTinyScrollbar.js +1 -1
  97. package/dist/components/content/Layout/index.js +1 -1
  98. package/dist/components/content/Layout/utils.js +1 -1
  99. package/dist/components/content/Paragraph.js +1 -1
  100. package/dist/components/content/Placeholder/Placeholder.js +1 -1
  101. package/dist/components/content/PrismCode/PrismCode.d.ts +6 -0
  102. package/dist/components/content/PrismCode/PrismCode.js +12 -3
  103. package/dist/components/content/PrismCode/PrismCode.js.map +1 -1
  104. package/dist/components/content/PrismCode/prismSetup.js +1 -1
  105. package/dist/components/content/PrismDiffCode/PrismDiffCode.js +1 -1
  106. package/dist/components/content/Result/Result.js +1 -1
  107. package/dist/components/content/Skeleton/Skeleton.js +1 -1
  108. package/dist/components/content/Tag/Tag.js +1 -1
  109. package/dist/components/content/Text.js +1 -1
  110. package/dist/components/content/TextItem/TextItem.js +1 -1
  111. package/dist/components/content/Title.js +1 -1
  112. package/dist/components/content/Tree/Tree.js +1 -1
  113. package/dist/components/content/Tree/TreeNode.js +1 -1
  114. package/dist/components/content/Tree/styled.js +1 -1
  115. package/dist/components/content/Tree/tree-index.js +1 -1
  116. package/dist/components/content/Tree/use-checkbox-tree.js +1 -1
  117. package/dist/components/content/Tree/use-load-data.js +1 -1
  118. package/dist/components/content/highlightText.js +1 -1
  119. package/dist/components/content/use-auto-tooltip.js +1 -1
  120. package/dist/components/data/DataTable/DataTable.js +1 -1
  121. package/dist/components/data/ItemTable/ItemTable.js +1 -1
  122. package/dist/components/data/ItemTable/ItemTableBulkBar.js +1 -1
  123. package/dist/components/data/ItemTable/ItemTableDragPreview.js +1 -1
  124. package/dist/components/data/ItemTable/ItemTableFooter.js +1 -1
  125. package/dist/components/data/ItemTable/ItemTableToolbar.js +1 -1
  126. package/dist/components/data/TableBase/ColumnResizer.js +1 -1
  127. package/dist/components/data/TableBase/RowCollection.js +1 -1
  128. package/dist/components/data/TableBase/TableHeaderCell.js +1 -1
  129. package/dist/components/data/TableBase/TableRow.js +1 -1
  130. package/dist/components/data/TableBase/TableView.js +1 -1
  131. package/dist/components/data/TableBase/column-menu.js +1 -1
  132. package/dist/components/data/TableBase/column-tint.js +12 -2
  133. package/dist/components/data/TableBase/column-tint.js.map +1 -1
  134. package/dist/components/data/TableBase/row-menu.js +1 -1
  135. package/dist/components/data/TableBase/styled.js +1 -1
  136. package/dist/components/data/TableBase/types.js +1 -1
  137. package/dist/components/data/TableBase/use-cell-selection.js +0 -0
  138. package/dist/components/data/TableBase/use-column-order.js +1 -1
  139. package/dist/components/data/TableBase/use-container-width.js +1 -1
  140. package/dist/components/data/TableBase/use-row-move-animation.js +1 -1
  141. package/dist/components/data/TableBase/use-scrollability.js +1 -1
  142. package/dist/components/data/TableBase/use-table-columns.js +1 -1
  143. package/dist/components/data/TableBase/use-table-search.js +1 -1
  144. package/dist/components/data/TableBase/use-table-selection.js +1 -1
  145. package/dist/components/data/TableBase/use-table-sort.js +1 -1
  146. package/dist/components/data/TableBase/use-table-sorts.js +1 -1
  147. package/dist/components/data/TableBase/use-table-storage.js +1 -1
  148. package/dist/components/fields/Checkbox/Checkbox.js +1 -1
  149. package/dist/components/fields/Checkbox/CheckboxGroup.js +1 -1
  150. package/dist/components/fields/Checkbox/context.js +1 -1
  151. package/dist/components/fields/ColorInput/ColorInput.js +12 -7
  152. package/dist/components/fields/ColorInput/ColorInput.js.map +1 -1
  153. package/dist/components/fields/ColorPicker/ColorPicker.js +3 -2
  154. package/dist/components/fields/ColorPicker/ColorPicker.js.map +1 -1
  155. package/dist/components/fields/{color → ColorSwatch}/ColorSwatch.d.ts +8 -2
  156. package/dist/components/fields/{color → ColorSwatch}/ColorSwatch.js +14 -6
  157. package/dist/components/fields/ColorSwatch/ColorSwatch.js.map +1 -0
  158. package/dist/components/fields/ColorSwatch/index.d.ts +2 -0
  159. package/dist/components/fields/ColorSwatchGroup/ColorSwatchGroup.js +1 -1
  160. package/dist/components/fields/ComboBox/ComboBox.js +2 -6
  161. package/dist/components/fields/ComboBox/ComboBox.js.map +1 -1
  162. package/dist/components/fields/CommandTextArea/CommandTextArea.js +1 -1
  163. package/dist/components/fields/CommandTextArea/caretPosition.js +1 -1
  164. package/dist/components/fields/CommandTextArea/useCaretAnchor.js +1 -1
  165. package/dist/components/fields/DatePicker/DateInput.js +1 -1
  166. package/dist/components/fields/DatePicker/DateInputBase.js +1 -1
  167. package/dist/components/fields/DatePicker/DatePicker.js +1 -1
  168. package/dist/components/fields/DatePicker/DatePickerButton.js +32 -2
  169. package/dist/components/fields/DatePicker/DatePickerButton.js.map +1 -1
  170. package/dist/components/fields/DatePicker/DatePickerElement.js +1 -1
  171. package/dist/components/fields/DatePicker/DatePickerInput.js +1 -1
  172. package/dist/components/fields/DatePicker/DatePickerSegment.js +1 -1
  173. package/dist/components/fields/DatePicker/DateRangePicker.js +1 -1
  174. package/dist/components/fields/DatePicker/DateRangeSeparatedPicker.js +1 -1
  175. package/dist/components/fields/DatePicker/MonthPicker.js +1 -1
  176. package/dist/components/fields/DatePicker/PeriodPicker.js +3 -2
  177. package/dist/components/fields/DatePicker/PeriodPicker.js.map +1 -1
  178. package/dist/components/fields/DatePicker/QuarterPicker.js +1 -1
  179. package/dist/components/fields/DatePicker/TimeInput.js +1 -1
  180. package/dist/components/fields/DatePicker/WeekPicker.js +1 -1
  181. package/dist/components/fields/DatePicker/YearPicker.js +1 -1
  182. package/dist/components/fields/DatePicker/parseDate.js +1 -1
  183. package/dist/components/fields/DatePicker/period.js +1 -1
  184. package/dist/components/fields/DatePicker/props.js +1 -1
  185. package/dist/components/fields/DatePicker/utils.js +1 -1
  186. package/dist/components/fields/FileInput/FileInput.js +1 -1
  187. package/dist/components/fields/FilterListBox/FilterListBox.js +1 -1
  188. package/dist/components/fields/FilterPicker/FilterPicker.js +1 -1
  189. package/dist/components/fields/FilterPicker/FilterPicker.js.map +1 -1
  190. package/dist/components/fields/Input/Input.js +1 -1
  191. package/dist/components/fields/ListBox/DraggableListBox.js +1 -1
  192. package/dist/components/fields/ListBox/ListBox.js +1 -1
  193. package/dist/components/fields/ListBoxPopover/ListBoxPopover.js +1 -1
  194. package/dist/components/fields/ListBoxPopover/listNavigation.js +1 -1
  195. package/dist/components/fields/ListBoxPopover/useCompositeFocus.js +1 -1
  196. package/dist/components/fields/NumberInput/NumberInput.js +1 -1
  197. package/dist/components/fields/NumberInput/StepButton.js +1 -1
  198. package/dist/components/fields/PasswordInput/PasswordInput.js +9 -6
  199. package/dist/components/fields/PasswordInput/PasswordInput.js.map +1 -1
  200. package/dist/components/fields/Picker/Picker.js +1 -1
  201. package/dist/components/fields/Picker/Picker.js.map +1 -1
  202. package/dist/components/fields/RadioGroup/Radio.js +1 -1
  203. package/dist/components/fields/RadioGroup/RadioGroup.js +2 -1
  204. package/dist/components/fields/RadioGroup/RadioGroup.js.map +1 -1
  205. package/dist/components/fields/RadioGroup/context.js +1 -1
  206. package/dist/components/fields/SearchComboBox/SearchComboBox.js +2 -6
  207. package/dist/components/fields/SearchComboBox/SearchComboBox.js.map +1 -1
  208. package/dist/components/fields/SearchInput/SearchInput.js +18 -16
  209. package/dist/components/fields/SearchInput/SearchInput.js.map +1 -1
  210. package/dist/components/fields/Select/Select.js +3 -6
  211. package/dist/components/fields/Select/Select.js.map +1 -1
  212. package/dist/components/fields/Slider/Gradation.js +1 -1
  213. package/dist/components/fields/Slider/HueSlider.js +15 -2
  214. package/dist/components/fields/Slider/HueSlider.js.map +1 -1
  215. package/dist/components/fields/Slider/RangeSlider.js +1 -1
  216. package/dist/components/fields/Slider/Slider.js +1 -1
  217. package/dist/components/fields/Slider/SliderBase.js +1 -1
  218. package/dist/components/fields/Slider/SliderThumb.js +1 -1
  219. package/dist/components/fields/Slider/SliderTrack.js +1 -1
  220. package/dist/components/fields/Slider/elements.js +1 -1
  221. package/dist/components/fields/Slider/index.js +1 -1
  222. package/dist/components/fields/Switch/Switch.js +1 -1
  223. package/dist/components/fields/TextArea/TextArea.js +1 -1
  224. package/dist/components/fields/TextInput/TextInput.js +1 -1
  225. package/dist/components/fields/TextInput/TextInputBase.js +1 -1
  226. package/dist/components/fields/TextInputMapper/TextInputMapper.js +1 -1
  227. package/dist/components/fields/color/ColorPanel.js +1 -1
  228. package/dist/components/fields/color/channels.js +1 -1
  229. package/dist/components/fields/color/color.js +4 -4
  230. package/dist/components/fields/color/color.js.map +1 -1
  231. package/dist/components/fields/color/context.js +1 -1
  232. package/dist/components/fields/color/index.d.ts +0 -1
  233. package/dist/components/fields/index.d.ts +2 -1
  234. package/dist/components/form/FieldWrapper/FieldWrapper.js +1 -1
  235. package/dist/components/form/Form/Field.js +1 -1
  236. package/dist/components/form/Form/Form.js +1 -1
  237. package/dist/components/form/Form/ResetButton/ResetButton.js +1 -1
  238. package/dist/components/form/Form/SubmitButton/SubmitButton.js +1 -1
  239. package/dist/components/form/Form/SubmitError.js +1 -1
  240. package/dist/components/form/Form/index.js +1 -1
  241. package/dist/components/form/Form/use-field/use-field-props.js +1 -1
  242. package/dist/components/form/Form/use-field/use-field.js +1 -1
  243. package/dist/components/form/Form/use-form.js +1 -1
  244. package/dist/components/form/Form/validation.js +1 -1
  245. package/dist/components/form/Label.js +1 -1
  246. package/dist/components/form/validation/ValidationIndicator.js +1 -1
  247. package/dist/components/form/validation/resolve-validation-props.js +1 -1
  248. package/dist/components/form/validation/use-validation-props.js +1 -1
  249. package/dist/components/form/wrapper.js +1 -1
  250. package/dist/components/helpers/DisplayTransition/DisplayTransition.js +1 -1
  251. package/dist/components/helpers/IconSwitch/IconSwitch.js +1 -1
  252. package/dist/components/layout/Board/Board.d.ts +35 -5
  253. package/dist/components/layout/Board/Board.js +11 -5
  254. package/dist/components/layout/Board/Board.js.map +1 -1
  255. package/dist/components/layout/Board/BoardProvider.js +1 -1
  256. package/dist/components/layout/Board/BoardResponsive.js +1 -1
  257. package/dist/components/layout/Board/Widget.d.ts +16 -1
  258. package/dist/components/layout/Board/Widget.js +3 -2
  259. package/dist/components/layout/Board/Widget.js.map +1 -1
  260. package/dist/components/layout/Board/WidgetHost.js +92 -17
  261. package/dist/components/layout/Board/WidgetHost.js.map +1 -1
  262. package/dist/components/layout/Board/board-context.js +5 -5
  263. package/dist/components/layout/Board/board-context.js.map +1 -1
  264. package/dist/components/layout/Board/board-store.d.ts +1 -0
  265. package/dist/components/layout/Board/board-store.js +2 -2
  266. package/dist/components/layout/Board/board-store.js.map +1 -1
  267. package/dist/components/layout/Board/grid-core/calculate.js +1 -1
  268. package/dist/components/layout/Board/grid-core/collision-modes.js +183 -0
  269. package/dist/components/layout/Board/grid-core/collision-modes.js.map +1 -0
  270. package/dist/components/layout/Board/grid-core/collision.js +1 -1
  271. package/dist/components/layout/Board/grid-core/compactors.js +1 -1
  272. package/dist/components/layout/Board/grid-core/constraints.js +1 -1
  273. package/dist/components/layout/Board/grid-core/group-move.js +1 -1
  274. package/dist/components/layout/Board/grid-core/layout.js +12 -2
  275. package/dist/components/layout/Board/grid-core/layout.js.map +1 -1
  276. package/dist/components/layout/Board/grid-core/sort.js +1 -1
  277. package/dist/components/layout/Board/grid-core/types.d.ts +17 -1
  278. package/dist/components/layout/Board/index.d.ts +2 -2
  279. package/dist/components/layout/Board/index.js +1 -1
  280. package/dist/components/layout/Board/index.js.map +1 -1
  281. package/dist/components/layout/Board/responsive-utils.js +1 -1
  282. package/dist/components/layout/Board/use-board-layout.js +1 -1
  283. package/dist/components/layout/Board/use-board-registry.js +57 -9
  284. package/dist/components/layout/Board/use-board-registry.js.map +1 -1
  285. package/dist/components/layout/Board/use-board-select-modifier-key.js +1 -1
  286. package/dist/components/layout/Board/use-board-selection.js +1 -1
  287. package/dist/components/layout/Flex.js +1 -1
  288. package/dist/components/layout/Flow.js +1 -1
  289. package/dist/components/layout/Grid.js +1 -1
  290. package/dist/components/layout/Panel.js +1 -1
  291. package/dist/components/layout/Prefix.js +1 -1
  292. package/dist/components/layout/ResizablePanel.js +1 -1
  293. package/dist/components/layout/Space.js +1 -1
  294. package/dist/components/layout/Suffix.js +1 -1
  295. package/dist/components/navigation/Pagination/Pagination.js +1 -1
  296. package/dist/components/navigation/Pagination/use-pagination.js +1 -1
  297. package/dist/components/navigation/Tabs/DraggableTabList.js +1 -1
  298. package/dist/components/navigation/Tabs/TabButton.js +2 -29
  299. package/dist/components/navigation/Tabs/TabButton.js.map +1 -1
  300. package/dist/components/navigation/Tabs/TabDropIndicator.js +1 -1
  301. package/dist/components/navigation/Tabs/TabPanel.js +1 -1
  302. package/dist/components/navigation/Tabs/TabPicker.js +1 -1
  303. package/dist/components/navigation/Tabs/Tabs.js +1 -1
  304. package/dist/components/navigation/Tabs/TabsAction.js +1 -1
  305. package/dist/components/navigation/Tabs/TabsContext.js +1 -1
  306. package/dist/components/navigation/Tabs/popover-placement.js +1 -1
  307. package/dist/components/navigation/Tabs/styled.js +1 -1
  308. package/dist/components/navigation/Tabs/types.js +1 -1
  309. package/dist/components/navigation/Tabs/use-tab-editing.js +1 -1
  310. package/dist/components/navigation/Tabs/use-tab-indicator.js +1 -1
  311. package/dist/components/organisms/StatsCard/StatsCard.js +1 -1
  312. package/dist/components/other/Calendar/Calendar.js +1 -1
  313. package/dist/components/other/Calendar/CalendarCell.js +1 -1
  314. package/dist/components/other/Calendar/CalendarGrid.js +1 -1
  315. package/dist/components/other/Calendar/CalendarHeader.js +1 -1
  316. package/dist/components/other/Calendar/CalendarPanel.js +1 -1
  317. package/dist/components/other/Calendar/PeriodCalendar.js +1 -1
  318. package/dist/components/other/Calendar/PeriodGrid.js +1 -1
  319. package/dist/components/other/Calendar/RangeCalendar.js +1 -1
  320. package/dist/components/other/Calendar/styled.js +1 -1
  321. package/dist/components/other/CubeLogo/CubeLogo.js +1 -1
  322. package/dist/components/other/NoDataIcon/NoDataIcon.d.ts +37 -0
  323. package/dist/components/other/NoDataIcon/NoDataIcon.js +56 -0
  324. package/dist/components/other/NoDataIcon/NoDataIcon.js.map +1 -0
  325. package/dist/components/other/NoDataIcon/index.d.ts +2 -0
  326. package/dist/components/overlays/AlertDialog/AlertDialog.js +1 -1
  327. package/dist/components/overlays/AlertDialog/AlertDialogApiProvider.js +1 -1
  328. package/dist/components/overlays/AlertDialog/AlertDialogZone.js +1 -1
  329. package/dist/components/overlays/Dialog/Dialog.js +4 -3
  330. package/dist/components/overlays/Dialog/Dialog.js.map +1 -1
  331. package/dist/components/overlays/Dialog/DialogContainer.js +1 -1
  332. package/dist/components/overlays/Dialog/DialogForm.js +1 -1
  333. package/dist/components/overlays/Dialog/DialogTrigger.d.ts +13 -0
  334. package/dist/components/overlays/Dialog/DialogTrigger.js +24 -5
  335. package/dist/components/overlays/Dialog/DialogTrigger.js.map +1 -1
  336. package/dist/components/overlays/Dialog/context.js +1 -1
  337. package/dist/components/overlays/Dialog/context.js.map +1 -1
  338. package/dist/components/overlays/Dialog/use-dialog-container.js +1 -1
  339. package/dist/components/overlays/Modal/Modal.js +1 -1
  340. package/dist/components/overlays/Modal/OpenTransitionContext.js +1 -1
  341. package/dist/components/overlays/Modal/Overlay.js +1 -1
  342. package/dist/components/overlays/Modal/Popover.js +1 -1
  343. package/dist/components/overlays/Modal/Tray.js +1 -1
  344. package/dist/components/overlays/Modal/Underlay.js +1 -1
  345. package/dist/components/overlays/Notifications/Notification.js +1 -1
  346. package/dist/components/overlays/Notifications/NotificationAction.js +1 -1
  347. package/dist/components/overlays/Notifications/NotificationCard.js +1 -1
  348. package/dist/components/overlays/Notifications/NotificationContext.js +1 -1
  349. package/dist/components/overlays/Notifications/NotificationItem.js +1 -1
  350. package/dist/components/overlays/Notifications/OverlayContainer.js +1 -1
  351. package/dist/components/overlays/Notifications/OverlayProvider.js +1 -1
  352. package/dist/components/overlays/Notifications/PersistentNotificationsList.js +1 -1
  353. package/dist/components/overlays/Notifications/dismissed-storage.js +1 -1
  354. package/dist/components/overlays/Notifications/format-relative-time.js +1 -1
  355. package/dist/components/overlays/Notifications/index.js +1 -1
  356. package/dist/components/overlays/Notifications/use-notification-state.js +1 -1
  357. package/dist/components/overlays/Notifications/use-notifications.js +1 -1
  358. package/dist/components/overlays/Notifications/use-overlay-timers.js +1 -1
  359. package/dist/components/overlays/Notifications/use-persistent-notifications.js +1 -1
  360. package/dist/components/overlays/Notifications/use-persistent-state.js +1 -1
  361. package/dist/components/overlays/Notifications/use-toast-state.js +1 -1
  362. package/dist/components/overlays/Toast/ToastItem.js +1 -1
  363. package/dist/components/overlays/Toast/index.js +1 -1
  364. package/dist/components/overlays/Toast/useProgressToast.js +1 -1
  365. package/dist/components/overlays/Toast/useToast.js +1 -1
  366. package/dist/components/overlays/Tooltip/Tooltip.js +1 -1
  367. package/dist/components/overlays/Tooltip/TooltipProvider.js +1 -1
  368. package/dist/components/overlays/Tooltip/TooltipTrigger.js +1 -1
  369. package/dist/components/overlays/Tooltip/context.js +1 -1
  370. package/dist/components/portal/Portal.js +1 -1
  371. package/dist/components/portal/PortalProvider.js +1 -1
  372. package/dist/components/portal/usePortal.js +1 -1
  373. package/dist/components/shared/DraggableCollection.js +1 -1
  374. package/dist/components/shared/InvalidIcon.js +1 -1
  375. package/dist/components/shared/ValidIcon.js +1 -1
  376. package/dist/components/status/LoadingAnimation/LoadingAnimation.js +1 -1
  377. package/dist/components/status/LoadingAnimation/LoadingAnimation.js.map +1 -1
  378. package/dist/components/status/Spin/Cube.js +1 -1
  379. package/dist/components/status/Spin/InternalSpinner.js +1 -1
  380. package/dist/components/status/Spin/Spin.js +1 -1
  381. package/dist/components/status/Spin/SpinsContainer.js +1 -1
  382. package/dist/data/item-themes.d.ts +1 -1
  383. package/dist/data/item-themes.js +217 -46
  384. package/dist/data/item-themes.js.map +1 -1
  385. package/dist/data/themes.js +1 -1
  386. package/dist/eslint-plugin/defaults.generated.js +29 -13
  387. package/dist/eslint-plugin/defaults.generated.js.map +1 -1
  388. package/dist/eslint-plugin/index.js +1 -1
  389. package/dist/eslint-plugin/rules/no-redundant-default-prop.js +1 -1
  390. package/dist/i18n/I18nProvider.js +1 -1
  391. package/dist/i18n/createFormatter.js +1 -1
  392. package/dist/i18n/index.js +1 -1
  393. package/dist/i18n/instance.js +1 -1
  394. package/dist/i18n/locales/de-DE/uikit.js +1 -1
  395. package/dist/i18n/locales/en-US/uikit.js +1 -1
  396. package/dist/i18n/locales/es-ES/uikit.js +1 -1
  397. package/dist/i18n/locales/es-MX/uikit.js +1 -1
  398. package/dist/i18n/locales/fr-FR/uikit.js +1 -1
  399. package/dist/i18n/locales/it-IT/uikit.js +1 -1
  400. package/dist/i18n/locales/ja-JP/uikit.js +1 -1
  401. package/dist/i18n/locales/nb-NO/uikit.js +1 -1
  402. package/dist/i18n/locales/pt-BR/uikit.js +1 -1
  403. package/dist/i18n/locales/pt-PT/uikit.js +1 -1
  404. package/dist/i18n/locales/sv-SE/uikit.js +1 -1
  405. package/dist/i18n/locales/vi-VN/uikit.js +1 -1
  406. package/dist/i18n/locales.js +1 -1
  407. package/dist/i18n/useFormatter.js +1 -1
  408. package/dist/i18n/useI18n.js +1 -1
  409. package/dist/icons/AdjustmentsHorizontalIcon.js +1 -1
  410. package/dist/icons/AdjustmentsIcon.js +1 -1
  411. package/dist/icons/AiIcon.js +1 -1
  412. package/dist/icons/AreaChartIcon.js +1 -1
  413. package/dist/icons/ArrowNarrowDownIcon.js +1 -1
  414. package/dist/icons/ArrowNarrowUpIcon.js +1 -1
  415. package/dist/icons/BackwardIcon.js +1 -1
  416. package/dist/icons/BarChartIcon.js +1 -1
  417. package/dist/icons/BellFilledIcon.js +1 -1
  418. package/dist/icons/BellIcon.js +1 -1
  419. package/dist/icons/BooleanIcon.js +1 -1
  420. package/dist/icons/CalendarEditIcon.js +1 -1
  421. package/dist/icons/CalendarIcon.js +1 -1
  422. package/dist/icons/CaretDownIcon.js +1 -1
  423. package/dist/icons/CaretUpIcon.js +1 -1
  424. package/dist/icons/ChartAreaStackedIcon.js +1 -1
  425. package/dist/icons/ChartAreaStackedPercentageIcon.js +1 -1
  426. package/dist/icons/ChartBarGroupedHorizontalIcon.js +1 -1
  427. package/dist/icons/ChartBarGroupedIcon.js +1 -1
  428. package/dist/icons/ChartBarHorizontalIcon.js +1 -1
  429. package/dist/icons/ChartBarLineIcon.js +1 -1
  430. package/dist/icons/ChartBarStackedHorizontalIcon.js +1 -1
  431. package/dist/icons/ChartBarStackedIcon.js +1 -1
  432. package/dist/icons/ChartBarStackedPercentageHorizontalIcon.js +1 -1
  433. package/dist/icons/ChartBarStackedPercentageIcon.js +1 -1
  434. package/dist/icons/ChartBoxPlot2Icon.js +1 -1
  435. package/dist/icons/ChartBoxPlotIcon.js +1 -1
  436. package/dist/icons/ChartBubbleIcon.js +1 -1
  437. package/dist/icons/ChartDonut2Icon.js +1 -1
  438. package/dist/icons/ChartFunnelIcon.js +1 -1
  439. package/dist/icons/ChartHeatmapIcon.js +1 -1
  440. package/dist/icons/ChartKPIIcon.js +1 -1
  441. package/dist/icons/ChartPie2Icon.js +1 -1
  442. package/dist/icons/ChartScatterIcon.js +1 -1
  443. package/dist/icons/CheckCircleFilledIcon.js +1 -1
  444. package/dist/icons/CheckCircleIcon.js +1 -1
  445. package/dist/icons/CheckIcon.js +1 -1
  446. package/dist/icons/CircleFilledIcon.js +1 -1
  447. package/dist/icons/ClearIcon.js +1 -1
  448. package/dist/icons/CloseCircleFilledIcon.js +1 -1
  449. package/dist/icons/CloseCircleIcon.js +1 -1
  450. package/dist/icons/CloseIcon.js +1 -1
  451. package/dist/icons/CodeIcon.js +1 -1
  452. package/dist/icons/ColumnTotalIcon.js +1 -1
  453. package/dist/icons/CopyIcon.js +1 -1
  454. package/dist/icons/CountIcon.js +1 -1
  455. package/dist/icons/CubeIcon.js +1 -1
  456. package/dist/icons/CubePauseIcon.js +1 -1
  457. package/dist/icons/CubePlayIcon.js +1 -1
  458. package/dist/icons/CurrencyDollarIcon.js +1 -1
  459. package/dist/icons/DangerIcon.js +1 -1
  460. package/dist/icons/DashboardIcon.js +1 -1
  461. package/dist/icons/DatabaseIcon.js +1 -1
  462. package/dist/icons/DecimalDecreaseIcon.js +1 -1
  463. package/dist/icons/DecimalIncreaseIcon.js +1 -1
  464. package/dist/icons/DirectionIcon.js +1 -1
  465. package/dist/icons/DonutIcon.js +1 -1
  466. package/dist/icons/DownIcon.js +1 -1
  467. package/dist/icons/EditIcon.js +1 -1
  468. package/dist/icons/ExclamationCircleFilledIcon.js +1 -1
  469. package/dist/icons/ExclamationCircleIcon.js +1 -1
  470. package/dist/icons/ExclamationIcon.js +1 -1
  471. package/dist/icons/EyeIcon.js +1 -1
  472. package/dist/icons/EyeInvisibleIcon.js +1 -1
  473. package/dist/icons/FilterIcon.js +1 -1
  474. package/dist/icons/FolderFilledIcon.js +1 -1
  475. package/dist/icons/FolderIcon.js +1 -1
  476. package/dist/icons/FolderOpenFilledIcon.js +1 -1
  477. package/dist/icons/FolderOpenIcon.js +1 -1
  478. package/dist/icons/ForwardIcon.js +1 -1
  479. package/dist/icons/GripVerticalIcon.js +1 -1
  480. package/dist/icons/HierarchyIcon.js +1 -1
  481. package/dist/icons/HierarchyOpenIcon.js +1 -1
  482. package/dist/icons/Icon.js +1 -1
  483. package/dist/icons/InfoCircleIcon.js +1 -1
  484. package/dist/icons/InfoIcon.js +1 -1
  485. package/dist/icons/KeyIcon.js +1 -1
  486. package/dist/icons/LeftIcon.js +1 -1
  487. package/dist/icons/LineChartIcon.js +1 -1
  488. package/dist/icons/LoadingIcon.js +1 -1
  489. package/dist/icons/LockFilledIcon.js +1 -1
  490. package/dist/icons/LockIcon.js +1 -1
  491. package/dist/icons/MoreIcon.js +1 -1
  492. package/dist/icons/NotAllowedIcon.js +1 -1
  493. package/dist/icons/Number123Icon.js +1 -1
  494. package/dist/icons/NumberIcon.js +1 -1
  495. package/dist/icons/PauseCircleFilledIcon.js +1 -1
  496. package/dist/icons/PauseCircleIcon.js +1 -1
  497. package/dist/icons/PauseIcon.js +1 -1
  498. package/dist/icons/PercentageIcon.js +1 -1
  499. package/dist/icons/PieChartIcon.js +1 -1
  500. package/dist/icons/PipetteIcon.js +1 -1
  501. package/dist/icons/PlayCircleIcon.js +1 -1
  502. package/dist/icons/PlayIcon.js +1 -1
  503. package/dist/icons/PlusIcon.js +1 -1
  504. package/dist/icons/ProgressBarIcon.js +1 -1
  505. package/dist/icons/ReloadIcon.js +1 -1
  506. package/dist/icons/ReportIcon.js +1 -1
  507. package/dist/icons/ReturnIcon.js +1 -1
  508. package/dist/icons/RightIcon.js +1 -1
  509. package/dist/icons/RowTotalsIcon.js +1 -1
  510. package/dist/icons/SchemeIcon.js +1 -1
  511. package/dist/icons/SearchIcon.js +1 -1
  512. package/dist/icons/SemanticQueryIcon.js +1 -1
  513. package/dist/icons/SettingsIcon.js +1 -1
  514. package/dist/icons/ShieldFilledIcon.js +1 -1
  515. package/dist/icons/ShieldIcon.js +1 -1
  516. package/dist/icons/SlashIcon.js +1 -1
  517. package/dist/icons/SparklesIcon.js +1 -1
  518. package/dist/icons/SqlIcon.js +1 -1
  519. package/dist/icons/StatsIcon.js +1 -1
  520. package/dist/icons/StopIcon.js +1 -1
  521. package/dist/icons/StringIcon.js +1 -1
  522. package/dist/icons/SubtotalsIcon.js +1 -1
  523. package/dist/icons/SwitchIcon.js +1 -1
  524. package/dist/icons/TableIcon.js +1 -1
  525. package/dist/icons/ThumbsDownIcon.js +1 -1
  526. package/dist/icons/ThumbsUpIcon.js +1 -1
  527. package/dist/icons/ThunderboltCrossedIcon.js +1 -1
  528. package/dist/icons/ThunderboltFilledIcon.js +1 -1
  529. package/dist/icons/ThunderboltIcon.js +1 -1
  530. package/dist/icons/TimeIcon.js +1 -1
  531. package/dist/icons/TrashIcon.js +1 -1
  532. package/dist/icons/UnlockIcon.js +1 -1
  533. package/dist/icons/UpIcon.js +1 -1
  534. package/dist/icons/UserGroupIcon.js +1 -1
  535. package/dist/icons/UserIcon.js +1 -1
  536. package/dist/icons/UserLockIcon.js +1 -1
  537. package/dist/icons/ViewIcon.js +1 -1
  538. package/dist/icons/WarningFilledIcon.js +1 -1
  539. package/dist/icons/WarningIcon.js +1 -1
  540. package/dist/icons/wrap-icon.js +1 -1
  541. package/dist/index.d.ts +8 -5
  542. package/dist/index.js +6 -4
  543. package/dist/index.js.map +1 -1
  544. package/dist/probe/canonicalize.js +1 -1
  545. package/dist/probe/css.js +1 -1
  546. package/dist/probe/index.js +1 -1
  547. package/dist/provider.js +1 -1
  548. package/dist/providers/TrackingProvider.js +1 -1
  549. package/dist/providers/navigationAdapter.default.js +1 -1
  550. package/dist/tokens/base.js +1 -1
  551. package/dist/tokens/color-seed.d.ts +44 -0
  552. package/dist/tokens/color-seed.js +48 -0
  553. package/dist/tokens/color-seed.js.map +1 -0
  554. package/dist/tokens/color-theme.d.ts +9 -1
  555. package/dist/tokens/color-theme.js +20 -7
  556. package/dist/tokens/color-theme.js.map +1 -1
  557. package/dist/tokens/colors.js +20 -3
  558. package/dist/tokens/colors.js.map +1 -1
  559. package/dist/tokens/index.d.ts +2 -1
  560. package/dist/tokens/index.js +3 -2
  561. package/dist/tokens/index.js.map +1 -1
  562. package/dist/tokens/layout.js +1 -1
  563. package/dist/tokens/lazy-styles.js +1 -1
  564. package/dist/tokens/palette-config.d.ts +304 -77
  565. package/dist/tokens/palette-config.js +188 -49
  566. package/dist/tokens/palette-config.js.map +1 -1
  567. package/dist/tokens/palette.js +617 -163
  568. package/dist/tokens/palette.js.map +1 -1
  569. package/dist/tokens/shadows.js +1 -1
  570. package/dist/tokens/sizes.js +1 -1
  571. package/dist/tokens/spacing.js +1 -1
  572. package/dist/tokens/typography.js +1 -1
  573. package/dist/utils/ResizeSensor.js +1 -1
  574. package/dist/utils/is-dev-env.js +1 -1
  575. package/dist/utils/modules.js +1 -1
  576. package/dist/utils/promise.js +1 -1
  577. package/dist/utils/raf.js +1 -1
  578. package/dist/utils/random.js +1 -1
  579. package/dist/utils/range.js +1 -1
  580. package/dist/utils/react/RenderCache.js +1 -1
  581. package/dist/utils/react/Slots.js +1 -1
  582. package/dist/utils/react/chain.js +1 -1
  583. package/dist/utils/react/disabledProps.js +1 -1
  584. package/dist/utils/react/forwardRefWithGenerics.js +1 -1
  585. package/dist/utils/react/index.js +1 -1
  586. package/dist/utils/react/interactions.js +1 -1
  587. package/dist/utils/react/isTextOnly.js +1 -1
  588. package/dist/utils/react/mapProps.js +1 -1
  589. package/dist/utils/react/mergeProps.js +1 -1
  590. package/dist/utils/react/nullableValue.js +1 -1
  591. package/dist/utils/react/resolveIcon.js +1 -1
  592. package/dist/utils/react/sharedStore.js +1 -1
  593. package/dist/utils/react/useBufferedValue.js +1 -1
  594. package/dist/utils/react/useCombinedRefs.js +1 -1
  595. package/dist/utils/react/useControlledFocusVisible.js +1 -1
  596. package/dist/utils/react/useEventBus.js +1 -1
  597. package/dist/utils/react/useId.js +1 -1
  598. package/dist/utils/react/useIsDarwin.js +1 -1
  599. package/dist/utils/react/useKeySymbols.js +1 -1
  600. package/dist/utils/react/useLayoutEffect.js +1 -1
  601. package/dist/utils/react/useLocalStorage.js +1 -1
  602. package/dist/utils/react/useMergeStyles.js +1 -1
  603. package/dist/utils/react/usePopoverSync.js +1 -1
  604. package/dist/utils/react/useQaProps.js +1 -1
  605. package/dist/utils/react/useViewportSize.js +1 -1
  606. package/dist/utils/react/wrapNodeIfPlain.js +1 -1
  607. package/dist/utils/selection.js +1 -1
  608. package/dist/utils/styles.js +1 -1
  609. package/dist/utils/tree.js +1 -1
  610. package/dist/utils/warnings.js +1 -1
  611. package/dist/version.js +3 -3
  612. package/docs/Theming.md +471 -83
  613. package/docs/Usage.md +37 -2
  614. package/docs/components/Root.md +1 -1
  615. package/docs/components/actions/Button.md +9 -3
  616. package/docs/components/actions/ItemAction.md +3 -3
  617. package/docs/components/actions/MenuTrigger.md +54 -2
  618. package/docs/components/actions/SubMenuTrigger.md +2 -2
  619. package/docs/components/content/CopySnippet.md +5 -5
  620. package/docs/components/content/InlineInput.md +1 -1
  621. package/docs/components/content/Item.md +10 -10
  622. package/docs/components/content/ItemBadge.md +2 -2
  623. package/docs/components/content/PrismCode.md +1 -0
  624. package/docs/components/fields/ColorSwatch.md +155 -0
  625. package/docs/components/layout/Board.md +20 -2
  626. package/docs/components/other/NoDataIcon.md +48 -0
  627. package/docs/components/overlays/DialogTrigger.md +3 -2
  628. package/docs/components/status/LoadingAnimation.md +12 -0
  629. package/docs/glaze/api.md +155 -47
  630. package/docs/glaze/methodology.md +15 -13
  631. package/docs/glaze/migration.md +36 -0
  632. package/docs/glaze/okhst.md +4 -0
  633. package/docs/tasty/ai-agents.md +3 -0
  634. package/docs/tasty/configuration.md +1 -0
  635. package/package.json +3 -3
  636. package/dist/components/fields/color/ColorSwatch.js.map +0 -1
@@ -1,4 +1,5 @@
1
- /** @license MIT | @cube-dev/ui-kit v0.163.0 | Cube Dev Team */
1
+ /** @license MIT | @cube-dev/ui-kit v0.164.0 | Cube Dev Team */
2
+ import { colorSeed } from "./color-seed.js";
2
3
  import { useSyncExternalStore } from "react";
3
4
 
4
5
  //#region src/tokens/palette-config.ts
@@ -6,9 +7,10 @@ import { useSyncExternalStore } from "react";
6
7
  * Runtime configuration for the Glaze-generated color palette.
7
8
  *
8
9
  * The palette recipe itself lives in `./palette.ts`; this module owns only the
9
- * *seeds* it is built from, plus a version counter the token caches watch. It
10
- * deliberately imports nothing elsenot the token modules, not `glaze` so
11
- * there is no import cycle and no ordering hazard.
10
+ * *seeds* it is built from, plus a version counter the token caches watch. Its only
11
+ * import is the leaf `./color-seed` helper never the token modules so there is
12
+ * no import cycle and no ordering hazard. See that file for why reading a colour
13
+ * cannot depend on the global Glaze config `./palette.ts` installs.
12
14
  *
13
15
  * Reading is free; writing rebuilds the palette on the next token read. See
14
16
  * `Getting Started/Theming` in Storybook for the full contract.
@@ -22,6 +24,31 @@ const DEFAULT_NOTE_HUE = 302.3;
22
24
  /** Seed saturation. Per-color `saturation` in the recipe is a 0–1 factor of it. */
23
25
  const DEFAULT_SATURATION = 100;
24
26
  /**
27
+ * Share of {@link DEFAULT_SATURATION} the neutral `surface` carries — the recipe's
28
+ * own `SURFACE_SATURATION` factor, as a fraction.
29
+ *
30
+ * It lives here rather than in the recipe because the dependency runs one way:
31
+ * `./palette.ts` imports the config, not the reverse — and the config needs this
32
+ * number to default `baseSaturation`. It is the anchor of the base zone's own 0–100
33
+ * scale: the share the *accent zone* lends the chrome when no `baseColor` names one
34
+ * outright, so an untouched palette lands on `12`.
35
+ */
36
+ const SURFACE_SATURATION_SHARE = .12;
37
+ /**
38
+ * Ceiling on a base saturation derived from a {@link PaletteConfig.base} color.
39
+ *
40
+ * A named base color lands the chrome near itself rather than at the 12% share the
41
+ * accent lends it, which is the whole reason to name one — but a fully saturated
42
+ * chrome stops being chrome. `50` is well past where the base colors begin to
43
+ * converge (`surface-inverse` tops out around `25`), so the clip costs nothing that
44
+ * was still moving and stops a vivid brand hex from being read as an instruction to
45
+ * paint the page with it.
46
+ *
47
+ * The manual slider in the tuner shares the number, so the two agree on what the top
48
+ * of the range means.
49
+ */
50
+ const MAX_BASE_SATURATION = 50;
51
+ /**
25
52
  * Seed saturation for the `code-*` syntax family, deliberately **not** the same
26
53
  * constant as {@link DEFAULT_SATURATION}.
27
54
  *
@@ -39,38 +66,111 @@ const DEFAULT_THEME_HUES = {
39
66
  warning: DEFAULT_WARNING_HUE,
40
67
  note: DEFAULT_NOTE_HUE
41
68
  };
69
+ /**
70
+ * Warned once per process, not once per call.
71
+ *
72
+ * `resolveConfig` runs on every write AND on every `resolvePaletteConfig` preview, so an
73
+ * unguarded warning would fire on every frame of a slider drag.
74
+ */
75
+ let warnedAboutPastelSaturation = false;
76
+ /**
77
+ * One zone's seed, normalized to the four things a resolver can ask for.
78
+ *
79
+ * The single place a {@link PaletteSeed} is taken apart, so the six zones cannot drift
80
+ * in how they read one. A string that {@link colorSeed} cannot parse comes back as the
81
+ * numeric path with nothing pinned, which is exactly the fallback the docs promise: the
82
+ * zone keeps its defaults rather than the render going down over a typo.
83
+ */
84
+ function seedOf(seed) {
85
+ if (typeof seed === "string") {
86
+ const parsed = colorSeed(seed);
87
+ return parsed ? {
88
+ color: seed,
89
+ hue: parsed.hue,
90
+ saturation: parsed.saturation,
91
+ tone: parsed.tone
92
+ } : {
93
+ color: null,
94
+ hue: void 0,
95
+ saturation: void 0,
96
+ tone: null
97
+ };
98
+ }
99
+ return {
100
+ color: null,
101
+ hue: seed?.hue,
102
+ saturation: seed?.saturation,
103
+ tone: null
104
+ };
105
+ }
106
+ /**
107
+ * Whether a zone pinned a saturation *as a number*.
108
+ *
109
+ * A color's chroma is not a pinned saturation — it reaches the palette through `from`,
110
+ * not through the seed — so it must not answer the question `pastel` asks below.
111
+ */
112
+ function pinnedSaturation(seed) {
113
+ return typeof seed === "string" ? void 0 : seed?.saturation;
114
+ }
42
115
  function resolveConfig(input) {
43
- const saturation = input.saturation ?? DEFAULT_SATURATION;
44
116
  const themes = input.themes ?? {};
45
- const hue = input.hue ?? DEFAULT_HUE;
117
+ const accent = seedOf(input.accent);
118
+ const base = seedOf(input.base);
119
+ const accentSaturationPin = pinnedSaturation(input.accent);
120
+ const pastel = input.pastel ?? accentSaturationPin === void 0;
121
+ const hue = accent.hue ?? DEFAULT_HUE;
122
+ const baseHue = base.hue ?? hue;
123
+ if (input.pastel === true && accentSaturationPin !== void 0 && accentSaturationPin !== DEFAULT_SATURATION && !warnedAboutPastelSaturation) {
124
+ warnedAboutPastelSaturation = true;
125
+ console.warn(`[cube-ui-kit] palette \`accent.saturation\` (${accentSaturationPin}) is ignored because \`pastel\` is on — pastel pins it to ${DEFAULT_SATURATION}. Drop \`pastel\`, or set it to \`false\`, to tune saturation yourself.`);
126
+ }
127
+ const saturation = pastel ? DEFAULT_SATURATION : accentSaturationPin ?? DEFAULT_SATURATION;
46
128
  return {
47
129
  hue,
48
- baseHue: input.baseHue ?? hue,
130
+ baseHue,
49
131
  saturation,
50
- pastel: input.pastel ?? true,
132
+ baseSaturation: pinnedSaturation(input.base) ?? (base.color ? Math.min(base.saturation, MAX_BASE_SATURATION, saturation) : Math.min(accent.saturation ?? saturation, saturation) * SURFACE_SATURATION_SHARE),
133
+ surfaceMode: input.surfaceMode ?? "neutral",
134
+ accentColor: accent.color,
135
+ baseColor: base.color,
136
+ accentTone: accent.tone,
137
+ accentSaturation: accent.color === null ? null : accent.saturation,
138
+ pastel,
51
139
  contrastLevel: input.contrastLevel ?? "auto",
52
140
  themes: {
53
- success: {
54
- hue: themes.success?.hue ?? DEFAULT_THEME_HUES.success,
55
- saturation: themes.success?.saturation ?? saturation
56
- },
57
- danger: {
58
- hue: themes.danger?.hue ?? DEFAULT_THEME_HUES.danger,
59
- saturation: themes.danger?.saturation ?? saturation
60
- },
61
- warning: {
62
- hue: themes.warning?.hue ?? DEFAULT_THEME_HUES.warning,
63
- saturation: themes.warning?.saturation ?? saturation
64
- },
65
- note: {
66
- hue: themes.note?.hue ?? DEFAULT_THEME_HUES.note,
67
- saturation: themes.note?.saturation ?? saturation
68
- },
141
+ success: resolveThemeSeed("success", themes.success, saturation),
142
+ danger: resolveThemeSeed("danger", themes.danger, saturation),
143
+ warning: resolveThemeSeed("warning", themes.warning, saturation),
144
+ note: resolveThemeSeed("note", themes.note, saturation),
69
145
  code: { saturation: themes.code?.saturation ?? DEFAULT_CODE_SATURATION }
70
146
  }
71
147
  };
72
148
  }
73
149
  /**
150
+ * One status theme's seed, on whichever path it is on.
151
+ *
152
+ * The color arm takes all three of the color's components — hue, chroma and tone — where
153
+ * the accent zone deliberately leaves the chroma out of its seed. The asymmetry is the
154
+ * point: this seed is inherited by nothing, so raising it re-chromatizes nothing, and
155
+ * raising it is what holds the theme's tinted surface, border and text ramp in the same
156
+ * proportion to the fill that the shipped derivation gives them.
157
+ */
158
+ function resolveThemeSeed(name, seed, saturation) {
159
+ const parsed = seedOf(seed);
160
+ if (parsed.color !== null) return {
161
+ hue: parsed.hue,
162
+ saturation: parsed.saturation,
163
+ color: parsed.color,
164
+ colorTone: parsed.tone
165
+ };
166
+ return {
167
+ hue: parsed.hue ?? DEFAULT_THEME_HUES[name],
168
+ saturation: parsed.saturation ?? saturation,
169
+ color: null,
170
+ colorTone: null
171
+ };
172
+ }
173
+ /**
74
174
  * The palette as it ships, with no tuning applied. Frozen — it is the baseline
75
175
  * every resolution falls back to, so a stray write to it would move the defaults
76
176
  * for the whole process.
@@ -87,6 +187,16 @@ let resolved = DEFAULT_PALETTE_CONFIG;
87
187
  let version = 0;
88
188
  const listeners = /* @__PURE__ */ new Set();
89
189
  function mergeSeed(base, patch) {
190
+ if (patch === void 0) return base;
191
+ if (base === void 0) return patch;
192
+ if (typeof base === "string" || typeof patch === "string") return patch;
193
+ return {
194
+ ...base,
195
+ ...patch
196
+ };
197
+ }
198
+ /** {@link mergeSeed} for the code seed, which is not a {@link PaletteSeed}. */
199
+ function mergeCodeSeed(base, patch) {
90
200
  if (!base) return patch;
91
201
  if (!patch) return base;
92
202
  return {
@@ -101,40 +211,62 @@ function mergeSeed(base, patch) {
101
211
  function mergeInput(base, patch) {
102
212
  const next = {
103
213
  ...base,
104
- ...patch
214
+ ...patch,
215
+ ...base.accent !== void 0 || patch.accent !== void 0 ? { accent: mergeSeed(base.accent, patch.accent) } : null,
216
+ ...base.base !== void 0 || patch.base !== void 0 ? { base: mergeSeed(base.base, patch.base) } : null
105
217
  };
106
218
  if (base.themes || patch.themes) next.themes = {
107
219
  success: mergeSeed(base.themes?.success, patch.themes?.success),
108
220
  danger: mergeSeed(base.themes?.danger, patch.themes?.danger),
109
221
  warning: mergeSeed(base.themes?.warning, patch.themes?.warning),
110
222
  note: mergeSeed(base.themes?.note, patch.themes?.note),
111
- code: mergeSeed(base.themes?.code, patch.themes?.code)
223
+ code: mergeCodeSeed(base.themes?.code, patch.themes?.code)
112
224
  };
113
225
  return next;
114
226
  }
115
227
  function isSameConfig(a, b) {
116
- return a.hue === b.hue && a.baseHue === b.baseHue && a.saturation === b.saturation && a.pastel === b.pastel && a.contrastLevel === b.contrastLevel && a.themes.code.saturation === b.themes.code.saturation && [
228
+ return a.hue === b.hue && a.baseHue === b.baseHue && a.saturation === b.saturation && a.baseSaturation === b.baseSaturation && a.surfaceMode === b.surfaceMode && a.accentColor === b.accentColor && a.baseColor === b.baseColor && a.pastel === b.pastel && a.contrastLevel === b.contrastLevel && a.themes.code.saturation === b.themes.code.saturation && [
117
229
  "success",
118
230
  "danger",
119
231
  "warning",
120
232
  "note"
121
- ].every((name) => a.themes[name].hue === b.themes[name].hue && a.themes[name].saturation === b.themes[name].saturation);
233
+ ].every((name) => a.themes[name].hue === b.themes[name].hue && a.themes[name].saturation === b.themes[name].saturation && a.themes[name].color === b.themes[name].color);
122
234
  }
123
235
  /**
124
236
  * Which fields are *explicitly set*, as an order-independent signature.
125
237
  *
126
238
  * Two configs can resolve to identical values yet differ in which of those values
127
- * are pinned rather than inherited — pinning `primary.saturation` to the number it
128
- * already inherited is the obvious case. That difference is invisible to
239
+ * are pinned rather than inherited — pinning a status theme's saturation to the number
240
+ * it already inherited is the obvious case. That difference is invisible to
129
241
  * {@link isSameConfig} but visible through {@link getPaletteConfigInput}, so it has
130
242
  * to count as a change or a settings UI reading it would never re-render.
131
243
  */
132
244
  function pinSignature(config) {
133
245
  const set = (value) => value === void 0 ? "0" : "1";
246
+ /**
247
+ * One zone, in three states — absent, numbers, or a color.
248
+ *
249
+ * **Absent is its own state**, not "numbers with nothing pinned". `base: {}` and no
250
+ * `base` at all resolve identically, but they read back differently, and a settings UI
251
+ * asks exactly that question to decide whether the chrome has a seed of its own or is
252
+ * still following the accent.
253
+ *
254
+ * A color carries its **value**, not just its presence. Presence alone cannot tell one
255
+ * unparseable string from another: both resolve to `null`, so `isSameConfig` sees no
256
+ * movement either, and replacing `'bad-one'` with `'bad-two'` returned early — leaving
257
+ * the field on the first string with no notification that the write was dropped.
258
+ *
259
+ * It also means two spellings of the same color (`'#ff0000'` / `'rgb(255 0 0)'`) bump
260
+ * the version. That is the same argument the presence check was already making: what a
261
+ * settings UI reads back changed, so it has to re-render. And a zone switching from
262
+ * `{ hue: 45 }` to a color that happens to derive hue 45 resolves to the same numbers,
263
+ * so without this the version would never bump and the control would look stuck.
264
+ */
265
+ const seed = (value) => value === void 0 ? "-" : typeof value === "string" ? JSON.stringify(value) : `${set(value.hue)}${set(value.saturation)}`;
134
266
  return [
135
- set(config.hue),
136
- set(config.baseHue),
137
- set(config.saturation),
267
+ seed(config.accent),
268
+ seed(config.base),
269
+ set(config.surfaceMode),
138
270
  set(config.pastel),
139
271
  set(config.contrastLevel),
140
272
  set(config.themes?.code?.saturation),
@@ -143,8 +275,8 @@ function pinSignature(config) {
143
275
  "danger",
144
276
  "warning",
145
277
  "note"
146
- ].flatMap((name) => [set(config.themes?.[name]?.hue), set(config.themes?.[name]?.saturation)])
147
- ].join("");
278
+ ].map((name) => seed(config.themes?.[name]))
279
+ ].join("|");
148
280
  }
149
281
  /**
150
282
  * Snapshot a config so the store owns it outright.
@@ -159,21 +291,27 @@ function pinSignature(config) {
159
291
  function freezeConfig(config) {
160
292
  const themes = config.themes;
161
293
  if (themes) {
162
- for (const seed of Object.values(themes)) if (seed) Object.freeze(seed);
294
+ for (const seed of Object.values(themes)) if (seed && typeof seed === "object") Object.freeze(seed);
163
295
  Object.freeze(themes);
164
296
  }
297
+ const zones = config;
298
+ if (typeof zones.accent === "object") Object.freeze(zones.accent);
299
+ if (typeof zones.base === "object") Object.freeze(zones.base);
165
300
  return Object.freeze(config);
166
301
  }
167
302
  /** {@link freezeConfig} on a copy, for a config we did not create ourselves. */
168
303
  function snapshotConfig(config) {
169
304
  const themes = config.themes;
305
+ const copy = (seed) => typeof seed === "object" ? { ...seed } : seed;
170
306
  return freezeConfig({
171
307
  ...config,
308
+ ...config.accent !== void 0 ? { accent: copy(config.accent) } : null,
309
+ ...config.base !== void 0 ? { base: copy(config.base) } : null,
172
310
  ...themes ? { themes: {
173
- ...themes.success ? { success: { ...themes.success } } : null,
174
- ...themes.danger ? { danger: { ...themes.danger } } : null,
175
- ...themes.warning ? { warning: { ...themes.warning } } : null,
176
- ...themes.note ? { note: { ...themes.note } } : null,
311
+ ...themes.success !== void 0 ? { success: copy(themes.success) } : null,
312
+ ...themes.danger !== void 0 ? { danger: copy(themes.danger) } : null,
313
+ ...themes.warning !== void 0 ? { warning: copy(themes.warning) } : null,
314
+ ...themes.note !== void 0 ? { note: copy(themes.note) } : null,
177
315
  ...themes.code ? { code: { ...themes.code } } : null
178
316
  } } : null
179
317
  });
@@ -195,8 +333,8 @@ function commit(nextInput) {
195
333
  * object, and re-applying the same object twice is the same as applying it once.
196
334
  *
197
335
  * ```ts
198
- * setPaletteConfig({ hue: 200, baseHue: 60 });
199
- * setPaletteConfig({ hue: 200 }); // baseHue is gone — back to inheriting `hue`
336
+ * setPaletteConfig({ accent: { hue: 200 }, base: { hue: 60 } });
337
+ * setPaletteConfig({ accent: { hue: 200 } }); // `base` is gone — follows accent again
200
338
  * ```
201
339
  *
202
340
  * To adjust one field of the config already in place — a slider in a settings UI —
@@ -204,7 +342,7 @@ function commit(nextInput) {
204
342
  * preserves which fields are pinned and which still inherit:
205
343
  *
206
344
  * ```ts
207
- * setPaletteConfig((config) => ({ ...config, hue: 200 }));
345
+ * setPaletteConfig((config) => ({ ...config, accent: { hue: 200 } }));
208
346
  * ```
209
347
  *
210
348
  * The palette is rebuilt lazily on the next token read, and any mounted `<Root>`
@@ -246,10 +384,10 @@ function getPaletteConfig() {
246
384
  * to offer a way back:
247
385
  *
248
386
  * ```ts
249
- * const pinned = getPaletteConfigInput().baseHue !== undefined;
387
+ * const own = getPaletteConfigInput().base !== undefined;
250
388
  *
251
389
  * // Re-link it to the brand by dropping the field.
252
- * setPaletteConfig(({ baseHue, ...config }) => config);
390
+ * setPaletteConfig(({ base, ...config }) => config);
253
391
  * ```
254
392
  *
255
393
  * It is also the value handed to a {@link setPaletteConfig} updater.
@@ -301,12 +439,13 @@ function usePaletteVersion() {
301
439
  *
302
440
  * <HueSlider
303
441
  * value={palette.hue}
304
- * onChange={(hue) => setPalette((config) => ({ ...config, hue }))}
442
+ * onChange={(hue) => setPalette((config) => ({ ...config, accent: { hue } }))}
305
443
  * />
306
444
  * ```
307
445
  *
308
- * The first element is the *resolved* config, so `palette.hue` is always a number.
309
- * The updater's argument is the sparse one see {@link getPaletteConfigInput}.
446
+ * The first element is the *resolved* config, so `palette.hue` is always a number — it
447
+ * keeps the flat shape the recipe reads, whichever way the zones were seeded. The
448
+ * updater's argument is the sparse one — see {@link getPaletteConfigInput}.
310
449
  *
311
450
  * The config is global process state, so every consumer of this hook — and every
312
451
  * mounted `<Root>` — sees the same palette.
@@ -317,5 +456,5 @@ function usePaletteConfig() {
317
456
  }
318
457
 
319
458
  //#endregion
320
- export { DEFAULT_PALETTE_CONFIG, DEFAULT_SATURATION, getPaletteConfig, getPaletteConfigInput, getPaletteVersion, invalidatePaletteTokens, resetPaletteConfig, resolvePaletteConfig, setPaletteConfig, subscribePaletteConfig, usePaletteConfig, usePaletteVersion };
459
+ export { DEFAULT_HUE, DEFAULT_PALETTE_CONFIG, DEFAULT_SATURATION, MAX_BASE_SATURATION, SURFACE_SATURATION_SHARE, getPaletteConfig, getPaletteConfigInput, getPaletteVersion, invalidatePaletteTokens, resetPaletteConfig, resolvePaletteConfig, setPaletteConfig, subscribePaletteConfig, usePaletteConfig, usePaletteVersion };
321
460
  //# sourceMappingURL=palette-config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"palette-config.js","names":[],"sources":["../../src/tokens/palette-config.ts"],"sourcesContent":["import { useSyncExternalStore } from 'react';\n\n/**\n * Runtime configuration for the Glaze-generated color palette.\n *\n * The palette recipe itself lives in `./palette.ts`; this module owns only the\n * *seeds* it is built from, plus a version counter the token caches watch. It\n * deliberately imports nothing else — not the token modules, not `glaze` — so\n * there is no import cycle and no ordering hazard.\n *\n * Reading is free; writing rebuilds the palette on the next token read. See\n * `Getting Started/Theming` in Storybook for the full contract.\n */\n\n// ============================================================================\n// Default seeds\n// ============================================================================\n\n/** Brand hue. Drives the `default`, `primary`, `purple` and `special` themes. */\nexport const DEFAULT_HUE = 280.3;\nexport const DEFAULT_SUCCESS_HUE = 156.9;\nexport const DEFAULT_DANGER_HUE = 23.1;\nexport const DEFAULT_WARNING_HUE = 84.3;\nexport const DEFAULT_NOTE_HUE = 302.3;\n\n/** Seed saturation. Per-color `saturation` in the recipe is a 0–1 factor of it. */\nexport const DEFAULT_SATURATION = 100;\n\n/**\n * Seed saturation for the `code-*` syntax family, deliberately **not** the same\n * constant as {@link DEFAULT_SATURATION}.\n *\n * The two were one value until the app seed moved to 100 for the pastel palette.\n * Sharing it would have dragged the syntax colors along for the ride — the exact\n * coupling that `PaletteCodeSeed` and the `pastel` opt-out both exist to prevent,\n * and the reason the code family answers to its own saturation and nothing else.\n * Splitting the constant is what lets the app seed move while the code palette\n * stays on the value it was calibrated against.\n */\nexport const DEFAULT_CODE_SATURATION = 80;\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Hue / saturation seed override for one colored theme. */\nexport interface PaletteThemeSeed {\n /** Hue in degrees (0–360). */\n hue?: number;\n /** Saturation (0–100). Defaults to the palette-level `saturation`. */\n saturation?: number;\n}\n\n/**\n * The `code-*` syntax family takes a saturation and nothing else — and answers to\n * nothing else either. This is the one knob that moves it.\n *\n * Its hues are absolute literals by design, so syntax colors never rotate with the\n * brand — strings would collide with numbers the moment the brand went green. Unlike\n * every other theme, its saturation does **not** inherit the palette-level one: the\n * code palette is calibrated once and stays there, so re-seeding the app cannot\n * quietly wash out a code block. {@link PaletteConfig.pastel} skips it for the same\n * reason.\n */\nexport interface PaletteCodeSeed {\n /** Saturation (0–100). Defaults to {@link DEFAULT_CODE_SATURATION}, not to `saturation`. */\n saturation?: number;\n}\n\n/** Names of the themes whose seeds can be overridden individually. */\nexport type PaletteThemeName =\n | 'success'\n | 'danger'\n | 'warning'\n | 'note'\n | 'code';\n\n/**\n * Palette tuning. Every field is optional; omitted fields keep their default,\n * and per-theme fields left unset inherit the palette-level value.\n */\nexport interface PaletteConfig {\n /**\n * Accent hue in degrees (0–360) — the brand.\n *\n * Drives the `accent-*` family on every theme, `primary` / `purple` / `special`,\n * and the brand-tinted odds and ends (`focus`, the loading faces, the disabled\n * chip). Also the default for {@link PaletteConfig.baseHue}.\n */\n hue?: number;\n /**\n * Hue of the **base** zone in degrees (0–360): the neutral chrome — `surface`\n * and its ladder, the `surface-text*` ramp, `border`, `placeholder`. Defaults to\n * `hue`, so the chrome carries a faint tint of the brand unless you say otherwise.\n *\n * Splitting it lets the chrome sit on a different hue from the accent — a warm\n * grey UI with a cool blue brand, say. Only the `default` theme is affected: a\n * colored theme's tinted `surface` deliberately follows *its own* hue, because a\n * danger banner should read as red.\n */\n baseHue?: number;\n /**\n * Seed saturation (0–100), and the fallback for every theme that does not set\n * its own.\n *\n * One scale for the whole theme, by design. Every color's own `saturation` is a\n * 0–1 factor of this seed — `surface` at 0.12, `border` at 0.175, the text ramp\n * at 0.2, the accent family at ~1.0 — so moving it rescales the palette while\n * keeping those proportions. Unlike hue, which is configured separately for the\n * brand and for each status theme, saturation is deliberately not split: the\n * ratio between a subtle surface tint and a saturated accent is part of the\n * design.\n */\n saturation?: number;\n /**\n * Global. Widens the usable chroma range by relaxing the sRGB-safe limit,\n * producing a softer, more even palette across hues. Glaze treats `pastel` as\n * instance-level, so it is threaded into every theme.\n *\n * Every theme except `code`. The syntax family is calibrated on its own\n * saturation and is deliberately left out — softening it collapses the chroma\n * spread the syntax hues rely on to stay apart. To soften a code block, lower\n * {@link PaletteCodeSeed.saturation} instead.\n */\n pastel?: boolean;\n /**\n * Global. `'auto'` (the default) keeps the two-tier model: normal colors plus\n * a separate high-contrast tier driven by `<html data-contrast=\"high\">` /\n * `prefers-contrast: more`.\n *\n * A number (0–100) replaces that switch with a slider — and, because a manual\n * level already carries the contrast preference, **drops the high-contrast\n * tier entirely**. `0` reproduces the normal output and `100` the\n * high-contrast output, bit for bit.\n */\n contrastLevel?: number | 'auto';\n /** Per-theme seed overrides. */\n themes?: {\n success?: PaletteThemeSeed;\n danger?: PaletteThemeSeed;\n warning?: PaletteThemeSeed;\n note?: PaletteThemeSeed;\n code?: PaletteCodeSeed;\n };\n}\n\n/** {@link PaletteConfig} with every field resolved to a concrete value. */\nexport interface ResolvedPaletteConfig {\n hue: number;\n baseHue: number;\n saturation: number;\n pastel: boolean;\n contrastLevel: number | 'auto';\n themes: {\n success: { hue: number; saturation: number };\n danger: { hue: number; saturation: number };\n warning: { hue: number; saturation: number };\n note: { hue: number; saturation: number };\n code: { saturation: number };\n };\n}\n\n// ============================================================================\n// Resolution\n// ============================================================================\n\nconst DEFAULT_THEME_HUES = {\n success: DEFAULT_SUCCESS_HUE,\n danger: DEFAULT_DANGER_HUE,\n warning: DEFAULT_WARNING_HUE,\n note: DEFAULT_NOTE_HUE,\n} as const;\n\nfunction resolveConfig(input: PaletteConfig): ResolvedPaletteConfig {\n const saturation = input.saturation ?? DEFAULT_SATURATION;\n const themes = input.themes ?? {};\n\n const hue = input.hue ?? DEFAULT_HUE;\n\n return {\n hue,\n baseHue: input.baseHue ?? hue,\n saturation,\n pastel: input.pastel ?? true,\n contrastLevel: input.contrastLevel ?? 'auto',\n themes: {\n success: {\n hue: themes.success?.hue ?? DEFAULT_THEME_HUES.success,\n saturation: themes.success?.saturation ?? saturation,\n },\n danger: {\n hue: themes.danger?.hue ?? DEFAULT_THEME_HUES.danger,\n saturation: themes.danger?.saturation ?? saturation,\n },\n warning: {\n hue: themes.warning?.hue ?? DEFAULT_THEME_HUES.warning,\n saturation: themes.warning?.saturation ?? saturation,\n },\n note: {\n hue: themes.note?.hue ?? DEFAULT_THEME_HUES.note,\n saturation: themes.note?.saturation ?? saturation,\n },\n // Deliberately NOT `?? saturation`: the code palette is calibrated once and\n // does not follow the palette-level seed. See `PaletteCodeSeed`.\n code: { saturation: themes.code?.saturation ?? DEFAULT_CODE_SATURATION },\n },\n };\n}\n\n/**\n * The palette as it ships, with no tuning applied. Frozen — it is the baseline\n * every resolution falls back to, so a stray write to it would move the defaults\n * for the whole process.\n */\nexport const DEFAULT_PALETTE_CONFIG: ResolvedPaletteConfig = freezeConfig(\n resolveConfig({}),\n);\n\n// ============================================================================\n// Store\n// ============================================================================\n\n/**\n * The last config *as written*, sparse — not the resolved one. Keeping the sparse\n * form is what makes inheritance live: a `saturation` with no `themes.danger.\n * saturation` beside it means danger genuinely follows the palette, rather than\n * having been frozen at whatever the palette read when it was set.\n */\nlet input: PaletteConfig = {};\nlet resolved: ResolvedPaletteConfig = DEFAULT_PALETTE_CONFIG;\nlet version = 0;\n\nconst listeners = new Set<() => void>();\n\nfunction mergeSeed<T extends PaletteThemeSeed | PaletteCodeSeed>(\n base: T | undefined,\n patch: T | undefined,\n): T | undefined {\n if (!base) return patch;\n // A patch that omits the theme entirely says nothing about it, so the base\n // survives. Clearing one of its fields is `{ theme: { hue: undefined } }`.\n if (!patch) return base;\n\n return { ...base, ...patch };\n}\n\n/**\n * Layer a patch over a base config. Used by {@link resolvePaletteConfig} for\n * previews — *not* by {@link setPaletteConfig}, which replaces.\n */\nfunction mergeInput(base: PaletteConfig, patch: PaletteConfig): PaletteConfig {\n const next: PaletteConfig = { ...base, ...patch };\n\n if (base.themes || patch.themes) {\n next.themes = {\n success: mergeSeed(base.themes?.success, patch.themes?.success),\n danger: mergeSeed(base.themes?.danger, patch.themes?.danger),\n warning: mergeSeed(base.themes?.warning, patch.themes?.warning),\n note: mergeSeed(base.themes?.note, patch.themes?.note),\n code: mergeSeed(base.themes?.code, patch.themes?.code),\n };\n }\n\n return next;\n}\n\nfunction isSameConfig(a: ResolvedPaletteConfig, b: ResolvedPaletteConfig) {\n return (\n a.hue === b.hue &&\n a.baseHue === b.baseHue &&\n a.saturation === b.saturation &&\n a.pastel === b.pastel &&\n a.contrastLevel === b.contrastLevel &&\n a.themes.code.saturation === b.themes.code.saturation &&\n (['success', 'danger', 'warning', 'note'] as const).every(\n (name) =>\n a.themes[name].hue === b.themes[name].hue &&\n a.themes[name].saturation === b.themes[name].saturation,\n )\n );\n}\n\n/**\n * Which fields are *explicitly set*, as an order-independent signature.\n *\n * Two configs can resolve to identical values yet differ in which of those values\n * are pinned rather than inherited — pinning `primary.saturation` to the number it\n * already inherited is the obvious case. That difference is invisible to\n * {@link isSameConfig} but visible through {@link getPaletteConfigInput}, so it has\n * to count as a change or a settings UI reading it would never re-render.\n */\nfunction pinSignature(config: PaletteConfig): string {\n const set = (value: unknown) => (value === undefined ? '0' : '1');\n\n return [\n set(config.hue),\n set(config.baseHue),\n set(config.saturation),\n set(config.pastel),\n set(config.contrastLevel),\n set(config.themes?.code?.saturation),\n ...(['success', 'danger', 'warning', 'note'] as const).flatMap((name) => [\n set(config.themes?.[name]?.hue),\n set(config.themes?.[name]?.saturation),\n ]),\n ].join('');\n}\n\n/**\n * Snapshot a config so the store owns it outright.\n *\n * Two hazards this closes. The caller's object stays theirs, so mutating it after\n * the call cannot desync `input` from `resolved`. And what we hand back from\n * {@link getPaletteConfig} / {@link getPaletteConfigInput} is frozen, so a caller\n * who writes to it gets a clear failure rather than silently corrupting the store\n * — the version would not bump, and every token cache would go on serving values\n * that no longer match the config.\n */\nfunction freezeConfig<T extends PaletteConfig | ResolvedPaletteConfig>(\n config: T,\n): T {\n const themes = config.themes;\n\n if (themes) {\n for (const seed of Object.values(themes)) {\n if (seed) Object.freeze(seed);\n }\n Object.freeze(themes);\n }\n\n return Object.freeze(config);\n}\n\n/** {@link freezeConfig} on a copy, for a config we did not create ourselves. */\nfunction snapshotConfig(config: PaletteConfig): PaletteConfig {\n const themes = config.themes;\n\n return freezeConfig({\n ...config,\n ...(themes\n ? {\n themes: {\n ...(themes.success ? { success: { ...themes.success } } : null),\n ...(themes.danger ? { danger: { ...themes.danger } } : null),\n ...(themes.warning ? { warning: { ...themes.warning } } : null),\n ...(themes.note ? { note: { ...themes.note } } : null),\n ...(themes.code ? { code: { ...themes.code } } : null),\n },\n }\n : null),\n });\n}\n\nfunction commit(nextInput: PaletteConfig) {\n const nextResolved = resolveConfig(nextInput);\n\n // Bail before bumping the version when nothing observable moved, so re-applying\n // the same config — an inline `<Root palette={{ … }}>` literal on every\n // render, a StrictMode double render — costs nothing.\n if (\n isSameConfig(resolved, nextResolved) &&\n pinSignature(input) === pinSignature(nextInput)\n ) {\n return;\n }\n\n input = snapshotConfig(nextInput);\n resolved = freezeConfig(nextResolved);\n version++;\n\n listeners.forEach((listener) => listener());\n}\n\n/**\n * Set the palette config.\n *\n * **Replaces**, like `useState` — the config you pass *is* the config, resolved\n * against the shipped defaults. Nothing accumulates, so a field you leave out is a\n * field you do not have: dropping a customization means dropping it from the\n * object, and re-applying the same object twice is the same as applying it once.\n *\n * ```ts\n * setPaletteConfig({ hue: 200, baseHue: 60 });\n * setPaletteConfig({ hue: 200 }); // baseHue is gone — back to inheriting `hue`\n * ```\n *\n * To adjust one field of the config already in place — a slider in a settings UI —\n * pass an updater. It receives the config as written, sparse, so spreading it\n * preserves which fields are pinned and which still inherit:\n *\n * ```ts\n * setPaletteConfig((config) => ({ ...config, hue: 200 }));\n * ```\n *\n * The palette is rebuilt lazily on the next token read, and any mounted `<Root>`\n * re-injects the token block — no component re-render is involved, because every\n * color in the kit resolves through a CSS custom property.\n */\nexport function setPaletteConfig(\n config: PaletteConfig | ((previous: PaletteConfig) => PaletteConfig),\n): void {\n commit(typeof config === 'function' ? config(input) : config);\n}\n\n/**\n * Drop all tuning and restore the palette the kit ships with. Identical to\n * `setPaletteConfig({})`; it exists to be readable at a call site.\n */\nexport function resetPaletteConfig(): void {\n commit({});\n}\n\n/**\n * Force every token to re-resolve without changing the palette config.\n *\n * Only needed if you drive Glaze directly — `glaze.configure({ darkTone })` and\n * the like. Glaze invalidates its own caches, but the kit's token maps are\n * memoized against *this* module's version, so they need telling. Calling it\n * before the first paint is unnecessary; nothing has been resolved yet.\n */\nexport function invalidatePaletteTokens(): void {\n version++;\n listeners.forEach((listener) => listener());\n}\n\n/** The current config, with every field resolved to a concrete value. */\nexport function getPaletteConfig(): ResolvedPaletteConfig {\n return resolved;\n}\n\n/**\n * The config **as set**, sparse — omitted fields are the ones still inheriting.\n *\n * {@link getPaletteConfig} resolves everything to concrete values, which loses the\n * distinction between \"explicitly 80\" and \"80 because it follows the brand\". A\n * settings UI needs that distinction to show an inherited value as inherited, and\n * to offer a way back:\n *\n * ```ts\n * const pinned = getPaletteConfigInput().baseHue !== undefined;\n *\n * // Re-link it to the brand by dropping the field.\n * setPaletteConfig(({ baseHue, ...config }) => config);\n * ```\n *\n * It is also the value handed to a {@link setPaletteConfig} updater.\n */\nexport function getPaletteConfigInput(): PaletteConfig {\n return input;\n}\n\n/**\n * Resolve a patch **over** the current config, without applying it.\n *\n * This one layers, unlike {@link setPaletteConfig}: a preview wants \"the theme in\n * use, but in dark\", so the fields it does not mention have to come from the live\n * config rather than from the defaults. The store is not touched and no listener\n * fires.\n */\nexport function resolvePaletteConfig(\n config?: PaletteConfig,\n): ResolvedPaletteConfig {\n if (!config) return resolved;\n\n return resolveConfig(mergeInput(input, config));\n}\n\n/**\n * Increments whenever the resolved config changes. The token caches in\n * `./palette.ts`, `./colors.ts` and `./index.ts` compare against it instead of\n * memoizing forever — the same idiom Glaze uses for its own config version.\n */\nexport function getPaletteVersion(): number {\n return version;\n}\n\n/** Subscribe to palette config changes. Returns an unsubscribe function. */\nexport function subscribePaletteConfig(listener: () => void): () => void {\n listeners.add(listener);\n\n return () => {\n listeners.delete(listener);\n };\n}\n\n// ============================================================================\n// React bindings\n// ============================================================================\n\n/**\n * Re-render on palette config changes. Returns the version, not the config, so\n * the snapshot is a primitive and React never warns about an uncached snapshot.\n */\nexport function usePaletteVersion(): number {\n return useSyncExternalStore(\n subscribePaletteConfig,\n getPaletteVersion,\n getPaletteVersion,\n );\n}\n\n/**\n * Read and tune the palette from React, `useState`-style — including the part\n * where the setter *replaces*. A control that changes one field wants the updater\n * form, or it will drop every other field:\n *\n * ```tsx\n * const [palette, setPalette] = usePaletteConfig();\n *\n * <HueSlider\n * value={palette.hue}\n * onChange={(hue) => setPalette((config) => ({ ...config, hue }))}\n * />\n * ```\n *\n * The first element is the *resolved* config, so `palette.hue` is always a number.\n * The updater's argument is the sparse one — see {@link getPaletteConfigInput}.\n *\n * The config is global process state, so every consumer of this hook — and every\n * mounted `<Root>` — sees the same palette.\n */\nexport function usePaletteConfig(): readonly [\n ResolvedPaletteConfig,\n typeof setPaletteConfig,\n] {\n usePaletteVersion();\n\n return [getPaletteConfig(), setPaletteConfig];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"palette-config.js","names":[],"sources":["../../src/tokens/palette-config.ts"],"sourcesContent":["import { useSyncExternalStore } from 'react';\n\nimport { colorSeed } from './color-seed';\n\n/**\n * Runtime configuration for the Glaze-generated color palette.\n *\n * The palette recipe itself lives in `./palette.ts`; this module owns only the\n * *seeds* it is built from, plus a version counter the token caches watch. Its only\n * import is the leaf `./color-seed` helper — never the token modules — so there is\n * no import cycle and no ordering hazard. See that file for why reading a colour\n * cannot depend on the global Glaze config `./palette.ts` installs.\n *\n * Reading is free; writing rebuilds the palette on the next token read. See\n * `Getting Started/Theming` in Storybook for the full contract.\n */\n\n// ============================================================================\n// Default seeds\n// ============================================================================\n\n/** Brand hue. Drives the `default`, `primary`, `purple` and `special` themes. */\nexport const DEFAULT_HUE = 280.3;\nexport const DEFAULT_SUCCESS_HUE = 156.9;\nexport const DEFAULT_DANGER_HUE = 23.1;\nexport const DEFAULT_WARNING_HUE = 84.3;\nexport const DEFAULT_NOTE_HUE = 302.3;\n\n/** Seed saturation. Per-color `saturation` in the recipe is a 0–1 factor of it. */\nexport const DEFAULT_SATURATION = 100;\n\n/**\n * Share of {@link DEFAULT_SATURATION} the neutral `surface` carries — the recipe's\n * own `SURFACE_SATURATION` factor, as a fraction.\n *\n * It lives here rather than in the recipe because the dependency runs one way:\n * `./palette.ts` imports the config, not the reverse — and the config needs this\n * number to default `baseSaturation`. It is the anchor of the base zone's own 0–100\n * scale: the share the *accent zone* lends the chrome when no `baseColor` names one\n * outright, so an untouched palette lands on `12`.\n */\nexport const SURFACE_SATURATION_SHARE = 0.12;\n\n/**\n * Ceiling on a base saturation derived from a {@link PaletteConfig.base} color.\n *\n * A named base color lands the chrome near itself rather than at the 12% share the\n * accent lends it, which is the whole reason to name one — but a fully saturated\n * chrome stops being chrome. `50` is well past where the base colors begin to\n * converge (`surface-inverse` tops out around `25`), so the clip costs nothing that\n * was still moving and stops a vivid brand hex from being read as an instruction to\n * paint the page with it.\n *\n * The manual slider in the tuner shares the number, so the two agree on what the top\n * of the range means.\n */\nexport const MAX_BASE_SATURATION = 50;\n\n/**\n * Seed saturation for the `code-*` syntax family, deliberately **not** the same\n * constant as {@link DEFAULT_SATURATION}.\n *\n * The two were one value until the app seed moved to 100 for the pastel palette.\n * Sharing it would have dragged the syntax colors along for the ride — the exact\n * coupling that `PaletteCodeSeed` and the `pastel` opt-out both exist to prevent,\n * and the reason the code family answers to its own saturation and nothing else.\n * Splitting the constant is what lets the app seed move while the code palette\n * stays on the value it was calibrated against.\n */\nexport const DEFAULT_CODE_SATURATION = 80;\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** The numeric arm of a {@link PaletteSeed}. */\nexport interface PaletteNumericSeed {\n /** Hue in degrees (0–360). */\n hue?: number;\n /** Saturation (0–100). */\n saturation?: number;\n}\n\n/**\n * One zone's seed: a **color**, or the **numbers**.\n *\n * A string is a color — anything Glaze parses: hex, `rgb()`, `hsl()`, `okhsl()`,\n * `okhst()`, `oklch()`. CSS color keywords (`rebeccapurple`) are not supported, and an\n * unparseable value warns once and falls back to the numeric path with nothing pinned.\n *\n * The union **is** the exclusivity. A zone is seeded one way or the other — never both,\n * never layered — so there is no precedence rule to learn and no contradiction to warn\n * about. It also means a patch that switches form *replaces* rather than merges; layering\n * happens within a path. See {@link resolvePaletteConfig}.\n *\n * What a color supplies differs by zone, and each zone's own field says so:\n * {@link PaletteConfig.accent} keeps the tone but lends the palette none of its chroma,\n * {@link PaletteConfig.base} discards the tone, and a status theme keeps the tone *and*\n * takes the chroma as its seed.\n */\nexport type PaletteSeed = string | PaletteNumericSeed;\n\n/**\n * The `code-*` syntax family takes a saturation and nothing else — and answers to\n * nothing else either. This is the one knob that moves it.\n *\n * Its hues are absolute literals by design, so syntax colors never rotate with the\n * brand — strings would collide with numbers the moment the brand went green, which is\n * also why it is not a {@link PaletteSeed} and cannot take a color. Unlike every other\n * theme, its saturation does **not** inherit the palette-level one: the code palette is\n * calibrated once and stays there, so re-seeding the app cannot quietly wash out a code\n * block. {@link PaletteConfig.pastel} skips it for the same reason.\n */\nexport interface PaletteCodeSeed {\n /** Saturation (0–100). Defaults to {@link DEFAULT_CODE_SATURATION}, not to the accent's. */\n saturation?: number;\n}\n\n/**\n * Where the neutral surface ramp sits on the tone scale — see\n * {@link PaletteConfig.surfaceMode}.\n */\nexport type SurfaceMode = 'neutral' | 'tinted';\n\n/** Names of the themes whose seeds can be overridden individually. */\nexport type PaletteThemeName =\n | 'success'\n | 'danger'\n | 'warning'\n | 'note'\n | 'code';\n\n/**\n * Palette tuning. Every field is optional; omitted fields keep their default, and\n * per-theme fields left unset inherit the palette-level value.\n *\n * Every zone takes the same {@link PaletteSeed} — a color or the numbers — so the whole\n * config is one idea spelled once, and a settings UI can drive all six zones with one\n * control.\n */\nexport interface PaletteConfig {\n /**\n * The **accent** zone — the brand. Drives the `accent-*` family on every theme,\n * `primary` / `purple` / `special`, and the brand-tinted odds and ends (`focus`, the\n * loading faces, the disabled chip). Its hue is also the default for\n * {@link PaletteConfig.base}, and its saturation the default every status theme\n * inherits.\n *\n * **As numbers** — `{ hue, saturation }`. One saturation scale for the whole theme, by\n * design: every color's own `saturation` is a 0–1 factor of this seed — `surface` at\n * 0.12, `border` at 0.175, the text ramp at 0.2, the accent family at ~1.0 — so moving\n * it rescales the palette while keeping those proportions. Unlike hue, which is\n * configured separately for the brand and for each status theme, saturation is\n * deliberately not split: the ratio between a subtle surface tint and a saturated\n * accent is part of the design.\n *\n * A `saturation` **belongs to the non-pastel path**, and writing one says so: with no\n * {@link PaletteConfig.pastel} beside it, it turns pastel off, because tuning a\n * saturation is the non-pastel path by definition. Pastel is one flat chroma ceiling,\n * so under it there is exactly one seed and it is the top of the scale. A\n * `pastel: true` written next to a saturation wins and the saturation is ignored (with\n * a dev warning) — but it is kept rather than dropped, so turning pastel back off\n * restores your number.\n *\n * **As a color** — the brand, as you have it. Unlike a\n * {@link PaletteConfig.base} color, which keeps only hue and saturation, this keeps\n * the **tone** as well — which is the whole point. Without it the brand fill is\n * authored as a fixed tone step off white, so every accent hue lands at roughly the\n * same lightness and the color you asked for never actually appears.\n *\n * The **light, normal-contrast** variant reproduces the color; dark and high contrast\n * adapt, as every other color in the palette does. Two things cost exactness even\n * there: {@link PaletteConfig.pastel} caps chroma (so `#FFD400` softens), and the fill\n * answers to two APCA floors of deliberately different sizes — **Lc 45 against the\n * white label** it carries, because a label is text, and only **Lc 25 against\n * `surface`**, because a fill is a shape. It moves as far as the nearer one requires\n * and no further.\n *\n * Those floors are APCA, not WCAG, and the difference is deliberate: one WCAG ratio\n * means two very different things by scheme (3:1 measures Lc 56 in light but only\n * Lc 23 in dark), which crushed light brands while letting dark ones through. A\n * consequence worth stating plainly — **the emitted fill can sit below WCAG 3:1**.\n * `#0EA5E9` renders at 2.77:1 against a white page and is correct at that value; the\n * Lc is the guarantee, not the ratio.\n *\n * A color's own chroma is **not** adopted as the zone's seed saturation, which stays at\n * its default. The accent family gets the chroma through Glaze's `from`, where it is\n * absolute and seed-independent, and leaving the seed alone is what keeps a saturated\n * brand out of every status theme that inherits it. A status theme's color behaves the\n * other way round for the same reason inverted — nothing inherits from a status theme.\n */\n accent?: PaletteSeed;\n /**\n * The **base** zone: the neutral chrome — `surface` and its ladder, the\n * `surface-text*` ramp, `border`, `placeholder`.\n *\n * **Omit it and the zone follows the accent** — the chrome carries a faint tint of the\n * brand, at {@link SURFACE_SATURATION_SHARE} of its chroma, unless you say otherwise.\n * That is the third state, and the reason this field is worth reading back from\n * {@link getPaletteConfigInput}: absent, an object, or a string are three different\n * answers to \"does the chrome have a seed of its own\".\n *\n * **As numbers** — `{ hue, saturation }`, each independently optional. Splitting the\n * hue off lets the chrome sit on a different one from the accent — a warm grey UI with\n * a cool blue brand, say. Only the `default` theme is affected: a colored theme's\n * tinted `surface` deliberately follows *its own* hue, because a danger banner should\n * read as red.\n *\n * The saturation is on the same 0–100 scale as the accent's, and read the same way.\n * **The shipped chrome is `12`** — a faint tint is what a neutral surface is — so the\n * useful range is mostly below a third, and the numbers above it are a deliberately\n * tinted theme rather than a neutral one. Set it and the base zone stops following the\n * brand: a vivid accent over near-grey chrome, or a muted accent over visibly warm\n * chrome, are both one number away and neither is reachable from a single scale.\n *\n * The base colors keep their proportions to one another — `border` more than `surface`,\n * the text ramp more than `border` — until the highest of them hits the top of the\n * scale, which happens around `25`. Past that they converge.\n *\n * Under {@link PaletteConfig.surfaceMode} `'neutral'` it reaches `surface-2`…\n * `surface-4`, `border`, `placeholder` and the text ramp, but not the page surface: at\n * the end of the tone scale there is no room for chroma, whatever the seed says.\n * `'tinted'` is what gives it somewhere to land.\n *\n * **As a color** — its **hue and saturation** are read; its **tone is discarded**,\n * because the chrome's lightness ladder is the design. A base color says which way the\n * greys lean and how far, not how dark they are.\n *\n * The saturation is clipped to {@link MAX_BASE_SATURATION}. Naming a base color says\n * \"the chrome is this color\", so it lands near it rather than at the share it would\n * inherit from the accent — but a fully saturated chrome is no longer chrome, so there\n * is a ceiling on how far \"near\" goes.\n *\n * Unlike the accent's, writing a base saturation does *not* turn pastel off: how much\n * hue the chrome carries says nothing about which chroma space the palette is in.\n */\n base?: PaletteSeed;\n /**\n * Global. Where the neutral surface ramp sits on the tone scale.\n *\n * - `'neutral'` — `surface` is the extreme: pure white in light, the darkest\n * step the dark tone window allows in dark. No room for chroma, so the page\n * carries no hue however saturated the base zone is.\n * - `'tinted'` — the whole ramp moves two tones inward, off the extreme.\n *\n * Two tones is not a visible lightness change; what it buys is *room*. Chroma\n * needs distance from white to exist at all, so at the extreme a light page is\n * white no matter what the base saturation says. Tinted trades two tones of\n * headroom for a page that actually carries its base hue.\n *\n * Everything below `surface` is positioned relative to it, so the ladder, the\n * borders and the text ramp all follow — and the contrast floors on the text\n * re-solve against the new background rather than drifting.\n */\n surfaceMode?: SurfaceMode;\n /**\n * Global. Widens the usable chroma range by relaxing the sRGB-safe limit,\n * producing a softer, more even palette across hues. Glaze treats `pastel` as\n * instance-level, so it is threaded into every theme.\n *\n * It also **pins the accent's saturation to 100**: the even, hue-independent ceiling\n * is what pastel is for, and a second saturation scale on top of it would only undo\n * that. Two paths, then — pastel with no saturation knob, or `pastel: false` with a\n * free 0–100 one.\n *\n * Which is why a lone `accent: { saturation }` picks the second path for you. State\n * this field only to override that: it is the coarser of the two choices, so it wins\n * wherever both are set, and a saturation it shadows is ignored with a dev warning.\n *\n * The same ceiling is why a color seed cannot render exactly under pastel — `#FFD400`\n * softens to `#e4d8ad`. Under pastel a color contributes its hue and its tone; turn\n * pastel off to get its chroma too.\n *\n * Every theme except `code`. The syntax family is calibrated on its own saturation and\n * is deliberately left out — softening it collapses the chroma spread the syntax hues\n * rely on to stay apart. To soften a code block, lower\n * {@link PaletteCodeSeed.saturation} instead.\n */\n pastel?: boolean;\n /**\n * Global. `'auto'` (the default) leaves contrast entirely to the two-tier model:\n * normal colors plus a high-contrast tier driven by `<html data-contrast=\"high\">`\n * / `prefers-contrast: more`.\n *\n * A number (0–100) additionally positions the **normal** colors on a slider, so\n * a product can offer its own contrast control. `0` is the shipped palette and\n * `100` is the high-contrast one, bit for bit.\n *\n * The two **compose** rather than replace each other: the high-contrast tier\n * stays the true high-contrast resolution at every level — identical to what\n * `'auto'` emits — so a slider raises the baseline while\n * `prefers-contrast: more` still escalates on top of it. The one exception is\n * `100`, where the normal colors already *are* the high-contrast ones and a\n * second tier would only duplicate them.\n */\n contrastLevel?: number | 'auto';\n /**\n * Per-theme seed overrides. Each status theme takes the same {@link PaletteSeed} the\n * accent zone does, and inherits the accent's saturation until it sets its own.\n *\n * **As numbers** — `{ hue, saturation }`. Status hues have to stay semantically\n * legible (danger red, warning amber, success green) and far enough apart from each\n * other and from the brand to read as different things.\n *\n * **As a color** — the theme's accent family becomes that color, on the same terms\n * {@link PaletteConfig.accent} sets: the light/normal-contrast variant reproduces it,\n * the softened APCA floors apply instead of the white-anchored ladder's WCAG ones, and\n * the tone is capped so the `#white` label every `type=\"primary\"` item paints on the\n * fill survives.\n *\n * Here the color's chroma **does** become the theme's seed saturation, unlike the\n * accent's. Nothing inherits from a status theme, so there is nothing to\n * re-chromatize — and it is what keeps the theme's tinted banner surface, border and\n * text ramp at their shipped proportions to the fill instead of drifting relative to\n * it.\n */\n themes?: {\n success?: PaletteSeed;\n danger?: PaletteSeed;\n warning?: PaletteSeed;\n note?: PaletteSeed;\n code?: PaletteCodeSeed;\n };\n}\n\n/** {@link PaletteConfig} with every field resolved to a concrete value. */\nexport interface ResolvedPaletteConfig {\n hue: number;\n baseHue: number;\n saturation: number;\n baseSaturation: number;\n surfaceMode: SurfaceMode;\n /**\n * The accent color as given, handed to Glaze's `from` so the brand family renders\n * as that literal value rather than as a shade re-derived from the seed.\n *\n * `null` — the common case — means no accent color was supplied and the family keeps\n * its white-anchored derivation, which is the only arrangement that reproduces the\n * shipped palette bit for bit.\n */\n accentColor: string | null;\n /**\n * The base color as given, or `null`.\n *\n * Nothing renders from it — the hue and the saturation it derives are what reach the\n * palette. It is resolved anyway so {@link isSameConfig} can compare it, which is the\n * only way a write that changes the *string* without changing either derived number\n * still counts as a change. `MAX_BASE_SATURATION` makes that a live case rather than\n * a theoretical one: every color above the clip on a given hue derives the same pair,\n * so without this a color picker's whole upper range — and its entire tone axis, which\n * a base color discards — would drop writes silently and leave the field stale.\n */\n baseColor: string | null;\n /**\n * The tone of {@link ResolvedPaletteConfig.accentColor}, or `null` alongside it.\n *\n * `from` carries the tone itself, so this exists for the one thing that needs the\n * *number*: the hover brand text sits a fixed tone step past the rest one, and a\n * step has to be computed.\n */\n accentTone: number | null;\n /**\n * The saturation of {@link ResolvedPaletteConfig.accentColor}, or `null` beside it.\n *\n * On the palette's 0–100 scale, like every other saturation here — and deliberately\n * *not* the same number as {@link ResolvedPaletteConfig.saturation}, which a color\n * never raises. The accent family gets this chroma through Glaze's `from`, where it is\n * absolute; the seed stays where it was so the status themes that inherit it are left\n * alone.\n *\n * Kept as a number because the accent seed is rebuilt from its three components rather\n * than handed over as the literal: the tone is capped so a white label survives on the\n * fill, and a capped tone means reconstructing the color anyway.\n */\n accentSaturation: number | null;\n pastel: boolean;\n contrastLevel: number | 'auto';\n themes: {\n success: ResolvedThemeSeed;\n danger: ResolvedThemeSeed;\n warning: ResolvedThemeSeed;\n note: ResolvedThemeSeed;\n code: { saturation: number };\n };\n}\n\n/** One status theme's seed, resolved. */\nexport interface ResolvedThemeSeed {\n hue: number;\n /**\n * The theme's Glaze seed, and a ceiling on every color in it.\n *\n * On the numeric path this inherits the accent's saturation until the theme pins its\n * own. On the color path it **is** the color's chroma, which is what keeps the theme's\n * tinted surface, border and text ramp at their shipped proportions to a fill whose\n * chroma arrives absolute through `from`. See {@link PaletteConfig.themes} for why the\n * accent zone does the opposite.\n */\n saturation: number;\n /**\n * The color as given, handed to Glaze's `from` so this theme's accent family renders\n * as that literal value rather than as a shade re-derived from the seed.\n *\n * `null` — the common case — means the theme is on the numeric path and its accent\n * family keeps the white-anchored derivation, which is the only arrangement that\n * reproduces the shipped palette bit for bit.\n */\n color: string | null;\n /**\n * The tone of {@link ResolvedThemeSeed.color}, or `null` alongside it.\n *\n * `from` carries the tone itself, so this exists for the two things that need the\n * *number*: the hover brand text sits a fixed tone step past the rest one, and the\n * tone is capped so the `#white` label a `type=\"primary\"` item paints on the fill\n * survives. Both are arithmetic, and neither can be done to a string.\n */\n colorTone: number | null;\n}\n\n// ============================================================================\n// Resolution\n// ============================================================================\n\nconst DEFAULT_THEME_HUES = {\n success: DEFAULT_SUCCESS_HUE,\n danger: DEFAULT_DANGER_HUE,\n warning: DEFAULT_WARNING_HUE,\n note: DEFAULT_NOTE_HUE,\n} as const;\n\n/**\n * Warned once per process, not once per call.\n *\n * `resolveConfig` runs on every write AND on every `resolvePaletteConfig` preview, so an\n * unguarded warning would fire on every frame of a slider drag.\n */\nlet warnedAboutPastelSaturation = false;\n\n/**\n * One zone's seed, normalized to the four things a resolver can ask for.\n *\n * The single place a {@link PaletteSeed} is taken apart, so the six zones cannot drift\n * in how they read one. A string that {@link colorSeed} cannot parse comes back as the\n * numeric path with nothing pinned, which is exactly the fallback the docs promise: the\n * zone keeps its defaults rather than the render going down over a typo.\n */\nfunction seedOf(seed: PaletteSeed | undefined): {\n color: string | null;\n hue: number | undefined;\n saturation: number | undefined;\n tone: number | null;\n} {\n if (typeof seed === 'string') {\n const parsed = colorSeed(seed);\n\n return parsed\n ? {\n color: seed,\n hue: parsed.hue,\n saturation: parsed.saturation,\n tone: parsed.tone,\n }\n : { color: null, hue: undefined, saturation: undefined, tone: null };\n }\n\n return {\n color: null,\n hue: seed?.hue,\n saturation: seed?.saturation,\n tone: null,\n };\n}\n\n/**\n * Whether a zone pinned a saturation *as a number*.\n *\n * A color's chroma is not a pinned saturation — it reaches the palette through `from`,\n * not through the seed — so it must not answer the question `pastel` asks below.\n */\nfunction pinnedSaturation(seed: PaletteSeed | undefined): number | undefined {\n return typeof seed === 'string' ? undefined : seed?.saturation;\n}\n\nfunction resolveConfig(input: PaletteConfig): ResolvedPaletteConfig {\n const themes = input.themes ?? {};\n\n const accent = seedOf(input.accent);\n const base = seedOf(input.base);\n\n // A numeric `saturation` with no `pastel` beside it turns pastel OFF.\n //\n // Under pastel there is one saturation and it is the top of the scale, so the two\n // fields cannot both be honoured — but writing a saturation is only ever a request to\n // tune it, which is the non-pastel path by definition. Reading it as one keeps\n // `setPaletteConfig({ accent: { saturation: 55 } })` doing what it always did.\n //\n // A COLOR does not answer this question. `accent: '#7a4dbf'` alone still resolves\n // pastel-on, so the color contributes its hue and its tone but not its chroma — the\n // documented behaviour, and the reason the tuner writes `pastel: false` explicitly\n // when it hands a zone a hex.\n //\n // An explicit `pastel` wins, both ways: it is the coarser choice of the two, and a\n // config that states it is choosing a color space rather than a value on one.\n const accentSaturationPin = pinnedSaturation(input.accent);\n const pastel = input.pastel ?? accentSaturationPin === undefined;\n\n // The zone's own seed, or the shipped default. There is no third arm to rank: the\n // paths are exclusive, so `accent.hue` is the color's on one and the written number on\n // the other, and never both.\n const hue = accent.hue ?? DEFAULT_HUE;\n\n // `base.tone` is never read — that is the whole enforcement of \"a base color says\n // which way the greys lean and how far, not how dark they are\". Its saturation\n // *is* read, below.\n const baseHue = base.hue ?? hue;\n\n // Only a CONTRADICTION warns — `pastel: true` written next to a saturation it will\n // ignore. The inference above means a lone saturation is not a contradiction, and a\n // saturation of exactly 100 is not one either: that is the value pastel pins it to.\n if (\n input.pastel === true &&\n accentSaturationPin !== undefined &&\n accentSaturationPin !== DEFAULT_SATURATION &&\n !warnedAboutPastelSaturation\n ) {\n warnedAboutPastelSaturation = true;\n console.warn(\n `[cube-ui-kit] palette \\`accent.saturation\\` (${accentSaturationPin}) is ignored ` +\n `because \\`pastel\\` is on — pastel pins it to ${DEFAULT_SATURATION}. Drop ` +\n `\\`pastel\\`, or set it to \\`false\\`, to tune saturation yourself.`,\n );\n }\n\n // Deliberately the numeric pin only, NOT `accent.saturation`: the accent family\n // carries a color's own chroma through Glaze's `from`, so the palette-level seed no\n // longer has to be raised to reach it. Leaving it alone is what keeps a saturated\n // brand out of every status theme, which all inherit this number.\n //\n // The neutral chrome is the one exception, and it is deliberate — `baseSaturation`\n // below takes its share of the accent's own chroma so a near-grey brand leaves\n // near-grey chrome. That is scoped to the base zone and capped by this seed; it does\n // not pass through here.\n //\n // A status theme's color is the mirror image, and for the mirror reason: nothing\n // inherits from a status theme, so its chroma DOES become its seed. See\n // {@link ResolvedThemeSeed.saturation}.\n const saturation = pastel\n ? DEFAULT_SATURATION\n : accentSaturationPin ?? DEFAULT_SATURATION;\n\n return {\n hue,\n baseHue,\n saturation,\n // Three arms, and the middle two are on deliberately different scales.\n //\n // A named base COLOR means \"the chrome IS this color\", so it lands near it, clipped\n // at `MAX_BASE_SATURATION`. Base merely FOLLOWING the accent means \"a faint tint of\n // the brand\", so it stays the 12% share it has always been — now of the accent's own\n // chroma, whether that arrived as a number or as a color. Reading `accent.saturation`\n // here is the one place a brand color reaches the base zone, and it has to: without\n // it, picking a near-grey brand would leave the chrome carrying 12% of a saturation\n // nobody asked for.\n //\n // BOTH derived arms are also capped by `saturation`, and that is load-bearing rather\n // than defensive. `baseSaturationScale` divides by the seed, so the chrome's absolute\n // chroma is a function of this field ALONE — without the cap, an accent color would\n // cancel the seed out of the base zone entirely and a muted `saturation: 20` would\n // leave the chrome 4.4x more chromatic than asked for. The seed is a ceiling\n // everywhere else in the palette; it is one here too.\n //\n // A numeric pin is NOT clipped: a number is the more specific instruction, and a\n // tuner that offers the range is entitled to the top of it.\n //\n // Nothing here reaches the status themes, which is what keeps the guarantee that an\n // accent color cannot re-chromatize them.\n //\n // The accent arm sits INSIDE the parentheses on purpose: hoisting it to\n // `accent.saturation ?? saturation * SHARE` would apply the share to only one of the\n // two and move the shipped default.\n baseSaturation:\n pinnedSaturation(input.base) ??\n (base.color\n ? Math.min(base.saturation!, MAX_BASE_SATURATION, saturation)\n : Math.min(accent.saturation ?? saturation, saturation) *\n SURFACE_SATURATION_SHARE),\n surfaceMode: input.surfaceMode ?? 'neutral',\n accentColor: accent.color,\n baseColor: base.color,\n accentTone: accent.tone,\n accentSaturation: accent.color === null ? null : accent.saturation!,\n pastel,\n contrastLevel: input.contrastLevel ?? 'auto',\n themes: {\n success: resolveThemeSeed('success', themes.success, saturation),\n danger: resolveThemeSeed('danger', themes.danger, saturation),\n warning: resolveThemeSeed('warning', themes.warning, saturation),\n note: resolveThemeSeed('note', themes.note, saturation),\n // Deliberately NOT `?? saturation`: the code palette is calibrated once and\n // does not follow the palette-level seed. See `PaletteCodeSeed`.\n code: { saturation: themes.code?.saturation ?? DEFAULT_CODE_SATURATION },\n },\n };\n}\n\n/**\n * One status theme's seed, on whichever path it is on.\n *\n * The color arm takes all three of the color's components — hue, chroma and tone — where\n * the accent zone deliberately leaves the chroma out of its seed. The asymmetry is the\n * point: this seed is inherited by nothing, so raising it re-chromatizes nothing, and\n * raising it is what holds the theme's tinted surface, border and text ramp in the same\n * proportion to the fill that the shipped derivation gives them.\n */\nfunction resolveThemeSeed(\n name: keyof typeof DEFAULT_THEME_HUES,\n seed: PaletteSeed | undefined,\n saturation: number,\n): ResolvedThemeSeed {\n const parsed = seedOf(seed);\n\n if (parsed.color !== null) {\n return {\n hue: parsed.hue!,\n saturation: parsed.saturation!,\n color: parsed.color,\n colorTone: parsed.tone,\n };\n }\n\n return {\n hue: parsed.hue ?? DEFAULT_THEME_HUES[name],\n saturation: parsed.saturation ?? saturation,\n color: null,\n colorTone: null,\n };\n}\n\n/**\n * The palette as it ships, with no tuning applied. Frozen — it is the baseline\n * every resolution falls back to, so a stray write to it would move the defaults\n * for the whole process.\n */\nexport const DEFAULT_PALETTE_CONFIG: ResolvedPaletteConfig = freezeConfig(\n resolveConfig({}),\n);\n\n// ============================================================================\n// Store\n// ============================================================================\n\n/**\n * The last config *as written*, sparse — not the resolved one. Keeping the sparse\n * form is what makes inheritance live: a `saturation` with no `themes.danger.\n * saturation` beside it means danger genuinely follows the palette, rather than\n * having been frozen at whatever the palette read when it was set.\n */\nlet input: PaletteConfig = {};\nlet resolved: ResolvedPaletteConfig = DEFAULT_PALETTE_CONFIG;\nlet version = 0;\n\nconst listeners = new Set<() => void>();\n\nfunction mergeSeed(\n base: PaletteSeed | undefined,\n patch: PaletteSeed | undefined,\n): PaletteSeed | undefined {\n // A patch that omits the zone entirely says nothing about it, so the base survives.\n // Clearing one of its fields is `{ zone: { hue: undefined } }`.\n if (patch === undefined) return base;\n if (base === undefined) return patch;\n\n // Either side being a color makes this a change of PATH, not of value, and a path is\n // replaced rather than merged — spreading a hex into an object is meaningless in one\n // direction and would resurrect a discarded color in the other. Layering is a\n // within-path operation, which is the whole reason `PaletteSeed` is a union.\n if (typeof base === 'string' || typeof patch === 'string') return patch;\n\n return { ...base, ...patch };\n}\n\n/** {@link mergeSeed} for the code seed, which is not a {@link PaletteSeed}. */\nfunction mergeCodeSeed(\n base: PaletteCodeSeed | undefined,\n patch: PaletteCodeSeed | undefined,\n): PaletteCodeSeed | undefined {\n if (!base) return patch;\n if (!patch) return base;\n\n return { ...base, ...patch };\n}\n\n/**\n * Layer a patch over a base config. Used by {@link resolvePaletteConfig} for\n * previews — *not* by {@link setPaletteConfig}, which replaces.\n */\nfunction mergeInput(base: PaletteConfig, patch: PaletteConfig): PaletteConfig {\n const next: PaletteConfig = {\n ...base,\n ...patch,\n // Spread alone would replace a zone's whole seed, so a preview of\n // `{ accent: { hue: 30 } }` over a pinned saturation would silently drop it.\n ...(base.accent !== undefined || patch.accent !== undefined\n ? { accent: mergeSeed(base.accent, patch.accent) }\n : null),\n ...(base.base !== undefined || patch.base !== undefined\n ? { base: mergeSeed(base.base, patch.base) }\n : null),\n };\n\n if (base.themes || patch.themes) {\n next.themes = {\n success: mergeSeed(base.themes?.success, patch.themes?.success),\n danger: mergeSeed(base.themes?.danger, patch.themes?.danger),\n warning: mergeSeed(base.themes?.warning, patch.themes?.warning),\n note: mergeSeed(base.themes?.note, patch.themes?.note),\n code: mergeCodeSeed(base.themes?.code, patch.themes?.code),\n };\n }\n\n return next;\n}\n\nfunction isSameConfig(a: ResolvedPaletteConfig, b: ResolvedPaletteConfig) {\n return (\n a.hue === b.hue &&\n a.baseHue === b.baseHue &&\n a.saturation === b.saturation &&\n a.baseSaturation === b.baseSaturation &&\n a.surfaceMode === b.surfaceMode &&\n // The colors themselves, not just what they derived: a color's chroma no longer\n // reaches `saturation` (the family gets it through Glaze's `from`), so two brands can\n // agree on every numeric seed and still render differently. And a base color's\n // derived pair collapses — everything above `MAX_BASE_SATURATION` on one hue lands\n // on the same two numbers, and its tone is discarded outright — so comparing the\n // string is the only thing that lets a color picker's upper range and tone axis\n // register at all.\n a.accentColor === b.accentColor &&\n a.baseColor === b.baseColor &&\n a.pastel === b.pastel &&\n a.contrastLevel === b.contrastLevel &&\n a.themes.code.saturation === b.themes.code.saturation &&\n (['success', 'danger', 'warning', 'note'] as const).every(\n (name) =>\n a.themes[name].hue === b.themes[name].hue &&\n a.themes[name].saturation === b.themes[name].saturation &&\n // A status color's hue and chroma DO land in the two numbers above, so this\n // catches only what they cannot: the tone, and one unparseable string replacing\n // another.\n a.themes[name].color === b.themes[name].color,\n )\n );\n}\n\n/**\n * Which fields are *explicitly set*, as an order-independent signature.\n *\n * Two configs can resolve to identical values yet differ in which of those values\n * are pinned rather than inherited — pinning a status theme's saturation to the number\n * it already inherited is the obvious case. That difference is invisible to\n * {@link isSameConfig} but visible through {@link getPaletteConfigInput}, so it has\n * to count as a change or a settings UI reading it would never re-render.\n */\nfunction pinSignature(config: PaletteConfig): string {\n const set = (value: unknown) => (value === undefined ? '0' : '1');\n\n /**\n * One zone, in three states — absent, numbers, or a color.\n *\n * **Absent is its own state**, not \"numbers with nothing pinned\". `base: {}` and no\n * `base` at all resolve identically, but they read back differently, and a settings UI\n * asks exactly that question to decide whether the chrome has a seed of its own or is\n * still following the accent.\n *\n * A color carries its **value**, not just its presence. Presence alone cannot tell one\n * unparseable string from another: both resolve to `null`, so `isSameConfig` sees no\n * movement either, and replacing `'bad-one'` with `'bad-two'` returned early — leaving\n * the field on the first string with no notification that the write was dropped.\n *\n * It also means two spellings of the same color (`'#ff0000'` / `'rgb(255 0 0)'`) bump\n * the version. That is the same argument the presence check was already making: what a\n * settings UI reads back changed, so it has to re-render. And a zone switching from\n * `{ hue: 45 }` to a color that happens to derive hue 45 resolves to the same numbers,\n * so without this the version would never bump and the control would look stuck.\n */\n const seed = (value: PaletteSeed | undefined) =>\n value === undefined\n ? '-'\n : typeof value === 'string'\n ? JSON.stringify(value)\n : `${set(value.hue)}${set(value.saturation)}`;\n\n return [\n seed(config.accent),\n seed(config.base),\n set(config.surfaceMode),\n set(config.pastel),\n set(config.contrastLevel),\n set(config.themes?.code?.saturation),\n ...(['success', 'danger', 'warning', 'note'] as const).map((name) =>\n seed(config.themes?.[name]),\n ),\n ].join('|');\n}\n\n/**\n * Snapshot a config so the store owns it outright.\n *\n * Two hazards this closes. The caller's object stays theirs, so mutating it after\n * the call cannot desync `input` from `resolved`. And what we hand back from\n * {@link getPaletteConfig} / {@link getPaletteConfigInput} is frozen, so a caller\n * who writes to it gets a clear failure rather than silently corrupting the store\n * — the version would not bump, and every token cache would go on serving values\n * that no longer match the config.\n */\nfunction freezeConfig<T extends PaletteConfig | ResolvedPaletteConfig>(\n config: T,\n): T {\n const themes = config.themes;\n\n if (themes) {\n // A string seed is already immutable, and `Object.freeze` on one is a no-op that\n // TypeScript would rather we did not ask for.\n for (const seed of Object.values(themes)) {\n if (seed && typeof seed === 'object') Object.freeze(seed);\n }\n Object.freeze(themes);\n }\n\n // Only the sparse INPUT carries zone seeds; the resolved config is flat, so this half\n // is a no-op for it rather than a branch either caller has to know about.\n const zones = config as Partial<PaletteConfig>;\n\n if (typeof zones.accent === 'object') Object.freeze(zones.accent);\n if (typeof zones.base === 'object') Object.freeze(zones.base);\n\n return Object.freeze(config);\n}\n\n/** {@link freezeConfig} on a copy, for a config we did not create ourselves. */\nfunction snapshotConfig(config: PaletteConfig): PaletteConfig {\n const themes = config.themes;\n // Copy the object arm; a string is already a value.\n const copy = (seed: PaletteSeed | undefined) =>\n typeof seed === 'object' ? { ...seed } : seed;\n\n return freezeConfig({\n ...config,\n ...(config.accent !== undefined ? { accent: copy(config.accent) } : null),\n ...(config.base !== undefined ? { base: copy(config.base) } : null),\n ...(themes\n ? {\n themes: {\n ...(themes.success !== undefined\n ? { success: copy(themes.success) }\n : null),\n ...(themes.danger !== undefined\n ? { danger: copy(themes.danger) }\n : null),\n ...(themes.warning !== undefined\n ? { warning: copy(themes.warning) }\n : null),\n ...(themes.note !== undefined ? { note: copy(themes.note) } : null),\n ...(themes.code ? { code: { ...themes.code } } : null),\n },\n }\n : null),\n });\n}\n\nfunction commit(nextInput: PaletteConfig) {\n const nextResolved = resolveConfig(nextInput);\n\n // Bail before bumping the version when nothing observable moved, so re-applying\n // the same config — an inline `<Root palette={{ … }}>` literal on every\n // render, a StrictMode double render — costs nothing.\n if (\n isSameConfig(resolved, nextResolved) &&\n pinSignature(input) === pinSignature(nextInput)\n ) {\n return;\n }\n\n input = snapshotConfig(nextInput);\n resolved = freezeConfig(nextResolved);\n version++;\n\n listeners.forEach((listener) => listener());\n}\n\n/**\n * Set the palette config.\n *\n * **Replaces**, like `useState` — the config you pass *is* the config, resolved\n * against the shipped defaults. Nothing accumulates, so a field you leave out is a\n * field you do not have: dropping a customization means dropping it from the\n * object, and re-applying the same object twice is the same as applying it once.\n *\n * ```ts\n * setPaletteConfig({ accent: { hue: 200 }, base: { hue: 60 } });\n * setPaletteConfig({ accent: { hue: 200 } }); // `base` is gone — follows accent again\n * ```\n *\n * To adjust one field of the config already in place — a slider in a settings UI —\n * pass an updater. It receives the config as written, sparse, so spreading it\n * preserves which fields are pinned and which still inherit:\n *\n * ```ts\n * setPaletteConfig((config) => ({ ...config, accent: { hue: 200 } }));\n * ```\n *\n * The palette is rebuilt lazily on the next token read, and any mounted `<Root>`\n * re-injects the token block — no component re-render is involved, because every\n * color in the kit resolves through a CSS custom property.\n */\nexport function setPaletteConfig(\n config: PaletteConfig | ((previous: PaletteConfig) => PaletteConfig),\n): void {\n commit(typeof config === 'function' ? config(input) : config);\n}\n\n/**\n * Drop all tuning and restore the palette the kit ships with. Identical to\n * `setPaletteConfig({})`; it exists to be readable at a call site.\n */\nexport function resetPaletteConfig(): void {\n commit({});\n}\n\n/**\n * Force every token to re-resolve without changing the palette config.\n *\n * Only needed if you drive Glaze directly — `glaze.configure({ darkTone })` and\n * the like. Glaze invalidates its own caches, but the kit's token maps are\n * memoized against *this* module's version, so they need telling. Calling it\n * before the first paint is unnecessary; nothing has been resolved yet.\n */\nexport function invalidatePaletteTokens(): void {\n version++;\n listeners.forEach((listener) => listener());\n}\n\n/** The current config, with every field resolved to a concrete value. */\nexport function getPaletteConfig(): ResolvedPaletteConfig {\n return resolved;\n}\n\n/**\n * The config **as set**, sparse — omitted fields are the ones still inheriting.\n *\n * {@link getPaletteConfig} resolves everything to concrete values, which loses the\n * distinction between \"explicitly 80\" and \"80 because it follows the brand\". A\n * settings UI needs that distinction to show an inherited value as inherited, and\n * to offer a way back:\n *\n * ```ts\n * const own = getPaletteConfigInput().base !== undefined;\n *\n * // Re-link it to the brand by dropping the field.\n * setPaletteConfig(({ base, ...config }) => config);\n * ```\n *\n * It is also the value handed to a {@link setPaletteConfig} updater.\n */\nexport function getPaletteConfigInput(): PaletteConfig {\n return input;\n}\n\n/**\n * Resolve a patch **over** the current config, without applying it.\n *\n * This one layers, unlike {@link setPaletteConfig}: a preview wants \"the theme in\n * use, but in dark\", so the fields it does not mention have to come from the live\n * config rather than from the defaults. The store is not touched and no listener\n * fires.\n */\nexport function resolvePaletteConfig(\n config?: PaletteConfig,\n): ResolvedPaletteConfig {\n if (!config) return resolved;\n\n return resolveConfig(mergeInput(input, config));\n}\n\n/**\n * Increments whenever the resolved config changes. The token caches in\n * `./palette.ts`, `./colors.ts` and `./index.ts` compare against it instead of\n * memoizing forever — the same idiom Glaze uses for its own config version.\n */\nexport function getPaletteVersion(): number {\n return version;\n}\n\n/** Subscribe to palette config changes. Returns an unsubscribe function. */\nexport function subscribePaletteConfig(listener: () => void): () => void {\n listeners.add(listener);\n\n return () => {\n listeners.delete(listener);\n };\n}\n\n// ============================================================================\n// React bindings\n// ============================================================================\n\n/**\n * Re-render on palette config changes. Returns the version, not the config, so\n * the snapshot is a primitive and React never warns about an uncached snapshot.\n */\nexport function usePaletteVersion(): number {\n return useSyncExternalStore(\n subscribePaletteConfig,\n getPaletteVersion,\n getPaletteVersion,\n );\n}\n\n/**\n * Read and tune the palette from React, `useState`-style — including the part\n * where the setter *replaces*. A control that changes one field wants the updater\n * form, or it will drop every other field:\n *\n * ```tsx\n * const [palette, setPalette] = usePaletteConfig();\n *\n * <HueSlider\n * value={palette.hue}\n * onChange={(hue) => setPalette((config) => ({ ...config, accent: { hue } }))}\n * />\n * ```\n *\n * The first element is the *resolved* config, so `palette.hue` is always a number — it\n * keeps the flat shape the recipe reads, whichever way the zones were seeded. The\n * updater's argument is the sparse one — see {@link getPaletteConfigInput}.\n *\n * The config is global process state, so every consumer of this hook — and every\n * mounted `<Root>` — sees the same palette.\n */\nexport function usePaletteConfig(): readonly [\n ResolvedPaletteConfig,\n typeof setPaletteConfig,\n] {\n usePaletteVersion();\n\n return [getPaletteConfig(), setPaletteConfig];\n}\n"],"mappings":"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"}