@weasel-js/diagram 1.9.3 → 1.9.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/dist/index.js CHANGED
@@ -1,6 +1,6 @@
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-EDSBULQM.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-EDSBULQM.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';
@@ -103,6 +103,23 @@ function labelDerivePose(opts = {}) {
103
103
  var DIAGRAM_LABEL = "diagram:label";
104
104
  var LABEL_DERIVE_POSE = labelDerivePose();
105
105
 
106
+ // src/selfLoop.ts
107
+ var dot = (a, b) => a.x * b.x + a.y * b.y;
108
+ function selfLoop(ports, standoff) {
109
+ const facing = ports.filter((p) => p.normal !== null);
110
+ const most = (dir, among) => among.reduce(
111
+ (best, p) => best === void 0 || dot(p.normal, dir) > dot(best.normal, dir) ? p : best,
112
+ void 0
113
+ );
114
+ const from = most({ x: 1, y: 0 }, facing);
115
+ if (from === void 0) return null;
116
+ const to = most({ x: 0, y: -1 }, facing.filter((p) => Math.abs(dot(p.normal, from.normal)) < 0.5));
117
+ if (to === void 0) return null;
118
+ const out = { x: from.point.x + from.normal.x * standoff, y: from.point.y + from.normal.y * standoff };
119
+ const reach = dot({ x: to.point.x - out.x, y: to.point.y - out.y }, to.normal) + standoff;
120
+ return { from, to, via: { x: out.x + to.normal.x * reach, y: out.y + to.normal.y * reach } };
121
+ }
122
+
106
123
  // src/edge.ts
107
124
  var straight = (req) => [req.from.point, ...req.waypoints, req.to.point];
108
125
  var orthogonal = (req) => {
@@ -123,12 +140,15 @@ var orthogonal = (req) => {
123
140
  out.push(b);
124
141
  continue;
125
142
  }
143
+ const prev = out.length > 1 ? out[out.length - 2] : null;
126
144
  if (leaveHorizontal) {
127
145
  const mid = (a.x + b.x) / 2;
128
- out.push({ x: mid, y: a.y }, { x: mid, y: b.y }, b);
146
+ if (prev !== null && (mid - a.x) * (a.x - prev.x) < 0) out.push({ x: a.x, y: b.y }, b);
147
+ else out.push({ x: mid, y: a.y }, { x: mid, y: b.y }, b);
129
148
  } else {
130
149
  const mid = (a.y + b.y) / 2;
131
- out.push({ x: a.x, y: mid }, { x: b.x, y: mid }, b);
150
+ if (prev !== null && (mid - a.y) * (a.y - prev.y) < 0) out.push({ x: b.x, y: a.y }, b);
151
+ else out.push({ x: a.x, y: mid }, { x: b.x, y: mid }, b);
132
152
  }
133
153
  }
134
154
  return out;
@@ -198,17 +218,24 @@ function edgeDerivePath(opts = {}) {
198
218
  const b = geometry.getBounds(d.pose);
199
219
  return { x: b.x + b.width / 2, y: b.y + b.height / 2 };
200
220
  };
221
+ const standoffOf = (d) => {
222
+ const b = geometry.getBounds(d.pose);
223
+ return Math.min(b.width, b.height) / 2;
224
+ };
201
225
  return (node, deps) => {
202
226
  const edge = diagramEdgeOf(node);
203
227
  if (edge === null) return null;
204
228
  const [fromDep, toDep] = deps;
205
229
  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);
230
+ 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;
231
+ const from = loop?.from ?? resolveEnd(edge.from, fromDep, centerOf(toDep), portOpts, allPorts);
232
+ const to = loop?.to ?? resolveEnd(edge.to, toDep, centerOf(fromDep), portOpts, allPorts);
208
233
  if (from === void 0 || to === void 0) return null;
234
+ const authored = edge.waypoints ?? [];
235
+ const waypoints = loop !== null && authored.length === 0 ? [loop.via] : authored;
209
236
  const routers = opts.routers ?? ROUTERS;
210
237
  const router = edge.router !== void 0 && routers[edge.router] || straight;
211
- const points = router({ from, to, waypoints: edge.waypoints ?? [] });
238
+ const points = router({ from, to, waypoints });
212
239
  return points.length < 2 ? null : polylineFromPoints(points);
213
240
  };
214
241
  }
@@ -462,6 +489,58 @@ function diagramPorts(opts) {
462
489
  contribution: createDiagramContribution(opts)
463
490
  };
464
491
  }
492
+
493
+ // src/groupTree.ts
494
+ function nestGroups(declared, isNode) {
495
+ const groupIds = new Set(declared.map((g) => g.id));
496
+ const parentOf = /* @__PURE__ */ new Map();
497
+ const claimedNodes = /* @__PURE__ */ new Set();
498
+ const direct = /* @__PURE__ */ new Map();
499
+ const isAncestorOrSelf = (candidate, of) => {
500
+ for (let at = of; at !== void 0; at = parentOf.get(at)) {
501
+ if (at === candidate) return true;
502
+ }
503
+ return false;
504
+ };
505
+ for (const group of declared) {
506
+ const own = [];
507
+ for (const id of group.members) {
508
+ if (isNode(id)) {
509
+ if (claimedNodes.has(id)) continue;
510
+ claimedNodes.add(id);
511
+ own.push(id);
512
+ } else if (groupIds.has(id) && !parentOf.has(id) && !isAncestorOrSelf(id, group.id)) {
513
+ parentOf.set(id, group.id);
514
+ own.push(id);
515
+ }
516
+ }
517
+ direct.set(group.id, own);
518
+ }
519
+ const flat = /* @__PURE__ */ new Map();
520
+ const flatten = (id) => {
521
+ const known = flat.get(id);
522
+ if (known !== void 0) return known;
523
+ const out = [];
524
+ for (const member of direct.get(id)) {
525
+ if (groupIds.has(member)) out.push(...flatten(member));
526
+ else out.push(member);
527
+ }
528
+ flat.set(id, out);
529
+ return out;
530
+ };
531
+ const live = new Set(declared.filter((g) => flatten(g.id).length > 0).map((g) => g.id));
532
+ return declared.filter((g) => live.has(g.id)).map((g) => {
533
+ const parent = parentOf.get(g.id);
534
+ return {
535
+ id: g.id,
536
+ direct: direct.get(g.id).filter((id) => !groupIds.has(id) || live.has(id)),
537
+ members: flat.get(g.id),
538
+ ...parent !== void 0 ? { parent } : {}
539
+ };
540
+ });
541
+ }
542
+
543
+ // src/graph.ts
465
544
  function buildGraph(source, opts = {}) {
466
545
  const geometry = opts.geometry ?? AUTO_POSE_DESCRIPTOR$1;
467
546
  const nodes = [];
@@ -495,17 +574,19 @@ function buildGraph(source, opts = {}) {
495
574
  nodes.push(entry);
496
575
  byId.set(entry.id, entry);
497
576
  }
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;
577
+ const insets = new Map(groupNodes.map((g) => [g.id, g.inset]));
578
+ const live = nestGroups(groupNodes, (id) => byId.has(id)).map((g) => {
579
+ for (const id of g.direct) {
580
+ const node = byId.get(id);
581
+ if (node !== void 0) node.group = g.id;
505
582
  }
506
- groups.push({ ...g, members });
507
- }
508
- const live = groups.filter((g) => g.members.length > 0);
583
+ return {
584
+ id: g.id,
585
+ members: g.members,
586
+ inset: insets.get(g.id),
587
+ ...g.parent !== void 0 ? { parent: g.parent } : {}
588
+ };
589
+ });
509
590
  const known = (id) => byId.has(id) || live.some((g) => g.id === id);
510
591
  const edges = expandGroupEdges(candidates.filter((e) => known(e.from) && known(e.to)), live);
511
592
  const out = /* @__PURE__ */ new Map();
@@ -532,39 +613,64 @@ function push(index, key, edge) {
532
613
 
533
614
  // src/dataGroups.ts
534
615
  function resolveGroups(groups, ids, nodeIds, opts) {
535
- const claimed = /* @__PURE__ */ new Set();
536
- const out = [];
537
616
  for (const group of groups) {
538
617
  if (ids.has(group.id)) throw new Error(`diagramScene: group id "${group.id}" is already a node or group id`);
539
618
  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;
619
+ }
620
+ const declared = new Map(groups.map((g) => [g.id, g]));
621
+ const nested = nestGroups(groups, (id) => nodeIds.has(id));
622
+ const depthOf = /* @__PURE__ */ new Map();
623
+ const byId = new Map(nested.map((g) => [g.id, g]));
624
+ const depth = (id) => {
625
+ const parent = byId.get(id).parent;
626
+ let d = depthOf.get(id);
627
+ if (d === void 0) depthOf.set(id, d = parent === void 0 ? 0 : depth(parent) + 1);
628
+ return d;
629
+ };
630
+ return nested.map((g, i) => ({ g, i })).sort((a, b) => depth(a.g.id) - depth(b.g.id) || a.i - b.i).map(({ g }) => {
631
+ const group = declared.get(g.id);
543
632
  const pad = group.padding ?? opts.padding;
544
633
  const title = group.label ? opts.title(group.label) : null;
545
634
  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;
635
+ return {
636
+ group,
637
+ direct: g.direct,
638
+ members: g.members,
639
+ inset,
640
+ pad,
641
+ title,
642
+ ...g.parent !== void 0 ? { parent: g.parent } : {}
643
+ };
644
+ });
549
645
  }
550
646
  function groupSpecs(groups, placedAt, styleOf) {
647
+ const byId = new Map(groups.map((g) => [g.group.id, g]));
648
+ const boxes = /* @__PURE__ */ new Map();
649
+ const boxOf = (id) => {
650
+ const known = boxes.get(id);
651
+ if (known !== void 0) return known;
652
+ const g = byId.get(id);
653
+ const rs = g.direct.map((m) => byId.has(m) ? boxOf(m) : placedAt.get(m));
654
+ const x = Math.min(...rs.map((r) => r.x)) - g.inset.left;
655
+ const y = Math.min(...rs.map((r) => r.y)) - g.inset.top;
656
+ const box = {
657
+ x,
658
+ y,
659
+ width: Math.max(...rs.map((r) => r.x + r.width)) + g.inset.right - x,
660
+ height: Math.max(...rs.map((r) => r.y + r.height)) + g.inset.bottom - y
661
+ };
662
+ boxes.set(id, box);
663
+ return box;
664
+ };
551
665
  const specs = [];
552
666
  for (const g of groups) {
553
667
  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;
668
+ const { x, y, width, height } = boxOf(g.group.id);
557
669
  specs.push({
558
670
  id: g.group.id,
559
671
  kind: "leaf",
560
672
  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
- },
673
+ pose: { x, y, width, height },
568
674
  data: {
569
675
  diagram: { outline: "rect", group: { inset: g.inset } },
570
676
  fill: { color: style.fill },
@@ -574,7 +680,7 @@ function groupSpecs(groups, placedAt, styleOf) {
574
680
  ...style.dash ? { dash: style.dash } : {}
575
681
  }
576
682
  },
577
- dependsOn: g.members,
683
+ dependsOn: g.direct,
578
684
  derivePose: GROUP_DERIVE_POSE
579
685
  });
580
686
  if (g.title === null || !g.group.label) continue;
@@ -665,9 +771,19 @@ function diagramScene(data, opts = {}) {
665
771
  );
666
772
  return { id: n.id, bounds: pose, pinned: n.pinned === true };
667
773
  });
668
- const graphGroups = groups.map((g) => ({ id: g.group.id, members: g.members, inset: g.inset }));
774
+ const graphGroups = groups.map((g) => ({
775
+ id: g.group.id,
776
+ members: g.members,
777
+ inset: g.inset,
778
+ ...g.parent !== void 0 ? { parent: g.parent } : {}
779
+ }));
669
780
  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;
781
+ for (const g of groups) {
782
+ for (const m of g.direct) {
783
+ const seed = seedOf.get(m);
784
+ if (seed !== void 0) seed.group = g.group.id;
785
+ }
786
+ }
671
787
  const graph = graphOf(
672
788
  seeds,
673
789
  expandGroupEdges(edges.map((e) => ({ id: edgeIdOf(e), from: e.from, to: e.to })), graphGroups),
@@ -1003,11 +1119,14 @@ function diagramShape(opts = {}) {
1003
1119
  const outline = outlineOf(node);
1004
1120
  if (outline === void 0) return [];
1005
1121
  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;
1122
+ const box = geometry.getBounds(pose);
1123
+ const declaredFill = d.fill === void 0 ? opts.defaultFill : d.fill;
1124
+ const declaredStroke = d.stroke === void 0 ? opts.defaultStroke : d.stroke;
1125
+ const fill = declaredFill && commandFill(declaredFill, box);
1126
+ const stroke = declaredStroke && commandStroke(declaredStroke, box);
1008
1127
  return [{
1009
1128
  kind: "path",
1010
- path: outlinePath(outline, geometry.getBounds(pose)),
1129
+ path: outlinePath(outline, box),
1011
1130
  ...fill ? { fill } : {},
1012
1131
  ...stroke ? { stroke } : {}
1013
1132
  }];
@@ -1179,6 +1298,6 @@ function fitDiagram(specs, size, fit = {}) {
1179
1298
  });
1180
1299
  }
1181
1300
 
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 };
1301
+ 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
1302
  //# sourceMappingURL=index.js.map
1184
1303
  //# sourceMappingURL=index.js.map