@adea-ai/ui 0.128.2 → 0.128.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/dist/components/theme/appearance-panel.js +1 -1
  2. package/dist/components/theme/index.js +1 -1
  3. package/dist/components/theme/lazy.d.ts +15 -0
  4. package/dist/components/theme/lazy.d.ts.map +1 -0
  5. package/dist/components/theme/lazy.js +9 -0
  6. package/dist/components/theme/theme-catalogue-source.d.ts +15 -0
  7. package/dist/components/theme/theme-catalogue-source.d.ts.map +1 -0
  8. package/dist/components/theme/theme-catalogue-source.js +51 -0
  9. package/dist/components/theme/theme-catalogue-source.js.map +1 -0
  10. package/dist/components/theme/theme-document-core.d.ts +123 -0
  11. package/dist/components/theme/theme-document-core.d.ts.map +1 -0
  12. package/dist/components/theme/{theme-document.js → theme-document-core.js} +12 -12
  13. package/dist/components/theme/theme-document-core.js.map +1 -0
  14. package/dist/components/theme/theme-document.d.ts +8 -107
  15. package/dist/components/theme/theme-document.d.ts.map +1 -1
  16. package/dist/components/theme/theme-persistence.d.ts +1 -1
  17. package/dist/components/theme/theme-persistence.d.ts.map +1 -1
  18. package/dist/components/theme/theme-persistence.js +9 -6
  19. package/dist/components/theme/theme-persistence.js.map +1 -1
  20. package/dist/components/theme/theme-provider-core.d.ts +19 -0
  21. package/dist/components/theme/theme-provider-core.d.ts.map +1 -0
  22. package/dist/components/theme/theme-provider-core.js +28 -0
  23. package/dist/components/theme/theme-provider-core.js.map +1 -0
  24. package/dist/components/theme/theme-provider-lazy.d.ts +28 -0
  25. package/dist/components/theme/theme-provider-lazy.d.ts.map +1 -0
  26. package/dist/components/theme/theme-provider-lazy.js +36 -0
  27. package/dist/components/theme/theme-provider-lazy.js.map +1 -0
  28. package/dist/components/theme/theme-provider-value.d.ts +175 -0
  29. package/dist/components/theme/theme-provider-value.d.ts.map +1 -0
  30. package/dist/components/theme/theme-provider-value.js +200 -0
  31. package/dist/components/theme/theme-provider-value.js.map +1 -0
  32. package/dist/components/theme/theme-provider.d.ts +11 -136
  33. package/dist/components/theme/theme-provider.d.ts.map +1 -1
  34. package/dist/components/theme/theme-provider.js +27 -149
  35. package/dist/components/theme/theme-provider.js.map +1 -1
  36. package/dist/components/theme/theme-script.js +4 -3
  37. package/dist/components/theme/theme-script.js.map +1 -1
  38. package/dist/components/theme/theme-state-core.d.ts +248 -0
  39. package/dist/components/theme/theme-state-core.d.ts.map +1 -0
  40. package/dist/components/theme/{theme-state.js → theme-state-core.js} +8 -7
  41. package/dist/components/theme/theme-state-core.js.map +1 -0
  42. package/dist/components/theme/theme-state.d.ts +8 -214
  43. package/dist/components/theme/theme-state.d.ts.map +1 -1
  44. package/dist/components/theme/theme-toggle.js.map +1 -1
  45. package/dist/index.js +7 -4
  46. package/dist/lib/accent-presets.d.ts +26 -0
  47. package/dist/lib/accent-presets.d.ts.map +1 -0
  48. package/dist/lib/accent-presets.js +11 -0
  49. package/dist/lib/accent-presets.js.map +1 -0
  50. package/dist/lib/theme-catalogue.d.ts +3 -0
  51. package/dist/lib/theme-catalogue.d.ts.map +1 -0
  52. package/dist/lib/theme-catalogue.js +22 -0
  53. package/dist/lib/theme-catalogue.js.map +1 -0
  54. package/dist/lib/theme-css.d.ts +43 -0
  55. package/dist/lib/theme-css.d.ts.map +1 -0
  56. package/dist/lib/theme-css.js +167 -0
  57. package/dist/lib/theme-css.js.map +1 -0
  58. package/dist/lib/theme-defaults.d.ts +46 -0
  59. package/dist/lib/theme-defaults.d.ts.map +1 -0
  60. package/dist/lib/theme-defaults.generated.d.ts +13 -0
  61. package/dist/lib/theme-defaults.generated.d.ts.map +1 -0
  62. package/dist/lib/theme-defaults.generated.js +318 -0
  63. package/dist/lib/theme-defaults.generated.js.map +1 -0
  64. package/dist/lib/theme-defaults.js +47 -0
  65. package/dist/lib/theme-defaults.js.map +1 -0
  66. package/dist/lib/theme-engine-light.d.ts +3 -0
  67. package/dist/lib/theme-engine-light.d.ts.map +1 -0
  68. package/dist/lib/theme-engine-light.js +75 -0
  69. package/dist/lib/theme-engine-light.js.map +1 -0
  70. package/dist/lib/theme-engine.d.ts +69 -0
  71. package/dist/lib/theme-engine.d.ts.map +1 -0
  72. package/dist/lib/theme-engine.js +33 -0
  73. package/dist/lib/theme-engine.js.map +1 -0
  74. package/dist/lib/themes.d.ts +14 -48
  75. package/dist/lib/themes.d.ts.map +1 -1
  76. package/dist/lib/themes.js +21 -163
  77. package/dist/lib/themes.js.map +1 -1
  78. package/dist/lib/tokens.d.ts +1 -25
  79. package/dist/lib/tokens.d.ts.map +1 -1
  80. package/dist/lib/tokens.js +1 -24
  81. package/dist/lib/tokens.js.map +1 -1
  82. package/dist/r/lib.json +35 -0
  83. package/dist/r/registry.json +70 -0
  84. package/dist/r/src/components/theme/lazy.ts +35 -0
  85. package/dist/r/src/components/theme/theme-catalogue-source.ts +59 -0
  86. package/dist/r/src/components/theme/theme-document-core.ts +262 -0
  87. package/dist/r/src/components/theme/theme-document.ts +23 -240
  88. package/dist/r/src/components/theme/theme-persistence.ts +14 -6
  89. package/dist/r/src/components/theme/theme-provider-core.tsx +40 -0
  90. package/dist/r/src/components/theme/theme-provider-lazy.tsx +34 -0
  91. package/dist/r/src/components/theme/theme-provider-value.ts +445 -0
  92. package/dist/r/src/components/theme/theme-provider.tsx +23 -346
  93. package/dist/r/src/components/theme/theme-script.ts +3 -3
  94. package/dist/r/src/components/theme/theme-state-core.ts +364 -0
  95. package/dist/r/src/components/theme/theme-state.ts +10 -329
  96. package/dist/r/src/components/theme/theme-toggle.tsx +1 -1
  97. package/dist/r/src/lib/accent-presets.ts +43 -0
  98. package/dist/r/src/lib/theme-catalogue.ts +26 -0
  99. package/dist/r/src/lib/theme-css.ts +201 -0
  100. package/dist/r/src/lib/theme-defaults.generated.ts +319 -0
  101. package/dist/r/src/lib/theme-defaults.ts +79 -0
  102. package/dist/r/src/lib/theme-engine-light.ts +117 -0
  103. package/dist/r/src/lib/theme-engine.ts +111 -0
  104. package/dist/r/src/lib/themes.ts +27 -191
  105. package/dist/r/src/lib/tokens.ts +1 -35
  106. package/dist/r/theming.json +35 -0
  107. package/package.json +10 -2
  108. package/registry.json +70 -0
  109. package/src/components/theme/lazy.ts +35 -0
  110. package/src/components/theme/theme-catalogue-source.ts +59 -0
  111. package/src/components/theme/theme-document-core.ts +262 -0
  112. package/src/components/theme/theme-document.ts +23 -240
  113. package/src/components/theme/theme-persistence.ts +14 -6
  114. package/src/components/theme/theme-provider-core.tsx +40 -0
  115. package/src/components/theme/theme-provider-lazy.tsx +34 -0
  116. package/src/components/theme/theme-provider-value.ts +445 -0
  117. package/src/components/theme/theme-provider.tsx +23 -346
  118. package/src/components/theme/theme-script.ts +3 -3
  119. package/src/components/theme/theme-state-core.ts +364 -0
  120. package/src/components/theme/theme-state.ts +10 -329
  121. package/src/components/theme/theme-toggle.tsx +1 -1
  122. package/src/lib/accent-presets.ts +43 -0
  123. package/src/lib/theme-catalogue.ts +26 -0
  124. package/src/lib/theme-css.ts +201 -0
  125. package/src/lib/theme-defaults.generated.ts +319 -0
  126. package/src/lib/theme-defaults.ts +79 -0
  127. package/src/lib/theme-engine-light.ts +117 -0
  128. package/src/lib/theme-engine.ts +111 -0
  129. package/src/lib/themes.ts +27 -191
  130. package/src/lib/tokens.ts +1 -35
  131. package/dist/components/theme/theme-document.js.map +0 -1
  132. package/dist/components/theme/theme-state.js.map +0 -1
package/src/lib/themes.ts CHANGED
@@ -44,7 +44,6 @@ import {
44
44
  shadcnDestructiveProjection,
45
45
  statusForeground,
46
46
  tint,
47
- primaryHover,
48
47
  primarySubtleCss,
49
48
  syntaxRoles,
50
49
  themes as catalogue,
@@ -57,11 +56,14 @@ import {
57
56
  type ContrastFinding,
58
57
  } from '@adea-ai/themes'
59
58
  import { shadcnVariables } from '@adea-ai/themes/adapters/shadcn'
60
- import { oklchToHex, parseColor } from '@adea-ai/themes/oklch'
61
59
  import { accentOverrideVariables, resolveAccentRoles, type AccentRoles } from './accent-roles'
62
60
  import { isThemeAccentId } from './theme-accents'
61
+ import { themeCssVariablesWith } from './theme-css'
62
+ import { defaultDarkThemeId, defaultLightThemeId } from './theme-defaults'
63
63
 
64
64
  export { isThemeAccentId } from './theme-accents'
65
+ export { terminalCssVariables, tokensAsHex } from './theme-css'
66
+ export { defaultDarkThemeId, defaultLightThemeId, resolveVariant } from './theme-defaults'
65
67
 
66
68
  export type ThemeAppearance = 'light' | 'dark'
67
69
 
@@ -376,46 +378,22 @@ export function primarySubtleFor(theme: Pick<ThemeVariant, 'id' | 'appearance'>)
376
378
  return record ? primarySubtleCss(record) : primarySubtleCss(theme.appearance)
377
379
  }
378
380
 
379
- /** The catalogue, in catalogue order. */
380
- export const builtinThemes: readonly ThemeVariant[] = Object.freeze(catalogue.map(lazyVariant))
381
+ /**
382
+ * The catalogue, in catalogue order.
383
+ *
384
+ * The `@__PURE__` marks let a bundler drop the whole table, and with it the catalogue,
385
+ * from a consumer that imports only this module's catalogue-free helpers
386
+ * (`tokensAsHex`, `isThemeAccentId`, `resolveVariant`…) and never reads it.
387
+ */
388
+ export const builtinThemes: readonly ThemeVariant[] = /* @__PURE__ */ Object.freeze(
389
+ /* @__PURE__ */ catalogue.map(lazyVariant)
390
+ )
381
391
 
382
392
  export function themeById(id: string): ThemeVariant | undefined {
383
393
  if (!hasTheme(id)) return undefined
384
394
  return builtinThemes.find((theme) => theme.id === id)
385
395
  }
386
396
 
387
- /**
388
- * The variant a stored id means, or the default of the same appearance.
389
- *
390
- * An id the registry does not hold — an imported theme that was deleted, a
391
- * catalogue entry that was renamed — must still paint *something of the appearance
392
- * being shown*. Falling back to whichever variant happens to be first in the
393
- * registry painted a dark palette under a light `color-scheme` for a light id, so
394
- * the fallback is the appearance's own default, then any variant of that
395
- * appearance, then the registry's first entry only when it holds nothing else.
396
- *
397
- * `registry` is the builtin catalogue plus whatever extras the host supplies; the
398
- * stored id is never rewritten, only resolved, so a host that keeps an unknown id
399
- * keeps resolving it if the theme comes back.
400
- *
401
- * The fallback order is translated from Zeron's `ThemeRegistry::resolve` (MIT,
402
- * Copyright (c) 2026 Wing); see NOTICE.
403
- */
404
- export function resolveVariant(
405
- registry: readonly ThemeVariant[],
406
- id: string,
407
- appearance: ThemeAppearance
408
- ): ThemeVariant {
409
- const found = registry.find((variant) => variant.id === id)
410
- if (found) return found
411
- const defaultId = appearance === 'dark' ? defaultDarkThemeId : defaultLightThemeId
412
- return (
413
- registry.find((variant) => variant.id === defaultId) ??
414
- registry.find((variant) => variant.appearance === appearance) ??
415
- registry[0]!
416
- )
417
- }
418
-
419
397
  /** The variants of one appearance, which is what a picker for that mode shows. */
420
398
  export function themesForAppearance(appearance: ThemeAppearance): readonly ThemeVariant[] {
421
399
  const ids = new Set(themesByAppearance(appearance).map((theme) => theme.id))
@@ -708,166 +686,27 @@ export function editorPaletteFor(
708
686
  /**
709
687
  * The roles a variant sets, as CSS custom properties.
710
688
  *
711
- * The one place that knows the mapping from a theme's roles to this system's variable
712
- * names. A provider applies this; nothing else repeats it. The `--terminal-*` and
713
- * `--editor-*` ramps are presentations of the catalogue's ANSI and syntax data rather
714
- * than separate roles, which is why they are built here and not in the catalogue.
715
- *
716
- * `options.editor` picks the `--editor-*` projection ({@link EditorRolesMode}).
717
- * Absent it, the result is what it has always been; the generated `theme-variants.css`
718
- * and the design-system artifact are built without it.
689
+ * The mapping itself is `themeCssVariablesWith` in `theme-css.ts`; this binds it to
690
+ * the catalogue, which measures a theme's `--primary-subtle` strength
691
+ * ({@link primarySubtleFor}) and projects its floor editor ramp
692
+ * ({@link editorPaletteFor}). Absent `options`, the result is what it has always
693
+ * been; the generated `theme-variants.css` and the design-system artifact are built
694
+ * without it.
719
695
  */
720
696
  export function themeCssVariables(
721
697
  theme: ThemeVariant,
722
698
  options: ThemeCssVariableOptions = {}
723
699
  ): Record<string, string> {
724
- const c = theme.colors
725
- const t = theme.terminal
726
- const e = editorPaletteFor(theme, options.editor)
727
- const derived = {
728
- '--background': c.background,
729
- '--foreground': c.foreground,
730
- '--card': c.card,
731
- '--card-foreground': c.cardForeground,
732
- '--popover': c.popover,
733
- '--popover-foreground': c.popoverForeground,
734
- '--primary': c.primary,
735
- '--primary-foreground': c.primaryForeground,
736
- /*
737
- * The two tokens derived from the primary, and they are here rather than left to
738
- * `theme.css` because they must follow `--primary` — which this function writes
739
- * for *any* theme in the catalogue, not just the two defaults `theme.css` carries.
740
- *
741
- * `--primary-hover` is a resolved colour, not a `color-mix()`: a uniform hover
742
- * step cannot be expressed as a mix (see `primaryHover`). Left out, a theme switch
743
- * would keep the previous theme's hover on the new theme's primary.
744
- *
745
- * `--primary-subtle` is a mix over `--primary`, so it follows an accent on its
746
- * own, but its *strength* is per theme: `primarySubtleCss(record)` lowers the
747
- * appearance ceiling only as far as this theme needs for body text to clear 4.5:1
748
- * on the tint, over every surface, for every accent it offers. It has to be
749
- * written per theme for that reason — the appearance-wide value is not legible on
750
- * every palette.
751
- */
752
- '--primary-hover': primaryHover(c.primary, theme.appearance),
753
- '--primary-subtle': primarySubtleFor(theme),
754
- '--secondary': c.secondary,
755
- '--secondary-foreground': c.secondaryForeground,
756
- '--muted': c.muted,
757
- '--muted-foreground': c.mutedForeground,
758
- '--accent': c.accent,
759
- '--accent-foreground': c.accentForeground,
760
- '--destructive': c.destructive,
761
- '--destructive-foreground': c.destructiveForeground,
762
- '--destructive-subtle': c.destructiveSubtle,
763
- '--destructive-action': c.destructiveAction,
764
- '--destructive-action-foreground': c.destructiveActionForeground,
765
- '--success': c.success,
766
- '--success-foreground': c.successForeground,
767
- '--success-subtle': c.successSubtle,
768
- '--warning': c.warning,
769
- '--warning-foreground': c.warningForeground,
770
- '--warning-subtle': c.warningSubtle,
771
- '--info': c.info,
772
- '--info-foreground': c.infoForeground,
773
- '--info-subtle': c.infoSubtle,
774
- '--border': c.border,
775
- '--input': c.input,
776
- '--ring': c.ring,
777
- '--sidebar': c.sidebar,
778
- '--sidebar-foreground': c.sidebarForeground,
779
- '--sidebar-accent': c.sidebarAccent,
780
- '--sidebar-border': c.sidebarBorder,
781
- '--sidebar-primary': c.primary,
782
- '--sidebar-primary-foreground': c.primaryForeground,
783
- '--sidebar-ring': c.ring,
784
- '--sidebar-muted-foreground': c.sidebarMutedForeground,
785
- '--surface-sunken': c.muted,
786
- '--surface-hover': c.accent,
787
- '--surface-active': c.border,
788
- '--chrome': c.background,
789
- '--terminal-background': t.background,
790
- '--terminal-foreground': t.foreground,
791
- '--terminal-cursor': t.cursor,
792
- '--terminal-selection': t.selection,
793
- '--terminal-ansi-black': t.black,
794
- '--terminal-ansi-red': t.red,
795
- '--terminal-ansi-green': t.green,
796
- '--terminal-ansi-yellow': t.yellow,
797
- '--terminal-ansi-blue': t.blue,
798
- '--terminal-ansi-magenta': t.magenta,
799
- '--terminal-ansi-cyan': t.cyan,
800
- '--terminal-ansi-white': t.white,
801
- '--terminal-ansi-bright-black': t.brightBlack,
802
- '--terminal-ansi-bright-red': t.brightRed,
803
- '--terminal-ansi-bright-green': t.brightGreen,
804
- '--terminal-ansi-bright-yellow': t.brightYellow,
805
- '--terminal-ansi-bright-blue': t.brightBlue,
806
- '--terminal-ansi-bright-magenta': t.brightMagenta,
807
- '--terminal-ansi-bright-cyan': t.brightCyan,
808
- '--terminal-ansi-bright-white': t.brightWhite,
809
- '--editor-keyword': e.keyword,
810
- '--editor-string': e.string,
811
- '--editor-number': e.number,
812
- '--editor-comment': e.comment,
813
- '--editor-function': e.function,
814
- '--editor-variable': e.variable,
815
- '--editor-type': e.type,
816
- '--editor-tag': e.tag,
817
- '--editor-attribute': e.attribute,
818
- '--editor-operator': e.operator,
819
- '--editor-heading': e.heading,
820
- '--editor-link': e.link,
821
- '--editor-diff-add': e.diffAdd,
822
- '--editor-diff-delete': e.diffDelete,
823
- '--editor-diff-hunk': e.diffHunk,
824
- '--editor-search-match': e.searchMatch,
825
- '--chart-1': theme.chart[0] ?? c.primary,
826
- '--chart-2': theme.chart[1] ?? c.info,
827
- '--chart-3': theme.chart[2] ?? c.success,
828
- '--chart-4': theme.chart[3] ?? c.warning,
829
- '--chart-5': theme.chart[4] ?? c.destructive,
830
- '--chart-6': theme.chart[5] ?? c.mutedForeground,
831
- }
832
- return theme.tokens ? { ...derived, ...theme.tokens } : derived
833
- }
834
-
835
- /**
836
- * The `--terminal-*` properties of a variant: the palette a terminal paints.
837
- *
838
- * What a pinned terminal theme writes over the interface variant's own. Taken from
839
- * {@link themeCssVariables} rather than rebuilt, so the names, and a custom
840
- * variant's verbatim `tokens`, can never drift from what the interface variant
841
- * would have written for the same palette.
842
- */
843
- export function terminalCssVariables(theme: ThemeVariant): Record<string, string> {
844
- return Object.fromEntries(
845
- Object.entries(themeCssVariables(theme)).filter(([name]) => name.startsWith('--terminal-'))
700
+ return themeCssVariablesWith(
701
+ {
702
+ primarySubtle: primarySubtleFor,
703
+ editor: (variant, mode) => editorPaletteFor(variant, mode),
704
+ },
705
+ theme,
706
+ options
846
707
  )
847
708
  }
848
709
 
849
- /**
850
- * Token values with every `oklch()` colour written as `#rrggbb`.
851
- *
852
- * For a host whose stylesheet, tests or native shell speak hex: the catalogue's
853
- * roles are `oklch()` strings, and this is the one conversion, gamut-mapped by the
854
- * catalogue's own `oklchToHex`. A value that is already hex, carries alpha
855
- * (`rgba()`), or is not a plain colour (`color-mix()`, `var()`) is left byte for
856
- * byte, because converting would either lose the alpha or be a guess. Suitable as
857
- * `ThemeProvider`'s `transformTokens`.
858
- */
859
- export function tokensAsHex(tokens: Readonly<Record<string, string>>): Record<string, string> {
860
- const converted: Record<string, string> = {}
861
- for (const [name, value] of Object.entries(tokens)) {
862
- const trimmed = value.trim()
863
- // `parseOklch` discards alpha, so a translucent oklch() stays as written.
864
- const opaque = trimmed.startsWith('oklch(') && !trimmed.includes('/')
865
- const parsed = opaque ? parseColor(trimmed) : undefined
866
- converted[name] = parsed ? oklchToHex(parsed) : value
867
- }
868
- return converted
869
- }
870
-
871
710
  /* ---------------------------------------------------------------------------
872
711
  * Validation
873
712
  *
@@ -914,8 +753,5 @@ export function validateThemeRegistry(
914
753
  return [...orphaned, ...validateCatalogue(records).map(toFinding)]
915
754
  }
916
755
 
917
- export const defaultLightThemeId = 'adea-light'
918
- export const defaultDarkThemeId = 'adea-dark'
919
-
920
756
  /** Re-exported so a consumer measuring a colour does not need a second import. */
921
757
  export { contrastRatio } from '@adea-ai/themes'
package/src/lib/tokens.ts CHANGED
@@ -17,7 +17,6 @@
17
17
  * token exists" a property the build enforces rather than a promise.
18
18
  */
19
19
 
20
- import { ACCENTS } from '@adea-ai/themes'
21
20
  export { fontOptions } from './font-catalog'
22
21
  export type { FontOption } from './font-catalog'
23
22
 
@@ -705,40 +704,7 @@ export const zIndexTokens: TokenDefinition[] = [
705
704
  },
706
705
  ]
707
706
 
708
- /**
709
- * The accent axis.
710
- *
711
- * adea's accent presets, as a selection rather than a constant. `theme` is the
712
- * default and means "the variant's own primary" — monochrome in adea's palette —
713
- * and each named preset overrides the interactive roles through the
714
- * `[data-accent]` blocks in `theme.css`.
715
- *
716
- * These are the same six presets `accentPresets` exports in adea's own
717
- * `appearance.ts`, and the values match. A consumer's appearance picker and the
718
- * workshop's theme toolbar read this one list, so the two cannot disagree about
719
- * what a preset is called or what colour it is.
720
- */
721
- export type AccentPreset = {
722
- /** The `data-accent` value. `theme` is the variant's own primary. */
723
- id: string
724
- label: string
725
- /** What the preset is for, in the gallery and in a picker. */
726
- description: string
727
- /** The accent as it appears in the light theme. Absent for `theme`. */
728
- light?: string
729
- /** The accent as it appears in the dark theme. Absent for `theme`. */
730
- dark?: string
731
- }
732
-
733
- export const accentPresets: readonly AccentPreset[] = Object.freeze([
734
- {
735
- id: 'theme',
736
- label: 'Theme',
737
- description:
738
- "The variant's own primary. Neutral in adea's palette, so the interface stays monochrome.",
739
- },
740
- ...ACCENTS,
741
- ])
707
+ export { accentPresets, type AccentPreset } from './accent-presets'
742
708
 
743
709
  /** Every token, in gallery order. */
744
710
  export const designTokens = {
@@ -1 +0,0 @@
1
- {"version":3,"file":"theme-document.js","names":[],"sources":["../../../src/components/theme/theme-document.ts"],"sourcesContent":["import {\n applyAppearanceFontSettings,\n normalizeAppearanceEditorFontSettings,\n} from '#lib/appearance-font-settings'\nimport {\n accentVariables,\n isCatalogueVariant,\n terminalCssVariables,\n themeCssVariables,\n type EditorRolesMode,\n type ThemeAppearance,\n type ThemeVariant,\n} from '#lib/themes'\nimport { isCustomAccent } from '#lib/accent-roles'\nimport { surfaceAlpha, type SurfaceState } from '#lib/theme-surface'\nimport { resolvedDensity, type ThemeSelection } from './theme-state'\n\n/**\n * The document half of the theme provider: the single writer of `<html>`.\n *\n * Written against the few members of an element it uses, so the rules can be tested\n * on a stand-in and the provider's effect stays a one-line call.\n */\nexport type ThemeRoot = Pick<\n HTMLElement,\n 'classList' | 'dataset' | 'style' | 'removeAttribute' | 'setAttribute'\n>\n\nexport type ThemeDocumentState = {\n /** What the document shows: the committed selection, or a preview laid over it. */\n selection: ThemeSelection\n appearance: ThemeAppearance\n variant: ThemeVariant\n /**\n * The variant whose terminal palette is pinned over the interface variant's.\n * Absent, the terminal follows the interface theme.\n */\n terminalVariant?: ThemeVariant\n /**\n * The resolved surface axis. Absent, the axis is off: no attribute and no\n * `--surface-alpha` is written, and any this writer wrote before is removed.\n */\n surface?: SurfaceState\n}\n\nexport type ThemeDocumentOptions = {\n /**\n * Rewrites the custom properties just before they are written: the variant's\n * roles with the accent laid over them. It sees every property of the pass and\n * must return the full map; a name it drops is not written.\n */\n transformTokens?: (tokens: Record<string, string>) => Record<string, string>\n /**\n * The contrast an accent is held to against the variant's background. Absent, a\n * preset is painted verbatim (the catalogue measured it for its own themes) and\n * only a custom `#rrggbb` is held to the default floor of 3:1.\n */\n accentContrastFloor?: number\n /**\n * Where a catalogue variant's palette comes from. `inline` (the default) writes\n * every role onto the root. `stylesheet` writes none for a catalogue variant,\n * because the host loads a stylesheet that carries a `[data-theme]` block for it;\n * a variant the catalogue does not hold (a host's imported theme) is still written\n * inline, since no stylesheet can know it. The accent, the terminal pin, the\n * surface alpha and the fonts are written the same in both.\n *\n * Switching from an imported variant back to a catalogue one removes what the\n * imported one wrote, so nothing inline outlives it to beat the stylesheet.\n */\n palette?: 'inline' | 'stylesheet'\n /**\n * The `--editor-*` projection. `syntax` (the default) is the catalogue's quiet\n * syntax roles, which is what `theme-variants.css` carries; `floor` holds every\n * editor role to 4.5:1 on the canvas, as hex.\n *\n * With `palette: 'stylesheet'`, `floor` is the one palette write a catalogue\n * variant still gets: the stylesheet is generated for the default projection, so\n * the sixteen `--editor-*` properties are written inline over it. The rest of the\n * palette stays in the stylesheet.\n */\n editorRoles?: EditorRolesMode\n}\n\n/**\n * What a catalogue variant still writes inline when its palette is the stylesheet's:\n * nothing, unless the editor projection is not the one the stylesheet was generated\n * for.\n */\nfunction stylesheetOverrides(\n variant: ThemeVariant,\n editorRoles: EditorRolesMode | undefined\n): Record<string, string> {\n if (editorRoles !== 'floor') return {}\n return Object.fromEntries(\n Object.entries(themeCssVariables(variant, { editor: 'floor' })).filter(([name]) =>\n name.startsWith('--editor-')\n )\n )\n}\n\n/**\n * The custom properties a state paints: the variant's roles, with the accent\n * selection laid over them, the pinned terminal palette over that, the surface\n * alpha, then `transformTokens`.\n *\n * The accent is applied *here* rather than left to the `[data-accent]` blocks in\n * `theme.css`, because the properties are written inline and an inline declaration\n * outranks every stylesheet selector however specific it is. The blocks still exist\n * and are still correct — they are what styles the window before this runs — but\n * they cannot win from here, so leaving the accent to them meant the attribute was\n * set, the rules were present, and nothing changed on screen.\n */\nexport function themeDocumentTokens(\n state: ThemeDocumentState,\n options: ThemeDocumentOptions = {}\n): { tokens: Record<string, string>; accented: boolean } {\n const fromStylesheet = options.palette === 'stylesheet' && isCatalogueVariant(state.variant)\n const properties = fromStylesheet\n ? stylesheetOverrides(state.variant, options.editorRoles)\n : { ...themeCssVariables(state.variant, { editor: options.editorRoles }) }\n const accent = accentVariables(state.selection.accent, state.appearance, state.selection, {\n background: state.variant.colors.background,\n floor: options.accentContrastFloor,\n variantId: state.variant.id,\n })\n if (accent) Object.assign(properties, accent)\n // Written after the variant and the accent, so it wins over the interface\n // variant's own terminal ramp (and over the stylesheet's, which it outranks as an\n // inline declaration). Dropping the pin needs no removal of its own: the ramp is\n // then rewritten by the variant, or swept back to the stylesheet.\n if (state.terminalVariant) Object.assign(properties, terminalCssVariables(state.terminalVariant))\n if (state.surface) properties['--surface-alpha'] = surfaceAlpha[state.surface.surface]\n return {\n tokens: options.transformTokens ? options.transformTokens(properties) : properties,\n accented: accent !== undefined,\n }\n}\n\n/**\n * Write a state onto a root and report which custom properties it owns.\n *\n * Pass the previous call's return as `previous` and any palette property that pass\n * wrote and this one does not is removed. For the catalogue the two sets are the\n * same, so nothing is ever removed; it matters for a host variant that carries\n * `tokens` the catalogue does not, which would otherwise stay inline, beating the\n * stylesheet, after the user switched away from it.\n */\nexport function applyThemeToRoot(\n root: ThemeRoot,\n state: ThemeDocumentState,\n options: ThemeDocumentOptions = {},\n previous?: ReadonlySet<string>\n): ReadonlySet<string> {\n const { tokens, accented } = themeDocumentTokens(state, options)\n\n for (const [name, value] of Object.entries(tokens)) {\n root.style.setProperty(name, value)\n }\n if (previous) {\n for (const name of previous) {\n if (!(name in tokens)) root.style.removeProperty(name)\n }\n }\n\n root.classList.toggle('dark', state.appearance === 'dark')\n root.dataset['theme'] = state.variant.id\n root.dataset['appearance'] = state.appearance\n root.style.colorScheme = state.appearance\n\n // A custom colour has no stylesheet block to select, and its value is the\n // attribute's worst possible content, so it is reported as `custom`.\n if (!accented) root.removeAttribute('data-accent')\n else\n root.dataset['accent'] = isCustomAccent(state.selection.accent)\n ? 'custom'\n : state.selection.accent\n\n // The surface axis is opt-in, so its absence removes rather than defaults: a host\n // that never enables it keeps an `<html>` with neither attribute, and one that\n // turns it off leaves nothing behind. (`--surface-alpha` is swept with the tokens.)\n if (state.surface) {\n root.dataset['surface'] = state.surface.surface\n root.dataset['reduceTransparency'] = state.surface.reduceTransparencyActive ? 'true' : 'false'\n } else {\n root.removeAttribute('data-surface')\n root.removeAttribute('data-reduce-transparency')\n }\n\n applyAppearanceFontSettings(\n root,\n normalizeAppearanceEditorFontSettings(state.selection.fonts, state.selection.font).settings\n )\n\n // Density is a pure token shift, so unlike the colour axes there is nothing to\n // compute: the attribute selects a block in `theme.css`. It is applied here for\n // the same reason as the others — this is the one place that knows what the\n // current selection is, and it is the only writer of these attributes.\n if (resolvedDensity(state.selection) === 'comfortable') root.removeAttribute('data-density')\n else root.dataset['density'] = resolvedDensity(state.selection)\n\n return new Set(Object.keys(tokens))\n}\n\n/** What {@link applyWithoutTransitions} needs of a document. */\nexport type TransitionDocument = {\n head: { appendChild(node: unknown): unknown }\n createElement(tag: 'style'): {\n textContent: string | null\n offsetHeight?: number\n remove(): void\n }\n}\n\n/**\n * Run `apply` with every CSS transition switched off for the frame that carries\n * it, so a theme switch cannot animate page-wide.\n *\n * Without this, flipping the custom properties on `<html>` starts a transition on\n * every element that declares one, and each fades from the old palette to the new\n * over its own duration: a theme change that looks like a rendering fault. The\n * rule is removed two frames later, after the browser has committed the new styles\n * with transitions off, and `restore` runs it early (the provider calls it when it\n * is disposed mid-switch).\n *\n * `schedule` is the frame source; it defaults to `requestAnimationFrame` and falls\n * back to running at once where there is none, which leaves transitions on rather\n * than off.\n */\nexport function applyWithoutTransitions(\n doc: TransitionDocument,\n apply: () => void,\n schedule: (callback: () => void) => void = defaultSchedule\n): () => void {\n const style = doc.createElement('style')\n style.textContent = '*{transition:none !important}'\n doc.head.appendChild(style)\n // Reading a layout property forces the browser to commit the rule before the\n // properties change; without it the two can be batched into one style recalc.\n void style.offsetHeight\n let removed = false\n const restore = () => {\n if (removed) return\n removed = true\n style.remove()\n }\n try {\n apply()\n } catch (error) {\n restore()\n throw error\n }\n schedule(() => schedule(restore))\n return restore\n}\n\nfunction defaultSchedule(callback: () => void): void {\n if (typeof requestAnimationFrame === 'function') requestAnimationFrame(callback)\n else callback()\n}\n"],"mappings":";;;;;;;;;;;AAwFA,SAAS,oBACP,SACA,aACwB;CACxB,IAAI,gBAAgB,SAAS,OAAO,CAAC;CACrC,OAAO,OAAO,YACZ,OAAO,QAAQ,kBAAkB,SAAS,EAAE,QAAQ,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,UACvE,KAAK,WAAW,WAAW,CAC7B,CACF;AACF;;;;;;;;;;;;;AAcA,SAAgB,oBACd,OACA,UAAgC,CAAC,GACsB;CAEvD,MAAM,aADiB,QAAQ,YAAY,gBAAgB,mBAAmB,MAAM,OAAO,IAEvF,oBAAoB,MAAM,SAAS,QAAQ,WAAW,IACtD,EAAE,GAAG,kBAAkB,MAAM,SAAS,EAAE,QAAQ,QAAQ,YAAY,CAAC,EAAE;CAC3E,MAAM,SAAS,gBAAgB,MAAM,UAAU,QAAQ,MAAM,YAAY,MAAM,WAAW;EACxF,YAAY,MAAM,QAAQ,OAAO;EACjC,OAAO,QAAQ;EACf,WAAW,MAAM,QAAQ;CAC3B,CAAC;CACD,IAAI,QAAQ,OAAO,OAAO,YAAY,MAAM;CAK5C,IAAI,MAAM,iBAAiB,OAAO,OAAO,YAAY,qBAAqB,MAAM,eAAe,CAAC;CAChG,IAAI,MAAM,SAAS,WAAW,qBAAqB,aAAa,MAAM,QAAQ;CAC9E,OAAO;EACL,QAAQ,QAAQ,kBAAkB,QAAQ,gBAAgB,UAAU,IAAI;EACxE,UAAU,WAAW,KAAA;CACvB;AACF;;;;;;;;;;AAWA,SAAgB,iBACd,MACA,OACA,UAAgC,CAAC,GACjC,UACqB;CACrB,MAAM,EAAE,QAAQ,aAAa,oBAAoB,OAAO,OAAO;CAE/D,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,MAAM,GAC/C,KAAK,MAAM,YAAY,MAAM,KAAK;CAEpC,IAAI,UACG;OAAA,MAAM,QAAQ,UACjB,IAAI,EAAE,QAAQ,SAAS,KAAK,MAAM,eAAe,IAAI;CAAA;CAIzD,KAAK,UAAU,OAAO,QAAQ,MAAM,eAAe,MAAM;CACzD,KAAK,QAAQ,WAAW,MAAM,QAAQ;CACtC,KAAK,QAAQ,gBAAgB,MAAM;CACnC,KAAK,MAAM,cAAc,MAAM;CAI/B,IAAI,CAAC,UAAU,KAAK,gBAAgB,aAAa;MAE/C,KAAK,QAAQ,YAAY,eAAe,MAAM,UAAU,MAAM,IAC1D,WACA,MAAM,UAAU;CAKtB,IAAI,MAAM,SAAS;EACjB,KAAK,QAAQ,aAAa,MAAM,QAAQ;EACxC,KAAK,QAAQ,wBAAwB,MAAM,QAAQ,2BAA2B,SAAS;CACzF,OAAO;EACL,KAAK,gBAAgB,cAAc;EACnC,KAAK,gBAAgB,0BAA0B;CACjD;CAEA,4BACE,MACA,sCAAsC,MAAM,UAAU,OAAO,MAAM,UAAU,IAAI,CAAC,CAAC,QACrF;CAMA,IAAI,gBAAgB,MAAM,SAAS,MAAM,eAAe,KAAK,gBAAgB,cAAc;MACtF,KAAK,QAAQ,aAAa,gBAAgB,MAAM,SAAS;CAE9D,OAAO,IAAI,IAAI,OAAO,KAAK,MAAM,CAAC;AACpC;;;;;;;;;;;;;;;;AA2BA,SAAgB,wBACd,KACA,OACA,WAA2C,iBAC/B;CACZ,MAAM,QAAQ,IAAI,cAAc,OAAO;CACvC,MAAM,cAAc;CACpB,IAAI,KAAK,YAAY,KAAK;CAG1B,MAAW;CACX,IAAI,UAAU;CACd,MAAM,gBAAgB;EACpB,IAAI,SAAS;EACb,UAAU;EACV,MAAM,OAAO;CACf;CACA,IAAI;EACF,MAAM;CACR,SAAS,OAAO;EACd,QAAQ;EACR,MAAM;CACR;CACA,eAAe,SAAS,OAAO,CAAC;CAChC,OAAO;AACT;AAEA,SAAS,gBAAgB,UAA4B;CACnD,IAAI,OAAO,0BAA0B,YAAY,sBAAsB,QAAQ;MAC1E,SAAS;AAChB"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"theme-state.js","names":[],"sources":["../../../src/components/theme/theme-state.ts"],"sourcesContent":["import {\n DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS,\n normalizeAppearanceEditorFontSettings,\n type AppearanceEditorFontSettings,\n} from '#lib/appearance-font-settings'\nimport type { AccentPreset } from '@adea-ai/themes'\nimport { normalizeCustomAccent, type AccentRoles } from '#lib/accent-roles'\nimport { stepAppearanceFontSizes, type FontZoomIntent } from '#lib/font-zoom'\nimport type { ThemeSurface } from '#lib/theme-surface'\nimport {\n accentRolesForSelection,\n defaultDarkThemeId,\n defaultLightThemeId,\n resolveVariant,\n type ThemeAppearance,\n type ThemeVariant,\n} from '#lib/themes'\n\n/**\n * The pure half of the theme provider: what a selection is, how two selections\n * merge, and what a selection resolves to.\n *\n * Nothing here touches the document, storage or a signal, so each rule can be\n * tested by calling it. `ThemeProvider` owns the reactivity and wires these to it;\n * `theme-document.ts` owns the writes; `theme-persistence.ts` owns the store.\n */\n\n/**\n * Four independent choices, and keeping them independent is the point:\n *\n * - **appearance** — light, dark, or follow the system.\n * - **a theme per appearance** — so a user can run Catppuccin Mocha by night and\n * Solarized Light by day without re-choosing when the system flips.\n * - **an accent** — adea's six presets, or one of the accents the selected theme\n * pair carries itself (`ansi-blue`…), which re-colour the interactive roles on\n * top of whichever theme is active.\n * - **fonts** — separate family and size choices for UI, content and code.\n */\nexport type ThemeSelection = {\n /** `system` follows `prefers-color-scheme` and updates when it changes. */\n appearance: ThemeAppearance | 'system'\n /** The variant to use when the resolved appearance is light. */\n lightThemeId: string\n /** The variant to use when the resolved appearance is dark. */\n darkThemeId: string\n /**\n * An `accentPresets` id, a theme accent id from `themeAccents()` (`ansi-blue`…),\n * `theme` for the variant's own primary, or a custom colour as `#rrggbb`. A theme\n * accent the current pair does not offer falls back to the variant's own primary.\n *\n * A custom colour is stored lower case and is raised to the contrast floor when it\n * is painted, never when it is stored: the selection keeps what the user chose.\n * Anything else that is not an id is dropped at paint, not rejected here.\n */\n accent: string\n /** UI-family alias for consumers of the original single-font preference. */\n font: string\n /** UI, content and code font settings. Omitted legacy records are migrated from `font`. */\n fonts?: AppearanceEditorFontSettings\n /**\n * `comfortable` is the system's default geometry; `compact` moves the control\n * ladder down one rung.\n *\n * Optional, and defaulted on read, so a preference stored before density existed\n * keeps working and `ThemeSelection` stays assignable without it.\n */\n density?: ThemeDensity\n /**\n * The terminal either follows the interface theme (`theme`, or absent) or is pinned\n * to another theme's terminal palette by that theme's id. The pin is written over\n * the interface variant's own `--terminal-*` properties, so a light interface can\n * carry a dark terminal.\n *\n * An id the registry does not hold paints the interface palette and stays in the\n * selection, the same rule as an unknown theme id.\n *\n * Optional and not defaulted: see {@link defaultThemeSelection}.\n */\n terminalThemeId?: string\n /**\n * How opaque elevated panels are. Written to `<html>` only when the provider's\n * `surface` prop is on or this is set; absent, a host paints no surface axis at\n * all. Resolved against the OS and the host's capability before it is painted —\n * see `resolveSurface` — so the value stored is a preference, never the outcome.\n */\n surface?: ThemeSurface\n /**\n * The user's own Reduce Transparency switch, which forces the `opaque` surface the\n * same way the operating system's setting does.\n */\n reduceTransparency?: boolean\n}\n\n/**\n * The two density rungs.\n *\n * `comfortable` is the geometry in `theme.css`, so it is the absence of an\n * attribute rather than a value: a document that ships no `data-density` at all\n * renders correctly, which is what keeps this additive.\n */\nexport type ThemeDensity = 'comfortable' | 'compact'\n\n/**\n * Deliberately lists only the fields every consumer has always had. A field added\n * to {@link ThemeSelection} later gets no default here: `JSON.stringify` drops an\n * `undefined`, so a store written by a consumer that never sets the field stays\n * byte-identical to what it was before the field existed.\n */\nexport const defaultThemeSelection: ThemeSelection = {\n appearance: 'system',\n lightThemeId: defaultLightThemeId,\n darkThemeId: defaultDarkThemeId,\n accent: 'theme',\n font: 'system',\n fonts: DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS,\n density: 'comfortable',\n}\n\n/**\n * A stored selection's density, defaulted.\n *\n * The stored value is read through here rather than trusted, because a preference\n * written before density existed has no `density` key and a hand-edited store may\n * hold anything.\n */\nexport function resolvedDensity(selection: ThemeSelection): ThemeDensity {\n return selection.density === 'compact' ? 'compact' : 'comfortable'\n}\n\nexport type ThemeContextValue = {\n /** The preference as stored. A {@link ThemeContextValue.preview} does not change it. */\n selection: () => ThemeSelection\n /** What the preference resolves to right now, after `system`. */\n resolvedAppearance: () => ThemeAppearance\n /** The variant in use. */\n variant: () => ThemeVariant\n /** Update part of the preference. */\n setSelection: (patch: Partial<ThemeSelection>) => void\n /**\n * The built-in catalogue, so a picker does not import it separately. A plain\n * array: {@link ThemeContextValue.allThemes} is the one that follows a host's\n * extra themes.\n */\n themes: readonly ThemeVariant[]\n /**\n * The accents the selected light/dark pair carries, offered beside the presets.\n * Follows the selection: a different theme pair may offer different slots.\n */\n themeAccents: () => readonly AccentPreset[]\n /**\n * The built-in catalogue followed by the provider's `themes` prop. Reactive when\n * that prop is a function that reads signals.\n */\n allThemes: () => readonly ThemeVariant[]\n /**\n * Paint the document from `draft` laid over the stored selection, without\n * persisting it or changing what {@link ThemeContextValue.selection} returns.\n * `undefined` ends the preview. A {@link ThemeContextValue.setSelection} ends it\n * too, because the committed selection is then what should be showing.\n */\n preview: (draft: Partial<ThemeSelection> | undefined) => void\n /** True while a {@link ThemeContextValue.preview} draft is on the document. */\n previewing: () => boolean\n /**\n * The roles `accent` paints over `variantId` (default: the variant in use), held\n * to the provider's `accentContrastFloor`, for an element-scoped override with\n * `paintAccentRoles` — a workspace that carries its own colour. `undefined` for\n * `null`, for `theme`, for an id that resolves to nothing, and for a variant the\n * registry does not hold: each means \"keep the inherited accent\".\n */\n accentRolesFor: (accent: string | null, variantId?: string) => AccentRoles | undefined\n /**\n * The surface the committed selection paints, after Reduce Transparency and the\n * host's translucency capability. `opaque` when the axis is off. Follows the\n * committed selection, not a preview, like {@link ThemeContextValue.variant}.\n */\n effectiveSurface: () => ThemeSurface\n /**\n * True while the operating system or the selection's `reduceTransparency` forces\n * opaque surfaces. Always false when the surface axis is off, because nothing is\n * then listening to the operating system.\n */\n reduceTransparencyActive: () => boolean\n /**\n * Step the three text sizes together, or put them back to the defaults: what the\n * `fontZoom` prop's chords do, for a host that binds its own keys or a menu. Goes\n * through {@link ThemeContextValue.setSelection}, so it persists and ends a preview.\n */\n zoomFonts: (intent: FontZoomIntent) => void\n}\n\n/** A selection with the `undefined` keys of a patch left out. */\nfunction definedOnly<T extends object>(value: T): Partial<T> {\n return Object.fromEntries(\n Object.entries(value).filter(([, entry]) => entry !== undefined)\n ) as Partial<T>\n}\n\n/** Keep the deprecated single-font view synchronized with the UI axis. */\nexport function mergeThemeSelection(\n base: ThemeSelection,\n patch: Partial<ThemeSelection>\n): ThemeSelection {\n const merged = { ...base, ...patch }\n let fonts: AppearanceEditorFontSettings\n\n if (patch.fonts !== undefined) {\n fonts = normalizeAppearanceEditorFontSettings(patch.fonts).settings\n } else if (patch.font !== undefined) {\n const currentFonts = normalizeAppearanceEditorFontSettings(base.fonts, base.font).settings\n fonts = normalizeAppearanceEditorFontSettings({\n ...currentFonts,\n ui: { ...currentFonts.ui, family: patch.font },\n }).settings\n } else {\n fonts = normalizeAppearanceEditorFontSettings(merged.fonts, merged.font).settings\n }\n\n return {\n ...merged,\n // One spelling for a custom colour, so equal choices compare and store equal.\n accent: normalizeCustomAccent(merged.accent) ?? merged.accent,\n fonts,\n font: fonts.ui.family,\n }\n}\n\n/**\n * The selection a provider starts from: the defaults, then whatever the store\n * returned, then `initial`.\n *\n * `initial` wins over the store on every mount. That precedence is part of the\n * contract Cortana relies on (its `initial` pins `appearance` and `font`), and it\n * is why the store's answer is only the middle layer. A store that returned\n * nothing, or threw, or answered with explicit `undefined`s, leaves the defaults\n * standing for those fields.\n */\nexport function initialThemeSelection(\n stored: Partial<ThemeSelection> | undefined,\n initial: Partial<ThemeSelection> | undefined\n): ThemeSelection {\n const base = mergeThemeSelection(defaultThemeSelection, definedOnly(stored ?? {}))\n return mergeThemeSelection(base, initial ?? {})\n}\n\n/** `system` resolved against what the operating system reports. */\nexport function resolveAppearance(\n preference: ThemeSelection['appearance'],\n system: ThemeAppearance\n): ThemeAppearance {\n return preference === 'system' ? system : preference\n}\n\n/** The theme id the selection names for a resolved appearance. */\nexport function selectedThemeId(selection: ThemeSelection, appearance: ThemeAppearance): string {\n return appearance === 'dark' ? selection.darkThemeId : selection.lightThemeId\n}\n\n/**\n * What a selection paints: the resolved appearance and the variant for it.\n *\n * An id the registry does not hold resolves to the appearance's default rather than\n * being rewritten (see `resolveVariant`), so the selection a host stores is never\n * changed by being unresolvable.\n */\nexport function resolveThemeState(\n selection: ThemeSelection,\n system: ThemeAppearance,\n registry: readonly ThemeVariant[]\n): { appearance: ThemeAppearance; variant: ThemeVariant } {\n const appearance = resolveAppearance(selection.appearance, system)\n return {\n appearance,\n variant: resolveVariant(registry, selectedThemeId(selection, appearance), appearance),\n }\n}\n\n/**\n * The variant whose terminal palette is pinned, or `undefined` when the terminal\n * follows the interface theme.\n *\n * `theme`, an absent id and an id the registry does not hold all mean the interface\n * palette: a terminal must never resolve to a blank or half-painted palette because a\n * theme was deleted after it was pinned.\n */\nexport function resolveTerminalVariant(\n terminalThemeId: string | undefined,\n registry: readonly ThemeVariant[]\n): ThemeVariant | undefined {\n if (terminalThemeId === undefined || terminalThemeId === 'theme') return undefined\n return registry.find((candidate) => candidate.id === terminalThemeId)\n}\n\n/**\n * The font settings a zoom chord asks for: one pixel up or down on every axis, or\n * the defaults for `reset`.\n */\nexport function zoomedFontSettings(\n selection: ThemeSelection,\n intent: FontZoomIntent\n): AppearanceEditorFontSettings {\n if (intent === 'reset') return { ...DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS }\n return stepAppearanceFontSizes(selection.fonts, intent === 'in' ? 1 : -1)\n}\n\n/**\n * The registry a provider resolves against: the catalogue, then the host's extras.\n *\n * The catalogue wins an id collision, because it is listed first and `resolveVariant`\n * takes the first match: a host's variant can add a theme but cannot shadow one.\n */\nexport function mergeRegistry(\n builtin: readonly ThemeVariant[],\n extra: readonly ThemeVariant[] | undefined\n): readonly ThemeVariant[] {\n return extra && extra.length > 0 ? [...builtin, ...extra] : builtin\n}\n\n/**\n * The roles an accent paints over a variant, for {@link ThemeContextValue.accentRolesFor}.\n *\n * Resolved against the committed selection's theme pair, so an `ansi-*` accent means\n * the same colour here as it does on `<html>`.\n */\nexport function accentRolesForState(\n accent: string | null,\n variantId: string | undefined,\n state: {\n selection: ThemeSelection\n variant: ThemeVariant\n registry: readonly ThemeVariant[]\n accentContrastFloor?: number\n }\n): AccentRoles | undefined {\n if (accent === null) return undefined\n const variant =\n variantId === undefined\n ? state.variant\n : state.registry.find((candidate) => candidate.id === variantId)\n if (!variant) return undefined\n return accentRolesForSelection(accent, variant.appearance, state.selection, {\n background: variant.colors.background,\n floor: state.accentContrastFloor,\n variantId: variant.id,\n })\n}\n"],"mappings":";;;;;;;;;;;AA4GA,IAAa,wBAAwC;CACnD,YAAY;CACZ,cAAc;CACd,aAAa;CACb,QAAQ;CACR,MAAM;CACN,OAAO;CACP,SAAS;AACX;;;;;;;;AASA,SAAgB,gBAAgB,WAAyC;CACvE,OAAO,UAAU,YAAY,YAAY,YAAY;AACvD;;AAiEA,SAAS,YAA8B,OAAsB;CAC3D,OAAO,OAAO,YACZ,OAAO,QAAQ,KAAK,CAAC,CAAC,QAAQ,GAAG,WAAW,UAAU,KAAA,CAAS,CACjE;AACF;;AAGA,SAAgB,oBACd,MACA,OACgB;CAChB,MAAM,SAAS;EAAE,GAAG;EAAM,GAAG;CAAM;CACnC,IAAI;CAEJ,IAAI,MAAM,UAAU,KAAA,GAClB,QAAQ,sCAAsC,MAAM,KAAK,CAAC,CAAC;MACtD,IAAI,MAAM,SAAS,KAAA,GAAW;EACnC,MAAM,eAAe,sCAAsC,KAAK,OAAO,KAAK,IAAI,CAAC,CAAC;EAClF,QAAQ,sCAAsC;GAC5C,GAAG;GACH,IAAI;IAAE,GAAG,aAAa;IAAI,QAAQ,MAAM;GAAK;EAC/C,CAAC,CAAC,CAAC;CACL,OACE,QAAQ,sCAAsC,OAAO,OAAO,OAAO,IAAI,CAAC,CAAC;CAG3E,OAAO;EACL,GAAG;EAEH,QAAQ,sBAAsB,OAAO,MAAM,KAAK,OAAO;EACvD;EACA,MAAM,MAAM,GAAG;CACjB;AACF;;;;;;;;;;;AAYA,SAAgB,sBACd,QACA,SACgB;CAEhB,OAAO,oBADM,oBAAoB,uBAAuB,YAAY,UAAU,CAAC,CAAC,CACrD,GAAM,WAAW,CAAC,CAAC;AAChD;;AAGA,SAAgB,kBACd,YACA,QACiB;CACjB,OAAO,eAAe,WAAW,SAAS;AAC5C;;AAGA,SAAgB,gBAAgB,WAA2B,YAAqC;CAC9F,OAAO,eAAe,SAAS,UAAU,cAAc,UAAU;AACnE;;;;;;;;AASA,SAAgB,kBACd,WACA,QACA,UACwD;CACxD,MAAM,aAAa,kBAAkB,UAAU,YAAY,MAAM;CACjE,OAAO;EACL;EACA,SAAS,eAAe,UAAU,gBAAgB,WAAW,UAAU,GAAG,UAAU;CACtF;AACF;;;;;;;;;AAUA,SAAgB,uBACd,iBACA,UAC0B;CAC1B,IAAI,oBAAoB,KAAA,KAAa,oBAAoB,SAAS,OAAO,KAAA;CACzE,OAAO,SAAS,MAAM,cAAc,UAAU,OAAO,eAAe;AACtE;;;;;AAMA,SAAgB,mBACd,WACA,QAC8B;CAC9B,IAAI,WAAW,SAAS,OAAO,EAAE,GAAG,wCAAwC;CAC5E,OAAO,wBAAwB,UAAU,OAAO,WAAW,OAAO,IAAI,EAAE;AAC1E;;;;;;;AAQA,SAAgB,cACd,SACA,OACyB;CACzB,OAAO,SAAS,MAAM,SAAS,IAAI,CAAC,GAAG,SAAS,GAAG,KAAK,IAAI;AAC9D;;;;;;;AAQA,SAAgB,oBACd,QACA,WACA,OAMyB;CACzB,IAAI,WAAW,MAAM,OAAO,KAAA;CAC5B,MAAM,UACJ,cAAc,KAAA,IACV,MAAM,UACN,MAAM,SAAS,MAAM,cAAc,UAAU,OAAO,SAAS;CACnE,IAAI,CAAC,SAAS,OAAO,KAAA;CACrB,OAAO,wBAAwB,QAAQ,QAAQ,YAAY,MAAM,WAAW;EAC1E,YAAY,QAAQ,OAAO;EAC3B,OAAO,MAAM;EACb,WAAW,QAAQ;CACrB,CAAC;AACH"}