@r0kshan/cairn 1.0.0-RC17 → 1.0.0-RC18
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 +1 -1
- package/bin/cairn.mjs +422 -61
- package/dist/cairn.mjs +433 -58
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ Furthermore, complexe software architecture with many flows and component genera
|
|
|
34
34
|
| **Typed diagrams with validation.** | Each view defines its element kinds and rules; `cairn validate` reports syntax, schema, and completeness issues as source-located, coded diagnostics, with a JSON mode for CI. |
|
|
35
35
|
| **Flow matrix.** | `cairn matrix` exports the flow matrix as CSV, Markdown, or SVG — from any view, with the columns that view can fill. In infrastructure it splits protocol from port and annotates endpoints that sit in a network zone; headers localise via `style { lang: fr }`. The same table is available to embedders through `compile(source, { matrix: true })`. |
|
|
36
36
|
| **Author-controlled positioning.** | Layout is automatic, with three opt-in overrides for when it gets a diagram wrong: `order: 2` sequences a top-level element along the disposition's own reading direction (left to right in `wide`, top to bottom in `tall`) and sorts siblings inside a container, `A.right -> B.left` pins which side of an element a flow leaves and arrives on, and the arrow glyph carries the line style (`->` solid, `-->` dashed, `..>` dotted). |
|
|
37
|
-
| **Playground drag-to-adjust.** | When the automatic layout needs a correction, the [playground](https://cairn-psi-five.vercel.app/) lets you drag elements, flow labels, a single route segment, or a flow's endpoint onto another side. Every drag is written back into the source as the matching hint — `offset:`, `label-offset:`, `segment-offset:`, `ID.side` — so the adjustment stays versionable text and re-renders identically from the CLI. |
|
|
37
|
+
| **Playground drag-to-adjust.** | When the automatic layout needs a correction, the [playground](https://cairn-psi-five.vercel.app/) lets you drag elements, flow labels, a single route segment, or a flow's endpoint onto another side. Every drag is written back into the source as the matching hint — `offset:`, `label-offset:`, `segment-offset:`, `ID.side` — so the adjustment stays versionable text and re-renders identically from the CLI. A hovered flow label also offers a wrap button, which writes that flow's own `flow-label-wrap:`. |
|
|
38
38
|
| **Enterprise-view extras.** | Business objects on flows, an auto-generated legend, and a numbered-flow table via `flow-text: numbered`. |
|
|
39
39
|
| **French or English output.** | `style { lang: fr }` localizes band titles, legend, and matrix headers while keeping keywords English for portable sources (open to adding other languages if you find this usefull) |
|
|
40
40
|
| **In-built themes and customizable colours** | Whether using the default or a chosen in-built theme, element colours can be overriden for all elements of a given kind in the `style` block |
|
package/bin/cairn.mjs
CHANGED
|
@@ -92386,7 +92386,8 @@ var init_ast = __esm({
|
|
|
92386
92386
|
E0216: "Network zones belong to a site (or nest inside a larger zone): DMZ and LAN only mean something relative to a perimeter.",
|
|
92387
92387
|
E0217: "A cluster groups the nodes that stand in for one another, so it needs a network location like any of them: put it in a `network-zone` or a `site`. Its members may be servers, deployed applications or databases \u2014 a Kubernetes cluster of worker nodes, or a database cluster of primary and standby.",
|
|
92388
92388
|
W0540: 'C4 container-diagram practice: inter-process relationships should be labelled with their technology/protocol ("the how, not just the what"). Human/actor interactions are exempt. Add `(API_REST, JSON)` after the label, or ignore if the diagram is intentionally functional-only.',
|
|
92389
|
-
E0223: "Unknown endpoint suffix. A flow endpoint may name the side it attaches to \u2014 `APP.right -> DB.left` \u2014 using the diagram as it is read: `left`, `right`, `top`, `bottom`. It may instead name its role towards a queue at the other end: `producer` or `consumer`. A declared side is a hint, not a guarantee: one the layout cannot reach is dropped with a W0570 warning rather than forced.",
|
|
92389
|
+
E0223: "Unknown endpoint suffix. A flow endpoint may name the side it attaches to \u2014 `APP.right -> DB.left` \u2014 using the diagram as it is read: `left`, `right`, `top`, `bottom`. It may instead name its role towards a queue at the other end: `producer` or `consumer`. It may name one of each, in either order (`CAPTURE.producer.top`). A declared side is a hint, not a guarantee: one the layout cannot reach is dropped with a W0570 warning rather than forced.",
|
|
92390
|
+
E0227: "One endpoint names two attachment sides, or two roles, and only one of each can hold: `A.top.bottom` asks for two sides of the same element, `A.producer.consumer` for two caps of the same queue. The first one written stands. A side and a *role* are a different matter \u2014 they answer different questions, so `CAPTURE.producer.top -> EVENTS` is read as both: the flow leaves CAPTURE's top and meets the queue's producer cap.",
|
|
92390
92391
|
E0224: "An endpoint role says what this element does with the *queue* at the other end of the flow (`CAPTURE.producer -> EVENTS`), so the other end has to be one. Point the flow at a queue, or drop the role \u2014 between two ordinary elements the arrow already says everything a role would.",
|
|
92391
92392
|
E0225: "A flow's queue endpoint carries both a declared side and a role at the other end, and they are two answers to one question: `producer` already means the queue's left cap and `consumer` its right. Keep the role and drop the side, or keep the side and drop the role.",
|
|
92392
92393
|
E0240: "The infrastructure view requires every flow to carry its protocol (and port if relevant): the flow matrix is the primary output of this view. Add `(HTTPS/443)` after the label.",
|
|
@@ -92397,6 +92398,9 @@ var init_ast = __esm({
|
|
|
92397
92398
|
E0109: "`offset:` takes two whole numbers \u2014 `offset: <dx>, <dy>` \u2014 the pixels to move this element by, right and down, from where the layout engine placed it. Either may be negative. It is a nudge, not a seat: elk still decides where the element belongs, so the diagram keeps re-flowing around it. Pair it with `order:` when the element is in the wrong place entirely \u2014 an offset large enough to change reading order is a sign the band is wrong, not the pixels.",
|
|
92398
92399
|
W0572: "An `offset:` (or `label-offset:`) moved something onto something else. The hint is still honored \u2014 an author's positioning request is not negotiated \u2014 but the drawing now has an overlap the layout would never have produced on its own, so it is reported rather than left to be discovered in a review. Reduce the offset, or move the element with `order:` instead.",
|
|
92399
92400
|
W0573: "An `offset:` was cut short so the element stays inside the container that holds it. Nesting is what a diagram *means* \u2014 a block drawn outside its system reads as a broken drawing, not a nudged one \u2014 so containment wins over the nudge here, and this is the one place a positioning hint is negotiated rather than honored outright. Nudge the container instead if the whole group belongs elsewhere, or use a smaller offset.",
|
|
92401
|
+
E0110: "`size:` takes two whole numbers \u2014 `size: <dw>, <dh>` \u2014 the pixels of room to add to this container, rightward and downward, on top of what the layout gave it. Either may be negative. A delta rather than a box, for the same reason `offset:` is one: elk still sizes a container from what it holds, so a hint naming an absolute width would go stale the moment a child is added.",
|
|
92402
|
+
E0226: "`size:` is a container's knob, and a container is something *drawn* as one. A leaf box is sized by the label in it \u2014 the uniform node width, or the tighter fit `label-padding:` asks for \u2014 so a delta there would argue with the one thing that decides it. A container kind holding nothing is the same case: with no children there is no frame, the layout gives it a plain box sized by its own label, and there is no room around anything to give. Put the elements it contains inside it, use `label-padding:` in `style` to change how tightly every box hugs its text, or `offset:` to move this one.",
|
|
92403
|
+
W0575: "A `size:` ran out of room. A container is sized to hug what it holds, so its border has no slack of its own \u2014 shrinking closes the empty bands *between* its children instead, and stops once those are down to the minimum gap. Growing has no limit at all: a container enlarged past the one that holds it makes that one grow too. Everything else is honored as written, so a container grown onto a neighbour is drawn as asked and reported as W0572. To make this box smaller still, move or resize what is inside it.",
|
|
92400
92404
|
W0571: "An endpoint reads `ID.side`, but `ID.side` is itself a declared element, and a declared id always wins \u2014 so the flow attaches to that element and no side is pinned. Rename the element if you meant the side."
|
|
92401
92405
|
};
|
|
92402
92406
|
}
|
|
@@ -93222,57 +93226,93 @@ function parseElement(p, sourceToken, parent) {
|
|
|
93222
93226
|
(parent ? parent.children : model.elements).push(element);
|
|
93223
93227
|
}
|
|
93224
93228
|
function splitEndpoint(token, flow, end) {
|
|
93225
|
-
const
|
|
93226
|
-
if (
|
|
93227
|
-
|
|
93228
|
-
const
|
|
93229
|
-
|
|
93230
|
-
|
|
93231
|
-
|
|
93232
|
-
|
|
93233
|
-
|
|
93234
|
-
base,
|
|
93235
|
-
baseSpan: { line: token.span.line, col: token.span.col, len: base.length },
|
|
93236
|
-
suffix,
|
|
93237
|
-
suffixSpan: { line: token.span.line, col: token.span.col + dot + 1, len: suffix.length }
|
|
93238
|
-
};
|
|
93229
|
+
const texts = token.text.split(".");
|
|
93230
|
+
if (texts.length < 2 || texts.some((text) => !text)) return null;
|
|
93231
|
+
let col = token.span.col;
|
|
93232
|
+
const parts = texts.map((text) => {
|
|
93233
|
+
const span = { line: token.span.line, col, len: text.length };
|
|
93234
|
+
col += text.length + 1;
|
|
93235
|
+
return { text, span };
|
|
93236
|
+
});
|
|
93237
|
+
return { flow, end, raw: token.text, rawSpan: token.span, parts };
|
|
93239
93238
|
}
|
|
93240
93239
|
function resolveEndpointSuffixes(splits, model, diagnostics) {
|
|
93240
|
+
const sideOf2 = (text) => ATTACH_SIDES.includes(text);
|
|
93241
|
+
const roleOf = (text) => ATTACH_ROLES.includes(text);
|
|
93241
93242
|
for (const split of splits) {
|
|
93242
|
-
const isSide = ATTACH_SIDES.includes(split.suffix);
|
|
93243
|
-
const isRole = ATTACH_ROLES.includes(split.suffix);
|
|
93244
93243
|
if (model.index.has(split.raw)) {
|
|
93245
|
-
|
|
93244
|
+
let head = split.parts.length;
|
|
93245
|
+
while (head > 1 && (sideOf2(split.parts[head - 1].text) || roleOf(split.parts[head - 1].text)))
|
|
93246
|
+
head--;
|
|
93247
|
+
const shadowed = split.parts.slice(head).map((part) => part.text);
|
|
93248
|
+
if (shadowed.length) {
|
|
93249
|
+
const base = split.parts.slice(0, head).map((part) => part.text).join(".");
|
|
93250
|
+
const reading = [
|
|
93251
|
+
shadowed.some((text) => roleOf(text)) ? "a role" : "",
|
|
93252
|
+
shadowed.some((text) => sideOf2(text)) ? "an attachment side" : ""
|
|
93253
|
+
].filter(Boolean);
|
|
93246
93254
|
diagnostics.push({
|
|
93247
93255
|
code: "W0571",
|
|
93248
93256
|
severity: "warning",
|
|
93249
|
-
message: `\`${split.raw}\` is a declared element, so \`.${
|
|
93257
|
+
message: `\`${split.raw}\` is a declared element, so \`.${shadowed.join(".")}\` is not read as ${reading.join(" and ")}`,
|
|
93250
93258
|
span: split.rawSpan,
|
|
93251
93259
|
note: "a declared id always wins over the `ID.side` reading",
|
|
93252
|
-
help:
|
|
93260
|
+
help: `rename the element if you meant \`.${shadowed.join(".")}\` on \`${base}\``
|
|
93253
93261
|
});
|
|
93262
|
+
}
|
|
93254
93263
|
continue;
|
|
93255
93264
|
}
|
|
93256
|
-
|
|
93257
|
-
|
|
93258
|
-
|
|
93259
|
-
|
|
93260
|
-
|
|
93261
|
-
|
|
93262
|
-
|
|
93263
|
-
|
|
93264
|
-
|
|
93265
|
-
|
|
93266
|
-
|
|
93267
|
-
const
|
|
93265
|
+
let cut = 0;
|
|
93266
|
+
for (let take = split.parts.length - 1; take >= 1; take--) {
|
|
93267
|
+
const base = split.parts.slice(0, take).map((part) => part.text).join(".");
|
|
93268
|
+
if (model.index.has(base)) {
|
|
93269
|
+
cut = take;
|
|
93270
|
+
break;
|
|
93271
|
+
}
|
|
93272
|
+
}
|
|
93273
|
+
if (!cut) continue;
|
|
93274
|
+
let side;
|
|
93275
|
+
let role;
|
|
93276
|
+
for (const part of split.parts.slice(cut)) {
|
|
93277
|
+
const partIsSide = sideOf2(part.text);
|
|
93278
|
+
const partIsRole = roleOf(part.text);
|
|
93279
|
+
if (!partIsSide && !partIsRole) {
|
|
93280
|
+
diagnostics.push({
|
|
93281
|
+
code: "E0223",
|
|
93282
|
+
severity: "error",
|
|
93283
|
+
message: `unknown endpoint suffix \`${part.text}\``,
|
|
93284
|
+
span: part.span,
|
|
93285
|
+
note: "an endpoint names either the side it attaches to, as the diagram is read, or its role towards a queue",
|
|
93286
|
+
help: "use `left`, `right`, `top` or `bottom` \u2014 `APP.right -> DB.left` \u2014 or `producer` / `consumer`, e.g. `CAPTURE.producer -> EVENTS`, or one of each: `CAPTURE.producer.top -> EVENTS`"
|
|
93287
|
+
});
|
|
93288
|
+
continue;
|
|
93289
|
+
}
|
|
93290
|
+
const held = partIsSide ? side : role;
|
|
93291
|
+
if (held) {
|
|
93292
|
+
diagnostics.push({
|
|
93293
|
+
code: "E0227",
|
|
93294
|
+
severity: "error",
|
|
93295
|
+
message: `this endpoint already names ${partIsSide ? `the \`${held.value}\` side` : `the \`${held.value}\` role`}`,
|
|
93296
|
+
span: part.span,
|
|
93297
|
+
note: `\`${split.raw}\` names two ${partIsSide ? "attachment sides" : "roles"}, and only one can hold`,
|
|
93298
|
+
help: partIsSide ? "keep one side \u2014 a side and a *role* do accumulate, e.g. `CAPTURE.producer.top`" : "keep one role \u2014 a role and a *side* do accumulate, e.g. `CAPTURE.producer.top`"
|
|
93299
|
+
});
|
|
93300
|
+
continue;
|
|
93301
|
+
}
|
|
93302
|
+
if (partIsSide) side = { value: part.text, span: part.span };
|
|
93303
|
+
else role = { value: part.text, span: part.span };
|
|
93304
|
+
}
|
|
93305
|
+
const baseParts = split.parts.slice(0, cut);
|
|
93306
|
+
const baseText = baseParts.map((part) => part.text).join(".");
|
|
93307
|
+
const baseSpan = { line: split.rawSpan.line, col: split.rawSpan.col, len: baseText.length };
|
|
93268
93308
|
if (split.end === "from") {
|
|
93269
|
-
split.flow.from =
|
|
93270
|
-
split.flow.fromSpan =
|
|
93309
|
+
split.flow.from = baseText;
|
|
93310
|
+
split.flow.fromSpan = baseSpan;
|
|
93271
93311
|
split.flow.fromSide = side;
|
|
93272
93312
|
split.flow.fromRole = role;
|
|
93273
93313
|
} else {
|
|
93274
|
-
split.flow.to =
|
|
93275
|
-
split.flow.toSpan =
|
|
93314
|
+
split.flow.to = baseText;
|
|
93315
|
+
split.flow.toSpan = baseSpan;
|
|
93276
93316
|
split.flow.toSide = side;
|
|
93277
93317
|
split.flow.toRole = role;
|
|
93278
93318
|
}
|
|
@@ -93322,9 +93362,9 @@ function readOffsetPair(p) {
|
|
|
93322
93362
|
span: { line: dx.span.line, col: dx.span.col, len: dy.span.col + dy.span.len - dx.span.col }
|
|
93323
93363
|
};
|
|
93324
93364
|
}
|
|
93325
|
-
function
|
|
93365
|
+
function tryPairEntry(p, parent, entry, put) {
|
|
93326
93366
|
const { matchToken, advance, reportCoded, save, restore, syncToNextLine } = p;
|
|
93327
|
-
if (!parent || !matchToken("id",
|
|
93367
|
+
if (!parent || !matchToken("id", entry.key)) return false;
|
|
93328
93368
|
const mark = save();
|
|
93329
93369
|
const keyToken = advance();
|
|
93330
93370
|
if (!matchToken("colon")) {
|
|
@@ -93334,16 +93374,11 @@ function tryOffsetEntry(p, parent) {
|
|
|
93334
93374
|
advance();
|
|
93335
93375
|
const pair2 = readOffsetPair(p);
|
|
93336
93376
|
if (!pair2) {
|
|
93337
|
-
reportCoded(
|
|
93338
|
-
"E0109",
|
|
93339
|
-
"`offset` expects two whole numbers \u2014 `offset: <dx>, <dy>`",
|
|
93340
|
-
keyToken.span,
|
|
93341
|
-
"e.g. `offset: 40, -20` \u2014 40px right and 20px up from where the layout put it"
|
|
93342
|
-
);
|
|
93377
|
+
reportCoded(entry.code, entry.message, keyToken.span, entry.help);
|
|
93343
93378
|
syncToNextLine();
|
|
93344
93379
|
return true;
|
|
93345
93380
|
}
|
|
93346
|
-
parent
|
|
93381
|
+
put(parent, pair2);
|
|
93347
93382
|
return true;
|
|
93348
93383
|
}
|
|
93349
93384
|
function tryLogoEntry(p, parent) {
|
|
@@ -93589,7 +93624,7 @@ function parse(src) {
|
|
|
93589
93624
|
}
|
|
93590
93625
|
if (tryStyleBlock(parent)) return;
|
|
93591
93626
|
if (tryOrderEntry(parser, parent)) return;
|
|
93592
|
-
if (tryOffsetEntry(parser, parent)) return;
|
|
93627
|
+
if (tryOffsetEntry(parser, parent) || trySizeEntry(parser, parent)) return;
|
|
93593
93628
|
if (tryLogoEntry(parser, parent)) return;
|
|
93594
93629
|
if (!parent && tryLegendBlock(parser)) return;
|
|
93595
93630
|
if (!parent && tryBusinessObject(parser)) return;
|
|
@@ -93784,7 +93819,7 @@ function applyStyleEntry(entry) {
|
|
|
93784
93819
|
});
|
|
93785
93820
|
}
|
|
93786
93821
|
}
|
|
93787
|
-
var LINE_STYLES, LABEL_POSITIONS, DISPOSITIONS, NUMERIC_STYLE_ENTRIES, UNIFORM_STYLE_ENTRIES;
|
|
93822
|
+
var tryOffsetEntry, trySizeEntry, LINE_STYLES, LABEL_POSITIONS, DISPOSITIONS, NUMERIC_STYLE_ENTRIES, UNIFORM_STYLE_ENTRIES;
|
|
93788
93823
|
var init_parser = __esm({
|
|
93789
93824
|
"src/parser.ts"() {
|
|
93790
93825
|
"use strict";
|
|
@@ -93793,6 +93828,32 @@ var init_parser = __esm({
|
|
|
93793
93828
|
init_themes();
|
|
93794
93829
|
init_element_tree();
|
|
93795
93830
|
init_text_metrics();
|
|
93831
|
+
tryOffsetEntry = (p, parent) => tryPairEntry(
|
|
93832
|
+
p,
|
|
93833
|
+
parent,
|
|
93834
|
+
{
|
|
93835
|
+
key: "offset",
|
|
93836
|
+
code: "E0109",
|
|
93837
|
+
message: "`offset` expects two whole numbers \u2014 `offset: <dx>, <dy>`",
|
|
93838
|
+
help: "e.g. `offset: 40, -20` \u2014 40px right and 20px up from where the layout put it"
|
|
93839
|
+
},
|
|
93840
|
+
(element, pair2) => {
|
|
93841
|
+
element.offset = pair2;
|
|
93842
|
+
}
|
|
93843
|
+
);
|
|
93844
|
+
trySizeEntry = (p, parent) => tryPairEntry(
|
|
93845
|
+
p,
|
|
93846
|
+
parent,
|
|
93847
|
+
{
|
|
93848
|
+
key: "size",
|
|
93849
|
+
code: "E0110",
|
|
93850
|
+
message: "`size` expects two whole numbers \u2014 `size: <dw>, <dh>`",
|
|
93851
|
+
help: "e.g. `size: 120, 40` \u2014 120px wider and 40px taller than the layout made it"
|
|
93852
|
+
},
|
|
93853
|
+
(element, pair2) => {
|
|
93854
|
+
element.size = { dw: pair2.dx, dh: pair2.dy, span: pair2.span };
|
|
93855
|
+
}
|
|
93856
|
+
);
|
|
93796
93857
|
LINE_STYLES = /* @__PURE__ */ new Set(["solid", "dashed", "dotted"]);
|
|
93797
93858
|
LABEL_POSITIONS = /* @__PURE__ */ new Set(["on-line", "above", "below"]);
|
|
93798
93859
|
DISPOSITIONS = /* @__PURE__ */ new Set(["wide", "tall", "slide", "page"]);
|
|
@@ -94764,6 +94825,7 @@ function validate(model) {
|
|
|
94764
94825
|
...checkNesting(elements, view),
|
|
94765
94826
|
...checkFlows(model, view),
|
|
94766
94827
|
...checkEndpointRoles(model, view),
|
|
94828
|
+
...checkSizes(elements, view),
|
|
94767
94829
|
...checkLogos(elements, view),
|
|
94768
94830
|
...checkBusinessObjects(model, view),
|
|
94769
94831
|
...checkMinimumCounts(elements, model, view),
|
|
@@ -94806,6 +94868,31 @@ function checkUnknownKinds(elements, view) {
|
|
|
94806
94868
|
}
|
|
94807
94869
|
return diagnostics;
|
|
94808
94870
|
}
|
|
94871
|
+
function checkSizes(elements, view) {
|
|
94872
|
+
const diagnostics = [];
|
|
94873
|
+
for (const element of elements) {
|
|
94874
|
+
if (!element.size) continue;
|
|
94875
|
+
if (!view.containerKinds.includes(element.kind))
|
|
94876
|
+
diagnostics.push({
|
|
94877
|
+
code: "E0226",
|
|
94878
|
+
severity: "error",
|
|
94879
|
+
message: `\`${element.kind}\` is not a container, so it has no \`size\` to give`,
|
|
94880
|
+
span: element.size.span,
|
|
94881
|
+
note: `the \`${view.name}\` view accepts \`size\` on: ${view.containerKinds.join(", ")}`,
|
|
94882
|
+
help: "a leaf box is sized by its own label \u2014 set `label-padding:` in `style` to change how tightly, or nudge this one with `offset:`"
|
|
94883
|
+
});
|
|
94884
|
+
else if (!element.children.length)
|
|
94885
|
+
diagnostics.push({
|
|
94886
|
+
code: "E0226",
|
|
94887
|
+
severity: "error",
|
|
94888
|
+
message: `\`${element.id}\` holds nothing, so it is drawn as a plain box rather than a container`,
|
|
94889
|
+
span: element.size.span,
|
|
94890
|
+
note: "a container's size is the room around what it holds \u2014 with no children there is no room to give",
|
|
94891
|
+
help: "put the elements it contains inside it, or drop the `size:` and nudge this one with `offset:`"
|
|
94892
|
+
});
|
|
94893
|
+
}
|
|
94894
|
+
return diagnostics;
|
|
94895
|
+
}
|
|
94809
94896
|
function checkLogos(elements, view) {
|
|
94810
94897
|
const diagnostics = [];
|
|
94811
94898
|
const logoKinds = view.logoKinds ?? [];
|
|
@@ -98288,6 +98375,58 @@ function applySwap(scene, pair2, swap, leaves) {
|
|
|
98288
98375
|
return true;
|
|
98289
98376
|
}
|
|
98290
98377
|
}
|
|
98378
|
+
function straightenCarriedRuns(scene, titleBoxes, only) {
|
|
98379
|
+
if (!only.size) return;
|
|
98380
|
+
const leaves = scene.nodes.filter((node) => !node.container);
|
|
98381
|
+
if (!leaves.length) return;
|
|
98382
|
+
const ctx = createTidyContext(scene, leaves, titleBoxes, false);
|
|
98383
|
+
const strikesTitle = (at, from, to, horizontal) => {
|
|
98384
|
+
const lo = Math.min(from, to);
|
|
98385
|
+
const hi = Math.max(from, to);
|
|
98386
|
+
return titleBoxes.some(
|
|
98387
|
+
(band) => horizontal ? at > band.y && at < band.y + band.height && lo < band.x + band.width && hi > band.x : at > band.x && at < band.x + band.width && lo < band.y + band.height && hi > band.y
|
|
98388
|
+
);
|
|
98389
|
+
};
|
|
98390
|
+
const taken = (seat, mine) => scene.edges.some(
|
|
98391
|
+
(other) => other !== mine && other.pts.length >= 2 && [other.pts[0], other.pts[other.pts.length - 1]].some(
|
|
98392
|
+
(end) => Math.abs(end.x - seat.x) < MIN_ATTACH_GAP && Math.abs(end.y - seat.y) < MIN_ATTACH_GAP
|
|
98393
|
+
)
|
|
98394
|
+
);
|
|
98395
|
+
for (const edge of scene.edges) {
|
|
98396
|
+
if (!only.has(edge.id) || edge.pts.length < 3) continue;
|
|
98397
|
+
if (edge.pinned?.start || edge.pinned?.end) continue;
|
|
98398
|
+
const src = sideOf(edge.pts[0], leaves);
|
|
98399
|
+
const dst = sideOf(edge.pts[edge.pts.length - 1], leaves);
|
|
98400
|
+
if (!src || !dst || src.node === dst.node) continue;
|
|
98401
|
+
const alongY = src.side === "east" || src.side === "west";
|
|
98402
|
+
const rightwards = dst.node.x > src.node.x;
|
|
98403
|
+
const downwards = dst.node.y > src.node.y;
|
|
98404
|
+
const srcSide = alongY ? rightwards ? "east" : "west" : downwards ? "south" : "north";
|
|
98405
|
+
const dstSide = alongY ? rightwards ? "west" : "east" : downwards ? "north" : "south";
|
|
98406
|
+
const span = (node) => alongY ? [node.y + MIN_SIDE_INSET, node.y + node.height - MIN_SIDE_INSET] : [node.x + MIN_SIDE_INSET, node.x + node.width - MIN_SIDE_INSET];
|
|
98407
|
+
const [srcLo, srcHi] = span(src.node);
|
|
98408
|
+
const [dstLo, dstHi] = span(dst.node);
|
|
98409
|
+
const low = Math.max(srcLo, dstLo);
|
|
98410
|
+
const high = Math.min(srcHi, dstHi);
|
|
98411
|
+
if (low > high) continue;
|
|
98412
|
+
const at = Math.round((low + high) / 2);
|
|
98413
|
+
const seatOnFace = (node, side) => side === "north" ? { x: at, y: node.y } : side === "south" ? { x: at, y: node.y + node.height } : side === "west" ? { x: node.x, y: at } : { x: node.x + node.width, y: at };
|
|
98414
|
+
const from = seatOnFace(src.node, srcSide);
|
|
98415
|
+
const to = seatOnFace(dst.node, dstSide);
|
|
98416
|
+
const travel = alongY ? to.x - from.x : to.y - from.y;
|
|
98417
|
+
const outward = (side) => side === "east" || side === "south" ? 1 : -1;
|
|
98418
|
+
if (travel === 0 || Math.sign(travel) !== outward(srcSide)) continue;
|
|
98419
|
+
const lane = alongY ? [from.x, to.x] : [from.y, to.y];
|
|
98420
|
+
const runFrom = Math.min(lane[0], lane[1]);
|
|
98421
|
+
const runTo = Math.max(lane[0], lane[1]);
|
|
98422
|
+
if (ctx.runHitsNode(!alongY, at, runFrom, runTo)) continue;
|
|
98423
|
+
if (strikesTitle(at, runFrom, runTo, alongY)) continue;
|
|
98424
|
+
const others = ctx.runsExcept(edge.id);
|
|
98425
|
+
if (!ctx.runIsClear(alongY ? others.horizontal : others.vertical, at, runFrom, runTo)) continue;
|
|
98426
|
+
if (taken(from, edge) || taken(to, edge)) continue;
|
|
98427
|
+
edge.pts = [from, to];
|
|
98428
|
+
}
|
|
98429
|
+
}
|
|
98291
98430
|
function swapCrossingSiblingSeats(scene) {
|
|
98292
98431
|
const leaves = scene.nodes.filter((node) => !node.container);
|
|
98293
98432
|
const endsOf = (edge) => [
|
|
@@ -100954,6 +101093,155 @@ function stampLabelOffsets(edges, model) {
|
|
|
100954
101093
|
for (const label of edge.labels) label.offset = { dx: declared.dx, dy: declared.dy };
|
|
100955
101094
|
}
|
|
100956
101095
|
}
|
|
101096
|
+
function freeBands(box, axis) {
|
|
101097
|
+
const { node, pad } = box;
|
|
101098
|
+
const { min, size } = AXIS[axis];
|
|
101099
|
+
const spans = kidsOf(box).map((kid) => ({ start: kid[min], end: kid[min] + kid[size] })).sort((a, b) => a.start - b.start || a.end - b.end);
|
|
101100
|
+
const merged = [];
|
|
101101
|
+
for (const span of spans) {
|
|
101102
|
+
const last = merged[merged.length - 1];
|
|
101103
|
+
if (last && span.start <= last.end) last.end = Math.max(last.end, span.end);
|
|
101104
|
+
else merged.push({ ...span });
|
|
101105
|
+
}
|
|
101106
|
+
if (!merged.length) return [];
|
|
101107
|
+
const bands = [];
|
|
101108
|
+
const add = (start, end, keep) => {
|
|
101109
|
+
if (end - start > keep) bands.push({ end, spare: end - start - keep });
|
|
101110
|
+
};
|
|
101111
|
+
add(node[min], merged[0].start, pad);
|
|
101112
|
+
for (let i = 0; i + 1 < merged.length; i++)
|
|
101113
|
+
add(merged[i].end, merged[i + 1].start, MIN_SIBLING_GAP);
|
|
101114
|
+
add(merged[merged.length - 1].end, node[min] + node[size], pad);
|
|
101115
|
+
return bands;
|
|
101116
|
+
}
|
|
101117
|
+
function holdsChildren(box) {
|
|
101118
|
+
const { node, pad } = box;
|
|
101119
|
+
const kids = kidsOf(box);
|
|
101120
|
+
if (!kids.length) return { minWidth: pad * 2, minHeight: pad * 2 };
|
|
101121
|
+
return {
|
|
101122
|
+
minWidth: Math.max(...kids.map((kid) => kid.x + kid.width)) + pad - node.x,
|
|
101123
|
+
minHeight: Math.max(...kids.map((kid) => kid.y + kid.height)) + pad - node.y
|
|
101124
|
+
};
|
|
101125
|
+
}
|
|
101126
|
+
function contentFloor(box) {
|
|
101127
|
+
const spare = (axis) => freeBands(box, axis).reduce((total, band) => total + band.spare, 0);
|
|
101128
|
+
return { minWidth: box.node.width - spare("x"), minHeight: box.node.height - spare("y") };
|
|
101129
|
+
}
|
|
101130
|
+
function squeezeInside(box, axis, target) {
|
|
101131
|
+
const { min, size } = AXIS[axis];
|
|
101132
|
+
const need = box.node[size] - target;
|
|
101133
|
+
if (need <= 0) return;
|
|
101134
|
+
const bands = freeBands(box, axis);
|
|
101135
|
+
const spare = bands.reduce((total, band) => total + band.spare, 0);
|
|
101136
|
+
if (spare <= 0) return;
|
|
101137
|
+
const share = Math.min(1, need / spare);
|
|
101138
|
+
const shiftAt = (at) => bands.reduce((total, band) => at >= band.end ? total + band.spare * share : total, 0);
|
|
101139
|
+
for (const child of box.element.children) {
|
|
101140
|
+
const kid = box.nodeById.get(child.id);
|
|
101141
|
+
if (!kid) continue;
|
|
101142
|
+
const shift = shiftAt(kid[min]);
|
|
101143
|
+
if (!shift) continue;
|
|
101144
|
+
for (const id of subtreeIds(child)) {
|
|
101145
|
+
const inside = box.nodeById.get(id);
|
|
101146
|
+
if (inside) inside[min] -= shift;
|
|
101147
|
+
}
|
|
101148
|
+
}
|
|
101149
|
+
}
|
|
101150
|
+
function applyContainerSizes(scene, model) {
|
|
101151
|
+
const carried = /* @__PURE__ */ new Set();
|
|
101152
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
101153
|
+
const walk = (elements) => {
|
|
101154
|
+
for (const element of elements) {
|
|
101155
|
+
walk(element.children);
|
|
101156
|
+
if (element.size) wanted.set(element.id, element.size);
|
|
101157
|
+
}
|
|
101158
|
+
};
|
|
101159
|
+
walk(model.elements);
|
|
101160
|
+
if (!wanted.size) return carried;
|
|
101161
|
+
const nodeById = new Map(scene.nodes.map((node) => [node.id, node]));
|
|
101162
|
+
const pad = containerPad(model);
|
|
101163
|
+
const clampedSpans = /* @__PURE__ */ new Set();
|
|
101164
|
+
const applied = /* @__PURE__ */ new Map();
|
|
101165
|
+
const before = new Map(
|
|
101166
|
+
scene.nodes.map((node) => [
|
|
101167
|
+
node.id,
|
|
101168
|
+
{ x: node.x, y: node.y, width: node.width, height: node.height }
|
|
101169
|
+
])
|
|
101170
|
+
);
|
|
101171
|
+
for (const [id, spec] of wanted) {
|
|
101172
|
+
const node = nodeById.get(id);
|
|
101173
|
+
const element = model.index.get(id);
|
|
101174
|
+
if (!node || !element) continue;
|
|
101175
|
+
const box = { node, element, nodeById, pad };
|
|
101176
|
+
const floor = contentFloor(box);
|
|
101177
|
+
const width = Math.max(node.width + spec.dw, floor.minWidth);
|
|
101178
|
+
const height = Math.max(node.height + spec.dh, floor.minHeight);
|
|
101179
|
+
if (width !== node.width + spec.dw || height !== node.height + spec.dh)
|
|
101180
|
+
clampedSpans.add(spec.span);
|
|
101181
|
+
squeezeInside(box, "x", width);
|
|
101182
|
+
squeezeInside(box, "y", height);
|
|
101183
|
+
node.width = width;
|
|
101184
|
+
node.height = height;
|
|
101185
|
+
}
|
|
101186
|
+
const containWithin = (elements) => {
|
|
101187
|
+
for (const element of elements) {
|
|
101188
|
+
containWithin(element.children);
|
|
101189
|
+
const node = nodeById.get(element.id);
|
|
101190
|
+
if (!node?.container) continue;
|
|
101191
|
+
const holds = holdsChildren({ node, element, nodeById, pad });
|
|
101192
|
+
node.width = Math.max(node.width, holds.minWidth);
|
|
101193
|
+
node.height = Math.max(node.height, holds.minHeight);
|
|
101194
|
+
}
|
|
101195
|
+
};
|
|
101196
|
+
containWithin(model.elements);
|
|
101197
|
+
for (const id of wanted.keys()) {
|
|
101198
|
+
const node = nodeById.get(id);
|
|
101199
|
+
const was = before.get(id);
|
|
101200
|
+
if (node && was)
|
|
101201
|
+
applied.set(id, { dw: node.width - was.width, dh: node.height - was.height });
|
|
101202
|
+
}
|
|
101203
|
+
if (clampedSpans.size) scene.clampedSizes = clampedSpans;
|
|
101204
|
+
scene.appliedSizes = applied;
|
|
101205
|
+
const seats = [];
|
|
101206
|
+
for (const node of scene.nodes) {
|
|
101207
|
+
const was = before.get(node.id);
|
|
101208
|
+
if (!was) continue;
|
|
101209
|
+
const moved = {
|
|
101210
|
+
dx: node.x - was.x,
|
|
101211
|
+
dy: node.y - was.y,
|
|
101212
|
+
dw: node.width - was.width,
|
|
101213
|
+
dh: node.height - was.height
|
|
101214
|
+
};
|
|
101215
|
+
if (!moved.dx && !moved.dy && !moved.dw && !moved.dh) continue;
|
|
101216
|
+
seats.push({ box: was, ...moved });
|
|
101217
|
+
}
|
|
101218
|
+
seats.sort((a, b) => a.box.width * a.box.height - b.box.width * b.box.height);
|
|
101219
|
+
if (!seats.length) return carried;
|
|
101220
|
+
for (const edge of scene.edges) {
|
|
101221
|
+
if (edge.pts.length < 2) continue;
|
|
101222
|
+
for (const which of ["first", "last"]) {
|
|
101223
|
+
const terminal = edge.pts[which === "first" ? 0 : edge.pts.length - 1];
|
|
101224
|
+
const seat = seats.find((candidate) => pointOn(terminal, candidate.box));
|
|
101225
|
+
if (!seat) continue;
|
|
101226
|
+
const onRight = terminal.x >= seat.box.x + seat.box.width - ON_BORDER;
|
|
101227
|
+
const onBottom = terminal.y >= seat.box.y + seat.box.height - ON_BORDER;
|
|
101228
|
+
const delta = {
|
|
101229
|
+
dx: seat.dx + (onRight ? seat.dw : 0),
|
|
101230
|
+
dy: seat.dy + (onBottom ? seat.dh : 0)
|
|
101231
|
+
};
|
|
101232
|
+
if (!delta.dx && !delta.dy) continue;
|
|
101233
|
+
carried.add(edge.id);
|
|
101234
|
+
carryTerminal(edge, which, delta, {
|
|
101235
|
+
x: seat.box.x + seat.dx,
|
|
101236
|
+
y: seat.box.y + seat.dy,
|
|
101237
|
+
width: seat.box.width + seat.dw,
|
|
101238
|
+
height: seat.box.height + seat.dh
|
|
101239
|
+
});
|
|
101240
|
+
}
|
|
101241
|
+
}
|
|
101242
|
+
for (const edge of scene.edges) if (carried.has(edge.id)) dropRedundantPoints(edge.pts);
|
|
101243
|
+
return carried;
|
|
101244
|
+
}
|
|
100957
101245
|
function applyNodeOffsets(scene, offsetOf) {
|
|
100958
101246
|
const carried = /* @__PURE__ */ new Set();
|
|
100959
101247
|
if (!offsetOf.size) return carried;
|
|
@@ -101018,19 +101306,36 @@ function applyNodeOffsets(scene, offsetOf) {
|
|
|
101018
101306
|
const seat = seats.find((candidate) => pointOn(terminal, candidate.box));
|
|
101019
101307
|
if (!seat || !seat.delta.dx && !seat.delta.dy) continue;
|
|
101020
101308
|
carried.add(edge.id);
|
|
101021
|
-
|
|
101022
|
-
|
|
101023
|
-
|
|
101024
|
-
|
|
101025
|
-
|
|
101026
|
-
|
|
101027
|
-
const degenerate = Math.abs(elbow.x - terminal.x) < 0.5 && Math.abs(elbow.y - terminal.y) < 0.5 || Math.abs(elbow.x - neighbour.x) < 0.5 && Math.abs(elbow.y - neighbour.y) < 0.5;
|
|
101028
|
-
if (!degenerate) edge.pts.splice(end === 0 ? 1 : edge.pts.length - 1, 0, elbow);
|
|
101309
|
+
carryTerminal(edge, end === 0 ? "first" : "last", seat.delta, {
|
|
101310
|
+
x: seat.box.x + seat.delta.dx,
|
|
101311
|
+
y: seat.box.y + seat.delta.dy,
|
|
101312
|
+
width: seat.box.width,
|
|
101313
|
+
height: seat.box.height
|
|
101314
|
+
});
|
|
101029
101315
|
}
|
|
101030
101316
|
}
|
|
101031
101317
|
for (const edge of scene.edges) if (carried.has(edge.id)) dropRedundantPoints(edge.pts);
|
|
101032
101318
|
return carried;
|
|
101033
101319
|
}
|
|
101320
|
+
function carryTerminal(edge, which, delta, face) {
|
|
101321
|
+
const end = which === "first" ? 0 : edge.pts.length - 1;
|
|
101322
|
+
const terminal = edge.pts[end];
|
|
101323
|
+
const neighbour = edge.pts[which === "first" ? 1 : edge.pts.length - 2];
|
|
101324
|
+
const wasHorizontal = Math.abs(terminal.y - neighbour.y) < SEGMENT_EPSILON;
|
|
101325
|
+
terminal.x += delta.dx;
|
|
101326
|
+
terminal.y += delta.dy;
|
|
101327
|
+
const slide = wasHorizontal ? { along: "y", low: face.y, high: face.y + face.height } : { along: "x", low: face.x, high: face.x + face.width };
|
|
101328
|
+
const residual = neighbour[slide.along] - terminal[slide.along];
|
|
101329
|
+
const seat = Math.min(
|
|
101330
|
+
Math.max(neighbour[slide.along], slide.low + SEAT_INSET),
|
|
101331
|
+
Math.max(slide.high - SEAT_INSET, slide.low + SEAT_INSET)
|
|
101332
|
+
);
|
|
101333
|
+
if (Math.abs(residual) < ARROW_ROOM2 && Math.abs(seat - neighbour[slide.along]) < SEGMENT_EPSILON)
|
|
101334
|
+
terminal[slide.along] = seat;
|
|
101335
|
+
const elbow = wasHorizontal ? { x: neighbour.x, y: terminal.y } : { x: terminal.x, y: neighbour.y };
|
|
101336
|
+
const degenerate = Math.abs(elbow.x - terminal.x) < SEGMENT_EPSILON && Math.abs(elbow.y - terminal.y) < SEGMENT_EPSILON || Math.abs(elbow.x - neighbour.x) < SEGMENT_EPSILON && Math.abs(elbow.y - neighbour.y) < SEGMENT_EPSILON;
|
|
101337
|
+
if (!degenerate) edge.pts.splice(which === "first" ? 1 : edge.pts.length - 1, 0, elbow);
|
|
101338
|
+
}
|
|
101034
101339
|
function dropRedundantPoints(pts) {
|
|
101035
101340
|
const inLine = (a, b, c) => Math.abs(a.x - b.x) < SEGMENT_EPSILON && Math.abs(b.x - c.x) < SEGMENT_EPSILON || Math.abs(a.y - b.y) < SEGMENT_EPSILON && Math.abs(b.y - c.y) < SEGMENT_EPSILON;
|
|
101036
101341
|
for (let i = pts.length - 2; i >= 1; i--)
|
|
@@ -101156,6 +101461,37 @@ function shiftIntoCanvas(scene) {
|
|
|
101156
101461
|
scene.width += shiftX;
|
|
101157
101462
|
scene.height += shiftY;
|
|
101158
101463
|
}
|
|
101464
|
+
function sizeOverlaps(scene, model, overlap) {
|
|
101465
|
+
const sized = /* @__PURE__ */ new Map();
|
|
101466
|
+
for (const element of model.index.values())
|
|
101467
|
+
if (element.size) sized.set(element.id, element.size.span);
|
|
101468
|
+
if (!sized.size) return [];
|
|
101469
|
+
const family = /* @__PURE__ */ new Map();
|
|
101470
|
+
const walk = (elements, ancestors) => {
|
|
101471
|
+
for (const element of elements) {
|
|
101472
|
+
if (sized.has(element.id))
|
|
101473
|
+
family.set(element.id, /* @__PURE__ */ new Set([...ancestors, ...subtreeIds(element)]));
|
|
101474
|
+
walk(element.children, [...ancestors, element.id]);
|
|
101475
|
+
}
|
|
101476
|
+
};
|
|
101477
|
+
walk(model.elements, []);
|
|
101478
|
+
const diagnostics = [];
|
|
101479
|
+
for (const node of scene.nodes) {
|
|
101480
|
+
const span = sized.get(node.id);
|
|
101481
|
+
if (!span) continue;
|
|
101482
|
+
const kin = family.get(node.id);
|
|
101483
|
+
const struck = scene.nodes.find((other) => !kin.has(other.id) && overlap(node, other));
|
|
101484
|
+
if (!struck) continue;
|
|
101485
|
+
diagnostics.push({
|
|
101486
|
+
code: "W0572",
|
|
101487
|
+
severity: "warning",
|
|
101488
|
+
message: `\`size\` on \`${node.id}\` overlaps \`${struck.id}\``,
|
|
101489
|
+
span,
|
|
101490
|
+
help: "the resize is applied as written \u2014 make it smaller, or give the drawing room with `order:`"
|
|
101491
|
+
});
|
|
101492
|
+
}
|
|
101493
|
+
return diagnostics;
|
|
101494
|
+
}
|
|
101159
101495
|
function offsetDiagnostics(scene, model) {
|
|
101160
101496
|
const hints = [];
|
|
101161
101497
|
for (const span of scene.segmentHints?.stale ?? [])
|
|
@@ -101174,6 +101510,14 @@ function offsetDiagnostics(scene, model) {
|
|
|
101174
101510
|
span,
|
|
101175
101511
|
help: "the run carries a terminal, so it can only slide as far as the side that terminal sits on \u2014 pin the side with `ID.side`, or move the element instead"
|
|
101176
101512
|
});
|
|
101513
|
+
for (const span of scene.clampedSizes ?? [])
|
|
101514
|
+
hints.push({
|
|
101515
|
+
code: "W0575",
|
|
101516
|
+
severity: "warning",
|
|
101517
|
+
message: "`size` was limited to keep this container around its own children",
|
|
101518
|
+
span,
|
|
101519
|
+
help: "a container is drawn around what it holds, which is not negotiable \u2014 resize or move the children first if the box has to be smaller"
|
|
101520
|
+
});
|
|
101177
101521
|
const spans = /* @__PURE__ */ new Map();
|
|
101178
101522
|
const walk = (elements, inherited) => {
|
|
101179
101523
|
for (const element of elements) {
|
|
@@ -101184,8 +101528,9 @@ function offsetDiagnostics(scene, model) {
|
|
|
101184
101528
|
};
|
|
101185
101529
|
walk(model.elements);
|
|
101186
101530
|
for (const flow of model.flows) if (flow.labelOffset) spans.set(flow.id, flow.labelOffset.span);
|
|
101187
|
-
if (!spans.size) return hints;
|
|
101188
101531
|
const overlap = (a, b) => a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height;
|
|
101532
|
+
hints.push(...sizeOverlaps(scene, model, overlap));
|
|
101533
|
+
if (!spans.size) return hints;
|
|
101189
101534
|
const leaves = scene.nodes.filter((node) => !node.container);
|
|
101190
101535
|
const labels = scene.edges.flatMap((edge) => edge.labels);
|
|
101191
101536
|
const diagnostics = [...hints];
|
|
@@ -101644,9 +101989,11 @@ function refuseScenicRepairs(scene, before) {
|
|
|
101644
101989
|
}
|
|
101645
101990
|
}
|
|
101646
101991
|
function applyAuthorPositioning(scene, model) {
|
|
101992
|
+
const runsBefore = new Map(scene.edges.map((edge) => [edge.id, straightRuns(edge.pts).length]));
|
|
101993
|
+
const resized = applyContainerSizes(scene, model);
|
|
101647
101994
|
const offsets = offsetAssignment(model);
|
|
101648
|
-
if (offsets.size) {
|
|
101649
|
-
const carried = applyNodeOffsets(scene, offsets);
|
|
101995
|
+
if (offsets.size || resized.size) {
|
|
101996
|
+
const carried = /* @__PURE__ */ new Set([...resized, ...applyNodeOffsets(scene, offsets)]);
|
|
101650
101997
|
for (const edge of scene.edges) if (carried.has(edge.id)) edge.repairedFrom = void 0;
|
|
101651
101998
|
const titles = titleBoxesOf(scene, model);
|
|
101652
101999
|
reaimAfterOffsets(scene, titles, carried);
|
|
@@ -101662,6 +102009,12 @@ function applyAuthorPositioning(scene, model) {
|
|
|
101662
102009
|
refuseScenicRepairs(scene, before);
|
|
101663
102010
|
decoincideAfterOffsets(scene, titles, repairable);
|
|
101664
102011
|
}
|
|
102012
|
+
const bent = new Set(
|
|
102013
|
+
scene.edges.filter(
|
|
102014
|
+
(edge) => carried.has(edge.id) && straightRuns(edge.pts).length > (runsBefore.get(edge.id) ?? 0)
|
|
102015
|
+
).map((edge) => edge.id)
|
|
102016
|
+
);
|
|
102017
|
+
straightenCarriedRuns(scene, titles, bent);
|
|
101665
102018
|
}
|
|
101666
102019
|
const nudgedRuns = applySegmentOffsets(scene, model);
|
|
101667
102020
|
if (nudgedRuns) {
|
|
@@ -101670,7 +102023,7 @@ function applyAuthorPositioning(scene, model) {
|
|
|
101670
102023
|
edge.repairedFrom = void 0;
|
|
101671
102024
|
}
|
|
101672
102025
|
const nudgedLabels = scene.edges.some((edge) => edge.labels.some((label) => label.offset));
|
|
101673
|
-
if (!offsets.size && !nudgedRuns && !nudgedLabels) return;
|
|
102026
|
+
if (!offsets.size && !resized.size && !nudgedRuns && !nudgedLabels) return;
|
|
101674
102027
|
anchorFlowLabels(scene, titleBoxesOf(scene, model));
|
|
101675
102028
|
fitCanvas(scene);
|
|
101676
102029
|
}
|
|
@@ -101821,7 +102174,7 @@ async function chooseLayout(model, view) {
|
|
|
101821
102174
|
}
|
|
101822
102175
|
return best?.scene ?? base;
|
|
101823
102176
|
}
|
|
101824
|
-
var orderOption, semiInteractiveOption, ACTOR_MIN_WIDTH, PORT_PASS_ROUNDS, centerOf, isElkSource, elkPortId, DENSE_ENOUGH, DENSITY_GAIN, INGRESS_PARTITION, DEFAULT_INGRESS_KINDS, EGRESS_PARTITION, SLOT_SCALE, SIDE_TO_ELK, sideRequest, pointOn, SEGMENT_EPSILON, SLIDE_UNBOUNDED, SEAT_INSET, LEFT_MARGIN, RECLAIM_GAP, MIN_RECLAIM, MIN_RECLAIM_KEPT, RECLAIM_SHARE, RECLAIM_UNBUYABLE_TIER, RECLAIM_TRADE_SHARE, RECLAIM_TRADE_MIN, RECLAIM_LIFT_SHARE, BORDER_AIR, ARROW_ROOM2, RUN_CLEAR, ASPECT_TARGETS, pathLength2;
|
|
102177
|
+
var orderOption, semiInteractiveOption, ACTOR_MIN_WIDTH, PORT_PASS_ROUNDS, centerOf, isElkSource, elkPortId, DENSE_ENOUGH, DENSITY_GAIN, INGRESS_PARTITION, DEFAULT_INGRESS_KINDS, EGRESS_PARTITION, SLOT_SCALE, SIDE_TO_ELK, sideRequest, ON_BORDER, pointOn, containerPad, MIN_SIBLING_GAP, kidsOf, AXIS, SEGMENT_EPSILON, SLIDE_UNBOUNDED, SEAT_INSET, LEFT_MARGIN, RECLAIM_GAP, MIN_RECLAIM, MIN_RECLAIM_KEPT, RECLAIM_SHARE, RECLAIM_UNBUYABLE_TIER, RECLAIM_TRADE_SHARE, RECLAIM_TRADE_MIN, RECLAIM_LIFT_SHARE, BORDER_AIR, ARROW_ROOM2, RUN_CLEAR, ASPECT_TARGETS, pathLength2;
|
|
101825
102178
|
var init_scene_layout = __esm({
|
|
101826
102179
|
"src/scene-layout.ts"() {
|
|
101827
102180
|
"use strict";
|
|
@@ -101855,7 +102208,15 @@ var init_scene_layout = __esm({
|
|
|
101855
102208
|
bottom: "SOUTH"
|
|
101856
102209
|
};
|
|
101857
102210
|
sideRequest = (side) => side ? { value: side } : void 0;
|
|
102211
|
+
ON_BORDER = 1;
|
|
101858
102212
|
pointOn = (point, box) => point.x >= box.x - 1 && point.x <= box.x + box.width + 1 && point.y >= box.y - 1 && point.y <= box.y + box.height + 1;
|
|
102213
|
+
containerPad = (model) => model.style.containerPadding ?? (model.style.compact ? 7 : 9);
|
|
102214
|
+
MIN_SIBLING_GAP = 20;
|
|
102215
|
+
kidsOf = (box) => box.element.children.map((child) => box.nodeById.get(child.id)).filter((child) => !!child);
|
|
102216
|
+
AXIS = {
|
|
102217
|
+
x: { min: "x", size: "width" },
|
|
102218
|
+
y: { min: "y", size: "height" }
|
|
102219
|
+
};
|
|
101859
102220
|
SEGMENT_EPSILON = 0.5;
|
|
101860
102221
|
SLIDE_UNBOUNDED = 1e4;
|
|
101861
102222
|
SEAT_INSET = 4;
|
|
@@ -103908,7 +104269,7 @@ CORE_I -> PARTNER "Nightly export" (SFTP/22)
|
|
|
103908
104269
|
};
|
|
103909
104270
|
args = process.argv.slice(2);
|
|
103910
104271
|
command = args[0];
|
|
103911
|
-
version = true ? "1.0.0-
|
|
104272
|
+
version = true ? "1.0.0-RC18" : pkg.version;
|
|
103912
104273
|
VALUE_FLAGS = /* @__PURE__ */ new Set(["-o", "--format", "--theme"]);
|
|
103913
104274
|
positionalFile = () => {
|
|
103914
104275
|
for (let index = 0; index < args.length; index++) {
|
package/dist/cairn.mjs
CHANGED
|
@@ -93129,57 +93129,93 @@ function parseElement(p, sourceToken, parent) {
|
|
|
93129
93129
|
(parent ? parent.children : model.elements).push(element);
|
|
93130
93130
|
}
|
|
93131
93131
|
function splitEndpoint(token, flow, end) {
|
|
93132
|
-
const
|
|
93133
|
-
if (
|
|
93134
|
-
|
|
93135
|
-
const
|
|
93136
|
-
|
|
93137
|
-
|
|
93138
|
-
|
|
93139
|
-
|
|
93140
|
-
|
|
93141
|
-
base,
|
|
93142
|
-
baseSpan: { line: token.span.line, col: token.span.col, len: base.length },
|
|
93143
|
-
suffix,
|
|
93144
|
-
suffixSpan: { line: token.span.line, col: token.span.col + dot + 1, len: suffix.length }
|
|
93145
|
-
};
|
|
93132
|
+
const texts = token.text.split(".");
|
|
93133
|
+
if (texts.length < 2 || texts.some((text) => !text)) return null;
|
|
93134
|
+
let col = token.span.col;
|
|
93135
|
+
const parts = texts.map((text) => {
|
|
93136
|
+
const span = { line: token.span.line, col, len: text.length };
|
|
93137
|
+
col += text.length + 1;
|
|
93138
|
+
return { text, span };
|
|
93139
|
+
});
|
|
93140
|
+
return { flow, end, raw: token.text, rawSpan: token.span, parts };
|
|
93146
93141
|
}
|
|
93147
93142
|
function resolveEndpointSuffixes(splits, model, diagnostics) {
|
|
93143
|
+
const sideOf2 = (text) => ATTACH_SIDES.includes(text);
|
|
93144
|
+
const roleOf = (text) => ATTACH_ROLES.includes(text);
|
|
93148
93145
|
for (const split of splits) {
|
|
93149
|
-
const isSide = ATTACH_SIDES.includes(split.suffix);
|
|
93150
|
-
const isRole = ATTACH_ROLES.includes(split.suffix);
|
|
93151
93146
|
if (model.index.has(split.raw)) {
|
|
93152
|
-
|
|
93147
|
+
let head = split.parts.length;
|
|
93148
|
+
while (head > 1 && (sideOf2(split.parts[head - 1].text) || roleOf(split.parts[head - 1].text)))
|
|
93149
|
+
head--;
|
|
93150
|
+
const shadowed = split.parts.slice(head).map((part) => part.text);
|
|
93151
|
+
if (shadowed.length) {
|
|
93152
|
+
const base = split.parts.slice(0, head).map((part) => part.text).join(".");
|
|
93153
|
+
const reading = [
|
|
93154
|
+
shadowed.some((text) => roleOf(text)) ? "a role" : "",
|
|
93155
|
+
shadowed.some((text) => sideOf2(text)) ? "an attachment side" : ""
|
|
93156
|
+
].filter(Boolean);
|
|
93153
93157
|
diagnostics.push({
|
|
93154
93158
|
code: "W0571",
|
|
93155
93159
|
severity: "warning",
|
|
93156
|
-
message: `\`${split.raw}\` is a declared element, so \`.${
|
|
93160
|
+
message: `\`${split.raw}\` is a declared element, so \`.${shadowed.join(".")}\` is not read as ${reading.join(" and ")}`,
|
|
93157
93161
|
span: split.rawSpan,
|
|
93158
93162
|
note: "a declared id always wins over the `ID.side` reading",
|
|
93159
|
-
help:
|
|
93163
|
+
help: `rename the element if you meant \`.${shadowed.join(".")}\` on \`${base}\``
|
|
93160
93164
|
});
|
|
93165
|
+
}
|
|
93161
93166
|
continue;
|
|
93162
93167
|
}
|
|
93163
|
-
|
|
93164
|
-
|
|
93165
|
-
|
|
93166
|
-
|
|
93167
|
-
|
|
93168
|
-
|
|
93169
|
-
|
|
93170
|
-
|
|
93171
|
-
|
|
93172
|
-
|
|
93173
|
-
|
|
93174
|
-
const
|
|
93168
|
+
let cut = 0;
|
|
93169
|
+
for (let take = split.parts.length - 1; take >= 1; take--) {
|
|
93170
|
+
const base = split.parts.slice(0, take).map((part) => part.text).join(".");
|
|
93171
|
+
if (model.index.has(base)) {
|
|
93172
|
+
cut = take;
|
|
93173
|
+
break;
|
|
93174
|
+
}
|
|
93175
|
+
}
|
|
93176
|
+
if (!cut) continue;
|
|
93177
|
+
let side;
|
|
93178
|
+
let role;
|
|
93179
|
+
for (const part of split.parts.slice(cut)) {
|
|
93180
|
+
const partIsSide = sideOf2(part.text);
|
|
93181
|
+
const partIsRole = roleOf(part.text);
|
|
93182
|
+
if (!partIsSide && !partIsRole) {
|
|
93183
|
+
diagnostics.push({
|
|
93184
|
+
code: "E0223",
|
|
93185
|
+
severity: "error",
|
|
93186
|
+
message: `unknown endpoint suffix \`${part.text}\``,
|
|
93187
|
+
span: part.span,
|
|
93188
|
+
note: "an endpoint names either the side it attaches to, as the diagram is read, or its role towards a queue",
|
|
93189
|
+
help: "use `left`, `right`, `top` or `bottom` \u2014 `APP.right -> DB.left` \u2014 or `producer` / `consumer`, e.g. `CAPTURE.producer -> EVENTS`, or one of each: `CAPTURE.producer.top -> EVENTS`"
|
|
93190
|
+
});
|
|
93191
|
+
continue;
|
|
93192
|
+
}
|
|
93193
|
+
const held = partIsSide ? side : role;
|
|
93194
|
+
if (held) {
|
|
93195
|
+
diagnostics.push({
|
|
93196
|
+
code: "E0227",
|
|
93197
|
+
severity: "error",
|
|
93198
|
+
message: `this endpoint already names ${partIsSide ? `the \`${held.value}\` side` : `the \`${held.value}\` role`}`,
|
|
93199
|
+
span: part.span,
|
|
93200
|
+
note: `\`${split.raw}\` names two ${partIsSide ? "attachment sides" : "roles"}, and only one can hold`,
|
|
93201
|
+
help: partIsSide ? "keep one side \u2014 a side and a *role* do accumulate, e.g. `CAPTURE.producer.top`" : "keep one role \u2014 a role and a *side* do accumulate, e.g. `CAPTURE.producer.top`"
|
|
93202
|
+
});
|
|
93203
|
+
continue;
|
|
93204
|
+
}
|
|
93205
|
+
if (partIsSide) side = { value: part.text, span: part.span };
|
|
93206
|
+
else role = { value: part.text, span: part.span };
|
|
93207
|
+
}
|
|
93208
|
+
const baseParts = split.parts.slice(0, cut);
|
|
93209
|
+
const baseText = baseParts.map((part) => part.text).join(".");
|
|
93210
|
+
const baseSpan = { line: split.rawSpan.line, col: split.rawSpan.col, len: baseText.length };
|
|
93175
93211
|
if (split.end === "from") {
|
|
93176
|
-
split.flow.from =
|
|
93177
|
-
split.flow.fromSpan =
|
|
93212
|
+
split.flow.from = baseText;
|
|
93213
|
+
split.flow.fromSpan = baseSpan;
|
|
93178
93214
|
split.flow.fromSide = side;
|
|
93179
93215
|
split.flow.fromRole = role;
|
|
93180
93216
|
} else {
|
|
93181
|
-
split.flow.to =
|
|
93182
|
-
split.flow.toSpan =
|
|
93217
|
+
split.flow.to = baseText;
|
|
93218
|
+
split.flow.toSpan = baseSpan;
|
|
93183
93219
|
split.flow.toSide = side;
|
|
93184
93220
|
split.flow.toRole = role;
|
|
93185
93221
|
}
|
|
@@ -93229,9 +93265,9 @@ function readOffsetPair(p) {
|
|
|
93229
93265
|
span: { line: dx.span.line, col: dx.span.col, len: dy.span.col + dy.span.len - dx.span.col }
|
|
93230
93266
|
};
|
|
93231
93267
|
}
|
|
93232
|
-
function
|
|
93268
|
+
function tryPairEntry(p, parent, entry, put) {
|
|
93233
93269
|
const { matchToken, advance, reportCoded, save, restore, syncToNextLine } = p;
|
|
93234
|
-
if (!parent || !matchToken("id",
|
|
93270
|
+
if (!parent || !matchToken("id", entry.key)) return false;
|
|
93235
93271
|
const mark = save();
|
|
93236
93272
|
const keyToken = advance();
|
|
93237
93273
|
if (!matchToken("colon")) {
|
|
@@ -93241,18 +93277,39 @@ function tryOffsetEntry(p, parent) {
|
|
|
93241
93277
|
advance();
|
|
93242
93278
|
const pair2 = readOffsetPair(p);
|
|
93243
93279
|
if (!pair2) {
|
|
93244
|
-
reportCoded(
|
|
93245
|
-
"E0109",
|
|
93246
|
-
"`offset` expects two whole numbers \u2014 `offset: <dx>, <dy>`",
|
|
93247
|
-
keyToken.span,
|
|
93248
|
-
"e.g. `offset: 40, -20` \u2014 40px right and 20px up from where the layout put it"
|
|
93249
|
-
);
|
|
93280
|
+
reportCoded(entry.code, entry.message, keyToken.span, entry.help);
|
|
93250
93281
|
syncToNextLine();
|
|
93251
93282
|
return true;
|
|
93252
93283
|
}
|
|
93253
|
-
parent
|
|
93284
|
+
put(parent, pair2);
|
|
93254
93285
|
return true;
|
|
93255
93286
|
}
|
|
93287
|
+
var tryOffsetEntry = (p, parent) => tryPairEntry(
|
|
93288
|
+
p,
|
|
93289
|
+
parent,
|
|
93290
|
+
{
|
|
93291
|
+
key: "offset",
|
|
93292
|
+
code: "E0109",
|
|
93293
|
+
message: "`offset` expects two whole numbers \u2014 `offset: <dx>, <dy>`",
|
|
93294
|
+
help: "e.g. `offset: 40, -20` \u2014 40px right and 20px up from where the layout put it"
|
|
93295
|
+
},
|
|
93296
|
+
(element, pair2) => {
|
|
93297
|
+
element.offset = pair2;
|
|
93298
|
+
}
|
|
93299
|
+
);
|
|
93300
|
+
var trySizeEntry = (p, parent) => tryPairEntry(
|
|
93301
|
+
p,
|
|
93302
|
+
parent,
|
|
93303
|
+
{
|
|
93304
|
+
key: "size",
|
|
93305
|
+
code: "E0110",
|
|
93306
|
+
message: "`size` expects two whole numbers \u2014 `size: <dw>, <dh>`",
|
|
93307
|
+
help: "e.g. `size: 120, 40` \u2014 120px wider and 40px taller than the layout made it"
|
|
93308
|
+
},
|
|
93309
|
+
(element, pair2) => {
|
|
93310
|
+
element.size = { dw: pair2.dx, dh: pair2.dy, span: pair2.span };
|
|
93311
|
+
}
|
|
93312
|
+
);
|
|
93256
93313
|
function tryLogoEntry(p, parent) {
|
|
93257
93314
|
const { matchToken, advance, reportCoded, save, restore, syncToNextLine } = p;
|
|
93258
93315
|
if (!parent || !matchToken("id", "logo")) return false;
|
|
@@ -93496,7 +93553,7 @@ function parse(src) {
|
|
|
93496
93553
|
}
|
|
93497
93554
|
if (tryStyleBlock(parent)) return;
|
|
93498
93555
|
if (tryOrderEntry(parser, parent)) return;
|
|
93499
|
-
if (tryOffsetEntry(parser, parent)) return;
|
|
93556
|
+
if (tryOffsetEntry(parser, parent) || trySizeEntry(parser, parent)) return;
|
|
93500
93557
|
if (tryLogoEntry(parser, parent)) return;
|
|
93501
93558
|
if (!parent && tryLegendBlock(parser)) return;
|
|
93502
93559
|
if (!parent && tryBusinessObject(parser)) return;
|
|
@@ -94648,6 +94705,7 @@ function validate(model) {
|
|
|
94648
94705
|
...checkNesting(elements, view),
|
|
94649
94706
|
...checkFlows(model, view),
|
|
94650
94707
|
...checkEndpointRoles(model, view),
|
|
94708
|
+
...checkSizes(elements, view),
|
|
94651
94709
|
...checkLogos(elements, view),
|
|
94652
94710
|
...checkBusinessObjects(model, view),
|
|
94653
94711
|
...checkMinimumCounts(elements, model, view),
|
|
@@ -94690,6 +94748,31 @@ function checkUnknownKinds(elements, view) {
|
|
|
94690
94748
|
}
|
|
94691
94749
|
return diagnostics;
|
|
94692
94750
|
}
|
|
94751
|
+
function checkSizes(elements, view) {
|
|
94752
|
+
const diagnostics = [];
|
|
94753
|
+
for (const element of elements) {
|
|
94754
|
+
if (!element.size) continue;
|
|
94755
|
+
if (!view.containerKinds.includes(element.kind))
|
|
94756
|
+
diagnostics.push({
|
|
94757
|
+
code: "E0226",
|
|
94758
|
+
severity: "error",
|
|
94759
|
+
message: `\`${element.kind}\` is not a container, so it has no \`size\` to give`,
|
|
94760
|
+
span: element.size.span,
|
|
94761
|
+
note: `the \`${view.name}\` view accepts \`size\` on: ${view.containerKinds.join(", ")}`,
|
|
94762
|
+
help: "a leaf box is sized by its own label \u2014 set `label-padding:` in `style` to change how tightly, or nudge this one with `offset:`"
|
|
94763
|
+
});
|
|
94764
|
+
else if (!element.children.length)
|
|
94765
|
+
diagnostics.push({
|
|
94766
|
+
code: "E0226",
|
|
94767
|
+
severity: "error",
|
|
94768
|
+
message: `\`${element.id}\` holds nothing, so it is drawn as a plain box rather than a container`,
|
|
94769
|
+
span: element.size.span,
|
|
94770
|
+
note: "a container's size is the room around what it holds \u2014 with no children there is no room to give",
|
|
94771
|
+
help: "put the elements it contains inside it, or drop the `size:` and nudge this one with `offset:`"
|
|
94772
|
+
});
|
|
94773
|
+
}
|
|
94774
|
+
return diagnostics;
|
|
94775
|
+
}
|
|
94693
94776
|
function checkLogos(elements, view) {
|
|
94694
94777
|
const diagnostics = [];
|
|
94695
94778
|
const logoKinds = view.logoKinds ?? [];
|
|
@@ -98089,6 +98172,58 @@ function applySwap(scene, pair2, swap, leaves) {
|
|
|
98089
98172
|
return true;
|
|
98090
98173
|
}
|
|
98091
98174
|
}
|
|
98175
|
+
function straightenCarriedRuns(scene, titleBoxes, only) {
|
|
98176
|
+
if (!only.size) return;
|
|
98177
|
+
const leaves = scene.nodes.filter((node) => !node.container);
|
|
98178
|
+
if (!leaves.length) return;
|
|
98179
|
+
const ctx = createTidyContext(scene, leaves, titleBoxes, false);
|
|
98180
|
+
const strikesTitle = (at, from, to, horizontal) => {
|
|
98181
|
+
const lo = Math.min(from, to);
|
|
98182
|
+
const hi = Math.max(from, to);
|
|
98183
|
+
return titleBoxes.some(
|
|
98184
|
+
(band) => horizontal ? at > band.y && at < band.y + band.height && lo < band.x + band.width && hi > band.x : at > band.x && at < band.x + band.width && lo < band.y + band.height && hi > band.y
|
|
98185
|
+
);
|
|
98186
|
+
};
|
|
98187
|
+
const taken = (seat, mine) => scene.edges.some(
|
|
98188
|
+
(other) => other !== mine && other.pts.length >= 2 && [other.pts[0], other.pts[other.pts.length - 1]].some(
|
|
98189
|
+
(end) => Math.abs(end.x - seat.x) < MIN_ATTACH_GAP && Math.abs(end.y - seat.y) < MIN_ATTACH_GAP
|
|
98190
|
+
)
|
|
98191
|
+
);
|
|
98192
|
+
for (const edge of scene.edges) {
|
|
98193
|
+
if (!only.has(edge.id) || edge.pts.length < 3) continue;
|
|
98194
|
+
if (edge.pinned?.start || edge.pinned?.end) continue;
|
|
98195
|
+
const src = sideOf(edge.pts[0], leaves);
|
|
98196
|
+
const dst = sideOf(edge.pts[edge.pts.length - 1], leaves);
|
|
98197
|
+
if (!src || !dst || src.node === dst.node) continue;
|
|
98198
|
+
const alongY = src.side === "east" || src.side === "west";
|
|
98199
|
+
const rightwards = dst.node.x > src.node.x;
|
|
98200
|
+
const downwards = dst.node.y > src.node.y;
|
|
98201
|
+
const srcSide = alongY ? rightwards ? "east" : "west" : downwards ? "south" : "north";
|
|
98202
|
+
const dstSide = alongY ? rightwards ? "west" : "east" : downwards ? "north" : "south";
|
|
98203
|
+
const span = (node) => alongY ? [node.y + MIN_SIDE_INSET, node.y + node.height - MIN_SIDE_INSET] : [node.x + MIN_SIDE_INSET, node.x + node.width - MIN_SIDE_INSET];
|
|
98204
|
+
const [srcLo, srcHi] = span(src.node);
|
|
98205
|
+
const [dstLo, dstHi] = span(dst.node);
|
|
98206
|
+
const low = Math.max(srcLo, dstLo);
|
|
98207
|
+
const high = Math.min(srcHi, dstHi);
|
|
98208
|
+
if (low > high) continue;
|
|
98209
|
+
const at = Math.round((low + high) / 2);
|
|
98210
|
+
const seatOnFace = (node, side) => side === "north" ? { x: at, y: node.y } : side === "south" ? { x: at, y: node.y + node.height } : side === "west" ? { x: node.x, y: at } : { x: node.x + node.width, y: at };
|
|
98211
|
+
const from = seatOnFace(src.node, srcSide);
|
|
98212
|
+
const to = seatOnFace(dst.node, dstSide);
|
|
98213
|
+
const travel = alongY ? to.x - from.x : to.y - from.y;
|
|
98214
|
+
const outward = (side) => side === "east" || side === "south" ? 1 : -1;
|
|
98215
|
+
if (travel === 0 || Math.sign(travel) !== outward(srcSide)) continue;
|
|
98216
|
+
const lane = alongY ? [from.x, to.x] : [from.y, to.y];
|
|
98217
|
+
const runFrom = Math.min(lane[0], lane[1]);
|
|
98218
|
+
const runTo = Math.max(lane[0], lane[1]);
|
|
98219
|
+
if (ctx.runHitsNode(!alongY, at, runFrom, runTo)) continue;
|
|
98220
|
+
if (strikesTitle(at, runFrom, runTo, alongY)) continue;
|
|
98221
|
+
const others = ctx.runsExcept(edge.id);
|
|
98222
|
+
if (!ctx.runIsClear(alongY ? others.horizontal : others.vertical, at, runFrom, runTo)) continue;
|
|
98223
|
+
if (taken(from, edge) || taken(to, edge)) continue;
|
|
98224
|
+
edge.pts = [from, to];
|
|
98225
|
+
}
|
|
98226
|
+
}
|
|
98092
98227
|
function swapCrossingSiblingSeats(scene) {
|
|
98093
98228
|
const leaves = scene.nodes.filter((node) => !node.container);
|
|
98094
98229
|
const endsOf = (edge) => [
|
|
@@ -100712,7 +100847,178 @@ function stampLabelOffsets(edges, model) {
|
|
|
100712
100847
|
for (const label of edge.labels) label.offset = { dx: declared.dx, dy: declared.dy };
|
|
100713
100848
|
}
|
|
100714
100849
|
}
|
|
100850
|
+
var ON_BORDER = 1;
|
|
100715
100851
|
var pointOn = (point, box) => point.x >= box.x - 1 && point.x <= box.x + box.width + 1 && point.y >= box.y - 1 && point.y <= box.y + box.height + 1;
|
|
100852
|
+
function containerSizeBounds(scene, model) {
|
|
100853
|
+
const nodeById = new Map(scene.nodes.map((node) => [node.id, node]));
|
|
100854
|
+
const pad = containerPad(model);
|
|
100855
|
+
const bounds = /* @__PURE__ */ new Map();
|
|
100856
|
+
const walk = (elements) => {
|
|
100857
|
+
for (const element of elements) {
|
|
100858
|
+
walk(element.children);
|
|
100859
|
+
const node = nodeById.get(element.id);
|
|
100860
|
+
if (node?.container) bounds.set(element.id, contentFloor({ node, element, nodeById, pad }));
|
|
100861
|
+
}
|
|
100862
|
+
};
|
|
100863
|
+
walk(model.elements);
|
|
100864
|
+
return bounds;
|
|
100865
|
+
}
|
|
100866
|
+
var containerPad = (model) => model.style.containerPadding ?? (model.style.compact ? 7 : 9);
|
|
100867
|
+
var MIN_SIBLING_GAP = 20;
|
|
100868
|
+
var kidsOf = (box) => box.element.children.map((child) => box.nodeById.get(child.id)).filter((child) => !!child);
|
|
100869
|
+
function freeBands(box, axis) {
|
|
100870
|
+
const { node, pad } = box;
|
|
100871
|
+
const { min, size } = AXIS[axis];
|
|
100872
|
+
const spans = kidsOf(box).map((kid) => ({ start: kid[min], end: kid[min] + kid[size] })).sort((a, b) => a.start - b.start || a.end - b.end);
|
|
100873
|
+
const merged = [];
|
|
100874
|
+
for (const span of spans) {
|
|
100875
|
+
const last = merged[merged.length - 1];
|
|
100876
|
+
if (last && span.start <= last.end) last.end = Math.max(last.end, span.end);
|
|
100877
|
+
else merged.push({ ...span });
|
|
100878
|
+
}
|
|
100879
|
+
if (!merged.length) return [];
|
|
100880
|
+
const bands = [];
|
|
100881
|
+
const add = (start, end, keep) => {
|
|
100882
|
+
if (end - start > keep) bands.push({ end, spare: end - start - keep });
|
|
100883
|
+
};
|
|
100884
|
+
add(node[min], merged[0].start, pad);
|
|
100885
|
+
for (let i = 0; i + 1 < merged.length; i++)
|
|
100886
|
+
add(merged[i].end, merged[i + 1].start, MIN_SIBLING_GAP);
|
|
100887
|
+
add(merged[merged.length - 1].end, node[min] + node[size], pad);
|
|
100888
|
+
return bands;
|
|
100889
|
+
}
|
|
100890
|
+
var AXIS = {
|
|
100891
|
+
x: { min: "x", size: "width" },
|
|
100892
|
+
y: { min: "y", size: "height" }
|
|
100893
|
+
};
|
|
100894
|
+
function holdsChildren(box) {
|
|
100895
|
+
const { node, pad } = box;
|
|
100896
|
+
const kids = kidsOf(box);
|
|
100897
|
+
if (!kids.length) return { minWidth: pad * 2, minHeight: pad * 2 };
|
|
100898
|
+
return {
|
|
100899
|
+
minWidth: Math.max(...kids.map((kid) => kid.x + kid.width)) + pad - node.x,
|
|
100900
|
+
minHeight: Math.max(...kids.map((kid) => kid.y + kid.height)) + pad - node.y
|
|
100901
|
+
};
|
|
100902
|
+
}
|
|
100903
|
+
function contentFloor(box) {
|
|
100904
|
+
const spare = (axis) => freeBands(box, axis).reduce((total, band) => total + band.spare, 0);
|
|
100905
|
+
return { minWidth: box.node.width - spare("x"), minHeight: box.node.height - spare("y") };
|
|
100906
|
+
}
|
|
100907
|
+
function squeezeInside(box, axis, target) {
|
|
100908
|
+
const { min, size } = AXIS[axis];
|
|
100909
|
+
const need = box.node[size] - target;
|
|
100910
|
+
if (need <= 0) return;
|
|
100911
|
+
const bands = freeBands(box, axis);
|
|
100912
|
+
const spare = bands.reduce((total, band) => total + band.spare, 0);
|
|
100913
|
+
if (spare <= 0) return;
|
|
100914
|
+
const share = Math.min(1, need / spare);
|
|
100915
|
+
const shiftAt = (at) => bands.reduce((total, band) => at >= band.end ? total + band.spare * share : total, 0);
|
|
100916
|
+
for (const child of box.element.children) {
|
|
100917
|
+
const kid = box.nodeById.get(child.id);
|
|
100918
|
+
if (!kid) continue;
|
|
100919
|
+
const shift = shiftAt(kid[min]);
|
|
100920
|
+
if (!shift) continue;
|
|
100921
|
+
for (const id of subtreeIds(child)) {
|
|
100922
|
+
const inside = box.nodeById.get(id);
|
|
100923
|
+
if (inside) inside[min] -= shift;
|
|
100924
|
+
}
|
|
100925
|
+
}
|
|
100926
|
+
}
|
|
100927
|
+
function applyContainerSizes(scene, model) {
|
|
100928
|
+
const carried = /* @__PURE__ */ new Set();
|
|
100929
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
100930
|
+
const walk = (elements) => {
|
|
100931
|
+
for (const element of elements) {
|
|
100932
|
+
walk(element.children);
|
|
100933
|
+
if (element.size) wanted.set(element.id, element.size);
|
|
100934
|
+
}
|
|
100935
|
+
};
|
|
100936
|
+
walk(model.elements);
|
|
100937
|
+
if (!wanted.size) return carried;
|
|
100938
|
+
const nodeById = new Map(scene.nodes.map((node) => [node.id, node]));
|
|
100939
|
+
const pad = containerPad(model);
|
|
100940
|
+
const clampedSpans = /* @__PURE__ */ new Set();
|
|
100941
|
+
const applied = /* @__PURE__ */ new Map();
|
|
100942
|
+
const before = new Map(
|
|
100943
|
+
scene.nodes.map((node) => [
|
|
100944
|
+
node.id,
|
|
100945
|
+
{ x: node.x, y: node.y, width: node.width, height: node.height }
|
|
100946
|
+
])
|
|
100947
|
+
);
|
|
100948
|
+
for (const [id, spec] of wanted) {
|
|
100949
|
+
const node = nodeById.get(id);
|
|
100950
|
+
const element = model.index.get(id);
|
|
100951
|
+
if (!node || !element) continue;
|
|
100952
|
+
const box = { node, element, nodeById, pad };
|
|
100953
|
+
const floor = contentFloor(box);
|
|
100954
|
+
const width = Math.max(node.width + spec.dw, floor.minWidth);
|
|
100955
|
+
const height = Math.max(node.height + spec.dh, floor.minHeight);
|
|
100956
|
+
if (width !== node.width + spec.dw || height !== node.height + spec.dh)
|
|
100957
|
+
clampedSpans.add(spec.span);
|
|
100958
|
+
squeezeInside(box, "x", width);
|
|
100959
|
+
squeezeInside(box, "y", height);
|
|
100960
|
+
node.width = width;
|
|
100961
|
+
node.height = height;
|
|
100962
|
+
}
|
|
100963
|
+
const containWithin = (elements) => {
|
|
100964
|
+
for (const element of elements) {
|
|
100965
|
+
containWithin(element.children);
|
|
100966
|
+
const node = nodeById.get(element.id);
|
|
100967
|
+
if (!node?.container) continue;
|
|
100968
|
+
const holds = holdsChildren({ node, element, nodeById, pad });
|
|
100969
|
+
node.width = Math.max(node.width, holds.minWidth);
|
|
100970
|
+
node.height = Math.max(node.height, holds.minHeight);
|
|
100971
|
+
}
|
|
100972
|
+
};
|
|
100973
|
+
containWithin(model.elements);
|
|
100974
|
+
for (const id of wanted.keys()) {
|
|
100975
|
+
const node = nodeById.get(id);
|
|
100976
|
+
const was = before.get(id);
|
|
100977
|
+
if (node && was)
|
|
100978
|
+
applied.set(id, { dw: node.width - was.width, dh: node.height - was.height });
|
|
100979
|
+
}
|
|
100980
|
+
if (clampedSpans.size) scene.clampedSizes = clampedSpans;
|
|
100981
|
+
scene.appliedSizes = applied;
|
|
100982
|
+
const seats = [];
|
|
100983
|
+
for (const node of scene.nodes) {
|
|
100984
|
+
const was = before.get(node.id);
|
|
100985
|
+
if (!was) continue;
|
|
100986
|
+
const moved = {
|
|
100987
|
+
dx: node.x - was.x,
|
|
100988
|
+
dy: node.y - was.y,
|
|
100989
|
+
dw: node.width - was.width,
|
|
100990
|
+
dh: node.height - was.height
|
|
100991
|
+
};
|
|
100992
|
+
if (!moved.dx && !moved.dy && !moved.dw && !moved.dh) continue;
|
|
100993
|
+
seats.push({ box: was, ...moved });
|
|
100994
|
+
}
|
|
100995
|
+
seats.sort((a, b) => a.box.width * a.box.height - b.box.width * b.box.height);
|
|
100996
|
+
if (!seats.length) return carried;
|
|
100997
|
+
for (const edge of scene.edges) {
|
|
100998
|
+
if (edge.pts.length < 2) continue;
|
|
100999
|
+
for (const which of ["first", "last"]) {
|
|
101000
|
+
const terminal = edge.pts[which === "first" ? 0 : edge.pts.length - 1];
|
|
101001
|
+
const seat = seats.find((candidate) => pointOn(terminal, candidate.box));
|
|
101002
|
+
if (!seat) continue;
|
|
101003
|
+
const onRight = terminal.x >= seat.box.x + seat.box.width - ON_BORDER;
|
|
101004
|
+
const onBottom = terminal.y >= seat.box.y + seat.box.height - ON_BORDER;
|
|
101005
|
+
const delta = {
|
|
101006
|
+
dx: seat.dx + (onRight ? seat.dw : 0),
|
|
101007
|
+
dy: seat.dy + (onBottom ? seat.dh : 0)
|
|
101008
|
+
};
|
|
101009
|
+
if (!delta.dx && !delta.dy) continue;
|
|
101010
|
+
carried.add(edge.id);
|
|
101011
|
+
carryTerminal(edge, which, delta, {
|
|
101012
|
+
x: seat.box.x + seat.dx,
|
|
101013
|
+
y: seat.box.y + seat.dy,
|
|
101014
|
+
width: seat.box.width + seat.dw,
|
|
101015
|
+
height: seat.box.height + seat.dh
|
|
101016
|
+
});
|
|
101017
|
+
}
|
|
101018
|
+
}
|
|
101019
|
+
for (const edge of scene.edges) if (carried.has(edge.id)) dropRedundantPoints(edge.pts);
|
|
101020
|
+
return carried;
|
|
101021
|
+
}
|
|
100716
101022
|
function applyNodeOffsets(scene, offsetOf) {
|
|
100717
101023
|
const carried = /* @__PURE__ */ new Set();
|
|
100718
101024
|
if (!offsetOf.size) return carried;
|
|
@@ -100777,19 +101083,36 @@ function applyNodeOffsets(scene, offsetOf) {
|
|
|
100777
101083
|
const seat = seats.find((candidate) => pointOn(terminal, candidate.box));
|
|
100778
101084
|
if (!seat || !seat.delta.dx && !seat.delta.dy) continue;
|
|
100779
101085
|
carried.add(edge.id);
|
|
100780
|
-
|
|
100781
|
-
|
|
100782
|
-
|
|
100783
|
-
|
|
100784
|
-
|
|
100785
|
-
|
|
100786
|
-
const degenerate = Math.abs(elbow.x - terminal.x) < 0.5 && Math.abs(elbow.y - terminal.y) < 0.5 || Math.abs(elbow.x - neighbour.x) < 0.5 && Math.abs(elbow.y - neighbour.y) < 0.5;
|
|
100787
|
-
if (!degenerate) edge.pts.splice(end === 0 ? 1 : edge.pts.length - 1, 0, elbow);
|
|
101086
|
+
carryTerminal(edge, end === 0 ? "first" : "last", seat.delta, {
|
|
101087
|
+
x: seat.box.x + seat.delta.dx,
|
|
101088
|
+
y: seat.box.y + seat.delta.dy,
|
|
101089
|
+
width: seat.box.width,
|
|
101090
|
+
height: seat.box.height
|
|
101091
|
+
});
|
|
100788
101092
|
}
|
|
100789
101093
|
}
|
|
100790
101094
|
for (const edge of scene.edges) if (carried.has(edge.id)) dropRedundantPoints(edge.pts);
|
|
100791
101095
|
return carried;
|
|
100792
101096
|
}
|
|
101097
|
+
function carryTerminal(edge, which, delta, face) {
|
|
101098
|
+
const end = which === "first" ? 0 : edge.pts.length - 1;
|
|
101099
|
+
const terminal = edge.pts[end];
|
|
101100
|
+
const neighbour = edge.pts[which === "first" ? 1 : edge.pts.length - 2];
|
|
101101
|
+
const wasHorizontal = Math.abs(terminal.y - neighbour.y) < SEGMENT_EPSILON;
|
|
101102
|
+
terminal.x += delta.dx;
|
|
101103
|
+
terminal.y += delta.dy;
|
|
101104
|
+
const slide = wasHorizontal ? { along: "y", low: face.y, high: face.y + face.height } : { along: "x", low: face.x, high: face.x + face.width };
|
|
101105
|
+
const residual = neighbour[slide.along] - terminal[slide.along];
|
|
101106
|
+
const seat = Math.min(
|
|
101107
|
+
Math.max(neighbour[slide.along], slide.low + SEAT_INSET),
|
|
101108
|
+
Math.max(slide.high - SEAT_INSET, slide.low + SEAT_INSET)
|
|
101109
|
+
);
|
|
101110
|
+
if (Math.abs(residual) < ARROW_ROOM2 && Math.abs(seat - neighbour[slide.along]) < SEGMENT_EPSILON)
|
|
101111
|
+
terminal[slide.along] = seat;
|
|
101112
|
+
const elbow = wasHorizontal ? { x: neighbour.x, y: terminal.y } : { x: terminal.x, y: neighbour.y };
|
|
101113
|
+
const degenerate = Math.abs(elbow.x - terminal.x) < SEGMENT_EPSILON && Math.abs(elbow.y - terminal.y) < SEGMENT_EPSILON || Math.abs(elbow.x - neighbour.x) < SEGMENT_EPSILON && Math.abs(elbow.y - neighbour.y) < SEGMENT_EPSILON;
|
|
101114
|
+
if (!degenerate) edge.pts.splice(which === "first" ? 1 : edge.pts.length - 1, 0, elbow);
|
|
101115
|
+
}
|
|
100793
101116
|
function dropRedundantPoints(pts) {
|
|
100794
101117
|
const inLine = (a, b, c) => Math.abs(a.x - b.x) < SEGMENT_EPSILON && Math.abs(b.x - c.x) < SEGMENT_EPSILON || Math.abs(a.y - b.y) < SEGMENT_EPSILON && Math.abs(b.y - c.y) < SEGMENT_EPSILON;
|
|
100795
101118
|
for (let i = pts.length - 2; i >= 1; i--)
|
|
@@ -100919,6 +101242,37 @@ function shiftIntoCanvas(scene) {
|
|
|
100919
101242
|
scene.width += shiftX;
|
|
100920
101243
|
scene.height += shiftY;
|
|
100921
101244
|
}
|
|
101245
|
+
function sizeOverlaps(scene, model, overlap) {
|
|
101246
|
+
const sized = /* @__PURE__ */ new Map();
|
|
101247
|
+
for (const element of model.index.values())
|
|
101248
|
+
if (element.size) sized.set(element.id, element.size.span);
|
|
101249
|
+
if (!sized.size) return [];
|
|
101250
|
+
const family = /* @__PURE__ */ new Map();
|
|
101251
|
+
const walk = (elements, ancestors) => {
|
|
101252
|
+
for (const element of elements) {
|
|
101253
|
+
if (sized.has(element.id))
|
|
101254
|
+
family.set(element.id, /* @__PURE__ */ new Set([...ancestors, ...subtreeIds(element)]));
|
|
101255
|
+
walk(element.children, [...ancestors, element.id]);
|
|
101256
|
+
}
|
|
101257
|
+
};
|
|
101258
|
+
walk(model.elements, []);
|
|
101259
|
+
const diagnostics = [];
|
|
101260
|
+
for (const node of scene.nodes) {
|
|
101261
|
+
const span = sized.get(node.id);
|
|
101262
|
+
if (!span) continue;
|
|
101263
|
+
const kin = family.get(node.id);
|
|
101264
|
+
const struck = scene.nodes.find((other) => !kin.has(other.id) && overlap(node, other));
|
|
101265
|
+
if (!struck) continue;
|
|
101266
|
+
diagnostics.push({
|
|
101267
|
+
code: "W0572",
|
|
101268
|
+
severity: "warning",
|
|
101269
|
+
message: `\`size\` on \`${node.id}\` overlaps \`${struck.id}\``,
|
|
101270
|
+
span,
|
|
101271
|
+
help: "the resize is applied as written \u2014 make it smaller, or give the drawing room with `order:`"
|
|
101272
|
+
});
|
|
101273
|
+
}
|
|
101274
|
+
return diagnostics;
|
|
101275
|
+
}
|
|
100922
101276
|
function offsetDiagnostics(scene, model) {
|
|
100923
101277
|
const hints = [];
|
|
100924
101278
|
for (const span of scene.segmentHints?.stale ?? [])
|
|
@@ -100937,6 +101291,14 @@ function offsetDiagnostics(scene, model) {
|
|
|
100937
101291
|
span,
|
|
100938
101292
|
help: "the run carries a terminal, so it can only slide as far as the side that terminal sits on \u2014 pin the side with `ID.side`, or move the element instead"
|
|
100939
101293
|
});
|
|
101294
|
+
for (const span of scene.clampedSizes ?? [])
|
|
101295
|
+
hints.push({
|
|
101296
|
+
code: "W0575",
|
|
101297
|
+
severity: "warning",
|
|
101298
|
+
message: "`size` was limited to keep this container around its own children",
|
|
101299
|
+
span,
|
|
101300
|
+
help: "a container is drawn around what it holds, which is not negotiable \u2014 resize or move the children first if the box has to be smaller"
|
|
101301
|
+
});
|
|
100940
101302
|
const spans = /* @__PURE__ */ new Map();
|
|
100941
101303
|
const walk = (elements, inherited) => {
|
|
100942
101304
|
for (const element of elements) {
|
|
@@ -100947,8 +101309,9 @@ function offsetDiagnostics(scene, model) {
|
|
|
100947
101309
|
};
|
|
100948
101310
|
walk(model.elements);
|
|
100949
101311
|
for (const flow of model.flows) if (flow.labelOffset) spans.set(flow.id, flow.labelOffset.span);
|
|
100950
|
-
if (!spans.size) return hints;
|
|
100951
101312
|
const overlap = (a, b) => a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height;
|
|
101313
|
+
hints.push(...sizeOverlaps(scene, model, overlap));
|
|
101314
|
+
if (!spans.size) return hints;
|
|
100952
101315
|
const leaves = scene.nodes.filter((node) => !node.container);
|
|
100953
101316
|
const labels = scene.edges.flatMap((edge) => edge.labels);
|
|
100954
101317
|
const diagnostics = [...hints];
|
|
@@ -101426,9 +101789,11 @@ function refuseScenicRepairs(scene, before) {
|
|
|
101426
101789
|
}
|
|
101427
101790
|
}
|
|
101428
101791
|
function applyAuthorPositioning(scene, model) {
|
|
101792
|
+
const runsBefore = new Map(scene.edges.map((edge) => [edge.id, straightRuns(edge.pts).length]));
|
|
101793
|
+
const resized = applyContainerSizes(scene, model);
|
|
101429
101794
|
const offsets = offsetAssignment(model);
|
|
101430
|
-
if (offsets.size) {
|
|
101431
|
-
const carried = applyNodeOffsets(scene, offsets);
|
|
101795
|
+
if (offsets.size || resized.size) {
|
|
101796
|
+
const carried = /* @__PURE__ */ new Set([...resized, ...applyNodeOffsets(scene, offsets)]);
|
|
101432
101797
|
for (const edge of scene.edges) if (carried.has(edge.id)) edge.repairedFrom = void 0;
|
|
101433
101798
|
const titles = titleBoxesOf(scene, model);
|
|
101434
101799
|
reaimAfterOffsets(scene, titles, carried);
|
|
@@ -101444,6 +101809,12 @@ function applyAuthorPositioning(scene, model) {
|
|
|
101444
101809
|
refuseScenicRepairs(scene, before);
|
|
101445
101810
|
decoincideAfterOffsets(scene, titles, repairable);
|
|
101446
101811
|
}
|
|
101812
|
+
const bent = new Set(
|
|
101813
|
+
scene.edges.filter(
|
|
101814
|
+
(edge) => carried.has(edge.id) && straightRuns(edge.pts).length > (runsBefore.get(edge.id) ?? 0)
|
|
101815
|
+
).map((edge) => edge.id)
|
|
101816
|
+
);
|
|
101817
|
+
straightenCarriedRuns(scene, titles, bent);
|
|
101447
101818
|
}
|
|
101448
101819
|
const nudgedRuns = applySegmentOffsets(scene, model);
|
|
101449
101820
|
if (nudgedRuns) {
|
|
@@ -101452,7 +101823,7 @@ function applyAuthorPositioning(scene, model) {
|
|
|
101452
101823
|
edge.repairedFrom = void 0;
|
|
101453
101824
|
}
|
|
101454
101825
|
const nudgedLabels = scene.edges.some((edge) => edge.labels.some((label) => label.offset));
|
|
101455
|
-
if (!offsets.size && !nudgedRuns && !nudgedLabels) return;
|
|
101826
|
+
if (!offsets.size && !resized.size && !nudgedRuns && !nudgedLabels) return;
|
|
101456
101827
|
anchorFlowLabels(scene, titleBoxesOf(scene, model));
|
|
101457
101828
|
fitCanvas(scene);
|
|
101458
101829
|
}
|
|
@@ -103076,6 +103447,7 @@ function layoutBoxes(model, scene) {
|
|
|
103076
103447
|
declarations.set(element.id, {
|
|
103077
103448
|
line: element.kindSpan.line,
|
|
103078
103449
|
offsetSpan: element.offset?.span,
|
|
103450
|
+
sizeSpan: element.size?.span,
|
|
103079
103451
|
parent
|
|
103080
103452
|
});
|
|
103081
103453
|
walk(element.children, element.id);
|
|
@@ -103083,6 +103455,7 @@ function layoutBoxes(model, scene) {
|
|
|
103083
103455
|
};
|
|
103084
103456
|
walk(model.elements);
|
|
103085
103457
|
const flowById = new Map(model.flows.map((flow) => [flow.id, flow]));
|
|
103458
|
+
const sizeBounds = containerSizeBounds(scene, model);
|
|
103086
103459
|
const boxes = [];
|
|
103087
103460
|
const leaves = scene.nodes.filter((node) => !node.container);
|
|
103088
103461
|
for (const node of scene.nodes) {
|
|
@@ -103096,6 +103469,8 @@ function layoutBoxes(model, scene) {
|
|
|
103096
103469
|
width: node.width,
|
|
103097
103470
|
height: node.height,
|
|
103098
103471
|
container: node.container,
|
|
103472
|
+
resize: sizeBounds.get(node.id),
|
|
103473
|
+
sizeApplied: scene.appliedSizes?.get(node.id),
|
|
103099
103474
|
...declaration
|
|
103100
103475
|
});
|
|
103101
103476
|
}
|
|
@@ -103161,7 +103536,7 @@ function layoutBoxes(model, scene) {
|
|
|
103161
103536
|
}
|
|
103162
103537
|
|
|
103163
103538
|
// src/api.ts
|
|
103164
|
-
var version = true ? "1.0.0-
|
|
103539
|
+
var version = true ? "1.0.0-RC18" : pkg.version;
|
|
103165
103540
|
|
|
103166
103541
|
// src/playground.ts
|
|
103167
103542
|
var ElkClass = import_elk_bundled.default;
|
package/package.json
CHANGED