@cube-dev/ui-kit 0.170.0 → 0.172.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.
- package/README.md +1 -1
- package/dist/CHANGELOG.md +41 -0
- package/dist/README.md +1 -1
- package/dist/_internal/hooks/use-chained-callback.js +1 -1
- package/dist/_internal/hooks/use-debounced-value.js +1 -1
- package/dist/_internal/hooks/use-deprecation-warning.js +1 -1
- package/dist/_internal/hooks/use-event.js +1 -1
- package/dist/_internal/hooks/use-is-first-render.js +1 -1
- package/dist/_internal/hooks/use-sync-ref.js +1 -1
- package/dist/_internal/hooks/use-timer/timer.js +1 -1
- package/dist/_internal/hooks/use-timer/use-timer.js +1 -1
- package/dist/_internal/hooks/use-warn.js +1 -1
- package/dist/components/Block.js +1 -1
- package/dist/components/CollectionItem.js +1 -1
- package/dist/components/GlobalStyles.js +6 -6
- package/dist/components/GlobalStyles.js.map +1 -1
- package/dist/components/GridProvider.js +1 -1
- package/dist/components/HiddenInput.js +1 -1
- package/dist/components/Root.js +2 -2
- package/dist/components/Root.js.map +1 -1
- package/dist/components/actions/Action/Action.js +1 -1
- package/dist/components/actions/Banner/Banner.js +1 -1
- package/dist/components/actions/Banner/Banner.js.map +1 -1
- package/dist/components/actions/Button/Button.js +1 -1
- package/dist/components/actions/ButtonGroup/ButtonGroup.js +1 -1
- package/dist/components/actions/ButtonSplit/ButtonSplit.js +1 -1
- package/dist/components/actions/ButtonSplit/context.js +1 -1
- package/dist/components/actions/CommandMenu/CommandMenu.js +1 -1
- package/dist/components/actions/CommandMenu/styled.js +1 -1
- package/dist/components/actions/ItemAction/ItemAction.js +1 -1
- package/dist/components/actions/ItemActionContext.js +1 -1
- package/dist/components/actions/ItemButton/ItemButton.js +1 -1
- package/dist/components/actions/Link/Link.js +1 -1
- package/dist/components/actions/Menu/Menu.js +1 -1
- package/dist/components/actions/Menu/MenuItem.js +1 -1
- package/dist/components/actions/Menu/MenuSection.js +1 -1
- package/dist/components/actions/Menu/MenuTrigger.js +1 -1
- package/dist/components/actions/Menu/SubMenuTrigger.js +1 -1
- package/dist/components/actions/Menu/SubmenuTriggerContext.js +1 -1
- package/dist/components/actions/Menu/context.js +1 -1
- package/dist/components/actions/Menu/styled.js +1 -1
- package/dist/components/actions/Menu/styled.js.map +1 -1
- package/dist/components/actions/index.js +1 -1
- package/dist/components/actions/use-action.js +1 -1
- package/dist/components/actions/use-anchored-menu.js +1 -1
- package/dist/components/actions/use-context-menu.js +1 -1
- package/dist/components/content/ActiveZone/ActiveZone.js +1 -1
- package/dist/components/content/Alert/Alert.js +1 -1
- package/dist/components/content/Alert/use-alert.js +1 -1
- package/dist/components/content/Avatar/Avatar.js +1 -1
- package/dist/components/content/Badge/Badge.js +1 -1
- package/dist/components/content/Card/Card.js +1 -1
- package/dist/components/content/Content.js +1 -1
- package/dist/components/content/CopyPasteBlock/CopyPasteBlock.js +1 -1
- package/dist/components/content/CopySnippet/CopySnippet.js +1 -1
- package/dist/components/content/Disclosure/Disclosure.js +1 -1
- package/dist/components/content/Divider.js +1 -1
- package/dist/components/content/Footer.js +1 -1
- package/dist/components/content/Header.js +1 -1
- package/dist/components/content/HotKeys/HotKeys.js +1 -1
- package/dist/components/content/InfoBadge/InfoBadge.js +1 -1
- package/dist/components/content/InlineInput/InlineInput.js +1 -1
- package/dist/components/content/Item/Item.js +1 -1
- package/dist/components/content/ItemBadge/ItemBadge.js +1 -1
- package/dist/components/content/ItemCard/ItemCard.js +1 -1
- package/dist/components/content/Layout/GridLayout.js +1 -1
- package/dist/components/content/Layout/Layout.js +1 -1
- package/dist/components/content/Layout/LayoutBlock.js +1 -1
- package/dist/components/content/Layout/LayoutCenter.js +1 -1
- package/dist/components/content/Layout/LayoutContainer.js +1 -1
- package/dist/components/content/Layout/LayoutContent.js +1 -1
- package/dist/components/content/Layout/LayoutContext.js +1 -1
- package/dist/components/content/Layout/LayoutFlex.js +1 -1
- package/dist/components/content/Layout/LayoutFooter.js +1 -1
- package/dist/components/content/Layout/LayoutGrid.js +1 -1
- package/dist/components/content/Layout/LayoutHeader.js +1 -1
- package/dist/components/content/Layout/LayoutPane.js +1 -1
- package/dist/components/content/Layout/LayoutPanel.js +1 -1
- package/dist/components/content/Layout/LayoutPanelHeader.js +1 -1
- package/dist/components/content/Layout/LayoutToolbar.js +1 -1
- package/dist/components/content/Layout/hooks/useTinyScrollbar.js +1 -1
- package/dist/components/content/Layout/index.js +1 -1
- package/dist/components/content/Layout/utils.js +1 -1
- package/dist/components/content/Paragraph.js +1 -1
- package/dist/components/content/Placeholder/Placeholder.js +1 -1
- package/dist/components/content/PrismCode/PrismCode.js +1 -1
- package/dist/components/content/PrismCode/prismSetup.js +1 -1
- package/dist/components/content/PrismDiffCode/PrismDiffCode.js +1 -1
- package/dist/components/content/Result/Result.js +1 -1
- package/dist/components/content/Skeleton/Skeleton.js +1 -1
- package/dist/components/content/Tag/Tag.js +1 -1
- package/dist/components/content/Text.js +1 -1
- package/dist/components/content/TextItem/TextItem.js +1 -1
- package/dist/components/content/Title.js +1 -1
- package/dist/components/content/Tree/Tree.js +1 -1
- package/dist/components/content/Tree/TreeNode.js +1 -1
- package/dist/components/content/Tree/styled.js +1 -1
- package/dist/components/content/Tree/tree-index.js +1 -1
- package/dist/components/content/Tree/use-checkbox-tree.js +1 -1
- package/dist/components/content/Tree/use-load-data.js +1 -1
- package/dist/components/content/highlightText.js +1 -1
- package/dist/components/content/use-auto-tooltip.js +1 -1
- package/dist/components/data/DataTable/DataTable.js +1 -1
- package/dist/components/data/ItemTable/ItemTable.js +1 -1
- package/dist/components/data/ItemTable/ItemTableBulkBar.js +1 -1
- package/dist/components/data/ItemTable/ItemTableDragPreview.js +1 -1
- package/dist/components/data/ItemTable/ItemTableFooter.js +1 -1
- package/dist/components/data/ItemTable/ItemTableToolbar.js +1 -1
- package/dist/components/data/TableBase/ColumnResizer.js +1 -1
- package/dist/components/data/TableBase/RowCollection.js +1 -1
- package/dist/components/data/TableBase/TableHeaderCell.js +1 -1
- package/dist/components/data/TableBase/TableRow.js +1 -1
- package/dist/components/data/TableBase/TableView.js +1 -1
- package/dist/components/data/TableBase/column-menu.js +1 -1
- package/dist/components/data/TableBase/column-tint.js +2 -2
- package/dist/components/data/TableBase/column-tint.js.map +1 -1
- package/dist/components/data/TableBase/row-menu.js +1 -1
- package/dist/components/data/TableBase/styled.js +1 -1
- package/dist/components/data/TableBase/table-tree.js +1 -1
- package/dist/components/data/TableBase/types.d.ts +3 -3
- package/dist/components/data/TableBase/types.js +1 -1
- package/dist/components/data/TableBase/types.js.map +1 -1
- package/dist/components/data/TableBase/use-cell-selection.js +0 -0
- package/dist/components/data/TableBase/use-column-order.js +1 -1
- package/dist/components/data/TableBase/use-container-width.js +1 -1
- package/dist/components/data/TableBase/use-row-move-animation.js +1 -1
- package/dist/components/data/TableBase/use-scrollability.js +1 -1
- package/dist/components/data/TableBase/use-table-columns.js +1 -1
- package/dist/components/data/TableBase/use-table-search.js +1 -1
- package/dist/components/data/TableBase/use-table-selection.js +1 -1
- package/dist/components/data/TableBase/use-table-sort.js +1 -1
- package/dist/components/data/TableBase/use-table-sorts.js +1 -1
- package/dist/components/data/TableBase/use-table-storage.js +1 -1
- package/dist/components/data/TableBase/use-table-tree-state.js +1 -1
- package/dist/components/fields/Checkbox/Checkbox.js +1 -1
- package/dist/components/fields/Checkbox/Checkbox.js.map +1 -1
- package/dist/components/fields/Checkbox/CheckboxGroup.js +1 -1
- package/dist/components/fields/Checkbox/context.js +1 -1
- package/dist/components/fields/ColorInput/ColorInput.js +1 -1
- package/dist/components/fields/ColorPicker/ColorPicker.js +1 -1
- package/dist/components/fields/ColorSwatch/ColorSwatch.js +1 -1
- package/dist/components/fields/ColorSwatchGroup/ColorSwatchGroup.js +2 -2
- package/dist/components/fields/ColorSwatchGroup/ColorSwatchGroup.js.map +1 -1
- package/dist/components/fields/ComboBox/ComboBox.js +1 -1
- package/dist/components/fields/CommandTextArea/CommandTextArea.js +1 -1
- package/dist/components/fields/CommandTextArea/caretPosition.js +1 -1
- package/dist/components/fields/CommandTextArea/useCaretAnchor.js +1 -1
- package/dist/components/fields/DatePicker/DateInput.js +1 -1
- package/dist/components/fields/DatePicker/DateInputBase.js +1 -1
- package/dist/components/fields/DatePicker/DatePicker.js +1 -1
- package/dist/components/fields/DatePicker/DatePickerButton.js +1 -1
- package/dist/components/fields/DatePicker/DatePickerElement.js +1 -1
- package/dist/components/fields/DatePicker/DatePickerInput.js +1 -1
- package/dist/components/fields/DatePicker/DatePickerSegment.js +1 -1
- package/dist/components/fields/DatePicker/DateRangePicker.js +1 -1
- package/dist/components/fields/DatePicker/DateRangeSeparatedPicker.js +1 -1
- package/dist/components/fields/DatePicker/MonthPicker.js +1 -1
- package/dist/components/fields/DatePicker/PeriodPicker.js +1 -1
- package/dist/components/fields/DatePicker/QuarterPicker.js +1 -1
- package/dist/components/fields/DatePicker/TimeInput.js +1 -1
- package/dist/components/fields/DatePicker/WeekPicker.js +1 -1
- package/dist/components/fields/DatePicker/YearPicker.js +1 -1
- package/dist/components/fields/DatePicker/parseDate.js +1 -1
- package/dist/components/fields/DatePicker/period.js +1 -1
- package/dist/components/fields/DatePicker/props.js +1 -1
- package/dist/components/fields/DatePicker/utils.js +1 -1
- package/dist/components/fields/FileInput/FileInput.js +1 -1
- package/dist/components/fields/FilterListBox/FilterListBox.js +1 -1
- package/dist/components/fields/FilterPicker/FilterPicker.js +1 -1
- package/dist/components/fields/Input/Input.js +1 -1
- package/dist/components/fields/ListBox/DraggableListBox.js +1 -1
- package/dist/components/fields/ListBox/ListBox.js +1 -1
- package/dist/components/fields/ListBoxPopover/ListBoxPopover.js +1 -1
- package/dist/components/fields/ListBoxPopover/listNavigation.js +1 -1
- package/dist/components/fields/ListBoxPopover/useCompositeFocus.js +1 -1
- package/dist/components/fields/NumberInput/NumberInput.js +1 -1
- package/dist/components/fields/NumberInput/StepButton.js +1 -1
- package/dist/components/fields/PasswordInput/PasswordInput.js +1 -1
- package/dist/components/fields/Picker/Picker.js +1 -1
- package/dist/components/fields/RadioGroup/Radio.js +1 -1
- package/dist/components/fields/RadioGroup/RadioGroup.js +1 -1
- package/dist/components/fields/RadioGroup/context.js +1 -1
- package/dist/components/fields/SearchComboBox/SearchComboBox.js +1 -1
- package/dist/components/fields/SearchInput/SearchInput.js +1 -1
- package/dist/components/fields/Select/Select.js +1 -1
- package/dist/components/fields/Select/Select.js.map +1 -1
- package/dist/components/fields/Slider/Gradation.js +1 -1
- package/dist/components/fields/Slider/HueSlider.js +1 -1
- package/dist/components/fields/Slider/RangeSlider.js +1 -1
- package/dist/components/fields/Slider/Slider.js +1 -1
- package/dist/components/fields/Slider/SliderBase.js +1 -1
- package/dist/components/fields/Slider/SliderThumb.js +1 -1
- package/dist/components/fields/Slider/SliderTrack.js +1 -1
- package/dist/components/fields/Slider/elements.js +1 -1
- package/dist/components/fields/Slider/index.js +1 -1
- package/dist/components/fields/Switch/Switch.js +1 -1
- package/dist/components/fields/TextArea/TextArea.js +1 -1
- package/dist/components/fields/TextInput/TextInput.js +1 -1
- package/dist/components/fields/TextInput/TextInputBase.js +1 -1
- package/dist/components/fields/TextInput/useAutoSizeTextArea.js +1 -1
- package/dist/components/fields/TextInputMapper/TextInputMapper.js +1 -1
- package/dist/components/fields/color/ColorPanel.js +1 -1
- package/dist/components/fields/color/channels.js +1 -1
- package/dist/components/fields/color/color.js +2 -2
- package/dist/components/fields/color/color.js.map +1 -1
- package/dist/components/fields/color/context.js +1 -1
- package/dist/components/form/FieldWrapper/FieldWrapper.js +1 -1
- package/dist/components/form/Form/Field.js +1 -1
- package/dist/components/form/Form/Form.js +1 -1
- package/dist/components/form/Form/ResetButton/ResetButton.js +1 -1
- package/dist/components/form/Form/SubmitButton/SubmitButton.js +1 -1
- package/dist/components/form/Form/SubmitError.js +1 -1
- package/dist/components/form/Form/index.js +1 -1
- package/dist/components/form/Form/use-field/use-field-props.js +1 -1
- package/dist/components/form/Form/use-field/use-field.js +1 -1
- package/dist/components/form/Form/use-form.js +1 -1
- package/dist/components/form/Form/validation.js +1 -1
- package/dist/components/form/Label.js +1 -1
- package/dist/components/form/validation/ValidationIndicator.js +1 -1
- package/dist/components/form/validation/resolve-validation-props.js +1 -1
- package/dist/components/form/validation/use-validation-props.js +1 -1
- package/dist/components/form/wrapper.js +1 -1
- package/dist/components/helpers/DisplayTransition/DisplayTransition.js +1 -1
- package/dist/components/helpers/IconSwitch/IconSwitch.js +1 -1
- package/dist/components/layout/Board/Board.d.ts +6 -4
- package/dist/components/layout/Board/Board.js +1 -1
- package/dist/components/layout/Board/Board.js.map +1 -1
- package/dist/components/layout/Board/BoardProvider.js +1 -1
- package/dist/components/layout/Board/BoardResponsive.js +1 -1
- package/dist/components/layout/Board/Widget.js +1 -1
- package/dist/components/layout/Board/WidgetHost.js +1 -1
- package/dist/components/layout/Board/WidgetHost.js.map +1 -1
- package/dist/components/layout/Board/board-context.js +1 -1
- package/dist/components/layout/Board/board-store.js +1 -1
- package/dist/components/layout/Board/grid-core/calculate.js +1 -1
- package/dist/components/layout/Board/grid-core/collision-modes.js +75 -7
- package/dist/components/layout/Board/grid-core/collision-modes.js.map +1 -1
- package/dist/components/layout/Board/grid-core/collision.js +1 -1
- package/dist/components/layout/Board/grid-core/compactors.js +1 -1
- package/dist/components/layout/Board/grid-core/constraints.js +1 -1
- package/dist/components/layout/Board/grid-core/group-move.js +1 -1
- package/dist/components/layout/Board/grid-core/layout.js +1 -1
- package/dist/components/layout/Board/grid-core/placement.js +1 -1
- package/dist/components/layout/Board/grid-core/sort.js +1 -1
- package/dist/components/layout/Board/index.js +1 -1
- package/dist/components/layout/Board/responsive-utils.js +1 -1
- package/dist/components/layout/Board/use-board-layout.js +1 -1
- package/dist/components/layout/Board/use-board-registry.js +95 -88
- package/dist/components/layout/Board/use-board-registry.js.map +1 -1
- package/dist/components/layout/Board/use-board-select-modifier-key.js +1 -1
- package/dist/components/layout/Board/use-board-selection.js +1 -1
- package/dist/components/layout/Flex.js +1 -1
- package/dist/components/layout/Flow.js +1 -1
- package/dist/components/layout/Grid.js +1 -1
- package/dist/components/layout/Panel.js +1 -1
- package/dist/components/layout/Prefix.js +1 -1
- package/dist/components/layout/ResizablePanel.js +1 -1
- package/dist/components/layout/Space.js +1 -1
- package/dist/components/layout/Suffix.js +1 -1
- package/dist/components/navigation/Pagination/Pagination.js +1 -1
- package/dist/components/navigation/Pagination/use-pagination.js +1 -1
- package/dist/components/navigation/Tabs/DraggableTabList.js +1 -1
- package/dist/components/navigation/Tabs/TabButton.js +1 -1
- package/dist/components/navigation/Tabs/TabDropIndicator.js +1 -1
- package/dist/components/navigation/Tabs/TabPanel.js +1 -1
- package/dist/components/navigation/Tabs/TabPicker.js +1 -1
- package/dist/components/navigation/Tabs/Tabs.js +1 -1
- package/dist/components/navigation/Tabs/TabsAction.js +1 -1
- package/dist/components/navigation/Tabs/TabsContext.js +1 -1
- package/dist/components/navigation/Tabs/popover-placement.js +1 -1
- package/dist/components/navigation/Tabs/styled.js +1 -1
- package/dist/components/navigation/Tabs/types.js +1 -1
- package/dist/components/navigation/Tabs/use-tab-editing.js +1 -1
- package/dist/components/navigation/Tabs/use-tab-indicator.js +1 -1
- package/dist/components/organisms/StatsCard/StatsCard.js +1 -1
- package/dist/components/other/Calendar/Calendar.js +1 -1
- package/dist/components/other/Calendar/CalendarCell.js +1 -1
- package/dist/components/other/Calendar/CalendarGrid.js +1 -1
- package/dist/components/other/Calendar/CalendarHeader.js +1 -1
- package/dist/components/other/Calendar/CalendarPanel.js +1 -1
- package/dist/components/other/Calendar/PeriodCalendar.js +1 -1
- package/dist/components/other/Calendar/PeriodGrid.js +1 -1
- package/dist/components/other/Calendar/RangeCalendar.js +1 -1
- package/dist/components/other/Calendar/styled.js +1 -1
- package/dist/components/other/CubeLogo/CubeLogo.d.ts +2 -2
- package/dist/components/other/CubeLogo/CubeLogo.js +12 -12
- package/dist/components/other/CubeLogo/CubeLogo.js.map +1 -1
- package/dist/components/other/NoDataIcon/NoDataIcon.js +1 -1
- package/dist/components/overlays/AlertDialog/AlertDialog.js +1 -1
- package/dist/components/overlays/AlertDialog/AlertDialogApiProvider.js +1 -1
- package/dist/components/overlays/AlertDialog/AlertDialogZone.js +1 -1
- package/dist/components/overlays/Dialog/Dialog.js +1 -1
- package/dist/components/overlays/Dialog/DialogContainer.js +1 -1
- package/dist/components/overlays/Dialog/DialogForm.js +1 -1
- package/dist/components/overlays/Dialog/DialogTrigger.js +1 -1
- package/dist/components/overlays/Dialog/context.js +1 -1
- package/dist/components/overlays/Dialog/use-dialog-container.js +1 -1
- package/dist/components/overlays/Modal/Modal.js +1 -1
- package/dist/components/overlays/Modal/OpenTransitionContext.js +1 -1
- package/dist/components/overlays/Modal/Overlay.js +1 -1
- package/dist/components/overlays/Modal/Popover.js +1 -1
- package/dist/components/overlays/Modal/Tray.js +1 -1
- package/dist/components/overlays/Modal/Underlay.js +1 -1
- package/dist/components/overlays/Notifications/Notification.js +1 -1
- package/dist/components/overlays/Notifications/NotificationAction.js +1 -1
- package/dist/components/overlays/Notifications/NotificationCard.js +1 -1
- package/dist/components/overlays/Notifications/NotificationContext.js +1 -1
- package/dist/components/overlays/Notifications/NotificationItem.js +1 -1
- package/dist/components/overlays/Notifications/OverlayContainer.js +1 -1
- package/dist/components/overlays/Notifications/OverlayProvider.js +1 -1
- package/dist/components/overlays/Notifications/PersistentNotificationsList.js +1 -1
- package/dist/components/overlays/Notifications/dismissed-storage.js +1 -1
- package/dist/components/overlays/Notifications/format-relative-time.js +1 -1
- package/dist/components/overlays/Notifications/index.js +1 -1
- package/dist/components/overlays/Notifications/use-notification-state.js +1 -1
- package/dist/components/overlays/Notifications/use-notifications.js +1 -1
- package/dist/components/overlays/Notifications/use-overlay-timers.js +1 -1
- package/dist/components/overlays/Notifications/use-persistent-notifications.js +1 -1
- package/dist/components/overlays/Notifications/use-persistent-state.js +1 -1
- package/dist/components/overlays/Notifications/use-toast-state.js +1 -1
- package/dist/components/overlays/Toast/ToastItem.js +1 -1
- package/dist/components/overlays/Toast/index.js +1 -1
- package/dist/components/overlays/Toast/useProgressToast.js +1 -1
- package/dist/components/overlays/Toast/useToast.js +1 -1
- package/dist/components/overlays/Tooltip/Tooltip.js +1 -1
- package/dist/components/overlays/Tooltip/Tooltip.js.map +1 -1
- package/dist/components/overlays/Tooltip/TooltipProvider.js +1 -1
- package/dist/components/overlays/Tooltip/TooltipTrigger.js +1 -1
- package/dist/components/overlays/Tooltip/context.js +1 -1
- package/dist/components/portal/Portal.js +1 -1
- package/dist/components/portal/PortalProvider.js +1 -1
- package/dist/components/portal/usePortal.js +1 -1
- package/dist/components/shared/DraggableCollection.js +1 -1
- package/dist/components/shared/InvalidIcon.js +1 -1
- package/dist/components/shared/ValidIcon.js +1 -1
- package/dist/components/status/LoadingAnimation/LoadingAnimation.js +1 -1
- package/dist/components/status/Spin/Cube.js +1 -1
- package/dist/components/status/Spin/InternalSpinner.js +1 -1
- package/dist/components/status/Spin/Spin.js +1 -1
- package/dist/components/status/Spin/SpinsContainer.js +1 -1
- package/dist/data/item-themes.js +1 -1
- package/dist/data/item-themes.js.map +1 -1
- package/dist/data/themes.js +1 -1
- package/dist/data/themes.js.map +1 -1
- package/dist/eslint-plugin/defaults.generated.js +1 -1
- package/dist/eslint-plugin/index.js +1 -1
- package/dist/eslint-plugin/rules/no-redundant-default-prop.js +1 -1
- package/dist/i18n/I18nProvider.js +1 -1
- package/dist/i18n/createFormatter.js +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/i18n/instance.js +1 -1
- package/dist/i18n/locales/de-DE/uikit.js +1 -1
- package/dist/i18n/locales/en-US/uikit.js +1 -1
- package/dist/i18n/locales/es-ES/uikit.js +1 -1
- package/dist/i18n/locales/es-MX/uikit.js +1 -1
- package/dist/i18n/locales/fr-FR/uikit.js +1 -1
- package/dist/i18n/locales/it-IT/uikit.js +1 -1
- package/dist/i18n/locales/ja-JP/uikit.js +1 -1
- package/dist/i18n/locales/nb-NO/uikit.js +1 -1
- package/dist/i18n/locales/pt-BR/uikit.js +1 -1
- package/dist/i18n/locales/pt-PT/uikit.js +1 -1
- package/dist/i18n/locales/sv-SE/uikit.js +1 -1
- package/dist/i18n/locales/vi-VN/uikit.js +1 -1
- package/dist/i18n/locales.js +1 -1
- package/dist/i18n/useFormatter.js +1 -1
- package/dist/i18n/useI18n.js +1 -1
- package/dist/icons/AdjustmentsHorizontalIcon.js +1 -1
- package/dist/icons/AdjustmentsIcon.js +1 -1
- package/dist/icons/AiIcon.js +1 -1
- package/dist/icons/AreaChartIcon.js +1 -1
- package/dist/icons/ArrowNarrowDownIcon.js +1 -1
- package/dist/icons/ArrowNarrowUpIcon.js +1 -1
- package/dist/icons/BackwardIcon.js +1 -1
- package/dist/icons/BarChartIcon.js +1 -1
- package/dist/icons/BellFilledIcon.js +1 -1
- package/dist/icons/BellIcon.js +1 -1
- package/dist/icons/BooleanIcon.js +1 -1
- package/dist/icons/CalendarEditIcon.js +1 -1
- package/dist/icons/CalendarIcon.js +1 -1
- package/dist/icons/CaretDownIcon.js +1 -1
- package/dist/icons/CaretUpIcon.js +1 -1
- package/dist/icons/ChartAreaStackedIcon.js +1 -1
- package/dist/icons/ChartAreaStackedPercentageIcon.js +1 -1
- package/dist/icons/ChartBarGroupedHorizontalIcon.js +1 -1
- package/dist/icons/ChartBarGroupedIcon.js +1 -1
- package/dist/icons/ChartBarHorizontalIcon.js +1 -1
- package/dist/icons/ChartBarLineIcon.js +1 -1
- package/dist/icons/ChartBarStackedHorizontalIcon.js +1 -1
- package/dist/icons/ChartBarStackedIcon.js +1 -1
- package/dist/icons/ChartBarStackedPercentageHorizontalIcon.js +1 -1
- package/dist/icons/ChartBarStackedPercentageIcon.js +1 -1
- package/dist/icons/ChartBoxPlot2Icon.js +1 -1
- package/dist/icons/ChartBoxPlotIcon.js +1 -1
- package/dist/icons/ChartBubbleIcon.js +1 -1
- package/dist/icons/ChartDonut2Icon.js +1 -1
- package/dist/icons/ChartFunnelIcon.js +1 -1
- package/dist/icons/ChartHeatmapIcon.js +1 -1
- package/dist/icons/ChartKPIIcon.js +1 -1
- package/dist/icons/ChartPie2Icon.js +1 -1
- package/dist/icons/ChartScatterIcon.js +1 -1
- package/dist/icons/CheckCircleFilledIcon.js +1 -1
- package/dist/icons/CheckCircleIcon.js +1 -1
- package/dist/icons/CheckIcon.js +1 -1
- package/dist/icons/CircleFilledIcon.js +1 -1
- package/dist/icons/ClearIcon.js +1 -1
- package/dist/icons/CloseCircleFilledIcon.js +1 -1
- package/dist/icons/CloseCircleIcon.js +1 -1
- package/dist/icons/CloseIcon.js +1 -1
- package/dist/icons/CodeIcon.js +1 -1
- package/dist/icons/ColumnTotalIcon.js +1 -1
- package/dist/icons/CopyIcon.js +1 -1
- package/dist/icons/CountIcon.js +1 -1
- package/dist/icons/CubeIcon.js +1 -1
- package/dist/icons/CubePauseIcon.js +1 -1
- package/dist/icons/CubePlayIcon.js +1 -1
- package/dist/icons/CurrencyDollarIcon.js +1 -1
- package/dist/icons/DangerIcon.js +1 -1
- package/dist/icons/DashboardIcon.js +1 -1
- package/dist/icons/DatabaseIcon.js +1 -1
- package/dist/icons/DecimalDecreaseIcon.js +1 -1
- package/dist/icons/DecimalIncreaseIcon.js +1 -1
- package/dist/icons/DirectionIcon.js +1 -1
- package/dist/icons/DonutIcon.js +1 -1
- package/dist/icons/DownIcon.js +1 -1
- package/dist/icons/EditIcon.js +1 -1
- package/dist/icons/ExclamationCircleFilledIcon.js +1 -1
- package/dist/icons/ExclamationCircleIcon.js +1 -1
- package/dist/icons/ExclamationIcon.js +1 -1
- package/dist/icons/EyeIcon.js +1 -1
- package/dist/icons/EyeInvisibleIcon.js +1 -1
- package/dist/icons/FilterIcon.js +1 -1
- package/dist/icons/FolderFilledIcon.js +1 -1
- package/dist/icons/FolderIcon.js +1 -1
- package/dist/icons/FolderOpenFilledIcon.js +1 -1
- package/dist/icons/FolderOpenIcon.js +1 -1
- package/dist/icons/ForwardIcon.js +1 -1
- package/dist/icons/GripVerticalIcon.js +1 -1
- package/dist/icons/HierarchyIcon.js +1 -1
- package/dist/icons/HierarchyOpenIcon.js +1 -1
- package/dist/icons/Icon.js +1 -1
- package/dist/icons/InfoCircleIcon.js +1 -1
- package/dist/icons/InfoIcon.js +1 -1
- package/dist/icons/KeyIcon.js +1 -1
- package/dist/icons/LeftIcon.js +1 -1
- package/dist/icons/LineChartIcon.js +1 -1
- package/dist/icons/LoadingIcon.js +1 -1
- package/dist/icons/LockFilledIcon.js +1 -1
- package/dist/icons/LockIcon.js +1 -1
- package/dist/icons/MoreIcon.js +1 -1
- package/dist/icons/NotAllowedIcon.js +1 -1
- package/dist/icons/Number123Icon.js +1 -1
- package/dist/icons/NumberIcon.js +1 -1
- package/dist/icons/PauseCircleFilledIcon.js +1 -1
- package/dist/icons/PauseCircleIcon.js +1 -1
- package/dist/icons/PauseIcon.js +1 -1
- package/dist/icons/PercentageIcon.js +1 -1
- package/dist/icons/PieChartIcon.js +1 -1
- package/dist/icons/PipetteIcon.js +1 -1
- package/dist/icons/PlayCircleIcon.js +1 -1
- package/dist/icons/PlayIcon.js +1 -1
- package/dist/icons/PlusIcon.js +1 -1
- package/dist/icons/ProgressBarIcon.js +1 -1
- package/dist/icons/ReloadIcon.js +1 -1
- package/dist/icons/ReportIcon.js +1 -1
- package/dist/icons/ReturnIcon.js +1 -1
- package/dist/icons/RightIcon.js +1 -1
- package/dist/icons/RowTotalsIcon.js +1 -1
- package/dist/icons/SchemeIcon.js +1 -1
- package/dist/icons/SearchIcon.js +1 -1
- package/dist/icons/SemanticQueryIcon.js +1 -1
- package/dist/icons/SettingsIcon.js +1 -1
- package/dist/icons/ShieldFilledIcon.js +1 -1
- package/dist/icons/ShieldIcon.js +1 -1
- package/dist/icons/SlashIcon.js +1 -1
- package/dist/icons/SparklesIcon.js +1 -1
- package/dist/icons/SqlIcon.js +1 -1
- package/dist/icons/StatsIcon.js +1 -1
- package/dist/icons/StopIcon.js +1 -1
- package/dist/icons/StringIcon.js +1 -1
- package/dist/icons/SubtotalsIcon.js +1 -1
- package/dist/icons/SwitchIcon.js +1 -1
- package/dist/icons/TableIcon.js +1 -1
- package/dist/icons/ThumbsDownIcon.js +1 -1
- package/dist/icons/ThumbsUpIcon.js +1 -1
- package/dist/icons/ThunderboltCrossedIcon.js +1 -1
- package/dist/icons/ThunderboltFilledIcon.js +1 -1
- package/dist/icons/ThunderboltIcon.js +1 -1
- package/dist/icons/TimeIcon.js +1 -1
- package/dist/icons/TrashIcon.js +1 -1
- package/dist/icons/UnlockIcon.js +1 -1
- package/dist/icons/UpIcon.js +1 -1
- package/dist/icons/UserGroupIcon.js +1 -1
- package/dist/icons/UserIcon.js +1 -1
- package/dist/icons/UserLockIcon.js +1 -1
- package/dist/icons/ViewIcon.js +1 -1
- package/dist/icons/WarningFilledIcon.js +1 -1
- package/dist/icons/WarningIcon.js +1 -1
- package/dist/icons/wrap-icon.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/probe/canonicalize.js +1 -1
- package/dist/probe/css.js +1 -1
- package/dist/probe/index.js +1 -1
- package/dist/provider.js +1 -1
- package/dist/providers/TrackingProvider.js +1 -1
- package/dist/providers/navigationAdapter.default.js +1 -1
- package/dist/tokens/all-tokens.js +1 -1
- package/dist/tokens/base.js +1 -1
- package/dist/tokens/color-seed.js +1 -1
- package/dist/tokens/color-theme.d.ts +2 -2
- package/dist/tokens/color-theme.js +2 -2
- package/dist/tokens/color-theme.js.map +1 -1
- package/dist/tokens/colors.d.ts +3 -3
- package/dist/tokens/colors.js +4 -4
- package/dist/tokens/colors.js.map +1 -1
- package/dist/tokens/layout.js +1 -1
- package/dist/tokens/lazy-styles.js +1 -1
- package/dist/tokens/palette-config.d.ts +1 -1
- package/dist/tokens/palette-config.js +1 -1
- package/dist/tokens/palette-config.js.map +1 -1
- package/dist/tokens/palette.d.ts +5 -5
- package/dist/tokens/palette.js +11 -11
- package/dist/tokens/palette.js.map +1 -1
- package/dist/tokens/resolve.d.ts +2 -2
- package/dist/tokens/resolve.js +9 -9
- package/dist/tokens/resolve.js.map +1 -1
- package/dist/tokens/shadows.d.ts +1 -1
- package/dist/tokens/shadows.js +2 -2
- package/dist/tokens/shadows.js.map +1 -1
- package/dist/tokens/sizes.js +1 -1
- package/dist/tokens/spacing.js +1 -1
- package/dist/tokens/typography.js +1 -1
- package/dist/utils/ResizeSensor.js +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/is-dev-env.js +1 -1
- package/dist/utils/modules.js +1 -1
- package/dist/utils/promise.js +1 -1
- package/dist/utils/raf.js +1 -1
- package/dist/utils/random.js +1 -1
- package/dist/utils/range.js +1 -1
- package/dist/utils/react/RenderCache.js +1 -1
- package/dist/utils/react/Slots.js +1 -1
- package/dist/utils/react/chain.js +1 -1
- package/dist/utils/react/disabledProps.js +1 -1
- package/dist/utils/react/forwardRefWithGenerics.js +1 -1
- package/dist/utils/react/index.d.ts +1 -1
- package/dist/utils/react/index.js +2 -2
- package/dist/utils/react/interactions.js +1 -1
- package/dist/utils/react/isTextOnly.js +1 -1
- package/dist/utils/react/mapProps.js +1 -1
- package/dist/utils/react/mergeProps.js +1 -1
- package/dist/utils/react/nullableValue.js +1 -1
- package/dist/utils/react/resolveIcon.js +1 -1
- package/dist/utils/react/sharedStore.js +1 -1
- package/dist/utils/react/useBufferedValue.js +1 -1
- package/dist/utils/react/useCombinedRefs.js +1 -1
- package/dist/utils/react/useControlledFocusVisible.js +1 -1
- package/dist/utils/react/useEventBus.js +1 -1
- package/dist/utils/react/useId.js +1 -1
- package/dist/utils/react/useIsDarwin.js +1 -1
- package/dist/utils/react/useKeySymbols.js +1 -1
- package/dist/utils/react/useLayoutEffect.js +1 -1
- package/dist/utils/react/useLocalStorage.js +1 -1
- package/dist/utils/react/useMergeStyles.js +1 -1
- package/dist/utils/react/usePopoverSync.js +1 -1
- package/dist/utils/react/useQaProps.js +1 -1
- package/dist/utils/react/{useSchema.d.ts → useScheme.d.ts} +16 -16
- package/dist/utils/react/{useSchema.js → useScheme.js} +30 -30
- package/dist/utils/react/{useSchema.js.map → useScheme.js.map} +1 -1
- package/dist/utils/react/useViewportSize.js +1 -1
- package/dist/utils/react/wrapNodeIfPlain.js +1 -1
- package/dist/utils/selection.js +1 -1
- package/dist/utils/styles.js +1 -1
- package/dist/utils/tree.js +1 -1
- package/dist/utils/warnings.js +1 -1
- package/dist/version.js +2 -2
- package/docs/Colors.md +2 -2
- package/docs/Introduction.md +1 -1
- package/docs/Theming.md +19 -19
- package/docs/Usage.md +19 -19
- package/docs/components/content/ItemCard.md +1 -1
- package/docs/components/content/PrismCode.md +2 -2
- package/docs/components/data/DataTable.md +1 -1
- package/docs/components/fields/Picker.md +1 -1
- package/docs/components/fields/RadioGroup.md +28 -8
- package/docs/components/layout/Board.md +2 -2
- package/docs/components/navigation/Tabs.md +1 -1
- package/docs/components/organisms/StatsCard.md +1 -1
- package/docs/components/other/CubeLogo.md +9 -9
- package/docs/components/overlays/Tooltip.md +3 -3
- package/docs/components/overlays/TooltipProvider.md +1 -1
- package/package.json +2 -2
|
@@ -9,7 +9,7 @@ interface ColorThemeConfig {
|
|
|
9
9
|
* `okhsl()`, `okhst()`, `oklch()`.
|
|
10
10
|
*
|
|
11
11
|
* From a colour only the HUE and SATURATION are taken; the lightness is
|
|
12
|
-
* discarded and re-derived per
|
|
12
|
+
* discarded and re-derived per scheme. That is what makes the result adaptive
|
|
13
13
|
* rather than a value that happens to work in one theme.
|
|
14
14
|
*/
|
|
15
15
|
hue: number | string;
|
|
@@ -47,7 +47,7 @@ interface ColorTheme {
|
|
|
47
47
|
* ```
|
|
48
48
|
*
|
|
49
49
|
* The tone {@link colorSeed} also reads is dropped here on purpose: a tint theme
|
|
50
|
-
* re-derives its lightness per
|
|
50
|
+
* re-derives its lightness per scheme, which is what makes it adaptive. The palette's
|
|
51
51
|
* The palette's accent zone keeps the tone, because a brand fill has to *be* the
|
|
52
52
|
* colour.
|
|
53
53
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** @license MIT | @cube-dev/ui-kit v0.
|
|
1
|
+
/** @license MIT | @cube-dev/ui-kit v0.172.0 | Cube Dev Team */
|
|
2
2
|
import { colorSeed } from "./color-seed.js";
|
|
3
3
|
import { DEFAULT_HUE, DEFAULT_SATURATION, getPaletteConfig, getPaletteVersion, usePaletteVersion } from "./palette-config.js";
|
|
4
4
|
import { tintRecipe } from "./palette.js";
|
|
@@ -53,7 +53,7 @@ function stableStringify(value) {
|
|
|
53
53
|
* ```
|
|
54
54
|
*
|
|
55
55
|
* The tone {@link colorSeed} also reads is dropped here on purpose: a tint theme
|
|
56
|
-
* re-derives its lightness per
|
|
56
|
+
* re-derives its lightness per scheme, which is what makes it adaptive. The palette's
|
|
57
57
|
* The palette's accent zone keeps the tone, because a brand fill has to *be* the
|
|
58
58
|
* colour.
|
|
59
59
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"color-theme.js","names":[],"sources":["../../src/tokens/color-theme.ts"],"sourcesContent":["import { glaze } from '@tenphi/glaze';\nimport { useGlobalStyles } from '@tenphi/tasty';\n\n// Imported for its SIDE EFFECT as much as for `tintRecipe`.\n//\n// `palette.ts` runs `glaze.configure({ states: { dark: '@dark', highContrast:\n// '@hc' }, modes: …, darkTone: … })` at module scope, and that global config is\n// what makes a theme emit `'@dark'` / `'@hc'` keys at all. Without this import\n// a runtime theme falls back to Glaze's defaults — `@media(prefers-color-scheme:\n// dark)` keys and NO high-contrast tier — which still renders, so the failure is\n// silent. `color-theme.test.ts` asserts the `'@hc'` key exists to catch it.\nimport { colorSeed } from './color-seed';\nimport { tintRecipe } from './palette';\nimport {\n DEFAULT_HUE,\n DEFAULT_SATURATION,\n getPaletteConfig,\n getPaletteVersion,\n usePaletteVersion,\n} from './palette-config';\n\nimport type { ColorMap, GlazeConfigOverride } from '@tenphi/glaze';\nimport type { Styles } from '@tenphi/tasty';\n\nexport interface ColorThemeConfig {\n /**\n * A hue in 0–360, or any colour Glaze parses — hex, `rgb()`, `hsl()`,\n * `okhsl()`, `okhst()`, `oklch()`.\n *\n * From a colour only the HUE and SATURATION are taken; the lightness is\n * discarded and re-derived per schema. That is what makes the result adaptive\n * rather than a value that happens to work in one theme.\n */\n hue: number | string;\n /** 0–100. @default the palette's own saturation seed */\n saturation?: number;\n pastel?: boolean;\n /**\n * Extra colour definitions, merged over {@link tintRecipe}. Anything Glaze's\n * `theme.colors()` accepts, including `contrast` floors against a sibling.\n */\n colors?: ColorMap;\n}\n\nexport interface ColorTheme {\n /**\n * A hash of the config, and the token prefix — `'tint-1a2b3c'`.\n *\n * Content-addressed rather than positional so two components asking for the\n * same colour name the same theme, and therefore share one injection. Also\n * makes it safe to stamp on the DOM as an identifier.\n */\n name: string;\n /**\n * Tasty token declarations, one state map per colour:\n * `{ '#tint-1a2b3c-surface': { '': …, '@dark': …, '@hc': …, '@dark & @hc': … } }`\n */\n tokens: Styles;\n /** Token references by their unprefixed name — `{ surface: '#tint-1a2b3c-surface' }`. */\n colors: Record<string, string>;\n}\n\n/**\n * FNV-1a, base36. Short, stable and dependency-free.\n *\n * These hashes name CSS custom properties and land in DOM attributes, so they\n * have to be deterministic across processes (SSR and the client must agree) and\n * safe as identifiers — but not cryptographic.\n */\nexport function hashString(input: string): string {\n let hash = 0x811c9dc5;\n\n for (let i = 0; i < input.length; i++) {\n hash ^= input.charCodeAt(i);\n // >>> 0 keeps it an unsigned 32-bit int; `Math.imul` is the 32-bit multiply.\n hash = Math.imul(hash, 0x01000193) >>> 0;\n }\n\n return hash.toString(36);\n}\n\n/**\n * Hashes the RESOLVED seed, not the config as written.\n *\n * `{ hue: '#0ea5e9' }` and `{ hue: 237.32, saturation: 98.19 }` are the same\n * colour said two ways; hashing the input would give them two names, two\n * injections and two sets of generated rules. Rounded, because a seed that\n * differs in the twelfth decimal is the same colour to every eye and every\n * renderer — and an unrounded float would defeat the deduplication it is here to\n * provide.\n */\nfunction hashSeed(seed: ResolvedSeed): string {\n return `tint-${hashString(\n JSON.stringify([\n seed.hue.toFixed(4),\n seed.saturation.toFixed(4),\n seed.pastel,\n seed.colors ? stableStringify(seed.colors) : null,\n ]),\n )}`;\n}\n\ninterface ResolvedSeed {\n hue: number;\n saturation: number;\n pastel: boolean;\n colors?: ColorMap;\n}\n\n/** Key order must not change the hash, or two identical configs get two slots. */\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== 'object') return JSON.stringify(value);\n\n if (Array.isArray(value)) {\n return `[${value.map(stableStringify).join(',')}]`;\n }\n\n const entries = Object.entries(value as Record<string, unknown>).sort(\n ([a], [b]) => (a < b ? -1 : a > b ? 1 : 0),\n );\n\n return `{${entries\n .map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`)\n .join(',')}}`;\n}\n\n/**\n * The hue and saturation of a colour, for use as a theme seed.\n *\n * ```ts\n * colorThemeSeed('#0ea5e9'); // → { hue: 237.32, saturation: 98.19 }\n * ```\n *\n * The tone {@link colorSeed} also reads is dropped here on purpose: a tint theme\n * re-derives its lightness per schema, which is what makes it adaptive. The palette's\n * The palette's accent zone keeps the tone, because a brand fill has to *be* the\n * colour.\n *\n * An unparseable value falls back to the shipped seed rather than throwing — a hue\n * typed into a settings field should not take the render down.\n */\nexport function colorThemeSeed(value: string): {\n hue: number;\n saturation: number;\n} {\n const seed = colorSeed(value);\n\n if (!seed) return { hue: DEFAULT_HUE, saturation: DEFAULT_SATURATION };\n\n return { hue: seed.hue, saturation: seed.saturation };\n}\n\n/**\n * Cached by `${paletteVersion}:${name}`.\n *\n * The palette version is in the KEY rather than triggering a clear, so a stale\n * entry is simply unreachable. `lastVersion` still clears the map on a change,\n * or repeated `setPaletteConfig` calls would grow it without bound.\n */\nconst cache = new Map<string, ColorTheme>();\nlet lastVersion = -1;\n\n/**\n * Build (or reuse) an adaptive theme from a hue.\n *\n * Pure and memoized: the same config returns the same object identity until the\n * palette is re-seeded. Registering the tokens is {@link useColorTheme}'s job —\n * this only computes them, so it is safe to call while rendering or from a test.\n */\nexport function getColorTheme(config: ColorThemeConfig): ColorTheme {\n const version = getPaletteVersion();\n\n if (version !== lastVersion) {\n cache.clear();\n lastVersion = version;\n }\n\n const paletteConfig = getPaletteConfig();\n const parsed =\n typeof config.hue === 'string' ? colorThemeSeed(config.hue) : null;\n const seed: ResolvedSeed = {\n hue: parsed ? parsed.hue : (config.hue as number),\n saturation:\n config.saturation ??\n parsed?.saturation ??\n paletteConfig.saturation ??\n DEFAULT_SATURATION,\n pastel: config.pastel ?? paletteConfig.pastel ?? false,\n colors: config.colors,\n };\n\n const name = hashSeed(seed);\n const cached = cache.get(name);\n\n if (cached) return cached;\n\n const { hue, saturation } = seed;\n\n // `pastel` and `contrastLevel` are instance-level in Glaze, not settable\n // through `glaze.configure`, so a runtime theme has to carry them itself or it\n // would ignore a pastel palette. Omitted entirely when off, so the default\n // output is provably the same as passing nothing.\n const overrides: GlazeConfigOverride = {\n ...(paletteConfig.contrastLevel != null\n ? { contrastLevel: paletteConfig.contrastLevel }\n : null),\n ...(seed.pastel ? { pastel: true } : null),\n };\n\n const theme = glaze(\n hue,\n saturation,\n Object.keys(overrides).length ? overrides : undefined,\n );\n\n // The recipe follows the palette's `surfaceMode`, so a runtime tint sits the same\n // distance off the page as a built-in status theme does. Safe to read here rather\n // than hash into the seed: the cache is cleared on every palette version change,\n // and `surfaceMode` is one of the fields that bumps it.\n theme.colors({ ...tintRecipe(paletteConfig), ...config.colors });\n\n // `prefix` is a palette-only option, so the keys are renamed here. `oklch`\n // because that is what the palette emits; tasty converts to the configured\n // colour space on the way out either way.\n const raw = theme.tasty({ format: 'oklch' }) as Record<string, unknown>;\n const tokens: Styles = {};\n const colors: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(raw)) {\n // Only the `#colour` entries are renamed; `splitHue` is off, so there are no\n // `$…-hue` companions to worry about, but a future one must not be mangled.\n if (!key.startsWith('#')) {\n (tokens as Record<string, unknown>)[key] = value;\n continue;\n }\n\n const bare = key.slice(1);\n const token = `#${name}-${bare}`;\n\n (tokens as Record<string, unknown>)[token] = value;\n colors[bare] = token;\n }\n\n const result: ColorTheme = { name, tokens, colors };\n\n cache.set(name, result);\n\n return result;\n}\n\n/**\n * Register an adaptive theme's tokens once per document, and get it back.\n *\n * The tokens land on `body` under a slot keyed by the theme's name, which is a\n * hash of the config — so every component asking for the same colour shares one\n * injection no matter how many of them there are. Tasty's global-style slots are\n * also permanent, which is what makes that safe: one component unmounting cannot\n * strip tokens another is still referencing.\n *\n * `body` rather than `:root`, which emits nothing at all — the same selector\n * `GlobalStyles` uses for the palette's own tokens. Custom properties inherit, so\n * anything rendered inside the document sees them; the one thing this cannot\n * reach is content portalled outside `<body>`, which nothing does.\n *\n * Because the slot id hashes the CONFIG rather than the resolved values,\n * re-seeding the palette replaces the same slot instead of leaking a new one.\n *\n * ```tsx\n * const theme = useColorTheme({ hue: '#0ea5e9' });\n * // theme.colors.surface → '#tint-1a2b3c-surface'\n * ```\n */\nexport function useColorTheme(config: ColorThemeConfig): ColorTheme {\n // Subscribes this component to a re-seed. `getColorTheme` reads the version\n // itself for its cache, so the value is unused here — the subscription is the\n // point, and it is what makes the tokens below re-inject.\n usePaletteVersion();\n\n const theme = getColorTheme(config);\n\n useGlobalStyles('body', theme.tokens, {\n id: `cube-color-theme-${theme.name}`,\n });\n\n return theme;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"color-theme.js","names":[],"sources":["../../src/tokens/color-theme.ts"],"sourcesContent":["import { glaze } from '@tenphi/glaze';\nimport { useGlobalStyles } from '@tenphi/tasty';\n\n// Imported for its SIDE EFFECT as much as for `tintRecipe`.\n//\n// `palette.ts` runs `glaze.configure({ states: { dark: '@dark', highContrast:\n// '@hc' }, modes: …, darkTone: … })` at module scope, and that global config is\n// what makes a theme emit `'@dark'` / `'@hc'` keys at all. Without this import\n// a runtime theme falls back to Glaze's defaults — `@media(prefers-color-scheme:\n// dark)` keys and NO high-contrast tier — which still renders, so the failure is\n// silent. `color-theme.test.ts` asserts the `'@hc'` key exists to catch it.\nimport { colorSeed } from './color-seed';\nimport { tintRecipe } from './palette';\nimport {\n DEFAULT_HUE,\n DEFAULT_SATURATION,\n getPaletteConfig,\n getPaletteVersion,\n usePaletteVersion,\n} from './palette-config';\n\nimport type { ColorMap, GlazeConfigOverride } from '@tenphi/glaze';\nimport type { Styles } from '@tenphi/tasty';\n\nexport interface ColorThemeConfig {\n /**\n * A hue in 0–360, or any colour Glaze parses — hex, `rgb()`, `hsl()`,\n * `okhsl()`, `okhst()`, `oklch()`.\n *\n * From a colour only the HUE and SATURATION are taken; the lightness is\n * discarded and re-derived per scheme. That is what makes the result adaptive\n * rather than a value that happens to work in one theme.\n */\n hue: number | string;\n /** 0–100. @default the palette's own saturation seed */\n saturation?: number;\n pastel?: boolean;\n /**\n * Extra colour definitions, merged over {@link tintRecipe}. Anything Glaze's\n * `theme.colors()` accepts, including `contrast` floors against a sibling.\n */\n colors?: ColorMap;\n}\n\nexport interface ColorTheme {\n /**\n * A hash of the config, and the token prefix — `'tint-1a2b3c'`.\n *\n * Content-addressed rather than positional so two components asking for the\n * same colour name the same theme, and therefore share one injection. Also\n * makes it safe to stamp on the DOM as an identifier.\n */\n name: string;\n /**\n * Tasty token declarations, one state map per colour:\n * `{ '#tint-1a2b3c-surface': { '': …, '@dark': …, '@hc': …, '@dark & @hc': … } }`\n */\n tokens: Styles;\n /** Token references by their unprefixed name — `{ surface: '#tint-1a2b3c-surface' }`. */\n colors: Record<string, string>;\n}\n\n/**\n * FNV-1a, base36. Short, stable and dependency-free.\n *\n * These hashes name CSS custom properties and land in DOM attributes, so they\n * have to be deterministic across processes (SSR and the client must agree) and\n * safe as identifiers — but not cryptographic.\n */\nexport function hashString(input: string): string {\n let hash = 0x811c9dc5;\n\n for (let i = 0; i < input.length; i++) {\n hash ^= input.charCodeAt(i);\n // >>> 0 keeps it an unsigned 32-bit int; `Math.imul` is the 32-bit multiply.\n hash = Math.imul(hash, 0x01000193) >>> 0;\n }\n\n return hash.toString(36);\n}\n\n/**\n * Hashes the RESOLVED seed, not the config as written.\n *\n * `{ hue: '#0ea5e9' }` and `{ hue: 237.32, saturation: 98.19 }` are the same\n * colour said two ways; hashing the input would give them two names, two\n * injections and two sets of generated rules. Rounded, because a seed that\n * differs in the twelfth decimal is the same colour to every eye and every\n * renderer — and an unrounded float would defeat the deduplication it is here to\n * provide.\n */\nfunction hashSeed(seed: ResolvedSeed): string {\n return `tint-${hashString(\n JSON.stringify([\n seed.hue.toFixed(4),\n seed.saturation.toFixed(4),\n seed.pastel,\n seed.colors ? stableStringify(seed.colors) : null,\n ]),\n )}`;\n}\n\ninterface ResolvedSeed {\n hue: number;\n saturation: number;\n pastel: boolean;\n colors?: ColorMap;\n}\n\n/** Key order must not change the hash, or two identical configs get two slots. */\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== 'object') return JSON.stringify(value);\n\n if (Array.isArray(value)) {\n return `[${value.map(stableStringify).join(',')}]`;\n }\n\n const entries = Object.entries(value as Record<string, unknown>).sort(\n ([a], [b]) => (a < b ? -1 : a > b ? 1 : 0),\n );\n\n return `{${entries\n .map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`)\n .join(',')}}`;\n}\n\n/**\n * The hue and saturation of a colour, for use as a theme seed.\n *\n * ```ts\n * colorThemeSeed('#0ea5e9'); // → { hue: 237.32, saturation: 98.19 }\n * ```\n *\n * The tone {@link colorSeed} also reads is dropped here on purpose: a tint theme\n * re-derives its lightness per scheme, which is what makes it adaptive. The palette's\n * The palette's accent zone keeps the tone, because a brand fill has to *be* the\n * colour.\n *\n * An unparseable value falls back to the shipped seed rather than throwing — a hue\n * typed into a settings field should not take the render down.\n */\nexport function colorThemeSeed(value: string): {\n hue: number;\n saturation: number;\n} {\n const seed = colorSeed(value);\n\n if (!seed) return { hue: DEFAULT_HUE, saturation: DEFAULT_SATURATION };\n\n return { hue: seed.hue, saturation: seed.saturation };\n}\n\n/**\n * Cached by `${paletteVersion}:${name}`.\n *\n * The palette version is in the KEY rather than triggering a clear, so a stale\n * entry is simply unreachable. `lastVersion` still clears the map on a change,\n * or repeated `setPaletteConfig` calls would grow it without bound.\n */\nconst cache = new Map<string, ColorTheme>();\nlet lastVersion = -1;\n\n/**\n * Build (or reuse) an adaptive theme from a hue.\n *\n * Pure and memoized: the same config returns the same object identity until the\n * palette is re-seeded. Registering the tokens is {@link useColorTheme}'s job —\n * this only computes them, so it is safe to call while rendering or from a test.\n */\nexport function getColorTheme(config: ColorThemeConfig): ColorTheme {\n const version = getPaletteVersion();\n\n if (version !== lastVersion) {\n cache.clear();\n lastVersion = version;\n }\n\n const paletteConfig = getPaletteConfig();\n const parsed =\n typeof config.hue === 'string' ? colorThemeSeed(config.hue) : null;\n const seed: ResolvedSeed = {\n hue: parsed ? parsed.hue : (config.hue as number),\n saturation:\n config.saturation ??\n parsed?.saturation ??\n paletteConfig.saturation ??\n DEFAULT_SATURATION,\n pastel: config.pastel ?? paletteConfig.pastel ?? false,\n colors: config.colors,\n };\n\n const name = hashSeed(seed);\n const cached = cache.get(name);\n\n if (cached) return cached;\n\n const { hue, saturation } = seed;\n\n // `pastel` and `contrastLevel` are instance-level in Glaze, not settable\n // through `glaze.configure`, so a runtime theme has to carry them itself or it\n // would ignore a pastel palette. Omitted entirely when off, so the default\n // output is provably the same as passing nothing.\n const overrides: GlazeConfigOverride = {\n ...(paletteConfig.contrastLevel != null\n ? { contrastLevel: paletteConfig.contrastLevel }\n : null),\n ...(seed.pastel ? { pastel: true } : null),\n };\n\n const theme = glaze(\n hue,\n saturation,\n Object.keys(overrides).length ? overrides : undefined,\n );\n\n // The recipe follows the palette's `surfaceMode`, so a runtime tint sits the same\n // distance off the page as a built-in status theme does. Safe to read here rather\n // than hash into the seed: the cache is cleared on every palette version change,\n // and `surfaceMode` is one of the fields that bumps it.\n theme.colors({ ...tintRecipe(paletteConfig), ...config.colors });\n\n // `prefix` is a palette-only option, so the keys are renamed here. `oklch`\n // because that is what the palette emits; tasty converts to the configured\n // colour space on the way out either way.\n const raw = theme.tasty({ format: 'oklch' }) as Record<string, unknown>;\n const tokens: Styles = {};\n const colors: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(raw)) {\n // Only the `#colour` entries are renamed; `splitHue` is off, so there are no\n // `$…-hue` companions to worry about, but a future one must not be mangled.\n if (!key.startsWith('#')) {\n (tokens as Record<string, unknown>)[key] = value;\n continue;\n }\n\n const bare = key.slice(1);\n const token = `#${name}-${bare}`;\n\n (tokens as Record<string, unknown>)[token] = value;\n colors[bare] = token;\n }\n\n const result: ColorTheme = { name, tokens, colors };\n\n cache.set(name, result);\n\n return result;\n}\n\n/**\n * Register an adaptive theme's tokens once per document, and get it back.\n *\n * The tokens land on `body` under a slot keyed by the theme's name, which is a\n * hash of the config — so every component asking for the same colour shares one\n * injection no matter how many of them there are. Tasty's global-style slots are\n * also permanent, which is what makes that safe: one component unmounting cannot\n * strip tokens another is still referencing.\n *\n * `body` rather than `:root`, which emits nothing at all — the same selector\n * `GlobalStyles` uses for the palette's own tokens. Custom properties inherit, so\n * anything rendered inside the document sees them; the one thing this cannot\n * reach is content portalled outside `<body>`, which nothing does.\n *\n * Because the slot id hashes the CONFIG rather than the resolved values,\n * re-seeding the palette replaces the same slot instead of leaking a new one.\n *\n * ```tsx\n * const theme = useColorTheme({ hue: '#0ea5e9' });\n * // theme.colors.surface → '#tint-1a2b3c-surface'\n * ```\n */\nexport function useColorTheme(config: ColorThemeConfig): ColorTheme {\n // Subscribes this component to a re-seed. `getColorTheme` reads the version\n // itself for its cache, so the value is unused here — the subscription is the\n // point, and it is what makes the tokens below re-inject.\n usePaletteVersion();\n\n const theme = getColorTheme(config);\n\n useGlobalStyles('body', theme.tokens, {\n id: `cube-color-theme-${theme.name}`,\n });\n\n return theme;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAqEA,SAAgB,WAAW,OAAuB;CAChD,IAAI,OAAO;AAEX,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAQ,MAAM,WAAW,EAAE;AAE3B,SAAO,KAAK,KAAK,MAAM,SAAW,KAAK;;AAGzC,QAAO,KAAK,SAAS,GAAG;;;;;;;;;;;;AAa1B,SAAS,SAAS,MAA4B;AAC5C,QAAO,QAAQ,WACb,KAAK,UAAU;EACb,KAAK,IAAI,QAAQ,EAAE;EACnB,KAAK,WAAW,QAAQ,EAAE;EAC1B,KAAK;EACL,KAAK,SAAS,gBAAgB,KAAK,OAAO,GAAG;EAC9C,CAAC,CACH;;;AAWH,SAAS,gBAAgB,OAAwB;AAC/C,KAAI,UAAU,QAAQ,OAAO,UAAU,SAAU,QAAO,KAAK,UAAU,MAAM;AAE7E,KAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,IAAI,MAAM,IAAI,gBAAgB,CAAC,KAAK,IAAI,CAAC;AAOlD,QAAO,IAJS,OAAO,QAAQ,MAAiC,CAAC,MAC9D,CAAC,IAAI,CAAC,OAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,EACzC,CAGE,KAAK,CAAC,KAAK,UAAU,GAAG,KAAK,UAAU,IAAI,CAAC,GAAG,gBAAgB,KAAK,GAAG,CACvE,KAAK,IAAI,CAAC;;;;;;;;;;;;;;;;;AAkBf,SAAgB,eAAe,OAG7B;CACA,MAAM,OAAO,UAAU,MAAM;AAE7B,KAAI,CAAC,KAAM,QAAO;EAAE,KAAK;EAAa,YAAY;EAAoB;AAEtE,QAAO;EAAE,KAAK,KAAK;EAAK,YAAY,KAAK;EAAY;;;;;;;;;AAUvD,MAAM,wBAAQ,IAAI,KAAyB;AAC3C,IAAI,cAAc;;;;;;;;AASlB,SAAgB,cAAc,QAAsC;CAClE,MAAM,UAAU,mBAAmB;AAEnC,KAAI,YAAY,aAAa;AAC3B,QAAM,OAAO;AACb,gBAAc;;CAGhB,MAAM,gBAAgB,kBAAkB;CACxC,MAAM,SACJ,OAAO,OAAO,QAAQ,WAAW,eAAe,OAAO,IAAI,GAAG;CAChE,MAAM,OAAqB;EACzB,KAAK,SAAS,OAAO,MAAO,OAAO;EACnC,YACE,OAAO,cACP,QAAQ,cACR,cAAc,cACd;EACF,QAAQ,OAAO,UAAU,cAAc,UAAU;EACjD,QAAQ,OAAO;EAChB;CAED,MAAM,OAAO,SAAS,KAAK;CAC3B,MAAM,SAAS,MAAM,IAAI,KAAK;AAE9B,KAAI,OAAQ,QAAO;CAEnB,MAAM,EAAE,KAAK,eAAe;CAM5B,MAAM,YAAiC;EACrC,GAAI,cAAc,iBAAiB,OAC/B,EAAE,eAAe,cAAc,eAAe,GAC9C;EACJ,GAAI,KAAK,SAAS,EAAE,QAAQ,MAAM,GAAG;EACtC;CAED,MAAM,QAAQ,MACZ,KACA,YACA,OAAO,KAAK,UAAU,CAAC,SAAS,YAAY,OAC7C;AAMD,OAAM,OAAO;EAAE,GAAG,WAAW,cAAc;EAAE,GAAG,OAAO;EAAQ,CAAC;CAKhE,MAAM,MAAM,MAAM,MAAM,EAAE,QAAQ,SAAS,CAAC;CAC5C,MAAM,SAAiB,EAAE;CACzB,MAAM,SAAiC,EAAE;AAEzC,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,IAAI,EAAE;AAG9C,MAAI,CAAC,IAAI,WAAW,IAAI,EAAE;AACxB,GAAC,OAAmC,OAAO;AAC3C;;EAGF,MAAM,OAAO,IAAI,MAAM,EAAE;EACzB,MAAM,QAAQ,IAAI,KAAK,GAAG;AAE1B,EAAC,OAAmC,SAAS;AAC7C,SAAO,QAAQ;;CAGjB,MAAM,SAAqB;EAAE;EAAM;EAAQ;EAAQ;AAEnD,OAAM,IAAI,MAAM,OAAO;AAEvB,QAAO;;;;;;;;;;;;;;;;;;;;;;;;AAyBT,SAAgB,cAAc,QAAsC;AAIlE,oBAAmB;CAEnB,MAAM,QAAQ,cAAc,OAAO;AAEnC,iBAAgB,QAAQ,MAAM,QAAQ,EACpC,IAAI,oBAAoB,MAAM,QAC/B,CAAC;AAEF,QAAO"}
|
package/dist/tokens/colors.d.ts
CHANGED
|
@@ -6,12 +6,12 @@ import { Styles, Tokens } from "@tenphi/tasty";
|
|
|
6
6
|
declare function getColorTokens(): Styles;
|
|
7
7
|
declare const COLOR_TOKENS: Styles;
|
|
8
8
|
/**
|
|
9
|
-
* Render every UI Kit color for one config and one
|
|
9
|
+
* Render every UI Kit color for one config and one scheme, as flat literal
|
|
10
10
|
* values ready to apply to a **region** via a tasty `tokens` prop.
|
|
11
11
|
*
|
|
12
12
|
* ```tsx
|
|
13
13
|
* const preview = useMemo(
|
|
14
|
-
* () => renderColorTokens({ hue: 210,
|
|
14
|
+
* () => renderColorTokens({ hue: 210, scheme: 'dark' }),
|
|
15
15
|
* [],
|
|
16
16
|
* );
|
|
17
17
|
*
|
|
@@ -21,7 +21,7 @@ declare const COLOR_TOKENS: Styles;
|
|
|
21
21
|
* ```
|
|
22
22
|
*
|
|
23
23
|
* Config fields merge over the *current* palette config, so
|
|
24
|
-
* `renderColorTokens({
|
|
24
|
+
* `renderColorTokens({ scheme: 'dark' })` previews the active theme in dark
|
|
25
25
|
* without restating it. Nothing is applied globally — the live palette is
|
|
26
26
|
* untouched.
|
|
27
27
|
*
|
package/dist/tokens/colors.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** @license MIT | @cube-dev/ui-kit v0.
|
|
1
|
+
/** @license MIT | @cube-dev/ui-kit v0.172.0 | Cube Dev Team */
|
|
2
2
|
import { getPaletteVersion } from "./palette-config.js";
|
|
3
3
|
import { BASE_TOKENS } from "./base.js";
|
|
4
4
|
import { lazyStyles } from "./lazy-styles.js";
|
|
@@ -147,12 +147,12 @@ const COLOR_DEPENDENT_TOKENS = {
|
|
|
147
147
|
"#scrollbar-corner": BASE_TOKENS["#scrollbar-corner"]
|
|
148
148
|
};
|
|
149
149
|
/**
|
|
150
|
-
* Render every UI Kit color for one config and one
|
|
150
|
+
* Render every UI Kit color for one config and one scheme, as flat literal
|
|
151
151
|
* values ready to apply to a **region** via a tasty `tokens` prop.
|
|
152
152
|
*
|
|
153
153
|
* ```tsx
|
|
154
154
|
* const preview = useMemo(
|
|
155
|
-
* () => renderColorTokens({ hue: 210,
|
|
155
|
+
* () => renderColorTokens({ hue: 210, scheme: 'dark' }),
|
|
156
156
|
* [],
|
|
157
157
|
* );
|
|
158
158
|
*
|
|
@@ -162,7 +162,7 @@ const COLOR_DEPENDENT_TOKENS = {
|
|
|
162
162
|
* ```
|
|
163
163
|
*
|
|
164
164
|
* Config fields merge over the *current* palette config, so
|
|
165
|
-
* `renderColorTokens({
|
|
165
|
+
* `renderColorTokens({ scheme: 'dark' })` previews the active theme in dark
|
|
166
166
|
* without restating it. Nothing is applied globally — the live palette is
|
|
167
167
|
* untouched.
|
|
168
168
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"colors.js","names":[],"sources":["../../src/tokens/colors.ts"],"sourcesContent":["import { BASE_TOKENS } from './base';\nimport { lazyStyles } from './lazy-styles';\nimport { getPaletteTokens, renderPaletteTokens } from './palette';\nimport { getPaletteVersion } from './palette-config';\nimport { SHADOW_TOKENS } from './shadows';\n\nimport type { Styles, Tokens } from '@tenphi/tasty';\nimport type { RenderPaletteOptions } from './palette';\n\n/**\n * Color tokens with `#` prefix for tasty color definitions.\n *\n * The base values come from the Glaze-generated palette (`palette.ts`):\n * `#surface`, `#surface-text*`, `#border`, `#focus`, `#disabled`, `#pink`,\n * `#shadow-*`, `#overlay`, the unprefixed `#accent-*` family, and the per-theme\n * `#primary-*` / `#purple-*` / `#success-*` / `#danger-*` / `#warning-*` /\n * `#note-*` families.\n *\n * Each Glaze token is a state map (`{ '': '...', '@dark': '...', '@hc': '...' }`),\n * giving us light, dark, and high-contrast variants for free. The `@dark` /\n * `@hc` predefined states are wired up globally in `src/components/Root.tsx`.\n *\n * The aliases below preserve backward compatibility with every legacy\n * `#name` used across components, stories, and tests. Each alias resolves\n * to a current Glaze token via tasty's `#token` reference syntax.\n */\n\nconst LEGACY_ALIASES: Styles = {\n // ---- Neutral text scale (legacy `#dark*`) ----\n '#text': '#surface-text-soft',\n '#dark': '#surface-text',\n '#dark-01': '#surface-text',\n '#dark-02': '#surface-text-soft',\n '#dark-03': '#surface-text-soft-2',\n '#dark-04': '#placeholder',\n '#dark-05': '#border',\n\n // Fixed-mode counterpart to `#dark`. Resolves to the same L≈12 surface\n // but uses Glaze `mode: 'fixed'` so it does NOT invert in dark schemas.\n // Use this whenever the design intentionally pins a dark color regardless\n // of schema. Points at `#special-surface` (`mode: 'fixed'`, L=12),\n // emitted by the standalone `specialTheme` in `palette.ts` — the canonical\n // source of fixed-mode color tokens for `special`-variant components.\n '#fixed-dark': '#special-surface',\n\n // Fixed-mode counterpart to `#primary-text`. `#primary-text` is anchored\n // to `surface` with `mode: 'auto'`, so it flips to a *light* purple in\n // dark schemas (correct on body content, which also inverts). When the\n // local fill is a fixed color instead (an always-white pill, etc.), the\n // adaptive text loses contrast (light purple on white) in dark mode.\n // Points at `#special-accent-text` (`mode: 'fixed'`, cr 6–8.5 vs fixed\n // white) — a dark purple readable on a white surface that stays put\n // across schemas.\n '#fixed-primary-text': '#special-accent-text',\n\n // ---- Misc neutral ----\n '#minor': '#surface-text-soft.65',\n '#shadow': '#shadow-md',\n '#light': '#surface-3',\n '#dark-bg': '#surface-2',\n '#clear': 'transparent',\n\n // Pink: independent hue, schema-static (no Glaze adaptation). Kept as a raw\n // literal rather than folded into a theme — nothing in the palette emits this\n // hue as a standalone token, and it is a documented public alias (see\n // `Usage.docs.mdx`, `tasty.config.ts`, and the `pink` key in\n // `tasty-augment.d.ts`), so dropping it would silently break consumer styles\n // while the types still advertised it.\n '#pink': 'okhsl(5 100% 67%)',\n\n // ---- Disabled state aliases ----\n // `#disabled-surface` and `#disabled-surface-text` are emitted directly by\n // the Glaze palette (`palette.ts`) as schema-symmetric, contrast-driven\n // tokens — no alias needed here. `#disabled` stays as a brand-tinted\n // backwards-compat anchor for the per-theme `#<theme>-disabled` aliases below.\n\n // ---- Primary / Purple legacy ----\n '#primary': '#primary-accent-surface',\n '#primary-text': '#primary-accent-text',\n '#primary-text-soft': '#primary-accent-text-soft',\n '#primary-bg': '#primary-surface',\n '#primary-icon': '#primary-accent-icon',\n '#primary-hover': '#primary-accent-surface-hover',\n '#primary-desaturated': '#primary-accent-surface-2',\n '#primary-disabled': '#disabled',\n\n '#purple': '#purple-accent-surface',\n '#purple-text': '#purple-accent-text',\n '#purple-text-soft': '#purple-accent-text-soft',\n '#purple-bg': '#purple-surface',\n '#purple-icon': '#purple-accent-icon',\n '#purple-hover': '#purple-accent-surface-hover',\n '#purple-disabled': '#disabled',\n\n // Purple scale (gradient/accent shades) — mapped to the accent-surface ramp.\n '#purple-01': '#purple-accent-surface',\n '#purple-02': '#purple-accent-surface-2',\n '#purple-03': '#purple-accent-surface-3',\n '#purple-04': '#purple-surface',\n\n // ---- Danger ----\n '#danger': '#danger-accent-surface',\n '#danger-text': '#danger-accent-text',\n '#danger-text-soft': '#danger-accent-text-soft',\n '#danger-bg': '#danger-surface',\n '#danger-icon': '#danger-accent-icon',\n '#danger-hover': '#danger-accent-surface-hover',\n '#danger-desaturated': '#danger-accent-surface-2',\n '#danger-disabled': '#disabled',\n\n // ---- Success ----\n '#success': '#success-accent-surface',\n '#success-text': '#success-accent-text',\n '#success-text-soft': '#success-accent-text-soft',\n '#success-bg': '#success-surface',\n '#success-icon': '#success-accent-icon',\n '#success-hover': '#success-accent-surface-hover',\n '#success-desaturated': '#success-accent-surface-2',\n '#success-disabled': '#disabled',\n\n // ---- Warning ----\n '#warning': '#warning-accent-surface',\n '#warning-text': '#warning-accent-text',\n '#warning-text-soft': '#warning-accent-text-soft',\n '#warning-bg': '#warning-surface',\n '#warning-icon': '#warning-accent-icon',\n '#warning-hover': '#warning-accent-surface-hover',\n '#warning-desaturated': '#warning-accent-surface-2',\n '#warning-disabled': '#disabled',\n\n // ---- Note ----\n '#note': '#note-accent-surface',\n '#note-text': '#note-accent-text',\n '#note-text-soft': '#note-accent-text-soft',\n '#note-bg': '#note-surface',\n '#note-icon': '#note-accent-icon',\n '#note-hover': '#note-accent-surface-hover',\n '#note-desaturated': '#note-accent-surface-2',\n '#note-disabled': '#disabled',\n};\n\n/**\n * Context hooks — tokens a CONTAINER sets to steer a component that cannot\n * resolve the color on its own.\n *\n * Declared with a default rather than read as `var(--x, fallback)` at every use\n * site, for two reasons: the default is stated once instead of repeated (and so\n * cannot drift), and a declared token gets its components companion for free —\n * which is the only way `#current-fill.5` can fade whatever the container\n * offered rather than the fallback.\n *\n * Included by reference in {@link renderColorTokens} alongside the legacy\n * aliases, so a region preview re-resolves the default against that region's own\n * `#surface` instead of freezing the outer theme's.\n */\nconst CONTEXT_TOKENS: Styles = {\n // The label color `current.primary` punches out of its `currentcolor` chip —\n // read by nothing else. That flavour fills with the color it INHERITS, so its\n // label has to contrast with an arbitrary color, and the page only manages\n // that while the inherited color sits away from the page. A container\n // whose own text color IS the page breaks it: a `Banner` labels itself\n // `#white`, and `#surface` is white in light mode, so label and chip collapse\n // to cr 1.00.\n //\n // Such a container sets this to a color that contrasts with its own fill —\n // usually its own surface — and the label, the rim and the icon slots all\n // follow. See `CURRENT_PRIMARY_STYLES` in `src/data/item-themes.ts`.\n '#current-fill': '#surface',\n};\n\n/**\n * Combined color token map: Glaze-generated palette + legacy aliases + context hooks.\n *\n * Memoized against the palette config version, so a runtime `setPaletteConfig()`\n * invalidates it while repeated reads stay free. Prefer {@link getColorTokens}\n * in new code.\n *\n * `#white` and `#black` are intentionally omitted — they are built-in\n * tasty named colors and resolve automatically.\n */\nlet colorTokensCache: Styles | null = null;\nlet cachedVersion = -1;\n\nexport function getColorTokens(): Styles {\n const version = getPaletteVersion();\n\n if (!colorTokensCache || cachedVersion !== version) {\n colorTokensCache = {\n ...getPaletteTokens(),\n ...LEGACY_ALIASES,\n ...CONTEXT_TOKENS,\n };\n cachedVersion = version;\n }\n return colorTokensCache;\n}\n\nexport const COLOR_TOKENS: Styles = lazyStyles(getColorTokens);\n\n/**\n * Tokens that live outside the palette but whose *values* reference a palette\n * color, so a region preview has to re-declare them. Kept by reference, exactly\n * as `<Root>` declares them — see {@link renderColorTokens}.\n *\n * Sourced from `./shadows` and the scrollbar block of `./base`; if either grows a\n * new colour-referencing token, add it here too.\n */\nconst COLOR_DEPENDENT_TOKENS: Styles = {\n ...SHADOW_TOKENS,\n '#scrollbar-thumb': BASE_TOKENS['#scrollbar-thumb'],\n '#scrollbar-outline': BASE_TOKENS['#scrollbar-outline'],\n '#scrollbar-bg': BASE_TOKENS['#scrollbar-bg'],\n '#scrollbar-corner': BASE_TOKENS['#scrollbar-corner'],\n};\n\n/**\n * Render every UI Kit color for one config and one schema, as flat literal\n * values ready to apply to a **region** via a tasty `tokens` prop.\n *\n * ```tsx\n * const preview = useMemo(\n * () => renderColorTokens({ hue: 210, schema: 'dark' }),\n * [],\n * );\n *\n * <Block tokens={preview} fill=\"#surface\" color=\"#surface-text\">\n * …renders in the previewed theme, inside a light page…\n * </Block>\n * ```\n *\n * Config fields merge over the *current* palette config, so\n * `renderColorTokens({ schema: 'dark' })` previews the active theme in dark\n * without restating it. Nothing is applied globally — the live palette is\n * untouched.\n *\n * The legacy aliases are included **by reference** (`'#dark': '#surface-text'`),\n * not resolved: tasty re-declares them on the region, so each `var()` resolves\n * against that region's own overridden value. Resolving them here would instead\n * freeze them to the outer theme's colors.\n *\n * The shadow tokens and the scrollbar colors come along for the same reason: their\n * values embed a palette color (`$card-shadow` → `#shadow-md`, `#scrollbar-thumb`\n * → `#text.5`). They are declared on `<Root>`, so CSS has already substituted the\n * outer theme's colors into them by the time a region inherits — re-declaring them\n * here is what lets them re-resolve.\n *\n * Not included: typography, spacing, sizes and layout. Nothing in their values\n * references a color, so a region inherits them from `<Root>` unchanged.\n *\n * The result is memoized for the last config rendered, but callers driving this\n * from state should still `useMemo` — a rebuild resolves the whole palette.\n */\nexport function renderColorTokens(options?: RenderPaletteOptions): Tokens {\n return {\n ...renderPaletteTokens(options),\n ...(LEGACY_ALIASES as Tokens),\n ...(CONTEXT_TOKENS as Tokens),\n ...(COLOR_DEPENDENT_TOKENS as Tokens),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,MAAM,iBAAyB;CAE7B,SAAS;CACT,SAAS;CACT,YAAY;CACZ,YAAY;CACZ,YAAY;CACZ,YAAY;CACZ,YAAY;CAQZ,eAAe;CAUf,uBAAuB;CAGvB,UAAU;CACV,WAAW;CACX,UAAU;CACV,YAAY;CACZ,UAAU;CAQV,SAAS;CAST,YAAY;CACZ,iBAAiB;CACjB,sBAAsB;CACtB,eAAe;CACf,iBAAiB;CACjB,kBAAkB;CAClB,wBAAwB;CACxB,qBAAqB;CAErB,WAAW;CACX,gBAAgB;CAChB,qBAAqB;CACrB,cAAc;CACd,gBAAgB;CAChB,iBAAiB;CACjB,oBAAoB;CAGpB,cAAc;CACd,cAAc;CACd,cAAc;CACd,cAAc;CAGd,WAAW;CACX,gBAAgB;CAChB,qBAAqB;CACrB,cAAc;CACd,gBAAgB;CAChB,iBAAiB;CACjB,uBAAuB;CACvB,oBAAoB;CAGpB,YAAY;CACZ,iBAAiB;CACjB,sBAAsB;CACtB,eAAe;CACf,iBAAiB;CACjB,kBAAkB;CAClB,wBAAwB;CACxB,qBAAqB;CAGrB,YAAY;CACZ,iBAAiB;CACjB,sBAAsB;CACtB,eAAe;CACf,iBAAiB;CACjB,kBAAkB;CAClB,wBAAwB;CACxB,qBAAqB;CAGrB,SAAS;CACT,cAAc;CACd,mBAAmB;CACnB,YAAY;CACZ,cAAc;CACd,eAAe;CACf,qBAAqB;CACrB,kBAAkB;CACnB;;;;;;;;;;;;;;;AAgBD,MAAM,iBAAyB,EAY7B,iBAAiB,YAClB;;;;;;;;;;;AAYD,IAAI,mBAAkC;AACtC,IAAI,gBAAgB;AAEpB,SAAgB,iBAAyB;CACvC,MAAM,UAAU,mBAAmB;AAEnC,KAAI,CAAC,oBAAoB,kBAAkB,SAAS;AAClD,qBAAmB;GACjB,GAAG,kBAAkB;GACrB,GAAG;GACH,GAAG;GACJ;AACD,kBAAgB;;AAElB,QAAO;;AAGT,MAAa,eAAuB,WAAW,eAAe;;;;;;;;;AAU9D,MAAM,yBAAiC;CACrC,GAAG;CACH,oBAAoB,YAAY;CAChC,sBAAsB,YAAY;CAClC,iBAAiB,YAAY;CAC7B,qBAAqB,YAAY;CAClC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCD,SAAgB,kBAAkB,SAAwC;AACxE,QAAO;EACL,GAAG,oBAAoB,QAAQ;EAC/B,GAAI;EACJ,GAAI;EACJ,GAAI;EACL"}
|
|
1
|
+
{"version":3,"file":"colors.js","names":[],"sources":["../../src/tokens/colors.ts"],"sourcesContent":["import { BASE_TOKENS } from './base';\nimport { lazyStyles } from './lazy-styles';\nimport { getPaletteTokens, renderPaletteTokens } from './palette';\nimport { getPaletteVersion } from './palette-config';\nimport { SHADOW_TOKENS } from './shadows';\n\nimport type { Styles, Tokens } from '@tenphi/tasty';\nimport type { RenderPaletteOptions } from './palette';\n\n/**\n * Color tokens with `#` prefix for tasty color definitions.\n *\n * The base values come from the Glaze-generated palette (`palette.ts`):\n * `#surface`, `#surface-text*`, `#border`, `#focus`, `#disabled`, `#pink`,\n * `#shadow-*`, `#overlay`, the unprefixed `#accent-*` family, and the per-theme\n * `#primary-*` / `#purple-*` / `#success-*` / `#danger-*` / `#warning-*` /\n * `#note-*` families.\n *\n * Each Glaze token is a state map (`{ '': '...', '@dark': '...', '@hc': '...' }`),\n * giving us light, dark, and high-contrast variants for free. The `@dark` /\n * `@hc` predefined states are wired up globally in `src/components/Root.tsx`.\n *\n * The aliases below preserve backward compatibility with every legacy\n * `#name` used across components, stories, and tests. Each alias resolves\n * to a current Glaze token via tasty's `#token` reference syntax.\n */\n\nconst LEGACY_ALIASES: Styles = {\n // ---- Neutral text scale (legacy `#dark*`) ----\n '#text': '#surface-text-soft',\n '#dark': '#surface-text',\n '#dark-01': '#surface-text',\n '#dark-02': '#surface-text-soft',\n '#dark-03': '#surface-text-soft-2',\n '#dark-04': '#placeholder',\n '#dark-05': '#border',\n\n // Fixed-mode counterpart to `#dark`. Resolves to the same L≈12 surface\n // but uses Glaze `mode: 'fixed'` so it does NOT invert in dark schemes.\n // Use this whenever the design intentionally pins a dark color regardless\n // of scheme. Points at `#special-surface` (`mode: 'fixed'`, L=12),\n // emitted by the standalone `specialTheme` in `palette.ts` — the canonical\n // source of fixed-mode color tokens for `special`-variant components.\n '#fixed-dark': '#special-surface',\n\n // Fixed-mode counterpart to `#primary-text`. `#primary-text` is anchored\n // to `surface` with `mode: 'auto'`, so it flips to a *light* purple in\n // dark schemes (correct on body content, which also inverts). When the\n // local fill is a fixed color instead (an always-white pill, etc.), the\n // adaptive text loses contrast (light purple on white) in dark mode.\n // Points at `#special-accent-text` (`mode: 'fixed'`, cr 6–8.5 vs fixed\n // white) — a dark purple readable on a white surface that stays put\n // across schemes.\n '#fixed-primary-text': '#special-accent-text',\n\n // ---- Misc neutral ----\n '#minor': '#surface-text-soft.65',\n '#shadow': '#shadow-md',\n '#light': '#surface-3',\n '#dark-bg': '#surface-2',\n '#clear': 'transparent',\n\n // Pink: independent hue, scheme-static (no Glaze adaptation). Kept as a raw\n // literal rather than folded into a theme — nothing in the palette emits this\n // hue as a standalone token, and it is a documented public alias (see\n // `Usage.docs.mdx`, `tasty.config.ts`, and the `pink` key in\n // `tasty-augment.d.ts`), so dropping it would silently break consumer styles\n // while the types still advertised it.\n '#pink': 'okhsl(5 100% 67%)',\n\n // ---- Disabled state aliases ----\n // `#disabled-surface` and `#disabled-surface-text` are emitted directly by\n // the Glaze palette (`palette.ts`) as scheme-symmetric, contrast-driven\n // tokens — no alias needed here. `#disabled` stays as a brand-tinted\n // backwards-compat anchor for the per-theme `#<theme>-disabled` aliases below.\n\n // ---- Primary / Purple legacy ----\n '#primary': '#primary-accent-surface',\n '#primary-text': '#primary-accent-text',\n '#primary-text-soft': '#primary-accent-text-soft',\n '#primary-bg': '#primary-surface',\n '#primary-icon': '#primary-accent-icon',\n '#primary-hover': '#primary-accent-surface-hover',\n '#primary-desaturated': '#primary-accent-surface-2',\n '#primary-disabled': '#disabled',\n\n '#purple': '#purple-accent-surface',\n '#purple-text': '#purple-accent-text',\n '#purple-text-soft': '#purple-accent-text-soft',\n '#purple-bg': '#purple-surface',\n '#purple-icon': '#purple-accent-icon',\n '#purple-hover': '#purple-accent-surface-hover',\n '#purple-disabled': '#disabled',\n\n // Purple scale (gradient/accent shades) — mapped to the accent-surface ramp.\n '#purple-01': '#purple-accent-surface',\n '#purple-02': '#purple-accent-surface-2',\n '#purple-03': '#purple-accent-surface-3',\n '#purple-04': '#purple-surface',\n\n // ---- Danger ----\n '#danger': '#danger-accent-surface',\n '#danger-text': '#danger-accent-text',\n '#danger-text-soft': '#danger-accent-text-soft',\n '#danger-bg': '#danger-surface',\n '#danger-icon': '#danger-accent-icon',\n '#danger-hover': '#danger-accent-surface-hover',\n '#danger-desaturated': '#danger-accent-surface-2',\n '#danger-disabled': '#disabled',\n\n // ---- Success ----\n '#success': '#success-accent-surface',\n '#success-text': '#success-accent-text',\n '#success-text-soft': '#success-accent-text-soft',\n '#success-bg': '#success-surface',\n '#success-icon': '#success-accent-icon',\n '#success-hover': '#success-accent-surface-hover',\n '#success-desaturated': '#success-accent-surface-2',\n '#success-disabled': '#disabled',\n\n // ---- Warning ----\n '#warning': '#warning-accent-surface',\n '#warning-text': '#warning-accent-text',\n '#warning-text-soft': '#warning-accent-text-soft',\n '#warning-bg': '#warning-surface',\n '#warning-icon': '#warning-accent-icon',\n '#warning-hover': '#warning-accent-surface-hover',\n '#warning-desaturated': '#warning-accent-surface-2',\n '#warning-disabled': '#disabled',\n\n // ---- Note ----\n '#note': '#note-accent-surface',\n '#note-text': '#note-accent-text',\n '#note-text-soft': '#note-accent-text-soft',\n '#note-bg': '#note-surface',\n '#note-icon': '#note-accent-icon',\n '#note-hover': '#note-accent-surface-hover',\n '#note-desaturated': '#note-accent-surface-2',\n '#note-disabled': '#disabled',\n};\n\n/**\n * Context hooks — tokens a CONTAINER sets to steer a component that cannot\n * resolve the color on its own.\n *\n * Declared with a default rather than read as `var(--x, fallback)` at every use\n * site, for two reasons: the default is stated once instead of repeated (and so\n * cannot drift), and a declared token gets its components companion for free —\n * which is the only way `#current-fill.5` can fade whatever the container\n * offered rather than the fallback.\n *\n * Included by reference in {@link renderColorTokens} alongside the legacy\n * aliases, so a region preview re-resolves the default against that region's own\n * `#surface` instead of freezing the outer theme's.\n */\nconst CONTEXT_TOKENS: Styles = {\n // The label color `current.primary` punches out of its `currentcolor` chip —\n // read by nothing else. That flavour fills with the color it INHERITS, so its\n // label has to contrast with an arbitrary color, and the page only manages\n // that while the inherited color sits away from the page. A container\n // whose own text color IS the page breaks it: a `Banner` labels itself\n // `#white`, and `#surface` is white in light mode, so label and chip collapse\n // to cr 1.00.\n //\n // Such a container sets this to a color that contrasts with its own fill —\n // usually its own surface — and the label, the rim and the icon slots all\n // follow. See `CURRENT_PRIMARY_STYLES` in `src/data/item-themes.ts`.\n '#current-fill': '#surface',\n};\n\n/**\n * Combined color token map: Glaze-generated palette + legacy aliases + context hooks.\n *\n * Memoized against the palette config version, so a runtime `setPaletteConfig()`\n * invalidates it while repeated reads stay free. Prefer {@link getColorTokens}\n * in new code.\n *\n * `#white` and `#black` are intentionally omitted — they are built-in\n * tasty named colors and resolve automatically.\n */\nlet colorTokensCache: Styles | null = null;\nlet cachedVersion = -1;\n\nexport function getColorTokens(): Styles {\n const version = getPaletteVersion();\n\n if (!colorTokensCache || cachedVersion !== version) {\n colorTokensCache = {\n ...getPaletteTokens(),\n ...LEGACY_ALIASES,\n ...CONTEXT_TOKENS,\n };\n cachedVersion = version;\n }\n return colorTokensCache;\n}\n\nexport const COLOR_TOKENS: Styles = lazyStyles(getColorTokens);\n\n/**\n * Tokens that live outside the palette but whose *values* reference a palette\n * color, so a region preview has to re-declare them. Kept by reference, exactly\n * as `<Root>` declares them — see {@link renderColorTokens}.\n *\n * Sourced from `./shadows` and the scrollbar block of `./base`; if either grows a\n * new colour-referencing token, add it here too.\n */\nconst COLOR_DEPENDENT_TOKENS: Styles = {\n ...SHADOW_TOKENS,\n '#scrollbar-thumb': BASE_TOKENS['#scrollbar-thumb'],\n '#scrollbar-outline': BASE_TOKENS['#scrollbar-outline'],\n '#scrollbar-bg': BASE_TOKENS['#scrollbar-bg'],\n '#scrollbar-corner': BASE_TOKENS['#scrollbar-corner'],\n};\n\n/**\n * Render every UI Kit color for one config and one scheme, as flat literal\n * values ready to apply to a **region** via a tasty `tokens` prop.\n *\n * ```tsx\n * const preview = useMemo(\n * () => renderColorTokens({ hue: 210, scheme: 'dark' }),\n * [],\n * );\n *\n * <Block tokens={preview} fill=\"#surface\" color=\"#surface-text\">\n * …renders in the previewed theme, inside a light page…\n * </Block>\n * ```\n *\n * Config fields merge over the *current* palette config, so\n * `renderColorTokens({ scheme: 'dark' })` previews the active theme in dark\n * without restating it. Nothing is applied globally — the live palette is\n * untouched.\n *\n * The legacy aliases are included **by reference** (`'#dark': '#surface-text'`),\n * not resolved: tasty re-declares them on the region, so each `var()` resolves\n * against that region's own overridden value. Resolving them here would instead\n * freeze them to the outer theme's colors.\n *\n * The shadow tokens and the scrollbar colors come along for the same reason: their\n * values embed a palette color (`$card-shadow` → `#shadow-md`, `#scrollbar-thumb`\n * → `#text.5`). They are declared on `<Root>`, so CSS has already substituted the\n * outer theme's colors into them by the time a region inherits — re-declaring them\n * here is what lets them re-resolve.\n *\n * Not included: typography, spacing, sizes and layout. Nothing in their values\n * references a color, so a region inherits them from `<Root>` unchanged.\n *\n * The result is memoized for the last config rendered, but callers driving this\n * from state should still `useMemo` — a rebuild resolves the whole palette.\n */\nexport function renderColorTokens(options?: RenderPaletteOptions): Tokens {\n return {\n ...renderPaletteTokens(options),\n ...(LEGACY_ALIASES as Tokens),\n ...(CONTEXT_TOKENS as Tokens),\n ...(COLOR_DEPENDENT_TOKENS as Tokens),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,MAAM,iBAAyB;CAE7B,SAAS;CACT,SAAS;CACT,YAAY;CACZ,YAAY;CACZ,YAAY;CACZ,YAAY;CACZ,YAAY;CAQZ,eAAe;CAUf,uBAAuB;CAGvB,UAAU;CACV,WAAW;CACX,UAAU;CACV,YAAY;CACZ,UAAU;CAQV,SAAS;CAST,YAAY;CACZ,iBAAiB;CACjB,sBAAsB;CACtB,eAAe;CACf,iBAAiB;CACjB,kBAAkB;CAClB,wBAAwB;CACxB,qBAAqB;CAErB,WAAW;CACX,gBAAgB;CAChB,qBAAqB;CACrB,cAAc;CACd,gBAAgB;CAChB,iBAAiB;CACjB,oBAAoB;CAGpB,cAAc;CACd,cAAc;CACd,cAAc;CACd,cAAc;CAGd,WAAW;CACX,gBAAgB;CAChB,qBAAqB;CACrB,cAAc;CACd,gBAAgB;CAChB,iBAAiB;CACjB,uBAAuB;CACvB,oBAAoB;CAGpB,YAAY;CACZ,iBAAiB;CACjB,sBAAsB;CACtB,eAAe;CACf,iBAAiB;CACjB,kBAAkB;CAClB,wBAAwB;CACxB,qBAAqB;CAGrB,YAAY;CACZ,iBAAiB;CACjB,sBAAsB;CACtB,eAAe;CACf,iBAAiB;CACjB,kBAAkB;CAClB,wBAAwB;CACxB,qBAAqB;CAGrB,SAAS;CACT,cAAc;CACd,mBAAmB;CACnB,YAAY;CACZ,cAAc;CACd,eAAe;CACf,qBAAqB;CACrB,kBAAkB;CACnB;;;;;;;;;;;;;;;AAgBD,MAAM,iBAAyB,EAY7B,iBAAiB,YAClB;;;;;;;;;;;AAYD,IAAI,mBAAkC;AACtC,IAAI,gBAAgB;AAEpB,SAAgB,iBAAyB;CACvC,MAAM,UAAU,mBAAmB;AAEnC,KAAI,CAAC,oBAAoB,kBAAkB,SAAS;AAClD,qBAAmB;GACjB,GAAG,kBAAkB;GACrB,GAAG;GACH,GAAG;GACJ;AACD,kBAAgB;;AAElB,QAAO;;AAGT,MAAa,eAAuB,WAAW,eAAe;;;;;;;;;AAU9D,MAAM,yBAAiC;CACrC,GAAG;CACH,oBAAoB,YAAY;CAChC,sBAAsB,YAAY;CAClC,iBAAiB,YAAY;CAC7B,qBAAqB,YAAY;CAClC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCD,SAAgB,kBAAkB,SAAwC;AACxE,QAAO;EACL,GAAG,oBAAoB,QAAQ;EAC/B,GAAI;EACJ,GAAI;EACJ,GAAI;EACL"}
|
package/dist/tokens/layout.js
CHANGED
|
@@ -108,7 +108,7 @@ interface PaletteConfig {
|
|
|
108
108
|
* and no further.
|
|
109
109
|
*
|
|
110
110
|
* Those floors are APCA, not WCAG, and the difference is deliberate: one WCAG ratio
|
|
111
|
-
* means two very different things by
|
|
111
|
+
* means two very different things by scheme (3:1 measures Lc 56 in light but only
|
|
112
112
|
* Lc 23 in dark), which crushed light brands while letting dark ones through. A
|
|
113
113
|
* consequence worth stating plainly — **the emitted fill can sit below WCAG 3:1**.
|
|
114
114
|
* `#0EA5E9` renders at 2.77:1 against a white page and is correct at that value; the
|
|
@@ -1 +1 @@
|
|
|
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 schema (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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"}
|
|
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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"}
|
package/dist/tokens/palette.d.ts
CHANGED
|
@@ -4,11 +4,11 @@ import { Styles, Tokens } from "@tenphi/tasty";
|
|
|
4
4
|
import { ColorMap, GlazePalette, GlazeTheme } from "@tenphi/glaze";
|
|
5
5
|
|
|
6
6
|
//#region src/tokens/palette.d.ts
|
|
7
|
-
/** Which resolved
|
|
7
|
+
/** Which resolved scheme variant {@link renderPaletteTokens} should return. */
|
|
8
8
|
interface RenderPaletteOptions extends PaletteConfig {
|
|
9
|
-
/** Color
|
|
10
|
-
|
|
11
|
-
/** Resolve the high-contrast variant of that
|
|
9
|
+
/** Color scheme to resolve. Default: `'light'`. */
|
|
10
|
+
scheme?: 'light' | 'dark';
|
|
11
|
+
/** Resolve the high-contrast variant of that scheme. Default: `false`. */
|
|
12
12
|
highContrast?: boolean;
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
@@ -32,7 +32,7 @@ declare function getPalette(): GlazePalette;
|
|
|
32
32
|
*/
|
|
33
33
|
declare function getCodeTheme(): GlazeTheme;
|
|
34
34
|
/**
|
|
35
|
-
* Resolve one
|
|
35
|
+
* Resolve one scheme variant of a palette to flat, literal color values.
|
|
36
36
|
*
|
|
37
37
|
* Unlike {@link getPaletteTokens}, which emits state maps (`@dark` / `@hc`) for
|
|
38
38
|
* the whole document, this collapses the palette to the single variant you ask
|