symbiote-ui 0.3.0-alpha.21 → 0.3.0-alpha.22
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/CHANGELOG.md +22 -0
- package/README.md +14 -2
- package/canvas/AutoLayout.js +0 -1
- package/canvas/CanvasGraph/CanvasGraph.js +195 -9
- package/canvas/CanvasGraph/CanvasGraphDrawState.js +9 -2
- package/canvas/ForceLayout.js +412 -15
- package/canvas/NodeCanvas/NodeCanvas.js +51 -7
- package/canvas/graph-explorer.js +81 -3
- package/canvas/graph-layout.js +20 -5
- package/control/Button/Button.css.js +79 -16
- package/control/Button/Button.js +132 -14
- package/control/Button/Button.tpl.js +6 -1
- package/control/Field/Field.css.js +77 -0
- package/control/Field/Field.js +161 -0
- package/control/Field/Field.tpl.js +12 -1
- package/control/Rating/Rating.css.js +58 -0
- package/control/Rating/Rating.js +206 -0
- package/control/Rating/Rating.tpl.js +6 -0
- package/control/SegmentedControl/SegmentedControl.css.js +60 -0
- package/control/SegmentedControl/SegmentedControl.js +208 -0
- package/control/SegmentedControl/SegmentedControl.tpl.js +5 -0
- package/control/SelectionControl/SelectionControl.css.js +250 -0
- package/control/SelectionControl/SelectionControl.js +333 -0
- package/control/SelectionControl/SelectionControl.tpl.js +17 -0
- package/control/Slider/Slider.css.js +77 -0
- package/control/Slider/Slider.js +208 -0
- package/control/Slider/Slider.tpl.js +11 -0
- package/custom-elements.json +5504 -3396
- package/display/Badge/Badge.js +5 -0
- package/display/Banner/Banner.js +22 -0
- package/display/DataTable/DataTable.css.js +97 -2
- package/display/DataTable/DataTable.js +129 -6
- package/display/DataTable/DataTable.tpl.js +9 -4
- package/display/EmptyState/EmptyState.js +5 -0
- package/display/LoadingOverlay/LoadingOverlay.js +16 -0
- package/display/Metric/Metric.css.js +13 -13
- package/display/Metric/Metric.js +19 -0
- package/display/Tooltip/Tooltip.css.js +35 -0
- package/display/Tooltip/Tooltip.js +169 -0
- package/display/Tooltip/Tooltip.tpl.js +8 -0
- package/layout/ProjectTabs/ProjectTabs.js +108 -2
- package/layout/ProjectTabs/ProjectTabs.tpl.js +1 -1
- package/list/ListItem/ListItem.css.js +56 -18
- package/list/ListItem/ListItem.js +99 -4
- package/list/ListItem/ListItem.tpl.js +11 -4
- package/llms.txt +2 -0
- package/manifest/component-registry.js +365 -1
- package/menu/ContextMenu/ContextMenu.css.js +67 -12
- package/menu/ContextMenu/ContextMenu.js +121 -23
- package/menu/ContextMenu/ContextMenu.tpl.js +8 -9
- package/navigation/QuickOpen/QuickOpen.css.js +1 -1
- package/navigation/QuickOpen/QuickOpen.js +29 -5
- package/navigation/QuickOpen/QuickOpen.tpl.js +10 -4
- package/package.json +3 -2
- package/skills/symbiote-adapter-ant-design/SKILL.md +72 -0
- package/skills/symbiote-adapter-chakra/SKILL.md +66 -0
- package/skills/symbiote-adapter-fluent-ui/SKILL.md +68 -0
- package/skills/symbiote-adapter-mantine/SKILL.md +69 -0
- package/skills/symbiote-adapter-material-web/SKILL.md +68 -0
- package/skills/symbiote-adapter-mui/SKILL.md +73 -0
- package/skills/symbiote-adapter-radix-zag/SKILL.md +69 -0
- package/skills/symbiote-adapter-shoelace/SKILL.md +76 -0
- package/skills/symbiote-adapter-spectrum/SKILL.md +71 -0
- package/skills/symbiote-library-adapter/SKILL.md +138 -0
- package/skills/symbiote-library-adapter/references/functional-comparison-checklist.md +174 -0
- package/skills/symbiote-ui/SKILL.md +21 -1
- package/surface/Card/Card.css.js +13 -6
- package/surface/Card/Card.js +27 -0
- package/surface/Card/Card.tpl.js +5 -1
- package/tree/TreePanel/TreePanel.js +8 -0
- package/tree/TreeView/TreeView.js +92 -4
- package/ui/index.js +29 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,28 @@ All notable changes to `symbiote-ui` will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## [0.3.0-alpha.22] - 2026-06-08
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Added `canvas-graph` multi-node focus via `fitNodes()`, `flyToNodes()`,
|
|
12
|
+
and `focusNodes()`, including WebMCP/discovery metadata for
|
|
13
|
+
`canvas_graph_focus_nodes`.
|
|
14
|
+
- Extended `createGraphViewModeController().focusNode()` with `flatNodeIds` so
|
|
15
|
+
flat graph demos can fit several visible nodes without drilling into a parent
|
|
16
|
+
group.
|
|
17
|
+
- Added a main-thread `ForceLayout` fallback so bundled hosts still render
|
|
18
|
+
`canvas-graph` when the standalone worker file is not served.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Kept flat graph selection emphasis scoped to the selected node by default so
|
|
23
|
+
multi-node focus does not fan out repeated pulse waves across every fitted
|
|
24
|
+
neighbor.
|
|
25
|
+
- Refreshed cached `canvas-graph` drawing colors on `cascade-theme-change` and
|
|
26
|
+
root/component theme mutations so flat canvas nodes, edges, and backgrounds
|
|
27
|
+
inherit the active cascade theme without host-local redraw hacks.
|
|
28
|
+
|
|
7
29
|
## [0.3.0-alpha.20] - 2026-06-08
|
|
8
30
|
|
|
9
31
|
### Changed
|
package/README.md
CHANGED
|
@@ -130,7 +130,7 @@ editor.addNode(node);
|
|
|
130
130
|
let canvas = document.querySelector('node-canvas');
|
|
131
131
|
canvas.setEditor(editor);
|
|
132
132
|
canvas.setPathStyle('pcb');
|
|
133
|
-
canvas.
|
|
133
|
+
canvas.applyLayout({ algorithm: 'flow', nodeIds: [node.id], direction: 'vertical', scroll: true });
|
|
134
134
|
|
|
135
135
|
let graphView = createGraphViewModeController({
|
|
136
136
|
shell: document.querySelector('graph-explorer-shell'),
|
|
@@ -144,9 +144,21 @@ let graphView = createGraphViewModeController({
|
|
|
144
144
|
});
|
|
145
145
|
|
|
146
146
|
graphView.setMode(new URLSearchParams(location.search).get('mode'));
|
|
147
|
-
graphView.focusNode({ nodeId: 'generated-view' });
|
|
147
|
+
graphView.focusNode({ nodeId: 'generated-view', flatNodeIds: ['generated-view'] });
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
+
For flat overview demos, keep the nodes that should be visible together at the
|
|
151
|
+
root of the `canvas-graph` model and pass `flatNodeIds` to
|
|
152
|
+
`focusNode()`. The older `flatNodeId` field focuses a single node and may enter
|
|
153
|
+
that node's parent group when the model is hierarchical.
|
|
154
|
+
|
|
155
|
+
`node-canvas.applyLayout()` is the agent-facing layout entrypoint. Use
|
|
156
|
+
`algorithm: 'auto'` for grouped graph auto-layout, `algorithm: 'tree'` for
|
|
157
|
+
structured directory/tree layouts, and `algorithm: 'flow'` for document-like
|
|
158
|
+
previews. Flat graph seed positions from `computeInitialGraphPositions()` are
|
|
159
|
+
stable by default; pass `random: Math.random` only when a non-deterministic demo
|
|
160
|
+
is intentional.
|
|
161
|
+
|
|
150
162
|
`panel-layout` owns reusable split and panel behavior only. Product routes,
|
|
151
163
|
transport, persistence, and permission checks remain host policy:
|
|
152
164
|
|
package/canvas/AutoLayout.js
CHANGED
|
@@ -414,7 +414,6 @@ export function computeAutoLayout(editor, options = {}) {
|
|
|
414
414
|
for (let r = 0; r < maxR; r += step) {
|
|
415
415
|
for (let delta = 0; delta <= M_PI; delta += angularStep) {
|
|
416
416
|
for (const sign of [1, -1]) {
|
|
417
|
-
cycleCount++;
|
|
418
417
|
let a = prefAngle + delta * sign;
|
|
419
418
|
let x = Math.round(Math.cos(a) * r);
|
|
420
419
|
let y = Math.round(Math.sin(a) * r);
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
} from './CanvasGraphGeometry.js';
|
|
17
17
|
import { GRAPH_TYPE_COLOR_TOKENS } from '../../graph/theme-contract.js';
|
|
18
18
|
import {
|
|
19
|
+
CANVAS_GRAPH_LAYER_TARGETS,
|
|
19
20
|
getDepthGroupsFrame,
|
|
20
21
|
getLayerAnimationFrame,
|
|
21
22
|
getNextPulseQueue,
|
|
@@ -48,6 +49,19 @@ const DEFAULT_MENU_ITEMS = Object.freeze([
|
|
|
48
49
|
{ action: 'view-code', label: 'View Code', path: 'M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0L19.2 12l-4.6-4.6L16 6l6 6-6 6-1.4-1.4z' },
|
|
49
50
|
]);
|
|
50
51
|
|
|
52
|
+
function normalizeFocusNodeIds(nodeIds) {
|
|
53
|
+
let ids = Array.isArray(nodeIds) ? nodeIds : [nodeIds];
|
|
54
|
+
let normalized = [];
|
|
55
|
+
let seen = new Set();
|
|
56
|
+
for (let id of ids) {
|
|
57
|
+
let normalizedId = String(id || '').trim();
|
|
58
|
+
if (!normalizedId || seen.has(normalizedId)) continue;
|
|
59
|
+
seen.add(normalizedId);
|
|
60
|
+
normalized.push(normalizedId);
|
|
61
|
+
}
|
|
62
|
+
return normalized;
|
|
63
|
+
}
|
|
64
|
+
|
|
51
65
|
function toRgba(rgb, alpha = 1) {
|
|
52
66
|
return `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${alpha})`;
|
|
53
67
|
}
|
|
@@ -94,6 +108,15 @@ function readThemeRgb(source, token, fallback) {
|
|
|
94
108
|
return resolveCanvasColor(value, fallback);
|
|
95
109
|
}
|
|
96
110
|
|
|
111
|
+
function scheduleFrame(callback) {
|
|
112
|
+
if (typeof globalThis.requestAnimationFrame === 'function') {
|
|
113
|
+
let id = globalThis.requestAnimationFrame(callback);
|
|
114
|
+
return () => globalThis.cancelAnimationFrame?.(id);
|
|
115
|
+
}
|
|
116
|
+
let id = setTimeout(callback, 0);
|
|
117
|
+
return () => clearTimeout(id);
|
|
118
|
+
}
|
|
119
|
+
|
|
97
120
|
export class CanvasGraph extends Symbiote {
|
|
98
121
|
init$ = {
|
|
99
122
|
// These defaults will be updated from external controller if needed
|
|
@@ -185,6 +208,8 @@ export class CanvasGraph extends Symbiote {
|
|
|
185
208
|
this._visualDragDeltaY = 0;
|
|
186
209
|
this._dragWorldTransform = null;
|
|
187
210
|
this._layoutSnapshot = null;
|
|
211
|
+
this._themeSyncQueued = false;
|
|
212
|
+
this._cancelThemeSync = null;
|
|
188
213
|
|
|
189
214
|
// Info panel state (typewriter HUD to the right of active node)
|
|
190
215
|
this._infoPanel = {
|
|
@@ -218,12 +243,7 @@ export class CanvasGraph extends Symbiote {
|
|
|
218
243
|
4: { scale: 1, opacity: 1, parallax: 0 }
|
|
219
244
|
};
|
|
220
245
|
|
|
221
|
-
this.LAYER_TARGETS =
|
|
222
|
-
scale: [1.12, 1.0, 0.95, 0.88, 0.78],
|
|
223
|
-
opacity: [1.0, 0.9, 0.55, 0.06, 0.03],
|
|
224
|
-
blur: [0, 0, 1, 3, 5],
|
|
225
|
-
parallax: [0, 0, 0.02, 0.04, 0.07]
|
|
226
|
-
};
|
|
246
|
+
this.LAYER_TARGETS = CANVAS_GRAPH_LAYER_TARGETS;
|
|
227
247
|
|
|
228
248
|
this.depthGroups = {
|
|
229
249
|
0: { edges: [], nodes: [] },
|
|
@@ -238,6 +258,7 @@ export class CanvasGraph extends Symbiote {
|
|
|
238
258
|
this.resizeCanvas();
|
|
239
259
|
|
|
240
260
|
this.bindEvents();
|
|
261
|
+
this._bindThemeSync();
|
|
241
262
|
|
|
242
263
|
this._wakeLoop();
|
|
243
264
|
|
|
@@ -249,12 +270,16 @@ export class CanvasGraph extends Symbiote {
|
|
|
249
270
|
});
|
|
250
271
|
}
|
|
251
272
|
|
|
252
|
-
|
|
273
|
+
this._scheduleCanvasThemeSync();
|
|
253
274
|
}
|
|
254
275
|
|
|
255
276
|
disconnectedCallback() {
|
|
256
277
|
this._loopRunning = false;
|
|
257
278
|
if (this._animationFrame) cancelAnimationFrame(this._animationFrame);
|
|
279
|
+
this._cancelThemeSync?.();
|
|
280
|
+
this._cancelThemeSync = null;
|
|
281
|
+
this.ownerDocument?.removeEventListener?.('cascade-theme-change', this._themeChangeHandler);
|
|
282
|
+
this._themeObserver?.disconnect();
|
|
258
283
|
if (this.worker) this.worker.stop();
|
|
259
284
|
}
|
|
260
285
|
|
|
@@ -284,6 +309,53 @@ export class CanvasGraph extends Symbiote {
|
|
|
284
309
|
this.fitView();
|
|
285
310
|
}
|
|
286
311
|
|
|
312
|
+
_getVisibleFocusFrame(nodeId, { fallbackToParent = true } = {}) {
|
|
313
|
+
let id = String(nodeId || '').trim();
|
|
314
|
+
if (!id) return null;
|
|
315
|
+
|
|
316
|
+
let node = this.nodeMap?.get(id);
|
|
317
|
+
if (!node && fallbackToParent) {
|
|
318
|
+
let graphNode = this.graphDB?.nodes?.get(id);
|
|
319
|
+
let parentId = graphNode?.parentId;
|
|
320
|
+
while (parentId && !node) {
|
|
321
|
+
node = this.nodeMap?.get(parentId);
|
|
322
|
+
if (node) {
|
|
323
|
+
id = parentId;
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
parentId = this.graphDB?.nodes?.get(parentId)?.parentId;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (!node) return null;
|
|
330
|
+
|
|
331
|
+
let pos = this.getSmooth(id) || this.nodePositions.get(id);
|
|
332
|
+
if (!pos) return null;
|
|
333
|
+
|
|
334
|
+
if (this.renderMode === 'dots') {
|
|
335
|
+
let connections = this.adjMap?.get(id)?.size || 0;
|
|
336
|
+
let radius = getNodeRadius(node, connections);
|
|
337
|
+
return {
|
|
338
|
+
id,
|
|
339
|
+
node,
|
|
340
|
+
minX: pos.x - radius,
|
|
341
|
+
minY: pos.y - radius,
|
|
342
|
+
maxX: pos.x + radius,
|
|
343
|
+
maxY: pos.y + radius,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
let width = Number.isFinite(node.w) ? node.w : 160;
|
|
348
|
+
let height = Number.isFinite(node.h) ? node.h : 40;
|
|
349
|
+
return {
|
|
350
|
+
id,
|
|
351
|
+
node,
|
|
352
|
+
minX: pos.x,
|
|
353
|
+
minY: pos.y,
|
|
354
|
+
maxX: pos.x + width,
|
|
355
|
+
maxY: pos.y + height,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
287
359
|
fitView(padding = 60, animate = true) {
|
|
288
360
|
if (!this.nodePositions.size) return;
|
|
289
361
|
const rect = this.canvas.getBoundingClientRect();
|
|
@@ -327,12 +399,90 @@ export class CanvasGraph extends Symbiote {
|
|
|
327
399
|
this._wakeLoop();
|
|
328
400
|
}
|
|
329
401
|
|
|
330
|
-
|
|
402
|
+
fitNodes(nodeIds, options = {}) {
|
|
403
|
+
let ids = normalizeFocusNodeIds(nodeIds);
|
|
404
|
+
if (ids.length === 0) return false;
|
|
405
|
+
|
|
406
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
407
|
+
if (rect.width === 0 || rect.height === 0) return false;
|
|
408
|
+
|
|
409
|
+
let frames = ids
|
|
410
|
+
.map((id) => this._getVisibleFocusFrame(id, { fallbackToParent: options.fallbackToParent !== false }))
|
|
411
|
+
.filter(Boolean);
|
|
412
|
+
if (frames.length === 0) return false;
|
|
413
|
+
|
|
414
|
+
let minX = Math.min(...frames.map((frame) => frame.minX));
|
|
415
|
+
let minY = Math.min(...frames.map((frame) => frame.minY));
|
|
416
|
+
let maxX = Math.max(...frames.map((frame) => frame.maxX));
|
|
417
|
+
let maxY = Math.max(...frames.map((frame) => frame.maxY));
|
|
418
|
+
let graphW = maxX - minX || 1;
|
|
419
|
+
let graphH = maxY - minY || 1;
|
|
420
|
+
let cx = (minX + maxX) / 2;
|
|
421
|
+
let cy = (minY + maxY) / 2;
|
|
422
|
+
let padding = Number.isFinite(options.padding) ? options.padding : 80;
|
|
423
|
+
let minZoom = Number.isFinite(options.minZoom) ? options.minZoom : 0.02;
|
|
424
|
+
let maxZoom = Number.isFinite(options.maxZoom) ? options.maxZoom : 2.0;
|
|
425
|
+
let newZoom = Math.max(minZoom, Math.min(
|
|
426
|
+
(rect.width - padding * 2) / graphW,
|
|
427
|
+
(rect.height - padding * 2) / graphH,
|
|
428
|
+
maxZoom
|
|
429
|
+
));
|
|
430
|
+
let newPanX = rect.width / 2 - cx * newZoom;
|
|
431
|
+
let newPanY = rect.height / 2 - cy * newZoom;
|
|
432
|
+
|
|
433
|
+
let animate = options.animate !== false;
|
|
434
|
+
this._zoomAnchor = null;
|
|
435
|
+
if (animate) {
|
|
436
|
+
this._targetZoom = newZoom;
|
|
437
|
+
this._targetPanX = newPanX;
|
|
438
|
+
this._targetPanY = newPanY;
|
|
439
|
+
} else {
|
|
440
|
+
this.zoom = newZoom;
|
|
441
|
+
this._targetZoom = newZoom;
|
|
442
|
+
this.panX = newPanX;
|
|
443
|
+
this.panY = newPanY;
|
|
444
|
+
this._targetPanX = null;
|
|
445
|
+
this._targetPanY = null;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
let selectedId = null;
|
|
449
|
+
if (typeof options.select === 'string') {
|
|
450
|
+
selectedId = options.select;
|
|
451
|
+
} else if (options.select === true) {
|
|
452
|
+
selectedId = frames[0]?.id || null;
|
|
453
|
+
}
|
|
454
|
+
if (selectedId && this.nodeMap?.has(selectedId)) {
|
|
455
|
+
this.activeNode = this.nodeMap.get(selectedId);
|
|
456
|
+
this.deactivating = false;
|
|
457
|
+
this.updateInteractionDepths();
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
this.needsDraw = true;
|
|
461
|
+
this._wakeLoop();
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
flyToNodes(nodeIds, options = {}) {
|
|
466
|
+
return this.fitNodes(nodeIds, options);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
focusNodes(nodeIds, options = {}) {
|
|
470
|
+
let ids = normalizeFocusNodeIds(nodeIds);
|
|
471
|
+
if (ids.length === 0) return false;
|
|
472
|
+
if (!Array.isArray(nodeIds) && ids.length === 1 && options.fit !== true) {
|
|
473
|
+
this.flyToNode(ids[0], options);
|
|
474
|
+
return true;
|
|
475
|
+
}
|
|
476
|
+
return this.fitNodes(ids, options);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
pulseNode(nodeId, durationMs = 1500, options = {}) {
|
|
331
480
|
this._pulses = getNextPulseQueue({
|
|
332
481
|
pulses: this._pulses || [],
|
|
333
482
|
nodeId,
|
|
334
483
|
startTime: performance.now(),
|
|
335
484
|
duration: durationMs,
|
|
485
|
+
waves: Number.isFinite(options.waves) ? options.waves : 1,
|
|
336
486
|
});
|
|
337
487
|
this.needsDraw = true;
|
|
338
488
|
this._wakeLoop();
|
|
@@ -756,6 +906,38 @@ export class CanvasGraph extends Symbiote {
|
|
|
756
906
|
return `rgb(${rr},${gg},${bbb})`;
|
|
757
907
|
}
|
|
758
908
|
|
|
909
|
+
_bindThemeSync() {
|
|
910
|
+
this._themeChangeHandler = () => this._scheduleCanvasThemeSync();
|
|
911
|
+
this.ownerDocument?.addEventListener?.('cascade-theme-change', this._themeChangeHandler);
|
|
912
|
+
|
|
913
|
+
if (typeof globalThis.MutationObserver !== 'function') return;
|
|
914
|
+
this._themeObserver = new MutationObserver(() => this._scheduleCanvasThemeSync());
|
|
915
|
+
let themeSources = [
|
|
916
|
+
this.ownerDocument?.documentElement,
|
|
917
|
+
this.ownerDocument?.body,
|
|
918
|
+
this.parentElement,
|
|
919
|
+
this,
|
|
920
|
+
].filter(Boolean);
|
|
921
|
+
let seen = new Set();
|
|
922
|
+
for (let source of themeSources) {
|
|
923
|
+
if (seen.has(source)) continue;
|
|
924
|
+
seen.add(source);
|
|
925
|
+
this._themeObserver.observe(source, { attributes: true, attributeFilter: ['class', 'style'] });
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
_scheduleCanvasThemeSync() {
|
|
930
|
+
if (this._themeSyncQueued) return;
|
|
931
|
+
this._themeSyncQueued = true;
|
|
932
|
+
this._cancelThemeSync = scheduleFrame(() => {
|
|
933
|
+
this._themeSyncQueued = false;
|
|
934
|
+
this._cancelThemeSync = null;
|
|
935
|
+
this.syncCanvasTheme();
|
|
936
|
+
this.needsDraw = true;
|
|
937
|
+
this._wakeLoop();
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
|
|
759
941
|
syncCanvasTheme() {
|
|
760
942
|
this._bgRgb = readThemeRgb(this, '--sn-bg', this._bgRgb);
|
|
761
943
|
this._edgeRgb = readThemeRgb(this, '--sn-conn-color', this._edgeRgb);
|
|
@@ -1004,6 +1186,8 @@ export class CanvasGraph extends Symbiote {
|
|
|
1004
1186
|
fillStyle = this.blendBg(fromTC[0], fromTC[1], fromTC[2], 0.35);
|
|
1005
1187
|
}
|
|
1006
1188
|
|
|
1189
|
+
currentCtx.save();
|
|
1190
|
+
currentCtx.globalAlpha *= edge.aAlpha;
|
|
1007
1191
|
currentCtx.fillStyle = fillStyle;
|
|
1008
1192
|
currentCtx.beginPath();
|
|
1009
1193
|
const midX = from.x + dx * 0.5, midY = from.y + dy * 0.5;
|
|
@@ -1018,6 +1202,7 @@ export class CanvasGraph extends Symbiote {
|
|
|
1018
1202
|
currentCtx.arc(from.x, from.y, wFrom, ang - Math.PI/2, ang - Math.PI * 1.5, true);
|
|
1019
1203
|
currentCtx.closePath();
|
|
1020
1204
|
currentCtx.fill();
|
|
1205
|
+
currentCtx.restore();
|
|
1021
1206
|
}
|
|
1022
1207
|
|
|
1023
1208
|
// Nodes
|
|
@@ -1133,7 +1318,8 @@ export class CanvasGraph extends Symbiote {
|
|
|
1133
1318
|
const pos = this.getSmooth(p.id) || this.nodePositions.get(p.id);
|
|
1134
1319
|
if (!pos) return false;
|
|
1135
1320
|
const progress = elapsed / p.duration;
|
|
1136
|
-
const
|
|
1321
|
+
const waves = Number.isFinite(p.waves) ? Math.max(1, p.waves) : 1;
|
|
1322
|
+
const pulsePhase = (progress * waves) % 1;
|
|
1137
1323
|
const r = 20 + (pulsePhase * 80);
|
|
1138
1324
|
const opacity = 1 - pulsePhase;
|
|
1139
1325
|
mainCtx.beginPath();
|
|
@@ -45,13 +45,20 @@ export function resolveViewportAnimation(options) {
|
|
|
45
45
|
return next;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
export function getNextPulseQueue({ pulses = [], nodeId, startTime, duration }) {
|
|
48
|
+
export function getNextPulseQueue({ pulses = [], nodeId, startTime, duration, waves = 1 }) {
|
|
49
49
|
return [
|
|
50
50
|
...pulses.filter((pulse) => pulse.id !== nodeId),
|
|
51
|
-
{ id: nodeId, startTime, duration },
|
|
51
|
+
{ id: nodeId, startTime, duration, waves },
|
|
52
52
|
];
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
export const CANVAS_GRAPH_LAYER_TARGETS = Object.freeze({
|
|
56
|
+
scale: Object.freeze([1.14, 0.96, 0.88, 0.78, 0.68]),
|
|
57
|
+
opacity: Object.freeze([1, 0.52, 0.24, 0.07, 0.02]),
|
|
58
|
+
blur: Object.freeze([0, 0.4, 1.6, 3.5, 6]),
|
|
59
|
+
parallax: Object.freeze([0, 0.02, 0.045, 0.075, 0.11]),
|
|
60
|
+
});
|
|
61
|
+
|
|
55
62
|
export function resolveGroupOrbitRotationFrame(options) {
|
|
56
63
|
let {
|
|
57
64
|
rotation = 0,
|