@fusion.dev/helix-docs 0.111.2 → 0.113.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/content/intent-reference.mdx +5 -2
- package/content/maps.mdx +49 -0
- package/content/metrics.mdx +6 -6
- package/content/theming.mdx +49 -10
- package/dist/sources.generated.js +109 -18
- package/package.json +10 -4
|
@@ -10,11 +10,14 @@ title: Intent reference
|
|
|
10
10
|
every field `ThemeIntent` accepts and what changes when you set it.
|
|
11
11
|
|
|
12
12
|
```ts
|
|
13
|
-
import {
|
|
13
|
+
import { initTheme } from '@fusion.dev/helix-theme'
|
|
14
14
|
|
|
15
|
-
const
|
|
15
|
+
const engine = await initTheme('/theme.wasm')
|
|
16
|
+
const theme = engine.derive({ brand: '#2563eb' })
|
|
16
17
|
```
|
|
17
18
|
|
|
19
|
+
[Deriving a theme](./theming) shows how Node loads the engine.
|
|
20
|
+
|
|
18
21
|
**`brand` is the only required input.** Every other field has a default,
|
|
19
22
|
computed from `brand` or from the Helix design language. A theme that sets only
|
|
20
23
|
`brand` is a valid theme.
|
package/content/maps.mdx
CHANGED
|
@@ -106,6 +106,19 @@ It also draws landmarks, highway signs, and neighborhood names:
|
|
|
106
106
|
roads.
|
|
107
107
|
- Neighborhoods are in spaced capitals, quieter than the towns.
|
|
108
108
|
|
|
109
|
+
Pass `landmarks` to choose which kinds the map draws. `false` draws none, for a
|
|
110
|
+
map whose own markers are the places a reader is looking for. A list draws only
|
|
111
|
+
those kinds:
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
mapLibreStyle(colors, { landmarks: ['nature', 'transit'] })
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The kinds are `culture`, `food`, `health`, `learning`, `lodging`, `nature`,
|
|
118
|
+
`shopping`, `sport`, and `transit`, and `MAP_LANDMARKS` lists them. The option
|
|
119
|
+
holds when `followTheme` restyles the map, because it builds the style from the
|
|
120
|
+
same options.
|
|
121
|
+
|
|
109
122
|
The same call builds a light map for a light theme and a dark map for a dark
|
|
110
123
|
theme. Map labels meet a contrast target of 4.5 against the land. Pass
|
|
111
124
|
`contrast` to change it, `language` to choose which name each place is labeled
|
|
@@ -139,6 +152,42 @@ changes its theme another way, such as by replacing a stylesheet, call
|
|
|
139
152
|
a frame or two after the page loads. If the colors never become available, it
|
|
140
153
|
reports which one is missing.
|
|
141
154
|
|
|
155
|
+
## Put a map behind your content
|
|
156
|
+
|
|
157
|
+
Set `fill` to lay a map behind your page's own content, such as a hero whose
|
|
158
|
+
words decide its height. The frame covers its container instead of taking a
|
|
159
|
+
height of its own, and draws no border or corners, because the container's edge
|
|
160
|
+
is the map's. Give the container a position, such as `position: relative`:
|
|
161
|
+
|
|
162
|
+
```html
|
|
163
|
+
<section class="hero">
|
|
164
|
+
<helix-map fill credit-placement="bottom-end" label="Downtown Austin">
|
|
165
|
+
<div id="map"></div>
|
|
166
|
+
</helix-map>
|
|
167
|
+
<div class="hero-words">
|
|
168
|
+
<h1>Every stop, one map</h1>
|
|
169
|
+
</div>
|
|
170
|
+
</section>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
```css
|
|
174
|
+
.hero,
|
|
175
|
+
.hero-words {
|
|
176
|
+
position: relative;
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Content after the map, with a position of its own, is drawn over it. Give that
|
|
181
|
+
content a ground of its own, such as a translucent panel, so it reads over any
|
|
182
|
+
part of the map.
|
|
183
|
+
|
|
184
|
+
`credit-placement` draws the map's credit in a corner over the map instead of in
|
|
185
|
+
a strip under it. Use `bottom-end` or `bottom-start`, and keep it away from the
|
|
186
|
+
corner your controls are in. The credit is one line until it is pointed at or
|
|
187
|
+
its links are focused, and then it shows in full. That compact form is the one
|
|
188
|
+
OpenStreetMap's attribution guidance accepts for a small credit. Most tile
|
|
189
|
+
licenses require the credit to stay visible, so Helix never hides it.
|
|
190
|
+
|
|
142
191
|
## Draw the map before it loads
|
|
143
192
|
|
|
144
193
|
`mapSnapshot(view)` draws the map on your server as an SVG, from the same tiles
|
package/content/metrics.mdx
CHANGED
|
@@ -60,8 +60,8 @@ either barrel. The per-component tables show what your page pays.
|
|
|
60
60
|
| `@fusion.dev/helix-core` | 177,056 | 56,663 | 48,189 | The element base class, mixins, decorators, and floating positioning. |
|
|
61
61
|
| `@fusion.dev/helix-headless` | 726,171 | 188,118 | 146,385 | Every headless family. The barrel entry includes all of them. |
|
|
62
62
|
| `@fusion.dev/helix-icons` | 55,565 | 17,727 | 14,864 | The whole glyph set. |
|
|
63
|
-
| `@fusion.dev/helix-theme` | 3,
|
|
64
|
-
| `@fusion.dev/helix` | 1,
|
|
63
|
+
| `@fusion.dev/helix-theme` | 3,626 | 1,597 | 1,384 | The derivation engine. Most sites run it at build time, and a theme switcher runs it in the browser. |
|
|
64
|
+
| `@fusion.dev/helix` | 1,259,099 | 294,668 | 216,376 | Every styled family. |
|
|
65
65
|
| `lit` | 15,394 | 6,118 | 5,460 | Lit itself, listed so you can subtract it from every row above. |
|
|
66
66
|
|
|
67
67
|
## Stylesheets
|
|
@@ -84,7 +84,7 @@ shows the whole cost of using the base layer on its own.
|
|
|
84
84
|
|
|
85
85
|
**Most of your first component is the shared runtime.** The median styled family is 20,614 bytes gzipped alone, and it adds 2,224 to a page that already has Helix. The shared runtime is 89 percent of the first component, and the component itself is 11 percent. Ten typical components cost the first number once, plus nine of the added number. The two columns in the tables below let you work that out for your own components.
|
|
86
86
|
|
|
87
|
-
Across the 103 styled components, the smallest is `code` at 9,782 bytes gzipped alone, and the largest is `data-table` at 63,610. Every family together is 294,
|
|
87
|
+
Across the 103 styled components, the smallest is `code` at 9,782 bytes gzipped alone, and the largest is `data-table` at 63,610. Every family together is 294,668.
|
|
88
88
|
|
|
89
89
|
No family imports a barrel. The `barrels` rule in `fusion workspace` enforces that. `@fusion.dev/helix-headless` marks every module in `dist` as having side effects, which is true of a file that calls `customElements.define`. A bundler can remove none of it. One `import { HelixHeadlessButton } from '@fusion.dev/helix-headless'` costs 188,118 bytes gzipped, whatever else the page imports. That is 9 times the median family above. All 103 styled families import the headless family they extend directly. The rule checks the shape of the import, so it applies to any package that sets `sideEffects`, whatever its name.
|
|
90
90
|
|
|
@@ -337,7 +337,7 @@ The headless tier plus presentation. `bun add @fusion.dev/helix` installs this t
|
|
|
337
337
|
| `list` | 36,624 | 11,044 | 9,710 | 5,782 | 1,307 | 1,089 |
|
|
338
338
|
| `listbox` | 111,463 | 35,406 | 28,903 | 15,768 | 4,108 | 3,250 |
|
|
339
339
|
| `loader` | 34,908 | 10,789 | 9,500 | 4,051 | 1,003 | 806 |
|
|
340
|
-
| `map` |
|
|
340
|
+
| `map` | 162,232 | 43,691 | 36,301 | 67,742 | 12,991 | 10,725 |
|
|
341
341
|
| `megamenu` | 66,881 | 21,799 | 19,289 | 9,805 | 2,417 | 1,975 |
|
|
342
342
|
| `menu` | 133,249 | 43,442 | 36,482 | 16,362 | 3,837 | 3,101 |
|
|
343
343
|
| `meter` | 40,330 | 12,019 | 10,617 | 9,479 | 2,223 | 1,972 |
|
|
@@ -402,8 +402,8 @@ stripped column shows the bytes we removed.
|
|
|
402
402
|
| `@fusion.dev/helix-headless` | 13,168,854 | 504 | 1,051,088 | `@fusion.dev/helix-core`, `@fusion.dev/helix-icons`, `uqr` |
|
|
403
403
|
| `@fusion.dev/helix-icons` | 150,162 | 21 | 50,346 | `svgo` |
|
|
404
404
|
| `@fusion.dev/helix-styles` | 102,108 | 6 | 0 | none |
|
|
405
|
-
| `@fusion.dev/helix-theme` |
|
|
406
|
-
| `@fusion.dev/helix` | 14,
|
|
405
|
+
| `@fusion.dev/helix-theme` | 500,416 | 8 | 5,191 | none |
|
|
406
|
+
| `@fusion.dev/helix` | 14,306,888 | 870 | 371,453 | `@fusion.dev/helix-core`, `@fusion.dev/helix-headless`, `@fusion.dev/helix-styles` |
|
|
407
407
|
|
|
408
408
|
## Not measured yet
|
|
409
409
|
|
package/content/theming.mdx
CHANGED
|
@@ -45,22 +45,35 @@ exact for the default seed and approximate for any other.
|
|
|
45
45
|
target, measures every pair, and returns the theme with any violations:
|
|
46
46
|
|
|
47
47
|
```ts
|
|
48
|
-
import {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
import { readFile } from 'node:fs/promises'
|
|
49
|
+
import { initTheme } from '@fusion.dev/helix-theme'
|
|
50
|
+
|
|
51
|
+
const engine = await initTheme(
|
|
52
|
+
await readFile(new URL(import.meta.resolve('@fusion.dev/helix-theme/wasm/fusion_helix_theme_bg.wasm'))),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
const theme = engine.derive(
|
|
56
|
+
{
|
|
57
|
+
brand: '#0b3d91',
|
|
58
|
+
contrastTarget: 4.5, // 4.5 is WCAG AA, 7 is AAA
|
|
59
|
+
scheme: 'light',
|
|
60
|
+
},
|
|
61
|
+
[':root'],
|
|
62
|
+
)
|
|
55
63
|
|
|
56
64
|
if (theme.violations.length > 0) {
|
|
57
65
|
throw new Error(theme.violations.join('\n'))
|
|
58
66
|
}
|
|
59
67
|
|
|
60
|
-
console.log(
|
|
68
|
+
console.log(theme.css[':root'])
|
|
61
69
|
```
|
|
62
70
|
|
|
63
|
-
The
|
|
71
|
+
The engine is WebAssembly, so `initTheme` loads it once and you wait for it.
|
|
72
|
+
Node passes the module's bytes, as above. A browser passes the URL you serve
|
|
73
|
+
`fusion_helix_theme_bg.wasm` from, such as `initTheme('/theme.wasm')`.
|
|
74
|
+
`derive` renders the CSS under each selector you name.
|
|
75
|
+
|
|
76
|
+
The CSS includes the seeds as well as the tokens. The stylesheet
|
|
64
77
|
computes the values the engine does not, from the same seeds the engine used:
|
|
65
78
|
|
|
66
79
|
- The twenty-four series colors for charts
|
|
@@ -71,6 +84,32 @@ This keeps both ways of theming in agreement on the same page.
|
|
|
71
84
|
|
|
72
85
|
Use tuned to try a color. Use verified for the theme you ship.
|
|
73
86
|
|
|
87
|
+
## Derive both modes
|
|
88
|
+
|
|
89
|
+
A site with light and dark mode needs both themes. `deriveModes` derives your
|
|
90
|
+
intent in each mode and renders one block, with each color as a
|
|
91
|
+
`light-dark()` pair, the way the shipped stylesheet writes its tokens:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { initTheme } from '@fusion.dev/helix-theme'
|
|
95
|
+
|
|
96
|
+
const engine = await initTheme('/theme.wasm')
|
|
97
|
+
const { css, dark, light } = engine.deriveModes({ brand: '#0b3d91' }, [':root'])
|
|
98
|
+
|
|
99
|
+
for (const theme of [light, dark]) {
|
|
100
|
+
if (theme.violations.length > 0) {
|
|
101
|
+
throw new Error(theme.violations.join('\n'))
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log(css[':root'])
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`color-scheme` picks the half, so the block follows the operating system and
|
|
109
|
+
`[data-color-mode]` on any element, as described in [Color modes](./color-modes).
|
|
110
|
+
A value that is not a color and still changes with the mode, such as the edge
|
|
111
|
+
of a floating surface, is set by rules after the block, one for each mode.
|
|
112
|
+
|
|
74
113
|
## Why Helix uses OKLCH
|
|
75
114
|
|
|
76
115
|
Equal numeric steps in sRGB do not look like equal steps. That makes scales and
|
|
@@ -106,7 +145,7 @@ engine solves each foreground color against the hardest surface it appears on.
|
|
|
106
145
|
Each derived token includes the result of its contrast check:
|
|
107
146
|
|
|
108
147
|
```ts
|
|
109
|
-
const theme =
|
|
148
|
+
const theme = engine.derive({ brand })
|
|
110
149
|
|
|
111
150
|
for (const token of theme.tokens) {
|
|
112
151
|
console.log(token.name, token.hex, token.contrast, token.chromaReduced)
|
|
@@ -1905,11 +1905,14 @@ title: Intent reference
|
|
|
1905
1905
|
every field \`ThemeIntent\` accepts and what changes when you set it.
|
|
1906
1906
|
|
|
1907
1907
|
\`\`\`ts
|
|
1908
|
-
import {
|
|
1908
|
+
import { initTheme } from '@fusion.dev/helix-theme'
|
|
1909
1909
|
|
|
1910
|
-
const
|
|
1910
|
+
const engine = await initTheme('/theme.wasm')
|
|
1911
|
+
const theme = engine.derive({ brand: '#2563eb' })
|
|
1911
1912
|
\`\`\`
|
|
1912
1913
|
|
|
1914
|
+
[Deriving a theme](./theming) shows how Node loads the engine.
|
|
1915
|
+
|
|
1913
1916
|
**\`brand\` is the only required input.** Every other field has a default,
|
|
1914
1917
|
computed from \`brand\` or from the Helix design language. A theme that sets only
|
|
1915
1918
|
\`brand\` is a valid theme.
|
|
@@ -2382,6 +2385,19 @@ It also draws landmarks, highway signs, and neighborhood names:
|
|
|
2382
2385
|
roads.
|
|
2383
2386
|
- Neighborhoods are in spaced capitals, quieter than the towns.
|
|
2384
2387
|
|
|
2388
|
+
Pass \`landmarks\` to choose which kinds the map draws. \`false\` draws none, for a
|
|
2389
|
+
map whose own markers are the places a reader is looking for. A list draws only
|
|
2390
|
+
those kinds:
|
|
2391
|
+
|
|
2392
|
+
\`\`\`js
|
|
2393
|
+
mapLibreStyle(colors, { landmarks: ['nature', 'transit'] })
|
|
2394
|
+
\`\`\`
|
|
2395
|
+
|
|
2396
|
+
The kinds are \`culture\`, \`food\`, \`health\`, \`learning\`, \`lodging\`, \`nature\`,
|
|
2397
|
+
\`shopping\`, \`sport\`, and \`transit\`, and \`MAP_LANDMARKS\` lists them. The option
|
|
2398
|
+
holds when \`followTheme\` restyles the map, because it builds the style from the
|
|
2399
|
+
same options.
|
|
2400
|
+
|
|
2385
2401
|
The same call builds a light map for a light theme and a dark map for a dark
|
|
2386
2402
|
theme. Map labels meet a contrast target of 4.5 against the land. Pass
|
|
2387
2403
|
\`contrast\` to change it, \`language\` to choose which name each place is labeled
|
|
@@ -2415,6 +2431,42 @@ changes its theme another way, such as by replacing a stylesheet, call
|
|
|
2415
2431
|
a frame or two after the page loads. If the colors never become available, it
|
|
2416
2432
|
reports which one is missing.
|
|
2417
2433
|
|
|
2434
|
+
## Put a map behind your content
|
|
2435
|
+
|
|
2436
|
+
Set \`fill\` to lay a map behind your page's own content, such as a hero whose
|
|
2437
|
+
words decide its height. The frame covers its container instead of taking a
|
|
2438
|
+
height of its own, and draws no border or corners, because the container's edge
|
|
2439
|
+
is the map's. Give the container a position, such as \`position: relative\`:
|
|
2440
|
+
|
|
2441
|
+
\`\`\`html
|
|
2442
|
+
<section class="hero">
|
|
2443
|
+
<helix-map fill credit-placement="bottom-end" label="Downtown Austin">
|
|
2444
|
+
<div id="map"></div>
|
|
2445
|
+
</helix-map>
|
|
2446
|
+
<div class="hero-words">
|
|
2447
|
+
<h1>Every stop, one map</h1>
|
|
2448
|
+
</div>
|
|
2449
|
+
</section>
|
|
2450
|
+
\`\`\`
|
|
2451
|
+
|
|
2452
|
+
\`\`\`css
|
|
2453
|
+
.hero,
|
|
2454
|
+
.hero-words {
|
|
2455
|
+
position: relative;
|
|
2456
|
+
}
|
|
2457
|
+
\`\`\`
|
|
2458
|
+
|
|
2459
|
+
Content after the map, with a position of its own, is drawn over it. Give that
|
|
2460
|
+
content a ground of its own, such as a translucent panel, so it reads over any
|
|
2461
|
+
part of the map.
|
|
2462
|
+
|
|
2463
|
+
\`credit-placement\` draws the map's credit in a corner over the map instead of in
|
|
2464
|
+
a strip under it. Use \`bottom-end\` or \`bottom-start\`, and keep it away from the
|
|
2465
|
+
corner your controls are in. The credit is one line until it is pointed at or
|
|
2466
|
+
its links are focused, and then it shows in full. That compact form is the one
|
|
2467
|
+
OpenStreetMap's attribution guidance accepts for a small credit. Most tile
|
|
2468
|
+
licenses require the credit to stay visible, so Helix never hides it.
|
|
2469
|
+
|
|
2418
2470
|
## Draw the map before it loads
|
|
2419
2471
|
|
|
2420
2472
|
\`mapSnapshot(view)\` draws the map on your server as an SVG, from the same tiles
|
|
@@ -2830,8 +2882,8 @@ either barrel. The per-component tables show what your page pays.
|
|
|
2830
2882
|
| \`@fusion.dev/helix-core\` | 177,056 | 56,663 | 48,189 | The element base class, mixins, decorators, and floating positioning. |
|
|
2831
2883
|
| \`@fusion.dev/helix-headless\` | 726,171 | 188,118 | 146,385 | Every headless family. The barrel entry includes all of them. |
|
|
2832
2884
|
| \`@fusion.dev/helix-icons\` | 55,565 | 17,727 | 14,864 | The whole glyph set. |
|
|
2833
|
-
| \`@fusion.dev/helix-theme\` | 3,
|
|
2834
|
-
| \`@fusion.dev/helix\` | 1,
|
|
2885
|
+
| \`@fusion.dev/helix-theme\` | 3,626 | 1,597 | 1,384 | The derivation engine. Most sites run it at build time, and a theme switcher runs it in the browser. |
|
|
2886
|
+
| \`@fusion.dev/helix\` | 1,259,099 | 294,668 | 216,376 | Every styled family. |
|
|
2835
2887
|
| \`lit\` | 15,394 | 6,118 | 5,460 | Lit itself, listed so you can subtract it from every row above. |
|
|
2836
2888
|
|
|
2837
2889
|
## Stylesheets
|
|
@@ -2854,7 +2906,7 @@ shows the whole cost of using the base layer on its own.
|
|
|
2854
2906
|
|
|
2855
2907
|
**Most of your first component is the shared runtime.** The median styled family is 20,614 bytes gzipped alone, and it adds 2,224 to a page that already has Helix. The shared runtime is 89 percent of the first component, and the component itself is 11 percent. Ten typical components cost the first number once, plus nine of the added number. The two columns in the tables below let you work that out for your own components.
|
|
2856
2908
|
|
|
2857
|
-
Across the 103 styled components, the smallest is \`code\` at 9,782 bytes gzipped alone, and the largest is \`data-table\` at 63,610. Every family together is 294,
|
|
2909
|
+
Across the 103 styled components, the smallest is \`code\` at 9,782 bytes gzipped alone, and the largest is \`data-table\` at 63,610. Every family together is 294,668.
|
|
2858
2910
|
|
|
2859
2911
|
No family imports a barrel. The \`barrels\` rule in \`fusion workspace\` enforces that. \`@fusion.dev/helix-headless\` marks every module in \`dist\` as having side effects, which is true of a file that calls \`customElements.define\`. A bundler can remove none of it. One \`import { HelixHeadlessButton } from '@fusion.dev/helix-headless'\` costs 188,118 bytes gzipped, whatever else the page imports. That is 9 times the median family above. All 103 styled families import the headless family they extend directly. The rule checks the shape of the import, so it applies to any package that sets \`sideEffects\`, whatever its name.
|
|
2860
2912
|
|
|
@@ -3107,7 +3159,7 @@ The headless tier plus presentation. \`bun add @fusion.dev/helix\` installs this
|
|
|
3107
3159
|
| \`list\` | 36,624 | 11,044 | 9,710 | 5,782 | 1,307 | 1,089 |
|
|
3108
3160
|
| \`listbox\` | 111,463 | 35,406 | 28,903 | 15,768 | 4,108 | 3,250 |
|
|
3109
3161
|
| \`loader\` | 34,908 | 10,789 | 9,500 | 4,051 | 1,003 | 806 |
|
|
3110
|
-
| \`map\` |
|
|
3162
|
+
| \`map\` | 162,232 | 43,691 | 36,301 | 67,742 | 12,991 | 10,725 |
|
|
3111
3163
|
| \`megamenu\` | 66,881 | 21,799 | 19,289 | 9,805 | 2,417 | 1,975 |
|
|
3112
3164
|
| \`menu\` | 133,249 | 43,442 | 36,482 | 16,362 | 3,837 | 3,101 |
|
|
3113
3165
|
| \`meter\` | 40,330 | 12,019 | 10,617 | 9,479 | 2,223 | 1,972 |
|
|
@@ -3172,8 +3224,8 @@ stripped column shows the bytes we removed.
|
|
|
3172
3224
|
| \`@fusion.dev/helix-headless\` | 13,168,854 | 504 | 1,051,088 | \`@fusion.dev/helix-core\`, \`@fusion.dev/helix-icons\`, \`uqr\` |
|
|
3173
3225
|
| \`@fusion.dev/helix-icons\` | 150,162 | 21 | 50,346 | \`svgo\` |
|
|
3174
3226
|
| \`@fusion.dev/helix-styles\` | 102,108 | 6 | 0 | none |
|
|
3175
|
-
| \`@fusion.dev/helix-theme\` |
|
|
3176
|
-
| \`@fusion.dev/helix\` | 14,
|
|
3227
|
+
| \`@fusion.dev/helix-theme\` | 500,416 | 8 | 5,191 | none |
|
|
3228
|
+
| \`@fusion.dev/helix\` | 14,306,888 | 870 | 371,453 | \`@fusion.dev/helix-core\`, \`@fusion.dev/helix-headless\`, \`@fusion.dev/helix-styles\` |
|
|
3177
3229
|
|
|
3178
3230
|
## Not measured yet
|
|
3179
3231
|
|
|
@@ -5173,22 +5225,35 @@ exact for the default seed and approximate for any other.
|
|
|
5173
5225
|
target, measures every pair, and returns the theme with any violations:
|
|
5174
5226
|
|
|
5175
5227
|
\`\`\`ts
|
|
5176
|
-
import {
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5228
|
+
import { readFile } from 'node:fs/promises'
|
|
5229
|
+
import { initTheme } from '@fusion.dev/helix-theme'
|
|
5230
|
+
|
|
5231
|
+
const engine = await initTheme(
|
|
5232
|
+
await readFile(new URL(import.meta.resolve('@fusion.dev/helix-theme/wasm/fusion_helix_theme_bg.wasm'))),
|
|
5233
|
+
)
|
|
5234
|
+
|
|
5235
|
+
const theme = engine.derive(
|
|
5236
|
+
{
|
|
5237
|
+
brand: '#0b3d91',
|
|
5238
|
+
contrastTarget: 4.5, // 4.5 is WCAG AA, 7 is AAA
|
|
5239
|
+
scheme: 'light',
|
|
5240
|
+
},
|
|
5241
|
+
[':root'],
|
|
5242
|
+
)
|
|
5183
5243
|
|
|
5184
5244
|
if (theme.violations.length > 0) {
|
|
5185
5245
|
throw new Error(theme.violations.join('\\n'))
|
|
5186
5246
|
}
|
|
5187
5247
|
|
|
5188
|
-
console.log(
|
|
5248
|
+
console.log(theme.css[':root'])
|
|
5189
5249
|
\`\`\`
|
|
5190
5250
|
|
|
5191
|
-
The
|
|
5251
|
+
The engine is WebAssembly, so \`initTheme\` loads it once and you wait for it.
|
|
5252
|
+
Node passes the module's bytes, as above. A browser passes the URL you serve
|
|
5253
|
+
\`fusion_helix_theme_bg.wasm\` from, such as \`initTheme('/theme.wasm')\`.
|
|
5254
|
+
\`derive\` renders the CSS under each selector you name.
|
|
5255
|
+
|
|
5256
|
+
The CSS includes the seeds as well as the tokens. The stylesheet
|
|
5192
5257
|
computes the values the engine does not, from the same seeds the engine used:
|
|
5193
5258
|
|
|
5194
5259
|
- The twenty-four series colors for charts
|
|
@@ -5199,6 +5264,32 @@ This keeps both ways of theming in agreement on the same page.
|
|
|
5199
5264
|
|
|
5200
5265
|
Use tuned to try a color. Use verified for the theme you ship.
|
|
5201
5266
|
|
|
5267
|
+
## Derive both modes
|
|
5268
|
+
|
|
5269
|
+
A site with light and dark mode needs both themes. \`deriveModes\` derives your
|
|
5270
|
+
intent in each mode and renders one block, with each color as a
|
|
5271
|
+
\`light-dark()\` pair, the way the shipped stylesheet writes its tokens:
|
|
5272
|
+
|
|
5273
|
+
\`\`\`ts
|
|
5274
|
+
import { initTheme } from '@fusion.dev/helix-theme'
|
|
5275
|
+
|
|
5276
|
+
const engine = await initTheme('/theme.wasm')
|
|
5277
|
+
const { css, dark, light } = engine.deriveModes({ brand: '#0b3d91' }, [':root'])
|
|
5278
|
+
|
|
5279
|
+
for (const theme of [light, dark]) {
|
|
5280
|
+
if (theme.violations.length > 0) {
|
|
5281
|
+
throw new Error(theme.violations.join('\\n'))
|
|
5282
|
+
}
|
|
5283
|
+
}
|
|
5284
|
+
|
|
5285
|
+
console.log(css[':root'])
|
|
5286
|
+
\`\`\`
|
|
5287
|
+
|
|
5288
|
+
\`color-scheme\` picks the half, so the block follows the operating system and
|
|
5289
|
+
\`[data-color-mode]\` on any element, as described in [Color modes](./color-modes).
|
|
5290
|
+
A value that is not a color and still changes with the mode, such as the edge
|
|
5291
|
+
of a floating surface, is set by rules after the block, one for each mode.
|
|
5292
|
+
|
|
5202
5293
|
## Why Helix uses OKLCH
|
|
5203
5294
|
|
|
5204
5295
|
Equal numeric steps in sRGB do not look like equal steps. That makes scales and
|
|
@@ -5234,7 +5325,7 @@ engine solves each foreground color against the hardest surface it appears on.
|
|
|
5234
5325
|
Each derived token includes the result of its contrast check:
|
|
5235
5326
|
|
|
5236
5327
|
\`\`\`ts
|
|
5237
|
-
const theme =
|
|
5328
|
+
const theme = engine.derive({ brand })
|
|
5238
5329
|
|
|
5239
5330
|
for (const token of theme.tokens) {
|
|
5240
5331
|
console.log(token.name, token.hex, token.contrast, token.chromaReduced)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fusion.dev/helix-docs",
|
|
3
3
|
"description": "Versioned documentation content and registry for Helix.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.113.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ross Sheppard <ross.sheppard@fusion.dev>",
|
|
7
7
|
"repository": {
|
|
@@ -32,12 +32,18 @@
|
|
|
32
32
|
"peers": "bun scripts/build-peers.mjs",
|
|
33
33
|
"test": "bun test src",
|
|
34
34
|
"timings": "bun scripts/build-timings.mjs",
|
|
35
|
-
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
35
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && bun scripts/check-snippets.mjs"
|
|
36
36
|
},
|
|
37
37
|
"bugs": "https://github.com/project-fusion/fusion/issues",
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@fusion.dev/helix
|
|
40
|
-
"@fusion.dev/helix-
|
|
39
|
+
"@fusion.dev/helix": "0.113.0",
|
|
40
|
+
"@fusion.dev/helix-cem": "0.113.0",
|
|
41
|
+
"@fusion.dev/helix-headless": "0.113.0",
|
|
42
|
+
"@fusion.dev/helix-icons": "0.113.0",
|
|
43
|
+
"@fusion.dev/helix-map": "0.113.0",
|
|
44
|
+
"@fusion.dev/helix-styles": "0.113.0",
|
|
45
|
+
"@fusion.dev/helix-testing": "0.113.0",
|
|
46
|
+
"@fusion.dev/helix-theme": "0.113.0",
|
|
41
47
|
"@fusion.dev/scripts": "0.0.0-dev",
|
|
42
48
|
"lit": "^3.2.1",
|
|
43
49
|
"playwright": "^1.62.1"
|