@ankhorage/zora 25.11.0 → 25.11.1

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.
Files changed (27) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +1 -1
  3. package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphController.d.ts +2 -0
  4. package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphController.d.ts.map +1 -1
  5. package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphController.js +68 -4
  6. package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphController.js.map +1 -1
  7. package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.d.ts +3 -1
  8. package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.d.ts.map +1 -1
  9. package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.js +41 -8
  10. package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.js.map +1 -1
  11. package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.d.ts.map +1 -1
  12. package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.js +6 -2
  13. package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.js.map +1 -1
  14. package/dist/features/graph-view/adapters/inbound/web-artifact/shouldFitGraphAfterLayout.d.ts +2 -2
  15. package/dist/features/graph-view/adapters/inbound/web-artifact/shouldFitGraphAfterLayout.d.ts.map +1 -1
  16. package/dist/features/graph-view/adapters/inbound/web-artifact/shouldFitGraphAfterLayout.js +3 -3
  17. package/dist/features/graph-view/adapters/inbound/web-artifact/shouldFitGraphAfterLayout.js.map +1 -1
  18. package/package.json +1 -1
  19. package/src/features/graph-view/adapters/inbound/web-artifact/createGraphController.test.ts +145 -1
  20. package/src/features/graph-view/adapters/inbound/web-artifact/createGraphController.ts +86 -5
  21. package/src/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.test.ts +163 -6
  22. package/src/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.ts +57 -8
  23. package/src/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.test.ts +42 -0
  24. package/src/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.ts +7 -1
  25. package/src/features/graph-view/adapters/inbound/web-artifact/shouldFitGraphAfterLayout.test.ts +7 -3
  26. package/src/features/graph-view/adapters/inbound/web-artifact/shouldFitGraphAfterLayout.ts +7 -3
  27. package/web-dist/components/graph-view/GraphView.js +107 -17
@@ -4,14 +4,18 @@ import { shouldFitGraphAfterLayout } from './shouldFitGraphAfterLayout';
4
4
 
5
5
  describe('shouldFitGraphAfterLayout', () => {
6
6
  it('fits the initial layout once', () => {
7
- expect(shouldFitGraphAfterLayout(false, false)).toBe(true);
7
+ expect(shouldFitGraphAfterLayout(false, false, false)).toBe(true);
8
8
  });
9
9
 
10
10
  it('preserves viewport after the graph is ready', () => {
11
- expect(shouldFitGraphAfterLayout(true, false)).toBe(false);
11
+ expect(shouldFitGraphAfterLayout(true, false, false)).toBe(false);
12
12
  });
13
13
 
14
14
  it('fits a changed package depth or filtered topology after readiness', () => {
15
- expect(shouldFitGraphAfterLayout(true, true)).toBe(true);
15
+ expect(shouldFitGraphAfterLayout(true, true, false)).toBe(true);
16
+ });
17
+
18
+ it('fits a newly selected layout algorithm with unchanged topology', () => {
19
+ expect(shouldFitGraphAfterLayout(true, false, true)).toBe(true);
16
20
  });
17
21
  });
@@ -1,4 +1,8 @@
1
- /*** Fit initial or structurally changed graphs while preserving the viewport across spacing updates. */
2
- export function shouldFitGraphAfterLayout(ready: boolean, topologyChanged: boolean): boolean {
3
- return !ready || topologyChanged;
1
+ /*** Fit initial, topology, or layout-algorithm changes while preserving viewport intent otherwise. */
2
+ export function shouldFitGraphAfterLayout(
3
+ ready: boolean,
4
+ topologyChanged: boolean,
5
+ layoutChanged: boolean,
6
+ ): boolean {
7
+ return !ready || topologyChanged || layoutChanged;
4
8
  }
@@ -122736,7 +122736,8 @@ function createGraphController(cy, fitPaddingRef, optimizeFit) {
122736
122736
  readableSize: 0,
122737
122737
  fitSize: Infinity,
122738
122738
  readableZoom: 0,
122739
- fitZoom: Infinity
122739
+ fitZoom: Infinity,
122740
+ resizeSnapshot: null
122740
122741
  };
122741
122742
  const controller = {
122742
122743
  fit(options) {
@@ -122786,6 +122787,9 @@ function createGraphController(cy, fitPaddingRef, optimizeFit) {
122786
122787
  getReadableZoomTarget() {
122787
122788
  return state.readableZoom;
122788
122789
  },
122790
+ beginResize() {
122791
+ state.resizeSnapshot = captureResizeSnapshot(cy);
122792
+ },
122789
122793
  settle(fit) {
122790
122794
  const logicalZoom = cy.zoom() / state.scale;
122791
122795
  const fonts = cy.nodes().filter((node) => !node.isParent() && Boolean(node.style("label"))).map((node) => Number.parseFloat(String(node.style("font-size")))).filter((size) => Number.isFinite(size) && size > 0);
@@ -122793,15 +122797,66 @@ function createGraphController(cy, fitPaddingRef, optimizeFit) {
122793
122797
  state.fitZoom = fonts.length ? state.fitSize / fonts.reduce((maximum, size) => Math.max(maximum, size), 0) : Infinity;
122794
122798
  state.scale = state.relative ? Math.min(getNodeFitZoom(cy, fitPaddingRef.current), state.fitZoom) : 1;
122795
122799
  applyZoomLimits(cy, state);
122796
- if (fit)
122800
+ if (fit) {
122801
+ state.resizeSnapshot = null;
122802
+ controller.fit();
122803
+ } else if (settlePreservedViewport(cy, state, logicalZoom))
122797
122804
  controller.fit();
122798
- else if (state.relative) {
122799
- setCenteredZoom(cy, logicalZoom * state.scale);
122800
- cy.center(cy.nodes());
122801
- }
122802
122805
  }
122803
122806
  };
122804
122807
  }
122808
+ function captureResizeSnapshot(cy) {
122809
+ const viewport = { width: cy.width(), height: cy.height() };
122810
+ const zoom = cy.zoom();
122811
+ if (!isUsableViewport(viewport) || !Number.isFinite(zoom) || zoom <= 0)
122812
+ return null;
122813
+ const pan = cy.pan();
122814
+ const renderedCenter = { x: viewport.width / 2, y: viewport.height / 2 };
122815
+ return {
122816
+ center: {
122817
+ x: (renderedCenter.x - pan.x) / zoom,
122818
+ y: (renderedCenter.y - pan.y) / zoom
122819
+ },
122820
+ nodeBounds: getNodeBoundsStatus(cy, viewport)
122821
+ };
122822
+ }
122823
+ function settlePreservedViewport(cy, state, logicalZoom) {
122824
+ const snapshot = state.resizeSnapshot;
122825
+ state.resizeSnapshot = null;
122826
+ const zoom = state.relative ? logicalZoom * state.scale : cy.zoom();
122827
+ if (snapshot === null) {
122828
+ if (state.relative)
122829
+ setCenteredZoom(cy, zoom);
122830
+ return false;
122831
+ }
122832
+ const viewport = { width: cy.width(), height: cy.height() };
122833
+ if (!isUsableViewport(viewport))
122834
+ return false;
122835
+ cy.viewport({
122836
+ zoom,
122837
+ pan: {
122838
+ x: viewport.width / 2 - snapshot.center.x * zoom,
122839
+ y: viewport.height / 2 - snapshot.center.y * zoom
122840
+ }
122841
+ });
122842
+ const nodeBounds = getNodeBoundsStatus(cy, viewport);
122843
+ return nodeBounds === "unusable" || snapshot.nodeBounds === "fit" && nodeBounds === "clipped";
122844
+ }
122845
+ function getNodeBoundsStatus(cy, viewport) {
122846
+ if (cy.nodes().empty())
122847
+ return "empty";
122848
+ if (!isUsableViewport(viewport))
122849
+ return "unusable";
122850
+ const bounds = cy.nodes().renderedBoundingBox();
122851
+ if (![bounds.x1, bounds.x2, bounds.y1, bounds.y2].every(Number.isFinite))
122852
+ return "unusable";
122853
+ const fitsHorizontally = bounds.x1 >= 0 && bounds.x2 <= viewport.width;
122854
+ const fitsVertically = bounds.y1 >= 0 && bounds.y2 <= viewport.height;
122855
+ return fitsHorizontally && fitsVertically ? "fit" : "clipped";
122856
+ }
122857
+ function isUsableViewport(viewport) {
122858
+ return Number.isFinite(viewport.width) && Number.isFinite(viewport.height) && viewport.width > 0 && viewport.height > 0;
122859
+ }
122805
122860
  function measureRenderedNodes(cy, nodeIds) {
122806
122861
  const viewport = { width: cy.width(), height: cy.height() };
122807
122862
  if (viewport.width <= 0 || viewport.height <= 0 || nodeIds.length === 0)
@@ -122900,31 +122955,62 @@ function createGraphResizeObserver(input) {
122900
122955
  if (Observer === null)
122901
122956
  return null;
122902
122957
  const sizeRef = { height: -1, width: -1 };
122958
+ const pendingLayoutResizeRef = { current: false };
122959
+ const pendingResizeSnapshotRef = { current: false };
122960
+ const resizeScheduledRef = { current: false };
122903
122961
  const observer = new Observer((entries) => {
122904
122962
  const entry = entries.at(0);
122905
122963
  if (entry === undefined || !hasSizeChanged(sizeRef, entry.contentRect))
122906
122964
  return;
122907
122965
  sizeRef.height = entry.contentRect.height;
122908
122966
  sizeRef.width = entry.contentRect.width;
122909
- if (input.layoutRunningRef.current)
122910
- return;
122911
122967
  if (!input.readyRef.current && !input.pendingInitialFitRef.current)
122912
122968
  return;
122913
- scheduleResizeFit(input);
122969
+ captureResizeSnapshot2(input, pendingResizeSnapshotRef);
122970
+ if (input.layoutRunningRef.current) {
122971
+ pendingLayoutResizeRef.current = true;
122972
+ return;
122973
+ }
122974
+ scheduleResizeFit(input, pendingLayoutResizeRef, pendingResizeSnapshotRef, resizeScheduledRef);
122914
122975
  });
122915
122976
  observer.observe(input.container);
122916
- return observer;
122977
+ return {
122978
+ disconnect: () => observer.disconnect(),
122979
+ flushAfterLayout: () => flushAfterLayout(input, pendingLayoutResizeRef, pendingResizeSnapshotRef)
122980
+ };
122981
+ }
122982
+ function captureResizeSnapshot2(input, pendingResizeSnapshotRef) {
122983
+ if (pendingResizeSnapshotRef.current)
122984
+ return;
122985
+ input.beginViewportResize();
122986
+ pendingResizeSnapshotRef.current = true;
122987
+ }
122988
+ function flushAfterLayout(input, pendingLayoutResizeRef, pendingResizeSnapshotRef) {
122989
+ if (!pendingLayoutResizeRef.current || input.cy.destroyed() || input.layoutRunningRef.current)
122990
+ return;
122991
+ pendingLayoutResizeRef.current = false;
122992
+ pendingResizeSnapshotRef.current = false;
122993
+ input.cy.resize();
122917
122994
  }
122918
- function scheduleResizeFit(input) {
122995
+ function scheduleResizeFit(input, pendingLayoutResizeRef, pendingResizeSnapshotRef, resizeScheduledRef) {
122996
+ if (resizeScheduledRef.current)
122997
+ return;
122998
+ resizeScheduledRef.current = true;
122919
122999
  scheduleGraphFrame(() => {
122920
- if (input.cy.destroyed() || input.layoutRunningRef.current)
123000
+ resizeScheduledRef.current = false;
123001
+ if (input.cy.destroyed())
122921
123002
  return;
123003
+ if (input.layoutRunningRef.current) {
123004
+ pendingLayoutResizeRef.current = true;
123005
+ return;
123006
+ }
122922
123007
  input.cy.resize();
122923
123008
  if (input.cy.width() <= 0 || input.cy.height() <= 0)
122924
123009
  return;
123010
+ pendingResizeSnapshotRef.current = false;
122925
123011
  const completedPendingInitialFit = input.pendingInitialFitRef.current;
122926
123012
  input.pendingInitialFitRef.current = false;
122927
- input.settleViewport();
123013
+ input.settleViewport(completedPendingInitialFit);
122928
123014
  input.onViewportSettled(completedPendingInitialFit);
122929
123015
  });
122930
123016
  }
@@ -123233,8 +123319,8 @@ function stopGraphLayout(layout, handleStop) {
123233
123319
  }
123234
123320
 
123235
123321
  // src/features/graph-view/adapters/inbound/web-artifact/shouldFitGraphAfterLayout.ts
123236
- function shouldFitGraphAfterLayout(ready, topologyChanged) {
123237
- return !ready || topologyChanged;
123322
+ function shouldFitGraphAfterLayout(ready, topologyChanged, layoutChanged) {
123323
+ return !ready || topologyChanged || layoutChanged;
123238
123324
  }
123239
123325
 
123240
123326
  // src/features/graph-view/adapters/inbound/web-artifact/sizeGraphNodesToLabels.ts
@@ -123384,6 +123470,7 @@ function createRuntimeState(cy, container, callbacksRef) {
123384
123470
  loopSizer: createGraphLoopSizer(cy),
123385
123471
  pendingInitialFitRef: { current: false },
123386
123472
  readyRef,
123473
+ settledLayoutRef: { current: null },
123387
123474
  settledTopologyRef: { current: null },
123388
123475
  relayoutScheduledRef: { current: false },
123389
123476
  renderedNodeListeners
@@ -123392,7 +123479,8 @@ function createRuntimeState(cy, container, callbacksRef) {
123392
123479
  const resizeObserver = createGraphResizeObserver({
123393
123480
  container,
123394
123481
  cy,
123395
- settleViewport: () => viewport.settle(true),
123482
+ beginViewportResize: () => viewport.beginResize(),
123483
+ settleViewport: (fit) => viewport.settle(fit),
123396
123484
  layoutRunningRef,
123397
123485
  pendingInitialFitRef: stateBase.pendingInitialFitRef,
123398
123486
  onViewportSettled: (completedPendingInitialFit) => {
@@ -123468,6 +123556,7 @@ function completeCurrentLayout(state, generation) {
123468
123556
  state.layoutRef.current = null;
123469
123557
  state.cy.resize();
123470
123558
  state.layoutRunningRef.current = false;
123559
+ state.resizeObserver?.flushAfterLayout();
123471
123560
  if (!hasUsableViewport(state.cy)) {
123472
123561
  if (!state.readyRef.current)
123473
123562
  state.pendingInitialFitRef.current = true;
@@ -123478,8 +123567,9 @@ function completeCurrentLayout(state, generation) {
123478
123567
  if (input === null)
123479
123568
  return;
123480
123569
  const topology = getGraphTopologyKey(input.nodes, input.edges);
123481
- const shouldFit = shouldFitGraphAfterLayout(state.readyRef.current, topology !== state.settledTopologyRef.current);
123570
+ const shouldFit = shouldFitGraphAfterLayout(state.readyRef.current, topology !== state.settledTopologyRef.current, input.layout !== state.settledLayoutRef.current);
123482
123571
  state.viewport.settle(shouldFit);
123572
+ state.settledLayoutRef.current = input.layout;
123483
123573
  state.settledTopologyRef.current = topology;
123484
123574
  const fitRequest = state.fitRequestRef.current;
123485
123575
  state.fitRequestRef.current = null;