@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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 25.11.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 8653d18: Stabilize GraphView automatic fitting after layout changes and preserve manual viewport intent across resize updates.
|
|
8
|
+
|
|
9
|
+
## 25.11.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 9f6a15f: Expose public bubbling GraphView browser lifecycle events carrying the existing controller, so integrations can measure and target actual rendered graph nodes without private renderer access.
|
|
14
|
+
|
|
3
15
|
## 25.10.9
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
# @ankhorage/zora
|
|
5
5
|
|
|
6
|
-
         
|
|
7
7
|
|
|
8
8
|
Opinionated React Native and React Native Web UI kit built on @ankhorage/surface.
|
|
9
9
|
|
|
@@ -8,10 +8,20 @@ import type { GraphViewProps } from '../../../../../types/graph-view';
|
|
|
8
8
|
* text. Keep padding in styleRules. Compound and rich-node dimensions retain their existing owners.
|
|
9
9
|
* minReadableLabelSize Expand the manual zoom ceiling until plain labels reach this CSS-pixel size.
|
|
10
10
|
* maxFitLabelSize Cap automatic fitting at this plain-label CSS-pixel size, leaving whitespace for tiny graphs.
|
|
11
|
+
* Browser lifecycle events named by public GRAPH_VIEW_EVENTS bubble from the root
|
|
12
|
+
* with `detail.controller`: the same public GraphViewController delivered to React.
|
|
13
|
+
* Browser integrations can call `measureNodes` without accessing the Cytoscape runtime.
|
|
14
|
+
* The standalone browser artifact exposes the same names as `GraphView.events`.
|
|
11
15
|
* onSpacingFactorChange Receive the spacing accepted by explicit fit({ optimizeSpacing: true }).
|
|
12
16
|
* Ordinary zoom never adjusts spacing or relayouts. Explicit optimization preserves layout ordering,
|
|
13
17
|
* stops once effective fit reaches maxFitLabelSize, keeps labels and unrelated compound boundaries
|
|
14
18
|
* collision-safe, and permits only a small rendered background-overlap fallback for dense graphs.
|
|
15
19
|
*/
|
|
16
20
|
export declare function GraphView(props: GraphViewProps): React.JSX.Element;
|
|
21
|
+
export declare namespace GraphView {
|
|
22
|
+
var events: {
|
|
23
|
+
readonly ready: "zora-graph-view-ready";
|
|
24
|
+
readonly layoutComplete: "zora-graph-view-layout-complete";
|
|
25
|
+
};
|
|
26
|
+
}
|
|
17
27
|
//# sourceMappingURL=GraphView.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GraphView.d.ts","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/GraphView.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAGV,cAAc,EAEf,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"GraphView.d.ts","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/GraphView.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAGV,cAAc,EAEf,MAAM,iCAAiC,CAAC;AAQzC;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,qBAiC9C;yBAjCe,SAAS"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { GRAPH_VIEW_EVENTS } from '../../../constants/events';
|
|
2
3
|
import { createGraphRuntime } from './createGraphRuntime';
|
|
4
|
+
import { createGraphViewDomCallbacks } from './createGraphViewDomCallbacks';
|
|
3
5
|
import { GraphNodeOverlay } from './GraphNodeOverlay';
|
|
4
6
|
/***
|
|
5
7
|
* Render a browser graph while one runtime owns Cytoscape layout, viewport, and disposal.
|
|
@@ -9,14 +11,19 @@ import { GraphNodeOverlay } from './GraphNodeOverlay';
|
|
|
9
11
|
* text. Keep padding in styleRules. Compound and rich-node dimensions retain their existing owners.
|
|
10
12
|
* minReadableLabelSize Expand the manual zoom ceiling until plain labels reach this CSS-pixel size.
|
|
11
13
|
* maxFitLabelSize Cap automatic fitting at this plain-label CSS-pixel size, leaving whitespace for tiny graphs.
|
|
14
|
+
* Browser lifecycle events named by public GRAPH_VIEW_EVENTS bubble from the root
|
|
15
|
+
* with `detail.controller`: the same public GraphViewController delivered to React.
|
|
16
|
+
* Browser integrations can call `measureNodes` without accessing the Cytoscape runtime.
|
|
17
|
+
* The standalone browser artifact exposes the same names as `GraphView.events`.
|
|
12
18
|
* onSpacingFactorChange Receive the spacing accepted by explicit fit({ optimizeSpacing: true }).
|
|
13
19
|
* Ordinary zoom never adjusts spacing or relayouts. Explicit optimization preserves layout ordering,
|
|
14
20
|
* stops once effective fit reaches maxFitLabelSize, keeps labels and unrelated compound boundaries
|
|
15
21
|
* collision-safe, and permits only a small rendered background-overlap fallback for dense graphs.
|
|
16
22
|
*/
|
|
17
23
|
export function GraphView(props) {
|
|
18
|
-
const
|
|
19
|
-
|
|
24
|
+
const hostRef = React.useRef(null);
|
|
25
|
+
const { containerRef, renderedNodes, runtimeRef } = useGraphViewRuntime(props, hostRef);
|
|
26
|
+
return (<div ref={hostRef} aria-label={props.ariaLabel ?? 'Graph'} className={props.className} role="application" style={{
|
|
20
27
|
height: '100%',
|
|
21
28
|
minHeight: 0,
|
|
22
29
|
minWidth: 0,
|
|
@@ -28,11 +35,13 @@ export function GraphView(props) {
|
|
|
28
35
|
{props.renderNode ? (<GraphNodeOverlay nodes={props.nodes} renderedNodes={renderedNodes} renderNode={props.renderNode} onNodeEvent={(id, type, selectionIntent) => runtimeRef.current?.handleOverlayNodeEvent(id, type, selectionIntent)} onNodeSize={(id, size) => runtimeRef.current?.setNodeSize(id, size)}/>) : null}
|
|
29
36
|
</div>);
|
|
30
37
|
}
|
|
38
|
+
/*** Publish the same canonical event registry on the standalone GraphView artifact. */
|
|
39
|
+
GraphView.events = GRAPH_VIEW_EVENTS;
|
|
31
40
|
/*** Compose React synchronization around the single imperative graph runtime. */
|
|
32
|
-
function useGraphViewRuntime(props) {
|
|
41
|
+
function useGraphViewRuntime(props, hostRef) {
|
|
33
42
|
const containerRef = React.useRef(null);
|
|
34
43
|
const runtimeRef = React.useRef(null);
|
|
35
|
-
const callbacksRef = useGraphCallbacksRef(props);
|
|
44
|
+
const callbacksRef = useGraphCallbacksRef(props, hostRef);
|
|
36
45
|
const [renderedNodes, setRenderedNodes] = React.useState([]);
|
|
37
46
|
useRuntimeLifecycle(containerRef, runtimeRef, callbacksRef);
|
|
38
47
|
useRuntimeGraphUpdate(runtimeRef, props);
|
|
@@ -42,18 +51,18 @@ function useGraphViewRuntime(props) {
|
|
|
42
51
|
return { containerRef, renderedNodes, runtimeRef };
|
|
43
52
|
}
|
|
44
53
|
/*** Keep the callback ref current without mutating refs during render. */
|
|
45
|
-
function useGraphCallbacksRef(props) {
|
|
46
|
-
const callbacksRef = React.useRef(readCallbacks(props));
|
|
54
|
+
function useGraphCallbacksRef(props, hostRef) {
|
|
55
|
+
const callbacksRef = React.useRef(createGraphViewDomCallbacks(readCallbacks(props), hostRef));
|
|
47
56
|
const { onEdgeEvent, onLayoutComplete, onNodeEvent, onReady, onViewportChange, onSpacingFactorChange, } = props;
|
|
48
57
|
React.useEffect(() => {
|
|
49
|
-
callbacksRef.current = {
|
|
58
|
+
callbacksRef.current = createGraphViewDomCallbacks({
|
|
50
59
|
onEdgeEvent,
|
|
51
60
|
onLayoutComplete,
|
|
52
61
|
onNodeEvent,
|
|
53
62
|
onReady,
|
|
54
63
|
onViewportChange,
|
|
55
64
|
onSpacingFactorChange,
|
|
56
|
-
};
|
|
65
|
+
}, hostRef);
|
|
57
66
|
}, [
|
|
58
67
|
onEdgeEvent,
|
|
59
68
|
onLayoutComplete,
|
|
@@ -61,6 +70,7 @@ function useGraphCallbacksRef(props) {
|
|
|
61
70
|
onReady,
|
|
62
71
|
onViewportChange,
|
|
63
72
|
onSpacingFactorChange,
|
|
73
|
+
hostRef,
|
|
64
74
|
]);
|
|
65
75
|
return callbacksRef;
|
|
66
76
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GraphView.js","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/GraphView.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { CytoscapeOptions } from 'cytoscape';\nimport React from 'react';\n\nimport type {\n GraphViewCallbacks,\n GraphViewNode,\n GraphViewProps,\n GraphViewRenderedNode,\n} from '../../../../../types/graph-view';\nimport { createGraphRuntime, type GraphRuntime } from './createGraphRuntime';\nimport { GraphNodeOverlay } from './GraphNodeOverlay';\n\ntype GraphContainer = NonNullable<CytoscapeOptions['container']>;\n\n/***\n * Render a browser graph while one runtime owns Cytoscape layout, viewport, and disposal.\n * zoomMode Use `fit-relative` to make controller zoom, events and min/max limits relative\n * to the visible-node fit (1 = fit). The default `absolute` keeps engine-space zoom units.\n * sizeNodesToLabels Opt in to renderer-measured plain labels, including bold and Unicode\n * text. Keep padding in styleRules. Compound and rich-node dimensions retain their existing owners.\n * minReadableLabelSize Expand the manual zoom ceiling until plain labels reach this CSS-pixel size.\n * maxFitLabelSize Cap automatic fitting at this plain-label CSS-pixel size, leaving whitespace for tiny graphs.\n * onSpacingFactorChange Receive the spacing accepted by explicit fit({ optimizeSpacing: true }).\n * Ordinary zoom never adjusts spacing or relayouts. Explicit optimization preserves layout ordering,\n * stops once effective fit reaches maxFitLabelSize, keeps labels and unrelated compound boundaries\n * collision-safe, and permits only a small rendered background-overlap fallback for dense graphs.\n */\nexport function GraphView(props: GraphViewProps) {\n const { containerRef, renderedNodes, runtimeRef } = useGraphViewRuntime(props);\n\n return (\n <div\n aria-label={props.ariaLabel ?? 'Graph'}\n className={props.className}\n role=\"application\"\n style={{\n height: '100%',\n minHeight: 0,\n minWidth: 0,\n position: 'relative',\n width: '100%',\n ...props.style,\n }}\n >\n <div ref={containerRef} style={{ height: '100%', width: '100%' }} />\n {props.renderNode ? (\n <GraphNodeOverlay\n nodes={props.nodes}\n renderedNodes={renderedNodes}\n renderNode={props.renderNode}\n onNodeEvent={(id, type, selectionIntent) =>\n runtimeRef.current?.handleOverlayNodeEvent(id, type, selectionIntent)\n }\n onNodeSize={(id, size) => runtimeRef.current?.setNodeSize(id, size)}\n />\n ) : null}\n </div>\n );\n}\n\n/*** Compose React synchronization around the single imperative graph runtime. */\nfunction useGraphViewRuntime(props: GraphViewProps) {\n const containerRef = React.useRef<GraphContainer | null>(null);\n const runtimeRef = React.useRef<GraphRuntime | null>(null);\n const callbacksRef = useGraphCallbacksRef(props);\n const [renderedNodes, setRenderedNodes] = React.useState<readonly GraphViewRenderedNode[]>([]);\n\n useRuntimeLifecycle(containerRef, runtimeRef, callbacksRef);\n useRuntimeGraphUpdate(runtimeRef, props);\n useRuntimeSelection(runtimeRef, props.selectedNodeIds);\n useRenderedNodeSubscription(runtimeRef, props.renderNode, setRenderedNodes);\n useKnownNodeSizes(runtimeRef, props.nodes, props.getNodeSize);\n\n return { containerRef, renderedNodes, runtimeRef };\n}\n\n/*** Keep the callback ref current without mutating refs during render. */\nfunction useGraphCallbacksRef(props: GraphViewProps) {\n const callbacksRef = React.useRef<GraphViewCallbacks>(readCallbacks(props));\n const {\n onEdgeEvent,\n onLayoutComplete,\n onNodeEvent,\n onReady,\n onViewportChange,\n onSpacingFactorChange,\n } = props;\n\n React.useEffect(() => {\n callbacksRef.current = {\n onEdgeEvent,\n onLayoutComplete,\n onNodeEvent,\n onReady,\n onViewportChange,\n onSpacingFactorChange,\n };\n }, [\n onEdgeEvent,\n onLayoutComplete,\n onNodeEvent,\n onReady,\n onViewportChange,\n onSpacingFactorChange,\n ]);\n\n return callbacksRef;\n}\n\n/*** Create and dispose the runtime only with the underlying browser container lifetime. */\nfunction useRuntimeLifecycle(\n containerRef: { current: GraphContainer | null },\n runtimeRef: { current: GraphRuntime | null },\n callbacksRef: { current: GraphViewCallbacks },\n) {\n React.useEffect(() => {\n const container = containerRef.current;\n if (container === null) return;\n const runtime = createGraphRuntime(container, callbacksRef);\n runtimeRef.current = runtime;\n return () => {\n runtime.destroy();\n runtimeRef.current = null;\n };\n }, [callbacksRef, containerRef, runtimeRef]);\n}\n\n/*** Synchronize graph data and layout policy without exposing Cytoscape to React. */\nfunction useRuntimeGraphUpdate(\n runtimeRef: { current: GraphRuntime | null },\n props: GraphViewProps,\n) {\n const {\n edges,\n fitPadding,\n layout,\n layoutOptions,\n maxZoom,\n minReadableLabelSize,\n maxFitLabelSize,\n minZoom,\n zoomMode,\n sizeNodesToLabels,\n nodes,\n renderNode,\n spacingFactor,\n styleRules,\n } = props;\n\n React.useEffect(() => {\n runtimeRef.current?.update({\n edges,\n fitPadding,\n layout,\n layoutOptions,\n maxZoom,\n minReadableLabelSize,\n maxFitLabelSize,\n minZoom,\n zoomMode,\n sizeNodesToLabels,\n nodes,\n richNodeRendering: renderNode !== undefined,\n spacingFactor,\n styleRules,\n });\n }, [\n edges,\n fitPadding,\n layout,\n layoutOptions,\n maxZoom,\n minReadableLabelSize,\n maxFitLabelSize,\n minZoom,\n zoomMode,\n sizeNodesToLabels,\n nodes,\n renderNode,\n runtimeRef,\n spacingFactor,\n styleRules,\n ]);\n}\n\n/*** Synchronize optional controlled selection independently from graph layout. */\nfunction useRuntimeSelection(\n runtimeRef: { current: GraphRuntime | null },\n selectedNodeIds: readonly string[] | undefined,\n) {\n React.useEffect(() => {\n runtimeRef.current?.setSelectedNodeIds(selectedNodeIds);\n }, [runtimeRef, selectedNodeIds]);\n}\n\n/*** Subscribe the optional rich-node overlay to rendered runtime positions. */\nfunction useRenderedNodeSubscription(\n runtimeRef: { current: GraphRuntime | null },\n renderNode: GraphViewProps['renderNode'],\n setRenderedNodes: (nodes: readonly GraphViewRenderedNode[]) => void,\n) {\n React.useEffect(() => {\n const runtime = runtimeRef.current;\n if (runtime === null || renderNode === undefined) {\n setRenderedNodes([]);\n return;\n }\n return runtime.subscribeRenderedNodes(setRenderedNodes);\n }, [renderNode, runtimeRef, setRenderedNodes]);\n}\n\n/*** Seed known rich-node dimensions without requiring DOM measurement. */\nfunction useKnownNodeSizes(\n runtimeRef: { current: GraphRuntime | null },\n nodes: readonly GraphViewNode[],\n getNodeSize: GraphViewProps['getNodeSize'],\n) {\n React.useEffect(() => {\n const runtime = runtimeRef.current;\n if (runtime === null || getNodeSize === undefined) return;\n for (const node of nodes) {\n const size = getNodeSize(node);\n if (size) runtime.setNodeSize(node.id, size);\n }\n }, [getNodeSize, nodes, runtimeRef]);\n}\n\n/*** Read callback props for the initial runtime callback-ref value. */\nfunction readCallbacks(props: GraphViewProps): GraphViewCallbacks {\n return {\n onEdgeEvent: props.onEdgeEvent,\n onLayoutComplete: props.onLayoutComplete,\n onNodeEvent: props.onNodeEvent,\n onReady: props.onReady,\n onViewportChange: props.onViewportChange,\n onSpacingFactorChange: props.onSpacingFactorChange,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"GraphView.js","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/GraphView.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { CytoscapeOptions } from 'cytoscape';\nimport React from 'react';\n\nimport type {\n GraphViewCallbacks,\n GraphViewNode,\n GraphViewProps,\n GraphViewRenderedNode,\n} from '../../../../../types/graph-view';\nimport { GRAPH_VIEW_EVENTS } from '../../../constants/events';\nimport { createGraphRuntime, type GraphRuntime } from './createGraphRuntime';\nimport { createGraphViewDomCallbacks } from './createGraphViewDomCallbacks';\nimport { GraphNodeOverlay } from './GraphNodeOverlay';\n\ntype GraphContainer = NonNullable<CytoscapeOptions['container']>;\n\n/***\n * Render a browser graph while one runtime owns Cytoscape layout, viewport, and disposal.\n * zoomMode Use `fit-relative` to make controller zoom, events and min/max limits relative\n * to the visible-node fit (1 = fit). The default `absolute` keeps engine-space zoom units.\n * sizeNodesToLabels Opt in to renderer-measured plain labels, including bold and Unicode\n * text. Keep padding in styleRules. Compound and rich-node dimensions retain their existing owners.\n * minReadableLabelSize Expand the manual zoom ceiling until plain labels reach this CSS-pixel size.\n * maxFitLabelSize Cap automatic fitting at this plain-label CSS-pixel size, leaving whitespace for tiny graphs.\n * Browser lifecycle events named by public GRAPH_VIEW_EVENTS bubble from the root\n * with `detail.controller`: the same public GraphViewController delivered to React.\n * Browser integrations can call `measureNodes` without accessing the Cytoscape runtime.\n * The standalone browser artifact exposes the same names as `GraphView.events`.\n * onSpacingFactorChange Receive the spacing accepted by explicit fit({ optimizeSpacing: true }).\n * Ordinary zoom never adjusts spacing or relayouts. Explicit optimization preserves layout ordering,\n * stops once effective fit reaches maxFitLabelSize, keeps labels and unrelated compound boundaries\n * collision-safe, and permits only a small rendered background-overlap fallback for dense graphs.\n */\nexport function GraphView(props: GraphViewProps) {\n const hostRef = React.useRef<HTMLDivElement | null>(null);\n const { containerRef, renderedNodes, runtimeRef } = useGraphViewRuntime(props, hostRef);\n\n return (\n <div\n ref={hostRef}\n aria-label={props.ariaLabel ?? 'Graph'}\n className={props.className}\n role=\"application\"\n style={{\n height: '100%',\n minHeight: 0,\n minWidth: 0,\n position: 'relative',\n width: '100%',\n ...props.style,\n }}\n >\n <div ref={containerRef} style={{ height: '100%', width: '100%' }} />\n {props.renderNode ? (\n <GraphNodeOverlay\n nodes={props.nodes}\n renderedNodes={renderedNodes}\n renderNode={props.renderNode}\n onNodeEvent={(id, type, selectionIntent) =>\n runtimeRef.current?.handleOverlayNodeEvent(id, type, selectionIntent)\n }\n onNodeSize={(id, size) => runtimeRef.current?.setNodeSize(id, size)}\n />\n ) : null}\n </div>\n );\n}\n\n/*** Publish the same canonical event registry on the standalone GraphView artifact. */\nGraphView.events = GRAPH_VIEW_EVENTS;\n\n/*** Compose React synchronization around the single imperative graph runtime. */\nfunction useGraphViewRuntime(\n props: GraphViewProps,\n hostRef: React.RefObject<HTMLDivElement | null>,\n) {\n const containerRef = React.useRef<GraphContainer | null>(null);\n const runtimeRef = React.useRef<GraphRuntime | null>(null);\n const callbacksRef = useGraphCallbacksRef(props, hostRef);\n const [renderedNodes, setRenderedNodes] = React.useState<readonly GraphViewRenderedNode[]>([]);\n\n useRuntimeLifecycle(containerRef, runtimeRef, callbacksRef);\n useRuntimeGraphUpdate(runtimeRef, props);\n useRuntimeSelection(runtimeRef, props.selectedNodeIds);\n useRenderedNodeSubscription(runtimeRef, props.renderNode, setRenderedNodes);\n useKnownNodeSizes(runtimeRef, props.nodes, props.getNodeSize);\n\n return { containerRef, renderedNodes, runtimeRef };\n}\n\n/*** Keep the callback ref current without mutating refs during render. */\nfunction useGraphCallbacksRef(\n props: GraphViewProps,\n hostRef: React.RefObject<HTMLDivElement | null>,\n) {\n const callbacksRef = React.useRef<GraphViewCallbacks>(\n createGraphViewDomCallbacks(readCallbacks(props), hostRef),\n );\n const {\n onEdgeEvent,\n onLayoutComplete,\n onNodeEvent,\n onReady,\n onViewportChange,\n onSpacingFactorChange,\n } = props;\n\n React.useEffect(() => {\n callbacksRef.current = createGraphViewDomCallbacks(\n {\n onEdgeEvent,\n onLayoutComplete,\n onNodeEvent,\n onReady,\n onViewportChange,\n onSpacingFactorChange,\n },\n hostRef,\n );\n }, [\n onEdgeEvent,\n onLayoutComplete,\n onNodeEvent,\n onReady,\n onViewportChange,\n onSpacingFactorChange,\n hostRef,\n ]);\n\n return callbacksRef;\n}\n\n/*** Create and dispose the runtime only with the underlying browser container lifetime. */\nfunction useRuntimeLifecycle(\n containerRef: { current: GraphContainer | null },\n runtimeRef: { current: GraphRuntime | null },\n callbacksRef: { current: GraphViewCallbacks },\n) {\n React.useEffect(() => {\n const container = containerRef.current;\n if (container === null) return;\n const runtime = createGraphRuntime(container, callbacksRef);\n runtimeRef.current = runtime;\n return () => {\n runtime.destroy();\n runtimeRef.current = null;\n };\n }, [callbacksRef, containerRef, runtimeRef]);\n}\n\n/*** Synchronize graph data and layout policy without exposing Cytoscape to React. */\nfunction useRuntimeGraphUpdate(\n runtimeRef: { current: GraphRuntime | null },\n props: GraphViewProps,\n) {\n const {\n edges,\n fitPadding,\n layout,\n layoutOptions,\n maxZoom,\n minReadableLabelSize,\n maxFitLabelSize,\n minZoom,\n zoomMode,\n sizeNodesToLabels,\n nodes,\n renderNode,\n spacingFactor,\n styleRules,\n } = props;\n\n React.useEffect(() => {\n runtimeRef.current?.update({\n edges,\n fitPadding,\n layout,\n layoutOptions,\n maxZoom,\n minReadableLabelSize,\n maxFitLabelSize,\n minZoom,\n zoomMode,\n sizeNodesToLabels,\n nodes,\n richNodeRendering: renderNode !== undefined,\n spacingFactor,\n styleRules,\n });\n }, [\n edges,\n fitPadding,\n layout,\n layoutOptions,\n maxZoom,\n minReadableLabelSize,\n maxFitLabelSize,\n minZoom,\n zoomMode,\n sizeNodesToLabels,\n nodes,\n renderNode,\n runtimeRef,\n spacingFactor,\n styleRules,\n ]);\n}\n\n/*** Synchronize optional controlled selection independently from graph layout. */\nfunction useRuntimeSelection(\n runtimeRef: { current: GraphRuntime | null },\n selectedNodeIds: readonly string[] | undefined,\n) {\n React.useEffect(() => {\n runtimeRef.current?.setSelectedNodeIds(selectedNodeIds);\n }, [runtimeRef, selectedNodeIds]);\n}\n\n/*** Subscribe the optional rich-node overlay to rendered runtime positions. */\nfunction useRenderedNodeSubscription(\n runtimeRef: { current: GraphRuntime | null },\n renderNode: GraphViewProps['renderNode'],\n setRenderedNodes: (nodes: readonly GraphViewRenderedNode[]) => void,\n) {\n React.useEffect(() => {\n const runtime = runtimeRef.current;\n if (runtime === null || renderNode === undefined) {\n setRenderedNodes([]);\n return;\n }\n return runtime.subscribeRenderedNodes(setRenderedNodes);\n }, [renderNode, runtimeRef, setRenderedNodes]);\n}\n\n/*** Seed known rich-node dimensions without requiring DOM measurement. */\nfunction useKnownNodeSizes(\n runtimeRef: { current: GraphRuntime | null },\n nodes: readonly GraphViewNode[],\n getNodeSize: GraphViewProps['getNodeSize'],\n) {\n React.useEffect(() => {\n const runtime = runtimeRef.current;\n if (runtime === null || getNodeSize === undefined) return;\n for (const node of nodes) {\n const size = getNodeSize(node);\n if (size) runtime.setNodeSize(node.id, size);\n }\n }, [getNodeSize, nodes, runtimeRef]);\n}\n\n/*** Read callback props for the initial runtime callback-ref value. */\nfunction readCallbacks(props: GraphViewProps): GraphViewCallbacks {\n return {\n onEdgeEvent: props.onEdgeEvent,\n onLayoutComplete: props.onLayoutComplete,\n onNodeEvent: props.onNodeEvent,\n onReady: props.onReady,\n onViewportChange: props.onViewportChange,\n onSpacingFactorChange: props.onSpacingFactorChange,\n };\n}\n"]}
|
|
@@ -11,6 +11,8 @@ export declare function createGraphController(cy: Core, fitPaddingRef: {
|
|
|
11
11
|
getFitZoomLimit(): number;
|
|
12
12
|
/*** Return the native label-readability threshold used before allowing background overlap. */
|
|
13
13
|
getReadableZoomTarget(): number;
|
|
14
|
+
/*** Capture the model point beneath the current viewport center before Cytoscape resizes. */
|
|
15
|
+
beginResize(): void;
|
|
14
16
|
settle(fit: boolean): void;
|
|
15
17
|
};
|
|
16
18
|
//# sourceMappingURL=createGraphController.d.ts.map
|
package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphController.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createGraphController.d.ts","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/createGraphController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,KAAK,EACV,mBAAmB,EACnB,mBAAmB,EAEpB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAGhF,kGAAkG;AAClG,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,IAAI,EACR,aAAa,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,EAClC,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI;;
|
|
1
|
+
{"version":3,"file":"createGraphController.d.ts","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/createGraphController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,KAAK,EACV,mBAAmB,EACnB,mBAAmB,EAEpB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAGhF,kGAAkG;AAClG,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,IAAI,EACR,aAAa,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,EAClC,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI;;qBAkDjC,kBAAkB;IASnC,6EAA6E;;IAI7E,8FAA8F;;IAI9F,6FAA6F;;gBAIjF,OAAO;EAuBtB"}
|
|
@@ -10,6 +10,7 @@ export function createGraphController(cy, fitPaddingRef, optimizeFit) {
|
|
|
10
10
|
fitSize: Infinity,
|
|
11
11
|
readableZoom: 0,
|
|
12
12
|
fitZoom: Infinity,
|
|
13
|
+
resizeSnapshot: null,
|
|
13
14
|
};
|
|
14
15
|
const controller = {
|
|
15
16
|
fit(options) {
|
|
@@ -61,6 +62,10 @@ export function createGraphController(cy, fitPaddingRef, optimizeFit) {
|
|
|
61
62
|
getReadableZoomTarget() {
|
|
62
63
|
return state.readableZoom;
|
|
63
64
|
},
|
|
65
|
+
/*** Capture the model point beneath the current viewport center before Cytoscape resizes. */
|
|
66
|
+
beginResize() {
|
|
67
|
+
state.resizeSnapshot = captureResizeSnapshot(cy);
|
|
68
|
+
},
|
|
64
69
|
settle(fit) {
|
|
65
70
|
const logicalZoom = cy.zoom() / state.scale;
|
|
66
71
|
const fonts = cy
|
|
@@ -78,15 +83,74 @@ export function createGraphController(cy, fitPaddingRef, optimizeFit) {
|
|
|
78
83
|
? Math.min(getNodeFitZoom(cy, fitPaddingRef.current), state.fitZoom)
|
|
79
84
|
: 1;
|
|
80
85
|
applyZoomLimits(cy, state);
|
|
81
|
-
if (fit)
|
|
86
|
+
if (fit) {
|
|
87
|
+
state.resizeSnapshot = null;
|
|
82
88
|
controller.fit();
|
|
83
|
-
else if (state.relative) {
|
|
84
|
-
setCenteredZoom(cy, logicalZoom * state.scale);
|
|
85
|
-
cy.center(cy.nodes());
|
|
86
89
|
}
|
|
90
|
+
else if (settlePreservedViewport(cy, state, logicalZoom))
|
|
91
|
+
controller.fit();
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/*** Capture the model-space center and whether the prior viewport was an intact graph fit. */
|
|
96
|
+
function captureResizeSnapshot(cy) {
|
|
97
|
+
const viewport = { width: cy.width(), height: cy.height() };
|
|
98
|
+
const zoom = cy.zoom();
|
|
99
|
+
if (!isUsableViewport(viewport) || !Number.isFinite(zoom) || zoom <= 0)
|
|
100
|
+
return null;
|
|
101
|
+
const pan = cy.pan();
|
|
102
|
+
const renderedCenter = { x: viewport.width / 2, y: viewport.height / 2 };
|
|
103
|
+
return {
|
|
104
|
+
center: {
|
|
105
|
+
x: (renderedCenter.x - pan.x) / zoom,
|
|
106
|
+
y: (renderedCenter.y - pan.y) / zoom,
|
|
87
107
|
},
|
|
108
|
+
nodeBounds: getNodeBoundsStatus(cy, viewport),
|
|
88
109
|
};
|
|
89
110
|
}
|
|
111
|
+
/*** Preserve a manual viewport through resize, fitting only when an intact prior fit becomes invalid. */
|
|
112
|
+
function settlePreservedViewport(cy, state, logicalZoom) {
|
|
113
|
+
const snapshot = state.resizeSnapshot;
|
|
114
|
+
state.resizeSnapshot = null;
|
|
115
|
+
const zoom = state.relative ? logicalZoom * state.scale : cy.zoom();
|
|
116
|
+
if (snapshot === null) {
|
|
117
|
+
if (state.relative)
|
|
118
|
+
setCenteredZoom(cy, zoom);
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
const viewport = { width: cy.width(), height: cy.height() };
|
|
122
|
+
if (!isUsableViewport(viewport))
|
|
123
|
+
return false;
|
|
124
|
+
cy.viewport({
|
|
125
|
+
zoom,
|
|
126
|
+
pan: {
|
|
127
|
+
x: viewport.width / 2 - snapshot.center.x * zoom,
|
|
128
|
+
y: viewport.height / 2 - snapshot.center.y * zoom,
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
const nodeBounds = getNodeBoundsStatus(cy, viewport);
|
|
132
|
+
return nodeBounds === 'unusable' || (snapshot.nodeBounds === 'fit' && nodeBounds === 'clipped');
|
|
133
|
+
}
|
|
134
|
+
/*** Accept only finite non-empty node bounds that are wholly visible in a usable viewport. */
|
|
135
|
+
function getNodeBoundsStatus(cy, viewport) {
|
|
136
|
+
if (cy.nodes().empty())
|
|
137
|
+
return 'empty';
|
|
138
|
+
if (!isUsableViewport(viewport))
|
|
139
|
+
return 'unusable';
|
|
140
|
+
const bounds = cy.nodes().renderedBoundingBox();
|
|
141
|
+
if (![bounds.x1, bounds.x2, bounds.y1, bounds.y2].every(Number.isFinite))
|
|
142
|
+
return 'unusable';
|
|
143
|
+
const fitsHorizontally = bounds.x1 >= 0 && bounds.x2 <= viewport.width;
|
|
144
|
+
const fitsVertically = bounds.y1 >= 0 && bounds.y2 <= viewport.height;
|
|
145
|
+
return fitsHorizontally && fitsVertically ? 'fit' : 'clipped';
|
|
146
|
+
}
|
|
147
|
+
/*** Reject zero, negative, and non-finite renderer dimensions before viewport arithmetic. */
|
|
148
|
+
function isUsableViewport(viewport) {
|
|
149
|
+
return (Number.isFinite(viewport.width) &&
|
|
150
|
+
Number.isFinite(viewport.height) &&
|
|
151
|
+
viewport.width > 0 &&
|
|
152
|
+
viewport.height > 0);
|
|
153
|
+
}
|
|
90
154
|
/*** Measure rendered node geometry for public consumers while keeping Cytoscape private. */
|
|
91
155
|
function measureRenderedNodes(cy, nodeIds) {
|
|
92
156
|
const viewport = { width: cy.width(), height: cy.height() };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createGraphController.js","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/createGraphController.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,kGAAkG;AAClG,MAAM,UAAU,qBAAqB,CACnC,EAAQ,EACR,aAAkC,EAClC,WAAoD;IAEpD,MAAM,KAAK,GAAG;QACZ,KAAK,EAAE,CAAC;QACR,QAAQ,EAAE,KAAK;QACf,GAAG,EAAE,IAAI;QACT,GAAG,EAAE,CAAC;QACN,YAAY,EAAE,CAAC;QACf,OAAO,EAAE,QAAQ;QACjB,YAAY,EAAE,CAAC;QACf,OAAO,EAAE,QAAQ;KAClB,CAAC;IACF,MAAM,UAAU,GAAwB;QACtC,GAAG,CAAC,OAAO;YACT,IAAI,OAAO,EAAE,eAAe,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,IAAI,WAAW,EAAE,CAAC;gBACxE,WAAW,CAAC,OAAO,CAAC,CAAC;gBACrB,OAAO;YACT,CAAC;YACD,gBAAgB,CACd,EAAE,EACF;gBACE,OAAO,EAAE,OAAO,EAAE,OAAO;gBACzB,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,aAAa,CAAC,OAAO;aACnD,EACD,KAAK,CAAC,OAAO,CACd,CAAC;QACJ,CAAC;QACD,YAAY,CAAC,OAAO;YAClB,OAAO,oBAAoB,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QAC3C,CAAC;QACD,WAAW;YACT,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;QAC1D,CAAC;QACD,YAAY;YACV,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;QAC9E,CAAC;QACD,MAAM,CAAC,GAAG;YACR,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,CAAC;QACD,OAAO,CAAC,IAAI;YACV,eAAe,CAAC,EAAE,EAAE,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1C,CAAC;QACD,MAAM,CAAC,MAAM;YACX,eAAe,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,GAAG,MAAM,CAAC,CAAC;QAC1C,CAAC;KACF,CAAC;IAEF,OAAO;QACL,UAAU;QACV,SAAS,CAAC,KAAyB;YACjC,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,KAAK,cAAc,CAAC;YACnD,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,OAAO,IAAI,IAAI,CAAC;YAClC,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;YAC/B,KAAK,CAAC,YAAY,GAAG,YAAY,CAAC,KAAK,CAAC,oBAAoB,EAAE,CAAC,CAAC,CAAC;YACjE,KAAK,CAAC,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC;YAC9D,IAAI,CAAC,KAAK,CAAC,QAAQ;gBAAE,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC;YACrC,eAAe,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QAC7B,CAAC;QACD,6EAA6E;QAC7E,eAAe;YACb,OAAO,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;QAC7E,CAAC;QACD,8FAA8F;QAC9F,qBAAqB;YACnB,OAAO,KAAK,CAAC,YAAY,CAAC;QAC5B,CAAC;QACD,MAAM,CAAC,GAAY;YACjB,MAAM,WAAW,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC;YAC5C,MAAM,KAAK,GAAG,EAAE;iBACb,KAAK,EAAE;iBACP,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;iBAClE,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;iBACjE,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC;YACvD,KAAK,CAAC,YAAY,GAAG,KAAK,CAAC,MAAM;gBAC/B,CAAC,CAAC,KAAK,CAAC,YAAY,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;gBACzF,CAAC,CAAC,CAAC,CAAC;YACN,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,MAAM;gBAC1B,CAAC,CAAC,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;gBAC7E,CAAC,CAAC,QAAQ,CAAC;YACb,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,QAAQ;gBAC1B,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,EAAE,aAAa,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC;gBACpE,CAAC,CAAC,CAAC,CAAC;YACN,eAAe,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;YAC3B,IAAI,GAAG;gBAAE,UAAU,CAAC,GAAG,EAAE,CAAC;iBACrB,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACxB,eAAe,CAAC,EAAE,EAAE,WAAW,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;gBAC/C,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC;YACxB,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,4FAA4F;AAC5F,SAAS,oBAAoB,CAAC,EAAQ,EAAE,OAA0B;IAChE,MAAM,QAAQ,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC;IAC5D,IAAI,QAAQ,CAAC,KAAK,IAAI,CAAC,IAAI,QAAQ,CAAC,MAAM,IAAI,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAErF,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE;QACjD,MAAM,IAAI,GAAG,EAAE,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;QACnC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAAE,OAAO,EAAE,CAAC;QAE9B,MAAM,MAAM,GAAG,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC1C,OAAO;YACL;gBACE,EAAE;gBACF,QAAQ,EAAE,IAAI,CAAC,gBAAgB,EAAE;gBACjC,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE;aAC5C;SACF,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;AACzD,CAAC;AAED,mGAAmG;AACnG,SAAS,eAAe,CACtB,EAAQ,EACR,KAKC;IAED,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC;IACpC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;IACxC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC;IACvE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;AAClB,CAAC;AAED,wFAAwF;AACxF,SAAS,YAAY,CAAC,KAAyB,EAAE,QAAgB;IAC/D,OAAO,KAAK,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC;AACvF,CAAC;AAED;;;GAGG;AACH,SAAS,cAAc,CAAC,EAAQ,EAAE,OAAe;IAC/C,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,CAAC,CAAC;IACjC,MAAM,MAAM,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,CAAC;IACxC,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC;IACvC,MAAM,MAAM,GAAG,EAAE,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC;IACzC,IAAI,KAAK,IAAI,CAAC,IAAI,MAAM,IAAI,CAAC,IAAI,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,CAAC,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IAC1E,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,+EAA+E;AAC/E,SAAS,eAAe,CAAC,EAAQ,EAAE,IAAY;IAC7C,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,gBAAgB,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;AACxF,CAAC","sourcesContent":["import type { Core } from 'cytoscape';\n\nimport type {\n GraphViewController,\n GraphViewFitOptions,\n GraphViewMeasurement,\n} from '../../../../../types/graph-view';\nimport type { GraphRuntimeUpdate } from '../../../../../types/graphViewRuntime';\nimport { fitGraphViewport } from './fitGraphViewport';\n\n/*** Own viewport units and limits alongside the public controller, without exposing Cytoscape. */\nexport function createGraphController(\n cy: Core,\n fitPaddingRef: { current: number },\n optimizeFit?: (options: GraphViewFitOptions) => void,\n) {\n const state = {\n scale: 1,\n relative: false,\n min: 0.05,\n max: 2,\n readableSize: 0,\n fitSize: Infinity,\n readableZoom: 0,\n fitZoom: Infinity,\n };\n const controller: GraphViewController = {\n fit(options) {\n if (options?.optimizeSpacing && !options.nodeIds?.length && optimizeFit) {\n optimizeFit(options);\n return;\n }\n fitGraphViewport(\n cy,\n {\n nodeIds: options?.nodeIds,\n padding: options?.padding ?? fitPaddingRef.current,\n },\n state.fitZoom,\n );\n },\n measureNodes(nodeIds) {\n return measureRenderedNodes(cy, nodeIds);\n },\n getViewport() {\n return { pan: cy.pan(), zoom: cy.zoom() / state.scale };\n },\n getZoomRange() {\n return { min: cy.minZoom() / state.scale, max: cy.maxZoom() / state.scale };\n },\n setPan(pan) {\n cy.pan(pan);\n },\n setZoom(zoom) {\n setCenteredZoom(cy, zoom * state.scale);\n },\n zoomBy(factor) {\n setCenteredZoom(cy, cy.zoom() * factor);\n },\n };\n\n return {\n controller,\n configure(input: GraphRuntimeUpdate) {\n state.relative = input.zoomMode === 'fit-relative';\n state.min = input.minZoom ?? 0.05;\n state.max = input.maxZoom ?? 2;\n state.readableSize = positiveSize(input.minReadableLabelSize, 0);\n state.fitSize = positiveSize(input.maxFitLabelSize, Infinity);\n if (!state.relative) state.scale = 1;\n applyZoomLimits(cy, state);\n },\n /*** Return the native automatic-fit ceiling used by spacing optimization. */\n getFitZoomLimit() {\n return state.relative ? state.fitZoom : Math.min(state.fitZoom, state.max);\n },\n /*** Return the native label-readability threshold used before allowing background overlap. */\n getReadableZoomTarget() {\n return state.readableZoom;\n },\n settle(fit: boolean) {\n const logicalZoom = cy.zoom() / state.scale;\n const fonts = cy\n .nodes()\n .filter((node) => !node.isParent() && Boolean(node.style('label')))\n .map((node) => Number.parseFloat(String(node.style('font-size'))))\n .filter((size) => Number.isFinite(size) && size > 0);\n state.readableZoom = fonts.length\n ? state.readableSize / fonts.reduce((minimum, size) => Math.min(minimum, size), Infinity)\n : 0;\n state.fitZoom = fonts.length\n ? state.fitSize / fonts.reduce((maximum, size) => Math.max(maximum, size), 0)\n : Infinity;\n state.scale = state.relative\n ? Math.min(getNodeFitZoom(cy, fitPaddingRef.current), state.fitZoom)\n : 1;\n applyZoomLimits(cy, state);\n if (fit) controller.fit();\n else if (state.relative) {\n setCenteredZoom(cy, logicalZoom * state.scale);\n cy.center(cy.nodes());\n }\n },\n };\n}\n\n/*** Measure rendered node geometry for public consumers while keeping Cytoscape private. */\nfunction measureRenderedNodes(cy: Core, nodeIds: readonly string[]): GraphViewMeasurement | null {\n const viewport = { width: cy.width(), height: cy.height() };\n if (viewport.width <= 0 || viewport.height <= 0 || nodeIds.length === 0) return null;\n\n const nodes = [...new Set(nodeIds)].flatMap((id) => {\n const node = cy.getElementById(id);\n if (!node.isNode()) return [];\n\n const bounds = node.renderedBoundingBox();\n return [\n {\n id,\n position: node.renderedPosition(),\n size: { width: bounds.w, height: bounds.h },\n },\n ];\n });\n return nodes.length === 0 ? null : { nodes, viewport };\n}\n\n/*** Keep pointer, wheel, and programmatic zoom on the same limits in the engine's native units. */\nfunction applyZoomLimits(\n cy: Core,\n state: {\n readonly min: number;\n readonly max: number;\n readonly scale: number;\n readonly readableZoom: number;\n },\n) {\n const min = state.min * state.scale;\n cy.minZoom(Math.min(cy.minZoom(), min));\n cy.maxZoom(Math.max(min, state.max * state.scale, state.readableZoom));\n cy.minZoom(min);\n}\n\n/*** Ignore invalid optional typography targets at the public configuration boundary. */\nfunction positiveSize(value: number | undefined, fallback: number): number {\n return value !== undefined && Number.isFinite(value) && value > 0 ? value : fallback;\n}\n\n/***\n * Recompute the fit reference only after layout or resize, never on every pan/zoom event.\n * Cache the geometry-derived scale in the viewport owner; labels are included, edges excluded.\n */\nfunction getNodeFitZoom(cy: Core, padding: number): number {\n if (cy.nodes().empty()) return 1;\n const bounds = cy.nodes().boundingBox();\n const width = cy.width() - 2 * padding;\n const height = cy.height() - 2 * padding;\n if (width <= 0 || height <= 0 || bounds.w <= 0 || bounds.h <= 0) return 1;\n return Math.min(width / bounds.w, height / bounds.h);\n}\n\n/*** Zoom around the visible viewport center instead of its top-left corner. */\nfunction setCenteredZoom(cy: Core, zoom: number) {\n cy.zoom({ level: zoom, renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 } });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"createGraphController.js","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/createGraphController.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Core } from 'cytoscape';\n\nimport type {\n GraphViewController,\n GraphViewFitOptions,\n GraphViewMeasurement,\n} from '../../../../../types/graph-view';\nimport type { GraphRuntimeUpdate } from '../../../../../types/graphViewRuntime';\nimport { fitGraphViewport } from './fitGraphViewport';\n\n/*** Own viewport units and limits alongside the public controller, without exposing Cytoscape. */\nexport function createGraphController(\n cy: Core,\n fitPaddingRef: { current: number },\n optimizeFit?: (options: GraphViewFitOptions) => void,\n) {\n const state = {\n scale: 1,\n relative: false,\n min: 0.05,\n max: 2,\n readableSize: 0,\n fitSize: Infinity,\n readableZoom: 0,\n fitZoom: Infinity,\n resizeSnapshot: null as GraphResizeSnapshot | null,\n };\n const controller: GraphViewController = {\n fit(options) {\n if (options?.optimizeSpacing && !options.nodeIds?.length && optimizeFit) {\n optimizeFit(options);\n return;\n }\n fitGraphViewport(\n cy,\n {\n nodeIds: options?.nodeIds,\n padding: options?.padding ?? fitPaddingRef.current,\n },\n state.fitZoom,\n );\n },\n measureNodes(nodeIds) {\n return measureRenderedNodes(cy, nodeIds);\n },\n getViewport() {\n return { pan: cy.pan(), zoom: cy.zoom() / state.scale };\n },\n getZoomRange() {\n return { min: cy.minZoom() / state.scale, max: cy.maxZoom() / state.scale };\n },\n setPan(pan) {\n cy.pan(pan);\n },\n setZoom(zoom) {\n setCenteredZoom(cy, zoom * state.scale);\n },\n zoomBy(factor) {\n setCenteredZoom(cy, cy.zoom() * factor);\n },\n };\n\n return {\n controller,\n configure(input: GraphRuntimeUpdate) {\n state.relative = input.zoomMode === 'fit-relative';\n state.min = input.minZoom ?? 0.05;\n state.max = input.maxZoom ?? 2;\n state.readableSize = positiveSize(input.minReadableLabelSize, 0);\n state.fitSize = positiveSize(input.maxFitLabelSize, Infinity);\n if (!state.relative) state.scale = 1;\n applyZoomLimits(cy, state);\n },\n /*** Return the native automatic-fit ceiling used by spacing optimization. */\n getFitZoomLimit() {\n return state.relative ? state.fitZoom : Math.min(state.fitZoom, state.max);\n },\n /*** Return the native label-readability threshold used before allowing background overlap. */\n getReadableZoomTarget() {\n return state.readableZoom;\n },\n /*** Capture the model point beneath the current viewport center before Cytoscape resizes. */\n beginResize() {\n state.resizeSnapshot = captureResizeSnapshot(cy);\n },\n settle(fit: boolean) {\n const logicalZoom = cy.zoom() / state.scale;\n const fonts = cy\n .nodes()\n .filter((node) => !node.isParent() && Boolean(node.style('label')))\n .map((node) => Number.parseFloat(String(node.style('font-size'))))\n .filter((size) => Number.isFinite(size) && size > 0);\n state.readableZoom = fonts.length\n ? state.readableSize / fonts.reduce((minimum, size) => Math.min(minimum, size), Infinity)\n : 0;\n state.fitZoom = fonts.length\n ? state.fitSize / fonts.reduce((maximum, size) => Math.max(maximum, size), 0)\n : Infinity;\n state.scale = state.relative\n ? Math.min(getNodeFitZoom(cy, fitPaddingRef.current), state.fitZoom)\n : 1;\n applyZoomLimits(cy, state);\n if (fit) {\n state.resizeSnapshot = null;\n controller.fit();\n } else if (settlePreservedViewport(cy, state, logicalZoom)) controller.fit();\n },\n };\n}\n\ninterface GraphResizeSnapshot {\n readonly center: { readonly x: number; readonly y: number };\n readonly nodeBounds: GraphNodeBoundsStatus;\n}\n\ntype GraphNodeBoundsStatus = 'clipped' | 'empty' | 'fit' | 'unusable';\n\n/*** Capture the model-space center and whether the prior viewport was an intact graph fit. */\nfunction captureResizeSnapshot(cy: Core): GraphResizeSnapshot | null {\n const viewport = { width: cy.width(), height: cy.height() };\n const zoom = cy.zoom();\n if (!isUsableViewport(viewport) || !Number.isFinite(zoom) || zoom <= 0) return null;\n const pan = cy.pan();\n const renderedCenter = { x: viewport.width / 2, y: viewport.height / 2 };\n return {\n center: {\n x: (renderedCenter.x - pan.x) / zoom,\n y: (renderedCenter.y - pan.y) / zoom,\n },\n nodeBounds: getNodeBoundsStatus(cy, viewport),\n };\n}\n\n/*** Preserve a manual viewport through resize, fitting only when an intact prior fit becomes invalid. */\nfunction settlePreservedViewport(\n cy: Core,\n state: {\n readonly relative: boolean;\n resizeSnapshot: GraphResizeSnapshot | null;\n readonly scale: number;\n },\n logicalZoom: number,\n): boolean {\n const snapshot = state.resizeSnapshot;\n state.resizeSnapshot = null;\n const zoom = state.relative ? logicalZoom * state.scale : cy.zoom();\n if (snapshot === null) {\n if (state.relative) setCenteredZoom(cy, zoom);\n return false;\n }\n const viewport = { width: cy.width(), height: cy.height() };\n if (!isUsableViewport(viewport)) return false;\n cy.viewport({\n zoom,\n pan: {\n x: viewport.width / 2 - snapshot.center.x * zoom,\n y: viewport.height / 2 - snapshot.center.y * zoom,\n },\n });\n const nodeBounds = getNodeBoundsStatus(cy, viewport);\n return nodeBounds === 'unusable' || (snapshot.nodeBounds === 'fit' && nodeBounds === 'clipped');\n}\n\n/*** Accept only finite non-empty node bounds that are wholly visible in a usable viewport. */\nfunction getNodeBoundsStatus(\n cy: Core,\n viewport: { readonly width: number; readonly height: number },\n): GraphNodeBoundsStatus {\n if (cy.nodes().empty()) return 'empty';\n if (!isUsableViewport(viewport)) return 'unusable';\n const bounds = cy.nodes().renderedBoundingBox();\n if (![bounds.x1, bounds.x2, bounds.y1, bounds.y2].every(Number.isFinite)) return 'unusable';\n const fitsHorizontally = bounds.x1 >= 0 && bounds.x2 <= viewport.width;\n const fitsVertically = bounds.y1 >= 0 && bounds.y2 <= viewport.height;\n return fitsHorizontally && fitsVertically ? 'fit' : 'clipped';\n}\n\n/*** Reject zero, negative, and non-finite renderer dimensions before viewport arithmetic. */\nfunction isUsableViewport(viewport: { readonly width: number; readonly height: number }): boolean {\n return (\n Number.isFinite(viewport.width) &&\n Number.isFinite(viewport.height) &&\n viewport.width > 0 &&\n viewport.height > 0\n );\n}\n\n/*** Measure rendered node geometry for public consumers while keeping Cytoscape private. */\nfunction measureRenderedNodes(cy: Core, nodeIds: readonly string[]): GraphViewMeasurement | null {\n const viewport = { width: cy.width(), height: cy.height() };\n if (viewport.width <= 0 || viewport.height <= 0 || nodeIds.length === 0) return null;\n\n const nodes = [...new Set(nodeIds)].flatMap((id) => {\n const node = cy.getElementById(id);\n if (!node.isNode()) return [];\n\n const bounds = node.renderedBoundingBox();\n return [\n {\n id,\n position: node.renderedPosition(),\n size: { width: bounds.w, height: bounds.h },\n },\n ];\n });\n return nodes.length === 0 ? null : { nodes, viewport };\n}\n\n/*** Keep pointer, wheel, and programmatic zoom on the same limits in the engine's native units. */\nfunction applyZoomLimits(\n cy: Core,\n state: {\n readonly min: number;\n readonly max: number;\n readonly scale: number;\n readonly readableZoom: number;\n },\n) {\n const min = state.min * state.scale;\n cy.minZoom(Math.min(cy.minZoom(), min));\n cy.maxZoom(Math.max(min, state.max * state.scale, state.readableZoom));\n cy.minZoom(min);\n}\n\n/*** Ignore invalid optional typography targets at the public configuration boundary. */\nfunction positiveSize(value: number | undefined, fallback: number): number {\n return value !== undefined && Number.isFinite(value) && value > 0 ? value : fallback;\n}\n\n/***\n * Recompute the fit reference only after layout or resize, never on every pan/zoom event.\n * Cache the geometry-derived scale in the viewport owner; labels are included, edges excluded.\n */\nfunction getNodeFitZoom(cy: Core, padding: number): number {\n if (cy.nodes().empty()) return 1;\n const bounds = cy.nodes().boundingBox();\n const width = cy.width() - 2 * padding;\n const height = cy.height() - 2 * padding;\n if (width <= 0 || height <= 0 || bounds.w <= 0 || bounds.h <= 0) return 1;\n return Math.min(width / bounds.w, height / bounds.h);\n}\n\n/*** Zoom around the visible viewport center instead of its top-left corner. */\nfunction setCenteredZoom(cy: Core, zoom: number) {\n cy.zoom({ level: zoom, renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 } });\n}\n"]}
|
package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.d.ts
CHANGED
|
@@ -2,7 +2,8 @@ import type { Core } from 'cytoscape';
|
|
|
2
2
|
interface CreateGraphResizeObserverInput {
|
|
3
3
|
readonly container: unknown;
|
|
4
4
|
readonly cy: Core;
|
|
5
|
-
readonly
|
|
5
|
+
readonly beginViewportResize: () => void;
|
|
6
|
+
readonly settleViewport: (fit: boolean) => void;
|
|
6
7
|
readonly layoutRunningRef: {
|
|
7
8
|
current: boolean;
|
|
8
9
|
};
|
|
@@ -16,6 +17,7 @@ interface CreateGraphResizeObserverInput {
|
|
|
16
17
|
}
|
|
17
18
|
export interface GraphResizeObserver {
|
|
18
19
|
disconnect(): void;
|
|
20
|
+
flushAfterLayout(): void;
|
|
19
21
|
}
|
|
20
22
|
/*** Observe settled container resizing without racing an active layout generation. */
|
|
21
23
|
export declare function createGraphResizeObserver(input: CreateGraphResizeObserverInput): GraphResizeObserver | null;
|
package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createGraphResizeObserver.d.ts","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAItC,UAAU,8BAA8B;IACtC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"createGraphResizeObserver.d.ts","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAItC,UAAU,8BAA8B;IACtC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,mBAAmB,EAAE,MAAM,IAAI,CAAC;IACzC,QAAQ,CAAC,cAAc,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IAChD,QAAQ,CAAC,gBAAgB,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAChD,QAAQ,CAAC,oBAAoB,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACpD,QAAQ,CAAC,QAAQ,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACxC,QAAQ,CAAC,iBAAiB,EAAE,CAAC,0BAA0B,EAAE,OAAO,KAAK,IAAI,CAAC;CAC3E;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,IAAI,IAAI,CAAC;IACnB,gBAAgB,IAAI,IAAI,CAAC;CAC1B;AAED,sFAAsF;AACtF,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,8BAA8B,GACpC,mBAAmB,GAAG,IAAI,CA2B5B"}
|
|
@@ -5,32 +5,65 @@ export function createGraphResizeObserver(input) {
|
|
|
5
5
|
if (Observer === null)
|
|
6
6
|
return null;
|
|
7
7
|
const sizeRef = { height: -1, width: -1 };
|
|
8
|
+
const pendingLayoutResizeRef = { current: false };
|
|
9
|
+
const pendingResizeSnapshotRef = { current: false };
|
|
10
|
+
const resizeScheduledRef = { current: false };
|
|
8
11
|
const observer = new Observer((entries) => {
|
|
9
12
|
const entry = entries.at(0);
|
|
10
13
|
if (entry === undefined || !hasSizeChanged(sizeRef, entry.contentRect))
|
|
11
14
|
return;
|
|
12
15
|
sizeRef.height = entry.contentRect.height;
|
|
13
16
|
sizeRef.width = entry.contentRect.width;
|
|
14
|
-
if (input.layoutRunningRef.current)
|
|
15
|
-
return;
|
|
16
17
|
if (!input.readyRef.current && !input.pendingInitialFitRef.current)
|
|
17
18
|
return;
|
|
18
|
-
|
|
19
|
+
captureResizeSnapshot(input, pendingResizeSnapshotRef);
|
|
20
|
+
if (input.layoutRunningRef.current) {
|
|
21
|
+
pendingLayoutResizeRef.current = true;
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
scheduleResizeFit(input, pendingLayoutResizeRef, pendingResizeSnapshotRef, resizeScheduledRef);
|
|
19
25
|
});
|
|
20
26
|
observer.observe(input.container);
|
|
21
|
-
return
|
|
27
|
+
return {
|
|
28
|
+
disconnect: () => observer.disconnect(),
|
|
29
|
+
flushAfterLayout: () => flushAfterLayout(input, pendingLayoutResizeRef, pendingResizeSnapshotRef),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/*** Keep the first pre-resize viewport intent until its resize or layout settlement consumes it. */
|
|
33
|
+
function captureResizeSnapshot(input, pendingResizeSnapshotRef) {
|
|
34
|
+
if (pendingResizeSnapshotRef.current)
|
|
35
|
+
return;
|
|
36
|
+
input.beginViewportResize();
|
|
37
|
+
pendingResizeSnapshotRef.current = true;
|
|
22
38
|
}
|
|
23
|
-
/***
|
|
24
|
-
function
|
|
39
|
+
/*** Apply a resize observed during layout before that layout settles the preserved viewport. */
|
|
40
|
+
function flushAfterLayout(input, pendingLayoutResizeRef, pendingResizeSnapshotRef) {
|
|
41
|
+
if (!pendingLayoutResizeRef.current || input.cy.destroyed() || input.layoutRunningRef.current)
|
|
42
|
+
return;
|
|
43
|
+
pendingLayoutResizeRef.current = false;
|
|
44
|
+
pendingResizeSnapshotRef.current = false;
|
|
45
|
+
input.cy.resize();
|
|
46
|
+
}
|
|
47
|
+
/*** Preserve settled viewport intent across one coalesced resize, except the deferred initial fit. */
|
|
48
|
+
function scheduleResizeFit(input, pendingLayoutResizeRef, pendingResizeSnapshotRef, resizeScheduledRef) {
|
|
49
|
+
if (resizeScheduledRef.current)
|
|
50
|
+
return;
|
|
51
|
+
resizeScheduledRef.current = true;
|
|
25
52
|
scheduleGraphFrame(() => {
|
|
26
|
-
|
|
53
|
+
resizeScheduledRef.current = false;
|
|
54
|
+
if (input.cy.destroyed())
|
|
55
|
+
return;
|
|
56
|
+
if (input.layoutRunningRef.current) {
|
|
57
|
+
pendingLayoutResizeRef.current = true;
|
|
27
58
|
return;
|
|
59
|
+
}
|
|
28
60
|
input.cy.resize();
|
|
29
61
|
if (input.cy.width() <= 0 || input.cy.height() <= 0)
|
|
30
62
|
return;
|
|
63
|
+
pendingResizeSnapshotRef.current = false;
|
|
31
64
|
const completedPendingInitialFit = input.pendingInitialFitRef.current;
|
|
32
65
|
input.pendingInitialFitRef.current = false;
|
|
33
|
-
input.settleViewport();
|
|
66
|
+
input.settleViewport(completedPendingInitialFit);
|
|
34
67
|
input.onViewportSettled(completedPendingInitialFit);
|
|
35
68
|
});
|
|
36
69
|
}
|
package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createGraphResizeObserver.js","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"createGraphResizeObserver.js","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Core } from 'cytoscape';\n\nimport { scheduleGraphFrame } from './scheduleGraphFrame';\n\ninterface CreateGraphResizeObserverInput {\n readonly container: unknown;\n readonly cy: Core;\n readonly beginViewportResize: () => void;\n readonly settleViewport: (fit: boolean) => void;\n readonly layoutRunningRef: { current: boolean };\n readonly pendingInitialFitRef: { current: boolean };\n readonly readyRef: { current: boolean };\n readonly onViewportSettled: (completedPendingInitialFit: boolean) => void;\n}\n\nexport interface GraphResizeObserver {\n disconnect(): void;\n flushAfterLayout(): void;\n}\n\n/*** Observe settled container resizing without racing an active layout generation. */\nexport function createGraphResizeObserver(\n input: CreateGraphResizeObserverInput,\n): GraphResizeObserver | null {\n const Observer = readResizeObserverConstructor();\n if (Observer === null) return null;\n const sizeRef = { height: -1, width: -1 };\n const pendingLayoutResizeRef = { current: false };\n const pendingResizeSnapshotRef = { current: false };\n const resizeScheduledRef = { current: false };\n\n const observer = new Observer((entries) => {\n const entry = entries.at(0);\n if (entry === undefined || !hasSizeChanged(sizeRef, entry.contentRect)) return;\n sizeRef.height = entry.contentRect.height;\n sizeRef.width = entry.contentRect.width;\n if (!input.readyRef.current && !input.pendingInitialFitRef.current) return;\n captureResizeSnapshot(input, pendingResizeSnapshotRef);\n if (input.layoutRunningRef.current) {\n pendingLayoutResizeRef.current = true;\n return;\n }\n scheduleResizeFit(input, pendingLayoutResizeRef, pendingResizeSnapshotRef, resizeScheduledRef);\n });\n observer.observe(input.container);\n return {\n disconnect: () => observer.disconnect(),\n flushAfterLayout: () =>\n flushAfterLayout(input, pendingLayoutResizeRef, pendingResizeSnapshotRef),\n };\n}\n\n/*** Keep the first pre-resize viewport intent until its resize or layout settlement consumes it. */\nfunction captureResizeSnapshot(\n input: CreateGraphResizeObserverInput,\n pendingResizeSnapshotRef: { current: boolean },\n) {\n if (pendingResizeSnapshotRef.current) return;\n input.beginViewportResize();\n pendingResizeSnapshotRef.current = true;\n}\n\n/*** Apply a resize observed during layout before that layout settles the preserved viewport. */\nfunction flushAfterLayout(\n input: CreateGraphResizeObserverInput,\n pendingLayoutResizeRef: { current: boolean },\n pendingResizeSnapshotRef: { current: boolean },\n) {\n if (!pendingLayoutResizeRef.current || input.cy.destroyed() || input.layoutRunningRef.current)\n return;\n pendingLayoutResizeRef.current = false;\n pendingResizeSnapshotRef.current = false;\n input.cy.resize();\n}\n\n/*** Preserve settled viewport intent across one coalesced resize, except the deferred initial fit. */\nfunction scheduleResizeFit(\n input: CreateGraphResizeObserverInput,\n pendingLayoutResizeRef: { current: boolean },\n pendingResizeSnapshotRef: { current: boolean },\n resizeScheduledRef: { current: boolean },\n) {\n if (resizeScheduledRef.current) return;\n resizeScheduledRef.current = true;\n scheduleGraphFrame(() => {\n resizeScheduledRef.current = false;\n if (input.cy.destroyed()) return;\n if (input.layoutRunningRef.current) {\n pendingLayoutResizeRef.current = true;\n return;\n }\n input.cy.resize();\n if (input.cy.width() <= 0 || input.cy.height() <= 0) return;\n pendingResizeSnapshotRef.current = false;\n const completedPendingInitialFit = input.pendingInitialFitRef.current;\n input.pendingInitialFitRef.current = false;\n input.settleViewport(completedPendingInitialFit);\n input.onViewportSettled(completedPendingInitialFit);\n });\n}\n\n/*** Return whether ResizeObserver reported a genuinely different container size. */\nfunction hasSizeChanged(\n previous: { readonly height: number; readonly width: number },\n next: { readonly height: number; readonly width: number },\n): boolean {\n return previous.height !== next.height || previous.width !== next.width;\n}\n\n/*** Read ResizeObserver without imposing DOM library types on the package target. */\nfunction readResizeObserverConstructor(): ResizeObserverConstructorLike | null {\n const value = (\n globalThis as unknown as {\n ResizeObserver?: ResizeObserverConstructorLike;\n }\n ).ResizeObserver;\n return value ?? null;\n}\n\ninterface ResizeObserverEntryLike {\n readonly contentRect: {\n readonly height: number;\n readonly width: number;\n };\n}\n\ninterface ResizeObserverLike extends GraphResizeObserver {\n observe(target: unknown): void;\n}\n\ntype ResizeObserverConstructorLike = new (\n callback: (entries: readonly ResizeObserverEntryLike[]) => void,\n) => ResizeObserverLike;\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createGraphRuntime.d.ts","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAkB,EAAa,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAExE,OAAO,KAAK,EACV,kBAAkB,EAClB,yBAAyB,EAEzB,qBAAqB,EACrB,aAAa,EAEd,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAiBhF,KAAK,cAAc,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;AACpD,KAAK,oBAAoB,GAAG,CAAC,KAAK,EAAE,SAAS,qBAAqB,EAAE,KAAK,IAAI,CAAC;AAE9E,MAAM,WAAW,YAAY;IAC3B,OAAO,IAAI,IAAI,CAAC;IAChB,sBAAsB,CACpB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,yBAAyB,EAC/B,eAAe,CAAC,EAAE,eAAe,GAChC,IAAI,CAAC;IACR,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI,CAAC;IACnD,kBAAkB,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,GAAG,IAAI,CAAC;IACjE,sBAAsB,CAAC,QAAQ,EAAE,oBAAoB,GAAG,MAAM,IAAI,CAAC;IACnE,MAAM,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACzC;
|
|
1
|
+
{"version":3,"file":"createGraphRuntime.d.ts","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAkB,EAAa,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAExE,OAAO,KAAK,EACV,kBAAkB,EAClB,yBAAyB,EAEzB,qBAAqB,EACrB,aAAa,EAEd,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAiBhF,KAAK,cAAc,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;AACpD,KAAK,oBAAoB,GAAG,CAAC,KAAK,EAAE,SAAS,qBAAqB,EAAE,KAAK,IAAI,CAAC;AAE9E,MAAM,WAAW,YAAY;IAC3B,OAAO,IAAI,IAAI,CAAC;IAChB,sBAAsB,CACpB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,yBAAyB,EAC/B,eAAe,CAAC,EAAE,eAAe,GAChC,IAAI,CAAC;IACR,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI,CAAC;IACnD,kBAAkB,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,GAAG,IAAI,CAAC;IACjE,sBAAsB,CAAC,QAAQ,EAAE,oBAAoB,GAAG,MAAM,IAAI,CAAC;IACnE,MAAM,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACzC;AA6BD,6FAA6F;AAC7F,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,cAAc,EACzB,YAAY,EAAE;IAAE,OAAO,EAAE,kBAAkB,CAAA;CAAE,GAC5C,YAAY,CAoBd"}
|
|
@@ -61,6 +61,7 @@ function createRuntimeState(cy, container, callbacksRef) {
|
|
|
61
61
|
loopSizer: createGraphLoopSizer(cy),
|
|
62
62
|
pendingInitialFitRef: { current: false },
|
|
63
63
|
readyRef,
|
|
64
|
+
settledLayoutRef: { current: null },
|
|
64
65
|
settledTopologyRef: { current: null },
|
|
65
66
|
relayoutScheduledRef: { current: false },
|
|
66
67
|
renderedNodeListeners,
|
|
@@ -69,7 +70,8 @@ function createRuntimeState(cy, container, callbacksRef) {
|
|
|
69
70
|
const resizeObserver = createGraphResizeObserver({
|
|
70
71
|
container,
|
|
71
72
|
cy,
|
|
72
|
-
|
|
73
|
+
beginViewportResize: () => viewport.beginResize(),
|
|
74
|
+
settleViewport: (fit) => viewport.settle(fit),
|
|
73
75
|
layoutRunningRef,
|
|
74
76
|
pendingInitialFitRef: stateBase.pendingInitialFitRef,
|
|
75
77
|
onViewportSettled: (completedPendingInitialFit) => {
|
|
@@ -161,6 +163,7 @@ function completeCurrentLayout(state, generation) {
|
|
|
161
163
|
state.layoutRef.current = null;
|
|
162
164
|
state.cy.resize();
|
|
163
165
|
state.layoutRunningRef.current = false;
|
|
166
|
+
state.resizeObserver?.flushAfterLayout();
|
|
164
167
|
if (!hasUsableViewport(state.cy)) {
|
|
165
168
|
if (!state.readyRef.current)
|
|
166
169
|
state.pendingInitialFitRef.current = true;
|
|
@@ -171,8 +174,9 @@ function completeCurrentLayout(state, generation) {
|
|
|
171
174
|
if (input === null)
|
|
172
175
|
return;
|
|
173
176
|
const topology = getGraphTopologyKey(input.nodes, input.edges);
|
|
174
|
-
const shouldFit = shouldFitGraphAfterLayout(state.readyRef.current, topology !== state.settledTopologyRef.current);
|
|
177
|
+
const shouldFit = shouldFitGraphAfterLayout(state.readyRef.current, topology !== state.settledTopologyRef.current, input.layout !== state.settledLayoutRef.current);
|
|
175
178
|
state.viewport.settle(shouldFit);
|
|
179
|
+
state.settledLayoutRef.current = input.layout;
|
|
176
180
|
state.settledTopologyRef.current = topology;
|
|
177
181
|
const fitRequest = state.fitRequestRef.current;
|
|
178
182
|
state.fitRequestRef.current = null;
|