@parity-app/cli 0.6.3 → 0.7.1
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/dist/bin.mjs +12 -8
- package/dist/{client-C4An81o9.mjs → client-D5kbiMmn.mjs} +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{schema-CiVy9tZT.mjs → schema-DcQLoUPP.mjs} +13 -1
- package/dist/{src-B40wy5fK.mjs → src-B5sEtTw_.mjs} +1 -1
- package/dist/{src-DzU4U2rh.mjs → src-C6QQltHZ.mjs} +1 -1
- package/dist/{src-C5-HaJcA.mjs → src-DM51TWZ6.mjs} +3 -3
- package/dist/{src-YQY8r4mh.mjs → src-IWA2JHPw.mjs} +557 -710
- package/dist/src-_5ETJOb0.mjs +4 -0
- package/package.json +1 -1
- package/skills/SKILL.md +16 -16
- package/dist/src-AKjejfTT.mjs +0 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import "./schema-
|
|
1
|
+
import { a as parseData, n as finiteNumber, o as plainObject, t as fileObject } from "./schema-DcQLoUPP.mjs";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
// ../core/src/adapters.ts
|
|
4
4
|
const packageVersions = z.record(z.string(), z.string());
|
|
@@ -55,6 +55,11 @@ function lengthToPixels(value) {
|
|
|
55
55
|
*/
|
|
56
56
|
const viewportWidthProperty = "--canvas-vw";
|
|
57
57
|
const viewportHeightProperty = "--canvas-vh";
|
|
58
|
+
/**
|
|
59
|
+
* The custom property `rem` is rewritten to: in a frame, the project's root font size, not Parity's. A frame sets it
|
|
60
|
+
* from `rootFontSizeProperty`, a length registered so that `1em` on the frame's root becomes that root's font size.
|
|
61
|
+
*/
|
|
62
|
+
const remProperty = "--canvas-rem";
|
|
58
63
|
/** Every custom property declaration in a stylesheet, in source order. */
|
|
59
64
|
function collectCustomProperties(css) {
|
|
60
65
|
const declarations = [];
|
|
@@ -151,42 +156,7 @@ function findStringEnd(css, start) {
|
|
|
151
156
|
else index++;
|
|
152
157
|
return css.length;
|
|
153
158
|
}
|
|
154
|
-
new RegExp(String.raw`"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|url\([^)]*\)|var\((?:${viewportWidthProperty}|${viewportHeightProperty}),[^)]*\)`, "g");
|
|
155
|
-
// ../core/src/validate.ts
|
|
156
|
-
function expectObject(value, path) {
|
|
157
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError(`${path || "/"}: expected an object`);
|
|
158
|
-
return value;
|
|
159
|
-
}
|
|
160
|
-
function expectString(value, path) {
|
|
161
|
-
if (typeof value !== "string") throw new TypeError(`${path}: expected a string`);
|
|
162
|
-
return value;
|
|
163
|
-
}
|
|
164
|
-
function expectNumber(value, path) {
|
|
165
|
-
if (typeof value !== "number" || !Number.isFinite(value)) throw new TypeError(`${path}: expected a number`);
|
|
166
|
-
return value;
|
|
167
|
-
}
|
|
168
|
-
function expectBoolean(value, path) {
|
|
169
|
-
if (typeof value !== "boolean") throw new TypeError(`${path}: expected true or false`);
|
|
170
|
-
return value;
|
|
171
|
-
}
|
|
172
|
-
function expectArray(value, path) {
|
|
173
|
-
if (!Array.isArray(value)) throw new TypeError(`${path}: expected an array`);
|
|
174
|
-
return value;
|
|
175
|
-
}
|
|
176
|
-
function expectOneOf(value, options, path) {
|
|
177
|
-
if (typeof value !== "string" || !options.includes(value)) throw new TypeError(`${path}: expected one of ${options.map((option) => `"${option}"`).join(", ")}`);
|
|
178
|
-
return value;
|
|
179
|
-
}
|
|
180
|
-
function optional(value, parse) {
|
|
181
|
-
return value === void 0 ? void 0 : parse(value);
|
|
182
|
-
}
|
|
183
|
-
/** Drops `undefined` entries, so parsed patches can be spread over defaults without erasing them. */
|
|
184
|
-
function compact(value) {
|
|
185
|
-
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
|
|
186
|
-
}
|
|
187
|
-
function expectKnownKeys(value, keys, path) {
|
|
188
|
-
for (const key of Object.keys(value)) if (!keys.includes(key)) throw new TypeError(`${path}/${key}: unknown property`);
|
|
189
|
-
}
|
|
159
|
+
new RegExp(String.raw`"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|url\([^)]*\)|var\((?:${viewportWidthProperty}|${viewportHeightProperty}|${remProperty}),[^)]*\)`, "g");
|
|
190
160
|
// ../core/src/modes.ts
|
|
191
161
|
const variableKinds = [
|
|
192
162
|
"viewport",
|
|
@@ -194,87 +164,69 @@ const variableKinds = [
|
|
|
194
164
|
"locale",
|
|
195
165
|
"custom"
|
|
196
166
|
];
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
167
|
+
/** What choosing this option does to a rendered frame. */
|
|
168
|
+
const variableOption = fileObject({
|
|
169
|
+
id: z.string(),
|
|
170
|
+
name: z.string(),
|
|
171
|
+
/** Frame width in CSS pixels (viewport options). */
|
|
172
|
+
width: finiteNumber.optional(),
|
|
173
|
+
/** The screen's height, for viewports taken from a device. Frames still grow with their content. */
|
|
174
|
+
height: finiteNumber.optional(),
|
|
175
|
+
/** What the viewport is, for its icon. Otherwise judged by its width. */
|
|
176
|
+
device: z.enum(deviceKinds).optional(),
|
|
177
|
+
/** Class added to the frame body, e.g. `dark` for shadcn themes. */
|
|
178
|
+
className: z.string().optional(),
|
|
179
|
+
/** Attributes set on the frame body, e.g. `lang` and `dir` for locales. */
|
|
180
|
+
attributes: z.record(z.string(), z.string()).optional()
|
|
181
|
+
});
|
|
182
|
+
const modeVariable = fileObject({
|
|
183
|
+
id: z.string(),
|
|
184
|
+
name: z.string(),
|
|
185
|
+
kind: z.enum(variableKinds),
|
|
186
|
+
options: z.array(variableOption).min(1, "expected at least one option")
|
|
187
|
+
});
|
|
188
|
+
const modeSpec = fileObject({
|
|
189
|
+
id: z.string(),
|
|
190
|
+
name: z.string(),
|
|
191
|
+
/** Option id per variable id. A variable that isn't listed uses its first option. */
|
|
192
|
+
values: z.record(z.string(), z.string())
|
|
193
|
+
});
|
|
194
|
+
/**
|
|
195
|
+
* Project-wide modes, like Chromatic modes in Storybook. A variable is one axis of the environment (viewport, theme,
|
|
196
|
+
* locale…); a mode is a named combination of one option per variable. Every design can show any mode, and each design
|
|
197
|
+
* chooses which ones it shows. The first mode is the primary one: the main frame of every design.
|
|
198
|
+
*/
|
|
199
|
+
const modeSettings = fileObject({
|
|
200
|
+
$schema: z.string().optional(),
|
|
201
|
+
version: z.literal(1),
|
|
202
|
+
variables: z.array(modeVariable),
|
|
203
|
+
modes: z.array(modeSpec).min(1, "expected at least one mode")
|
|
204
|
+
}).superRefine((settings, context) => {
|
|
205
|
+
for (const [index, mode] of settings.modes.entries()) for (const [variableId, optionId] of Object.entries(mode.values)) {
|
|
206
|
+
const path = [
|
|
207
|
+
"modes",
|
|
208
|
+
index,
|
|
209
|
+
"values",
|
|
210
|
+
variableId
|
|
211
|
+
];
|
|
212
|
+
const variable = settings.variables.find((candidate) => candidate.id === variableId);
|
|
213
|
+
if (!variable) context.addIssue({
|
|
214
|
+
code: "custom",
|
|
215
|
+
message: "unknown variable",
|
|
216
|
+
path,
|
|
217
|
+
input: variableId
|
|
240
218
|
});
|
|
241
|
-
if (options.
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const modes = expectArray(settings.modes, "/modes").map((item, index) => {
|
|
250
|
-
const at = `/modes/${index}`;
|
|
251
|
-
const mode = expectObject(item, at);
|
|
252
|
-
expectKnownKeys(mode, [
|
|
253
|
-
"id",
|
|
254
|
-
"name",
|
|
255
|
-
"values"
|
|
256
|
-
], at);
|
|
257
|
-
const values = {};
|
|
258
|
-
for (const [variableId, optionId] of Object.entries(expectObject(mode.values, `${at}/values`))) {
|
|
259
|
-
const variable = variables.find((candidate) => candidate.id === variableId);
|
|
260
|
-
if (!variable) throw new TypeError(`${at}/values/${variableId}: unknown variable`);
|
|
261
|
-
const option = expectString(optionId, `${at}/values/${variableId}`);
|
|
262
|
-
if (!variable.options.some((candidate) => candidate.id === option)) throw new TypeError(`${at}/values/${variableId}: "${option}" is not an option of ${variable.name}`);
|
|
263
|
-
values[variableId] = option;
|
|
219
|
+
else if (!variable.options.some((option) => option.id === optionId)) {
|
|
220
|
+
const message = `"${optionId}" is not an option of ${variable.name}`;
|
|
221
|
+
context.addIssue({
|
|
222
|
+
code: "custom",
|
|
223
|
+
message,
|
|
224
|
+
path,
|
|
225
|
+
input: optionId
|
|
226
|
+
});
|
|
264
227
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
name: expectString(mode.name, `${at}/name`),
|
|
268
|
-
values
|
|
269
|
-
};
|
|
270
|
-
});
|
|
271
|
-
if (modes.length === 0) throw new TypeError("/modes: expected at least one mode");
|
|
272
|
-
return {
|
|
273
|
-
version: 1,
|
|
274
|
-
variables,
|
|
275
|
-
modes
|
|
276
|
-
};
|
|
277
|
-
}
|
|
228
|
+
}
|
|
229
|
+
});
|
|
278
230
|
// ../core/src/edit.ts
|
|
279
231
|
const idAlphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
280
232
|
/** A short random id that isn't in `taken`, for nodes and designs created in the editor or by agents. */
|
|
@@ -286,16 +238,45 @@ function generateId(taken, length = 5) {
|
|
|
286
238
|
}
|
|
287
239
|
}
|
|
288
240
|
// ../core/src/document.ts
|
|
241
|
+
const position = fileObject({
|
|
242
|
+
x: finiteNumber,
|
|
243
|
+
y: finiteNumber
|
|
244
|
+
});
|
|
289
245
|
const canvasObjectKinds = [
|
|
290
246
|
"table",
|
|
291
247
|
"operation",
|
|
292
248
|
"route"
|
|
293
249
|
];
|
|
250
|
+
const canvasObjectSpec = fileObject({
|
|
251
|
+
kind: z.enum(canvasObjectKinds),
|
|
252
|
+
/** A table as `schema.table`, an operation by its id, a route by its URL pattern. */
|
|
253
|
+
ref: z.string(),
|
|
254
|
+
position
|
|
255
|
+
});
|
|
256
|
+
/** The canvas objects, each table, operation and route at most once. */
|
|
257
|
+
const canvasObjects = z.array(canvasObjectSpec).superRefine((objects, context) => {
|
|
258
|
+
const placed = /* @__PURE__ */ new Set();
|
|
259
|
+
for (const [index, { kind, ref }] of objects.entries()) {
|
|
260
|
+
const key = JSON.stringify([kind, ref]);
|
|
261
|
+
if (placed.has(key)) problem(context, [index], `the ${kind} "${ref}" is already on the canvas`);
|
|
262
|
+
placed.add(key);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
294
265
|
const linkEndKinds = [
|
|
295
266
|
"screen",
|
|
296
267
|
"component",
|
|
297
268
|
...canvasObjectKinds
|
|
298
269
|
];
|
|
270
|
+
const linkEnd = fileObject({
|
|
271
|
+
kind: z.enum(linkEndKinds),
|
|
272
|
+
ref: z.string()
|
|
273
|
+
});
|
|
274
|
+
const linkType = z.enum([
|
|
275
|
+
"shows",
|
|
276
|
+
"calls",
|
|
277
|
+
"reads",
|
|
278
|
+
"writes"
|
|
279
|
+
]);
|
|
299
280
|
/** Each type of link, and the kinds it may join, from and to. */
|
|
300
281
|
const linkTypes = {
|
|
301
282
|
/** A screen shown at a route's URL. */
|
|
@@ -328,30 +309,301 @@ function linkAllowed(type, from, to) {
|
|
|
328
309
|
const allowed = linkTypes[type];
|
|
329
310
|
return allowed.from.includes(from) && allowed.to.includes(to);
|
|
330
311
|
}
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
312
|
+
const linkSpec = fileObject({
|
|
313
|
+
id: z.string(),
|
|
314
|
+
type: linkType,
|
|
315
|
+
from: linkEnd,
|
|
316
|
+
to: linkEnd
|
|
317
|
+
}).superRefine((link, context) => {
|
|
318
|
+
if (linkAllowed(link.type, link.from.kind, link.to.kind)) return;
|
|
319
|
+
const { from, to } = linkTypes[link.type];
|
|
320
|
+
const joined = `${from.join(" or ")} to ${to.join(" or ")}, not ${link.from.kind} to ${link.to.kind}`;
|
|
321
|
+
problem(context, [], `a ${link.type} link goes from ${joined}`);
|
|
322
|
+
});
|
|
323
|
+
/** The links the person drew, each id once. */
|
|
324
|
+
const linkSpecs = z.array(linkSpec).superRefine((links, context) => {
|
|
325
|
+
const ids = /* @__PURE__ */ new Set();
|
|
326
|
+
for (const [index, { id }] of links.entries()) {
|
|
327
|
+
if (ids.has(id)) problem(context, [index, "id"], `duplicate link id "${id}"`);
|
|
328
|
+
ids.add(id);
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
const lensSources = fileObject({
|
|
332
|
+
data: z.string().optional(),
|
|
333
|
+
api: z.string().optional(),
|
|
334
|
+
routes: z.string().optional()
|
|
335
|
+
});
|
|
336
|
+
const componentRef = fileObject({
|
|
337
|
+
path: z.string(),
|
|
338
|
+
export: z.string()
|
|
339
|
+
});
|
|
340
|
+
const detectionCorrections = fileObject({
|
|
341
|
+
include: z.array(componentRef).optional(),
|
|
342
|
+
exclude: z.unknown().optional()
|
|
343
|
+
}).transform(({ exclude: _dropped, ...corrections }) => corrections);
|
|
344
|
+
const noteSpec = fileObject({
|
|
345
|
+
id: z.string(),
|
|
346
|
+
text: z.string(),
|
|
347
|
+
position,
|
|
348
|
+
/** Wraps at this width; unset notes grow with their text. */
|
|
349
|
+
width: finiteNumber.optional()
|
|
350
|
+
});
|
|
351
|
+
/** Steps of the spacing scale (`4` is `calc(var(--spacing) * 4)`), or the name of a length token. */
|
|
352
|
+
const length = z.union([finiteNumber, z.string()]);
|
|
353
|
+
const sides = "expected a length, [vertical, horizontal] or [top, right, bottom, left]";
|
|
354
|
+
const padding = z.union([
|
|
355
|
+
z.tuple([length, length], sides),
|
|
356
|
+
z.tuple([
|
|
357
|
+
length,
|
|
358
|
+
length,
|
|
359
|
+
length,
|
|
360
|
+
length
|
|
361
|
+
], sides),
|
|
362
|
+
length
|
|
363
|
+
]);
|
|
364
|
+
const alignment = z.enum([
|
|
365
|
+
"start",
|
|
366
|
+
"center",
|
|
367
|
+
"end",
|
|
368
|
+
"stretch"
|
|
369
|
+
]);
|
|
370
|
+
const nodeStyle = fileObject({
|
|
371
|
+
/** Auto layout (flex, the default) or a grid. */
|
|
372
|
+
display: z.enum(["flex", "grid"]).optional(),
|
|
373
|
+
direction: z.enum(["row", "column"]).optional(),
|
|
374
|
+
/** A grid's columns, all the same width; or rows, all the same height. */
|
|
375
|
+
columns: finiteNumber.optional(),
|
|
376
|
+
rows: finiteNumber.optional(),
|
|
377
|
+
gap: length.optional(),
|
|
378
|
+
/** In a grid or wrapping layout, the gap between rows when it differs from `gap`. */
|
|
379
|
+
rowGap: length.optional(),
|
|
380
|
+
/** All sides, `[vertical, horizontal]`, or `[top, right, bottom, left]`. */
|
|
381
|
+
padding: padding.optional(),
|
|
382
|
+
/** Space outside the node, in the same forms as padding. */
|
|
383
|
+
margin: padding.optional(),
|
|
384
|
+
/** Children on the cross axis. */
|
|
385
|
+
align: alignment.optional(),
|
|
386
|
+
/** Children on the main axis. */
|
|
387
|
+
justify: z.enum([
|
|
388
|
+
"start",
|
|
389
|
+
"center",
|
|
390
|
+
"end",
|
|
391
|
+
"between",
|
|
392
|
+
"around",
|
|
393
|
+
"evenly"
|
|
394
|
+
]).optional(),
|
|
395
|
+
wrap: z.boolean().optional(),
|
|
396
|
+
width: length.optional(),
|
|
397
|
+
height: length.optional(),
|
|
398
|
+
minWidth: length.optional(),
|
|
399
|
+
maxWidth: length.optional(),
|
|
400
|
+
minHeight: length.optional(),
|
|
401
|
+
maxHeight: length.optional(),
|
|
402
|
+
/** Grid cells the node spans, in a grid. */
|
|
403
|
+
colSpan: finiteNumber.optional(),
|
|
404
|
+
rowSpan: finiteNumber.optional(),
|
|
405
|
+
/** Fill along the parent's direction. Same as `fill` for the parent's main axis. */
|
|
406
|
+
grow: z.boolean().optional(),
|
|
407
|
+
/** Where this node sits on its parent's cross axis, overriding the parent's `align`. */
|
|
408
|
+
self: alignment.optional(),
|
|
409
|
+
position: z.enum(["absolute"]).optional(),
|
|
410
|
+
top: length.optional(),
|
|
411
|
+
right: length.optional(),
|
|
412
|
+
bottom: length.optional(),
|
|
413
|
+
left: length.optional(),
|
|
414
|
+
background: z.string().optional(),
|
|
415
|
+
foreground: z.string().optional(),
|
|
416
|
+
/** Border color. */
|
|
417
|
+
border: z.string().optional(),
|
|
418
|
+
/** Border width in pixels, as in Tailwind: 0, 1, 2, 4 or 8. */
|
|
419
|
+
borderWidth: finiteNumber.optional(),
|
|
420
|
+
borderStyle: z.enum([
|
|
421
|
+
"solid",
|
|
422
|
+
"dashed",
|
|
423
|
+
"dotted"
|
|
424
|
+
]).optional(),
|
|
425
|
+
radius: z.string().optional(),
|
|
426
|
+
shadow: z.string().optional(),
|
|
427
|
+
/** Percent, 0 to 100. */
|
|
428
|
+
opacity: finiteNumber.optional(),
|
|
429
|
+
overflow: z.enum([
|
|
430
|
+
"visible",
|
|
431
|
+
"hidden",
|
|
432
|
+
"scroll"
|
|
433
|
+
]).optional(),
|
|
434
|
+
font: z.string().optional(),
|
|
435
|
+
size: z.string().optional(),
|
|
436
|
+
weight: finiteNumber.optional(),
|
|
437
|
+
leading: z.string().optional(),
|
|
438
|
+
tracking: z.string().optional(),
|
|
439
|
+
textAlign: z.enum([
|
|
440
|
+
"start",
|
|
441
|
+
"center",
|
|
442
|
+
"end",
|
|
443
|
+
"justify"
|
|
444
|
+
]).optional(),
|
|
445
|
+
transform: z.enum([
|
|
446
|
+
"uppercase",
|
|
447
|
+
"lowercase",
|
|
448
|
+
"capitalize"
|
|
449
|
+
]).optional(),
|
|
450
|
+
decoration: z.enum(["underline", "line-through"]).optional(),
|
|
451
|
+
italic: z.boolean().optional(),
|
|
452
|
+
/** Lines of text before it's cut off with an ellipsis. */
|
|
453
|
+
lines: finiteNumber.optional()
|
|
454
|
+
});
|
|
455
|
+
const styleKey = nodeStyle.keyof();
|
|
456
|
+
styleKey.options;
|
|
457
|
+
const patchUnset = fileObject({
|
|
458
|
+
props: z.array(z.string()).optional(),
|
|
459
|
+
style: z.array(styleKey).optional()
|
|
460
|
+
});
|
|
461
|
+
const nodePatch = fileObject({
|
|
462
|
+
hidden: z.boolean().optional(),
|
|
463
|
+
text: z.string().optional(),
|
|
464
|
+
props: plainObject.optional(),
|
|
465
|
+
style: nodeStyle.optional(),
|
|
466
|
+
/** What the patch takes away from what's under it, so a state can turn off a prop or style the design sets. */
|
|
467
|
+
unset: patchUnset.optional()
|
|
468
|
+
});
|
|
469
|
+
/** Patches keyed by node id, or by `instance/inner` for a node inside an instance. */
|
|
470
|
+
const nodePatches = z.record(z.string(), nodePatch);
|
|
471
|
+
const nodeBinding = fileObject({
|
|
472
|
+
text: z.string().optional(),
|
|
473
|
+
props: z.record(z.string(), z.string()).optional(),
|
|
474
|
+
visible: z.string().optional()
|
|
475
|
+
});
|
|
476
|
+
const nodeBase = fileObject({
|
|
477
|
+
id: z.string(),
|
|
478
|
+
name: z.string().optional(),
|
|
479
|
+
hidden: z.boolean().optional(),
|
|
480
|
+
style: nodeStyle.optional(),
|
|
481
|
+
/**
|
|
482
|
+
* Patches applied in one viewport, keyed by the viewport option id from the project's modes. Layout changes per
|
|
483
|
+
* viewport live here, not in copies.
|
|
484
|
+
*/
|
|
485
|
+
responsive: nodePatches.optional(),
|
|
486
|
+
/**
|
|
487
|
+
* Inside a component design: which of this node's values come from the component's params, so each instance can
|
|
488
|
+
* show its own. `text` and `props` name a param; `visible` names a boolean param that shows or hides the node.
|
|
489
|
+
*/
|
|
490
|
+
bind: nodeBinding.optional()
|
|
491
|
+
});
|
|
492
|
+
const layoutNode = nodeBase.extend({
|
|
493
|
+
type: z.literal("layout"),
|
|
494
|
+
get children() {
|
|
495
|
+
return z.array(designNode);
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
const componentNode$1 = nodeBase.extend({
|
|
499
|
+
type: z.literal("component"),
|
|
500
|
+
component: z.string(),
|
|
501
|
+
props: plainObject.optional(),
|
|
502
|
+
/** Child nodes by slot. `children` is the default slot; other names become node-valued props. */
|
|
503
|
+
get slots() {
|
|
504
|
+
return z.record(z.string(), z.array(designNode)).optional();
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
const textNode = nodeBase.extend({
|
|
508
|
+
type: z.literal("text"),
|
|
509
|
+
text: z.string(),
|
|
510
|
+
tag: z.enum([
|
|
511
|
+
"p",
|
|
512
|
+
"span",
|
|
513
|
+
"h1",
|
|
514
|
+
"h2",
|
|
515
|
+
"h3",
|
|
516
|
+
"h4",
|
|
517
|
+
"strong"
|
|
518
|
+
]).optional()
|
|
519
|
+
});
|
|
520
|
+
const instanceNode = nodeBase.extend({
|
|
521
|
+
type: z.literal("instance"),
|
|
522
|
+
/** The component design's id. */
|
|
523
|
+
design: z.string(),
|
|
524
|
+
args: plainObject.optional(),
|
|
525
|
+
/** Which of the component's states this copy shows. */
|
|
526
|
+
state: z.string().optional(),
|
|
527
|
+
/**
|
|
528
|
+
* What this copy changes inside the component, keyed by the inner node's id, or `nested/inner` for a node inside a
|
|
529
|
+
* nested instance. The copy stays linked: everything it doesn't change follows the component.
|
|
530
|
+
*/
|
|
531
|
+
overrides: nodePatches.optional()
|
|
532
|
+
});
|
|
533
|
+
const imageFits = [
|
|
534
|
+
"cover",
|
|
535
|
+
"contain",
|
|
536
|
+
"fill"
|
|
335
537
|
];
|
|
538
|
+
const imageCredit = fileObject({
|
|
539
|
+
name: z.string(),
|
|
540
|
+
url: z.string(),
|
|
541
|
+
/** The library it came from. */
|
|
542
|
+
source: z.string().optional()
|
|
543
|
+
});
|
|
544
|
+
const imageNode = nodeBase.extend({
|
|
545
|
+
type: z.literal("image"),
|
|
546
|
+
/** A URL, or a path the project's dev server serves, like `/images/photo.jpg`. */
|
|
547
|
+
src: z.string(),
|
|
548
|
+
alt: z.string().optional(),
|
|
549
|
+
/** How it fills its box: cropped to cover it (the default), shown whole, or stretched. */
|
|
550
|
+
fit: z.enum(imageFits).optional(),
|
|
551
|
+
/** Who made it and where it came from, for photos that ask to be credited. */
|
|
552
|
+
credit: imageCredit.optional()
|
|
553
|
+
});
|
|
554
|
+
const designNode = z.discriminatedUnion("type", [
|
|
555
|
+
layoutNode,
|
|
556
|
+
componentNode$1,
|
|
557
|
+
textNode,
|
|
558
|
+
instanceNode,
|
|
559
|
+
imageNode
|
|
560
|
+
]);
|
|
561
|
+
/** A node tree as a design or an insertion holds it, each node with an id of its own. */
|
|
562
|
+
const nodeTree = designNode.superRefine((root, context) => {
|
|
563
|
+
const ids = /* @__PURE__ */ new Set();
|
|
564
|
+
const visit = (node, path) => {
|
|
565
|
+
if (ids.has(node.id)) problem(context, [...path, "id"], `duplicate node id "${node.id}"`);
|
|
566
|
+
ids.add(node.id);
|
|
567
|
+
if (node.type === "layout") for (const [index, child] of node.children.entries()) visit(child, [
|
|
568
|
+
...path,
|
|
569
|
+
"children",
|
|
570
|
+
index
|
|
571
|
+
]);
|
|
572
|
+
else if (node.type === "component") for (const [slot, children] of Object.entries(node.slots ?? {})) for (const [index, child] of children.entries()) visit(child, [
|
|
573
|
+
...path,
|
|
574
|
+
"slots",
|
|
575
|
+
slot,
|
|
576
|
+
index
|
|
577
|
+
]);
|
|
578
|
+
};
|
|
579
|
+
visit(root, []);
|
|
580
|
+
});
|
|
581
|
+
/** Calls `visit` for every node in a tree, depth first, including nodes in slots. */
|
|
582
|
+
function walkNodes(node, visit, depth = 0) {
|
|
583
|
+
visit(node, depth);
|
|
584
|
+
let children = [];
|
|
585
|
+
if (node.type === "layout") children = node.children;
|
|
586
|
+
else if (node.type === "component") children = Object.values(node.slots ?? {}).flat();
|
|
587
|
+
for (const child of children) walkNodes(child, visit, depth + 1);
|
|
588
|
+
}
|
|
336
589
|
const containerKinds = [
|
|
337
590
|
"screen",
|
|
338
591
|
"component",
|
|
339
592
|
"frame"
|
|
340
593
|
];
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
];
|
|
594
|
+
const frameSize = fileObject({
|
|
595
|
+
width: finiteNumber.optional(),
|
|
596
|
+
height: finiteNumber.optional()
|
|
597
|
+
});
|
|
598
|
+
const variantOption = fileObject({
|
|
599
|
+
id: z.string(),
|
|
600
|
+
name: z.string(),
|
|
601
|
+
/**
|
|
602
|
+
* Patches keyed by node id, or by `instance/inner` for a node inside an instance. They apply over the design and
|
|
603
|
+
* under its states.
|
|
604
|
+
*/
|
|
605
|
+
overrides: nodePatches.optional()
|
|
606
|
+
});
|
|
355
607
|
const paramTypes = [
|
|
356
608
|
"text",
|
|
357
609
|
"boolean",
|
|
@@ -359,596 +611,178 @@ const paramTypes = [
|
|
|
359
611
|
"value",
|
|
360
612
|
"variant"
|
|
361
613
|
];
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
"center",
|
|
371
|
-
"end",
|
|
372
|
-
"between",
|
|
373
|
-
"around",
|
|
374
|
-
"evenly"
|
|
375
|
-
];
|
|
376
|
-
/** Every style key, in the order panels show them. */
|
|
377
|
-
const styleKeys = [
|
|
378
|
-
"display",
|
|
379
|
-
"direction",
|
|
380
|
-
"columns",
|
|
381
|
-
"rows",
|
|
382
|
-
"gap",
|
|
383
|
-
"rowGap",
|
|
384
|
-
"padding",
|
|
385
|
-
"margin",
|
|
386
|
-
"align",
|
|
387
|
-
"justify",
|
|
388
|
-
"wrap",
|
|
389
|
-
"width",
|
|
390
|
-
"height",
|
|
391
|
-
"minWidth",
|
|
392
|
-
"maxWidth",
|
|
393
|
-
"minHeight",
|
|
394
|
-
"maxHeight",
|
|
395
|
-
"colSpan",
|
|
396
|
-
"rowSpan",
|
|
397
|
-
"grow",
|
|
398
|
-
"self",
|
|
399
|
-
"position",
|
|
400
|
-
"top",
|
|
401
|
-
"right",
|
|
402
|
-
"bottom",
|
|
403
|
-
"left",
|
|
404
|
-
"background",
|
|
405
|
-
"foreground",
|
|
406
|
-
"border",
|
|
407
|
-
"borderWidth",
|
|
408
|
-
"borderStyle",
|
|
409
|
-
"radius",
|
|
410
|
-
"shadow",
|
|
411
|
-
"opacity",
|
|
412
|
-
"overflow",
|
|
413
|
-
"font",
|
|
414
|
-
"size",
|
|
415
|
-
"weight",
|
|
416
|
-
"leading",
|
|
417
|
-
"tracking",
|
|
418
|
-
"textAlign",
|
|
419
|
-
"transform",
|
|
420
|
-
"decoration",
|
|
421
|
-
"italic",
|
|
422
|
-
"lines"
|
|
423
|
-
];
|
|
424
|
-
function parseCanvasDocument(value) {
|
|
425
|
-
const document = expectObject(value, "");
|
|
426
|
-
if (document.version !== 3) throw new TypeError("/version: expected 3");
|
|
427
|
-
expectKnownKeys(document, [
|
|
428
|
-
"version",
|
|
429
|
-
"containers",
|
|
430
|
-
"notes",
|
|
431
|
-
"modes",
|
|
432
|
-
"detection",
|
|
433
|
-
"objects",
|
|
434
|
-
"links",
|
|
435
|
-
"sources"
|
|
436
|
-
], "");
|
|
437
|
-
return compact({
|
|
438
|
-
version: 3,
|
|
439
|
-
containers: expectArray(document.containers, "/containers").map((item, index) => parseContainer(item, `/containers/${index}`)),
|
|
440
|
-
modes: optional(document.modes, (value) => parseModeSettings(value)),
|
|
441
|
-
notes: optional(document.notes, (value) => expectArray(value, "/notes").map((item, index) => parseNote(item, `/notes/${index}`))),
|
|
442
|
-
detection: optional(document.detection, (value) => parseDetection(value, "/detection")),
|
|
443
|
-
objects: optional(document.objects, (value) => parseObjects(value, "/objects")),
|
|
444
|
-
links: optional(document.links, (value) => parseLinks(value, "/links")),
|
|
445
|
-
sources: optional(document.sources, (value) => parseSources(value, "/sources"))
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
function parsePosition(value, path) {
|
|
449
|
-
const position = expectObject(value, path);
|
|
450
|
-
expectKnownKeys(position, ["x", "y"], path);
|
|
451
|
-
return {
|
|
452
|
-
x: expectNumber(position.x, `${path}/x`),
|
|
453
|
-
y: expectNumber(position.y, `${path}/y`)
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
function parseObjects(value, path) {
|
|
457
|
-
const placed = /* @__PURE__ */ new Set();
|
|
458
|
-
return expectArray(value, path).map((item, index) => {
|
|
459
|
-
const at = `${path}/${index}`;
|
|
460
|
-
const object = expectObject(item, at);
|
|
461
|
-
expectKnownKeys(object, [
|
|
462
|
-
"kind",
|
|
463
|
-
"ref",
|
|
464
|
-
"position"
|
|
465
|
-
], at);
|
|
466
|
-
const kind = expectOneOf(object.kind, canvasObjectKinds, `${at}/kind`);
|
|
467
|
-
const ref = expectString(object.ref, `${at}/ref`);
|
|
468
|
-
const key = JSON.stringify([kind, ref]);
|
|
469
|
-
if (placed.has(key)) throw new TypeError(`${at}: the ${kind} "${ref}" is already on the canvas`);
|
|
470
|
-
placed.add(key);
|
|
471
|
-
return {
|
|
472
|
-
kind,
|
|
473
|
-
ref,
|
|
474
|
-
position: parsePosition(object.position, `${at}/position`)
|
|
475
|
-
};
|
|
476
|
-
});
|
|
477
|
-
}
|
|
478
|
-
function parseLinkEnd(value, path) {
|
|
479
|
-
const end = expectObject(value, path);
|
|
480
|
-
expectKnownKeys(end, ["kind", "ref"], path);
|
|
481
|
-
return {
|
|
482
|
-
kind: expectOneOf(end.kind, linkEndKinds, `${path}/kind`),
|
|
483
|
-
ref: expectString(end.ref, `${path}/ref`)
|
|
484
|
-
};
|
|
485
|
-
}
|
|
486
|
-
function parseLinks(value, path) {
|
|
614
|
+
const paramSpec = fileObject({
|
|
615
|
+
type: z.enum(paramTypes),
|
|
616
|
+
/** A variant's default is the id of one of its options; unset means the first. */
|
|
617
|
+
default: z.unknown().optional(),
|
|
618
|
+
description: z.string().optional(),
|
|
619
|
+
/** A variant's choices: size small, medium or large. Each option patches the design, the way a state does. */
|
|
620
|
+
options: z.array(variantOption).optional()
|
|
621
|
+
}).superRefine((param, context) => {
|
|
487
622
|
const ids = /* @__PURE__ */ new Set();
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
"type",
|
|
495
|
-
"from",
|
|
496
|
-
"to"
|
|
497
|
-
], at);
|
|
498
|
-
const id = expectString(link.id, `${at}/id`);
|
|
499
|
-
if (ids.has(id)) throw new TypeError(`${at}/id: duplicate link id "${id}"`);
|
|
623
|
+
for (const [index, { id }] of (param.options ?? []).entries()) {
|
|
624
|
+
if (ids.has(id)) problem(context, [
|
|
625
|
+
"options",
|
|
626
|
+
index,
|
|
627
|
+
"id"
|
|
628
|
+
], `duplicate option id "${id}"`);
|
|
500
629
|
ids.add(id);
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
const
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
"name",
|
|
560
|
-
"kind",
|
|
561
|
-
"position",
|
|
562
|
-
"root",
|
|
630
|
+
}
|
|
631
|
+
if (param.type === "variant" && !param.options?.length) problem(context, ["options"], "a variant needs options");
|
|
632
|
+
if (param.type !== "variant" && param.options) problem(context, ["options"], "only variants have options");
|
|
633
|
+
});
|
|
634
|
+
const containerViewSettings = fileObject({
|
|
635
|
+
/** The mode of the main frame. Defaults to the project's primary mode. */
|
|
636
|
+
mode: z.string().optional(),
|
|
637
|
+
/** Other modes shown beside the main frame. */
|
|
638
|
+
modes: z.array(z.string()).optional(),
|
|
639
|
+
/** The state the canvas shows. Defaults to the first. */
|
|
640
|
+
state: z.string().optional(),
|
|
641
|
+
/** Show every state, one row each, instead of only the active one. */
|
|
642
|
+
allStates: z.boolean().optional(),
|
|
643
|
+
/** The option the board shows for each variant param, by option id. Unset ones show their default. */
|
|
644
|
+
variants: z.record(z.string(), z.string()).optional()
|
|
645
|
+
});
|
|
646
|
+
const stateSpec = fileObject({
|
|
647
|
+
id: z.string(),
|
|
648
|
+
name: z.string(),
|
|
649
|
+
/**
|
|
650
|
+
* Patches keyed by node id, or by `instance/inner` for a node inside an instance. A state wins over a viewport's
|
|
651
|
+
* responsive patch and over the design's variants.
|
|
652
|
+
*/
|
|
653
|
+
overrides: nodePatches.optional()
|
|
654
|
+
});
|
|
655
|
+
const containerSpec = fileObject({
|
|
656
|
+
id: z.string(),
|
|
657
|
+
name: z.string(),
|
|
658
|
+
/** Defaults to `screen`. */
|
|
659
|
+
kind: z.enum(containerKinds).optional(),
|
|
660
|
+
position,
|
|
661
|
+
root: nodeTree,
|
|
662
|
+
states: z.array(stateSpec).min(1, "expected at least one state"),
|
|
663
|
+
view: containerViewSettings.optional(),
|
|
664
|
+
/** A component's inputs. Instances pass values for them; nodes inside bind to them. */
|
|
665
|
+
params: z.record(z.string(), paramSpec).optional(),
|
|
666
|
+
/**
|
|
667
|
+
* Components this design sits inside when shown on its own board, outermost first, like a Storybook decorator: a
|
|
668
|
+
* table row needs a table around it to render properly.
|
|
669
|
+
*/
|
|
670
|
+
context: z.array(fileObject({
|
|
671
|
+
component: z.string(),
|
|
672
|
+
props: plainObject.optional()
|
|
673
|
+
})).optional(),
|
|
674
|
+
/**
|
|
675
|
+
* A screen's or component's frame sizes in CSS pixels, keyed by viewport option id. Without one, a frame is as wide
|
|
676
|
+
* as its mode's viewport and as tall as its content.
|
|
677
|
+
*/
|
|
678
|
+
frames: z.record(z.string(), frameSize).optional(),
|
|
679
|
+
/**
|
|
680
|
+
* A frame's own size in CSS pixels, whatever the project's viewports are. Without a width it's as wide as the primary
|
|
681
|
+
* mode's viewport, and without a height as tall as its content. Only a frame has one.
|
|
682
|
+
*/
|
|
683
|
+
size: frameSize.optional()
|
|
684
|
+
}).superRefine((container, context) => {
|
|
685
|
+
const ids = /* @__PURE__ */ new Set();
|
|
686
|
+
walkNodes(container.root, (node) => ids.add(node.id));
|
|
687
|
+
for (const [index, state] of container.states.entries()) checkPatchKeys(state.overrides, ids, context, [
|
|
563
688
|
"states",
|
|
564
|
-
|
|
689
|
+
index,
|
|
690
|
+
"overrides"
|
|
691
|
+
]);
|
|
692
|
+
if (container.kind === "frame") checkFrame(container, context);
|
|
693
|
+
else if (container.size !== void 0) problem(context, ["size"], `only a frame has a size of its own; size a ${container.kind ?? "screen"} in frames`);
|
|
694
|
+
for (const [name, param] of Object.entries(container.params ?? {})) for (const [index, option] of (param.options ?? []).entries()) checkPatchKeys(option.overrides, ids, context, [
|
|
565
695
|
"params",
|
|
566
|
-
|
|
567
|
-
"
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
],
|
|
580
|
-
|
|
581
|
-
mode: optional(settings.mode, (mode) => expectString(mode, `${path}/view/mode`)),
|
|
582
|
-
modes: optional(settings.modes, (list) => expectArray(list, `${path}/view/modes`).map((mode, index) => expectString(mode, `${path}/view/modes/${index}`))),
|
|
583
|
-
state: optional(settings.state, (state) => expectString(state, `${path}/view/state`)),
|
|
584
|
-
allStates: optional(settings.allStates, (all) => expectBoolean(all, `${path}/view/allStates`)),
|
|
585
|
-
variants: optional(settings.variants, (choices) => {
|
|
586
|
-
const variants = {};
|
|
587
|
-
for (const [param, option] of Object.entries(expectObject(choices, `${path}/view/variants`))) variants[param] = expectString(option, `${path}/view/variants/${param}`);
|
|
588
|
-
return variants;
|
|
589
|
-
})
|
|
590
|
-
});
|
|
591
|
-
});
|
|
592
|
-
const ids = /* @__PURE__ */ new Set();
|
|
593
|
-
const root = parseNode(container.root, `${path}/root`, ids);
|
|
594
|
-
const states = expectArray(container.states, `${path}/states`).map((item, index) => {
|
|
595
|
-
const at = `${path}/states/${index}`;
|
|
596
|
-
const state = expectObject(item, at);
|
|
597
|
-
expectKnownKeys(state, [
|
|
598
|
-
"id",
|
|
599
|
-
"name",
|
|
600
|
-
"overrides"
|
|
601
|
-
], at);
|
|
602
|
-
return {
|
|
603
|
-
id: expectString(state.id, `${at}/id`),
|
|
604
|
-
name: expectString(state.name, `${at}/name`),
|
|
605
|
-
overrides: optional(state.overrides, (v) => parsePatches(v, `${at}/overrides`, ids))
|
|
606
|
-
};
|
|
607
|
-
});
|
|
608
|
-
if (states.length === 0) throw new TypeError(`${path}/states: expected at least one state`);
|
|
609
|
-
const kind = optional(container.kind, (v) => expectOneOf(v, containerKinds, `${path}/kind`));
|
|
610
|
-
if (kind === "frame") checkFrame(container, root, states, path);
|
|
611
|
-
else if (container.size !== void 0) throw new TypeError(`${path}/size: only a frame has a size of its own; size a ${kind ?? "screen"} in frames`);
|
|
612
|
-
return compact({
|
|
613
|
-
id: expectString(container.id, `${path}/id`),
|
|
614
|
-
name: expectString(container.name, `${path}/name`),
|
|
615
|
-
kind,
|
|
616
|
-
context: optional(container.context, (v) => expectArray(v, `${path}/context`).map((item, index) => {
|
|
617
|
-
const at = `${path}/context/${index}`;
|
|
618
|
-
const wrapper = expectObject(item, at);
|
|
619
|
-
expectKnownKeys(wrapper, ["component", "props"], at);
|
|
620
|
-
return compact({
|
|
621
|
-
component: expectString(wrapper.component, `${at}/component`),
|
|
622
|
-
props: optional(wrapper.props, (p) => expectObject(p, `${at}/props`))
|
|
623
|
-
});
|
|
624
|
-
})),
|
|
625
|
-
params: optional(container.params, (v) => {
|
|
626
|
-
const params = {};
|
|
627
|
-
for (const [name, entry] of Object.entries(expectObject(v, `${path}/params`))) {
|
|
628
|
-
const at = `${path}/params/${name}`;
|
|
629
|
-
const param = expectObject(entry, at);
|
|
630
|
-
expectKnownKeys(param, [
|
|
631
|
-
"type",
|
|
632
|
-
"default",
|
|
633
|
-
"description",
|
|
634
|
-
"options"
|
|
635
|
-
], at);
|
|
636
|
-
const type = expectOneOf(param.type, paramTypes, `${at}/type`);
|
|
637
|
-
const options = optional(param.options, (list) => {
|
|
638
|
-
const seen = /* @__PURE__ */ new Set();
|
|
639
|
-
return expectArray(list, `${at}/options`).map((item, index) => {
|
|
640
|
-
const where = `${at}/options/${index}`;
|
|
641
|
-
const option = expectObject(item, where);
|
|
642
|
-
expectKnownKeys(option, [
|
|
643
|
-
"id",
|
|
644
|
-
"name",
|
|
645
|
-
"overrides"
|
|
646
|
-
], where);
|
|
647
|
-
const id = expectString(option.id, `${where}/id`);
|
|
648
|
-
if (seen.has(id)) throw new TypeError(`${where}/id: duplicate option id "${id}"`);
|
|
649
|
-
seen.add(id);
|
|
650
|
-
return compact({
|
|
651
|
-
id,
|
|
652
|
-
name: expectString(option.name, `${where}/name`),
|
|
653
|
-
overrides: optional(option.overrides, (v) => parsePatches(v, `${where}/overrides`, ids))
|
|
654
|
-
});
|
|
655
|
-
});
|
|
656
|
-
});
|
|
657
|
-
if (type === "variant" && !options?.length) throw new TypeError(`${at}/options: a variant needs options`);
|
|
658
|
-
if (type !== "variant" && options) throw new TypeError(`${at}/options: only variants have options`);
|
|
659
|
-
params[name] = compact({
|
|
660
|
-
type,
|
|
661
|
-
default: param.default,
|
|
662
|
-
description: optional(param.description, (d) => expectString(d, `${at}/description`)),
|
|
663
|
-
options
|
|
664
|
-
});
|
|
665
|
-
}
|
|
666
|
-
return params;
|
|
667
|
-
}),
|
|
668
|
-
frames: optional(container.frames, (v) => {
|
|
669
|
-
const frames = {};
|
|
670
|
-
for (const [viewport, entry] of Object.entries(expectObject(v, `${path}/frames`))) frames[viewport] = parseFrameSize(entry, `${path}/frames/${viewport}`);
|
|
671
|
-
return frames;
|
|
672
|
-
}),
|
|
673
|
-
size: optional(container.size, (v) => parseFrameSize(v, `${path}/size`)),
|
|
674
|
-
position: {
|
|
675
|
-
x: expectNumber(position.x, `${path}/position/x`),
|
|
676
|
-
y: expectNumber(position.y, `${path}/position/y`)
|
|
677
|
-
},
|
|
678
|
-
root,
|
|
679
|
-
states,
|
|
680
|
-
view
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
function parseFrameSize(value, path) {
|
|
684
|
-
const size = expectObject(value, path);
|
|
685
|
-
expectKnownKeys(size, ["width", "height"], path);
|
|
686
|
-
return compact({
|
|
687
|
-
width: optional(size.width, (w) => expectNumber(w, `${path}/width`)),
|
|
688
|
-
height: optional(size.height, (h) => expectNumber(h, `${path}/height`))
|
|
689
|
-
});
|
|
696
|
+
name,
|
|
697
|
+
"options",
|
|
698
|
+
index,
|
|
699
|
+
"overrides"
|
|
700
|
+
]);
|
|
701
|
+
});
|
|
702
|
+
/**
|
|
703
|
+
* A design's patches keyed by its own nodes. A key names a node of the design, or starts with one: `instance/inner`
|
|
704
|
+
* reaches inside an instance, which can't be checked without the component.
|
|
705
|
+
*/
|
|
706
|
+
function checkPatchKeys(patches, ids, context, path) {
|
|
707
|
+
for (const key of Object.keys(patches ?? {})) {
|
|
708
|
+
const [node] = key.split("/");
|
|
709
|
+
if (!ids.has(node)) problem(context, [...path, key], "no node has this id");
|
|
710
|
+
}
|
|
690
711
|
}
|
|
691
712
|
/**
|
|
692
713
|
* What a frame may hold: a layout at its root and one state, drawn as its layers are, with no params, no view (it
|
|
693
714
|
* shows only the primary mode and that state) and no sizes per viewport, since it has one of its own.
|
|
694
715
|
*/
|
|
695
|
-
function checkFrame(
|
|
696
|
-
if (root.type !== "layout")
|
|
697
|
-
if (states.length > 1)
|
|
698
|
-
if (Object.keys(states[0]
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
716
|
+
function checkFrame(frame, context) {
|
|
717
|
+
if (frame.root.type !== "layout") problem(context, ["root"], "a frame's root is a layout");
|
|
718
|
+
if (frame.states.length > 1) problem(context, ["states"], "a frame has one state");
|
|
719
|
+
if (Object.keys(frame.states[0]?.overrides ?? {}).length > 0) problem(context, [
|
|
720
|
+
"states",
|
|
721
|
+
0,
|
|
722
|
+
"overrides"
|
|
723
|
+
], "a frame is drawn as its layers are");
|
|
724
|
+
if (frame.params !== void 0) problem(context, ["params"], "a frame has no params");
|
|
725
|
+
if (frame.view !== void 0) problem(context, ["view"], "a frame shows only the primary mode and its one state");
|
|
726
|
+
if (frame.frames !== void 0) problem(context, ["frames"], "a frame's size is in size, not per viewport");
|
|
702
727
|
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
"
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
"instance",
|
|
710
|
-
"image"
|
|
711
|
-
], `${path}/type`);
|
|
712
|
-
const id = expectString(node.id, `${path}/id`);
|
|
713
|
-
if (ids.has(id)) throw new TypeError(`${path}/id: duplicate node id "${id}"`);
|
|
714
|
-
ids.add(id);
|
|
715
|
-
const base = compact({
|
|
716
|
-
id,
|
|
717
|
-
name: optional(node.name, (v) => expectString(v, `${path}/name`)),
|
|
718
|
-
hidden: optional(node.hidden, (v) => expectBoolean(v, `${path}/hidden`)),
|
|
719
|
-
style: optional(node.style, (v) => parseStyle(v, `${path}/style`)),
|
|
720
|
-
responsive: optional(node.responsive, (v) => parsePatches(v, `${path}/responsive`)),
|
|
721
|
-
bind: optional(node.bind, (v) => parseBinding(v, `${path}/bind`))
|
|
728
|
+
/** A rule's problem with the value it checks, at `path` inside that value. */
|
|
729
|
+
function problem(context, path, message) {
|
|
730
|
+
context.addIssue({
|
|
731
|
+
code: "custom",
|
|
732
|
+
message,
|
|
733
|
+
path
|
|
722
734
|
});
|
|
723
|
-
const common = [
|
|
724
|
-
"type",
|
|
725
|
-
"id",
|
|
726
|
-
"name",
|
|
727
|
-
"hidden",
|
|
728
|
-
"style",
|
|
729
|
-
"responsive",
|
|
730
|
-
"bind"
|
|
731
|
-
];
|
|
732
|
-
switch (type) {
|
|
733
|
-
case "layout":
|
|
734
|
-
expectKnownKeys(node, [...common, "children"], path);
|
|
735
|
-
return {
|
|
736
|
-
...base,
|
|
737
|
-
type,
|
|
738
|
-
children: expectArray(node.children, `${path}/children`).map((child, index) => parseNode(child, `${path}/children/${index}`, ids))
|
|
739
|
-
};
|
|
740
|
-
case "component": {
|
|
741
|
-
expectKnownKeys(node, [
|
|
742
|
-
...common,
|
|
743
|
-
"component",
|
|
744
|
-
"props",
|
|
745
|
-
"slots"
|
|
746
|
-
], path);
|
|
747
|
-
const slots = optional(node.slots, (v) => {
|
|
748
|
-
const result = {};
|
|
749
|
-
for (const [slot, children] of Object.entries(expectObject(v, `${path}/slots`))) result[slot] = expectArray(children, `${path}/slots/${slot}`).map((child, index) => parseNode(child, `${path}/slots/${slot}/${index}`, ids));
|
|
750
|
-
return result;
|
|
751
|
-
});
|
|
752
|
-
return {
|
|
753
|
-
...base,
|
|
754
|
-
type,
|
|
755
|
-
component: expectString(node.component, `${path}/component`),
|
|
756
|
-
props: optional(node.props, (v) => expectObject(v, `${path}/props`)),
|
|
757
|
-
slots
|
|
758
|
-
};
|
|
759
|
-
}
|
|
760
|
-
case "instance":
|
|
761
|
-
expectKnownKeys(node, [
|
|
762
|
-
...common,
|
|
763
|
-
"design",
|
|
764
|
-
"args",
|
|
765
|
-
"state",
|
|
766
|
-
"overrides"
|
|
767
|
-
], path);
|
|
768
|
-
return compact({
|
|
769
|
-
...base,
|
|
770
|
-
type,
|
|
771
|
-
design: expectString(node.design, `${path}/design`),
|
|
772
|
-
args: optional(node.args, (v) => expectObject(v, `${path}/args`)),
|
|
773
|
-
state: optional(node.state, (v) => expectString(v, `${path}/state`)),
|
|
774
|
-
overrides: optional(node.overrides, (v) => parsePatches(v, `${path}/overrides`))
|
|
775
|
-
});
|
|
776
|
-
case "text":
|
|
777
|
-
expectKnownKeys(node, [
|
|
778
|
-
...common,
|
|
779
|
-
"text",
|
|
780
|
-
"tag"
|
|
781
|
-
], path);
|
|
782
|
-
return {
|
|
783
|
-
...base,
|
|
784
|
-
type,
|
|
785
|
-
text: expectString(node.text, `${path}/text`),
|
|
786
|
-
tag: optional(node.tag, (v) => expectOneOf(v, textTags, `${path}/tag`))
|
|
787
|
-
};
|
|
788
|
-
case "image":
|
|
789
|
-
expectKnownKeys(node, [
|
|
790
|
-
...common,
|
|
791
|
-
"src",
|
|
792
|
-
"alt",
|
|
793
|
-
"fit",
|
|
794
|
-
"credit"
|
|
795
|
-
], path);
|
|
796
|
-
return compact({
|
|
797
|
-
...base,
|
|
798
|
-
type,
|
|
799
|
-
src: expectString(node.src, `${path}/src`),
|
|
800
|
-
alt: optional(node.alt, (v) => expectString(v, `${path}/alt`)),
|
|
801
|
-
fit: optional(node.fit, (v) => expectOneOf(v, imageFits, `${path}/fit`)),
|
|
802
|
-
credit: optional(node.credit, (v) => {
|
|
803
|
-
const credit = expectObject(v, `${path}/credit`);
|
|
804
|
-
expectKnownKeys(credit, [
|
|
805
|
-
"name",
|
|
806
|
-
"url",
|
|
807
|
-
"source"
|
|
808
|
-
], `${path}/credit`);
|
|
809
|
-
return compact({
|
|
810
|
-
name: expectString(credit.name, `${path}/credit/name`),
|
|
811
|
-
url: expectString(credit.url, `${path}/credit/url`),
|
|
812
|
-
source: optional(credit.source, (s) => expectString(s, `${path}/credit/source`))
|
|
813
|
-
});
|
|
814
|
-
})
|
|
815
|
-
});
|
|
816
|
-
}
|
|
817
735
|
}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
736
|
+
const canvasDocument = fileObject({
|
|
737
|
+
version: z.literal(3),
|
|
738
|
+
containers: z.array(containerSpec),
|
|
739
|
+
/** The project's modes. Unset means the defaults. */
|
|
740
|
+
modes: modeSettings.optional(),
|
|
741
|
+
/** Text placed on the canvas itself, outside any design: annotations, flow labels, explanations. */
|
|
742
|
+
notes: z.array(noteSpec).optional(),
|
|
743
|
+
/** Where the person corrected which exports are components. Unset while detection gets them right. */
|
|
744
|
+
detection: detectionCorrections.optional(),
|
|
745
|
+
/** Where the person put tables, operations and routes on the canvas. The models are read from the project. */
|
|
746
|
+
objects: canvasObjects.optional(),
|
|
747
|
+
/** Links the person drew by hand. Links found in code or traces are read each time and never saved. */
|
|
748
|
+
links: linkSpecs.optional(),
|
|
749
|
+
/** The source each lens reads, where the person switched from the one Parity picked. */
|
|
750
|
+
sources: lensSources.optional()
|
|
751
|
+
});
|
|
752
|
+
/** Reads a project file, or throws `TypeError` with the first problem as `/json/pointer: message`. */
|
|
753
|
+
function parseCanvasDocument(value) {
|
|
754
|
+
return parseData(canvasDocument, value);
|
|
834
755
|
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
function parsePatches(value, path, ids) {
|
|
840
|
-
const patches = {};
|
|
841
|
-
for (const [key, entry] of Object.entries(expectObject(value, path))) {
|
|
842
|
-
if (ids && !ids.has(key.split("/")[0])) throw new TypeError(`${path}/${key}: no node has this id`);
|
|
843
|
-
patches[key] = parsePatch(entry, `${path}/${key}`);
|
|
844
|
-
}
|
|
845
|
-
return patches;
|
|
756
|
+
// ../core/src/validate.ts
|
|
757
|
+
function expectObject(value, path) {
|
|
758
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError(`${path || "/"}: expected an object`);
|
|
759
|
+
return value;
|
|
846
760
|
}
|
|
847
|
-
function
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
"hidden",
|
|
851
|
-
"text",
|
|
852
|
-
"props",
|
|
853
|
-
"style",
|
|
854
|
-
"unset"
|
|
855
|
-
], path);
|
|
856
|
-
return compact({
|
|
857
|
-
hidden: optional(patch.hidden, (v) => expectBoolean(v, `${path}/hidden`)),
|
|
858
|
-
text: optional(patch.text, (v) => expectString(v, `${path}/text`)),
|
|
859
|
-
props: optional(patch.props, (v) => expectObject(v, `${path}/props`)),
|
|
860
|
-
style: optional(patch.style, (v) => parseStyle(v, `${path}/style`)),
|
|
861
|
-
unset: optional(patch.unset, (v) => parseUnset(v, `${path}/unset`))
|
|
862
|
-
});
|
|
761
|
+
function expectString(value, path) {
|
|
762
|
+
if (typeof value !== "string") throw new TypeError(`${path}: expected a string`);
|
|
763
|
+
return value;
|
|
863
764
|
}
|
|
864
|
-
function
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
const keys = (key, item) => optional(unset[key], (v) => expectArray(v, `${path}/${key}`).map((entry, index) => item(entry, `${path}/${key}/${index}`)));
|
|
868
|
-
return compact({
|
|
869
|
-
props: keys("props", expectString),
|
|
870
|
-
style: keys("style", (v, where) => expectOneOf(v, styleKeys, where))
|
|
871
|
-
});
|
|
765
|
+
function expectNumber(value, path) {
|
|
766
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw new TypeError(`${path}: expected a number`);
|
|
767
|
+
return value;
|
|
872
768
|
}
|
|
873
|
-
function
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
direction: optional(style.direction, (v) => expectOneOf(v, ["row", "column"], at("direction"))),
|
|
891
|
-
columns: number("columns"),
|
|
892
|
-
rows: number("rows"),
|
|
893
|
-
gap: length("gap"),
|
|
894
|
-
rowGap: length("rowGap"),
|
|
895
|
-
padding: sides("padding"),
|
|
896
|
-
margin: sides("margin"),
|
|
897
|
-
align: optional(style.align, (v) => expectOneOf(v, alignments, at("align"))),
|
|
898
|
-
justify: optional(style.justify, (v) => expectOneOf(v, justifications, at("justify"))),
|
|
899
|
-
wrap: flag("wrap"),
|
|
900
|
-
width: size("width"),
|
|
901
|
-
height: size("height"),
|
|
902
|
-
minWidth: length("minWidth"),
|
|
903
|
-
maxWidth: length("maxWidth"),
|
|
904
|
-
minHeight: length("minHeight"),
|
|
905
|
-
maxHeight: length("maxHeight"),
|
|
906
|
-
colSpan: number("colSpan"),
|
|
907
|
-
rowSpan: number("rowSpan"),
|
|
908
|
-
grow: flag("grow"),
|
|
909
|
-
self: optional(style.self, (v) => expectOneOf(v, alignments, at("self"))),
|
|
910
|
-
position: optional(style.position, (v) => expectOneOf(v, ["absolute"], at("position"))),
|
|
911
|
-
top: length("top"),
|
|
912
|
-
right: length("right"),
|
|
913
|
-
bottom: length("bottom"),
|
|
914
|
-
left: length("left"),
|
|
915
|
-
background: token("background"),
|
|
916
|
-
foreground: token("foreground"),
|
|
917
|
-
border: token("border"),
|
|
918
|
-
borderWidth: number("borderWidth"),
|
|
919
|
-
borderStyle: optional(style.borderStyle, (v) => expectOneOf(v, [
|
|
920
|
-
"solid",
|
|
921
|
-
"dashed",
|
|
922
|
-
"dotted"
|
|
923
|
-
], at("borderStyle"))),
|
|
924
|
-
radius: token("radius"),
|
|
925
|
-
shadow: token("shadow"),
|
|
926
|
-
opacity: number("opacity"),
|
|
927
|
-
overflow: optional(style.overflow, (v) => expectOneOf(v, [
|
|
928
|
-
"visible",
|
|
929
|
-
"hidden",
|
|
930
|
-
"scroll"
|
|
931
|
-
], at("overflow"))),
|
|
932
|
-
font: token("font"),
|
|
933
|
-
size: token("size"),
|
|
934
|
-
weight: number("weight"),
|
|
935
|
-
leading: token("leading"),
|
|
936
|
-
tracking: token("tracking"),
|
|
937
|
-
textAlign: optional(style.textAlign, (v) => expectOneOf(v, [
|
|
938
|
-
"start",
|
|
939
|
-
"center",
|
|
940
|
-
"end",
|
|
941
|
-
"justify"
|
|
942
|
-
], at("textAlign"))),
|
|
943
|
-
transform: optional(style.transform, (v) => expectOneOf(v, [
|
|
944
|
-
"uppercase",
|
|
945
|
-
"lowercase",
|
|
946
|
-
"capitalize"
|
|
947
|
-
], at("transform"))),
|
|
948
|
-
decoration: optional(style.decoration, (v) => expectOneOf(v, ["underline", "line-through"], at("decoration"))),
|
|
949
|
-
italic: flag("italic"),
|
|
950
|
-
lines: number("lines")
|
|
951
|
-
});
|
|
769
|
+
function expectArray(value, path) {
|
|
770
|
+
if (!Array.isArray(value)) throw new TypeError(`${path}: expected an array`);
|
|
771
|
+
return value;
|
|
772
|
+
}
|
|
773
|
+
function expectOneOf(value, options, path) {
|
|
774
|
+
if (typeof value !== "string" || !options.includes(value)) throw new TypeError(`${path}: expected one of ${options.map((option) => `"${option}"`).join(", ")}`);
|
|
775
|
+
return value;
|
|
776
|
+
}
|
|
777
|
+
function optional(value, parse) {
|
|
778
|
+
return value === void 0 ? void 0 : parse(value);
|
|
779
|
+
}
|
|
780
|
+
/** Drops `undefined` entries, so parsed patches can be spread over defaults without erasing them. */
|
|
781
|
+
function compact(value) {
|
|
782
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
|
|
783
|
+
}
|
|
784
|
+
function expectKnownKeys(value, keys, path) {
|
|
785
|
+
for (const key of Object.keys(value)) if (!keys.includes(key)) throw new TypeError(`${path}/${key}: unknown property`);
|
|
952
786
|
}
|
|
953
787
|
// ../core/src/library.ts
|
|
954
788
|
/** How a component was recognised, which is also why it's believed to be one. */
|
|
@@ -1358,6 +1192,16 @@ function expectStringRecord(value, path) {
|
|
|
1358
1192
|
for (const [key, item] of Object.entries(record)) expectString(item, `${path}/${key}`);
|
|
1359
1193
|
return record;
|
|
1360
1194
|
}
|
|
1195
|
+
// ../core/src/project.ts
|
|
1196
|
+
/**
|
|
1197
|
+
* Props that say what's open or chosen: `defaultOpen` on what opens (a dialog, a popover, a menu), `defaultValue` on
|
|
1198
|
+
* what opens or chooses its items by value (an accordion, a navigation menu, tabs) and `value` on those items.
|
|
1199
|
+
*/
|
|
1200
|
+
const openProps = [
|
|
1201
|
+
"defaultOpen",
|
|
1202
|
+
"defaultValue",
|
|
1203
|
+
"value"
|
|
1204
|
+
];
|
|
1361
1205
|
// ../core/src/library-insert.ts
|
|
1362
1206
|
/** Nodes whose `children` is the name `componentNode` gave them, rather than text someone set. */
|
|
1363
1207
|
const named = /* @__PURE__ */ new WeakSet();
|
|
@@ -1574,6 +1418,8 @@ function elementProps(props) {
|
|
|
1574
1418
|
* Discovery has already rewritten module imports for the name the project installed the package under.
|
|
1575
1419
|
*/
|
|
1576
1420
|
function librarySource(library, name, component, path) {
|
|
1421
|
+
const declared = new Set(Object.keys(component.props ?? {}).map((prop) => prop.replace(/\?$/, "")));
|
|
1422
|
+
const open = openProps.filter((prop) => declared.has(prop));
|
|
1577
1423
|
return {
|
|
1578
1424
|
path,
|
|
1579
1425
|
export: name,
|
|
@@ -1585,7 +1431,8 @@ function librarySource(library, name, component, path) {
|
|
|
1585
1431
|
...component.visual === false ? { visual: false } : {},
|
|
1586
1432
|
...component.optionalParents ? { optionalParents: component.optionalParents.map((parent) => `${library}/${parent}`) } : {},
|
|
1587
1433
|
...component.parentParts ? { parentParts: true } : {},
|
|
1588
|
-
...component.sample ? { sample: sampleIds(component.sample, library) } : {}
|
|
1434
|
+
...component.sample ? { sample: sampleIds(component.sample, library) } : {},
|
|
1435
|
+
...open.length > 0 ? { openProps: open } : {}
|
|
1589
1436
|
};
|
|
1590
1437
|
}
|
|
1591
1438
|
/**
|
|
@@ -1620,4 +1467,4 @@ function sampleIds(sample, library) {
|
|
|
1620
1467
|
...sample.children ? { children: sample.children.map(child) } : {}
|
|
1621
1468
|
};
|
|
1622
1469
|
}
|
|
1623
|
-
export {
|
|
1470
|
+
export { viewportWidthProperty as A, parseCanvasDocument as C, collectCustomProperties as D, modeSettings as E, lengthToPixels as M, packageManifest as N, remProperty as O, paramTypes as S, generateId as T, containerKinds as _, openProps as a, linkEndKinds as b, isMadeValue as c, libraryManifestExtension as d, parentSignals as f, canvasObjectKinds as g, renderSlotOf as h, withLibraryContext as i, deviceKinds as j, viewportHeightProperty as k, isSampleElement as l, parseLibraryManifest as m, librarySource as n, applyLibraryEdits as o, parseLibraryEdits as p, sampleIds as r, componentEvidence as s, componentNode as t, isSampleRender as u, imageFits as v, walkNodes as w, linkTypes as x, linkAllowed as y };
|