@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.
Files changed (285) hide show
  1. package/LICENSE.txt +93 -0
  2. package/NOTICE.txt +30 -0
  3. package/README.md +142 -0
  4. package/THIRD_PARTY_LICENSES.md +768 -0
  5. package/dist/cjs/collector.js +420 -0
  6. package/dist/cjs/collector.js.map +1 -0
  7. package/dist/cjs/css_scan.js +186 -0
  8. package/dist/cjs/css_scan.js.map +1 -0
  9. package/dist/cjs/dom_sink.js +40 -0
  10. package/dist/cjs/dom_sink.js.map +1 -0
  11. package/dist/cjs/emotion.js +189 -0
  12. package/dist/cjs/emotion.js.map +1 -0
  13. package/dist/cjs/engine.js +79 -0
  14. package/dist/cjs/engine.js.map +1 -0
  15. package/dist/cjs/environment.js +9 -0
  16. package/dist/cjs/environment.js.map +1 -0
  17. package/dist/cjs/hash.js +86 -0
  18. package/dist/cjs/hash.js.map +1 -0
  19. package/dist/cjs/idents.js +23 -0
  20. package/dist/cjs/idents.js.map +1 -0
  21. package/dist/cjs/index.js +42 -0
  22. package/dist/cjs/index.js.map +1 -0
  23. package/dist/cjs/instance.js +34 -0
  24. package/dist/cjs/instance.js.map +1 -0
  25. package/dist/cjs/local_vars.js +100 -0
  26. package/dist/cjs/local_vars.js.map +1 -0
  27. package/dist/cjs/minify.js +77 -0
  28. package/dist/cjs/minify.js.map +1 -0
  29. package/dist/cjs/names.js +77 -0
  30. package/dist/cjs/names.js.map +1 -0
  31. package/dist/cjs/nesting.js +406 -0
  32. package/dist/cjs/nesting.js.map +1 -0
  33. package/dist/cjs/package.json +3 -0
  34. package/dist/cjs/runtime.js +257 -0
  35. package/dist/cjs/runtime.js.map +1 -0
  36. package/dist/cjs/styles/authoring.js +169 -0
  37. package/dist/cjs/styles/authoring.js.map +1 -0
  38. package/dist/cjs/styles/declarations.js +139 -0
  39. package/dist/cjs/styles/declarations.js.map +1 -0
  40. package/dist/cjs/styles/index.js +32 -0
  41. package/dist/cjs/styles/index.js.map +1 -0
  42. package/dist/cjs/styles/module.js +233 -0
  43. package/dist/cjs/styles/module.js.map +1 -0
  44. package/dist/cjs/styles/registry.js +187 -0
  45. package/dist/cjs/styles/registry.js.map +1 -0
  46. package/dist/cjs/styles/types.js +9 -0
  47. package/dist/cjs/styles/types.js.map +1 -0
  48. package/dist/cjs/testing.js +37 -0
  49. package/dist/cjs/testing.js.map +1 -0
  50. package/dist/cjs/theme.js +249 -0
  51. package/dist/cjs/theme.js.map +1 -0
  52. package/dist/cjs/tokens/contextual_var.js +39 -0
  53. package/dist/cjs/tokens/contextual_var.js.map +1 -0
  54. package/dist/cjs/tokens/index.js +24 -0
  55. package/dist/cjs/tokens/index.js.map +1 -0
  56. package/dist/cjs/tokens/kind.js +14 -0
  57. package/dist/cjs/tokens/kind.js.map +1 -0
  58. package/dist/cjs/tokens/light_dark.js +42 -0
  59. package/dist/cjs/tokens/light_dark.js.map +1 -0
  60. package/dist/cjs/tokens/scale_token.js +28 -0
  61. package/dist/cjs/tokens/scale_token.js.map +1 -0
  62. package/dist/cjs/tokens/theme_token.js +28 -0
  63. package/dist/cjs/tokens/theme_token.js.map +1 -0
  64. package/dist/cjs/var_invariant.js +115 -0
  65. package/dist/cjs/var_invariant.js.map +1 -0
  66. package/dist/collector.d.ts +84 -0
  67. package/dist/collector.d.ts.map +1 -0
  68. package/dist/collector.js +416 -0
  69. package/dist/collector.js.map +1 -0
  70. package/dist/css_scan.d.ts +57 -0
  71. package/dist/css_scan.d.ts.map +1 -0
  72. package/dist/css_scan.js +180 -0
  73. package/dist/css_scan.js.map +1 -0
  74. package/dist/dom_sink.d.ts +27 -0
  75. package/dist/dom_sink.d.ts.map +1 -0
  76. package/dist/dom_sink.js +36 -0
  77. package/dist/dom_sink.js.map +1 -0
  78. package/dist/emotion.d.ts +47 -0
  79. package/dist/emotion.d.ts.map +1 -0
  80. package/dist/emotion.js +184 -0
  81. package/dist/emotion.js.map +1 -0
  82. package/dist/engine.d.ts +41 -0
  83. package/dist/engine.d.ts.map +1 -0
  84. package/dist/engine.js +75 -0
  85. package/dist/engine.js.map +1 -0
  86. package/dist/environment.d.ts +43 -0
  87. package/dist/environment.d.ts.map +1 -0
  88. package/dist/environment.js +8 -0
  89. package/dist/environment.js.map +1 -0
  90. package/dist/hash.d.ts +2 -0
  91. package/dist/hash.d.ts.map +1 -0
  92. package/dist/hash.js +82 -0
  93. package/dist/hash.js.map +1 -0
  94. package/dist/idents.d.ts +9 -0
  95. package/dist/idents.d.ts.map +1 -0
  96. package/dist/idents.js +19 -0
  97. package/dist/idents.js.map +1 -0
  98. package/dist/index.d.ts +9 -0
  99. package/dist/index.d.ts.map +1 -0
  100. package/dist/index.js +14 -0
  101. package/dist/index.js.map +1 -0
  102. package/dist/instance.d.ts +16 -0
  103. package/dist/instance.d.ts.map +1 -0
  104. package/dist/instance.js +30 -0
  105. package/dist/instance.js.map +1 -0
  106. package/dist/local_vars.d.ts +75 -0
  107. package/dist/local_vars.d.ts.map +1 -0
  108. package/dist/local_vars.js +92 -0
  109. package/dist/local_vars.js.map +1 -0
  110. package/dist/minify.d.ts +11 -0
  111. package/dist/minify.d.ts.map +1 -0
  112. package/dist/minify.js +73 -0
  113. package/dist/minify.js.map +1 -0
  114. package/dist/names.d.ts +39 -0
  115. package/dist/names.d.ts.map +1 -0
  116. package/dist/names.js +70 -0
  117. package/dist/names.js.map +1 -0
  118. package/dist/nesting.d.ts +59 -0
  119. package/dist/nesting.d.ts.map +1 -0
  120. package/dist/nesting.js +400 -0
  121. package/dist/nesting.js.map +1 -0
  122. package/dist/runtime.d.ts +36 -0
  123. package/dist/runtime.d.ts.map +1 -0
  124. package/dist/runtime.js +252 -0
  125. package/dist/runtime.js.map +1 -0
  126. package/dist/styles/authoring.d.ts +59 -0
  127. package/dist/styles/authoring.d.ts.map +1 -0
  128. package/dist/styles/authoring.js +155 -0
  129. package/dist/styles/authoring.js.map +1 -0
  130. package/dist/styles/declarations.d.ts +10 -0
  131. package/dist/styles/declarations.d.ts.map +1 -0
  132. package/dist/styles/declarations.js +132 -0
  133. package/dist/styles/declarations.js.map +1 -0
  134. package/dist/styles/index.d.ts +7 -0
  135. package/dist/styles/index.d.ts.map +1 -0
  136. package/dist/styles/index.js +12 -0
  137. package/dist/styles/index.js.map +1 -0
  138. package/dist/styles/module.d.ts +5 -0
  139. package/dist/styles/module.d.ts.map +1 -0
  140. package/dist/styles/module.js +229 -0
  141. package/dist/styles/module.js.map +1 -0
  142. package/dist/styles/registry.d.ts +48 -0
  143. package/dist/styles/registry.d.ts.map +1 -0
  144. package/dist/styles/registry.js +182 -0
  145. package/dist/styles/registry.js.map +1 -0
  146. package/dist/styles/types.d.ts +211 -0
  147. package/dist/styles/types.d.ts.map +1 -0
  148. package/dist/styles/types.js +8 -0
  149. package/dist/styles/types.js.map +1 -0
  150. package/dist/testing.d.ts +10 -0
  151. package/dist/testing.d.ts.map +1 -0
  152. package/dist/testing.js +31 -0
  153. package/dist/testing.js.map +1 -0
  154. package/dist/theme.d.ts +85 -0
  155. package/dist/theme.d.ts.map +1 -0
  156. package/dist/theme.js +240 -0
  157. package/dist/theme.js.map +1 -0
  158. package/dist/tokens/contextual_var.d.ts +35 -0
  159. package/dist/tokens/contextual_var.d.ts.map +1 -0
  160. package/dist/tokens/contextual_var.js +33 -0
  161. package/dist/tokens/contextual_var.js.map +1 -0
  162. package/dist/tokens/index.d.ts +5 -0
  163. package/dist/tokens/index.d.ts.map +1 -0
  164. package/dist/tokens/index.js +11 -0
  165. package/dist/tokens/index.js.map +1 -0
  166. package/dist/tokens/kind.d.ts +2 -0
  167. package/dist/tokens/kind.d.ts.map +1 -0
  168. package/dist/tokens/kind.js +10 -0
  169. package/dist/tokens/kind.js.map +1 -0
  170. package/dist/tokens/light_dark.d.ts +20 -0
  171. package/dist/tokens/light_dark.d.ts.map +1 -0
  172. package/dist/tokens/light_dark.js +37 -0
  173. package/dist/tokens/light_dark.js.map +1 -0
  174. package/dist/tokens/scale_token.d.ts +20 -0
  175. package/dist/tokens/scale_token.d.ts.map +1 -0
  176. package/dist/tokens/scale_token.js +23 -0
  177. package/dist/tokens/scale_token.js.map +1 -0
  178. package/dist/tokens/theme_token.d.ts +20 -0
  179. package/dist/tokens/theme_token.d.ts.map +1 -0
  180. package/dist/tokens/theme_token.js +23 -0
  181. package/dist/tokens/theme_token.js.map +1 -0
  182. package/dist/var_invariant.d.ts +25 -0
  183. package/dist/var_invariant.d.ts.map +1 -0
  184. package/dist/var_invariant.js +108 -0
  185. package/dist/var_invariant.js.map +1 -0
  186. package/docs/concepts/authoring.md +115 -0
  187. package/docs/concepts/collection.md +66 -0
  188. package/docs/concepts/distillery.md +47 -0
  189. package/docs/concepts/index.md +18 -0
  190. package/docs/concepts/naming-and-output.md +123 -0
  191. package/docs/concepts/ordering.md +61 -0
  192. package/docs/concepts/single-copy.md +103 -0
  193. package/docs/concepts/tokens.md +99 -0
  194. package/docs/docset.yml +53 -0
  195. package/docs/examples/01-stylesheet.ts +20 -0
  196. package/docs/examples/02-artifact.ts +26 -0
  197. package/docs/examples/03-variants.ts +40 -0
  198. package/docs/examples/04-emotion.ts +33 -0
  199. package/docs/examples/05-var-invariant.ts +31 -0
  200. package/docs/examples/06-payload.ts +85 -0
  201. package/docs/examples/fixture.ts +22 -0
  202. package/docs/getting-started/index.md +14 -0
  203. package/docs/getting-started/installation.md +30 -0
  204. package/docs/getting-started/quick-start.md +61 -0
  205. package/docs/getting-started/your-first-stylesheet.md +57 -0
  206. package/docs/guides/compact-artifacts.md +87 -0
  207. package/docs/guides/emotion-migration.md +60 -0
  208. package/docs/guides/index.md +17 -0
  209. package/docs/guides/name-collisions.md +37 -0
  210. package/docs/guides/non-css-surfaces.md +73 -0
  211. package/docs/guides/react-renderer.md +158 -0
  212. package/docs/guides/theming.md +107 -0
  213. package/docs/guides/var-invariant.md +24 -0
  214. package/docs/guides/vs-emotion.md +61 -0
  215. package/docs/index.md +73 -0
  216. package/docs/logo.svg +7 -0
  217. package/docs/playground/index.html +12 -0
  218. package/docs/playground/package.json +23 -0
  219. package/docs/playground/src/app/app.tsx +109 -0
  220. package/docs/playground/src/app/css_panel.tsx +119 -0
  221. package/docs/playground/src/app/editor_panel.tsx +87 -0
  222. package/docs/playground/src/app/index.ts +8 -0
  223. package/docs/playground/src/app/monaco_css_editor.tsx +84 -0
  224. package/docs/playground/src/app/monaco_source_editor.tsx +132 -0
  225. package/docs/playground/src/app/preview_panel.tsx +99 -0
  226. package/docs/playground/src/app/tokens_panel.tsx +117 -0
  227. package/docs/playground/src/app/types.ts +10 -0
  228. package/docs/playground/src/examples/snippets.test.ts +28 -0
  229. package/docs/playground/src/examples/snippets.ts +418 -0
  230. package/docs/playground/src/lib/compile.test.ts +157 -0
  231. package/docs/playground/src/lib/compile.ts +242 -0
  232. package/docs/playground/src/lib/demo_environment.test.ts +123 -0
  233. package/docs/playground/src/lib/demo_environment.ts +140 -0
  234. package/docs/playground/src/lib/editor_lib.test.ts +39 -0
  235. package/docs/playground/src/lib/editor_lib.ts +142 -0
  236. package/docs/playground/src/lib/format_css.test.ts +41 -0
  237. package/docs/playground/src/lib/format_css.ts +71 -0
  238. package/docs/playground/src/main.tsx +25 -0
  239. package/docs/playground/src/setup/chrome.test.ts +47 -0
  240. package/docs/playground/src/setup/chrome.ts +365 -0
  241. package/docs/playground/src/setup/monaco_env.ts +64 -0
  242. package/docs/playground/tsconfig.json +22 -0
  243. package/docs/playground/vite.config.ts +37 -0
  244. package/docs/playground/vitest.config.ts +35 -0
  245. package/docs/playground.md +28 -0
  246. package/docs/reference/api/emotion.md +372 -0
  247. package/docs/reference/api/index-1.md +2180 -0
  248. package/docs/reference/api/index.md +9 -0
  249. package/docs/reference/api/testing.md +92 -0
  250. package/docs/reference/api.md +12 -0
  251. package/docs/reference/entry-points.md +125 -0
  252. package/docs/reference/environment.md +60 -0
  253. package/docs/reference/index.md +11 -0
  254. package/docs/reference/okf-map.md +92 -0
  255. package/package.json +127 -4
  256. package/src/collector.ts +526 -0
  257. package/src/css_scan.ts +263 -0
  258. package/src/dom_sink.ts +65 -0
  259. package/src/emotion.ts +311 -0
  260. package/src/engine.ts +191 -0
  261. package/src/environment.ts +56 -0
  262. package/src/hash.ts +111 -0
  263. package/src/idents.ts +21 -0
  264. package/src/index.ts +93 -0
  265. package/src/instance.ts +49 -0
  266. package/src/local_vars.ts +220 -0
  267. package/src/minify.ts +87 -0
  268. package/src/names.ts +129 -0
  269. package/src/nesting.ts +596 -0
  270. package/src/runtime.ts +428 -0
  271. package/src/styles/authoring.ts +244 -0
  272. package/src/styles/declarations.ts +172 -0
  273. package/src/styles/index.ts +58 -0
  274. package/src/styles/module.ts +396 -0
  275. package/src/styles/registry.ts +261 -0
  276. package/src/styles/types.ts +283 -0
  277. package/src/testing.ts +48 -0
  278. package/src/theme.ts +454 -0
  279. package/src/tokens/contextual_var.ts +72 -0
  280. package/src/tokens/index.ts +17 -0
  281. package/src/tokens/kind.ts +13 -0
  282. package/src/tokens/light_dark.ts +58 -0
  283. package/src/tokens/scale_token.ts +41 -0
  284. package/src/tokens/theme_token.ts +42 -0
  285. package/src/var_invariant.ts +147 -0
@@ -0,0 +1,261 @@
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
+ import type { DistilleryEnvironment } from '../environment';
9
+ import { cssVarName } from '../names';
10
+
11
+ import type {
12
+ ContextualVarPath,
13
+ CssVarPath,
14
+ Declarations,
15
+ StyleRegistrySnapshot,
16
+ StylesModule,
17
+ } from './types';
18
+
19
+ /**
20
+ * Registered style modules for one distillery. Throws on duplicate module names or colliding readable class/var names.
21
+ */
22
+ export class StyleRegistry {
23
+ private readonly modulesByName = new Map<string, StylesModule>();
24
+ private targetedKeys: Set<string> | undefined;
25
+
26
+ constructor(
27
+ private readonly prefix: string,
28
+ private readonly reservedVarOwners: ReadonlyMap<string, string> = new Map(),
29
+ private readonly sharedVarPaths: ReadonlySet<string> = new Set()
30
+ ) {}
31
+
32
+ /**
33
+ * Adds `module` to this registry.
34
+ *
35
+ * @throws If another module already uses this name, or if a readable class or CSS-variable name collides (including `themeVars` and `sharedVars`).
36
+ */
37
+ registerModule(module: StylesModule): void {
38
+ const existing = this.modulesByName.get(module.name);
39
+ if (existing === module) {
40
+ return;
41
+ }
42
+ if (existing) {
43
+ throw new Error(`Style module "${module.name}" is already registered.`);
44
+ }
45
+ assertReadableNameCollisions(
46
+ this.prefix,
47
+ this.reservedVarOwners,
48
+ this.sharedVarPaths,
49
+ this.modulesByName.values(),
50
+ module
51
+ );
52
+ this.modulesByName.set(module.name, module);
53
+ this.targetedKeys = undefined;
54
+ }
55
+
56
+ /**
57
+ * True when some rule, or some inner rule of an `@media` / `@container` block, reads `key` in its selector.
58
+ *
59
+ * A nested `&` rule records its owning handle through `recordRuleDeps`, so a handle with an empty self block but real nested rules is targeted. A rule-less nested block targets nothing.
60
+ */
61
+ targetsHandle(key: string): boolean {
62
+ return this.targetedHandleKeys().has(key);
63
+ }
64
+
65
+ private targetedHandleKeys(): Set<string> {
66
+ if (this.targetedKeys) {
67
+ return this.targetedKeys;
68
+ }
69
+ const keys = new Set<string>();
70
+ for (const module of this.modulesByName.values()) {
71
+ for (const entry of module.entries) {
72
+ if (entry.kind === 'rule') {
73
+ entry.dependsOn.forEach((dep) => keys.add(dep));
74
+ } else if (entry.kind === 'media') {
75
+ for (const inner of entry.rules) {
76
+ inner.dependsOn.forEach((dep) => keys.add(dep));
77
+ }
78
+ }
79
+ }
80
+ }
81
+ this.targetedKeys = keys;
82
+ return keys;
83
+ }
84
+
85
+ /** Registered modules, sorted by name. */
86
+ get modules(): readonly StylesModule[] {
87
+ return [...this.modulesByName.values()].sort((a, b) =>
88
+ a.name.localeCompare(b.name)
89
+ );
90
+ }
91
+
92
+ /** Looks up a module by the name passed to `createStyleModule`. */
93
+ module(name: string): StylesModule | undefined {
94
+ return this.modulesByName.get(name);
95
+ }
96
+
97
+ /** Handle keys across all modules. Compact-name domain for a full stylesheet. */
98
+ get classKeys(): readonly string[] {
99
+ return this.modules.flatMap((module) =>
100
+ module.handleList.map((handle) => handle.key)
101
+ );
102
+ }
103
+
104
+ /** Theme and contextual-var keys across all modules. */
105
+ get cssVarKeys(): readonly CssVarPath[] {
106
+ const keys = new Set<CssVarPath>();
107
+ for (const module of this.modules) {
108
+ module.themeDeps.forEach((path) => keys.add(path));
109
+ module.varDeps.forEach((path) => keys.add(path));
110
+ }
111
+ return [...keys].sort();
112
+ }
113
+
114
+ /**
115
+ * Frozen class and CSS-var keys for a {@link StyleNameResolver}.
116
+ *
117
+ * @param options Overrides the registry-wide key sets when compact-naming a subset.
118
+ */
119
+ freeze(
120
+ options: {
121
+ /** Compact class-name domain override. */
122
+ classKeys?: Iterable<string>;
123
+ /** Compact CSS-var domain override. */
124
+ cssVarKeys?: Iterable<CssVarPath>;
125
+ } = {}
126
+ ): StyleRegistrySnapshot {
127
+ return Object.freeze({
128
+ classKeys: Object.freeze(
129
+ sortedUnique(options.classKeys ?? this.classKeys)
130
+ ),
131
+ cssVarKeys: Object.freeze(
132
+ sortedUnique(options.cssVarKeys ?? this.cssVarKeys)
133
+ ),
134
+ });
135
+ }
136
+ }
137
+
138
+ const sortedUnique = <TValue extends string>(
139
+ values: Iterable<TValue>
140
+ ): readonly TValue[] => [...new Set(values)].sort();
141
+
142
+ const claimReadableVar = (
143
+ owners: Map<string, string>,
144
+ name: string,
145
+ owner: string
146
+ ): void => {
147
+ const prior = owners.get(name);
148
+ if (prior) {
149
+ throw new Error(
150
+ `Readable CSS variable "${name}" collides between ${prior} and ${owner}.`
151
+ );
152
+ }
153
+ owners.set(name, owner);
154
+ };
155
+
156
+ /** Claimed readable custom-property name and the owner that registered it. */
157
+ interface ReadableLocalVarOwner {
158
+ /** Canonical variable path. */
159
+ readonly path: ContextualVarPath;
160
+ /** Human-readable owner used in collision errors. */
161
+ readonly owner: string;
162
+ }
163
+
164
+ /** Readable custom-property names already claimed by `themeVars` / `sharedVars`. */
165
+ export const readableVarOwnersFromEnvironment = (
166
+ environment: Pick<
167
+ DistilleryEnvironment,
168
+ 'prefix' | 'themeVars' | 'sharedVars'
169
+ >
170
+ ): ReadonlyMap<string, string> => {
171
+ const owners = new Map<string, string>();
172
+ const { prefix } = environment;
173
+ for (const path of Object.keys(environment.themeVars)) {
174
+ claimReadableVar(owners, cssVarName(prefix, path), `theme var "${path}"`);
175
+ }
176
+ for (const path of environment.sharedVars ?? []) {
177
+ claimReadableVar(owners, cssVarName(prefix, path), `shared var "${path}"`);
178
+ }
179
+ return owners;
180
+ };
181
+
182
+ const assertReadableNameCollisions = (
183
+ prefix: string,
184
+ reservedVarOwners: ReadonlyMap<string, string>,
185
+ sharedVarPaths: ReadonlySet<string>,
186
+ existing: Iterable<StylesModule>,
187
+ incoming: StylesModule
188
+ ): void => {
189
+ const classOwners = new Map<string, string>();
190
+ const reservedOwners = new Map(reservedVarOwners);
191
+ const localVarOwners = new Map<string, ReadableLocalVarOwner>();
192
+ const index = (module: StylesModule): void => {
193
+ for (const handle of module.handleList) {
194
+ const prior = classOwners.get(handle.readableName);
195
+ if (prior) {
196
+ throw new Error(
197
+ `Readable class name "${handle.readableName}" collides between ${prior} and module "${module.name}" handle "${handle.localName}".`
198
+ );
199
+ }
200
+ classOwners.set(
201
+ handle.readableName,
202
+ `module "${module.name}" handle "${handle.localName}"`
203
+ );
204
+ }
205
+ for (const path of localVarPathsOf(module, sharedVarPaths)) {
206
+ const name = cssVarName(prefix, path);
207
+ const owner = `module "${module.name}" var "${path}"`;
208
+ const reserved = reservedOwners.get(name);
209
+ if (reserved) {
210
+ throw new Error(
211
+ `Readable CSS variable "${name}" collides between ${reserved} and ${owner}.`
212
+ );
213
+ }
214
+ const prior = localVarOwners.get(name);
215
+ if (prior && prior.path !== path) {
216
+ throw new Error(
217
+ `Readable CSS variable "${name}" collides between ${prior.owner} and ${owner}.`
218
+ );
219
+ }
220
+ localVarOwners.set(name, { path, owner });
221
+ }
222
+ };
223
+ for (const module of existing) {
224
+ index(module);
225
+ }
226
+ index(incoming);
227
+ };
228
+
229
+ // `deps.vars` records every contextual var a module *references*, which includes
230
+ // shared ones the module does not own. A shared path synthesizes the same
231
+ // readable name as a local var would, so leaving them in makes two modules that
232
+ // merely reference one shared var collide on it. The same exclusion is applied
233
+ // when compaction maps readable names to compact ones.
234
+ const localVarPathsOf = (
235
+ module: StylesModule,
236
+ sharedVarPaths: ReadonlySet<string>
237
+ ): readonly ContextualVarPath[] => {
238
+ const paths = new Set<ContextualVarPath>();
239
+ const addFrom = (declarations: Declarations): void => {
240
+ declarations.deps.vars.forEach((path) => {
241
+ if (!sharedVarPaths.has(path)) {
242
+ paths.add(path);
243
+ }
244
+ });
245
+ for (const group of declarations.deps.defaults) {
246
+ for (const item of group.keys) {
247
+ paths.add(item.path);
248
+ }
249
+ }
250
+ };
251
+ for (const entry of module.entries) {
252
+ if (entry.kind === 'handle' || entry.kind === 'rule') {
253
+ addFrom(entry.declarations);
254
+ } else {
255
+ for (const nested of entry.rules) {
256
+ addFrom(nested.declarations);
257
+ }
258
+ }
259
+ }
260
+ return [...paths];
261
+ };
@@ -0,0 +1,283 @@
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
+ import type {
9
+ LocalVarDefaultMarker,
10
+ LocalVarGroup,
11
+ LocalVarMarker,
12
+ LocalVarOverrideMarker,
13
+ LocalVarRef,
14
+ } from '../local_vars';
15
+ import type { SelectorAlternative } from '../nesting';
16
+ import type {
17
+ ContextualCssVar,
18
+ ContextualCssVarName,
19
+ CssToken,
20
+ ScaleToken,
21
+ } from '../tokens';
22
+
23
+ export type {
24
+ LocalVarDefaultMarker,
25
+ LocalVarGroup,
26
+ LocalVarMarker,
27
+ LocalVarOverrideMarker,
28
+ LocalVarRef,
29
+ };
30
+
31
+ /** Canonical `vars/<group>/<key>` path for a contextual or module-local CSS variable. */
32
+ export type ContextualVarPath = `vars/${string}`;
33
+ /** Theme-token path or any other CSS-var key the resolver knows. */
34
+ export type CssVarPath = string;
35
+
36
+ /** Values accepted by `t.vars(...)` defaults and `.set(...)`. */
37
+ export type CssValue =
38
+ | string
39
+ | number
40
+ | CssToken
41
+ | ContextualCssVar
42
+ | ContextualCssVarName
43
+ | ScaleToken
44
+ | LocalVarRef;
45
+
46
+ /** Reachability sets captured while authoring a declaration block. */
47
+ export interface StyleDeps {
48
+ /** Theme-token paths interpolated into this block. */
49
+ readonly theme: ReadonlySet<string>;
50
+ /** Unconditional writes and reads. Default-marker keys land here only when reachable. */
51
+ readonly vars: ReadonlySet<ContextualVarPath>;
52
+ /** Read-side `var(...)` paths. Drives default-emission reachability. */
53
+ readonly refs: ReadonlySet<ContextualVarPath>;
54
+ /** `t.vars(...)` default-emission sites, resolved after collection. */
55
+ readonly defaults: ReadonlyArray<DefaultGroupDeps>;
56
+ }
57
+
58
+ /** One `t.vars(...)` group's default-emission metadata. */
59
+ export interface DefaultGroupDeps {
60
+ /** Target variable namespace group. */
61
+ readonly groupPath: `vars/${string}`;
62
+ /** Defaults registered within this group. */
63
+ readonly keys: ReadonlyArray<DefaultKeyDeps>;
64
+ }
65
+
66
+ /** One key inside a {@link DefaultGroupDeps} group. */
67
+ export interface DefaultKeyDeps {
68
+ /** The specific variable key within the group. */
69
+ readonly key: string;
70
+ /** Full CSS variable path (e.g. `vars/group/key`). */
71
+ readonly path: ContextualVarPath;
72
+ /** Theme/var paths referenced by the default value. Kept if the key is reachable. */
73
+ readonly valueDeps: {
74
+ /** Theme-token paths the default value interpolates. */
75
+ readonly theme: ReadonlySet<string>;
76
+ /** Contextual-var paths the default value interpolates. */
77
+ readonly vars: ReadonlySet<ContextualVarPath>;
78
+ };
79
+ }
80
+
81
+ /** Mutable twin of {@link StyleDeps} used while scanning interpolations. */
82
+ export interface MutableStyleDeps {
83
+ /** Theme-token paths interpolated into this block. */
84
+ theme: Set<string>;
85
+ /** Unconditional writes and reads. */
86
+ vars: Set<ContextualVarPath>;
87
+ /** Read-side `var(...)` paths. */
88
+ refs: Set<ContextualVarPath>;
89
+ /** `t.vars(...)` default-emission sites. */
90
+ defaults: DefaultGroupDeps[];
91
+ }
92
+
93
+ /** Literal CSS text or a local-var interpolation marker. */
94
+ export type DeclarationSegment = string | LocalVarMarker;
95
+
96
+ /** Authored CSS plus the deps the collector uses for reachability. */
97
+ export interface Declarations {
98
+ /** Interleaved literal CSS and local variable markers. */
99
+ readonly css: readonly DeclarationSegment[];
100
+ /** Reachability tracking dependencies. */
101
+ readonly deps: StyleDeps;
102
+ }
103
+
104
+ /** A named class: readable name, declarations, and collection identity. */
105
+ export interface StyleHandle {
106
+ /** Discriminant for {@link StyleEntry}. */
107
+ readonly kind: 'handle';
108
+ /** The registered module name this handle belongs to. */
109
+ readonly moduleName: string;
110
+ /** Leaf key if at root, or composed path for nested handles. */
111
+ readonly localName: string;
112
+ /** Full path joined by dots. */
113
+ readonly key: string;
114
+ /** Predictable name used for output CSS classes. */
115
+ readonly readableName: string;
116
+ /** CSS properties for this handle. */
117
+ readonly declarations: Declarations;
118
+ /** `true` when produced inside `variants(...)`. Skipped by {@link index.StylesCollector#use | StylesCollector.use}. */
119
+ readonly variant: boolean;
120
+ /** Produces the final class name via the provided resolver context. */
121
+ resolveClassName(context: {
122
+ /** Joins one or more handles into a class-name string. */
123
+ resolveClassName: (...handles: StyleHandle[]) => string;
124
+ }): string;
125
+ }
126
+
127
+ /** A selector plus declarations, optionally auto-collected from handle deps. */
128
+ export interface StyleRule {
129
+ /** Discriminant for {@link StyleEntry}. */
130
+ readonly kind: 'rule';
131
+ /** The registered module name this rule belongs to. */
132
+ readonly moduleName: string;
133
+ /** Unique key for this rule within the module. */
134
+ readonly key: string;
135
+ /** Selector string factory receiving a resolver for `&` and other handles. */
136
+ readonly selector: (h: StyleSelectorResolver) => string;
137
+ /** CSS properties for this rule. */
138
+ readonly declarations: Declarations;
139
+ /** `true` when produced inside `variants(...)`. */
140
+ readonly variant: boolean;
141
+ /** Handle keys this selector reads. Empty until `assignEntries` fills it. */
142
+ readonly dependsOn: ReadonlySet<string>;
143
+ /** When `false`, skip auto-collection; the caller must `use` the rule. */
144
+ readonly auto: boolean;
145
+ }
146
+
147
+ /** Options for {@link rule}. */
148
+ export interface RuleOptions {
149
+ /** When `false`, the collector must `use` this rule explicitly. Defaults to `true`. */
150
+ auto?: boolean;
151
+ }
152
+
153
+ /** `@media` or `@container` block wrapping inner {@link StyleRule}s. */
154
+ export interface StyleMedia {
155
+ /** Discriminant for {@link StyleEntry}. */
156
+ readonly kind: 'media';
157
+ /**
158
+ * At-rule this entry emits. `kind` stays `'media'` so collector rank and deps apply uniformly; see finding `media-block-ordering`.
159
+ */
160
+ readonly atRule?: 'media' | 'container';
161
+ /** Stable identity for deduplication across multiple renders. */
162
+ readonly key: string;
163
+ /** The registered module name this media block belongs to. */
164
+ readonly moduleName: string;
165
+ /** The conditional query (e.g. `(min-width: 600px)`). */
166
+ readonly query: string;
167
+ /** Inner rules collected inside this block. */
168
+ readonly rules: readonly StyleRule[];
169
+ /** `true` when produced inside `variants(...)`. */
170
+ readonly variant: boolean;
171
+ }
172
+
173
+ /** One registered handle, selector rule, or at-rule block. */
174
+ export type StyleEntry = StyleHandle | StyleRule | StyleMedia;
175
+ /** Selector factory: call with a handle, or read sibling local names as `.class` strings. */
176
+ export type StyleSelectorResolver = ((handle: StyleHandle) => string) &
177
+ Record<string, string>;
178
+
179
+ /** Named handle tree plus flat entries after `createStyleModule`. */
180
+ export interface StylesModule<TStyles = StylesObject> {
181
+ /** The unique registered name of the module. */
182
+ readonly name: string;
183
+ /** Authored tree of handles, rules, and media; variants nest as records. */
184
+ readonly handles: TStyles;
185
+ /** Flat list of style entries (handles, rules, media) defined in this module. */
186
+ readonly entries: readonly StyleEntry[];
187
+ /** Flat list of just the style handles defined in this module. */
188
+ readonly handleList: readonly StyleHandle[];
189
+ /** Aggregated theme dependencies across all entries. */
190
+ readonly themeDeps: ReadonlySet<string>;
191
+ /** Aggregated variable dependencies across all entries. */
192
+ readonly varDeps: ReadonlySet<ContextualVarPath>;
193
+ }
194
+
195
+ /** Authored module tree before {@link ResolvedStyles} substitution. */
196
+ export type StylesObject = Record<string, unknown>;
197
+ /** Class and CSS-var key snapshot used to build a {@link StyleNameResolver}. */
198
+ export interface StyleRegistrySnapshot {
199
+ /** Collected readable class names from registered modules. */
200
+ readonly classKeys: readonly string[];
201
+ /** Collected custom property (CSS variable) keys. */
202
+ readonly cssVarKeys: readonly CssVarPath[];
203
+ }
204
+
205
+ /** Resolves nested pending handles, rules, and medias into their runtime types. */
206
+ export type ResolvedStyles<TValue> = TValue extends PendingStyleHandle
207
+ ? StyleHandle
208
+ : TValue extends StyleRule
209
+ ? StyleRule
210
+ : TValue extends StyleMedia
211
+ ? StyleMedia
212
+ : TValue extends readonly (infer TItem)[]
213
+ ? readonly ResolvedStyles<TItem>[]
214
+ : TValue extends Record<string, unknown>
215
+ ? { [TKey in keyof TValue]: ResolvedStyles<TValue[TKey]> }
216
+ : TValue;
217
+
218
+ /** Authoring API passed to `createStyleModule` factories. */
219
+ export interface StyleAuthoringApi<TTokens = unknown> {
220
+ /** Creates a handle template where nested `&` and `@media` flatten into sibling rules. */
221
+ css: (
222
+ strings: TemplateStringsArray,
223
+ ...values: readonly unknown[]
224
+ ) => PendingStyleHandle;
225
+ /** Creates a declaration block template for direct CSS properties. */
226
+ decls: (
227
+ strings: TemplateStringsArray,
228
+ ...values: readonly unknown[]
229
+ ) => Declarations;
230
+ /** Module-local CSS variable group. Interpolate `${group}`, `${group.key}`, or `${group.set({...})}`. */
231
+ vars: <TKey extends string>(
232
+ group: string,
233
+ defaults: Record<TKey, CssValue>
234
+ ) => LocalVarGroup<TKey>;
235
+ /** Environment token tree. */
236
+ tokens: TTokens;
237
+ }
238
+
239
+ /** Authoring-time handle before module registration rewrites it to a {@link StyleHandle}. */
240
+ export interface PendingStyleHandle {
241
+ /** Discriminant for pending templates. */
242
+ readonly kind: 'pending-handle';
243
+ /** Declaration properties authored on this pending handle. */
244
+ readonly declarations: Declarations;
245
+ /** Nested `&` / `@media` rules expanded into sibling entries at registration. */
246
+ readonly nested?: readonly PendingNested[];
247
+ }
248
+
249
+ /** Nested `&` rule captured on a pending handle. */
250
+ export interface PendingNestedRule {
251
+ /** Discriminant for pending nested entries. */
252
+ readonly kind: 'nested-rule';
253
+ /** Comma alternatives relative to the parent handle. */
254
+ readonly alternatives: readonly SelectorAlternative[];
255
+ /** CSS properties for this nested rule. */
256
+ readonly declarations: Declarations;
257
+ }
258
+
259
+ /** Nested `@media` captured on a pending handle. */
260
+ export interface PendingNestedMedia {
261
+ /** Discriminant for pending nested entries. */
262
+ readonly kind: 'nested-media';
263
+ /** Media query text. */
264
+ readonly query: string;
265
+ /** Bare declarations inside the block, applied to the parent handle. */
266
+ readonly self?: Declarations;
267
+ /** Nested `&` rules inside this media block. */
268
+ readonly rules: readonly PendingNestedRule[];
269
+ }
270
+
271
+ /** Nested `&` rule or `@media` on a pending handle. */
272
+ export type PendingNested = PendingNestedRule | PendingNestedMedia;
273
+
274
+ /** Pending handle or already-resolved rule/media during registration. */
275
+ export type PendingEntry = PendingStyleHandle | StyleRule | StyleMedia;
276
+
277
+ /** Narrow `t` for `primitiveStyles`: handles and tokens only. */
278
+ export interface PrimitiveStyleAuthoringApi<TTokens = unknown> {
279
+ /** Creates named style handles using the same template tag as `css`. */
280
+ style: StyleAuthoringApi<TTokens>['css'];
281
+ /** Environment token tree. */
282
+ tokens: TTokens;
283
+ }
package/src/testing.ts ADDED
@@ -0,0 +1,48 @@
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
+ import { isCssToken, isScaleToken } from './tokens';
9
+
10
+ export {
11
+ assertVarRefsHaveDeclarations,
12
+ findVarRefViolations,
13
+ type VarInvariantViolation,
14
+ } from './var_invariant';
15
+
16
+ /**
17
+ * Emits a TypeScript `interface` for a derived token tree, for Monaco extraLib text.
18
+ *
19
+ * @param typeName Interface identifier.
20
+ * @param tree Derived {@link CssToken} / {@link ScaleToken} tree.
21
+ * @param leafType Identifier used for every leaf. Defaults to `CssInterpolable`.
22
+ */
23
+ export const tokenTreeDts = (
24
+ typeName: string,
25
+ tree: object,
26
+ leafType = 'CssInterpolable'
27
+ ): string => `interface ${typeName} ${emitTokenObject(tree, 0, leafType)}`;
28
+
29
+ const emitTokenObject = (
30
+ value: object,
31
+ indent: number,
32
+ leafType: string
33
+ ): string => {
34
+ const pad = ' '.repeat(indent);
35
+ const inner = ' '.repeat(indent + 1);
36
+ const lines = Object.entries(value as Record<string, unknown>).map(
37
+ ([key, child]) => {
38
+ if (isCssToken(child) || isScaleToken(child)) {
39
+ return `${inner}readonly ${key}: ${leafType};`;
40
+ }
41
+ if (child && typeof child === 'object') {
42
+ return `${inner}readonly ${key}: ${emitTokenObject(child, indent + 1, leafType)};`;
43
+ }
44
+ throw new Error(`tokenTreeDts: unexpected leaf at "${key}"`);
45
+ }
46
+ );
47
+ return `{\n${lines.join('\n')}\n${pad}}`;
48
+ };