@elastic/distillate 0.0.0 → 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.
- package/LICENSE.txt +93 -0
- package/NOTICE.txt +30 -0
- package/README.md +142 -0
- package/THIRD_PARTY_LICENSES.md +768 -0
- package/dist/cjs/collector.js +420 -0
- package/dist/cjs/collector.js.map +1 -0
- package/dist/cjs/css_scan.js +186 -0
- package/dist/cjs/css_scan.js.map +1 -0
- package/dist/cjs/dom_sink.js +40 -0
- package/dist/cjs/dom_sink.js.map +1 -0
- package/dist/cjs/emotion.js +189 -0
- package/dist/cjs/emotion.js.map +1 -0
- package/dist/cjs/engine.js +79 -0
- package/dist/cjs/engine.js.map +1 -0
- package/dist/cjs/environment.js +9 -0
- package/dist/cjs/environment.js.map +1 -0
- package/dist/cjs/hash.js +86 -0
- package/dist/cjs/hash.js.map +1 -0
- package/dist/cjs/idents.js +23 -0
- package/dist/cjs/idents.js.map +1 -0
- package/dist/cjs/index.js +42 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/instance.js +34 -0
- package/dist/cjs/instance.js.map +1 -0
- package/dist/cjs/local_vars.js +100 -0
- package/dist/cjs/local_vars.js.map +1 -0
- package/dist/cjs/minify.js +77 -0
- package/dist/cjs/minify.js.map +1 -0
- package/dist/cjs/names.js +77 -0
- package/dist/cjs/names.js.map +1 -0
- package/dist/cjs/nesting.js +406 -0
- package/dist/cjs/nesting.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/runtime.js +257 -0
- package/dist/cjs/runtime.js.map +1 -0
- package/dist/cjs/styles/authoring.js +169 -0
- package/dist/cjs/styles/authoring.js.map +1 -0
- package/dist/cjs/styles/declarations.js +139 -0
- package/dist/cjs/styles/declarations.js.map +1 -0
- package/dist/cjs/styles/index.js +32 -0
- package/dist/cjs/styles/index.js.map +1 -0
- package/dist/cjs/styles/module.js +233 -0
- package/dist/cjs/styles/module.js.map +1 -0
- package/dist/cjs/styles/registry.js +187 -0
- package/dist/cjs/styles/registry.js.map +1 -0
- package/dist/cjs/styles/types.js +9 -0
- package/dist/cjs/styles/types.js.map +1 -0
- package/dist/cjs/testing.js +37 -0
- package/dist/cjs/testing.js.map +1 -0
- package/dist/cjs/theme.js +249 -0
- package/dist/cjs/theme.js.map +1 -0
- package/dist/cjs/tokens/contextual_var.js +39 -0
- package/dist/cjs/tokens/contextual_var.js.map +1 -0
- package/dist/cjs/tokens/index.js +24 -0
- package/dist/cjs/tokens/index.js.map +1 -0
- package/dist/cjs/tokens/kind.js +14 -0
- package/dist/cjs/tokens/kind.js.map +1 -0
- package/dist/cjs/tokens/light_dark.js +42 -0
- package/dist/cjs/tokens/light_dark.js.map +1 -0
- package/dist/cjs/tokens/scale_token.js +28 -0
- package/dist/cjs/tokens/scale_token.js.map +1 -0
- package/dist/cjs/tokens/theme_token.js +28 -0
- package/dist/cjs/tokens/theme_token.js.map +1 -0
- package/dist/cjs/var_invariant.js +115 -0
- package/dist/cjs/var_invariant.js.map +1 -0
- package/dist/collector.d.ts +84 -0
- package/dist/collector.d.ts.map +1 -0
- package/dist/collector.js +416 -0
- package/dist/collector.js.map +1 -0
- package/dist/css_scan.d.ts +57 -0
- package/dist/css_scan.d.ts.map +1 -0
- package/dist/css_scan.js +180 -0
- package/dist/css_scan.js.map +1 -0
- package/dist/dom_sink.d.ts +27 -0
- package/dist/dom_sink.d.ts.map +1 -0
- package/dist/dom_sink.js +36 -0
- package/dist/dom_sink.js.map +1 -0
- package/dist/emotion.d.ts +47 -0
- package/dist/emotion.d.ts.map +1 -0
- package/dist/emotion.js +184 -0
- package/dist/emotion.js.map +1 -0
- package/dist/engine.d.ts +41 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +75 -0
- package/dist/engine.js.map +1 -0
- package/dist/environment.d.ts +43 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +8 -0
- package/dist/environment.js.map +1 -0
- package/dist/hash.d.ts +2 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +82 -0
- package/dist/hash.js.map +1 -0
- package/dist/idents.d.ts +9 -0
- package/dist/idents.d.ts.map +1 -0
- package/dist/idents.js +19 -0
- package/dist/idents.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/instance.d.ts +16 -0
- package/dist/instance.d.ts.map +1 -0
- package/dist/instance.js +30 -0
- package/dist/instance.js.map +1 -0
- package/dist/local_vars.d.ts +75 -0
- package/dist/local_vars.d.ts.map +1 -0
- package/dist/local_vars.js +92 -0
- package/dist/local_vars.js.map +1 -0
- package/dist/minify.d.ts +11 -0
- package/dist/minify.d.ts.map +1 -0
- package/dist/minify.js +73 -0
- package/dist/minify.js.map +1 -0
- package/dist/names.d.ts +39 -0
- package/dist/names.d.ts.map +1 -0
- package/dist/names.js +70 -0
- package/dist/names.js.map +1 -0
- package/dist/nesting.d.ts +59 -0
- package/dist/nesting.d.ts.map +1 -0
- package/dist/nesting.js +400 -0
- package/dist/nesting.js.map +1 -0
- package/dist/runtime.d.ts +36 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +252 -0
- package/dist/runtime.js.map +1 -0
- package/dist/styles/authoring.d.ts +59 -0
- package/dist/styles/authoring.d.ts.map +1 -0
- package/dist/styles/authoring.js +155 -0
- package/dist/styles/authoring.js.map +1 -0
- package/dist/styles/declarations.d.ts +10 -0
- package/dist/styles/declarations.d.ts.map +1 -0
- package/dist/styles/declarations.js +132 -0
- package/dist/styles/declarations.js.map +1 -0
- package/dist/styles/index.d.ts +7 -0
- package/dist/styles/index.d.ts.map +1 -0
- package/dist/styles/index.js +12 -0
- package/dist/styles/index.js.map +1 -0
- package/dist/styles/module.d.ts +5 -0
- package/dist/styles/module.d.ts.map +1 -0
- package/dist/styles/module.js +229 -0
- package/dist/styles/module.js.map +1 -0
- package/dist/styles/registry.d.ts +48 -0
- package/dist/styles/registry.d.ts.map +1 -0
- package/dist/styles/registry.js +182 -0
- package/dist/styles/registry.js.map +1 -0
- package/dist/styles/types.d.ts +211 -0
- package/dist/styles/types.d.ts.map +1 -0
- package/dist/styles/types.js +8 -0
- package/dist/styles/types.js.map +1 -0
- package/dist/testing.d.ts +10 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +31 -0
- package/dist/testing.js.map +1 -0
- package/dist/theme.d.ts +85 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +240 -0
- package/dist/theme.js.map +1 -0
- package/dist/tokens/contextual_var.d.ts +35 -0
- package/dist/tokens/contextual_var.d.ts.map +1 -0
- package/dist/tokens/contextual_var.js +33 -0
- package/dist/tokens/contextual_var.js.map +1 -0
- package/dist/tokens/index.d.ts +5 -0
- package/dist/tokens/index.d.ts.map +1 -0
- package/dist/tokens/index.js +11 -0
- package/dist/tokens/index.js.map +1 -0
- package/dist/tokens/kind.d.ts +2 -0
- package/dist/tokens/kind.d.ts.map +1 -0
- package/dist/tokens/kind.js +10 -0
- package/dist/tokens/kind.js.map +1 -0
- package/dist/tokens/light_dark.d.ts +20 -0
- package/dist/tokens/light_dark.d.ts.map +1 -0
- package/dist/tokens/light_dark.js +37 -0
- package/dist/tokens/light_dark.js.map +1 -0
- package/dist/tokens/scale_token.d.ts +20 -0
- package/dist/tokens/scale_token.d.ts.map +1 -0
- package/dist/tokens/scale_token.js +23 -0
- package/dist/tokens/scale_token.js.map +1 -0
- package/dist/tokens/theme_token.d.ts +20 -0
- package/dist/tokens/theme_token.d.ts.map +1 -0
- package/dist/tokens/theme_token.js +23 -0
- package/dist/tokens/theme_token.js.map +1 -0
- package/dist/var_invariant.d.ts +25 -0
- package/dist/var_invariant.d.ts.map +1 -0
- package/dist/var_invariant.js +108 -0
- package/dist/var_invariant.js.map +1 -0
- package/docs/concepts/authoring.md +115 -0
- package/docs/concepts/collection.md +66 -0
- package/docs/concepts/distillery.md +47 -0
- package/docs/concepts/index.md +18 -0
- package/docs/concepts/naming-and-output.md +123 -0
- package/docs/concepts/ordering.md +61 -0
- package/docs/concepts/single-copy.md +103 -0
- package/docs/concepts/tokens.md +99 -0
- package/docs/docset.yml +53 -0
- package/docs/examples/01-stylesheet.ts +20 -0
- package/docs/examples/02-artifact.ts +26 -0
- package/docs/examples/03-variants.ts +40 -0
- package/docs/examples/04-emotion.ts +33 -0
- package/docs/examples/05-var-invariant.ts +31 -0
- package/docs/examples/06-payload.ts +85 -0
- package/docs/examples/fixture.ts +22 -0
- package/docs/getting-started/index.md +14 -0
- package/docs/getting-started/installation.md +30 -0
- package/docs/getting-started/quick-start.md +61 -0
- package/docs/getting-started/your-first-stylesheet.md +57 -0
- package/docs/guides/compact-artifacts.md +87 -0
- package/docs/guides/emotion-migration.md +60 -0
- package/docs/guides/index.md +17 -0
- package/docs/guides/name-collisions.md +37 -0
- package/docs/guides/non-css-surfaces.md +73 -0
- package/docs/guides/react-renderer.md +158 -0
- package/docs/guides/theming.md +107 -0
- package/docs/guides/var-invariant.md +24 -0
- package/docs/guides/vs-emotion.md +61 -0
- package/docs/index.md +73 -0
- package/docs/logo.svg +7 -0
- package/docs/playground/index.html +12 -0
- package/docs/playground/package.json +23 -0
- package/docs/playground/src/app/app.tsx +109 -0
- package/docs/playground/src/app/css_panel.tsx +119 -0
- package/docs/playground/src/app/editor_panel.tsx +87 -0
- package/docs/playground/src/app/index.ts +8 -0
- package/docs/playground/src/app/monaco_css_editor.tsx +84 -0
- package/docs/playground/src/app/monaco_source_editor.tsx +132 -0
- package/docs/playground/src/app/preview_panel.tsx +99 -0
- package/docs/playground/src/app/tokens_panel.tsx +117 -0
- package/docs/playground/src/app/types.ts +10 -0
- package/docs/playground/src/examples/snippets.test.ts +28 -0
- package/docs/playground/src/examples/snippets.ts +418 -0
- package/docs/playground/src/lib/compile.test.ts +157 -0
- package/docs/playground/src/lib/compile.ts +242 -0
- package/docs/playground/src/lib/demo_environment.test.ts +123 -0
- package/docs/playground/src/lib/demo_environment.ts +140 -0
- package/docs/playground/src/lib/editor_lib.test.ts +39 -0
- package/docs/playground/src/lib/editor_lib.ts +142 -0
- package/docs/playground/src/lib/format_css.test.ts +41 -0
- package/docs/playground/src/lib/format_css.ts +71 -0
- package/docs/playground/src/main.tsx +25 -0
- package/docs/playground/src/setup/chrome.test.ts +47 -0
- package/docs/playground/src/setup/chrome.ts +365 -0
- package/docs/playground/src/setup/monaco_env.ts +64 -0
- package/docs/playground/tsconfig.json +22 -0
- package/docs/playground/vite.config.ts +37 -0
- package/docs/playground/vitest.config.ts +35 -0
- package/docs/playground.md +28 -0
- package/docs/reference/api/emotion.md +372 -0
- package/docs/reference/api/index-1.md +2180 -0
- package/docs/reference/api/index.md +9 -0
- package/docs/reference/api/testing.md +92 -0
- package/docs/reference/api.md +12 -0
- package/docs/reference/entry-points.md +125 -0
- package/docs/reference/environment.md +60 -0
- package/docs/reference/index.md +11 -0
- package/docs/reference/okf-map.md +92 -0
- package/package.json +127 -4
- package/src/collector.ts +526 -0
- package/src/css_scan.ts +263 -0
- package/src/dom_sink.ts +65 -0
- package/src/emotion.ts +311 -0
- package/src/engine.ts +191 -0
- package/src/environment.ts +56 -0
- package/src/hash.ts +111 -0
- package/src/idents.ts +21 -0
- package/src/index.ts +93 -0
- package/src/instance.ts +49 -0
- package/src/local_vars.ts +220 -0
- package/src/minify.ts +87 -0
- package/src/names.ts +129 -0
- package/src/nesting.ts +596 -0
- package/src/runtime.ts +428 -0
- package/src/styles/authoring.ts +244 -0
- package/src/styles/declarations.ts +172 -0
- package/src/styles/index.ts +58 -0
- package/src/styles/module.ts +396 -0
- package/src/styles/registry.ts +261 -0
- package/src/styles/types.ts +283 -0
- package/src/testing.ts +48 -0
- package/src/theme.ts +454 -0
- package/src/tokens/contextual_var.ts +72 -0
- package/src/tokens/index.ts +17 -0
- package/src/tokens/kind.ts +13 -0
- package/src/tokens/light_dark.ts +58 -0
- package/src/tokens/scale_token.ts +41 -0
- package/src/tokens/theme_token.ts +42 -0
- package/src/var_invariant.ts +147 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
navigation_title: Your first stylesheet
|
|
3
|
+
description: Register several handles and emit a readable sheet for an app.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Your first stylesheet
|
|
7
|
+
|
|
8
|
+
Use the **stylesheet** target when a host app will load one CSS file and refer to stable class names. Readable names are `${moduleName}-${path}`: `.panel-root`, `.panel-title`.
|
|
9
|
+
|
|
10
|
+
This continues the [quick start](quick-start.md) environment (`colors.ink`, `colors.accent`, `colors.surface`, `gap`). The file below is complete on its own.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { createDistillery, cq, lightDark } from '@elastic/distillate';
|
|
14
|
+
|
|
15
|
+
const distillery = createDistillery({
|
|
16
|
+
prefix: 'eui',
|
|
17
|
+
themeScope: '.eui-view',
|
|
18
|
+
theme: {
|
|
19
|
+
colors: {
|
|
20
|
+
ink: lightDark('#111', '#eee'),
|
|
21
|
+
accent: lightDark('#06c', '#8cf'),
|
|
22
|
+
surface: lightDark('#fff', '#000'),
|
|
23
|
+
},
|
|
24
|
+
gap: cq('8px', '2cqi'),
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const panel = distillery.createStyleModule('panel', ({ css, tokens }) => ({
|
|
29
|
+
root: css`
|
|
30
|
+
padding: ${tokens.gap};
|
|
31
|
+
background: ${tokens.colors.surface};
|
|
32
|
+
`,
|
|
33
|
+
title: css`
|
|
34
|
+
color: ${tokens.colors.ink};
|
|
35
|
+
`,
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
const css = distillery.renderStyles(distillery.stylesheetCollector());
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```css
|
|
42
|
+
.eui-view {
|
|
43
|
+
--eui-colors-ink: light-dark(#111, #eee);
|
|
44
|
+
--eui-colors-surface: light-dark(#fff, #000);
|
|
45
|
+
}
|
|
46
|
+
.panel-root {
|
|
47
|
+
padding: 8px;
|
|
48
|
+
background: var(--eui-colors-surface);
|
|
49
|
+
}
|
|
50
|
+
.panel-title {
|
|
51
|
+
color: var(--eui-colors-ink);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`stylesheetCollector()` preloads every registered module, including variants. Apply `panel.handles.root.readableName` (or `String(handle)` on an emotion wrapper) in markup. Readable stringification does **not** collect; it only works when the document's stylesheet also used readable names.
|
|
56
|
+
|
|
57
|
+
For HTML that leaves the app with its CSS inlined, switch to [compact artifacts](../guides/compact-artifacts.md). For how names and targets combine, see [naming and output](../concepts/naming-and-output.md).
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
navigation_title: Ship a compact artifact
|
|
3
|
+
description: Collect the handles a render reached and emit a self-contained CSS payload, with a measured size comparison.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Ship a compact artifact
|
|
7
|
+
|
|
8
|
+
Use this path when HTML and CSS leave the app together: an email, an SVG, a Slack message, an agent reply. Compact names are assigned from the collected key set, so they are not stable across renders. Emit markup and CSS from the same collector.
|
|
9
|
+
|
|
10
|
+
The environment matches [quick start](../getting-started/quick-start.md). Every handle that appears on an element must be collected **and** resolved:
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import {
|
|
14
|
+
createDistillery,
|
|
15
|
+
cq,
|
|
16
|
+
lightDark,
|
|
17
|
+
variants,
|
|
18
|
+
} from '@elastic/distillate';
|
|
19
|
+
|
|
20
|
+
const distillery = createDistillery({
|
|
21
|
+
prefix: 'eui',
|
|
22
|
+
themeScope: '.eui-view',
|
|
23
|
+
theme: {
|
|
24
|
+
colors: {
|
|
25
|
+
ink: lightDark('#111', '#eee'),
|
|
26
|
+
accent: lightDark('#06c', '#8cf'),
|
|
27
|
+
surface: lightDark('#fff', '#000'),
|
|
28
|
+
},
|
|
29
|
+
gap: cq('8px', '2cqi'),
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const panel = distillery.createStyleModule('panel', ({ css, tokens }) => ({
|
|
34
|
+
root: css`
|
|
35
|
+
padding: ${tokens.gap};
|
|
36
|
+
background: ${tokens.colors.surface};
|
|
37
|
+
`,
|
|
38
|
+
tone: variants(
|
|
39
|
+
['calm', 'loud'] as const,
|
|
40
|
+
(tone) =>
|
|
41
|
+
css`
|
|
42
|
+
outline-color: ${tone === 'calm' ? tokens.colors.surface : tokens.colors.accent};
|
|
43
|
+
`
|
|
44
|
+
),
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
const collector = distillery.artifactCollector('compact');
|
|
48
|
+
const used = [panel.handles.root, panel.handles.tone.loud]; // include the variant; use(panel) skips it
|
|
49
|
+
const retained = collector.useHandles(used);
|
|
50
|
+
|
|
51
|
+
const resolver = collector.createResolver(); // after collection; compact ids depend on the full set
|
|
52
|
+
const className = retained
|
|
53
|
+
.map((handle) => resolver.className(handle.key, handle.readableName))
|
|
54
|
+
.join(' ');
|
|
55
|
+
const css = distillery.renderStyles(collector, resolver);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`useHandles` auto-collects nested `&:hover` and `media(...)` / `container(...)` inner rules whose selector deps are met.
|
|
59
|
+
|
|
60
|
+
## Two-pass render
|
|
61
|
+
|
|
62
|
+
Compact class names depend on the **full** collected set. If you print class names during the walk that collects handles, you will mint names too early.
|
|
63
|
+
|
|
64
|
+
1. Walk the tree. For every style you would apply, `collector.useHandles([...])`. Do not read compact names yet.
|
|
65
|
+
2. `const resolver = collector.createResolver()`.
|
|
66
|
+
3. Walk again (or serialize from a recorded list). Write `resolver.className(handle.key, handle.readableName)` only for handles `useHandles` retained.
|
|
67
|
+
4. `distillery.renderStyles(collector, resolver)` and inline the CSS.
|
|
68
|
+
|
|
69
|
+
A readable artifact (`artifactCollector('readable')`) can skip the second pass because `handle.readableName` does not depend on the collected set. Compact is cheaper on the wire; readable is easier to debug.
|
|
70
|
+
|
|
71
|
+
## How much smaller
|
|
72
|
+
|
|
73
|
+
[`docs/examples/06-payload.ts`](https://github.com/elastic/distillate/blob/main/docs/examples/06-payload.ts) authors one module with unused variants and handles, then emits the full readable stylesheet and a compact artifact from a single `root` + `tone.calm` render. The example returns both strings and their UTF-8 byte lengths. In this fixture the compact artifact is about a quarter of the stylesheet. The test asserts it stays under 40% — shaking plus compact names, which is the payload that actually ships.
|
|
74
|
+
|
|
75
|
+
The playground CSS pane shows the same comparison live: full stylesheet versus compact artifact, with byte counts visible without switching tabs.
|
|
76
|
+
|
|
77
|
+
## What is pruned
|
|
78
|
+
|
|
79
|
+
- Unnamed variant entries
|
|
80
|
+
- Handles the walk never named
|
|
81
|
+
- Empty untargeted handles (no-op templates). `useHandles` omits them from its return value; resolve only that list.
|
|
82
|
+
- Local-var defaults that no collected declaration reads. Per-handle, `reachableDefaults` keeps only keys referenced by that handle or by a collected rule whose selector targets it. Rule-level default markers have no host handle and emit every listed key. See [reachability collection](../concepts/collection.md).
|
|
83
|
+
- Theme tokens whose paths were never collected (not interpolated into a collected declaration, not a surviving default-marker value dep, and not marked with `useThemeVar`). A collected path still emits even if the body does not textually contain `var(...)`.
|
|
84
|
+
|
|
85
|
+
`injectGlobal` styles are **not** in an artifact unless you `collector.use(module)` each `globalModules()` entry. Call `stylesheet()` / `stylesheetCollector()` when globals should always ship.
|
|
86
|
+
|
|
87
|
+
A React host that does both passes for you is in [Integrate with a React renderer](react-renderer.md).
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
navigation_title: Migrate from Emotion
|
|
3
|
+
description: Adopt createEmotion without rewriting every call site.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Migrate from Emotion
|
|
7
|
+
|
|
8
|
+
`@elastic/distillate/emotion` is an `@emotion/css`-shaped surface over a distillery: `css`, `cx`, `injectGlobal`, `stylesheet`, `globalModules`. Nested `&` and `@media` use stylis, so existing templates keep their meaning.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { createEmotion, createDomSink } from '@elastic/distillate/emotion';
|
|
12
|
+
|
|
13
|
+
const sink = createDomSink({ document });
|
|
14
|
+
const { css, cx, injectGlobal } = createEmotion(distillery, { sink });
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## What maps
|
|
18
|
+
|
|
19
|
+
| Emotion | Distillate |
|
|
20
|
+
| ------------------- | ---------------------------------------------------------------------------------- |
|
|
21
|
+
| `css\`...\`` | `css\`...\`` — returns a handle that stringifies to the readable class. |
|
|
22
|
+
| `cx(...)` | `cx(...)` — strings, numbers, falsy, arrays, maps, handles. |
|
|
23
|
+
| `injectGlobal` | `injectGlobal` — registered as a module; ships in `stylesheet()`. |
|
|
24
|
+
| `<style>` injection | `createDomSink` — one element, rewritten on each registration, one flush per turn. |
|
|
25
|
+
|
|
26
|
+
## What does not map
|
|
27
|
+
|
|
28
|
+
| Emotion | Status |
|
|
29
|
+
| ---------------------------------------- | --------------------------------------------------------------------------- |
|
|
30
|
+
| `css({ color: 'red' })` | Rejected. Tagged templates only. |
|
|
31
|
+
| `keyframes` | Rejected. |
|
|
32
|
+
| `@supports` / `@container` in `css` | Rejected. Use `container(...)` from the root entry. |
|
|
33
|
+
| `styled.*` / `@emotion/react` `css` prop | Out of scope. |
|
|
34
|
+
| Runtime template values | Styles are static after first construction. Put variation on CSS variables. |
|
|
35
|
+
|
|
36
|
+
## Two ways to use a handle
|
|
37
|
+
|
|
38
|
+
`String(css\`...\`)`is the readable class name and **does not collect**. Use it only against a readable stylesheet (the DOM sink or`stylesheet()`).
|
|
39
|
+
|
|
40
|
+
The same value passed through `resolveClassName` / `collector.useHandles` participates in compact artifact emission. SSR: render `stylesheet()` on the server; content hashes make client and server class names agree.
|
|
41
|
+
|
|
42
|
+
## Composition
|
|
43
|
+
|
|
44
|
+
Do not rely on declaration order across separate `css` calls. Interpolate:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
const base = css`
|
|
48
|
+
color: red;
|
|
49
|
+
`;
|
|
50
|
+
const ext = css`
|
|
51
|
+
${base} <1>
|
|
52
|
+
color: blue;
|
|
53
|
+
`;
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
1. Compose by interpolation. Both declarations land on `ext`'s class; later wins.
|
|
57
|
+
|
|
58
|
+
## Mix with native modules
|
|
59
|
+
|
|
60
|
+
`cx` accepts native `StyleHandle` values and uses their `readableName`. A host can migrate file by file: new modules through `createStyleModule`, remaining call sites through `createEmotion` on the same distillery.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
navigation_title: Guides
|
|
3
|
+
description: When to use Distillate, theming, non-CSS surfaces, compact artifacts, React, Emotion migration, testing, and name collisions.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Guides
|
|
7
|
+
|
|
8
|
+
Task-oriented pages. Concepts live under [Concepts](../concepts/index.md).
|
|
9
|
+
|
|
10
|
+
- [Distillate vs CSS-in-JS](vs-emotion.md) — when Distillate is the right tool
|
|
11
|
+
- [Declare and select variations](theming.md) — named diffs, `{ flatten }`, `{ alternates }`
|
|
12
|
+
- [Read values outside CSS](non-css-surfaces.md) — literal theme values for SVG and other non-CSS surfaces
|
|
13
|
+
- [Ship a compact artifact](compact-artifacts.md) — tree-shaken CSS for HTML that leaves the app
|
|
14
|
+
- [Integrate with a React renderer](react-renderer.md) — collect handles while rendering
|
|
15
|
+
- [Migrate from Emotion](emotion-migration.md) — `createEmotion` over the same registry
|
|
16
|
+
- [Test the var invariant](var-invariant.md) — every `var(...)` has a declaration
|
|
17
|
+
- [Avoid readable-name collisions](name-collisions.md) — hyphen-joined names must be unique
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
navigation_title: Avoid readable-name collisions
|
|
3
|
+
description: Hyphen-joined class and variable names must be unique across the registry.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Avoid readable-name collisions
|
|
7
|
+
|
|
8
|
+
Readable class names join path segments with hyphens: module `card` plus handle `header/title` becomes `card-header-title`. Module `card-header` plus handle `title` becomes the same string. Distillate throws at `createStyleModule` rather than emit two rules for one class.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
distillery.createStyleModule('card', ({ css, tokens }) => ({
|
|
12
|
+
header: {
|
|
13
|
+
title: css`
|
|
14
|
+
color: ${tokens.colors.ink};
|
|
15
|
+
`,
|
|
16
|
+
},
|
|
17
|
+
})); <1>
|
|
18
|
+
distillery.createStyleModule('card-header', ({ css, tokens }) => ({
|
|
19
|
+
title: css`
|
|
20
|
+
color: ${tokens.colors.accent};
|
|
21
|
+
`,
|
|
22
|
+
})); <2>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
1. Readable name: `card-header-title`.
|
|
26
|
+
2. Same readable name. Throws at `createStyleModule`.
|
|
27
|
+
|
|
28
|
+
Local vars join `--${prefix}-${module}-${group}-${key}` via `cssVarName`. These also throw:
|
|
29
|
+
|
|
30
|
+
- Two `vars` groups in one module that hyphen-join to the same property (`look` + `bg` vs `lo` + `ok-bg` is fine; `b-c`/`d` vs `b`/`c-d` is not).
|
|
31
|
+
- A local var that hyphenates to the same property as a theme or shared path (`chip` + `look`/`bg` vs theme path `chip/look/bg`).
|
|
32
|
+
- A theme path and a shared path that hyphenate to the same property (`colors/ink` vs `vars/colors/ink`).
|
|
33
|
+
- A theme-tree key that contains `-` (`colors-ink`). Hyphens are rejected so path segments reverse uniquely.
|
|
34
|
+
|
|
35
|
+
Compact mode is path-keyed and unaffected. Identifier segments (`prefix`, module names, group names, keys) must match `[A-Za-z_][A-Za-z0-9_-]*`. Hyphens are allowed so emotion modules named `css-${hash}` stay legal — they are also why collisions are possible.
|
|
36
|
+
|
|
37
|
+
Pick one nesting convention per library (flat handle names, or nested objects, not both colliding) and keep `prefix` short and unique.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
navigation_title: Read values outside CSS
|
|
3
|
+
description: Resolve theme literals, or emit a stylesheet with one scheme already chosen, for surfaces that cannot use CSS custom properties or `light-dark()`.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Read values outside CSS
|
|
7
|
+
|
|
8
|
+
`tokens` stringify to `var(--x)`. That is the right shape for CSS. A headless SVG or PNG rasterizer has no custom-property engine, so it needs the nested literal strings `createDistillery` already computed. A target that consumes the stylesheet itself but has no color scheme to resolve `light-dark()` against — an image or PDF backend, email HTML, an older browser — needs the same values written into the theme block.
|
|
9
|
+
|
|
10
|
+
`resolveValues(scheme, variation?)` walks the original theme tree: `lightDark` and string leaves come from `themeVars` for that scheme, `cq` / `ScaleToken` leaves become `.value`. Named variations apply the same diffs `renderStyles({ flatten })` would. Resolution is lazy; unused variations are not precomputed.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { createDistillery, cq, lightDark, mapDomain } from '@elastic/distillate';
|
|
14
|
+
|
|
15
|
+
const theme = {
|
|
16
|
+
colors: {
|
|
17
|
+
ink: lightDark('#111', '#eee'),
|
|
18
|
+
accent: lightDark('#06c', '#8cf'),
|
|
19
|
+
},
|
|
20
|
+
type: {
|
|
21
|
+
size: {
|
|
22
|
+
s: '12px',
|
|
23
|
+
m: '16px',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
gap: cq('8px', '2cqi'),
|
|
27
|
+
} as const;
|
|
28
|
+
|
|
29
|
+
const distillery = createDistillery({
|
|
30
|
+
prefix: 'eui',
|
|
31
|
+
themeScope: '.eui-view',
|
|
32
|
+
theme,
|
|
33
|
+
variations: {
|
|
34
|
+
muted: { colors: { accent: '#0077cc' } },
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const light = distillery.resolveValues('light');
|
|
39
|
+
light.colors.ink; // '#111'
|
|
40
|
+
light.type.size.m; // '16px'
|
|
41
|
+
light.gap; // '8px'
|
|
42
|
+
|
|
43
|
+
const mutedDark = distillery.resolveValues('dark', 'muted');
|
|
44
|
+
mutedDark.colors.accent; // '#0077cc'
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Emit a single-scheme stylesheet
|
|
48
|
+
|
|
49
|
+
When the consumer still wants CSS — the React tree plus the emitted sheet — pass `{ scheme: 'light' | 'dark' }` to `renderStyles`. Differing `lightDark` leaves emit that scheme's literal instead of `light-dark(...)`.
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
distillery.renderStyles(collector, undefined, { scheme: 'light' });
|
|
53
|
+
// --eui-colors-ink:#111 (not light-dark(#111,#eee))
|
|
54
|
+
|
|
55
|
+
distillery.renderStyles(collector, undefined, {
|
|
56
|
+
scheme: 'light',
|
|
57
|
+
flatten: 'muted',
|
|
58
|
+
});
|
|
59
|
+
// --eui-colors-accent:#0077cc (the variation, not the base)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`scheme` reads the `ThemeVarDefinition` each var-block already selected, so it composes with `{ flatten }` and `{ alternates }`. `themeValueOverrides` still wins. A variation that differs from the base only on the unselected side still emits its diff; under `scheme` that declaration can equal the primary block. Combining `scheme` with `alternates` is allowed: `scheme` describes the target, not the content.
|
|
63
|
+
|
|
64
|
+
`resolveThemeValues(theme, themeVars, scheme, variation?)` is the same walk when you already have the tree and registry and do not want to go through the distillery.
|
|
65
|
+
|
|
66
|
+
The engine does not strip units. `'16px'` stays `'16px'`; a renderer that wants a number parses the string. `.cq` stays on the `ScaleToken` in `theme` / `tokens` if the surface needs the container-relative form.
|
|
67
|
+
|
|
68
|
+
`mapDomain` is the non-CSS half of `variants()`: map an enum to a value with no collector side effect. `variants()` calls it, then marks each value so `collector.use(module)` skips it.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
const sizes = ['s', 'm'] as const;
|
|
72
|
+
const typeSize = mapDomain(sizes, (size) => light.type.size[size]);
|
|
73
|
+
```
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
---
|
|
2
|
+
navigation_title: Integrate with a React renderer
|
|
3
|
+
description: Collect handles while rendering, then emit class names and CSS.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Integrate with a React renderer
|
|
7
|
+
|
|
8
|
+
Distillate does not ship a React runtime. A host renderer owns the class-name context and decides when to collect.
|
|
9
|
+
|
|
10
|
+
## Readable stylesheet (app)
|
|
11
|
+
|
|
12
|
+
Register modules at module-eval time. Emit one stylesheet (build step or `createDomSink`) and put `handle.readableName` on `className`. No collector is required at render time:
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { createDistillery, cq, lightDark } from '@elastic/distillate';
|
|
16
|
+
import type { ReactNode } from 'react';
|
|
17
|
+
|
|
18
|
+
const distillery = createDistillery({
|
|
19
|
+
prefix: 'eui',
|
|
20
|
+
themeScope: '.eui-view',
|
|
21
|
+
theme: {
|
|
22
|
+
colors: { ink: lightDark('#111', '#eee') },
|
|
23
|
+
gap: cq('8px', '2cqi'),
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const { handles } = distillery.createStyleModule('button', ({ css, tokens }) => ({
|
|
28
|
+
root: css`
|
|
29
|
+
color: ${tokens.colors.ink};
|
|
30
|
+
`,
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
export const Button = ({ children }: { children: ReactNode }) => (
|
|
34
|
+
<button className={handles.root.readableName}>{children}</button>
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// writeFileSync(
|
|
38
|
+
// 'dist/styles.css',
|
|
39
|
+
// distillery.renderStyles(distillery.stylesheetCollector())
|
|
40
|
+
// );
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Compact artifact (export / email / SVG)
|
|
44
|
+
|
|
45
|
+
Collection has to observe the tree. Compact names depend on the full collected set, so render twice: once to `useHandles`, once to write class names. `combineClassNames` is the component-facing API so views never mention the collector.
|
|
46
|
+
|
|
47
|
+
Reuse the distillery and `button` module from above. The extra pieces are a context, views that call `combineClassNames`, and a two-pass helper. Imports in the next snippet cover the rest of this section.
|
|
48
|
+
|
|
49
|
+
### Pass a class-name context
|
|
50
|
+
|
|
51
|
+
Both render passes implement this shape. Views only ever see `resolveClassName`:
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import {
|
|
55
|
+
combineClassNames,
|
|
56
|
+
type Distillery,
|
|
57
|
+
type StyleHandle,
|
|
58
|
+
type StylesModule,
|
|
59
|
+
} from '@elastic/distillate';
|
|
60
|
+
import { createContext, useContext, type ReactElement, type ReactNode } from 'react';
|
|
61
|
+
import { renderToStaticMarkup } from 'react-dom/server';
|
|
62
|
+
|
|
63
|
+
interface StyleContextValue {
|
|
64
|
+
resolveClassName: (...handles: StyleHandle[]) => string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const StyleContext = createContext<StyleContextValue | null>(null);
|
|
68
|
+
|
|
69
|
+
const useStyleContext = (): StyleContextValue => {
|
|
70
|
+
const value = useContext(StyleContext);
|
|
71
|
+
if (!value) {
|
|
72
|
+
throw new Error('StyleContext is missing.');
|
|
73
|
+
}
|
|
74
|
+
return value;
|
|
75
|
+
};
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Views call `combineClassNames`
|
|
79
|
+
|
|
80
|
+
Same `handles.root` as the app button. Swap `readableName` for the context:
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
const Button = ({ children }: { children: ReactNode }) => {
|
|
84
|
+
const ctx = useStyleContext();
|
|
85
|
+
return <button className={combineClassNames(ctx, handles.root)}>{children}</button>;
|
|
86
|
+
};
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Pass every handle the element should wear: `combineClassNames(ctx, handles.root, other.handles.title)`.
|
|
90
|
+
|
|
91
|
+
### Render twice
|
|
92
|
+
|
|
93
|
+
The first pass must finish before any class name is printed. `createResolver()` assigns compact names from the collected set; reading them earlier mints names too soon. Leave `globals` empty unless you have `injectGlobal` styles (next section).
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
export const renderArtifact = (
|
|
97
|
+
tree: ReactElement,
|
|
98
|
+
distillery: Distillery,
|
|
99
|
+
globals: readonly StylesModule[] = []
|
|
100
|
+
): { html: string; css: string } => {
|
|
101
|
+
const collector = distillery.artifactCollector('compact');
|
|
102
|
+
for (const module of globals) {
|
|
103
|
+
collector.use(module);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Pass 1: record handles. Compact names are not assigned yet.
|
|
107
|
+
const collecting: StyleContextValue = {
|
|
108
|
+
resolveClassName: (...handles) => {
|
|
109
|
+
collector.useHandles(handles);
|
|
110
|
+
return '';
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
renderToStaticMarkup(
|
|
115
|
+
<StyleContext.Provider value={collecting}>{tree}</StyleContext.Provider>
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const resolver = collector.createResolver();
|
|
119
|
+
|
|
120
|
+
// Pass 2: write class names now that the collected set is complete.
|
|
121
|
+
const emitting: StyleContextValue = {
|
|
122
|
+
resolveClassName: (...handles) =>
|
|
123
|
+
collector
|
|
124
|
+
.useHandles(handles)
|
|
125
|
+
.map((handle) => resolver.className(handle.key, handle.readableName))
|
|
126
|
+
.join(' '),
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const html = renderToStaticMarkup(
|
|
130
|
+
<StyleContext.Provider value={emitting}>{tree}</StyleContext.Provider>
|
|
131
|
+
);
|
|
132
|
+
return { html, css: distillery.renderStyles(collector, resolver) };
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
renderArtifact(<Button>Hello</Button>, distillery);
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
A readable artifact (`artifactCollector('readable')`) can skip the emitting pass because `handle.readableName` does not depend on the collected set. See [Ship a compact artifact](compact-artifacts.md).
|
|
139
|
+
|
|
140
|
+
### Include `injectGlobal` styles
|
|
141
|
+
|
|
142
|
+
Globals from `injectGlobal` are separate modules. They are not implied by collecting handles; pass `globalModules()` into the helper when they should ship with the artifact:
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
import { createEmotion } from '@elastic/distillate/emotion';
|
|
146
|
+
|
|
147
|
+
const { injectGlobal, globalModules } = createEmotion(distillery);
|
|
148
|
+
|
|
149
|
+
injectGlobal`
|
|
150
|
+
html {
|
|
151
|
+
color-scheme: light dark;
|
|
152
|
+
}
|
|
153
|
+
`;
|
|
154
|
+
|
|
155
|
+
renderArtifact(<Button>Hello</Button>, distillery, globalModules());
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Do not mix a compact stylesheet with markup that used `readableName`. The DOM will carry `.button-root` while the `<style>` contains `.a`.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
navigation_title: Declare and select variations
|
|
3
|
+
description: Named variations, render-time flatten and alternates, and when not to use a variation.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Declare and select variations
|
|
7
|
+
|
|
8
|
+
`theme` is the default tree you ship. If you only ship one brand, that brand is `theme`, not a variation.
|
|
9
|
+
|
|
10
|
+
`variations` are named, value-only diffs of that same tree. Declaring one does not emit it. Name it at `renderStyles` with `{ flatten }` or `{ alternates }` so unused variations cost nothing.
|
|
11
|
+
|
|
12
|
+
## Declare
|
|
13
|
+
|
|
14
|
+
Named variations cannot introduce new paths. They do not chain: each is resolved against the base, not against another variation. `media` is the query only (`'(prefers-contrast: more)'`), not `@media (...)`.
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
const distillery = createDistillery({
|
|
18
|
+
prefix: 'eui',
|
|
19
|
+
themeScope: '.eui-view',
|
|
20
|
+
theme: {
|
|
21
|
+
colors: {
|
|
22
|
+
ink: lightDark('#111', '#eee'),
|
|
23
|
+
accent: lightDark('#06c', '#8cf'),
|
|
24
|
+
},
|
|
25
|
+
gap: cq('8px', '2cqi'),
|
|
26
|
+
},
|
|
27
|
+
variations: {
|
|
28
|
+
muted: {
|
|
29
|
+
colors: { accent: '#0077cc' }, // flatten with { flatten: 'muted' }
|
|
30
|
+
},
|
|
31
|
+
highContrast: {
|
|
32
|
+
media: '(prefers-contrast: more)',
|
|
33
|
+
variation: {
|
|
34
|
+
colors: { ink: lightDark('#000', '#fff') },
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
A selector is the consuming page's DOM contract, so it is supplied per render rather than on the variation.
|
|
42
|
+
|
|
43
|
+
`cq()` / `ScaleToken` leaves inline at authoring time and cannot change in a variation. Spacing authored with `cq('8px', '2cqi')` is fixed; a `dense` variation that tried to swap `cq('4px', '1cqi')` throws. Making density selectable means authoring that size as a string leaf so it becomes a theme var, and giving up inlining.
|
|
44
|
+
|
|
45
|
+
## Select
|
|
46
|
+
|
|
47
|
+
Base only is the default, and byte-for-byte what a distillery without `variations` emits:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
distillery.renderStyles(collector);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Flatten one non-media variation into `themeScope`. Same declaration count as the base; values change:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
distillery.renderStyles(collector, undefined, { flatten: 'muted' });
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`{ scheme: 'light' | 'dark' }` is the same idea for the built-in color scheme: emit one side's literal instead of `light-dark(...)`. It applies to every emitted block, including `alternates`. See [read values outside CSS](non-css-surfaces.md).
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
distillery.renderStyles(collector, undefined, { scheme: 'light' });
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Runtime switching: the base fills `themeScope`, each alternate emits only its diff:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
distillery.renderStyles(collector, undefined, {
|
|
69
|
+
alternates: [
|
|
70
|
+
{ variation: 'muted', selector: '[data-eui-theme="muted"]' },
|
|
71
|
+
],
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Independent paths compose through the cascade. Flatten `muted` and list `highContrast` as an alternate:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
distillery.renderStyles(collector, undefined, {
|
|
79
|
+
flatten: 'muted',
|
|
80
|
+
alternates: [{ variation: 'highContrast' }],
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
That writes muted values into `themeScope`, then the high-contrast diff (computed against the base, not against muted) inside `@media (prefers-contrast: more)`. Colliding paths do not merge: the media block's value wins inside the query. A combined variation is only needed if the high-contrast values themselves depend on which flattenable variation is selected.
|
|
85
|
+
|
|
86
|
+
`:host` composes as `:host(selector)`. Media variations may omit `selector`; the diffs wrap in `@media`. Flattening a media variation does **not** replace the primary block: the base still fills `themeScope`, and the diffs wrap in `@media`. That is byte-identical to listing the same name in `alternates`.
|
|
87
|
+
|
|
88
|
+
`themeValueOverrides` still apply per render for values known only at request time. They win over both `flatten` and `scheme`. Declared variations are for values known when the distillery is created. A non-CSS consumer that needs those declared values calls `distillery.resolveValues(scheme, variation?)`; that walk does not apply `themeValueOverrides`. A CSS-consuming surface that cannot resolve `light-dark()` uses `renderStyles({ scheme })` instead. See [read values outside CSS](non-css-surfaces.md).
|
|
89
|
+
|
|
90
|
+
## What a variation cannot do
|
|
91
|
+
|
|
92
|
+
A variation changes values for token paths that are already collected. It never changes which declarations exist. A structural response to `prefers-contrast` — replacing `box-shadow` with a border, hiding a decorative rule — belongs in the module via [`media()`](../concepts/authoring.md):
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
media('(prefers-contrast: more)', [
|
|
96
|
+
rule(
|
|
97
|
+
(h) => h.root,
|
|
98
|
+
decls`box-shadow: none; border: 1px solid ${tokens.colors.ink};`
|
|
99
|
+
),
|
|
100
|
+
]);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## When not to use a variation
|
|
104
|
+
|
|
105
|
+
Anything that can diverge structurally over time — a second brand, a second product — is a second distillery with its own base tree. One distillery per library keeps registries private; see [the distillery](../concepts/distillery.md).
|
|
106
|
+
|
|
107
|
+
The engine will not stop you from growing `theme` with paths that exist only so one variation can touch them. That is token feature-flagging. If a difference needs new paths or different leaf kinds, it is not a variation.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
navigation_title: Test the var invariant
|
|
3
|
+
description: Assert every var() in emitted CSS has a matching declaration.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Test the var invariant
|
|
7
|
+
|
|
8
|
+
A compact payload that references `--missing` will look fine in development if the host app's sheet happens to declare it, then break in email or SVG. `@elastic/distillate/testing` scans CSS text and reports `var(...)` names that are never declared in the same string.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import {
|
|
12
|
+
assertVarRefsHaveDeclarations,
|
|
13
|
+
findVarRefViolations,
|
|
14
|
+
} from '@elastic/distillate/testing';
|
|
15
|
+
|
|
16
|
+
const css = distillery.renderStyles(collector);
|
|
17
|
+
assertVarRefsHaveDeclarations(css); // throws with surrounding context
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`findVarRefViolations` returns `{ reference, context }[]` when the assertion should not throw.
|
|
21
|
+
|
|
22
|
+
The scanner is whitespace-tolerant (`var( --x )`) and ignores strings, comments, and function names that merely end in `var`. A BEM selector like `.button--active:hover` is not a declaration of `--active`.
|
|
23
|
+
|
|
24
|
+
This entry reaches no third-party package. Run it against both compact artifacts and readable stylesheets; the helper does not care which names you used.
|