polly-graph 0.1.13 → 0.1.14

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.
Files changed (3) hide show
  1. package/dist/index.cjs +287 -21
  2. package/dist/index.js +287 -21
  3. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -5117,34 +5117,294 @@ function createDragBehavior(simulation, onDragStart, canvasBounds) {
5117
5117
  });
5118
5118
  }
5119
5119
 
5120
+ // src/utils/node-style-manager.ts
5121
+ var NodeStyleManager = class {
5122
+ styleStates = /* @__PURE__ */ new Map();
5123
+ /**
5124
+ * Initialize a node's style state by capturing its original styles
5125
+ */
5126
+ initializeNode(element, node) {
5127
+ if (this.styleStates.has(element)) {
5128
+ return;
5129
+ }
5130
+ const domBackup = {
5131
+ stroke: element.getAttribute("stroke"),
5132
+ strokeWidth: element.getAttribute("stroke-width"),
5133
+ opacity: element.getAttribute("opacity"),
5134
+ fill: element.getAttribute("fill")
5135
+ };
5136
+ const originalStyle = {
5137
+ stroke: node.style?.stroke || void 0,
5138
+ strokeWidth: node.style?.strokeWidth || void 0,
5139
+ opacity: node.style?.opacity || void 0,
5140
+ fill: node.style?.fill || void 0
5141
+ };
5142
+ this.styleStates.set(element, {
5143
+ original: originalStyle,
5144
+ current: { ...originalStyle },
5145
+ domBackup
5146
+ });
5147
+ }
5148
+ /**
5149
+ * Apply temporary styles (e.g., hover effects) that will be reset later
5150
+ */
5151
+ applyTemporaryStyles(element, styles) {
5152
+ this.applyStylesToDOM(element, styles);
5153
+ }
5154
+ /**
5155
+ * Apply permanent styles that become the new base styles
5156
+ */
5157
+ applyPermanentStyles(element, styles) {
5158
+ const state = this.styleStates.get(element);
5159
+ if (!state) {
5160
+ console.warn("[NodeStyleManager] Node not initialized, cannot apply permanent styles");
5161
+ return;
5162
+ }
5163
+ this.applyStylesToDOM(element, styles);
5164
+ Object.assign(state.current, styles);
5165
+ }
5166
+ /**
5167
+ * Reset node to its current base styles (removes temporary styles)
5168
+ */
5169
+ resetToBase(element) {
5170
+ const state = this.styleStates.get(element);
5171
+ if (!state) {
5172
+ this.clearAllStyles(element);
5173
+ return;
5174
+ }
5175
+ this.clearAllStyles(element);
5176
+ this.applyStylesToDOM(element, state.current);
5177
+ }
5178
+ /**
5179
+ * Reset node to its original styles (as captured during initialization)
5180
+ */
5181
+ resetToOriginal(element) {
5182
+ const state = this.styleStates.get(element);
5183
+ if (!state) {
5184
+ this.clearAllStyles(element);
5185
+ return;
5186
+ }
5187
+ this.clearAllStyles(element);
5188
+ this.restoreOriginalDOM(element, state.domBackup);
5189
+ state.current = { ...state.original };
5190
+ }
5191
+ /**
5192
+ * Check if node is in a specific state (selected, hovered, etc.)
5193
+ */
5194
+ hasState(element, stateName) {
5195
+ return element.dataset[stateName] === "true";
5196
+ }
5197
+ /**
5198
+ * Set state marker on node
5199
+ */
5200
+ setState(element, stateName, value) {
5201
+ if (value) {
5202
+ element.dataset[stateName] = "true";
5203
+ } else {
5204
+ delete element.dataset[stateName];
5205
+ }
5206
+ }
5207
+ /**
5208
+ * Get the original styles for a node
5209
+ */
5210
+ getOriginalStyles(element) {
5211
+ const state = this.styleStates.get(element);
5212
+ return state ? { ...state.original } : null;
5213
+ }
5214
+ /**
5215
+ * Get the current base styles for a node
5216
+ */
5217
+ getCurrentStyles(element) {
5218
+ const state = this.styleStates.get(element);
5219
+ return state ? { ...state.current } : null;
5220
+ }
5221
+ /**
5222
+ * Remove a node from management (cleanup)
5223
+ */
5224
+ removeNode(element) {
5225
+ this.styleStates.delete(element);
5226
+ }
5227
+ /**
5228
+ * Clear all managed nodes (for cleanup)
5229
+ */
5230
+ clear() {
5231
+ this.styleStates.clear();
5232
+ }
5233
+ /**
5234
+ * Private: Apply styles to DOM element
5235
+ */
5236
+ applyStylesToDOM(element, styles) {
5237
+ if (styles.stroke !== void 0) {
5238
+ if (styles.stroke === null || styles.stroke === "") {
5239
+ element.removeAttribute("stroke");
5240
+ element.style.stroke = "";
5241
+ } else {
5242
+ element.style.stroke = styles.stroke;
5243
+ }
5244
+ }
5245
+ if (styles.strokeWidth !== void 0) {
5246
+ if (styles.strokeWidth === null || styles.strokeWidth === 0) {
5247
+ element.removeAttribute("stroke-width");
5248
+ element.style.strokeWidth = "";
5249
+ } else {
5250
+ element.style.strokeWidth = String(styles.strokeWidth);
5251
+ }
5252
+ }
5253
+ if (styles.opacity !== void 0) {
5254
+ if (styles.opacity === null || styles.opacity === 1) {
5255
+ element.removeAttribute("opacity");
5256
+ element.style.opacity = "";
5257
+ } else {
5258
+ element.style.opacity = String(styles.opacity);
5259
+ }
5260
+ }
5261
+ if (styles.fill !== void 0) {
5262
+ if (styles.fill === null || styles.fill === "") {
5263
+ element.removeAttribute("fill");
5264
+ element.style.fill = "";
5265
+ } else {
5266
+ element.style.fill = styles.fill;
5267
+ }
5268
+ }
5269
+ if (styles.radius !== void 0) {
5270
+ if (styles.radius === null || styles.radius === 0) {
5271
+ element.removeAttribute("r");
5272
+ element.style.removeProperty("r");
5273
+ } else {
5274
+ element.setAttribute("r", String(styles.radius));
5275
+ }
5276
+ }
5277
+ }
5278
+ /**
5279
+ * Private: Clear all inline styles and remove hover-related attributes
5280
+ */
5281
+ clearAllStyles(element) {
5282
+ element.style.stroke = "";
5283
+ element.style.strokeWidth = "";
5284
+ element.style.opacity = "";
5285
+ element.style.fill = "";
5286
+ element.style.removeProperty("r");
5287
+ this.removeIfHoverAttribute(element, "stroke");
5288
+ this.removeIfHoverAttribute(element, "stroke-width");
5289
+ this.removeIfHoverAttribute(element, "opacity");
5290
+ }
5291
+ /**
5292
+ * Private: Restore original DOM attributes
5293
+ */
5294
+ restoreOriginalDOM(element, domBackup) {
5295
+ if (domBackup.stroke !== void 0) {
5296
+ if (domBackup.stroke === null) {
5297
+ element.removeAttribute("stroke");
5298
+ } else {
5299
+ element.setAttribute("stroke", domBackup.stroke);
5300
+ }
5301
+ }
5302
+ if (domBackup.strokeWidth !== void 0) {
5303
+ if (domBackup.strokeWidth === null) {
5304
+ element.removeAttribute("stroke-width");
5305
+ } else {
5306
+ element.setAttribute("stroke-width", domBackup.strokeWidth);
5307
+ }
5308
+ }
5309
+ if (domBackup.opacity !== void 0) {
5310
+ if (domBackup.opacity === null) {
5311
+ element.removeAttribute("opacity");
5312
+ } else {
5313
+ element.setAttribute("opacity", domBackup.opacity);
5314
+ }
5315
+ }
5316
+ if (domBackup.fill !== void 0) {
5317
+ if (domBackup.fill === null) {
5318
+ element.removeAttribute("fill");
5319
+ } else {
5320
+ element.setAttribute("fill", domBackup.fill);
5321
+ }
5322
+ }
5323
+ }
5324
+ /**
5325
+ * Private: Remove attribute only if it looks like it was set by hover/interaction
5326
+ */
5327
+ removeIfHoverAttribute(element, attr) {
5328
+ const value = element.getAttribute(attr);
5329
+ if (!value) return;
5330
+ const hoverPatterns = {
5331
+ "stroke": ["#6366f1", "#8b5cf6", "#3b82f6", "#ffffff", "#fff", "white"],
5332
+ // Common hover stroke colors
5333
+ "stroke-width": ["2", "2.5", "3", "4"],
5334
+ // Common hover stroke widths
5335
+ "opacity": ["0.8", "0.9", "0.7"]
5336
+ // Common hover opacity values
5337
+ };
5338
+ const patterns = hoverPatterns[attr];
5339
+ if (patterns && patterns.includes(value)) {
5340
+ element.removeAttribute(attr);
5341
+ }
5342
+ }
5343
+ };
5344
+ var nodeStyleManager = new NodeStyleManager();
5345
+ function applyHoverStyles(element, node, hoverStyle) {
5346
+ if (nodeStyleManager.hasState(element, "selected")) {
5347
+ return;
5348
+ }
5349
+ nodeStyleManager.initializeNode(element, node);
5350
+ nodeStyleManager.applyTemporaryStyles(element, hoverStyle);
5351
+ nodeStyleManager.setState(element, "hovered", true);
5352
+ }
5353
+ function removeHoverStyles(element, node) {
5354
+ if (nodeStyleManager.hasState(element, "selected")) {
5355
+ return;
5356
+ }
5357
+ nodeStyleManager.initializeNode(element, node);
5358
+ nodeStyleManager.resetToBase(element);
5359
+ nodeStyleManager.setState(element, "hovered", false);
5360
+ }
5361
+
5120
5362
  // src/interactions/create-node-hover.ts
5121
- function createNodeHover(nodeSelection, hoverStyle) {
5363
+ var currentHoveredNode = null;
5364
+ var hoverTimerManager = new TimerManager();
5365
+ function createNodeHover(nodeSelection, hoverStyle, options) {
5122
5366
  const firstNode = nodeSelection.node();
5123
5367
  if (!firstNode) return;
5368
+ const {
5369
+ enableDebouncing = false,
5370
+ enterDelay = 16,
5371
+ // ~1 frame at 60fps
5372
+ leaveDelay = 50
5373
+ // Longer delay for smoother transitions
5374
+ } = options || {};
5124
5375
  if (hoverStyle) {
5125
- nodeSelection.on("mouseenter.hover", function(_event, _node) {
5376
+ nodeSelection.on("mouseenter.hover", function(_event, node) {
5126
5377
  const circle = this;
5127
- if (circle.dataset.selected === "true") {
5128
- return;
5129
- }
5130
- if (hoverStyle.stroke !== void 0) {
5131
- circle.style.stroke = hoverStyle.stroke;
5132
- }
5133
- if (hoverStyle.strokeWidth !== void 0) {
5134
- circle.style.strokeWidth = String(hoverStyle.strokeWidth);
5135
- }
5136
- if (hoverStyle.opacity !== void 0) {
5137
- circle.style.opacity = String(hoverStyle.opacity);
5378
+ const applyHover = () => {
5379
+ if (currentHoveredNode && currentHoveredNode.element !== circle) {
5380
+ removeHoverStyles(currentHoveredNode.element, currentHoveredNode.node);
5381
+ clearAllHoverLayers();
5382
+ }
5383
+ currentHoveredNode = { element: circle, node };
5384
+ applyHoverStyles(circle, node, hoverStyle);
5385
+ };
5386
+ if (enableDebouncing) {
5387
+ hoverTimerManager.clearTimer("hover-enter");
5388
+ hoverTimerManager.clearTimer("hover-leave");
5389
+ hoverTimerManager.debounce("hover-enter", applyHover, enterDelay);
5390
+ } else {
5391
+ applyHover();
5138
5392
  }
5139
- }).on("mouseleave.hover", function(_event, _node) {
5393
+ }).on("mouseleave.hover", function(_event, node) {
5140
5394
  const circle = this;
5141
- clearAllHoverLayers();
5142
- if (circle.dataset.selected === "true") {
5143
- return;
5395
+ const removeHover = () => {
5396
+ if (currentHoveredNode?.element === circle) {
5397
+ currentHoveredNode = null;
5398
+ removeHoverStyles(circle, node);
5399
+ clearAllHoverLayers();
5400
+ }
5401
+ };
5402
+ if (enableDebouncing) {
5403
+ hoverTimerManager.clearTimer("hover-enter");
5404
+ hoverTimerManager.debounce("hover-leave", removeHover, leaveDelay);
5405
+ } else {
5406
+ removeHover();
5144
5407
  }
5145
- circle.style.stroke = "";
5146
- circle.style.strokeWidth = "";
5147
- circle.style.opacity = "";
5148
5408
  });
5149
5409
  }
5150
5410
  const svgElement = firstNode.ownerSVGElement;
@@ -5198,6 +5458,9 @@ function createNodeHover(nodeSelection, hoverStyle) {
5198
5458
  if (hoveredNodeElement.dataset.selected === "true") {
5199
5459
  return;
5200
5460
  }
5461
+ if (!currentHoveredNode || currentHoveredNode.element !== hoveredNodeElement) {
5462
+ return;
5463
+ }
5201
5464
  clearAllHoverLayers();
5202
5465
  const hoverNodesLayer = root2.select('[data-layer="hover-nodes"]').node();
5203
5466
  if (hoverNodesLayer) {
@@ -5244,7 +5507,10 @@ function createNodeHover(nodeSelection, hoverStyle) {
5244
5507
  }
5245
5508
  });
5246
5509
  }).on("mouseleave.links", function(_event, _hoveredNode) {
5247
- clearAllHoverLayers();
5510
+ const hoveredNodeElement = this;
5511
+ if (currentHoveredNode?.element === hoveredNodeElement) {
5512
+ clearAllHoverLayers();
5513
+ }
5248
5514
  });
5249
5515
  }
5250
5516
 
package/dist/index.js CHANGED
@@ -5085,34 +5085,294 @@ function createDragBehavior(simulation, onDragStart, canvasBounds) {
5085
5085
  });
5086
5086
  }
5087
5087
 
5088
+ // src/utils/node-style-manager.ts
5089
+ var NodeStyleManager = class {
5090
+ styleStates = /* @__PURE__ */ new Map();
5091
+ /**
5092
+ * Initialize a node's style state by capturing its original styles
5093
+ */
5094
+ initializeNode(element, node) {
5095
+ if (this.styleStates.has(element)) {
5096
+ return;
5097
+ }
5098
+ const domBackup = {
5099
+ stroke: element.getAttribute("stroke"),
5100
+ strokeWidth: element.getAttribute("stroke-width"),
5101
+ opacity: element.getAttribute("opacity"),
5102
+ fill: element.getAttribute("fill")
5103
+ };
5104
+ const originalStyle = {
5105
+ stroke: node.style?.stroke || void 0,
5106
+ strokeWidth: node.style?.strokeWidth || void 0,
5107
+ opacity: node.style?.opacity || void 0,
5108
+ fill: node.style?.fill || void 0
5109
+ };
5110
+ this.styleStates.set(element, {
5111
+ original: originalStyle,
5112
+ current: { ...originalStyle },
5113
+ domBackup
5114
+ });
5115
+ }
5116
+ /**
5117
+ * Apply temporary styles (e.g., hover effects) that will be reset later
5118
+ */
5119
+ applyTemporaryStyles(element, styles) {
5120
+ this.applyStylesToDOM(element, styles);
5121
+ }
5122
+ /**
5123
+ * Apply permanent styles that become the new base styles
5124
+ */
5125
+ applyPermanentStyles(element, styles) {
5126
+ const state = this.styleStates.get(element);
5127
+ if (!state) {
5128
+ console.warn("[NodeStyleManager] Node not initialized, cannot apply permanent styles");
5129
+ return;
5130
+ }
5131
+ this.applyStylesToDOM(element, styles);
5132
+ Object.assign(state.current, styles);
5133
+ }
5134
+ /**
5135
+ * Reset node to its current base styles (removes temporary styles)
5136
+ */
5137
+ resetToBase(element) {
5138
+ const state = this.styleStates.get(element);
5139
+ if (!state) {
5140
+ this.clearAllStyles(element);
5141
+ return;
5142
+ }
5143
+ this.clearAllStyles(element);
5144
+ this.applyStylesToDOM(element, state.current);
5145
+ }
5146
+ /**
5147
+ * Reset node to its original styles (as captured during initialization)
5148
+ */
5149
+ resetToOriginal(element) {
5150
+ const state = this.styleStates.get(element);
5151
+ if (!state) {
5152
+ this.clearAllStyles(element);
5153
+ return;
5154
+ }
5155
+ this.clearAllStyles(element);
5156
+ this.restoreOriginalDOM(element, state.domBackup);
5157
+ state.current = { ...state.original };
5158
+ }
5159
+ /**
5160
+ * Check if node is in a specific state (selected, hovered, etc.)
5161
+ */
5162
+ hasState(element, stateName) {
5163
+ return element.dataset[stateName] === "true";
5164
+ }
5165
+ /**
5166
+ * Set state marker on node
5167
+ */
5168
+ setState(element, stateName, value) {
5169
+ if (value) {
5170
+ element.dataset[stateName] = "true";
5171
+ } else {
5172
+ delete element.dataset[stateName];
5173
+ }
5174
+ }
5175
+ /**
5176
+ * Get the original styles for a node
5177
+ */
5178
+ getOriginalStyles(element) {
5179
+ const state = this.styleStates.get(element);
5180
+ return state ? { ...state.original } : null;
5181
+ }
5182
+ /**
5183
+ * Get the current base styles for a node
5184
+ */
5185
+ getCurrentStyles(element) {
5186
+ const state = this.styleStates.get(element);
5187
+ return state ? { ...state.current } : null;
5188
+ }
5189
+ /**
5190
+ * Remove a node from management (cleanup)
5191
+ */
5192
+ removeNode(element) {
5193
+ this.styleStates.delete(element);
5194
+ }
5195
+ /**
5196
+ * Clear all managed nodes (for cleanup)
5197
+ */
5198
+ clear() {
5199
+ this.styleStates.clear();
5200
+ }
5201
+ /**
5202
+ * Private: Apply styles to DOM element
5203
+ */
5204
+ applyStylesToDOM(element, styles) {
5205
+ if (styles.stroke !== void 0) {
5206
+ if (styles.stroke === null || styles.stroke === "") {
5207
+ element.removeAttribute("stroke");
5208
+ element.style.stroke = "";
5209
+ } else {
5210
+ element.style.stroke = styles.stroke;
5211
+ }
5212
+ }
5213
+ if (styles.strokeWidth !== void 0) {
5214
+ if (styles.strokeWidth === null || styles.strokeWidth === 0) {
5215
+ element.removeAttribute("stroke-width");
5216
+ element.style.strokeWidth = "";
5217
+ } else {
5218
+ element.style.strokeWidth = String(styles.strokeWidth);
5219
+ }
5220
+ }
5221
+ if (styles.opacity !== void 0) {
5222
+ if (styles.opacity === null || styles.opacity === 1) {
5223
+ element.removeAttribute("opacity");
5224
+ element.style.opacity = "";
5225
+ } else {
5226
+ element.style.opacity = String(styles.opacity);
5227
+ }
5228
+ }
5229
+ if (styles.fill !== void 0) {
5230
+ if (styles.fill === null || styles.fill === "") {
5231
+ element.removeAttribute("fill");
5232
+ element.style.fill = "";
5233
+ } else {
5234
+ element.style.fill = styles.fill;
5235
+ }
5236
+ }
5237
+ if (styles.radius !== void 0) {
5238
+ if (styles.radius === null || styles.radius === 0) {
5239
+ element.removeAttribute("r");
5240
+ element.style.removeProperty("r");
5241
+ } else {
5242
+ element.setAttribute("r", String(styles.radius));
5243
+ }
5244
+ }
5245
+ }
5246
+ /**
5247
+ * Private: Clear all inline styles and remove hover-related attributes
5248
+ */
5249
+ clearAllStyles(element) {
5250
+ element.style.stroke = "";
5251
+ element.style.strokeWidth = "";
5252
+ element.style.opacity = "";
5253
+ element.style.fill = "";
5254
+ element.style.removeProperty("r");
5255
+ this.removeIfHoverAttribute(element, "stroke");
5256
+ this.removeIfHoverAttribute(element, "stroke-width");
5257
+ this.removeIfHoverAttribute(element, "opacity");
5258
+ }
5259
+ /**
5260
+ * Private: Restore original DOM attributes
5261
+ */
5262
+ restoreOriginalDOM(element, domBackup) {
5263
+ if (domBackup.stroke !== void 0) {
5264
+ if (domBackup.stroke === null) {
5265
+ element.removeAttribute("stroke");
5266
+ } else {
5267
+ element.setAttribute("stroke", domBackup.stroke);
5268
+ }
5269
+ }
5270
+ if (domBackup.strokeWidth !== void 0) {
5271
+ if (domBackup.strokeWidth === null) {
5272
+ element.removeAttribute("stroke-width");
5273
+ } else {
5274
+ element.setAttribute("stroke-width", domBackup.strokeWidth);
5275
+ }
5276
+ }
5277
+ if (domBackup.opacity !== void 0) {
5278
+ if (domBackup.opacity === null) {
5279
+ element.removeAttribute("opacity");
5280
+ } else {
5281
+ element.setAttribute("opacity", domBackup.opacity);
5282
+ }
5283
+ }
5284
+ if (domBackup.fill !== void 0) {
5285
+ if (domBackup.fill === null) {
5286
+ element.removeAttribute("fill");
5287
+ } else {
5288
+ element.setAttribute("fill", domBackup.fill);
5289
+ }
5290
+ }
5291
+ }
5292
+ /**
5293
+ * Private: Remove attribute only if it looks like it was set by hover/interaction
5294
+ */
5295
+ removeIfHoverAttribute(element, attr) {
5296
+ const value = element.getAttribute(attr);
5297
+ if (!value) return;
5298
+ const hoverPatterns = {
5299
+ "stroke": ["#6366f1", "#8b5cf6", "#3b82f6", "#ffffff", "#fff", "white"],
5300
+ // Common hover stroke colors
5301
+ "stroke-width": ["2", "2.5", "3", "4"],
5302
+ // Common hover stroke widths
5303
+ "opacity": ["0.8", "0.9", "0.7"]
5304
+ // Common hover opacity values
5305
+ };
5306
+ const patterns = hoverPatterns[attr];
5307
+ if (patterns && patterns.includes(value)) {
5308
+ element.removeAttribute(attr);
5309
+ }
5310
+ }
5311
+ };
5312
+ var nodeStyleManager = new NodeStyleManager();
5313
+ function applyHoverStyles(element, node, hoverStyle) {
5314
+ if (nodeStyleManager.hasState(element, "selected")) {
5315
+ return;
5316
+ }
5317
+ nodeStyleManager.initializeNode(element, node);
5318
+ nodeStyleManager.applyTemporaryStyles(element, hoverStyle);
5319
+ nodeStyleManager.setState(element, "hovered", true);
5320
+ }
5321
+ function removeHoverStyles(element, node) {
5322
+ if (nodeStyleManager.hasState(element, "selected")) {
5323
+ return;
5324
+ }
5325
+ nodeStyleManager.initializeNode(element, node);
5326
+ nodeStyleManager.resetToBase(element);
5327
+ nodeStyleManager.setState(element, "hovered", false);
5328
+ }
5329
+
5088
5330
  // src/interactions/create-node-hover.ts
5089
- function createNodeHover(nodeSelection, hoverStyle) {
5331
+ var currentHoveredNode = null;
5332
+ var hoverTimerManager = new TimerManager();
5333
+ function createNodeHover(nodeSelection, hoverStyle, options) {
5090
5334
  const firstNode = nodeSelection.node();
5091
5335
  if (!firstNode) return;
5336
+ const {
5337
+ enableDebouncing = false,
5338
+ enterDelay = 16,
5339
+ // ~1 frame at 60fps
5340
+ leaveDelay = 50
5341
+ // Longer delay for smoother transitions
5342
+ } = options || {};
5092
5343
  if (hoverStyle) {
5093
- nodeSelection.on("mouseenter.hover", function(_event, _node) {
5344
+ nodeSelection.on("mouseenter.hover", function(_event, node) {
5094
5345
  const circle = this;
5095
- if (circle.dataset.selected === "true") {
5096
- return;
5097
- }
5098
- if (hoverStyle.stroke !== void 0) {
5099
- circle.style.stroke = hoverStyle.stroke;
5100
- }
5101
- if (hoverStyle.strokeWidth !== void 0) {
5102
- circle.style.strokeWidth = String(hoverStyle.strokeWidth);
5103
- }
5104
- if (hoverStyle.opacity !== void 0) {
5105
- circle.style.opacity = String(hoverStyle.opacity);
5346
+ const applyHover = () => {
5347
+ if (currentHoveredNode && currentHoveredNode.element !== circle) {
5348
+ removeHoverStyles(currentHoveredNode.element, currentHoveredNode.node);
5349
+ clearAllHoverLayers();
5350
+ }
5351
+ currentHoveredNode = { element: circle, node };
5352
+ applyHoverStyles(circle, node, hoverStyle);
5353
+ };
5354
+ if (enableDebouncing) {
5355
+ hoverTimerManager.clearTimer("hover-enter");
5356
+ hoverTimerManager.clearTimer("hover-leave");
5357
+ hoverTimerManager.debounce("hover-enter", applyHover, enterDelay);
5358
+ } else {
5359
+ applyHover();
5106
5360
  }
5107
- }).on("mouseleave.hover", function(_event, _node) {
5361
+ }).on("mouseleave.hover", function(_event, node) {
5108
5362
  const circle = this;
5109
- clearAllHoverLayers();
5110
- if (circle.dataset.selected === "true") {
5111
- return;
5363
+ const removeHover = () => {
5364
+ if (currentHoveredNode?.element === circle) {
5365
+ currentHoveredNode = null;
5366
+ removeHoverStyles(circle, node);
5367
+ clearAllHoverLayers();
5368
+ }
5369
+ };
5370
+ if (enableDebouncing) {
5371
+ hoverTimerManager.clearTimer("hover-enter");
5372
+ hoverTimerManager.debounce("hover-leave", removeHover, leaveDelay);
5373
+ } else {
5374
+ removeHover();
5112
5375
  }
5113
- circle.style.stroke = "";
5114
- circle.style.strokeWidth = "";
5115
- circle.style.opacity = "";
5116
5376
  });
5117
5377
  }
5118
5378
  const svgElement = firstNode.ownerSVGElement;
@@ -5166,6 +5426,9 @@ function createNodeHover(nodeSelection, hoverStyle) {
5166
5426
  if (hoveredNodeElement.dataset.selected === "true") {
5167
5427
  return;
5168
5428
  }
5429
+ if (!currentHoveredNode || currentHoveredNode.element !== hoveredNodeElement) {
5430
+ return;
5431
+ }
5169
5432
  clearAllHoverLayers();
5170
5433
  const hoverNodesLayer = root2.select('[data-layer="hover-nodes"]').node();
5171
5434
  if (hoverNodesLayer) {
@@ -5212,7 +5475,10 @@ function createNodeHover(nodeSelection, hoverStyle) {
5212
5475
  }
5213
5476
  });
5214
5477
  }).on("mouseleave.links", function(_event, _hoveredNode) {
5215
- clearAllHoverLayers();
5478
+ const hoveredNodeElement = this;
5479
+ if (currentHoveredNode?.element === hoveredNodeElement) {
5480
+ clearAllHoverLayers();
5481
+ }
5216
5482
  });
5217
5483
  }
5218
5484
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polly-graph",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
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",