@wtfalch/design 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +147 -0
  3. package/dist/components/Brand.d.ts +10 -0
  4. package/dist/components/Brand.js +212 -0
  5. package/dist/components/Button.d.ts +63 -0
  6. package/dist/components/Button.js +74 -0
  7. package/dist/components/Callout.d.ts +37 -0
  8. package/dist/components/Callout.js +71 -0
  9. package/dist/components/Card.d.ts +42 -0
  10. package/dist/components/Card.js +30 -0
  11. package/dist/components/Checkbox.d.ts +32 -0
  12. package/dist/components/Checkbox.js +31 -0
  13. package/dist/components/DangerZone.d.ts +59 -0
  14. package/dist/components/DangerZone.js +50 -0
  15. package/dist/components/Dialog.d.ts +28 -0
  16. package/dist/components/Dialog.js +29 -0
  17. package/dist/components/Empty.d.ts +45 -0
  18. package/dist/components/Empty.js +35 -0
  19. package/dist/components/Field.d.ts +58 -0
  20. package/dist/components/Field.js +46 -0
  21. package/dist/components/Icon.d.ts +64 -0
  22. package/dist/components/Icon.js +235 -0
  23. package/dist/components/Illustration.d.ts +36 -0
  24. package/dist/components/Illustration.js +48 -0
  25. package/dist/components/Input.d.ts +14 -0
  26. package/dist/components/Input.js +65 -0
  27. package/dist/components/Markdown.d.ts +21 -0
  28. package/dist/components/Markdown.js +29 -0
  29. package/dist/components/Modal.d.ts +59 -0
  30. package/dist/components/Modal.js +72 -0
  31. package/dist/components/Pill.d.ts +40 -0
  32. package/dist/components/Pill.js +41 -0
  33. package/dist/components/Progress.d.ts +35 -0
  34. package/dist/components/Progress.js +27 -0
  35. package/dist/components/Rows.d.ts +101 -0
  36. package/dist/components/Rows.js +55 -0
  37. package/dist/components/Select.d.ts +28 -0
  38. package/dist/components/Select.js +56 -0
  39. package/dist/components/SizeGrid.d.ts +34 -0
  40. package/dist/components/SizeGrid.js +41 -0
  41. package/dist/components/Skeleton.d.ts +45 -0
  42. package/dist/components/Skeleton.js +47 -0
  43. package/dist/components/Slider.d.ts +70 -0
  44. package/dist/components/Slider.js +100 -0
  45. package/dist/components/Table.d.ts +43 -0
  46. package/dist/components/Table.js +13 -0
  47. package/dist/components/Tabs.d.ts +72 -0
  48. package/dist/components/Tabs.js +82 -0
  49. package/dist/components/Textarea.d.ts +9 -0
  50. package/dist/components/Textarea.js +22 -0
  51. package/dist/components/Toast.d.ts +43 -0
  52. package/dist/components/Toast.js +78 -0
  53. package/dist/components/Toggle.d.ts +56 -0
  54. package/dist/components/Toggle.js +189 -0
  55. package/dist/components/Tooltip.d.ts +22 -0
  56. package/dist/components/Tooltip.js +62 -0
  57. package/dist/components/Tour.d.ts +33 -0
  58. package/dist/components/Tour.js +108 -0
  59. package/dist/components/iconNames.d.ts +18 -0
  60. package/dist/components/iconNames.js +60 -0
  61. package/dist/components/tourMarker.d.ts +29 -0
  62. package/dist/components/tourMarker.js +58 -0
  63. package/dist/contrast.d.ts +18 -0
  64. package/dist/contrast.js +27 -0
  65. package/dist/hooks/useTrapFocus.d.ts +24 -0
  66. package/dist/hooks/useTrapFocus.js +67 -0
  67. package/dist/illustrations.d.ts +11 -0
  68. package/dist/illustrations.js +55 -0
  69. package/dist/index.d.ts +72 -0
  70. package/dist/index.js +65 -0
  71. package/dist/styles/index.css +3124 -0
  72. package/dist/themes.d.ts +210 -0
  73. package/dist/themes.js +300 -0
  74. package/dist/tokens.css +251 -0
  75. package/package.json +74 -0
@@ -0,0 +1,210 @@
1
+ /**
2
+ * The themes, and the vocabulary they are allowed to speak.
3
+ *
4
+ * A theme is a `Partial<ThemeTokens>`: name the tokens you change, inherit the
5
+ * rest from `tokens.css`. Without that, every theme is a forty-six line chore
6
+ * and every token added later means editing all of them.
7
+ *
8
+ * The type is the enforcement. A typo in a token name is a compile error rather
9
+ * than a property that silently does nothing — which is exactly the failure a
10
+ * string-keyed map would produce, at run time, invisibly. That property is the
11
+ * reason this file is exported from the package at all: a consumer writing a
12
+ * theme gets the same compile error tf gets, and a theme that ships as JSON or
13
+ * as a hand-written CSS file does not.
14
+ *
15
+ * ---
16
+ *
17
+ * **A token is themeable, derived, or fixed, and the three are separate lists.**
18
+ *
19
+ * The old contract had one list, and it had drifted from the stylesheet in both
20
+ * directions: it declared `--pad-1`…`--pad-5`, which `tokens.css` stopped
21
+ * defining when the spacing scale was renamed to `--space-*`, so five keys a
22
+ * theme could name resolved to nothing at all. And it named none of the sixteen
23
+ * derived tokens, so there was no way to tell a token deliberately withheld
24
+ * from a token somebody forgot.
25
+ *
26
+ * Splitting the list is what makes that a decision rather than an omission:
27
+ *
28
+ * - **themeable** — a theme may set it. Forty-six of them.
29
+ * - **derived** — computed from a themeable token, and a theme must NOT set it.
30
+ * `--text-*` are `calc()` off `--font-size` and `--space-*` are `calc()` off
31
+ * `--density`, so overriding one step with a literal is how you get a type
32
+ * scale that no longer scales. Move the input, not the output.
33
+ * - **fixed** — not themeable at all. Geometry other things are measured
34
+ * against, and one glyph.
35
+ *
36
+ * `tokens.test.ts` holds all three to `tokens.css` in both directions, so a
37
+ * token added to the stylesheet and to no list fails the suite rather than
38
+ * becoming a fourth, undocumented category.
39
+ */
40
+ /** Every token a theme may set. */
41
+ export interface ThemeTokens {
42
+ '--bg': string;
43
+ '--panel': string;
44
+ '--panel-2': string;
45
+ '--border': string;
46
+ /** A control's outline, which WCAG 1.4.11 wants at 3:1 — a different job
47
+ * from `--border`, which draws hairline dividers and has no minimum. They
48
+ * shared a value once and it could only ever be right for one of them. */
49
+ '--border-strong': string;
50
+ '--text': string;
51
+ '--muted': string;
52
+ '--accent': string;
53
+ '--accent-dim': string;
54
+ '--good': string;
55
+ '--warn': string;
56
+ '--bad': string;
57
+ '--info': string;
58
+ /** Text that sits *on* the accent. Never assume white: on a pale accent a
59
+ * primary button vanishes into its own background. */
60
+ '--on-accent': string;
61
+ /** Consumed as `background:`, never `background-color:`, so a theme may hand
62
+ * it a gradient or an image rather than only a colour. */
63
+ '--app-bg': string;
64
+ /** A slot. Inert unless a theme fills it — this is how a theme gets a grain,
65
+ * a vignette or a wash without shipping a rule to create the layer. */
66
+ '--app-overlay': string;
67
+ '--app-overlay-opacity': string;
68
+ /** The surface a control sits on. Derived from the panel tones by default,
69
+ * and settable, the same bargain `--app-bg` makes. */
70
+ '--control': string;
71
+ /** The paper an illustration is drawn on; its ink takes `currentColor`. */
72
+ '--illo-paper': string;
73
+ '--shadow-1': string;
74
+ '--shadow-2': string;
75
+ '--shadow-3': string;
76
+ '--scrim': string;
77
+ '--font': string;
78
+ '--font-mono': string;
79
+ /** The one input to the type scale. Every `--text-*` step is `calc()` off
80
+ * this, so moving it moves all seven together. */
81
+ '--font-size': string;
82
+ '--line-height': string;
83
+ '--tracking': string;
84
+ '--weight': string;
85
+ '--weight-strong': string;
86
+ '--radius-sm': string;
87
+ '--radius': string;
88
+ '--radius-md': string;
89
+ '--radius-lg': string;
90
+ '--radius-pill': string;
91
+ '--border-width': string;
92
+ /** The one input to the spacing scale. Every `--space-*` step is `calc()`
93
+ * off this, so a theme can be roomy or tight without restating a single
94
+ * padding. */
95
+ '--density': string;
96
+ '--dur-fast': string;
97
+ '--dur-md': string;
98
+ '--dur-slow': string;
99
+ '--dur-drag': string;
100
+ '--ease': string;
101
+ '--ease-out': string;
102
+ '--ease-spring': string;
103
+ '--hover-lift': string;
104
+ '--hover-scale': string;
105
+ '--press-scale': string;
106
+ /** How much darker a control goes while pressed: the share of `--text` mixed
107
+ * into its hover colour. One number, so a button, a row and a tab press the
108
+ * same amount. */
109
+ '--press-ink': string;
110
+ '--focus-ring': string;
111
+ }
112
+ /**
113
+ * Computed from a themeable token, and not settable.
114
+ *
115
+ * A theme that sets `--text-lg` directly gets a scale with a step that no
116
+ * longer moves when `--font-size` does, which is the same as having no scale.
117
+ * Move `--font-size` and `--density`; these follow.
118
+ */
119
+ export declare const DERIVED_TOKENS: readonly ["--text-2xs", "--text-xs", "--text-sm", "--text-base", "--text-md", "--text-lg", "--text-xl", "--space-1", "--space-2", "--space-3", "--space-4", "--space-5", "--space-6", "--space-8", "--space-10", "--space-15"];
120
+ /**
121
+ * Not themeable, and each for its own reason.
122
+ *
123
+ * `--tile-control` is geometry other things are measured against — a panel head
124
+ * without a cog has to be the same height as one with, or the content below it
125
+ * gets a different remainder.
126
+ *
127
+ * `--nudge` is optical alignment, not spacing: a 2px offset that nudges a glyph
128
+ * off a baseline does not scale with density, because the glyph did not.
129
+ *
130
+ * `--tick-mask` is the checkmark, drawn once as a mask so whatever wears it
131
+ * supplies its own colour. It stays fixed because an arbitrary SVG data URI
132
+ * from a theme is a theme shipping markup, which is the one thing the whole
133
+ * design refuses.
134
+ */
135
+ export declare const FIXED_TOKENS: readonly ["--tile-control", "--nudge", "--tick-mask"];
136
+ /**
137
+ * The themeable vocabulary, at run time.
138
+ *
139
+ * `ThemeTokens` is a type and disappears at compile time, but applying a theme
140
+ * — or sending one into an applet's frame — means enumerating the keys with the
141
+ * program running. The check below keeps the two from drifting: adding a token
142
+ * to the interface without adding it here is a compile error, which is the same
143
+ * bargain the interface itself makes.
144
+ */
145
+ export declare const TOKEN_KEYS: readonly ["--bg", "--panel", "--panel-2", "--border", "--border-strong", "--text", "--muted", "--accent", "--accent-dim", "--good", "--warn", "--bad", "--info", "--on-accent", "--app-bg", "--app-overlay", "--app-overlay-opacity", "--control", "--illo-paper", "--shadow-1", "--shadow-2", "--shadow-3", "--scrim", "--font", "--font-mono", "--font-size", "--line-height", "--tracking", "--weight", "--weight-strong", "--radius-sm", "--radius", "--radius-md", "--radius-lg", "--radius-pill", "--border-width", "--density", "--dur-fast", "--dur-md", "--dur-slow", "--dur-drag", "--ease", "--ease-out", "--ease-spring", "--hover-lift", "--hover-scale", "--press-scale", "--press-ink", "--focus-ring"];
146
+ export interface Theme {
147
+ /** What the picker calls it. */
148
+ name: string;
149
+ /** One line, shown under the name. */
150
+ note: string;
151
+ /**
152
+ * Whether the palette reads light or dark, for `color-scheme`.
153
+ *
154
+ * Native controls and scrollbars follow this. Getting it wrong gives you a
155
+ * white scrollbar down a black page, and there is no other signal that tells
156
+ * the browser which way round the page is.
157
+ */
158
+ scheme: 'light' | 'dark';
159
+ tokens: Partial<ThemeTokens>;
160
+ }
161
+ /**
162
+ * Declare a theme.
163
+ *
164
+ * The only thing this adds over an object literal is that the literal is
165
+ * checked *at the point it is written* rather than wherever it is first used —
166
+ * so a consumer gets the red squiggle under the typo, not under the import.
167
+ */
168
+ export declare function defineTheme(theme: Theme): Theme;
169
+ /**
170
+ * The built-ins, which are **examples and not the menu**.
171
+ *
172
+ * tf ships these four; an app that installs this package is expected to bring
173
+ * its own and is not expected to look like tf. They are here because a package
174
+ * that ships a token vocabulary and no theme written in it leaves the first
175
+ * consumer guessing at how wide the vocabulary really is — and because the
176
+ * contrast test needs something concrete to measure.
177
+ *
178
+ * `system` is a theme, not a mode. A `prefers-color-scheme` block that
179
+ * overrides `:root` unconditionally means choosing a dark theme on a
180
+ * light-mode laptop gets silently repainted; that media query is scoped to this
181
+ * theme, so following the OS is a choice among the others rather than a rule
182
+ * above them.
183
+ */
184
+ export declare const THEMES: Record<string, Theme>;
185
+ export declare const DEFAULT_THEME = "system";
186
+ export declare function isTheme(name: unknown): name is string;
187
+ /**
188
+ * Put a theme on an element.
189
+ *
190
+ * **The element is an argument, and that is the whole difference between this
191
+ * and the version that lived in the app.** tf owns its document, so writing to
192
+ * `documentElement` was free. A package does not: mounted inside a page it did
193
+ * not build, it has to be able to theme a subtree and leave the rest alone.
194
+ * Defaulting to `documentElement` keeps the app case a no-op.
195
+ *
196
+ * Every themeable key is written, including the ones the theme did not name —
197
+ * cleared to `''` so they fall back to `tokens.css`. Setting only what a theme
198
+ * declares leaves the *previous* theme's values behind on every key it happens
199
+ * not to mention, which reads as two themes at once and is very hard to see.
200
+ */
201
+ export declare function applyTheme(theme: string | Theme, el?: HTMLElement): void;
202
+ /**
203
+ * The theme as *values*, resolved against the document.
204
+ *
205
+ * A theme is a sparse map, so reading it tells you what it overrode rather than
206
+ * what is on screen. Anything that has to send the palette somewhere it cannot
207
+ * follow a CSS variable — an applet's canvas, a chart library, a screenshot
208
+ * runner — needs the resolved set instead.
209
+ */
210
+ export declare function resolvedTokens(el?: HTMLElement): Partial<ThemeTokens>;
package/dist/themes.js ADDED
@@ -0,0 +1,300 @@
1
+ /**
2
+ * The themes, and the vocabulary they are allowed to speak.
3
+ *
4
+ * A theme is a `Partial<ThemeTokens>`: name the tokens you change, inherit the
5
+ * rest from `tokens.css`. Without that, every theme is a forty-six line chore
6
+ * and every token added later means editing all of them.
7
+ *
8
+ * The type is the enforcement. A typo in a token name is a compile error rather
9
+ * than a property that silently does nothing — which is exactly the failure a
10
+ * string-keyed map would produce, at run time, invisibly. That property is the
11
+ * reason this file is exported from the package at all: a consumer writing a
12
+ * theme gets the same compile error tf gets, and a theme that ships as JSON or
13
+ * as a hand-written CSS file does not.
14
+ *
15
+ * ---
16
+ *
17
+ * **A token is themeable, derived, or fixed, and the three are separate lists.**
18
+ *
19
+ * The old contract had one list, and it had drifted from the stylesheet in both
20
+ * directions: it declared `--pad-1`…`--pad-5`, which `tokens.css` stopped
21
+ * defining when the spacing scale was renamed to `--space-*`, so five keys a
22
+ * theme could name resolved to nothing at all. And it named none of the sixteen
23
+ * derived tokens, so there was no way to tell a token deliberately withheld
24
+ * from a token somebody forgot.
25
+ *
26
+ * Splitting the list is what makes that a decision rather than an omission:
27
+ *
28
+ * - **themeable** — a theme may set it. Forty-six of them.
29
+ * - **derived** — computed from a themeable token, and a theme must NOT set it.
30
+ * `--text-*` are `calc()` off `--font-size` and `--space-*` are `calc()` off
31
+ * `--density`, so overriding one step with a literal is how you get a type
32
+ * scale that no longer scales. Move the input, not the output.
33
+ * - **fixed** — not themeable at all. Geometry other things are measured
34
+ * against, and one glyph.
35
+ *
36
+ * `tokens.test.ts` holds all three to `tokens.css` in both directions, so a
37
+ * token added to the stylesheet and to no list fails the suite rather than
38
+ * becoming a fourth, undocumented category.
39
+ */
40
+ /**
41
+ * Computed from a themeable token, and not settable.
42
+ *
43
+ * A theme that sets `--text-lg` directly gets a scale with a step that no
44
+ * longer moves when `--font-size` does, which is the same as having no scale.
45
+ * Move `--font-size` and `--density`; these follow.
46
+ */
47
+ export const DERIVED_TOKENS = [
48
+ '--text-2xs',
49
+ '--text-xs',
50
+ '--text-sm',
51
+ '--text-base',
52
+ '--text-md',
53
+ '--text-lg',
54
+ '--text-xl',
55
+ '--space-1',
56
+ '--space-2',
57
+ '--space-3',
58
+ '--space-4',
59
+ '--space-5',
60
+ '--space-6',
61
+ '--space-8',
62
+ '--space-10',
63
+ '--space-15',
64
+ ];
65
+ /**
66
+ * Not themeable, and each for its own reason.
67
+ *
68
+ * `--tile-control` is geometry other things are measured against — a panel head
69
+ * without a cog has to be the same height as one with, or the content below it
70
+ * gets a different remainder.
71
+ *
72
+ * `--nudge` is optical alignment, not spacing: a 2px offset that nudges a glyph
73
+ * off a baseline does not scale with density, because the glyph did not.
74
+ *
75
+ * `--tick-mask` is the checkmark, drawn once as a mask so whatever wears it
76
+ * supplies its own colour. It stays fixed because an arbitrary SVG data URI
77
+ * from a theme is a theme shipping markup, which is the one thing the whole
78
+ * design refuses.
79
+ */
80
+ export const FIXED_TOKENS = ['--tile-control', '--nudge', '--tick-mask'];
81
+ /**
82
+ * The themeable vocabulary, at run time.
83
+ *
84
+ * `ThemeTokens` is a type and disappears at compile time, but applying a theme
85
+ * — or sending one into an applet's frame — means enumerating the keys with the
86
+ * program running. The check below keeps the two from drifting: adding a token
87
+ * to the interface without adding it here is a compile error, which is the same
88
+ * bargain the interface itself makes.
89
+ */
90
+ export const TOKEN_KEYS = [
91
+ '--bg',
92
+ '--panel',
93
+ '--panel-2',
94
+ '--border',
95
+ '--border-strong',
96
+ '--text',
97
+ '--muted',
98
+ '--accent',
99
+ '--accent-dim',
100
+ '--good',
101
+ '--warn',
102
+ '--bad',
103
+ '--info',
104
+ '--on-accent',
105
+ '--app-bg',
106
+ '--app-overlay',
107
+ '--app-overlay-opacity',
108
+ '--control',
109
+ '--illo-paper',
110
+ '--shadow-1',
111
+ '--shadow-2',
112
+ '--shadow-3',
113
+ '--scrim',
114
+ '--font',
115
+ '--font-mono',
116
+ '--font-size',
117
+ '--line-height',
118
+ '--tracking',
119
+ '--weight',
120
+ '--weight-strong',
121
+ '--radius-sm',
122
+ '--radius',
123
+ '--radius-md',
124
+ '--radius-lg',
125
+ '--radius-pill',
126
+ '--border-width',
127
+ '--density',
128
+ '--dur-fast',
129
+ '--dur-md',
130
+ '--dur-slow',
131
+ '--dur-drag',
132
+ '--ease',
133
+ '--ease-out',
134
+ '--ease-spring',
135
+ '--hover-lift',
136
+ '--hover-scale',
137
+ '--press-scale',
138
+ '--press-ink',
139
+ '--focus-ring',
140
+ ];
141
+ // Compile error if a token exists in the type and not in the list above.
142
+ const _covers = true;
143
+ void _covers;
144
+ // And the other direction, which the old check did not have: a key in the list
145
+ // that the type does not declare.
146
+ const _exact = true;
147
+ void _exact;
148
+ /**
149
+ * Declare a theme.
150
+ *
151
+ * The only thing this adds over an object literal is that the literal is
152
+ * checked *at the point it is written* rather than wherever it is first used —
153
+ * so a consumer gets the red squiggle under the typo, not under the import.
154
+ */
155
+ export function defineTheme(theme) {
156
+ return theme;
157
+ }
158
+ /**
159
+ * The dark palette, written out rather than inherited.
160
+ *
161
+ * `night` was `tokens: {}` once — "whatever the base is" — and that was wrong
162
+ * twice over. It made the theme depend on a file it does not own, and it made
163
+ * its swatch in the picker preview *the theme currently applied*, because an
164
+ * empty map falls back to the live values.
165
+ */
166
+ const NIGHT = {
167
+ '--bg': '#0f1115',
168
+ '--panel': '#161a21',
169
+ '--panel-2': '#1c222b',
170
+ '--border': '#262d38',
171
+ '--text': '#e6e9ef',
172
+ '--muted': '#8b94a4',
173
+ '--accent': '#5b9dff',
174
+ '--accent-dim': '#2a4877',
175
+ /* No `--on-accent` here on purpose, so it inherits the base near-black.
176
+ Night used to set `#ffffff`, which is 2.72:1 on this accent -- the exact
177
+ pair `tokens.css` records as the reason the token exists at all. The base
178
+ was fixed and the theme carrying the old palette was never revisited, so
179
+ the primary button in the app's fixed dark theme failed the body-text
180
+ minimum by a wide margin for as long as the token had been "fixed".
181
+ `#06181a` on `#5b9dff` is 6.69:1. Found by the contrast scan, which is the
182
+ argument for shipping the measurement rather than the rule. */
183
+ '--app-bg': '#0f1115',
184
+ };
185
+ /**
186
+ * The built-ins, which are **examples and not the menu**.
187
+ *
188
+ * tf ships these four; an app that installs this package is expected to bring
189
+ * its own and is not expected to look like tf. They are here because a package
190
+ * that ships a token vocabulary and no theme written in it leaves the first
191
+ * consumer guessing at how wide the vocabulary really is — and because the
192
+ * contrast test needs something concrete to measure.
193
+ *
194
+ * `system` is a theme, not a mode. A `prefers-color-scheme` block that
195
+ * overrides `:root` unconditionally means choosing a dark theme on a
196
+ * light-mode laptop gets silently repainted; that media query is scoped to this
197
+ * theme, so following the OS is a choice among the others rather than a rule
198
+ * above them.
199
+ */
200
+ export const THEMES = {
201
+ system: {
202
+ name: 'System',
203
+ note: 'Follows your OS between light and dark',
204
+ scheme: 'dark',
205
+ // Empty on purpose: this is the one theme that must *not* state a palette,
206
+ // because the `prefers-color-scheme` block is scoped to it and needs the
207
+ // base values to fall through. Its swatch is a special case.
208
+ tokens: {},
209
+ },
210
+ night: {
211
+ name: 'Night',
212
+ note: 'The dark palette, fixed — ignores the OS',
213
+ scheme: 'dark',
214
+ tokens: NIGHT,
215
+ },
216
+ paper: {
217
+ name: 'Paper',
218
+ note: 'Light, with shadows that suit it',
219
+ scheme: 'light',
220
+ tokens: {
221
+ '--bg': '#f6f7f9',
222
+ '--panel': '#ffffff',
223
+ '--panel-2': '#f0f2f5',
224
+ '--border': '#e4e8ec',
225
+ '--border-strong': '#8792a1',
226
+ '--text': '#191d23',
227
+ '--muted': '#5d6773',
228
+ '--accent': '#0e7872',
229
+ '--accent-dim': '#7fbdb8',
230
+ '--on-accent': '#ffffff',
231
+ // The base set is tuned for a dark panel. Unoverridden, `--good` was
232
+ // 1.74:1 on white -- a `running` pill nobody could read.
233
+ '--good': '#1c7a4a',
234
+ '--warn': '#8a6216',
235
+ '--bad': '#b3312c',
236
+ // `#2f7fe6` was 3.96:1 on this theme's white panel -- the "information is
237
+ // blue" colour was measured on the dark base when it was added and never
238
+ // on a light one. Found by contrast.test.ts on its first run. This is the
239
+ // lightest step on the same hue that clears 4.5:1 on the panel *and* the
240
+ // page, with room: 5.24 on white, 4.88 on the page.
241
+ '--info': '#216bc9',
242
+ // Black shadows are right on a dark UI and muddy on a light one. This is
243
+ // the whole reason elevation had to become a token.
244
+ '--shadow-1': '0 4px 14px rgba(16, 24, 40, 0.08)',
245
+ '--shadow-2': '0 8px 24px rgba(16, 24, 40, 0.10)',
246
+ '--shadow-3': '0 12px 32px rgba(16, 24, 40, 0.12)',
247
+ '--scrim': 'rgba(16, 24, 40, 0.32)',
248
+ },
249
+ },
250
+ };
251
+ export const DEFAULT_THEME = 'system';
252
+ export function isTheme(name) {
253
+ return typeof name === 'string' && name in THEMES;
254
+ }
255
+ /**
256
+ * Put a theme on an element.
257
+ *
258
+ * **The element is an argument, and that is the whole difference between this
259
+ * and the version that lived in the app.** tf owns its document, so writing to
260
+ * `documentElement` was free. A package does not: mounted inside a page it did
261
+ * not build, it has to be able to theme a subtree and leave the rest alone.
262
+ * Defaulting to `documentElement` keeps the app case a no-op.
263
+ *
264
+ * Every themeable key is written, including the ones the theme did not name —
265
+ * cleared to `''` so they fall back to `tokens.css`. Setting only what a theme
266
+ * declares leaves the *previous* theme's values behind on every key it happens
267
+ * not to mention, which reads as two themes at once and is very hard to see.
268
+ */
269
+ export function applyTheme(theme, el = document.documentElement) {
270
+ /* A registered name, or a `Theme` object straight from `defineTheme`.
271
+ The second is how an app that is not tf applies its own palette without
272
+ first pushing it into a registry it does not own -- `THEMES` is the
273
+ built-ins, and a consumer's theme is theirs. */
274
+ const resolved = typeof theme === 'string' ? (THEMES[theme] ?? THEMES[DEFAULT_THEME]) : theme;
275
+ const name = typeof theme === 'string' ? theme : resolved.name.toLowerCase().replace(/\s+/g, '-');
276
+ for (const key of TOKEN_KEYS) {
277
+ const value = resolved.tokens[key];
278
+ if (value === undefined)
279
+ el.style.removeProperty(key);
280
+ else
281
+ el.style.setProperty(key, value);
282
+ }
283
+ el.dataset.theme = name;
284
+ el.style.colorScheme = resolved.scheme;
285
+ }
286
+ /**
287
+ * The theme as *values*, resolved against the document.
288
+ *
289
+ * A theme is a sparse map, so reading it tells you what it overrode rather than
290
+ * what is on screen. Anything that has to send the palette somewhere it cannot
291
+ * follow a CSS variable — an applet's canvas, a chart library, a screenshot
292
+ * runner — needs the resolved set instead.
293
+ */
294
+ export function resolvedTokens(el = document.documentElement) {
295
+ const style = getComputedStyle(el);
296
+ const out = {};
297
+ for (const key of TOKEN_KEYS)
298
+ out[key] = style.getPropertyValue(key).trim();
299
+ return out;
300
+ }