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

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 (140) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/design/contrast.ts +49 -0
  3. package/design/define-call.ts +5 -8
  4. package/design/define-part.ts +63 -1
  5. package/design/morph.ts +67 -0
  6. package/design/motion-runtime.ts +200 -0
  7. package/design/web/env.d.ts +6 -2
  8. package/design/web/frame.ts +39 -1
  9. package/package.json +2 -1
  10. package/src/lib/design/changed-parts.d.ts +4 -4
  11. package/src/lib/design/changed-parts.js +10 -6
  12. package/src/lib/design/changed-parts.js.map +1 -1
  13. package/src/lib/design/contact-sheet.d.ts +26 -13
  14. package/src/lib/design/contact-sheet.js +122 -40
  15. package/src/lib/design/contact-sheet.js.map +1 -1
  16. package/src/lib/design/design-command.js +20 -2
  17. package/src/lib/design/design-command.js.map +1 -1
  18. package/src/lib/design/generated-stylesheets.d.ts +15 -0
  19. package/src/lib/design/generated-stylesheets.js +52 -0
  20. package/src/lib/design/generated-stylesheets.js.map +1 -0
  21. package/src/lib/design/inbox-command.d.ts +1 -0
  22. package/src/lib/design/inbox-command.js +15 -0
  23. package/src/lib/design/inbox-command.js.map +1 -1
  24. package/src/lib/design/merge-compare.d.ts +10 -0
  25. package/src/lib/design/merge-compare.js +17 -4
  26. package/src/lib/design/merge-compare.js.map +1 -1
  27. package/src/lib/design/paths.d.ts +11 -0
  28. package/src/lib/design/paths.js.map +1 -1
  29. package/src/lib/design/serve.js +74 -18
  30. package/src/lib/design/serve.js.map +1 -1
  31. package/src/lib/design/shot-command.d.ts +4 -4
  32. package/src/lib/design/shot-command.js +48 -40
  33. package/src/lib/design/shot-command.js.map +1 -1
  34. package/src/lib/design/shot.d.ts +14 -4
  35. package/src/lib/design/shot.js +71 -50
  36. package/src/lib/design/shot.js.map +1 -1
  37. package/src/lib/design/sketch-command.js +55 -24
  38. package/src/lib/design/sketch-command.js.map +1 -1
  39. package/src/lib/design/sketch-stylesheets.d.ts +7 -0
  40. package/src/lib/design/sketch-stylesheets.js +63 -0
  41. package/src/lib/design/sketch-stylesheets.js.map +1 -0
  42. package/src/lib/design/studio-api.d.ts +9 -6
  43. package/src/lib/design/studio-api.js +50 -16
  44. package/src/lib/design/studio-api.js.map +1 -1
  45. package/src/lib/design/system/check-system.js +8 -0
  46. package/src/lib/design/system/check-system.js.map +1 -1
  47. package/src/lib/design/system/comments/comment.d.ts +17 -0
  48. package/src/lib/design/system/comments/comment.js +35 -1
  49. package/src/lib/design/system/comments/comment.js.map +1 -1
  50. package/src/lib/design/system/comments/complete.js +4 -2
  51. package/src/lib/design/system/comments/complete.js.map +1 -1
  52. package/src/lib/design/system/complete-command.d.ts +9 -0
  53. package/src/lib/design/system/complete-command.js +64 -0
  54. package/src/lib/design/system/complete-command.js.map +1 -0
  55. package/src/lib/design/system/complete.d.ts +73 -0
  56. package/src/lib/design/system/complete.js +655 -0
  57. package/src/lib/design/system/complete.js.map +1 -0
  58. package/src/lib/design/system/contrast-check.d.ts +26 -0
  59. package/src/lib/design/system/contrast-check.js +153 -0
  60. package/src/lib/design/system/contrast-check.js.map +1 -0
  61. package/src/lib/design/system/imports.d.ts +28 -3
  62. package/src/lib/design/system/imports.js +84 -7
  63. package/src/lib/design/system/imports.js.map +1 -1
  64. package/src/lib/design/system/list-system.d.ts +11 -0
  65. package/src/lib/design/system/list-system.js +17 -1
  66. package/src/lib/design/system/list-system.js.map +1 -1
  67. package/src/lib/design/system/product-stylesheets.d.ts +3 -0
  68. package/src/lib/design/system/product-stylesheets.js +40 -0
  69. package/src/lib/design/system/product-stylesheets.js.map +1 -0
  70. package/src/lib/design/system/render-check.js +11 -3
  71. package/src/lib/design/system/render-check.js.map +1 -1
  72. package/src/lib/design/system/rules/canonical-class.d.ts +9 -0
  73. package/src/lib/design/system/rules/canonical-class.js +132 -0
  74. package/src/lib/design/system/rules/canonical-class.js.map +1 -0
  75. package/src/lib/design/system/rules/inline-svg.d.ts +7 -0
  76. package/src/lib/design/system/rules/inline-svg.js +28 -0
  77. package/src/lib/design/system/rules/inline-svg.js.map +1 -0
  78. package/src/lib/design/system/rules/motion.d.ts +4 -0
  79. package/src/lib/design/system/rules/motion.js +46 -2
  80. package/src/lib/design/system/rules/motion.js.map +1 -1
  81. package/src/lib/design/system/rules/utility-css.d.ts +11 -0
  82. package/src/lib/design/system/rules/utility-css.js +290 -0
  83. package/src/lib/design/system/rules/utility-css.js.map +1 -0
  84. package/src/lib/design/system/sketch/baseline.d.ts +3 -2
  85. package/src/lib/design/system/sketch/baseline.js +10 -4
  86. package/src/lib/design/system/sketch/baseline.js.map +1 -1
  87. package/src/lib/design/system/sketch/merge-preview.d.ts +4 -2
  88. package/src/lib/design/system/sketch/merge-preview.js +20 -3
  89. package/src/lib/design/system/sketch/merge-preview.js.map +1 -1
  90. package/src/lib/design/system/sketch/merge-shared.d.ts +42 -6
  91. package/src/lib/design/system/sketch/merge-shared.js +122 -24
  92. package/src/lib/design/system/sketch/merge-shared.js.map +1 -1
  93. package/src/lib/design/system/sketch/merge.d.ts +3 -3
  94. package/src/lib/design/system/sketch/merge.js +54 -29
  95. package/src/lib/design/system/sketch/merge.js.map +1 -1
  96. package/src/lib/design/system/sketch/merged-parts.d.ts +7 -0
  97. package/src/lib/design/system/sketch/merged-parts.js +13 -1
  98. package/src/lib/design/system/sketch/merged-parts.js.map +1 -1
  99. package/src/lib/design/system/sketch/no-visible-change.d.ts +4 -3
  100. package/src/lib/design/system/sketch/no-visible-change.js +50 -19
  101. package/src/lib/design/system/sketch/no-visible-change.js.map +1 -1
  102. package/src/lib/design/system/sketch/overlay.js +7 -2
  103. package/src/lib/design/system/sketch/overlay.js.map +1 -1
  104. package/src/lib/design/system/sketch/part-definition.d.ts +12 -0
  105. package/src/lib/design/system/sketch/part-definition.js +40 -1
  106. package/src/lib/design/system/sketch/part-definition.js.map +1 -1
  107. package/src/lib/design/system/sketch/sketch.d.ts +8 -3
  108. package/src/lib/design/system/sketch/sketch.js +76 -34
  109. package/src/lib/design/system/sketch/sketch.js.map +1 -1
  110. package/src/lib/design/system/sketch/unchanged-states.d.ts +10 -4
  111. package/src/lib/design/system/sketch/unchanged-states.js +37 -23
  112. package/src/lib/design/system/sketch/unchanged-states.js.map +1 -1
  113. package/src/lib/design/system/system-modules.d.ts +5 -1
  114. package/src/lib/design/system/system-modules.js +26 -8
  115. package/src/lib/design/system/system-modules.js.map +1 -1
  116. package/src/lib/design/system/system-tsconfig.js +2 -1
  117. package/src/lib/design/system/system-tsconfig.js.map +1 -1
  118. package/src/lib/design/thumb.d.ts +6 -2
  119. package/src/lib/design/thumb.js +36 -15
  120. package/src/lib/design/thumb.js.map +1 -1
  121. package/src/lib/design/variant-diff.d.ts +12 -10
  122. package/src/lib/design/variant-diff.js +14 -13
  123. package/src/lib/design/variant-diff.js.map +1 -1
  124. package/src/lib/design/warm-pages.d.ts +20 -0
  125. package/src/lib/design/warm-pages.js +78 -0
  126. package/src/lib/design/warm-pages.js.map +1 -0
  127. package/src/lib/update/commits.d.ts +9 -0
  128. package/src/lib/update/commits.js +15 -1
  129. package/src/lib/update/commits.js.map +1 -1
  130. package/src/lib/update/migration-manifest.d.ts +6 -0
  131. package/src/lib/update/migration-manifest.js +7 -0
  132. package/src/lib/update/migration-manifest.js.map +1 -1
  133. package/src/lib/update/migrations-command.js +1 -0
  134. package/src/lib/update/migrations-command.js.map +1 -1
  135. package/src/lib/update/run-migrations.d.ts +22 -0
  136. package/src/lib/update/run-migrations.js +60 -1
  137. package/src/lib/update/run-migrations.js.map +1 -1
  138. package/src/lib/update/update-command.d.ts +10 -0
  139. package/src/lib/update/update-command.js +28 -2
  140. package/src/lib/update/update-command.js.map +1 -1
@@ -0,0 +1,655 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.completenessOf = exports.completenessGapLineOf = exports.completenessGapsOf = void 0;
13
+ const path_1 = require("path");
14
+ const browser_1 = require("../browser");
15
+ const force_interactions_1 = require("../force-interactions");
16
+ const serve_1 = require("../serve");
17
+ const shot_1 = require("../shot");
18
+ const thumb_1 = require("../thumb");
19
+ const resolve_product_1 = require("./resolve-product");
20
+ const baseline_1 = require("./sketch/baseline");
21
+ const merged_parts_1 = require("./sketch/merged-parts");
22
+ const system_modules_1 = require("./system-modules");
23
+ const CHECKED_KINDS = ['disclosure'];
24
+ const INPUT_INTERACTIONS = { pointer: ['hover', 'active'], keyboard: ['focus-visible'] };
25
+ const isRecord = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
26
+ const requiredInteractionsOf = (control) => control.input.flatMap((input) => { var _a; return (_a = INPUT_INTERACTIONS[input]) !== null && _a !== void 0 ? _a : []; });
27
+ const gridGapOf = (options) => {
28
+ const { grid, states, interactions } = options;
29
+ const fix = `draw its states with interactionRows, which gives the grid a column per interaction (${interactions.join(', ')})`;
30
+ if (!isRecord(grid) || !Array.isArray(grid['columns']) || !isRecord(grid['rows']))
31
+ return `the part has no grid - ${fix}`;
32
+ const columns = grid['columns'];
33
+ const missing = interactions.filter((interaction) => !columns.includes(interaction));
34
+ if (missing.length > 0)
35
+ return `its grid has no column ${missing.join(', ')} - ${fix}`;
36
+ const holes = Object.entries(grid['rows']).flatMap(([row, cells]) => interactions
37
+ .filter((interaction) => {
38
+ const state = isRecord(cells) ? cells[interaction] : undefined;
39
+ return typeof state !== 'string' || !states.includes(state);
40
+ })
41
+ .map((interaction) => `${row} ${interaction}`));
42
+ return holes.length > 0 ? `its grid draws no state in ${holes.join(', ')} - ${fix}` : null;
43
+ };
44
+ const declaredControlsOf = (value) => isRecord(value)
45
+ ? Object.fromEntries(Object.entries(value).map(([id, control]) => {
46
+ const declared = isRecord(control) ? control : {};
47
+ return [
48
+ id,
49
+ {
50
+ kind: typeof declared['kind'] === 'string' ? declared['kind'] : '',
51
+ part: typeof declared['part'] === 'string' ? declared['part'] : null,
52
+ input: Array.isArray(declared['input'])
53
+ ? declared['input'].filter((input) => typeof input === 'string')
54
+ : [],
55
+ surfaces: isRecord(declared['surfaces'])
56
+ ? Object.fromEntries(Object.entries(declared['surfaces']).filter((entry) => typeof entry[1] === 'string'))
57
+ : {},
58
+ },
59
+ ];
60
+ }))
61
+ : null;
62
+ const discoveryGapsOf = (options) => {
63
+ var _a, _b, _c, _d;
64
+ const { part, declared, frames } = options;
65
+ const ownedByPart = (found) => found.within.some((id) => { var _a, _b; return ((_b = (_a = declared[id]) === null || _a === void 0 ? void 0 : _a.part) !== null && _b !== void 0 ? _b : null) !== null; });
66
+ const groups = new Map();
67
+ for (const frame of frames.filter((seen) => seen.interaction === null)) {
68
+ const counts = new Map();
69
+ for (const found of (_a = frame.found) !== null && _a !== void 0 ? _a : []) {
70
+ if (ownedByPart(found) || (found.kind === 'control' && found.marked !== null))
71
+ continue;
72
+ const key = `${found.kind} ${found.what} ${found.path}`;
73
+ const group = (_b = groups.get(key)) !== null && _b !== void 0 ? _b : { found, states: [], count: 0 };
74
+ counts.set(key, ((_c = counts.get(key)) !== null && _c !== void 0 ? _c : 0) + 1);
75
+ if (!group.states.includes(frame.state))
76
+ group.states.push(frame.state);
77
+ group.count = Math.max(group.count, (_d = counts.get(key)) !== null && _d !== void 0 ? _d : 0);
78
+ groups.set(key, group);
79
+ }
80
+ }
81
+ return [...groups.values()]
82
+ .sort((a, b) => a.found.path.localeCompare(b.found.path) || a.found.what.localeCompare(b.found.what))
83
+ .map(({ found, states, count }) => {
84
+ const shown = states.length > 4 ? `${states.slice(0, 3).join(', ')} and ${states.length - 3} more` : states.join(', ');
85
+ const where = `${found.what} at ${found.path} in ${shown}${count > 1 ? ` (${count} of them)` : ''}`;
86
+ return found.kind === 'control'
87
+ ? {
88
+ code: 'CONTROL-UNDECLARED',
89
+ target: part,
90
+ detail: `${where} - mark it with data-et-control and declare it in controls`,
91
+ }
92
+ : {
93
+ code: 'CONTROL-UNCLASSIFIED',
94
+ target: part,
95
+ detail: `${where} - draw it as a native control or give it an interactive role and declare it, or drop the pointer cursor and :hover style`,
96
+ };
97
+ });
98
+ };
99
+ const interactionGapOf = (options) => {
100
+ var _a;
101
+ const { frames, id, interaction } = options;
102
+ const forced = frames.flatMap((frame) => frame.controls
103
+ .filter((control) => control.id === id)
104
+ .flatMap((control) => control.forced.filter((force) => force.interaction === interaction))
105
+ .map((force) => ({ state: frame.state, differs: force.differs })));
106
+ if (forced.some((force) => force.differs))
107
+ return null;
108
+ if (forced.length > 0)
109
+ return `state ${(_a = forced[0]) === null || _a === void 0 ? void 0 : _a.state} forces ${interaction} on it, but its box draws no different pixel than at rest`;
110
+ return `no state draws it ${interaction} - draw a state that forces ${interaction} on the data-et-control element, e.g. with forceInteraction`;
111
+ };
112
+ const stillGapOf = (options) => {
113
+ var _a;
114
+ const { frames, id, interaction } = options;
115
+ for (const frame of frames)
116
+ for (const control of frame.controls.filter((seen) => seen.id === id))
117
+ for (const force of control.forced.filter((seen) => { var _a; return seen.interaction === interaction && ((_a = seen.still) === null || _a === void 0 ? void 0 : _a.length); }))
118
+ return `state ${frame.state} changes ${(_a = force.still) === null || _a === void 0 ? void 0 : _a.join(', ')} in one frame - give them a transition with the product's motion tokens`;
119
+ return null;
120
+ };
121
+ const disclosureGapsOf = (options) => {
122
+ const { frames, id, control, states } = options;
123
+ const rest = frames.filter((frame) => frame.interaction === null);
124
+ const body = control.surfaces['body'];
125
+ const bodyId = `${id}.body`;
126
+ const showsBody = (frame) => frame.surfaces.some((surface) => surface.id === bodyId && surface.shown);
127
+ const drawsControl = (frame) => frame.controls.some((seen) => seen.id === id);
128
+ const gaps = [];
129
+ for (const interaction of control.part === null ? requiredInteractionsOf(control) : []) {
130
+ const gap = interactionGapOf({ frames, id, interaction });
131
+ if (gap)
132
+ gaps.push([interaction, gap]);
133
+ }
134
+ if (!rest.some((frame) => drawsControl(frame) && !showsBody(frame)))
135
+ gaps.push(['collapsed', `every state that draws it also shows its body - draw a state with ${bodyId} closed`]);
136
+ if (body === undefined)
137
+ gaps.push(['body', 'declares no surfaces.body - name the state that draws it open']);
138
+ else if (!states.includes(body))
139
+ gaps.push(['body', `surfaces.body names state ${body}, which the part does not declare`]);
140
+ else if (!rest.some((frame) => frame.state === body && drawsControl(frame) && showsBody(frame)))
141
+ gaps.push([
142
+ 'body',
143
+ `state ${body} draws no visible, non-empty data-et-surface="${bodyId}" next to data-et-control="${id}"`,
144
+ ]);
145
+ return gaps;
146
+ };
147
+ /**
148
+ * The gaps of one part, from its `controls` declaration, its state names and what each drawn frame showed.
149
+ * A part that is not `reviewed` (its files differ from its merged.json hash) has no reviewed evidence at all.
150
+ */
151
+ const completenessGapsOf = (options) => {
152
+ var _a;
153
+ const { part, states, frames, reviewed, grid, referenced = {} } = options;
154
+ const declared = (_a = declaredControlsOf(options.controls)) !== null && _a !== void 0 ? _a : {};
155
+ const gaps = [];
156
+ if (!reviewed)
157
+ gaps.push({
158
+ code: 'PART-UNREVIEWED',
159
+ target: part,
160
+ detail: 'its files differ from the hash merged.json records for it, so no frame of it is reviewed - merge or accept it',
161
+ });
162
+ const seenIds = new Set(frames.flatMap((frame) => frame.controls.map((control) => control.id)));
163
+ for (const id of [...seenIds].filter((seen) => !(seen in declared)).sort())
164
+ gaps.push({
165
+ code: 'CONTROL-UNKNOWN',
166
+ target: `${part}.${id}`,
167
+ detail: `data-et-control="${id}" is not in controls`,
168
+ });
169
+ const surfaceIds = new Set(frames.flatMap((frame) => frame.surfaces.map((surface) => surface.id)));
170
+ for (const id of [...surfaceIds].sort()) {
171
+ const [control = '', surface = ''] = id.split('.');
172
+ if (!declared[control] || declared[control].surfaces[surface] === undefined)
173
+ gaps.push({
174
+ code: 'CONTROL-UNKNOWN',
175
+ target: `${part}.${control}`,
176
+ detail: `data-et-surface="${id}" names no surface its control declares`,
177
+ });
178
+ }
179
+ for (const [id, control] of Object.entries(declared)) {
180
+ if (!CHECKED_KINDS.includes(control.kind)) {
181
+ gaps.push({
182
+ code: 'CONTROL-KIND',
183
+ target: `${part}.${id}`,
184
+ detail: `kind "${control.kind}" is not checked yet - kinds are ${CHECKED_KINDS.join(', ')}`,
185
+ });
186
+ continue;
187
+ }
188
+ if (control.part === null && part.startsWith('pages/'))
189
+ gaps.push({
190
+ code: 'CONTROL-PART-MISSING',
191
+ target: `${part}.${id}`,
192
+ detail: "a page draws no interactions of its controls - name the part that draws them, e.g. part: 'components/disclosure'",
193
+ });
194
+ const used = control.part === null ? undefined : referenced[control.part];
195
+ if (typeof used === 'string')
196
+ gaps.push({ code: 'CONTROL-PART-INCOMPLETE', target: `${part}.${id}`, detail: `${control.part}: ${used}` });
197
+ else if (used && used.length > 0)
198
+ gaps.push({
199
+ code: 'CONTROL-PART-INCOMPLETE',
200
+ target: `${part}.${id}`,
201
+ detail: `${control.part} is not design complete: ${used.length} gap(s) - run design complete ${control.part}`,
202
+ });
203
+ const interactions = control.part === null && !part.startsWith('pages/') ? requiredInteractionsOf(control) : [];
204
+ const gridGap = interactions.length > 0 ? gridGapOf({ grid, states, interactions }) : null;
205
+ if (gridGap)
206
+ gaps.push({ code: 'CONTROL-GRID-MISSING', target: `${part}.${id}`, detail: gridGap });
207
+ if (!seenIds.has(id)) {
208
+ gaps.push({ code: 'CONTROL-UNDRAWN', target: `${part}.${id}`, detail: `no state draws data-et-control="${id}"` });
209
+ continue;
210
+ }
211
+ for (const [requirement, detail] of disclosureGapsOf({ frames, id, control, states }))
212
+ gaps.push({ code: 'REQUIREMENT-MISSING', target: `${part}.${id}.${requirement}`, detail });
213
+ for (const interaction of interactions) {
214
+ const gap = stillGapOf({ frames, id, interaction });
215
+ if (gap)
216
+ gaps.push({ code: 'CONTROL-TRANSITION-MISSING', target: `${part}.${id}.${interaction}`, detail: gap });
217
+ }
218
+ }
219
+ return [...gaps, ...discoveryGapsOf({ part, declared, frames })];
220
+ };
221
+ exports.completenessGapsOf = completenessGapsOf;
222
+ const completenessGapLineOf = (gap) => `${gap.code} ${gap.target} - ${gap.detail}`;
223
+ exports.completenessGapLineOf = completenessGapLineOf;
224
+ const NO_MOTION = '*, *::before, *::after { transition: none !important; animation: none !important; }';
225
+ const PAD = 8;
226
+ const COLOUR_PROPERTIES = [
227
+ 'background-color',
228
+ 'color',
229
+ 'border-top-color',
230
+ 'border-right-color',
231
+ 'border-bottom-color',
232
+ 'border-left-color',
233
+ 'box-shadow',
234
+ ];
235
+ const COLOUR = COLOUR_PROPERTIES.indexOf('color');
236
+ const colourSnapshot = (at) => `((at) => {
237
+ const control = document.querySelector('[data-et-complete="' + at + '"]');
238
+ const motionOff = [...document.querySelectorAll('style')].find((style) => style.textContent === ${JSON.stringify(NO_MOTION)});
239
+ const elements = [control, ...control.querySelectorAll('*')];
240
+ const colours = elements.map((element) => {
241
+ const style = getComputedStyle(element);
242
+ return ${JSON.stringify(COLOUR_PROPERTIES)}.map((name) => {
243
+ const side = name.match(/^border-(\\w+)-color$/);
244
+ return side && parseFloat(style.getPropertyValue('border-' + side[1] + '-width')) === 0 ? '' : style.getPropertyValue(name);
245
+ });
246
+ });
247
+ if (motionOff) motionOff.disabled = true;
248
+ const transitions = elements.map((element) => {
249
+ const style = getComputedStyle(element);
250
+ return {
251
+ properties: style.transitionProperty.split(',').map((name) => name.trim()),
252
+ durations: style.transitionDuration.split(',').map((duration) => parseFloat(duration)),
253
+ };
254
+ });
255
+ if (motionOff) motionOff.disabled = false;
256
+ const parents = elements.map((element) => elements.indexOf(element.parentElement));
257
+ const names = elements.map((element) => element.tagName.toLowerCase() + (element.classList[0] ? '.' + element.classList[0] : ''));
258
+ return { colours, transitions, parents, names };
259
+ })(${JSON.stringify(at)})`;
260
+ const coversOf = (property) => {
261
+ if (property === 'background-color')
262
+ return ['all', 'background', property];
263
+ if (property.startsWith('border-'))
264
+ return ['all', 'border', 'border-color', property.replace(/-color$/, ''), property];
265
+ return ['all', property];
266
+ };
267
+ const stillChangesOf = (rest, forced) => forced.names.flatMap((name, element) => {
268
+ var _a;
269
+ const { properties, durations } = (_a = forced.transitions[element]) !== null && _a !== void 0 ? _a : { properties: [], durations: [] };
270
+ const still = COLOUR_PROPERTIES.filter((property, at) => {
271
+ var _a, _b, _c, _d, _e;
272
+ const [before, after] = [(_a = rest.colours[element]) !== null && _a !== void 0 ? _a : [], (_b = forced.colours[element]) !== null && _b !== void 0 ? _b : []];
273
+ if (before[at] === after[at])
274
+ return false;
275
+ const parent = (_c = forced.parents[element]) !== null && _c !== void 0 ? _c : -1;
276
+ const inherited = parent >= 0 ? [(_d = rest.colours[parent]) === null || _d === void 0 ? void 0 : _d[at], (_e = forced.colours[parent]) === null || _e === void 0 ? void 0 : _e[at]] : [];
277
+ if (property !== 'color' && before[at] === before[COLOUR] && after[at] === after[COLOUR])
278
+ return false;
279
+ if (property === 'color' && before[at] === inherited[0] && after[at] === inherited[1])
280
+ return false;
281
+ return !properties.some((covered, index) => { var _a; return coversOf(property).includes(covered) && ((_a = durations[index % durations.length]) !== null && _a !== void 0 ? _a : 0) > 0; });
282
+ });
283
+ return still.length > 0 ? [`${still.join(', ')} of ${name}`] : [];
284
+ });
285
+ const INTERACTIVE_ROLES = [
286
+ 'button',
287
+ 'checkbox',
288
+ 'combobox',
289
+ 'link',
290
+ 'menuitem',
291
+ 'menuitemcheckbox',
292
+ 'menuitemradio',
293
+ 'option',
294
+ 'radio',
295
+ 'scrollbar',
296
+ 'searchbox',
297
+ 'slider',
298
+ 'spinbutton',
299
+ 'switch',
300
+ 'tab',
301
+ 'textbox',
302
+ 'treeitem',
303
+ ];
304
+ const SDK_CONTROLS = [
305
+ '[et-button]',
306
+ '[et-icon-button]',
307
+ '[et-text-button]',
308
+ '[et-fab]',
309
+ '[et-window-control-button]',
310
+ '[et-overlay-close]',
311
+ 'et-accordion',
312
+ 'et-cascader',
313
+ 'et-checkbox',
314
+ 'et-chip',
315
+ 'et-color-input',
316
+ 'et-counter',
317
+ 'et-date-input',
318
+ 'et-date-range-input',
319
+ 'et-date-time-input',
320
+ 'et-duration-input',
321
+ 'et-form-field',
322
+ 'et-input',
323
+ 'et-menu-checkbox-item',
324
+ 'et-menu-radio-item',
325
+ 'et-number-input',
326
+ 'et-otp-input',
327
+ 'et-password-input',
328
+ 'et-phone-input',
329
+ 'et-radio',
330
+ 'et-range-slider',
331
+ 'et-rating',
332
+ 'et-segmented-button',
333
+ 'et-select',
334
+ 'et-select-option',
335
+ 'et-slider',
336
+ 'et-split-button',
337
+ 'et-switch',
338
+ 'et-tab',
339
+ 'et-tag-input',
340
+ 'et-textarea',
341
+ 'et-time-input',
342
+ 'et-toggletip',
343
+ ];
344
+ const LIKELY_CONTROLS = [
345
+ ...SDK_CONTROLS,
346
+ 'button',
347
+ 'a[href]',
348
+ 'input:not([type="hidden"])',
349
+ 'select',
350
+ 'textarea',
351
+ 'summary',
352
+ '[tabindex]',
353
+ ...INTERACTIVE_ROLES.map((role) => `[role="${role}"]`),
354
+ ];
355
+ const discoverControls = `((root) => {
356
+ const LIKELY = ${JSON.stringify(LIKELY_CONTROLS)};
357
+ const likely = LIKELY.join(', ');
358
+ const sdk = ${JSON.stringify(SDK_CONTROLS.join(', '))};
359
+ const rendered = (element) => element.getClientRects().length > 0;
360
+ const nameOf = (element) => {
361
+ const cls = [...element.classList].find((name) => /^[a-z][\\w-]*$/i.test(name));
362
+ return element.tagName.toLowerCase() + (cls ? '.' + cls : '');
363
+ };
364
+ const pathOf = (element) => {
365
+ const names = [];
366
+ for (let at = element; at && at !== root; at = at.parentElement) names.unshift(nameOf(at));
367
+ return (names.length > 4 ? '... > ' : '') + names.slice(-4).join(' > ');
368
+ };
369
+ const splitTop = (selector) => {
370
+ const parts = [];
371
+ let depth = 0;
372
+ let from = 0;
373
+ for (let at = 0; at < selector.length; at++) {
374
+ const char = selector[at];
375
+ if (char === '\\\\') at++;
376
+ else if (char === '(' || char === '[') depth++;
377
+ else if (char === ')' || char === ']') depth--;
378
+ else if (char === ',' && depth === 0) {
379
+ parts.push(selector.slice(from, at).trim());
380
+ from = at + 1;
381
+ }
382
+ }
383
+ parts.push(selector.slice(from).trim());
384
+ return parts.filter(Boolean);
385
+ };
386
+ const HOVER = /(?<!\\\\):hover(?![\\w-])/g;
387
+ const FORCED_HOVER = /:is\\(:hover,\\s*\\[data-et-force~="?hover"?\\]\\)/g;
388
+ const hoveredOf = (written) => {
389
+ const selector = written.replace(FORCED_HOVER, ':hover');
390
+ const match = new RegExp(HOVER.source).exec(selector);
391
+ if (!match) return null;
392
+ let depth = 0;
393
+ let end = selector.length;
394
+ for (let at = 0; at < selector.length; at++) {
395
+ const char = selector[at];
396
+ if (char === '\\\\') at++;
397
+ else if (char === '(' || char === '[') depth++;
398
+ else if (char === ')' || char === ']') depth--;
399
+ else if (at > match.index && depth === 0 && /[\\s>+~]/.test(char)) {
400
+ end = at;
401
+ break;
402
+ }
403
+ }
404
+ return selector.slice(0, end).replace(HOVER, '').trim() || null;
405
+ };
406
+ const hovered = new Set();
407
+ const walk = (rules, parent) => {
408
+ for (const rule of rules) {
409
+ if (rule instanceof CSSStyleRule) {
410
+ const own = splitTop(rule.selectorText).map((selector) =>
411
+ parent === null ? selector : selector.includes('&') ? selector.replaceAll('&', ':is(' + parent + ')') : ':is(' + parent + ') ' + selector,
412
+ );
413
+ for (const selector of own) {
414
+ const target = hoveredOf(selector);
415
+ if (target === null) continue;
416
+ try {
417
+ for (const element of root.querySelectorAll(target)) hovered.add(element);
418
+ } catch {}
419
+ }
420
+ if (rule.cssRules && rule.cssRules.length > 0) walk(rule.cssRules, own.join(', '));
421
+ } else if (rule.cssRules) walk(rule.cssRules, parent);
422
+ }
423
+ };
424
+ for (const sheet of document.styleSheets) {
425
+ try {
426
+ walk(sheet.cssRules, null);
427
+ } catch {}
428
+ }
429
+ const withinOf = (element) => {
430
+ const ids = [];
431
+ for (let at = element; at && at !== root; at = at.parentElement)
432
+ if (at.hasAttribute('data-et-control')) ids.push(at.getAttribute('data-et-control'));
433
+ return ids;
434
+ };
435
+ const markedOf = (element) => {
436
+ if (element.hasAttribute('data-et-control')) return element.getAttribute('data-et-control');
437
+ for (let at = element.parentElement; at && at !== root; at = at.parentElement) {
438
+ if (at.matches(likely)) return null;
439
+ if (at.hasAttribute('data-et-control')) return at.getAttribute('data-et-control');
440
+ }
441
+ return null;
442
+ };
443
+ const found = [];
444
+ for (const element of root.querySelectorAll('*')) {
445
+ if (!rendered(element)) continue;
446
+ const what = LIKELY.find((selector) => element.matches(selector));
447
+ if (what) {
448
+ const inSdk = element.parentElement.closest(sdk);
449
+ if (inSdk !== null && root.contains(inSdk)) continue;
450
+ found.push({ kind: 'control', what: what.startsWith('[') ? element.tagName.toLowerCase() + what : what, path: pathOf(element), marked: markedOf(element), within: withinOf(element) });
451
+ continue;
452
+ }
453
+ const cursor = getComputedStyle(element).cursor;
454
+ const pointer = cursor === 'pointer' && getComputedStyle(element.parentElement).cursor !== 'pointer';
455
+ if (!pointer && !hovered.has(element)) continue;
456
+ if (element.closest('[data-et-control], ' + likely) !== null) continue;
457
+ const reason = [pointer ? 'cursor: pointer' : '', hovered.has(element) ? ':hover rule' : ''].filter(Boolean).join(' and ');
458
+ found.push({ kind: 'unclassified', what: nameOf(element) + ' with ' + reason, path: pathOf(element), marked: null, within: [] });
459
+ }
460
+ return found;
461
+ })`;
462
+ const seeFrame = (discover) => `(() => {
463
+ const root = document.getElementById('root');
464
+ const refused = root.innerText.trim();
465
+ if (refused.startsWith('design-explore:')) return { refused: refused.split('\\n')[0] };
466
+ const shown = (element) => {
467
+ const box = element.getBoundingClientRect();
468
+ let { left, top, right, bottom } = box;
469
+ if (box.width <= 0 || box.height <= 0) return false;
470
+ if (['hidden', 'collapse'].includes(getComputedStyle(element).visibility)) return false;
471
+ for (let at = element; at; at = at.parentElement) {
472
+ const style = getComputedStyle(at);
473
+ if (Number(style.opacity) === 0) return false;
474
+ if (at === element) continue;
475
+ const clip = at.getBoundingClientRect();
476
+ if (style.overflowX !== 'visible') [left, right] = [Math.max(left, clip.left), Math.min(right, clip.right)];
477
+ if (style.overflowY !== 'visible') [top, bottom] = [Math.max(top, clip.top), Math.min(bottom, clip.bottom)];
478
+ if (right <= left || bottom <= top) return false;
479
+ }
480
+ return element.innerText.trim() !== '' || !!element.querySelector('img, svg, canvas, video, iframe, input, select, textarea');
481
+ };
482
+ const controls = [...root.querySelectorAll('[data-et-control]')].map((element, at) => {
483
+ element.setAttribute('data-et-complete', String(at));
484
+ const box = element.getBoundingClientRect();
485
+ const x = Math.max(0, box.x + scrollX - ${PAD});
486
+ const y = Math.max(0, box.y + scrollY - ${PAD});
487
+ return {
488
+ id: element.getAttribute('data-et-control'),
489
+ at,
490
+ forced: (element.getAttribute('data-et-force') ?? '').split(' ').filter(Boolean),
491
+ box: box.width > 0 && box.height > 0 ? { x, y, width: box.width + ${PAD * 2}, height: box.height + ${PAD * 2} } : null,
492
+ };
493
+ });
494
+ const surfaces = [...root.querySelectorAll('[data-et-surface]')].map((element) => ({
495
+ id: element.getAttribute('data-et-surface'),
496
+ shown: shown(element),
497
+ }));
498
+ return { controls, surfaces, found: ${discover} ? ${discoverControls}(root) : [] };
499
+ })()`;
500
+ const forceOn = ({ at, interaction, on, }) => `((at, interaction, on) => {
501
+ const element = document.querySelector('[data-et-complete="' + at + '"]');
502
+ const tokens = new Set((element.getAttribute('data-et-force') ?? '').split(' ').filter(Boolean));
503
+ if (on) tokens.add(interaction);
504
+ else tokens.delete(interaction);
505
+ if (tokens.size > 0) element.setAttribute('data-et-force', [...tokens].join(' '));
506
+ else element.removeAttribute('data-et-force');
507
+ })(${JSON.stringify(at)}, ${JSON.stringify(interaction)}, ${JSON.stringify(on)})`;
508
+ const FORCEABLE = new Set(['hover', 'focus-visible', 'active']);
509
+ const seeFrames = (options) => __awaiter(void 0, void 0, void 0, function* () {
510
+ const { browser, base, frames } = options;
511
+ const context = yield browser.newContext();
512
+ const seen = [];
513
+ const refused = [];
514
+ try {
515
+ const page = yield context.newPage();
516
+ for (const frame of frames) {
517
+ yield page.setViewportSize((0, shot_1.viewportOf)(frame.size));
518
+ yield (0, thumb_1.openFrame)(page, `${base}/frame.html?${frame.search}`);
519
+ yield page.addStyleTag({ content: NO_MOTION });
520
+ yield page.evaluate(thumb_1.settled);
521
+ const found = (yield page.evaluate(seeFrame(frame.interaction === null)));
522
+ if ('refused' in found) {
523
+ refused.push(`${frame.state}: ${found.refused}`);
524
+ continue;
525
+ }
526
+ const controls = [];
527
+ for (const control of found.controls) {
528
+ const forced = [];
529
+ for (const interaction of control.forced.filter((name) => FORCEABLE.has(name))) {
530
+ if (!control.box) {
531
+ forced.push({ interaction, differs: false });
532
+ continue;
533
+ }
534
+ const shot = () => { var _a; return page.screenshot({ type: 'png', fullPage: true, clip: (_a = control.box) !== null && _a !== void 0 ? _a : undefined, animations: 'disabled' }); };
535
+ const drawn = yield shot();
536
+ const drawnColours = (yield page.evaluate(colourSnapshot(control.at)));
537
+ yield page.evaluate(forceOn({ at: control.at, interaction, on: false }));
538
+ yield page.evaluate(thumb_1.settled);
539
+ const rest = yield shot();
540
+ const restColours = (yield page.evaluate(colourSnapshot(control.at)));
541
+ yield page.evaluate(forceOn({ at: control.at, interaction, on: true }));
542
+ forced.push({
543
+ interaction,
544
+ differs: !drawn.equals(rest),
545
+ still: stillChangesOf(restColours, drawnColours),
546
+ });
547
+ }
548
+ controls.push({ id: control.id, forced });
549
+ }
550
+ seen.push({
551
+ state: frame.state,
552
+ interaction: frame.interaction,
553
+ controls,
554
+ surfaces: found.surfaces,
555
+ found: found.found,
556
+ });
557
+ }
558
+ }
559
+ finally {
560
+ yield context.close();
561
+ }
562
+ return { seen, refused };
563
+ });
564
+ /**
565
+ * Draws every state of one part, at rest and in each interaction it declares, once at its first preset or width,
566
+ * and answers the gaps of its `controls` declaration, or why it could not draw. A part a control names in `part`
567
+ * is checked the same way, and its gaps fail the control.
568
+ */
569
+ const completenessOf = (options) => __awaiter(void 0, void 0, void 0, function* () {
570
+ var _a, _b, _c, _d;
571
+ const { target, config, product, part } = options;
572
+ const resolveChromium = (_a = options.chromiumOf) !== null && _a !== void 0 ? _a : browser_1.chromiumOf;
573
+ if (!product.parts.some((candidate) => (0, resolve_product_1.partKeyOf)(candidate) === part))
574
+ return { problem: `product "${product.name}" has no part "${part}"` };
575
+ if (!(yield resolveChromium(target)))
576
+ return { unavailable: 'neither the checkout nor this package resolves playwright or playwright-core' };
577
+ const queue = (0, thumb_1.createDrawQueue)({ target, chromiumOf: resolveChromium });
578
+ const server = yield (0, serve_1.createDesignServer)({ target, config, listen: true, port: 0 });
579
+ try {
580
+ yield server.listen();
581
+ const { port } = (_b = server.httpServer) === null || _b === void 0 ? void 0 : _b.address();
582
+ const system = (0, system_modules_1.createSystemModules)({ target, config });
583
+ const presets = Object.keys((_d = (_c = system.drawnOf(product.name, null)) === null || _c === void 0 ? void 0 : _c.presets) !== null && _d !== void 0 ? _d : {});
584
+ const gapsOfPart = (key, chain) => __awaiter(void 0, void 0, void 0, function* () {
585
+ var _a, _b, _c, _d, _e;
586
+ const resolved = product.parts.find((candidate) => (0, resolve_product_1.partKeyOf)(candidate) === key);
587
+ if (!resolved)
588
+ return `product "${product.name}" has no part "${key}"`;
589
+ if (chain.includes(key))
590
+ return `its controls name each other in a cycle: ${[...chain, key].join(' > ')}`;
591
+ const loaded = yield server
592
+ .ssrLoadModule((0, system_modules_1.productModuleIdOf)({ file: resolved.file, product: product.name, sketch: null }))
593
+ .then((module) => ({ module: module }))
594
+ .catch((error) => ({ error: error instanceof Error ? error.message : String(error) }));
595
+ if ('error' in loaded)
596
+ return [{ code: 'RENDER', target: key, detail: (_a = loaded.error.split('\n')[0]) !== null && _a !== void 0 ? _a : '' }];
597
+ const definition = loaded.module.default;
598
+ const states = isRecord(definition === null || definition === void 0 ? void 0 : definition.states) ? Object.keys(definition.states) : [];
599
+ const interactions = Array.isArray(definition === null || definition === void 0 ? void 0 : definition.interactions)
600
+ ? definition.interactions.filter((name) => force_interactions_1.INTERACTIONS.some((known) => known === name))
601
+ : [];
602
+ const widths = Array.isArray(definition === null || definition === void 0 ? void 0 : definition.widths)
603
+ ? definition.widths.filter((width) => typeof width === 'number')
604
+ : [];
605
+ const preset = resolved.layer === 'pages' ? ((_b = presets[0]) !== null && _b !== void 0 ? _b : null) : null;
606
+ const width = preset === null ? ((_c = widths[0]) !== null && _c !== void 0 ? _c : null) : null;
607
+ const frames = [];
608
+ for (const state of states)
609
+ for (const interaction of [null, ...interactions]) {
610
+ const frame = (0, shot_1.partFrameOf)({
611
+ system,
612
+ product: product.name,
613
+ part: key,
614
+ state,
615
+ preset,
616
+ width,
617
+ sketch: null,
618
+ interaction,
619
+ });
620
+ if (!(frame instanceof Error))
621
+ frames.push({ state, interaction, search: frame.search.toString(), size: (_d = frame.size) !== null && _d !== void 0 ? _d : null });
622
+ }
623
+ const { seen, refused } = yield queue.run((browser) => seeFrames({ browser, base: `http://localhost:${port}`, frames }));
624
+ const reviewed = (0, merged_parts_1.readMerged)(product.dir)[key] === (0, baseline_1.partHashOf)({ product, dir: (0, path_1.dirname)(resolved.file) });
625
+ const referenced = {};
626
+ for (const control of Object.values((_e = declaredControlsOf(definition === null || definition === void 0 ? void 0 : definition.controls)) !== null && _e !== void 0 ? _e : {}))
627
+ if (control.part !== null && !(control.part in referenced))
628
+ referenced[control.part] = yield gapsOfPart(control.part, [...chain, key]);
629
+ return [
630
+ ...refused.map((detail) => ({ code: 'RENDER', target: key, detail })),
631
+ ...(0, exports.completenessGapsOf)({
632
+ part: key,
633
+ controls: definition === null || definition === void 0 ? void 0 : definition.controls,
634
+ states,
635
+ frames: seen,
636
+ reviewed,
637
+ grid: definition === null || definition === void 0 ? void 0 : definition.grid,
638
+ referenced,
639
+ }),
640
+ ];
641
+ });
642
+ const gaps = yield gapsOfPart(part, []);
643
+ return typeof gaps === 'string' ? { problem: gaps } : { gaps };
644
+ }
645
+ catch (error) {
646
+ if (error instanceof thumb_1.ThumbUnavailableError)
647
+ return { unavailable: error.message };
648
+ throw error;
649
+ }
650
+ finally {
651
+ yield Promise.all([queue.close(), server.close()]);
652
+ }
653
+ });
654
+ exports.completenessOf = completenessOf;
655
+ //# sourceMappingURL=complete.js.map