@ankhorage/zora 20.1.6 → 20.2.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/GraphNodeOverlay.d.ts +13 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/GraphNodeOverlay.d.ts.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/GraphNodeOverlay.js +71 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/GraphNodeOverlay.js.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/GraphView.d.ts +87 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/GraphView.d.ts.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/GraphView.js +132 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/GraphView.js.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/bindGraphEvents.d.ts +7 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/bindGraphEvents.d.ts.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/bindGraphEvents.js +59 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/bindGraphEvents.js.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphController.d.ts +7 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphController.d.ts.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphController.js +32 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphController.js.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.d.ts +22 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.d.ts.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.js +41 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.js.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.d.ts +30 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.d.ts.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.js +222 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.js.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/createRenderableGraphElements.d.ts +5 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/createRenderableGraphElements.d.ts.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/createRenderableGraphElements.js +51 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/createRenderableGraphElements.js.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/fitGraphViewport.d.ts +5 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/fitGraphViewport.d.ts.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/fitGraphViewport.js +18 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/fitGraphViewport.js.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/readGraphRenderedNodes.d.ts +5 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/readGraphRenderedNodes.d.ts.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/readGraphRenderedNodes.js +14 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/readGraphRenderedNodes.js.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/runGraphLayout.d.ts +14 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/runGraphLayout.d.ts.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/runGraphLayout.js +34 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/runGraphLayout.js.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/scheduleGraphFrame.d.ts +3 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/scheduleGraphFrame.d.ts.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/scheduleGraphFrame.js +10 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/scheduleGraphFrame.js.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/syncGraphElements.d.ts +5 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/syncGraphElements.d.ts.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/syncGraphElements.js +12 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/syncGraphElements.js.map +1 -0
- package/dist/features/web-component-artifact/application/use-cases/materializeWebComponentArtifactAsync.d.ts.map +1 -1
- package/dist/features/web-component-artifact/application/use-cases/materializeWebComponentArtifactAsync.js +25 -10
- package/dist/features/web-component-artifact/application/use-cases/materializeWebComponentArtifactAsync.js.map +1 -1
- package/package.json +6 -4
- package/src/features/graph-view/adapters/inbound/web-artifact/GraphNodeOverlay.tsx +142 -0
- package/src/features/graph-view/adapters/inbound/web-artifact/GraphView.tsx +293 -0
- package/src/features/graph-view/adapters/inbound/web-artifact/bindGraphEvents.ts +102 -0
- package/src/features/graph-view/adapters/inbound/web-artifact/createGraphController.ts +38 -0
- package/src/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.ts +80 -0
- package/src/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.ts +301 -0
- package/src/features/graph-view/adapters/inbound/web-artifact/createRenderableGraphElements.test.ts +49 -0
- package/src/features/graph-view/adapters/inbound/web-artifact/createRenderableGraphElements.ts +72 -0
- package/src/features/graph-view/adapters/inbound/web-artifact/fitGraphViewport.ts +20 -0
- package/src/features/graph-view/adapters/inbound/web-artifact/readGraphRenderedNodes.ts +20 -0
- package/src/features/graph-view/adapters/inbound/web-artifact/runGraphLayout.ts +53 -0
- package/src/features/graph-view/adapters/inbound/web-artifact/scheduleGraphFrame.ts +13 -0
- package/src/features/graph-view/adapters/inbound/web-artifact/syncGraphElements.ts +19 -0
- package/src/features/web-component-artifact/application/use-cases/materializeWebComponentArtifactAsync.test.ts +47 -31
- package/src/features/web-component-artifact/application/use-cases/materializeWebComponentArtifactAsync.ts +47 -11
- package/src/types/cytoscape-elk.d.ts +4 -0
- package/web-dist/graph-view/GraphView.d.ts +87 -0
- package/web-dist/graph-view/GraphView.js +123308 -0
- package/web-dist/graph-view/artifact.json +7 -0
- package/web-dist/tree-view/artifact.json +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 20.2.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 38db5b8: Update Ankhorage dependencies: `@ankhorage/contracts`, `@ankhorage/surface`.
|
|
8
|
+
|
|
9
|
+
## 20.2.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 25d5ed9: Add a materializable Cytoscape-backed GraphView web artifact with centralized layout/viewport ownership and optional rich React node rendering.
|
|
14
|
+
|
|
3
15
|
## 20.1.6
|
|
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
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { GraphViewElementEventType, GraphViewNode, GraphViewNodeRenderContext, GraphViewRenderedNode, GraphViewSize } from './GraphView';
|
|
3
|
+
interface GraphNodeOverlayProps {
|
|
4
|
+
readonly nodes: readonly GraphViewNode[];
|
|
5
|
+
readonly renderedNodes: readonly GraphViewRenderedNode[];
|
|
6
|
+
readonly renderNode: (context: GraphViewNodeRenderContext) => React.ReactNode;
|
|
7
|
+
readonly onNodeEvent: (id: string, type: GraphViewElementEventType) => void;
|
|
8
|
+
readonly onNodeSize: (id: string, size: GraphViewSize) => void;
|
|
9
|
+
}
|
|
10
|
+
/*** Render optional React node content above Cytoscape at its authoritative rendered positions. */
|
|
11
|
+
export declare function GraphNodeOverlay(props: GraphNodeOverlayProps): React.JSX.Element;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=GraphNodeOverlay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GraphNodeOverlay.d.ts","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/GraphNodeOverlay.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EACV,yBAAyB,EACzB,aAAa,EACb,0BAA0B,EAC1B,qBAAqB,EACrB,aAAa,EACd,MAAM,aAAa,CAAC;AAErB,UAAU,qBAAqB;IAC7B,QAAQ,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,CAAC;IACzC,QAAQ,CAAC,aAAa,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACzD,QAAQ,CAAC,UAAU,EAAE,CAAC,OAAO,EAAE,0BAA0B,KAAK,KAAK,CAAC,SAAS,CAAC;IAC9E,QAAQ,CAAC,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,yBAAyB,KAAK,IAAI,CAAC;IAC5E,QAAQ,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;CAChE;AAED,mGAAmG;AACnG,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,qBAgC5D"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
/*** Render optional React node content above Cytoscape at its authoritative rendered positions. */
|
|
4
|
+
export function GraphNodeOverlay(props) {
|
|
5
|
+
const nodesById = React.useMemo(() => new Map(props.nodes.map((node) => [node.id, node])), [props.nodes]);
|
|
6
|
+
return (<div aria-hidden style={{
|
|
7
|
+
inset: 0,
|
|
8
|
+
overflow: 'hidden',
|
|
9
|
+
pointerEvents: 'none',
|
|
10
|
+
position: 'absolute',
|
|
11
|
+
}}>
|
|
12
|
+
{props.renderedNodes.map((renderedNode) => {
|
|
13
|
+
const node = nodesById.get(renderedNode.id);
|
|
14
|
+
if (!node)
|
|
15
|
+
return null;
|
|
16
|
+
return (<MeasuredGraphNode key={node.id} node={node} renderedNode={renderedNode} renderNode={props.renderNode} onNodeEvent={props.onNodeEvent} onNodeSize={props.onNodeSize}/>);
|
|
17
|
+
})}
|
|
18
|
+
</div>);
|
|
19
|
+
}
|
|
20
|
+
/*** Measure one rich node at model scale and translate its DOM interactions into graph events. */
|
|
21
|
+
function MeasuredGraphNode(props) {
|
|
22
|
+
const elementRef = React.useRef(null);
|
|
23
|
+
const { node, onNodeSize, renderedNode } = props;
|
|
24
|
+
React.useLayoutEffect(() => {
|
|
25
|
+
const element = elementRef.current;
|
|
26
|
+
if (element === null)
|
|
27
|
+
return;
|
|
28
|
+
reportElementSize(element, node.id, onNodeSize);
|
|
29
|
+
const Observer = readResizeObserverConstructor();
|
|
30
|
+
if (Observer === null)
|
|
31
|
+
return;
|
|
32
|
+
const observer = new Observer(() => reportElementSize(element, node.id, onNodeSize));
|
|
33
|
+
observer.observe(element);
|
|
34
|
+
return () => observer.disconnect();
|
|
35
|
+
}, [node.id, onNodeSize]);
|
|
36
|
+
return (<div ref={(element) => {
|
|
37
|
+
elementRef.current = element;
|
|
38
|
+
}} style={{
|
|
39
|
+
left: renderedNode.position.x,
|
|
40
|
+
pointerEvents: 'auto',
|
|
41
|
+
position: 'absolute',
|
|
42
|
+
top: renderedNode.position.y,
|
|
43
|
+
transform: `translate(-50%, -50%) scale(${renderedNode.zoom})`,
|
|
44
|
+
transformOrigin: 'center',
|
|
45
|
+
}} onClick={() => props.onNodeEvent(node.id, 'press')} onDoubleClick={() => props.onNodeEvent(node.id, 'double-press')} onMouseEnter={() => props.onNodeEvent(node.id, 'pointer-enter')} onMouseLeave={() => props.onNodeEvent(node.id, 'pointer-leave')}>
|
|
46
|
+
{props.renderNode({
|
|
47
|
+
hovered: renderedNode.hovered,
|
|
48
|
+
node,
|
|
49
|
+
selected: renderedNode.selected,
|
|
50
|
+
zoom: renderedNode.zoom,
|
|
51
|
+
})}
|
|
52
|
+
</div>);
|
|
53
|
+
}
|
|
54
|
+
/*** Report unscaled DOM dimensions only when a measurable node has positive size. */
|
|
55
|
+
function reportElementSize(element, nodeId, onNodeSize) {
|
|
56
|
+
const measured = element;
|
|
57
|
+
if (typeof measured.offsetWidth !== 'number' || typeof measured.offsetHeight !== 'number')
|
|
58
|
+
return;
|
|
59
|
+
if (measured.offsetWidth <= 0 || measured.offsetHeight <= 0)
|
|
60
|
+
return;
|
|
61
|
+
onNodeSize(nodeId, {
|
|
62
|
+
width: measured.offsetWidth,
|
|
63
|
+
height: measured.offsetHeight,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/*** Read ResizeObserver without requiring the DOM lib in the package TypeScript target. */
|
|
67
|
+
function readResizeObserverConstructor() {
|
|
68
|
+
const observer = globalThis.ResizeObserver;
|
|
69
|
+
return observer ?? null;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=GraphNodeOverlay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GraphNodeOverlay.js","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/GraphNodeOverlay.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport React from 'react';\n\nimport type {\n GraphViewElementEventType,\n GraphViewNode,\n GraphViewNodeRenderContext,\n GraphViewRenderedNode,\n GraphViewSize,\n} from './GraphView';\n\ninterface GraphNodeOverlayProps {\n readonly nodes: readonly GraphViewNode[];\n readonly renderedNodes: readonly GraphViewRenderedNode[];\n readonly renderNode: (context: GraphViewNodeRenderContext) => React.ReactNode;\n readonly onNodeEvent: (id: string, type: GraphViewElementEventType) => void;\n readonly onNodeSize: (id: string, size: GraphViewSize) => void;\n}\n\n/*** Render optional React node content above Cytoscape at its authoritative rendered positions. */\nexport function GraphNodeOverlay(props: GraphNodeOverlayProps) {\n const nodesById = React.useMemo(\n () => new Map(props.nodes.map((node) => [node.id, node] as const)),\n [props.nodes],\n );\n\n return (\n <div\n aria-hidden\n style={{\n inset: 0,\n overflow: 'hidden',\n pointerEvents: 'none',\n position: 'absolute',\n }}\n >\n {props.renderedNodes.map((renderedNode) => {\n const node = nodesById.get(renderedNode.id);\n if (!node) return null;\n return (\n <MeasuredGraphNode\n key={node.id}\n node={node}\n renderedNode={renderedNode}\n renderNode={props.renderNode}\n onNodeEvent={props.onNodeEvent}\n onNodeSize={props.onNodeSize}\n />\n );\n })}\n </div>\n );\n}\n\ninterface MeasuredGraphNodeProps {\n readonly node: GraphViewNode;\n readonly renderedNode: GraphViewRenderedNode;\n readonly renderNode: GraphNodeOverlayProps['renderNode'];\n readonly onNodeEvent: GraphNodeOverlayProps['onNodeEvent'];\n readonly onNodeSize: GraphNodeOverlayProps['onNodeSize'];\n}\n\n/*** Measure one rich node at model scale and translate its DOM interactions into graph events. */\nfunction MeasuredGraphNode(props: MeasuredGraphNodeProps) {\n const elementRef = React.useRef<HTMLDivElement | null>(null);\n const { node, onNodeSize, renderedNode } = props;\n\n React.useLayoutEffect(() => {\n const element = elementRef.current;\n if (element === null) return;\n reportElementSize(element, node.id, onNodeSize);\n\n const Observer = readResizeObserverConstructor();\n if (Observer === null) return;\n const observer = new Observer(() => reportElementSize(element, node.id, onNodeSize));\n observer.observe(element);\n return () => observer.disconnect();\n }, [node.id, onNodeSize]);\n\n return (\n <div\n ref={(element) => {\n elementRef.current = element;\n }}\n style={{\n left: renderedNode.position.x,\n pointerEvents: 'auto',\n position: 'absolute',\n top: renderedNode.position.y,\n transform: `translate(-50%, -50%) scale(${renderedNode.zoom})`,\n transformOrigin: 'center',\n }}\n onClick={() => props.onNodeEvent(node.id, 'press')}\n onDoubleClick={() => props.onNodeEvent(node.id, 'double-press')}\n onMouseEnter={() => props.onNodeEvent(node.id, 'pointer-enter')}\n onMouseLeave={() => props.onNodeEvent(node.id, 'pointer-leave')}\n >\n {props.renderNode({\n hovered: renderedNode.hovered,\n node,\n selected: renderedNode.selected,\n zoom: renderedNode.zoom,\n })}\n </div>\n );\n}\n\n/*** Report unscaled DOM dimensions only when a measurable node has positive size. */\nfunction reportElementSize(\n element: unknown,\n nodeId: string,\n onNodeSize: (id: string, size: GraphViewSize) => void,\n) {\n const measured = element as {\n readonly offsetHeight?: unknown;\n readonly offsetWidth?: unknown;\n };\n if (typeof measured.offsetWidth !== 'number' || typeof measured.offsetHeight !== 'number') return;\n if (measured.offsetWidth <= 0 || measured.offsetHeight <= 0) return;\n onNodeSize(nodeId, {\n width: measured.offsetWidth,\n height: measured.offsetHeight,\n });\n}\n\n/*** Read ResizeObserver without requiring the DOM lib in the package TypeScript target. */\nfunction readResizeObserverConstructor(): ResizeObserverConstructorLike | null {\n const observer = (\n globalThis as unknown as {\n ResizeObserver?: ResizeObserverConstructorLike;\n }\n ).ResizeObserver;\n return observer ?? null;\n}\n\ninterface ResizeObserverLike {\n disconnect(): void;\n observe(target: unknown): void;\n}\n\ntype ResizeObserverConstructorLike = new (callback: () => void) => ResizeObserverLike;\n"]}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type GraphViewLayoutName = 'breadthfirst' | 'circle' | 'concentric' | 'elk' | 'grid';
|
|
3
|
+
export type GraphViewElementEventType = 'press' | 'double-press' | 'select' | 'unselect' | 'pointer-enter' | 'pointer-leave';
|
|
4
|
+
export interface GraphViewNode {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly label?: string;
|
|
7
|
+
readonly parentId?: string;
|
|
8
|
+
readonly classes?: string;
|
|
9
|
+
readonly data?: Readonly<Record<string, unknown>>;
|
|
10
|
+
}
|
|
11
|
+
export interface GraphViewEdge {
|
|
12
|
+
readonly id?: string;
|
|
13
|
+
readonly source: string;
|
|
14
|
+
readonly target: string;
|
|
15
|
+
readonly classes?: string;
|
|
16
|
+
readonly data?: Readonly<Record<string, unknown>>;
|
|
17
|
+
}
|
|
18
|
+
export interface GraphViewStyleRule {
|
|
19
|
+
readonly selector: string;
|
|
20
|
+
readonly style: Readonly<Record<string, unknown>>;
|
|
21
|
+
}
|
|
22
|
+
export interface GraphViewPoint {
|
|
23
|
+
readonly x: number;
|
|
24
|
+
readonly y: number;
|
|
25
|
+
}
|
|
26
|
+
export interface GraphViewSize {
|
|
27
|
+
readonly width: number;
|
|
28
|
+
readonly height: number;
|
|
29
|
+
}
|
|
30
|
+
export interface GraphViewNodeRenderContext {
|
|
31
|
+
readonly hovered: boolean;
|
|
32
|
+
readonly node: GraphViewNode;
|
|
33
|
+
readonly selected: boolean;
|
|
34
|
+
readonly zoom: number;
|
|
35
|
+
}
|
|
36
|
+
export interface GraphViewRenderedNode {
|
|
37
|
+
readonly hovered: boolean;
|
|
38
|
+
readonly id: string;
|
|
39
|
+
readonly position: GraphViewPoint;
|
|
40
|
+
readonly selected: boolean;
|
|
41
|
+
readonly zoom: number;
|
|
42
|
+
}
|
|
43
|
+
export interface GraphViewViewport {
|
|
44
|
+
readonly zoom: number;
|
|
45
|
+
readonly pan: GraphViewPoint;
|
|
46
|
+
}
|
|
47
|
+
export interface GraphViewFitOptions {
|
|
48
|
+
readonly nodeIds?: readonly string[];
|
|
49
|
+
readonly padding?: number;
|
|
50
|
+
}
|
|
51
|
+
export interface GraphViewElementEvent {
|
|
52
|
+
readonly id: string;
|
|
53
|
+
readonly type: GraphViewElementEventType;
|
|
54
|
+
}
|
|
55
|
+
export interface GraphViewController {
|
|
56
|
+
fit(options?: GraphViewFitOptions): void;
|
|
57
|
+
getViewport(): GraphViewViewport;
|
|
58
|
+
setPan(pan: GraphViewPoint): void;
|
|
59
|
+
setZoom(zoom: number): void;
|
|
60
|
+
zoomBy(factor: number): void;
|
|
61
|
+
}
|
|
62
|
+
export interface GraphViewProps {
|
|
63
|
+
readonly nodes: readonly GraphViewNode[];
|
|
64
|
+
readonly edges: readonly GraphViewEdge[];
|
|
65
|
+
readonly layout?: GraphViewLayoutName;
|
|
66
|
+
readonly layoutOptions?: Readonly<Record<string, unknown>>;
|
|
67
|
+
readonly spacingFactor?: number;
|
|
68
|
+
readonly styleRules?: readonly GraphViewStyleRule[];
|
|
69
|
+
readonly selectedNodeIds?: readonly string[];
|
|
70
|
+
readonly renderNode?: (context: GraphViewNodeRenderContext) => React.ReactNode;
|
|
71
|
+
readonly getNodeSize?: (node: GraphViewNode) => GraphViewSize | undefined;
|
|
72
|
+
readonly fitPadding?: number;
|
|
73
|
+
readonly minZoom?: number;
|
|
74
|
+
readonly maxZoom?: number;
|
|
75
|
+
readonly className?: string;
|
|
76
|
+
readonly style?: React.CSSProperties;
|
|
77
|
+
readonly ariaLabel?: string;
|
|
78
|
+
readonly onReady?: (controller: GraphViewController) => void;
|
|
79
|
+
readonly onLayoutComplete?: (controller: GraphViewController) => void;
|
|
80
|
+
readonly onNodeEvent?: (event: GraphViewElementEvent) => void;
|
|
81
|
+
readonly onEdgeEvent?: (event: GraphViewElementEvent) => void;
|
|
82
|
+
readonly onViewportChange?: (viewport: GraphViewViewport) => void;
|
|
83
|
+
}
|
|
84
|
+
export type GraphViewCallbacks = Pick<GraphViewProps, 'onEdgeEvent' | 'onLayoutComplete' | 'onNodeEvent' | 'onReady' | 'onViewportChange'>;
|
|
85
|
+
/*** Render a browser graph while one runtime owns Cytoscape layout, viewport, and disposal. */
|
|
86
|
+
export declare function GraphView(props: GraphViewProps): React.JSX.Element;
|
|
87
|
+
//# sourceMappingURL=GraphView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GraphView.d.ts","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/GraphView.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { createGraphRuntime } from './createGraphRuntime';
|
|
4
|
+
import { GraphNodeOverlay } from './GraphNodeOverlay';
|
|
5
|
+
/*** Render a browser graph while one runtime owns Cytoscape layout, viewport, and disposal. */
|
|
6
|
+
export function GraphView(props) {
|
|
7
|
+
const { containerRef, renderedNodes, runtimeRef } = useGraphViewRuntime(props);
|
|
8
|
+
return (<div aria-label={props.ariaLabel ?? 'Graph'} className={props.className} role="application" style={{
|
|
9
|
+
height: '100%',
|
|
10
|
+
minHeight: 0,
|
|
11
|
+
minWidth: 0,
|
|
12
|
+
position: 'relative',
|
|
13
|
+
width: '100%',
|
|
14
|
+
...props.style,
|
|
15
|
+
}}>
|
|
16
|
+
<div ref={containerRef} style={{ height: '100%', width: '100%' }}/>
|
|
17
|
+
{props.renderNode ? (<GraphNodeOverlay nodes={props.nodes} renderedNodes={renderedNodes} renderNode={props.renderNode} onNodeEvent={(id, type) => runtimeRef.current?.handleOverlayNodeEvent(id, type)} onNodeSize={(id, size) => runtimeRef.current?.setNodeSize(id, size)}/>) : null}
|
|
18
|
+
</div>);
|
|
19
|
+
}
|
|
20
|
+
/*** Compose React synchronization around the single imperative graph runtime. */
|
|
21
|
+
function useGraphViewRuntime(props) {
|
|
22
|
+
const containerRef = React.useRef(null);
|
|
23
|
+
const runtimeRef = React.useRef(null);
|
|
24
|
+
const callbacksRef = useGraphCallbacksRef(props);
|
|
25
|
+
const [renderedNodes, setRenderedNodes] = React.useState([]);
|
|
26
|
+
useRuntimeLifecycle(containerRef, runtimeRef, callbacksRef);
|
|
27
|
+
useRuntimeGraphUpdate(runtimeRef, props);
|
|
28
|
+
useRuntimeSelection(runtimeRef, props.selectedNodeIds);
|
|
29
|
+
useRenderedNodeSubscription(runtimeRef, props.renderNode, setRenderedNodes);
|
|
30
|
+
useKnownNodeSizes(runtimeRef, props.nodes, props.getNodeSize);
|
|
31
|
+
return { containerRef, renderedNodes, runtimeRef };
|
|
32
|
+
}
|
|
33
|
+
/*** Keep the callback ref current without mutating refs during render. */
|
|
34
|
+
function useGraphCallbacksRef(props) {
|
|
35
|
+
const callbacksRef = React.useRef(readCallbacks(props));
|
|
36
|
+
const { onEdgeEvent, onLayoutComplete, onNodeEvent, onReady, onViewportChange } = props;
|
|
37
|
+
React.useEffect(() => {
|
|
38
|
+
callbacksRef.current = {
|
|
39
|
+
onEdgeEvent,
|
|
40
|
+
onLayoutComplete,
|
|
41
|
+
onNodeEvent,
|
|
42
|
+
onReady,
|
|
43
|
+
onViewportChange,
|
|
44
|
+
};
|
|
45
|
+
}, [onEdgeEvent, onLayoutComplete, onNodeEvent, onReady, onViewportChange]);
|
|
46
|
+
return callbacksRef;
|
|
47
|
+
}
|
|
48
|
+
/*** Create and dispose the runtime only with the underlying browser container lifetime. */
|
|
49
|
+
function useRuntimeLifecycle(containerRef, runtimeRef, callbacksRef) {
|
|
50
|
+
React.useEffect(() => {
|
|
51
|
+
const container = containerRef.current;
|
|
52
|
+
if (container === null)
|
|
53
|
+
return;
|
|
54
|
+
const runtime = createGraphRuntime(container, callbacksRef);
|
|
55
|
+
runtimeRef.current = runtime;
|
|
56
|
+
return () => {
|
|
57
|
+
runtime.destroy();
|
|
58
|
+
runtimeRef.current = null;
|
|
59
|
+
};
|
|
60
|
+
}, [callbacksRef, containerRef, runtimeRef]);
|
|
61
|
+
}
|
|
62
|
+
/*** Synchronize graph data and layout policy without exposing Cytoscape to React. */
|
|
63
|
+
function useRuntimeGraphUpdate(runtimeRef, props) {
|
|
64
|
+
const { edges, fitPadding, layout, layoutOptions, maxZoom, minZoom, nodes, renderNode, spacingFactor, styleRules, } = props;
|
|
65
|
+
React.useEffect(() => {
|
|
66
|
+
runtimeRef.current?.update({
|
|
67
|
+
edges,
|
|
68
|
+
fitPadding,
|
|
69
|
+
layout,
|
|
70
|
+
layoutOptions,
|
|
71
|
+
maxZoom,
|
|
72
|
+
minZoom,
|
|
73
|
+
nodes,
|
|
74
|
+
richNodeRendering: renderNode !== undefined,
|
|
75
|
+
spacingFactor,
|
|
76
|
+
styleRules,
|
|
77
|
+
});
|
|
78
|
+
}, [
|
|
79
|
+
edges,
|
|
80
|
+
fitPadding,
|
|
81
|
+
layout,
|
|
82
|
+
layoutOptions,
|
|
83
|
+
maxZoom,
|
|
84
|
+
minZoom,
|
|
85
|
+
nodes,
|
|
86
|
+
renderNode,
|
|
87
|
+
runtimeRef,
|
|
88
|
+
spacingFactor,
|
|
89
|
+
styleRules,
|
|
90
|
+
]);
|
|
91
|
+
}
|
|
92
|
+
/*** Synchronize optional controlled selection independently from graph layout. */
|
|
93
|
+
function useRuntimeSelection(runtimeRef, selectedNodeIds) {
|
|
94
|
+
React.useEffect(() => {
|
|
95
|
+
runtimeRef.current?.setSelectedNodeIds(selectedNodeIds);
|
|
96
|
+
}, [runtimeRef, selectedNodeIds]);
|
|
97
|
+
}
|
|
98
|
+
/*** Subscribe the optional rich-node overlay to rendered runtime positions. */
|
|
99
|
+
function useRenderedNodeSubscription(runtimeRef, renderNode, setRenderedNodes) {
|
|
100
|
+
React.useEffect(() => {
|
|
101
|
+
const runtime = runtimeRef.current;
|
|
102
|
+
if (runtime === null || renderNode === undefined) {
|
|
103
|
+
setRenderedNodes([]);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
return runtime.subscribeRenderedNodes(setRenderedNodes);
|
|
107
|
+
}, [renderNode, runtimeRef, setRenderedNodes]);
|
|
108
|
+
}
|
|
109
|
+
/*** Seed known rich-node dimensions without requiring DOM measurement. */
|
|
110
|
+
function useKnownNodeSizes(runtimeRef, nodes, getNodeSize) {
|
|
111
|
+
React.useEffect(() => {
|
|
112
|
+
const runtime = runtimeRef.current;
|
|
113
|
+
if (runtime === null || getNodeSize === undefined)
|
|
114
|
+
return;
|
|
115
|
+
for (const node of nodes) {
|
|
116
|
+
const size = getNodeSize(node);
|
|
117
|
+
if (size)
|
|
118
|
+
runtime.setNodeSize(node.id, size);
|
|
119
|
+
}
|
|
120
|
+
}, [getNodeSize, nodes, runtimeRef]);
|
|
121
|
+
}
|
|
122
|
+
/*** Read callback props for the initial runtime callback-ref value. */
|
|
123
|
+
function readCallbacks(props) {
|
|
124
|
+
return {
|
|
125
|
+
onEdgeEvent: props.onEdgeEvent,
|
|
126
|
+
onLayoutComplete: props.onLayoutComplete,
|
|
127
|
+
onNodeEvent: props.onNodeEvent,
|
|
128
|
+
onReady: props.onReady,
|
|
129
|
+
onViewportChange: props.onViewportChange,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=GraphView.js.map
|
|
@@ -0,0 +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":["'use client';\n\nimport type { CytoscapeOptions } from 'cytoscape';\nimport React from 'react';\n\nimport { createGraphRuntime, type GraphRuntime } from './createGraphRuntime';\nimport { GraphNodeOverlay } from './GraphNodeOverlay';\n\nexport type GraphViewLayoutName = 'breadthfirst' | 'circle' | 'concentric' | 'elk' | 'grid';\nexport type GraphViewElementEventType =\n 'press' | 'double-press' | 'select' | 'unselect' | 'pointer-enter' | 'pointer-leave';\n\nexport interface GraphViewNode {\n readonly id: string;\n readonly label?: string;\n readonly parentId?: string;\n readonly classes?: string;\n readonly data?: Readonly<Record<string, unknown>>;\n}\n\nexport interface GraphViewEdge {\n readonly id?: string;\n readonly source: string;\n readonly target: string;\n readonly classes?: string;\n readonly data?: Readonly<Record<string, unknown>>;\n}\n\nexport interface GraphViewStyleRule {\n readonly selector: string;\n readonly style: Readonly<Record<string, unknown>>;\n}\n\nexport interface GraphViewPoint {\n readonly x: number;\n readonly y: number;\n}\n\nexport interface GraphViewSize {\n readonly width: number;\n readonly height: number;\n}\n\nexport interface GraphViewNodeRenderContext {\n readonly hovered: boolean;\n readonly node: GraphViewNode;\n readonly selected: boolean;\n readonly zoom: number;\n}\n\nexport interface GraphViewRenderedNode {\n readonly hovered: boolean;\n readonly id: string;\n readonly position: GraphViewPoint;\n readonly selected: boolean;\n readonly zoom: number;\n}\n\nexport interface GraphViewViewport {\n readonly zoom: number;\n readonly pan: GraphViewPoint;\n}\n\nexport interface GraphViewFitOptions {\n readonly nodeIds?: readonly string[];\n readonly padding?: number;\n}\n\nexport interface GraphViewElementEvent {\n readonly id: string;\n readonly type: GraphViewElementEventType;\n}\n\nexport interface GraphViewController {\n fit(options?: GraphViewFitOptions): void;\n getViewport(): GraphViewViewport;\n setPan(pan: GraphViewPoint): void;\n setZoom(zoom: number): void;\n zoomBy(factor: number): void;\n}\n\nexport interface GraphViewProps {\n readonly nodes: readonly GraphViewNode[];\n readonly edges: readonly GraphViewEdge[];\n readonly layout?: GraphViewLayoutName;\n readonly layoutOptions?: Readonly<Record<string, unknown>>;\n readonly spacingFactor?: number;\n readonly styleRules?: readonly GraphViewStyleRule[];\n readonly selectedNodeIds?: readonly string[];\n readonly renderNode?: (context: GraphViewNodeRenderContext) => React.ReactNode;\n readonly getNodeSize?: (node: GraphViewNode) => GraphViewSize | undefined;\n readonly fitPadding?: number;\n readonly minZoom?: number;\n readonly maxZoom?: number;\n readonly className?: string;\n readonly style?: React.CSSProperties;\n readonly ariaLabel?: string;\n readonly onReady?: (controller: GraphViewController) => void;\n readonly onLayoutComplete?: (controller: GraphViewController) => void;\n readonly onNodeEvent?: (event: GraphViewElementEvent) => void;\n readonly onEdgeEvent?: (event: GraphViewElementEvent) => void;\n readonly onViewportChange?: (viewport: GraphViewViewport) => void;\n}\n\nexport type GraphViewCallbacks = Pick<\n GraphViewProps,\n 'onEdgeEvent' | 'onLayoutComplete' | 'onNodeEvent' | 'onReady' | 'onViewportChange'\n>;\n\ntype GraphContainer = NonNullable<CytoscapeOptions['container']>;\n\n/*** Render a browser graph while one runtime owns Cytoscape layout, viewport, and disposal. */\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) => runtimeRef.current?.handleOverlayNodeEvent(id, type)}\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 { onEdgeEvent, onLayoutComplete, onNodeEvent, onReady, onViewportChange } = props;\n\n React.useEffect(() => {\n callbacksRef.current = {\n onEdgeEvent,\n onLayoutComplete,\n onNodeEvent,\n onReady,\n onViewportChange,\n };\n }, [onEdgeEvent, onLayoutComplete, onNodeEvent, onReady, onViewportChange]);\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 minZoom,\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 minZoom,\n nodes,\n richNodeRendering: renderNode !== undefined,\n spacingFactor,\n styleRules,\n });\n }, [\n edges,\n fitPadding,\n layout,\n layoutOptions,\n maxZoom,\n minZoom,\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 };\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Core } from 'cytoscape';
|
|
2
|
+
import type { GraphViewCallbacks, GraphViewController } from './GraphView';
|
|
3
|
+
/*** Bind stable Cytoscape events that always read the latest React callbacks. */
|
|
4
|
+
export declare function bindGraphEvents(cy: Core, callbacksRef: {
|
|
5
|
+
current: GraphViewCallbacks;
|
|
6
|
+
}, controller: GraphViewController, onRuntimeChange: () => void): () => void;
|
|
7
|
+
//# sourceMappingURL=bindGraphEvents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bindGraphEvents.d.ts","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/bindGraphEvents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAe,MAAM,WAAW,CAAC;AAEnD,OAAO,KAAK,EACV,kBAAkB,EAClB,mBAAmB,EAEpB,MAAM,aAAa,CAAC;AAErB,iFAAiF;AACjF,wBAAgB,eAAe,CAC7B,EAAE,EAAE,IAAI,EACR,YAAY,EAAE;IAAE,OAAO,EAAE,kBAAkB,CAAA;CAAE,EAC7C,UAAU,EAAE,mBAAmB,EAC/B,eAAe,EAAE,MAAM,IAAI,cAe5B"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/*** Bind stable Cytoscape events that always read the latest React callbacks. */
|
|
2
|
+
export function bindGraphEvents(cy, callbacksRef, controller, onRuntimeChange) {
|
|
3
|
+
const nodeHandlers = bindElementEvents(cy, 'node', callbacksRef, onRuntimeChange);
|
|
4
|
+
const edgeHandlers = bindElementEvents(cy, 'edge', callbacksRef, onRuntimeChange);
|
|
5
|
+
const viewportHandler = () => {
|
|
6
|
+
callbacksRef.current.onViewportChange?.(controller.getViewport());
|
|
7
|
+
onRuntimeChange();
|
|
8
|
+
};
|
|
9
|
+
cy.on('zoom pan', viewportHandler);
|
|
10
|
+
return () => {
|
|
11
|
+
unbindElementEvents(cy, 'node', nodeHandlers);
|
|
12
|
+
unbindElementEvents(cy, 'edge', edgeHandlers);
|
|
13
|
+
cy.off('zoom pan', viewportHandler);
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/*** Bind one selector's translated event set without resubscribing on callback changes. */
|
|
17
|
+
function bindElementEvents(cy, selector, callbacksRef, onRuntimeChange) {
|
|
18
|
+
return EVENT_TYPES.map(([cytoscapeEvent, type]) => {
|
|
19
|
+
const handler = (event) => {
|
|
20
|
+
const target = event.target;
|
|
21
|
+
if (!isGraphEventTarget(target))
|
|
22
|
+
return;
|
|
23
|
+
emitElementEvent(callbacksRef.current, selector, {
|
|
24
|
+
id: target.id(),
|
|
25
|
+
type,
|
|
26
|
+
});
|
|
27
|
+
onRuntimeChange();
|
|
28
|
+
};
|
|
29
|
+
cy.on(cytoscapeEvent, selector, handler);
|
|
30
|
+
return { cytoscapeEvent, handler };
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/*** Return whether a Cytoscape event target exposes the element identifier contract. */
|
|
34
|
+
function isGraphEventTarget(value) {
|
|
35
|
+
return (typeof value === 'object' && value !== null && 'id' in value && typeof value.id === 'function');
|
|
36
|
+
}
|
|
37
|
+
/*** Emit one translated element event without dynamic callback-property access. */
|
|
38
|
+
function emitElementEvent(callbacks, selector, event) {
|
|
39
|
+
if (selector === 'node') {
|
|
40
|
+
callbacks.onNodeEvent?.(event);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
callbacks.onEdgeEvent?.(event);
|
|
44
|
+
}
|
|
45
|
+
/*** Remove one selector's event handlers using the exact bound callback identities. */
|
|
46
|
+
function unbindElementEvents(cy, selector, handlers) {
|
|
47
|
+
for (const { cytoscapeEvent, handler } of handlers) {
|
|
48
|
+
cy.off(cytoscapeEvent, selector, handler);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const EVENT_TYPES = [
|
|
52
|
+
['tap', 'press'],
|
|
53
|
+
['dbltap', 'double-press'],
|
|
54
|
+
['select', 'select'],
|
|
55
|
+
['unselect', 'unselect'],
|
|
56
|
+
['mouseover', 'pointer-enter'],
|
|
57
|
+
['mouseout', 'pointer-leave'],
|
|
58
|
+
];
|
|
59
|
+
//# sourceMappingURL=bindGraphEvents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bindGraphEvents.js","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/bindGraphEvents.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Core, EventObject } from 'cytoscape';\n\nimport type {\n GraphViewCallbacks,\n GraphViewController,\n GraphViewElementEventType,\n} from './GraphView';\n\n/*** Bind stable Cytoscape events that always read the latest React callbacks. */\nexport function bindGraphEvents(\n cy: Core,\n callbacksRef: { current: GraphViewCallbacks },\n controller: GraphViewController,\n onRuntimeChange: () => void,\n) {\n const nodeHandlers = bindElementEvents(cy, 'node', callbacksRef, onRuntimeChange);\n const edgeHandlers = bindElementEvents(cy, 'edge', callbacksRef, onRuntimeChange);\n const viewportHandler = () => {\n callbacksRef.current.onViewportChange?.(controller.getViewport());\n onRuntimeChange();\n };\n cy.on('zoom pan', viewportHandler);\n\n return () => {\n unbindElementEvents(cy, 'node', nodeHandlers);\n unbindElementEvents(cy, 'edge', edgeHandlers);\n cy.off('zoom pan', viewportHandler);\n };\n}\n\ntype ElementSelector = 'edge' | 'node';\n\ninterface BoundElementHandler {\n readonly cytoscapeEvent: string;\n readonly handler: (event: EventObject) => void;\n}\n\n/*** Bind one selector's translated event set without resubscribing on callback changes. */\nfunction bindElementEvents(\n cy: Core,\n selector: ElementSelector,\n callbacksRef: { current: GraphViewCallbacks },\n onRuntimeChange: () => void,\n): readonly BoundElementHandler[] {\n return EVENT_TYPES.map(([cytoscapeEvent, type]) => {\n const handler = (event: EventObject) => {\n const target: unknown = event.target;\n if (!isGraphEventTarget(target)) return;\n emitElementEvent(callbacksRef.current, selector, {\n id: target.id(),\n type,\n });\n onRuntimeChange();\n };\n cy.on(cytoscapeEvent, selector, handler);\n return { cytoscapeEvent, handler };\n });\n}\n\n/*** Return whether a Cytoscape event target exposes the element identifier contract. */\nfunction isGraphEventTarget(value: unknown): value is GraphEventTarget {\n return (\n typeof value === 'object' && value !== null && 'id' in value && typeof value.id === 'function'\n );\n}\n\ninterface GraphEventTarget {\n id(): string;\n}\n\n/*** Emit one translated element event without dynamic callback-property access. */\nfunction emitElementEvent(\n callbacks: GraphViewCallbacks,\n selector: ElementSelector,\n event: { readonly id: string; readonly type: GraphViewElementEventType },\n) {\n if (selector === 'node') {\n callbacks.onNodeEvent?.(event);\n return;\n }\n callbacks.onEdgeEvent?.(event);\n}\n\n/*** Remove one selector's event handlers using the exact bound callback identities. */\nfunction unbindElementEvents(\n cy: Core,\n selector: ElementSelector,\n handlers: readonly BoundElementHandler[],\n) {\n for (const { cytoscapeEvent, handler } of handlers) {\n cy.off(cytoscapeEvent, selector, handler);\n }\n}\n\nconst EVENT_TYPES = [\n ['tap', 'press'],\n ['dbltap', 'double-press'],\n ['select', 'select'],\n ['unselect', 'unselect'],\n ['mouseover', 'pointer-enter'],\n ['mouseout', 'pointer-leave'],\n] as const satisfies readonly (readonly [string, GraphViewElementEventType])[];\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Core } from 'cytoscape';
|
|
2
|
+
import type { GraphViewController } from './GraphView';
|
|
3
|
+
/*** Create the public viewport controller while keeping the Cytoscape core private. */
|
|
4
|
+
export declare function createGraphController(cy: Core, fitPaddingRef: {
|
|
5
|
+
current: number;
|
|
6
|
+
}): GraphViewController;
|
|
7
|
+
//# sourceMappingURL=createGraphController.d.ts.map
|
|
@@ -0,0 +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;AAGtC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD,uFAAuF;AACvF,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,IAAI,EACR,aAAa,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GACjC,mBAAmB,CA4BrB"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { fitGraphViewport } from './fitGraphViewport';
|
|
2
|
+
/*** Create the public viewport controller while keeping the Cytoscape core private. */
|
|
3
|
+
export function createGraphController(cy, fitPaddingRef) {
|
|
4
|
+
return {
|
|
5
|
+
fit(options) {
|
|
6
|
+
fitGraphViewport(cy, {
|
|
7
|
+
nodeIds: options?.nodeIds,
|
|
8
|
+
padding: options?.padding ?? fitPaddingRef.current,
|
|
9
|
+
});
|
|
10
|
+
},
|
|
11
|
+
getViewport() {
|
|
12
|
+
return { pan: cy.pan(), zoom: cy.zoom() };
|
|
13
|
+
},
|
|
14
|
+
setPan(pan) {
|
|
15
|
+
cy.pan(pan);
|
|
16
|
+
},
|
|
17
|
+
setZoom(zoom) {
|
|
18
|
+
cy.zoom({
|
|
19
|
+
level: zoom,
|
|
20
|
+
renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 },
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
zoomBy(factor) {
|
|
24
|
+
const zoom = cy.zoom() * factor;
|
|
25
|
+
cy.zoom({
|
|
26
|
+
level: zoom,
|
|
27
|
+
renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 },
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=createGraphController.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createGraphController.js","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/createGraphController.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGtD,uFAAuF;AACvF,MAAM,UAAU,qBAAqB,CACnC,EAAQ,EACR,aAAkC;IAElC,OAAO;QACL,GAAG,CAAC,OAAO;YACT,gBAAgB,CAAC,EAAE,EAAE;gBACnB,OAAO,EAAE,OAAO,EAAE,OAAO;gBACzB,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,aAAa,CAAC,OAAO;aACnD,CAAC,CAAC;QACL,CAAC;QACD,WAAW;YACT,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;QAC5C,CAAC;QACD,MAAM,CAAC,GAAG;YACR,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,CAAC;QACD,OAAO,CAAC,IAAI;YACV,EAAE,CAAC,IAAI,CAAC;gBACN,KAAK,EAAE,IAAI;gBACX,gBAAgB,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE;aAC5D,CAAC,CAAC;QACL,CAAC;QACD,MAAM,CAAC,MAAM;YACX,MAAM,IAAI,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,MAAM,CAAC;YAChC,EAAE,CAAC,IAAI,CAAC;gBACN,KAAK,EAAE,IAAI;gBACX,gBAAgB,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE;aAC5D,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { Core } from 'cytoscape';\n\nimport { fitGraphViewport } from './fitGraphViewport';\nimport type { GraphViewController } from './GraphView';\n\n/*** Create the public viewport controller while keeping the Cytoscape core private. */\nexport function createGraphController(\n cy: Core,\n fitPaddingRef: { current: number },\n): GraphViewController {\n return {\n fit(options) {\n fitGraphViewport(cy, {\n nodeIds: options?.nodeIds,\n padding: options?.padding ?? fitPaddingRef.current,\n });\n },\n getViewport() {\n return { pan: cy.pan(), zoom: cy.zoom() };\n },\n setPan(pan) {\n cy.pan(pan);\n },\n setZoom(zoom) {\n cy.zoom({\n level: zoom,\n renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 },\n });\n },\n zoomBy(factor) {\n const zoom = cy.zoom() * factor;\n cy.zoom({\n level: zoom,\n renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 },\n });\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Core } from 'cytoscape';
|
|
2
|
+
interface CreateGraphResizeObserverInput {
|
|
3
|
+
readonly container: unknown;
|
|
4
|
+
readonly cy: Core;
|
|
5
|
+
readonly fitPaddingRef: {
|
|
6
|
+
current: number;
|
|
7
|
+
};
|
|
8
|
+
readonly layoutRunningRef: {
|
|
9
|
+
current: boolean;
|
|
10
|
+
};
|
|
11
|
+
readonly readyRef: {
|
|
12
|
+
current: boolean;
|
|
13
|
+
};
|
|
14
|
+
readonly onViewportSettled: () => void;
|
|
15
|
+
}
|
|
16
|
+
export interface GraphResizeObserver {
|
|
17
|
+
disconnect(): void;
|
|
18
|
+
}
|
|
19
|
+
/*** Observe settled container resizing without racing an active layout generation. */
|
|
20
|
+
export declare function createGraphResizeObserver(input: CreateGraphResizeObserverInput): GraphResizeObserver | null;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=createGraphResizeObserver.d.ts.map
|
package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.d.ts.map
ADDED
|
@@ -0,0 +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;AAKtC,UAAU,8BAA8B;IACtC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,aAAa,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,QAAQ,CAAC,gBAAgB,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAChD,QAAQ,CAAC,QAAQ,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACxC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,IAAI,CAAC;CACxC;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,IAAI,IAAI,CAAC;CACpB;AAED,sFAAsF;AACtF,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,8BAA8B,GACpC,mBAAmB,GAAG,IAAI,CAe5B"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { fitGraphViewport } from './fitGraphViewport';
|
|
2
|
+
import { scheduleGraphFrame } from './scheduleGraphFrame';
|
|
3
|
+
/*** Observe settled container resizing without racing an active layout generation. */
|
|
4
|
+
export function createGraphResizeObserver(input) {
|
|
5
|
+
const Observer = readResizeObserverConstructor();
|
|
6
|
+
if (Observer === null)
|
|
7
|
+
return null;
|
|
8
|
+
const sizeRef = { height: -1, width: -1 };
|
|
9
|
+
const observer = new Observer((entries) => {
|
|
10
|
+
const entry = entries.at(0);
|
|
11
|
+
if (entry === undefined || !hasSizeChanged(sizeRef, entry.contentRect))
|
|
12
|
+
return;
|
|
13
|
+
sizeRef.height = entry.contentRect.height;
|
|
14
|
+
sizeRef.width = entry.contentRect.width;
|
|
15
|
+
if (!input.readyRef.current || input.layoutRunningRef.current)
|
|
16
|
+
return;
|
|
17
|
+
scheduleResizeFit(input);
|
|
18
|
+
});
|
|
19
|
+
observer.observe(input.container);
|
|
20
|
+
return observer;
|
|
21
|
+
}
|
|
22
|
+
/*** Fit one genuine resize only after the active layout has settled. */
|
|
23
|
+
function scheduleResizeFit(input) {
|
|
24
|
+
scheduleGraphFrame(() => {
|
|
25
|
+
if (input.cy.destroyed() || input.layoutRunningRef.current)
|
|
26
|
+
return;
|
|
27
|
+
input.cy.resize();
|
|
28
|
+
fitGraphViewport(input.cy, { padding: input.fitPaddingRef.current });
|
|
29
|
+
input.onViewportSettled();
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/*** Return whether ResizeObserver reported a genuinely different container size. */
|
|
33
|
+
function hasSizeChanged(previous, next) {
|
|
34
|
+
return previous.height !== next.height || previous.width !== next.width;
|
|
35
|
+
}
|
|
36
|
+
/*** Read ResizeObserver without imposing DOM library types on the package target. */
|
|
37
|
+
function readResizeObserverConstructor() {
|
|
38
|
+
const value = globalThis.ResizeObserver;
|
|
39
|
+
return value ?? null;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=createGraphResizeObserver.js.map
|
package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createGraphResizeObserver.js","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/createGraphResizeObserver.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAe1D,sFAAsF;AACtF,MAAM,UAAU,yBAAyB,CACvC,KAAqC;IAErC,MAAM,QAAQ,GAAG,6BAA6B,EAAE,CAAC;IACjD,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACnC,MAAM,OAAO,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC;IAE1C,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,CAAC,OAAO,EAAE,EAAE;QACxC,MAAM,KAAK,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC5B,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,KAAK,CAAC,WAAW,CAAC;YAAE,OAAO;QAC/E,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC;QAC1C,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC;QACxC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,IAAI,KAAK,CAAC,gBAAgB,CAAC,OAAO;YAAE,OAAO;QACtE,iBAAiB,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC,CAAC,CAAC;IACH,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAClC,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,wEAAwE;AACxE,SAAS,iBAAiB,CAAC,KAAqC;IAC9D,kBAAkB,CAAC,GAAG,EAAE;QACtB,IAAI,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,IAAI,KAAK,CAAC,gBAAgB,CAAC,OAAO;YAAE,OAAO;QACnE,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC;QAClB,gBAAgB,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,aAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QACrE,KAAK,CAAC,iBAAiB,EAAE,CAAC;IAC5B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,oFAAoF;AACpF,SAAS,cAAc,CACrB,QAA6D,EAC7D,IAAyD;IAEzD,OAAO,QAAQ,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,IAAI,QAAQ,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,CAAC;AAC1E,CAAC;AAED,qFAAqF;AACrF,SAAS,6BAA6B;IACpC,MAAM,KAAK,GACT,UAGD,CAAC,cAAc,CAAC;IACjB,OAAO,KAAK,IAAI,IAAI,CAAC;AACvB,CAAC","sourcesContent":["import type { Core } from 'cytoscape';\n\nimport { fitGraphViewport } from './fitGraphViewport';\nimport { scheduleGraphFrame } from './scheduleGraphFrame';\n\ninterface CreateGraphResizeObserverInput {\n readonly container: unknown;\n readonly cy: Core;\n readonly fitPaddingRef: { current: number };\n readonly layoutRunningRef: { current: boolean };\n readonly readyRef: { current: boolean };\n readonly onViewportSettled: () => void;\n}\n\nexport interface GraphResizeObserver {\n disconnect(): 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\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.layoutRunningRef.current) return;\n scheduleResizeFit(input);\n });\n observer.observe(input.container);\n return observer;\n}\n\n/*** Fit one genuine resize only after the active layout has settled. */\nfunction scheduleResizeFit(input: CreateGraphResizeObserverInput) {\n scheduleGraphFrame(() => {\n if (input.cy.destroyed() || input.layoutRunningRef.current) return;\n input.cy.resize();\n fitGraphViewport(input.cy, { padding: input.fitPaddingRef.current });\n input.onViewportSettled();\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"]}
|