virtual-tree-canvas 0.3.9 → 0.5.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 (36) hide show
  1. package/README.md +173 -225
  2. package/docs/inspector-demo.png +0 -0
  3. package/docs/tree-demo.png +0 -0
  4. package/package.json +2 -1
  5. package/src/assets/icons/air.svg +1 -0
  6. package/src/assets/icons/aircraft.svg +1 -0
  7. package/src/assets/icons/bus.svg +1 -0
  8. package/src/assets/icons/control.svg +1 -0
  9. package/src/assets/icons/damage.svg +1 -0
  10. package/src/assets/icons/error.svg +1 -0
  11. package/src/assets/icons/folder.svg +1 -0
  12. package/src/assets/icons/ground.svg +1 -0
  13. package/src/assets/icons/inspector-array.svg +1 -0
  14. package/src/assets/icons/inspector-object.svg +1 -0
  15. package/src/assets/icons/inspector-value.svg +1 -0
  16. package/src/assets/icons/munition.svg +1 -0
  17. package/src/assets/icons/placeholder.svg +1 -0
  18. package/src/assets/icons/point.svg +1 -0
  19. package/src/assets/icons/radar.svg +1 -0
  20. package/src/assets/icons/situation.svg +1 -0
  21. package/src/assets/icons/space.svg +1 -0
  22. package/src/assets/icons/subsurface.svg +1 -0
  23. package/src/assets/icons/surface.svg +1 -0
  24. package/src/assets/icons/task.svg +1 -0
  25. package/src/assets/icons/track.svg +1 -0
  26. package/src/assets/icons/warning.svg +1 -0
  27. package/src/assets/icons.js +25 -0
  28. package/src/core/icon-registry.js +173 -495
  29. package/src/core/theme-manager.js +1 -0
  30. package/src/core/tree-worker-client.js +17 -0
  31. package/src/index.d.ts +17 -0
  32. package/src/inspector/cell-editor-manager.js +23 -18
  33. package/src/inspector/editor-resolver.js +6 -1
  34. package/src/inspector/numeric-layout.js +16 -0
  35. package/src/renderers/tree-row-renderer.js +56 -4
  36. package/src/tree-view-controller.js +13 -5
package/README.md CHANGED
@@ -1,24 +1,23 @@
1
1
  # virtual-tree-canvas
2
2
 
3
- `virtual-tree-canvas` is a framework-agnostic Canvas2D virtual tree/table widget for large hierarchical datasets.
3
+ **A fast, framework-agnostic tree and tree-table for large hierarchical data.**
4
4
 
5
- It behaves like a normal TreeView or tree-table component, but renders into a canvas instead of creating one DOM element per row.
5
+ `virtual-tree-canvas` gives applications the interactions users expect from a
6
+ native tree view—expandable branches, columns, selection, search, filtering and
7
+ keyboard navigation—without creating a DOM node for every row. It renders only
8
+ what is visible with Canvas2D, so it stays responsive as data and live updates
9
+ grow.
6
10
 
7
- ## Features
11
+ ![Editable model inspector built with virtual-tree-canvas](./docs/inspector-demo.png)
8
12
 
9
- - Virtualized tree rows
10
- - Tree-table columns
11
- - Expand/collapse
12
- - Single and multi-selection
13
- - Search and focus
14
- - Keyboard navigation
15
- - Horizontal and vertical scrolling
16
- - Themes and type-based styles
17
- - Canvas vector icons and image icons
18
- - Batched dynamic state updates
19
- - Benchmark/demo mode
13
+ ## Why use it?
20
14
 
21
- No React, Web Components, or frontend framework required.
15
+ - **Built for large trees.** Only visible rows plus a small overscan range are painted.
16
+ - **Interactive by default.** Tree-table columns, sorting, resizing, selection, focus, tooltips and keyboard navigation are included.
17
+ - **Good for live data.** Batched state patches update status, progress and values without rebuilding the tree.
18
+ - **Framework-free.** Use it directly from browser JavaScript; no React or Web Component runtime is required.
19
+ - **Easy to style.** Built-in dark, light and tactical themes, type-based styling, and editable SVG icons.
20
+ - **Ready for inspectors.** Turn plain JavaScript objects into compact editable forms or property tables.
22
21
 
23
22
  ## Install
24
23
 
@@ -26,165 +25,85 @@ No React, Web Components, or frontend framework required.
26
25
  npm install virtual-tree-canvas
27
26
  ```
28
27
 
29
- ## Performance Model
28
+ ## Quick start
30
29
 
31
- The renderer only draws visible rows plus a small overscan range.
32
-
33
- Dynamic updates are handled as patches:
34
-
35
- ```js
36
- tree.setDynamicState([
37
- { id: 'node-1', state: { status: 1, progress: 0.7, value: 42 } }
38
- ]);
30
+ ```html
31
+ <canvas id="assets-tree"></canvas>
39
32
  ```
40
33
 
41
- Dynamic patches update node state without rebuilding:
42
-
43
- - tree indexes
44
- - visible rows
45
- - expansion state
46
- - layout
47
-
48
- Cold-path operations such as `setData()`, expand/collapse, and search may rebuild the visible row list.
49
-
50
- For large datasets, `enableWorkers()` moves search and filtered row rebuilds off the main thread when browser Workers are available. The synchronous APIs remain available for small datasets, tests, and custom integrations.
51
-
52
- ## Basic Usage
34
+ ```css
35
+ #assets-tree {
36
+ display: block;
37
+ width: 100%;
38
+ height: 480px;
39
+ }
40
+ ```
53
41
 
54
42
  ```js
55
43
  import { TreeViewController } from 'virtual-tree-canvas';
56
44
 
57
- const canvas = document.querySelector('canvas');
58
- const tree = new TreeViewController({ canvas });
45
+ const tree = new TreeViewController({
46
+ canvas: document.querySelector('#assets-tree'),
47
+ initialExpandDepth: 2,
48
+ });
59
49
 
60
50
  tree.setData([
61
- { id: 'root', label: 'Root', type: 'root' },
62
- { id: 'child-1', parentId: 'root', label: 'Child', type: 'sensor' }
51
+ { id: 'fleet', label: 'Fleet', type: 'root' },
52
+ { id: 'radar-1', parentId: 'fleet', label: 'Forward radar', type: 'sensor' },
53
+ { id: 'track-100', parentId: 'radar-1', label: 'Track T-100', type: 'track' },
63
54
  ]);
64
- ```
65
-
66
- ## Public API
67
-
68
- ```js
69
- tree.setData(nodes);
70
- tree.setModel(model, meta, { presentation: 'pane' });
71
- tree.setDynamicState(patches);
72
-
73
- tree.expand(nodeId);
74
- tree.collapse(nodeId);
75
- tree.toggle(nodeId);
76
- tree.expandAll();
77
- tree.collapseAll();
78
-
79
- tree.search(query);
80
- tree.searchAsync(query);
81
- tree.clearSearch();
82
- tree.getSearchState();
83
- tree.nextSearchResult();
84
- tree.previousSearchResult();
85
- tree.setFilter(queryOrPredicate);
86
- tree.setFilterAsync(query);
87
- tree.clearFilter();
88
-
89
- tree.focusNode(nodeId);
90
- tree.scrollToNode(nodeId, 'center');
91
-
92
- tree.getSelection();
93
- tree.setSelection(['node-1', 'node-2']);
94
- tree.clearSelection();
95
-
96
- tree.setTheme(theme);
97
- tree.setColumns(columns);
98
- tree.resizeColumn(columnId, width);
99
- tree.moveColumn(columnId, targetIndex);
100
- tree.sortBy(columnId, 'asc');
101
- tree.clearSort();
102
- tree.registerIcon('custom', imageOrUrlOrDrawFunction);
103
-
104
- tree.enableWorkers();
105
- tree.disableWorkers();
106
- ```
107
55
 
108
- ## Model Inspector
109
-
110
- `setModel(model, meta, options)` renders plain JSON-like objects as an editable inspector.
56
+ tree.setDynamicState([
57
+ { id: 'radar-1', state: { status: 0, progress: 0.72 } },
58
+ { id: 'track-100', state: { status: 1, value: 430 } },
59
+ ]);
111
60
 
112
- ```js
113
- tree.setModel(
114
- {
115
- sensor: { enabled: true, range: 72, mode: 'track' },
116
- tracks: [{ id: 'T-100', speed: 430 }]
117
- },
118
- {
119
- 'sensor.range': { min: 0, max: 120, step: 1, integer: true },
120
- 'sensor.mode': { options: { Search: 'search', Track: 'track' } },
121
- 'tracks.*.speed': { min: 0, max: 900, step: 5 },
122
- 'tracks.*.id': { readonly: true }
123
- }
124
- );
61
+ tree.render();
125
62
  ```
126
63
 
127
- Inspector options:
64
+ The controller observes the canvas size. Call `tree.render()` from your own
65
+ animation loop when the data is live, or after changing data in a static view.
128
66
 
129
- ```js
130
- tree.setModel(model, meta, {
131
- presentation: 'pane',
132
- flatRoot: true, // render root properties directly
133
- enforceMeta: true, // fields without metadata are readonly/disabled
134
- filter: true, // use the header as a filter input
135
- markUpdated: false // do not show update dots for local user edits
136
- });
137
- ```
67
+ ![Virtual tree-table with status, progress and SVG icons](./docs/tree-demo.png)
138
68
 
139
- Presentations:
69
+ ## Common tasks
140
70
 
141
71
  ```js
142
- tree.setModel(model, meta, { presentation: 'pane' }); // compact folders + key/value controls
143
- tree.setModel(model, meta, { presentation: 'table' }); // Property | Value | Type | Description
144
- ```
145
-
146
- Metadata is path-based. Dot paths target object properties, and array items use numeric indexes or `*` wildcards:
147
-
148
- ```text
149
- sensor.range
150
- tracks.0.speed
151
- tracks.*.speed
152
- ```
153
-
154
- Inspector editors are inferred from values and metadata: checkbox, range, number, text, select, color, button, object, and array.
72
+ // Navigate and select
73
+ tree.expand('radar-1');
74
+ tree.focusNode('track-100', { align: 'center', select: true });
75
+ tree.setSelection(['track-100']);
76
+
77
+ // Search and filter
78
+ tree.search('forward radar');
79
+ tree.setFilter('track');
80
+ tree.clearFilter();
155
81
 
156
- Inspector events:
82
+ // Configure the tree-table
83
+ tree.setColumns(columns);
84
+ tree.sortBy('status', 'asc');
85
+ tree.resizeColumn('name', 320);
157
86
 
158
- ```js
159
- tree.on('valuechange', (event) => {});
160
- tree.on('modelchange', (event) => {});
161
- tree.on('action', (event) => {});
87
+ // Use a worker for expensive search/filter work
88
+ await tree.enableWorkers();
162
89
  ```
163
90
 
164
- `scrollToNode()` supports:
165
-
166
- ```text
167
- start | center | end | nearest
168
- ```
91
+ ## Live updates and performance
169
92
 
170
- ## Events
93
+ Use `setDynamicState()` for values that change frequently. It updates the
94
+ dynamic state of affected nodes while retaining indexes, expansion state and
95
+ visible rows.
171
96
 
172
97
  ```js
173
- tree.on('nodehover', (event) => {});
174
- tree.on('nodeclick', (event) => {});
175
- tree.on('nodedblclick', (event) => {});
176
- tree.on('selectionchange', (event) => {});
177
- tree.on('expand', (event) => {});
178
- tree.on('collapse', (event) => {});
179
- tree.on('focuschange', (event) => {});
180
- tree.on('searchchange', (event) => {});
181
- tree.on('filterchange', (event) => {});
182
- tree.on('sortchange', (event) => {});
183
- tree.on('columnschange', (event) => {});
184
- tree.on('viewportchange', (event) => {});
98
+ tree.setDynamicState([
99
+ { id: 'track-100', state: { status: 1, progress: 0.7, value: 42 } },
100
+ ]);
185
101
  ```
186
102
 
187
- The event payload is available as `event.detail`.
103
+ `setData()`, expand/collapse, sorting and filtering are structural operations
104
+ and may rebuild the visible row list. For very large datasets,
105
+ `enableWorkers()` moves search and filtered row rebuilds off the main thread
106
+ when Workers are available.
188
107
 
189
108
  ## Columns
190
109
 
@@ -195,9 +114,8 @@ tree.setColumns([
195
114
  label: 'Name',
196
115
  width: 340,
197
116
  minWidth: 160,
198
- align: 'left',
199
117
  kind: 'tree',
200
- value: (node) => node.label ?? node.id
118
+ value: (node) => node.label ?? node.id,
201
119
  },
202
120
  {
203
121
  id: 'status',
@@ -206,128 +124,158 @@ tree.setColumns([
206
124
  minWidth: 64,
207
125
  align: 'center',
208
126
  kind: 'status',
209
- value: (_node, state) => state.status
210
- }
127
+ value: (_node, state) => state.status,
128
+ },
129
+ {
130
+ id: 'progress',
131
+ label: 'Progress',
132
+ width: 140,
133
+ kind: 'progress',
134
+ value: (_node, state) => state.progress,
135
+ },
211
136
  ]);
212
137
  ```
213
138
 
214
- Column shape:
139
+ Available column kinds are `tree`, `status`, `value`, `progress`, `type`,
140
+ `updated` and `text`. A column can also provide `render(ctx, cell)` for custom
141
+ Canvas2D content. Import `builtInColumns` or `defaultTreeTableColumns` to start
142
+ from the default set.
215
143
 
216
- ```js
217
- {
218
- id: 'status',
219
- label: 'Status',
220
- width: 80,
221
- minWidth: 40,
222
- align: 'left' | 'center' | 'right',
223
- kind: 'tree' | 'status' | 'value' | 'progress' | 'type' | 'updated' | 'text',
224
- sortable: true,
225
- value: (node, state) => string | number,
226
- render: (ctx, cell) => {}
227
- }
228
- ```
144
+ ## Themes and icons
229
145
 
230
- Built-in helpers:
146
+ Three themes are included:
231
147
 
232
148
  ```js
233
- import { builtInColumns, defaultTreeTableColumns } from 'virtual-tree-canvas';
234
- ```
149
+ import { darkTheme, lightTheme, tacticalTheme } from 'virtual-tree-canvas';
235
150
 
236
- The tree column renders indentation, chevron, icon, and label. Other columns render table cells and participate in horizontal scrolling.
237
-
238
- Columns can be resized, reordered, and sorted through the controller API. The demo also supports header click sorting and drag-to-resize on column edges.
151
+ tree.setTheme(tacticalTheme);
152
+ ```
239
153
 
240
- ## Themes
154
+ Themes can map domain types to a colour and icon:
241
155
 
242
156
  ```js
243
157
  tree.setTheme({
244
158
  rowHeight: 28,
245
- indentWidth: 18,
246
159
  font: '12px system-ui',
247
- colors: {
248
- background: '#0b1020',
249
- row: '#0b1020',
250
- rowHover: '#111827',
251
- rowSelected: '#1e3a8a',
252
- rowHighlighted: '#3b0764',
253
- text: '#e5e7eb',
254
- textMuted: '#94a3b8',
255
- guide: '#1f2937',
256
- chevron: '#94a3b8',
257
- focus: '#38bdf8',
258
- progressTrack: '#1f2937',
259
- progressFill: '#22c55e',
260
- badgeText: '#ffffff'
261
- },
262
160
  types: {
263
161
  root: { icon: 'folder', color: '#38bdf8' },
264
162
  platform: { icon: 'aircraft', color: '#60a5fa' },
265
163
  sensor: { icon: 'radar', color: '#34d399' },
266
164
  warning: { icon: 'warning', color: '#facc15' },
267
- error: { icon: 'error', color: '#ef4444' }
268
165
  },
269
- statuses: {
270
- 0: { label: 'OK', color: '#22c55e' },
271
- 1: { label: 'WARN', color: '#facc15' },
272
- 2: { label: 'ERR', color: '#ef4444' }
273
- }
274
166
  });
275
167
  ```
276
168
 
277
- Built-in theme exports:
169
+ Built-in icons are editable SVG files in [`src/assets/icons`](./src/assets/icons).
170
+ They are preloaded and rasterized once per icon, colour, CSS size and device
171
+ pixel ratio. Rendering rows then uses a cached `drawImage`, not SVG parsing or
172
+ path drawing.
173
+
174
+ Register application icons from an SVG URL, inline SVG or an image:
278
175
 
279
176
  ```js
280
- import { themes, darkTheme, lightTheme, tacticalTheme } from 'virtual-tree-canvas';
177
+ tree.registerIcon('camera', '/icons/camera.svg');
178
+ tree.registerIcon('satellite', '<svg viewBox="0 0 24 24"><path fill="currentColor" d="..."/></svg>');
179
+ tree.registerIcon('logo', imageElement);
281
180
  ```
282
181
 
283
- Style resolution order:
182
+ Use `currentColor` in an SVG to inherit the type or dynamic-state colour.
183
+ `tree.iconRegistry.prepare({ icons, size, color, pixelRatio })` is also
184
+ available when an application wants to warm a known icon set before display.
284
185
 
285
- 1. Dynamic state override, such as `state.color`
286
- 2. Node type rule, such as `theme.types.sensor`
287
- 3. Default theme color
186
+ ## Model inspector
288
187
 
289
- ## Icons
188
+ `setModel()` renders JSON-like data as an editable inspector. Metadata controls
189
+ the editor, bounds, choices, labels and descriptions.
290
190
 
291
- Built-in Canvas2D vector icons:
292
-
293
- ```text
294
- folder, aircraft, radar, warning, error, task, track, placeholder
191
+ ```js
192
+ tree.setModel(
193
+ {
194
+ sensor: { enabled: true, range: 72, mode: 'track' },
195
+ tracks: [{ id: 'T-100', speed: 430 }],
196
+ },
197
+ {
198
+ 'sensor.range': { min: 0, max: 120, step: 1, integer: true },
199
+ 'sensor.mode': { options: { Search: 'search', Track: 'track' } },
200
+ 'tracks.*.speed': { min: 0, max: 900, step: 5, unit: 'm/s', precision: 2 },
201
+ 'tracks.*.id': { readonly: true },
202
+ },
203
+ { presentation: 'pane', flatRoot: true },
204
+ );
295
205
  ```
296
206
 
297
- Register custom icons:
207
+ Choose `presentation: 'pane'` for a compact folder-and-controls view, or
208
+ `presentation: 'table'` for Property / Value / Type / Description columns.
209
+ Editors are inferred from data and metadata: checkbox, range, number, text,
210
+ select, colour, button, object and array.
211
+
212
+ Numeric metadata supports `unit` (a display-only string suffix) and `precision`
213
+ (decimal places, 0–20). The unit has a reserved slot beside the number in both
214
+ pane and table presentations, including range sliders. It stays visible while
215
+ editing; the editor receives the full original numeric value and emits a number.
216
+ Precision changes display only, not stored values, limits or step size. Omit both
217
+ options to keep the existing formatting. Invalid precision values are ignored.
218
+ Customize the suffix with `theme.unitFont` and `theme.colors.unit` (defaults to
219
+ `textMuted`). Long units are truncated to fit the available cell width.
220
+
221
+ Listen for user edits and actions with `valuechange`, `modelchange` and
222
+ `action` events.
223
+
224
+ ## Events
298
225
 
299
226
  ```js
300
- tree.registerIcon('camera', imageElement);
301
- tree.registerIcon('camera-url', '/icons/camera.png');
302
- tree.registerIcon('custom-vector', (ctx, x, y, size, color) => {
303
- ctx.fillStyle = color;
304
- ctx.fillRect(x, y, size, size);
227
+ tree.on('nodeclick', (event) => {
228
+ console.log(event.detail.nodeId);
305
229
  });
230
+ tree.on('selectionchange', (event) => {});
231
+ tree.on('expand', (event) => {});
232
+ tree.on('searchchange', (event) => {});
233
+ tree.on('filterchange', (event) => {});
234
+ tree.on('valuechange', (event) => {});
306
235
  ```
307
236
 
308
- Icons are cached by name and drawn only for rendered rows.
237
+ Other events include `nodehover`, `nodedblclick`, `collapse`, `focuschange`,
238
+ `sortchange`, `columnschange`, `viewportchange`, `modelchange` and `action`.
239
+ The payload is always available as `event.detail`.
309
240
 
310
- ## Demo
241
+ ## API overview
311
242
 
312
- ```bash
313
- npm run demo
314
- ```
243
+ ```js
244
+ tree.setData(nodes);
245
+ tree.setDynamicState(patches);
246
+ tree.setTheme(theme);
247
+ tree.setColumns(columns);
248
+
249
+ tree.expand(nodeId);
250
+ tree.collapse(nodeId);
251
+ tree.toggle(nodeId);
252
+ tree.expandAll();
253
+ tree.collapseAll();
254
+
255
+ tree.search(query, { caseSensitive, wholeWord });
256
+ tree.setFilter(queryOrPredicate, { caseSensitive, wholeWord });
257
+ tree.clearSearch();
258
+ tree.clearFilter();
315
259
 
316
- Open:
260
+ tree.focusNode(nodeId, { align: 'start' | 'center' | 'end' | 'nearest' });
261
+ tree.scrollToNode(nodeId, 'center');
262
+ tree.scrollTo(x, y);
317
263
 
318
- ```text
319
- http://localhost:4173/demo/
264
+ tree.setSelection(ids);
265
+ tree.getSelection();
266
+ tree.registerIcon(name, svgOrImage);
267
+ tree.enableWorkers();
268
+ tree.disableWorkers();
320
269
  ```
321
270
 
322
- Inspector demo:
271
+ ## Demo
323
272
 
324
- ```text
325
- http://localhost:4173/demo/inspector.html
273
+ ```bash
274
+ npm run demo
326
275
  ```
327
276
 
328
- The demo includes dataset sizes, update rates, benchmark stats, search, filtering, selection, expand/collapse, themes, and tree-table columns.
329
-
330
- Benchmark stats separate frame, patch, scene, render, search, filter, and worker timings. Use "Copy JSON" to export the current sample.
277
+ Open <http://localhost:4173/demo/> for the large-tree benchmark, or
278
+ <http://localhost:4173/demo/inspector.html> for the editable inspector demo.
331
279
 
332
280
  ## Tests
333
281
 
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "virtual-tree-canvas",
3
- "version": "0.3.9",
3
+ "version": "0.5.0",
4
4
  "description": "High-performance Canvas2D virtual tree/table widget for very large hierarchical datasets.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -20,6 +20,7 @@
20
20
  },
21
21
  "files": [
22
22
  "src",
23
+ "docs",
23
24
  "README.md",
24
25
  "LICENSE"
25
26
  ],
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2.5l1.4 8.1 7.2 4.2-7.9-1.3L12 21.5l-.7-8-7.9 1.3 7.2-4.2z"/><path d="M9.8 18.8L6.5 21M14.2 18.8l3.3 2.2" fill="none"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2.5l1.4 8.1 7.2 4.2-7.9-1.3L12 21.5l-.7-8-7.9 1.3 7.2-4.2z"/><path d="M9.8 18.8L6.5 21M14.2 18.8l3.3 2.2" fill="none"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 4h12v13H6zM4 8h2m12 0h2M6 17l-2 3m14-3 2 3"/><path d="M8 7h3v4H8zm5 0h3v4h-3zM8 15h.01M16 15h.01"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round" stroke-linecap="round"><path d="M4 17h16l2 3H2zM12 17l-2-8"/><circle cx="9.5" cy="7" r="2" fill="currentColor"/><circle cx="16" cy="19" r=".8" fill="currentColor"/><circle cx="19" cy="19" r=".8" fill="currentColor"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-linejoin="round"><path d="m12 2 2.5 5L21 5l-3 5 4 3-5.5 1L17 21l-5-3-5 3 .5-7L2 13l4-3-3-5 6.5 2z"/><circle cx="12" cy="12" r="2.2" fill="#fff"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"><circle cx="12" cy="12" r="8.5"/><path d="m9 9 6 6m0-6-6 6"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M3 6.5A2.5 2.5 0 0 1 5.5 4H10l2.1 2H18.5A2.5 2.5 0 0 1 21 8.5v8A2.5 2.5 0 0 1 18.5 19h-13A2.5 2.5 0 0 1 3 16.5z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M4 10h12l3 3v5H4zm4-3h7v3H8zM6.5 17a2 2 0 1 0 0 4 2 2 0 0 0 0-4m10 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="8" cy="12" r="4"/><circle cx="16" cy="12" r="4"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"><rect x="5" y="5" width="14" height="14" rx="1"/><path d="M10 5v14m4-14v14"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="7"/><circle cx="12" cy="12" r="2.5" fill="currentColor" stroke="none"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-linejoin="round"><path d="m12 2 3 5v10l-3 5-3-5V7z"/><path d="m9 14-6 5 6-1m6-4 6 5-6-1" fill="none" stroke-width="2" stroke-linecap="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"><rect x="4" y="4" width="16" height="16" rx="2"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="3" fill="currentColor"/><circle cx="12" cy="12" r="8"/><path d="M12 2v3m0 14v3M2 12h3m14 0h3"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 12l6.5-5M12 12a7 7 0 1 0 7 7"/><path d="M12 5a7 7 0 0 1 7 7"/><circle cx="12" cy="12" r="1.5" fill="currentColor" stroke="none"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="4" opacity=".55"/><path d="M3 12h18M12 3v18m0-9 6-5"/><circle cx="16" cy="8.5" r="1" fill="currentColor" stroke="none"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"><circle cx="12" cy="12" r="3" fill="currentColor"/><path d="M3 9h5v6H3zm13 0h5v6h-5zM8 12h8M4 18c4-3 12-3 16 0"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"><path d="M3 14c2-3 16-3 18 0-2 3-16 3-18 0M10 8h4v5h-4zM9 7h6"/><path d="M4 20c2-1.5 4-1.5 6 0s4 1.5 6 0" fill="none" stroke-linecap="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"><path d="M3 14h14l4 3-16 2zM9 8h5v6H9zM12 4v4"/><path d="M4 21c2-1.5 4-1.5 6 0s4 1.5 6 0" fill="none" stroke-linecap="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="4" width="14" height="16" rx="2"/><path d="M9 4.5h6M8.5 11.5l2 2 4-4M9 16h6"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="7"/><path d="M12 3v18M3 12h18"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M10.27 3.3a2 2 0 0 1 3.46 0l8.02 13.9A2 2 0 0 1 20.02 20H3.98a2 2 0 0 1-1.73-2.8zM11 9v5h2V9zm0 7v2h2v-2z"/></svg>
@@ -0,0 +1,25 @@
1
+ /** Built-in SVG sources. Use registerIcon(name, urlOrSvg) to add application icons. */
2
+ export const builtinIconUrls = Object.freeze({
3
+ placeholder: new URL('./icons/placeholder.svg', import.meta.url).href,
4
+ folder: new URL('./icons/folder.svg', import.meta.url).href,
5
+ aircraft: new URL('./icons/aircraft.svg', import.meta.url).href,
6
+ radar: new URL('./icons/radar.svg', import.meta.url).href,
7
+ warning: new URL('./icons/warning.svg', import.meta.url).href,
8
+ error: new URL('./icons/error.svg', import.meta.url).href,
9
+ task: new URL('./icons/task.svg', import.meta.url).href,
10
+ bus: new URL('./icons/bus.svg', import.meta.url).href,
11
+ track: new URL('./icons/track.svg', import.meta.url).href,
12
+ point: new URL('./icons/point.svg', import.meta.url).href,
13
+ munition: new URL('./icons/munition.svg', import.meta.url).href,
14
+ air: new URL('./icons/air.svg', import.meta.url).href,
15
+ ground: new URL('./icons/ground.svg', import.meta.url).href,
16
+ surface: new URL('./icons/surface.svg', import.meta.url).href,
17
+ subsurface: new URL('./icons/subsurface.svg', import.meta.url).href,
18
+ space: new URL('./icons/space.svg', import.meta.url).href,
19
+ control: new URL('./icons/control.svg', import.meta.url).href,
20
+ situation: new URL('./icons/situation.svg', import.meta.url).href,
21
+ damage: new URL('./icons/damage.svg', import.meta.url).href,
22
+ 'inspector-object': new URL('./icons/inspector-object.svg', import.meta.url).href,
23
+ 'inspector-array': new URL('./icons/inspector-array.svg', import.meta.url).href,
24
+ 'inspector-value': new URL('./icons/inspector-value.svg', import.meta.url).href,
25
+ });