@elastic/distillate 0.1.0 → 0.2.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/dist/cjs/collector.js +129 -58
- package/dist/cjs/collector.js.map +1 -1
- package/dist/cjs/dom_sink.js +2 -2
- package/dist/cjs/dom_sink.js.map +1 -1
- package/dist/cjs/emotion.js.map +1 -1
- package/dist/cjs/engine.js +24 -8
- package/dist/cjs/engine.js.map +1 -1
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/sink.js +9 -0
- package/dist/cjs/sink.js.map +1 -0
- package/dist/collector.d.ts +18 -1
- package/dist/collector.d.ts.map +1 -1
- package/dist/collector.js +129 -58
- package/dist/collector.js.map +1 -1
- package/dist/dom_sink.d.ts +8 -12
- package/dist/dom_sink.d.ts.map +1 -1
- package/dist/dom_sink.js +2 -2
- package/dist/dom_sink.js.map +1 -1
- package/dist/emotion.d.ts +3 -6
- package/dist/emotion.d.ts.map +1 -1
- package/dist/emotion.js.map +1 -1
- package/dist/engine.d.ts +26 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +24 -8
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/sink.d.ts +16 -0
- package/dist/sink.d.ts.map +1 -0
- package/dist/sink.js +8 -0
- package/dist/sink.js.map +1 -0
- package/docs/concepts/collection.md +1 -1
- package/docs/getting-started/installation.md +5 -5
- package/docs/guides/react-renderer.md +45 -1
- package/docs/reference/api/emotion.md +58 -140
- package/docs/reference/api/index-1.md +452 -245
- package/docs/reference/api/testing.md +6 -6
- package/docs/reference/entry-points.md +18 -4
- package/docs/reference/okf-map.md +2 -1
- package/package.json +1 -1
- package/src/collector.ts +145 -59
- package/src/dom_sink.ts +9 -10
- package/src/emotion.ts +3 -6
- package/src/engine.ts +59 -9
- package/src/index.ts +14 -1
- package/src/sink.ts +25 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
### VarInvariantViolation [#varinvariantviolation]
|
|
6
6
|
|
|
7
|
-
Defined in: [var\_invariant.ts:14](https://github.com/elastic/distillate/blob/
|
|
7
|
+
Defined in: [var\_invariant.ts:14](https://github.com/elastic/distillate/blob/d1dccb0eb5372d5984331c21603220340752a304/src/var_invariant.ts#L14)
|
|
8
8
|
|
|
9
9
|
A `var(...)` whose custom property is never declared in the same CSS text.
|
|
10
10
|
|
|
@@ -12,8 +12,8 @@ A `var(...)` whose custom property is never declared in the same CSS text.
|
|
|
12
12
|
|
|
13
13
|
| Property | Modifier | Type | Description | Defined in |
|
|
14
14
|
| ------ | ------ | ------ | ------ | ------ |
|
|
15
|
-
| $$$context$$$ `context` | `readonly` | `string` | Surrounding CSS text for error reporting. | [var\_invariant.ts:18](https://github.com/elastic/distillate/blob/
|
|
16
|
-
| $$$reference$$$ `reference` | `readonly` | `string` | The undeclared custom property name (e.g. `--aui-color-bg`). | [var\_invariant.ts:16](https://github.com/elastic/distillate/blob/
|
|
15
|
+
| $$$context$$$ `context` | `readonly` | `string` | Surrounding CSS text for error reporting. | [var\_invariant.ts:18](https://github.com/elastic/distillate/blob/d1dccb0eb5372d5984331c21603220340752a304/src/var_invariant.ts#L18) |
|
|
16
|
+
| $$$reference$$$ `reference` | `readonly` | `string` | The undeclared custom property name (e.g. `--aui-color-bg`). | [var\_invariant.ts:16](https://github.com/elastic/distillate/blob/d1dccb0eb5372d5984331c21603220340752a304/src/var_invariant.ts#L16) |
|
|
17
17
|
|
|
18
18
|
## Functions
|
|
19
19
|
|
|
@@ -23,7 +23,7 @@ A `var(...)` whose custom property is never declared in the same CSS text.
|
|
|
23
23
|
function assertVarRefsHaveDeclarations(css): void;
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
Defined in: [var\_invariant.ts:135](https://github.com/elastic/distillate/blob/
|
|
26
|
+
Defined in: [var\_invariant.ts:135](https://github.com/elastic/distillate/blob/d1dccb0eb5372d5984331c21603220340752a304/src/var_invariant.ts#L135)
|
|
27
27
|
|
|
28
28
|
Throws if any `var(...)` in `css` has no matching custom-property declaration.
|
|
29
29
|
|
|
@@ -49,7 +49,7 @@ If any reference has no declaration in the same text.
|
|
|
49
49
|
function findVarRefViolations(css): readonly VarInvariantViolation[];
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
Defined in: [var\_invariant.ts:109](https://github.com/elastic/distillate/blob/
|
|
52
|
+
Defined in: [var\_invariant.ts:109](https://github.com/elastic/distillate/blob/d1dccb0eb5372d5984331c21603220340752a304/src/var_invariant.ts#L109)
|
|
53
53
|
|
|
54
54
|
Returns all `var(...)` references whose custom property is never declared in `css`.
|
|
55
55
|
|
|
@@ -75,7 +75,7 @@ function tokenTreeDts(
|
|
|
75
75
|
): string;
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
Defined in: [testing.ts:23](https://github.com/elastic/distillate/blob/
|
|
78
|
+
Defined in: [testing.ts:23](https://github.com/elastic/distillate/blob/d1dccb0eb5372d5984331c21603220340752a304/src/testing.ts#L23)
|
|
79
79
|
|
|
80
80
|
Emits a TypeScript `interface` for a derived token tree, for Monaco extraLib text.
|
|
81
81
|
|
|
@@ -31,13 +31,16 @@ import {
|
|
|
31
31
|
| `css` / `decls` | runtime | Handle and declaration templates. Also on the `createStyleModule` factory argument. |
|
|
32
32
|
| `rule` / `media` / `container` / `variants` / `mapDomain` | runtime | Root-entry authoring helpers. `mapDomain` has no collector side effect. |
|
|
33
33
|
| `combineClassNames` | runtime | `context.resolveClassName(...handles)`. |
|
|
34
|
-
| `StylesCollector` | runtime | Class; `artifactCollector` / `stylesheetCollector` return instances. |
|
|
34
|
+
| `StylesCollector` | runtime | Class; `artifactCollector` / `stylesheetCollector` / `liveCollection` return or wrap instances. `subscribe` reports growth. |
|
|
35
|
+
| `createDomSink` | runtime | One `<style>` element in `document.head` or a given `parent` (e.g. a `ShadowRoot`), flushed per turn. Also exported from `./emotion`. |
|
|
35
36
|
| `renderStyles` | runtime | Unbound renderer; prefer `distillery.renderStyles`. |
|
|
36
37
|
| `createStyleNameResolver` | runtime | Standalone compact/readable name map. |
|
|
37
38
|
| `StyleRegistry` | runtime | Module registry class. |
|
|
38
39
|
| `Distillery` / `DistilleryOptions` / `DistilleryEnvironment` / `ThemeVarDefinition` | type | Bindings. |
|
|
39
40
|
| `TokensOf` / `ValuesOf` / `PathsOf` / `ThemeTree` / `SchemePair` / `ThemeVariation` / `ResolvedThemeVariation` / `ThemeAlternate` / `RenderStylesOptions` | type | Theme derivation and render selection. |
|
|
40
41
|
| `StyleHandle` / `StylesModule` / `StyleNameResolver` / `StyleNameMode` / `StyleTarget` | type | Handles, modules, naming. |
|
|
42
|
+
| `LiveCollection` / `LiveCollectionOptions` | type | `distillery.liveCollection` result and options. |
|
|
43
|
+
| `StyleSink` / `CreateDomSinkOptions` / `DocumentLike` / `StyleElementLike` / `StyleParentLike` | type | Sink surface. |
|
|
41
44
|
|
|
42
45
|
### Key signatures
|
|
43
46
|
|
|
@@ -57,6 +60,11 @@ distillery.stylesheetCollector(
|
|
|
57
60
|
names?: 'compact' | 'readable',
|
|
58
61
|
options?: { warn?: (message: string) => void }
|
|
59
62
|
): StylesCollector;
|
|
63
|
+
distillery.liveCollection(options?: {
|
|
64
|
+
sink?: StyleSink;
|
|
65
|
+
render?: RenderStylesOptions;
|
|
66
|
+
warn?: (message: string) => void;
|
|
67
|
+
}): LiveCollection; // { collector, resolveClassName(...handles), css() }
|
|
60
68
|
distillery.renderStyles(
|
|
61
69
|
collector: StylesCollector,
|
|
62
70
|
resolver?: StyleNameResolver,
|
|
@@ -66,6 +74,12 @@ distillery.renderStyles(
|
|
|
66
74
|
collector.use(module | entry | entries): void;
|
|
67
75
|
collector.useHandles(handles: readonly StyleHandle[]): readonly StyleHandle[];
|
|
68
76
|
collector.createResolver(): StyleNameResolver;
|
|
77
|
+
collector.subscribe(listener: () => void): () => void; // once per outermost call that grows
|
|
78
|
+
createDomSink(options: {
|
|
79
|
+
document: DocumentLike;
|
|
80
|
+
parent?: StyleParentLike;
|
|
81
|
+
schedule?: (flush: () => void) => void;
|
|
82
|
+
}): StyleSink;
|
|
69
83
|
```
|
|
70
84
|
|
|
71
85
|
Field-level environment types: [Environment](environment.md).
|
|
@@ -88,13 +102,13 @@ const { css, cx, injectGlobal, stylesheet, globalModules } = createEmotion(
|
|
|
88
102
|
| Export | Kind | Role |
|
|
89
103
|
| ------ | ---- | ---- |
|
|
90
104
|
| `createEmotion` | runtime | `css` / `cx` / `injectGlobal` over the distillery registry. |
|
|
91
|
-
| `createDomSink` | runtime | One `<style>` element, flushed per turn. |
|
|
105
|
+
| `createDomSink` | runtime | One `<style>` element, flushed per turn. Re-exported from the root entry. |
|
|
92
106
|
| `Emotion` / `EmotionCss` / `CreateEmotionOptions` / `StyleSink` | type | Return and option types. |
|
|
93
|
-
| `CreateDomSinkOptions` / `DocumentLike` / `StyleElementLike` | type | DOM sink surface. |
|
|
107
|
+
| `CreateDomSinkOptions` / `DocumentLike` / `StyleElementLike` / `StyleParentLike` | type | DOM sink surface. Same as the root entry. |
|
|
94
108
|
|
|
95
109
|
```ts
|
|
96
110
|
createEmotion(distillery, options?: { sink?: StyleSink }): Emotion;
|
|
97
|
-
createDomSink(options: { document: DocumentLike; schedule?: (flush: () => void) => void }): StyleSink;
|
|
111
|
+
createDomSink(options: { document: DocumentLike; parent?: StyleParentLike; schedule?: (flush: () => void) => void }): StyleSink;
|
|
98
112
|
```
|
|
99
113
|
|
|
100
114
|
The string form of a `css` template is readable and does not collect. The wrapper is still a `StyleHandle` for `useHandles` / `resolveClassName`.
|
|
@@ -8,7 +8,7 @@ description: Generated map of the Distillate OKF concept graph.
|
|
|
8
8
|
Generated from `.okf/distillate` by `pnpm okf:map`. Do not edit by hand.
|
|
9
9
|
|
|
10
10
|
- Concepts: 19
|
|
11
|
-
- Links:
|
|
11
|
+
- Links: 30
|
|
12
12
|
- Isolated concepts: 0
|
|
13
13
|
|
|
14
14
|
## Graph
|
|
@@ -44,6 +44,7 @@ flowchart LR
|
|
|
44
44
|
concepts_ordering --> concepts_naming
|
|
45
45
|
concepts_tokens --> concepts_distillery
|
|
46
46
|
concepts_tokens --> reference_public_contract
|
|
47
|
+
entry_points_emotion --> entry_points_root
|
|
47
48
|
entry_points_emotion --> playbooks_migrate_from_emotion
|
|
48
49
|
entry_points_root --> entry_points_emotion
|
|
49
50
|
entry_points_root --> entry_points_testing
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elastic/distillate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Theme-agnostic CSS style engine: typed authoring with dependency capture, render-driven reachability collection, and dual readable/compact output targets. One runtime dependency: stylis.",
|
|
5
5
|
"license": "Elastic-2.0",
|
|
6
6
|
"author": "Elastic",
|
package/src/collector.ts
CHANGED
|
@@ -113,6 +113,9 @@ export class StylesCollector {
|
|
|
113
113
|
private readonly handles = new Map<string, StyleHandle>();
|
|
114
114
|
private readonly themeDeps = new Set<string>();
|
|
115
115
|
private readonly varDeps = new Set<ContextualVarPath>();
|
|
116
|
+
private readonly listeners = new Set<() => void>();
|
|
117
|
+
private depth = 0;
|
|
118
|
+
private dirty = false;
|
|
116
119
|
|
|
117
120
|
constructor({
|
|
118
121
|
target,
|
|
@@ -138,6 +141,12 @@ export class StylesCollector {
|
|
|
138
141
|
* @param value Module (all non-variant entries), a single entry, or an array of entries.
|
|
139
142
|
*/
|
|
140
143
|
use(value: StylesModule | StyleEntry | readonly StyleEntry[]): void {
|
|
144
|
+
this.batch(() => this.useValue(value));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private useValue(
|
|
148
|
+
value: StylesModule | StyleEntry | readonly StyleEntry[]
|
|
149
|
+
): void {
|
|
141
150
|
if (isReadonlyArray(value)) {
|
|
142
151
|
value.forEach((entry) => this.use(entry));
|
|
143
152
|
return;
|
|
@@ -155,7 +164,7 @@ export class StylesCollector {
|
|
|
155
164
|
if (entry.kind === 'handle' && !this.retainHandle(entry)) {
|
|
156
165
|
continue;
|
|
157
166
|
}
|
|
158
|
-
this.
|
|
167
|
+
this.setEntry(entry);
|
|
159
168
|
if (entry.kind === 'handle' || entry.kind === 'rule') {
|
|
160
169
|
this.addDeclarationDeps(entry.declarations);
|
|
161
170
|
} else {
|
|
@@ -171,7 +180,7 @@ export class StylesCollector {
|
|
|
171
180
|
if (!this.retainHandle(handle)) {
|
|
172
181
|
continue;
|
|
173
182
|
}
|
|
174
|
-
this.
|
|
183
|
+
this.setHandle(handle);
|
|
175
184
|
}
|
|
176
185
|
return;
|
|
177
186
|
}
|
|
@@ -185,69 +194,89 @@ export class StylesCollector {
|
|
|
185
194
|
* @param module Module whose entries should all be collected.
|
|
186
195
|
*/
|
|
187
196
|
useAllEntries(module: StylesModule): void {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
197
|
+
this.batch(() => {
|
|
198
|
+
for (const entry of module.entries) {
|
|
199
|
+
if (entry.kind === 'handle' && !this.retainHandle(entry)) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
this.setEntry(entry);
|
|
191
203
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
204
|
+
for (const handle of module.handleList) {
|
|
205
|
+
if (!this.retainHandle(handle)) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
this.setHandle(handle);
|
|
197
209
|
}
|
|
198
|
-
this.
|
|
199
|
-
}
|
|
200
|
-
module.themeDeps.forEach((path) => this.themeDeps.add(path));
|
|
201
|
-
module.varDeps.forEach((path) => this.varDeps.add(path));
|
|
210
|
+
this.addDeps(module.themeDeps, module.varDeps);
|
|
211
|
+
});
|
|
202
212
|
}
|
|
203
213
|
|
|
204
214
|
/**
|
|
205
215
|
* Collects handles and auto-includes rules whose selector deps are all present.
|
|
206
216
|
*
|
|
207
|
-
* Media blocks
|
|
217
|
+
* Media blocks gain inner rules whose deps are met and keep rules already collected.
|
|
208
218
|
*
|
|
209
219
|
* @param handles Handles resolved during this render pass (e.g. from `resolveClassName`).
|
|
210
220
|
* @returns Handles that were retained. Empty untargeted handles are omitted in compact mode.
|
|
211
221
|
*/
|
|
212
222
|
useHandles(handles: readonly StyleHandle[]): readonly StyleHandle[] {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
for (const handle of retained) {
|
|
227
|
-
const module = this.registry.module(handle.moduleName);
|
|
228
|
-
if (!module) {
|
|
229
|
-
continue;
|
|
223
|
+
return this.batch(() => {
|
|
224
|
+
const retained: StyleHandle[] = [];
|
|
225
|
+
for (const handle of handles) {
|
|
226
|
+
if (!this.retainHandle(handle)) {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
retained.push(handle);
|
|
230
|
+
if (this.handles.has(handle.key)) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
this.setEntry(handle);
|
|
234
|
+
this.setHandle(handle);
|
|
235
|
+
this.addDeclarationDeps(handle.declarations);
|
|
230
236
|
}
|
|
231
|
-
for (const
|
|
232
|
-
this.
|
|
237
|
+
for (const handle of retained) {
|
|
238
|
+
const module = this.registry.module(handle.moduleName);
|
|
239
|
+
if (!module) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
for (const dependent of dependentEntries(module, handle.key)) {
|
|
243
|
+
this.tryActivateDependent(dependent);
|
|
244
|
+
}
|
|
233
245
|
}
|
|
234
|
-
|
|
235
|
-
|
|
246
|
+
return retained;
|
|
247
|
+
});
|
|
236
248
|
}
|
|
237
249
|
|
|
238
250
|
/** Collects `auto` rules on `module` whose every selector dependency is already collected. */
|
|
239
251
|
useRulesWhenDepsMet(module: StylesModule): void {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
this.
|
|
252
|
+
this.batch(() => {
|
|
253
|
+
for (const entry of module.entries) {
|
|
254
|
+
if (entry.kind !== 'rule') {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (this.entries.has(entryKey(entry))) {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (this.allDepsMet(entry.dependsOn)) {
|
|
261
|
+
this.addEntry(entry);
|
|
262
|
+
}
|
|
249
263
|
}
|
|
250
|
-
}
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Calls `listener` after a public mutation adds an entry, handle, theme var, or contextual var.
|
|
269
|
+
*
|
|
270
|
+
* Fires at most once per outermost call; nested calls such as `use([a, b])` batch into it.
|
|
271
|
+
*
|
|
272
|
+
* @param listener Called with no arguments after the collection grows.
|
|
273
|
+
* @returns Unsubscribes `listener`.
|
|
274
|
+
*/
|
|
275
|
+
subscribe(listener: () => void): () => void {
|
|
276
|
+
this.listeners.add(listener);
|
|
277
|
+
return () => {
|
|
278
|
+
this.listeners.delete(listener);
|
|
279
|
+
};
|
|
251
280
|
}
|
|
252
281
|
|
|
253
282
|
private tryActivateDependent(entry: StyleEntry): void {
|
|
@@ -265,18 +294,22 @@ export class StylesCollector {
|
|
|
265
294
|
return;
|
|
266
295
|
}
|
|
267
296
|
if (entry.kind === 'media') {
|
|
268
|
-
|
|
269
|
-
|
|
297
|
+
// Add newly live rules; keep rules already collected (e.g. by `use`).
|
|
298
|
+
const existing = this.entries.get(entryKey(entry));
|
|
299
|
+
const collected = new Set<StyleRule>(
|
|
300
|
+
existing?.kind === 'media' ? existing.rules : []
|
|
301
|
+
);
|
|
302
|
+
const rules = entry.rules.filter(
|
|
303
|
+
(rule) =>
|
|
304
|
+
collected.has(rule) || (rule.auto && this.allDepsMet(rule.dependsOn))
|
|
270
305
|
);
|
|
271
|
-
if (
|
|
306
|
+
if (rules.length === collected.size) {
|
|
272
307
|
return;
|
|
273
308
|
}
|
|
274
309
|
const filtered: StyleMedia =
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
this.entries.set(entryKey(filtered), filtered);
|
|
279
|
-
for (const rule of liveRules) {
|
|
310
|
+
rules.length === entry.rules.length ? entry : { ...entry, rules };
|
|
311
|
+
this.setEntry(filtered);
|
|
312
|
+
for (const rule of rules) {
|
|
280
313
|
this.addDeclarationDeps(rule.declarations);
|
|
281
314
|
}
|
|
282
315
|
}
|
|
@@ -293,7 +326,7 @@ export class StylesCollector {
|
|
|
293
326
|
|
|
294
327
|
/** Marks a theme token path as reachable so it appears in the emitted theme block, even if no collected declaration reads it. */
|
|
295
328
|
useThemeVar(path: string): void {
|
|
296
|
-
this.
|
|
329
|
+
this.batch(() => this.addDeps([path], []));
|
|
297
330
|
}
|
|
298
331
|
|
|
299
332
|
/** Creates a {@link StyleNameResolver} scoped to this collector's current dep set. */
|
|
@@ -439,9 +472,9 @@ export class StylesCollector {
|
|
|
439
472
|
if (entry.kind === 'handle' && !this.retainHandle(entry)) {
|
|
440
473
|
return;
|
|
441
474
|
}
|
|
442
|
-
this.
|
|
475
|
+
this.setEntry(entry);
|
|
443
476
|
if (entry.kind === 'handle') {
|
|
444
|
-
this.
|
|
477
|
+
this.setHandle(entry);
|
|
445
478
|
this.addDeclarationDeps(entry.declarations);
|
|
446
479
|
return;
|
|
447
480
|
}
|
|
@@ -455,8 +488,61 @@ export class StylesCollector {
|
|
|
455
488
|
}
|
|
456
489
|
|
|
457
490
|
private addDeclarationDeps(declarations: Declarations): void {
|
|
458
|
-
declarations.deps.theme
|
|
459
|
-
|
|
491
|
+
this.addDeps(declarations.deps.theme, declarations.deps.vars);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
private addDeps(
|
|
495
|
+
theme: Iterable<string>,
|
|
496
|
+
vars: Iterable<ContextualVarPath>
|
|
497
|
+
): void {
|
|
498
|
+
const before = this.themeDeps.size + this.varDeps.size;
|
|
499
|
+
for (const path of theme) {
|
|
500
|
+
this.themeDeps.add(path);
|
|
501
|
+
}
|
|
502
|
+
for (const path of vars) {
|
|
503
|
+
this.varDeps.add(path);
|
|
504
|
+
}
|
|
505
|
+
if (this.themeDeps.size + this.varDeps.size !== before) {
|
|
506
|
+
this.dirty = true;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// A media entry changes when it gains inner rules.
|
|
511
|
+
private setEntry(entry: StyleEntry): void {
|
|
512
|
+
const key = entryKey(entry);
|
|
513
|
+
const existing = this.entries.get(key);
|
|
514
|
+
this.entries.set(key, entry);
|
|
515
|
+
if (
|
|
516
|
+
!existing ||
|
|
517
|
+
(existing.kind === 'media' &&
|
|
518
|
+
entry.kind === 'media' &&
|
|
519
|
+
existing.rules.length !== entry.rules.length)
|
|
520
|
+
) {
|
|
521
|
+
this.dirty = true;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
private setHandle(handle: StyleHandle): void {
|
|
526
|
+
if (!this.handles.has(handle.key)) {
|
|
527
|
+
this.dirty = true;
|
|
528
|
+
}
|
|
529
|
+
this.handles.set(handle.key, handle);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Listeners run once the outermost public mutation returns.
|
|
533
|
+
private batch<T>(mutate: () => T): T {
|
|
534
|
+
this.depth += 1;
|
|
535
|
+
try {
|
|
536
|
+
return mutate();
|
|
537
|
+
} finally {
|
|
538
|
+
this.depth -= 1;
|
|
539
|
+
if (this.depth === 0 && this.dirty) {
|
|
540
|
+
this.dirty = false;
|
|
541
|
+
for (const listener of [...this.listeners]) {
|
|
542
|
+
listener();
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
460
546
|
}
|
|
461
547
|
}
|
|
462
548
|
|
package/src/dom_sink.ts
CHANGED
|
@@ -5,26 +5,24 @@
|
|
|
5
5
|
* 2.0.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { StyleSink } from './
|
|
8
|
+
import type { StyleElementLike, StyleParentLike, StyleSink } from './sink';
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
export interface StyleElementLike {
|
|
12
|
-
/** Current stylesheet text, or `null` before the first flush. */
|
|
13
|
-
textContent: string | null;
|
|
14
|
-
}
|
|
10
|
+
export type { StyleElementLike, StyleParentLike } from './sink';
|
|
15
11
|
|
|
16
12
|
/** Minimal interface for a DOM `Document` able to create and append style tags. */
|
|
17
13
|
export interface DocumentLike {
|
|
18
14
|
/** Creates a `<style>` element. */
|
|
19
15
|
createElement(tagName: 'style'): StyleElementLike;
|
|
20
|
-
/**
|
|
21
|
-
readonly head:
|
|
16
|
+
/** Default parent for the created style element. */
|
|
17
|
+
readonly head: StyleParentLike;
|
|
22
18
|
}
|
|
23
19
|
|
|
24
20
|
/** Host document and optional flush scheduler for a `<style>` sink. */
|
|
25
21
|
export interface CreateDomSinkOptions {
|
|
26
|
-
/**
|
|
22
|
+
/** Document that creates the style element. */
|
|
27
23
|
document: DocumentLike;
|
|
24
|
+
/** Node that receives the style element, e.g. a `ShadowRoot`. Defaults to `document.head`. */
|
|
25
|
+
parent?: StyleParentLike;
|
|
28
26
|
/** Defers the flush. Defaults to `queueMicrotask`. */
|
|
29
27
|
schedule?: (flush: () => void) => void;
|
|
30
28
|
}
|
|
@@ -34,6 +32,7 @@ export interface CreateDomSinkOptions {
|
|
|
34
32
|
*/
|
|
35
33
|
export const createDomSink = ({
|
|
36
34
|
document,
|
|
35
|
+
parent,
|
|
37
36
|
schedule = queueMicrotask,
|
|
38
37
|
}: CreateDomSinkOptions): StyleSink => {
|
|
39
38
|
let element: StyleElementLike | null = null;
|
|
@@ -47,7 +46,7 @@ export const createDomSink = ({
|
|
|
47
46
|
}
|
|
48
47
|
if (!element) {
|
|
49
48
|
element = document.createElement('style');
|
|
50
|
-
document.head.appendChild(element);
|
|
49
|
+
(parent ?? document.head).appendChild(element);
|
|
51
50
|
}
|
|
52
51
|
element.textContent = latestRender();
|
|
53
52
|
};
|
package/src/emotion.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import type { Distillery } from './engine';
|
|
9
9
|
import { contentHash64 } from './hash';
|
|
10
10
|
import { flattenTemplate, isHandleLike, type TemplateSlice } from './nesting';
|
|
11
|
+
import type { StyleSink } from './sink';
|
|
11
12
|
import {
|
|
12
13
|
globalStylesFromFlattened,
|
|
13
14
|
pendingHandleFromFlattened,
|
|
@@ -21,13 +22,9 @@ export {
|
|
|
21
22
|
type CreateDomSinkOptions,
|
|
22
23
|
type DocumentLike,
|
|
23
24
|
type StyleElementLike,
|
|
25
|
+
type StyleParentLike,
|
|
24
26
|
} from './dom_sink';
|
|
25
|
-
|
|
26
|
-
/** Receives stylesheet invalidation from Emotion compat modules. */
|
|
27
|
-
export interface StyleSink {
|
|
28
|
-
/** Marks the sink dirty. `render` produces the current stylesheet when it flushes. */
|
|
29
|
-
invalidate(render: () => string): void;
|
|
30
|
-
}
|
|
27
|
+
export type { StyleSink } from './sink';
|
|
31
28
|
|
|
32
29
|
/** Shared sink for Emotion compat modules on one registry. */
|
|
33
30
|
export interface CreateEmotionOptions {
|
package/src/engine.ts
CHANGED
|
@@ -19,12 +19,14 @@ import {
|
|
|
19
19
|
type StyleNameResolverOptions,
|
|
20
20
|
} from './names';
|
|
21
21
|
import { renderStyles, type RenderStylesOptions } from './runtime';
|
|
22
|
+
import type { StyleSink } from './sink';
|
|
22
23
|
import {
|
|
23
24
|
createStyleModuleWithEnvironment,
|
|
24
25
|
type PrimitiveStyleAuthoringApi,
|
|
25
26
|
readableVarOwnersFromEnvironment,
|
|
26
27
|
type ResolvedStyles,
|
|
27
28
|
type StyleAuthoringApi,
|
|
29
|
+
type StyleHandle,
|
|
28
30
|
StyleRegistry,
|
|
29
31
|
type StylesModule,
|
|
30
32
|
type StylesObject,
|
|
@@ -39,6 +41,30 @@ import {
|
|
|
39
41
|
type ValuesOf,
|
|
40
42
|
} from './theme';
|
|
41
43
|
|
|
44
|
+
/** Sink, render options, and warning sink for {@link Distillery.liveCollection}. */
|
|
45
|
+
export interface LiveCollectionOptions {
|
|
46
|
+
/** Invalidated whenever the collection grows. Nothing is written before the first handle. */
|
|
47
|
+
sink?: StyleSink;
|
|
48
|
+
/** Options forwarded to `renderStyles` by `css()`. */
|
|
49
|
+
render?: RenderStylesOptions;
|
|
50
|
+
/** Warning sink for no-op handles. Used only when the distillery has `dev: true`. */
|
|
51
|
+
warn?: (message: string) => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Readable collection filled by one live render.
|
|
56
|
+
*
|
|
57
|
+
* Readable only: compact names depend on the complete collected set and need two passes.
|
|
58
|
+
*/
|
|
59
|
+
export interface LiveCollection {
|
|
60
|
+
/** Underlying readable artifact collector. Mutations here (e.g. `use(globalModule)`, `useThemeVar`) also invalidate the sink. */
|
|
61
|
+
readonly collector: StylesCollector;
|
|
62
|
+
/** Collects `handles` and returns their readable class names, space-separated. */
|
|
63
|
+
readonly resolveClassName: (...handles: StyleHandle[]) => string;
|
|
64
|
+
/** Renders the CSS collected so far. */
|
|
65
|
+
readonly css: () => string;
|
|
66
|
+
}
|
|
67
|
+
|
|
42
68
|
/** One component library's binding: environment, registry, and pre-bound operations. Destructure-safe (no `this`). */
|
|
43
69
|
export interface Distillery<
|
|
44
70
|
TTokens = unknown,
|
|
@@ -79,6 +105,8 @@ export interface Distillery<
|
|
|
79
105
|
names?: StyleNameMode,
|
|
80
106
|
options?: Pick<StylesCollectorOptions, 'warn'>
|
|
81
107
|
) => StylesCollector;
|
|
108
|
+
/** Readable collection for one live render; keeps `sink` current as handles resolve. */
|
|
109
|
+
readonly liveCollection: (options?: LiveCollectionOptions) => LiveCollection;
|
|
82
110
|
/** Emits collected CSS for this environment. */
|
|
83
111
|
readonly renderStyles: (
|
|
84
112
|
collector: StylesCollector,
|
|
@@ -135,6 +163,19 @@ export const createDistillery = <const TTheme extends ThemeTree>(
|
|
|
135
163
|
sharedVars ?? new Set()
|
|
136
164
|
);
|
|
137
165
|
|
|
166
|
+
const artifactCollector = (
|
|
167
|
+
names: StyleNameMode,
|
|
168
|
+
collectorOptions?: Pick<StylesCollectorOptions, 'warn'>
|
|
169
|
+
): StylesCollector =>
|
|
170
|
+
new StylesCollector({
|
|
171
|
+
target: 'artifact',
|
|
172
|
+
names,
|
|
173
|
+
registry,
|
|
174
|
+
prefix,
|
|
175
|
+
dev,
|
|
176
|
+
...(collectorOptions?.warn ? { warn: collectorOptions.warn } : {}),
|
|
177
|
+
});
|
|
178
|
+
|
|
138
179
|
return {
|
|
139
180
|
environment,
|
|
140
181
|
tokens,
|
|
@@ -160,15 +201,7 @@ export const createDistillery = <const TTheme extends ThemeTree>(
|
|
|
160
201
|
({ css: style, tokens: moduleTokens }) =>
|
|
161
202
|
factory({ style, tokens: moduleTokens })
|
|
162
203
|
),
|
|
163
|
-
artifactCollector
|
|
164
|
-
new StylesCollector({
|
|
165
|
-
target: 'artifact',
|
|
166
|
-
names,
|
|
167
|
-
registry,
|
|
168
|
-
prefix,
|
|
169
|
-
dev,
|
|
170
|
-
...(options?.warn ? { warn: options.warn } : {}),
|
|
171
|
-
}),
|
|
204
|
+
artifactCollector,
|
|
172
205
|
stylesheetCollector: (names = 'readable', options) => {
|
|
173
206
|
const collector = new StylesCollector({
|
|
174
207
|
target: 'stylesheet',
|
|
@@ -183,6 +216,23 @@ export const createDistillery = <const TTheme extends ThemeTree>(
|
|
|
183
216
|
}
|
|
184
217
|
return collector;
|
|
185
218
|
},
|
|
219
|
+
liveCollection: ({ sink, render, warn } = {}) => {
|
|
220
|
+
const collector = artifactCollector('readable', warn ? { warn } : {});
|
|
221
|
+
const css = (): string =>
|
|
222
|
+
renderStyles(environment, collector, undefined, render);
|
|
223
|
+
if (sink) {
|
|
224
|
+
collector.subscribe(() => sink.invalidate(css));
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
collector,
|
|
228
|
+
resolveClassName: (...handles) =>
|
|
229
|
+
collector
|
|
230
|
+
.useHandles(handles)
|
|
231
|
+
.map((handle) => handle.readableName)
|
|
232
|
+
.join(' '),
|
|
233
|
+
css,
|
|
234
|
+
};
|
|
235
|
+
},
|
|
186
236
|
renderStyles: (collector, resolver, renderOptions) =>
|
|
187
237
|
renderStyles(environment, collector, resolver, renderOptions),
|
|
188
238
|
createNameResolver: (resolverOptions) =>
|
package/src/index.ts
CHANGED
|
@@ -5,7 +5,20 @@
|
|
|
5
5
|
* 2.0.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
export {
|
|
8
|
+
export {
|
|
9
|
+
createDistillery,
|
|
10
|
+
type Distillery,
|
|
11
|
+
type LiveCollection,
|
|
12
|
+
type LiveCollectionOptions,
|
|
13
|
+
} from './engine';
|
|
14
|
+
export {
|
|
15
|
+
createDomSink,
|
|
16
|
+
type CreateDomSinkOptions,
|
|
17
|
+
type DocumentLike,
|
|
18
|
+
type StyleElementLike,
|
|
19
|
+
type StyleParentLike,
|
|
20
|
+
} from './dom_sink';
|
|
21
|
+
export type { StyleSink } from './sink';
|
|
9
22
|
export {
|
|
10
23
|
type DistilleryEnvironment,
|
|
11
24
|
type DistilleryOptions,
|
package/src/sink.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
3
|
+
* or more contributor license agreements. Licensed under the Elastic License
|
|
4
|
+
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
5
|
+
* 2.0.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Receives stylesheet invalidation from a live collection or Emotion compat modules. */
|
|
9
|
+
export interface StyleSink {
|
|
10
|
+
/** Marks the sink dirty. `render` produces the current stylesheet when it flushes. */
|
|
11
|
+
invalidate(render: () => string): void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Minimal interface for a DOM `<style>` element. */
|
|
15
|
+
export interface StyleElementLike {
|
|
16
|
+
/** Current stylesheet text, or `null` before the first flush. */
|
|
17
|
+
textContent: string | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Node that receives a managed `<style>` element, e.g. `document.head` or a `ShadowRoot`. */
|
|
21
|
+
export interface StyleParentLike {
|
|
22
|
+
// Method syntax (bivariant) so DOM `Node.appendChild<T extends Node>` assigns.
|
|
23
|
+
/** Appends the managed style element. */
|
|
24
|
+
appendChild(node: StyleElementLike): void;
|
|
25
|
+
}
|