@weasel-js/diagram 1.9.3 → 1.10.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/dist/index.js CHANGED
@@ -1,9 +1,10 @@
1
- import { insetOf, layered, portOf, outlinePath, diagramNodeOf, expandGroupEdges, forceRelaxation, center, force, tree, DIAGRAM_TRAIT_KEY, portsOf, DEFAULT_GROUP_INSET, sizeToBody, measureBody, buildBody } from './chunk-ERT3XWBS.js';
2
- export { COMPASS, DEFAULT_GROUP_INSET, DEFAULT_NODE_GAP, DEFAULT_PORTS, DEFAULT_RANK_GAP, DIAGRAM_TRAIT_KEY, axesFor, backEdges, bodyOutline, bodyTrait, boxForContent, buildBody, canvasMeasure, contentBox, createDiagramNodes, dataKeyReader, diagramNodeOf, expandGroupEdges, force, forceRelaxation, forestOf, graphOrder, insetOf, isPinned, layered, layoutBody, layoutRowPorts, measureBody, outlinePath, outlinePolyline, packAcross, packClusters, pinnedSet, portOf, portsOf, portsOnOutline, rankGaps, ranksOf, rayHit, seededOrder, settle, sizeToBody, translated, tree } from './chunk-ERT3XWBS.js';
3
- import { dragPanContribution, WeaselProvider, useScene, useSelection, useLatest, reconcileSpecs, SceneCanvas, fitViewToBounds, AUTO_POSE_DESCRIPTOR, translatePoseViaDescriptor, pointAlongPath, polylineFromPoints, registerNodeShape, effectivePose, usePoseRun, RECT_POSE_DESCRIPTOR, composeAffordanceLayer } from '@weasel-js/core';
1
+ import { insetOf, layered, portsOf, portOf, outlinePath, diagramNodeOf, expandGroupEdges, forceRelaxation, center, force, tree, DIAGRAM_TRAIT_KEY, DEFAULT_GROUP_INSET, sizeToBody, measureBody, buildBody } from './chunk-BWQJYEA3.js';
2
+ export { COMPASS, DEFAULT_GROUP_INSET, DEFAULT_NODE_GAP, DEFAULT_PORTS, DEFAULT_RANK_GAP, DIAGRAM_TRAIT_KEY, axesFor, backEdges, bodyOutline, bodyTrait, boxForContent, buildBody, canvasMeasure, contentBox, createDiagramNodes, dataKeyReader, diagramNodeOf, expandGroupEdges, force, forceRelaxation, forestOf, graphOrder, insetOf, isPinned, layered, layoutBody, layoutRowPorts, measureBody, outlinePath, outlinePolyline, packAcross, packClusters, pinnedSet, portOf, portsOf, portsOnOutline, rankGaps, ranksOf, rayHit, seededOrder, settle, sizeToBody, translated, tree, untilSettled } from './chunk-BWQJYEA3.js';
3
+ import { dragPanContribution, WeaselProvider, useScene, useSelection, useLatest, reconcileSpecs, SceneCanvas, fitViewToBounds, AUTO_POSE_DESCRIPTOR, translatePoseViaDescriptor, pointAlongPath, polylineFromPoints, registerNodeShape, effectivePose, usePoseRun, RECT_POSE_DESCRIPTOR, commandFill, commandStroke, composeAffordanceLayer } from '@weasel-js/core';
4
4
  import { portCurvePoints } from '@weasel-js/geom';
5
5
  import { AUTO_POSE_DESCRIPTOR as AUTO_POSE_DESCRIPTOR$1 } from '@weasel-js/core/math';
6
6
  import { forwardRef, useEffect, useMemo, useRef, useState, useImperativeHandle, useCallback } from 'react';
7
+ import '@weasel-js/animator';
7
8
  import { jsx } from 'react/jsx-runtime';
8
9
 
9
10
  function traitOf(node) {
@@ -103,6 +104,23 @@ function labelDerivePose(opts = {}) {
103
104
  var DIAGRAM_LABEL = "diagram:label";
104
105
  var LABEL_DERIVE_POSE = labelDerivePose();
105
106
 
107
+ // src/selfLoop.ts
108
+ var dot = (a, b) => a.x * b.x + a.y * b.y;
109
+ function selfLoop(ports, standoff) {
110
+ const facing = ports.filter((p) => p.normal !== null);
111
+ const most = (dir, among) => among.reduce(
112
+ (best, p) => best === void 0 || dot(p.normal, dir) > dot(best.normal, dir) ? p : best,
113
+ void 0
114
+ );
115
+ const from = most({ x: 1, y: 0 }, facing);
116
+ if (from === void 0) return null;
117
+ const to = most({ x: 0, y: -1 }, facing.filter((p) => Math.abs(dot(p.normal, from.normal)) < 0.5));
118
+ if (to === void 0) return null;
119
+ const out = { x: from.point.x + from.normal.x * standoff, y: from.point.y + from.normal.y * standoff };
120
+ const reach = dot({ x: to.point.x - out.x, y: to.point.y - out.y }, to.normal) + standoff;
121
+ return { from, to, via: { x: out.x + to.normal.x * reach, y: out.y + to.normal.y * reach } };
122
+ }
123
+
106
124
  // src/edge.ts
107
125
  var straight = (req) => [req.from.point, ...req.waypoints, req.to.point];
108
126
  var orthogonal = (req) => {
@@ -123,12 +141,15 @@ var orthogonal = (req) => {
123
141
  out.push(b);
124
142
  continue;
125
143
  }
144
+ const prev = out.length > 1 ? out[out.length - 2] : null;
126
145
  if (leaveHorizontal) {
127
146
  const mid = (a.x + b.x) / 2;
128
- out.push({ x: mid, y: a.y }, { x: mid, y: b.y }, b);
147
+ if (prev !== null && (mid - a.x) * (a.x - prev.x) < 0) out.push({ x: a.x, y: b.y }, b);
148
+ else out.push({ x: mid, y: a.y }, { x: mid, y: b.y }, b);
129
149
  } else {
130
150
  const mid = (a.y + b.y) / 2;
131
- out.push({ x: a.x, y: mid }, { x: b.x, y: mid }, b);
151
+ if (prev !== null && (mid - a.y) * (a.y - prev.y) < 0) out.push({ x: b.x, y: a.y }, b);
152
+ else out.push({ x: a.x, y: mid }, { x: b.x, y: mid }, b);
132
153
  }
133
154
  }
134
155
  return out;
@@ -198,17 +219,24 @@ function edgeDerivePath(opts = {}) {
198
219
  const b = geometry.getBounds(d.pose);
199
220
  return { x: b.x + b.width / 2, y: b.y + b.height / 2 };
200
221
  };
222
+ const standoffOf = (d) => {
223
+ const b = geometry.getBounds(d.pose);
224
+ return Math.min(b.width, b.height) / 2;
225
+ };
201
226
  return (node, deps) => {
202
227
  const edge = diagramEdgeOf(node);
203
228
  if (edge === null) return null;
204
229
  const [fromDep, toDep] = deps;
205
230
  if (fromDep === void 0 || toDep === void 0) return null;
206
- const from = resolveEnd(edge.from, fromDep, centerOf(toDep), portOpts, allPorts);
207
- const to = resolveEnd(edge.to, toDep, centerOf(fromDep), portOpts, allPorts);
231
+ const loop = fromDep.node.id === toDep.node.id && edge.from.port === void 0 && edge.to.port === void 0 ? selfLoop(allPorts(fromDep), opts.selfLoopStandoff ?? standoffOf(fromDep)) : null;
232
+ const from = loop?.from ?? resolveEnd(edge.from, fromDep, centerOf(toDep), portOpts, allPorts);
233
+ const to = loop?.to ?? resolveEnd(edge.to, toDep, centerOf(fromDep), portOpts, allPorts);
208
234
  if (from === void 0 || to === void 0) return null;
235
+ const authored = edge.waypoints ?? [];
236
+ const waypoints = loop !== null && authored.length === 0 ? [loop.via] : authored;
209
237
  const routers = opts.routers ?? ROUTERS;
210
238
  const router = edge.router !== void 0 && routers[edge.router] || straight;
211
- const points = router({ from, to, waypoints: edge.waypoints ?? [] });
239
+ const points = router({ from, to, waypoints });
212
240
  return points.length < 2 ? null : polylineFromPoints(points);
213
241
  };
214
242
  }
@@ -337,9 +365,6 @@ function commitEdgeToScene(edge, ctx) {
337
365
  kind: "leaf",
338
366
  layer: source?.layer ?? scene.layers[0].id,
339
367
  pose: { x: 0, y: 0, width: 0, height: 0 },
340
- // An edge runs *from* one node *to* another, and a plain line does not say
341
- // so. The arrowhead is an ordinary stroke marker, which is why nothing in
342
- // this package draws one.
343
368
  data: { diagram: trait, stroke: DEFAULT_EDGE_STROKE },
344
369
  dependsOn: [edge.from.nodeId, edge.to.nodeId],
345
370
  // Without this the edge re-resolves on every dependency move and paints
@@ -462,6 +487,58 @@ function diagramPorts(opts) {
462
487
  contribution: createDiagramContribution(opts)
463
488
  };
464
489
  }
490
+
491
+ // src/groupTree.ts
492
+ function nestGroups(declared, isNode) {
493
+ const groupIds = new Set(declared.map((g) => g.id));
494
+ const parentOf = /* @__PURE__ */ new Map();
495
+ const claimedNodes = /* @__PURE__ */ new Set();
496
+ const direct = /* @__PURE__ */ new Map();
497
+ const isAncestorOrSelf = (candidate, of) => {
498
+ for (let at = of; at !== void 0; at = parentOf.get(at)) {
499
+ if (at === candidate) return true;
500
+ }
501
+ return false;
502
+ };
503
+ for (const group of declared) {
504
+ const own = [];
505
+ for (const id of group.members) {
506
+ if (isNode(id)) {
507
+ if (claimedNodes.has(id)) continue;
508
+ claimedNodes.add(id);
509
+ own.push(id);
510
+ } else if (groupIds.has(id) && !parentOf.has(id) && !isAncestorOrSelf(id, group.id)) {
511
+ parentOf.set(id, group.id);
512
+ own.push(id);
513
+ }
514
+ }
515
+ direct.set(group.id, own);
516
+ }
517
+ const flat = /* @__PURE__ */ new Map();
518
+ const flatten = (id) => {
519
+ const known = flat.get(id);
520
+ if (known !== void 0) return known;
521
+ const out = [];
522
+ for (const member of direct.get(id)) {
523
+ if (groupIds.has(member)) out.push(...flatten(member));
524
+ else out.push(member);
525
+ }
526
+ flat.set(id, out);
527
+ return out;
528
+ };
529
+ const live = new Set(declared.filter((g) => flatten(g.id).length > 0).map((g) => g.id));
530
+ return declared.filter((g) => live.has(g.id)).map((g) => {
531
+ const parent = parentOf.get(g.id);
532
+ return {
533
+ id: g.id,
534
+ direct: direct.get(g.id).filter((id) => !groupIds.has(id) || live.has(id)),
535
+ members: flat.get(g.id),
536
+ ...parent !== void 0 ? { parent } : {}
537
+ };
538
+ });
539
+ }
540
+
541
+ // src/graph.ts
465
542
  function buildGraph(source, opts = {}) {
466
543
  const geometry = opts.geometry ?? AUTO_POSE_DESCRIPTOR$1;
467
544
  const nodes = [];
@@ -495,17 +572,19 @@ function buildGraph(source, opts = {}) {
495
572
  nodes.push(entry);
496
573
  byId.set(entry.id, entry);
497
574
  }
498
- const groups = [];
499
- const claimed = /* @__PURE__ */ new Set();
500
- for (const g of groupNodes) {
501
- const members = g.members.filter((id) => byId.has(id) && !claimed.has(id));
502
- for (const id of members) {
503
- claimed.add(id);
504
- byId.get(id).group = g.id;
575
+ const insets = new Map(groupNodes.map((g) => [g.id, g.inset]));
576
+ const live = nestGroups(groupNodes, (id) => byId.has(id)).map((g) => {
577
+ for (const id of g.direct) {
578
+ const node = byId.get(id);
579
+ if (node !== void 0) node.group = g.id;
505
580
  }
506
- groups.push({ ...g, members });
507
- }
508
- const live = groups.filter((g) => g.members.length > 0);
581
+ return {
582
+ id: g.id,
583
+ members: g.members,
584
+ inset: insets.get(g.id),
585
+ ...g.parent !== void 0 ? { parent: g.parent } : {}
586
+ };
587
+ });
509
588
  const known = (id) => byId.has(id) || live.some((g) => g.id === id);
510
589
  const edges = expandGroupEdges(candidates.filter((e) => known(e.from) && known(e.to)), live);
511
590
  const out = /* @__PURE__ */ new Map();
@@ -532,49 +611,74 @@ function push(index, key, edge) {
532
611
 
533
612
  // src/dataGroups.ts
534
613
  function resolveGroups(groups, ids, nodeIds, opts) {
535
- const claimed = /* @__PURE__ */ new Set();
536
- const out = [];
537
614
  for (const group of groups) {
538
615
  if (ids.has(group.id)) throw new Error(`diagramScene: group id "${group.id}" is already a node or group id`);
539
616
  ids.add(group.id);
540
- const members = group.members.filter((m) => nodeIds.has(m) && !claimed.has(m));
541
- for (const m of members) claimed.add(m);
542
- if (members.length === 0) continue;
617
+ }
618
+ const declared = new Map(groups.map((g) => [g.id, g]));
619
+ const nested = nestGroups(groups, (id) => nodeIds.has(id));
620
+ const depthOf = /* @__PURE__ */ new Map();
621
+ const byId = new Map(nested.map((g) => [g.id, g]));
622
+ const depth = (id) => {
623
+ const parent = byId.get(id).parent;
624
+ let d = depthOf.get(id);
625
+ if (d === void 0) depthOf.set(id, d = parent === void 0 ? 0 : depth(parent) + 1);
626
+ return d;
627
+ };
628
+ return nested.map((g, i) => ({ g, i })).sort((a, b) => depth(a.g.id) - depth(b.g.id) || a.i - b.i).map(({ g }) => {
629
+ const group = declared.get(g.id);
543
630
  const pad = group.padding ?? opts.padding;
544
631
  const title = group.label ? opts.title(group.label) : null;
545
632
  const inset = { top: pad + (title ? title.height + pad / 2 : 0), right: pad, bottom: pad, left: pad };
546
- out.push({ group, members, inset, pad, title });
547
- }
548
- return out;
633
+ return {
634
+ group,
635
+ direct: g.direct,
636
+ members: g.members,
637
+ inset,
638
+ pad,
639
+ title,
640
+ ...g.parent !== void 0 ? { parent: g.parent } : {}
641
+ };
642
+ });
549
643
  }
550
644
  function groupSpecs(groups, placedAt, styleOf) {
645
+ const byId = new Map(groups.map((g) => [g.group.id, g]));
646
+ const boxes = /* @__PURE__ */ new Map();
647
+ const boxOf = (id) => {
648
+ const known = boxes.get(id);
649
+ if (known !== void 0) return known;
650
+ const g = byId.get(id);
651
+ const rs = g.direct.map((m) => byId.has(m) ? boxOf(m) : placedAt.get(m));
652
+ const x = Math.min(...rs.map((r) => r.x)) - g.inset.left;
653
+ const y = Math.min(...rs.map((r) => r.y)) - g.inset.top;
654
+ const box = {
655
+ x,
656
+ y,
657
+ width: Math.max(...rs.map((r) => r.x + r.width)) + g.inset.right - x,
658
+ height: Math.max(...rs.map((r) => r.y + r.height)) + g.inset.bottom - y
659
+ };
660
+ boxes.set(id, box);
661
+ return box;
662
+ };
551
663
  const specs = [];
552
664
  for (const g of groups) {
553
665
  const { style, text } = styleOf(g.group);
554
- const rs = g.members.map((m) => placedAt.get(m));
555
- const x = Math.min(...rs.map((r) => r.x)) - g.inset.left;
556
- const y = Math.min(...rs.map((r) => r.y)) - g.inset.top;
666
+ const { x, y, width, height } = boxOf(g.group.id);
557
667
  specs.push({
558
668
  id: g.group.id,
559
669
  kind: "leaf",
560
670
  layer: "main",
561
- pickable: false,
562
- pose: {
563
- x,
564
- y,
565
- width: Math.max(...rs.map((r) => r.x + r.width)) + g.inset.right - x,
566
- height: Math.max(...rs.map((r) => r.y + r.height)) + g.inset.bottom - y
567
- },
671
+ pose: { x, y, width, height },
568
672
  data: {
569
673
  diagram: { outline: "rect", group: { inset: g.inset } },
570
- fill: { color: style.fill },
674
+ fill: { paint: { color: style.fill } },
571
675
  stroke: {
572
676
  paint: { color: style.stroke },
573
677
  width: style.strokeWidth ?? 1,
574
678
  ...style.dash ? { dash: style.dash } : {}
575
679
  }
576
680
  },
577
- dependsOn: g.members,
681
+ dependsOn: g.direct,
578
682
  derivePose: GROUP_DERIVE_POSE
579
683
  });
580
684
  if (g.title === null || !g.group.label) continue;
@@ -585,7 +689,7 @@ function groupSpecs(groups, placedAt, styleOf) {
585
689
  layer: "main",
586
690
  pickable: false,
587
691
  pose: { x: x + g.pad, y: y + g.pad, width: g.title.width, height: g.title.height },
588
- data: { diagram: { anchor }, text: g.group.label, style: text, fill: { color: style.text } },
692
+ data: { diagram: { anchor }, text: g.group.label, style: text, fill: { paint: { color: style.text } } },
589
693
  dependsOn: [g.group.id],
590
694
  derivePose: ANCHOR_DERIVE_POSE
591
695
  });
@@ -665,9 +769,19 @@ function diagramScene(data, opts = {}) {
665
769
  );
666
770
  return { id: n.id, bounds: pose, pinned: n.pinned === true };
667
771
  });
668
- const graphGroups = groups.map((g) => ({ id: g.group.id, members: g.members, inset: g.inset }));
772
+ const graphGroups = groups.map((g) => ({
773
+ id: g.group.id,
774
+ members: g.members,
775
+ inset: g.inset,
776
+ ...g.parent !== void 0 ? { parent: g.parent } : {}
777
+ }));
669
778
  const seedOf = new Map(seeds.map((n) => [n.id, n]));
670
- for (const g of groups) for (const m of g.members) seedOf.get(m).group = g.group.id;
779
+ for (const g of groups) {
780
+ for (const m of g.direct) {
781
+ const seed = seedOf.get(m);
782
+ if (seed !== void 0) seed.group = g.group.id;
783
+ }
784
+ }
671
785
  const graph = graphOf(
672
786
  seeds,
673
787
  expandGroupEdges(edges.map((e) => ({ id: edgeIdOf(e), from: e.from, to: e.to })), graphGroups),
@@ -689,7 +803,7 @@ function diagramScene(data, opts = {}) {
689
803
  const seed = seeds[i].bounds;
690
804
  const at = placedAt.get(n.id);
691
805
  const style = opts.nodeStyle?.(n) ?? NODE;
692
- const textData = (text, s) => ({ text, style: styleOf(s), fill: { color: style.text } });
806
+ const textData = (text, s) => ({ text, style: styleOf(s), fill: { paint: { color: style.text } } });
693
807
  const built = buildBody(
694
808
  bodies[i],
695
809
  { x: at.x, y: at.y, width: seed.width, height: seed.height },
@@ -699,7 +813,7 @@ function diagramScene(data, opts = {}) {
699
813
  measure,
700
814
  body: (trait) => ({
701
815
  diagram: n.pinned === true ? { ...trait, pinned: true } : trait,
702
- fill: { color: style.fill },
816
+ fill: { paint: { color: style.fill } },
703
817
  stroke: {
704
818
  paint: { color: style.stroke },
705
819
  width: style.strokeWidth ?? 2,
@@ -755,7 +869,7 @@ function diagramScene(data, opts = {}) {
755
869
  diagram: { label: e.labelPlacement ?? opts.labelPlacement ?? LABEL_PLACEMENT },
756
870
  text: e.label,
757
871
  style: labelStyle,
758
- fill: { color: style.label ?? style.stroke }
872
+ fill: { paint: { color: style.label ?? style.stroke } }
759
873
  },
760
874
  dependsOn: [id],
761
875
  derivePose: LABEL_DERIVE_POSE
@@ -1003,11 +1117,14 @@ function diagramShape(opts = {}) {
1003
1117
  const outline = outlineOf(node);
1004
1118
  if (outline === void 0) return [];
1005
1119
  const d = node.data;
1006
- const fill = d.fill === void 0 ? opts.defaultFill : d.fill;
1007
- const stroke = d.stroke === void 0 ? opts.defaultStroke : d.stroke;
1120
+ const box = geometry.getBounds(pose);
1121
+ const declaredFill = d.fill === void 0 ? opts.defaultFill : d.fill;
1122
+ const declaredStroke = d.stroke === void 0 ? opts.defaultStroke : d.stroke;
1123
+ const fill = declaredFill && commandFill(declaredFill, box);
1124
+ const stroke = declaredStroke && commandStroke(declaredStroke, box);
1008
1125
  return [{
1009
1126
  kind: "path",
1010
- path: outlinePath(outline, geometry.getBounds(pose)),
1127
+ path: outlinePath(outline, box),
1011
1128
  ...fill ? { fill } : {},
1012
1129
  ...stroke ? { stroke } : {}
1013
1130
  }];
@@ -1179,6 +1296,6 @@ function fitDiagram(specs, size, fit = {}) {
1179
1296
  });
1180
1297
  }
1181
1298
 
1182
- export { ANCHOR_DERIVE_POSE, CONNECT_ACTION_ID, DEFAULT_EDGE_STROKE, DIAGRAM_ANCHOR, DIAGRAM_EDGE, DIAGRAM_GROUP, DIAGRAM_LABEL, DiagramView, EASED_LAYOUTS, EDGE_DERIVE_PATH, GRAB_PORT_ACTION_ID, GROUP_DERIVE_POSE, LABEL_DERIVE_POSE, LAYOUTS2 as LAYOUTS, LAYOUT_ACTION_ID, PORT_AFFORDANCE_KIND, PORT_LAYER_ID, ROUTERS, anchorDerivePose, applyLayout, bezier, buildGraph, commitEdgeToScene, connectBinding, createConnectAction, createDiagramContribution, createLayoutAction, createPortAffordance, defaultCanConnect, diagramAnchorOf, diagramEdgeOf, diagramGroupOf, diagramLabelOf, diagramPorts, diagramScene, diagramShape, easedProducer, edgeDerivePath, edgeIdOf, forceProducer, grabPortAction, grabPortBindings, groupDerivePose, labelDerivePose, layoutPoses, orthogonal, portLayer, portScratchOf, registerDiagramShape, resolveEnd, sceneParticipants, straight, useLiveLayout, useMirroredSelection, withDiagramRegistry };
1299
+ export { ANCHOR_DERIVE_POSE, CONNECT_ACTION_ID, DEFAULT_EDGE_STROKE, DIAGRAM_ANCHOR, DIAGRAM_EDGE, DIAGRAM_GROUP, DIAGRAM_LABEL, DiagramView, EASED_LAYOUTS, EDGE_DERIVE_PATH, GRAB_PORT_ACTION_ID, GROUP_DERIVE_POSE, LABEL_DERIVE_POSE, LAYOUTS2 as LAYOUTS, LAYOUT_ACTION_ID, PORT_AFFORDANCE_KIND, PORT_LAYER_ID, ROUTERS, anchorDerivePose, applyLayout, bezier, buildGraph, commitEdgeToScene, connectBinding, createConnectAction, createDiagramContribution, createLayoutAction, createPortAffordance, defaultCanConnect, diagramAnchorOf, diagramEdgeOf, diagramGroupOf, diagramLabelOf, diagramPorts, diagramScene, diagramShape, easedProducer, edgeDerivePath, edgeIdOf, forceProducer, grabPortAction, grabPortBindings, groupDerivePose, labelDerivePose, layoutPoses, orthogonal, portLayer, portScratchOf, registerDiagramShape, resolveEnd, sceneParticipants, selfLoop, straight, useLiveLayout, useMirroredSelection, withDiagramRegistry };
1183
1300
  //# sourceMappingURL=index.js.map
1184
1301
  //# sourceMappingURL=index.js.map