@ankhorage/zora 25.10.9 → 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.
- package/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/dist/features/graph-view/adapters/inbound/web-artifact/GraphView.d.ts +10 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/GraphView.d.ts.map +1 -1
- package/dist/features/graph-view/adapters/inbound/web-artifact/GraphView.js +18 -8
- package/dist/features/graph-view/adapters/inbound/web-artifact/GraphView.js.map +1 -1
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphController.d.ts +2 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphController.d.ts.map +1 -1
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphController.js +68 -4
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphController.js.map +1 -1
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.d.ts +3 -1
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.d.ts.map +1 -1
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.js +41 -8
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.js.map +1 -1
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.d.ts.map +1 -1
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.js +6 -2
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.js.map +1 -1
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphViewDomCallbacks.d.ts +9 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphViewDomCallbacks.d.ts.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphViewDomCallbacks.js +36 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphViewDomCallbacks.js.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/shouldFitGraphAfterLayout.d.ts +2 -2
- package/dist/features/graph-view/adapters/inbound/web-artifact/shouldFitGraphAfterLayout.d.ts.map +1 -1
- package/dist/features/graph-view/adapters/inbound/web-artifact/shouldFitGraphAfterLayout.js +3 -3
- package/dist/features/graph-view/adapters/inbound/web-artifact/shouldFitGraphAfterLayout.js.map +1 -1
- package/dist/features/graph-view/constants/events.d.ts +11 -0
- package/dist/features/graph-view/constants/events.d.ts.map +1 -0
- package/dist/features/graph-view/constants/events.js +10 -0
- package/dist/features/graph-view/constants/events.js.map +1 -0
- package/dist/features/graph-view/public.d.ts +2 -1
- package/dist/features/graph-view/public.d.ts.map +1 -1
- package/dist/features/graph-view/public.js +1 -0
- package/dist/features/graph-view/public.js.map +1 -1
- package/dist/types/graph-view.d.ts +4 -0
- package/dist/types/graph-view.d.ts.map +1 -1
- package/dist/types/graph-view.js.map +1 -1
- package/package.json +1 -1
- package/src/features/graph-view/adapters/inbound/web-artifact/GraphView.tsx +36 -13
- package/src/features/graph-view/adapters/inbound/web-artifact/createGraphController.test.ts +145 -1
- package/src/features/graph-view/adapters/inbound/web-artifact/createGraphController.ts +86 -5
- package/src/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.test.ts +163 -6
- package/src/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.ts +57 -8
- package/src/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.test.ts +42 -0
- package/src/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.ts +7 -1
- package/src/features/graph-view/adapters/inbound/web-artifact/createGraphViewDomCallbacks.test.ts +77 -0
- package/src/features/graph-view/adapters/inbound/web-artifact/createGraphViewDomCallbacks.ts +55 -0
- package/src/features/graph-view/adapters/inbound/web-artifact/shouldFitGraphAfterLayout.test.ts +7 -3
- package/src/features/graph-view/adapters/inbound/web-artifact/shouldFitGraphAfterLayout.ts +7 -3
- package/src/features/graph-view/constants/events.ts +12 -0
- package/src/features/graph-view/public.ts +2 -0
- package/src/types/graph-view.ts +5 -0
- package/web-dist/components/graph-view/GraphView.d.ts +12 -1
- package/web-dist/components/graph-view/GraphView.js +152 -25
- package/web-dist/components/graph-view/GraphView.types-0.d.ts +4 -0
package/src/features/graph-view/adapters/inbound/web-artifact/createGraphViewDomCallbacks.test.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { expect, test } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import { GRAPH_VIEW_EVENTS } from '../../../constants/events';
|
|
4
|
+
import { createGraphViewDomCallbacks } from './createGraphViewDomCallbacks';
|
|
5
|
+
|
|
6
|
+
test('exposes public controller on bubbling ready and layout events without changing React callbacks', () => {
|
|
7
|
+
const host = new EventTarget();
|
|
8
|
+
const controller = createController();
|
|
9
|
+
const observed: string[] = [];
|
|
10
|
+
const browserEvents: string[] = [];
|
|
11
|
+
for (const name of [GRAPH_VIEW_EVENTS.ready, GRAPH_VIEW_EVENTS.layoutComplete]) {
|
|
12
|
+
host.addEventListener(name, (event) => {
|
|
13
|
+
if (!(event instanceof CustomEvent)) throw new Error('Expected a CustomEvent.');
|
|
14
|
+
const detail: unknown = event.detail;
|
|
15
|
+
if (typeof detail !== 'object' || detail === null || !('controller' in detail))
|
|
16
|
+
throw new Error('Expected a public controller in the browser event.');
|
|
17
|
+
expect(detail.controller).toBe(controller);
|
|
18
|
+
expect(event.bubbles).toBe(true);
|
|
19
|
+
browserEvents.push(name);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const callbacks = createGraphViewDomCallbacks(
|
|
24
|
+
{
|
|
25
|
+
onReady: (received) => {
|
|
26
|
+
expect(received).toBe(controller);
|
|
27
|
+
observed.push('ready');
|
|
28
|
+
},
|
|
29
|
+
onLayoutComplete: (received) => {
|
|
30
|
+
expect(received).toBe(controller);
|
|
31
|
+
observed.push('layout');
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{ current: host },
|
|
35
|
+
);
|
|
36
|
+
callbacks.onReady?.(controller);
|
|
37
|
+
callbacks.onLayoutComplete?.(controller);
|
|
38
|
+
|
|
39
|
+
expect(observed).toEqual(['ready', 'layout']);
|
|
40
|
+
expect(browserEvents).toEqual([GRAPH_VIEW_EVENTS.ready, GRAPH_VIEW_EVENTS.layoutComplete]);
|
|
41
|
+
expect(controller.measureNodes(['src']).nodes[0]?.id).toBe('src');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('preserves React callbacks when the browser host is unmounted', () => {
|
|
45
|
+
const controller = createController();
|
|
46
|
+
const observed: string[] = [];
|
|
47
|
+
const callbacks = createGraphViewDomCallbacks(
|
|
48
|
+
{
|
|
49
|
+
onReady: () => observed.push('ready'),
|
|
50
|
+
onLayoutComplete: () => observed.push('layout'),
|
|
51
|
+
},
|
|
52
|
+
{ current: null },
|
|
53
|
+
);
|
|
54
|
+
callbacks.onReady?.(controller);
|
|
55
|
+
callbacks.onLayoutComplete?.(controller);
|
|
56
|
+
expect(observed).toEqual(['ready', 'layout']);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
/*** Supplies public viewport and rendered geometry without a Cytoscape instance. */
|
|
60
|
+
function createController() {
|
|
61
|
+
return {
|
|
62
|
+
fit: () => undefined,
|
|
63
|
+
measureNodes: (ids: readonly string[]) => ({
|
|
64
|
+
nodes: ids.map((id) => ({
|
|
65
|
+
id,
|
|
66
|
+
position: { x: 120, y: 80 },
|
|
67
|
+
size: { width: 100, height: 48 },
|
|
68
|
+
})),
|
|
69
|
+
viewport: { width: 800, height: 600 },
|
|
70
|
+
}),
|
|
71
|
+
getViewport: () => ({ pan: { x: 0, y: 0 }, zoom: 1 }),
|
|
72
|
+
getZoomRange: () => ({ min: 0.5, max: 2 }),
|
|
73
|
+
setPan: () => undefined,
|
|
74
|
+
setZoom: () => undefined,
|
|
75
|
+
zoomBy: () => undefined,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
GraphViewCallbacks,
|
|
3
|
+
GraphViewController,
|
|
4
|
+
GraphViewDomControllerEventDetail,
|
|
5
|
+
} from '../../../../../types/graph-view';
|
|
6
|
+
import { GRAPH_VIEW_EVENTS, type GraphViewDomControllerEventName } from '../../../constants/events';
|
|
7
|
+
|
|
8
|
+
/***
|
|
9
|
+
* Mirror public GraphView controller callbacks as bubbling browser lifecycle events.
|
|
10
|
+
* Browser hosts can measure actual rendered nodes without reaching into the graph engine.
|
|
11
|
+
*/
|
|
12
|
+
export function createGraphViewDomCallbacks(
|
|
13
|
+
callbacks: GraphViewCallbacks,
|
|
14
|
+
hostRef: { readonly current: unknown },
|
|
15
|
+
): GraphViewCallbacks {
|
|
16
|
+
return {
|
|
17
|
+
...callbacks,
|
|
18
|
+
onReady(controller) {
|
|
19
|
+
callbacks.onReady?.(controller);
|
|
20
|
+
dispatchGraphViewControllerEvent(hostRef, GRAPH_VIEW_EVENTS.ready, controller);
|
|
21
|
+
},
|
|
22
|
+
onLayoutComplete(controller) {
|
|
23
|
+
callbacks.onLayoutComplete?.(controller);
|
|
24
|
+
dispatchGraphViewControllerEvent(hostRef, GRAPH_VIEW_EVENTS.layoutComplete, controller);
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/*** Deliver the existing public controller reference on the GraphView root event channel. */
|
|
30
|
+
function dispatchGraphViewControllerEvent(
|
|
31
|
+
hostRef: { readonly current: unknown },
|
|
32
|
+
name: GraphViewDomControllerEventName,
|
|
33
|
+
controller: GraphViewController,
|
|
34
|
+
): void {
|
|
35
|
+
const host = hostRef.current;
|
|
36
|
+
if (!canDispatchBrowserEvent(host)) return;
|
|
37
|
+
host.dispatchEvent(
|
|
38
|
+
new CustomEvent<GraphViewDomControllerEventDetail>(name, {
|
|
39
|
+
bubbles: true,
|
|
40
|
+
detail: { controller },
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/*** Narrow an untrusted browser host to the standard event-dispatch method. */
|
|
46
|
+
function canDispatchBrowserEvent(
|
|
47
|
+
value: unknown,
|
|
48
|
+
): value is { dispatchEvent(event: Event): boolean } {
|
|
49
|
+
return (
|
|
50
|
+
typeof value === 'object' &&
|
|
51
|
+
value !== null &&
|
|
52
|
+
'dispatchEvent' in value &&
|
|
53
|
+
typeof value.dispatchEvent === 'function'
|
|
54
|
+
);
|
|
55
|
+
}
|
package/src/features/graph-view/adapters/inbound/web-artifact/shouldFitGraphAfterLayout.test.ts
CHANGED
|
@@ -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
|
|
2
|
-
export function shouldFitGraphAfterLayout(
|
|
3
|
-
|
|
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
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/***
|
|
2
|
+
* Canonical public GraphView browser lifecycle event names.
|
|
3
|
+
* Candidate for a shared Ankhorage event strategy: define common naming, typed
|
|
4
|
+
* payloads and dispatch conventions before extracting a cross-component contract.
|
|
5
|
+
*/
|
|
6
|
+
export const GRAPH_VIEW_EVENTS = {
|
|
7
|
+
ready: 'zora-graph-view-ready',
|
|
8
|
+
layoutComplete: 'zora-graph-view-layout-complete',
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
export type GraphViewDomControllerEventName =
|
|
12
|
+
(typeof GRAPH_VIEW_EVENTS)[keyof typeof GRAPH_VIEW_EVENTS];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export type {
|
|
2
2
|
GraphViewCallbacks,
|
|
3
3
|
GraphViewController,
|
|
4
|
+
GraphViewDomControllerEventDetail,
|
|
4
5
|
GraphViewEdge,
|
|
5
6
|
GraphViewElementEvent,
|
|
6
7
|
GraphViewElementEventType,
|
|
@@ -18,3 +19,4 @@ export type {
|
|
|
18
19
|
GraphViewViewport,
|
|
19
20
|
} from '../../types/graph-view';
|
|
20
21
|
export { GraphView } from './adapters/inbound/web-artifact/GraphView';
|
|
22
|
+
export { GRAPH_VIEW_EVENTS } from './constants/events';
|
package/src/types/graph-view.ts
CHANGED
|
@@ -94,6 +94,11 @@ export interface GraphViewController {
|
|
|
94
94
|
zoomBy(factor: number): void;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/*** The browser lifecycle events carry only the existing public graph controller. */
|
|
98
|
+
export interface GraphViewDomControllerEventDetail {
|
|
99
|
+
readonly controller: GraphViewController;
|
|
100
|
+
}
|
|
101
|
+
|
|
97
102
|
export interface GraphViewProps {
|
|
98
103
|
readonly nodes: readonly GraphViewNode[];
|
|
99
104
|
readonly edges: readonly GraphViewEdge[];
|
|
@@ -10,10 +10,21 @@ import type { GraphViewProps } from './GraphView.types-0';
|
|
|
10
10
|
* text. Keep padding in styleRules. Compound and rich-node dimensions retain their existing owners.
|
|
11
11
|
* minReadableLabelSize Expand the manual zoom ceiling until plain labels reach this CSS-pixel size.
|
|
12
12
|
* maxFitLabelSize Cap automatic fitting at this plain-label CSS-pixel size, leaving whitespace for tiny graphs.
|
|
13
|
+
* Browser lifecycle events named by public GRAPH_VIEW_EVENTS bubble from the root
|
|
14
|
+
* with `detail.controller`: the same public GraphViewController delivered to React.
|
|
15
|
+
* Browser integrations can call `measureNodes` without accessing the Cytoscape runtime.
|
|
16
|
+
* The standalone browser artifact exposes the same names as `GraphView.events`.
|
|
13
17
|
* onSpacingFactorChange Receive the spacing accepted by explicit fit({ optimizeSpacing: true }).
|
|
14
18
|
* Ordinary zoom never adjusts spacing or relayouts. Explicit optimization preserves layout ordering,
|
|
15
19
|
* stops once effective fit reaches maxFitLabelSize, keeps labels and unrelated compound boundaries
|
|
16
20
|
* collision-safe, and permits only a small rendered background-overlap fallback for dense graphs.
|
|
17
21
|
*/
|
|
18
22
|
export declare function GraphView(props: GraphViewProps): React.JSX.Element;
|
|
19
|
-
|
|
23
|
+
|
|
24
|
+
export declare namespace GraphView {
|
|
25
|
+
var events: {
|
|
26
|
+
readonly ready: "zora-graph-view-ready";
|
|
27
|
+
readonly layoutComplete: "zora-graph-view-layout-complete";
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export type { GraphViewCallbacks, GraphViewController, GraphViewDomControllerEventDetail, GraphViewEdge, GraphViewElementEvent, GraphViewElementEventType, GraphViewFitOptions, GraphViewLayoutName, GraphViewMeasurement, GraphViewNode, GraphViewNodeMeasurement, GraphViewNodeRenderContext, GraphViewPoint, GraphViewProps, GraphViewRenderedNode, GraphViewSize, GraphViewStyleRule, GraphViewViewport } from './GraphView.types-0';
|
|
@@ -91964,6 +91964,12 @@ Consider installing the package or pass your own 'workerFactory' to ELK's constr
|
|
|
91964
91964
|
// src/features/graph-view/adapters/inbound/web-artifact/GraphView.tsx
|
|
91965
91965
|
import React2 from "react";
|
|
91966
91966
|
|
|
91967
|
+
// src/features/graph-view/constants/events.ts
|
|
91968
|
+
var GRAPH_VIEW_EVENTS = {
|
|
91969
|
+
ready: "zora-graph-view-ready",
|
|
91970
|
+
layoutComplete: "zora-graph-view-layout-complete"
|
|
91971
|
+
};
|
|
91972
|
+
|
|
91967
91973
|
// node_modules/cytoscape/dist/cytoscape.esm.mjs
|
|
91968
91974
|
function _arrayLikeToArray(r, a) {
|
|
91969
91975
|
(a == null || a > r.length) && (a = r.length);
|
|
@@ -122730,7 +122736,8 @@ function createGraphController(cy, fitPaddingRef, optimizeFit) {
|
|
|
122730
122736
|
readableSize: 0,
|
|
122731
122737
|
fitSize: Infinity,
|
|
122732
122738
|
readableZoom: 0,
|
|
122733
|
-
fitZoom: Infinity
|
|
122739
|
+
fitZoom: Infinity,
|
|
122740
|
+
resizeSnapshot: null
|
|
122734
122741
|
};
|
|
122735
122742
|
const controller = {
|
|
122736
122743
|
fit(options) {
|
|
@@ -122780,6 +122787,9 @@ function createGraphController(cy, fitPaddingRef, optimizeFit) {
|
|
|
122780
122787
|
getReadableZoomTarget() {
|
|
122781
122788
|
return state.readableZoom;
|
|
122782
122789
|
},
|
|
122790
|
+
beginResize() {
|
|
122791
|
+
state.resizeSnapshot = captureResizeSnapshot(cy);
|
|
122792
|
+
},
|
|
122783
122793
|
settle(fit) {
|
|
122784
122794
|
const logicalZoom = cy.zoom() / state.scale;
|
|
122785
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);
|
|
@@ -122787,15 +122797,66 @@ function createGraphController(cy, fitPaddingRef, optimizeFit) {
|
|
|
122787
122797
|
state.fitZoom = fonts.length ? state.fitSize / fonts.reduce((maximum, size) => Math.max(maximum, size), 0) : Infinity;
|
|
122788
122798
|
state.scale = state.relative ? Math.min(getNodeFitZoom(cy, fitPaddingRef.current), state.fitZoom) : 1;
|
|
122789
122799
|
applyZoomLimits(cy, state);
|
|
122790
|
-
if (fit)
|
|
122800
|
+
if (fit) {
|
|
122801
|
+
state.resizeSnapshot = null;
|
|
122802
|
+
controller.fit();
|
|
122803
|
+
} else if (settlePreservedViewport(cy, state, logicalZoom))
|
|
122791
122804
|
controller.fit();
|
|
122792
|
-
else if (state.relative) {
|
|
122793
|
-
setCenteredZoom(cy, logicalZoom * state.scale);
|
|
122794
|
-
cy.center(cy.nodes());
|
|
122795
|
-
}
|
|
122796
122805
|
}
|
|
122797
122806
|
};
|
|
122798
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
|
+
}
|
|
122799
122860
|
function measureRenderedNodes(cy, nodeIds) {
|
|
122800
122861
|
const viewport = { width: cy.width(), height: cy.height() };
|
|
122801
122862
|
if (viewport.width <= 0 || viewport.height <= 0 || nodeIds.length === 0)
|
|
@@ -122894,31 +122955,62 @@ function createGraphResizeObserver(input) {
|
|
|
122894
122955
|
if (Observer === null)
|
|
122895
122956
|
return null;
|
|
122896
122957
|
const sizeRef = { height: -1, width: -1 };
|
|
122958
|
+
const pendingLayoutResizeRef = { current: false };
|
|
122959
|
+
const pendingResizeSnapshotRef = { current: false };
|
|
122960
|
+
const resizeScheduledRef = { current: false };
|
|
122897
122961
|
const observer = new Observer((entries) => {
|
|
122898
122962
|
const entry = entries.at(0);
|
|
122899
122963
|
if (entry === undefined || !hasSizeChanged(sizeRef, entry.contentRect))
|
|
122900
122964
|
return;
|
|
122901
122965
|
sizeRef.height = entry.contentRect.height;
|
|
122902
122966
|
sizeRef.width = entry.contentRect.width;
|
|
122903
|
-
if (input.layoutRunningRef.current)
|
|
122904
|
-
return;
|
|
122905
122967
|
if (!input.readyRef.current && !input.pendingInitialFitRef.current)
|
|
122906
122968
|
return;
|
|
122907
|
-
|
|
122969
|
+
captureResizeSnapshot2(input, pendingResizeSnapshotRef);
|
|
122970
|
+
if (input.layoutRunningRef.current) {
|
|
122971
|
+
pendingLayoutResizeRef.current = true;
|
|
122972
|
+
return;
|
|
122973
|
+
}
|
|
122974
|
+
scheduleResizeFit(input, pendingLayoutResizeRef, pendingResizeSnapshotRef, resizeScheduledRef);
|
|
122908
122975
|
});
|
|
122909
122976
|
observer.observe(input.container);
|
|
122910
|
-
return
|
|
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();
|
|
122911
122994
|
}
|
|
122912
|
-
function scheduleResizeFit(input) {
|
|
122995
|
+
function scheduleResizeFit(input, pendingLayoutResizeRef, pendingResizeSnapshotRef, resizeScheduledRef) {
|
|
122996
|
+
if (resizeScheduledRef.current)
|
|
122997
|
+
return;
|
|
122998
|
+
resizeScheduledRef.current = true;
|
|
122913
122999
|
scheduleGraphFrame(() => {
|
|
122914
|
-
|
|
123000
|
+
resizeScheduledRef.current = false;
|
|
123001
|
+
if (input.cy.destroyed())
|
|
123002
|
+
return;
|
|
123003
|
+
if (input.layoutRunningRef.current) {
|
|
123004
|
+
pendingLayoutResizeRef.current = true;
|
|
122915
123005
|
return;
|
|
123006
|
+
}
|
|
122916
123007
|
input.cy.resize();
|
|
122917
123008
|
if (input.cy.width() <= 0 || input.cy.height() <= 0)
|
|
122918
123009
|
return;
|
|
123010
|
+
pendingResizeSnapshotRef.current = false;
|
|
122919
123011
|
const completedPendingInitialFit = input.pendingInitialFitRef.current;
|
|
122920
123012
|
input.pendingInitialFitRef.current = false;
|
|
122921
|
-
input.settleViewport();
|
|
123013
|
+
input.settleViewport(completedPendingInitialFit);
|
|
122922
123014
|
input.onViewportSettled(completedPendingInitialFit);
|
|
122923
123015
|
});
|
|
122924
123016
|
}
|
|
@@ -123227,8 +123319,8 @@ function stopGraphLayout(layout, handleStop) {
|
|
|
123227
123319
|
}
|
|
123228
123320
|
|
|
123229
123321
|
// src/features/graph-view/adapters/inbound/web-artifact/shouldFitGraphAfterLayout.ts
|
|
123230
|
-
function shouldFitGraphAfterLayout(ready, topologyChanged) {
|
|
123231
|
-
return !ready || topologyChanged;
|
|
123322
|
+
function shouldFitGraphAfterLayout(ready, topologyChanged, layoutChanged) {
|
|
123323
|
+
return !ready || topologyChanged || layoutChanged;
|
|
123232
123324
|
}
|
|
123233
123325
|
|
|
123234
123326
|
// src/features/graph-view/adapters/inbound/web-artifact/sizeGraphNodesToLabels.ts
|
|
@@ -123378,6 +123470,7 @@ function createRuntimeState(cy, container, callbacksRef) {
|
|
|
123378
123470
|
loopSizer: createGraphLoopSizer(cy),
|
|
123379
123471
|
pendingInitialFitRef: { current: false },
|
|
123380
123472
|
readyRef,
|
|
123473
|
+
settledLayoutRef: { current: null },
|
|
123381
123474
|
settledTopologyRef: { current: null },
|
|
123382
123475
|
relayoutScheduledRef: { current: false },
|
|
123383
123476
|
renderedNodeListeners
|
|
@@ -123386,7 +123479,8 @@ function createRuntimeState(cy, container, callbacksRef) {
|
|
|
123386
123479
|
const resizeObserver = createGraphResizeObserver({
|
|
123387
123480
|
container,
|
|
123388
123481
|
cy,
|
|
123389
|
-
|
|
123482
|
+
beginViewportResize: () => viewport.beginResize(),
|
|
123483
|
+
settleViewport: (fit) => viewport.settle(fit),
|
|
123390
123484
|
layoutRunningRef,
|
|
123391
123485
|
pendingInitialFitRef: stateBase.pendingInitialFitRef,
|
|
123392
123486
|
onViewportSettled: (completedPendingInitialFit) => {
|
|
@@ -123462,6 +123556,7 @@ function completeCurrentLayout(state, generation) {
|
|
|
123462
123556
|
state.layoutRef.current = null;
|
|
123463
123557
|
state.cy.resize();
|
|
123464
123558
|
state.layoutRunningRef.current = false;
|
|
123559
|
+
state.resizeObserver?.flushAfterLayout();
|
|
123465
123560
|
if (!hasUsableViewport(state.cy)) {
|
|
123466
123561
|
if (!state.readyRef.current)
|
|
123467
123562
|
state.pendingInitialFitRef.current = true;
|
|
@@ -123472,8 +123567,9 @@ function completeCurrentLayout(state, generation) {
|
|
|
123472
123567
|
if (input === null)
|
|
123473
123568
|
return;
|
|
123474
123569
|
const topology = getGraphTopologyKey(input.nodes, input.edges);
|
|
123475
|
-
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);
|
|
123476
123571
|
state.viewport.settle(shouldFit);
|
|
123572
|
+
state.settledLayoutRef.current = input.layout;
|
|
123477
123573
|
state.settledTopologyRef.current = topology;
|
|
123478
123574
|
const fitRequest = state.fitRequestRef.current;
|
|
123479
123575
|
state.fitRequestRef.current = null;
|
|
@@ -123615,6 +123711,33 @@ function destroyRuntime(state) {
|
|
|
123615
123711
|
}
|
|
123616
123712
|
registerGraphLayouts();
|
|
123617
123713
|
|
|
123714
|
+
// src/features/graph-view/adapters/inbound/web-artifact/createGraphViewDomCallbacks.ts
|
|
123715
|
+
function createGraphViewDomCallbacks(callbacks, hostRef) {
|
|
123716
|
+
return {
|
|
123717
|
+
...callbacks,
|
|
123718
|
+
onReady(controller) {
|
|
123719
|
+
callbacks.onReady?.(controller);
|
|
123720
|
+
dispatchGraphViewControllerEvent(hostRef, GRAPH_VIEW_EVENTS.ready, controller);
|
|
123721
|
+
},
|
|
123722
|
+
onLayoutComplete(controller) {
|
|
123723
|
+
callbacks.onLayoutComplete?.(controller);
|
|
123724
|
+
dispatchGraphViewControllerEvent(hostRef, GRAPH_VIEW_EVENTS.layoutComplete, controller);
|
|
123725
|
+
}
|
|
123726
|
+
};
|
|
123727
|
+
}
|
|
123728
|
+
function dispatchGraphViewControllerEvent(hostRef, name, controller) {
|
|
123729
|
+
const host = hostRef.current;
|
|
123730
|
+
if (!canDispatchBrowserEvent(host))
|
|
123731
|
+
return;
|
|
123732
|
+
host.dispatchEvent(new CustomEvent(name, {
|
|
123733
|
+
bubbles: true,
|
|
123734
|
+
detail: { controller }
|
|
123735
|
+
}));
|
|
123736
|
+
}
|
|
123737
|
+
function canDispatchBrowserEvent(value) {
|
|
123738
|
+
return typeof value === "object" && value !== null && "dispatchEvent" in value && typeof value.dispatchEvent === "function";
|
|
123739
|
+
}
|
|
123740
|
+
|
|
123618
123741
|
// src/features/graph-view/adapters/inbound/web-artifact/GraphNodeOverlay.tsx
|
|
123619
123742
|
import React from "react";
|
|
123620
123743
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -123700,8 +123823,10 @@ function readResizeObserverConstructor2() {
|
|
|
123700
123823
|
// src/features/graph-view/adapters/inbound/web-artifact/GraphView.tsx
|
|
123701
123824
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
123702
123825
|
function GraphView(props) {
|
|
123703
|
-
const
|
|
123826
|
+
const hostRef = React2.useRef(null);
|
|
123827
|
+
const { containerRef, renderedNodes, runtimeRef } = useGraphViewRuntime(props, hostRef);
|
|
123704
123828
|
return /* @__PURE__ */ jsxs("div", {
|
|
123829
|
+
ref: hostRef,
|
|
123705
123830
|
"aria-label": props.ariaLabel ?? "Graph",
|
|
123706
123831
|
className: props.className,
|
|
123707
123832
|
role: "application",
|
|
@@ -123728,10 +123853,11 @@ function GraphView(props) {
|
|
|
123728
123853
|
]
|
|
123729
123854
|
});
|
|
123730
123855
|
}
|
|
123731
|
-
|
|
123856
|
+
GraphView.events = GRAPH_VIEW_EVENTS;
|
|
123857
|
+
function useGraphViewRuntime(props, hostRef) {
|
|
123732
123858
|
const containerRef = React2.useRef(null);
|
|
123733
123859
|
const runtimeRef = React2.useRef(null);
|
|
123734
|
-
const callbacksRef = useGraphCallbacksRef(props);
|
|
123860
|
+
const callbacksRef = useGraphCallbacksRef(props, hostRef);
|
|
123735
123861
|
const [renderedNodes, setRenderedNodes] = React2.useState([]);
|
|
123736
123862
|
useRuntimeLifecycle(containerRef, runtimeRef, callbacksRef);
|
|
123737
123863
|
useRuntimeGraphUpdate(runtimeRef, props);
|
|
@@ -123740,8 +123866,8 @@ function useGraphViewRuntime(props) {
|
|
|
123740
123866
|
useKnownNodeSizes(runtimeRef, props.nodes, props.getNodeSize);
|
|
123741
123867
|
return { containerRef, renderedNodes, runtimeRef };
|
|
123742
123868
|
}
|
|
123743
|
-
function useGraphCallbacksRef(props) {
|
|
123744
|
-
const callbacksRef = React2.useRef(readCallbacks(props));
|
|
123869
|
+
function useGraphCallbacksRef(props, hostRef) {
|
|
123870
|
+
const callbacksRef = React2.useRef(createGraphViewDomCallbacks(readCallbacks(props), hostRef));
|
|
123745
123871
|
const {
|
|
123746
123872
|
onEdgeEvent,
|
|
123747
123873
|
onLayoutComplete,
|
|
@@ -123751,21 +123877,22 @@ function useGraphCallbacksRef(props) {
|
|
|
123751
123877
|
onSpacingFactorChange
|
|
123752
123878
|
} = props;
|
|
123753
123879
|
React2.useEffect(() => {
|
|
123754
|
-
callbacksRef.current = {
|
|
123880
|
+
callbacksRef.current = createGraphViewDomCallbacks({
|
|
123755
123881
|
onEdgeEvent,
|
|
123756
123882
|
onLayoutComplete,
|
|
123757
123883
|
onNodeEvent,
|
|
123758
123884
|
onReady,
|
|
123759
123885
|
onViewportChange,
|
|
123760
123886
|
onSpacingFactorChange
|
|
123761
|
-
};
|
|
123887
|
+
}, hostRef);
|
|
123762
123888
|
}, [
|
|
123763
123889
|
onEdgeEvent,
|
|
123764
123890
|
onLayoutComplete,
|
|
123765
123891
|
onNodeEvent,
|
|
123766
123892
|
onReady,
|
|
123767
123893
|
onViewportChange,
|
|
123768
|
-
onSpacingFactorChange
|
|
123894
|
+
onSpacingFactorChange,
|
|
123895
|
+
hostRef
|
|
123769
123896
|
]);
|
|
123770
123897
|
return callbacksRef;
|
|
123771
123898
|
}
|
|
@@ -81,6 +81,10 @@ export interface GraphViewController {
|
|
|
81
81
|
setZoom(zoom: number): void;
|
|
82
82
|
zoomBy(factor: number): void;
|
|
83
83
|
}
|
|
84
|
+
/*** The browser lifecycle events carry only the existing public graph controller. */
|
|
85
|
+
export interface GraphViewDomControllerEventDetail {
|
|
86
|
+
readonly controller: GraphViewController;
|
|
87
|
+
}
|
|
84
88
|
export interface GraphViewProps {
|
|
85
89
|
readonly nodes: readonly GraphViewNode[];
|
|
86
90
|
readonly edges: readonly GraphViewEdge[];
|