@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,242 @@
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 {
9
+ container,
10
+ media,
11
+ rule,
12
+ type StyleHandle,
13
+ type StylesCollector,
14
+ variants,
15
+ } from '@elastic/distillate';
16
+ import { type ClassNameArg, createEmotion } from '@elastic/distillate/emotion';
17
+ import { transform } from 'sucrase';
18
+
19
+ import {
20
+ createDemoDistillery,
21
+ type DemoTokens,
22
+ demoTokens,
23
+ } from './demo_environment';
24
+
25
+ export type PlaygroundMode = 'emotion' | 'native';
26
+
27
+ export type CompileStage = 'transform' | 'evaluate' | 'render';
28
+
29
+ export interface CompileError {
30
+ readonly stage: CompileStage;
31
+ readonly message: string;
32
+ }
33
+
34
+ export interface CompileSuccess {
35
+ readonly ok: true;
36
+ readonly previewHtml: string;
37
+ readonly readableCss: string;
38
+ readonly compactCss: string;
39
+ readonly artifactCss: string;
40
+ readonly stylesheetBytes: number;
41
+ readonly artifactBytes: number;
42
+ }
43
+
44
+ export interface CompileFailure {
45
+ readonly ok: false;
46
+ readonly error: CompileError;
47
+ }
48
+
49
+ export type PlaygroundResult = CompileSuccess | CompileFailure;
50
+
51
+ type Distillery = ReturnType<typeof createDemoDistillery>;
52
+
53
+ const utf8Bytes = (css: string): number =>
54
+ new TextEncoder().encode(css).byteLength;
55
+
56
+ const stripImports = (source: string): string =>
57
+ source.replace(/^\s*import\b.*(?:\n|$)/gm, '');
58
+
59
+ const messageOf = (error: unknown): string =>
60
+ error instanceof Error ? error.message : String(error);
61
+
62
+ const isStyleHandle = (value: unknown): value is StyleHandle =>
63
+ Boolean(
64
+ value &&
65
+ typeof value === 'object' &&
66
+ (value as { kind?: string }).kind === 'handle'
67
+ );
68
+
69
+ const collectStyleHandles = (
70
+ collector: StylesCollector,
71
+ values: readonly unknown[]
72
+ ): void => {
73
+ const handles: StyleHandle[] = [];
74
+ const walk = (value: unknown): void => {
75
+ if (Array.isArray(value)) {
76
+ value.forEach(walk);
77
+ return;
78
+ }
79
+ if (isStyleHandle(value)) {
80
+ handles.push(value);
81
+ }
82
+ };
83
+ for (const value of values) {
84
+ walk(value);
85
+ }
86
+ if (handles.length > 0) {
87
+ collector.useHandles(handles);
88
+ }
89
+ };
90
+
91
+ const readableClassName = (...args: readonly unknown[]): string =>
92
+ args
93
+ .filter((arg): arg is NonNullable<typeof arg> => Boolean(arg))
94
+ .map((arg) => {
95
+ if (typeof arg === 'string') {
96
+ return arg;
97
+ }
98
+ if (typeof arg === 'number') {
99
+ return String(arg);
100
+ }
101
+ const readable = (arg as Partial<StyleHandle>).readableName;
102
+ return typeof readable === 'string' ? readable : '';
103
+ })
104
+ .filter(Boolean)
105
+ .join(' ');
106
+
107
+ const cnFor =
108
+ (collector: StylesCollector) =>
109
+ (...args: readonly unknown[]): string => {
110
+ collectStyleHandles(collector, args);
111
+ return readableClassName(...args);
112
+ };
113
+
114
+ const bindEmotionCollection = (
115
+ distillery: Distillery,
116
+ collector: StylesCollector
117
+ ): Record<string, unknown> => {
118
+ const emotion = createEmotion<DemoTokens>(distillery);
119
+ const css = (
120
+ strings: TemplateStringsArray,
121
+ ...values: readonly unknown[]
122
+ ): ReturnType<typeof emotion.css> => {
123
+ const handle = emotion.css(strings, ...values);
124
+ const asString = (): string => {
125
+ collector.useHandles([handle]);
126
+ return handle.readableName;
127
+ };
128
+ handle.toString = asString;
129
+ handle[Symbol.toPrimitive] = asString;
130
+ return handle;
131
+ };
132
+ const cx = (...args: readonly ClassNameArg[]): string => {
133
+ collectStyleHandles(collector, args);
134
+ return emotion.cx(...args);
135
+ };
136
+ return {
137
+ css,
138
+ cx,
139
+ injectGlobal: (
140
+ strings: TemplateStringsArray,
141
+ ...values: readonly unknown[]
142
+ ): void => emotion.injectGlobal(strings, ...values),
143
+ };
144
+ };
145
+
146
+ const buildScope = (
147
+ mode: PlaygroundMode,
148
+ distillery: Distillery,
149
+ collector: StylesCollector
150
+ ): Record<string, unknown> => {
151
+ const shared: Record<string, unknown> = {
152
+ cn: cnFor(collector),
153
+ tokens: demoTokens,
154
+ };
155
+ if (mode === 'emotion') {
156
+ return { ...shared, ...bindEmotionCollection(distillery, collector) };
157
+ }
158
+ const { createStyleModule, primitiveStyles } = distillery;
159
+ return {
160
+ ...shared,
161
+ createStyleModule,
162
+ primitiveStyles,
163
+ rule,
164
+ media,
165
+ container,
166
+ variants,
167
+ };
168
+ };
169
+
170
+ const evaluate = (
171
+ transformed: string,
172
+ scope: Record<string, unknown>
173
+ ): unknown => {
174
+ // Playground compiler: eval is the point.
175
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
176
+ const factory = new Function(
177
+ 'scope',
178
+ `with (scope) {\n${transformed}\n}`
179
+ ) as (scope: Record<string, unknown>) => unknown;
180
+ return factory(scope);
181
+ };
182
+
183
+ export const compilePlayground = (
184
+ source: string,
185
+ mode: PlaygroundMode
186
+ ): PlaygroundResult => {
187
+ const normalized = stripImports(source);
188
+
189
+ let transformed: string;
190
+ try {
191
+ transformed = transform(normalized, {
192
+ transforms: ['typescript'],
193
+ production: true,
194
+ }).code;
195
+ } catch (error) {
196
+ return {
197
+ ok: false,
198
+ error: { stage: 'transform', message: messageOf(error) },
199
+ };
200
+ }
201
+
202
+ const distillery = createDemoDistillery();
203
+ const artifactCollector = distillery.artifactCollector('compact');
204
+ let returned: unknown;
205
+ try {
206
+ returned = evaluate(
207
+ transformed,
208
+ buildScope(mode, distillery, artifactCollector)
209
+ );
210
+ } catch (error) {
211
+ return {
212
+ ok: false,
213
+ error: { stage: 'evaluate', message: messageOf(error) },
214
+ };
215
+ }
216
+
217
+ try {
218
+ const previewHtml = typeof returned === 'string' ? returned : '';
219
+ const readableCss = distillery.renderStyles(
220
+ distillery.stylesheetCollector('readable')
221
+ );
222
+ const compactCss = distillery.renderStyles(
223
+ distillery.stylesheetCollector('compact')
224
+ );
225
+ const artifactResolver = artifactCollector.createResolver();
226
+ const artifactCss = distillery.renderStyles(
227
+ artifactCollector,
228
+ artifactResolver
229
+ );
230
+ return {
231
+ ok: true,
232
+ previewHtml,
233
+ readableCss,
234
+ compactCss,
235
+ artifactCss,
236
+ stylesheetBytes: utf8Bytes(readableCss),
237
+ artifactBytes: utf8Bytes(artifactCss),
238
+ };
239
+ } catch (error) {
240
+ return { ok: false, error: { stage: 'render', message: messageOf(error) } };
241
+ }
242
+ };
@@ -0,0 +1,123 @@
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 { describe, expect, it } from 'vitest';
9
+
10
+ import {
11
+ createDemoDistillery,
12
+ demoTokenCatalog,
13
+ demoTokens,
14
+ } from './demo_environment';
15
+
16
+ describe('demoTokenCatalog', () => {
17
+ it('lists every playground token with its resolved values', () => {
18
+ const byAccess = new Map(
19
+ demoTokenCatalog.map((entry) => [entry.access, entry])
20
+ );
21
+ expect(byAccess.size).toBe(demoTokenCatalog.length);
22
+
23
+ const accent = byAccess.get('tokens.color.accent');
24
+ expect(accent).toMatchObject({
25
+ kind: 'theme',
26
+ cssVar: demoTokens.color.accent.cssVar,
27
+ light: '#BC1E70',
28
+ dark: '#EE72A6',
29
+ });
30
+ expect(byAccess.get('tokens.color.primary')).toMatchObject({
31
+ kind: 'theme',
32
+ light: '#0B64DD',
33
+ dark: '#61A2FF',
34
+ });
35
+ expect(byAccess.get('tokens.color.accentSecondary')).toMatchObject({
36
+ kind: 'theme',
37
+ light: '#008B87',
38
+ dark: '#16C5C0',
39
+ });
40
+ expect(byAccess.get('tokens.color.success')).toMatchObject({
41
+ kind: 'theme',
42
+ light: '#008A5E',
43
+ dark: '#24C292',
44
+ });
45
+ expect(byAccess.get('tokens.color.warning')).toMatchObject({
46
+ kind: 'theme',
47
+ light: '#FACB3D',
48
+ dark: '#FACB3D',
49
+ });
50
+ expect(byAccess.get('tokens.color.danger')).toMatchObject({
51
+ kind: 'theme',
52
+ light: '#C61E25',
53
+ dark: '#F6726A',
54
+ });
55
+
56
+ const space = byAccess.get('tokens.space.s');
57
+ expect(space).toMatchObject({
58
+ kind: 'scale',
59
+ value: demoTokens.space.s.value,
60
+ cq: demoTokens.space.s.cq,
61
+ });
62
+
63
+ expect(demoTokenCatalog.map((entry) => entry.access)).toEqual([
64
+ ...Object.keys(demoTokens.color).map((key) => `tokens.color.${key}`),
65
+ ...Object.keys(demoTokens.space).map((key) => `tokens.space.${key}`),
66
+ ...Object.keys(demoTokens.radius).map((key) => `tokens.radius.${key}`),
67
+ ...Object.keys(demoTokens.font).map((key) => `tokens.font.${key}`),
68
+ ]);
69
+ });
70
+ });
71
+
72
+ describe('demo theme variations', () => {
73
+ const renderDemo = (options?: {
74
+ flatten?: string;
75
+ alternates?: readonly { variation: string; selector?: string }[];
76
+ }): string => {
77
+ const distillery = createDemoDistillery();
78
+ distillery.createStyleModule('swatch', (t) => ({
79
+ root: t.css`
80
+ color: ${t.tokens.color.accent};
81
+ background: ${t.tokens.color.surface};
82
+ `,
83
+ }));
84
+ return distillery.renderStyles(
85
+ distillery.stylesheetCollector(),
86
+ undefined,
87
+ options
88
+ );
89
+ };
90
+
91
+ it('keeps base-only output free of declared variations', () => {
92
+ const css = renderDemo();
93
+ expect(css).toContain('--dstl-color-accent:light-dark(#BC1E70,#EE72A6)');
94
+ expect(css).not.toContain('#0077cc');
95
+ expect(css).not.toContain('prefers-contrast');
96
+ });
97
+
98
+ it('flattens muted into :host', () => {
99
+ const css = renderDemo({ flatten: 'muted' });
100
+ expect(css).toContain(':host{');
101
+ expect(css).toContain('--dstl-color-accent:#0077cc');
102
+ expect(css).not.toContain('data-eui-theme');
103
+ });
104
+
105
+ it('emits muted as a :host() alternate', () => {
106
+ const css = renderDemo({
107
+ alternates: [
108
+ { variation: 'muted', selector: '[data-eui-theme="muted"]' },
109
+ ],
110
+ });
111
+ expect(css).toContain(
112
+ ':host([data-eui-theme="muted"]){--dstl-color-accent:#0077cc}'
113
+ );
114
+ });
115
+
116
+ it('wraps highContrast in its media query under :host', () => {
117
+ const css = renderDemo({ flatten: 'highContrast' });
118
+ expect(css).toContain(
119
+ '@media (prefers-contrast:more){:host{--dstl-color-surface:light-dark(#fff,#000)}}'
120
+ );
121
+ expect(css).not.toContain('--dstl-color-text:light-dark(#000,#fff)');
122
+ });
123
+ });
@@ -0,0 +1,140 @@
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 {
9
+ cq,
10
+ createDistillery,
11
+ type Distillery,
12
+ type DistilleryEnvironment,
13
+ isCssToken,
14
+ isScaleToken,
15
+ lightDark,
16
+ type TokensOf,
17
+ } from '@elastic/distillate';
18
+
19
+ const sansStack = 'system-ui, -apple-system, "Segoe UI", sans-serif';
20
+ const monoStack = 'ui-monospace, SFMono-Regular, Menlo, Monaco, monospace';
21
+
22
+ const demoTheme = {
23
+ color: {
24
+ text: lightDark('#1B1E28', '#E6E8F0'),
25
+ surface: lightDark('#FFFFFF', '#141A27'),
26
+ subtle: lightDark('#5A6472', '#9AA4B6'),
27
+ border: lightDark('#DCE2EA', '#2B3242'),
28
+ primary: lightDark('#0B64DD', '#61A2FF'),
29
+ accent: lightDark('#BC1E70', '#EE72A6'),
30
+ accentSecondary: lightDark('#008B87', '#16C5C0'),
31
+ success: lightDark('#008A5E', '#24C292'),
32
+ warning: '#FACB3D',
33
+ danger: lightDark('#C61E25', '#F6726A'),
34
+ },
35
+ space: {
36
+ xs: cq('4px', '1cqi'),
37
+ s: cq('8px', '2cqi'),
38
+ m: cq('16px', '4cqi'),
39
+ l: cq('24px', '6cqi'),
40
+ },
41
+ radius: {
42
+ s: cq('6px', '6px'),
43
+ m: cq('12px', '12px'),
44
+ },
45
+ font: {
46
+ sans: sansStack,
47
+ mono: monoStack,
48
+ },
49
+ };
50
+
51
+ export type DemoTokens = TokensOf<typeof demoTheme>;
52
+
53
+ const demoBind = {
54
+ prefix: 'dstl',
55
+ themeScope: ':host',
56
+ theme: demoTheme,
57
+ dev: true,
58
+ variations: {
59
+ muted: {
60
+ color: {
61
+ accent: '#0077cc',
62
+ },
63
+ },
64
+ highContrast: {
65
+ media: '(prefers-contrast: more)',
66
+ variation: {
67
+ color: {
68
+ text: lightDark('#000', '#fff'),
69
+ surface: lightDark('#fff', '#000'),
70
+ },
71
+ },
72
+ },
73
+ },
74
+ };
75
+
76
+ const catalogDistillery = createDistillery(demoBind);
77
+
78
+ export const demoTokens: DemoTokens = catalogDistillery.tokens;
79
+ export const demoEnvironment: DistilleryEnvironment<DemoTokens> =
80
+ catalogDistillery.environment;
81
+
82
+ export const createDemoDistillery = (): Distillery<DemoTokens> =>
83
+ createDistillery(demoBind);
84
+
85
+ /** A single entry in the token catalog table. */
86
+ export type DemoTokenCatalogEntry =
87
+ | {
88
+ readonly kind: 'theme';
89
+ readonly access: string;
90
+ readonly cssVar: string;
91
+ readonly light: string;
92
+ readonly dark: string;
93
+ }
94
+ | {
95
+ readonly kind: 'scale';
96
+ readonly access: string;
97
+ readonly value: string;
98
+ readonly cq: string;
99
+ };
100
+
101
+ const catalogFromTokens = (
102
+ tokens: object,
103
+ prefix = 'tokens'
104
+ ): DemoTokenCatalogEntry[] => {
105
+ const out: DemoTokenCatalogEntry[] = [];
106
+ const walk = (node: object, access: string): void => {
107
+ for (const [key, child] of Object.entries(
108
+ node as Record<string, unknown>
109
+ )) {
110
+ const next = `${access}.${key}`;
111
+ if (isCssToken(child)) {
112
+ const def = demoEnvironment.themeVars[child.path];
113
+ out.push({
114
+ kind: 'theme',
115
+ access: next,
116
+ cssVar: child.cssVar,
117
+ light: def?.light ?? '',
118
+ dark: def?.dark ?? '',
119
+ });
120
+ } else if (isScaleToken(child)) {
121
+ out.push({
122
+ kind: 'scale',
123
+ access: next,
124
+ value: child.value,
125
+ cq: child.cq,
126
+ });
127
+ } else if (child && typeof child === 'object') {
128
+ walk(child, next);
129
+ } else {
130
+ throw new Error(`Unknown token type at ${next}`);
131
+ }
132
+ }
133
+ };
134
+ walk(tokens, prefix);
135
+ return out;
136
+ };
137
+
138
+ /** Flat catalog of all demo tokens, in group/key insertion order. */
139
+ export const demoTokenCatalog: readonly DemoTokenCatalogEntry[] =
140
+ catalogFromTokens(demoTokens);
@@ -0,0 +1,39 @@
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 { describe, expect, it } from 'vitest';
9
+
10
+ import { extraLibSource, playgroundScopeNames } from './editor_lib';
11
+
12
+ const isDeclared = (source: string, name: string): boolean =>
13
+ source.includes(`declare function ${name}`) ||
14
+ source.includes(`declare const ${name}`);
15
+
16
+ describe('extraLibSource', () => {
17
+ it('declares every emotion eval binding and omits native-only names', () => {
18
+ const source = extraLibSource('emotion');
19
+ for (const name of playgroundScopeNames('emotion')) {
20
+ expect(isDeclared(source, name), name).toBe(true);
21
+ }
22
+ expect(source).toContain('readonly primary');
23
+ expect(source).toContain('readonly accent');
24
+ expect(source).toContain('readonly accentSecondary');
25
+ expect(source).toContain('readonly success');
26
+ expect(source).toContain('readonly warning');
27
+ expect(source).toContain('readonly danger');
28
+ expect(source).not.toContain('createStyleModule');
29
+ });
30
+
31
+ it('declares every native eval binding and omits emotion-only names', () => {
32
+ const source = extraLibSource('native');
33
+ for (const name of playgroundScopeNames('native')) {
34
+ expect(isDeclared(source, name), name).toBe(true);
35
+ }
36
+ expect(source).toContain('LocalVarGroup');
37
+ expect(source).not.toContain('injectGlobal');
38
+ });
39
+ });
@@ -0,0 +1,142 @@
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 { tokenTreeDts } from '@elastic/distillate/testing';
9
+
10
+ import type { PlaygroundMode } from './compile';
11
+ import { demoTokens } from './demo_environment';
12
+
13
+ /** Virtual path for the playground ambient extraLib. */
14
+ export const PLAYGROUND_EXTRA_LIB_PATH = 'file:///playground-globals.d.ts';
15
+
16
+ const demoTokensInterface = tokenTreeDts('DemoTokens', demoTokens);
17
+
18
+ const sharedTypes = `
19
+ interface CssInterpolable {
20
+ readonly [Symbol.toPrimitive]: (hint: string) => string;
21
+ toString(): string;
22
+ }
23
+
24
+ type CssValue = CssInterpolable | string | number;
25
+
26
+ ${demoTokensInterface}
27
+
28
+ interface StyleHandle extends CssInterpolable {
29
+ readonly readableName: string;
30
+ readonly localName: string;
31
+ readonly key: string;
32
+ }
33
+
34
+ type LocalVarGroup<K extends string> = { readonly [P in K]: CssInterpolable } & {
35
+ set(overrides: Partial<Record<K, CssValue>>): CssInterpolable;
36
+ };
37
+
38
+ interface StyleAuthoringApi {
39
+ css(strings: TemplateStringsArray, ...values: unknown[]): StyleHandle;
40
+ decls(strings: TemplateStringsArray, ...values: unknown[]): unknown;
41
+ vars<K extends string>(
42
+ group: string,
43
+ defaults: Record<K, CssValue>
44
+ ): LocalVarGroup<K>;
45
+ readonly tokens: DemoTokens;
46
+ }
47
+
48
+ interface PrimitiveStyleAuthoringApi {
49
+ style(strings: TemplateStringsArray, ...values: unknown[]): StyleHandle;
50
+ readonly tokens: DemoTokens;
51
+ }
52
+
53
+ interface StylesModule<T> {
54
+ readonly name: string;
55
+ readonly handles: T;
56
+ }
57
+
58
+ /** Demo theme and scale tokens. Interpolate into \`css\` templates. */
59
+ declare const tokens: DemoTokens;
60
+
61
+ /** Joins class names and handle \`readableName\`s. Collects handles for the artifact. */
62
+ declare function cn(
63
+ ...args: Array<StyleHandle | string | number | false | null | undefined>
64
+ ): string;
65
+ `;
66
+
67
+ const emotionGlobals = `
68
+ /** Emotion-compat tagged template. Nested \`&\` and \`@media\` flatten. */
69
+ declare function css(
70
+ strings: TemplateStringsArray,
71
+ ...values: unknown[]
72
+ ): StyleHandle;
73
+
74
+ /** Joins class names, handles, arrays, and truthy maps. */
75
+ declare function cx(
76
+ ...args: Array<
77
+ | StyleHandle
78
+ | string
79
+ | number
80
+ | false
81
+ | null
82
+ | undefined
83
+ | ReadonlyArray<unknown>
84
+ | { readonly [className: string]: boolean }
85
+ >
86
+ ): string;
87
+
88
+ /** Registers global rules (\`body\`, \`html\`, …). */
89
+ declare function injectGlobal(
90
+ strings: TemplateStringsArray,
91
+ ...values: unknown[]
92
+ ): void;
93
+ `;
94
+
95
+ const nativeGlobals = `
96
+ /** Named module. Destructure \`css\` / \`vars\` / \`tokens\` from the factory argument. */
97
+ declare function createStyleModule<T extends Record<string, unknown>>(
98
+ name: string,
99
+ factory: (api: StyleAuthoringApi) => T
100
+ ): StylesModule<T>;
101
+
102
+ /** Named handles plus tokens only. */
103
+ declare function primitiveStyles<T extends Record<string, unknown>>(
104
+ name: string,
105
+ factory: (api: PrimitiveStyleAuthoringApi) => T
106
+ ): StylesModule<T>;
107
+
108
+ declare function rule(
109
+ selector: (h: (handle: StyleHandle) => string) => string,
110
+ declarations: unknown
111
+ ): unknown;
112
+
113
+ declare function media(query: string, rules: readonly unknown[]): unknown;
114
+
115
+ declare function container(query: string, rules: readonly unknown[]): unknown;
116
+
117
+ declare function variants<K extends string, V>(
118
+ domain: readonly K[],
119
+ factory: (value: K) => V
120
+ ): Record<K, V>;
121
+ `;
122
+
123
+ /** Ambient `.d.ts` matching the playground eval scope for `mode`. */
124
+ export const extraLibSource = (mode: PlaygroundMode): string =>
125
+ `${sharedTypes}${mode === 'emotion' ? emotionGlobals : nativeGlobals}`;
126
+
127
+ /** Binding names injected at eval time for `mode`. */
128
+ export const playgroundScopeNames = (
129
+ mode: PlaygroundMode
130
+ ): readonly string[] =>
131
+ mode === 'emotion'
132
+ ? ['tokens', 'cn', 'css', 'cx', 'injectGlobal']
133
+ : [
134
+ 'tokens',
135
+ 'cn',
136
+ 'createStyleModule',
137
+ 'primitiveStyles',
138
+ 'rule',
139
+ 'media',
140
+ 'container',
141
+ 'variants',
142
+ ];