@ethlete/cli 3.0.0-next.24 → 3.0.0-next.25
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.
- package/CHANGELOG.md +37 -0
- package/design/define-call.ts +43 -0
- package/design/define-part.ts +28 -8
- package/design/embed-page.ts +37 -4
- package/design/icon-sheet.ts +151 -0
- package/design/mark-part.ts +79 -2
- package/design/motion-runtime.ts +212 -5
- package/design/spring.ts +53 -0
- package/design/token-sheet.ts +87 -0
- package/design/web/frame.ts +139 -24
- package/package.json +1 -1
- package/src/lib/design/calls-tsconfig.js +1 -2
- package/src/lib/design/calls-tsconfig.js.map +1 -1
- package/src/lib/design/design-command.js +34 -4
- package/src/lib/design/design-command.js.map +1 -1
- package/src/lib/design/force-interactions.d.ts +2 -1
- package/src/lib/design/force-interactions.js +7 -4
- package/src/lib/design/force-interactions.js.map +1 -1
- package/src/lib/design/mcp-command.d.ts +60 -0
- package/src/lib/design/mcp-command.js +327 -0
- package/src/lib/design/mcp-command.js.map +1 -0
- package/src/lib/design/motion-command.d.ts +71 -0
- package/src/lib/design/motion-command.js +314 -0
- package/src/lib/design/motion-command.js.map +1 -0
- package/src/lib/design/motion-endpoint.d.ts +61 -0
- package/src/lib/design/motion-endpoint.js +133 -0
- package/src/lib/design/motion-endpoint.js.map +1 -0
- package/src/lib/design/motion-flow.d.ts +5 -0
- package/src/lib/design/motion-flow.js +29 -0
- package/src/lib/design/motion-flow.js.map +1 -0
- package/src/lib/design/motion-shot.d.ts +43 -0
- package/src/lib/design/motion-shot.js +97 -0
- package/src/lib/design/motion-shot.js.map +1 -0
- package/src/lib/design/ready-shots.d.ts +7 -0
- package/src/lib/design/ready-shots.js +22 -1
- package/src/lib/design/ready-shots.js.map +1 -1
- package/src/lib/design/render-packages.d.ts +9 -0
- package/src/lib/design/render-packages.js +13 -3
- package/src/lib/design/render-packages.js.map +1 -1
- package/src/lib/design/render.js +3 -3
- package/src/lib/design/render.js.map +1 -1
- package/src/lib/design/serve.js +34 -3
- package/src/lib/design/serve.js.map +1 -1
- package/src/lib/design/shot-command.d.ts +8 -2
- package/src/lib/design/shot-command.js +12 -8
- package/src/lib/design/shot-command.js.map +1 -1
- package/src/lib/design/shot.d.ts +16 -0
- package/src/lib/design/shot.js +68 -4
- package/src/lib/design/shot.js.map +1 -1
- package/src/lib/design/sketch-command.js +78 -16
- package/src/lib/design/sketch-command.js.map +1 -1
- package/src/lib/design/studio-api.d.ts +19 -0
- package/src/lib/design/studio-api.js +52 -1
- package/src/lib/design/studio-api.js.map +1 -1
- package/src/lib/design/system/complete-command.d.ts +1 -1
- package/src/lib/design/system/complete-command.js +76 -6
- package/src/lib/design/system/complete-command.js.map +1 -1
- package/src/lib/design/system/complete.d.ts +107 -9
- package/src/lib/design/system/complete.js +592 -141
- package/src/lib/design/system/complete.js.map +1 -1
- package/src/lib/design/system/exceptions.d.ts +59 -0
- package/src/lib/design/system/exceptions.js +132 -0
- package/src/lib/design/system/exceptions.js.map +1 -0
- package/src/lib/design/system/flows/flow.d.ts +3 -0
- package/src/lib/design/system/flows/flow.js +5 -3
- package/src/lib/design/system/flows/flow.js.map +1 -1
- package/src/lib/design/system/handoff.d.ts +11 -0
- package/src/lib/design/system/handoff.js +30 -0
- package/src/lib/design/system/handoff.js.map +1 -0
- package/src/lib/design/system/inventory-approval.d.ts +41 -0
- package/src/lib/design/system/inventory-approval.js +71 -0
- package/src/lib/design/system/inventory-approval.js.map +1 -0
- package/src/lib/design/system/inventory.d.ts +65 -0
- package/src/lib/design/system/inventory.js +168 -0
- package/src/lib/design/system/inventory.js.map +1 -0
- package/src/lib/design/system/list-system.js +9 -2
- package/src/lib/design/system/list-system.js.map +1 -1
- package/src/lib/design/system/render-check.js +19 -1
- package/src/lib/design/system/render-check.js.map +1 -1
- package/src/lib/design/system/resolve-product.d.ts +2 -3
- package/src/lib/design/system/resolve-product.js +4 -6
- package/src/lib/design/system/resolve-product.js.map +1 -1
- package/src/lib/design/system/rules/stale-replace.js +41 -2
- package/src/lib/design/system/rules/stale-replace.js.map +1 -1
- package/src/lib/design/system/rules/token-selectors.js +1 -1
- package/src/lib/design/system/rules/token-selectors.js.map +1 -1
- package/src/lib/design/system/sketch/host-edit.d.ts +4 -0
- package/src/lib/design/system/sketch/host-edit.js +68 -0
- package/src/lib/design/system/sketch/host-edit.js.map +1 -0
- package/src/lib/design/system/sketch/merge.d.ts +3 -2
- package/src/lib/design/system/sketch/merge.js +47 -12
- package/src/lib/design/system/sketch/merge.js.map +1 -1
- package/src/lib/design/system/sketch/merged-parts.d.ts +10 -0
- package/src/lib/design/system/sketch/merged-parts.js +24 -3
- package/src/lib/design/system/sketch/merged-parts.js.map +1 -1
- package/src/lib/design/system/sketch/no-visible-change.d.ts +6 -0
- package/src/lib/design/system/sketch/no-visible-change.js +25 -1
- package/src/lib/design/system/sketch/no-visible-change.js.map +1 -1
- package/src/lib/design/system/sketch/overlay.js +11 -3
- package/src/lib/design/system/sketch/overlay.js.map +1 -1
- package/src/lib/design/system/sketch/sketch.d.ts +32 -0
- package/src/lib/design/system/sketch/sketch.js +73 -18
- package/src/lib/design/system/sketch/sketch.js.map +1 -1
- package/src/lib/design/system/sketch/unused-closure.d.ts +12 -0
- package/src/lib/design/system/sketch/unused-closure.js +185 -0
- package/src/lib/design/system/sketch/unused-closure.js.map +1 -0
- package/src/lib/design/system/system-modules.js +6 -9
- package/src/lib/design/system/system-modules.js.map +1 -1
- package/src/lib/design/system/system-tsconfig.d.ts +2 -1
- package/src/lib/design/system/system-tsconfig.js +9 -5
- package/src/lib/design/system/system-tsconfig.js.map +1 -1
|
@@ -9,7 +9,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.completenessOf = exports.completenessGapLineOf = exports.completenessGapsOf = void 0;
|
|
12
|
+
exports.completenessOf = exports.inventoryIdsOf = exports.seePart = exports.completenessGapLineOf = exports.completenessGapsOf = exports.findingKeyOf = exports.isFindingOf = exports.declaredControlsOf = void 0;
|
|
13
|
+
const fs_1 = require("fs");
|
|
13
14
|
const path_1 = require("path");
|
|
14
15
|
const browser_1 = require("../browser");
|
|
15
16
|
const force_interactions_1 = require("../force-interactions");
|
|
@@ -20,10 +21,36 @@ const resolve_product_1 = require("./resolve-product");
|
|
|
20
21
|
const baseline_1 = require("./sketch/baseline");
|
|
21
22
|
const merged_parts_1 = require("./sketch/merged-parts");
|
|
22
23
|
const system_modules_1 = require("./system-modules");
|
|
23
|
-
const
|
|
24
|
+
const exceptions_1 = require("./exceptions");
|
|
25
|
+
const inventory_approval_1 = require("./inventory-approval");
|
|
26
|
+
const KINDS = {
|
|
27
|
+
disclosure: { surface: 'body', closed: 'collapsed' },
|
|
28
|
+
button: { adds: ['active'] },
|
|
29
|
+
select: { surface: 'open', options: true },
|
|
30
|
+
menu: { surface: 'open', options: true },
|
|
31
|
+
row: { selects: true, virtualFocus: true },
|
|
32
|
+
field: { noActive: true },
|
|
33
|
+
tooltip: { surface: 'tooltip' },
|
|
34
|
+
};
|
|
35
|
+
const CHECKED_KINDS = Object.keys(KINDS);
|
|
24
36
|
const INPUT_INTERACTIONS = { pointer: ['hover', 'active'], keyboard: ['focus-visible'] };
|
|
37
|
+
const MARKS = {
|
|
38
|
+
selected: 'aria-selected="true", aria-checked="true", aria-pressed="true" or aria-current',
|
|
39
|
+
disabled: 'disabled or aria-disabled="true"',
|
|
40
|
+
};
|
|
41
|
+
const CONTROL_MARKS = Object.assign(Object.assign({}, MARKS), { disabled: `${MARKS.disabled} on it or a data-et-control-part in it` });
|
|
25
42
|
const isRecord = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
26
|
-
const requiredInteractionsOf = (control) =>
|
|
43
|
+
const requiredInteractionsOf = (control) => {
|
|
44
|
+
var _a, _b;
|
|
45
|
+
return [
|
|
46
|
+
...new Set([
|
|
47
|
+
...control.input
|
|
48
|
+
.flatMap((input) => { var _a; return (_a = INPUT_INTERACTIONS[input]) !== null && _a !== void 0 ? _a : []; })
|
|
49
|
+
.filter((interaction) => { var _a; return !(((_a = KINDS[control.kind]) === null || _a === void 0 ? void 0 : _a.noActive) && interaction === 'active'); }),
|
|
50
|
+
...((_b = (_a = KINDS[control.kind]) === null || _a === void 0 ? void 0 : _a.adds) !== null && _b !== void 0 ? _b : []),
|
|
51
|
+
]),
|
|
52
|
+
];
|
|
53
|
+
};
|
|
27
54
|
const gridGapOf = (options) => {
|
|
28
55
|
const { grid, states, interactions } = options;
|
|
29
56
|
const fix = `draw its states with interactionRows, which gives the grid a column per interaction (${interactions.join(', ')})`;
|
|
@@ -55,21 +82,33 @@ const declaredControlsOf = (value) => isRecord(value)
|
|
|
55
82
|
surfaces: isRecord(declared['surfaces'])
|
|
56
83
|
? Object.fromEntries(Object.entries(declared['surfaces']).filter((entry) => typeof entry[1] === 'string'))
|
|
57
84
|
: {},
|
|
85
|
+
disabled: declared['disabled'] === true,
|
|
58
86
|
},
|
|
59
87
|
];
|
|
60
88
|
}))
|
|
61
89
|
: null;
|
|
90
|
+
exports.declaredControlsOf = declaredControlsOf;
|
|
91
|
+
/** Whether a discovered element is an undeclared or unclassified finding of the part, not owned or declared. */
|
|
92
|
+
const isFindingOf = (declared) => (found) => {
|
|
93
|
+
var _a;
|
|
94
|
+
return ((_a = found.owner) !== null && _a !== void 0 ? _a : null) === null &&
|
|
95
|
+
!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; }) &&
|
|
96
|
+
!(found.kind === 'control' && found.marked !== null);
|
|
97
|
+
};
|
|
98
|
+
exports.isFindingOf = isFindingOf;
|
|
99
|
+
const findingKeyOf = (found) => `${found.kind} ${found.what} ${found.path}`;
|
|
100
|
+
exports.findingKeyOf = findingKeyOf;
|
|
62
101
|
const discoveryGapsOf = (options) => {
|
|
63
102
|
var _a, _b, _c, _d;
|
|
64
103
|
const { part, declared, frames } = options;
|
|
65
|
-
const
|
|
104
|
+
const isFinding = (0, exports.isFindingOf)(declared);
|
|
66
105
|
const groups = new Map();
|
|
67
106
|
for (const frame of frames.filter((seen) => seen.interaction === null)) {
|
|
68
107
|
const counts = new Map();
|
|
69
108
|
for (const found of (_a = frame.found) !== null && _a !== void 0 ? _a : []) {
|
|
70
|
-
if (
|
|
109
|
+
if (!isFinding(found))
|
|
71
110
|
continue;
|
|
72
|
-
const key =
|
|
111
|
+
const key = (0, exports.findingKeyOf)(found);
|
|
73
112
|
const group = (_b = groups.get(key)) !== null && _b !== void 0 ? _b : { found, states: [], count: 0 };
|
|
74
113
|
counts.set(key, ((_c = counts.get(key)) !== null && _c !== void 0 ? _c : 0) + 1);
|
|
75
114
|
if (!group.states.includes(frame.state))
|
|
@@ -81,8 +120,7 @@ const discoveryGapsOf = (options) => {
|
|
|
81
120
|
return [...groups.values()]
|
|
82
121
|
.sort((a, b) => a.found.path.localeCompare(b.found.path) || a.found.what.localeCompare(b.found.what))
|
|
83
122
|
.map(({ found, states, count }) => {
|
|
84
|
-
const
|
|
85
|
-
const where = `${found.what} at ${found.path} in ${shown}${count > 1 ? ` (${count} of them)` : ''}`;
|
|
123
|
+
const where = `${found.what} at ${found.path} in ${statesShownOf(states)}${count > 1 ? ` (${count} of them)` : ''}`;
|
|
86
124
|
return found.kind === 'control'
|
|
87
125
|
? {
|
|
88
126
|
code: 'CONTROL-UNDECLARED',
|
|
@@ -96,6 +134,62 @@ const discoveryGapsOf = (options) => {
|
|
|
96
134
|
};
|
|
97
135
|
});
|
|
98
136
|
};
|
|
137
|
+
const statesShownOf = (states) => states.length > 4 ? `${states.slice(0, 3).join(', ')} and ${states.length - 3} more` : states.join(', ');
|
|
138
|
+
const overrideGapsOf = (options) => {
|
|
139
|
+
var _a, _b;
|
|
140
|
+
const groups = new Map();
|
|
141
|
+
for (const frame of options.frames.filter((seen) => seen.interaction === null))
|
|
142
|
+
for (const override of (_a = frame.overrides) !== null && _a !== void 0 ? _a : []) {
|
|
143
|
+
const key = `${override.owner} ${override.selector} ${override.path}`;
|
|
144
|
+
const group = (_b = groups.get(key)) !== null && _b !== void 0 ? _b : { override, states: [] };
|
|
145
|
+
if (!group.states.includes(frame.state))
|
|
146
|
+
group.states.push(frame.state);
|
|
147
|
+
groups.set(key, group);
|
|
148
|
+
}
|
|
149
|
+
return [...groups.values()]
|
|
150
|
+
.sort((a, b) => a.override.owner.localeCompare(b.override.owner) || a.override.path.localeCompare(b.override.path))
|
|
151
|
+
.map(({ override, states }) => ({
|
|
152
|
+
code: 'CONTROL-OVERRIDDEN',
|
|
153
|
+
target: options.part,
|
|
154
|
+
detail: `"${override.selector}" sets ${override.properties.join(', ')} on ${override.path}, drawn by ${override.owner}, in ${statesShownOf(states)} - change ${override.owner} instead, or mark the control with data-et-control and declare it in controls`,
|
|
155
|
+
}));
|
|
156
|
+
};
|
|
157
|
+
const ownerGapsOf = (options) => {
|
|
158
|
+
var _a, _b, _c;
|
|
159
|
+
const { part, declared, frames, referenced } = options;
|
|
160
|
+
const named = new Set(Object.values(declared).map((control) => control.part));
|
|
161
|
+
const owned = new Map();
|
|
162
|
+
for (const frame of frames.filter((seen) => seen.interaction === null))
|
|
163
|
+
for (const found of (_a = frame.found) !== null && _a !== void 0 ? _a : []) {
|
|
164
|
+
const owner = (_b = found.owner) !== null && _b !== void 0 ? _b : null;
|
|
165
|
+
if (owner === null || named.has(owner))
|
|
166
|
+
continue;
|
|
167
|
+
owned.set(owner, ((_c = owned.get(owner)) !== null && _c !== void 0 ? _c : new Set()).add(`${found.what} ${found.path}`));
|
|
168
|
+
}
|
|
169
|
+
return [...owned.entries()]
|
|
170
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
171
|
+
.flatMap(([owner, controls]) => {
|
|
172
|
+
const used = referenced[owner];
|
|
173
|
+
const drawn = `${owner} draws ${controls.size} control(s) here`;
|
|
174
|
+
if (typeof used === 'string')
|
|
175
|
+
return [{ code: 'CONTROL-PART-INCOMPLETE', target: part, detail: `${drawn}: ${used}` }];
|
|
176
|
+
if (!used || used.length === 0)
|
|
177
|
+
return [];
|
|
178
|
+
return [
|
|
179
|
+
{
|
|
180
|
+
code: 'CONTROL-PART-INCOMPLETE',
|
|
181
|
+
target: part,
|
|
182
|
+
detail: `${drawn} and is not design complete: ${used.length} gap(s) - run design complete ${owner}`,
|
|
183
|
+
},
|
|
184
|
+
];
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
const ownersOf = (frames) => [
|
|
188
|
+
...new Set(frames
|
|
189
|
+
.filter((seen) => seen.interaction === null)
|
|
190
|
+
.flatMap((seen) => { var _a; return ((_a = seen.found) !== null && _a !== void 0 ? _a : []).map((found) => { var _a; return (_a = found.owner) !== null && _a !== void 0 ? _a : null; }); })
|
|
191
|
+
.filter((owner) => owner !== null)),
|
|
192
|
+
];
|
|
99
193
|
const interactionGapOf = (options) => {
|
|
100
194
|
var _a;
|
|
101
195
|
const { frames, id, interaction } = options;
|
|
@@ -106,8 +200,20 @@ const interactionGapOf = (options) => {
|
|
|
106
200
|
if (forced.some((force) => force.differs))
|
|
107
201
|
return null;
|
|
108
202
|
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`;
|
|
203
|
+
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 - if a descendant draws ${interaction}, mark it with data-et-control-part`;
|
|
204
|
+
return `no state draws it ${interaction} - draw a state that forces ${interaction} on the data-et-control element, e.g. with forceInteraction - a data-et-control-part in it takes the force too`;
|
|
205
|
+
};
|
|
206
|
+
const keyboardActiveRowGapOf = (options) => {
|
|
207
|
+
const { instances, forcedGap } = options;
|
|
208
|
+
const active = instances.filter((seen) => seen.keyboard);
|
|
209
|
+
const plain = instances.filter((seen) => !seen.keyboard && !seen.selected && !seen.disabled);
|
|
210
|
+
if (active.length === 0)
|
|
211
|
+
return `${forcedGap}; no state marks it data-active-source="keyboard" either`;
|
|
212
|
+
if (plain.length === 0)
|
|
213
|
+
return `a state marks it data-active-source="keyboard", but no state draws it plain (not selected, active or disabled) to compare it with`;
|
|
214
|
+
if (active.some((seen) => plain.some((other) => other.look !== seen.look)))
|
|
215
|
+
return null;
|
|
216
|
+
return `it looks the same with data-active-source="keyboard" as plain - give the active look its own colours`;
|
|
111
217
|
};
|
|
112
218
|
const stillGapOf = (options) => {
|
|
113
219
|
var _a;
|
|
@@ -118,30 +224,137 @@ const stillGapOf = (options) => {
|
|
|
118
224
|
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
225
|
return null;
|
|
120
226
|
};
|
|
121
|
-
const
|
|
122
|
-
const {
|
|
227
|
+
const lookGapOf = (options) => {
|
|
228
|
+
const { seen, flag, what, marks = MARKS } = options;
|
|
229
|
+
const on = seen.filter((one) => one.flag);
|
|
230
|
+
const off = seen.filter((one) => !one.flag);
|
|
231
|
+
if (on.length === 0)
|
|
232
|
+
return `no state draws ${what} ${flag} - mark it with ${marks[flag]} in a state`;
|
|
233
|
+
if (off.length === 0)
|
|
234
|
+
return `every state draws ${what} ${flag} - draw it without ${flag} too, to compare`;
|
|
235
|
+
if (!on.some((one) => off.some((other) => one.look !== other.look)))
|
|
236
|
+
return `${what} looks the same ${flag} as without - give the ${flag} look its own colours`;
|
|
237
|
+
return null;
|
|
238
|
+
};
|
|
239
|
+
const forcedGapOf = (options) => {
|
|
240
|
+
var _a;
|
|
241
|
+
const { forced, what, interaction } = options;
|
|
242
|
+
if (forced.some((force) => force.differs))
|
|
243
|
+
return null;
|
|
244
|
+
if (forced.length > 0)
|
|
245
|
+
return `state ${(_a = forced[0]) === null || _a === void 0 ? void 0 : _a.state} forces ${interaction} on ${what}, but its box draws no different pixel than at rest`;
|
|
246
|
+
return `no state draws ${what} ${interaction} - force ${interaction} on it, e.g. with forceInteraction`;
|
|
247
|
+
};
|
|
248
|
+
const surfaceGapsOf = (options) => {
|
|
249
|
+
const { frames, id, control, states, surface, closed } = options;
|
|
123
250
|
const rest = frames.filter((frame) => frame.interaction === null);
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
const
|
|
251
|
+
const state = control.surfaces[surface];
|
|
252
|
+
const surfaceId = `${id}.${surface}`;
|
|
253
|
+
const shows = (frame) => frame.surfaces.some((seen) => seen.id === surfaceId && seen.shown);
|
|
127
254
|
const drawsControl = (frame) => frame.controls.some((seen) => seen.id === id);
|
|
128
255
|
const gaps = [];
|
|
129
|
-
|
|
130
|
-
|
|
256
|
+
if (closed && !rest.some((frame) => drawsControl(frame) && !shows(frame)))
|
|
257
|
+
gaps.push([closed, `every state that draws it also shows its ${surface} - draw a state with ${surfaceId} closed`]);
|
|
258
|
+
if (state === undefined)
|
|
259
|
+
gaps.push([surface, `declares no surfaces.${surface} - name the state that draws it open`]);
|
|
260
|
+
else if (!states.includes(state))
|
|
261
|
+
gaps.push([surface, `surfaces.${surface} names state ${state}, which the part does not declare`]);
|
|
262
|
+
else if (!rest.some((frame) => frame.state === state && drawsControl(frame) && shows(frame)))
|
|
263
|
+
gaps.push([
|
|
264
|
+
surface,
|
|
265
|
+
`state ${state} draws no visible, non-empty data-et-surface="${surfaceId}" next to data-et-control="${id}"`,
|
|
266
|
+
]);
|
|
267
|
+
return gaps;
|
|
268
|
+
};
|
|
269
|
+
const KEYBOARD_ACTIVE = 'aria-activedescendant or data-active-source="keyboard"';
|
|
270
|
+
const isKeyboardActive = (option) => !!option.named || !!option.keyboard;
|
|
271
|
+
const isPlain = (option) => !option.selected && !isKeyboardActive(option) && option.forced.length === 0;
|
|
272
|
+
const keyboardActiveGapOf = (options) => {
|
|
273
|
+
var _a, _b;
|
|
274
|
+
const { frames, shownIn, forcedGap } = options;
|
|
275
|
+
const active = frames
|
|
276
|
+
.map((frame) => ({ state: frame.state, options: shownIn(frame) }))
|
|
277
|
+
.filter((frame) => frame.options.some(isKeyboardActive));
|
|
278
|
+
const compared = active.filter((frame) => frame.options.some(isPlain));
|
|
279
|
+
if (active.length === 0)
|
|
280
|
+
return `${forcedGap}; no option is marked active by ${KEYBOARD_ACTIVE} either`;
|
|
281
|
+
if (compared.length === 0)
|
|
282
|
+
return `state ${(_a = active[0]) === null || _a === void 0 ? void 0 : _a.state} marks an option active by ${KEYBOARD_ACTIVE}, but draws no plain option (not selected, active or forced) to compare it with`;
|
|
283
|
+
if (compared.some((frame) => frame.options.some((option) => isKeyboardActive(option) && frame.options.some((plain) => isPlain(plain) && plain.look !== option.look))))
|
|
284
|
+
return null;
|
|
285
|
+
return `state ${(_b = compared[0]) === null || _b === void 0 ? void 0 : _b.state} marks an option active by ${KEYBOARD_ACTIVE}, but it looks the same as a plain option - give the active option its own look`;
|
|
286
|
+
};
|
|
287
|
+
const optionGapsOf = (options) => {
|
|
288
|
+
const { frames, surfaceId } = options;
|
|
289
|
+
const shownIn = (frame) => frame.surfaces.filter((seen) => seen.id === surfaceId && seen.shown).flatMap((seen) => { var _a; return (_a = seen.options) !== null && _a !== void 0 ? _a : []; });
|
|
290
|
+
const what = `an option in ${surfaceId}`;
|
|
291
|
+
const gaps = [];
|
|
292
|
+
for (const interaction of ['hover', 'focus-visible']) {
|
|
293
|
+
const forced = frames.flatMap((frame) => shownIn(frame).flatMap((option) => option.forced
|
|
294
|
+
.filter((force) => force.interaction === interaction)
|
|
295
|
+
.map((force) => ({ state: frame.state, differs: force.differs }))));
|
|
296
|
+
const gap = forcedGapOf({ forced, what, interaction });
|
|
297
|
+
const missing = gap && interaction === 'focus-visible' ? keyboardActiveGapOf({ frames, shownIn, forcedGap: gap }) : gap;
|
|
298
|
+
if (missing)
|
|
299
|
+
gaps.push([`option-${interaction}`, missing]);
|
|
300
|
+
}
|
|
301
|
+
const seen = frames
|
|
302
|
+
.filter((frame) => frame.interaction === null)
|
|
303
|
+
.flatMap((frame) => shownIn(frame)
|
|
304
|
+
.filter((option) => option.forced.length === 0 && !isKeyboardActive(option))
|
|
305
|
+
.map((option) => ({ look: option.look, flag: option.selected })));
|
|
306
|
+
const selected = lookGapOf({ seen, flag: 'selected', what });
|
|
307
|
+
if (selected)
|
|
308
|
+
gaps.push(['option-selected', selected]);
|
|
309
|
+
return gaps;
|
|
310
|
+
};
|
|
311
|
+
const requirementGapsOf = (options) => {
|
|
312
|
+
var _a;
|
|
313
|
+
const { frames, id, control } = options;
|
|
314
|
+
const kind = (_a = KINDS[control.kind]) !== null && _a !== void 0 ? _a : {};
|
|
315
|
+
const own = control.part === null;
|
|
316
|
+
const instances = frames
|
|
317
|
+
.filter((frame) => frame.interaction === null)
|
|
318
|
+
.flatMap((frame) => frame.controls.filter((seen) => seen.id === id && seen.forced.length === 0));
|
|
319
|
+
const gaps = [];
|
|
320
|
+
for (const interaction of own ? requiredInteractionsOf(control) : []) {
|
|
321
|
+
const forcedGap = interactionGapOf({ frames, id, interaction });
|
|
322
|
+
const gap = forcedGap && interaction === 'focus-visible' && kind.virtualFocus
|
|
323
|
+
? keyboardActiveRowGapOf({ instances, forcedGap })
|
|
324
|
+
: forcedGap;
|
|
131
325
|
if (gap)
|
|
132
326
|
gaps.push([interaction, gap]);
|
|
133
327
|
}
|
|
134
|
-
if (
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
'
|
|
143
|
-
|
|
144
|
-
|
|
328
|
+
if (own && kind.selects) {
|
|
329
|
+
const selected = lookGapOf({
|
|
330
|
+
seen: instances.map((seen) => { var _a; return ({ look: seen.look, flag: (_a = seen.selected) !== null && _a !== void 0 ? _a : false }); }),
|
|
331
|
+
flag: 'selected',
|
|
332
|
+
what: 'it',
|
|
333
|
+
});
|
|
334
|
+
const forced = frames.flatMap((frame) => frame.controls
|
|
335
|
+
.filter((seen) => seen.id === id && seen.selected)
|
|
336
|
+
.flatMap((seen) => seen.forced.filter((force) => force.interaction === 'hover'))
|
|
337
|
+
.map((force) => ({ state: frame.state, differs: force.differs })));
|
|
338
|
+
const hovered = forcedGapOf({ forced, what: 'it selected', interaction: 'hover' });
|
|
339
|
+
if (selected)
|
|
340
|
+
gaps.push(['selected', selected]);
|
|
341
|
+
if (hovered)
|
|
342
|
+
gaps.push(['selected-hover', hovered]);
|
|
343
|
+
}
|
|
344
|
+
if (own && control.disabled) {
|
|
345
|
+
const disabled = lookGapOf({
|
|
346
|
+
seen: instances.map((seen) => { var _a; return ({ look: seen.look, flag: (_a = seen.disabled) !== null && _a !== void 0 ? _a : false }); }),
|
|
347
|
+
flag: 'disabled',
|
|
348
|
+
what: 'it',
|
|
349
|
+
marks: CONTROL_MARKS,
|
|
350
|
+
});
|
|
351
|
+
if (disabled)
|
|
352
|
+
gaps.push(['disabled', disabled]);
|
|
353
|
+
}
|
|
354
|
+
if (kind.surface)
|
|
355
|
+
gaps.push(...surfaceGapsOf(Object.assign(Object.assign({}, options), { surface: kind.surface, closed: kind.closed })));
|
|
356
|
+
if (own && kind.options && kind.surface)
|
|
357
|
+
gaps.push(...optionGapsOf({ frames, surfaceId: `${id}.${kind.surface}` }));
|
|
145
358
|
return gaps;
|
|
146
359
|
};
|
|
147
360
|
/**
|
|
@@ -150,8 +363,8 @@ const disclosureGapsOf = (options) => {
|
|
|
150
363
|
*/
|
|
151
364
|
const completenessGapsOf = (options) => {
|
|
152
365
|
var _a;
|
|
153
|
-
const { part, states, frames, reviewed, grid, referenced = {} } = options;
|
|
154
|
-
const declared = (_a = declaredControlsOf(options.controls)) !== null && _a !== void 0 ? _a : {};
|
|
366
|
+
const { part, states, frames, reviewed, grid, referenced = {}, inventory } = options;
|
|
367
|
+
const declared = (_a = (0, exports.declaredControlsOf)(options.controls)) !== null && _a !== void 0 ? _a : {};
|
|
155
368
|
const gaps = [];
|
|
156
369
|
if (!reviewed)
|
|
157
370
|
gaps.push({
|
|
@@ -208,7 +421,7 @@ const completenessGapsOf = (options) => {
|
|
|
208
421
|
gaps.push({ code: 'CONTROL-UNDRAWN', target: `${part}.${id}`, detail: `no state draws data-et-control="${id}"` });
|
|
209
422
|
continue;
|
|
210
423
|
}
|
|
211
|
-
for (const [requirement, detail] of
|
|
424
|
+
for (const [requirement, detail] of requirementGapsOf({ frames, id, control, states }))
|
|
212
425
|
gaps.push({ code: 'REQUIREMENT-MISSING', target: `${part}.${id}.${requirement}`, detail });
|
|
213
426
|
for (const interaction of interactions) {
|
|
214
427
|
const gap = stillGapOf({ frames, id, interaction });
|
|
@@ -216,9 +429,21 @@ const completenessGapsOf = (options) => {
|
|
|
216
429
|
gaps.push({ code: 'CONTROL-TRANSITION-MISSING', target: `${part}.${id}.${interaction}`, detail: gap });
|
|
217
430
|
}
|
|
218
431
|
}
|
|
219
|
-
return [
|
|
432
|
+
return [
|
|
433
|
+
...gaps,
|
|
434
|
+
...discoveryGapsOf({ part, declared, frames }),
|
|
435
|
+
...ownerGapsOf({ part, declared, frames, referenced }),
|
|
436
|
+
...overrideGapsOf({ part, frames }),
|
|
437
|
+
...inventoryGapsOf({ part, inventory }),
|
|
438
|
+
];
|
|
220
439
|
};
|
|
221
440
|
exports.completenessGapsOf = completenessGapsOf;
|
|
441
|
+
const inventoryGapsOf = ({ part, inventory }) => {
|
|
442
|
+
const reason = inventory ? (0, inventory_approval_1.inventoryReasonOf)(inventory) : null;
|
|
443
|
+
return reason === null
|
|
444
|
+
? []
|
|
445
|
+
: [{ code: 'INVENTORY-UNAPPROVED', target: part, detail: `${reason} - check and approve it in Studio` }];
|
|
446
|
+
};
|
|
222
447
|
const completenessGapLineOf = (gap) => `${gap.code} ${gap.target} - ${gap.detail}`;
|
|
223
448
|
exports.completenessGapLineOf = completenessGapLineOf;
|
|
224
449
|
const NO_MOTION = '*, *::before, *::after { transition: none !important; animation: none !important; }';
|
|
@@ -352,37 +577,13 @@ const LIKELY_CONTROLS = [
|
|
|
352
577
|
'[tabindex]',
|
|
353
578
|
...INTERACTIVE_ROLES.map((role) => `[role="${role}"]`),
|
|
354
579
|
];
|
|
580
|
+
const LOOK_PROPERTIES = [...COLOUR_PROPERTIES, 'opacity', 'box-shadow', 'font-weight', 'text-decoration-line'];
|
|
581
|
+
const OPTION_SELECTOR = '[role="option"], [role^="menuitem"], [data-et-option]';
|
|
355
582
|
const discoverControls = `((root) => {
|
|
356
583
|
const LIKELY = ${JSON.stringify(LIKELY_CONTROLS)};
|
|
357
584
|
const likely = LIKELY.join(', ');
|
|
358
585
|
const sdk = ${JSON.stringify(SDK_CONTROLS.join(', '))};
|
|
359
586
|
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
587
|
const HOVER = /(?<!\\\\):hover(?![\\w-])/g;
|
|
387
588
|
const FORCED_HOVER = /:is\\(:hover,\\s*\\[data-et-force~="?hover"?\\]\\)/g;
|
|
388
589
|
const hoveredOf = (written) => {
|
|
@@ -408,7 +609,7 @@ const discoverControls = `((root) => {
|
|
|
408
609
|
for (const rule of rules) {
|
|
409
610
|
if (rule instanceof CSSStyleRule) {
|
|
410
611
|
const own = splitTop(rule.selectorText).map((selector) =>
|
|
411
|
-
|
|
612
|
+
nested(selector, parent),
|
|
412
613
|
);
|
|
413
614
|
for (const selector of own) {
|
|
414
615
|
const target = hoveredOf(selector);
|
|
@@ -426,17 +627,22 @@ const discoverControls = `((root) => {
|
|
|
426
627
|
walk(sheet.cssRules, null);
|
|
427
628
|
} catch {}
|
|
428
629
|
}
|
|
630
|
+
const markerOf = (at, element) =>
|
|
631
|
+
at.getAttribute('data-et-control') ?? (element.matches(${JSON.stringify(OPTION_SELECTOR)}) ? at.getAttribute('data-et-surface')?.split('.')[0] : null) ?? null;
|
|
429
632
|
const withinOf = (element) => {
|
|
430
633
|
const ids = [];
|
|
431
|
-
for (let at = element; at && at !== root; at = at.parentElement)
|
|
432
|
-
|
|
634
|
+
for (let at = element; at && at !== root; at = at.parentElement) {
|
|
635
|
+
const id = markerOf(at, element);
|
|
636
|
+
if (id !== null) ids.push(id);
|
|
637
|
+
}
|
|
433
638
|
return ids;
|
|
434
639
|
};
|
|
435
640
|
const markedOf = (element) => {
|
|
436
641
|
if (element.hasAttribute('data-et-control')) return element.getAttribute('data-et-control');
|
|
437
642
|
for (let at = element.parentElement; at && at !== root; at = at.parentElement) {
|
|
438
643
|
if (at.matches(likely)) return null;
|
|
439
|
-
|
|
644
|
+
const id = markerOf(at, element);
|
|
645
|
+
if (id !== null) return id;
|
|
440
646
|
}
|
|
441
647
|
return null;
|
|
442
648
|
};
|
|
@@ -447,7 +653,7 @@ const discoverControls = `((root) => {
|
|
|
447
653
|
if (what) {
|
|
448
654
|
const inSdk = element.parentElement.closest(sdk);
|
|
449
655
|
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) });
|
|
656
|
+
found.push({ kind: 'control', what: what.startsWith('[') ? element.tagName.toLowerCase() + what : what, path: pathOf(element), marked: markedOf(element), within: withinOf(element), owner: ownerOf(element)?.key ?? null, box: frameBoxOf(element) });
|
|
451
657
|
continue;
|
|
452
658
|
}
|
|
453
659
|
const cursor = getComputedStyle(element).cursor;
|
|
@@ -455,14 +661,130 @@ const discoverControls = `((root) => {
|
|
|
455
661
|
if (!pointer && !hovered.has(element)) continue;
|
|
456
662
|
if (element.closest('[data-et-control], ' + likely) !== null) continue;
|
|
457
663
|
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: [] });
|
|
664
|
+
found.push({ kind: 'unclassified', what: nameOf(element) + ' with ' + reason, path: pathOf(element), marked: null, within: [], owner: ownerOf(element)?.key ?? null, box: frameBoxOf(element) });
|
|
459
665
|
}
|
|
460
666
|
return found;
|
|
461
667
|
})`;
|
|
462
|
-
const
|
|
668
|
+
const ROOT_LAYOUT = [
|
|
669
|
+
'^margin',
|
|
670
|
+
'^inset',
|
|
671
|
+
'^(top|right|bottom|left)$',
|
|
672
|
+
'^(min-|max-)?(width|height|inline-size|block-size)$',
|
|
673
|
+
'^position$',
|
|
674
|
+
'^z-index$',
|
|
675
|
+
'^flex(-grow|-shrink|-basis)?$',
|
|
676
|
+
'^grid-(area|row|column)',
|
|
677
|
+
'^(align|justify|place)-self$',
|
|
678
|
+
'^order$',
|
|
679
|
+
'^(transform|translate|rotate|scale)$',
|
|
680
|
+
];
|
|
681
|
+
const findOverrides = `((ownCss) => {
|
|
682
|
+
const LAYOUT = new RegExp(${JSON.stringify(ROOT_LAYOUT.join('|'))});
|
|
683
|
+
const USER_ACTION = /(?<!\\\\):(hover|focus-visible|focus-within|focus|active)(?![\\w-])/g;
|
|
684
|
+
const PSEUDO_ELEMENT = /::?(before|after|marker|placeholder|selection|backdrop|file-selector-button|first-line|first-letter)(?![\\w-])/g;
|
|
685
|
+
const declaredByHost = (element) => {
|
|
686
|
+
for (let at = element; at && at !== root.parentElement; at = at.parentElement)
|
|
687
|
+
if (at.hasAttribute('data-et-control') && ownerOf(at) === null) return true;
|
|
688
|
+
return false;
|
|
689
|
+
};
|
|
690
|
+
const overrides = [];
|
|
691
|
+
const seen = new Set();
|
|
692
|
+
const check = (selector, style) => {
|
|
693
|
+
const properties = [...new Set(style.cssText.split(';').map((declaration) => declaration.split(':')[0].trim()).filter(Boolean))];
|
|
694
|
+
if (properties.length === 0) return;
|
|
695
|
+
let matched = [];
|
|
696
|
+
try {
|
|
697
|
+
matched = [...root.querySelectorAll(selector.replace(PSEUDO_ELEMENT, '').replace(USER_ACTION, ':where(*)'))];
|
|
698
|
+
} catch {
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
for (const element of matched) {
|
|
702
|
+
const owner = ownerOf(element);
|
|
703
|
+
if (owner === null || declaredByHost(element)) continue;
|
|
704
|
+
const look = owner.at === element ? properties.filter((property) => !LAYOUT.test(property)) : properties;
|
|
705
|
+
const key = selector + ' ' + pathOf(element);
|
|
706
|
+
if (look.length === 0 || seen.has(key)) continue;
|
|
707
|
+
seen.add(key);
|
|
708
|
+
overrides.push({ owner: owner.key, selector, path: pathOf(element), properties: look });
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
const walk = (rules, parent) => {
|
|
712
|
+
for (const rule of rules) {
|
|
713
|
+
if (rule instanceof CSSStyleRule) {
|
|
714
|
+
const own = splitTop(rule.selectorText).map((selector) => nested(selector, parent));
|
|
715
|
+
for (const selector of own) check(selector, rule.style);
|
|
716
|
+
if (rule.cssRules && rule.cssRules.length > 0) walk(rule.cssRules, own.join(', '));
|
|
717
|
+
} else if (parent !== null && rule.constructor.name === 'CSSNestedDeclarations') check(parent, rule.style);
|
|
718
|
+
else if (rule.cssRules) walk(rule.cssRules, parent);
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
for (const text of ownCss) {
|
|
722
|
+
const sheet = new CSSStyleSheet();
|
|
723
|
+
try {
|
|
724
|
+
sheet.replaceSync(text);
|
|
725
|
+
} catch {
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
walk(sheet.cssRules, null);
|
|
729
|
+
}
|
|
730
|
+
return overrides;
|
|
731
|
+
})`;
|
|
732
|
+
/** A control root and every `data-et-control-part` whose nearest `data-et-control` is that root, in the page. */
|
|
733
|
+
const controlPartsOf = `((control) => [
|
|
734
|
+
control,
|
|
735
|
+
...[...control.querySelectorAll('[data-et-control-part]')].filter((part) => part.parentElement.closest('[data-et-control]') === control),
|
|
736
|
+
])`;
|
|
737
|
+
const seeFrame = (options) => `(() => {
|
|
463
738
|
const root = document.getElementById('root');
|
|
464
739
|
const refused = root.innerText.trim();
|
|
465
740
|
if (refused.startsWith('design-explore:')) return { refused: refused.split('\\n')[0] };
|
|
741
|
+
const ORIGIN = ${options.clip} ? root.getBoundingClientRect() : { left: -scrollX, top: -scrollY };
|
|
742
|
+
const frameBoxOf = (element) => {
|
|
743
|
+
const box = element.getBoundingClientRect();
|
|
744
|
+
return box.width > 0 && box.height > 0
|
|
745
|
+
? { x: Math.round(box.left - ORIGIN.left), y: Math.round(box.top - ORIGIN.top), width: Math.round(box.width), height: Math.round(box.height) }
|
|
746
|
+
: null;
|
|
747
|
+
};
|
|
748
|
+
const OWN = ${JSON.stringify(options.part)};
|
|
749
|
+
const tokensOf = (element, name) => (element.getAttribute(name) ?? '').split(' ').filter(Boolean);
|
|
750
|
+
const ownerOf = (element) => {
|
|
751
|
+
const slotted = tokensOf(element, 'data-et-slot');
|
|
752
|
+
for (let at = element; at; at = at.parentElement)
|
|
753
|
+
for (const key of tokensOf(at, 'data-et-part')) {
|
|
754
|
+
const index = slotted.indexOf(key);
|
|
755
|
+
if (index >= 0) slotted.splice(index, 1);
|
|
756
|
+
else return key === OWN ? null : { key, at };
|
|
757
|
+
}
|
|
758
|
+
return null;
|
|
759
|
+
};
|
|
760
|
+
const nameOf = (element) => {
|
|
761
|
+
const cls = [...element.classList].find((name) => /^[a-z][\\w-]*$/i.test(name));
|
|
762
|
+
return element.tagName.toLowerCase() + (cls ? '.' + cls : '');
|
|
763
|
+
};
|
|
764
|
+
const pathOf = (element) => {
|
|
765
|
+
const names = [];
|
|
766
|
+
for (let at = element; at && at !== root; at = at.parentElement) names.unshift(nameOf(at));
|
|
767
|
+
return (names.length > 4 ? '... > ' : '') + names.slice(-4).join(' > ');
|
|
768
|
+
};
|
|
769
|
+
const splitTop = (selector) => {
|
|
770
|
+
const parts = [];
|
|
771
|
+
let depth = 0;
|
|
772
|
+
let from = 0;
|
|
773
|
+
for (let at = 0; at < selector.length; at++) {
|
|
774
|
+
const char = selector[at];
|
|
775
|
+
if (char === '\\\\') at++;
|
|
776
|
+
else if (char === '(' || char === '[') depth++;
|
|
777
|
+
else if (char === ')' || char === ']') depth--;
|
|
778
|
+
else if (char === ',' && depth === 0) {
|
|
779
|
+
parts.push(selector.slice(from, at).trim());
|
|
780
|
+
from = at + 1;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
parts.push(selector.slice(from).trim());
|
|
784
|
+
return parts.filter(Boolean);
|
|
785
|
+
};
|
|
786
|
+
const nested = (selector, parent) =>
|
|
787
|
+
parent === null ? selector : selector.includes('&') ? selector.replaceAll('&', ':is(' + parent + ')') : ':is(' + parent + ') ' + selector;
|
|
466
788
|
const shown = (element) => {
|
|
467
789
|
const box = element.getBoundingClientRect();
|
|
468
790
|
let { left, top, right, bottom } = box;
|
|
@@ -479,35 +801,75 @@ const seeFrame = (discover) => `(() => {
|
|
|
479
801
|
}
|
|
480
802
|
return element.innerText.trim() !== '' || !!element.querySelector('img, svg, canvas, video, iframe, input, select, textarea');
|
|
481
803
|
};
|
|
482
|
-
const
|
|
804
|
+
const LOOK = ${JSON.stringify(LOOK_PROPERTIES)};
|
|
805
|
+
const lookOf = (element) => {
|
|
806
|
+
const own = LOOK.map((name) => getComputedStyle(element).getPropertyValue(name));
|
|
807
|
+
const inside = [...element.querySelectorAll('*')].map((child) => {
|
|
808
|
+
const style = getComputedStyle(child);
|
|
809
|
+
return LOOK.map((name) => style.getPropertyValue(name)).join(' ');
|
|
810
|
+
});
|
|
811
|
+
return JSON.stringify([own, [...new Set(inside)].sort()]);
|
|
812
|
+
};
|
|
813
|
+
const named = new Set([...root.querySelectorAll('[aria-activedescendant]')].filter((element) => ownerOf(element) === null).map((element) => element.getAttribute('aria-activedescendant')));
|
|
814
|
+
const isSelected = (element) =>
|
|
815
|
+
['aria-selected', 'aria-checked', 'aria-pressed'].some((name) => element.getAttribute(name) === 'true') ||
|
|
816
|
+
(element.hasAttribute('aria-current') && element.getAttribute('aria-current') !== 'false');
|
|
817
|
+
const isDisabled = (element) =>
|
|
818
|
+
element.matches(':disabled') || element.getAttribute('aria-disabled') === 'true' || tokensOf(element, 'data-et-force').includes('disabled');
|
|
819
|
+
const isKeyboard = (element) => element.getAttribute('data-active-source') === 'keyboard';
|
|
820
|
+
let next = 0;
|
|
821
|
+
const tagged = (element) => {
|
|
822
|
+
const at = next++;
|
|
483
823
|
element.setAttribute('data-et-complete', String(at));
|
|
484
824
|
const box = element.getBoundingClientRect();
|
|
485
825
|
const x = Math.max(0, box.x + scrollX - ${PAD});
|
|
486
826
|
const y = Math.max(0, box.y + scrollY - ${PAD});
|
|
487
827
|
return {
|
|
488
|
-
id: element.getAttribute('data-et-control'),
|
|
489
828
|
at,
|
|
490
|
-
forced: (element
|
|
829
|
+
forced: tokensOf(element, 'data-et-force'),
|
|
491
830
|
box: box.width > 0 && box.height > 0 ? { x, y, width: box.width + ${PAD * 2}, height: box.height + ${PAD * 2} } : null,
|
|
831
|
+
selected: isSelected(element),
|
|
832
|
+
look: lookOf(element),
|
|
833
|
+
};
|
|
834
|
+
};
|
|
835
|
+
const controls = [...root.querySelectorAll('[data-et-control]')].filter((element) => ownerOf(element) === null).map((element) => {
|
|
836
|
+
const parts = ${controlPartsOf}(element);
|
|
837
|
+
const forced = new Set(parts.flatMap((part) => tokensOf(part, 'data-et-force')));
|
|
838
|
+
for (const part of parts) if (forced.size > 0) part.setAttribute('data-et-force', [...forced].join(' '));
|
|
839
|
+
return {
|
|
840
|
+
id: element.getAttribute('data-et-control'),
|
|
841
|
+
disabled: parts.some(isDisabled),
|
|
842
|
+
keyboard: parts.some(isKeyboard),
|
|
843
|
+
frameBox: frameBoxOf(element),
|
|
844
|
+
...tagged(element),
|
|
492
845
|
};
|
|
493
846
|
});
|
|
494
|
-
const surfaces = [...root.querySelectorAll('[data-et-surface]')].map((element) => ({
|
|
847
|
+
const surfaces = [...root.querySelectorAll('[data-et-surface]')].filter((element) => ownerOf(element) === null).map((element) => ({
|
|
495
848
|
id: element.getAttribute('data-et-surface'),
|
|
496
849
|
shown: shown(element),
|
|
850
|
+
options: [...element.querySelectorAll(${JSON.stringify(OPTION_SELECTOR)})].filter((option) => ownerOf(option) === null && shown(option)).map((option) => ({ ...tagged(option), named: option.id !== '' && named.has(option.id), keyboard: isKeyboard(option) })),
|
|
497
851
|
}));
|
|
498
|
-
|
|
852
|
+
const discover = ${options.discover};
|
|
853
|
+
return {
|
|
854
|
+
controls,
|
|
855
|
+
surfaces,
|
|
856
|
+
found: discover ? ${discoverControls}(root) : [],
|
|
857
|
+
overrides: discover ? ${findOverrides}(${JSON.stringify(options.ownCss)}) : [],
|
|
858
|
+
};
|
|
499
859
|
})()`;
|
|
500
860
|
const forceOn = ({ at, interaction, on, }) => `((at, interaction, on) => {
|
|
501
|
-
const
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
861
|
+
const found = document.querySelector('[data-et-complete="' + at + '"]');
|
|
862
|
+
for (const element of found.hasAttribute('data-et-control') ? ${controlPartsOf}(found) : [found]) {
|
|
863
|
+
const tokens = new Set((element.getAttribute('data-et-force') ?? '').split(' ').filter(Boolean));
|
|
864
|
+
if (on) tokens.add(interaction);
|
|
865
|
+
else tokens.delete(interaction);
|
|
866
|
+
if (tokens.size > 0) element.setAttribute('data-et-force', [...tokens].join(' '));
|
|
867
|
+
else element.removeAttribute('data-et-force');
|
|
868
|
+
}
|
|
507
869
|
})(${JSON.stringify(at)}, ${JSON.stringify(interaction)}, ${JSON.stringify(on)})`;
|
|
508
870
|
const FORCEABLE = new Set(['hover', 'focus-visible', 'active']);
|
|
509
871
|
const seeFrames = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
510
|
-
const { browser, base, frames } = options;
|
|
872
|
+
const { browser, base, frames, part, ownCss } = options;
|
|
511
873
|
const context = yield browser.newContext();
|
|
512
874
|
const seen = [];
|
|
513
875
|
const refused = [];
|
|
@@ -518,41 +880,51 @@ const seeFrames = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
518
880
|
yield (0, thumb_1.openFrame)(page, `${base}/frame.html?${frame.search}`);
|
|
519
881
|
yield page.addStyleTag({ content: NO_MOTION });
|
|
520
882
|
yield page.evaluate(thumb_1.settled);
|
|
521
|
-
const found = (yield page.evaluate(seeFrame(frame.interaction === null)));
|
|
883
|
+
const found = (yield page.evaluate(seeFrame({ discover: frame.interaction === null, part, ownCss, clip: frame.clip })));
|
|
522
884
|
if ('refused' in found) {
|
|
523
885
|
refused.push(`${frame.state}: ${found.refused}`);
|
|
524
886
|
continue;
|
|
525
887
|
}
|
|
526
|
-
const
|
|
527
|
-
for (const control of found.controls) {
|
|
888
|
+
const forcedOf = (element) => __awaiter(void 0, void 0, void 0, function* () {
|
|
528
889
|
const forced = [];
|
|
529
|
-
for (const interaction of
|
|
530
|
-
if (!
|
|
890
|
+
for (const interaction of element.forced.filter((name) => FORCEABLE.has(name))) {
|
|
891
|
+
if (!element.box) {
|
|
531
892
|
forced.push({ interaction, differs: false });
|
|
532
893
|
continue;
|
|
533
894
|
}
|
|
534
|
-
const shot = () => { var _a; return page.screenshot({ type: 'png', fullPage: true, clip: (_a =
|
|
895
|
+
const shot = () => { var _a; return page.screenshot({ type: 'png', fullPage: true, clip: (_a = element.box) !== null && _a !== void 0 ? _a : undefined, animations: 'disabled' }); };
|
|
535
896
|
const drawn = yield shot();
|
|
536
|
-
const drawnColours = (yield page.evaluate(colourSnapshot(
|
|
537
|
-
yield page.evaluate(forceOn({ at:
|
|
897
|
+
const drawnColours = (yield page.evaluate(colourSnapshot(element.at)));
|
|
898
|
+
yield page.evaluate(forceOn({ at: element.at, interaction, on: false }));
|
|
538
899
|
yield page.evaluate(thumb_1.settled);
|
|
539
900
|
const rest = yield shot();
|
|
540
|
-
const restColours = (yield page.evaluate(colourSnapshot(
|
|
541
|
-
yield page.evaluate(forceOn({ at:
|
|
901
|
+
const restColours = (yield page.evaluate(colourSnapshot(element.at)));
|
|
902
|
+
yield page.evaluate(forceOn({ at: element.at, interaction, on: true }));
|
|
542
903
|
forced.push({
|
|
543
904
|
interaction,
|
|
544
905
|
differs: !drawn.equals(rest),
|
|
545
906
|
still: stillChangesOf(restColours, drawnColours),
|
|
546
907
|
});
|
|
547
908
|
}
|
|
548
|
-
|
|
909
|
+
return forced;
|
|
910
|
+
});
|
|
911
|
+
const controls = [];
|
|
912
|
+
const surfaces = [];
|
|
913
|
+
for (const control of found.controls)
|
|
914
|
+
controls.push(Object.assign(Object.assign({ id: control.id, forced: yield forcedOf(control), selected: control.selected, disabled: control.disabled, look: control.look }, (control.keyboard ? { keyboard: true } : {})), { box: control.frameBox }));
|
|
915
|
+
for (const surface of found.surfaces) {
|
|
916
|
+
const options = [];
|
|
917
|
+
for (const option of surface.options)
|
|
918
|
+
options.push(Object.assign(Object.assign({ selected: option.selected, look: option.look, forced: yield forcedOf(option) }, (option.named ? { named: true } : {})), (option.keyboard ? { keyboard: true } : {})));
|
|
919
|
+
surfaces.push({ id: surface.id, shown: surface.shown, options });
|
|
549
920
|
}
|
|
550
921
|
seen.push({
|
|
551
922
|
state: frame.state,
|
|
552
923
|
interaction: frame.interaction,
|
|
553
924
|
controls,
|
|
554
|
-
surfaces
|
|
925
|
+
surfaces,
|
|
555
926
|
found: found.found,
|
|
927
|
+
overrides: found.overrides,
|
|
556
928
|
});
|
|
557
929
|
}
|
|
558
930
|
}
|
|
@@ -561,74 +933,146 @@ const seeFrames = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
561
933
|
}
|
|
562
934
|
return { seen, refused };
|
|
563
935
|
});
|
|
936
|
+
/**
|
|
937
|
+
* Draws every state of one part of the product, at rest and, unless `restOnly`, in each interaction it declares,
|
|
938
|
+
* once at its first preset or width, and answers what each frame showed, or why its module did not load as a string.
|
|
939
|
+
*/
|
|
940
|
+
const seePart = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
941
|
+
var _a, _b, _c, _d, _e;
|
|
942
|
+
const { product, part, system, base, load, draw, restOnly = false, sketch = null } = options;
|
|
943
|
+
const key = (0, resolve_product_1.partKeyOf)(part);
|
|
944
|
+
const loaded = yield load((0, system_modules_1.productModuleIdOf)({ file: part.file, product: product.name, sketch }))
|
|
945
|
+
.then((module) => ({ module: module }))
|
|
946
|
+
.catch((error) => ({ error: error instanceof Error ? error.message : String(error) }));
|
|
947
|
+
if ('error' in loaded)
|
|
948
|
+
return loaded.error;
|
|
949
|
+
const definition = loaded.module.default;
|
|
950
|
+
const states = isRecord(definition === null || definition === void 0 ? void 0 : definition.states) ? Object.keys(definition.states) : [];
|
|
951
|
+
const interactions = Array.isArray(definition === null || definition === void 0 ? void 0 : definition.interactions)
|
|
952
|
+
? definition.interactions.filter((name) => force_interactions_1.INTERACTIONS.some((known) => known === name))
|
|
953
|
+
: [];
|
|
954
|
+
const widths = Array.isArray(definition === null || definition === void 0 ? void 0 : definition.widths)
|
|
955
|
+
? definition.widths.filter((width) => typeof width === 'number')
|
|
956
|
+
: [];
|
|
957
|
+
const presets = Object.keys((_b = (_a = system.drawnOf(product.name, null)) === null || _a === void 0 ? void 0 : _a.presets) !== null && _b !== void 0 ? _b : {});
|
|
958
|
+
const preset = part.layer === 'pages' ? ((_c = presets[0]) !== null && _c !== void 0 ? _c : null) : null;
|
|
959
|
+
const width = preset === null ? ((_d = widths[0]) !== null && _d !== void 0 ? _d : null) : null;
|
|
960
|
+
const frames = [];
|
|
961
|
+
for (const state of states)
|
|
962
|
+
for (const interaction of [null, ...(restOnly ? [] : interactions)]) {
|
|
963
|
+
const frame = (0, shot_1.partFrameOf)({
|
|
964
|
+
system,
|
|
965
|
+
product: product.name,
|
|
966
|
+
part: key,
|
|
967
|
+
state,
|
|
968
|
+
preset,
|
|
969
|
+
width,
|
|
970
|
+
sketch,
|
|
971
|
+
interaction,
|
|
972
|
+
});
|
|
973
|
+
if (!(frame instanceof Error))
|
|
974
|
+
frames.push({
|
|
975
|
+
state,
|
|
976
|
+
interaction,
|
|
977
|
+
search: frame.search.toString(),
|
|
978
|
+
size: (_e = frame.size) !== null && _e !== void 0 ? _e : null,
|
|
979
|
+
clip: preset === null,
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
const dir = (0, path_1.dirname)(part.file);
|
|
983
|
+
const ownCss = (0, fs_1.readdirSync)(dir)
|
|
984
|
+
.filter((file) => file.endsWith('.css'))
|
|
985
|
+
.sort()
|
|
986
|
+
.map((file) => (0, fs_1.readFileSync)((0, path_1.join)(dir, file), 'utf8'));
|
|
987
|
+
const { seen, refused } = yield draw((browser) => seeFrames({ browser, base, frames, part: key, ownCss }));
|
|
988
|
+
const owner = (0, inventory_approval_1.partOwnerOf)({ product, part });
|
|
989
|
+
return {
|
|
990
|
+
definition,
|
|
991
|
+
states,
|
|
992
|
+
seen,
|
|
993
|
+
refused,
|
|
994
|
+
addresses: states.map((state) => ({ state, preset, width })),
|
|
995
|
+
owner,
|
|
996
|
+
hash: (0, baseline_1.partHashOf)({ product: owner, dir }),
|
|
997
|
+
};
|
|
998
|
+
});
|
|
999
|
+
exports.seePart = seePart;
|
|
1000
|
+
/** The control ids a page draws at rest, the set its inventory approval holds. */
|
|
1001
|
+
const inventoryIdsOf = (frames) => [
|
|
1002
|
+
...new Set(frames.filter((frame) => frame.interaction === null).flatMap((frame) => frame.controls.map(({ id }) => id))),
|
|
1003
|
+
].sort();
|
|
1004
|
+
exports.inventoryIdsOf = inventoryIdsOf;
|
|
564
1005
|
/**
|
|
565
1006
|
* 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
|
|
1007
|
+
* and answers the gaps of its `controls` declaration, or why it could not draw. A part a control names in `part`,
|
|
1008
|
+
* and an embedded part whose `data-et-part` root draws a control, is checked the same way, and its gaps fail it.
|
|
1009
|
+
* A page fails until the user approves its control inventory in Studio. An exception the user approved in Studio
|
|
1010
|
+
* drops the gap it names until the part changes; a pending one still fails. With `sketch`, every part is drawn as
|
|
1011
|
+
* that variant draws it, and a part the variant changes is not merged yet, so it never fails as unreviewed.
|
|
568
1012
|
*/
|
|
569
1013
|
const completenessOf = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
570
1014
|
var _a, _b, _c, _d;
|
|
571
|
-
const { target, config, product, part } = options;
|
|
1015
|
+
const { target, config, product, part, sketch = null } = options;
|
|
572
1016
|
const resolveChromium = (_a = options.chromiumOf) !== null && _a !== void 0 ? _a : browser_1.chromiumOf;
|
|
573
|
-
|
|
574
|
-
|
|
1017
|
+
const system = (0, system_modules_1.createSystemModules)({ target, config });
|
|
1018
|
+
const parts = (_c = (_b = (sketch ? system.drawnOf(product.name, sketch) : product)) === null || _b === void 0 ? void 0 : _b.parts) !== null && _c !== void 0 ? _c : [];
|
|
1019
|
+
if (!parts.some((candidate) => (0, resolve_product_1.partKeyOf)(candidate) === part))
|
|
1020
|
+
return {
|
|
1021
|
+
problem: sketch
|
|
1022
|
+
? `variant "${sketch.variant}" of the sketch on ${sketch.target} in product "${product.name}" has no part "${part}"`
|
|
1023
|
+
: `product "${product.name}" has no part "${part}"`,
|
|
1024
|
+
};
|
|
575
1025
|
if (!(yield resolveChromium(target)))
|
|
576
1026
|
return { unavailable: 'neither the checkout nor this package resolves playwright or playwright-core' };
|
|
577
1027
|
const queue = (0, thumb_1.createDrawQueue)({ target, chromiumOf: resolveChromium });
|
|
578
1028
|
const server = yield (0, serve_1.createDesignServer)({ target, config, listen: true, port: 0 });
|
|
579
1029
|
try {
|
|
580
1030
|
yield server.listen();
|
|
581
|
-
const { port } = (
|
|
582
|
-
const
|
|
583
|
-
const
|
|
1031
|
+
const { port } = (_d = server.httpServer) === null || _d === void 0 ? void 0 : _d.address();
|
|
1032
|
+
const checked = new Map();
|
|
1033
|
+
const exceptions = [];
|
|
584
1034
|
const gapsOfPart = (key, chain) => __awaiter(void 0, void 0, void 0, function* () {
|
|
585
|
-
|
|
586
|
-
|
|
1035
|
+
const known = checked.get(key);
|
|
1036
|
+
if (known !== undefined && !chain.includes(key))
|
|
1037
|
+
return known;
|
|
1038
|
+
const gaps = yield freshGapsOfPart(key, chain);
|
|
1039
|
+
checked.set(key, gaps);
|
|
1040
|
+
return gaps;
|
|
1041
|
+
});
|
|
1042
|
+
const freshGapsOfPart = (key, chain) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1043
|
+
var _a, _b, _c;
|
|
1044
|
+
const resolved = parts.find((candidate) => (0, resolve_product_1.partKeyOf)(candidate) === key);
|
|
587
1045
|
if (!resolved)
|
|
588
1046
|
return `product "${product.name}" has no part "${key}"`;
|
|
589
1047
|
if (chain.includes(key))
|
|
590
1048
|
return `its controls name each other in a cycle: ${[...chain, key].join(' > ')}`;
|
|
591
|
-
const
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
: [];
|
|
602
|
-
const
|
|
603
|
-
|
|
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) });
|
|
1049
|
+
const sight = yield (0, exports.seePart)({
|
|
1050
|
+
product,
|
|
1051
|
+
part: resolved,
|
|
1052
|
+
system,
|
|
1053
|
+
base: `http://localhost:${port}`,
|
|
1054
|
+
load: (id) => server.ssrLoadModule(id),
|
|
1055
|
+
draw: (job) => queue.run(job),
|
|
1056
|
+
sketch,
|
|
1057
|
+
});
|
|
1058
|
+
if (typeof sight === 'string')
|
|
1059
|
+
return [{ code: 'RENDER', target: key, detail: (_a = sight.split('\n')[0]) !== null && _a !== void 0 ? _a : '' }];
|
|
1060
|
+
const { definition, states, seen, refused, owner, hash } = sight;
|
|
1061
|
+
const sketched = ((_b = product.parts.find((candidate) => (0, resolve_product_1.partKeyOf)(candidate) === key)) === null || _b === void 0 ? void 0 : _b.file) !== resolved.file;
|
|
1062
|
+
const reviewed = sketched || (0, merged_parts_1.readMerged)(owner.dir)[key] === hash;
|
|
625
1063
|
const referenced = {};
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
1064
|
+
const used = [
|
|
1065
|
+
...Object.values((_c = (0, exports.declaredControlsOf)(definition === null || definition === void 0 ? void 0 : definition.controls)) !== null && _c !== void 0 ? _c : {}).map((control) => control.part),
|
|
1066
|
+
...ownersOf(seen),
|
|
1067
|
+
];
|
|
1068
|
+
for (const other of used)
|
|
1069
|
+
if (other !== null && !(other in referenced))
|
|
1070
|
+
referenced[other] = yield gapsOfPart(other, [...chain, key]);
|
|
1071
|
+
const excepted = (0, exceptions_1.withExceptions)({
|
|
1072
|
+
part: key,
|
|
1073
|
+
exceptions: (0, exceptions_1.readExceptions)(owner.dir),
|
|
1074
|
+
hash,
|
|
1075
|
+
gaps: (0, exports.completenessGapsOf)({
|
|
632
1076
|
part: key,
|
|
633
1077
|
controls: definition === null || definition === void 0 ? void 0 : definition.controls,
|
|
634
1078
|
states,
|
|
@@ -636,11 +1080,18 @@ const completenessOf = (options) => __awaiter(void 0, void 0, void 0, function*
|
|
|
636
1080
|
reviewed,
|
|
637
1081
|
grid: definition === null || definition === void 0 ? void 0 : definition.grid,
|
|
638
1082
|
referenced,
|
|
1083
|
+
inventory: resolved.layer === 'pages'
|
|
1084
|
+
? (0, inventory_approval_1.inventoryStateOf)({ approval: (0, inventory_approval_1.readInventory)(owner.dir)[key], ids: (0, exports.inventoryIdsOf)(seen), hash })
|
|
1085
|
+
: undefined,
|
|
639
1086
|
}),
|
|
640
|
-
|
|
1087
|
+
});
|
|
1088
|
+
exceptions.push(...excepted.listed);
|
|
1089
|
+
return [...refused.map((detail) => ({ code: 'RENDER', target: key, detail })), ...excepted.gaps];
|
|
641
1090
|
});
|
|
642
1091
|
const gaps = yield gapsOfPart(part, []);
|
|
643
|
-
|
|
1092
|
+
if (typeof gaps === 'string')
|
|
1093
|
+
return { problem: gaps };
|
|
1094
|
+
return exceptions.length > 0 ? { gaps, exceptions } : { gaps };
|
|
644
1095
|
}
|
|
645
1096
|
catch (error) {
|
|
646
1097
|
if (error instanceof thumb_1.ThumbUnavailableError)
|