polly-graph 0.1.6 → 0.1.7

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/dist/index.cjs CHANGED
@@ -3767,56 +3767,6 @@ function createGraphSimulation(config) {
3767
3767
  return { simulation };
3768
3768
  }
3769
3769
 
3770
- // src/utils/get-link-marker-id.ts
3771
- function getLinkMarkerId(style) {
3772
- const markerStyle = {
3773
- stroke: style.stroke ?? "#94a3b8",
3774
- strokeWidth: style.strokeWidth ?? 2,
3775
- arrowFill: style.arrow?.fill ?? style.stroke ?? "#94a3b8",
3776
- arrowSize: style.arrow?.size ?? 6
3777
- };
3778
- const serializedStyle = JSON.stringify(markerStyle);
3779
- const hash = createHash(serializedStyle);
3780
- return `graph-arrow-${hash}`;
3781
- }
3782
- function createHash(value) {
3783
- let hash = 0;
3784
- for (let index2 = 0; index2 < value.length; index2 += 1) {
3785
- const charCode = value.charCodeAt(index2);
3786
- hash = (hash << 5) - hash + charCode;
3787
- hash |= 0;
3788
- }
3789
- return Math.abs(hash).toString(36);
3790
- }
3791
-
3792
- // src/core/create-arrow-marker.ts
3793
- function createArrowMarker(params) {
3794
- const markerId = getLinkMarkerId(params.style);
3795
- const existingMarker = params.svg.querySelector(`#${markerId}`);
3796
- if (existingMarker) {
3797
- return markerId;
3798
- }
3799
- const arrowSize = params.style.arrow?.size ?? 6;
3800
- const fill = params.style.arrow?.fill ?? params.style.stroke ?? "#94a3b8";
3801
- const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
3802
- const marker = document.createElementNS("http://www.w3.org/2000/svg", "marker");
3803
- marker.setAttribute("id", markerId);
3804
- marker.setAttribute("viewBox", "0 0 20 20");
3805
- marker.setAttribute("refX", "0");
3806
- marker.setAttribute("refY", "10");
3807
- marker.setAttribute("markerWidth", String(arrowSize * 2));
3808
- marker.setAttribute("markerHeight", String(arrowSize * 2));
3809
- marker.setAttribute("orient", "auto");
3810
- marker.setAttribute("markerUnits", "userSpaceOnUse");
3811
- const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
3812
- path.setAttribute("d", "M 0 0 L 20 10 L 0 20 z");
3813
- path.setAttribute("fill", fill);
3814
- marker.appendChild(path);
3815
- defs.appendChild(marker);
3816
- params.svg.insertBefore(defs, params.svg.firstChild);
3817
- return markerId;
3818
- }
3819
-
3820
3770
  // src/controls/graph-controls.utils.ts
3821
3771
  function resolveControlsPosition(position) {
3822
3772
  return position ?? "bottom-left";
@@ -4045,6 +3995,56 @@ function mergeLinkStyle(base, override) {
4045
3995
  };
4046
3996
  }
4047
3997
 
3998
+ // src/utils/get-link-marker-id.ts
3999
+ function getLinkMarkerId(style) {
4000
+ const markerStyle = {
4001
+ stroke: style.stroke ?? "#94a3b8",
4002
+ strokeWidth: style.strokeWidth ?? 2,
4003
+ arrowFill: style.arrow?.fill ?? style.stroke ?? "#94a3b8",
4004
+ arrowSize: style.arrow?.size ?? 6
4005
+ };
4006
+ const serializedStyle = JSON.stringify(markerStyle);
4007
+ const hash = createHash(serializedStyle);
4008
+ return `graph-arrow-${hash}`;
4009
+ }
4010
+ function createHash(value) {
4011
+ let hash = 0;
4012
+ for (let index2 = 0; index2 < value.length; index2 += 1) {
4013
+ const charCode = value.charCodeAt(index2);
4014
+ hash = (hash << 5) - hash + charCode;
4015
+ hash |= 0;
4016
+ }
4017
+ return Math.abs(hash).toString(36);
4018
+ }
4019
+
4020
+ // src/core/create-arrow-marker.ts
4021
+ function createArrowMarker(params) {
4022
+ const markerId = getLinkMarkerId(params.style);
4023
+ const existingMarker = params.svg.querySelector(`#${markerId}`);
4024
+ if (existingMarker) {
4025
+ return markerId;
4026
+ }
4027
+ const arrowSize = params.style.arrow?.size ?? 6;
4028
+ const fill = params.style.arrow?.fill ?? params.style.stroke ?? "#94a3b8";
4029
+ const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
4030
+ const marker = document.createElementNS("http://www.w3.org/2000/svg", "marker");
4031
+ marker.setAttribute("id", markerId);
4032
+ marker.setAttribute("viewBox", "0 0 20 20");
4033
+ marker.setAttribute("refX", "0");
4034
+ marker.setAttribute("refY", "10");
4035
+ marker.setAttribute("markerWidth", String(arrowSize * 2));
4036
+ marker.setAttribute("markerHeight", String(arrowSize * 2));
4037
+ marker.setAttribute("orient", "auto");
4038
+ marker.setAttribute("markerUnits", "userSpaceOnUse");
4039
+ const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
4040
+ path.setAttribute("d", "M 0 0 L 20 10 L 0 20 z");
4041
+ path.setAttribute("fill", fill);
4042
+ marker.appendChild(path);
4043
+ defs.appendChild(marker);
4044
+ params.svg.insertBefore(defs, params.svg.firstChild);
4045
+ return markerId;
4046
+ }
4047
+
4048
4048
  // src/renderer/links.ts
4049
4049
  function getShortenedTargetPoint(link, style) {
4050
4050
  const source = link.source;
@@ -4524,6 +4524,60 @@ async function captureAndDownloadGraph(container, options = {}) {
4524
4524
  }
4525
4525
  }
4526
4526
 
4527
+ // src/utils/node-link-selection.utils.ts
4528
+ function deselectNode(nodeElement, root2, nodeSelectHandlers, nodeDeselectHandlers) {
4529
+ nodeElement.style.fill = "";
4530
+ nodeElement.style.stroke = "";
4531
+ nodeElement.style.strokeWidth = "";
4532
+ nodeElement.style.opacity = "";
4533
+ nodeElement.style.removeProperty("r");
4534
+ root2.selectAll(".link-label.label-selection-pinned").classed("label-selection-pinned", false).interrupt().transition().duration(200).style("opacity", 0).style("pointer-events", "none");
4535
+ const nodeData = select_default2(nodeElement).datum();
4536
+ nodeSelectHandlers.clear();
4537
+ nodeDeselectHandlers.forEach((handler) => handler(nodeData, nodeElement));
4538
+ }
4539
+ function deselectLink(linkElement, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers) {
4540
+ linkElement.style.stroke = "";
4541
+ linkElement.style.strokeWidth = "";
4542
+ linkElement.style.opacity = "";
4543
+ const originalMarkerEnd = linkMarkerSnapshots.get(linkElement);
4544
+ if (originalMarkerEnd) {
4545
+ linkElement.setAttribute("marker-end", originalMarkerEnd);
4546
+ } else {
4547
+ linkElement.removeAttribute("marker-end");
4548
+ }
4549
+ const linkData = select_default2(linkElement).datum().link;
4550
+ linkSelectHandlers.clear();
4551
+ linkDeselectHandlers.forEach((handler) => handler(linkData, linkElement));
4552
+ }
4553
+ function selectLink(event, renderableLink, linkElement, selectionConfig, layers, linkSelectHandlers) {
4554
+ event.stopPropagation();
4555
+ const linkStyle = selectionConfig.linkStyle;
4556
+ if (linkStyle) {
4557
+ if (linkStyle.stroke !== void 0) {
4558
+ linkElement.style.stroke = linkStyle.stroke;
4559
+ }
4560
+ if (linkStyle.strokeWidth !== void 0) {
4561
+ linkElement.style.strokeWidth = String(linkStyle.strokeWidth);
4562
+ }
4563
+ if (linkStyle.opacity !== void 0) {
4564
+ linkElement.style.opacity = String(linkStyle.opacity);
4565
+ }
4566
+ if (linkStyle.stroke !== void 0 && renderableLink.style.arrow.enabled) {
4567
+ const selectionMarkerStyle = {
4568
+ stroke: linkStyle.stroke,
4569
+ arrow: { fill: linkStyle.stroke, size: renderableLink.style.arrow.size }
4570
+ };
4571
+ const selectionMarkerId = createArrowMarker({ svg: layers.svg, style: selectionMarkerStyle });
4572
+ select_default2(linkElement).attr("marker-end", `url(#${selectionMarkerId})`);
4573
+ }
4574
+ }
4575
+ linkSelectHandlers.forEach((handler) => handler(renderableLink.link, linkElement));
4576
+ }
4577
+ function createLinkHitArea(root2, linkSelection) {
4578
+ return root2.select('[data-layer="links"]').selectAll("line.link-hit-area").data(linkSelection.data()).join("line").attr("class", "link-hit-area").attr("stroke", "rgba(0,0,0,0)").attr("stroke-width", (item) => item.style.arrow.size * 4).style("pointer-events", "stroke").style("cursor", "pointer").attr("opacity", 0);
4579
+ }
4580
+
4527
4581
  // src/create-graph.ts
4528
4582
  function createGraph(config) {
4529
4583
  let cleanupResize = null;
@@ -4538,7 +4592,9 @@ function createGraph(config) {
4538
4592
  let zoomBehavior = null;
4539
4593
  let simulation = null;
4540
4594
  const nodeSelectHandlers = /* @__PURE__ */ new Set();
4595
+ const nodeDeselectHandlers = /* @__PURE__ */ new Set();
4541
4596
  const linkSelectHandlers = /* @__PURE__ */ new Set();
4597
+ const linkDeselectHandlers = /* @__PURE__ */ new Set();
4542
4598
  function on(event, handler) {
4543
4599
  if (event === "nodeSelect") {
4544
4600
  nodeSelectHandlers.add(handler);
@@ -4546,16 +4602,32 @@ function createGraph(config) {
4546
4602
  nodeSelectHandlers.delete(handler);
4547
4603
  };
4548
4604
  }
4549
- linkSelectHandlers.add(handler);
4605
+ if (event === "nodeDeselect") {
4606
+ nodeDeselectHandlers.add(handler);
4607
+ return () => {
4608
+ nodeDeselectHandlers.delete(handler);
4609
+ };
4610
+ }
4611
+ if (event === "linkSelect") {
4612
+ linkSelectHandlers.add(handler);
4613
+ return () => {
4614
+ linkSelectHandlers.delete(handler);
4615
+ };
4616
+ }
4617
+ linkDeselectHandlers.add(handler);
4550
4618
  return () => {
4551
- linkSelectHandlers.delete(handler);
4619
+ linkDeselectHandlers.delete(handler);
4552
4620
  };
4553
4621
  }
4554
4622
  function off(event, handler) {
4555
4623
  if (event === "nodeSelect") {
4556
4624
  nodeSelectHandlers.delete(handler);
4557
- } else {
4625
+ } else if (event === "nodeDeselect") {
4626
+ nodeDeselectHandlers.delete(handler);
4627
+ } else if (event === "linkSelect") {
4558
4628
  linkSelectHandlers.delete(handler);
4629
+ } else {
4630
+ linkDeselectHandlers.delete(handler);
4559
4631
  }
4560
4632
  }
4561
4633
  function render() {
@@ -4650,44 +4722,22 @@ function createGraph(config) {
4650
4722
  const linkElement = this;
4651
4723
  linkMarkerSnapshots.set(linkElement, linkElement.getAttribute("marker-end"));
4652
4724
  });
4653
- const deselectNode = () => {
4654
- if (!selectedNodeElement) {
4655
- return;
4656
- }
4657
- const nodeElement = selectedNodeElement;
4658
- nodeElement.style.fill = "";
4659
- nodeElement.style.stroke = "";
4660
- nodeElement.style.strokeWidth = "";
4661
- nodeElement.style.opacity = "";
4662
- nodeElement.style.removeProperty("r");
4663
- root2.selectAll(".link-label.label-selection-pinned").classed("label-selection-pinned", false).interrupt().transition().duration(200).style("opacity", 0).style("pointer-events", "none");
4664
- selectedNodeElement = null;
4665
- };
4666
- const deselectLink = () => {
4667
- if (!selectedLinkElement) {
4668
- return;
4669
- }
4670
- const linkElement = selectedLinkElement;
4671
- linkElement.style.stroke = "";
4672
- linkElement.style.strokeWidth = "";
4673
- linkElement.style.opacity = "";
4674
- const originalMarkerEnd = linkMarkerSnapshots.get(linkElement);
4675
- if (originalMarkerEnd) {
4676
- linkElement.setAttribute("marker-end", originalMarkerEnd);
4677
- } else {
4678
- linkElement.removeAttribute("marker-end");
4679
- }
4680
- selectedLinkElement = null;
4681
- };
4682
4725
  nodeSelection.on("click.select", function(event, node) {
4683
4726
  event.stopPropagation();
4684
4727
  const nodeElement = this;
4685
4728
  if (selectedNodeElement === nodeElement) {
4686
- deselectNode();
4729
+ deselectNode(nodeElement, root2, nodeSelectHandlers, nodeDeselectHandlers);
4730
+ selectedNodeElement = null;
4687
4731
  return;
4688
4732
  }
4689
- deselectNode();
4690
- deselectLink();
4733
+ if (selectedNodeElement) {
4734
+ deselectNode(selectedNodeElement, root2, nodeSelectHandlers, nodeDeselectHandlers);
4735
+ selectedNodeElement = null;
4736
+ }
4737
+ if (selectedLinkElement) {
4738
+ deselectLink(selectedLinkElement, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers);
4739
+ selectedLinkElement = null;
4740
+ }
4691
4741
  selectedNodeElement = nodeElement;
4692
4742
  const nodeStyle = selectionConfig.nodeStyle;
4693
4743
  if (nodeStyle) {
@@ -4717,53 +4767,57 @@ function createGraph(config) {
4717
4767
  }).classed("label-selection-pinned", true).interrupt().transition().duration(200).style("opacity", 1).style("pointer-events", "auto");
4718
4768
  nodeSelectHandlers.forEach((handler) => handler(node, nodeElement));
4719
4769
  });
4720
- const selectLink = (event, renderableLink, linkElement) => {
4721
- event.stopPropagation();
4770
+ linkSelection.on("click.select", function(event, renderableLink) {
4771
+ const linkElement = this;
4722
4772
  if (selectedLinkElement === linkElement) {
4723
- deselectLink();
4773
+ deselectLink(linkElement, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers);
4774
+ selectedLinkElement = null;
4724
4775
  return;
4725
4776
  }
4726
- deselectLink();
4727
- deselectNode();
4728
- selectedLinkElement = linkElement;
4729
- const linkStyle = selectionConfig.linkStyle;
4730
- if (linkStyle) {
4731
- if (linkStyle.stroke !== void 0) {
4732
- linkElement.style.stroke = linkStyle.stroke;
4733
- }
4734
- if (linkStyle.strokeWidth !== void 0) {
4735
- linkElement.style.strokeWidth = String(linkStyle.strokeWidth);
4736
- }
4737
- if (linkStyle.opacity !== void 0) {
4738
- linkElement.style.opacity = String(linkStyle.opacity);
4739
- }
4740
- if (linkStyle.stroke !== void 0 && renderableLink.style.arrow.enabled) {
4741
- const selectionMarkerStyle = {
4742
- stroke: linkStyle.stroke,
4743
- arrow: { fill: linkStyle.stroke, size: renderableLink.style.arrow.size }
4744
- };
4745
- const selectionMarkerId = createArrowMarker({ svg: layers.svg, style: selectionMarkerStyle });
4746
- select_default2(linkElement).attr("marker-end", `url(#${selectionMarkerId})`);
4747
- }
4777
+ if (selectedLinkElement) {
4778
+ deselectLink(selectedLinkElement, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers);
4779
+ selectedLinkElement = null;
4748
4780
  }
4749
- linkSelectHandlers.forEach((handler) => handler(renderableLink.link, linkElement));
4750
- };
4751
- linkSelection.on("click.select", function(event, renderableLink) {
4752
- selectLink(event, renderableLink, this);
4781
+ if (selectedNodeElement) {
4782
+ deselectNode(selectedNodeElement, root2, nodeSelectHandlers, nodeDeselectHandlers);
4783
+ selectedNodeElement = null;
4784
+ }
4785
+ selectedLinkElement = linkElement;
4786
+ selectLink(event, renderableLink, linkElement, selectionConfig, layers, linkSelectHandlers);
4753
4787
  });
4754
- const linkHitAreaSelection = root2.select('[data-layer="links"]').selectAll("line.link-hit-area").data(linkSelection.data()).join("line").attr("class", "link-hit-area").attr("stroke", "rgba(0,0,0,0)").attr("stroke-width", (item) => item.style.arrow.size * 4).style("pointer-events", "stroke").style("cursor", "pointer").attr("opacity", 0);
4788
+ const linkHitAreaSelection = createLinkHitArea(root2, linkSelection);
4755
4789
  simulation.on("tick.hitarea", () => {
4756
4790
  linkHitAreaSelection.attr("x1", (item) => item.link.source.x ?? 0).attr("y1", (item) => item.link.source.y ?? 0).attr("x2", (item) => item.link.target.x ?? 0).attr("y2", (item) => item.link.target.y ?? 0);
4757
4791
  });
4758
4792
  linkHitAreaSelection.on("click.select", function(event, renderableLink) {
4759
4793
  const visibleLinkNode = linkSelection.filter((d) => d === renderableLink).node();
4760
4794
  if (visibleLinkNode) {
4761
- selectLink(event, renderableLink, visibleLinkNode);
4795
+ if (selectedLinkElement === visibleLinkNode) {
4796
+ deselectLink(visibleLinkNode, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers);
4797
+ selectedLinkElement = null;
4798
+ return;
4799
+ }
4800
+ if (selectedLinkElement) {
4801
+ deselectLink(selectedLinkElement, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers);
4802
+ selectedLinkElement = null;
4803
+ }
4804
+ if (selectedNodeElement) {
4805
+ deselectNode(selectedNodeElement, root2, nodeSelectHandlers, nodeDeselectHandlers);
4806
+ selectedNodeElement = null;
4807
+ }
4808
+ selectedLinkElement = visibleLinkNode;
4809
+ selectLink(event, renderableLink, visibleLinkNode, selectionConfig, layers, linkSelectHandlers);
4762
4810
  }
4763
4811
  });
4764
4812
  select_default2(layers.svg).on("click.deselect", () => {
4765
- deselectNode();
4766
- deselectLink();
4813
+ if (selectedNodeElement) {
4814
+ deselectNode(selectedNodeElement, root2, nodeSelectHandlers, nodeDeselectHandlers);
4815
+ selectedNodeElement = null;
4816
+ }
4817
+ if (selectedLinkElement) {
4818
+ deselectLink(selectedLinkElement, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers);
4819
+ selectedLinkElement = null;
4820
+ }
4767
4821
  });
4768
4822
  }
4769
4823
  if (config.controls?.enabled) {
package/dist/index.d.cts CHANGED
@@ -139,9 +139,13 @@ interface GraphInstance {
139
139
  destroy(): void;
140
140
  exportGraph(fileName?: string): void;
141
141
  on(event: 'nodeSelect', handler: (node: GraphNode, element: SVGCircleElement) => void): () => void;
142
+ on(event: 'nodeDeselect', handler: (node: GraphNode, element: SVGCircleElement) => void): () => void;
142
143
  on(event: 'linkSelect', handler: (link: GraphLink, element: SVGLineElement) => void): () => void;
144
+ on(event: 'linkDeselect', handler: (link: GraphLink, element: SVGLineElement) => void): () => void;
143
145
  off(event: 'nodeSelect', handler: (node: GraphNode, element: SVGCircleElement) => void): void;
146
+ off(event: 'nodeDeselect', handler: (node: GraphNode, element: SVGCircleElement) => void): void;
144
147
  off(event: 'linkSelect', handler: (link: GraphLink, element: SVGLineElement) => void): void;
148
+ off(event: 'linkDeselect', handler: (link: GraphLink, element: SVGLineElement) => void): void;
145
149
  }
146
150
 
147
151
  declare function createGraph(config: GraphConfig): GraphInstance;
package/dist/index.d.ts CHANGED
@@ -139,9 +139,13 @@ interface GraphInstance {
139
139
  destroy(): void;
140
140
  exportGraph(fileName?: string): void;
141
141
  on(event: 'nodeSelect', handler: (node: GraphNode, element: SVGCircleElement) => void): () => void;
142
+ on(event: 'nodeDeselect', handler: (node: GraphNode, element: SVGCircleElement) => void): () => void;
142
143
  on(event: 'linkSelect', handler: (link: GraphLink, element: SVGLineElement) => void): () => void;
144
+ on(event: 'linkDeselect', handler: (link: GraphLink, element: SVGLineElement) => void): () => void;
143
145
  off(event: 'nodeSelect', handler: (node: GraphNode, element: SVGCircleElement) => void): void;
146
+ off(event: 'nodeDeselect', handler: (node: GraphNode, element: SVGCircleElement) => void): void;
144
147
  off(event: 'linkSelect', handler: (link: GraphLink, element: SVGLineElement) => void): void;
148
+ off(event: 'linkDeselect', handler: (link: GraphLink, element: SVGLineElement) => void): void;
145
149
  }
146
150
 
147
151
  declare function createGraph(config: GraphConfig): GraphInstance;
package/dist/index.js CHANGED
@@ -3731,56 +3731,6 @@ function createGraphSimulation(config) {
3731
3731
  return { simulation };
3732
3732
  }
3733
3733
 
3734
- // src/utils/get-link-marker-id.ts
3735
- function getLinkMarkerId(style) {
3736
- const markerStyle = {
3737
- stroke: style.stroke ?? "#94a3b8",
3738
- strokeWidth: style.strokeWidth ?? 2,
3739
- arrowFill: style.arrow?.fill ?? style.stroke ?? "#94a3b8",
3740
- arrowSize: style.arrow?.size ?? 6
3741
- };
3742
- const serializedStyle = JSON.stringify(markerStyle);
3743
- const hash = createHash(serializedStyle);
3744
- return `graph-arrow-${hash}`;
3745
- }
3746
- function createHash(value) {
3747
- let hash = 0;
3748
- for (let index2 = 0; index2 < value.length; index2 += 1) {
3749
- const charCode = value.charCodeAt(index2);
3750
- hash = (hash << 5) - hash + charCode;
3751
- hash |= 0;
3752
- }
3753
- return Math.abs(hash).toString(36);
3754
- }
3755
-
3756
- // src/core/create-arrow-marker.ts
3757
- function createArrowMarker(params) {
3758
- const markerId = getLinkMarkerId(params.style);
3759
- const existingMarker = params.svg.querySelector(`#${markerId}`);
3760
- if (existingMarker) {
3761
- return markerId;
3762
- }
3763
- const arrowSize = params.style.arrow?.size ?? 6;
3764
- const fill = params.style.arrow?.fill ?? params.style.stroke ?? "#94a3b8";
3765
- const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
3766
- const marker = document.createElementNS("http://www.w3.org/2000/svg", "marker");
3767
- marker.setAttribute("id", markerId);
3768
- marker.setAttribute("viewBox", "0 0 20 20");
3769
- marker.setAttribute("refX", "0");
3770
- marker.setAttribute("refY", "10");
3771
- marker.setAttribute("markerWidth", String(arrowSize * 2));
3772
- marker.setAttribute("markerHeight", String(arrowSize * 2));
3773
- marker.setAttribute("orient", "auto");
3774
- marker.setAttribute("markerUnits", "userSpaceOnUse");
3775
- const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
3776
- path.setAttribute("d", "M 0 0 L 20 10 L 0 20 z");
3777
- path.setAttribute("fill", fill);
3778
- marker.appendChild(path);
3779
- defs.appendChild(marker);
3780
- params.svg.insertBefore(defs, params.svg.firstChild);
3781
- return markerId;
3782
- }
3783
-
3784
3734
  // src/controls/graph-controls.utils.ts
3785
3735
  function resolveControlsPosition(position) {
3786
3736
  return position ?? "bottom-left";
@@ -4009,6 +3959,56 @@ function mergeLinkStyle(base, override) {
4009
3959
  };
4010
3960
  }
4011
3961
 
3962
+ // src/utils/get-link-marker-id.ts
3963
+ function getLinkMarkerId(style) {
3964
+ const markerStyle = {
3965
+ stroke: style.stroke ?? "#94a3b8",
3966
+ strokeWidth: style.strokeWidth ?? 2,
3967
+ arrowFill: style.arrow?.fill ?? style.stroke ?? "#94a3b8",
3968
+ arrowSize: style.arrow?.size ?? 6
3969
+ };
3970
+ const serializedStyle = JSON.stringify(markerStyle);
3971
+ const hash = createHash(serializedStyle);
3972
+ return `graph-arrow-${hash}`;
3973
+ }
3974
+ function createHash(value) {
3975
+ let hash = 0;
3976
+ for (let index2 = 0; index2 < value.length; index2 += 1) {
3977
+ const charCode = value.charCodeAt(index2);
3978
+ hash = (hash << 5) - hash + charCode;
3979
+ hash |= 0;
3980
+ }
3981
+ return Math.abs(hash).toString(36);
3982
+ }
3983
+
3984
+ // src/core/create-arrow-marker.ts
3985
+ function createArrowMarker(params) {
3986
+ const markerId = getLinkMarkerId(params.style);
3987
+ const existingMarker = params.svg.querySelector(`#${markerId}`);
3988
+ if (existingMarker) {
3989
+ return markerId;
3990
+ }
3991
+ const arrowSize = params.style.arrow?.size ?? 6;
3992
+ const fill = params.style.arrow?.fill ?? params.style.stroke ?? "#94a3b8";
3993
+ const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
3994
+ const marker = document.createElementNS("http://www.w3.org/2000/svg", "marker");
3995
+ marker.setAttribute("id", markerId);
3996
+ marker.setAttribute("viewBox", "0 0 20 20");
3997
+ marker.setAttribute("refX", "0");
3998
+ marker.setAttribute("refY", "10");
3999
+ marker.setAttribute("markerWidth", String(arrowSize * 2));
4000
+ marker.setAttribute("markerHeight", String(arrowSize * 2));
4001
+ marker.setAttribute("orient", "auto");
4002
+ marker.setAttribute("markerUnits", "userSpaceOnUse");
4003
+ const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
4004
+ path.setAttribute("d", "M 0 0 L 20 10 L 0 20 z");
4005
+ path.setAttribute("fill", fill);
4006
+ marker.appendChild(path);
4007
+ defs.appendChild(marker);
4008
+ params.svg.insertBefore(defs, params.svg.firstChild);
4009
+ return markerId;
4010
+ }
4011
+
4012
4012
  // src/renderer/links.ts
4013
4013
  function getShortenedTargetPoint(link, style) {
4014
4014
  const source = link.source;
@@ -4488,6 +4488,60 @@ async function captureAndDownloadGraph(container, options = {}) {
4488
4488
  }
4489
4489
  }
4490
4490
 
4491
+ // src/utils/node-link-selection.utils.ts
4492
+ function deselectNode(nodeElement, root2, nodeSelectHandlers, nodeDeselectHandlers) {
4493
+ nodeElement.style.fill = "";
4494
+ nodeElement.style.stroke = "";
4495
+ nodeElement.style.strokeWidth = "";
4496
+ nodeElement.style.opacity = "";
4497
+ nodeElement.style.removeProperty("r");
4498
+ root2.selectAll(".link-label.label-selection-pinned").classed("label-selection-pinned", false).interrupt().transition().duration(200).style("opacity", 0).style("pointer-events", "none");
4499
+ const nodeData = select_default2(nodeElement).datum();
4500
+ nodeSelectHandlers.clear();
4501
+ nodeDeselectHandlers.forEach((handler) => handler(nodeData, nodeElement));
4502
+ }
4503
+ function deselectLink(linkElement, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers) {
4504
+ linkElement.style.stroke = "";
4505
+ linkElement.style.strokeWidth = "";
4506
+ linkElement.style.opacity = "";
4507
+ const originalMarkerEnd = linkMarkerSnapshots.get(linkElement);
4508
+ if (originalMarkerEnd) {
4509
+ linkElement.setAttribute("marker-end", originalMarkerEnd);
4510
+ } else {
4511
+ linkElement.removeAttribute("marker-end");
4512
+ }
4513
+ const linkData = select_default2(linkElement).datum().link;
4514
+ linkSelectHandlers.clear();
4515
+ linkDeselectHandlers.forEach((handler) => handler(linkData, linkElement));
4516
+ }
4517
+ function selectLink(event, renderableLink, linkElement, selectionConfig, layers, linkSelectHandlers) {
4518
+ event.stopPropagation();
4519
+ const linkStyle = selectionConfig.linkStyle;
4520
+ if (linkStyle) {
4521
+ if (linkStyle.stroke !== void 0) {
4522
+ linkElement.style.stroke = linkStyle.stroke;
4523
+ }
4524
+ if (linkStyle.strokeWidth !== void 0) {
4525
+ linkElement.style.strokeWidth = String(linkStyle.strokeWidth);
4526
+ }
4527
+ if (linkStyle.opacity !== void 0) {
4528
+ linkElement.style.opacity = String(linkStyle.opacity);
4529
+ }
4530
+ if (linkStyle.stroke !== void 0 && renderableLink.style.arrow.enabled) {
4531
+ const selectionMarkerStyle = {
4532
+ stroke: linkStyle.stroke,
4533
+ arrow: { fill: linkStyle.stroke, size: renderableLink.style.arrow.size }
4534
+ };
4535
+ const selectionMarkerId = createArrowMarker({ svg: layers.svg, style: selectionMarkerStyle });
4536
+ select_default2(linkElement).attr("marker-end", `url(#${selectionMarkerId})`);
4537
+ }
4538
+ }
4539
+ linkSelectHandlers.forEach((handler) => handler(renderableLink.link, linkElement));
4540
+ }
4541
+ function createLinkHitArea(root2, linkSelection) {
4542
+ return root2.select('[data-layer="links"]').selectAll("line.link-hit-area").data(linkSelection.data()).join("line").attr("class", "link-hit-area").attr("stroke", "rgba(0,0,0,0)").attr("stroke-width", (item) => item.style.arrow.size * 4).style("pointer-events", "stroke").style("cursor", "pointer").attr("opacity", 0);
4543
+ }
4544
+
4491
4545
  // src/create-graph.ts
4492
4546
  function createGraph(config) {
4493
4547
  let cleanupResize = null;
@@ -4502,7 +4556,9 @@ function createGraph(config) {
4502
4556
  let zoomBehavior = null;
4503
4557
  let simulation = null;
4504
4558
  const nodeSelectHandlers = /* @__PURE__ */ new Set();
4559
+ const nodeDeselectHandlers = /* @__PURE__ */ new Set();
4505
4560
  const linkSelectHandlers = /* @__PURE__ */ new Set();
4561
+ const linkDeselectHandlers = /* @__PURE__ */ new Set();
4506
4562
  function on(event, handler) {
4507
4563
  if (event === "nodeSelect") {
4508
4564
  nodeSelectHandlers.add(handler);
@@ -4510,16 +4566,32 @@ function createGraph(config) {
4510
4566
  nodeSelectHandlers.delete(handler);
4511
4567
  };
4512
4568
  }
4513
- linkSelectHandlers.add(handler);
4569
+ if (event === "nodeDeselect") {
4570
+ nodeDeselectHandlers.add(handler);
4571
+ return () => {
4572
+ nodeDeselectHandlers.delete(handler);
4573
+ };
4574
+ }
4575
+ if (event === "linkSelect") {
4576
+ linkSelectHandlers.add(handler);
4577
+ return () => {
4578
+ linkSelectHandlers.delete(handler);
4579
+ };
4580
+ }
4581
+ linkDeselectHandlers.add(handler);
4514
4582
  return () => {
4515
- linkSelectHandlers.delete(handler);
4583
+ linkDeselectHandlers.delete(handler);
4516
4584
  };
4517
4585
  }
4518
4586
  function off(event, handler) {
4519
4587
  if (event === "nodeSelect") {
4520
4588
  nodeSelectHandlers.delete(handler);
4521
- } else {
4589
+ } else if (event === "nodeDeselect") {
4590
+ nodeDeselectHandlers.delete(handler);
4591
+ } else if (event === "linkSelect") {
4522
4592
  linkSelectHandlers.delete(handler);
4593
+ } else {
4594
+ linkDeselectHandlers.delete(handler);
4523
4595
  }
4524
4596
  }
4525
4597
  function render() {
@@ -4614,44 +4686,22 @@ function createGraph(config) {
4614
4686
  const linkElement = this;
4615
4687
  linkMarkerSnapshots.set(linkElement, linkElement.getAttribute("marker-end"));
4616
4688
  });
4617
- const deselectNode = () => {
4618
- if (!selectedNodeElement) {
4619
- return;
4620
- }
4621
- const nodeElement = selectedNodeElement;
4622
- nodeElement.style.fill = "";
4623
- nodeElement.style.stroke = "";
4624
- nodeElement.style.strokeWidth = "";
4625
- nodeElement.style.opacity = "";
4626
- nodeElement.style.removeProperty("r");
4627
- root2.selectAll(".link-label.label-selection-pinned").classed("label-selection-pinned", false).interrupt().transition().duration(200).style("opacity", 0).style("pointer-events", "none");
4628
- selectedNodeElement = null;
4629
- };
4630
- const deselectLink = () => {
4631
- if (!selectedLinkElement) {
4632
- return;
4633
- }
4634
- const linkElement = selectedLinkElement;
4635
- linkElement.style.stroke = "";
4636
- linkElement.style.strokeWidth = "";
4637
- linkElement.style.opacity = "";
4638
- const originalMarkerEnd = linkMarkerSnapshots.get(linkElement);
4639
- if (originalMarkerEnd) {
4640
- linkElement.setAttribute("marker-end", originalMarkerEnd);
4641
- } else {
4642
- linkElement.removeAttribute("marker-end");
4643
- }
4644
- selectedLinkElement = null;
4645
- };
4646
4689
  nodeSelection.on("click.select", function(event, node) {
4647
4690
  event.stopPropagation();
4648
4691
  const nodeElement = this;
4649
4692
  if (selectedNodeElement === nodeElement) {
4650
- deselectNode();
4693
+ deselectNode(nodeElement, root2, nodeSelectHandlers, nodeDeselectHandlers);
4694
+ selectedNodeElement = null;
4651
4695
  return;
4652
4696
  }
4653
- deselectNode();
4654
- deselectLink();
4697
+ if (selectedNodeElement) {
4698
+ deselectNode(selectedNodeElement, root2, nodeSelectHandlers, nodeDeselectHandlers);
4699
+ selectedNodeElement = null;
4700
+ }
4701
+ if (selectedLinkElement) {
4702
+ deselectLink(selectedLinkElement, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers);
4703
+ selectedLinkElement = null;
4704
+ }
4655
4705
  selectedNodeElement = nodeElement;
4656
4706
  const nodeStyle = selectionConfig.nodeStyle;
4657
4707
  if (nodeStyle) {
@@ -4681,53 +4731,57 @@ function createGraph(config) {
4681
4731
  }).classed("label-selection-pinned", true).interrupt().transition().duration(200).style("opacity", 1).style("pointer-events", "auto");
4682
4732
  nodeSelectHandlers.forEach((handler) => handler(node, nodeElement));
4683
4733
  });
4684
- const selectLink = (event, renderableLink, linkElement) => {
4685
- event.stopPropagation();
4734
+ linkSelection.on("click.select", function(event, renderableLink) {
4735
+ const linkElement = this;
4686
4736
  if (selectedLinkElement === linkElement) {
4687
- deselectLink();
4737
+ deselectLink(linkElement, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers);
4738
+ selectedLinkElement = null;
4688
4739
  return;
4689
4740
  }
4690
- deselectLink();
4691
- deselectNode();
4692
- selectedLinkElement = linkElement;
4693
- const linkStyle = selectionConfig.linkStyle;
4694
- if (linkStyle) {
4695
- if (linkStyle.stroke !== void 0) {
4696
- linkElement.style.stroke = linkStyle.stroke;
4697
- }
4698
- if (linkStyle.strokeWidth !== void 0) {
4699
- linkElement.style.strokeWidth = String(linkStyle.strokeWidth);
4700
- }
4701
- if (linkStyle.opacity !== void 0) {
4702
- linkElement.style.opacity = String(linkStyle.opacity);
4703
- }
4704
- if (linkStyle.stroke !== void 0 && renderableLink.style.arrow.enabled) {
4705
- const selectionMarkerStyle = {
4706
- stroke: linkStyle.stroke,
4707
- arrow: { fill: linkStyle.stroke, size: renderableLink.style.arrow.size }
4708
- };
4709
- const selectionMarkerId = createArrowMarker({ svg: layers.svg, style: selectionMarkerStyle });
4710
- select_default2(linkElement).attr("marker-end", `url(#${selectionMarkerId})`);
4711
- }
4741
+ if (selectedLinkElement) {
4742
+ deselectLink(selectedLinkElement, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers);
4743
+ selectedLinkElement = null;
4712
4744
  }
4713
- linkSelectHandlers.forEach((handler) => handler(renderableLink.link, linkElement));
4714
- };
4715
- linkSelection.on("click.select", function(event, renderableLink) {
4716
- selectLink(event, renderableLink, this);
4745
+ if (selectedNodeElement) {
4746
+ deselectNode(selectedNodeElement, root2, nodeSelectHandlers, nodeDeselectHandlers);
4747
+ selectedNodeElement = null;
4748
+ }
4749
+ selectedLinkElement = linkElement;
4750
+ selectLink(event, renderableLink, linkElement, selectionConfig, layers, linkSelectHandlers);
4717
4751
  });
4718
- const linkHitAreaSelection = root2.select('[data-layer="links"]').selectAll("line.link-hit-area").data(linkSelection.data()).join("line").attr("class", "link-hit-area").attr("stroke", "rgba(0,0,0,0)").attr("stroke-width", (item) => item.style.arrow.size * 4).style("pointer-events", "stroke").style("cursor", "pointer").attr("opacity", 0);
4752
+ const linkHitAreaSelection = createLinkHitArea(root2, linkSelection);
4719
4753
  simulation.on("tick.hitarea", () => {
4720
4754
  linkHitAreaSelection.attr("x1", (item) => item.link.source.x ?? 0).attr("y1", (item) => item.link.source.y ?? 0).attr("x2", (item) => item.link.target.x ?? 0).attr("y2", (item) => item.link.target.y ?? 0);
4721
4755
  });
4722
4756
  linkHitAreaSelection.on("click.select", function(event, renderableLink) {
4723
4757
  const visibleLinkNode = linkSelection.filter((d) => d === renderableLink).node();
4724
4758
  if (visibleLinkNode) {
4725
- selectLink(event, renderableLink, visibleLinkNode);
4759
+ if (selectedLinkElement === visibleLinkNode) {
4760
+ deselectLink(visibleLinkNode, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers);
4761
+ selectedLinkElement = null;
4762
+ return;
4763
+ }
4764
+ if (selectedLinkElement) {
4765
+ deselectLink(selectedLinkElement, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers);
4766
+ selectedLinkElement = null;
4767
+ }
4768
+ if (selectedNodeElement) {
4769
+ deselectNode(selectedNodeElement, root2, nodeSelectHandlers, nodeDeselectHandlers);
4770
+ selectedNodeElement = null;
4771
+ }
4772
+ selectedLinkElement = visibleLinkNode;
4773
+ selectLink(event, renderableLink, visibleLinkNode, selectionConfig, layers, linkSelectHandlers);
4726
4774
  }
4727
4775
  });
4728
4776
  select_default2(layers.svg).on("click.deselect", () => {
4729
- deselectNode();
4730
- deselectLink();
4777
+ if (selectedNodeElement) {
4778
+ deselectNode(selectedNodeElement, root2, nodeSelectHandlers, nodeDeselectHandlers);
4779
+ selectedNodeElement = null;
4780
+ }
4781
+ if (selectedLinkElement) {
4782
+ deselectLink(selectedLinkElement, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers);
4783
+ selectedLinkElement = null;
4784
+ }
4731
4785
  });
4732
4786
  }
4733
4787
  if (config.controls?.enabled) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polly-graph",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Reusable D3-based graph visualization SDK with configurable nodes, links, labels, interactions, and layout behaviors.",
5
5
  "license": "MIT",
6
6
  "author": "Badal",