machinalayout 0.1.0 → 0.2.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 +280 -49
- package/dist/chunk-BJOQRPPX.js +382 -0
- package/dist/chunk-HU6XYOH7.js +133 -0
- package/dist/chunk-KYWOCAHK.js +205 -0
- package/dist/chunk-RJYRJ3LD.js +0 -0
- package/dist/chunk-TR24ERZT.js +66 -0
- package/dist/dispatch/index.d.ts +49 -0
- package/dist/dispatch/index.js +217 -0
- package/dist/index.d.ts +15 -238
- package/dist/index.js +596 -591
- package/dist/react/index.d.ts +33 -0
- package/dist/react/index.js +7 -0
- package/dist/react-native/index.d.ts +30 -0
- package/dist/react-native/index.js +83 -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-BudfpzZX.d.ts +184 -0
- package/dist/types-C4poVJpR.d.ts +74 -0
- package/dist/vue/index.d.ts +173 -0
- package/dist/vue/index.js +111 -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/error-codes.md +84 -0
- package/docs/grid-arrange-m5a-contract.md +480 -0
- package/docs/grid-arrange.md +51 -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 +51 -69
- 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/vue-adapter.md +55 -0
- package/docs/vue-text-renderer.md +55 -0
- package/package.json +60 -5
package/dist/index.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import {
|
|
2
|
+
MachinaReactView
|
|
3
|
+
} from "./chunk-HU6XYOH7.js";
|
|
4
|
+
import "./chunk-RJYRJ3LD.js";
|
|
5
|
+
import {
|
|
6
|
+
MachinaTextView
|
|
7
|
+
} from "./chunk-KYWOCAHK.js";
|
|
8
|
+
import {
|
|
9
|
+
parseMachinaText,
|
|
10
|
+
parseMachinaTextInline
|
|
11
|
+
} from "./chunk-BJOQRPPX.js";
|
|
12
|
+
import {
|
|
13
|
+
MachinaLayoutError,
|
|
14
|
+
toResolvedTree
|
|
15
|
+
} from "./chunk-TR24ERZT.js";
|
|
10
16
|
|
|
11
17
|
// src/validation.ts
|
|
12
18
|
function assertFiniteNumber(value, fieldName) {
|
|
@@ -83,7 +89,10 @@ function resolveUiLength(length, axisSize, fieldName = "length") {
|
|
|
83
89
|
if (unit === "ui") {
|
|
84
90
|
return value * axisSize;
|
|
85
91
|
}
|
|
86
|
-
throw new MachinaLayoutError(
|
|
92
|
+
throw new MachinaLayoutError(
|
|
93
|
+
"InvalidLengthUnit",
|
|
94
|
+
`Invalid UiLength unit for ${fieldName}: ${String(unit)}.`
|
|
95
|
+
);
|
|
87
96
|
}
|
|
88
97
|
|
|
89
98
|
// src/offset.ts
|
|
@@ -118,12 +127,18 @@ function compileLayoutRows(rows) {
|
|
|
118
127
|
assertFiniteNumber(row.order, `rows[${rowIndex}].order`);
|
|
119
128
|
}
|
|
120
129
|
if (row.frame.kind === "root" && row.parent !== void 0) {
|
|
121
|
-
throw new MachinaLayoutError(
|
|
130
|
+
throw new MachinaLayoutError(
|
|
131
|
+
"RootFrameNotRoot",
|
|
132
|
+
`row ${row.id} uses RootFrame but is not a root row.`
|
|
133
|
+
);
|
|
122
134
|
}
|
|
123
135
|
if (row.z !== void 0) {
|
|
124
136
|
assertFiniteNumber(row.z, `rows[${rowIndex}].z`);
|
|
125
137
|
if (!Number.isInteger(row.z) || row.z < -5 || row.z > 5) {
|
|
126
|
-
throw new MachinaLayoutError(
|
|
138
|
+
throw new MachinaLayoutError(
|
|
139
|
+
"InvalidZ",
|
|
140
|
+
`rows[${rowIndex}].z must be an integer in range -5..5`
|
|
141
|
+
);
|
|
127
142
|
}
|
|
128
143
|
}
|
|
129
144
|
rowById.set(row.id, row);
|
|
@@ -135,6 +150,7 @@ function compileLayoutRows(rows) {
|
|
|
135
150
|
view: row.view,
|
|
136
151
|
slot: row.slot,
|
|
137
152
|
debugLabel: row.debugLabel,
|
|
153
|
+
layer: row.layer,
|
|
138
154
|
offset: row.offset
|
|
139
155
|
};
|
|
140
156
|
if (row.parent === void 0) {
|
|
@@ -149,10 +165,16 @@ function compileLayoutRows(rows) {
|
|
|
149
165
|
}
|
|
150
166
|
const rootId = rootCandidates[0];
|
|
151
167
|
if (nodes[rootId].frame.kind === "fill") {
|
|
152
|
-
throw new MachinaLayoutError(
|
|
168
|
+
throw new MachinaLayoutError(
|
|
169
|
+
"FillFrameWithoutArranger",
|
|
170
|
+
"FillFrame cannot be used as the root frame."
|
|
171
|
+
);
|
|
153
172
|
}
|
|
154
173
|
if (nodes[rootId].frame.kind === "fixed") {
|
|
155
|
-
throw new MachinaLayoutError(
|
|
174
|
+
throw new MachinaLayoutError(
|
|
175
|
+
"FixedFrameWithoutArranger",
|
|
176
|
+
"FixedFrame cannot be used as the root frame."
|
|
177
|
+
);
|
|
156
178
|
}
|
|
157
179
|
const childrenEntries = /* @__PURE__ */ new Map();
|
|
158
180
|
for (let rowIndex = 0; rowIndex < rows.length; rowIndex += 1) {
|
|
@@ -164,7 +186,10 @@ function compileLayoutRows(rows) {
|
|
|
164
186
|
throw new MachinaLayoutError("SelfParent", `node ${row.id} cannot parent itself.`);
|
|
165
187
|
}
|
|
166
188
|
if (!rowById.has(row.parent) || row.parent.trim().length === 0) {
|
|
167
|
-
throw new MachinaLayoutError(
|
|
189
|
+
throw new MachinaLayoutError(
|
|
190
|
+
"UnknownParent",
|
|
191
|
+
`node ${row.id} references unknown parent: ${row.parent}`
|
|
192
|
+
);
|
|
168
193
|
}
|
|
169
194
|
const entry = {
|
|
170
195
|
childId: row.id,
|
|
@@ -233,6 +258,101 @@ function compileLayoutRows(rows) {
|
|
|
233
258
|
return { rootId, nodes, children };
|
|
234
259
|
}
|
|
235
260
|
|
|
261
|
+
// src/selectLayoutRowsForRoot.ts
|
|
262
|
+
function validateRootRect(rootRect) {
|
|
263
|
+
assertFiniteNumber(rootRect.x, "rootRect.x");
|
|
264
|
+
assertFiniteNumber(rootRect.y, "rootRect.y");
|
|
265
|
+
assertNonNegativeSize(rootRect.width, "rootRect.width");
|
|
266
|
+
assertNonNegativeSize(rootRect.height, "rootRect.height");
|
|
267
|
+
}
|
|
268
|
+
function validateCondition(condition, rowIndex, variantIndex) {
|
|
269
|
+
if (condition.minWidth !== void 0) {
|
|
270
|
+
assertFiniteNumber(
|
|
271
|
+
condition.minWidth,
|
|
272
|
+
`rows[${rowIndex}].variants[${variantIndex}].when.minWidth`
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
if (condition.maxWidth !== void 0) {
|
|
276
|
+
assertFiniteNumber(
|
|
277
|
+
condition.maxWidth,
|
|
278
|
+
`rows[${rowIndex}].variants[${variantIndex}].when.maxWidth`
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
if (condition.minHeight !== void 0) {
|
|
282
|
+
assertFiniteNumber(
|
|
283
|
+
condition.minHeight,
|
|
284
|
+
`rows[${rowIndex}].variants[${variantIndex}].when.minHeight`
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
if (condition.maxHeight !== void 0) {
|
|
288
|
+
assertFiniteNumber(
|
|
289
|
+
condition.maxHeight,
|
|
290
|
+
`rows[${rowIndex}].variants[${variantIndex}].when.maxHeight`
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
if (condition.minWidth !== void 0 && condition.maxWidth !== void 0 && condition.minWidth > condition.maxWidth) {
|
|
294
|
+
throw new MachinaLayoutError(
|
|
295
|
+
"InvalidVariantCondition",
|
|
296
|
+
`rows[${rowIndex}].variants[${variantIndex}].when has minWidth > maxWidth`
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
if (condition.minHeight !== void 0 && condition.maxHeight !== void 0 && condition.minHeight > condition.maxHeight) {
|
|
300
|
+
throw new MachinaLayoutError(
|
|
301
|
+
"InvalidVariantCondition",
|
|
302
|
+
`rows[${rowIndex}].variants[${variantIndex}].when has minHeight > maxHeight`
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function conditionMatches(condition, rootRect) {
|
|
307
|
+
if (condition.minWidth !== void 0 && rootRect.width < condition.minWidth) return false;
|
|
308
|
+
if (condition.maxWidth !== void 0 && rootRect.width > condition.maxWidth) return false;
|
|
309
|
+
if (condition.minHeight !== void 0 && rootRect.height < condition.minHeight) return false;
|
|
310
|
+
if (condition.maxHeight !== void 0 && rootRect.height > condition.maxHeight) return false;
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
function validateVariantZ(variant, rowIndex, variantIndex) {
|
|
314
|
+
if (variant.z === void 0) return;
|
|
315
|
+
assertFiniteNumber(variant.z, `rows[${rowIndex}].variants[${variantIndex}].z`);
|
|
316
|
+
if (!Number.isInteger(variant.z) || variant.z < -5 || variant.z > 5) {
|
|
317
|
+
throw new MachinaLayoutError(
|
|
318
|
+
"InvalidZ",
|
|
319
|
+
`rows[${rowIndex}].variants[${variantIndex}].z must be an integer in range -5..5`
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function selectLayoutRowsForRoot(rows, rootRect) {
|
|
324
|
+
validateRootRect(rootRect);
|
|
325
|
+
return rows.map((row, rowIndex) => {
|
|
326
|
+
const baseRow = { ...row };
|
|
327
|
+
delete baseRow.variants;
|
|
328
|
+
const variants = row.variants;
|
|
329
|
+
if (!variants || variants.length === 0) {
|
|
330
|
+
return baseRow;
|
|
331
|
+
}
|
|
332
|
+
for (let variantIndex = 0; variantIndex < variants.length; variantIndex += 1) {
|
|
333
|
+
const variant = variants[variantIndex];
|
|
334
|
+
validateCondition(variant.when, rowIndex, variantIndex);
|
|
335
|
+
validateVariantZ(variant, rowIndex, variantIndex);
|
|
336
|
+
if (!conditionMatches(variant.when, rootRect)) {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
const selected = {
|
|
340
|
+
...baseRow,
|
|
341
|
+
frame: variant.frame ?? baseRow.frame,
|
|
342
|
+
arrange: variant.arrange ?? baseRow.arrange,
|
|
343
|
+
offset: variant.offset ?? baseRow.offset,
|
|
344
|
+
z: variant.z ?? baseRow.z,
|
|
345
|
+
view: variant.view ?? baseRow.view,
|
|
346
|
+
slot: variant.slot ?? baseRow.slot,
|
|
347
|
+
debugLabel: variant.debugLabel ?? baseRow.debugLabel,
|
|
348
|
+
layer: variant.layer ?? baseRow.layer
|
|
349
|
+
};
|
|
350
|
+
return selected;
|
|
351
|
+
}
|
|
352
|
+
return baseRow;
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
236
356
|
// src/resolveFrame.ts
|
|
237
357
|
function validateParentRect(parent) {
|
|
238
358
|
assertFiniteNumber(parent.x, "parent.x");
|
|
@@ -260,11 +380,17 @@ function resolveAnchor(parent, frame) {
|
|
|
260
380
|
if (hasHeight) assertNonNegativeSize(explicitHeight, "frame.height");
|
|
261
381
|
const horizontalCount = Number(hasLeft) + Number(hasRight) + Number(hasWidth);
|
|
262
382
|
if (horizontalCount !== 2) {
|
|
263
|
-
throw new MachinaLayoutError(
|
|
383
|
+
throw new MachinaLayoutError(
|
|
384
|
+
"InvalidAnchorHorizontal",
|
|
385
|
+
"Anchor frame must specify exactly two horizontal constraints: left, right, width."
|
|
386
|
+
);
|
|
264
387
|
}
|
|
265
388
|
const verticalCount = Number(hasTop) + Number(hasBottom) + Number(hasHeight);
|
|
266
389
|
if (verticalCount !== 2) {
|
|
267
|
-
throw new MachinaLayoutError(
|
|
390
|
+
throw new MachinaLayoutError(
|
|
391
|
+
"InvalidAnchorVertical",
|
|
392
|
+
"Anchor frame must specify exactly two vertical constraints: top, bottom, height."
|
|
393
|
+
);
|
|
268
394
|
}
|
|
269
395
|
let x;
|
|
270
396
|
let width;
|
|
@@ -306,29 +432,159 @@ function resolveFrame(parent, frame) {
|
|
|
306
432
|
assertFiniteNumber(frame.y, "frame.y");
|
|
307
433
|
assertNonNegativeSize(frame.width, "frame.width");
|
|
308
434
|
assertNonNegativeSize(frame.height, "frame.height");
|
|
309
|
-
return {
|
|
435
|
+
return {
|
|
436
|
+
x: parent.x + frame.x,
|
|
437
|
+
y: parent.y + frame.y,
|
|
438
|
+
width: frame.width,
|
|
439
|
+
height: frame.height
|
|
440
|
+
};
|
|
310
441
|
}
|
|
311
442
|
case "anchor":
|
|
312
443
|
return resolveAnchor(parent, frame);
|
|
313
444
|
case "root":
|
|
314
|
-
throw new MachinaLayoutError(
|
|
445
|
+
throw new MachinaLayoutError(
|
|
446
|
+
"RootFrameWithoutRoot",
|
|
447
|
+
"RootFrame can only be declared on the root row."
|
|
448
|
+
);
|
|
315
449
|
case "fixed": {
|
|
316
450
|
assertNonNegativeSize(frame.width, "frame.width");
|
|
317
451
|
assertNonNegativeSize(frame.height, "frame.height");
|
|
318
|
-
throw new MachinaLayoutError(
|
|
452
|
+
throw new MachinaLayoutError(
|
|
453
|
+
"FixedFrameWithoutArranger",
|
|
454
|
+
"Fixed frames require an arranger to determine placement."
|
|
455
|
+
);
|
|
319
456
|
}
|
|
320
457
|
case "fill":
|
|
321
|
-
throw new MachinaLayoutError(
|
|
458
|
+
throw new MachinaLayoutError(
|
|
459
|
+
"FillFrameWithoutArranger",
|
|
460
|
+
"Fill frames require a stack arranger to determine placement."
|
|
461
|
+
);
|
|
462
|
+
case "cell":
|
|
463
|
+
throw new MachinaLayoutError(
|
|
464
|
+
"CellFrameWithoutGrid",
|
|
465
|
+
"Cell frames require a grid arranger to determine placement."
|
|
466
|
+
);
|
|
467
|
+
case "guide":
|
|
468
|
+
throw new MachinaLayoutError(
|
|
469
|
+
"GuideTargetUnresolved",
|
|
470
|
+
"Guide frames require document-level dependency resolution and cannot be resolved directly."
|
|
471
|
+
);
|
|
322
472
|
}
|
|
323
473
|
}
|
|
324
474
|
|
|
325
475
|
// src/resolveLayoutDocument.ts
|
|
326
|
-
function
|
|
476
|
+
function validateRootRect2(rootRect) {
|
|
327
477
|
assertFiniteNumber(rootRect.x, "rootRect.x");
|
|
328
478
|
assertFiniteNumber(rootRect.y, "rootRect.y");
|
|
329
479
|
assertNonNegativeSize(rootRect.width, "rootRect.width");
|
|
330
480
|
assertNonNegativeSize(rootRect.height, "rootRect.height");
|
|
331
481
|
}
|
|
482
|
+
var H_EDGES = /* @__PURE__ */ new Set(["left", "right", "centerX"]);
|
|
483
|
+
var V_EDGES = /* @__PURE__ */ new Set(["top", "bottom", "centerY"]);
|
|
484
|
+
var ALL_EDGES = /* @__PURE__ */ new Set(["left", "right", "top", "bottom", "centerX", "centerY"]);
|
|
485
|
+
var isEdgeRef = (value) => Boolean(value && typeof value === "object" && "ref" in value && "edge" in value);
|
|
486
|
+
function getRectEdgeValue(rect, edge) {
|
|
487
|
+
switch (edge) {
|
|
488
|
+
case "left":
|
|
489
|
+
return rect.x;
|
|
490
|
+
case "right":
|
|
491
|
+
return rect.x + rect.width;
|
|
492
|
+
case "centerX":
|
|
493
|
+
return rect.x + rect.width / 2;
|
|
494
|
+
case "top":
|
|
495
|
+
return rect.y;
|
|
496
|
+
case "bottom":
|
|
497
|
+
return rect.y + rect.height;
|
|
498
|
+
case "centerY":
|
|
499
|
+
return rect.y + rect.height / 2;
|
|
500
|
+
default:
|
|
501
|
+
throw new MachinaLayoutError("InvalidGuideFrame", `unknown guide edge: ${String(edge)}`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
function validateGuideFrame(nodeId, frame, document) {
|
|
505
|
+
const hCount = Number(frame.left !== void 0) + Number(frame.right !== void 0) + Number(frame.width !== void 0);
|
|
506
|
+
const vCount = Number(frame.top !== void 0) + Number(frame.bottom !== void 0) + Number(frame.height !== void 0);
|
|
507
|
+
if (hCount !== 2 || vCount !== 2)
|
|
508
|
+
throw new MachinaLayoutError(
|
|
509
|
+
"InvalidGuideFrame",
|
|
510
|
+
`guide frame must provide exactly two constraints per axis: ${nodeId}`
|
|
511
|
+
);
|
|
512
|
+
const hRefs = [frame.left, frame.right].filter(isEdgeRef);
|
|
513
|
+
const vRefs = [frame.top, frame.bottom].filter(isEdgeRef);
|
|
514
|
+
if (hRefs.length > 1 || vRefs.length > 1)
|
|
515
|
+
throw new MachinaLayoutError(
|
|
516
|
+
"GuideTooManyReferencesPerAxis",
|
|
517
|
+
`guide has too many refs on one axis: ${nodeId}`
|
|
518
|
+
);
|
|
519
|
+
for (const ref of [...hRefs, ...vRefs]) {
|
|
520
|
+
if (ref.ref === nodeId)
|
|
521
|
+
throw new MachinaLayoutError(
|
|
522
|
+
"GuideSelfReference",
|
|
523
|
+
`guide cannot reference itself: ${nodeId}`
|
|
524
|
+
);
|
|
525
|
+
if (!document.nodes[ref.ref])
|
|
526
|
+
throw new MachinaLayoutError("GuideTargetNotFound", `guide target not found: ${ref.ref}`);
|
|
527
|
+
if (!ALL_EDGES.has(ref.edge))
|
|
528
|
+
throw new MachinaLayoutError("InvalidGuideFrame", `unknown edge: ${String(ref.edge)}`);
|
|
529
|
+
}
|
|
530
|
+
for (const ref of hRefs)
|
|
531
|
+
if (!H_EDGES.has(ref.edge))
|
|
532
|
+
throw new MachinaLayoutError(
|
|
533
|
+
"GuideInvalidEdgeForAxis",
|
|
534
|
+
`horizontal guide ref must use horizontal edge: ${nodeId}`
|
|
535
|
+
);
|
|
536
|
+
for (const ref of vRefs)
|
|
537
|
+
if (!V_EDGES.has(ref.edge))
|
|
538
|
+
throw new MachinaLayoutError(
|
|
539
|
+
"GuideInvalidEdgeForAxis",
|
|
540
|
+
`vertical guide ref must use vertical edge: ${nodeId}`
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
function resolveGuidePosition(parentRect, side, value, resolvedNodes) {
|
|
544
|
+
if (isEdgeRef(value)) {
|
|
545
|
+
const target = resolvedNodes[value.ref];
|
|
546
|
+
if (!target)
|
|
547
|
+
throw new MachinaLayoutError(
|
|
548
|
+
"GuideTargetUnresolved",
|
|
549
|
+
`guide target unresolved: ${value.ref}`
|
|
550
|
+
);
|
|
551
|
+
const axisSize2 = side === "left" || side === "right" ? parentRect.width : parentRect.height;
|
|
552
|
+
const offset = value.offset === void 0 ? 0 : resolveUiLength(value.offset, axisSize2, `frame.${side}.offset`);
|
|
553
|
+
return getRectEdgeValue(target.rect, value.edge) + offset;
|
|
554
|
+
}
|
|
555
|
+
const axisSize = side === "left" || side === "right" ? parentRect.width : parentRect.height;
|
|
556
|
+
const scalar = resolveUiLength(value, axisSize, `frame.${side}`);
|
|
557
|
+
if (side === "left") return parentRect.x + scalar;
|
|
558
|
+
if (side === "right") return parentRect.x + parentRect.width - scalar;
|
|
559
|
+
if (side === "top") return parentRect.y + scalar;
|
|
560
|
+
return parentRect.y + parentRect.height - scalar;
|
|
561
|
+
}
|
|
562
|
+
function resolveGuideFrame(parentRect, frame, resolvedNodes) {
|
|
563
|
+
const hasLeft = frame.left !== void 0;
|
|
564
|
+
const hasRight = frame.right !== void 0;
|
|
565
|
+
const hasWidth = frame.width !== void 0;
|
|
566
|
+
const hasTop = frame.top !== void 0;
|
|
567
|
+
const hasBottom = frame.bottom !== void 0;
|
|
568
|
+
const hasHeight = frame.height !== void 0;
|
|
569
|
+
const left = hasLeft ? resolveGuidePosition(parentRect, "left", frame.left, resolvedNodes) : void 0;
|
|
570
|
+
const right = hasRight ? resolveGuidePosition(parentRect, "right", frame.right, resolvedNodes) : void 0;
|
|
571
|
+
const top = hasTop ? resolveGuidePosition(parentRect, "top", frame.top, resolvedNodes) : void 0;
|
|
572
|
+
const bottom = hasBottom ? resolveGuidePosition(parentRect, "bottom", frame.bottom, resolvedNodes) : void 0;
|
|
573
|
+
const explicitWidth = hasWidth ? resolveUiLength(frame.width, parentRect.width, "frame.width") : void 0;
|
|
574
|
+
const explicitHeight = hasHeight ? resolveUiLength(frame.height, parentRect.height, "frame.height") : void 0;
|
|
575
|
+
if (hasWidth) assertNonNegativeSize(explicitWidth, "frame.width");
|
|
576
|
+
if (hasHeight) assertNonNegativeSize(explicitHeight, "frame.height");
|
|
577
|
+
const x = hasLeft && hasWidth ? left : hasRight && hasWidth ? right - explicitWidth : left;
|
|
578
|
+
const width = hasWidth ? explicitWidth : right - left;
|
|
579
|
+
const y = hasTop && hasHeight ? top : hasBottom && hasHeight ? bottom - explicitHeight : top;
|
|
580
|
+
const height = hasHeight ? explicitHeight : bottom - top;
|
|
581
|
+
if (width < 0 || height < 0)
|
|
582
|
+
throw new MachinaLayoutError(
|
|
583
|
+
"NegativeResolvedSize",
|
|
584
|
+
`Resolved guide frame size must be non-negative. Received width=${width}, height=${height}.`
|
|
585
|
+
);
|
|
586
|
+
return { x, y, width, height };
|
|
587
|
+
}
|
|
332
588
|
function resolveStackChildRects(parentRect, arrange, childIds, document) {
|
|
333
589
|
const gap = arrange.gap ?? 0;
|
|
334
590
|
const justify = arrange.justify ?? "start";
|
|
@@ -341,9 +597,11 @@ function resolveStackChildRects(parentRect, arrange, childIds, document) {
|
|
|
341
597
|
width: parentRect.width - padding.left - padding.right,
|
|
342
598
|
height: parentRect.height - padding.top - padding.bottom
|
|
343
599
|
};
|
|
344
|
-
if (content.width < 0 || content.height < 0)
|
|
345
|
-
throw new MachinaLayoutError(
|
|
346
|
-
|
|
600
|
+
if (content.width < 0 || content.height < 0)
|
|
601
|
+
throw new MachinaLayoutError(
|
|
602
|
+
"StackContentNegative",
|
|
603
|
+
"stack content size cannot be negative after applying padding"
|
|
604
|
+
);
|
|
347
605
|
const isHorizontal = arrange.axis === "horizontal";
|
|
348
606
|
const contentMain = isHorizontal ? content.width : content.height;
|
|
349
607
|
const contentCross = isHorizontal ? content.height : content.width;
|
|
@@ -352,9 +610,11 @@ function resolveStackChildRects(parentRect, arrange, childIds, document) {
|
|
|
352
610
|
const fillWeights = [];
|
|
353
611
|
for (const childId of childIds) {
|
|
354
612
|
const childNode = document.nodes[childId];
|
|
355
|
-
if (!childNode)
|
|
356
|
-
throw new MachinaLayoutError(
|
|
357
|
-
|
|
613
|
+
if (!childNode)
|
|
614
|
+
throw new MachinaLayoutError(
|
|
615
|
+
"UnknownParent",
|
|
616
|
+
`child id ${childId} referenced by arranged parent is missing`
|
|
617
|
+
);
|
|
358
618
|
if (childNode.frame.kind === "fixed") {
|
|
359
619
|
assertNonNegativeSize(childNode.frame.width, `${childId}.frame.width`);
|
|
360
620
|
assertNonNegativeSize(childNode.frame.height, `${childId}.frame.height`);
|
|
@@ -363,14 +623,18 @@ function resolveStackChildRects(parentRect, arrange, childIds, document) {
|
|
|
363
623
|
fillWeights.push(0);
|
|
364
624
|
continue;
|
|
365
625
|
}
|
|
366
|
-
if (childNode.frame.kind !== "fill")
|
|
367
|
-
throw new MachinaLayoutError(
|
|
368
|
-
|
|
626
|
+
if (childNode.frame.kind !== "fill")
|
|
627
|
+
throw new MachinaLayoutError(
|
|
628
|
+
"StackChildMustBeFixed",
|
|
629
|
+
`stack child must use fixed or fill frame: ${childId}`
|
|
630
|
+
);
|
|
369
631
|
const weight = childNode.frame.weight ?? 1;
|
|
370
632
|
assertFiniteNumber(weight, `${childId}.frame.weight`);
|
|
371
|
-
if (weight <= 0)
|
|
372
|
-
throw new MachinaLayoutError(
|
|
373
|
-
|
|
633
|
+
if (weight <= 0)
|
|
634
|
+
throw new MachinaLayoutError(
|
|
635
|
+
"InvalidFillWeight",
|
|
636
|
+
`${childId}.frame.weight must be greater than 0`
|
|
637
|
+
);
|
|
374
638
|
const cross = childNode.frame.cross ?? "fill";
|
|
375
639
|
let childCross = contentCross;
|
|
376
640
|
if (cross !== "fill") {
|
|
@@ -381,49 +645,31 @@ function resolveStackChildRects(parentRect, arrange, childIds, document) {
|
|
|
381
645
|
childCrossSizes.push(childCross);
|
|
382
646
|
fillWeights.push(weight);
|
|
383
647
|
}
|
|
384
|
-
const fixedMainTotal = childIds.reduce(
|
|
648
|
+
const fixedMainTotal = childIds.reduce(
|
|
649
|
+
(sum, _id, i) => sum + (fillWeights[i] === 0 ? childMainSizes[i] : 0),
|
|
650
|
+
0
|
|
651
|
+
);
|
|
385
652
|
const totalGap = gap * Math.max(0, childIds.length - 1);
|
|
386
653
|
const remainingMain = contentMain - fixedMainTotal - totalGap;
|
|
387
|
-
if (remainingMain < 0)
|
|
388
|
-
throw new MachinaLayoutError("StackOverflow", "stack main axis overflow");
|
|
389
|
-
}
|
|
654
|
+
if (remainingMain < 0) throw new MachinaLayoutError("StackOverflow", "stack main axis overflow");
|
|
390
655
|
const totalFillWeight = fillWeights.reduce((sum, w) => sum + w, 0);
|
|
391
656
|
if (totalFillWeight > 0) {
|
|
392
|
-
for (let i = 0; i < childMainSizes.length; i += 1)
|
|
393
|
-
if (fillWeights[i] > 0)
|
|
657
|
+
for (let i = 0; i < childMainSizes.length; i += 1)
|
|
658
|
+
if (fillWeights[i] > 0)
|
|
394
659
|
childMainSizes[i] = remainingMain * fillWeights[i] / totalFillWeight;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
660
|
}
|
|
398
|
-
for (const childCross of childCrossSizes)
|
|
399
|
-
if (childCross > contentCross)
|
|
661
|
+
for (const childCross of childCrossSizes)
|
|
662
|
+
if (childCross > contentCross)
|
|
400
663
|
throw new MachinaLayoutError("StackOverflow", "stack cross axis overflow");
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
664
|
const occupiedMain = childMainSizes.reduce((sum, size) => sum + size, 0) + totalGap;
|
|
404
665
|
const remainingMainAfterFill = contentMain - occupiedMain;
|
|
405
666
|
let startOffset = 0;
|
|
406
667
|
let actualGap = gap;
|
|
407
668
|
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
|
-
}
|
|
669
|
+
if (justify === "center") startOffset = remainingMainAfterFill / 2;
|
|
670
|
+
else if (justify === "end") startOffset = remainingMainAfterFill;
|
|
671
|
+
else if (justify === "space-between")
|
|
672
|
+
actualGap = childIds.length <= 1 ? 0 : gap + remainingMainAfterFill / (childIds.length - 1);
|
|
427
673
|
}
|
|
428
674
|
const rects = {};
|
|
429
675
|
let currentMain = startOffset;
|
|
@@ -431,134 +677,247 @@ function resolveStackChildRects(parentRect, arrange, childIds, document) {
|
|
|
431
677
|
const childMain = childMainSizes[index];
|
|
432
678
|
const childCross = childCrossSizes[index];
|
|
433
679
|
let crossOffset = 0;
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
680
|
+
if (align === "center") crossOffset = (contentCross - childCross) / 2;
|
|
681
|
+
else if (align === "end") crossOffset = contentCross - childCross;
|
|
682
|
+
rects[childId] = isHorizontal ? {
|
|
683
|
+
x: content.x + currentMain,
|
|
684
|
+
y: content.y + crossOffset,
|
|
685
|
+
width: childMain,
|
|
686
|
+
height: childCross
|
|
687
|
+
} : {
|
|
688
|
+
x: content.x + crossOffset,
|
|
689
|
+
y: content.y + currentMain,
|
|
690
|
+
width: childCross,
|
|
691
|
+
height: childMain
|
|
692
|
+
};
|
|
447
693
|
currentMain += childMain + actualGap;
|
|
448
694
|
});
|
|
449
695
|
return rects;
|
|
450
696
|
}
|
|
697
|
+
function validateGridTrack(track, axis, index) {
|
|
698
|
+
if (track.kind === "fixed") {
|
|
699
|
+
if (!Number.isFinite(track.size) || track.size < 0)
|
|
700
|
+
throw new MachinaLayoutError(
|
|
701
|
+
"InvalidGridTrack",
|
|
702
|
+
`${axis}[${index}].size must be finite and non-negative`
|
|
703
|
+
);
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
if (track.kind === "fill") {
|
|
707
|
+
const weight = track.weight ?? 1;
|
|
708
|
+
if (!Number.isFinite(weight) || weight <= 0)
|
|
709
|
+
throw new MachinaLayoutError(
|
|
710
|
+
"InvalidGridTrack",
|
|
711
|
+
`${axis}[${index}].weight must be finite and greater than 0`
|
|
712
|
+
);
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
throw new MachinaLayoutError("InvalidGridTrack", `${axis}[${index}] has unknown track kind`);
|
|
716
|
+
}
|
|
717
|
+
function resolveGridTracks(contentAxisSize, tracks, gap, axis) {
|
|
718
|
+
if (!Number.isFinite(gap) || gap < 0 || tracks.length === 0)
|
|
719
|
+
throw new MachinaLayoutError("InvalidGridTrack", `invalid ${axis} configuration`);
|
|
720
|
+
tracks.forEach((t, i) => {
|
|
721
|
+
validateGridTrack(t, axis, i);
|
|
722
|
+
});
|
|
723
|
+
const fixedTotal = tracks.reduce((s, t) => s + (t.kind === "fixed" ? t.size : 0), 0);
|
|
724
|
+
const gapTotal = gap * Math.max(0, tracks.length - 1);
|
|
725
|
+
const remaining = contentAxisSize - fixedTotal - gapTotal;
|
|
726
|
+
if (remaining < 0) throw new MachinaLayoutError("GridOverflow", `grid ${axis} overflow`);
|
|
727
|
+
const totalWeight = tracks.reduce((s, t) => s + (t.kind === "fill" ? t.weight ?? 1 : 0), 0);
|
|
728
|
+
const sizes = tracks.map(
|
|
729
|
+
(t) => t.kind === "fixed" ? t.size : totalWeight <= 0 ? 0 : remaining * (t.weight ?? 1) / totalWeight
|
|
730
|
+
);
|
|
731
|
+
let current = 0;
|
|
732
|
+
return sizes.map((size) => {
|
|
733
|
+
const r = { start: current, size };
|
|
734
|
+
current += size + gap;
|
|
735
|
+
return r;
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
function resolveGridChildRect(childNode, columns, rows, columnGap, rowGap, content) {
|
|
739
|
+
if (childNode.frame.kind !== "cell")
|
|
740
|
+
throw new MachinaLayoutError(
|
|
741
|
+
"GridChildMustBeCell",
|
|
742
|
+
`grid child must use cell frame: ${childNode.id}`
|
|
743
|
+
);
|
|
744
|
+
const { row, col } = childNode.frame;
|
|
745
|
+
const rowSpan = childNode.frame.rowSpan ?? 1;
|
|
746
|
+
const colSpan = childNode.frame.colSpan ?? 1;
|
|
747
|
+
if (!Number.isInteger(row) || row < 0 || !Number.isInteger(col) || col < 0 || !Number.isInteger(rowSpan) || rowSpan <= 0 || !Number.isInteger(colSpan) || colSpan <= 0)
|
|
748
|
+
throw new MachinaLayoutError(
|
|
749
|
+
"InvalidGridCell",
|
|
750
|
+
`invalid cell coordinates/spans for node ${childNode.id}`
|
|
751
|
+
);
|
|
752
|
+
if (row + rowSpan > rows.length || col + colSpan > columns.length)
|
|
753
|
+
throw new MachinaLayoutError(
|
|
754
|
+
"InvalidGridCell",
|
|
755
|
+
`cell exceeds grid bounds for node ${childNode.id}`
|
|
756
|
+
);
|
|
757
|
+
const x = content.x + columns[col].start;
|
|
758
|
+
const y = content.y + rows[row].start;
|
|
759
|
+
let width = columnGap * (colSpan - 1);
|
|
760
|
+
for (let i = col; i < col + colSpan; i += 1) width += columns[i].size;
|
|
761
|
+
let height = rowGap * (rowSpan - 1);
|
|
762
|
+
for (let i = row; i < row + rowSpan; i += 1) height += rows[i].size;
|
|
763
|
+
return { x, y, width, height };
|
|
764
|
+
}
|
|
451
765
|
function resolveLayoutDocument(document, rootRect) {
|
|
452
|
-
|
|
766
|
+
validateRootRect2(rootRect);
|
|
453
767
|
const rootNode = document.nodes[document.rootId];
|
|
454
|
-
if (!rootNode)
|
|
768
|
+
if (!rootNode)
|
|
455
769
|
throw new MachinaLayoutError("MissingRoot", `root node not found for id: ${document.rootId}`);
|
|
456
|
-
}
|
|
457
770
|
const resolvedNodes = {};
|
|
458
771
|
const resolvedChildren = {};
|
|
459
772
|
const visitState = /* @__PURE__ */ new Map();
|
|
460
|
-
|
|
773
|
+
const pendingGuides = /* @__PURE__ */ new Map();
|
|
461
774
|
const resolveNode = (nodeId, rect) => {
|
|
462
775
|
const state = visitState.get(nodeId) ?? 0;
|
|
463
|
-
if (state === 1) {
|
|
464
|
-
|
|
465
|
-
}
|
|
466
|
-
if (state === 2) {
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
776
|
+
if (state === 1) throw new MachinaLayoutError("Cycle", `cycle detected at node ${nodeId}`);
|
|
777
|
+
if (state === 2) return;
|
|
469
778
|
const node = document.nodes[nodeId];
|
|
470
|
-
if (!node)
|
|
471
|
-
throw new MachinaLayoutError(
|
|
472
|
-
|
|
779
|
+
if (!node)
|
|
780
|
+
throw new MachinaLayoutError(
|
|
781
|
+
"UnknownParent",
|
|
782
|
+
`node referenced in children but missing from nodes: ${nodeId}`
|
|
783
|
+
);
|
|
473
784
|
visitState.set(nodeId, 1);
|
|
474
|
-
visitedCount += 1;
|
|
475
785
|
resolvedNodes[nodeId] = {
|
|
476
786
|
id: node.id,
|
|
477
787
|
z: node.z,
|
|
478
|
-
rect: {
|
|
788
|
+
rect: { ...rect },
|
|
479
789
|
frame: node.frame,
|
|
480
790
|
arrange: node.arrange,
|
|
481
791
|
view: node.view,
|
|
482
792
|
slot: node.slot,
|
|
483
793
|
debugLabel: node.debugLabel,
|
|
794
|
+
layer: node.layer,
|
|
484
795
|
offset: node.offset
|
|
485
796
|
};
|
|
486
797
|
const childIds = document.children[nodeId] ?? [];
|
|
487
798
|
resolvedChildren[nodeId] = [...childIds];
|
|
488
|
-
|
|
799
|
+
let childRects;
|
|
800
|
+
if (node.arrange?.kind === "stack")
|
|
801
|
+
childRects = resolveStackChildRects(rect, node.arrange, childIds, document);
|
|
802
|
+
else if (node.arrange?.kind === "grid") {
|
|
803
|
+
const columnGap = node.arrange.columnGap ?? 0;
|
|
804
|
+
const rowGap = node.arrange.rowGap ?? 0;
|
|
805
|
+
const padding = normalizePadding(node.arrange.padding);
|
|
806
|
+
const content = {
|
|
807
|
+
x: rect.x + padding.left,
|
|
808
|
+
y: rect.y + padding.top,
|
|
809
|
+
width: rect.width - padding.left - padding.right,
|
|
810
|
+
height: rect.height - padding.top - padding.bottom
|
|
811
|
+
};
|
|
812
|
+
if (content.width < 0 || content.height < 0)
|
|
813
|
+
throw new MachinaLayoutError(
|
|
814
|
+
"GridContentNegative",
|
|
815
|
+
"grid content size cannot be negative after applying padding"
|
|
816
|
+
);
|
|
817
|
+
const columns = resolveGridTracks(content.width, node.arrange.columns, columnGap, "columns");
|
|
818
|
+
const rows = resolveGridTracks(content.height, node.arrange.rows, rowGap, "rows");
|
|
819
|
+
childRects = {};
|
|
820
|
+
for (const childId of childIds) {
|
|
821
|
+
const childNode = document.nodes[childId];
|
|
822
|
+
if (!childNode)
|
|
823
|
+
throw new MachinaLayoutError(
|
|
824
|
+
"UnknownParent",
|
|
825
|
+
`child id ${childId} referenced by ${nodeId} is missing`
|
|
826
|
+
);
|
|
827
|
+
childRects[childId] = resolveGridChildRect(
|
|
828
|
+
childNode,
|
|
829
|
+
columns,
|
|
830
|
+
rows,
|
|
831
|
+
columnGap,
|
|
832
|
+
rowGap,
|
|
833
|
+
content
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
489
837
|
for (const childId of childIds) {
|
|
490
838
|
const childNode = document.nodes[childId];
|
|
491
|
-
if (!childNode)
|
|
492
|
-
throw new MachinaLayoutError(
|
|
839
|
+
if (!childNode)
|
|
840
|
+
throw new MachinaLayoutError(
|
|
841
|
+
"UnknownParent",
|
|
842
|
+
`child id ${childId} referenced by ${nodeId} is missing`
|
|
843
|
+
);
|
|
844
|
+
if (childNode.frame.kind === "guide" && !childRects) {
|
|
845
|
+
validateGuideFrame(childId, childNode.frame, document);
|
|
846
|
+
pendingGuides.set(childId, { nodeId: childId, parentId: nodeId });
|
|
847
|
+
continue;
|
|
493
848
|
}
|
|
494
849
|
const normalChildRect = childRects?.[childId] ?? resolveFrame(rect, childNode.frame);
|
|
495
|
-
|
|
496
|
-
resolveNode(childId, childRect);
|
|
850
|
+
resolveNode(childId, applyOffset(normalChildRect, rect, childNode.offset));
|
|
497
851
|
}
|
|
498
852
|
visitState.set(nodeId, 2);
|
|
499
853
|
};
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
854
|
+
const processPending = () => {
|
|
855
|
+
while (pendingGuides.size > 0) {
|
|
856
|
+
let progressed = false;
|
|
857
|
+
for (const [id, pending] of [...pendingGuides.entries()]) {
|
|
858
|
+
const parentResolved = resolvedNodes[pending.parentId];
|
|
859
|
+
const node = document.nodes[id];
|
|
860
|
+
if (!parentResolved || !node || node.frame.kind !== "guide") continue;
|
|
861
|
+
const refs = [node.frame.left, node.frame.right, node.frame.top, node.frame.bottom].filter(
|
|
862
|
+
isEdgeRef
|
|
863
|
+
);
|
|
864
|
+
if (refs.some((r) => !resolvedNodes[r.ref])) continue;
|
|
865
|
+
const rect = resolveGuideFrame(parentResolved.rect, node.frame, resolvedNodes);
|
|
866
|
+
resolveNode(id, applyOffset(rect, parentResolved.rect, node.offset));
|
|
867
|
+
pendingGuides.delete(id);
|
|
868
|
+
progressed = true;
|
|
869
|
+
}
|
|
870
|
+
if (pendingGuides.size === 0) return;
|
|
871
|
+
if (progressed) continue;
|
|
872
|
+
const remaining = /* @__PURE__ */ new Set([...pendingGuides.keys()]);
|
|
873
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
874
|
+
const visited = /* @__PURE__ */ new Set();
|
|
875
|
+
const hasCycle = (id) => {
|
|
876
|
+
if (visiting.has(id)) return true;
|
|
877
|
+
if (visited.has(id)) return false;
|
|
878
|
+
visiting.add(id);
|
|
879
|
+
const node = document.nodes[id];
|
|
880
|
+
if (node?.frame.kind === "guide") {
|
|
881
|
+
for (const ref of [
|
|
882
|
+
node.frame.left,
|
|
883
|
+
node.frame.right,
|
|
884
|
+
node.frame.top,
|
|
885
|
+
node.frame.bottom
|
|
886
|
+
].filter(isEdgeRef)) {
|
|
887
|
+
if (remaining.has(ref.ref) && hasCycle(ref.ref)) return true;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
visiting.delete(id);
|
|
891
|
+
visited.add(id);
|
|
892
|
+
return false;
|
|
893
|
+
};
|
|
894
|
+
for (const id of remaining) {
|
|
895
|
+
if (hasCycle(id))
|
|
896
|
+
throw new MachinaLayoutError("GuideReferenceCycle", "guide reference cycle detected");
|
|
897
|
+
}
|
|
898
|
+
throw new MachinaLayoutError(
|
|
899
|
+
"GuideTargetUnresolved",
|
|
900
|
+
"one or more guide targets could not be resolved"
|
|
901
|
+
);
|
|
902
|
+
}
|
|
508
903
|
};
|
|
904
|
+
resolveNode(document.rootId, { ...rootRect });
|
|
905
|
+
processPending();
|
|
906
|
+
if (Object.keys(resolvedNodes).length !== Object.keys(document.nodes).length)
|
|
907
|
+
throw new MachinaLayoutError(
|
|
908
|
+
"UnreachableNode",
|
|
909
|
+
"one or more nodes are unreachable from the root."
|
|
910
|
+
);
|
|
911
|
+
return { rootId: document.rootId, nodes: resolvedNodes, children: resolvedChildren };
|
|
509
912
|
}
|
|
510
913
|
|
|
511
914
|
// src/resolveLayoutRows.ts
|
|
512
915
|
function resolveLayoutRows(rows, rootRect) {
|
|
513
|
-
const
|
|
916
|
+
const selectedRows = selectLayoutRowsForRoot(rows, rootRect);
|
|
917
|
+
const document = compileLayoutRows(selectedRows);
|
|
514
918
|
return resolveLayoutDocument(document, rootRect);
|
|
515
919
|
}
|
|
516
920
|
|
|
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
921
|
// src/flattenResolvedTree.ts
|
|
563
922
|
function flattenResolvedTree(tree) {
|
|
564
923
|
const out = [];
|
|
@@ -572,6 +931,7 @@ function flattenResolvedTree(tree) {
|
|
|
572
931
|
view: node.view,
|
|
573
932
|
slot: node.slot,
|
|
574
933
|
debugLabel: node.debugLabel,
|
|
934
|
+
layer: node.layer,
|
|
575
935
|
offset: node.offset
|
|
576
936
|
});
|
|
577
937
|
for (const child of node.children) {
|
|
@@ -587,464 +947,105 @@ function formatRect(rect) {
|
|
|
587
947
|
return `x=${rect.x} y=${rect.y} w=${rect.width} h=${rect.height}`;
|
|
588
948
|
}
|
|
589
949
|
|
|
590
|
-
// src/
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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
|
-
);
|
|
655
|
-
}
|
|
656
|
-
function MachinaReactView(props) {
|
|
657
|
-
const {
|
|
658
|
-
layout,
|
|
659
|
-
views = {},
|
|
660
|
-
viewData,
|
|
661
|
-
nodeData,
|
|
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
|
|
676
|
-
};
|
|
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
|
-
}
|
|
691
|
-
|
|
692
|
-
// src/text/parseMachinaText.ts
|
|
693
|
-
function makeDiagnostic(code, message, index, length, line, column) {
|
|
694
|
-
return { code, message, index, length, line, column, level: "error" };
|
|
695
|
-
}
|
|
696
|
-
function toLines(source) {
|
|
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;
|
|
950
|
+
// src/lerp.ts
|
|
951
|
+
function assertFiniteNumber2(value) {
|
|
952
|
+
if (!Number.isFinite(value)) {
|
|
953
|
+
throw new MachinaLayoutError(
|
|
954
|
+
"NonFiniteNumber",
|
|
955
|
+
`Expected finite number, got ${String(value)}.`
|
|
956
|
+
);
|
|
709
957
|
}
|
|
710
|
-
return lines;
|
|
711
958
|
}
|
|
712
|
-
function
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
if (
|
|
718
|
-
|
|
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;
|
|
959
|
+
function sameStringArray(a, b) {
|
|
960
|
+
if (a.length !== b.length) {
|
|
961
|
+
return false;
|
|
962
|
+
}
|
|
963
|
+
for (let index = 0; index < a.length; index += 1) {
|
|
964
|
+
if (a[index] !== b[index]) {
|
|
965
|
+
return false;
|
|
825
966
|
}
|
|
826
|
-
pushText(text.slice(cursor, next));
|
|
827
|
-
cursor = next;
|
|
828
967
|
}
|
|
829
|
-
return
|
|
830
|
-
}
|
|
831
|
-
function classifyForbiddenBlock(line) {
|
|
832
|
-
if (/^#{1,6}\s+/.test(line)) return "heading_forbidden";
|
|
833
|
-
if (/^\d+\.\s+/.test(line)) return "unsupported_syntax";
|
|
834
|
-
if (/^\s*-\s+\[[ xX]\]\s+/.test(line)) return "unsupported_syntax";
|
|
835
|
-
if (/^>\s+/.test(line)) return "unsupported_syntax";
|
|
836
|
-
if (/^```/.test(line)) return "unsupported_syntax";
|
|
837
|
-
if (/^\s*<\/?[a-zA-Z][^>]*>/.test(line)) return "unsupported_syntax";
|
|
838
|
-
if (/^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$/.test(line)) return "unsupported_syntax";
|
|
839
|
-
return void 0;
|
|
968
|
+
return true;
|
|
840
969
|
}
|
|
841
|
-
function
|
|
842
|
-
if (
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
}
|
|
848
|
-
function parseMachinaTextInline(text) {
|
|
849
|
-
return parseInline(text, 0, 1);
|
|
850
|
-
}
|
|
851
|
-
function parseMachinaText(source) {
|
|
852
|
-
const src = typeof source === "string" ? { kind: "machina-text", text: source } : source;
|
|
853
|
-
if (src?.kind !== "plain" && src?.kind !== "machina-text") {
|
|
854
|
-
const diagnostic = makeDiagnostic("unsupported_syntax", "Unsupported MachinaText source kind.", 0, 0, 1, 1);
|
|
855
|
-
return { ok: false, document: { blocks: [] }, diagnostics: [diagnostic] };
|
|
970
|
+
function assertCompatibleResolvedLayouts(a, b) {
|
|
971
|
+
if (a.rootId !== b.rootId) {
|
|
972
|
+
throw new MachinaLayoutError(
|
|
973
|
+
"IncompatibleLayouts",
|
|
974
|
+
`Layout roots differ: ${a.rootId} !== ${b.rootId}.`
|
|
975
|
+
);
|
|
856
976
|
}
|
|
857
|
-
if (
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
};
|
|
977
|
+
if (!(a.rootId in a.nodes) || !(b.rootId in b.nodes)) {
|
|
978
|
+
throw new MachinaLayoutError(
|
|
979
|
+
"IncompatibleLayouts",
|
|
980
|
+
`Root id ${a.rootId} must exist in both node maps.`
|
|
981
|
+
);
|
|
863
982
|
}
|
|
864
|
-
const
|
|
865
|
-
const
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
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;
|
|
983
|
+
const aNodeIds = Object.keys(a.nodes).sort();
|
|
984
|
+
const bNodeIds = Object.keys(b.nodes).sort();
|
|
985
|
+
if (!sameStringArray(aNodeIds, bNodeIds)) {
|
|
986
|
+
throw new MachinaLayoutError(
|
|
987
|
+
"IncompatibleLayouts",
|
|
988
|
+
"Resolved layouts must have the same node ids."
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
const aParentIds = Object.keys(a.children).sort();
|
|
992
|
+
const bParentIds = Object.keys(b.children).sort();
|
|
993
|
+
if (!sameStringArray(aParentIds, bParentIds)) {
|
|
994
|
+
throw new MachinaLayoutError(
|
|
995
|
+
"IncompatibleLayouts",
|
|
996
|
+
"Resolved layouts must have the same parent-child map."
|
|
997
|
+
);
|
|
998
|
+
}
|
|
999
|
+
for (const parentId of aParentIds) {
|
|
1000
|
+
const aChildren = a.children[parentId] ?? [];
|
|
1001
|
+
const bChildren = b.children[parentId] ?? [];
|
|
1002
|
+
if (!sameStringArray(aChildren, bChildren)) {
|
|
1003
|
+
throw new MachinaLayoutError(
|
|
1004
|
+
"IncompatibleLayouts",
|
|
1005
|
+
`Child order differs for parent ${parentId}.`
|
|
1006
|
+
);
|
|
925
1007
|
}
|
|
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 });
|
|
931
1008
|
}
|
|
932
|
-
return { ok: diagnostics.every((d) => d.level !== "error"), document: { blocks }, diagnostics };
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
// src/text/react/MachinaTextView.tsx
|
|
936
|
-
import React2 from "react";
|
|
937
|
-
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
938
|
-
var DEFAULT_POLICY = { variant: "body", wrap: "word", overflow: "clip", align: "start", leading: "normal", blockGap: 8, listGap: 2, valign: "top" };
|
|
939
|
-
var INLINE_CODE_FONT = 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace';
|
|
940
|
-
var VARIANT_STYLE = {
|
|
941
|
-
body: { fontSize: "14px", fontWeight: 400, lineHeight: 1.4 },
|
|
942
|
-
label: { fontSize: "12px", fontWeight: 500, lineHeight: 1.3 },
|
|
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;
|
|
949
|
-
}
|
|
950
|
-
function isMachinaTextSpec(value) {
|
|
951
|
-
return typeof value === "object" && value !== null && "kind" in value && value.kind === "text";
|
|
952
1009
|
}
|
|
953
|
-
function
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1010
|
+
function copyChildren(children) {
|
|
1011
|
+
const copied = {};
|
|
1012
|
+
for (const [parentId, childIds] of Object.entries(children)) {
|
|
1013
|
+
copied[parentId] = [...childIds];
|
|
1014
|
+
}
|
|
1015
|
+
return copied;
|
|
958
1016
|
}
|
|
959
|
-
function
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1017
|
+
function lerpNumber(a, b, t) {
|
|
1018
|
+
assertFiniteNumber2(a);
|
|
1019
|
+
assertFiniteNumber2(b);
|
|
1020
|
+
assertFiniteNumber2(t);
|
|
1021
|
+
return a + (b - a) * t;
|
|
963
1022
|
}
|
|
964
|
-
function
|
|
1023
|
+
function lerpRect(a, b, t) {
|
|
965
1024
|
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
|
|
1025
|
+
x: lerpNumber(a.x, b.x, t),
|
|
1026
|
+
y: lerpNumber(a.y, b.y, t),
|
|
1027
|
+
width: lerpNumber(a.width, b.width, t),
|
|
1028
|
+
height: lerpNumber(a.height, b.height, t)
|
|
974
1029
|
};
|
|
975
1030
|
}
|
|
976
|
-
function
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1031
|
+
function lerpResolvedLayouts(a, b, t) {
|
|
1032
|
+
assertFiniteNumber2(t);
|
|
1033
|
+
assertCompatibleResolvedLayouts(a, b);
|
|
1034
|
+
const nodes = {};
|
|
1035
|
+
for (const id of Object.keys(b.nodes)) {
|
|
1036
|
+
const aNode = a.nodes[id];
|
|
1037
|
+
const bNode = b.nodes[id];
|
|
1038
|
+
nodes[id] = {
|
|
1039
|
+
...bNode,
|
|
1040
|
+
rect: lerpRect(aNode.rect, bNode.rect, t)
|
|
1041
|
+
};
|
|
981
1042
|
}
|
|
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
1043
|
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]
|
|
1044
|
+
rootId: b.rootId,
|
|
1045
|
+
nodes,
|
|
1046
|
+
children: copyChildren(b.children)
|
|
1017
1047
|
};
|
|
1018
1048
|
}
|
|
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
1049
|
export {
|
|
1049
1050
|
MachinaLayoutError,
|
|
1050
1051
|
MachinaReactView,
|
|
@@ -1057,6 +1058,9 @@ export {
|
|
|
1057
1058
|
compileLayoutRows,
|
|
1058
1059
|
flattenResolvedTree,
|
|
1059
1060
|
formatRect,
|
|
1061
|
+
lerpNumber,
|
|
1062
|
+
lerpRect,
|
|
1063
|
+
lerpResolvedLayouts,
|
|
1060
1064
|
normalizePadding,
|
|
1061
1065
|
parseMachinaText,
|
|
1062
1066
|
parseMachinaTextInline,
|
|
@@ -1064,5 +1068,6 @@ export {
|
|
|
1064
1068
|
resolveLayoutDocument,
|
|
1065
1069
|
resolveLayoutRows,
|
|
1066
1070
|
resolveUiLength,
|
|
1071
|
+
selectLayoutRowsForRoot,
|
|
1067
1072
|
toResolvedTree
|
|
1068
1073
|
};
|