@graphty/graphty-element 2.3.1 → 2.4.0

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 (69) hide show
  1. package/AGENTS.md +4 -3
  2. package/dist/ai.js +3 -3
  3. package/dist/catalog.js +28 -27
  4. package/dist/chunks/{AiManager-BBmGJbH4.js → AiManager-BD9XK30e.js} +5 -5
  5. package/dist/chunks/{DataSource-OeN3NeyD.js → DataSource-B8vf2uhW.js} +3 -3
  6. package/dist/chunks/{GraphSession-Bef1AYw9.js → GraphSession-dcwOjGJh.js} +2821 -2743
  7. package/dist/chunks/{GraphtyError-BwcnblTH.js → GraphtyError-B93WRH3e.js} +8 -6
  8. package/dist/chunks/{GraphtyLogger-5KEttFUo.js → GraphtyLogger-CqOVV13Y.js} +2 -2
  9. package/dist/chunks/{NodeStyle-DKj7HjMJ.js → NodeStyle-CtmA7dXi.js} +16 -14
  10. package/dist/chunks/{VoiceInputAdapter-Dr9Gcmds.js → VoiceInputAdapter-D0tHHi9G.js} +1 -1
  11. package/dist/chunks/{XRPivotCameraController-BbfgZWpS.js → XRPivotCameraController-DTfhvhHz.js} +2 -2
  12. package/dist/chunks/{algorithms-CpX56sUB.js → algorithms-BF0X6RPw.js} +780 -635
  13. package/dist/chunks/{capability-check-Blhb2aBB.js → capability-check-BJzlK4oL.js} +1 -1
  14. package/dist/chunks/{detect-Cqwshr9a.js → detect-vJxK7n0D.js} +2 -2
  15. package/dist/chunks/{format-detection-BXGO1lSn.js → format-detection-C80TLQ2e.js} +1 -1
  16. package/dist/chunks/{index-C0mIoumR.js → index-2xkq7wyD.js} +2623 -2241
  17. package/dist/chunks/{optionsFromZod-17lkrAJs.js → optionsFromZod-BuTOFgVM.js} +146 -139
  18. package/dist/chunks/{paletteRegistry-x7WOEKZY.js → paletteRegistry-A63C71Gn.js} +8 -6
  19. package/dist/chunks/{registry-CSba5QGJ.js → registry-jB46Gmeb.js} +1 -1
  20. package/dist/chunks/{scales-BRwl51k8.js → scales-B2d-7Bf0.js} +572 -399
  21. package/dist/chunks/{types-B7bX5c0K.js → types-C_c53VgR.js} +31 -26
  22. package/dist/commands.d.ts +4 -0
  23. package/dist/custom-elements.json +1 -1
  24. package/dist/extend.js +8 -8
  25. package/dist/graphty-catalog.json +245 -12
  26. package/dist/graphty.bundle.js +37836 -36835
  27. package/dist/graphty.js +11 -11
  28. package/dist/logging.js +2 -2
  29. package/dist/schema.d.ts +1 -1
  30. package/dist/schema.js +42 -40
  31. package/dist/session.js +6 -6
  32. package/dist/src/Graph.d.ts +79 -7
  33. package/dist/src/acceleration/AccelerationController.d.ts +14 -3
  34. package/dist/src/acceleration/types.d.ts +78 -0
  35. package/dist/src/algorithms/KCoreAlgorithm.d.ts +26 -0
  36. package/dist/src/algorithms/LinkPredictionAlgorithm.d.ts +41 -0
  37. package/dist/src/catalog/algorithms.d.ts +5 -5
  38. package/dist/src/catalog/index.d.ts +2 -2
  39. package/dist/src/catalog/layouts.d.ts +7 -6
  40. package/dist/src/catalog/types.d.ts +77 -6
  41. package/dist/src/config/EdgeStyle.d.ts +35 -0
  42. package/dist/src/config/index.d.ts +1 -1
  43. package/dist/src/data/GEXFDataSource.d.ts +23 -0
  44. package/dist/src/errors/codes.d.ts +14 -0
  45. package/dist/src/events.d.ts +30 -0
  46. package/dist/src/graphty-element.d.ts +66 -26
  47. package/dist/src/layout/GridLayoutEngine.d.ts +37 -0
  48. package/dist/src/layout/LayoutEngine.d.ts +7 -7
  49. package/dist/src/layout/NGraphLayoutEngine.d.ts +2 -0
  50. package/dist/src/layout/RadialLayoutEngine.d.ts +37 -0
  51. package/dist/src/layout/SimulationLayoutEngine.d.ts +12 -0
  52. package/dist/src/managers/DataManager.d.ts +69 -2
  53. package/dist/src/managers/EventManager.d.ts +10 -4
  54. package/dist/src/managers/GraphContext.d.ts +2 -1
  55. package/dist/src/managers/LayoutManager.d.ts +30 -3
  56. package/dist/src/meshes/MeshCache.d.ts +18 -0
  57. package/dist/src/meshes/NodeEffects.d.ts +16 -11
  58. package/dist/src/meshes/NodeMesh.d.ts +2 -2
  59. package/dist/src/meshes/RichTextParser.d.ts +26 -0
  60. package/dist/src/meshes/RichTextRenderer.d.ts +0 -1
  61. package/dist/src/session/cost/estimate.d.ts +1 -1
  62. package/dist/src/session/layout.d.ts +3 -3
  63. package/dist/src/session/runs/RunsApi.d.ts +1 -1
  64. package/dist/src/session/styles/StylesApi.d.ts +6 -0
  65. package/dist/src/session/styles/intern.d.ts +26 -5
  66. package/dist/src/session/styles/repaint.d.ts +2 -1
  67. package/dist/src/session/types.d.ts +6 -6
  68. package/dist/webgpu.js +2 -2
  69. package/package.json +6 -6
@@ -0,0 +1,37 @@
1
+ import { z } from "zod/v4";
2
+ import { type OptionsSchema } from "../config";
3
+ import { SimpleLayoutEngine } from "./LayoutEngine";
4
+ declare const RadialLayoutConfig: z.ZodObject<{
5
+ root: z.ZodDefault<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
6
+ scale: z.ZodDefault<z.ZodNumber>;
7
+ center: z.ZodDefault<z.ZodUnion<[z.ZodArray<z.ZodNumber>, z.ZodNull]>>;
8
+ scalingFactor: z.ZodDefault<z.ZodNumber>;
9
+ }, z.core.$strict>;
10
+ type RadialLayoutConfigType = z.infer<typeof RadialLayoutConfig>;
11
+ type RadialLayoutOpts = Partial<RadialLayoutConfigType>;
12
+ /**
13
+ * Radial layout engine that places nodes on rings by hop distance from a root node
14
+ */
15
+ export declare class RadialLayout extends SimpleLayoutEngine {
16
+ static type: string;
17
+ static maxDimensions: number;
18
+ static zodOptionsSchema: OptionsSchema;
19
+ scalingFactor: number;
20
+ config: RadialLayoutConfigType;
21
+ /**
22
+ * Create a radial layout engine
23
+ * @param opts - Configuration options including the root node
24
+ */
25
+ constructor(opts: RadialLayoutOpts);
26
+ /**
27
+ * Get dimension-specific options for radial layout
28
+ * @param dimension - The desired dimension (2 or 3)
29
+ * @returns Empty object for 2D, null for 3D (unsupported)
30
+ */
31
+ static getOptionsForDimension(dimension: 2 | 3): object | null;
32
+ /**
33
+ * Compute node positions on rings around the root
34
+ */
35
+ doLayout(): void;
36
+ }
37
+ export {};
@@ -323,6 +323,18 @@ export declare class SimulationLayoutEngine extends LayoutEngine {
323
323
  * @returns A promise that resolves when the batch has landed, or rejects with its failure.
324
324
  */
325
325
  stepAsync(iterations: number): Promise<void>;
326
+ /**
327
+ * Whether the manager has stopped stepping this layout -- a consumer's pause, or any other
328
+ * stop. A paused layout's work span closes once its in-flight batches land, because nothing
329
+ * is being submitted after them.
330
+ * @returns True while the layout is not being stepped.
331
+ */
332
+ get paused(): boolean;
333
+ /**
334
+ * Tells the bridge whether the manager is stepping it.
335
+ * @param value - True when the manager has stopped stepping this layout.
336
+ */
337
+ set paused(value: boolean);
326
338
  /**
327
339
  * Starts the settle count again, on a simulation that has one.
328
340
  *
@@ -112,6 +112,12 @@ export declare class DataManager implements Manager {
112
112
  private loadEndpoints;
113
113
  /** The tally the load in progress is counting into, or null outside a load. */
114
114
  private loadTally;
115
+ /**
116
+ * Bumped by every REPLACING load as it is asked for, and by `supersedeLoads`. A load that
117
+ * sees it move has been overtaken, and stops with `E_SUPERSEDED` rather than touching the
118
+ * graph: see `addDataFromSource`.
119
+ */
120
+ private replaceGeneration;
115
121
  /**
116
122
  * Creates an instance of DataManager
117
123
  * @param eventManager - Event manager for emitting data events
@@ -273,6 +279,22 @@ export declare class DataManager implements Manager {
273
279
  * @param idPath - JMESPath expression to extract node ID from data
274
280
  */
275
281
  addNode(node: AdHocData, idPath?: string): void;
282
+ /**
283
+ * The id `addNodes` reads off a node record.
284
+ * @param node - the record
285
+ * @param idPath - JMESPath expression to extract the id; the configured node id path when unset
286
+ * @returns the node's id
287
+ */
288
+ nodeIdOf(node: Record<string | number, unknown>, idPath?: string): NodeIdType;
289
+ /**
290
+ * Refuse a replacing node set the renderer cannot hold, before the replace removes anything.
291
+ *
292
+ * The node half of what {@link setEdges} decides first: the new set is counted against an
293
+ * emptied graph, so a refused replace keeps the nodes the graph had.
294
+ * @param count - how many distinct nodes the graph would hold afterwards
295
+ * @throws A `GraphtyError` with `E_TOO_LARGE` when `count` is past the ceiling
296
+ */
297
+ refuseNodeSetAboveCeiling(count: number): void;
276
298
  /**
277
299
  * Adds multiple nodes to the graph
278
300
  * @param nodes - Array of node data objects
@@ -472,12 +494,57 @@ export declare class DataManager implements Manager {
472
494
  * while the source has still declared nothing
473
495
  */
474
496
  private applyDeclaredDirection;
497
+ /**
498
+ * Reserve a load's place in line, at the moment the caller asked for it.
499
+ *
500
+ * A caller that reads a file or sniffs a URL before it loads calls this FIRST, so a load that
501
+ * was asked for later still wins however long the earlier one spends reading. A replacing load
502
+ * supersedes every load reserved before it.
503
+ * @param replace - Whether the load will replace the graph
504
+ * @returns The generation to hand to `addDataFromSource` and `throwIfSuperseded`
505
+ */
506
+ beginLoad(replace: boolean): number;
507
+ /**
508
+ * Abandon every load in flight: each rejects with `E_SUPERSEDED` and adds nothing more.
509
+ * The element's `clearData` calls this, so a load finishing after the graph was closed does
510
+ * not bring its data back.
511
+ */
512
+ supersedeLoads(): void;
513
+ /**
514
+ * Throw `E_SUPERSEDED` when a load reserved at `generation` has been overtaken.
515
+ * @param generation - What `beginLoad` returned for the load
516
+ * @param type - The load's format, for the message
517
+ */
518
+ throwIfSuperseded(generation: number, type: string): void;
475
519
  /**
476
520
  * Loads data from a registered data source
521
+ *
522
+ * A REPLACING load reads the whole source into memory before it touches the store, and only
523
+ * once the source has finished without an error does it clear the graph and add what it read.
524
+ * A malformed or empty file therefore leaves the graph it would have replaced exactly as it
525
+ * was. An additive load streams each chunk straight in, as it always has.
526
+ *
527
+ * A load that reads no node records and no edge records at all fails with `E_EMPTY_LOAD`
528
+ * rather than completing with zero counts. A file of edges alone is not empty: its endpoints
529
+ * become nodes.
530
+ *
531
+ * The load that STARTED last wins. Once a replacing load has started, every load started
532
+ * before it -- replacing or additive -- is superseded: it adds nothing more and rejects with
533
+ * `E_SUPERSEDED`, whichever order the sources finish in. A superseded load emits no
534
+ * `data-loading-error`, because nothing went wrong with its source.
477
535
  * @param type - Data source type identifier
478
536
  * @param opts - Options to pass to the data source
479
- */
480
- addDataFromSource(type: string, opts?: object): Promise<void>;
537
+ * @param load - Which load this is, for every event it emits, and whether it replaces the graph
538
+ * @param load.loadId - The id every event about this load carries
539
+ * @param load.replace - Swap the graph for what the source holds, once it has all parsed
540
+ * @param load.generation - The place `beginLoad` reserved for this load when the caller's call
541
+ * was made; left unset, the load takes its place now
542
+ */
543
+ addDataFromSource(type: string, opts?: object, load?: {
544
+ loadId?: number;
545
+ replace?: boolean;
546
+ generation?: number;
547
+ }): Promise<void>;
481
548
  /**
482
549
  * Freeze one load's counters into the report a consumer reads, and keep it for `lastImport`.
483
550
  * @param format - the data source that read the file
@@ -65,8 +65,9 @@ export declare class EventManager implements Manager {
65
65
  * @param chunksLoaded - Number of data chunks loaded
66
66
  * @param dataSourceType - Type of data source used
67
67
  * @param report - What the load did, including which endpoint spelling resolved
68
+ * @param loadId - Which load this is, when it is one
68
69
  */
69
- emitGraphDataLoaded(graph: Graph | GraphContext, chunksLoaded: number, dataSourceType: string, report: ImportReport): void;
70
+ emitGraphDataLoaded(graph: Graph | GraphContext, chunksLoaded: number, dataSourceType: string, report: ImportReport, loadId?: number): void;
70
71
  /**
71
72
  * Emits the removal event naming every node and edge one removal call took away.
72
73
  * @param nodes - the nodes that were removed
@@ -122,8 +123,9 @@ export declare class EventManager implements Manager {
122
123
  * @param nodeRecordsLoaded - How many node RECORDS the source has handed over so far
123
124
  * @param edgeRecordsLoaded - How many edge RECORDS the source has handed over so far
124
125
  * @param chunksProcessed - Number of data chunks processed
126
+ * @param loadId - Which load this is, when it is one
125
127
  */
126
- emitDataLoadingProgress(format: string, bytesProcessed: number, totalBytes: number | undefined, nodeRecordsLoaded: number, edgeRecordsLoaded: number, chunksProcessed: number): void;
128
+ emitDataLoadingProgress(format: string, bytesProcessed: number, totalBytes: number | undefined, nodeRecordsLoaded: number, edgeRecordsLoaded: number, chunksProcessed: number, loadId?: number): void;
127
129
  /**
128
130
  * Emits a data loading error event when an error occurs during import
129
131
  * @param error - Error object
@@ -134,12 +136,14 @@ export declare class EventManager implements Manager {
134
136
  * @param details.nodeId - Node ID related to error
135
137
  * @param details.edgeId - Edge ID related to error
136
138
  * @param details.canContinue - Whether loading can continue after this error
139
+ * @param details.loadId - Which load this is, when it is one
137
140
  */
138
141
  emitDataLoadingError(error: Error, context: DataLoadingErrorEvent["context"], format: string | undefined, details: {
139
142
  line?: number;
140
143
  nodeId?: unknown;
141
144
  edgeId?: string;
142
145
  canContinue: boolean;
146
+ loadId?: number;
143
147
  }): void;
144
148
  /**
145
149
  * Emits a summary of all data loading errors after import completes
@@ -149,8 +153,9 @@ export declare class EventManager implements Manager {
149
153
  * @param detailedReport - Detailed error report
150
154
  * @param primaryCategory - Primary error category
151
155
  * @param suggestion - Suggested fix for the errors
156
+ * @param loadId - Which load this is, when it is one
152
157
  */
153
- emitDataLoadingErrorSummary(format: string, totalErrors: number, message: string, detailedReport: string, primaryCategory?: string, suggestion?: string): void;
158
+ emitDataLoadingErrorSummary(format: string, totalErrors: number, message: string, detailedReport: string, primaryCategory?: string, suggestion?: string, loadId?: number): void;
154
159
  /**
155
160
  * Emits a data loading complete event when import finishes
156
161
  * @param format - Data format that was loaded
@@ -161,8 +166,9 @@ export declare class EventManager implements Manager {
161
166
  * @param warnings - Number of warnings encountered
162
167
  * @param success - Whether loading was successful
163
168
  * @param report - What the load did, including which endpoint spelling resolved
169
+ * @param loadId - Which load this is, when it is one
164
170
  */
165
- emitDataLoadingComplete(format: string, nodesLoaded: number, edgesLoaded: number, duration: number, errors: number, warnings: number, success: boolean, report: ImportReport): void;
171
+ emitDataLoadingComplete(format: string, nodesLoaded: number, edgesLoaded: number, duration: number, errors: number, warnings: number, success: boolean, report: ImportReport, loadId?: number): void;
166
172
  /**
167
173
  * Emits a selection changed event when node selection changes
168
174
  * @param previousNode - Previously selected node (or null)
@@ -69,7 +69,8 @@ export interface GraphContext {
69
69
  */
70
70
  isRunning(): boolean;
71
71
  /**
72
- * Set the running state
72
+ * Play or pause the layout on a consumer's behalf. A pause holds until `setRunning(true)`;
73
+ * the element's own restarts write `getLayoutManager().running`, which the pause refuses.
73
74
  */
74
75
  setRunning(running: boolean): void;
75
76
  /**
@@ -42,6 +42,13 @@ export declare class LayoutManager implements Manager {
42
42
  private styles;
43
43
  layoutEngine?: LayoutEngine;
44
44
  private _running;
45
+ /**
46
+ * Set while a CONSUMER has paused the layout, through {@link LayoutManager.setPaused}. Nothing
47
+ * inside the element clears it: a load, a freeze, an accelerator attaching, a new layout or a
48
+ * drag may rebuild or place nodes, but their `running = true` is refused until the consumer
49
+ * resumes.
50
+ */
51
+ private _paused;
45
52
  /**
46
53
  * Set when a layout was built over a graph with nothing in it, and the pre-steps it is
47
54
  * configured with have therefore not been spent. See
@@ -68,8 +75,19 @@ export declare class LayoutManager implements Manager {
68
75
  * again and the next frame moves nodes. Going true -> false only stops `step()` being
69
76
  * called; batches already in flight land in the position array by themselves, nothing is
70
77
  * disposed and nothing is released.
78
+ *
79
+ * While the consumer has paused the layout (see {@link LayoutManager.setPaused}) a `true` is
80
+ * ignored, so the element's own restarts cannot undo the pause.
71
81
  */
72
82
  set running(value: boolean);
83
+ /**
84
+ * Pause or resume the layout on a consumer's behalf.
85
+ *
86
+ * A pause holds until the consumer resumes it: internal restarts are refused while it is set.
87
+ * Resuming runs the layout, and reheats a simulation that had settled.
88
+ * @param paused - True to pause, false to resume.
89
+ */
90
+ setPaused(paused: boolean): void;
73
91
  private graphContext;
74
92
  /**
75
93
  * The graph's acceleration controller, once a context has arrived. A manager built without a
@@ -134,7 +152,7 @@ export declare class LayoutManager implements Manager {
134
152
  /**
135
153
  * Internal method for setting layout - bypasses queue
136
154
  * Used by operations that are already queued to prevent nested queueing
137
- * @param type - Layout type identifier
155
+ * @param layout - A registered engine name, or a catalogue layout id
138
156
  * @param opts - Layout-specific options
139
157
  */
140
158
  private _setLayoutInternal;
@@ -246,10 +264,18 @@ export declare class LayoutManager implements Manager {
246
264
  */
247
265
  getNodePosition(node: Node): [number, number, number] | undefined;
248
266
  /**
249
- * Check if layout has settled
250
- * @returns True if layout has settled, false otherwise
267
+ * Whether the layout engine has converged: the positions are final.
268
+ *
269
+ * A layout that was stopped part-way is NOT settled; it is {@link LayoutManager.isPaused}. A
270
+ * reader that only needs "positions are not moving right now" asks `!running || isSettled`.
271
+ * @returns True when the engine has converged, or when there is no engine.
251
272
  */
252
273
  get isSettled(): boolean;
274
+ /**
275
+ * Whether the layout was stopped before it converged, so its positions are not final.
276
+ * @returns True when the layout is not running and has not settled.
277
+ */
278
+ get isPaused(): boolean;
253
279
  /**
254
280
  * Get nodes from layout engine
255
281
  * @returns Iterable of nodes managed by the layout engine
@@ -295,6 +321,7 @@ export declare class LayoutManager implements Manager {
295
321
  layoutType: string | undefined;
296
322
  isRunning: boolean;
297
323
  isSettled: boolean;
324
+ isPaused: boolean;
298
325
  nodeCount: number;
299
326
  edgeCount: number;
300
327
  };
@@ -26,6 +26,24 @@ export declare class MeshCache {
26
26
  * Clear all cached meshes and reset statistics
27
27
  */
28
28
  clear(): void;
29
+ /**
30
+ * Dispose and forget every cached mesh that nothing is drawn from any more.
31
+ *
32
+ * Every source mesh is handed out only as instances, so one with no instances is a look no
33
+ * element has: a size a layer used to paint, a halo nobody is selected for. Without this the
34
+ * cache holds one mesh per look ever drawn, which grows with every edit to a size or a shape
35
+ * until the next dataset boundary. A name that is asked for again is simply built again.
36
+ */
37
+ prune(): void;
38
+ /**
39
+ * Dispose a source mesh and the material it was built with.
40
+ *
41
+ * Every creator handed to get() builds a fresh material for its mesh, so the material dies
42
+ * with it; `mesh.dispose()` alone would leave it in `scene.materials` for good. Textures are
43
+ * kept: node gradient textures are shared across materials per scene.
44
+ * @param mesh - The cached source mesh
45
+ */
46
+ private static disposeSource;
29
47
  /**
30
48
  * Get the number of cached meshes
31
49
  * @returns Count of cached meshes
@@ -83,13 +83,16 @@ export declare class NodeEffects {
83
83
  * must not pay for it, so the only call site is the `effect.glow` branch of
84
84
  * {@link NodeEffects.applyGlowEffect}; the removal branch deliberately does NOT create one.
85
85
  *
86
+ * DISPOSED AGAIN WHEN NOTHING GLOWS, by {@link NodeEffects.syncGlowStrengths}. The inclusion
87
+ * list alone never says so: `node.glow` is a mesh channel, so a node that stops glowing moves
88
+ * to another source mesh and the old glowing source stays listed with no instances. The next
89
+ * glow recreates the layer through this method.
90
+ *
86
91
  * `excludeByDefault: true` is a safety catch, not a tuning knob. Babylon's inclusion list
87
92
  * means "only these" when it is non-empty and "no opinion" -- i.e. EVERY mesh in the scene --
88
- * when it is empty (ThinGlowLayer.hasMesh). An empty list is not reachable today, because the
89
- * layer is only created at the moment a mesh is added to it and membership is keyed by the
90
- * shared source mesh (see resolveRenderedMesh), but "the whole graph blooms" is a bad enough
91
- * failure to be worth one option: with this set, `_internalShouldRender` returns false while
92
- * the list is empty, so an emptied layer renders nothing at all.
93
+ * when it is empty (ThinGlowLayer.hasMesh). With this set, `_internalShouldRender` returns
94
+ * false while the list is empty, so an emptied layer renders nothing rather than blooming
95
+ * the whole graph.
93
96
  * @param scene - The Babylon.js scene
94
97
  * @returns The glow layer for the scene
95
98
  */
@@ -142,6 +145,10 @@ export declare class NodeEffects {
142
145
  * back to 0.1) leaves a mesh with no instances behind; counted, it would hold the layer at 100
143
146
  * and round the live glow away in the 8-bit map. It keeps its entry, because a node that goes
144
147
  * back to that strength reuses the cached mesh. A disposed mesh is dropped.
148
+ *
149
+ * When no glowing source mesh draws a node any more, the layer is disposed: it is a
150
+ * full-screen post-process that would otherwise render nothing every frame for the life of
151
+ * the scene. Every node mesh is an instance, so a source with no instances draws nothing.
145
152
  * @param glowLayer - The scene's glow layer
146
153
  * @param scene - The Babylon.js scene
147
154
  */
@@ -155,8 +162,8 @@ export declare class NodeEffects {
155
162
  * with one outline configuration -- and resolving to it here would take the outline away from
156
163
  * every sibling still on screen. That is the same reasoning `Node.dispose` records for glow,
157
164
  * and it has the same consequence: for an instanced node this removes nothing, the source is
158
- * freed when `MeshCache` is cleared, and the layer's leftover uniqueId is inert because
159
- * Babylon's uniqueIds are monotonic per scene and never reused.
165
+ * freed when `MeshCache` prunes or clears it, and the layer's leftover uniqueId is inert
166
+ * because Babylon's uniqueIds are monotonic per scene and never reused.
160
167
  * @param mesh - The mesh to remove from highlighting
161
168
  */
162
169
  static removeFromHighlight(mesh: AbstractMesh): void;
@@ -178,10 +185,8 @@ export declare class NodeEffects {
178
185
  * The colour map is keyed by mesh uniqueId, so it MUST die with the layer: leaving it behind
179
186
  * would let a later mesh that happens to reuse a uniqueId inherit a dead style's glow colour.
180
187
  *
181
- * Sibling of {@link NodeEffects.disposeHighlightLayer} and, like it, currently called from
182
- * nowhere in this package -- the scene outlives every dataset, so the layers are reused rather
183
- * than rebuilt. It exists so that whoever tears a scene down has one call for each layer this
184
- * class can create, instead of discovering the glow layer only by leaking it.
188
+ * Called by {@link NodeEffects.syncGlowStrengths} once no node glows; the next glow
189
+ * recreates the layer lazily.
185
190
  * @param scene - The Babylon.js scene
186
191
  */
187
192
  static disposeGlowLayer(scene: Scene): void;
@@ -120,8 +120,8 @@ export declare class NodeMesh {
120
120
  * provenance is invisible here, but unbounded allocation is not.
121
121
  *
122
122
  * The cache is a WeakMap keyed by scene, so the entries die with the scene and never leak
123
- * across scenes or across tests. `MeshCache.clear()` disposes meshes only -- not materials and
124
- * not textures -- so a cached texture stays valid across a 2D/3D switch.
123
+ * across scenes or across tests. `MeshCache` disposes a source mesh's material but never its
124
+ * textures, so a cached texture stays valid across a 2D/3D switch.
125
125
  * @param gradient - The normalised gradient to paint
126
126
  * @param scene - Babylon.js scene that will own the texture
127
127
  * @returns The shared texture, or undefined if one cannot or should not be allocated
@@ -35,3 +35,29 @@ export declare class RichTextParser {
35
35
  totalHeight: number;
36
36
  };
37
37
  }
38
+ /**
39
+ * Measures one line of segments from the font's own metrics.
40
+ *
41
+ * The line's ink height is the tallest ascent plus the deepest descent over its segments, each
42
+ * the larger of the font box (`fontBoundingBoxAscent` / `fontBoundingBoxDescent`) and the actual
43
+ * glyph box, plus the outline on both sides. The line box is that height times `lineHeight`, and
44
+ * the extra leading is split evenly above and below, as CSS does. The label's panel height and
45
+ * the renderer's line advance both come from here, so they cannot disagree.
46
+ * @param ctx - Canvas rendering context for measurement
47
+ * @param lineSegments - The segments of one line
48
+ * @param options - Layout options
49
+ * @param options.lineHeight - Line height multiplier
50
+ * @param options.textOutline - Whether text outline is enabled
51
+ * @param options.textOutlineWidth - Width of text outline
52
+ * @returns The line's advance width, its line box height, and the distance from the top of the
53
+ * line box to the alphabetic baseline
54
+ */
55
+ export declare function measureLine(ctx: CanvasRenderingContext2D, lineSegments: TextSegment[], options: {
56
+ lineHeight: number;
57
+ textOutline: boolean;
58
+ textOutlineWidth: number;
59
+ }): {
60
+ width: number;
61
+ lineBox: number;
62
+ baseline: number;
63
+ };
@@ -43,7 +43,6 @@ export declare class RichTextRenderer {
43
43
  width: number;
44
44
  height: number;
45
45
  }): void;
46
- private measureLine;
47
46
  private calculateLineStartX;
48
47
  private drawLine;
49
48
  private drawTextWithShadow;
@@ -27,7 +27,7 @@
27
27
  * Nothing here reaches Babylon.js, Lit or the DOM: an estimate is arithmetic over plain data,
28
28
  * published from the Node-safe `./session` entry point.
29
29
  */
30
- import type { AlgorithmDescriptor, AlgorithmKey, CostClass, Scope } from "../../catalog/types";
30
+ import { type AlgorithmDescriptor, type AlgorithmKey, type CostClass, type Scope } from "../../catalog/types";
31
31
  import { GraphtyError } from "../../errors/GraphtyError";
32
32
  import type { GraphStatistics } from "../types";
33
33
  /**
@@ -29,8 +29,8 @@ export interface LayoutRecommendation {
29
29
  * The arrangement, exactly as `catalog.layouts()` publishes it.
30
30
  *
31
31
  * `layout.id` is the public arrangement name -- `"force"`, `"circular"` -- and `layout.engine`
32
- * is the registered engine name `setLayout` takes. A consumer needs the second in order to
33
- * act: `element.setLayout(recommendation.layout.engine)`.
32
+ * is the registered engine that runs it by default. `setLayout` takes either, so a consumer
33
+ * acts on the id: `element.setLayout(recommendation.layout.id)`.
34
34
  */
35
35
  readonly layout: LayoutDescriptor;
36
36
  /** Why this arrangement suits this graph, in a sentence a consumer can show a reader. */
@@ -80,7 +80,7 @@ export interface LayoutRecommendationOptions {
80
80
  * });
81
81
  *
82
82
  * if (advice !== undefined) {
83
- * await element.setLayout(advice.layout.engine);
83
+ * await element.setLayout(advice.layout.id);
84
84
  * }
85
85
  * ```
86
86
  */
@@ -18,7 +18,7 @@
18
18
  * structural view of `OperationQueueManager` narrow enough that the session never imports the
19
19
  * renderer's manager to talk to it.
20
20
  */
21
- import type { AlgorithmDescriptor, LayerId, RunId, Scope } from "../../catalog/types";
21
+ import { type AlgorithmDescriptor, type LayerId, type RunId, type Scope } from "../../catalog/types";
22
22
  import type { AutoApplyPolicy } from "../styles/autoApply";
23
23
  import { type RunExecutor, type RunQueueContext } from "./Run";
24
24
  import { type Caveats, type EngineVersions, type ResolvedScope, type RunChange, type RunsApi } from "./types";
@@ -493,6 +493,12 @@ export interface StylesSources {
493
493
  * @param change - What changed, and how much was painted.
494
494
  */
495
495
  readonly onChange?: (change: StyleChange) => void;
496
+ /**
497
+ * Aborted when the session holding the stack is disposed. Every edit still pending is
498
+ * cancelled with it, so a caller awaiting one gets an `AbortError` rather than waiting for
499
+ * ever, and an edit issued afterwards is cancelled before it starts.
500
+ */
501
+ readonly disposed?: AbortSignal;
496
502
  }
497
503
  /**
498
504
  * What a highlight paints when its caller names no style.
@@ -26,9 +26,17 @@
26
26
  * does today. {@link channelRole} is where that split is written down, and
27
27
  * {@link meshChannelsFor} is the list a repaint pushes.
28
28
  *
29
- * A KEY IS OPAQUE AND SESSION-LOCAL. It is the order a style was first seen in, so it is stable
30
- * for the life of the session and meaningless outside it. Nothing may persist one, compare two
31
- * from different sessions, or read anything back out of the number.
29
+ * A KEY IS OPAQUE AND SESSION-LOCAL. It names one style for the life of the session and is
30
+ * meaningless outside it. Nothing may persist one, compare two from different sessions, or read
31
+ * anything back out of the number.
32
+ *
33
+ * A KEY IS RELEASED WHEN NOTHING IS DRAWN FROM IT, AND NEVER HANDED OUT AGAIN. The repaint counts
34
+ * the elements drawn from each key ({@link StyleInterner.retain}, {@link StyleInterner.release}),
35
+ * and a style no element holds is forgotten, so what the interner keeps follows the looks on
36
+ * screen rather than every edit ever made. The number is not recycled: a renderer names a cached
37
+ * source mesh after it, and a recycled key would hand one look's mesh to another. The storage
38
+ * behind a key IS recycled, which is what keeps a session that edits a size a thousand times
39
+ * the size of the picture it draws.
32
40
  *
33
41
  * Nothing here reaches Babylon.js, Lit or the DOM.
34
42
  */
@@ -76,7 +84,7 @@ export declare function meshChannelsFor(target: "node" | "edge"): readonly Chann
76
84
  * into the interner.
77
85
  */
78
86
  export interface StyleInterner<T> {
79
- /** How many distinct styles have been minted. Keys run from 0 to this less one. */
87
+ /** How many distinct styles are held: minted and not yet released. */
80
88
  readonly size: number;
81
89
  /** Start a new sequence, discarding anything pushed and not ended. */
82
90
  begin(): void;
@@ -105,9 +113,22 @@ export interface StyleInterner<T> {
105
113
  /**
106
114
  * Finish the sequence and answer with the key the style it describes is known by.
107
115
  * @param mint - Builds the style object, called ONLY when the sequence is new.
108
- * @returns The key: the order the style was first seen in.
116
+ * @returns The key, which no other style is ever given in this interner.
109
117
  */
110
118
  end(mint: () => T): number;
119
+ /**
120
+ * Say that one more element is drawn from a key.
121
+ * @param key - The key, as {@link end} answered with.
122
+ */
123
+ retain(key: number): void;
124
+ /**
125
+ * Say that one element fewer is drawn from a key, and forget its style when none is left.
126
+ *
127
+ * A style that has been minted and never retained is held until something retains and then
128
+ * releases it: minting is a lookup, and a lookup must not take a style away.
129
+ * @param key - The key, as {@link end} answered with.
130
+ */
131
+ release(key: number): void;
111
132
  /**
112
133
  * The style one key stands for.
113
134
  * @param key - The key, as {@link end} answered with.
@@ -168,7 +168,8 @@ export interface ElementPaint {
168
168
  *
169
169
  * This is the number a structural hash exists to keep small: it must follow the distinct
170
170
  * SHAPES and SIZES in the picture, never the element count, and a colour encoding must not
171
- * move it.
171
+ * move it. A mesh no element is drawn from any more is not counted: its key is released and
172
+ * never handed out again.
172
173
  * @param target - Nodes or edges.
173
174
  * @returns The count, which is at least one.
174
175
  */
@@ -15,7 +15,7 @@
15
15
  import type { DerivedGraph, GraphSnapshot, NodeId } from "@graphty/graph-format";
16
16
  import type { z } from "zod/v4";
17
17
  import type { AccelerationCapabilities, AccelerationPolicy, AccelerationStatus, GraphAccelerator } from "../acceleration";
18
- import type { AlgorithmKey, AttributeDescriptor, CatalogApi, EdgeId, RunId, Scope } from "../catalog/types";
18
+ import type { AlgorithmKey, AttributeDescriptor, CatalogApi, DeprecatedCatalogMethod, EdgeId, RunId, Scope } from "../catalog/types";
19
19
  import type { DataConfig } from "../config/DataConfig";
20
20
  import type { ElementPositions } from "../data/positions";
21
21
  import type { ImportReport } from "../data/report";
@@ -347,12 +347,12 @@ export interface SessionDataApi {
347
347
  * been run. A consumer wanting only the metrics that CAN run filters on `available`; both lists
348
348
  * come off one call rather than two that could disagree.
349
349
  *
350
- * The rest of the graph-dependent half of {@link CatalogApi} -- what an option's bounds resolve to
351
- * over a scope, whether an expression references anything real -- is still absent rather than
352
- * stubbed: the session's query engine exists, and the catalogue is not wired to it yet. A consumer discovers that gap by
353
- * autocomplete finding nothing, not by a call that throws.
350
+ * The rest of {@link CatalogApi} -- the methods named in `DeprecatedCatalogMethod` -- is absent
351
+ * rather than stubbed, and deprecated on `CatalogApi` itself. A consumer discovers that gap by
352
+ * autocomplete finding nothing, not by a call that throws. This type is derived from that list,
353
+ * so implementing one of them means deleting its name there and nothing here.
354
354
  */
355
- export type SessionCatalogApi = Pick<CatalogApi, "algorithms" | "cameras" | "formats" | "layouts" | "logSinks" | "metrics" | "palettes" | "scales">;
355
+ export type SessionCatalogApi = Omit<CatalogApi, DeprecatedCatalogMethod>;
356
356
  /** The configuration a session carries. */
357
357
  export interface SessionConfig {
358
358
  /** The data configuration: id paths, weight paths, position scale, direction, id coercion. */
package/dist/webgpu.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { EXACT_MAX_NODES as n, createAccelerator as p, verifyDevice as u } from "@graphty/webgpu-graph-algorithms";
2
2
  import { probeBrowserWebGpu as l, requestGpuContext as d } from "@graphty/webgpu-graph-algorithms/browser";
3
- import { r as h } from "./chunks/registry-CSba5QGJ.js";
4
- import { G as s } from "./chunks/GraphtyError-BwcnblTH.js";
3
+ import { r as h } from "./chunks/registry-jB46Gmeb.js";
4
+ import { G as s } from "./chunks/GraphtyError-B93WRH3e.js";
5
5
  const a = "webgpu-graph-algorithms", w = /* @__PURE__ */ new Set(["kind", "ctx", "options", "dispose", "verify"]);
6
6
  function b(o, e) {
7
7
  for (const [r, t] of Object.entries(o))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphty/graphty-element",
3
- "version": "2.3.1",
3
+ "version": "2.4.0",
4
4
  "description": "A Web Component library for 3D/2D graph visualization built with Lit and Babylon.js",
5
5
  "type": "module",
6
6
  "customElements": "./dist/custom-elements.json",
@@ -134,7 +134,7 @@
134
134
  "vitepress": "^1.6.3",
135
135
  "vitest": "^3.2.4",
136
136
  "@graphty/remote-logger": "^1.3.7",
137
- "@graphty/webgpu-graph-algorithms": "^0.6.5"
137
+ "@graphty/webgpu-graph-algorithms": "^0.6.6"
138
138
  },
139
139
  "peerDependencies": {
140
140
  "@ai-sdk/anthropic": "^2.0.50",
@@ -146,7 +146,7 @@
146
146
  "ai": "^5.0.104",
147
147
  "encrypt-storage": "^2.14.7",
148
148
  "lit": "^3.0.0",
149
- "@graphty/webgpu-graph-algorithms": "^0.6.5"
149
+ "@graphty/webgpu-graph-algorithms": "^0.6.6"
150
150
  },
151
151
  "peerDependenciesMeta": {
152
152
  "@ai-sdk/anthropic": {
@@ -184,9 +184,9 @@
184
184
  "papaparse": "^5.5.3",
185
185
  "toposort": "^2.0.2",
186
186
  "zod": "^3.25.28",
187
- "@graphty/algorithms": "^2.0.3",
188
- "@graphty/graph-format": "^1.0.5",
189
- "@graphty/layout": "^1.9.1"
187
+ "@graphty/layout": "^1.10.0",
188
+ "@graphty/algorithms": "^2.0.4",
189
+ "@graphty/graph-format": "^1.0.5"
190
190
  },
191
191
  "overrides": {
192
192
  "storybook": "$storybook"