flowfig 0.11.0 → 0.12.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 +148 -21
- package/dist/atlas.js +6 -2
- package/dist/browser.d.ts +1 -0
- package/dist/browser.js +9 -4
- package/dist/check.d.ts +2 -2
- package/dist/check.js +135 -5
- package/dist/cli.js +174 -70
- package/dist/diff.d.ts +2 -0
- package/dist/diff.js +79 -24
- package/dist/element-looks.js +4 -0
- package/dist/element-world.js +6 -0
- package/dist/element.d.ts +27 -0
- package/dist/element.js +67 -0
- package/dist/explain.d.ts +45 -0
- package/dist/explain.js +161 -0
- package/dist/frames.d.ts +34 -0
- package/dist/frames.js +117 -0
- package/dist/index.js +283 -131
- package/dist/load.js +9 -4
- package/dist/looks.d.ts +15 -0
- package/dist/looks.js +32 -0
- package/dist/mcp.d.ts +4 -0
- package/dist/mcp.js +14 -6
- package/dist/model.d.ts +97 -0
- package/dist/model.js +182 -1
- package/dist/scene.d.ts +2 -0
- package/dist/source.d.ts +4 -2
- package/dist/source.js +15 -9
- package/dist/svg.d.ts +12 -0
- package/dist/svg.js +235 -75
- package/dist/verify.d.ts +6 -0
- package/dist/verify.js +104 -56
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/world-art.d.ts +59 -0
- package/dist/world-art.js +854 -0
- package/dist/world.d.ts +233 -0
- package/dist/world.js +1577 -0
- package/package.json +9 -3
package/dist/svg.js
CHANGED
|
@@ -6,7 +6,7 @@ import { checkRendered, planFor } from './check.js';
|
|
|
6
6
|
import { autoLayout, rowBreaks } from './auto.js';
|
|
7
7
|
export { parseSource, links } from './source.js';
|
|
8
8
|
export { diff, formatDiff, mergeFigures } from './diff.js';
|
|
9
|
-
import { ASYNC_TAG_W, BASE_RATE, CARD_LINE, CARD_PAD, CARD_WIDTH, DARK, EDGE_OFF, EDGE_ON, LIGHT, TONES, toneFill, toneTint, labelPillW, ON_ACCENT, str, edgeId, groupGap, isGroup, isRows, LANE_PAD, LANE_ROW_GAP, LANE_BLOCK_GAP, FRAME_SIDE, laneGutter as gutterOf, STUB_ROOM, laneBlock, laneEnd, tightCopies, LABEL_LINE, itemWidth, labelLines, fitCap, diamondLines, diamondRoom, isLanesLayout, timelineBeats, timelineLayout, TL_AXIS_H, TL_AXIS_W, TL_BAR_H, TL_ROW_GAP, toBeat, altText,
|
|
9
|
+
import { ASYNC_TAG_W, BASE_RATE, CARD_LINE, CARD_PAD, CARD_WIDTH, DARK, EDGE_OFF, EDGE_ON, LIGHT, TONES, toneFill, toneTint, labelPillW, ON_ACCENT, str, edgeId, groupGap, isGroup, isRows, LANE_PAD, LANE_ROW_GAP, LANE_BLOCK_GAP, FRAME_SIDE, laneGutter as gutterOf, STUB_ROOM, laneBlock, laneEnd, tightCopies, LABEL_LINE, itemWidth, labelLines, fitCap, diamondLines, diamondRoom, isLanesLayout, timelineBeats, timelineLayout, TL_AXIS_H, TL_AXIS_W, TL_BAR_H, TL_ROW_GAP, toBeat, altText, beatTimes, n2, STEP_HOLD_MS, playheadItem, loopStartItem, dateLabelRaised, labelSpan, outsideLabelRect, edgeTip, scenes, tourPlan, flowOrder, OPEN_MS, } from './model.js';
|
|
10
10
|
export { LIGHT, DARK } from './model.js';
|
|
11
11
|
const LINE = CARD_LINE, CARD_SIDE = 8, ROW_GAP = 4;
|
|
12
12
|
const SUB_LINE = 15;
|
|
@@ -20,7 +20,6 @@ const color = (t, k) => {
|
|
|
20
20
|
const vars = (t, cardOn) => `--accent:${color(t, 'accent')}; --fg:${color(t, 'fg')}; --muted:${color(t, 'muted')}; --bg:${color(t, 'bg')}; --surface:${color(t, 'surface')}; --border:${color(t, 'border')}; --card-on:${cardOn}; --tint:color-mix(in srgb, var(--accent) 10%, var(--surface));`;
|
|
21
21
|
const content = (c) => (isRows(c) ? c : str(c));
|
|
22
22
|
const esc = (s) => s.replace(/[<>&"]/g, (c) => ({ '<': '<', '>': '>', '&': '&', '"': '"' })[c]);
|
|
23
|
-
const n2 = (v) => Math.round(v * 10) / 10;
|
|
24
23
|
// Four decimals: one decimal rounds a 0.7 s hop away and the packet jumps.
|
|
25
24
|
const n4 = (v) => Math.round(v * 10000) / 10000;
|
|
26
25
|
const pct = (v) => Math.round(v * 10000) / 100 + '%';
|
|
@@ -166,6 +165,7 @@ function place(item, x, y, s, out) {
|
|
|
166
165
|
}
|
|
167
166
|
}
|
|
168
167
|
const MARK_COLOR = { added: TONES.green, removed: TONES.red, changed: TONES.orange };
|
|
168
|
+
const tagged = (op, id, inner) => op ? `<g data-diff="${op}" data-id="${esc(id)}">${inner}</g>` : inner;
|
|
169
169
|
const marked = (mark, inner) => mark === 'removed' ? `<g opacity="0.5" stroke-dasharray="5 4">${inner}</g>` : mark ? `<g stroke-width="2">${inner}</g>` : inner;
|
|
170
170
|
function legendOf(m) {
|
|
171
171
|
const count = (kind) => Object.values(m.boxes).filter((k) => k === kind).length + Object.values(m.edges).filter((k) => k === kind).length;
|
|
@@ -183,7 +183,9 @@ function legendSvg(items, W, H) {
|
|
|
183
183
|
let x = (W - legendWidth(items)) / 2;
|
|
184
184
|
return items
|
|
185
185
|
.map((i, k) => {
|
|
186
|
-
const sq = i.color === 'none'
|
|
186
|
+
const sq = i.color === 'none'
|
|
187
|
+
? ''
|
|
188
|
+
: `<rect x="${n2(x)}" y="${n2(H - 24)}" width="10" height="10" rx="2" fill="${i.color}" data-diff="${i.text.split(' ')[0]}"/>`;
|
|
187
189
|
const out = `${sq}<text x="${n2(x + (i.color === 'none' ? 0 : 16))}" y="${n2(H - 15)}" font-size="12" fill="var(--muted)">${esc(i.text)}</text>\n`;
|
|
188
190
|
x += widths[k] + 20;
|
|
189
191
|
return out;
|
|
@@ -191,10 +193,10 @@ function legendSvg(items, W, H) {
|
|
|
191
193
|
.join('');
|
|
192
194
|
}
|
|
193
195
|
const SYSTEM_FONT = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif";
|
|
194
|
-
|
|
195
|
-
export function render(spec, opts = {}) {
|
|
196
|
+
function draw(spec, opts, at) {
|
|
196
197
|
const fig = autoLayout(spec);
|
|
197
|
-
const
|
|
198
|
+
const sc = opts.scope ?? '';
|
|
199
|
+
const pre = at?.prefix ?? sc;
|
|
198
200
|
const pad = opts.padding ?? 24;
|
|
199
201
|
const tl = fig.timeline && isLanesLayout(fig.layout) ? timelineLayout(fig, TL_AXIS_W) : null;
|
|
200
202
|
const synthetic = tl != null && !fig.steps?.length && !opts.marks;
|
|
@@ -323,12 +325,10 @@ export function render(spec, opts = {}) {
|
|
|
323
325
|
const segs = [];
|
|
324
326
|
const hops = [];
|
|
325
327
|
const edgeOf = Object.fromEntries(fig.edges.map((e, i) => [ids[i], e]));
|
|
326
|
-
|
|
328
|
+
const times = at?.times ?? beatTimes(beats, opts.speed ?? fig.speed ?? 900);
|
|
327
329
|
beats.forEach((stepBeats, si) => {
|
|
328
330
|
stepBeats.forEach((b, bi) => {
|
|
329
|
-
const
|
|
330
|
-
const dur = beatMs(b, opts.speed ?? fig.speed ?? 900) / 1000 / BASE_RATE;
|
|
331
|
-
const hold = last ? STEP_HOLD_MS / 1000 / BASE_RATE : 0;
|
|
331
|
+
const { t0, t1, tEnd } = times.beats[si][bi];
|
|
332
332
|
b.hops.forEach((h, lane) => {
|
|
333
333
|
if (byId[h.edge])
|
|
334
334
|
hops.push({
|
|
@@ -340,16 +340,15 @@ export function render(spec, opts = {}) {
|
|
|
340
340
|
back: h.back,
|
|
341
341
|
data: h.data,
|
|
342
342
|
tone: h.tone && TONES[h.tone],
|
|
343
|
-
t0
|
|
344
|
-
t1
|
|
345
|
-
tEnd
|
|
343
|
+
t0,
|
|
344
|
+
t1,
|
|
345
|
+
tEnd,
|
|
346
346
|
});
|
|
347
347
|
});
|
|
348
|
-
segs.push({ t0
|
|
349
|
-
t += dur + hold;
|
|
348
|
+
segs.push({ t0, t1: bi === stepBeats.length - 1 ? times.ends[si] : tEnd, si, bi });
|
|
350
349
|
});
|
|
351
350
|
});
|
|
352
|
-
const total =
|
|
351
|
+
const total = at?.total ?? (times.ends.at(-1) || 1);
|
|
353
352
|
const shownAt = segs.map(({ si, bi }) => Object.assign({}, ...beats[si].slice(0, bi + 1).map((b) => b.show)));
|
|
354
353
|
const litEdges = segs.map(({ si, bi }) => new Set(beats[si].slice(0, bi + 1).flatMap((b) => b.hops.map((h) => h.edge))));
|
|
355
354
|
const litNodes = segs.map((seg, i) => {
|
|
@@ -387,7 +386,7 @@ export function render(spec, opts = {}) {
|
|
|
387
386
|
return '';
|
|
388
387
|
const key = prefix + on.map((x) => (x ? 1 : 0)).join('');
|
|
389
388
|
if (!seen.has(key)) {
|
|
390
|
-
const name =
|
|
389
|
+
const name = `${pre}a${seen.size}`;
|
|
391
390
|
seen.set(key, name);
|
|
392
391
|
const frames = runs(on.map((x) => (x ? onCss : offCss)));
|
|
393
392
|
css.push(`@keyframes ${name} { ${frames} }\n.${name} { animation: ${name} ${n2(total)}s infinite step-end; }`);
|
|
@@ -404,7 +403,7 @@ export function render(spec, opts = {}) {
|
|
|
404
403
|
return '';
|
|
405
404
|
const key = prefix + values.join('|');
|
|
406
405
|
if (!seen.has(key)) {
|
|
407
|
-
const name =
|
|
406
|
+
const name = `${pre}a${seen.size}`;
|
|
408
407
|
seen.set(key, name);
|
|
409
408
|
const kf = runs(values);
|
|
410
409
|
css.push(`@keyframes ${name} { ${kf} }\n.${name} { animation: ${name} ${n2(total)}s infinite step-end; }`);
|
|
@@ -468,7 +467,7 @@ export function render(spec, opts = {}) {
|
|
|
468
467
|
const kf = merged.map((f) => `${pct(f.a / total)},${pct(f.b / total - 0.0001)} { ${f.css} }`);
|
|
469
468
|
const key = (shape ? 'B' : 'R') + kf.join(' ');
|
|
470
469
|
if (!seen.has(key)) {
|
|
471
|
-
const name =
|
|
470
|
+
const name = `${pre}a${seen.size}`;
|
|
472
471
|
seen.set(key, name);
|
|
473
472
|
css.push(`@keyframes ${name} { ${kf.join(' ')} }\n.${name} { animation: ${name} ${n2(total)}s infinite linear; }`);
|
|
474
473
|
}
|
|
@@ -486,9 +485,11 @@ export function render(spec, opts = {}) {
|
|
|
486
485
|
}
|
|
487
486
|
const diffMark = opts.marks?.boxes[item.id];
|
|
488
487
|
const href = opts.links?.[item.id];
|
|
488
|
+
const fade = at?.fades?.get(item.id);
|
|
489
489
|
const tip = (inner) => {
|
|
490
|
-
const body = marked(diffMark, item.source ? `<g><title>${esc(item.source)}</title>${inner}</g>` : inner);
|
|
491
|
-
|
|
490
|
+
const body = tagged(diffMark, item.id, marked(diffMark, item.source ? `<g><title>${esc(item.source)}</title>${inner}</g>` : inner));
|
|
491
|
+
const linked = href == null ? body : `<a href="${esc(href)}" tabindex="-1">${body}</a>`;
|
|
492
|
+
return fade ? `<g class="${fade}">${linked}</g>` : linked;
|
|
492
493
|
};
|
|
493
494
|
const bt = diffMark ? MARK_COLOR[diffMark] : item.tone && TONES[item.tone];
|
|
494
495
|
const stroke = cls(boxAnim(item.id, true, bt));
|
|
@@ -505,6 +506,7 @@ export function render(spec, opts = {}) {
|
|
|
505
506
|
return tip(shape + `<text x="${n2(tx)}" y="${n2(cy + 4.5)}" class="bar">${esc(label)}</text>`);
|
|
506
507
|
}
|
|
507
508
|
const rim = item.shape === 'store' ? cls(boxAnim(item.id, false, bt)) : '';
|
|
509
|
+
const port = at?.ports.has(item.id);
|
|
508
510
|
const fill0 = bt ? toneTint(bt, 'var(--bg)') : 'var(--bg)';
|
|
509
511
|
const stroke0 = bt ?? 'var(--border)';
|
|
510
512
|
const cx = p.x + p.w / 2;
|
|
@@ -517,11 +519,16 @@ export function render(spec, opts = {}) {
|
|
|
517
519
|
: item.shape === 'store'
|
|
518
520
|
? `<path d="M${n2(p.x)} ${n2(p.y + 12)} a ${n2(p.w / 2)} 12 0 0 1 ${n2(p.w)} 0 v ${n2(p.h - 24)} a ${n2(p.w / 2)} 12 0 0 1 ${n2(-p.w)} 0 z" fill="${fill0}" stroke="${stroke0}"${stroke}/>` +
|
|
519
521
|
`<path d="M${n2(p.x)} ${n2(p.y + 12)} a ${n2(p.w / 2)} 12 0 0 0 ${n2(p.w)} 0" fill="none" stroke="${stroke0}"${rim}/>`
|
|
520
|
-
: `<rect x="${n2(p.x)}" y="${n2(p.y)}" width="${n2(p.w)}" height="${n2(p.h)}" rx="10" fill="${fill0}" stroke="${stroke0}"${stroke}/>`;
|
|
522
|
+
: `<rect x="${n2(p.x)}" y="${n2(p.y)}" width="${n2(p.w)}" height="${n2(p.h)}" rx="10" fill="${fill0}" stroke="${stroke0}"${port ? ' stroke-dasharray="4 3"' : ''}${stroke}/>`;
|
|
523
|
+
const opens = at?.owners.has(item.id) && item.shape !== 'decision'
|
|
524
|
+
? `<rect x="${n2(p.x + 3)}" y="${n2(p.y + 3)}" width="${n2(p.w)}" height="${n2(p.h)}" rx="10" fill="var(--bg)" stroke="var(--border)"/>`
|
|
525
|
+
: '';
|
|
521
526
|
const lines = labelLines(item, p.w);
|
|
522
527
|
const extra = (lines.length - 1) * LABEL_LINE;
|
|
523
528
|
const firstY = item.shape === 'store' || contents || dl ? labelY : labelY - extra / 2;
|
|
524
|
-
const label = lines
|
|
529
|
+
const label = lines
|
|
530
|
+
.map((l, i) => `<text x="${n2(cx)}" y="${n2(firstY + i * LABEL_LINE)}"${cls('label', port && 'muted')}>${esc(l)}</text>`)
|
|
531
|
+
.join('');
|
|
525
532
|
const sub = dl
|
|
526
533
|
? dl.subs.map((l) => `<text x="${n2(cx)}" y="${n2(p.y + p.h / 2 + l.dy)}" class="sub">${esc(l.text)}</text>`).join('')
|
|
527
534
|
: item.sub
|
|
@@ -536,7 +543,7 @@ export function render(spec, opts = {}) {
|
|
|
536
543
|
: item.mark === 'end'
|
|
537
544
|
? `<circle cx="${mx}" cy="${my}" r="6.25" fill="none" stroke="${dot}" stroke-width="1.5"/><circle cx="${mx}" cy="${my}" r="4" fill="${dot}"/>`
|
|
538
545
|
: '';
|
|
539
|
-
return tip(shape + label + sub + mark + (contents ? card(p, cardTop, contents) : ''));
|
|
546
|
+
return tip(opens + shape + label + sub + mark + (contents ? card(p, cardTop, contents) : ''));
|
|
540
547
|
});
|
|
541
548
|
function card(p, top, contents) {
|
|
542
549
|
const id = p.item.id;
|
|
@@ -607,9 +614,9 @@ export function render(spec, opts = {}) {
|
|
|
607
614
|
const tip = edgeTip(r.id, e.source, beats.flat());
|
|
608
615
|
const path0 = r.stub
|
|
609
616
|
? r.stub.parts
|
|
610
|
-
.map((d) => `<path d="${d}" fill="none" stroke="${line}" stroke-width="${EDGE_OFF}" marker-end="url(
|
|
611
|
-
.join('') + `<path id="p-${esc(r.id)}" d="${r.d}" fill="none" stroke="none"/>`
|
|
612
|
-
: `<path id="p-${esc(r.id)}" d="${r.d}" fill="none" stroke="${line}" stroke-width="${EDGE_OFF}" marker-end="url(
|
|
617
|
+
.map((d) => `<path d="${d}" fill="none" stroke="${line}" stroke-width="${EDGE_OFF}" marker-end="url(#${sc}arrow)"${lit}/>`)
|
|
618
|
+
.join('') + `<path id="${pre}p-${esc(r.id)}" d="${r.d}" fill="none" stroke="none"/>`
|
|
619
|
+
: `<path id="${pre}p-${esc(r.id)}" d="${r.d}" fill="none" stroke="${line}" stroke-width="${EDGE_OFF}" marker-end="url(#${sc}arrow)"${lit}/>`;
|
|
613
620
|
const path = tip ? `<g><title>${esc(tip)}</title>${path0}</g>` : path0;
|
|
614
621
|
const pillColor = em ? MARK_COLOR[em] : undefined;
|
|
615
622
|
const pill = (x, y, lw, text) => `<rect x="${n2(x - lw / 2)}" y="${n2(y - 9)}" width="${n2(lw)}" height="18" rx="9" fill="${pillColor ? toneTint(pillColor, 'var(--bg)', 14) : 'var(--bg)'}" stroke="${pillColor ?? 'var(--border)'}"` +
|
|
@@ -629,11 +636,11 @@ export function render(spec, opts = {}) {
|
|
|
629
636
|
fonts.push(11);
|
|
630
637
|
label = pill(r.mid.x, r.mid.y, lw, str(e.label));
|
|
631
638
|
}
|
|
632
|
-
return marked(em === 'removed' ? em : undefined, hidden ? `<g opacity="0"${shown}>${path}${label}</g>` : path + label);
|
|
639
|
+
return tagged(em, r.id, marked(em === 'removed' ? em : undefined, hidden ? `<g opacity="0"${shown}>${path}${label}</g>` : path + label));
|
|
633
640
|
});
|
|
634
641
|
const packets = hops.map((h, i) => {
|
|
635
|
-
const t0 = h.t0 / total, t1 = h.t1 / total, tEnd = h.tEnd / total;
|
|
636
|
-
const name =
|
|
642
|
+
const t0 = h.t0 / total, t1 = Math.min(h.t1 / total, 1), tEnd = h.tEnd / total, reach = n4((t1 - t0) / (h.t1 / total - t0));
|
|
643
|
+
const name = `${pre}p${i}`;
|
|
637
644
|
css.push(`@keyframes ${name} { 0%,${pct(t0)} { opacity: 0 } ${pct(t0 + 0.0001)},${pct(tEnd - 0.0001)} { opacity: 1 } ${pct(tEnd)},100% { opacity: 0 } }\n` +
|
|
638
645
|
`.${name} { animation: ${name} ${n2(total)}s infinite step-end; }`);
|
|
639
646
|
const chip = (() => {
|
|
@@ -648,8 +655,8 @@ export function render(spec, opts = {}) {
|
|
|
648
655
|
lines.map((l, li) => `<text x="0" y="${n2(-boxH - 12 + 15 * li + 15)}" class="chip">${esc(l)}</text>`).join(''));
|
|
649
656
|
})();
|
|
650
657
|
return (`<g${cls(name)} opacity="0"><circle r="10" fill="${h.tone ?? 'var(--accent)'}" opacity="0.2"/><circle r="4.5" fill="${h.tone ?? 'var(--accent)'}"/>${chip}` +
|
|
651
|
-
`<animateMotion dur="${n2(total)}s" repeatCount="indefinite" keyTimes="0;${n4(t0)};${n4(t1)};1" keyPoints="${h.back ?
|
|
652
|
-
`<mpath href="
|
|
658
|
+
`<animateMotion dur="${n2(total)}s" repeatCount="indefinite" keyTimes="0;${n4(t0)};${n4(t1)};1" keyPoints="${h.back ? `1;1;${n4(1 - reach)};${n4(1 - reach)}` : `0;0;${reach};${reach}`}" calcMode="linear">` +
|
|
659
|
+
`<mpath href="#${pre}p-${esc(h.edge)}" xlink:href="#${pre}p-${esc(h.edge)}"/></animateMotion></g>`);
|
|
653
660
|
});
|
|
654
661
|
let axisSvg = '', lineSvg = '', todaySvg = '';
|
|
655
662
|
if (tl) {
|
|
@@ -701,7 +708,7 @@ export function render(spec, opts = {}) {
|
|
|
701
708
|
at = home;
|
|
702
709
|
}
|
|
703
710
|
});
|
|
704
|
-
const name =
|
|
711
|
+
const name = `${pre}a${seen.size}`;
|
|
705
712
|
seen.set('playhead', name);
|
|
706
713
|
const kf = pts.map(([t, x]) => `${pct(t / total)} { transform: translateX(${n2(x - home)}px) }`).join(' ');
|
|
707
714
|
css.push(`@keyframes ${name} { ${kf} }\n.${name} { animation: ${name} ${n2(total)}s infinite linear; }`);
|
|
@@ -710,7 +717,7 @@ export function render(spec, opts = {}) {
|
|
|
710
717
|
const on = dates.map((x) => x.d === d);
|
|
711
718
|
const key = `d${d}${on.map((x) => (x ? 1 : 0)).join('')}`;
|
|
712
719
|
if (!seen.has(key)) {
|
|
713
|
-
const nm =
|
|
720
|
+
const nm = `${pre}a${seen.size}`;
|
|
714
721
|
seen.set(key, nm);
|
|
715
722
|
const fr = dates
|
|
716
723
|
.map((x) => `${pct(x.a / total)},${pct(x.b / total - 0.0001)} { opacity: ${x.d === d ? 1 : 0} } ${pct(x.b / total)} { opacity: 0 }`)
|
|
@@ -743,17 +750,20 @@ export function render(spec, opts = {}) {
|
|
|
743
750
|
const H = (only ? pad + rail.height + capH : mapH + (rail ? RAIL.gap + rail.height : 0) + capH) + legendH;
|
|
744
751
|
const shift = (W - (bounds.w + pad * 2 + arcL + arcR)) / 2 + arcL;
|
|
745
752
|
const railX = rail ? (W - rail.width) / 2 : 0;
|
|
753
|
+
const still = (on) => !!at?.fades && on.every(Boolean);
|
|
746
754
|
const labels = rail || synthetic
|
|
747
755
|
? []
|
|
748
756
|
: steps.map((_, si) => {
|
|
749
757
|
const on = segs.map((s) => s.si === si);
|
|
750
|
-
return `<text x="${n2(W / 2)}" y="${n2(H - capH + 16)}" opacity="0"${cls('steplabel', anim(on, 'opacity: 1', 'opacity: 0', 's'))}>${esc(str(steps[si].label))}</text>`;
|
|
758
|
+
return `<text x="${n2(W / 2)}" y="${n2(H - legendH - capH + 16)}"${still(on) ? '' : ' opacity="0"'}${cls('steplabel', !still(on) && anim(on, 'opacity: 1', 'opacity: 0', 's'))}>${esc(str(steps[si].label))}</text>`;
|
|
751
759
|
});
|
|
752
760
|
const said = [...new Set(captions)].map((text) => {
|
|
753
761
|
const on = captions.map((c) => c === text);
|
|
754
762
|
const lines = wrap(text, wrapW, 13.5);
|
|
755
|
-
return (`<g opacity="0"${cls(anim(on, 'opacity: 1', 'opacity: 0', 'y'))}>` +
|
|
756
|
-
lines
|
|
763
|
+
return (`<g${still(on) ? '' : ` opacity="0"${cls(anim(on, 'opacity: 1', 'opacity: 0', 'y'))}`}>` +
|
|
764
|
+
lines
|
|
765
|
+
.map((l, li) => `<text x="${n2(W / 2)}" y="${n2(H - legendH - capH + capTop + 16 + li * 20)}" class="caption">${esc(l)}</text>`)
|
|
766
|
+
.join('') +
|
|
757
767
|
`</g>`);
|
|
758
768
|
});
|
|
759
769
|
const railSvg = !rail
|
|
@@ -822,47 +832,15 @@ export function render(spec, opts = {}) {
|
|
|
822
832
|
: '';
|
|
823
833
|
const tip = edgeTip(row.edge, fig.edges[ids.indexOf(row.edge)].source, beats.flat());
|
|
824
834
|
parts.push(`<g${cls('railrow', moveRow(i))}>${tip ? `<title>${esc(tip)}</title>` : ''}<g${cls(anim(next, 'opacity: .45', 'opacity: 1', 'r'))}>` +
|
|
825
|
-
`<path d="M ${n2(x1)} ${n2(ly)} H ${n2(x2)}" fill="none" stroke="var(--muted)" stroke-width="${EDGE_OFF}"${row.async ? ' stroke-dasharray="4 3"' : ''} marker-end="url(
|
|
835
|
+
`<path d="M ${n2(x1)} ${n2(ly)} H ${n2(x2)}" fill="none" stroke="var(--muted)" stroke-width="${EDGE_OFF}"${row.async ? ' stroke-dasharray="4 3"' : ''} marker-end="url(#${sc}arrow)"${cls(lit)}/>` +
|
|
826
836
|
tag +
|
|
827
837
|
pill +
|
|
828
838
|
`</g></g>`);
|
|
829
839
|
});
|
|
830
840
|
return `<g transform="translate(${n2(railX)} 0)">${parts.join('')}</g>`;
|
|
831
841
|
})();
|
|
832
|
-
const { font, ...t0 } = { ...LIGHT, ...fig.theme, ...opts.theme };
|
|
833
|
-
const alt = altText(fig);
|
|
834
842
|
const holdAt = beats[0]?.length ? n2(segs[beats[0].length - 1].t1 - 0.1) : 0;
|
|
835
|
-
const
|
|
836
|
-
<title>${esc(alt.title)}</title>
|
|
837
|
-
<desc>${esc(alt.desc)}</desc>
|
|
838
|
-
<style>
|
|
839
|
-
svg { ${vars(t0, '#eef5fd')} }
|
|
840
|
-
@media (prefers-color-scheme: dark) { svg { ${vars(DARK, '#1d2733')} } }
|
|
841
|
-
.label { fill: var(--fg); font-size: 14px; font-weight: 500; text-anchor: middle; }
|
|
842
|
-
.sub { fill: var(--muted); font-size: 12px; text-anchor: middle; }
|
|
843
|
-
.frame { fill: var(--muted); font-size: 11px; font-weight: 600; letter-spacing: .04em; }
|
|
844
|
-
.edgelabel { fill: var(--muted); font-size: 11px; text-anchor: middle; font-family: ui-monospace, Menlo, monospace; }
|
|
845
|
-
.row { fill: var(--fg); font-size: 11px; }
|
|
846
|
-
.row.mono { font-size: 10.5px; font-family: ui-monospace, Menlo, monospace; }
|
|
847
|
-
.muted { fill: var(--muted); }
|
|
848
|
-
.bar { fill: var(--fg); font-size: 13px; font-weight: 500; }
|
|
849
|
-
.tick { fill: var(--muted); font-size: 11px; }
|
|
850
|
-
.today { fill: var(--accent); font-size: 11px; font-weight: 600; }
|
|
851
|
-
.tag { font-size: 9px; font-weight: 600; letter-spacing: .03em; text-anchor: middle; }
|
|
852
|
-
.mark { fill: var(--accent); font-size: 11px; font-weight: 600; text-anchor: end; }
|
|
853
|
-
.chip { fill: ${ON_ACCENT}; font-size: 11.5px; text-anchor: middle; }
|
|
854
|
-
.steplabel { fill: var(--fg); font-size: 13px; font-weight: 600; text-anchor: middle; font-family: ui-monospace, Menlo, monospace; }
|
|
855
|
-
.caption { fill: var(--muted); font-size: 13.5px; text-anchor: middle; }
|
|
856
|
-
.railcol { fill: var(--fg); font-size: 12px; font-weight: 500; text-anchor: middle; }
|
|
857
|
-
.railphase { fill: var(--fg); font-size: 13px; font-weight: 600; font-family: ui-monospace, Menlo, monospace; }
|
|
858
|
-
.railphase.muted { fill: var(--muted); }
|
|
859
|
-
.end { text-anchor: end; }
|
|
860
|
-
${css.join('\n')}
|
|
861
|
-
@media (prefers-reduced-motion: reduce) { * { animation-play-state: paused !important; animation-delay: -${holdAt}s !important; } }
|
|
862
|
-
</style>
|
|
863
|
-
<defs><marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M 0 1 L 9 5 L 0 9 z" fill="context-stroke"/></marker></defs>
|
|
864
|
-
<rect width="100%" height="100%" fill="var(--bg)"/>
|
|
865
|
-
${only
|
|
843
|
+
const body = `${only
|
|
866
844
|
? ''
|
|
867
845
|
: `<g transform="translate(${n2(shift)} ${arcT})">
|
|
868
846
|
${axisSvg}
|
|
@@ -876,8 +854,7 @@ ${todaySvg}
|
|
|
876
854
|
`}${railSvg}
|
|
877
855
|
${labels.join('\n')}
|
|
878
856
|
${said.join('\n')}
|
|
879
|
-
${legendSvg(legend, W, H)}
|
|
880
|
-
`;
|
|
857
|
+
${legendSvg(legend, W, H)}`;
|
|
881
858
|
if (rail)
|
|
882
859
|
fonts.push(12, 13);
|
|
883
860
|
if (legend.length)
|
|
@@ -927,7 +904,189 @@ ${legendSvg(legend, W, H)}</svg>
|
|
|
927
904
|
lanes: placed.filter((p) => p.lane).map((p) => ({ id: str(p.item.label), rect: { x: p.x, y: p.y, w: p.w, h: p.h } })),
|
|
928
905
|
}),
|
|
929
906
|
};
|
|
930
|
-
|
|
907
|
+
const frame = { x: shift - arcL, w: bounds.w + pad * 2 + arcL + arcR, h: mapH };
|
|
908
|
+
return { W, H, css: css.join('\n'), body, holdAt, scene, fig, frame };
|
|
909
|
+
}
|
|
910
|
+
const page = (fig, opts, W, H, css, holdAt, body) => {
|
|
911
|
+
const { font, ...t0 } = { ...LIGHT, ...fig.theme, ...opts.theme };
|
|
912
|
+
const alt = altText(fig);
|
|
913
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${n2(W)}" height="${n2(H)}" viewBox="0 0 ${n2(W)} ${n2(H)}" font-family="${esc(font ?? SYSTEM_FONT)}" role="img">
|
|
914
|
+
<title>${esc(alt.title)}</title>
|
|
915
|
+
<desc>${esc(alt.desc)}</desc>
|
|
916
|
+
<style>
|
|
917
|
+
svg { ${vars(t0, '#eef5fd')} }
|
|
918
|
+
@media (prefers-color-scheme: dark) { svg { ${vars(DARK, '#1d2733')} } }
|
|
919
|
+
.label { fill: var(--fg); font-size: 14px; font-weight: 500; text-anchor: middle; }
|
|
920
|
+
.sub { fill: var(--muted); font-size: 12px; text-anchor: middle; }
|
|
921
|
+
.frame { fill: var(--muted); font-size: 11px; font-weight: 600; letter-spacing: .04em; }
|
|
922
|
+
.edgelabel { fill: var(--muted); font-size: 11px; text-anchor: middle; font-family: ui-monospace, Menlo, monospace; }
|
|
923
|
+
.row { fill: var(--fg); font-size: 11px; }
|
|
924
|
+
.row.mono { font-size: 10.5px; font-family: ui-monospace, Menlo, monospace; }
|
|
925
|
+
.muted { fill: var(--muted); }
|
|
926
|
+
.bar { fill: var(--fg); font-size: 13px; font-weight: 500; }
|
|
927
|
+
.tick { fill: var(--muted); font-size: 11px; }
|
|
928
|
+
.today { fill: var(--accent); font-size: 11px; font-weight: 600; }
|
|
929
|
+
.tag { font-size: 9px; font-weight: 600; letter-spacing: .03em; text-anchor: middle; }
|
|
930
|
+
.mark { fill: var(--accent); font-size: 11px; font-weight: 600; text-anchor: end; }
|
|
931
|
+
.chip { fill: ${ON_ACCENT}; font-size: 11.5px; text-anchor: middle; }
|
|
932
|
+
.steplabel { fill: var(--fg); font-size: 13px; font-weight: 600; text-anchor: middle; font-family: ui-monospace, Menlo, monospace; }
|
|
933
|
+
.caption { fill: var(--muted); font-size: 13.5px; text-anchor: middle; }
|
|
934
|
+
.railcol { fill: var(--fg); font-size: 12px; font-weight: 500; text-anchor: middle; }
|
|
935
|
+
.railphase { fill: var(--fg); font-size: 13px; font-weight: 600; font-family: ui-monospace, Menlo, monospace; }
|
|
936
|
+
.railphase.muted { fill: var(--muted); }
|
|
937
|
+
.end { text-anchor: end; }
|
|
938
|
+
${css}
|
|
939
|
+
@media (prefers-reduced-motion: reduce) { * { animation-play-state: paused !important; animation-delay: -${holdAt}s !important; } }
|
|
940
|
+
</style>
|
|
941
|
+
<defs><marker id="${opts.scope ?? ''}arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M 0 1 L 9 5 L 0 9 z" fill="context-stroke"/></marker></defs>
|
|
942
|
+
<rect width="100%" height="100%" fill="var(--bg)"/>
|
|
943
|
+
${body}</svg>
|
|
944
|
+
`;
|
|
945
|
+
};
|
|
946
|
+
const SCRIPT = `(()=>{const s=document.currentScript.closest('svg'),G=[...s.querySelectorAll('.scene')],S=[G[0]],R=matchMedia('(prefers-reduced-motion: reduce)').matches,
|
|
947
|
+
W=(g,k)=>g.dataset[k].split(' ').map(p=>p.split(',').map(Number));let q=[],L=W(G[0],'loop'),w=[0,-1],o,t=0,p=performance.now();
|
|
948
|
+
const seek=t=>{s.pauseAnimations();s.setCurrentTime(t);for(const a of s.getAnimations({subtree:true}))a.pause(),a.currentTime=t*1e3+a.effect.getTiming().delay},
|
|
949
|
+
tick=n=>{if(t>=w[1]){o=q.length>0;if(!o)q=L.slice();w=q.shift();t=w[0]}seek(R?w[1]:Math.min(t,w[1]));t+=R?(o?1e9:0):(n-p)/1e3;p=n;requestAnimationFrame(tick)},
|
|
950
|
+
show=()=>{const c=S.at(-1);for(const g of G){g.style.pointerEvents=g==c?'':'none';for(const x of g.querySelectorAll('.hit,.back'))x.setAttribute('role','button'),x.setAttribute('tabindex',g==c?0:-1)}},
|
|
951
|
+
go=(a,b)=>{q=a;L=b;w=[0,-1];t=0;show()},
|
|
952
|
+
back=()=>{if(S.length>1){const g=S.pop();go(W(g,'back'),W(S.at(-1),'loop'))}},
|
|
953
|
+
act=e=>{const x=e.target.closest('.hit,.back');if(!x||x.closest('.scene')!=S.at(-1))return;if(x.classList.contains('back'))back();else{const g=G.find(g=>g.id=='s-'+x.dataset.open);S.push(g);go(W(g,'open'),W(g,'loop'))}return 1};
|
|
954
|
+
s.setAttribute('role','group');s.addEventListener('click',act);
|
|
955
|
+
s.addEventListener('keydown',e=>{const k=e.key;if(k=='Escape'||k=='Backspace')back();else if(!((k=='Enter'||k==' ')&&act(e)))return;e.preventDefault()});
|
|
956
|
+
show();requestAnimationFrame(tick)})()`;
|
|
957
|
+
const BAND = 28;
|
|
958
|
+
/** The SVG string and the scene it drew. The scene shape may change; use `toSvg` for the SVG only. */
|
|
959
|
+
export function render(spec, opts = {}) {
|
|
960
|
+
const { scene: one, script, ...rest } = opts;
|
|
961
|
+
const all = scenes(spec);
|
|
962
|
+
const key = (path) => path.join('>');
|
|
963
|
+
const under = (list, path) => list.filter((x) => x.path.length === path.length + 1 && key(x.path.slice(0, -1)) === key(path));
|
|
964
|
+
const at = (s) => ({
|
|
965
|
+
ports: new Set(s.ports.map((p) => p.id)),
|
|
966
|
+
owners: new Map(under(all, s.path).map((x) => [x.path.at(-1), x.parts])),
|
|
967
|
+
});
|
|
968
|
+
if (one != null) {
|
|
969
|
+
const s = all.find((x) => x.path.at(-1) === one);
|
|
970
|
+
if (!s)
|
|
971
|
+
throw new Error(`box "${one}" has no level to open`);
|
|
972
|
+
const plain = (g) => ({
|
|
973
|
+
...g,
|
|
974
|
+
children: g.children.map((c) => (isGroup(c) ? plain(c) : { ...c, inside: undefined })),
|
|
975
|
+
});
|
|
976
|
+
const d = draw({ ...s.props, layout: plain(s.props.layout) }, rest, at(s));
|
|
977
|
+
return { svg: page(d.fig, rest, d.W, d.H, d.css, d.holdAt, d.body), scene: d.scene };
|
|
978
|
+
}
|
|
979
|
+
if (all.length < 2) {
|
|
980
|
+
const d = draw(spec, rest);
|
|
981
|
+
return { svg: page(d.fig, rest, d.W, d.H, d.css, d.holdAt, d.body), scene: d.scene };
|
|
982
|
+
}
|
|
983
|
+
const plan = tourPlan(spec, opts.speed ?? spec.speed);
|
|
984
|
+
const total = plan.ms / 1000, eps = plan.ms / 10000;
|
|
985
|
+
const { fade, grow, stagger, hide, shrink } = OPEN_MS;
|
|
986
|
+
const tour = [];
|
|
987
|
+
const uid = `${opts.scope ?? ''}f${([...JSON.stringify(spec)].reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 7) >>> 0).toString(36)}`;
|
|
988
|
+
const track = (pts) => {
|
|
989
|
+
const name = `${uid}t${tour.length}`;
|
|
990
|
+
const kf = pts.map(([t, v, ease]) => `${pct(t / plan.ms)} { ${v}${ease ? `; animation-timing-function: ${ease}` : ''} }`).join(' ');
|
|
991
|
+
tour.push(`@keyframes ${name} { ${kf} }\n.${name} { animation: ${name} ${n2(total)}s infinite linear; }`);
|
|
992
|
+
return name;
|
|
993
|
+
};
|
|
994
|
+
const fadeAt = (o) => o.grown - grow - fade;
|
|
995
|
+
const dips = (path) => under(plan.opens, path).flatMap((n) => [
|
|
996
|
+
[fadeAt(n), 1],
|
|
997
|
+
[fadeAt(n) + fade, 0],
|
|
998
|
+
[n.back[1] - fade, 0],
|
|
999
|
+
[n.back[1], 1],
|
|
1000
|
+
]);
|
|
1001
|
+
const drawn = all.map((s, i) => {
|
|
1002
|
+
const o = plan.opens.find((x) => key(x.path) === key(s.path));
|
|
1003
|
+
const props = o ? { ...s.props, steps: o.step === -1 ? [] : [s.props.steps[o.step]] } : spec;
|
|
1004
|
+
const fades = o &&
|
|
1005
|
+
new Map(flowOrder(s).map((id, k) => {
|
|
1006
|
+
const t = o.grown + stagger * k;
|
|
1007
|
+
return [
|
|
1008
|
+
id,
|
|
1009
|
+
track([
|
|
1010
|
+
[0, 'opacity: 0'],
|
|
1011
|
+
[t, 'opacity: 0'],
|
|
1012
|
+
[t + fade, 'opacity: 1'],
|
|
1013
|
+
[plan.ms, 'opacity: 1'],
|
|
1014
|
+
]),
|
|
1015
|
+
];
|
|
1016
|
+
}));
|
|
1017
|
+
const times = o ? o.times : plan.times;
|
|
1018
|
+
return { s, o, props, d: draw(props, rest, { prefix: `${uid}s${i}`, ...at(s), fades, times, total }) };
|
|
1019
|
+
});
|
|
1020
|
+
const W = Math.max(...drawn.map((x) => x.d.W)), H = Math.max(...drawn.map((x) => x.d.H)) + BAND;
|
|
1021
|
+
const off = (d) => [(W - d.W) / 2, (H + BAND - d.H) / 2];
|
|
1022
|
+
const windows = (w, at) => (w.length ? w : [[at, at]]).map(([a, b]) => `${n2(a / 1000)},${n2(b / 1000)}`).join(' ');
|
|
1023
|
+
const groups = drawn.map(({ s, o, d }) => {
|
|
1024
|
+
const shown = o
|
|
1025
|
+
? [
|
|
1026
|
+
[0, 0],
|
|
1027
|
+
[o.grown, 0],
|
|
1028
|
+
[o.grown + eps, 1],
|
|
1029
|
+
...dips(s.path),
|
|
1030
|
+
[o.back[1] - fade - shrink - hide, 1],
|
|
1031
|
+
[o.back[1] - fade - shrink, 0],
|
|
1032
|
+
[plan.ms, 0],
|
|
1033
|
+
]
|
|
1034
|
+
: [[0, 1], ...dips(s.path), [plan.ms, 1]];
|
|
1035
|
+
const band = o
|
|
1036
|
+
? `<text class="back ${uid}back" x="16" y="19">Back</text><text class="${uid}path" x="${n2(16 + textWidth('Back', 14) + 12)}" y="19">${esc(s.line)}</text>`
|
|
1037
|
+
: '';
|
|
1038
|
+
const area = d.scene.area;
|
|
1039
|
+
const hits = !script
|
|
1040
|
+
? ''
|
|
1041
|
+
: under(all, s.path)
|
|
1042
|
+
.map((x) => {
|
|
1043
|
+
const r = d.scene.boxes.find((b) => b.id === x.path.at(-1)).rect;
|
|
1044
|
+
return `<rect class="hit" x="${n2(r.x - area.x)}" y="${n2(r.y - area.y)}" width="${n2(r.w)}" height="${n2(r.h)}" fill="transparent" tabindex="0" role="button" aria-label="Open ${esc(x.name)}, ${x.parts} parts" data-open="${esc(x.path.at(-1))}"/>`;
|
|
1045
|
+
})
|
|
1046
|
+
.join('');
|
|
1047
|
+
const data = !script
|
|
1048
|
+
? ''
|
|
1049
|
+
: o
|
|
1050
|
+
? ` data-open="${windows([o.open], 0)}" data-loop="${windows(o.loop, o.open[1])}" data-back="${windows([o.back], 0)}"`
|
|
1051
|
+
: ` data-loop="${windows(plan.loop, 0)}"`;
|
|
1052
|
+
return (`<g class="${track(shown.map(([t, v]) => [t, `opacity: ${v}`]))}"><g class="scene"${o ? ` id="s-${esc(s.path.at(-1))}"` : ''}${data}>${band}` +
|
|
1053
|
+
`<g transform="translate(${n2(off(d)[0])} ${n2(off(d)[1])})">\n${d.body}${hits}</g></g></g>`);
|
|
1054
|
+
});
|
|
1055
|
+
const frames = drawn.slice(1).map(({ s, o, d }) => {
|
|
1056
|
+
const parent = drawn.find((x) => key(x.s.path) === key(s.path.slice(0, -1))).d;
|
|
1057
|
+
const r = parent.scene.boxes.find((b) => b.id === s.path.at(-1)).rect;
|
|
1058
|
+
const [px, py] = off(parent), fx = off(d)[0] + d.frame.x, fy = off(d)[1];
|
|
1059
|
+
const dx = px + r.x - parent.scene.area.x - fx, dy = py + r.y - parent.scene.area.y - fy;
|
|
1060
|
+
const small = `transform: translate(${n2(dx)}px, ${n2(dy)}px) scale(${n4(r.w / d.frame.w)}, ${n4(r.h / d.frame.h)})`;
|
|
1061
|
+
const full = 'transform: translate(0px, 0px) scale(1, 1)';
|
|
1062
|
+
const from = fadeAt(o), shut = o.back[1] - fade;
|
|
1063
|
+
const name = track([
|
|
1064
|
+
[0, `opacity: 0; ${small}`],
|
|
1065
|
+
[from, `opacity: 0; ${small}`],
|
|
1066
|
+
[from + eps, `opacity: 1; ${small}`],
|
|
1067
|
+
[from + fade, `opacity: 1; ${small}`, 'cubic-bezier(0.2, 0, 0, 1)'],
|
|
1068
|
+
[o.grown, `opacity: 1; ${full}`],
|
|
1069
|
+
...dips(s.path).map(([t, v]) => [t, `opacity: ${v}; ${full}`]),
|
|
1070
|
+
[shut - shrink, `opacity: 1; ${full}`, 'cubic-bezier(0.4, 0, 1, 1)'],
|
|
1071
|
+
[shut, `opacity: 1; ${small}`],
|
|
1072
|
+
[shut + eps, `opacity: 0; ${small}`],
|
|
1073
|
+
[plan.ms, `opacity: 0; ${small}`],
|
|
1074
|
+
]);
|
|
1075
|
+
return (`<rect class="open-frame ${name}" data-for="${esc(s.path.at(-1))}" x="${n2(fx)}" y="${n2(fy)}" width="${n2(d.frame.w)}" height="${n2(d.frame.h)}" rx="10"` +
|
|
1076
|
+
` fill="none" stroke="var(--border)" vector-effect="non-scaling-stroke" style="transform-box: view-box; transform-origin: ${n2(fx)}px ${n2(fy)}px"/>`);
|
|
1077
|
+
});
|
|
1078
|
+
const root = drawn[0].d;
|
|
1079
|
+
const css = [
|
|
1080
|
+
...drawn.map((x) => x.d.css),
|
|
1081
|
+
`.${uid}back { fill: var(--accent); font-size: 14px; }\n.${uid}path { fill: var(--fg); font-size: 14px; }`,
|
|
1082
|
+
...tour,
|
|
1083
|
+
];
|
|
1084
|
+
const body = `${groups.join('\n')}\n<g pointer-events="none">${frames.join('')}</g>\n${script ? `<script><![CDATA[${SCRIPT}]]></script>\n` : ''}`;
|
|
1085
|
+
return {
|
|
1086
|
+
svg: page(root.fig, rest, W, H, css.join('\n'), root.holdAt, body),
|
|
1087
|
+
scene: root.scene,
|
|
1088
|
+
levels: drawn.map(({ s, props, d }) => ({ line: s.line, props, scene: d.scene })),
|
|
1089
|
+
};
|
|
931
1090
|
}
|
|
932
1091
|
/** One self-contained animated SVG string for the figure. Needs no React and no browser. */
|
|
933
1092
|
export function toSvg(fig, opts = {}) {
|
|
@@ -935,5 +1094,6 @@ export function toSvg(fig, opts = {}) {
|
|
|
935
1094
|
}
|
|
936
1095
|
/** Every fault `flowfig check` knows about, for this figure as the SVG lays it out. */
|
|
937
1096
|
export function check(fig, opts = {}) {
|
|
938
|
-
|
|
1097
|
+
const r = render(fig, opts);
|
|
1098
|
+
return checkRendered(fig, opts, r.scene, r.levels);
|
|
939
1099
|
}
|
package/dist/verify.d.ts
CHANGED
|
@@ -13,8 +13,14 @@ export type Coverage = {
|
|
|
13
13
|
id: string;
|
|
14
14
|
reason: string;
|
|
15
15
|
}[];
|
|
16
|
+
/** The coverage of each level inside a box, by path line. */
|
|
17
|
+
levels?: {
|
|
18
|
+
path: string;
|
|
19
|
+
coverage: Coverage;
|
|
20
|
+
}[];
|
|
16
21
|
};
|
|
17
22
|
export declare const coverageLine: (figure: string, c: Coverage) => string;
|
|
23
|
+
export declare const levelLines: (c: Coverage) => string[];
|
|
18
24
|
export declare const unsureLines: (c: Coverage) => string[];
|
|
19
25
|
export declare const SPEC_MARK = "<metadata id=\"figure-spec\">";
|
|
20
26
|
export declare function detailFindings(fig: FlowProps, root: string, read?: Read): Finding[];
|