@petrarca/sonnet-graph 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -118
- package/dist/index.d.ts +51 -31
- package/dist/index.js +51 -27
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -185,6 +185,10 @@ var EMPTY_NORMALIZED = {
|
|
|
185
185
|
// src/stores/Selection.ts
|
|
186
186
|
import { create } from "zustand";
|
|
187
187
|
import { devLog } from "@petrarca/sonnet-core";
|
|
188
|
+
var MAX_NODE_SELECTION = 2;
|
|
189
|
+
function evictToMax(selected, id, max = MAX_NODE_SELECTION) {
|
|
190
|
+
return [...selected.filter((existing) => existing !== id), id].slice(-max);
|
|
191
|
+
}
|
|
188
192
|
function createSelectionStore() {
|
|
189
193
|
return create()((set, get) => ({
|
|
190
194
|
selectedNodeIds: [],
|
|
@@ -1046,7 +1050,7 @@ function useGraphStores() {
|
|
|
1046
1050
|
const ctx = useContext(GraphStoresContext);
|
|
1047
1051
|
if (!ctx) {
|
|
1048
1052
|
throw new Error(
|
|
1049
|
-
"useGraphStores must be used within a
|
|
1053
|
+
"useGraphStores must be used within a GraphStoresContext.Provider. Wrap your graph UI with <GraphStoresContext.Provider value={...}>."
|
|
1050
1054
|
);
|
|
1051
1055
|
}
|
|
1052
1056
|
return ctx;
|
|
@@ -1063,6 +1067,9 @@ function useWorkspaceGraphExploration() {
|
|
|
1063
1067
|
function useWorkspaceSelection() {
|
|
1064
1068
|
return useGraphStores().selection;
|
|
1065
1069
|
}
|
|
1070
|
+
function useWorkspaceGraphFocus() {
|
|
1071
|
+
return useGraphStores().graphFocus;
|
|
1072
|
+
}
|
|
1066
1073
|
|
|
1067
1074
|
// src/hooks/useStableRef.ts
|
|
1068
1075
|
import { useCallback, useRef } from "react";
|
|
@@ -1082,12 +1089,13 @@ function useLiveGraphData() {
|
|
|
1082
1089
|
const useGraphFilter2 = useWorkspaceGraphFilter();
|
|
1083
1090
|
const useGraphData2 = useWorkspaceGraphData();
|
|
1084
1091
|
const useGraphExploration = useWorkspaceGraphExploration();
|
|
1092
|
+
const useGraphFocus2 = useWorkspaceGraphFocus();
|
|
1085
1093
|
const lastDataChangeTime = useGraphFilter2((s) => s.lastDataChangeTime);
|
|
1086
1094
|
const filterLabels = useGraphFilter2((s) => s.nodeLabelFilters);
|
|
1087
1095
|
const allLabelsDisabled = useGraphFilter2((s) => s.allLabelsDisabled);
|
|
1088
1096
|
const edgeFilterLabels = useGraphFilter2((s) => s.edgeLabelFilters);
|
|
1089
1097
|
const allEdgeLabelsDisabled = useGraphFilter2((s) => s.allEdgeLabelsDisabled);
|
|
1090
|
-
const overrideElementIds =
|
|
1098
|
+
const overrideElementIds = useGraphFocus2((s) => s.overrideElementIds);
|
|
1091
1099
|
const isFocusMode = useGraphExploration((s) => s.isFocusMode);
|
|
1092
1100
|
const focusedNodeIds = useGraphExploration((s) => s.focusedNodeIds);
|
|
1093
1101
|
const hiddenElementIds = useGraphExploration((s) => s.hiddenElementIds);
|
|
@@ -1104,8 +1112,8 @@ function useLiveGraphData() {
|
|
|
1104
1112
|
hiddenCount: hiddenElementIds.size
|
|
1105
1113
|
});
|
|
1106
1114
|
useEffect(() => {
|
|
1107
|
-
|
|
1108
|
-
}, [lastDataChangeTime]);
|
|
1115
|
+
useGraphFocus2.getState().clearOverrides();
|
|
1116
|
+
}, [lastDataChangeTime, useGraphFocus2]);
|
|
1109
1117
|
const allLabelCounts = useMemo(() => {
|
|
1110
1118
|
if (isValueSet) return {};
|
|
1111
1119
|
const counts = {};
|
|
@@ -1302,9 +1310,10 @@ function useLiveGraphData() {
|
|
|
1302
1310
|
};
|
|
1303
1311
|
}, [stats, availableNodes.length, availableEdges.length]);
|
|
1304
1312
|
useEffect(() => {
|
|
1305
|
-
const { clearOverrides } =
|
|
1313
|
+
const { clearOverrides } = useGraphFocus2.getState();
|
|
1306
1314
|
clearOverrides();
|
|
1307
1315
|
}, [
|
|
1316
|
+
useGraphFocus2,
|
|
1308
1317
|
filterLabels,
|
|
1309
1318
|
allLabelsDisabled,
|
|
1310
1319
|
edgeFilterLabels,
|
|
@@ -2102,6 +2111,7 @@ function ReaGraph({
|
|
|
2102
2111
|
const useGraphSelection = useWorkspaceSelection();
|
|
2103
2112
|
const useGraphFilter2 = useWorkspaceGraphFilter();
|
|
2104
2113
|
const useGraphExploration = useWorkspaceGraphExploration();
|
|
2114
|
+
const useGraphFocus2 = useWorkspaceGraphFocus();
|
|
2105
2115
|
const hiddenElementIds = useGraphExploration((s) => s.hiddenElementIds);
|
|
2106
2116
|
const isFocusMode = useGraphExploration((s) => s.isFocusMode);
|
|
2107
2117
|
const hasHiddenElements = hiddenElementIds.size > 0;
|
|
@@ -2125,7 +2135,7 @@ function ReaGraph({
|
|
|
2125
2135
|
type: "multi"
|
|
2126
2136
|
});
|
|
2127
2137
|
useEffect3(() => {
|
|
2128
|
-
const unsubscribe =
|
|
2138
|
+
const unsubscribe = useGraphFocus2.subscribe((state) => {
|
|
2129
2139
|
const focusAction = state.focusAction;
|
|
2130
2140
|
if (!focusAction || !canvas.current) return;
|
|
2131
2141
|
devLog8("[ReGraph] Processing focus action", {
|
|
@@ -2143,11 +2153,11 @@ function ReaGraph({
|
|
|
2143
2153
|
setSelectedEdge
|
|
2144
2154
|
);
|
|
2145
2155
|
if (handled) {
|
|
2146
|
-
|
|
2156
|
+
useGraphFocus2.setState({ focusAction: null });
|
|
2147
2157
|
}
|
|
2148
2158
|
});
|
|
2149
2159
|
return unsubscribe;
|
|
2150
|
-
}, [nodes, edges, setSelectedNodeIds, setSelectedEdge]);
|
|
2160
|
+
}, [nodes, edges, setSelectedNodeIds, setSelectedEdge, useGraphFocus2]);
|
|
2151
2161
|
useEffect3(() => {
|
|
2152
2162
|
if (!canvas.current) return;
|
|
2153
2163
|
clearSelections();
|
|
@@ -2222,7 +2232,13 @@ function ReaGraph({
|
|
|
2222
2232
|
}
|
|
2223
2233
|
function handleNodeClick(node) {
|
|
2224
2234
|
setSelectedEdge(null);
|
|
2225
|
-
if (
|
|
2235
|
+
if (!onNodeClick) return;
|
|
2236
|
+
const alreadySelected = selections.includes(node.id);
|
|
2237
|
+
if (alreadySelected || selections.length < MAX_NODE_SELECTION) {
|
|
2238
|
+
onNodeClick(node);
|
|
2239
|
+
return;
|
|
2240
|
+
}
|
|
2241
|
+
setSelections(evictToMax(selections, node.id));
|
|
2226
2242
|
}
|
|
2227
2243
|
function handleEdgeClick(edge) {
|
|
2228
2244
|
const startNode = normalized.nodesById[edge.data.start_id_ + ""];
|
|
@@ -2257,10 +2273,8 @@ function ReaGraph({
|
|
|
2257
2273
|
}
|
|
2258
2274
|
useEffect3(() => {
|
|
2259
2275
|
if (!isMountedRef.current) return;
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
setSelectedNodeIds(selectedNodes.map((n) => n.id));
|
|
2263
|
-
}
|
|
2276
|
+
const selectedNodes = selections.map((id) => nodes.find((n) => n.id === id)).filter(Boolean);
|
|
2277
|
+
setSelectedNodeIds(selectedNodes.map((n) => n.id));
|
|
2264
2278
|
}, [selections, nodes]);
|
|
2265
2279
|
useEffect3(() => {
|
|
2266
2280
|
if (!isMountedRef.current) return;
|
|
@@ -3133,6 +3147,7 @@ function applyLayout({
|
|
|
3133
3147
|
}
|
|
3134
3148
|
}
|
|
3135
3149
|
function processPendingFocus({
|
|
3150
|
+
focusStore,
|
|
3136
3151
|
cy,
|
|
3137
3152
|
pendingFocus,
|
|
3138
3153
|
edgesRef,
|
|
@@ -3157,7 +3172,7 @@ function processPendingFocus({
|
|
|
3157
3172
|
}
|
|
3158
3173
|
);
|
|
3159
3174
|
pendingFocusActionRef.current = null;
|
|
3160
|
-
|
|
3175
|
+
focusStore.setState({ focusAction: null });
|
|
3161
3176
|
return;
|
|
3162
3177
|
}
|
|
3163
3178
|
cy.animate(
|
|
@@ -3177,7 +3192,7 @@ function processPendingFocus({
|
|
|
3177
3192
|
});
|
|
3178
3193
|
}
|
|
3179
3194
|
pendingFocusActionRef.current = null;
|
|
3180
|
-
|
|
3195
|
+
focusStore.setState({ focusAction: null });
|
|
3181
3196
|
}, 100);
|
|
3182
3197
|
}
|
|
3183
3198
|
function selectFocusedElement({
|
|
@@ -3220,6 +3235,7 @@ function selectFocusedElement({
|
|
|
3220
3235
|
}
|
|
3221
3236
|
}
|
|
3222
3237
|
function handleFocusStateChange({
|
|
3238
|
+
focusStore,
|
|
3223
3239
|
focusAction,
|
|
3224
3240
|
cyRef,
|
|
3225
3241
|
edgesRef,
|
|
@@ -3240,6 +3256,7 @@ function handleFocusStateChange({
|
|
|
3240
3256
|
const element = cy.$id(elementId);
|
|
3241
3257
|
if (element.length > 0) {
|
|
3242
3258
|
handleImmediateFocus({
|
|
3259
|
+
focusStore,
|
|
3243
3260
|
cy,
|
|
3244
3261
|
element,
|
|
3245
3262
|
focusAction,
|
|
@@ -3256,7 +3273,7 @@ function handleFocusStateChange({
|
|
|
3256
3273
|
elementType: focusAction.elementType
|
|
3257
3274
|
});
|
|
3258
3275
|
pendingFocusActionRef.current = focusAction;
|
|
3259
|
-
|
|
3276
|
+
focusStore.setState((state) => {
|
|
3260
3277
|
const overrides = new Set(state.overrideElementIds);
|
|
3261
3278
|
overrides.add(elementId);
|
|
3262
3279
|
if (focusAction.elementType === "edge") {
|
|
@@ -3280,6 +3297,7 @@ function handleFocusStateChange({
|
|
|
3280
3297
|
});
|
|
3281
3298
|
}
|
|
3282
3299
|
function handleImmediateFocus({
|
|
3300
|
+
focusStore,
|
|
3283
3301
|
cy,
|
|
3284
3302
|
element,
|
|
3285
3303
|
focusAction,
|
|
@@ -3310,7 +3328,7 @@ function handleImmediateFocus({
|
|
|
3310
3328
|
selectedEdgeRef
|
|
3311
3329
|
});
|
|
3312
3330
|
}
|
|
3313
|
-
|
|
3331
|
+
focusStore.setState({ focusAction: null });
|
|
3314
3332
|
}
|
|
3315
3333
|
function computeGraphViewState({
|
|
3316
3334
|
hasNodes,
|
|
@@ -3400,16 +3418,14 @@ function setupCytoscapeEventHandlers({
|
|
|
3400
3418
|
updateSelectedEdge(null);
|
|
3401
3419
|
}
|
|
3402
3420
|
let selectedIds = evtCy.$("node:selected").map((e) => e.id());
|
|
3403
|
-
if (selectedIds.length >
|
|
3404
|
-
const
|
|
3405
|
-
const updatedOrder = [...currentOrder.filter((cid) => cid !== id), id];
|
|
3406
|
-
const keep = updatedOrder.slice(-2);
|
|
3421
|
+
if (selectedIds.length > MAX_NODE_SELECTION) {
|
|
3422
|
+
const keep = evictToMax(selectedNodeIdsRef.current, id);
|
|
3407
3423
|
evtCy.batch(() => {
|
|
3408
3424
|
evtCy.$("node:selected").unselect();
|
|
3409
3425
|
keep.forEach((kid) => evtCy.$id(kid).select());
|
|
3410
3426
|
});
|
|
3411
3427
|
selectedIds = keep;
|
|
3412
|
-
devLog10("[CytoscapeGraph] enforced max
|
|
3428
|
+
devLog10("[CytoscapeGraph] enforced max selection after select", { keep });
|
|
3413
3429
|
}
|
|
3414
3430
|
updateSelectedNodeIds(selectedIds);
|
|
3415
3431
|
});
|
|
@@ -3491,6 +3507,7 @@ function CytoscapeGraph({
|
|
|
3491
3507
|
const useGraphFilter2 = useWorkspaceGraphFilter();
|
|
3492
3508
|
const useGraphExploration = useWorkspaceGraphExploration();
|
|
3493
3509
|
const useSelection3 = useWorkspaceSelection();
|
|
3510
|
+
const useGraphFocus2 = useWorkspaceGraphFocus();
|
|
3494
3511
|
const lastDataChangeTime = useGraphFilter2((s) => s.lastDataChangeTime);
|
|
3495
3512
|
const resetView = useGraphExploration((s) => s.resetView);
|
|
3496
3513
|
const hasHiddenElements = useGraphExploration(
|
|
@@ -3782,6 +3799,7 @@ function CytoscapeGraph({
|
|
|
3782
3799
|
const pendingFocus = pendingFocusActionRef.current;
|
|
3783
3800
|
if (pendingFocus) {
|
|
3784
3801
|
processPendingFocus({
|
|
3802
|
+
focusStore: useGraphFocus2,
|
|
3785
3803
|
cy,
|
|
3786
3804
|
pendingFocus,
|
|
3787
3805
|
edgesRef,
|
|
@@ -3799,8 +3817,9 @@ function CytoscapeGraph({
|
|
|
3799
3817
|
clearSelectionStore();
|
|
3800
3818
|
}, [lastDataChangeTime, clearSelectionStore]);
|
|
3801
3819
|
useEffect4(() => {
|
|
3802
|
-
const unsubscribe =
|
|
3820
|
+
const unsubscribe = useGraphFocus2.subscribe((state) => {
|
|
3803
3821
|
handleFocusStateChange({
|
|
3822
|
+
focusStore: useGraphFocus2,
|
|
3804
3823
|
focusAction: state.focusAction,
|
|
3805
3824
|
cyRef,
|
|
3806
3825
|
edgesRef,
|
|
@@ -3813,7 +3832,12 @@ function CytoscapeGraph({
|
|
|
3813
3832
|
});
|
|
3814
3833
|
});
|
|
3815
3834
|
return unsubscribe;
|
|
3816
|
-
}, [
|
|
3835
|
+
}, [
|
|
3836
|
+
baseNormalized,
|
|
3837
|
+
setSelectedEdgeStore,
|
|
3838
|
+
setSelectedNodeIdsStore,
|
|
3839
|
+
useGraphFocus2
|
|
3840
|
+
]);
|
|
3817
3841
|
const updateLayout = (newLayout) => {
|
|
3818
3842
|
setCurrentLayout(newLayout);
|
|
3819
3843
|
};
|
|
@@ -4385,7 +4409,6 @@ import { useVirtualizer } from "@tanstack/react-virtual";
|
|
|
4385
4409
|
import { cn as cn2 } from "@petrarca/sonnet-core";
|
|
4386
4410
|
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
4387
4411
|
var SR_ONLY = "absolute h-px w-px overflow-hidden whitespace-nowrap border-0 p-0 [clip:rect(0,0,0,0)] [clip-path:inset(50%)] -m-px";
|
|
4388
|
-
var MAX_NODE_SELECTION = 2;
|
|
4389
4412
|
function GraphA11yList({
|
|
4390
4413
|
visible = false,
|
|
4391
4414
|
rowHeight = 28,
|
|
@@ -4437,7 +4460,7 @@ function GraphA11yList({
|
|
|
4437
4460
|
setSelectedNodeIds(selectedNodeIds.filter((x) => x !== id));
|
|
4438
4461
|
return;
|
|
4439
4462
|
}
|
|
4440
|
-
setSelectedNodeIds(
|
|
4463
|
+
setSelectedNodeIds(evictToMax(selectedNodeIds, id));
|
|
4441
4464
|
},
|
|
4442
4465
|
[selectedNodeIds, setSelectedNodeIds, setSelectedEdge]
|
|
4443
4466
|
);
|
|
@@ -6040,7 +6063,7 @@ function createEditorStores() {
|
|
|
6040
6063
|
expandNode: async () => ({ nodes: [], edges: [], has_more: false }),
|
|
6041
6064
|
getNodes: async () => []
|
|
6042
6065
|
});
|
|
6043
|
-
return { graphFilter, graphData, graphExploration, selection };
|
|
6066
|
+
return { graphFilter, graphData, graphExploration, selection, graphFocus };
|
|
6044
6067
|
}
|
|
6045
6068
|
function loadModel(stores, model) {
|
|
6046
6069
|
const { nodes: rawNodes, edges: rawEdges } = modelToGraph(model);
|
|
@@ -7476,6 +7499,7 @@ export {
|
|
|
7476
7499
|
useWorkspaceGraphData,
|
|
7477
7500
|
useWorkspaceGraphExploration,
|
|
7478
7501
|
useWorkspaceGraphFilter,
|
|
7502
|
+
useWorkspaceGraphFocus,
|
|
7479
7503
|
useWorkspaceSelection,
|
|
7480
7504
|
validateCapabilities,
|
|
7481
7505
|
validateGraphCapabilities
|