@weasel-js/diagram 1.11.1 → 1.13.0-rc.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 CHANGED
@@ -415,7 +415,7 @@ the `insert` dep it displaced, so core's rect, ellipse and text tools keep worki
415
415
  ### Containers and pools
416
416
 
417
417
  A container is a titled box that holds other shapes; a pool is a header and a
418
- stack of lanes, each holding shapes. Both are core `ContainerNode`s carrying a
418
+ stack of lanes, each holding shapes. Both are core `GroupNode`s carrying a
419
419
  `container` trait (`diagramContainerOf`): a role of `container`, `pool`, or
420
420
  `lane`, and a header. A region's title is a text child named
421
421
  `<id>/title`, so double-clicking a header renames it.
@@ -1,9 +1,9 @@
1
- import { createAvoidRouter, placeLabels, edgeHops, DEFAULT_HOP_SIZE, HOPS_DOC_KEY, simplifyStops } from './chunk-T6DQT553.js';
2
- import { edgeRouter, registerMarkShape, withDiagramRegistry, diagramGroupOf, diagramLabelOf, editEdgeLabelBinding, editEdgeLabelAction, diagramContainerOf, headerBox, labelFollowsLayout, buildGraph, LABEL_DRAG_AFFORDANCE_KIND, EDIT_EDGE_LABEL_ACTION_ID, labelDragLayer, laneOutlineOf, routerOf, labelScratchOf, ROUTERS, avoid, LABEL_PLACEMENT, editableLabels, titleIdOf, orthogonal, diagramAnchorOf, MIN_LANE_SIZE, endAims, resolveEnd, EDGE_DERIVE_PATH, titlePose, regionSpecs, DIAGRAM_THEME_KEY, diagramThemeOf, boxCenter, themeLabelFamily, roleEdgeStyle, roleStyle, themeLabelFont, firstLanes, DEFAULT_LANE_SIZE, bodyBox } from './chunk-43LDZ6L4.js';
3
- import { diagramEdgeOf, isPromise, dataKeyReader, diagramNodeOf, portsOf, portSitesOf, outlinePath, samePoints, layoutWritesRoute, plannerPlan, planOf, layered, forceRelaxation, center, layoutOwnsPort, chain, force, tree, portOf, labelCenter, labelSpotAt, isPinned, layoutOwnsRoute } from './chunk-IM6DHWZO.js';
4
- import { bodyPredicate, dragPanContribution, WeaselProvider, useScene, useSelection, useLatest, SceneCanvas, useDepSource, fitViewToBounds, registerNodeShape, reconcileSpecs, nodeGates, effectivePose, pagesBounds, pagesLayer, AUTO_POSE_DESCRIPTOR, usePoseRun, drawnBoundsScene, pointAlongPath, drawnAABBOf, RECT_POSE_DESCRIPTOR, commandFill, commandStroke, translatePoseViaDescriptor, composeAffordanceLayer, createResizeAction, createDeleteAction, polylineFromPoints, SNAP_ROLES, ActionDisabledReason, paintsLayer, viewToTransform, ellipsePath, snapMods, documentDepOf, carriedDescendants, sceneCarrySource, derivedDepOf, PathBuilder, createInsertOp, createSetDataOp, documentPose } from '@weasel-js/core';
1
+ import { createAvoidRouter, placeLabels, edgeHops, DEFAULT_HOP_SIZE, HOPS_DOC_KEY, simplifyStops } from './chunk-Y72FK3Y7.js';
2
+ import { edgeRouter, registerMarkShape, withDiagramRegistry, diagramLabelOf, editEdgeLabelBinding, editEdgeLabelAction, diagramGroupOf, diagramContainerOf, headerBox, labelFollowsLayout, buildGraph, LABEL_DRAG_AFFORDANCE_KIND, EDIT_EDGE_LABEL_ACTION_ID, labelDragLayer, laneOutlineOf, routerOf, labelScratchOf, ROUTERS, avoid, LABEL_PLACEMENT, editableLabels, titleIdOf, orthogonal, diagramAnchorOf, MIN_LANE_SIZE, endAims, resolveEnd, EDGE_DERIVE_PATH, titlePose, regionSpecs, DIAGRAM_THEME_KEY, diagramThemeOf, boxCenter, themeLabelFamily, roleEdgeStyle, roleStyle, themeLabelFont, firstLanes, DEFAULT_LANE_SIZE, bodyBox } from './chunk-QU4QZUC6.js';
3
+ import { diagramEdgeOf, isPromise, dataKeyReader, diagramNodeOf, portsOf, portSitesOf, outlinePath, samePoints, layoutWritesRoute, plannerPlan, planOf, layered, forceRelaxation, center, layoutOwnsPort, chain, force, tree, portOf, labelCenter, labelSpotAt, isPinned, layoutOwnsRoute } from './chunk-L4H444QI.js';
4
+ import { bodyPredicate, dragPanContribution, WeaselProvider, useScene, useSelection, useLatest, SceneCanvas, useDepSource, registerNodeShape, reconcileSpecs, nodeGates, effectivePose, pagesBounds, pagesLayer, AUTO_POSE_DESCRIPTOR, usePoseRun, drawnBoundsScene, pointAlongPath, drawnAABBOf, RECT_POSE_DESCRIPTOR, resolveFill, resolveStroke, commandFill, commandStroke, fitViewToBounds, translatePoseViaDescriptor, composeAffordanceLayer, createResizeAction, createDeleteAction, polylineFromPoints, SNAP_ROLES, ActionDisabledReason, paintsLayer, viewToTransform, ellipsePath, snapMods, documentDepOf, carriedDescendants, sceneCarrySource, derivedDepOf, PathBuilder, createInsertOp, createSetDataOp, documentPose } from '@weasel-js/core';
5
5
  import { AUTO_POSE_DESCRIPTOR as AUTO_POSE_DESCRIPTOR$1, rotatePoint } from '@weasel-js/core/math';
6
- import { placeRect, pathContainsPoint } from '@weasel-js/geom';
6
+ import { clampRectWithin, placeRect, pathContainsPoint } from '@weasel-js/geom';
7
7
  import { useLatest as useLatest$1, adoptCssOnce } from '@weasel-js/react';
8
8
  import { forwardRef, useMemo, useEffect, useState, useRef, useCallback, useImperativeHandle, useLayoutEffect } from 'react';
9
9
  import '@weasel-js/animator';
@@ -181,7 +181,8 @@ var DEFAULT_ROUTER = "straight";
181
181
  function connectBinding(actionId = CONNECT_ACTION_ID) {
182
182
  return {
183
183
  spec: { kind: "drag", target: `affordance:${PORT_AFFORDANCE_KIND}` },
184
- actionId
184
+ actionId,
185
+ opts: { id: `${actionId}.drag` }
185
186
  };
186
187
  }
187
188
  var GRAB_PORT_ACTION_ID = "diagram.grabPort";
@@ -197,8 +198,8 @@ function grabPortAction(id = GRAB_PORT_ACTION_ID) {
197
198
  }
198
199
  function grabPortBindings(actionId = GRAB_PORT_ACTION_ID, kind = PORT_AFFORDANCE_KIND) {
199
200
  return [
200
- { spec: { kind: "pointerDown", target: `affordance:${kind}` }, actionId },
201
- { spec: { kind: "click", target: `affordance:${kind}` }, actionId }
201
+ { spec: { kind: "pointerDown", target: `affordance:${kind}` }, actionId, opts: { id: `${actionId}.pointerDown` } },
202
+ { spec: { kind: "click", target: `affordance:${kind}` }, actionId, opts: { id: `${actionId}.click` } }
202
203
  ];
203
204
  }
204
205
  function commitEdgeToScene(edge, ctx, opts = {}) {
@@ -211,7 +212,7 @@ function commitEdgeToScene(edge, ctx, opts = {}) {
211
212
  };
212
213
  const source = scene.get(edge.from.nodeId);
213
214
  scene.add({
214
- kind: "leaf",
215
+ kind: "mark",
215
216
  layer: source?.layer ?? scene.layers[0].id,
216
217
  pose: { x: 0, y: 0, width: 0, height: 0 },
217
218
  data: { diagram: trait, stroke: opts.stroke ?? DEFAULT_EDGE_STROKE },
@@ -314,7 +315,7 @@ function diagramDropParentAt(scene, point, role) {
314
315
  const walk = (ids) => {
315
316
  for (const id of ids) {
316
317
  const node = scene.get(id);
317
- if (node === void 0 || node.kind !== "container") continue;
318
+ if (node === void 0) continue;
318
319
  const trait = diagramContainerOf(node);
319
320
  const b = bodyBox(node.pose, trait?.header);
320
321
  const inside2 = point.x >= b.x && point.x < b.x + b.width && point.y >= b.y && point.y < b.y + b.height;
@@ -772,8 +773,8 @@ function diagramContainers(opts) {
772
773
  grabPortAction(GRAB_LANE_ACTION_ID)
773
774
  ],
774
775
  bindings: [
775
- { spec: { kind: "drag", target }, actionId: LANE_RESIZE_ACTION_ID },
776
- { spec: { kind: "click", target }, actionId: ADD_LANE_ACTION_ID },
776
+ { spec: { kind: "drag", target }, actionId: LANE_RESIZE_ACTION_ID, opts: { id: "weasel-diagram-containers.drag" } },
777
+ { spec: { kind: "click", target }, actionId: ADD_LANE_ACTION_ID, opts: { id: "weasel-diagram-containers.click" } },
777
778
  ...grabPortBindings(GRAB_LANE_ACTION_ID, LANE_AFFORDANCE_KIND).filter((b) => b.spec.kind !== "click")
778
779
  ]
779
780
  }
@@ -798,7 +799,7 @@ function editContainerTitleAction(commit) {
798
799
  label: "Edit container title",
799
800
  group: "diagram",
800
801
  requires: ["scene", "nodeAtPoint"],
801
- eligible: { mode: { in: ["normal", "isolation"] } },
802
+ eligible: { mode: "normal" },
802
803
  invoker: {
803
804
  timing: "immediate",
804
805
  run(deps, params) {
@@ -811,7 +812,11 @@ function editContainerTitleAction(commit) {
811
812
  }
812
813
  var onBody = bodyPredicate((target) => target === "selected-body" || target === "unselected-body");
813
814
  function editContainerTitleBinding() {
814
- return { spec: { kind: "doubleClick", target: { kindOf: onBody } }, actionId: EDIT_CONTAINER_TITLE_ACTION_ID };
815
+ return {
816
+ spec: { kind: "doubleClick", target: { kindOf: onBody } },
817
+ actionId: EDIT_CONTAINER_TITLE_ACTION_ID,
818
+ opts: { id: `${EDIT_CONTAINER_TITLE_ACTION_ID}.doubleClick` }
819
+ };
815
820
  }
816
821
 
817
822
  // src/hops/hopsAction.ts
@@ -886,7 +891,7 @@ function createLabelDragAction(opts = {}) {
886
891
  label: "Move label",
887
892
  group: "diagram",
888
893
  requires: ["scene"],
889
- eligible: { mode: { in: ["normal", "isolation"] } },
894
+ eligible: { mode: "normal" },
890
895
  invoker: {
891
896
  timing: "ongoing",
892
897
  start(ctx) {
@@ -1573,8 +1578,8 @@ function diagramWaypoints(opts) {
1573
1578
  grabPortAction(GRAB_WAYPOINT_ACTION_ID)
1574
1579
  ],
1575
1580
  bindings: [
1576
- { spec: { kind: "drag", target }, actionId },
1577
- { spec: { kind: "doubleClick", target }, actionId: REMOVE_WAYPOINT_ACTION_ID },
1581
+ { spec: { kind: "drag", target }, actionId, opts: { id: "weasel-diagram-waypoints.drag" } },
1582
+ { spec: { kind: "doubleClick", target }, actionId: REMOVE_WAYPOINT_ACTION_ID, opts: { id: "weasel-diagram-waypoints.doubleClick" } },
1578
1583
  ...grabPortBindings(GRAB_WAYPOINT_ACTION_ID, WAYPOINT_AFFORDANCE_KIND)
1579
1584
  ]
1580
1585
  }
@@ -1616,10 +1621,10 @@ function diagramLabelDrag(opts) {
1616
1621
  selectLabelEdgeAction()
1617
1622
  ],
1618
1623
  bindings: [
1619
- { spec: { kind: "drag", target }, actionId },
1620
- { spec: { kind: "pointerDown", target }, actionId: GRAB_LABEL_ACTION_ID },
1621
- { spec: { kind: "click", target, mods: ANY_MODIFIERS }, actionId: SELECT_LABEL_EDGE_ACTION_ID },
1622
- { spec: { kind: "doubleClick", target }, actionId: EDIT_EDGE_LABEL_ACTION_ID }
1624
+ { spec: { kind: "drag", target }, actionId, opts: { id: "weasel-diagram-label-drag.drag" } },
1625
+ { spec: { kind: "pointerDown", target }, actionId: GRAB_LABEL_ACTION_ID, opts: { id: "weasel-diagram-label-drag.pointerDown" } },
1626
+ { spec: { kind: "click", target, mods: ANY_MODIFIERS }, actionId: SELECT_LABEL_EDGE_ACTION_ID, opts: { id: "weasel-diagram-label-drag.click" } },
1627
+ { spec: { kind: "doubleClick", target }, actionId: EDIT_EDGE_LABEL_ACTION_ID, opts: { id: "weasel-diagram-label-drag.doubleClick" } }
1623
1628
  ]
1624
1629
  }
1625
1630
  };
@@ -1957,7 +1962,7 @@ function createReconnectAction(opts) {
1957
1962
  };
1958
1963
  }
1959
1964
  function reconnectBinding(actionId = RECONNECT_ACTION_ID) {
1960
- return { spec: { kind: "drag", target: `affordance:${EDGE_END_AFFORDANCE_KIND}` }, actionId };
1965
+ return { spec: { kind: "drag", target: `affordance:${EDGE_END_AFFORDANCE_KIND}` }, actionId, opts: { id: `${actionId}.drag` } };
1961
1966
  }
1962
1967
  function diagramEdgeEnds(opts) {
1963
1968
  const actionId = opts.id ?? RECONNECT_ACTION_ID;
@@ -2150,7 +2155,7 @@ function createReorderAction(opts) {
2150
2155
  }
2151
2156
  var ANYWHERE = bodyPredicate(() => true);
2152
2157
  function reorderBinding(actionId = REORDER_ACTION_ID, on = ANYWHERE) {
2153
- return { spec: { kind: "drag", target: { kindOf: on } }, actionId };
2158
+ return { spec: { kind: "drag", target: { kindOf: on } }, actionId, opts: { id: on === ANYWHERE ? `${actionId}.drag` : `${actionId}.bodyDrag` } };
2154
2159
  }
2155
2160
  function reorderContribution(opts) {
2156
2161
  const actionId = opts.id ?? REORDER_ACTION_ID;
@@ -2179,16 +2184,10 @@ function screenRectOf(rect, view) {
2179
2184
  };
2180
2185
  }
2181
2186
  var CENTER = "center";
2182
- function place(el, overlay, anchor, width, height) {
2187
+ function place(el, overlay, anchor, view) {
2183
2188
  const size = { width: el.offsetWidth, height: el.offsetHeight };
2184
2189
  const placement = overlay.placement ?? CENTER;
2185
- const at = placement === CENTER ? { x: anchor.x + (anchor.width - size.width) / 2, y: anchor.y + (anchor.height - size.height) / 2 } : placeRect({
2186
- anchor,
2187
- overlay: size,
2188
- boundary: { x: 0, y: 0, width, height },
2189
- placement,
2190
- offset: overlay.offset ?? 0
2191
- }).rect;
2190
+ const at = placement === CENTER ? { x: anchor.x + (anchor.width - size.width) / 2, y: anchor.y + (anchor.height - size.height) / 2 } : clampRectWithin(placeRect({ anchor, overlay: size, boundary: view, placement, offset: overlay.offset ?? 0 }).rect, view);
2192
2191
  el.style.transform = `translate(${Math.round(at.x)}px, ${Math.round(at.y)}px)`;
2193
2192
  }
2194
2193
  function useOverlayPlacement(canvasRef, scene, overlays) {
@@ -2207,7 +2206,12 @@ function useOverlayPlacement(canvasRef, scene, overlays) {
2207
2206
  if (world === null) continue;
2208
2207
  const rect = screenRectOf(world, view);
2209
2208
  const anchor = { ...rect, x: rect.x + surface.offsetLeft, y: rect.y + surface.offsetTop };
2210
- place(el, overlay, anchor, surface.offsetWidth, surface.offsetHeight);
2209
+ place(el, overlay, anchor, {
2210
+ x: surface.offsetLeft,
2211
+ y: surface.offsetTop,
2212
+ width: surface.offsetWidth,
2213
+ height: surface.offsetHeight
2214
+ });
2211
2215
  }
2212
2216
  }, [canvasRef, scene, current]);
2213
2217
  useLayoutEffect(() => {
@@ -2309,10 +2313,10 @@ function groupReorderSlots(deps, at) {
2309
2313
  })));
2310
2314
  }
2311
2315
  var STEPS = [
2312
- { key: "ArrowUp", axis: "y", step: -1 },
2313
- { key: "ArrowDown", axis: "y", step: 1 },
2314
- { key: "ArrowLeft", axis: "x", step: -1 },
2315
- { key: "ArrowRight", axis: "x", step: 1 }
2316
+ { key: "ArrowUp", role: "up", axis: "y", step: -1 },
2317
+ { key: "ArrowDown", role: "down", axis: "y", step: 1 },
2318
+ { key: "ArrowLeft", role: "left", axis: "x", step: -1 },
2319
+ { key: "ArrowRight", role: "right", axis: "x", step: 1 }
2316
2320
  ];
2317
2321
  function createStepReorderAction(report) {
2318
2322
  const selectedTarget = (deps) => targetAt(deps, deps?.selection?.get()[0]);
@@ -2320,9 +2324,9 @@ function createStepReorderAction(report) {
2320
2324
  id: STEP_REORDER_ACTION_ID,
2321
2325
  label: "Move along",
2322
2326
  group: "diagram",
2323
- defaultBinding: STEPS.map(({ key, axis, step }) => ({
2327
+ defaultBinding: STEPS.map(({ key, role, axis, step }) => ({
2324
2328
  spec: { kind: "key", key, mods: { alt: true } },
2325
- opts: { params: { axis, step } }
2329
+ opts: { id: `${STEP_REORDER_ACTION_ID}.${role}`, params: { axis, step } }
2326
2330
  })),
2327
2331
  requires: ["scene", "selection"],
2328
2332
  invoker: {
@@ -2786,14 +2790,14 @@ function pathHold(scene) {
2786
2790
  const held = /* @__PURE__ */ new Map();
2787
2791
  const release = (id, entry) => {
2788
2792
  held.delete(id);
2789
- if (scene.overrides.get(id) === entry) scene.overrides.clear(id);
2793
+ scene.overrides.clear(id, entry);
2790
2794
  };
2791
2795
  return {
2792
2796
  show(paths) {
2793
2797
  for (const [id, entry] of held) if (!paths.has(id)) release(id, entry);
2794
2798
  for (const [id, path] of paths) {
2795
2799
  const entry = held.get(id);
2796
- if (entry !== void 0 && scene.overrides.get(id) === entry) {
2800
+ if (entry !== void 0 && scene.overrides.has(id, entry)) {
2797
2801
  entry.path = path;
2798
2802
  continue;
2799
2803
  }
@@ -2967,13 +2971,13 @@ function diagramShape(opts = {}) {
2967
2971
  return {
2968
2972
  id: opts.id ?? "diagram:outline",
2969
2973
  matches: (node) => outlineOf(node) !== void 0,
2970
- paint: (node, pose) => {
2974
+ paint: (node, pose, ctx) => {
2971
2975
  const outline = outlineOf(node);
2972
2976
  if (outline === void 0) return [];
2973
2977
  const d = node.data;
2974
2978
  const box = geometry.getBounds(pose);
2975
- const declaredFill = d.fill === void 0 ? opts.defaultFill : d.fill;
2976
- const declaredStroke = d.stroke === void 0 ? opts.defaultStroke : d.stroke;
2979
+ const declaredFill = resolveFill(d.fill, ctx?.inherited?.fill);
2980
+ const declaredStroke = resolveStroke(d.stroke, ctx?.inherited?.stroke);
2977
2981
  const fill = declaredFill && commandFill(declaredFill, box);
2978
2982
  const stroke = declaredStroke && commandStroke(declaredStroke, box);
2979
2983
  const header = headerBox(box, diagramContainerOf(node)?.header);
@@ -2999,6 +3003,10 @@ function dividerPath(box, header) {
2999
3003
  function registerDiagramShape(opts = {}) {
3000
3004
  return registerNodeShape(diagramShape(opts), { priority: "high" });
3001
3005
  }
3006
+
3007
+ // src/bodyNode.ts
3008
+ var isDiagramBody = (node) => diagramContainerOf(node) === null && diagramGroupOf(node) === null && dataKeyReader(node) !== null;
3009
+ var isBodyOrRegion = (node) => diagramContainerOf(node) !== null || isDiagramBody(node);
3002
3010
  function useMirroredSelection(selection, selected, onSelect) {
3003
3011
  const want = selected ?? null;
3004
3012
  const pushed = useRef(void 0);
@@ -3079,10 +3087,11 @@ function readScene(scene) {
3079
3087
  const nodes = /* @__PURE__ */ new Map();
3080
3088
  const routes = /* @__PURE__ */ new Map();
3081
3089
  for (const n of scene.renderOrderNodes()) {
3082
- if (n.kind === "container") {
3083
- if (diagramContainerOf(n) === null) nodes.set(n.id, { x: n.pose.x, y: n.pose.y, parent: n.parent });
3090
+ if (isDiagramBody(n)) {
3091
+ nodes.set(n.id, { x: n.pose.x, y: n.pose.y, parent: n.parent });
3084
3092
  continue;
3085
3093
  }
3094
+ if (diagramContainerOf(n) !== null) continue;
3086
3095
  const edge = diagramEdgeOf(n);
3087
3096
  if (edge !== null) routes.set(n.id, routeOf2(edge));
3088
3097
  }
@@ -3095,7 +3104,7 @@ function diagramLayoutOf(specs) {
3095
3104
  for (const s of specs) {
3096
3105
  const id = s.id;
3097
3106
  if (id === void 0) continue;
3098
- if (s.kind === "container") {
3107
+ if (isBodyOrRegion(s)) {
3099
3108
  const region = diagramContainerOf(s);
3100
3109
  const pinned = isPinned({ id, kind: s.kind, data: s.data });
3101
3110
  if (region !== null) {
@@ -3103,7 +3112,7 @@ function diagramLayoutOf(specs) {
3103
3112
  const { x, y, width, height } = s.pose;
3104
3113
  containers.push({ kind: "box", id, box: { x, y, width, height } });
3105
3114
  }
3106
- } else if (diagramGroupOf(s) === null && !pinned) moves.push({ id, x: s.pose.x, y: s.pose.y });
3115
+ } else if (!pinned) moves.push({ id, x: s.pose.x, y: s.pose.y });
3107
3116
  continue;
3108
3117
  }
3109
3118
  const edge = diagramEdgeOf(s);
@@ -3175,7 +3184,7 @@ function laneResizeReports(scene, resize) {
3175
3184
  const { x, y, width, height } = pose;
3176
3185
  if (region !== null) {
3177
3186
  if (region.role === "container" && region.fitted !== true) boxes.push({ kind: "box", id, box: { x, y, width, height } });
3178
- } else if (node.kind === "container" && dataKeyReader(node) !== null) moves.push({ id, x, y });
3187
+ } else if (isDiagramBody(node)) moves.push({ id, x, y });
3179
3188
  }
3180
3189
  return { lanes: { kind: "lanes", pool: pool.id, lanes }, moves, boxes };
3181
3190
  }
@@ -3357,12 +3366,94 @@ function usePagesView(pages) {
3357
3366
  };
3358
3367
  }, [placed, fill]);
3359
3368
  }
3369
+ function diagramBounds(specs, extra) {
3370
+ const boxes = specs.filter((s) => isBodyOrRegion(s) || diagramGroupOf(s) !== null).map((s) => s.pose);
3371
+ if (extra !== void 0) boxes.push(extra);
3372
+ if (boxes.length === 0) return null;
3373
+ const x = Math.min(...boxes.map((b) => b.x));
3374
+ const y = Math.min(...boxes.map((b) => b.y));
3375
+ return {
3376
+ x,
3377
+ y,
3378
+ width: Math.max(...boxes.map((b) => b.x + b.width)) - x,
3379
+ height: Math.max(...boxes.map((b) => b.y + b.height)) - y
3380
+ };
3381
+ }
3382
+ function fitDiagram(specs, size, fit = {}, extra) {
3383
+ const bounds = diagramBounds(specs, extra);
3384
+ if (bounds === null) return void 0;
3385
+ return fitViewToBounds(bounds, size, { x: 0, y: 0, scale: { x: 1, y: 1 } }, {
3386
+ maxScale: fit.maxScale ?? 1,
3387
+ ...fit.minScale !== void 0 ? { minScale: fit.minScale } : {},
3388
+ ...fit.anchor !== void 0 ? { anchor: fit.anchor } : {},
3389
+ ...fit.padding !== void 0 ? { padding: fit.padding } : {}
3390
+ });
3391
+ }
3392
+ var IDENTITY = { x: 0, y: 0, scale: { x: 1, y: 1 } };
3393
+ var sameView = (a, b) => a.x === b.x && a.y === b.y && a.scale.x === b.scale.x && a.scale.y === b.scale.y;
3394
+ function inView(inner, view, size) {
3395
+ const x1 = view.x + size.width / view.scale.x;
3396
+ const y1 = view.y + size.height / view.scale.y;
3397
+ return inner.x >= view.x && inner.y >= view.y && inner.x + inner.width <= x1 && inner.y + inner.height <= y1;
3398
+ }
3399
+ function useDiagramFit(args) {
3400
+ const { canvasRef, specs, controlled, size, fit, extra } = args;
3401
+ const unsized = size.width === 0 || size.height === 0;
3402
+ const defaultView = useMemo(
3403
+ () => controlled || unsized ? void 0 : fitDiagram(specs, size, fit, extra),
3404
+ // A later `specs` is the overflow check's to answer.
3405
+ // eslint-disable-next-line react-hooks/exhaustive-deps
3406
+ [controlled, unsized, size.width, size.height, fit.minScale, fit.maxScale, fit.anchor, fit.padding, extra]
3407
+ );
3408
+ const [later, setLater] = useState(null);
3409
+ const target = later !== null && later.over === defaultView ? later.view : defaultView;
3410
+ const fitted = useRef(target);
3411
+ const opts = useLatest({ controlled, unsized, size, fit, extra, defaultView, current: args.current });
3412
+ useEffect(() => {
3413
+ const canvas = canvasRef.current;
3414
+ const was = fitted.current;
3415
+ if (canvas === null || target === void 0 || was === target) return;
3416
+ if (!sameView(canvas.getView(), was ?? IDENTITY)) return;
3417
+ fitted.current = target;
3418
+ canvas.setView(target);
3419
+ }, [canvasRef, target]);
3420
+ const first = useRef(true);
3421
+ useEffect(() => {
3422
+ if (first.current) {
3423
+ first.current = false;
3424
+ return;
3425
+ }
3426
+ const { controlled: c, unsized: u, size: box, fit: f, extra: more, defaultView: over } = opts.current;
3427
+ const canvas = canvasRef.current;
3428
+ const bounds = diagramBounds(specs, more);
3429
+ if (c || u || canvas === null || bounds === null || inView(bounds, canvas.getView(), box)) return;
3430
+ setLater({ view: fitDiagram(specs, box, f, more), over });
3431
+ }, [specs, canvasRef, opts]);
3432
+ return useMemo(() => {
3433
+ const now = () => {
3434
+ const { size: box, fit: f, extra: more, current } = opts.current;
3435
+ return fitDiagram(current(), box, f, more);
3436
+ };
3437
+ return {
3438
+ defaultView,
3439
+ settle: () => {
3440
+ if (opts.current.controlled) return;
3441
+ setLater({ view: now(), over: opts.current.defaultView });
3442
+ },
3443
+ fitNow: () => {
3444
+ const canvas = canvasRef.current;
3445
+ const view = now();
3446
+ if (canvas === null || view === void 0) return;
3447
+ fitted.current = view;
3448
+ canvas.setView(view);
3449
+ }
3450
+ };
3451
+ }, [defaultView, canvasRef, opts]);
3452
+ }
3360
3453
  var VIEW_RULE = ':where([data-wzl-diagram]) { position: relative; width: fit-content; } :where([data-wzl-diagram~="fill-x"]) { width: 100%; } :where([data-wzl-diagram~="fill-y"]) { height: 100%; } :where([data-wzl-diagram]) > canvas { display: block; } :where([data-wzl-diagram-overlay]) { position: absolute; left: 0; top: 0; } :where([data-wzl-diagram-overlay][hidden]) { display: none; }';
3361
3454
  var adoptViewRule = (el) => {
3362
3455
  if (el !== null) adoptCssOnce(el, VIEW_RULE);
3363
3456
  };
3364
- var IDENTITY = { x: 0, y: 0, scale: { x: 1, y: 1 } };
3365
- var sameView = (a, b) => a.x === b.x && a.y === b.y && a.scale.x === b.scale.x && a.scale.y === b.scale.y;
3366
3457
  var DiagramView = forwardRef(function DiagramView2(props, ref) {
3367
3458
  return /* @__PURE__ */ jsx(WeaselProvider, { children: /* @__PURE__ */ jsx(Inner, { ...props, apiRef: ref }) });
3368
3459
  });
@@ -3433,24 +3524,22 @@ function Inner({
3433
3524
  const controlled = view !== void 0;
3434
3525
  const fills = width === void 0 || height === void 0;
3435
3526
  const [measured, setMeasured] = useState(null);
3436
- const boxWidth = width ?? measured?.width ?? 0;
3437
- const boxHeight = height ?? measured?.height ?? 0;
3438
- const unsized = boxWidth === 0 || boxHeight === 0;
3439
- const fitOpts = { minScale, maxScale, anchor, padding: fitPadding };
3440
3527
  const { layers, box: pagesBox } = usePagesView(pages);
3441
- const defaultView = useMemo(
3442
- () => controlled || unsized ? void 0 : fitDiagram(specs, { width: boxWidth, height: boxHeight }, fitOpts, pagesBox),
3443
- // A later `specs` keeps the view it has.
3444
- // eslint-disable-next-line react-hooks/exhaustive-deps
3445
- [controlled, unsized, boxWidth, boxHeight, minScale, maxScale, anchor, fitPadding, pagesBox]
3446
- );
3447
- const [settledFit, setSettledFit] = useState(null);
3448
- const fit = settledFit !== null && settledFit.over === defaultView ? settledFit.view : defaultView;
3528
+ const canvasRef = useRef(null);
3529
+ const current = useCallback(() => Array.from(source(), ({ node, pose }) => ({ ...node, pose })), [source]);
3530
+ const fitting = useDiagramFit({
3531
+ canvasRef,
3532
+ specs,
3533
+ current,
3534
+ controlled,
3535
+ size: { width: width ?? measured?.width ?? 0, height: height ?? measured?.height ?? 0 },
3536
+ fit: { minScale, maxScale, anchor, padding: fitPadding },
3537
+ extra: pagesBox
3538
+ });
3539
+ const { defaultView } = fitting;
3449
3540
  const settled = useLatest((moved) => {
3450
3541
  liveOpts?.onSettle?.(moved);
3451
- if (controlled || moved === 0) return;
3452
- const boxes = Array.from(source(), ({ node, pose }) => ({ ...node, pose }));
3453
- setSettledFit({ view: fitDiagram(boxes, { width: boxWidth, height: boxHeight }, fitOpts, pagesBox), over: defaultView });
3542
+ if (moved !== 0) fitting.settle();
3454
3543
  });
3455
3544
  const live = useLiveLayout({
3456
3545
  ...liveOpts,
@@ -3462,8 +3551,7 @@ function Inner({
3462
3551
  useEffect(() => {
3463
3552
  if (run !== null) live.restart();
3464
3553
  }, [run, live]);
3465
- const canvasRef = useRef(null);
3466
- useDiagramApi(apiRef, canvasRef, scene, live, setRun);
3554
+ useDiagramApi(apiRef, canvasRef, scene, live, setRun, fitting.fitNow);
3467
3555
  const selectedIds = useCallback(() => selection.get(), [selection]);
3468
3556
  const editing = useDiagramEditing({
3469
3557
  scene,
@@ -3496,7 +3584,6 @@ function Inner({
3496
3584
  const edits = moves || onConnect !== void 0 || onReorder !== void 0 || resizes || edgesPick;
3497
3585
  const features = useMemo(() => ["view", "pick", ...moves ? ["move"] : [], ...resizes ? ["resize"] : []], [moves, resizes]);
3498
3586
  const selectTool = useMemo(() => ({ resize: { resizable: diagramResizable(scene) } }), [scene]);
3499
- useRefit(canvasRef, fit);
3500
3587
  const overlayRef = useOverlayPlacement(canvasRef, scene, overlays);
3501
3588
  const fillsOn = [width === void 0 ? "fill-x" : "", height === void 0 ? "fill-y" : ""].join(" ").trim();
3502
3589
  return /* @__PURE__ */ jsxs("div", { ref: adoptViewRule, "data-wzl-diagram": fillsOn, className, children: [
@@ -3536,9 +3623,10 @@ function DropRules({ scene }) {
3536
3623
  useDepSource("canDropInto", () => canDropInto);
3537
3624
  return null;
3538
3625
  }
3539
- function useDiagramApi(apiRef, canvasRef, scene, live, setRun) {
3626
+ function useDiagramApi(apiRef, canvasRef, scene, live, setRun, fitNow) {
3540
3627
  useImperativeHandle(apiRef, () => ({
3541
3628
  layout: (algorithm = "force") => setRun({ algorithm }),
3629
+ fit: fitNow,
3542
3630
  live,
3543
3631
  worldAt: (clientX, clientY) => {
3544
3632
  const [x, y] = canvasRef.current?.clientToWorld(clientX, clientY) ?? [clientX, clientY];
@@ -3562,39 +3650,9 @@ function useDiagramApi(apiRef, canvasRef, scene, live, setRun) {
3562
3650
  },
3563
3651
  subscribeFrame: (fn) => canvasRef.current?.subscribeFrame(() => fn()) ?? (() => {
3564
3652
  })
3565
- }), [canvasRef, live, scene, setRun]);
3566
- }
3567
- function useRefit(canvasRef, fit) {
3568
- const fitted = useRef(fit);
3569
- useEffect(() => {
3570
- const canvas = canvasRef.current;
3571
- const was = fitted.current;
3572
- if (canvas === null || fit === void 0 || was === fit) return;
3573
- if (!sameView(canvas.getView(), was ?? IDENTITY)) return;
3574
- fitted.current = fit;
3575
- canvas.setView(fit);
3576
- }, [canvasRef, fit]);
3577
- }
3578
- function fitDiagram(specs, size, fit = {}, extra) {
3579
- const boxes = specs.filter((s) => s.kind === "container" || diagramGroupOf(s) !== null).map((s) => s.pose);
3580
- if (extra !== void 0) boxes.push({ ...extra });
3581
- if (boxes.length === 0) return void 0;
3582
- const x = Math.min(...boxes.map((b) => b.x));
3583
- const y = Math.min(...boxes.map((b) => b.y));
3584
- const bounds = {
3585
- x,
3586
- y,
3587
- width: Math.max(...boxes.map((b) => b.x + b.width)) - x,
3588
- height: Math.max(...boxes.map((b) => b.y + b.height)) - y
3589
- };
3590
- return fitViewToBounds(bounds, size, { x: 0, y: 0, scale: { x: 1, y: 1 } }, {
3591
- maxScale: fit.maxScale ?? 1,
3592
- ...fit.minScale !== void 0 ? { minScale: fit.minScale } : {},
3593
- ...fit.anchor !== void 0 ? { anchor: fit.anchor } : {},
3594
- ...fit.padding !== void 0 ? { padding: fit.padding } : {}
3595
- });
3653
+ }), [canvasRef, live, scene, setRun, fitNow]);
3596
3654
  }
3597
3655
 
3598
- export { ADD_LANE_ACTION_ID, ANYWHERE, CONNECT_ACTION_ID, CONTAINER_STENCILS, DEFAULT_EDGE_STROKE, DiagramView, EASED_LAYOUTS, EDGE_END_AFFORDANCE_KIND, EDGE_END_LAYER_ID, EDIT_CONTAINER_TITLE_ACTION_ID, GRAB_EDGE_END_ACTION_ID, GRAB_LABEL_ACTION_ID, GRAB_PORT_ACTION_ID, GRAB_WAYPOINT_ACTION_ID, LANE_AFFORDANCE_KIND, LANE_LAYER_ID, LANE_RESIZE_ACTION_ID, LAYOUTS, LAYOUT_ACTION_ID, MOVE_LABEL_ACTION_ID, PORT_AFFORDANCE_KIND, PORT_LAYER_ID, RECONNECT_ACTION_ID, RELEASE_MEMBERS_ACTION_ID, REMOVE_WAYPOINT_ACTION_ID, REORDER_ACTION_ID, RESET_LABEL_ACTION_ID, RESET_ROUTE_ACTION_ID, ROUTER_LABELS, ROUTE_EDIT_ACTION_ID, SELECT_LABEL_EDGE_ACTION_ID, SET_HOPS_ACTION_ID, SET_ROUTER_ACTION_IDS, STEP_REORDER_ACTION_ID, WAYPOINT_AFFORDANCE_KIND, WAYPOINT_HIT_PX, WAYPOINT_LAYER_ID, addLane, applyLayout, avoidSceneInput, bodyAt, commitEdgeToScene, commitLabelToScene, commitLaneResizeToScene, commitReconnectToScene, commitRouteToScene, commitRouterToScene, connectBinding, containerBuild, createAddLaneAction, createConnectAction, createDiagramContribution, createEdgeEndAffordance, createLabelDragAction, createLaneAffordance, createLaneResizeAction, createLayoutAction, createPortAffordance, createReconnectAction, createReleaseMembersAction, createRemoveWaypointAction, createReorderAction, createResetLabelAction, createResetRouteAction, createRouteEditAction, createSetHopsAction, createSetRouterAction, createSpecHops, createSpecLabelPlacer, createSpecRouter, createStepReorderAction, createWaypointAffordance, defaultCanConnect, diagramCanDropInto, diagramContainers, diagramDropParentAt, diagramEdgeEnds, diagramLabelDrag, diagramLayoutOf, diagramPorts, diagramResizable, diagramShape, diagramWaypoints, dragSegment, dragVertex, easedProducer, edgeEndLayer, edgeEndScratchOf, edgePolylines, editContainerTitleAction, editContainerTitleBinding, forceProducer, grabPortAction, grabPortBindings, groupReorderContribution, groupReorderSlots, headerIdOf, hopDataOps, inOrUnder, insertWaypoint, isBodyEnd, laneLayer, laneResizeFrame, laneScratchOf, lanesOf, layoutPoses, movedLabelData, pinSegment, poolAxisOf, poolsOf, portLayer, portScratchOf, reconnectBinding, regionHeaderAt, registerDiagramShape, releaseMembers, removeSegment, removeWaypoint, reorderBinding, reorderContribution, routeHandles, routeStateOf, routeWrites, routedData, routerActions, routesOf, sceneEdges, sceneParticipants, segmentAxis, sequenceSlots, themeWrites, unpinSegment, useLiveLayout, useMirroredSelection, waypointLayer, waypointScratchOf, withHopsData, withRouter };
3599
- //# sourceMappingURL=chunk-CI3I3I6T.js.map
3600
- //# sourceMappingURL=chunk-CI3I3I6T.js.map
3656
+ export { ADD_LANE_ACTION_ID, ANYWHERE, CONNECT_ACTION_ID, CONTAINER_STENCILS, DEFAULT_EDGE_STROKE, DiagramView, EASED_LAYOUTS, EDGE_END_AFFORDANCE_KIND, EDGE_END_LAYER_ID, EDIT_CONTAINER_TITLE_ACTION_ID, GRAB_EDGE_END_ACTION_ID, GRAB_LABEL_ACTION_ID, GRAB_PORT_ACTION_ID, GRAB_WAYPOINT_ACTION_ID, LANE_AFFORDANCE_KIND, LANE_LAYER_ID, LANE_RESIZE_ACTION_ID, LAYOUTS, LAYOUT_ACTION_ID, MOVE_LABEL_ACTION_ID, PORT_AFFORDANCE_KIND, PORT_LAYER_ID, RECONNECT_ACTION_ID, RELEASE_MEMBERS_ACTION_ID, REMOVE_WAYPOINT_ACTION_ID, REORDER_ACTION_ID, RESET_LABEL_ACTION_ID, RESET_ROUTE_ACTION_ID, ROUTER_LABELS, ROUTE_EDIT_ACTION_ID, SELECT_LABEL_EDGE_ACTION_ID, SET_HOPS_ACTION_ID, SET_ROUTER_ACTION_IDS, STEP_REORDER_ACTION_ID, WAYPOINT_AFFORDANCE_KIND, WAYPOINT_HIT_PX, WAYPOINT_LAYER_ID, addLane, applyLayout, avoidSceneInput, bodyAt, commitEdgeToScene, commitLabelToScene, commitLaneResizeToScene, commitReconnectToScene, commitRouteToScene, commitRouterToScene, connectBinding, containerBuild, createAddLaneAction, createConnectAction, createDiagramContribution, createEdgeEndAffordance, createLabelDragAction, createLaneAffordance, createLaneResizeAction, createLayoutAction, createPortAffordance, createReconnectAction, createReleaseMembersAction, createRemoveWaypointAction, createReorderAction, createResetLabelAction, createResetRouteAction, createRouteEditAction, createSetHopsAction, createSetRouterAction, createSpecHops, createSpecLabelPlacer, createSpecRouter, createStepReorderAction, createWaypointAffordance, defaultCanConnect, diagramCanDropInto, diagramContainers, diagramDropParentAt, diagramEdgeEnds, diagramLabelDrag, diagramLayoutOf, diagramPorts, diagramResizable, diagramShape, diagramWaypoints, dragSegment, dragVertex, easedProducer, edgeEndLayer, edgeEndScratchOf, edgePolylines, editContainerTitleAction, editContainerTitleBinding, forceProducer, grabPortAction, grabPortBindings, groupReorderContribution, groupReorderSlots, headerIdOf, hopDataOps, inOrUnder, insertWaypoint, isBodyEnd, isBodyOrRegion, isDiagramBody, laneLayer, laneResizeFrame, laneScratchOf, lanesOf, layoutPoses, movedLabelData, pinSegment, poolAxisOf, poolsOf, portLayer, portScratchOf, reconnectBinding, regionHeaderAt, registerDiagramShape, releaseMembers, removeSegment, removeWaypoint, reorderBinding, reorderContribution, routeHandles, routeStateOf, routeWrites, routedData, routerActions, routesOf, sceneEdges, sceneParticipants, segmentAxis, sequenceSlots, themeWrites, unpinSegment, useLiveLayout, useMirroredSelection, waypointLayer, waypointScratchOf, withHopsData, withRouter };
3657
+ //# sourceMappingURL=chunk-ESFIS7BB.js.map
3658
+ //# sourceMappingURL=chunk-ESFIS7BB.js.map