@ethlete/cli 3.0.0-next.24 → 3.0.0-next.26

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 (194) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/design/define-call.ts +69 -0
  3. package/design/define-part.ts +43 -9
  4. package/design/embed-page.ts +37 -4
  5. package/design/icon-sheet.ts +151 -0
  6. package/design/logo-sheet.ts +52 -0
  7. package/design/mark-part.ts +79 -2
  8. package/design/motion-drive.ts +215 -0
  9. package/design/motion-runtime.ts +224 -5
  10. package/design/motion-tokens.ts +177 -0
  11. package/design/spring.ts +53 -0
  12. package/design/token-sheet.ts +87 -0
  13. package/design/web/frame-fill.ts +4 -0
  14. package/design/web/frame-scheme.ts +44 -0
  15. package/design/web/frame.ts +239 -31
  16. package/package.json +1 -1
  17. package/src/lib/design/calls-tsconfig.d.ts +2 -0
  18. package/src/lib/design/calls-tsconfig.js +11 -5
  19. package/src/lib/design/calls-tsconfig.js.map +1 -1
  20. package/src/lib/design/check.d.ts +1 -0
  21. package/src/lib/design/check.js +62 -6
  22. package/src/lib/design/check.js.map +1 -1
  23. package/src/lib/design/contact-sheet.d.ts +9 -0
  24. package/src/lib/design/contact-sheet.js +38 -4
  25. package/src/lib/design/contact-sheet.js.map +1 -1
  26. package/src/lib/design/design-command.js +56 -7
  27. package/src/lib/design/design-command.js.map +1 -1
  28. package/src/lib/design/draw-ledger.d.ts +36 -0
  29. package/src/lib/design/draw-ledger.js +52 -0
  30. package/src/lib/design/draw-ledger.js.map +1 -0
  31. package/src/lib/design/force-interactions.d.ts +2 -1
  32. package/src/lib/design/force-interactions.js +7 -4
  33. package/src/lib/design/force-interactions.js.map +1 -1
  34. package/src/lib/design/mcp-command.d.ts +81 -0
  35. package/src/lib/design/mcp-command.js +422 -0
  36. package/src/lib/design/mcp-command.js.map +1 -0
  37. package/src/lib/design/motion-command.d.ts +71 -0
  38. package/src/lib/design/motion-command.js +308 -0
  39. package/src/lib/design/motion-command.js.map +1 -0
  40. package/src/lib/design/motion-endpoint.d.ts +61 -0
  41. package/src/lib/design/motion-endpoint.js +133 -0
  42. package/src/lib/design/motion-endpoint.js.map +1 -0
  43. package/src/lib/design/motion-flow.d.ts +5 -0
  44. package/src/lib/design/motion-flow.js +29 -0
  45. package/src/lib/design/motion-flow.js.map +1 -0
  46. package/src/lib/design/motion-shot.d.ts +43 -0
  47. package/src/lib/design/motion-shot.js +97 -0
  48. package/src/lib/design/motion-shot.js.map +1 -0
  49. package/src/lib/design/paths.d.ts +14 -0
  50. package/src/lib/design/paths.js +50 -1
  51. package/src/lib/design/paths.js.map +1 -1
  52. package/src/lib/design/ready-shots.d.ts +7 -0
  53. package/src/lib/design/ready-shots.js +22 -1
  54. package/src/lib/design/ready-shots.js.map +1 -1
  55. package/src/lib/design/render-packages.d.ts +14 -0
  56. package/src/lib/design/render-packages.js +18 -3
  57. package/src/lib/design/render-packages.js.map +1 -1
  58. package/src/lib/design/render-worker.js +1 -0
  59. package/src/lib/design/render-worker.js.map +1 -1
  60. package/src/lib/design/render.js +3 -3
  61. package/src/lib/design/render.js.map +1 -1
  62. package/src/lib/design/serve.d.ts +2 -2
  63. package/src/lib/design/serve.js +191 -21
  64. package/src/lib/design/serve.js.map +1 -1
  65. package/src/lib/design/shot-command.d.ts +13 -4
  66. package/src/lib/design/shot-command.js +30 -24
  67. package/src/lib/design/shot-command.js.map +1 -1
  68. package/src/lib/design/shot-store.d.ts +24 -0
  69. package/src/lib/design/shot-store.js +120 -0
  70. package/src/lib/design/shot-store.js.map +1 -0
  71. package/src/lib/design/shot.d.ts +37 -1
  72. package/src/lib/design/shot.js +185 -34
  73. package/src/lib/design/shot.js.map +1 -1
  74. package/src/lib/design/sketch-command.js +197 -51
  75. package/src/lib/design/sketch-command.js.map +1 -1
  76. package/src/lib/design/ssr-shims.d.ts +2 -0
  77. package/src/lib/design/ssr-shims.js +12 -1
  78. package/src/lib/design/ssr-shims.js.map +1 -1
  79. package/src/lib/design/studio-api.d.ts +31 -0
  80. package/src/lib/design/studio-api.js +118 -1
  81. package/src/lib/design/studio-api.js.map +1 -1
  82. package/src/lib/design/system/check-system.d.ts +6 -2
  83. package/src/lib/design/system/check-system.js +102 -20
  84. package/src/lib/design/system/check-system.js.map +1 -1
  85. package/src/lib/design/system/colour.d.ts +4 -1
  86. package/src/lib/design/system/colour.js +54 -2
  87. package/src/lib/design/system/colour.js.map +1 -1
  88. package/src/lib/design/system/complete-command.d.ts +1 -1
  89. package/src/lib/design/system/complete-command.js +80 -6
  90. package/src/lib/design/system/complete-command.js.map +1 -1
  91. package/src/lib/design/system/complete.d.ts +172 -9
  92. package/src/lib/design/system/complete.js +792 -167
  93. package/src/lib/design/system/complete.js.map +1 -1
  94. package/src/lib/design/system/contrast-check.d.ts +6 -1
  95. package/src/lib/design/system/contrast-check.js +167 -12
  96. package/src/lib/design/system/contrast-check.js.map +1 -1
  97. package/src/lib/design/system/drawn-open-points.d.ts +16 -0
  98. package/src/lib/design/system/drawn-open-points.js +43 -0
  99. package/src/lib/design/system/drawn-open-points.js.map +1 -0
  100. package/src/lib/design/system/exceptions.d.ts +59 -0
  101. package/src/lib/design/system/exceptions.js +132 -0
  102. package/src/lib/design/system/exceptions.js.map +1 -0
  103. package/src/lib/design/system/flows/flow.d.ts +3 -0
  104. package/src/lib/design/system/flows/flow.js +5 -3
  105. package/src/lib/design/system/flows/flow.js.map +1 -1
  106. package/src/lib/design/system/handoff.d.ts +11 -0
  107. package/src/lib/design/system/handoff.js +30 -0
  108. package/src/lib/design/system/handoff.js.map +1 -0
  109. package/src/lib/design/system/inventory-approval.d.ts +41 -0
  110. package/src/lib/design/system/inventory-approval.js +71 -0
  111. package/src/lib/design/system/inventory-approval.js.map +1 -0
  112. package/src/lib/design/system/inventory.d.ts +65 -0
  113. package/src/lib/design/system/inventory.js +168 -0
  114. package/src/lib/design/system/inventory.js.map +1 -0
  115. package/src/lib/design/system/list-system.d.ts +4 -1
  116. package/src/lib/design/system/list-system.js +11 -3
  117. package/src/lib/design/system/list-system.js.map +1 -1
  118. package/src/lib/design/system/open-points.d.ts +101 -0
  119. package/src/lib/design/system/open-points.js +156 -0
  120. package/src/lib/design/system/open-points.js.map +1 -0
  121. package/src/lib/design/system/part-ready.d.ts +52 -0
  122. package/src/lib/design/system/part-ready.js +101 -0
  123. package/src/lib/design/system/part-ready.js.map +1 -0
  124. package/src/lib/design/system/read-open-points.d.ts +27 -0
  125. package/src/lib/design/system/read-open-points.js +81 -0
  126. package/src/lib/design/system/read-open-points.js.map +1 -0
  127. package/src/lib/design/system/ready-command.d.ts +7 -0
  128. package/src/lib/design/system/ready-command.js +65 -0
  129. package/src/lib/design/system/ready-command.js.map +1 -0
  130. package/src/lib/design/system/render-check.d.ts +2 -0
  131. package/src/lib/design/system/render-check.js +216 -32
  132. package/src/lib/design/system/render-check.js.map +1 -1
  133. package/src/lib/design/system/resolve-product.d.ts +4 -4
  134. package/src/lib/design/system/resolve-product.js +22 -7
  135. package/src/lib/design/system/resolve-product.js.map +1 -1
  136. package/src/lib/design/system/rules/implements.d.ts +7 -0
  137. package/src/lib/design/system/rules/implements.js +43 -0
  138. package/src/lib/design/system/rules/implements.js.map +1 -0
  139. package/src/lib/design/system/rules/motion.d.ts +2 -0
  140. package/src/lib/design/system/rules/motion.js +26 -1
  141. package/src/lib/design/system/rules/motion.js.map +1 -1
  142. package/src/lib/design/system/rules/option-values.d.ts +15 -0
  143. package/src/lib/design/system/rules/option-values.js +145 -0
  144. package/src/lib/design/system/rules/option-values.js.map +1 -0
  145. package/src/lib/design/system/rules/stale-replace.js +80 -3
  146. package/src/lib/design/system/rules/stale-replace.js.map +1 -1
  147. package/src/lib/design/system/rules/token-selectors.js +1 -1
  148. package/src/lib/design/system/rules/token-selectors.js.map +1 -1
  149. package/src/lib/design/system/sketch/baseline.d.ts +11 -2
  150. package/src/lib/design/system/sketch/baseline.js +4 -2
  151. package/src/lib/design/system/sketch/baseline.js.map +1 -1
  152. package/src/lib/design/system/sketch/check-sketches.d.ts +1 -0
  153. package/src/lib/design/system/sketch/check-sketches.js +4 -2
  154. package/src/lib/design/system/sketch/check-sketches.js.map +1 -1
  155. package/src/lib/design/system/sketch/host-edit.d.ts +4 -0
  156. package/src/lib/design/system/sketch/host-edit.js +68 -0
  157. package/src/lib/design/system/sketch/host-edit.js.map +1 -0
  158. package/src/lib/design/system/sketch/merge.d.ts +3 -2
  159. package/src/lib/design/system/sketch/merge.js +79 -12
  160. package/src/lib/design/system/sketch/merge.js.map +1 -1
  161. package/src/lib/design/system/sketch/merged-parts.d.ts +11 -1
  162. package/src/lib/design/system/sketch/merged-parts.js +24 -3
  163. package/src/lib/design/system/sketch/merged-parts.js.map +1 -1
  164. package/src/lib/design/system/sketch/no-visible-change.d.ts +6 -0
  165. package/src/lib/design/system/sketch/no-visible-change.js +25 -1
  166. package/src/lib/design/system/sketch/no-visible-change.js.map +1 -1
  167. package/src/lib/design/system/sketch/overlay.js +11 -3
  168. package/src/lib/design/system/sketch/overlay.js.map +1 -1
  169. package/src/lib/design/system/sketch/part-definition.d.ts +36 -0
  170. package/src/lib/design/system/sketch/part-definition.js +56 -2
  171. package/src/lib/design/system/sketch/part-definition.js.map +1 -1
  172. package/src/lib/design/system/sketch/sketch.d.ts +47 -0
  173. package/src/lib/design/system/sketch/sketch.js +137 -44
  174. package/src/lib/design/system/sketch/sketch.js.map +1 -1
  175. package/src/lib/design/system/sketch/unchanged-states.d.ts +4 -1
  176. package/src/lib/design/system/sketch/unchanged-states.js +9 -5
  177. package/src/lib/design/system/sketch/unchanged-states.js.map +1 -1
  178. package/src/lib/design/system/sketch/unused-closure.d.ts +12 -0
  179. package/src/lib/design/system/sketch/unused-closure.js +195 -0
  180. package/src/lib/design/system/sketch/unused-closure.js.map +1 -0
  181. package/src/lib/design/system/sketch/unused-files.d.ts +1 -1
  182. package/src/lib/design/system/sketch/unused-files.js +6 -3
  183. package/src/lib/design/system/sketch/unused-files.js.map +1 -1
  184. package/src/lib/design/system/system-modules.js +6 -9
  185. package/src/lib/design/system/system-modules.js.map +1 -1
  186. package/src/lib/design/system/system-tsconfig.d.ts +2 -1
  187. package/src/lib/design/system/system-tsconfig.js +10 -6
  188. package/src/lib/design/system/system-tsconfig.js.map +1 -1
  189. package/src/lib/design/system/ts-source.d.ts +3 -0
  190. package/src/lib/design/system/ts-source.js +21 -4
  191. package/src/lib/design/system/ts-source.js.map +1 -1
  192. package/src/lib/design/thumb.d.ts +31 -4
  193. package/src/lib/design/thumb.js +105 -40
  194. package/src/lib/design/thumb.js.map +1 -1
@@ -0,0 +1,177 @@
1
+ import { MotionTrack } from './motion-runtime';
2
+
3
+ /** A CSS custom property declared on `:root`, with its computed value. */
4
+ export type MotionToken = { name: string; value: string };
5
+
6
+ /** A track with the names of the tokens whose value equals its duration or easing, in declaration order. */
7
+ export type NamedMotionTrack = MotionTrack & { durationTokens?: string[]; easingTokens?: string[] };
8
+
9
+ const KEYWORD_EASINGS: Record<string, string> = {
10
+ ease: 'cubic-bezier(0.25,0.1,0.25,1)',
11
+ 'ease-in': 'cubic-bezier(0.42,0,1,1)',
12
+ 'ease-out': 'cubic-bezier(0,0,0.58,1)',
13
+ 'ease-in-out': 'cubic-bezier(0.42,0,0.58,1)',
14
+ 'step-start': 'steps(1,jump-start)',
15
+ 'step-end': 'steps(1,jump-end)',
16
+ };
17
+
18
+ const TIME = /^(-?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?)(ms|s)$/u;
19
+ const EASING_FUNCTION = /^(cubic-bezier|linear|steps)\((.*)\)$/u;
20
+
21
+ const roundedOf = (value: number) => String(Math.round(value * 10000) / 10000);
22
+
23
+ const msOf = (value: string) => {
24
+ const match = TIME.exec(value.trim().toLowerCase());
25
+
26
+ return match ? Number(match[1]) * (match[2] === 's' ? 1000 : 1) : null;
27
+ };
28
+
29
+ const linearOf = (body: string) => {
30
+ const points: { output: number; input: number | null }[] = [];
31
+
32
+ for (const stop of body.split(',')) {
33
+ const [output = '', ...inputs] = stop.trim().split(/\s+/u);
34
+ const value = Number(output);
35
+
36
+ if (output === '' || !Number.isFinite(value) || inputs.length > 2) return null;
37
+
38
+ const percents = inputs.map((input) => (input.endsWith('%') ? Number(input.slice(0, -1)) : Number.NaN));
39
+
40
+ if (percents.some((percent) => !Number.isFinite(percent))) return null;
41
+
42
+ if (percents.length === 0) points.push({ output: value, input: null });
43
+ for (const percent of percents) points.push({ output: value, input: percent });
44
+ }
45
+
46
+ if (points.length < 2) return null;
47
+
48
+ const first = points[0];
49
+ const last = points[points.length - 1];
50
+
51
+ if (first && first.input === null) first.input = 0;
52
+ if (last && last.input === null) last.input = Math.max(100, ...points.map((point) => point.input ?? 0));
53
+
54
+ let highest = 0;
55
+
56
+ for (const point of points) {
57
+ if (point.input !== null) point.input = Math.max(point.input, highest);
58
+ highest = point.input ?? highest;
59
+ }
60
+
61
+ for (let index = 1; index < points.length; index++) {
62
+ if (points[index]?.input !== null) continue;
63
+
64
+ const before = index - 1;
65
+ let after = index;
66
+
67
+ while (points[after]?.input === null) after++;
68
+
69
+ const from = points[before]?.input ?? 0;
70
+ const to = points[after]?.input ?? 100;
71
+
72
+ for (let gap = index; gap < after; gap++) {
73
+ const point = points[gap];
74
+
75
+ if (point) point.input = from + ((to - from) * (gap - before)) / (after - before);
76
+ }
77
+ }
78
+
79
+ return `linear(${points.map((point) => `${roundedOf(point.output)} ${roundedOf(point.input ?? 0)}%`).join(',')})`;
80
+ };
81
+
82
+ /** An easing in one spelling, so `ease` equals `cubic-bezier(.25, .1, .25, 1)`; `null` when it is not an easing. */
83
+ export const normalisedEasingOf = (value: string): string | null => {
84
+ const easing = value.trim().toLowerCase().replace(/\s+/gu, ' ');
85
+
86
+ if (easing === 'linear') return 'linear';
87
+ if (KEYWORD_EASINGS[easing]) return KEYWORD_EASINGS[easing];
88
+
89
+ const match = EASING_FUNCTION.exec(easing);
90
+
91
+ if (!match) return null;
92
+
93
+ const [, kind = '', body = ''] = match;
94
+
95
+ if (kind === 'linear') return linearOf(body);
96
+
97
+ const args = body.split(',').map((arg) => arg.trim());
98
+
99
+ if (kind === 'steps') return args.length === 1 || args.length === 2 ? `steps(${args[0]},${args[1] ?? 'end'})` : null;
100
+
101
+ const numbers = args.map(Number);
102
+
103
+ return numbers.length === 4 && numbers.every(Number.isFinite)
104
+ ? `cubic-bezier(${numbers.map(roundedOf).join(',')})`
105
+ : null;
106
+ };
107
+
108
+ /** What a token stands for: a time, an easing, or both as a `spring()` token stores them (`<duration>ms linear(...)`). */
109
+ const meaningOf = (value: string) => {
110
+ const trimmed = value.trim();
111
+ const ms = msOf(trimmed);
112
+
113
+ if (ms !== null) return { ms, easing: null };
114
+
115
+ const [head = '', ...rest] = trimmed.split(/\s+/u);
116
+ const leading = msOf(head);
117
+
118
+ if (leading !== null) {
119
+ const easing = normalisedEasingOf(rest.join(' '));
120
+
121
+ return easing ? { ms: leading, easing } : null;
122
+ }
123
+
124
+ const easing = normalisedEasingOf(trimmed);
125
+
126
+ return easing ? { ms: null, easing } : null;
127
+ };
128
+
129
+ /**
130
+ * Adds the names of the `tokens` whose value equals a track's duration (`durationTokens`) or easing (`easingTokens`),
131
+ * in the order of `tokens`; a field without a match is left out.
132
+ */
133
+ export const withTokenNames = (track: MotionTrack, tokens: readonly MotionToken[]): NamedMotionTrack => {
134
+ const easing = normalisedEasingOf(track.easing);
135
+ const durationTokens: string[] = [];
136
+ const easingTokens: string[] = [];
137
+
138
+ for (const token of tokens) {
139
+ const meaning = meaningOf(token.value);
140
+
141
+ if (!meaning) continue;
142
+ if (meaning.ms !== null && Math.abs(meaning.ms - track.duration) < 0.001) durationTokens.push(token.name);
143
+ if (meaning.easing !== null && easing !== null && meaning.easing === easing) easingTokens.push(token.name);
144
+ }
145
+
146
+ return {
147
+ ...track,
148
+ ...(durationTokens.length ? { durationTokens } : {}),
149
+ ...(easingTokens.length ? { easingTokens } : {}),
150
+ };
151
+ };
152
+
153
+ const rootNamesOf = (rules: CSSRuleList, names: Set<string>) => {
154
+ for (const rule of [...rules]) {
155
+ if (rule instanceof CSSStyleRule && /^(:root|html)$/u.test(rule.selectorText.trim()))
156
+ for (const property of [...rule.style]) if (property.startsWith('--')) names.add(property);
157
+
158
+ if ('cssRules' in rule && rule.cssRules instanceof CSSRuleList) rootNamesOf(rule.cssRules, names);
159
+ }
160
+ };
161
+
162
+ /** The custom properties declared on `:root` in the stylesheets of `document`, in declaration order, as computed there. */
163
+ export const rootTokensOf = (document: Document): MotionToken[] => {
164
+ const names = new Set<string>();
165
+
166
+ for (const sheet of [...document.styleSheets]) {
167
+ try {
168
+ rootNamesOf(sheet.cssRules, names);
169
+ } catch {
170
+ continue;
171
+ }
172
+ }
173
+
174
+ const computed = getComputedStyle(document.documentElement);
175
+
176
+ return [...names].map((name) => ({ name, value: computed.getPropertyValue(name).trim() }));
177
+ };
@@ -0,0 +1,53 @@
1
+ /** A spring as a CSS easing: `easing` is a `linear(...)` function, `duration` the ms until it settles; as a string, `<duration>ms <easing>`. */
2
+ export type Spring = { easing: string; duration: number; toString: () => string };
3
+
4
+ const SETTLED = 0.001;
5
+ const LONGEST_MS = 10000;
6
+
7
+ const positionOf = (damping: number, frequency: number) => {
8
+ const natural = 2 * Math.PI * frequency;
9
+
10
+ if (damping < 1) {
11
+ const damped = natural * Math.sqrt(1 - damping * damping);
12
+ const decay = damping * natural;
13
+
14
+ return (seconds: number) =>
15
+ 1 - Math.exp(-decay * seconds) * (Math.cos(damped * seconds) + (decay / damped) * Math.sin(damped * seconds));
16
+ }
17
+
18
+ if (damping === 1) return (seconds: number) => 1 - Math.exp(-natural * seconds) * (1 + natural * seconds);
19
+
20
+ const root = Math.sqrt(damping * damping - 1);
21
+ const slow = -natural * (damping - root);
22
+ const fast = -natural * (damping + root);
23
+
24
+ return (seconds: number) => 1 - (fast * Math.exp(slow * seconds) - slow * Math.exp(fast * seconds)) / (fast - slow);
25
+ };
26
+
27
+ /**
28
+ * Samples a spring that moves from 0 to 1 at rest, for a motion token in `foundations`, e.g.
29
+ * `--motion-capsule: ${spring({ damping: 0.6, frequency: 2 })}`. `damping` is the damping ratio (below 1 it
30
+ * overshoots) and `frequency` the undamped frequency in Hz.
31
+ */
32
+ export const spring = (options: { damping: number; frequency: number }): Spring => {
33
+ const { damping, frequency } = options;
34
+
35
+ if (!(damping > 0) || !(frequency > 0) || !Number.isFinite(damping) || !Number.isFinite(frequency))
36
+ throw new Error(
37
+ `design-explore: spring() needs a damping and a frequency above 0, got ${damping} and ${frequency}`,
38
+ );
39
+
40
+ const position = positionOf(damping, frequency);
41
+ let last = 0;
42
+
43
+ for (let ms = 0; ms <= LONGEST_MS; ms++) if (Math.abs(1 - position(ms / 1000)) >= SETTLED) last = ms;
44
+
45
+ const duration = Math.max(1, last + 1);
46
+ const count = Math.min(200, Math.max(10, Math.ceil(duration / 16)));
47
+ const points = Array.from({ length: count + 1 }, (_, index) =>
48
+ index === count ? 1 : Math.round(position(((index / count) * duration) / 1000) * 10000) / 10000,
49
+ );
50
+ const easing = `linear(${points.join(', ')})`;
51
+
52
+ return { easing, duration, toString: () => `${duration}ms ${easing}` };
53
+ };
@@ -0,0 +1,87 @@
1
+ import { Drawing, html } from './define-call';
2
+
3
+ export type TokenSheetOptions = {
4
+ title: string;
5
+ note?: string;
6
+ scope?: string;
7
+ css: string;
8
+ sample: string;
9
+ sampleOf: (name: string) => string;
10
+ sampleClass?: string;
11
+ only?: (name: string) => boolean;
12
+ pixels?: boolean;
13
+ styles?: string;
14
+ setup?: (root: HTMLElement) => void;
15
+ };
16
+
17
+ const SHEET_STYLES = `
18
+ .ts-root { position: relative; display: flex; flex-direction: column; gap: var(--space-4, 1.6rem); width: 960px; padding: var(--space-6, 2.4rem); box-sizing: border-box; background: var(--surface, oklch(0.17 0 0)); color: var(--text, oklch(0.94 0 0)); font-family: var(--font-ui, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif); font-size: var(--text-md, 1.3rem); line-height: var(--text-md-line, 1.8rem); }
19
+ .ts-title { margin: 0; font-size: var(--text-xl, 1.7rem); line-height: var(--text-xl-line, 2.2rem); font-weight: var(--weight-strong, 600); }
20
+ .ts-note { margin: 0; max-width: 64rem; color: var(--text-muted, oklch(0.72 0 0)); }
21
+ .ts-panel { margin: 0; padding: var(--space-4, 1.6rem); background: var(--surface, oklch(0.17 0 0)); border: 1px solid var(--surface-border, oklch(0.34 0 0)); border-radius: var(--radius-control-lg, 1rem); }
22
+ .ts-list { list-style: none; display: flex; flex-direction: column; gap: var(--space-1, 0.4rem); }
23
+ .ts-row { display: grid; grid-template-columns: 200px 240px 1fr; align-items: center; gap: var(--space-3, 1.2rem); min-height: var(--row, 2.4rem); }
24
+ .ts-name { font-family: var(--font-code, ui-monospace, "SF Mono", Menlo, Consolas, monospace); font-size: var(--text-sm, 1.15rem); line-height: var(--text-sm-line, 1.6rem); color: var(--text-muted, oklch(0.72 0 0)); }
25
+ .ts-value { font-family: var(--font-code, ui-monospace, "SF Mono", Menlo, Consolas, monospace); font-size: var(--text-sm, 1.15rem); line-height: var(--text-sm-line, 1.6rem); overflow-wrap: anywhere; }
26
+ .ts-sample { display: flex; align-items: center; min-width: 0; }
27
+ .ts-probe { position: absolute; left: 0; top: 0; height: 0; visibility: hidden; }
28
+ `;
29
+
30
+ const tokenNamesOf = (css: string, only: (name: string) => boolean) => [
31
+ ...new Set([...css.matchAll(/^\s*--([\w-]+):/gmu)].map(([, name]) => name ?? '').filter(only)),
32
+ ];
33
+
34
+ const fillTokenValues = (root: HTMLElement, pixels: boolean) => {
35
+ const sheet = root.querySelector<HTMLElement>('.ts-root') ?? root;
36
+ const style = getComputedStyle(sheet);
37
+ const probe = sheet.querySelector<HTMLElement>('.ts-probe');
38
+
39
+ for (const cell of sheet.querySelectorAll<HTMLElement>('[data-token]')) {
40
+ const value = style.getPropertyValue(`--${cell.dataset['token']}`).trim();
41
+ let suffix = '';
42
+ if (pixels && probe && /^[\d.]+rem$/u.test(value)) {
43
+ probe.style.width = value;
44
+ suffix = ` · ${Math.round(probe.getBoundingClientRect().width * 10) / 10}px`;
45
+ }
46
+ cell.textContent = value + suffix;
47
+ }
48
+ };
49
+
50
+ /**
51
+ * Draws a foundation sheet: a sample panel, then one row per custom property declared in `css` (narrowed by `only`)
52
+ * with its value as the drawing resolves it, in px too for rem values when `pixels` is set.
53
+ * `scope` adds classes to the sheet root, so tokens scoped to a theme class resolve; `note` is markup under the title.
54
+ */
55
+ export const tokenSheet = ({
56
+ title,
57
+ note,
58
+ scope,
59
+ css,
60
+ sample,
61
+ sampleOf,
62
+ sampleClass = '',
63
+ only = () => true,
64
+ pixels = false,
65
+ styles = '',
66
+ setup,
67
+ }: TokenSheetOptions): Drawing => ({
68
+ body: html`<div class="ts-root${scope ? ` ${scope}` : ''}">
69
+ <h2 class="ts-title">${title}</h2>
70
+ ${note && html`<p class="ts-note">${note}</p>`}
71
+ <section class="ts-panel ${sampleClass}">${sample}</section>
72
+ <ul class="ts-panel ts-list">
73
+ ${tokenNamesOf(css, only)
74
+ .map(
75
+ (name) =>
76
+ html`<li class="ts-row">
77
+ <span class="ts-name">--${name}</span><span class="ts-value" data-token="${name}"></span
78
+ ><span class="ts-sample">${sampleOf(name)}</span>
79
+ </li>`,
80
+ )
81
+ .join('')}
82
+ </ul>
83
+ <span class="ts-probe"></span>
84
+ </div>`,
85
+ styles: `${SHEET_STYLES}${styles}`,
86
+ setup: [(root: HTMLElement) => fillTokenValues(root, pixels), ...(setup ? [setup] : [])],
87
+ });
@@ -0,0 +1,4 @@
1
+ /** Paints `body` with the token `--<fill>` over the product's page colour, which shows when the product has no such token. */
2
+ export const paintFill = (body: HTMLElement, fill: string) => {
3
+ body.style.backgroundImage = `linear-gradient(var(--${fill}), var(--${fill}))`;
4
+ };
@@ -0,0 +1,44 @@
1
+ export type ColorScheme = 'light' | 'dark';
2
+
3
+ const SCHEME_FEATURE = /\(\s*prefers-color-scheme\s*:\s*(light|dark)\s*\)/giu;
4
+
5
+ /** `media` with each `prefers-color-scheme` feature replaced by one that always or never matches, as `scheme` decides. */
6
+ export const pinnedMediaText = (media: string, scheme: ColorScheme) =>
7
+ media.replace(SCHEME_FEATURE, (_, wanted: string) =>
8
+ wanted.toLowerCase() === scheme ? '(width >= 0)' : '(width < 0)',
9
+ );
10
+
11
+ const pinRules = (rules: CSSRuleList, scheme: ColorScheme) => {
12
+ for (const rule of Array.from(rules)) {
13
+ if (rule instanceof CSSMediaRule) {
14
+ const pinned = pinnedMediaText(rule.media.mediaText, scheme);
15
+
16
+ if (pinned !== rule.media.mediaText) rule.media.mediaText = pinned;
17
+ }
18
+
19
+ if ('cssRules' in rule) pinRules((rule as CSSGroupingRule).cssRules, scheme);
20
+ }
21
+ };
22
+
23
+ const pinSheets = (document: Document, scheme: ColorScheme) => {
24
+ for (const sheet of Array.from(document.styleSheets)) {
25
+ try {
26
+ pinRules(sheet.cssRules, scheme);
27
+ } catch {
28
+ // A cross-origin sheet hides its rules.
29
+ }
30
+ }
31
+ };
32
+
33
+ export const pinScheme = (document: Document, scheme: ColorScheme) => {
34
+ document.documentElement.style.colorScheme = scheme;
35
+ pinSheets(document, scheme);
36
+
37
+ new MutationObserver((records) => {
38
+ pinSheets(document, scheme);
39
+
40
+ for (const record of records)
41
+ for (const node of Array.from(record.addedNodes))
42
+ if (node instanceof HTMLLinkElement) node.addEventListener('load', () => pinSheets(document, scheme));
43
+ }).observe(document.head, { childList: true, subtree: true, characterData: true });
44
+ };