flowfig 0.8.2 → 0.8.4
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 +2 -2
- package/dist/code.d.ts +1 -1
- package/dist/code.js +30 -1
- package/dist/geometry.js +22 -13
- package/dist/model.js +4 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ jobs:
|
|
|
65
65
|
- uses: actions/setup-node@v4
|
|
66
66
|
with:
|
|
67
67
|
node-version: 22
|
|
68
|
-
- uses: iamalvisng/flowfig@v0.8.
|
|
68
|
+
- uses: iamalvisng/flowfig@v0.8.4
|
|
69
69
|
with:
|
|
70
70
|
figures: 'docs/**/*.svg' # default **/*.svg
|
|
71
71
|
```
|
|
@@ -478,7 +478,7 @@ jobs:
|
|
|
478
478
|
- uses: actions/setup-node@v4
|
|
479
479
|
with:
|
|
480
480
|
node-version: 22
|
|
481
|
-
- uses: iamalvisng/flowfig@v0.8.
|
|
481
|
+
- uses: iamalvisng/flowfig@v0.8.4
|
|
482
482
|
with:
|
|
483
483
|
figures: 'docs/**/*.svg' # default **/*.svg
|
|
484
484
|
```
|
package/dist/code.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export declare function matchClose(code: string, open: number): number;
|
|
|
18
18
|
export declare function pyBody(code: string, start: number): Extent;
|
|
19
19
|
export declare function outside(root: string, path: string): boolean;
|
|
20
20
|
export declare function codeFile(root: string, path: string, read?: Read, cache?: Map<string, CodeFile | null>): CodeFile | null;
|
|
21
|
-
export declare
|
|
21
|
+
export declare const locate: (file: CodeFile, symbol: string) => {
|
|
22
22
|
start: number;
|
|
23
23
|
end: number;
|
|
24
24
|
} | null;
|
package/dist/code.js
CHANGED
|
@@ -511,7 +511,7 @@ export function codeFile(root, path, read = readFile, cache = new Map()) {
|
|
|
511
511
|
cache.set(full, file);
|
|
512
512
|
return file;
|
|
513
513
|
}
|
|
514
|
-
|
|
514
|
+
function locateDefinition(file, symbol) {
|
|
515
515
|
const { code, lang } = file;
|
|
516
516
|
const parts = symbol.split('.');
|
|
517
517
|
if (!parts.every((p) => /^[\p{L}_$][\p{L}\p{N}_$]*$/u.test(p)))
|
|
@@ -565,4 +565,33 @@ export function locate(file, symbol) {
|
|
|
565
565
|
}
|
|
566
566
|
return null;
|
|
567
567
|
}
|
|
568
|
+
function enumMember(file, symbol) {
|
|
569
|
+
const { code, lang } = file;
|
|
570
|
+
const [owner, name, ...rest] = symbol.split('.');
|
|
571
|
+
if (!name || rest.length || lang === 'go' || lang === 'py')
|
|
572
|
+
return null;
|
|
573
|
+
const head = new RegExp(`\\benum\\s+${esc(owner)}${E}[^;{}]*\\{`, 'u').exec(code);
|
|
574
|
+
if (!head)
|
|
575
|
+
return null;
|
|
576
|
+
const open = head.index + head[0].length - 1;
|
|
577
|
+
const close = matchClose(code, open);
|
|
578
|
+
let from = open + 1;
|
|
579
|
+
for (let i = from, d = 0; i <= close; i++) {
|
|
580
|
+
const c = code[i];
|
|
581
|
+
if ('{(['.includes(c))
|
|
582
|
+
d++;
|
|
583
|
+
else if (')]'.includes(c) || (c === '}' && i < close))
|
|
584
|
+
d--;
|
|
585
|
+
else if (d > 0 || (c !== ',' && c !== ';' && i < close))
|
|
586
|
+
continue;
|
|
587
|
+
const seg = code.slice(from, i).replace(/^(\s|@\w+(\([^)]*\))?|#\[[^\]]*\]|\[[^\]]*\])+/, '');
|
|
588
|
+
if (new RegExp(`^${esc(name)}${E}`, 'u').test(seg))
|
|
589
|
+
return { start: i - seg.length, end: i };
|
|
590
|
+
if (c === ';')
|
|
591
|
+
return null;
|
|
592
|
+
from = i + 1;
|
|
593
|
+
}
|
|
594
|
+
return null;
|
|
595
|
+
}
|
|
596
|
+
export const locate = (file, symbol) => locateDefinition(file, symbol) ?? enumMember(file, symbol);
|
|
568
597
|
export const isDefined = (file, symbol) => locate(file, symbol) != null;
|
package/dist/geometry.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { STUB } from './model.js';
|
|
1
|
+
import { EDGE_ON, STUB } from './model.js';
|
|
2
2
|
export const avoidOf = (boxes, extra, lanes) => [
|
|
3
3
|
...extra,
|
|
4
4
|
...boxes.map((r) => (lanes ? r : { ...r, box: true })),
|
|
@@ -78,6 +78,8 @@ export function route(edges, rects, tips = new Set(), avoid = [], area, lanes, p
|
|
|
78
78
|
if (around && blocked(around, arc)) {
|
|
79
79
|
const fits = Array.from({ length: ARC_STEPS + 1 }, (_, k) => [around, outward(around, k)]);
|
|
80
80
|
arc = fits.find(([way, at]) => !blocked(way, at))?.[1] ?? arc;
|
|
81
|
+
if (blocked(around, arc))
|
|
82
|
+
around = undefined;
|
|
81
83
|
}
|
|
82
84
|
if (!around && !e.sides && !e.stub && !e.elbow && past.length) {
|
|
83
85
|
const ways = stacked ? ['left', 'right'] : ['above', 'below'];
|
|
@@ -177,6 +179,7 @@ export function route(edges, rects, tips = new Set(), avoid = [], area, lanes, p
|
|
|
177
179
|
const all = Object.values(rects);
|
|
178
180
|
const [x0, y0] = [Math.min(...all.map((r) => r.x)), Math.min(...all.map((r) => r.y))];
|
|
179
181
|
const [x1, y1] = [Math.max(...all.map((r) => r.x + r.w)), Math.max(...all.map((r) => r.y + r.h))];
|
|
182
|
+
const room = (d) => (d > 0 ? d + EDGE_ON / 2 : 0);
|
|
180
183
|
const outside = (way, { x, y }) => (way === 'above' ? y0 - y : way === 'below' ? y - y1 : way === 'left' ? x0 - x : x - x1);
|
|
181
184
|
const roomOf = (way, at, curve, labelW) => Math.max(ARC - 6, lanes || !labelW ? outside(way, { x: at, y: at }) - 6 : outside(way, bezier(curve, 0.5)) + (upright(way) ? labelW / 2 : 9));
|
|
182
185
|
const one = (p) => {
|
|
@@ -335,21 +338,24 @@ export function route(edges, rects, tips = new Set(), avoid = [], area, lanes, p
|
|
|
335
338
|
if (e.x - s.x < 16 && s.y === e.y) {
|
|
336
339
|
return { id: p.id, d: `M ${s.x} ${s.y} H ${e.x}`, mid: { x: (s.x + e.x) / 2, y: s.y }, curve: [s, s, e, e], elbow: [s, e] };
|
|
337
340
|
}
|
|
341
|
+
const own = (r) => [p.a, p.b].some((q) => same(q, r));
|
|
342
|
+
const all = avoid.filter((r) => !own(r));
|
|
343
|
+
const boxes = all.filter((r) => r.box);
|
|
344
|
+
const vClear = (x, ya, yb, m = 2) => !all.some((r) => x > r.x - m && x < r.x + r.w + m && Math.max(ya, yb) > r.y && Math.min(ya, yb) < r.y + r.h);
|
|
345
|
+
const hClear = (y, xa, xb, list = boxes, m = 2) => !list.some((r) => y > r.y - m && y < r.y + r.h + m && Math.max(xa, xb) > r.x && Math.min(xa, xb) < r.x + r.w);
|
|
338
346
|
if (e.x - s.x < 16) {
|
|
339
|
-
const mx = s.x + 8, ex = e.x - 8,
|
|
347
|
+
const mx = s.x + 8, ex = e.x - 8, mid = (s.y + e.y) / 2;
|
|
348
|
+
const ys = Array.from({ length: Math.floor((y1 - y0 + 24) / 4) + 1 }, (_, i) => y0 - 12 + i * 4).sort((u, w) => Math.abs(u - mid) - Math.abs(w - mid));
|
|
349
|
+
const my = ys.find((y) => vClear(mx, s.y, y) && hClear(y, ex, mx, all) && vClear(ex, y, e.y)) ?? mid;
|
|
340
350
|
return {
|
|
341
351
|
id: p.id,
|
|
342
352
|
d: `M ${s.x} ${s.y} H ${mx} V ${my} H ${ex} V ${e.y} H ${e.x}`,
|
|
343
353
|
mid: { x: (mx + ex) / 2, y: my },
|
|
344
354
|
curve: [s, { x: mx, y: my }, { x: ex, y: my }, e],
|
|
345
355
|
elbow: [s, { x: mx, y: s.y }, { x: mx, y: my }, { x: ex, y: my }, { x: ex, y: e.y }, e],
|
|
356
|
+
bleed: { above: room(y0 - my), below: room(my - y1) },
|
|
346
357
|
};
|
|
347
358
|
}
|
|
348
|
-
const own = (r) => [p.a, p.b].some((q) => same(q, r));
|
|
349
|
-
const all = avoid.filter((r) => !own(r));
|
|
350
|
-
const boxes = all.filter((r) => r.box);
|
|
351
|
-
const vClear = (x, ya, yb, m = 2) => !all.some((r) => x > r.x - m && x < r.x + r.w + m && Math.max(ya, yb) > r.y && Math.min(ya, yb) < r.y + r.h);
|
|
352
|
-
const hClear = (y, xa, xb, list = boxes, m = 2) => !list.some((r) => y > r.y - m && y < r.y + r.h + m && Math.max(xa, xb) > r.x && Math.min(xa, xb) < r.x + r.w);
|
|
353
359
|
const mid = (s.x + e.x) / 2;
|
|
354
360
|
const gap = Array.from({ length: Math.floor((e.x - s.x - 16) / 4) + 1 }, (_, i) => s.x + 8 + i * 4).sort((u, w) => Math.abs(u - mid) - Math.abs(w - mid));
|
|
355
361
|
const mx = [mid, e.x - 8, s.x + 8, ...gap].find((x) => vClear(x, s.y, e.y) && hClear(s.y, s.x, x) && hClear(e.y, x, e.x));
|
|
@@ -384,10 +390,10 @@ export function route(edges, rects, tips = new Set(), avoid = [], area, lanes, p
|
|
|
384
390
|
curve: [s, { x, y }, { x: ex, y }, e],
|
|
385
391
|
elbow: [s, { x, y: s.y }, { x, y }, { x: ex, y }, { x: ex, y: e.y }, e],
|
|
386
392
|
bleed: {
|
|
387
|
-
left:
|
|
388
|
-
right:
|
|
389
|
-
above:
|
|
390
|
-
below:
|
|
393
|
+
left: room(x0 - Math.min(x, ex)),
|
|
394
|
+
right: room(Math.max(x, ex) - x1),
|
|
395
|
+
above: room(y0 - y),
|
|
396
|
+
below: room(y - y1),
|
|
391
397
|
},
|
|
392
398
|
};
|
|
393
399
|
}
|
|
@@ -499,13 +505,16 @@ export function labelAlong(r, w, boxes, placed, fig, lines = []) {
|
|
|
499
505
|
y: Math.max(fig.y + 9, Math.min(at.y, fig.y + fig.h - 9)),
|
|
500
506
|
}
|
|
501
507
|
: at;
|
|
502
|
-
const free = (at, strict) => {
|
|
508
|
+
const free = (at, strict, room = 1) => {
|
|
503
509
|
const pill = { x: at.x - w / 2, y: at.y - 9, w, h: 18 };
|
|
504
510
|
return (inFig(at) &&
|
|
505
|
-
!boxes.some((b) => overlaps(pill, b,
|
|
511
|
+
!boxes.some((b) => overlaps(pill, b, room)) &&
|
|
506
512
|
!placed.some((b) => overlaps(pill, b, 1)) &&
|
|
507
513
|
(!strict || !lines.some((q) => q.x > pill.x - 2 && q.x < pill.x + w + 2 && q.y > pill.y - 2 && q.y < pill.y + 20)));
|
|
508
514
|
};
|
|
515
|
+
for (const at of spots)
|
|
516
|
+
if (free(at, true, 6))
|
|
517
|
+
return at;
|
|
509
518
|
for (const strict of [true, false])
|
|
510
519
|
for (const at of spots)
|
|
511
520
|
if (free(at, strict))
|
package/dist/model.js
CHANGED
|
@@ -452,20 +452,21 @@ export const counts = (p) => {
|
|
|
452
452
|
};
|
|
453
453
|
};
|
|
454
454
|
const idsIn = (c) => (isGroup(c) ? [...(c.id ? [c.id] : []), ...c.children.flatMap(idsIn)] : [c.id]);
|
|
455
|
+
const LINE_CLEAR = 16;
|
|
455
456
|
export function groupGap(g, edges) {
|
|
456
457
|
const side = new Map();
|
|
457
458
|
g.children.forEach((c, i) => idsIn(c).forEach((id) => side.set(id, i)));
|
|
458
459
|
const spans = edges.filter((e) => e.label != null && side.has(e.from) && side.has(e.to) && side.get(e.from) !== side.get(e.to) && !(g.gap != null && e.around));
|
|
459
460
|
if (g.gap != null && !spans.length)
|
|
460
461
|
return g.gap;
|
|
461
|
-
const need = Math.max(0, ...spans.map((e) => labelPillW(str(e.label)) +
|
|
462
|
+
const need = Math.max(0, ...spans.map((e) => labelPillW(str(e.label)) + 2 * LINE_CLEAR));
|
|
462
463
|
if (g.gap != null && g.direction !== 'column')
|
|
463
|
-
return Math.max(g.gap, need
|
|
464
|
+
return Math.max(g.gap, need);
|
|
464
465
|
let auto;
|
|
465
466
|
if (g.direction === 'column') {
|
|
466
467
|
const ends = spans.map((e) => [side.get(e.from), side.get(e.to)].sort((p, q) => p - q));
|
|
467
468
|
const most = Math.max(0, ...g.children.map((_, k) => ends.filter(([lo, hi]) => lo <= k && k < hi).length));
|
|
468
|
-
auto = most
|
|
469
|
+
auto = most ? Math.min(most, 3) * 22 + 2 * LINE_CLEAR - 4 : 28;
|
|
469
470
|
}
|
|
470
471
|
else
|
|
471
472
|
auto = Math.max(56, need);
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.8.
|
|
1
|
+
export declare const VERSION = "0.8.4";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// The release list bumps this with package.json.
|
|
2
|
-
export const VERSION = '0.8.
|
|
2
|
+
export const VERSION = '0.8.4';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowfig",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
4
4
|
"description": "Your coding agent draws animated diagrams from your code, and flowfig checks each box and arrow against that code. CI fails when a diagram drifts from the code. The output is one animated SVG with no script, for a README, PR or issue. Works with Claude Code, Cursor, GitHub Copilot, Codex, Gemini CLI, Windsurf and Kiro.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|