@parity-app/cli 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.mjs +14 -15
- package/dist/{client-BUaE9b77.mjs → client-D5kbiMmn.mjs} +1 -1
- package/dist/index.mjs +1 -1
- package/dist/schema-DcQLoUPP.mjs +239 -0
- package/dist/src-BaOpaXkV.mjs +4 -0
- package/dist/{src-V9wTMkxO.mjs → src-CSah1pZ-.mjs} +605 -715
- package/dist/src-D2kRGtaF.mjs +129 -0
- package/dist/src-vUHtM2X4.mjs +1 -0
- package/dist/{src-BvKhu6dX.mjs → src-x0VQ8I6l.mjs} +260 -123
- package/package.json +1 -1
- package/skills/SKILL.md +16 -16
- package/dist/schema-zRNxotYl.mjs +0 -121
- package/dist/src-BIVDlIGc.mjs +0 -53
- package/dist/src-BgQ7gnXE.mjs +0 -3
- package/dist/src-CiqdFzei.mjs +0 -1
|
@@ -1,12 +1,34 @@
|
|
|
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());
|
|
5
|
-
z.
|
|
5
|
+
const exportsTarget = z.lazy(() => z.union([
|
|
6
|
+
z.string(),
|
|
7
|
+
z.null(),
|
|
8
|
+
z.array(exportsTarget),
|
|
9
|
+
z.record(z.string(), exportsTarget)
|
|
10
|
+
]));
|
|
11
|
+
/**
|
|
12
|
+
* The parts of a package.json Parity reads: the dependencies adapters detect a stack by, and where an installed
|
|
13
|
+
* package's entry points and a workspace's packages are (PAR-573). It's someone else's file, so the fields Parity
|
|
14
|
+
* doesn't read pass through unchecked.
|
|
15
|
+
*/
|
|
16
|
+
const packageManifest = z.looseObject({
|
|
6
17
|
name: z.string().optional(),
|
|
18
|
+
version: z.string().optional(),
|
|
7
19
|
dependencies: packageVersions.optional(),
|
|
8
20
|
devDependencies: packageVersions.optional(),
|
|
9
|
-
scripts: z.record(z.string(), z.string()).optional()
|
|
21
|
+
scripts: z.record(z.string(), z.string()).optional(),
|
|
22
|
+
/** Entry points by subpath (`.`, `./tailwind.css`), or where the package's one entry point leads. */
|
|
23
|
+
exports: exportsTarget.optional(),
|
|
24
|
+
types: z.string().optional(),
|
|
25
|
+
typings: z.string().optional(),
|
|
26
|
+
module: z.string().optional(),
|
|
27
|
+
main: z.string().optional(),
|
|
28
|
+
/** The stylesheet a project imports to use the package, as some packages name it. */
|
|
29
|
+
style: z.string().optional(),
|
|
30
|
+
/** A workspace root's package folders, as npm, Yarn and Bun write them. */
|
|
31
|
+
workspaces: z.union([z.array(z.string()), z.looseObject({ packages: z.array(z.string()).optional() })]).optional()
|
|
10
32
|
});
|
|
11
33
|
// ../core/src/devices.ts
|
|
12
34
|
const deviceKinds = [
|
|
@@ -33,6 +55,11 @@ function lengthToPixels(value) {
|
|
|
33
55
|
*/
|
|
34
56
|
const viewportWidthProperty = "--canvas-vw";
|
|
35
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";
|
|
36
63
|
/** Every custom property declaration in a stylesheet, in source order. */
|
|
37
64
|
function collectCustomProperties(css) {
|
|
38
65
|
const declarations = [];
|
|
@@ -129,42 +156,7 @@ function findStringEnd(css, start) {
|
|
|
129
156
|
else index++;
|
|
130
157
|
return css.length;
|
|
131
158
|
}
|
|
132
|
-
new RegExp(String.raw`"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|url\([^)]*\)|var\((?:${viewportWidthProperty}|${viewportHeightProperty}),[^)]*\)`, "g");
|
|
133
|
-
// ../core/src/validate.ts
|
|
134
|
-
function expectObject(value, path) {
|
|
135
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError(`${path || "/"}: expected an object`);
|
|
136
|
-
return value;
|
|
137
|
-
}
|
|
138
|
-
function expectString(value, path) {
|
|
139
|
-
if (typeof value !== "string") throw new TypeError(`${path}: expected a string`);
|
|
140
|
-
return value;
|
|
141
|
-
}
|
|
142
|
-
function expectNumber(value, path) {
|
|
143
|
-
if (typeof value !== "number" || !Number.isFinite(value)) throw new TypeError(`${path}: expected a number`);
|
|
144
|
-
return value;
|
|
145
|
-
}
|
|
146
|
-
function expectBoolean(value, path) {
|
|
147
|
-
if (typeof value !== "boolean") throw new TypeError(`${path}: expected true or false`);
|
|
148
|
-
return value;
|
|
149
|
-
}
|
|
150
|
-
function expectArray(value, path) {
|
|
151
|
-
if (!Array.isArray(value)) throw new TypeError(`${path}: expected an array`);
|
|
152
|
-
return value;
|
|
153
|
-
}
|
|
154
|
-
function expectOneOf(value, options, path) {
|
|
155
|
-
if (typeof value !== "string" || !options.includes(value)) throw new TypeError(`${path}: expected one of ${options.map((option) => `"${option}"`).join(", ")}`);
|
|
156
|
-
return value;
|
|
157
|
-
}
|
|
158
|
-
function optional(value, parse) {
|
|
159
|
-
return value === void 0 ? void 0 : parse(value);
|
|
160
|
-
}
|
|
161
|
-
/** Drops `undefined` entries, so parsed patches can be spread over defaults without erasing them. */
|
|
162
|
-
function compact(value) {
|
|
163
|
-
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
|
|
164
|
-
}
|
|
165
|
-
function expectKnownKeys(value, keys, path) {
|
|
166
|
-
for (const key of Object.keys(value)) if (!keys.includes(key)) throw new TypeError(`${path}/${key}: unknown property`);
|
|
167
|
-
}
|
|
159
|
+
new RegExp(String.raw`"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|url\([^)]*\)|var\((?:${viewportWidthProperty}|${viewportHeightProperty}|${remProperty}),[^)]*\)`, "g");
|
|
168
160
|
// ../core/src/modes.ts
|
|
169
161
|
const variableKinds = [
|
|
170
162
|
"viewport",
|
|
@@ -172,87 +164,69 @@ const variableKinds = [
|
|
|
172
164
|
"locale",
|
|
173
165
|
"custom"
|
|
174
166
|
];
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
|
218
218
|
});
|
|
219
|
-
if (options.
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const modes = expectArray(settings.modes, "/modes").map((item, index) => {
|
|
228
|
-
const at = `/modes/${index}`;
|
|
229
|
-
const mode = expectObject(item, at);
|
|
230
|
-
expectKnownKeys(mode, [
|
|
231
|
-
"id",
|
|
232
|
-
"name",
|
|
233
|
-
"values"
|
|
234
|
-
], at);
|
|
235
|
-
const values = {};
|
|
236
|
-
for (const [variableId, optionId] of Object.entries(expectObject(mode.values, `${at}/values`))) {
|
|
237
|
-
const variable = variables.find((candidate) => candidate.id === variableId);
|
|
238
|
-
if (!variable) throw new TypeError(`${at}/values/${variableId}: unknown variable`);
|
|
239
|
-
const option = expectString(optionId, `${at}/values/${variableId}`);
|
|
240
|
-
if (!variable.options.some((candidate) => candidate.id === option)) throw new TypeError(`${at}/values/${variableId}: "${option}" is not an option of ${variable.name}`);
|
|
241
|
-
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
|
+
});
|
|
242
227
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
name: expectString(mode.name, `${at}/name`),
|
|
246
|
-
values
|
|
247
|
-
};
|
|
248
|
-
});
|
|
249
|
-
if (modes.length === 0) throw new TypeError("/modes: expected at least one mode");
|
|
250
|
-
return {
|
|
251
|
-
version: 1,
|
|
252
|
-
variables,
|
|
253
|
-
modes
|
|
254
|
-
};
|
|
255
|
-
}
|
|
228
|
+
}
|
|
229
|
+
});
|
|
256
230
|
// ../core/src/edit.ts
|
|
257
231
|
const idAlphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
258
232
|
/** A short random id that isn't in `taken`, for nodes and designs created in the editor or by agents. */
|
|
@@ -264,16 +238,45 @@ function generateId(taken, length = 5) {
|
|
|
264
238
|
}
|
|
265
239
|
}
|
|
266
240
|
// ../core/src/document.ts
|
|
241
|
+
const position = fileObject({
|
|
242
|
+
x: finiteNumber,
|
|
243
|
+
y: finiteNumber
|
|
244
|
+
});
|
|
267
245
|
const canvasObjectKinds = [
|
|
268
246
|
"table",
|
|
269
247
|
"operation",
|
|
270
248
|
"route"
|
|
271
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
|
+
});
|
|
272
265
|
const linkEndKinds = [
|
|
273
266
|
"screen",
|
|
274
267
|
"component",
|
|
275
268
|
...canvasObjectKinds
|
|
276
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
|
+
]);
|
|
277
280
|
/** Each type of link, and the kinds it may join, from and to. */
|
|
278
281
|
const linkTypes = {
|
|
279
282
|
/** A screen shown at a route's URL. */
|
|
@@ -306,30 +309,301 @@ function linkAllowed(type, from, to) {
|
|
|
306
309
|
const allowed = linkTypes[type];
|
|
307
310
|
return allowed.from.includes(from) && allowed.to.includes(to);
|
|
308
311
|
}
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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"
|
|
313
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
|
+
}
|
|
314
589
|
const containerKinds = [
|
|
315
590
|
"screen",
|
|
316
591
|
"component",
|
|
317
592
|
"frame"
|
|
318
593
|
];
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
];
|
|
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
|
+
});
|
|
333
607
|
const paramTypes = [
|
|
334
608
|
"text",
|
|
335
609
|
"boolean",
|
|
@@ -337,596 +611,178 @@ const paramTypes = [
|
|
|
337
611
|
"value",
|
|
338
612
|
"variant"
|
|
339
613
|
];
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
"center",
|
|
349
|
-
"end",
|
|
350
|
-
"between",
|
|
351
|
-
"around",
|
|
352
|
-
"evenly"
|
|
353
|
-
];
|
|
354
|
-
/** Every style key, in the order panels show them. */
|
|
355
|
-
const styleKeys = [
|
|
356
|
-
"display",
|
|
357
|
-
"direction",
|
|
358
|
-
"columns",
|
|
359
|
-
"rows",
|
|
360
|
-
"gap",
|
|
361
|
-
"rowGap",
|
|
362
|
-
"padding",
|
|
363
|
-
"margin",
|
|
364
|
-
"align",
|
|
365
|
-
"justify",
|
|
366
|
-
"wrap",
|
|
367
|
-
"width",
|
|
368
|
-
"height",
|
|
369
|
-
"minWidth",
|
|
370
|
-
"maxWidth",
|
|
371
|
-
"minHeight",
|
|
372
|
-
"maxHeight",
|
|
373
|
-
"colSpan",
|
|
374
|
-
"rowSpan",
|
|
375
|
-
"grow",
|
|
376
|
-
"self",
|
|
377
|
-
"position",
|
|
378
|
-
"top",
|
|
379
|
-
"right",
|
|
380
|
-
"bottom",
|
|
381
|
-
"left",
|
|
382
|
-
"background",
|
|
383
|
-
"foreground",
|
|
384
|
-
"border",
|
|
385
|
-
"borderWidth",
|
|
386
|
-
"borderStyle",
|
|
387
|
-
"radius",
|
|
388
|
-
"shadow",
|
|
389
|
-
"opacity",
|
|
390
|
-
"overflow",
|
|
391
|
-
"font",
|
|
392
|
-
"size",
|
|
393
|
-
"weight",
|
|
394
|
-
"leading",
|
|
395
|
-
"tracking",
|
|
396
|
-
"textAlign",
|
|
397
|
-
"transform",
|
|
398
|
-
"decoration",
|
|
399
|
-
"italic",
|
|
400
|
-
"lines"
|
|
401
|
-
];
|
|
402
|
-
function parseCanvasDocument(value) {
|
|
403
|
-
const document = expectObject(value, "");
|
|
404
|
-
if (document.version !== 3) throw new TypeError("/version: expected 3");
|
|
405
|
-
expectKnownKeys(document, [
|
|
406
|
-
"version",
|
|
407
|
-
"containers",
|
|
408
|
-
"notes",
|
|
409
|
-
"modes",
|
|
410
|
-
"detection",
|
|
411
|
-
"objects",
|
|
412
|
-
"links",
|
|
413
|
-
"sources"
|
|
414
|
-
], "");
|
|
415
|
-
return compact({
|
|
416
|
-
version: 3,
|
|
417
|
-
containers: expectArray(document.containers, "/containers").map((item, index) => parseContainer(item, `/containers/${index}`)),
|
|
418
|
-
modes: optional(document.modes, (value) => parseModeSettings(value)),
|
|
419
|
-
notes: optional(document.notes, (value) => expectArray(value, "/notes").map((item, index) => parseNote(item, `/notes/${index}`))),
|
|
420
|
-
detection: optional(document.detection, (value) => parseDetection(value, "/detection")),
|
|
421
|
-
objects: optional(document.objects, (value) => parseObjects(value, "/objects")),
|
|
422
|
-
links: optional(document.links, (value) => parseLinks(value, "/links")),
|
|
423
|
-
sources: optional(document.sources, (value) => parseSources(value, "/sources"))
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
function parsePosition(value, path) {
|
|
427
|
-
const position = expectObject(value, path);
|
|
428
|
-
expectKnownKeys(position, ["x", "y"], path);
|
|
429
|
-
return {
|
|
430
|
-
x: expectNumber(position.x, `${path}/x`),
|
|
431
|
-
y: expectNumber(position.y, `${path}/y`)
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
function parseObjects(value, path) {
|
|
435
|
-
const placed = /* @__PURE__ */ new Set();
|
|
436
|
-
return expectArray(value, path).map((item, index) => {
|
|
437
|
-
const at = `${path}/${index}`;
|
|
438
|
-
const object = expectObject(item, at);
|
|
439
|
-
expectKnownKeys(object, [
|
|
440
|
-
"kind",
|
|
441
|
-
"ref",
|
|
442
|
-
"position"
|
|
443
|
-
], at);
|
|
444
|
-
const kind = expectOneOf(object.kind, canvasObjectKinds, `${at}/kind`);
|
|
445
|
-
const ref = expectString(object.ref, `${at}/ref`);
|
|
446
|
-
const key = JSON.stringify([kind, ref]);
|
|
447
|
-
if (placed.has(key)) throw new TypeError(`${at}: the ${kind} "${ref}" is already on the canvas`);
|
|
448
|
-
placed.add(key);
|
|
449
|
-
return {
|
|
450
|
-
kind,
|
|
451
|
-
ref,
|
|
452
|
-
position: parsePosition(object.position, `${at}/position`)
|
|
453
|
-
};
|
|
454
|
-
});
|
|
455
|
-
}
|
|
456
|
-
function parseLinkEnd(value, path) {
|
|
457
|
-
const end = expectObject(value, path);
|
|
458
|
-
expectKnownKeys(end, ["kind", "ref"], path);
|
|
459
|
-
return {
|
|
460
|
-
kind: expectOneOf(end.kind, linkEndKinds, `${path}/kind`),
|
|
461
|
-
ref: expectString(end.ref, `${path}/ref`)
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
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) => {
|
|
465
622
|
const ids = /* @__PURE__ */ new Set();
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
"type",
|
|
473
|
-
"from",
|
|
474
|
-
"to"
|
|
475
|
-
], at);
|
|
476
|
-
const id = expectString(link.id, `${at}/id`);
|
|
477
|
-
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}"`);
|
|
478
629
|
ids.add(id);
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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
|
-
const
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
"name",
|
|
538
|
-
"kind",
|
|
539
|
-
"position",
|
|
540
|
-
"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, [
|
|
541
688
|
"states",
|
|
542
|
-
|
|
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, [
|
|
543
695
|
"params",
|
|
544
|
-
|
|
545
|
-
"
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
],
|
|
558
|
-
|
|
559
|
-
mode: optional(settings.mode, (mode) => expectString(mode, `${path}/view/mode`)),
|
|
560
|
-
modes: optional(settings.modes, (list) => expectArray(list, `${path}/view/modes`).map((mode, index) => expectString(mode, `${path}/view/modes/${index}`))),
|
|
561
|
-
state: optional(settings.state, (state) => expectString(state, `${path}/view/state`)),
|
|
562
|
-
allStates: optional(settings.allStates, (all) => expectBoolean(all, `${path}/view/allStates`)),
|
|
563
|
-
variants: optional(settings.variants, (choices) => {
|
|
564
|
-
const variants = {};
|
|
565
|
-
for (const [param, option] of Object.entries(expectObject(choices, `${path}/view/variants`))) variants[param] = expectString(option, `${path}/view/variants/${param}`);
|
|
566
|
-
return variants;
|
|
567
|
-
})
|
|
568
|
-
});
|
|
569
|
-
});
|
|
570
|
-
const ids = /* @__PURE__ */ new Set();
|
|
571
|
-
const root = parseNode(container.root, `${path}/root`, ids);
|
|
572
|
-
const states = expectArray(container.states, `${path}/states`).map((item, index) => {
|
|
573
|
-
const at = `${path}/states/${index}`;
|
|
574
|
-
const state = expectObject(item, at);
|
|
575
|
-
expectKnownKeys(state, [
|
|
576
|
-
"id",
|
|
577
|
-
"name",
|
|
578
|
-
"overrides"
|
|
579
|
-
], at);
|
|
580
|
-
return {
|
|
581
|
-
id: expectString(state.id, `${at}/id`),
|
|
582
|
-
name: expectString(state.name, `${at}/name`),
|
|
583
|
-
overrides: optional(state.overrides, (v) => parsePatches(v, `${at}/overrides`, ids))
|
|
584
|
-
};
|
|
585
|
-
});
|
|
586
|
-
if (states.length === 0) throw new TypeError(`${path}/states: expected at least one state`);
|
|
587
|
-
const kind = optional(container.kind, (v) => expectOneOf(v, containerKinds, `${path}/kind`));
|
|
588
|
-
if (kind === "frame") checkFrame(container, root, states, path);
|
|
589
|
-
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`);
|
|
590
|
-
return compact({
|
|
591
|
-
id: expectString(container.id, `${path}/id`),
|
|
592
|
-
name: expectString(container.name, `${path}/name`),
|
|
593
|
-
kind,
|
|
594
|
-
context: optional(container.context, (v) => expectArray(v, `${path}/context`).map((item, index) => {
|
|
595
|
-
const at = `${path}/context/${index}`;
|
|
596
|
-
const wrapper = expectObject(item, at);
|
|
597
|
-
expectKnownKeys(wrapper, ["component", "props"], at);
|
|
598
|
-
return compact({
|
|
599
|
-
component: expectString(wrapper.component, `${at}/component`),
|
|
600
|
-
props: optional(wrapper.props, (p) => expectObject(p, `${at}/props`))
|
|
601
|
-
});
|
|
602
|
-
})),
|
|
603
|
-
params: optional(container.params, (v) => {
|
|
604
|
-
const params = {};
|
|
605
|
-
for (const [name, entry] of Object.entries(expectObject(v, `${path}/params`))) {
|
|
606
|
-
const at = `${path}/params/${name}`;
|
|
607
|
-
const param = expectObject(entry, at);
|
|
608
|
-
expectKnownKeys(param, [
|
|
609
|
-
"type",
|
|
610
|
-
"default",
|
|
611
|
-
"description",
|
|
612
|
-
"options"
|
|
613
|
-
], at);
|
|
614
|
-
const type = expectOneOf(param.type, paramTypes, `${at}/type`);
|
|
615
|
-
const options = optional(param.options, (list) => {
|
|
616
|
-
const seen = /* @__PURE__ */ new Set();
|
|
617
|
-
return expectArray(list, `${at}/options`).map((item, index) => {
|
|
618
|
-
const where = `${at}/options/${index}`;
|
|
619
|
-
const option = expectObject(item, where);
|
|
620
|
-
expectKnownKeys(option, [
|
|
621
|
-
"id",
|
|
622
|
-
"name",
|
|
623
|
-
"overrides"
|
|
624
|
-
], where);
|
|
625
|
-
const id = expectString(option.id, `${where}/id`);
|
|
626
|
-
if (seen.has(id)) throw new TypeError(`${where}/id: duplicate option id "${id}"`);
|
|
627
|
-
seen.add(id);
|
|
628
|
-
return compact({
|
|
629
|
-
id,
|
|
630
|
-
name: expectString(option.name, `${where}/name`),
|
|
631
|
-
overrides: optional(option.overrides, (v) => parsePatches(v, `${where}/overrides`, ids))
|
|
632
|
-
});
|
|
633
|
-
});
|
|
634
|
-
});
|
|
635
|
-
if (type === "variant" && !options?.length) throw new TypeError(`${at}/options: a variant needs options`);
|
|
636
|
-
if (type !== "variant" && options) throw new TypeError(`${at}/options: only variants have options`);
|
|
637
|
-
params[name] = compact({
|
|
638
|
-
type,
|
|
639
|
-
default: param.default,
|
|
640
|
-
description: optional(param.description, (d) => expectString(d, `${at}/description`)),
|
|
641
|
-
options
|
|
642
|
-
});
|
|
643
|
-
}
|
|
644
|
-
return params;
|
|
645
|
-
}),
|
|
646
|
-
frames: optional(container.frames, (v) => {
|
|
647
|
-
const frames = {};
|
|
648
|
-
for (const [viewport, entry] of Object.entries(expectObject(v, `${path}/frames`))) frames[viewport] = parseFrameSize(entry, `${path}/frames/${viewport}`);
|
|
649
|
-
return frames;
|
|
650
|
-
}),
|
|
651
|
-
size: optional(container.size, (v) => parseFrameSize(v, `${path}/size`)),
|
|
652
|
-
position: {
|
|
653
|
-
x: expectNumber(position.x, `${path}/position/x`),
|
|
654
|
-
y: expectNumber(position.y, `${path}/position/y`)
|
|
655
|
-
},
|
|
656
|
-
root,
|
|
657
|
-
states,
|
|
658
|
-
view
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
function parseFrameSize(value, path) {
|
|
662
|
-
const size = expectObject(value, path);
|
|
663
|
-
expectKnownKeys(size, ["width", "height"], path);
|
|
664
|
-
return compact({
|
|
665
|
-
width: optional(size.width, (w) => expectNumber(w, `${path}/width`)),
|
|
666
|
-
height: optional(size.height, (h) => expectNumber(h, `${path}/height`))
|
|
667
|
-
});
|
|
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
|
+
}
|
|
668
711
|
}
|
|
669
712
|
/**
|
|
670
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
|
|
671
714
|
* shows only the primary mode and that state) and no sizes per viewport, since it has one of its own.
|
|
672
715
|
*/
|
|
673
|
-
function checkFrame(
|
|
674
|
-
if (root.type !== "layout")
|
|
675
|
-
if (states.length > 1)
|
|
676
|
-
if (Object.keys(states[0]
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
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");
|
|
680
727
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
"
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
"instance",
|
|
688
|
-
"image"
|
|
689
|
-
], `${path}/type`);
|
|
690
|
-
const id = expectString(node.id, `${path}/id`);
|
|
691
|
-
if (ids.has(id)) throw new TypeError(`${path}/id: duplicate node id "${id}"`);
|
|
692
|
-
ids.add(id);
|
|
693
|
-
const base = compact({
|
|
694
|
-
id,
|
|
695
|
-
name: optional(node.name, (v) => expectString(v, `${path}/name`)),
|
|
696
|
-
hidden: optional(node.hidden, (v) => expectBoolean(v, `${path}/hidden`)),
|
|
697
|
-
style: optional(node.style, (v) => parseStyle(v, `${path}/style`)),
|
|
698
|
-
responsive: optional(node.responsive, (v) => parsePatches(v, `${path}/responsive`)),
|
|
699
|
-
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
|
|
700
734
|
});
|
|
701
|
-
const common = [
|
|
702
|
-
"type",
|
|
703
|
-
"id",
|
|
704
|
-
"name",
|
|
705
|
-
"hidden",
|
|
706
|
-
"style",
|
|
707
|
-
"responsive",
|
|
708
|
-
"bind"
|
|
709
|
-
];
|
|
710
|
-
switch (type) {
|
|
711
|
-
case "layout":
|
|
712
|
-
expectKnownKeys(node, [...common, "children"], path);
|
|
713
|
-
return {
|
|
714
|
-
...base,
|
|
715
|
-
type,
|
|
716
|
-
children: expectArray(node.children, `${path}/children`).map((child, index) => parseNode(child, `${path}/children/${index}`, ids))
|
|
717
|
-
};
|
|
718
|
-
case "component": {
|
|
719
|
-
expectKnownKeys(node, [
|
|
720
|
-
...common,
|
|
721
|
-
"component",
|
|
722
|
-
"props",
|
|
723
|
-
"slots"
|
|
724
|
-
], path);
|
|
725
|
-
const slots = optional(node.slots, (v) => {
|
|
726
|
-
const result = {};
|
|
727
|
-
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));
|
|
728
|
-
return result;
|
|
729
|
-
});
|
|
730
|
-
return {
|
|
731
|
-
...base,
|
|
732
|
-
type,
|
|
733
|
-
component: expectString(node.component, `${path}/component`),
|
|
734
|
-
props: optional(node.props, (v) => expectObject(v, `${path}/props`)),
|
|
735
|
-
slots
|
|
736
|
-
};
|
|
737
|
-
}
|
|
738
|
-
case "instance":
|
|
739
|
-
expectKnownKeys(node, [
|
|
740
|
-
...common,
|
|
741
|
-
"design",
|
|
742
|
-
"args",
|
|
743
|
-
"state",
|
|
744
|
-
"overrides"
|
|
745
|
-
], path);
|
|
746
|
-
return compact({
|
|
747
|
-
...base,
|
|
748
|
-
type,
|
|
749
|
-
design: expectString(node.design, `${path}/design`),
|
|
750
|
-
args: optional(node.args, (v) => expectObject(v, `${path}/args`)),
|
|
751
|
-
state: optional(node.state, (v) => expectString(v, `${path}/state`)),
|
|
752
|
-
overrides: optional(node.overrides, (v) => parsePatches(v, `${path}/overrides`))
|
|
753
|
-
});
|
|
754
|
-
case "text":
|
|
755
|
-
expectKnownKeys(node, [
|
|
756
|
-
...common,
|
|
757
|
-
"text",
|
|
758
|
-
"tag"
|
|
759
|
-
], path);
|
|
760
|
-
return {
|
|
761
|
-
...base,
|
|
762
|
-
type,
|
|
763
|
-
text: expectString(node.text, `${path}/text`),
|
|
764
|
-
tag: optional(node.tag, (v) => expectOneOf(v, textTags, `${path}/tag`))
|
|
765
|
-
};
|
|
766
|
-
case "image":
|
|
767
|
-
expectKnownKeys(node, [
|
|
768
|
-
...common,
|
|
769
|
-
"src",
|
|
770
|
-
"alt",
|
|
771
|
-
"fit",
|
|
772
|
-
"credit"
|
|
773
|
-
], path);
|
|
774
|
-
return compact({
|
|
775
|
-
...base,
|
|
776
|
-
type,
|
|
777
|
-
src: expectString(node.src, `${path}/src`),
|
|
778
|
-
alt: optional(node.alt, (v) => expectString(v, `${path}/alt`)),
|
|
779
|
-
fit: optional(node.fit, (v) => expectOneOf(v, imageFits, `${path}/fit`)),
|
|
780
|
-
credit: optional(node.credit, (v) => {
|
|
781
|
-
const credit = expectObject(v, `${path}/credit`);
|
|
782
|
-
expectKnownKeys(credit, [
|
|
783
|
-
"name",
|
|
784
|
-
"url",
|
|
785
|
-
"source"
|
|
786
|
-
], `${path}/credit`);
|
|
787
|
-
return compact({
|
|
788
|
-
name: expectString(credit.name, `${path}/credit/name`),
|
|
789
|
-
url: expectString(credit.url, `${path}/credit/url`),
|
|
790
|
-
source: optional(credit.source, (s) => expectString(s, `${path}/credit/source`))
|
|
791
|
-
});
|
|
792
|
-
})
|
|
793
|
-
});
|
|
794
|
-
}
|
|
795
735
|
}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
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);
|
|
812
755
|
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
function parsePatches(value, path, ids) {
|
|
818
|
-
const patches = {};
|
|
819
|
-
for (const [key, entry] of Object.entries(expectObject(value, path))) {
|
|
820
|
-
if (ids && !ids.has(key.split("/")[0])) throw new TypeError(`${path}/${key}: no node has this id`);
|
|
821
|
-
patches[key] = parsePatch(entry, `${path}/${key}`);
|
|
822
|
-
}
|
|
823
|
-
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;
|
|
824
760
|
}
|
|
825
|
-
function
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
"hidden",
|
|
829
|
-
"text",
|
|
830
|
-
"props",
|
|
831
|
-
"style",
|
|
832
|
-
"unset"
|
|
833
|
-
], path);
|
|
834
|
-
return compact({
|
|
835
|
-
hidden: optional(patch.hidden, (v) => expectBoolean(v, `${path}/hidden`)),
|
|
836
|
-
text: optional(patch.text, (v) => expectString(v, `${path}/text`)),
|
|
837
|
-
props: optional(patch.props, (v) => expectObject(v, `${path}/props`)),
|
|
838
|
-
style: optional(patch.style, (v) => parseStyle(v, `${path}/style`)),
|
|
839
|
-
unset: optional(patch.unset, (v) => parseUnset(v, `${path}/unset`))
|
|
840
|
-
});
|
|
761
|
+
function expectString(value, path) {
|
|
762
|
+
if (typeof value !== "string") throw new TypeError(`${path}: expected a string`);
|
|
763
|
+
return value;
|
|
841
764
|
}
|
|
842
|
-
function
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
const keys = (key, item) => optional(unset[key], (v) => expectArray(v, `${path}/${key}`).map((entry, index) => item(entry, `${path}/${key}/${index}`)));
|
|
846
|
-
return compact({
|
|
847
|
-
props: keys("props", expectString),
|
|
848
|
-
style: keys("style", (v, where) => expectOneOf(v, styleKeys, where))
|
|
849
|
-
});
|
|
765
|
+
function expectNumber(value, path) {
|
|
766
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw new TypeError(`${path}: expected a number`);
|
|
767
|
+
return value;
|
|
850
768
|
}
|
|
851
|
-
function
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
direction: optional(style.direction, (v) => expectOneOf(v, ["row", "column"], at("direction"))),
|
|
869
|
-
columns: number("columns"),
|
|
870
|
-
rows: number("rows"),
|
|
871
|
-
gap: length("gap"),
|
|
872
|
-
rowGap: length("rowGap"),
|
|
873
|
-
padding: sides("padding"),
|
|
874
|
-
margin: sides("margin"),
|
|
875
|
-
align: optional(style.align, (v) => expectOneOf(v, alignments, at("align"))),
|
|
876
|
-
justify: optional(style.justify, (v) => expectOneOf(v, justifications, at("justify"))),
|
|
877
|
-
wrap: flag("wrap"),
|
|
878
|
-
width: size("width"),
|
|
879
|
-
height: size("height"),
|
|
880
|
-
minWidth: length("minWidth"),
|
|
881
|
-
maxWidth: length("maxWidth"),
|
|
882
|
-
minHeight: length("minHeight"),
|
|
883
|
-
maxHeight: length("maxHeight"),
|
|
884
|
-
colSpan: number("colSpan"),
|
|
885
|
-
rowSpan: number("rowSpan"),
|
|
886
|
-
grow: flag("grow"),
|
|
887
|
-
self: optional(style.self, (v) => expectOneOf(v, alignments, at("self"))),
|
|
888
|
-
position: optional(style.position, (v) => expectOneOf(v, ["absolute"], at("position"))),
|
|
889
|
-
top: length("top"),
|
|
890
|
-
right: length("right"),
|
|
891
|
-
bottom: length("bottom"),
|
|
892
|
-
left: length("left"),
|
|
893
|
-
background: token("background"),
|
|
894
|
-
foreground: token("foreground"),
|
|
895
|
-
border: token("border"),
|
|
896
|
-
borderWidth: number("borderWidth"),
|
|
897
|
-
borderStyle: optional(style.borderStyle, (v) => expectOneOf(v, [
|
|
898
|
-
"solid",
|
|
899
|
-
"dashed",
|
|
900
|
-
"dotted"
|
|
901
|
-
], at("borderStyle"))),
|
|
902
|
-
radius: token("radius"),
|
|
903
|
-
shadow: token("shadow"),
|
|
904
|
-
opacity: number("opacity"),
|
|
905
|
-
overflow: optional(style.overflow, (v) => expectOneOf(v, [
|
|
906
|
-
"visible",
|
|
907
|
-
"hidden",
|
|
908
|
-
"scroll"
|
|
909
|
-
], at("overflow"))),
|
|
910
|
-
font: token("font"),
|
|
911
|
-
size: token("size"),
|
|
912
|
-
weight: number("weight"),
|
|
913
|
-
leading: token("leading"),
|
|
914
|
-
tracking: token("tracking"),
|
|
915
|
-
textAlign: optional(style.textAlign, (v) => expectOneOf(v, [
|
|
916
|
-
"start",
|
|
917
|
-
"center",
|
|
918
|
-
"end",
|
|
919
|
-
"justify"
|
|
920
|
-
], at("textAlign"))),
|
|
921
|
-
transform: optional(style.transform, (v) => expectOneOf(v, [
|
|
922
|
-
"uppercase",
|
|
923
|
-
"lowercase",
|
|
924
|
-
"capitalize"
|
|
925
|
-
], at("transform"))),
|
|
926
|
-
decoration: optional(style.decoration, (v) => expectOneOf(v, ["underline", "line-through"], at("decoration"))),
|
|
927
|
-
italic: flag("italic"),
|
|
928
|
-
lines: number("lines")
|
|
929
|
-
});
|
|
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`);
|
|
930
786
|
}
|
|
931
787
|
// ../core/src/library.ts
|
|
932
788
|
/** How a component was recognised, which is also why it's believed to be one. */
|
|
@@ -1341,7 +1197,8 @@ function expectStringRecord(value, path) {
|
|
|
1341
1197
|
const named = /* @__PURE__ */ new WeakSet();
|
|
1342
1198
|
/**
|
|
1343
1199
|
* A new node for a component, ready to show something: required props get their default or first option, and a
|
|
1344
|
-
* component that takes children gets its own name as text, so it isn't an empty box, unless `given` has its own
|
|
1200
|
+
* component that takes children gets its own name as text, so it isn't an empty box, unless `given` has its own or the
|
|
1201
|
+
* component sets its own size (`fixedSize`), where the name would be cut off ("Avata").
|
|
1345
1202
|
*/
|
|
1346
1203
|
function componentNode(component, info, taken, given = {}) {
|
|
1347
1204
|
const props = { ...given };
|
|
@@ -1353,7 +1210,7 @@ function componentNode(component, info, taken, given = {}) {
|
|
|
1353
1210
|
else if (prop.kind === "number") props[prop.name] = 0;
|
|
1354
1211
|
else if (prop.kind === "string") props[prop.name] = prop.name;
|
|
1355
1212
|
}
|
|
1356
|
-
const labelled = !("children" in given) && info?.props.some((prop) => prop.name === "children" && prop.kind === "node");
|
|
1213
|
+
const labelled = !("children" in given) && !info?.fixedSize && info?.props.some((prop) => prop.name === "children" && prop.kind === "node");
|
|
1357
1214
|
if (labelled) props.children = component.split("/").at(-1) ?? component;
|
|
1358
1215
|
const node = {
|
|
1359
1216
|
type: "component",
|
|
@@ -1434,10 +1291,10 @@ function withLibraryContext(node, sources, around, taken) {
|
|
|
1434
1291
|
};
|
|
1435
1292
|
};
|
|
1436
1293
|
let placed = node;
|
|
1437
|
-
const sample = source.sample;
|
|
1294
|
+
const sample = source.sample ?? (named.has(node) ? usedIn(node.component, sources) : void 0);
|
|
1438
1295
|
if (sample) {
|
|
1439
1296
|
const given = { ...node.props };
|
|
1440
|
-
if (
|
|
1297
|
+
if (named.has(node)) delete given.children;
|
|
1441
1298
|
const { props, slots } = held({
|
|
1442
1299
|
...sample.props,
|
|
1443
1300
|
...given
|
|
@@ -1514,6 +1371,39 @@ function withLibraryContext(node, sources, around, taken) {
|
|
|
1514
1371
|
}
|
|
1515
1372
|
}
|
|
1516
1373
|
/**
|
|
1374
|
+
* How a part with no sample of its own is used in its library's other samples: with the children the first use gives it,
|
|
1375
|
+
* or none when it's used without any (`<MarkerIcon />`) or with elements the sample can't show (an icon, `[]`).
|
|
1376
|
+
* Undefined when no sample uses it.
|
|
1377
|
+
*/
|
|
1378
|
+
function usedIn(component, sources) {
|
|
1379
|
+
const find = (children) => {
|
|
1380
|
+
for (const child of children ?? []) {
|
|
1381
|
+
if (typeof child === "string") continue;
|
|
1382
|
+
if (isSampleRender(child)) {
|
|
1383
|
+
const found = find(child.$render);
|
|
1384
|
+
if (found) return found;
|
|
1385
|
+
continue;
|
|
1386
|
+
}
|
|
1387
|
+
if (child.component === component) return child;
|
|
1388
|
+
const found = find(child.children) ?? find(elementProps(child.props));
|
|
1389
|
+
if (found) return found;
|
|
1390
|
+
}
|
|
1391
|
+
};
|
|
1392
|
+
const library = sources[component]?.library;
|
|
1393
|
+
for (const source of Object.values(sources)) {
|
|
1394
|
+
if (source.library !== library || !source.sample) continue;
|
|
1395
|
+
const found = find(source.sample.children) ?? find(elementProps(source.sample.props));
|
|
1396
|
+
if (found) return { children: found.children ?? [] };
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
/** The elements and render functions a sample gives as props' values, as children are. */
|
|
1400
|
+
function elementProps(props) {
|
|
1401
|
+
return Object.values(props ?? {}).flatMap((value) => {
|
|
1402
|
+
if (isSampleRender(value)) return [value];
|
|
1403
|
+
return isSampleElement(value) ? value.$element : [];
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
/**
|
|
1517
1407
|
* A library file's component as the canvas inserts it, with its parents and sample named by component id.
|
|
1518
1408
|
* Discovery has already rewritten module imports for the name the project installed the package under.
|
|
1519
1409
|
*/
|
|
@@ -1564,4 +1454,4 @@ function sampleIds(sample, library) {
|
|
|
1564
1454
|
...sample.children ? { children: sample.children.map(child) } : {}
|
|
1565
1455
|
};
|
|
1566
1456
|
}
|
|
1567
|
-
export { deviceKinds as A,
|
|
1457
|
+
export { deviceKinds as A, walkNodes as C, remProperty as D, collectCustomProperties as E, packageManifest as M, viewportHeightProperty as O, parseCanvasDocument as S, modeSettings as T, imageFits as _, applyLibraryEdits as a, linkTypes as b, isSampleElement as c, parentSignals as d, parseLibraryEdits as f, containerKinds as g, canvasObjectKinds as h, withLibraryContext as i, lengthToPixels as j, viewportWidthProperty as k, isSampleRender as l, renderSlotOf as m, librarySource as n, componentEvidence as o, parseLibraryManifest as p, sampleIds as r, isMadeValue as s, componentNode as t, libraryManifestExtension as u, linkAllowed as v, generateId as w, paramTypes as x, linkEndKinds as y };
|