machinalayout 0.1.0 → 0.3.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/README.md +295 -49
- package/dist/chunk-2ZQ2RFFI.js +400 -0
- package/dist/chunk-33CKBEJH.js +186 -0
- package/dist/chunk-BJOQRPPX.js +382 -0
- package/dist/chunk-KYWOCAHK.js +205 -0
- package/dist/chunk-RJYRJ3LD.js +0 -0
- package/dist/chunk-SVWYWI7I.js +59 -0
- package/dist/chunk-VREK57S3.js +13 -0
- package/dist/chunk-ZVDE7PX4.js +222 -0
- package/dist/debugOverlay-pJpj0n5H.d.ts +125 -0
- package/dist/deus/index.d.ts +14 -0
- package/dist/deus/index.js +26 -0
- package/dist/dispatch/index.d.ts +49 -0
- package/dist/dispatch/index.js +217 -0
- package/dist/handoff/index.d.ts +44 -0
- package/dist/handoff/index.js +83 -0
- package/dist/index.d.ts +54 -236
- package/dist/index.js +753 -583
- package/dist/inspect/index.d.ts +8 -0
- package/dist/inspect/index.js +97 -0
- package/dist/react/index.d.ts +41 -0
- package/dist/react/index.js +9 -0
- package/dist/react-native/index.d.ts +30 -0
- package/dist/react-native/index.js +84 -0
- package/dist/screenCatalog-ZjonGiOi.d.ts +46 -0
- package/dist/text/index.d.ts +10 -0
- package/dist/text/index.js +9 -0
- package/dist/text/react/index.d.ts +14 -0
- package/dist/text/react/index.js +7 -0
- package/dist/text/react-native/index.d.ts +16 -0
- package/dist/text/react-native/index.js +155 -0
- package/dist/text/vue/index.d.ts +113 -0
- package/dist/text/vue/index.js +202 -0
- package/dist/types-B90jb3RW.d.ts +184 -0
- package/dist/types-C4poVJpR.d.ts +74 -0
- package/dist/types-DLYAhNXw.d.ts +32 -0
- package/dist/vue/index.d.ts +173 -0
- package/dist/vue/index.js +112 -0
- package/docs/adapter-packaging-a0-plan.md +352 -0
- package/docs/adapters.md +19 -0
- package/docs/api-coherence-m8-audit.md +397 -0
- package/docs/deusmachina.md +108 -0
- package/docs/error-codes.md +95 -0
- package/docs/grid-arrange-m5a-contract.md +480 -0
- package/docs/grid-arrange.md +51 -0
- package/docs/inspection-and-handoff.md +126 -0
- package/docs/layout-interpolation.md +52 -0
- package/docs/machina-dispatch-d0-contract.md +496 -0
- package/docs/machina-dispatch.md +143 -0
- package/docs/named-layers.md +40 -0
- package/docs/react-adapter.md +63 -58
- package/docs/react-native-adapter.md +56 -0
- package/docs/react-native-text-renderer.md +50 -0
- package/docs/reference-alignment-m7a-contract.md +384 -0
- package/docs/reference-alignment.md +44 -0
- package/docs/responsive-variants.md +54 -0
- package/docs/screen-catalog-and-viewports.md +124 -0
- package/docs/stack-geometry-helpers.md +115 -0
- package/docs/vue-adapter.md +55 -0
- package/docs/vue-text-renderer.md +55 -0
- package/package.json +127 -60
package/dist/index.js
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import {
|
|
2
|
+
createViewportMatrix,
|
|
3
|
+
defineMachinaScreens,
|
|
4
|
+
defineMachinaViewports,
|
|
5
|
+
expandScreenViewportTasks,
|
|
6
|
+
getMachinaViewport,
|
|
7
|
+
slugMachinaArtifactName
|
|
8
|
+
} from "./chunk-33CKBEJH.js";
|
|
9
|
+
import {
|
|
10
|
+
MachinaReactView
|
|
11
|
+
} from "./chunk-ZVDE7PX4.js";
|
|
12
|
+
import "./chunk-2ZQ2RFFI.js";
|
|
13
|
+
import "./chunk-RJYRJ3LD.js";
|
|
14
|
+
import {
|
|
15
|
+
MachinaTextView
|
|
16
|
+
} from "./chunk-KYWOCAHK.js";
|
|
17
|
+
import {
|
|
18
|
+
parseMachinaText,
|
|
19
|
+
parseMachinaTextInline
|
|
20
|
+
} from "./chunk-BJOQRPPX.js";
|
|
21
|
+
import {
|
|
22
|
+
toResolvedTree
|
|
23
|
+
} from "./chunk-SVWYWI7I.js";
|
|
24
|
+
import {
|
|
25
|
+
MachinaLayoutError
|
|
26
|
+
} from "./chunk-VREK57S3.js";
|
|
10
27
|
|
|
11
28
|
// src/validation.ts
|
|
12
29
|
function assertFiniteNumber(value, fieldName) {
|
|
@@ -83,7 +100,10 @@ function resolveUiLength(length, axisSize, fieldName = "length") {
|
|
|
83
100
|
if (unit === "ui") {
|
|
84
101
|
return value * axisSize;
|
|
85
102
|
}
|
|
86
|
-
throw new MachinaLayoutError(
|
|
103
|
+
throw new MachinaLayoutError(
|
|
104
|
+
"InvalidLengthUnit",
|
|
105
|
+
`Invalid UiLength unit for ${fieldName}: ${String(unit)}.`
|
|
106
|
+
);
|
|
87
107
|
}
|
|
88
108
|
|
|
89
109
|
// src/offset.ts
|
|
@@ -118,12 +138,18 @@ function compileLayoutRows(rows) {
|
|
|
118
138
|
assertFiniteNumber(row.order, `rows[${rowIndex}].order`);
|
|
119
139
|
}
|
|
120
140
|
if (row.frame.kind === "root" && row.parent !== void 0) {
|
|
121
|
-
throw new MachinaLayoutError(
|
|
141
|
+
throw new MachinaLayoutError(
|
|
142
|
+
"RootFrameNotRoot",
|
|
143
|
+
`row ${row.id} uses RootFrame but is not a root row.`
|
|
144
|
+
);
|
|
122
145
|
}
|
|
123
146
|
if (row.z !== void 0) {
|
|
124
147
|
assertFiniteNumber(row.z, `rows[${rowIndex}].z`);
|
|
125
148
|
if (!Number.isInteger(row.z) || row.z < -5 || row.z > 5) {
|
|
126
|
-
throw new MachinaLayoutError(
|
|
149
|
+
throw new MachinaLayoutError(
|
|
150
|
+
"InvalidZ",
|
|
151
|
+
`rows[${rowIndex}].z must be an integer in range -5..5`
|
|
152
|
+
);
|
|
127
153
|
}
|
|
128
154
|
}
|
|
129
155
|
rowById.set(row.id, row);
|
|
@@ -135,6 +161,7 @@ function compileLayoutRows(rows) {
|
|
|
135
161
|
view: row.view,
|
|
136
162
|
slot: row.slot,
|
|
137
163
|
debugLabel: row.debugLabel,
|
|
164
|
+
layer: row.layer,
|
|
138
165
|
offset: row.offset
|
|
139
166
|
};
|
|
140
167
|
if (row.parent === void 0) {
|
|
@@ -149,10 +176,16 @@ function compileLayoutRows(rows) {
|
|
|
149
176
|
}
|
|
150
177
|
const rootId = rootCandidates[0];
|
|
151
178
|
if (nodes[rootId].frame.kind === "fill") {
|
|
152
|
-
throw new MachinaLayoutError(
|
|
179
|
+
throw new MachinaLayoutError(
|
|
180
|
+
"FillFrameWithoutArranger",
|
|
181
|
+
"FillFrame cannot be used as the root frame."
|
|
182
|
+
);
|
|
153
183
|
}
|
|
154
184
|
if (nodes[rootId].frame.kind === "fixed") {
|
|
155
|
-
throw new MachinaLayoutError(
|
|
185
|
+
throw new MachinaLayoutError(
|
|
186
|
+
"FixedFrameWithoutArranger",
|
|
187
|
+
"FixedFrame cannot be used as the root frame."
|
|
188
|
+
);
|
|
156
189
|
}
|
|
157
190
|
const childrenEntries = /* @__PURE__ */ new Map();
|
|
158
191
|
for (let rowIndex = 0; rowIndex < rows.length; rowIndex += 1) {
|
|
@@ -164,7 +197,10 @@ function compileLayoutRows(rows) {
|
|
|
164
197
|
throw new MachinaLayoutError("SelfParent", `node ${row.id} cannot parent itself.`);
|
|
165
198
|
}
|
|
166
199
|
if (!rowById.has(row.parent) || row.parent.trim().length === 0) {
|
|
167
|
-
throw new MachinaLayoutError(
|
|
200
|
+
throw new MachinaLayoutError(
|
|
201
|
+
"UnknownParent",
|
|
202
|
+
`node ${row.id} references unknown parent: ${row.parent}`
|
|
203
|
+
);
|
|
168
204
|
}
|
|
169
205
|
const entry = {
|
|
170
206
|
childId: row.id,
|
|
@@ -233,6 +269,101 @@ function compileLayoutRows(rows) {
|
|
|
233
269
|
return { rootId, nodes, children };
|
|
234
270
|
}
|
|
235
271
|
|
|
272
|
+
// src/selectLayoutRowsForRoot.ts
|
|
273
|
+
function validateRootRect(rootRect) {
|
|
274
|
+
assertFiniteNumber(rootRect.x, "rootRect.x");
|
|
275
|
+
assertFiniteNumber(rootRect.y, "rootRect.y");
|
|
276
|
+
assertNonNegativeSize(rootRect.width, "rootRect.width");
|
|
277
|
+
assertNonNegativeSize(rootRect.height, "rootRect.height");
|
|
278
|
+
}
|
|
279
|
+
function validateCondition(condition, rowIndex, variantIndex) {
|
|
280
|
+
if (condition.minWidth !== void 0) {
|
|
281
|
+
assertFiniteNumber(
|
|
282
|
+
condition.minWidth,
|
|
283
|
+
`rows[${rowIndex}].variants[${variantIndex}].when.minWidth`
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
if (condition.maxWidth !== void 0) {
|
|
287
|
+
assertFiniteNumber(
|
|
288
|
+
condition.maxWidth,
|
|
289
|
+
`rows[${rowIndex}].variants[${variantIndex}].when.maxWidth`
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
if (condition.minHeight !== void 0) {
|
|
293
|
+
assertFiniteNumber(
|
|
294
|
+
condition.minHeight,
|
|
295
|
+
`rows[${rowIndex}].variants[${variantIndex}].when.minHeight`
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
if (condition.maxHeight !== void 0) {
|
|
299
|
+
assertFiniteNumber(
|
|
300
|
+
condition.maxHeight,
|
|
301
|
+
`rows[${rowIndex}].variants[${variantIndex}].when.maxHeight`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
if (condition.minWidth !== void 0 && condition.maxWidth !== void 0 && condition.minWidth > condition.maxWidth) {
|
|
305
|
+
throw new MachinaLayoutError(
|
|
306
|
+
"InvalidVariantCondition",
|
|
307
|
+
`rows[${rowIndex}].variants[${variantIndex}].when has minWidth > maxWidth`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
if (condition.minHeight !== void 0 && condition.maxHeight !== void 0 && condition.minHeight > condition.maxHeight) {
|
|
311
|
+
throw new MachinaLayoutError(
|
|
312
|
+
"InvalidVariantCondition",
|
|
313
|
+
`rows[${rowIndex}].variants[${variantIndex}].when has minHeight > maxHeight`
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function conditionMatches(condition, rootRect) {
|
|
318
|
+
if (condition.minWidth !== void 0 && rootRect.width < condition.minWidth) return false;
|
|
319
|
+
if (condition.maxWidth !== void 0 && rootRect.width > condition.maxWidth) return false;
|
|
320
|
+
if (condition.minHeight !== void 0 && rootRect.height < condition.minHeight) return false;
|
|
321
|
+
if (condition.maxHeight !== void 0 && rootRect.height > condition.maxHeight) return false;
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
function validateVariantZ(variant, rowIndex, variantIndex) {
|
|
325
|
+
if (variant.z === void 0) return;
|
|
326
|
+
assertFiniteNumber(variant.z, `rows[${rowIndex}].variants[${variantIndex}].z`);
|
|
327
|
+
if (!Number.isInteger(variant.z) || variant.z < -5 || variant.z > 5) {
|
|
328
|
+
throw new MachinaLayoutError(
|
|
329
|
+
"InvalidZ",
|
|
330
|
+
`rows[${rowIndex}].variants[${variantIndex}].z must be an integer in range -5..5`
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function selectLayoutRowsForRoot(rows, rootRect) {
|
|
335
|
+
validateRootRect(rootRect);
|
|
336
|
+
return rows.map((row, rowIndex) => {
|
|
337
|
+
const baseRow = { ...row };
|
|
338
|
+
delete baseRow.variants;
|
|
339
|
+
const variants = row.variants;
|
|
340
|
+
if (!variants || variants.length === 0) {
|
|
341
|
+
return baseRow;
|
|
342
|
+
}
|
|
343
|
+
for (let variantIndex = 0; variantIndex < variants.length; variantIndex += 1) {
|
|
344
|
+
const variant = variants[variantIndex];
|
|
345
|
+
validateCondition(variant.when, rowIndex, variantIndex);
|
|
346
|
+
validateVariantZ(variant, rowIndex, variantIndex);
|
|
347
|
+
if (!conditionMatches(variant.when, rootRect)) {
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
const selected = {
|
|
351
|
+
...baseRow,
|
|
352
|
+
frame: variant.frame ?? baseRow.frame,
|
|
353
|
+
arrange: variant.arrange ?? baseRow.arrange,
|
|
354
|
+
offset: variant.offset ?? baseRow.offset,
|
|
355
|
+
z: variant.z ?? baseRow.z,
|
|
356
|
+
view: variant.view ?? baseRow.view,
|
|
357
|
+
slot: variant.slot ?? baseRow.slot,
|
|
358
|
+
debugLabel: variant.debugLabel ?? baseRow.debugLabel,
|
|
359
|
+
layer: variant.layer ?? baseRow.layer
|
|
360
|
+
};
|
|
361
|
+
return selected;
|
|
362
|
+
}
|
|
363
|
+
return baseRow;
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
236
367
|
// src/resolveFrame.ts
|
|
237
368
|
function validateParentRect(parent) {
|
|
238
369
|
assertFiniteNumber(parent.x, "parent.x");
|
|
@@ -260,11 +391,17 @@ function resolveAnchor(parent, frame) {
|
|
|
260
391
|
if (hasHeight) assertNonNegativeSize(explicitHeight, "frame.height");
|
|
261
392
|
const horizontalCount = Number(hasLeft) + Number(hasRight) + Number(hasWidth);
|
|
262
393
|
if (horizontalCount !== 2) {
|
|
263
|
-
throw new MachinaLayoutError(
|
|
394
|
+
throw new MachinaLayoutError(
|
|
395
|
+
"InvalidAnchorHorizontal",
|
|
396
|
+
"Anchor frame must specify exactly two horizontal constraints: left, right, width."
|
|
397
|
+
);
|
|
264
398
|
}
|
|
265
399
|
const verticalCount = Number(hasTop) + Number(hasBottom) + Number(hasHeight);
|
|
266
400
|
if (verticalCount !== 2) {
|
|
267
|
-
throw new MachinaLayoutError(
|
|
401
|
+
throw new MachinaLayoutError(
|
|
402
|
+
"InvalidAnchorVertical",
|
|
403
|
+
"Anchor frame must specify exactly two vertical constraints: top, bottom, height."
|
|
404
|
+
);
|
|
268
405
|
}
|
|
269
406
|
let x;
|
|
270
407
|
let width;
|
|
@@ -306,29 +443,159 @@ function resolveFrame(parent, frame) {
|
|
|
306
443
|
assertFiniteNumber(frame.y, "frame.y");
|
|
307
444
|
assertNonNegativeSize(frame.width, "frame.width");
|
|
308
445
|
assertNonNegativeSize(frame.height, "frame.height");
|
|
309
|
-
return {
|
|
446
|
+
return {
|
|
447
|
+
x: parent.x + frame.x,
|
|
448
|
+
y: parent.y + frame.y,
|
|
449
|
+
width: frame.width,
|
|
450
|
+
height: frame.height
|
|
451
|
+
};
|
|
310
452
|
}
|
|
311
453
|
case "anchor":
|
|
312
454
|
return resolveAnchor(parent, frame);
|
|
313
455
|
case "root":
|
|
314
|
-
throw new MachinaLayoutError(
|
|
456
|
+
throw new MachinaLayoutError(
|
|
457
|
+
"RootFrameWithoutRoot",
|
|
458
|
+
"RootFrame can only be declared on the root row."
|
|
459
|
+
);
|
|
315
460
|
case "fixed": {
|
|
316
461
|
assertNonNegativeSize(frame.width, "frame.width");
|
|
317
462
|
assertNonNegativeSize(frame.height, "frame.height");
|
|
318
|
-
throw new MachinaLayoutError(
|
|
463
|
+
throw new MachinaLayoutError(
|
|
464
|
+
"FixedFrameWithoutArranger",
|
|
465
|
+
"Fixed frames require an arranger to determine placement."
|
|
466
|
+
);
|
|
319
467
|
}
|
|
320
468
|
case "fill":
|
|
321
|
-
throw new MachinaLayoutError(
|
|
469
|
+
throw new MachinaLayoutError(
|
|
470
|
+
"FillFrameWithoutArranger",
|
|
471
|
+
"Fill frames require a stack arranger to determine placement."
|
|
472
|
+
);
|
|
473
|
+
case "cell":
|
|
474
|
+
throw new MachinaLayoutError(
|
|
475
|
+
"CellFrameWithoutGrid",
|
|
476
|
+
"Cell frames require a grid arranger to determine placement."
|
|
477
|
+
);
|
|
478
|
+
case "guide":
|
|
479
|
+
throw new MachinaLayoutError(
|
|
480
|
+
"GuideTargetUnresolved",
|
|
481
|
+
"Guide frames require document-level dependency resolution and cannot be resolved directly."
|
|
482
|
+
);
|
|
322
483
|
}
|
|
323
484
|
}
|
|
324
485
|
|
|
325
486
|
// src/resolveLayoutDocument.ts
|
|
326
|
-
function
|
|
487
|
+
function validateRootRect2(rootRect) {
|
|
327
488
|
assertFiniteNumber(rootRect.x, "rootRect.x");
|
|
328
489
|
assertFiniteNumber(rootRect.y, "rootRect.y");
|
|
329
490
|
assertNonNegativeSize(rootRect.width, "rootRect.width");
|
|
330
491
|
assertNonNegativeSize(rootRect.height, "rootRect.height");
|
|
331
492
|
}
|
|
493
|
+
var H_EDGES = /* @__PURE__ */ new Set(["left", "right", "centerX"]);
|
|
494
|
+
var V_EDGES = /* @__PURE__ */ new Set(["top", "bottom", "centerY"]);
|
|
495
|
+
var ALL_EDGES = /* @__PURE__ */ new Set(["left", "right", "top", "bottom", "centerX", "centerY"]);
|
|
496
|
+
var isEdgeRef = (value) => Boolean(value && typeof value === "object" && "ref" in value && "edge" in value);
|
|
497
|
+
function getRectEdgeValue(rect, edge) {
|
|
498
|
+
switch (edge) {
|
|
499
|
+
case "left":
|
|
500
|
+
return rect.x;
|
|
501
|
+
case "right":
|
|
502
|
+
return rect.x + rect.width;
|
|
503
|
+
case "centerX":
|
|
504
|
+
return rect.x + rect.width / 2;
|
|
505
|
+
case "top":
|
|
506
|
+
return rect.y;
|
|
507
|
+
case "bottom":
|
|
508
|
+
return rect.y + rect.height;
|
|
509
|
+
case "centerY":
|
|
510
|
+
return rect.y + rect.height / 2;
|
|
511
|
+
default:
|
|
512
|
+
throw new MachinaLayoutError("InvalidGuideFrame", `unknown guide edge: ${String(edge)}`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
function validateGuideFrame(nodeId, frame, document) {
|
|
516
|
+
const hCount = Number(frame.left !== void 0) + Number(frame.right !== void 0) + Number(frame.width !== void 0);
|
|
517
|
+
const vCount = Number(frame.top !== void 0) + Number(frame.bottom !== void 0) + Number(frame.height !== void 0);
|
|
518
|
+
if (hCount !== 2 || vCount !== 2)
|
|
519
|
+
throw new MachinaLayoutError(
|
|
520
|
+
"InvalidGuideFrame",
|
|
521
|
+
`guide frame must provide exactly two constraints per axis: ${nodeId}`
|
|
522
|
+
);
|
|
523
|
+
const hRefs = [frame.left, frame.right].filter(isEdgeRef);
|
|
524
|
+
const vRefs = [frame.top, frame.bottom].filter(isEdgeRef);
|
|
525
|
+
if (hRefs.length > 1 || vRefs.length > 1)
|
|
526
|
+
throw new MachinaLayoutError(
|
|
527
|
+
"GuideTooManyReferencesPerAxis",
|
|
528
|
+
`guide has too many refs on one axis: ${nodeId}`
|
|
529
|
+
);
|
|
530
|
+
for (const ref of [...hRefs, ...vRefs]) {
|
|
531
|
+
if (ref.ref === nodeId)
|
|
532
|
+
throw new MachinaLayoutError(
|
|
533
|
+
"GuideSelfReference",
|
|
534
|
+
`guide cannot reference itself: ${nodeId}`
|
|
535
|
+
);
|
|
536
|
+
if (!document.nodes[ref.ref])
|
|
537
|
+
throw new MachinaLayoutError("GuideTargetNotFound", `guide target not found: ${ref.ref}`);
|
|
538
|
+
if (!ALL_EDGES.has(ref.edge))
|
|
539
|
+
throw new MachinaLayoutError("InvalidGuideFrame", `unknown edge: ${String(ref.edge)}`);
|
|
540
|
+
}
|
|
541
|
+
for (const ref of hRefs)
|
|
542
|
+
if (!H_EDGES.has(ref.edge))
|
|
543
|
+
throw new MachinaLayoutError(
|
|
544
|
+
"GuideInvalidEdgeForAxis",
|
|
545
|
+
`horizontal guide ref must use horizontal edge: ${nodeId}`
|
|
546
|
+
);
|
|
547
|
+
for (const ref of vRefs)
|
|
548
|
+
if (!V_EDGES.has(ref.edge))
|
|
549
|
+
throw new MachinaLayoutError(
|
|
550
|
+
"GuideInvalidEdgeForAxis",
|
|
551
|
+
`vertical guide ref must use vertical edge: ${nodeId}`
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
function resolveGuidePosition(parentRect, side, value, resolvedNodes) {
|
|
555
|
+
if (isEdgeRef(value)) {
|
|
556
|
+
const target = resolvedNodes[value.ref];
|
|
557
|
+
if (!target)
|
|
558
|
+
throw new MachinaLayoutError(
|
|
559
|
+
"GuideTargetUnresolved",
|
|
560
|
+
`guide target unresolved: ${value.ref}`
|
|
561
|
+
);
|
|
562
|
+
const axisSize2 = side === "left" || side === "right" ? parentRect.width : parentRect.height;
|
|
563
|
+
const offset = value.offset === void 0 ? 0 : resolveUiLength(value.offset, axisSize2, `frame.${side}.offset`);
|
|
564
|
+
return getRectEdgeValue(target.rect, value.edge) + offset;
|
|
565
|
+
}
|
|
566
|
+
const axisSize = side === "left" || side === "right" ? parentRect.width : parentRect.height;
|
|
567
|
+
const scalar = resolveUiLength(value, axisSize, `frame.${side}`);
|
|
568
|
+
if (side === "left") return parentRect.x + scalar;
|
|
569
|
+
if (side === "right") return parentRect.x + parentRect.width - scalar;
|
|
570
|
+
if (side === "top") return parentRect.y + scalar;
|
|
571
|
+
return parentRect.y + parentRect.height - scalar;
|
|
572
|
+
}
|
|
573
|
+
function resolveGuideFrame(parentRect, frame, resolvedNodes) {
|
|
574
|
+
const hasLeft = frame.left !== void 0;
|
|
575
|
+
const hasRight = frame.right !== void 0;
|
|
576
|
+
const hasWidth = frame.width !== void 0;
|
|
577
|
+
const hasTop = frame.top !== void 0;
|
|
578
|
+
const hasBottom = frame.bottom !== void 0;
|
|
579
|
+
const hasHeight = frame.height !== void 0;
|
|
580
|
+
const left = hasLeft ? resolveGuidePosition(parentRect, "left", frame.left, resolvedNodes) : void 0;
|
|
581
|
+
const right = hasRight ? resolveGuidePosition(parentRect, "right", frame.right, resolvedNodes) : void 0;
|
|
582
|
+
const top = hasTop ? resolveGuidePosition(parentRect, "top", frame.top, resolvedNodes) : void 0;
|
|
583
|
+
const bottom = hasBottom ? resolveGuidePosition(parentRect, "bottom", frame.bottom, resolvedNodes) : void 0;
|
|
584
|
+
const explicitWidth = hasWidth ? resolveUiLength(frame.width, parentRect.width, "frame.width") : void 0;
|
|
585
|
+
const explicitHeight = hasHeight ? resolveUiLength(frame.height, parentRect.height, "frame.height") : void 0;
|
|
586
|
+
if (hasWidth) assertNonNegativeSize(explicitWidth, "frame.width");
|
|
587
|
+
if (hasHeight) assertNonNegativeSize(explicitHeight, "frame.height");
|
|
588
|
+
const x = hasLeft && hasWidth ? left : hasRight && hasWidth ? right - explicitWidth : left;
|
|
589
|
+
const width = hasWidth ? explicitWidth : right - left;
|
|
590
|
+
const y = hasTop && hasHeight ? top : hasBottom && hasHeight ? bottom - explicitHeight : top;
|
|
591
|
+
const height = hasHeight ? explicitHeight : bottom - top;
|
|
592
|
+
if (width < 0 || height < 0)
|
|
593
|
+
throw new MachinaLayoutError(
|
|
594
|
+
"NegativeResolvedSize",
|
|
595
|
+
`Resolved guide frame size must be non-negative. Received width=${width}, height=${height}.`
|
|
596
|
+
);
|
|
597
|
+
return { x, y, width, height };
|
|
598
|
+
}
|
|
332
599
|
function resolveStackChildRects(parentRect, arrange, childIds, document) {
|
|
333
600
|
const gap = arrange.gap ?? 0;
|
|
334
601
|
const justify = arrange.justify ?? "start";
|
|
@@ -341,9 +608,11 @@ function resolveStackChildRects(parentRect, arrange, childIds, document) {
|
|
|
341
608
|
width: parentRect.width - padding.left - padding.right,
|
|
342
609
|
height: parentRect.height - padding.top - padding.bottom
|
|
343
610
|
};
|
|
344
|
-
if (content.width < 0 || content.height < 0)
|
|
345
|
-
throw new MachinaLayoutError(
|
|
346
|
-
|
|
611
|
+
if (content.width < 0 || content.height < 0)
|
|
612
|
+
throw new MachinaLayoutError(
|
|
613
|
+
"StackContentNegative",
|
|
614
|
+
"stack content size cannot be negative after applying padding"
|
|
615
|
+
);
|
|
347
616
|
const isHorizontal = arrange.axis === "horizontal";
|
|
348
617
|
const contentMain = isHorizontal ? content.width : content.height;
|
|
349
618
|
const contentCross = isHorizontal ? content.height : content.width;
|
|
@@ -352,9 +621,11 @@ function resolveStackChildRects(parentRect, arrange, childIds, document) {
|
|
|
352
621
|
const fillWeights = [];
|
|
353
622
|
for (const childId of childIds) {
|
|
354
623
|
const childNode = document.nodes[childId];
|
|
355
|
-
if (!childNode)
|
|
356
|
-
throw new MachinaLayoutError(
|
|
357
|
-
|
|
624
|
+
if (!childNode)
|
|
625
|
+
throw new MachinaLayoutError(
|
|
626
|
+
"UnknownParent",
|
|
627
|
+
`child id ${childId} referenced by arranged parent is missing`
|
|
628
|
+
);
|
|
358
629
|
if (childNode.frame.kind === "fixed") {
|
|
359
630
|
assertNonNegativeSize(childNode.frame.width, `${childId}.frame.width`);
|
|
360
631
|
assertNonNegativeSize(childNode.frame.height, `${childId}.frame.height`);
|
|
@@ -363,14 +634,18 @@ function resolveStackChildRects(parentRect, arrange, childIds, document) {
|
|
|
363
634
|
fillWeights.push(0);
|
|
364
635
|
continue;
|
|
365
636
|
}
|
|
366
|
-
if (childNode.frame.kind !== "fill")
|
|
367
|
-
throw new MachinaLayoutError(
|
|
368
|
-
|
|
637
|
+
if (childNode.frame.kind !== "fill")
|
|
638
|
+
throw new MachinaLayoutError(
|
|
639
|
+
"StackChildMustBeFixed",
|
|
640
|
+
`stack child must use fixed or fill frame: ${childId}`
|
|
641
|
+
);
|
|
369
642
|
const weight = childNode.frame.weight ?? 1;
|
|
370
643
|
assertFiniteNumber(weight, `${childId}.frame.weight`);
|
|
371
|
-
if (weight <= 0)
|
|
372
|
-
throw new MachinaLayoutError(
|
|
373
|
-
|
|
644
|
+
if (weight <= 0)
|
|
645
|
+
throw new MachinaLayoutError(
|
|
646
|
+
"InvalidFillWeight",
|
|
647
|
+
`${childId}.frame.weight must be greater than 0`
|
|
648
|
+
);
|
|
374
649
|
const cross = childNode.frame.cross ?? "fill";
|
|
375
650
|
let childCross = contentCross;
|
|
376
651
|
if (cross !== "fill") {
|
|
@@ -381,49 +656,31 @@ function resolveStackChildRects(parentRect, arrange, childIds, document) {
|
|
|
381
656
|
childCrossSizes.push(childCross);
|
|
382
657
|
fillWeights.push(weight);
|
|
383
658
|
}
|
|
384
|
-
const fixedMainTotal = childIds.reduce(
|
|
659
|
+
const fixedMainTotal = childIds.reduce(
|
|
660
|
+
(sum, _id, i) => sum + (fillWeights[i] === 0 ? childMainSizes[i] : 0),
|
|
661
|
+
0
|
|
662
|
+
);
|
|
385
663
|
const totalGap = gap * Math.max(0, childIds.length - 1);
|
|
386
664
|
const remainingMain = contentMain - fixedMainTotal - totalGap;
|
|
387
|
-
if (remainingMain < 0)
|
|
388
|
-
throw new MachinaLayoutError("StackOverflow", "stack main axis overflow");
|
|
389
|
-
}
|
|
665
|
+
if (remainingMain < 0) throw new MachinaLayoutError("StackOverflow", "stack main axis overflow");
|
|
390
666
|
const totalFillWeight = fillWeights.reduce((sum, w) => sum + w, 0);
|
|
391
667
|
if (totalFillWeight > 0) {
|
|
392
|
-
for (let i = 0; i < childMainSizes.length; i += 1)
|
|
393
|
-
if (fillWeights[i] > 0)
|
|
668
|
+
for (let i = 0; i < childMainSizes.length; i += 1)
|
|
669
|
+
if (fillWeights[i] > 0)
|
|
394
670
|
childMainSizes[i] = remainingMain * fillWeights[i] / totalFillWeight;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
671
|
}
|
|
398
|
-
for (const childCross of childCrossSizes)
|
|
399
|
-
if (childCross > contentCross)
|
|
672
|
+
for (const childCross of childCrossSizes)
|
|
673
|
+
if (childCross > contentCross)
|
|
400
674
|
throw new MachinaLayoutError("StackOverflow", "stack cross axis overflow");
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
675
|
const occupiedMain = childMainSizes.reduce((sum, size) => sum + size, 0) + totalGap;
|
|
404
676
|
const remainingMainAfterFill = contentMain - occupiedMain;
|
|
405
677
|
let startOffset = 0;
|
|
406
678
|
let actualGap = gap;
|
|
407
679
|
if (totalFillWeight === 0) {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
startOffset = remainingMainAfterFill / 2;
|
|
413
|
-
break;
|
|
414
|
-
case "end":
|
|
415
|
-
startOffset = remainingMainAfterFill;
|
|
416
|
-
break;
|
|
417
|
-
case "space-between":
|
|
418
|
-
if (childIds.length <= 1) {
|
|
419
|
-
actualGap = 0;
|
|
420
|
-
} else {
|
|
421
|
-
actualGap = gap + remainingMainAfterFill / (childIds.length - 1);
|
|
422
|
-
}
|
|
423
|
-
break;
|
|
424
|
-
default:
|
|
425
|
-
throw new Error(`Unsupported stack justify: ${String(justify)}`);
|
|
426
|
-
}
|
|
680
|
+
if (justify === "center") startOffset = remainingMainAfterFill / 2;
|
|
681
|
+
else if (justify === "end") startOffset = remainingMainAfterFill;
|
|
682
|
+
else if (justify === "space-between")
|
|
683
|
+
actualGap = childIds.length <= 1 ? 0 : gap + remainingMainAfterFill / (childIds.length - 1);
|
|
427
684
|
}
|
|
428
685
|
const rects = {};
|
|
429
686
|
let currentMain = startOffset;
|
|
@@ -431,134 +688,247 @@ function resolveStackChildRects(parentRect, arrange, childIds, document) {
|
|
|
431
688
|
const childMain = childMainSizes[index];
|
|
432
689
|
const childCross = childCrossSizes[index];
|
|
433
690
|
let crossOffset = 0;
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
691
|
+
if (align === "center") crossOffset = (contentCross - childCross) / 2;
|
|
692
|
+
else if (align === "end") crossOffset = contentCross - childCross;
|
|
693
|
+
rects[childId] = isHorizontal ? {
|
|
694
|
+
x: content.x + currentMain,
|
|
695
|
+
y: content.y + crossOffset,
|
|
696
|
+
width: childMain,
|
|
697
|
+
height: childCross
|
|
698
|
+
} : {
|
|
699
|
+
x: content.x + crossOffset,
|
|
700
|
+
y: content.y + currentMain,
|
|
701
|
+
width: childCross,
|
|
702
|
+
height: childMain
|
|
703
|
+
};
|
|
447
704
|
currentMain += childMain + actualGap;
|
|
448
705
|
});
|
|
449
706
|
return rects;
|
|
450
707
|
}
|
|
708
|
+
function validateGridTrack(track, axis, index) {
|
|
709
|
+
if (track.kind === "fixed") {
|
|
710
|
+
if (!Number.isFinite(track.size) || track.size < 0)
|
|
711
|
+
throw new MachinaLayoutError(
|
|
712
|
+
"InvalidGridTrack",
|
|
713
|
+
`${axis}[${index}].size must be finite and non-negative`
|
|
714
|
+
);
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
if (track.kind === "fill") {
|
|
718
|
+
const weight = track.weight ?? 1;
|
|
719
|
+
if (!Number.isFinite(weight) || weight <= 0)
|
|
720
|
+
throw new MachinaLayoutError(
|
|
721
|
+
"InvalidGridTrack",
|
|
722
|
+
`${axis}[${index}].weight must be finite and greater than 0`
|
|
723
|
+
);
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
throw new MachinaLayoutError("InvalidGridTrack", `${axis}[${index}] has unknown track kind`);
|
|
727
|
+
}
|
|
728
|
+
function resolveGridTracks(contentAxisSize, tracks, gap, axis) {
|
|
729
|
+
if (!Number.isFinite(gap) || gap < 0 || tracks.length === 0)
|
|
730
|
+
throw new MachinaLayoutError("InvalidGridTrack", `invalid ${axis} configuration`);
|
|
731
|
+
tracks.forEach((t, i) => {
|
|
732
|
+
validateGridTrack(t, axis, i);
|
|
733
|
+
});
|
|
734
|
+
const fixedTotal = tracks.reduce((s, t) => s + (t.kind === "fixed" ? t.size : 0), 0);
|
|
735
|
+
const gapTotal = gap * Math.max(0, tracks.length - 1);
|
|
736
|
+
const remaining = contentAxisSize - fixedTotal - gapTotal;
|
|
737
|
+
if (remaining < 0) throw new MachinaLayoutError("GridOverflow", `grid ${axis} overflow`);
|
|
738
|
+
const totalWeight = tracks.reduce((s, t) => s + (t.kind === "fill" ? t.weight ?? 1 : 0), 0);
|
|
739
|
+
const sizes = tracks.map(
|
|
740
|
+
(t) => t.kind === "fixed" ? t.size : totalWeight <= 0 ? 0 : remaining * (t.weight ?? 1) / totalWeight
|
|
741
|
+
);
|
|
742
|
+
let current = 0;
|
|
743
|
+
return sizes.map((size) => {
|
|
744
|
+
const r = { start: current, size };
|
|
745
|
+
current += size + gap;
|
|
746
|
+
return r;
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
function resolveGridChildRect(childNode, columns, rows, columnGap, rowGap, content) {
|
|
750
|
+
if (childNode.frame.kind !== "cell")
|
|
751
|
+
throw new MachinaLayoutError(
|
|
752
|
+
"GridChildMustBeCell",
|
|
753
|
+
`grid child must use cell frame: ${childNode.id}`
|
|
754
|
+
);
|
|
755
|
+
const { row, col } = childNode.frame;
|
|
756
|
+
const rowSpan = childNode.frame.rowSpan ?? 1;
|
|
757
|
+
const colSpan = childNode.frame.colSpan ?? 1;
|
|
758
|
+
if (!Number.isInteger(row) || row < 0 || !Number.isInteger(col) || col < 0 || !Number.isInteger(rowSpan) || rowSpan <= 0 || !Number.isInteger(colSpan) || colSpan <= 0)
|
|
759
|
+
throw new MachinaLayoutError(
|
|
760
|
+
"InvalidGridCell",
|
|
761
|
+
`invalid cell coordinates/spans for node ${childNode.id}`
|
|
762
|
+
);
|
|
763
|
+
if (row + rowSpan > rows.length || col + colSpan > columns.length)
|
|
764
|
+
throw new MachinaLayoutError(
|
|
765
|
+
"InvalidGridCell",
|
|
766
|
+
`cell exceeds grid bounds for node ${childNode.id}`
|
|
767
|
+
);
|
|
768
|
+
const x = content.x + columns[col].start;
|
|
769
|
+
const y = content.y + rows[row].start;
|
|
770
|
+
let width = columnGap * (colSpan - 1);
|
|
771
|
+
for (let i = col; i < col + colSpan; i += 1) width += columns[i].size;
|
|
772
|
+
let height = rowGap * (rowSpan - 1);
|
|
773
|
+
for (let i = row; i < row + rowSpan; i += 1) height += rows[i].size;
|
|
774
|
+
return { x, y, width, height };
|
|
775
|
+
}
|
|
451
776
|
function resolveLayoutDocument(document, rootRect) {
|
|
452
|
-
|
|
777
|
+
validateRootRect2(rootRect);
|
|
453
778
|
const rootNode = document.nodes[document.rootId];
|
|
454
|
-
if (!rootNode)
|
|
779
|
+
if (!rootNode)
|
|
455
780
|
throw new MachinaLayoutError("MissingRoot", `root node not found for id: ${document.rootId}`);
|
|
456
|
-
}
|
|
457
781
|
const resolvedNodes = {};
|
|
458
782
|
const resolvedChildren = {};
|
|
459
783
|
const visitState = /* @__PURE__ */ new Map();
|
|
460
|
-
|
|
784
|
+
const pendingGuides = /* @__PURE__ */ new Map();
|
|
461
785
|
const resolveNode = (nodeId, rect) => {
|
|
462
786
|
const state = visitState.get(nodeId) ?? 0;
|
|
463
|
-
if (state === 1) {
|
|
464
|
-
|
|
465
|
-
}
|
|
466
|
-
if (state === 2) {
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
787
|
+
if (state === 1) throw new MachinaLayoutError("Cycle", `cycle detected at node ${nodeId}`);
|
|
788
|
+
if (state === 2) return;
|
|
469
789
|
const node = document.nodes[nodeId];
|
|
470
|
-
if (!node)
|
|
471
|
-
throw new MachinaLayoutError(
|
|
472
|
-
|
|
790
|
+
if (!node)
|
|
791
|
+
throw new MachinaLayoutError(
|
|
792
|
+
"UnknownParent",
|
|
793
|
+
`node referenced in children but missing from nodes: ${nodeId}`
|
|
794
|
+
);
|
|
473
795
|
visitState.set(nodeId, 1);
|
|
474
|
-
visitedCount += 1;
|
|
475
796
|
resolvedNodes[nodeId] = {
|
|
476
797
|
id: node.id,
|
|
477
798
|
z: node.z,
|
|
478
|
-
rect: {
|
|
799
|
+
rect: { ...rect },
|
|
479
800
|
frame: node.frame,
|
|
480
801
|
arrange: node.arrange,
|
|
481
802
|
view: node.view,
|
|
482
803
|
slot: node.slot,
|
|
483
804
|
debugLabel: node.debugLabel,
|
|
805
|
+
layer: node.layer,
|
|
484
806
|
offset: node.offset
|
|
485
807
|
};
|
|
486
808
|
const childIds = document.children[nodeId] ?? [];
|
|
487
809
|
resolvedChildren[nodeId] = [...childIds];
|
|
488
|
-
|
|
810
|
+
let childRects;
|
|
811
|
+
if (node.arrange?.kind === "stack")
|
|
812
|
+
childRects = resolveStackChildRects(rect, node.arrange, childIds, document);
|
|
813
|
+
else if (node.arrange?.kind === "grid") {
|
|
814
|
+
const columnGap = node.arrange.columnGap ?? 0;
|
|
815
|
+
const rowGap = node.arrange.rowGap ?? 0;
|
|
816
|
+
const padding = normalizePadding(node.arrange.padding);
|
|
817
|
+
const content = {
|
|
818
|
+
x: rect.x + padding.left,
|
|
819
|
+
y: rect.y + padding.top,
|
|
820
|
+
width: rect.width - padding.left - padding.right,
|
|
821
|
+
height: rect.height - padding.top - padding.bottom
|
|
822
|
+
};
|
|
823
|
+
if (content.width < 0 || content.height < 0)
|
|
824
|
+
throw new MachinaLayoutError(
|
|
825
|
+
"GridContentNegative",
|
|
826
|
+
"grid content size cannot be negative after applying padding"
|
|
827
|
+
);
|
|
828
|
+
const columns = resolveGridTracks(content.width, node.arrange.columns, columnGap, "columns");
|
|
829
|
+
const rows = resolveGridTracks(content.height, node.arrange.rows, rowGap, "rows");
|
|
830
|
+
childRects = {};
|
|
831
|
+
for (const childId of childIds) {
|
|
832
|
+
const childNode = document.nodes[childId];
|
|
833
|
+
if (!childNode)
|
|
834
|
+
throw new MachinaLayoutError(
|
|
835
|
+
"UnknownParent",
|
|
836
|
+
`child id ${childId} referenced by ${nodeId} is missing`
|
|
837
|
+
);
|
|
838
|
+
childRects[childId] = resolveGridChildRect(
|
|
839
|
+
childNode,
|
|
840
|
+
columns,
|
|
841
|
+
rows,
|
|
842
|
+
columnGap,
|
|
843
|
+
rowGap,
|
|
844
|
+
content
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
489
848
|
for (const childId of childIds) {
|
|
490
849
|
const childNode = document.nodes[childId];
|
|
491
|
-
if (!childNode)
|
|
492
|
-
throw new MachinaLayoutError(
|
|
850
|
+
if (!childNode)
|
|
851
|
+
throw new MachinaLayoutError(
|
|
852
|
+
"UnknownParent",
|
|
853
|
+
`child id ${childId} referenced by ${nodeId} is missing`
|
|
854
|
+
);
|
|
855
|
+
if (childNode.frame.kind === "guide" && !childRects) {
|
|
856
|
+
validateGuideFrame(childId, childNode.frame, document);
|
|
857
|
+
pendingGuides.set(childId, { nodeId: childId, parentId: nodeId });
|
|
858
|
+
continue;
|
|
493
859
|
}
|
|
494
860
|
const normalChildRect = childRects?.[childId] ?? resolveFrame(rect, childNode.frame);
|
|
495
|
-
|
|
496
|
-
resolveNode(childId, childRect);
|
|
861
|
+
resolveNode(childId, applyOffset(normalChildRect, rect, childNode.offset));
|
|
497
862
|
}
|
|
498
863
|
visitState.set(nodeId, 2);
|
|
499
864
|
};
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
865
|
+
const processPending = () => {
|
|
866
|
+
while (pendingGuides.size > 0) {
|
|
867
|
+
let progressed = false;
|
|
868
|
+
for (const [id, pending] of [...pendingGuides.entries()]) {
|
|
869
|
+
const parentResolved = resolvedNodes[pending.parentId];
|
|
870
|
+
const node = document.nodes[id];
|
|
871
|
+
if (!parentResolved || !node || node.frame.kind !== "guide") continue;
|
|
872
|
+
const refs = [node.frame.left, node.frame.right, node.frame.top, node.frame.bottom].filter(
|
|
873
|
+
isEdgeRef
|
|
874
|
+
);
|
|
875
|
+
if (refs.some((r) => !resolvedNodes[r.ref])) continue;
|
|
876
|
+
const rect = resolveGuideFrame(parentResolved.rect, node.frame, resolvedNodes);
|
|
877
|
+
resolveNode(id, applyOffset(rect, parentResolved.rect, node.offset));
|
|
878
|
+
pendingGuides.delete(id);
|
|
879
|
+
progressed = true;
|
|
880
|
+
}
|
|
881
|
+
if (pendingGuides.size === 0) return;
|
|
882
|
+
if (progressed) continue;
|
|
883
|
+
const remaining = /* @__PURE__ */ new Set([...pendingGuides.keys()]);
|
|
884
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
885
|
+
const visited = /* @__PURE__ */ new Set();
|
|
886
|
+
const hasCycle = (id) => {
|
|
887
|
+
if (visiting.has(id)) return true;
|
|
888
|
+
if (visited.has(id)) return false;
|
|
889
|
+
visiting.add(id);
|
|
890
|
+
const node = document.nodes[id];
|
|
891
|
+
if (node?.frame.kind === "guide") {
|
|
892
|
+
for (const ref of [
|
|
893
|
+
node.frame.left,
|
|
894
|
+
node.frame.right,
|
|
895
|
+
node.frame.top,
|
|
896
|
+
node.frame.bottom
|
|
897
|
+
].filter(isEdgeRef)) {
|
|
898
|
+
if (remaining.has(ref.ref) && hasCycle(ref.ref)) return true;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
visiting.delete(id);
|
|
902
|
+
visited.add(id);
|
|
903
|
+
return false;
|
|
904
|
+
};
|
|
905
|
+
for (const id of remaining) {
|
|
906
|
+
if (hasCycle(id))
|
|
907
|
+
throw new MachinaLayoutError("GuideReferenceCycle", "guide reference cycle detected");
|
|
908
|
+
}
|
|
909
|
+
throw new MachinaLayoutError(
|
|
910
|
+
"GuideTargetUnresolved",
|
|
911
|
+
"one or more guide targets could not be resolved"
|
|
912
|
+
);
|
|
913
|
+
}
|
|
508
914
|
};
|
|
915
|
+
resolveNode(document.rootId, { ...rootRect });
|
|
916
|
+
processPending();
|
|
917
|
+
if (Object.keys(resolvedNodes).length !== Object.keys(document.nodes).length)
|
|
918
|
+
throw new MachinaLayoutError(
|
|
919
|
+
"UnreachableNode",
|
|
920
|
+
"one or more nodes are unreachable from the root."
|
|
921
|
+
);
|
|
922
|
+
return { rootId: document.rootId, nodes: resolvedNodes, children: resolvedChildren };
|
|
509
923
|
}
|
|
510
924
|
|
|
511
925
|
// src/resolveLayoutRows.ts
|
|
512
926
|
function resolveLayoutRows(rows, rootRect) {
|
|
513
|
-
const
|
|
927
|
+
const selectedRows = selectLayoutRowsForRoot(rows, rootRect);
|
|
928
|
+
const document = compileLayoutRows(selectedRows);
|
|
514
929
|
return resolveLayoutDocument(document, rootRect);
|
|
515
930
|
}
|
|
516
931
|
|
|
517
|
-
// src/toResolvedTree.ts
|
|
518
|
-
function toResolvedTree(document) {
|
|
519
|
-
const root = document.nodes[document.rootId];
|
|
520
|
-
if (!root) {
|
|
521
|
-
throw new MachinaLayoutError("MissingRoot", `root node '${document.rootId}' is missing`);
|
|
522
|
-
}
|
|
523
|
-
const visiting = /* @__PURE__ */ new Set();
|
|
524
|
-
const visited = /* @__PURE__ */ new Set();
|
|
525
|
-
const build = (node) => {
|
|
526
|
-
if (visiting.has(node.id)) {
|
|
527
|
-
throw new MachinaLayoutError("Cycle", `cycle detected at '${node.id}'`);
|
|
528
|
-
}
|
|
529
|
-
visiting.add(node.id);
|
|
530
|
-
visited.add(node.id);
|
|
531
|
-
const childIds = document.children[node.id] ?? [];
|
|
532
|
-
const children = childIds.map((childId) => {
|
|
533
|
-
const child = document.nodes[childId];
|
|
534
|
-
if (!child) {
|
|
535
|
-
throw new MachinaLayoutError("UnknownParent", `missing child node '${childId}' referenced by '${node.id}'`);
|
|
536
|
-
}
|
|
537
|
-
return build(child);
|
|
538
|
-
});
|
|
539
|
-
visiting.delete(node.id);
|
|
540
|
-
return {
|
|
541
|
-
id: node.id,
|
|
542
|
-
z: node.z,
|
|
543
|
-
rect: { ...node.rect },
|
|
544
|
-
frame: node.frame,
|
|
545
|
-
arrange: node.arrange,
|
|
546
|
-
view: node.view,
|
|
547
|
-
slot: node.slot,
|
|
548
|
-
debugLabel: node.debugLabel,
|
|
549
|
-
offset: node.offset,
|
|
550
|
-
children
|
|
551
|
-
};
|
|
552
|
-
};
|
|
553
|
-
const tree = build(root);
|
|
554
|
-
for (const nodeId of Object.keys(document.nodes)) {
|
|
555
|
-
if (!visited.has(nodeId)) {
|
|
556
|
-
throw new MachinaLayoutError("UnreachableNode", `node '${nodeId}' is unreachable from root '${document.rootId}'`);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
return tree;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
932
|
// src/flattenResolvedTree.ts
|
|
563
933
|
function flattenResolvedTree(tree) {
|
|
564
934
|
const out = [];
|
|
@@ -572,6 +942,7 @@ function flattenResolvedTree(tree) {
|
|
|
572
942
|
view: node.view,
|
|
573
943
|
slot: node.slot,
|
|
574
944
|
debugLabel: node.debugLabel,
|
|
945
|
+
layer: node.layer,
|
|
575
946
|
offset: node.offset
|
|
576
947
|
});
|
|
577
948
|
for (const child of node.children) {
|
|
@@ -587,464 +958,248 @@ function formatRect(rect) {
|
|
|
587
958
|
return `x=${rect.x} y=${rect.y} w=${rect.width} h=${rect.height}`;
|
|
588
959
|
}
|
|
589
960
|
|
|
590
|
-
// src/
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
function renderNode(node, parentRect, views, viewData, nodeData, nodeClassName, debug, nodeContainment, nodeContentVisibility, nodeContainIntrinsicSize, nodesById) {
|
|
594
|
-
const viewKey = node.view ?? node.slot;
|
|
595
|
-
const View = viewKey ? views[viewKey] : void 0;
|
|
596
|
-
const selectedViewData = viewKey ? viewData?.[viewKey] : void 0;
|
|
597
|
-
const selectedNodeData = nodeData?.[node.id];
|
|
598
|
-
const left = node.rect.x - parentRect.x;
|
|
599
|
-
const top = node.rect.y - parentRect.y;
|
|
600
|
-
const style = {
|
|
601
|
-
position: "absolute",
|
|
602
|
-
left,
|
|
603
|
-
top,
|
|
604
|
-
width: node.rect.width,
|
|
605
|
-
height: node.rect.height,
|
|
606
|
-
boxSizing: "border-box",
|
|
607
|
-
zIndex: node.z ?? 0,
|
|
608
|
-
...nodeContainment === "layout-paint" ? { contain: "layout paint" } : null,
|
|
609
|
-
...nodeContainment === "strict" ? { contain: "strict" } : null,
|
|
610
|
-
...nodeContentVisibility === "auto" ? { contentVisibility: "auto" } : null,
|
|
611
|
-
...nodeContainIntrinsicSize !== void 0 ? { containIntrinsicSize: nodeContainIntrinsicSize } : null,
|
|
612
|
-
...debug ? { outline: "1px dashed rgba(59, 130, 246, 0.9)" } : null
|
|
613
|
-
};
|
|
614
|
-
const renderedSlot = View && nodesById[node.id] ? React.createElement(View, {
|
|
615
|
-
id: node.id,
|
|
616
|
-
rect: { ...node.rect },
|
|
617
|
-
debugLabel: node.debugLabel,
|
|
618
|
-
node: { ...nodesById[node.id], rect: { ...nodesById[node.id].rect } },
|
|
619
|
-
viewKey,
|
|
620
|
-
viewData: selectedViewData,
|
|
621
|
-
nodeData: selectedNodeData
|
|
622
|
-
}) : null;
|
|
623
|
-
return /* @__PURE__ */ jsxs(
|
|
624
|
-
"div",
|
|
625
|
-
{
|
|
626
|
-
"data-testid": `machina-node-${node.id}`,
|
|
627
|
-
className: nodeClassName,
|
|
628
|
-
style,
|
|
629
|
-
"data-machina-node-id": node.id,
|
|
630
|
-
"data-machina-slot": node.slot,
|
|
631
|
-
"data-machina-view": viewKey,
|
|
632
|
-
"data-machina-debug-label": node.debugLabel,
|
|
633
|
-
children: [
|
|
634
|
-
debug ? /* @__PURE__ */ jsx("small", { children: node.debugLabel ?? node.id }) : null,
|
|
635
|
-
renderedSlot,
|
|
636
|
-
[...node.children].map((child, index) => ({ child, index })).sort((a, b) => (a.child.z ?? 0) - (b.child.z ?? 0) || a.index - b.index).map(
|
|
637
|
-
({ child }) => renderNode(
|
|
638
|
-
child,
|
|
639
|
-
node.rect,
|
|
640
|
-
views,
|
|
641
|
-
viewData,
|
|
642
|
-
nodeData,
|
|
643
|
-
nodeClassName,
|
|
644
|
-
debug,
|
|
645
|
-
nodeContainment,
|
|
646
|
-
nodeContentVisibility,
|
|
647
|
-
nodeContainIntrinsicSize,
|
|
648
|
-
nodesById
|
|
649
|
-
)
|
|
650
|
-
)
|
|
651
|
-
]
|
|
652
|
-
},
|
|
653
|
-
node.id
|
|
654
|
-
);
|
|
961
|
+
// src/stackGeometry.ts
|
|
962
|
+
function copyRect(rect) {
|
|
963
|
+
return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
|
|
655
964
|
}
|
|
656
|
-
function
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
className,
|
|
663
|
-
style,
|
|
664
|
-
nodeClassName,
|
|
665
|
-
debug,
|
|
666
|
-
nodeContainment = "layout-paint",
|
|
667
|
-
nodeContentVisibility = "none",
|
|
668
|
-
nodeContainIntrinsicSize
|
|
669
|
-
} = props;
|
|
670
|
-
const tree = toResolvedTree(layout);
|
|
671
|
-
const wrapperStyle = {
|
|
672
|
-
position: "relative",
|
|
673
|
-
width: tree.rect.width,
|
|
674
|
-
height: tree.rect.height,
|
|
675
|
-
...style
|
|
965
|
+
function applyPadding(parentRect, padding) {
|
|
966
|
+
return {
|
|
967
|
+
x: parentRect.x + padding.left,
|
|
968
|
+
y: parentRect.y + padding.top,
|
|
969
|
+
width: parentRect.width - padding.left - padding.right,
|
|
970
|
+
height: parentRect.height - padding.top - padding.bottom
|
|
676
971
|
};
|
|
677
|
-
return /* @__PURE__ */ jsx("div", { className, style: wrapperStyle, "data-machina-root-id": tree.id, children: renderNode(
|
|
678
|
-
tree,
|
|
679
|
-
tree.rect,
|
|
680
|
-
views,
|
|
681
|
-
viewData,
|
|
682
|
-
nodeData,
|
|
683
|
-
nodeClassName,
|
|
684
|
-
debug,
|
|
685
|
-
nodeContainment,
|
|
686
|
-
nodeContentVisibility,
|
|
687
|
-
nodeContainIntrinsicSize,
|
|
688
|
-
layout.nodes
|
|
689
|
-
) });
|
|
690
972
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
const lines = [];
|
|
698
|
-
let i = 0;
|
|
699
|
-
let line = 1;
|
|
700
|
-
while (i <= source.length) {
|
|
701
|
-
const start = i;
|
|
702
|
-
while (i < source.length && source[i] !== "\n" && source[i] !== "\r") i += 1;
|
|
703
|
-
const text = source.slice(start, i);
|
|
704
|
-
lines.push({ text, index: start, line });
|
|
705
|
-
if (i >= source.length) break;
|
|
706
|
-
if (source[i] === "\r" && source[i + 1] === "\n") i += 2;
|
|
707
|
-
else i += 1;
|
|
708
|
-
line += 1;
|
|
973
|
+
function assertContentNonNegative(contentRect, code) {
|
|
974
|
+
if (contentRect.width < 0 || contentRect.height < 0) {
|
|
975
|
+
throw new MachinaLayoutError(
|
|
976
|
+
code,
|
|
977
|
+
`${code === "StackContentNegative" ? "stack" : "grid"} content size cannot be negative after applying padding`
|
|
978
|
+
);
|
|
709
979
|
}
|
|
710
|
-
return lines;
|
|
711
980
|
}
|
|
712
|
-
function
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
const pushText = (t) => {
|
|
717
|
-
if (!t) return;
|
|
718
|
-
const prev = inline[inline.length - 1];
|
|
719
|
-
if (prev?.kind === "text") prev.text += t;
|
|
720
|
-
else inline.push({ kind: "text", text: t });
|
|
721
|
-
};
|
|
722
|
-
const allowedEscapes = /* @__PURE__ */ new Set(["\\", "*", "`", "[", "]", "(", ")", "-"]);
|
|
723
|
-
const consumeEscape = () => {
|
|
724
|
-
if (text[cursor] !== "\\") return false;
|
|
725
|
-
if (cursor === text.length - 1) {
|
|
726
|
-
diagnostics.push(makeDiagnostic("invalid_escape", "Dangling escape sequence.", lineIndex + cursor, 1, line, cursor + 1));
|
|
727
|
-
pushText("\\");
|
|
728
|
-
cursor += 1;
|
|
729
|
-
return true;
|
|
730
|
-
}
|
|
731
|
-
const escaped = text[cursor + 1];
|
|
732
|
-
if (allowedEscapes.has(escaped)) {
|
|
733
|
-
pushText(escaped);
|
|
734
|
-
cursor += 2;
|
|
735
|
-
return true;
|
|
736
|
-
}
|
|
737
|
-
diagnostics.push(makeDiagnostic("invalid_escape", `Unsupported escape sequence: \\${escaped}`, lineIndex + cursor, 2, line, cursor + 1));
|
|
738
|
-
pushText(escaped);
|
|
739
|
-
cursor += 2;
|
|
740
|
-
return true;
|
|
741
|
-
};
|
|
742
|
-
while (cursor < text.length) {
|
|
743
|
-
if (consumeEscape()) continue;
|
|
744
|
-
if (text.startsWith("![", cursor)) {
|
|
745
|
-
diagnostics.push(makeDiagnostic("unsupported_syntax", "Images are not supported.", lineIndex + cursor, 2, line, cursor + 1));
|
|
746
|
-
pushText("![");
|
|
747
|
-
cursor += 2;
|
|
748
|
-
continue;
|
|
749
|
-
}
|
|
750
|
-
if (text[cursor] === "`") {
|
|
751
|
-
const close = text.indexOf("`", cursor + 1);
|
|
752
|
-
if (close < 0) {
|
|
753
|
-
diagnostics.push(makeDiagnostic("unclosed_inline", "Unclosed inline code marker.", lineIndex + cursor, text.length - cursor, line, cursor + 1));
|
|
754
|
-
pushText(text.slice(cursor));
|
|
755
|
-
break;
|
|
756
|
-
}
|
|
757
|
-
inline.push({ kind: "code", text: text.slice(cursor + 1, close) });
|
|
758
|
-
cursor = close + 1;
|
|
759
|
-
continue;
|
|
760
|
-
}
|
|
761
|
-
if (text.startsWith("**", cursor)) {
|
|
762
|
-
const close = text.indexOf("**", cursor + 2);
|
|
763
|
-
if (close < 0) {
|
|
764
|
-
diagnostics.push(makeDiagnostic("unclosed_inline", "Unclosed strong marker.", lineIndex + cursor, text.length - cursor, line, cursor + 1));
|
|
765
|
-
pushText(text.slice(cursor));
|
|
766
|
-
break;
|
|
767
|
-
}
|
|
768
|
-
const children = parseInline(text.slice(cursor + 2, close), lineIndex + cursor + 2, line);
|
|
769
|
-
diagnostics.push(...children.diagnostics);
|
|
770
|
-
inline.push({ kind: "strong", children: children.inline });
|
|
771
|
-
cursor = close + 2;
|
|
772
|
-
continue;
|
|
773
|
-
}
|
|
774
|
-
if (text[cursor] === "*") {
|
|
775
|
-
const close = text.indexOf("*", cursor + 1);
|
|
776
|
-
if (close < 0) {
|
|
777
|
-
diagnostics.push(makeDiagnostic("unclosed_inline", "Unclosed emphasis marker.", lineIndex + cursor, text.length - cursor, line, cursor + 1));
|
|
778
|
-
pushText(text.slice(cursor));
|
|
779
|
-
break;
|
|
780
|
-
}
|
|
781
|
-
const children = parseInline(text.slice(cursor + 1, close), lineIndex + cursor + 1, line);
|
|
782
|
-
diagnostics.push(...children.diagnostics);
|
|
783
|
-
inline.push({ kind: "emphasis", children: children.inline });
|
|
784
|
-
cursor = close + 1;
|
|
785
|
-
continue;
|
|
786
|
-
}
|
|
787
|
-
if (text[cursor] === "[") {
|
|
788
|
-
const closeBracket = text.indexOf("]", cursor + 1);
|
|
789
|
-
if (closeBracket < 0 || text[closeBracket + 1] !== "(") {
|
|
790
|
-
diagnostics.push(makeDiagnostic("malformed_link", "Malformed link syntax.", lineIndex + cursor, Math.max(1, text.length - cursor), line, cursor + 1));
|
|
791
|
-
pushText("[");
|
|
792
|
-
cursor += 1;
|
|
793
|
-
continue;
|
|
794
|
-
}
|
|
795
|
-
const closeParen = text.indexOf(")", closeBracket + 2);
|
|
796
|
-
if (closeParen < 0) {
|
|
797
|
-
diagnostics.push(makeDiagnostic("malformed_link", "Malformed link syntax.", lineIndex + cursor, text.length - cursor, line, cursor + 1));
|
|
798
|
-
pushText(text.slice(cursor));
|
|
799
|
-
break;
|
|
800
|
-
}
|
|
801
|
-
const label = text.slice(cursor + 1, closeBracket);
|
|
802
|
-
const href = text.slice(closeBracket + 2, closeParen);
|
|
803
|
-
if (label.length === 0) {
|
|
804
|
-
diagnostics.push(makeDiagnostic("malformed_link", "Link label cannot be empty.", lineIndex + cursor, closeParen - cursor + 1, line, cursor + 1));
|
|
805
|
-
pushText(text.slice(cursor, closeParen + 1));
|
|
806
|
-
cursor = closeParen + 1;
|
|
807
|
-
continue;
|
|
808
|
-
}
|
|
809
|
-
const labelInline = parseInline(label, lineIndex + cursor + 1, line);
|
|
810
|
-
diagnostics.push(...labelInline.diagnostics);
|
|
811
|
-
inline.push({ kind: "link", href, children: labelInline.inline });
|
|
812
|
-
cursor = closeParen + 1;
|
|
813
|
-
continue;
|
|
814
|
-
}
|
|
815
|
-
const specials = ["![", "`", "**", "*", "[", "\\"];
|
|
816
|
-
let next = text.length;
|
|
817
|
-
for (const special of specials) {
|
|
818
|
-
const p = text.indexOf(special, cursor);
|
|
819
|
-
if (p >= 0 && p < next) next = p;
|
|
820
|
-
}
|
|
821
|
-
if (next === cursor) {
|
|
822
|
-
pushText(text[cursor]);
|
|
823
|
-
cursor += 1;
|
|
824
|
-
continue;
|
|
825
|
-
}
|
|
826
|
-
pushText(text.slice(cursor, next));
|
|
827
|
-
cursor = next;
|
|
828
|
-
}
|
|
829
|
-
return { inline, diagnostics };
|
|
981
|
+
function requireNode(layout, nodeId) {
|
|
982
|
+
const node = layout.nodes[nodeId];
|
|
983
|
+
if (!node) throw new MachinaLayoutError("InvalidId", `node id not found: ${nodeId}`);
|
|
984
|
+
return node;
|
|
830
985
|
}
|
|
831
|
-
function
|
|
832
|
-
|
|
833
|
-
if (
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
return
|
|
986
|
+
function requireStackArrange(layout, parentId) {
|
|
987
|
+
const parent = requireNode(layout, parentId);
|
|
988
|
+
if (parent.arrange?.kind !== "stack") {
|
|
989
|
+
throw new MachinaLayoutError(
|
|
990
|
+
"ExpectedStackArrange",
|
|
991
|
+
`expected stack arrange for node: ${parentId}`
|
|
992
|
+
);
|
|
993
|
+
}
|
|
994
|
+
return parent.arrange;
|
|
840
995
|
}
|
|
841
|
-
function
|
|
842
|
-
if (
|
|
843
|
-
if (
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
996
|
+
function getArrangeContentRect(parentRect, arrange) {
|
|
997
|
+
if (!arrange) return copyRect(parentRect);
|
|
998
|
+
if (arrange.kind === "stack") {
|
|
999
|
+
const contentRect = applyPadding(parentRect, normalizePadding(arrange.padding));
|
|
1000
|
+
assertContentNonNegative(contentRect, "StackContentNegative");
|
|
1001
|
+
return contentRect;
|
|
1002
|
+
}
|
|
1003
|
+
if (arrange.kind === "grid") {
|
|
1004
|
+
const contentRect = applyPadding(parentRect, normalizePadding(arrange.padding));
|
|
1005
|
+
assertContentNonNegative(contentRect, "GridContentNegative");
|
|
1006
|
+
return contentRect;
|
|
1007
|
+
}
|
|
1008
|
+
return copyRect(parentRect);
|
|
847
1009
|
}
|
|
848
|
-
function
|
|
849
|
-
|
|
1010
|
+
function getStackContentRect(layout, parentId) {
|
|
1011
|
+
const parent = requireNode(layout, parentId);
|
|
1012
|
+
const arrange = requireStackArrange(layout, parentId);
|
|
1013
|
+
return getArrangeContentRect(parent.rect, arrange);
|
|
850
1014
|
}
|
|
851
|
-
function
|
|
852
|
-
const
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1015
|
+
function getStackMainAxisMetrics(layout, parentId) {
|
|
1016
|
+
const parent = requireNode(layout, parentId);
|
|
1017
|
+
const arrange = requireStackArrange(layout, parentId);
|
|
1018
|
+
const contentRect = getArrangeContentRect(parent.rect, arrange);
|
|
1019
|
+
const isHorizontal = arrange.axis === "horizontal";
|
|
1020
|
+
const childIds = [...layout.children[parentId] ?? []];
|
|
1021
|
+
const childMetrics = childIds.map((id) => {
|
|
1022
|
+
const child = requireNode(layout, id);
|
|
1023
|
+
const rect = copyRect(child.rect);
|
|
1024
|
+
const mainStart = isHorizontal ? rect.x - contentRect.x : rect.y - contentRect.y;
|
|
1025
|
+
const mainSize = isHorizontal ? rect.width : rect.height;
|
|
1026
|
+
const crossStart = isHorizontal ? rect.y - contentRect.y : rect.x - contentRect.x;
|
|
1027
|
+
const crossSize = isHorizontal ? rect.height : rect.width;
|
|
858
1028
|
return {
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
1029
|
+
id,
|
|
1030
|
+
rect,
|
|
1031
|
+
mainStart,
|
|
1032
|
+
mainEnd: mainStart + mainSize,
|
|
1033
|
+
mainSize,
|
|
1034
|
+
crossStart,
|
|
1035
|
+
crossEnd: crossStart + crossSize,
|
|
1036
|
+
crossSize,
|
|
1037
|
+
frameKind: child.frame.kind,
|
|
1038
|
+
z: child.z,
|
|
1039
|
+
layer: child.layer
|
|
862
1040
|
};
|
|
1041
|
+
});
|
|
1042
|
+
const contentMainSize = isHorizontal ? contentRect.width : contentRect.height;
|
|
1043
|
+
const contentCrossSize = isHorizontal ? contentRect.height : contentRect.width;
|
|
1044
|
+
const totalChildMainSize = childMetrics.reduce((sum, metric) => sum + metric.mainSize, 0);
|
|
1045
|
+
const totalGapSize = (arrange.gap ?? 0) * Math.max(0, childMetrics.length - 1);
|
|
1046
|
+
const usedMainSize = totalChildMainSize + totalGapSize;
|
|
1047
|
+
return {
|
|
1048
|
+
parentId,
|
|
1049
|
+
axis: arrange.axis,
|
|
1050
|
+
parentRect: copyRect(parent.rect),
|
|
1051
|
+
contentRect,
|
|
1052
|
+
padding: normalizePadding(arrange.padding),
|
|
1053
|
+
gap: arrange.gap ?? 0,
|
|
1054
|
+
childIds,
|
|
1055
|
+
childMetrics,
|
|
1056
|
+
contentMainSize,
|
|
1057
|
+
contentCrossSize,
|
|
1058
|
+
totalChildMainSize,
|
|
1059
|
+
totalGapSize,
|
|
1060
|
+
usedMainSize,
|
|
1061
|
+
unusedMainSize: contentMainSize - usedMainSize
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
function getStackChildRects(layout, parentId) {
|
|
1065
|
+
requireStackArrange(layout, parentId);
|
|
1066
|
+
const rects = {};
|
|
1067
|
+
for (const childId of layout.children[parentId] ?? []) {
|
|
1068
|
+
rects[childId] = copyRect(requireNode(layout, childId).rect);
|
|
863
1069
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
blocks.push({ kind: "paragraph", inline: [{ kind: "text", text: lineInfo.text }] });
|
|
880
|
-
i += 1;
|
|
881
|
-
continue;
|
|
882
|
-
}
|
|
883
|
-
const bullet = parseBulletLine(lineInfo.text);
|
|
884
|
-
if (bullet) {
|
|
885
|
-
const items = [];
|
|
886
|
-
let lastTop;
|
|
887
|
-
while (i < lines.length) {
|
|
888
|
-
const current = lines[i];
|
|
889
|
-
if (current.text.trim().length === 0) break;
|
|
890
|
-
const currentBullet = parseBulletLine(current.text);
|
|
891
|
-
if (!currentBullet) break;
|
|
892
|
-
if (/^\s*-\s+\[[ xX]\]\s+/.test(current.text)) {
|
|
893
|
-
diagnostics.push(makeDiagnostic("unsupported_syntax", "Task lists are not supported.", current.index, current.text.length || 1, current.line, 1));
|
|
894
|
-
}
|
|
895
|
-
if (currentBullet.depth > 2) {
|
|
896
|
-
diagnostics.push(makeDiagnostic("max_list_depth_exceeded", "Maximum bullet depth is 2.", current.index, current.text.length || 1, current.line, 1));
|
|
897
|
-
const parsed3 = parseInline(current.text.trim(), current.index + (current.text.length - current.text.trimStart().length), current.line);
|
|
898
|
-
diagnostics.push(...parsed3.diagnostics);
|
|
899
|
-
blocks.push({ kind: "paragraph", inline: parsed3.inline.length ? parsed3.inline : [{ kind: "text", text: current.text }] });
|
|
900
|
-
i += 1;
|
|
901
|
-
continue;
|
|
902
|
-
}
|
|
903
|
-
const parsed2 = parseInline(currentBullet.text, current.index + (currentBullet.depth === 1 ? 2 : 4), current.line);
|
|
904
|
-
diagnostics.push(...parsed2.diagnostics);
|
|
905
|
-
const item = { inline: parsed2.inline };
|
|
906
|
-
if (currentBullet.depth === 1) {
|
|
907
|
-
items.push(item);
|
|
908
|
-
lastTop = item;
|
|
909
|
-
} else if (lastTop) {
|
|
910
|
-
if (!lastTop.children) lastTop.children = [];
|
|
911
|
-
lastTop.children.push(item);
|
|
912
|
-
} else {
|
|
913
|
-
diagnostics.push(makeDiagnostic("unsupported_syntax", "Nested bullet requires a parent bullet.", current.index, current.text.length || 1, current.line, 1));
|
|
914
|
-
blocks.push({ kind: "paragraph", inline: [{ kind: "text", text: current.text }] });
|
|
915
|
-
}
|
|
916
|
-
i += 1;
|
|
917
|
-
}
|
|
918
|
-
blocks.push({ kind: "bulletList", items });
|
|
919
|
-
continue;
|
|
920
|
-
}
|
|
921
|
-
const paragraphLines = [];
|
|
922
|
-
while (i < lines.length && lines[i].text.trim().length > 0 && !parseBulletLine(lines[i].text) && !classifyForbiddenBlock(lines[i].text)) {
|
|
923
|
-
paragraphLines.push(lines[i]);
|
|
924
|
-
i += 1;
|
|
925
|
-
}
|
|
926
|
-
const paragraphText = paragraphLines.map((line) => line.text).join("\n");
|
|
927
|
-
const first = paragraphLines[0];
|
|
928
|
-
const parsed = parseInline(paragraphText, first?.index ?? 0, first?.line ?? 1);
|
|
929
|
-
diagnostics.push(...parsed.diagnostics);
|
|
930
|
-
blocks.push({ kind: "paragraph", inline: parsed.inline });
|
|
1070
|
+
return rects;
|
|
1071
|
+
}
|
|
1072
|
+
function getRemainingStackRect(layout, options) {
|
|
1073
|
+
const metrics = getStackMainAxisMetrics(layout, options.parentId);
|
|
1074
|
+
const byId = new Map(metrics.childMetrics.map((metric) => [metric.id, metric]));
|
|
1075
|
+
const after = options.afterChildren ?? [];
|
|
1076
|
+
const before = options.beforeChildren ?? [];
|
|
1077
|
+
const start = after.length === 0 ? 0 : Math.max(...after.map((id) => requireStackMetric(byId, id).mainEnd));
|
|
1078
|
+
const end = before.length === 0 ? metrics.contentMainSize : Math.min(...before.map((id) => requireStackMetric(byId, id).mainStart));
|
|
1079
|
+
const size = end - start;
|
|
1080
|
+
if (size < 0) {
|
|
1081
|
+
throw new MachinaLayoutError(
|
|
1082
|
+
"StackQueryInvalidRange",
|
|
1083
|
+
`remaining stack interval is negative for parent: ${options.parentId}`
|
|
1084
|
+
);
|
|
931
1085
|
}
|
|
932
|
-
return
|
|
1086
|
+
return metrics.axis === "horizontal" ? {
|
|
1087
|
+
x: metrics.contentRect.x + start,
|
|
1088
|
+
y: metrics.contentRect.y,
|
|
1089
|
+
width: size,
|
|
1090
|
+
height: metrics.contentRect.height
|
|
1091
|
+
} : {
|
|
1092
|
+
x: metrics.contentRect.x,
|
|
1093
|
+
y: metrics.contentRect.y + start,
|
|
1094
|
+
width: metrics.contentRect.width,
|
|
1095
|
+
height: size
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
function requireStackMetric(metrics, childId) {
|
|
1099
|
+
const metric = metrics.get(childId);
|
|
1100
|
+
if (!metric) throw new MachinaLayoutError("InvalidId", `stack child id not found: ${childId}`);
|
|
1101
|
+
return metric;
|
|
933
1102
|
}
|
|
934
1103
|
|
|
935
|
-
// src/
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
caption: { fontSize: "11px", fontWeight: 400, lineHeight: 1.25, opacity: 0.8 },
|
|
944
|
-
title: { fontSize: "18px", fontWeight: 700, lineHeight: 1.25 },
|
|
945
|
-
mono: { fontSize: "12px", lineHeight: 1.35, fontFamily: INLINE_CODE_FONT }
|
|
946
|
-
};
|
|
947
|
-
function isMachinaTextDocument(value) {
|
|
948
|
-
return typeof value === "object" && value !== null && "blocks" in value;
|
|
1104
|
+
// src/lerp.ts
|
|
1105
|
+
function assertFiniteNumber2(value) {
|
|
1106
|
+
if (!Number.isFinite(value)) {
|
|
1107
|
+
throw new MachinaLayoutError(
|
|
1108
|
+
"NonFiniteNumber",
|
|
1109
|
+
`Expected finite number, got ${String(value)}.`
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
949
1112
|
}
|
|
950
|
-
function
|
|
951
|
-
|
|
1113
|
+
function sameStringArray(a, b) {
|
|
1114
|
+
if (a.length !== b.length) {
|
|
1115
|
+
return false;
|
|
1116
|
+
}
|
|
1117
|
+
for (let index = 0; index < a.length; index += 1) {
|
|
1118
|
+
if (a[index] !== b[index]) {
|
|
1119
|
+
return false;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
return true;
|
|
952
1123
|
}
|
|
953
|
-
function
|
|
954
|
-
|
|
1124
|
+
function assertCompatibleResolvedLayouts(a, b) {
|
|
1125
|
+
if (a.rootId !== b.rootId) {
|
|
1126
|
+
throw new MachinaLayoutError(
|
|
1127
|
+
"IncompatibleLayouts",
|
|
1128
|
+
`Layout roots differ: ${a.rootId} !== ${b.rootId}.`
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
if (!(a.rootId in a.nodes) || !(b.rootId in b.nodes)) {
|
|
1132
|
+
throw new MachinaLayoutError(
|
|
1133
|
+
"IncompatibleLayouts",
|
|
1134
|
+
`Root id ${a.rootId} must exist in both node maps.`
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
const aNodeIds = Object.keys(a.nodes).sort();
|
|
1138
|
+
const bNodeIds = Object.keys(b.nodes).sort();
|
|
1139
|
+
if (!sameStringArray(aNodeIds, bNodeIds)) {
|
|
1140
|
+
throw new MachinaLayoutError(
|
|
1141
|
+
"IncompatibleLayouts",
|
|
1142
|
+
"Resolved layouts must have the same node ids."
|
|
1143
|
+
);
|
|
1144
|
+
}
|
|
1145
|
+
const aParentIds = Object.keys(a.children).sort();
|
|
1146
|
+
const bParentIds = Object.keys(b.children).sort();
|
|
1147
|
+
if (!sameStringArray(aParentIds, bParentIds)) {
|
|
1148
|
+
throw new MachinaLayoutError(
|
|
1149
|
+
"IncompatibleLayouts",
|
|
1150
|
+
"Resolved layouts must have the same parent-child map."
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
1153
|
+
for (const parentId of aParentIds) {
|
|
1154
|
+
const aChildren = a.children[parentId] ?? [];
|
|
1155
|
+
const bChildren = b.children[parentId] ?? [];
|
|
1156
|
+
if (!sameStringArray(aChildren, bChildren)) {
|
|
1157
|
+
throw new MachinaLayoutError(
|
|
1158
|
+
"IncompatibleLayouts",
|
|
1159
|
+
`Child order differs for parent ${parentId}.`
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
955
1163
|
}
|
|
956
|
-
function
|
|
957
|
-
|
|
1164
|
+
function copyChildren(children) {
|
|
1165
|
+
const copied = {};
|
|
1166
|
+
for (const [parentId, childIds] of Object.entries(children)) {
|
|
1167
|
+
copied[parentId] = [...childIds];
|
|
1168
|
+
}
|
|
1169
|
+
return copied;
|
|
958
1170
|
}
|
|
959
|
-
function
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1171
|
+
function lerpNumber(a, b, t) {
|
|
1172
|
+
assertFiniteNumber2(a);
|
|
1173
|
+
assertFiniteNumber2(b);
|
|
1174
|
+
assertFiniteNumber2(t);
|
|
1175
|
+
return a + (b - a) * t;
|
|
963
1176
|
}
|
|
964
|
-
function
|
|
1177
|
+
function lerpRect(a, b, t) {
|
|
965
1178
|
return {
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
leading: normalizeLeading(spec.leading),
|
|
971
|
-
blockGap: normalizeNonNegative(spec.blockGap, DEFAULT_POLICY.blockGap),
|
|
972
|
-
listGap: normalizeNonNegative(spec.listGap, DEFAULT_POLICY.listGap),
|
|
973
|
-
valign: spec.valign ?? DEFAULT_POLICY.valign
|
|
1179
|
+
x: lerpNumber(a.x, b.x, t),
|
|
1180
|
+
y: lerpNumber(a.y, b.y, t),
|
|
1181
|
+
width: lerpNumber(a.width, b.width, t),
|
|
1182
|
+
height: lerpNumber(a.height, b.height, t)
|
|
974
1183
|
};
|
|
975
1184
|
}
|
|
976
|
-
function
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1185
|
+
function lerpResolvedLayouts(a, b, t) {
|
|
1186
|
+
assertFiniteNumber2(t);
|
|
1187
|
+
assertCompatibleResolvedLayouts(a, b);
|
|
1188
|
+
const nodes = {};
|
|
1189
|
+
for (const id of Object.keys(b.nodes)) {
|
|
1190
|
+
const aNode = a.nodes[id];
|
|
1191
|
+
const bNode = b.nodes[id];
|
|
1192
|
+
nodes[id] = {
|
|
1193
|
+
...bNode,
|
|
1194
|
+
rect: lerpRect(aNode.rect, bNode.rect, t)
|
|
1195
|
+
};
|
|
981
1196
|
}
|
|
982
|
-
const result = parseMachinaText(typeof text === "string" ? { kind: "machina-text", text } : text);
|
|
983
|
-
return { document: result.document, diagnostics: result.diagnostics, policy: DEFAULT_POLICY };
|
|
984
|
-
}
|
|
985
|
-
function resolveLineHeight(policy) {
|
|
986
|
-
if (policy.leading === "tight") return 1.15;
|
|
987
|
-
if (policy.leading === "loose") return 1.6;
|
|
988
|
-
if (typeof policy.leading === "number") return policy.leading;
|
|
989
|
-
return VARIANT_STYLE[policy.variant].lineHeight;
|
|
990
|
-
}
|
|
991
|
-
function policyStyle(policy) {
|
|
992
|
-
const wrapStyle = { word: { whiteSpace: "normal", overflowWrap: "anywhere" }, none: { whiteSpace: "nowrap" } };
|
|
993
|
-
const overflowStyle = {
|
|
994
|
-
clip: { overflow: "hidden" },
|
|
995
|
-
ellipsis: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
996
|
-
scroll: { overflow: "auto" }
|
|
997
|
-
};
|
|
998
|
-
const alignStyle = { start: { textAlign: "left" }, center: { textAlign: "center" }, end: { textAlign: "right" } };
|
|
999
|
-
const justifyContent = {
|
|
1000
|
-
top: "flex-start",
|
|
1001
|
-
center: "center",
|
|
1002
|
-
bottom: "flex-end"
|
|
1003
|
-
};
|
|
1004
1197
|
return {
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
display: "flex",
|
|
1009
|
-
flexDirection: "column",
|
|
1010
|
-
justifyContent: justifyContent[policy.valign],
|
|
1011
|
-
minWidth: 0,
|
|
1012
|
-
...VARIANT_STYLE[policy.variant],
|
|
1013
|
-
lineHeight: resolveLineHeight(policy),
|
|
1014
|
-
...wrapStyle[policy.wrap],
|
|
1015
|
-
...overflowStyle[policy.overflow],
|
|
1016
|
-
...alignStyle[policy.align]
|
|
1198
|
+
rootId: b.rootId,
|
|
1199
|
+
nodes,
|
|
1200
|
+
children: copyChildren(b.children)
|
|
1017
1201
|
};
|
|
1018
1202
|
}
|
|
1019
|
-
function renderInline(inline, key, props) {
|
|
1020
|
-
switch (inline.kind) {
|
|
1021
|
-
case "text":
|
|
1022
|
-
return /* @__PURE__ */ jsx2(React2.Fragment, { children: inline.text }, key);
|
|
1023
|
-
case "strong":
|
|
1024
|
-
return /* @__PURE__ */ jsx2("strong", { children: inline.children.map((c, i) => renderInline(c, `${key}-s-${i}`, props)) }, key);
|
|
1025
|
-
case "emphasis":
|
|
1026
|
-
return /* @__PURE__ */ jsx2("em", { children: inline.children.map((c, i) => renderInline(c, `${key}-e-${i}`, props)) }, key);
|
|
1027
|
-
case "code":
|
|
1028
|
-
return /* @__PURE__ */ jsx2("code", { style: { fontFamily: INLINE_CODE_FONT, backgroundColor: "rgba(127, 127, 127, 0.15)", borderRadius: 3, padding: "0 0.25em" }, children: inline.text }, key);
|
|
1029
|
-
case "link": {
|
|
1030
|
-
const rel = props.linkTarget === "_blank" ? "noreferrer noopener" : void 0;
|
|
1031
|
-
return /* @__PURE__ */ jsx2("a", { href: inline.href, target: props.linkTarget, rel, onClick: (event) => props.onLinkClick?.(inline.href, event), children: inline.children.map((c, i) => renderInline(c, `${key}-l-${i}`, props)) }, key);
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
function renderBulletItem(item, path, props, listGap) {
|
|
1036
|
-
return /* @__PURE__ */ jsxs2("li", { style: { marginBottom: listGap }, children: [
|
|
1037
|
-
item.inline.map((i, idx) => renderInline(i, `${path}-i-${idx}`, props)),
|
|
1038
|
-
item.children?.length ? /* @__PURE__ */ jsx2("ul", { style: { margin: "0.25em 0 0 0", paddingLeft: "1.25em" }, children: item.children.map((c, idx) => renderBulletItem(c, `${path}-c-${idx}`, props, listGap)) }) : null
|
|
1039
|
-
] }, path);
|
|
1040
|
-
}
|
|
1041
|
-
function MachinaTextView(props) {
|
|
1042
|
-
const normalized = normalizeText(props.text);
|
|
1043
|
-
return /* @__PURE__ */ jsx2("div", { className: props.className, style: { ...policyStyle(normalized.policy), ...props.style }, children: /* @__PURE__ */ jsxs2("div", { style: { minWidth: 0 }, children: [
|
|
1044
|
-
normalized.document.blocks.map((block, index) => block.kind === "paragraph" ? /* @__PURE__ */ jsx2("p", { style: { margin: index === normalized.document.blocks.length - 1 ? "0" : `0 0 ${normalized.policy.blockGap}px 0` }, children: block.inline.map((i, idx) => renderInline(i, `b-${index}-${idx}`, props)) }, `b-${index}`) : /* @__PURE__ */ jsx2("ul", { style: { margin: index === normalized.document.blocks.length - 1 ? "0" : `0 0 ${normalized.policy.blockGap}px 0`, paddingLeft: "1.25em" }, children: block.items.map((item, itemIndex) => renderBulletItem(item, `b-${index}-item-${itemIndex}`, props, normalized.policy.listGap)) }, `b-${index}`)),
|
|
1045
|
-
props.showDiagnostics && normalized.diagnostics.length > 0 ? /* @__PURE__ */ jsx2("pre", { style: { margin: `${normalized.policy.blockGap}px 0 0 0`, padding: "0.5em", fontSize: "11px", fontFamily: INLINE_CODE_FONT, whiteSpace: "pre-wrap", background: "rgba(127, 127, 127, 0.12)" }, children: normalized.diagnostics.map((d) => `${d.code} (${d.line}:${d.column}) ${d.message}`).join("\n") }) : null
|
|
1046
|
-
] }) });
|
|
1047
|
-
}
|
|
1048
1203
|
export {
|
|
1049
1204
|
MachinaLayoutError,
|
|
1050
1205
|
MachinaReactView,
|
|
@@ -1055,8 +1210,21 @@ export {
|
|
|
1055
1210
|
assertNonNegativePadding,
|
|
1056
1211
|
assertNonNegativeSize,
|
|
1057
1212
|
compileLayoutRows,
|
|
1213
|
+
createViewportMatrix,
|
|
1214
|
+
defineMachinaScreens,
|
|
1215
|
+
defineMachinaViewports,
|
|
1216
|
+
expandScreenViewportTasks,
|
|
1058
1217
|
flattenResolvedTree,
|
|
1059
1218
|
formatRect,
|
|
1219
|
+
getArrangeContentRect,
|
|
1220
|
+
getMachinaViewport,
|
|
1221
|
+
getRemainingStackRect,
|
|
1222
|
+
getStackChildRects,
|
|
1223
|
+
getStackContentRect,
|
|
1224
|
+
getStackMainAxisMetrics,
|
|
1225
|
+
lerpNumber,
|
|
1226
|
+
lerpRect,
|
|
1227
|
+
lerpResolvedLayouts,
|
|
1060
1228
|
normalizePadding,
|
|
1061
1229
|
parseMachinaText,
|
|
1062
1230
|
parseMachinaTextInline,
|
|
@@ -1064,5 +1232,7 @@ export {
|
|
|
1064
1232
|
resolveLayoutDocument,
|
|
1065
1233
|
resolveLayoutRows,
|
|
1066
1234
|
resolveUiLength,
|
|
1235
|
+
selectLayoutRowsForRoot,
|
|
1236
|
+
slugMachinaArtifactName,
|
|
1067
1237
|
toResolvedTree
|
|
1068
1238
|
};
|