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.
- package/dist/index.cjs +287 -21
- package/dist/index.js +287 -21
- 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
|
-
|
|
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,
|
|
5376
|
+
nodeSelection.on("mouseenter.hover", function(_event, node) {
|
|
5126
5377
|
const circle = this;
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
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,
|
|
5393
|
+
}).on("mouseleave.hover", function(_event, node) {
|
|
5140
5394
|
const circle = this;
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
5344
|
+
nodeSelection.on("mouseenter.hover", function(_event, node) {
|
|
5094
5345
|
const circle = this;
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
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,
|
|
5361
|
+
}).on("mouseleave.hover", function(_event, node) {
|
|
5108
5362
|
const circle = this;
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
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
|
-
|
|
5478
|
+
const hoveredNodeElement = this;
|
|
5479
|
+
if (currentHoveredNode?.element === hoveredNodeElement) {
|
|
5480
|
+
clearAllHoverLayers();
|
|
5481
|
+
}
|
|
5216
5482
|
});
|
|
5217
5483
|
}
|
|
5218
5484
|
|
package/package.json
CHANGED