@adea-ai/ui 0.128.1 → 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 (141) 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/accent-roles.js +94 -9
  51. package/dist/lib/accent-roles.js.map +1 -1
  52. package/dist/lib/font-zoom.js +4 -2
  53. package/dist/lib/font-zoom.js.map +1 -1
  54. package/dist/lib/overlay.js +3 -1
  55. package/dist/lib/overlay.js.map +1 -1
  56. package/dist/lib/theme-catalogue.d.ts +3 -0
  57. package/dist/lib/theme-catalogue.d.ts.map +1 -0
  58. package/dist/lib/theme-catalogue.js +22 -0
  59. package/dist/lib/theme-catalogue.js.map +1 -0
  60. package/dist/lib/theme-css.d.ts +43 -0
  61. package/dist/lib/theme-css.d.ts.map +1 -0
  62. package/dist/lib/theme-css.js +167 -0
  63. package/dist/lib/theme-css.js.map +1 -0
  64. package/dist/lib/theme-defaults.d.ts +46 -0
  65. package/dist/lib/theme-defaults.d.ts.map +1 -0
  66. package/dist/lib/theme-defaults.generated.d.ts +13 -0
  67. package/dist/lib/theme-defaults.generated.d.ts.map +1 -0
  68. package/dist/lib/theme-defaults.generated.js +318 -0
  69. package/dist/lib/theme-defaults.generated.js.map +1 -0
  70. package/dist/lib/theme-defaults.js +47 -0
  71. package/dist/lib/theme-defaults.js.map +1 -0
  72. package/dist/lib/theme-engine-light.d.ts +3 -0
  73. package/dist/lib/theme-engine-light.d.ts.map +1 -0
  74. package/dist/lib/theme-engine-light.js +75 -0
  75. package/dist/lib/theme-engine-light.js.map +1 -0
  76. package/dist/lib/theme-engine.d.ts +69 -0
  77. package/dist/lib/theme-engine.d.ts.map +1 -0
  78. package/dist/lib/theme-engine.js +33 -0
  79. package/dist/lib/theme-engine.js.map +1 -0
  80. package/dist/lib/themes.d.ts +14 -48
  81. package/dist/lib/themes.d.ts.map +1 -1
  82. package/dist/lib/themes.js +24 -164
  83. package/dist/lib/themes.js.map +1 -1
  84. package/dist/lib/tokens.d.ts +1 -25
  85. package/dist/lib/tokens.d.ts.map +1 -1
  86. package/dist/lib/tokens.js +1 -24
  87. package/dist/lib/tokens.js.map +1 -1
  88. package/dist/lib/tooltip-focus-gate.js +7 -1
  89. package/dist/lib/tooltip-focus-gate.js.map +1 -1
  90. package/dist/lib/variants.js +2 -2
  91. package/dist/r/lib.json +35 -0
  92. package/dist/r/registry.json +70 -0
  93. package/dist/r/src/components/theme/lazy.ts +35 -0
  94. package/dist/r/src/components/theme/theme-catalogue-source.ts +59 -0
  95. package/dist/r/src/components/theme/theme-document-core.ts +262 -0
  96. package/dist/r/src/components/theme/theme-document.ts +23 -240
  97. package/dist/r/src/components/theme/theme-persistence.ts +14 -6
  98. package/dist/r/src/components/theme/theme-provider-core.tsx +40 -0
  99. package/dist/r/src/components/theme/theme-provider-lazy.tsx +34 -0
  100. package/dist/r/src/components/theme/theme-provider-value.ts +445 -0
  101. package/dist/r/src/components/theme/theme-provider.tsx +23 -346
  102. package/dist/r/src/components/theme/theme-script.ts +3 -3
  103. package/dist/r/src/components/theme/theme-state-core.ts +364 -0
  104. package/dist/r/src/components/theme/theme-state.ts +10 -329
  105. package/dist/r/src/components/theme/theme-toggle.tsx +1 -1
  106. package/dist/r/src/lib/accent-presets.ts +43 -0
  107. package/dist/r/src/lib/theme-catalogue.ts +26 -0
  108. package/dist/r/src/lib/theme-css.ts +201 -0
  109. package/dist/r/src/lib/theme-defaults.generated.ts +319 -0
  110. package/dist/r/src/lib/theme-defaults.ts +79 -0
  111. package/dist/r/src/lib/theme-engine-light.ts +117 -0
  112. package/dist/r/src/lib/theme-engine.ts +111 -0
  113. package/dist/r/src/lib/themes.ts +27 -191
  114. package/dist/r/src/lib/tokens.ts +1 -35
  115. package/dist/r/theming.json +35 -0
  116. package/package.json +12 -3
  117. package/registry.json +70 -0
  118. package/src/components/theme/lazy.ts +35 -0
  119. package/src/components/theme/theme-catalogue-source.ts +59 -0
  120. package/src/components/theme/theme-document-core.ts +262 -0
  121. package/src/components/theme/theme-document.ts +23 -240
  122. package/src/components/theme/theme-persistence.ts +14 -6
  123. package/src/components/theme/theme-provider-core.tsx +40 -0
  124. package/src/components/theme/theme-provider-lazy.tsx +34 -0
  125. package/src/components/theme/theme-provider-value.ts +445 -0
  126. package/src/components/theme/theme-provider.tsx +23 -346
  127. package/src/components/theme/theme-script.ts +3 -3
  128. package/src/components/theme/theme-state-core.ts +364 -0
  129. package/src/components/theme/theme-state.ts +10 -329
  130. package/src/components/theme/theme-toggle.tsx +1 -1
  131. package/src/lib/accent-presets.ts +43 -0
  132. package/src/lib/theme-catalogue.ts +26 -0
  133. package/src/lib/theme-css.ts +201 -0
  134. package/src/lib/theme-defaults.generated.ts +319 -0
  135. package/src/lib/theme-defaults.ts +79 -0
  136. package/src/lib/theme-engine-light.ts +117 -0
  137. package/src/lib/theme-engine.ts +111 -0
  138. package/src/lib/themes.ts +27 -191
  139. package/src/lib/tokens.ts +1 -35
  140. package/dist/components/theme/theme-document.js.map +0 -1
  141. package/dist/components/theme/theme-state.js.map +0 -1
@@ -2527,11 +2527,26 @@
2527
2527
  "type": "registry:ui",
2528
2528
  "target": "components/theming/index.ts"
2529
2529
  },
2530
+ {
2531
+ "path": "src/components/theme/lazy.ts",
2532
+ "type": "registry:ui",
2533
+ "target": "components/theming/lazy.ts"
2534
+ },
2535
+ {
2536
+ "path": "src/components/theme/theme-catalogue-source.ts",
2537
+ "type": "registry:ui",
2538
+ "target": "components/theming/theme-catalogue-source.ts"
2539
+ },
2530
2540
  {
2531
2541
  "path": "src/components/theme/theme-context.ts",
2532
2542
  "type": "registry:ui",
2533
2543
  "target": "components/theming/theme-context.ts"
2534
2544
  },
2545
+ {
2546
+ "path": "src/components/theme/theme-document-core.ts",
2547
+ "type": "registry:ui",
2548
+ "target": "components/theming/theme-document-core.ts"
2549
+ },
2535
2550
  {
2536
2551
  "path": "src/components/theme/theme-document.ts",
2537
2552
  "type": "registry:ui",
@@ -2552,6 +2567,21 @@
2552
2567
  "type": "registry:ui",
2553
2568
  "target": "components/theming/theme-picker.tsx"
2554
2569
  },
2570
+ {
2571
+ "path": "src/components/theme/theme-provider-core.tsx",
2572
+ "type": "registry:ui",
2573
+ "target": "components/theming/theme-provider-core.tsx"
2574
+ },
2575
+ {
2576
+ "path": "src/components/theme/theme-provider-lazy.tsx",
2577
+ "type": "registry:ui",
2578
+ "target": "components/theming/theme-provider-lazy.tsx"
2579
+ },
2580
+ {
2581
+ "path": "src/components/theme/theme-provider-value.ts",
2582
+ "type": "registry:ui",
2583
+ "target": "components/theming/theme-provider-value.ts"
2584
+ },
2555
2585
  {
2556
2586
  "path": "src/components/theme/theme-provider.tsx",
2557
2587
  "type": "registry:ui",
@@ -2562,6 +2592,11 @@
2562
2592
  "type": "registry:ui",
2563
2593
  "target": "components/theming/theme-script.ts"
2564
2594
  },
2595
+ {
2596
+ "path": "src/components/theme/theme-state-core.ts",
2597
+ "type": "registry:ui",
2598
+ "target": "components/theming/theme-state-core.ts"
2599
+ },
2565
2600
  {
2566
2601
  "path": "src/components/theme/theme-state.ts",
2567
2602
  "type": "registry:ui",
@@ -2654,6 +2689,11 @@
2654
2689
  ],
2655
2690
  "registryDependencies": [],
2656
2691
  "files": [
2692
+ {
2693
+ "path": "src/lib/accent-presets.ts",
2694
+ "type": "registry:lib",
2695
+ "target": "lib/accent-presets.ts"
2696
+ },
2657
2697
  {
2658
2698
  "path": "src/lib/accent-roles.ts",
2659
2699
  "type": "registry:lib",
@@ -2719,6 +2759,36 @@
2719
2759
  "type": "registry:lib",
2720
2760
  "target": "lib/theme-accents.ts"
2721
2761
  },
2762
+ {
2763
+ "path": "src/lib/theme-catalogue.ts",
2764
+ "type": "registry:lib",
2765
+ "target": "lib/theme-catalogue.ts"
2766
+ },
2767
+ {
2768
+ "path": "src/lib/theme-css.ts",
2769
+ "type": "registry:lib",
2770
+ "target": "lib/theme-css.ts"
2771
+ },
2772
+ {
2773
+ "path": "src/lib/theme-defaults.generated.ts",
2774
+ "type": "registry:lib",
2775
+ "target": "lib/theme-defaults.generated.ts"
2776
+ },
2777
+ {
2778
+ "path": "src/lib/theme-defaults.ts",
2779
+ "type": "registry:lib",
2780
+ "target": "lib/theme-defaults.ts"
2781
+ },
2782
+ {
2783
+ "path": "src/lib/theme-engine-light.ts",
2784
+ "type": "registry:lib",
2785
+ "target": "lib/theme-engine-light.ts"
2786
+ },
2787
+ {
2788
+ "path": "src/lib/theme-engine.ts",
2789
+ "type": "registry:lib",
2790
+ "target": "lib/theme-engine.ts"
2791
+ },
2722
2792
  {
2723
2793
  "path": "src/lib/theme-surface.ts",
2724
2794
  "type": "registry:lib",
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The theme provider with the catalogue out of the start-up path.
3
+ *
4
+ * Everything here is free of the 34-theme catalogue: `import` this entry instead of
5
+ * `@adea-ai/ui/components/theme` in a host that paints its theme from a stylesheet.
6
+ * The catalogue loads, in a chunk of its own, the first time a stored or previewed
7
+ * theme needs it. See {@link ThemeProvider} for the rules and `docs/consumption.md`
8
+ * for the setup.
9
+ */
10
+ export { loadThemeCatalogue } from './theme-catalogue-source'
11
+ export { ThemeProvider } from './theme-provider-lazy'
12
+ export {
13
+ createVersionedPersistence,
14
+ defaultThemeSelection,
15
+ inlineScriptLiteral,
16
+ localStoragePersistence,
17
+ resolvedDensity,
18
+ themeScript,
19
+ useOptionalTheme,
20
+ useTheme,
21
+ type EditorRolesMode,
22
+ type ThemeCatalogueSource,
23
+ type ThemeContextValue,
24
+ type ThemeDensity,
25
+ type ThemePersistence,
26
+ type ThemeProviderProps,
27
+ type ThemeScriptOptions,
28
+ type ThemeSelection,
29
+ type ThemeStorageSchema,
30
+ type ThemeSurface,
31
+ type VersionedCodec,
32
+ type VersionedPersistenceOptions,
33
+ } from './theme-provider-core'
34
+ export { ThemeToggle, type ThemeToggleProps } from './theme-toggle'
35
+ export { ThemeModeToggle, type ThemeModeToggleProps } from './theme-mode-toggle'
@@ -0,0 +1,59 @@
1
+ import { createSignal } from 'solid-js'
2
+ import type { ThemeEngine } from '../../lib/theme-engine'
3
+ import { lightThemeEngine } from '../../lib/theme-engine-light'
4
+ import type { ThemeCatalogueSource } from './theme-provider-value'
5
+
6
+ /**
7
+ * The full catalogue engine once something has asked for it, shared by every lazy
8
+ * provider and every caller of {@link loadThemeCatalogue} in the page. A provider
9
+ * created after the load starts with it already in hand.
10
+ */
11
+ let loadedEngine: ThemeEngine | undefined
12
+ let loading: Promise<ThemeEngine> | undefined
13
+
14
+ /**
15
+ * Load the whole theme catalogue (about 129 KB raw, 34 KB gzipped) and resolve to the
16
+ * engine backed by it. Idempotent: every call shares one import, and a failed import is
17
+ * forgotten so the next call retries.
18
+ *
19
+ * The lazy provider calls this itself the moment a selection needs a theme beyond the
20
+ * default pair. Call it earlier — when a settings page mounts, or on hover of its
21
+ * entry — to have the picker ready by the time it opens.
22
+ */
23
+ export function loadThemeCatalogue(): Promise<ThemeEngine> {
24
+ if (loadedEngine) return Promise.resolve(loadedEngine)
25
+ loading ??= import('../../lib/theme-catalogue').then(
26
+ (module) => {
27
+ loadedEngine = module.catalogueThemeEngine
28
+ return loadedEngine
29
+ },
30
+ (error: unknown) => {
31
+ loading = undefined
32
+ throw error
33
+ }
34
+ )
35
+ return loading
36
+ }
37
+
38
+ /** A catalogue source for one provider: the shipped defaults until the load resolves. */
39
+ export function createLazyCatalogue(): ThemeCatalogueSource {
40
+ const [full, setFull] = createSignal<ThemeEngine | undefined>(loadedEngine)
41
+ const [failed, setFailed] = createSignal(false)
42
+ const load = () =>
43
+ loadThemeCatalogue().then(
44
+ (loaded) => {
45
+ setFailed(false)
46
+ setFull(() => loaded)
47
+ },
48
+ () => {
49
+ // The theme the stylesheet painted stays; the next request retries.
50
+ setFailed(true)
51
+ }
52
+ )
53
+ return {
54
+ engine: () => full() ?? lightThemeEngine,
55
+ loaded: () => full() !== undefined,
56
+ failed,
57
+ load,
58
+ }
59
+ }
@@ -0,0 +1,262 @@
1
+ import {
2
+ applyAppearanceFontSettings,
3
+ normalizeAppearanceEditorFontSettings,
4
+ } from '../../lib/appearance-font-settings'
5
+ import { terminalCssVariables } from '../../lib/theme-css'
6
+ import type { ThemeEngine } from '../../lib/theme-engine'
7
+ import type { EditorRolesMode, ThemeAppearance, ThemeVariant } from '../../lib/themes'
8
+ import { isCustomAccent } from '../../lib/accent-roles'
9
+ import { surfaceAlpha, type SurfaceState } from '../../lib/theme-surface'
10
+ import { resolvedDensity, type ThemeSelection } from './theme-state-core'
11
+
12
+ /**
13
+ * The document half of the theme provider: the single writer of `<html>`.
14
+ *
15
+ * Written against the few members of an element it uses, so the rules can be tested
16
+ * on a stand-in and the provider's effect stays a one-line call.
17
+ *
18
+ * This is the engine-parameterised half: every question the catalogue answers goes
19
+ * through the {@link ThemeEngine} it is given, so it imports no catalogue code or data.
20
+ * `theme-document.ts` binds the full catalogue and keeps the signatures the eager
21
+ * provider and the tests have always used.
22
+ */
23
+ export type ThemeRoot = Pick<
24
+ HTMLElement,
25
+ 'classList' | 'dataset' | 'style' | 'removeAttribute' | 'setAttribute'
26
+ >
27
+
28
+ export type ThemeDocumentState = {
29
+ /** What the document shows: the committed selection, or a preview laid over it. */
30
+ selection: ThemeSelection
31
+ appearance: ThemeAppearance
32
+ variant: ThemeVariant
33
+ /**
34
+ * The variant whose terminal palette is pinned over the interface variant's.
35
+ * Absent, the terminal follows the interface theme.
36
+ */
37
+ terminalVariant?: ThemeVariant
38
+ /**
39
+ * The resolved surface axis. Absent, the axis is off: no attribute and no
40
+ * `--surface-alpha` is written, and any this writer wrote before is removed.
41
+ */
42
+ surface?: SurfaceState
43
+ }
44
+
45
+ export type ThemeDocumentOptions = {
46
+ /**
47
+ * Rewrites the custom properties just before they are written: the variant's
48
+ * roles with the accent laid over them. It sees every property of the pass and
49
+ * must return the full map; a name it drops is not written.
50
+ */
51
+ transformTokens?: (tokens: Record<string, string>) => Record<string, string>
52
+ /**
53
+ * The contrast an accent is held to against the variant's background. Absent, a
54
+ * preset is painted verbatim (the catalogue measured it for its own themes) and
55
+ * only a custom `#rrggbb` is held to the default floor of 3:1.
56
+ */
57
+ accentContrastFloor?: number
58
+ /**
59
+ * Where a catalogue variant's palette comes from. `inline` (the default) writes
60
+ * every role onto the root. `stylesheet` writes none for a catalogue variant,
61
+ * because the host loads a stylesheet that carries a `[data-theme]` block for it;
62
+ * a variant the catalogue does not hold (a host's imported theme) is still written
63
+ * inline, since no stylesheet can know it. The accent, the terminal pin, the
64
+ * surface alpha and the fonts are written the same in both.
65
+ *
66
+ * Switching from an imported variant back to a catalogue one removes what the
67
+ * imported one wrote, so nothing inline outlives it to beat the stylesheet.
68
+ */
69
+ palette?: 'inline' | 'stylesheet'
70
+ /**
71
+ * The `--editor-*` projection. `syntax` (the default) is the catalogue's quiet
72
+ * syntax roles, which is what `theme-variants.css` carries; `floor` holds every
73
+ * editor role to 4.5:1 on the canvas, as hex.
74
+ *
75
+ * With `palette: 'stylesheet'`, `floor` is the one palette write a catalogue
76
+ * variant still gets: the stylesheet is generated for the default projection, so
77
+ * the sixteen `--editor-*` properties are written inline over it. The rest of the
78
+ * palette stays in the stylesheet.
79
+ */
80
+ editorRoles?: EditorRolesMode
81
+ }
82
+
83
+ /**
84
+ * What a catalogue variant still writes inline when its palette is the stylesheet's:
85
+ * nothing, unless the editor projection is not the one the stylesheet was generated
86
+ * for.
87
+ */
88
+ function stylesheetOverrides(
89
+ engine: ThemeEngine,
90
+ variant: ThemeVariant,
91
+ editorRoles: EditorRolesMode | undefined
92
+ ): Record<string, string> {
93
+ if (editorRoles !== 'floor') return {}
94
+ return Object.fromEntries(
95
+ Object.entries(engine.themeCssVariables(variant, { editor: 'floor' })).filter(([name]) =>
96
+ name.startsWith('--editor-')
97
+ )
98
+ )
99
+ }
100
+
101
+ /**
102
+ * The custom properties a state paints: the variant's roles, with the accent
103
+ * selection laid over them, the pinned terminal palette over that, the surface
104
+ * alpha, then `transformTokens`.
105
+ *
106
+ * The accent is applied *here* rather than left to the `[data-accent]` blocks in
107
+ * `theme.css`, because the properties are written inline and an inline declaration
108
+ * outranks every stylesheet selector however specific it is. The blocks still exist
109
+ * and are still correct — they are what styles the window before this runs — but
110
+ * they cannot win from here, so leaving the accent to them meant the attribute was
111
+ * set, the rules were present, and nothing changed on screen.
112
+ */
113
+ export function themeDocumentTokensWith(
114
+ engine: ThemeEngine,
115
+ state: ThemeDocumentState,
116
+ options: ThemeDocumentOptions = {}
117
+ ): { tokens: Record<string, string>; accented: boolean } {
118
+ const fromStylesheet =
119
+ options.palette === 'stylesheet' && engine.isCatalogueVariant(state.variant)
120
+ const properties = fromStylesheet
121
+ ? stylesheetOverrides(engine, state.variant, options.editorRoles)
122
+ : { ...engine.themeCssVariables(state.variant, { editor: options.editorRoles }) }
123
+ const accent = engine.accentVariables(state.selection.accent, state.appearance, state.selection, {
124
+ background: state.variant.colors.background,
125
+ floor: options.accentContrastFloor,
126
+ variantId: state.variant.id,
127
+ })
128
+ if (accent) Object.assign(properties, accent)
129
+ // Written after the variant and the accent, so it wins over the interface
130
+ // variant's own terminal ramp (and over the stylesheet's, which it outranks as an
131
+ // inline declaration). Dropping the pin needs no removal of its own: the ramp is
132
+ // then rewritten by the variant, or swept back to the stylesheet.
133
+ if (state.terminalVariant) Object.assign(properties, terminalCssVariables(state.terminalVariant))
134
+ if (state.surface) properties['--surface-alpha'] = surfaceAlpha[state.surface.surface]
135
+ return {
136
+ tokens: options.transformTokens ? options.transformTokens(properties) : properties,
137
+ accented: accent !== undefined,
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Write a state onto a root and report which custom properties it owns.
143
+ *
144
+ * Pass the previous call's return as `previous` and any palette property that pass
145
+ * wrote and this one does not is removed. For the catalogue the two sets are the
146
+ * same, so nothing is ever removed; it matters for a host variant that carries
147
+ * `tokens` the catalogue does not, which would otherwise stay inline, beating the
148
+ * stylesheet, after the user switched away from it.
149
+ */
150
+ export function applyThemeToRootWith(
151
+ engine: ThemeEngine,
152
+ root: ThemeRoot,
153
+ state: ThemeDocumentState,
154
+ options: ThemeDocumentOptions = {},
155
+ previous?: ReadonlySet<string>
156
+ ): ReadonlySet<string> {
157
+ const { tokens, accented } = themeDocumentTokensWith(engine, state, options)
158
+
159
+ for (const [name, value] of Object.entries(tokens)) {
160
+ root.style.setProperty(name, value)
161
+ }
162
+ if (previous) {
163
+ for (const name of previous) {
164
+ if (!(name in tokens)) root.style.removeProperty(name)
165
+ }
166
+ }
167
+
168
+ root.classList.toggle('dark', state.appearance === 'dark')
169
+ root.dataset['theme'] = state.variant.id
170
+ root.dataset['appearance'] = state.appearance
171
+ root.style.colorScheme = state.appearance
172
+
173
+ // A custom colour has no stylesheet block to select, and its value is the
174
+ // attribute's worst possible content, so it is reported as `custom`.
175
+ if (!accented) root.removeAttribute('data-accent')
176
+ else
177
+ root.dataset['accent'] = isCustomAccent(state.selection.accent)
178
+ ? 'custom'
179
+ : state.selection.accent
180
+
181
+ // The surface axis is opt-in, so its absence removes rather than defaults: a host
182
+ // that never enables it keeps an `<html>` with neither attribute, and one that
183
+ // turns it off leaves nothing behind. (`--surface-alpha` is swept with the tokens.)
184
+ if (state.surface) {
185
+ root.dataset['surface'] = state.surface.surface
186
+ root.dataset['reduceTransparency'] = state.surface.reduceTransparencyActive ? 'true' : 'false'
187
+ } else {
188
+ root.removeAttribute('data-surface')
189
+ root.removeAttribute('data-reduce-transparency')
190
+ }
191
+
192
+ applyAppearanceFontSettings(
193
+ root,
194
+ normalizeAppearanceEditorFontSettings(state.selection.fonts, state.selection.font).settings
195
+ )
196
+
197
+ // Density is a pure token shift, so unlike the colour axes there is nothing to
198
+ // compute: the attribute selects a block in `theme.css`. It is applied here for
199
+ // the same reason as the others — this is the one place that knows what the
200
+ // current selection is, and it is the only writer of these attributes.
201
+ if (resolvedDensity(state.selection) === 'comfortable') root.removeAttribute('data-density')
202
+ else root.dataset['density'] = resolvedDensity(state.selection)
203
+
204
+ return new Set(Object.keys(tokens))
205
+ }
206
+
207
+ /** What {@link applyWithoutTransitions} needs of a document. */
208
+ export type TransitionDocument = {
209
+ head: { appendChild(node: unknown): unknown }
210
+ createElement(tag: 'style'): {
211
+ textContent: string | null
212
+ offsetHeight?: number
213
+ remove(): void
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Run `apply` with every CSS transition switched off for the frame that carries
219
+ * it, so a theme switch cannot animate page-wide.
220
+ *
221
+ * Without this, flipping the custom properties on `<html>` starts a transition on
222
+ * every element that declares one, and each fades from the old palette to the new
223
+ * over its own duration: a theme change that looks like a rendering fault. The
224
+ * rule is removed two frames later, after the browser has committed the new styles
225
+ * with transitions off, and `restore` runs it early (the provider calls it when it
226
+ * is disposed mid-switch).
227
+ *
228
+ * `schedule` is the frame source; it defaults to `requestAnimationFrame` and falls
229
+ * back to running at once where there is none, which leaves transitions on rather
230
+ * than off.
231
+ */
232
+ export function applyWithoutTransitions(
233
+ doc: TransitionDocument,
234
+ apply: () => void,
235
+ schedule: (callback: () => void) => void = defaultSchedule
236
+ ): () => void {
237
+ const style = doc.createElement('style')
238
+ style.textContent = '*{transition:none !important}'
239
+ doc.head.appendChild(style)
240
+ // Reading a layout property forces the browser to commit the rule before the
241
+ // properties change; without it the two can be batched into one style recalc.
242
+ void style.offsetHeight
243
+ let removed = false
244
+ const restore = () => {
245
+ if (removed) return
246
+ removed = true
247
+ style.remove()
248
+ }
249
+ try {
250
+ apply()
251
+ } catch (error) {
252
+ restore()
253
+ throw error
254
+ }
255
+ schedule(() => schedule(restore))
256
+ return restore
257
+ }
258
+
259
+ function defaultSchedule(callback: () => void): void {
260
+ if (typeof requestAnimationFrame === 'function') requestAnimationFrame(callback)
261
+ else callback()
262
+ }