spytial-core 1.4.20 → 1.4.21
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.
|
@@ -91364,6 +91364,16 @@ VVbdfjptxz|~\x80\x82\x84\xA6\xA8W\b
|
|
|
91364
91364
|
edgesBetweenNodes: /* @__PURE__ */ new Map(),
|
|
91365
91365
|
alignmentEdges: /* @__PURE__ */ new Set()
|
|
91366
91366
|
};
|
|
91367
|
+
/**
|
|
91368
|
+
* Tracks whether the user has manually interacted with zoom/pan.
|
|
91369
|
+
* When true, we don't auto-fit the viewport to preserve user's view.
|
|
91370
|
+
*/
|
|
91371
|
+
this.userHasManuallyZoomed = false;
|
|
91372
|
+
/**
|
|
91373
|
+
* Tracks whether this is the initial render (first layout).
|
|
91374
|
+
* We always fit viewport on initial render.
|
|
91375
|
+
*/
|
|
91376
|
+
this.isInitialRender = true;
|
|
91367
91377
|
/**
|
|
91368
91378
|
* Stores the starting coordinates when a node begins dragging so
|
|
91369
91379
|
* drag end events can report both the previous and new positions.
|
|
@@ -91689,6 +91699,7 @@ VVbdfjptxz|~\x80\x82\x84\xA6\xA8W\b
|
|
|
91689
91699
|
<div id="zoom-controls">
|
|
91690
91700
|
<button id="zoom-in" title="Zoom In" aria-label="Zoom in">+</button>
|
|
91691
91701
|
<button id="zoom-out" title="Zoom Out" aria-label="Zoom out">\u2212</button>
|
|
91702
|
+
<button id="zoom-fit" title="Fit to View" aria-label="Fit graph to view">\u2922</button>
|
|
91692
91703
|
</div>
|
|
91693
91704
|
</div>
|
|
91694
91705
|
<div id="svg-container">
|
|
@@ -91724,7 +91735,11 @@ VVbdfjptxz|~\x80\x82\x84\xA6\xA8W\b
|
|
|
91724
91735
|
this.svg = d32.select(this.shadowRoot.querySelector("#svg"));
|
|
91725
91736
|
this.container = this.svg.select(".zoomable");
|
|
91726
91737
|
if (d32.zoom) {
|
|
91727
|
-
this.zoomBehavior = d32.zoom().scaleExtent([0.01, 20]).on("
|
|
91738
|
+
this.zoomBehavior = d32.zoom().scaleExtent([0.01, 20]).on("start", () => {
|
|
91739
|
+
if (d32.event.sourceEvent) {
|
|
91740
|
+
this.userHasManuallyZoomed = true;
|
|
91741
|
+
}
|
|
91742
|
+
}).on("zoom", () => {
|
|
91728
91743
|
this.container.attr("transform", d32.event.transform);
|
|
91729
91744
|
this.updateZoomControlStates();
|
|
91730
91745
|
this.updateSmallNodeClasses();
|
|
@@ -91741,16 +91756,24 @@ VVbdfjptxz|~\x80\x82\x84\xA6\xA8W\b
|
|
|
91741
91756
|
initializeZoomControls() {
|
|
91742
91757
|
const zoomInButton = this.shadowRoot.querySelector("#zoom-in");
|
|
91743
91758
|
const zoomOutButton = this.shadowRoot.querySelector("#zoom-out");
|
|
91759
|
+
const zoomFitButton = this.shadowRoot.querySelector("#zoom-fit");
|
|
91744
91760
|
if (zoomInButton) {
|
|
91745
91761
|
zoomInButton.addEventListener("click", () => {
|
|
91762
|
+
this.userHasManuallyZoomed = true;
|
|
91746
91763
|
this.zoomIn();
|
|
91747
91764
|
});
|
|
91748
91765
|
}
|
|
91749
91766
|
if (zoomOutButton) {
|
|
91750
91767
|
zoomOutButton.addEventListener("click", () => {
|
|
91768
|
+
this.userHasManuallyZoomed = true;
|
|
91751
91769
|
this.zoomOut();
|
|
91752
91770
|
});
|
|
91753
91771
|
}
|
|
91772
|
+
if (zoomFitButton) {
|
|
91773
|
+
zoomFitButton.addEventListener("click", () => {
|
|
91774
|
+
this.resetViewToFitContent();
|
|
91775
|
+
});
|
|
91776
|
+
}
|
|
91754
91777
|
this.updateZoomControlStates();
|
|
91755
91778
|
}
|
|
91756
91779
|
/**
|
|
@@ -91899,6 +91922,7 @@ VVbdfjptxz|~\x80\x82\x84\xA6\xA8W\b
|
|
|
91899
91922
|
*/
|
|
91900
91923
|
setupNodeDragHandlers(nodeDrag) {
|
|
91901
91924
|
nodeDrag.on("start.cnd", (d) => {
|
|
91925
|
+
this.userHasManuallyZoomed = true;
|
|
91902
91926
|
const start = { x: d.x, y: d.y };
|
|
91903
91927
|
this.dragStartPositions.set(d.id, start);
|
|
91904
91928
|
this.dispatchEvent(
|
|
@@ -92130,6 +92154,10 @@ VVbdfjptxz|~\x80\x82\x84\xA6\xA8W\b
|
|
|
92130
92154
|
if (!isInstanceLayout(instanceLayout)) {
|
|
92131
92155
|
throw new Error("Invalid instance layout provided. Expected an InstanceLayout instance.");
|
|
92132
92156
|
}
|
|
92157
|
+
if (!options?.priorPositions) {
|
|
92158
|
+
this.isInitialRender = true;
|
|
92159
|
+
this.userHasManuallyZoomed = false;
|
|
92160
|
+
}
|
|
92133
92161
|
if (this.svg && this.zoomBehavior && d32) {
|
|
92134
92162
|
try {
|
|
92135
92163
|
const identity = d32.zoomIdentity;
|
|
@@ -93688,36 +93716,51 @@ VVbdfjptxz|~\x80\x82\x84\xA6\xA8W\b
|
|
|
93688
93716
|
minimizeOverlap(currentLabel, overlappingLabels) {
|
|
93689
93717
|
}
|
|
93690
93718
|
/**
|
|
93691
|
-
* Fits the viewport to show all content with
|
|
93692
|
-
* Uses
|
|
93719
|
+
* Fits the viewport to show all content with appropriate zoom and pan.
|
|
93720
|
+
* Uses D3 zoom transform for smooth, consistent behavior.
|
|
93721
|
+
* Only performs fit if:
|
|
93722
|
+
* - This is the initial render, OR
|
|
93723
|
+
* - User has not manually zoomed/panned, OR
|
|
93724
|
+
* - Force parameter is true (e.g., from reset button)
|
|
93725
|
+
*
|
|
93726
|
+
* @param force - If true, fit regardless of user interaction state
|
|
93727
|
+
*/
|
|
93728
|
+
fitViewportToContent(force = false) {
|
|
93729
|
+
const svgElement = this.svg?.node();
|
|
93730
|
+
if (!svgElement || !this.zoomBehavior) return;
|
|
93731
|
+
if (this.userHasManuallyZoomed && !this.isInitialRender && !force) {
|
|
93732
|
+
return;
|
|
93733
|
+
}
|
|
93734
|
+
const bounds = this.calculateContentBounds();
|
|
93735
|
+
if (!bounds) return;
|
|
93736
|
+
const containerWidth = svgElement.clientWidth || svgElement.parentElement?.clientWidth || 800;
|
|
93737
|
+
const containerHeight = svgElement.clientHeight || svgElement.parentElement?.clientHeight || 600;
|
|
93738
|
+
const padding = _WebColaCnDGraph.VIEWBOX_PADDING * 4;
|
|
93739
|
+
const scaleX = (containerWidth - padding * 2) / bounds.width;
|
|
93740
|
+
const scaleY = (containerHeight - padding * 2) / bounds.height;
|
|
93741
|
+
const scale = Math.min(scaleX, scaleY, 1);
|
|
93742
|
+
const [minScale, maxScale] = this.zoomBehavior.scaleExtent();
|
|
93743
|
+
const clampedScale = Math.max(minScale, Math.min(maxScale, scale));
|
|
93744
|
+
const contentCenterX = bounds.x + bounds.width / 2;
|
|
93745
|
+
const contentCenterY = bounds.y + bounds.height / 2;
|
|
93746
|
+
const translateX = containerWidth / 2 - contentCenterX * clampedScale;
|
|
93747
|
+
const translateY = containerHeight / 2 - contentCenterY * clampedScale;
|
|
93748
|
+
const transform = d32.zoomIdentity.translate(translateX, translateY).scale(clampedScale);
|
|
93749
|
+
if (this.isInitialRender) {
|
|
93750
|
+
this.svg.call(this.zoomBehavior.transform, transform);
|
|
93751
|
+
this.isInitialRender = false;
|
|
93752
|
+
} else {
|
|
93753
|
+
this.svg.transition().duration(300).ease(d32.easeCubicOut).call(this.zoomBehavior.transform, transform);
|
|
93754
|
+
}
|
|
93755
|
+
this.updateZoomControlStates();
|
|
93756
|
+
}
|
|
93757
|
+
/**
|
|
93758
|
+
* Resets the view to fit all content, clearing user zoom state.
|
|
93759
|
+
* Called when user clicks the reset/fit button.
|
|
93693
93760
|
*/
|
|
93694
|
-
|
|
93695
|
-
|
|
93696
|
-
|
|
93697
|
-
const manualBounds = this.calculateContentBounds();
|
|
93698
|
-
const bbox = manualBounds || svgElement.getBBox();
|
|
93699
|
-
const padding = _WebColaCnDGraph.VIEWBOX_PADDING;
|
|
93700
|
-
let extraBottomPadding = 50;
|
|
93701
|
-
if (this.currentLayout?.nodes) {
|
|
93702
|
-
const bottomNode = this.currentLayout.nodes.reduce((bottom, node) => {
|
|
93703
|
-
if (typeof node.x === "number" && typeof node.y === "number") {
|
|
93704
|
-
const nodeBottom = node.y + (node.height || 0);
|
|
93705
|
-
const currentBottom = bottom ? bottom.y + (bottom.height || 0) : -Infinity;
|
|
93706
|
-
return nodeBottom > currentBottom ? node : bottom;
|
|
93707
|
-
}
|
|
93708
|
-
return bottom;
|
|
93709
|
-
}, null);
|
|
93710
|
-
if (bottomNode && bottomNode.height) {
|
|
93711
|
-
extraBottomPadding = Math.min(50, bottomNode.height / 1.5);
|
|
93712
|
-
}
|
|
93713
|
-
}
|
|
93714
|
-
const viewBox = [
|
|
93715
|
-
bbox.x - padding,
|
|
93716
|
-
bbox.y - padding,
|
|
93717
|
-
bbox.width + 2 * padding,
|
|
93718
|
-
bbox.height + 2 * padding + extraBottomPadding
|
|
93719
|
-
].join(" ");
|
|
93720
|
-
this.svg.attr("viewBox", viewBox);
|
|
93761
|
+
resetViewToFitContent() {
|
|
93762
|
+
this.userHasManuallyZoomed = false;
|
|
93763
|
+
this.fitViewportToContent(true);
|
|
93721
93764
|
}
|
|
93722
93765
|
/**
|
|
93723
93766
|
* Manually calculates the bounding box of all content to ensure accurate viewport fitting.
|
|
@@ -94273,7 +94316,7 @@ VVbdfjptxz|~\x80\x82\x84\xA6\xA8W\b
|
|
|
94273
94316
|
#zoom-controls {
|
|
94274
94317
|
display: flex;
|
|
94275
94318
|
flex-direction: row;
|
|
94276
|
-
gap:
|
|
94319
|
+
gap: 8px;
|
|
94277
94320
|
align-items: center;
|
|
94278
94321
|
}
|
|
94279
94322
|
|