@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,172 @@
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 { isBlankCss } from '../css_scan';
9
+ import { isLocalVarMarker, isLocalVarRef } from '../local_vars';
10
+ import { isHandleLike } from '../nesting';
11
+ import {
12
+ isContextualCssVar,
13
+ isContextualCssVarName,
14
+ isCssToken,
15
+ isScaleToken,
16
+ } from '../tokens';
17
+
18
+ import type {
19
+ Declarations,
20
+ DeclarationSegment,
21
+ DefaultKeyDeps,
22
+ LocalVarMarker,
23
+ MutableStyleDeps,
24
+ } from './types';
25
+
26
+ /** Builds a declaration block: interpolations stringify; local-var markers stay until render. */
27
+ export const buildDeclarations = (
28
+ strings: readonly string[],
29
+ values: readonly unknown[]
30
+ ): Declarations => {
31
+ const deps: MutableStyleDeps = {
32
+ theme: new Set(),
33
+ vars: new Set(),
34
+ refs: new Set(),
35
+ defaults: [],
36
+ };
37
+ const segments: DeclarationSegment[] = [];
38
+ let pending = '';
39
+
40
+ const flushPending = (): void => {
41
+ if (pending.length > 0) {
42
+ segments.push(pending);
43
+ pending = '';
44
+ }
45
+ };
46
+
47
+ strings.forEach((part, index) => {
48
+ pending += part;
49
+ if (index < values.length) {
50
+ const value = values[index];
51
+ if (isLocalVarMarker(value)) {
52
+ flushPending();
53
+ // Both default-emission and override-emission markers may carry
54
+ // theme tokens or shared contextual var refs as values. Walk those
55
+ // values and record any nested deps onto the host declaration so
56
+ // tree-shaking and registry registration include them.
57
+ recordMarkerDeps(value, deps);
58
+ segments.push(value);
59
+ } else {
60
+ pending += stringifyCssValue(value, deps);
61
+ }
62
+ }
63
+ });
64
+ flushPending();
65
+
66
+ assertDeclarationBlock(segments);
67
+
68
+ return {
69
+ css: segments,
70
+ deps,
71
+ };
72
+ };
73
+
74
+ const recordMarkerDeps = (
75
+ marker: LocalVarMarker,
76
+ deps: MutableStyleDeps
77
+ ): void => {
78
+ if (marker.__kind === 'local-var-default-marker') {
79
+ // Default markers contribute their var paths and per-value deps only
80
+ // through the collector's reachability pass at resolver creation. We
81
+ // capture per-key value deps here so the collector can fold them in
82
+ // verbatim when the key survives reachability — and skip them entirely
83
+ // when it doesn't.
84
+ deps.defaults.push({
85
+ groupPath: marker.groupPath,
86
+ keys: marker.defaults.map((item) => ({
87
+ key: item.key,
88
+ path: item.path,
89
+ valueDeps: computeValueDeps(item.value),
90
+ })),
91
+ });
92
+ return;
93
+ }
94
+ for (const item of marker.overrides) {
95
+ deps.vars.add(item.path);
96
+ // Stringify each value once to capture nested theme/var deps. The
97
+ // string return is discarded — the runtime re-stringifies at render
98
+ // time so compaction sees the same readable refs we record here.
99
+ stringifyCssValue(item.value, deps);
100
+ }
101
+ };
102
+
103
+ /** Theme/var deps recorded while stringifying one declaration value. */
104
+ export const computeValueDeps = (
105
+ value: unknown
106
+ ): DefaultKeyDeps['valueDeps'] => {
107
+ const local: MutableStyleDeps = {
108
+ theme: new Set(),
109
+ vars: new Set(),
110
+ refs: new Set(),
111
+ defaults: [],
112
+ };
113
+ stringifyCssValue(value, local);
114
+ return { theme: local.theme, vars: local.vars };
115
+ };
116
+
117
+ /** Stringifies one interpolable CSS value and records theme/var deps onto `deps`. */
118
+ export const stringifyCssValue = (
119
+ value: unknown,
120
+ deps: MutableStyleDeps
121
+ ): string => {
122
+ if (isHandleLike(value)) {
123
+ throw new Error(
124
+ 'A style handle cannot be interpolated into a declaration value; compose with the emotion compat css instead.'
125
+ );
126
+ }
127
+ if (isCssToken(value)) {
128
+ deps.theme.add(value.path);
129
+ return value.ref;
130
+ }
131
+ if (isContextualCssVar(value)) {
132
+ deps.vars.add(value.path);
133
+ deps.refs.add(value.path);
134
+ return value.ref;
135
+ }
136
+ if (isContextualCssVarName(value)) {
137
+ // `.name` interpolations are left-hand-side custom-property
138
+ // identifiers, not `var(...)` references — they belong in `vars` for
139
+ // registry coverage but not in `refs` (which feeds reachability).
140
+ deps.vars.add(value.path);
141
+ return value.name;
142
+ }
143
+ if (isLocalVarRef(value)) {
144
+ deps.vars.add(value.path);
145
+ deps.refs.add(value.path);
146
+ return value.ref;
147
+ }
148
+ if (isScaleToken(value)) {
149
+ return value.value;
150
+ }
151
+ return String(value);
152
+ };
153
+
154
+ /** True when every segment is comments, whitespace, or bare `;`. A local-var marker counts as content. */
155
+ export const isEmptyDeclarations = ({ css }: Declarations): boolean =>
156
+ css.every((segment) => typeof segment === 'string' && isBlankCss(segment));
157
+
158
+ const assertDeclarationBlock = (
159
+ segments: readonly DeclarationSegment[]
160
+ ): void => {
161
+ for (const segment of segments) {
162
+ if (typeof segment !== 'string') {
163
+ continue;
164
+ }
165
+ if (
166
+ /[{}]/.test(segment) ||
167
+ /(^|[\s;])@(?:media|supports|container)\b/i.test(segment)
168
+ ) {
169
+ throw new Error('css and decls accept declaration blocks only.');
170
+ }
171
+ }
172
+ };
@@ -0,0 +1,58 @@
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 '../instance';
9
+
10
+ export {
11
+ combineClassNames,
12
+ container,
13
+ css,
14
+ decls,
15
+ globalStylesFromFlattened,
16
+ isVariantMarked,
17
+ mapDomain,
18
+ media,
19
+ pendingHandleFromFlattened,
20
+ rule,
21
+ variants,
22
+ } from './authoring';
23
+ export {
24
+ computeValueDeps,
25
+ isEmptyDeclarations,
26
+ stringifyCssValue,
27
+ } from './declarations';
28
+ export { createStyleModuleWithEnvironment } from './module';
29
+ export { readableVarOwnersFromEnvironment, StyleRegistry } from './registry';
30
+ export type {
31
+ ContextualVarPath,
32
+ CssValue,
33
+ CssVarPath,
34
+ DeclarationSegment,
35
+ Declarations,
36
+ DefaultGroupDeps,
37
+ DefaultKeyDeps,
38
+ LocalVarDefaultMarker,
39
+ LocalVarGroup,
40
+ LocalVarMarker,
41
+ LocalVarOverrideMarker,
42
+ LocalVarRef,
43
+ MutableStyleDeps,
44
+ PendingStyleHandle,
45
+ PrimitiveStyleAuthoringApi,
46
+ ResolvedStyles,
47
+ RuleOptions,
48
+ StyleAuthoringApi,
49
+ StyleDeps,
50
+ StyleEntry,
51
+ StyleHandle,
52
+ StyleMedia,
53
+ StyleRegistrySnapshot,
54
+ StyleRule,
55
+ StylesModule,
56
+ StylesObject,
57
+ StyleSelectorResolver,
58
+ } from './types';
@@ -0,0 +1,396 @@
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 { assertCssIdentSegment } from '../idents';
10
+ import { createLocalVarGroup, type LocalVarGroup } from '../local_vars';
11
+ import type { SelectorAlternative } from '../nesting';
12
+
13
+ import { css, decls, EMPTY_DEPS, isVariantMarked } from './authoring';
14
+ import type { StyleRegistry } from './registry';
15
+ import type {
16
+ ContextualVarPath,
17
+ CssValue,
18
+ PendingEntry,
19
+ PendingNested,
20
+ PendingNestedRule,
21
+ PendingStyleHandle,
22
+ ResolvedStyles,
23
+ StyleAuthoringApi,
24
+ StyleDeps,
25
+ StyleEntry,
26
+ StyleHandle,
27
+ StyleMedia,
28
+ StyleRule,
29
+ StyleSelectorResolver,
30
+ StylesModule,
31
+ StylesObject,
32
+ } from './types';
33
+
34
+ export const createStyleModuleWithEnvironment = <
35
+ TTokens,
36
+ TStyles extends StylesObject,
37
+ >(
38
+ environment: DistilleryEnvironment<TTokens>,
39
+ registry: StyleRegistry,
40
+ name: string,
41
+ factory: (t: StyleAuthoringApi<TTokens>) => TStyles
42
+ ): StylesModule<ResolvedStyles<TStyles>> => {
43
+ assertCssIdentSegment(name, 'Style module name');
44
+ const t = createAuthoringApi(environment, name);
45
+ const authored = factory(t);
46
+ const entries: StyleEntry[] = [];
47
+ const handleList: StyleHandle[] = [];
48
+ const themeDeps = new Set<string>();
49
+ const varDeps = new Set<ContextualVarPath>();
50
+
51
+ assignEntries(name, authored, [], entries, handleList, themeDeps, varDeps);
52
+ recordRuleDeps(entries, handleList);
53
+
54
+ const module: StylesModule<ResolvedStyles<TStyles>> = {
55
+ name,
56
+ handles: authored as ResolvedStyles<TStyles>,
57
+ entries,
58
+ handleList,
59
+ themeDeps,
60
+ varDeps,
61
+ };
62
+ registry.registerModule(module);
63
+ return module;
64
+ };
65
+
66
+ const createAuthoringApi = <TTokens>(
67
+ environment: DistilleryEnvironment<TTokens>,
68
+ moduleName: string
69
+ ): StyleAuthoringApi<TTokens> => ({
70
+ css,
71
+ decls,
72
+ vars: <TKey extends string>(
73
+ group: string,
74
+ defaults: Record<TKey, CssValue>
75
+ ): LocalVarGroup<TKey> =>
76
+ createLocalVarGroup(environment.prefix, moduleName, group, defaults),
77
+ tokens: environment.tokens,
78
+ });
79
+
80
+ const assignEntries = (
81
+ moduleName: string,
82
+ value: unknown,
83
+ path: readonly string[],
84
+ entries: StyleEntry[],
85
+ handles: StyleHandle[],
86
+ themeDeps: Set<string>,
87
+ varDeps: Set<ContextualVarPath>,
88
+ inheritedVariant = false
89
+ ): unknown => {
90
+ // A value is considered "variant" when either the immediate node was
91
+ // produced inside a `variants(...)` factory, or it sits beneath a parent
92
+ // `variants(...)` record (covers nested handles inside a per-variant
93
+ // object literal). The flag is sticky once set during the walk.
94
+ const variantHere = inheritedVariant || isVariantMarked(value);
95
+
96
+ if (isPendingHandle(value)) {
97
+ const localName = path.join('/');
98
+ const handle: StyleHandle = {
99
+ kind: 'handle',
100
+ moduleName,
101
+ localName,
102
+ key: `${moduleName}/${localName}`,
103
+ readableName: `${moduleName}-${path.join('-')}`,
104
+ declarations: value.declarations,
105
+ variant: variantHere,
106
+ resolveClassName: (context) => context.resolveClassName(handle),
107
+ };
108
+ entries.push(handle);
109
+ handles.push(handle);
110
+ copyDeps(value.declarations.deps, themeDeps, varDeps);
111
+ if (value.nested && value.nested.length > 0) {
112
+ expandNestedEntries(
113
+ moduleName,
114
+ handle,
115
+ value.nested,
116
+ variantHere,
117
+ entries,
118
+ themeDeps,
119
+ varDeps
120
+ );
121
+ }
122
+ return handle;
123
+ }
124
+
125
+ if (isRule(value)) {
126
+ const keyedRule: StyleRule = {
127
+ ...value,
128
+ moduleName,
129
+ key: `${moduleName}/${path.join('/')}`,
130
+ variant: variantHere,
131
+ };
132
+ entries.push(keyedRule);
133
+ copyDeps(value.declarations.deps, themeDeps, varDeps);
134
+ return keyedRule;
135
+ }
136
+
137
+ if (isMedia(value)) {
138
+ const mediaKey = `${moduleName}/${path.join('/')}`;
139
+ const keyedMedia: StyleMedia = {
140
+ ...value,
141
+ key: mediaKey,
142
+ moduleName,
143
+ variant: variantHere,
144
+ rules: value.rules.map((child, index) => ({
145
+ ...child,
146
+ moduleName,
147
+ key: `${mediaKey}/${index}`,
148
+ variant: variantHere,
149
+ })),
150
+ };
151
+ entries.push(keyedMedia);
152
+ for (const child of keyedMedia.rules) {
153
+ copyDeps(child.declarations.deps, themeDeps, varDeps);
154
+ }
155
+ return keyedMedia;
156
+ }
157
+
158
+ if (value && typeof value === 'object') {
159
+ for (const [key, child] of Object.entries(value)) {
160
+ assertCssIdentSegment(key, `Style module "${moduleName}" key`);
161
+ const assigned = assignEntries(
162
+ moduleName,
163
+ child,
164
+ [...path, key],
165
+ entries,
166
+ handles,
167
+ themeDeps,
168
+ varDeps,
169
+ variantHere
170
+ );
171
+ if (assigned) {
172
+ (value as Record<string, unknown>)[key] = assigned;
173
+ }
174
+ }
175
+ }
176
+
177
+ return value;
178
+ };
179
+
180
+ const MAX_NESTED_SIBLINGS = 1000;
181
+
182
+ // Zero-padded so `collectedEntries` (lexicographic sort) preserves source order past 9 siblings.
183
+ const paddedIndex = (index: number): string => {
184
+ if (index >= MAX_NESTED_SIBLINGS) {
185
+ throw new Error(
186
+ `A single css template cannot have more than ${MAX_NESTED_SIBLINGS} nested blocks.`
187
+ );
188
+ }
189
+ return String(index).padStart(3, '0');
190
+ };
191
+
192
+ const composeNestedSelector =
193
+ (self: StyleHandle, alternatives: readonly SelectorAlternative[]) =>
194
+ (h: StyleSelectorResolver): string =>
195
+ alternatives
196
+ .map((parts) =>
197
+ parts
198
+ .map((part) =>
199
+ typeof part === 'string'
200
+ ? part.replaceAll('&', `.${h(self)}`)
201
+ : `.${h(part)}`
202
+ )
203
+ .join('')
204
+ )
205
+ .join(',');
206
+
207
+ const expandNestedEntries = (
208
+ moduleName: string,
209
+ self: StyleHandle,
210
+ nested: readonly PendingNested[],
211
+ variant: boolean,
212
+ entries: StyleEntry[],
213
+ themeDeps: Set<string>,
214
+ varDeps: Set<ContextualVarPath>
215
+ ): void => {
216
+ nested.forEach((entry, index) => {
217
+ const key = `${self.key}/&/${paddedIndex(index)}`;
218
+ if (entry.kind === 'nested-rule') {
219
+ entries.push(
220
+ makeNestedRule(
221
+ moduleName,
222
+ key,
223
+ self,
224
+ entry,
225
+ variant,
226
+ themeDeps,
227
+ varDeps
228
+ )
229
+ );
230
+ return;
231
+ }
232
+ const rules: StyleRule[] = [];
233
+ if (entry.self) {
234
+ rules.push(
235
+ makeNestedRule(
236
+ moduleName,
237
+ `${key}/${paddedIndex(0)}`,
238
+ self,
239
+ {
240
+ kind: 'nested-rule',
241
+ alternatives: [['&']],
242
+ declarations: entry.self,
243
+ },
244
+ variant,
245
+ themeDeps,
246
+ varDeps
247
+ )
248
+ );
249
+ }
250
+ entry.rules.forEach((rule, ruleIndex) => {
251
+ rules.push(
252
+ makeNestedRule(
253
+ moduleName,
254
+ `${key}/${paddedIndex(ruleIndex + (entry.self ? 1 : 0))}`,
255
+ self,
256
+ rule,
257
+ variant,
258
+ themeDeps,
259
+ varDeps
260
+ )
261
+ );
262
+ });
263
+ entries.push({
264
+ kind: 'media',
265
+ key,
266
+ moduleName,
267
+ query: entry.query,
268
+ rules,
269
+ variant,
270
+ });
271
+ });
272
+ };
273
+
274
+ const makeNestedRule = (
275
+ moduleName: string,
276
+ key: string,
277
+ self: StyleHandle,
278
+ rule: PendingNestedRule,
279
+ variant: boolean,
280
+ themeDeps: Set<string>,
281
+ varDeps: Set<ContextualVarPath>
282
+ ): StyleRule => {
283
+ copyDeps(rule.declarations.deps, themeDeps, varDeps);
284
+ return {
285
+ kind: 'rule',
286
+ moduleName,
287
+ key,
288
+ selector: composeNestedSelector(self, rule.alternatives),
289
+ declarations: rule.declarations,
290
+ dependsOn: EMPTY_DEPS,
291
+ auto: true,
292
+ variant,
293
+ };
294
+ };
295
+
296
+ const recordRuleDeps = (
297
+ entries: readonly StyleEntry[],
298
+ handles: readonly StyleHandle[]
299
+ ): void => {
300
+ if (entries.length === 0) {
301
+ return;
302
+ }
303
+ for (const entry of entries) {
304
+ if (entry.kind === 'rule') {
305
+ assignDependsOn(entry, computeRuleDeps(entry, handles));
306
+ continue;
307
+ }
308
+ if (entry.kind === 'media') {
309
+ for (const nested of entry.rules) {
310
+ assignDependsOn(nested, computeRuleDeps(nested, handles));
311
+ }
312
+ }
313
+ }
314
+ };
315
+
316
+ const assignDependsOn = (
317
+ rule: StyleRule,
318
+ dependsOn: ReadonlySet<string>
319
+ ): void => {
320
+ (rule as { dependsOn: ReadonlySet<string> }).dependsOn = dependsOn;
321
+ };
322
+
323
+ const computeRuleDeps = (
324
+ rule: StyleRule,
325
+ handles: readonly StyleHandle[]
326
+ ): ReadonlySet<string> => {
327
+ const accessed = new Set<string>();
328
+ const trackingResolver = ((handle: StyleHandle) => {
329
+ accessed.add(handle.key);
330
+ return `.${handle.readableName}`;
331
+ }) as StyleSelectorResolver;
332
+ for (const handle of handles) {
333
+ defineTrackingAlias(trackingResolver, handle.localName, handle, accessed);
334
+ const leafName = handle.localName.split('/').at(-1);
335
+ if (leafName && leafName !== handle.localName) {
336
+ defineTrackingAlias(trackingResolver, leafName, handle, accessed);
337
+ }
338
+ }
339
+ try {
340
+ rule.selector(trackingResolver);
341
+ } catch {
342
+ // Selector factories are expected to be pure string-builders, but a
343
+ // factory that throws on dummy input shouldn't break module construction.
344
+ // The dep set stays whatever was recorded before the throw.
345
+ }
346
+ return accessed;
347
+ };
348
+
349
+ const defineTrackingAlias = (
350
+ resolver: StyleSelectorResolver,
351
+ name: string,
352
+ handle: StyleHandle,
353
+ accessed: Set<string>
354
+ ): void => {
355
+ if (!/^[A-Za-z_$][\w$]*$/.test(name) || name in resolver) {
356
+ return;
357
+ }
358
+ Object.defineProperty(resolver, name, {
359
+ configurable: true,
360
+ enumerable: true,
361
+ get: () => {
362
+ accessed.add(handle.key);
363
+ return `.${handle.readableName}`;
364
+ },
365
+ });
366
+ };
367
+
368
+ const copyDeps = (
369
+ deps: StyleDeps,
370
+ themeDeps: Set<string>,
371
+ varDeps: Set<ContextualVarPath>
372
+ ): void => {
373
+ deps.theme.forEach((path) => themeDeps.add(path));
374
+ deps.vars.forEach((path) => varDeps.add(path));
375
+ };
376
+
377
+ const isPendingHandle = (value: unknown): value is PendingStyleHandle =>
378
+ Boolean(
379
+ value &&
380
+ typeof value === 'object' &&
381
+ (value as PendingEntry).kind === 'pending-handle'
382
+ );
383
+
384
+ const isRule = (value: unknown): value is StyleRule =>
385
+ Boolean(
386
+ value &&
387
+ typeof value === 'object' &&
388
+ (value as PendingEntry).kind === 'rule'
389
+ );
390
+
391
+ const isMedia = (value: unknown): value is StyleMedia =>
392
+ Boolean(
393
+ value &&
394
+ typeof value === 'object' &&
395
+ (value as PendingEntry).kind === 'media'
396
+ );