virtual-tree-canvas 0.7.0 → 0.7.2

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.
@@ -3,7 +3,8 @@ export const darkTheme = {
3
3
  indentWidth: 18,
4
4
  font: '12px Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif',
5
5
  unitFont: '11px ui-monospace, SFMono-Regular, Consolas, monospace',
6
- monoFont: '12px "JetBrains Mono", "Cascadia Mono", "Fira Code", ui-monospace, SFMono-Regular, Consolas, monospace',
6
+ monoFont:
7
+ '12px "JetBrains Mono", "Cascadia Mono", "Fira Code", ui-monospace, SFMono-Regular, Consolas, monospace',
7
8
  colors: {
8
9
  background: '#0a0f1c',
9
10
  row: '#0a0f1c',
@@ -30,46 +31,146 @@ export const darkTheme = {
30
31
  badgeText: '#ffffff',
31
32
  border: '#1b2637',
32
33
  borderStrong: '#2b3b52',
33
- shadow: 'rgba(0,0,0,.24)',
34
+ shadow: 'rgba(0,0,0,.24)'
35
+ },
36
+ valueColors: {
37
+ number: '#8bd5ee',
38
+ bigint: '#8bd5ee',
39
+ string: '#dde5ef',
40
+ boolean: '#86d6b5',
41
+ enum: '#c4b5fd',
42
+ null: '#9aa9bc',
43
+ undefined: '#9aa9bc'
34
44
  },
35
- valueColors: { number: '#8bd5ee', bigint: '#8bd5ee', string: '#dde5ef', boolean: '#86d6b5', enum: '#c4b5fd', null: '#9aa9bc', undefined: '#9aa9bc' },
36
45
  types: {
37
- root: { icon: 'folder', color: '#38bdf8' },
38
- system: { icon: 'folder', color: '#818cf8' },
39
- platform: { icon: 'aircraft', color: '#60a5fa' },
40
- air: { icon: 'aircraft', color: '#60a5fa' },
41
- ground: { icon: 'ground', color: '#a3e635' },
42
- surface: { icon: 'surface', color: '#22d3ee' },
43
- subsurface: { icon: 'subsurface', color: '#38bdf8' },
44
- space: { icon: 'space', color: '#c084fc' },
45
- munition: { icon: 'munition', color: '#fb7185' },
46
- sensor: { icon: 'radar', color: '#34d399' },
47
- track: { icon: 'track', color: '#a78bfa' },
48
- geometry: { icon: 'point', color: '#38bdf8' },
49
- control: { icon: 'control', color: '#f59e0b' },
50
- situation: { icon: 'situation', color: '#2dd4bf' },
51
- damage: { icon: 'damage', color: '#fb7185' },
52
- warning: { icon: 'warning', color: '#facc15' },
53
- error: { icon: 'error', color: '#ef4444' },
54
- task: { icon: 'task', color: '#f97316' },
55
- bus: { icon: 'data-bus', color: '#22d3ee' },
56
- object: { icon: 'inspector-object', color: '#7dd3fc' },
57
- array: { icon: 'inspector-array', color: '#a78bfa' },
58
- string: { icon: 'inspector-value', color: '#94a3b8' },
59
- number: { icon: 'inspector-value', color: '#34d399' },
60
- boolean: { icon: 'inspector-value', color: '#22c55e' },
61
- null: { icon: 'inspector-value', color: '#64748b' },
46
+ root: {
47
+ icon: 'folder',
48
+ color: '#38bdf8'
49
+ },
50
+ system: {
51
+ icon: 'folder',
52
+ color: '#818cf8'
53
+ },
54
+ platform: {
55
+ icon: 'aircraft',
56
+ color: '#60a5fa'
57
+ },
58
+ air: {
59
+ icon: 'aircraft',
60
+ color: '#60a5fa'
61
+ },
62
+ ground: {
63
+ icon: 'ground',
64
+ color: '#a3e635'
65
+ },
66
+ surface: {
67
+ icon: 'surface',
68
+ color: '#22d3ee'
69
+ },
70
+ subsurface: {
71
+ icon: 'subsurface',
72
+ color: '#38bdf8'
73
+ },
74
+ space: {
75
+ icon: 'space',
76
+ color: '#c084fc'
77
+ },
78
+ munition: {
79
+ icon: 'munition',
80
+ color: '#fb7185'
81
+ },
82
+ sensor: {
83
+ icon: 'radar',
84
+ color: '#34d399'
85
+ },
86
+ track: {
87
+ icon: 'track',
88
+ color: '#a78bfa'
89
+ },
90
+ geometry: {
91
+ icon: 'point',
92
+ color: '#38bdf8'
93
+ },
94
+ control: {
95
+ icon: 'control',
96
+ color: '#f59e0b'
97
+ },
98
+ situation: {
99
+ icon: 'situation',
100
+ color: '#2dd4bf'
101
+ },
102
+ damage: {
103
+ icon: 'damage',
104
+ color: '#fb7185'
105
+ },
106
+ warning: {
107
+ icon: 'warning',
108
+ color: '#facc15'
109
+ },
110
+ error: {
111
+ icon: 'error',
112
+ color: '#ef4444'
113
+ },
114
+ task: {
115
+ icon: 'task',
116
+ color: '#f97316'
117
+ },
118
+ bus: {
119
+ icon: 'data-bus',
120
+ color: '#22d3ee'
121
+ },
122
+ object: {
123
+ icon: 'inspector-object',
124
+ color: '#7dd3fc'
125
+ },
126
+ array: {
127
+ icon: 'inspector-array',
128
+ color: '#a78bfa'
129
+ },
130
+ string: {
131
+ icon: 'inspector-value',
132
+ color: '#94a3b8'
133
+ },
134
+ number: {
135
+ icon: 'inspector-value',
136
+ color: '#34d399'
137
+ },
138
+ boolean: {
139
+ icon: 'inspector-value',
140
+ color: '#22c55e'
141
+ },
142
+ null: {
143
+ icon: 'inspector-value',
144
+ color: '#64748b'
145
+ }
62
146
  },
63
147
  statuses: {
64
- 0: { label: 'OK', color: '#22c55e' },
65
- 1: { label: 'WARN', color: '#facc15' },
66
- 2: { label: 'ERR', color: '#ef4444' },
67
- },
148
+ 0: {
149
+ label: 'OK',
150
+ color: '#22c55e'
151
+ },
152
+ 1: {
153
+ label: 'WARN',
154
+ color: '#facc15'
155
+ },
156
+ 2: {
157
+ label: 'ERR',
158
+ color: '#ef4444'
159
+ }
160
+ }
68
161
  };
69
162
 
70
163
  export const lightTheme = {
71
164
  ...darkTheme,
72
- valueColors: { number: '#075985', bigint: '#075985', string: '#182438', boolean: '#176249', enum: '#6b3fa0', null: '#576579', undefined: '#576579' },
165
+ valueColors: {
166
+ number: '#075985',
167
+ bigint: '#075985',
168
+ string: '#182438',
169
+ boolean: '#176249',
170
+ enum: '#6b3fa0',
171
+ null: '#576579',
172
+ undefined: '#576579'
173
+ },
73
174
  colors: {
74
175
  background: '#f8fafc',
75
176
  row: '#ffffff',
@@ -96,8 +197,8 @@ export const lightTheme = {
96
197
  badgeFill: '#e2e8f0',
97
198
  border: '#e2e8f0',
98
199
  borderStrong: '#cbd5e1',
99
- shadow: 'rgba(15,23,42,.1)',
100
- },
200
+ shadow: 'rgba(15,23,42,.1)'
201
+ }
101
202
  };
102
203
 
103
204
  export const tacticalTheme = {
@@ -125,34 +226,85 @@ export const tacticalTheme = {
125
226
  badgeFill: '#102217',
126
227
  border: '#183321',
127
228
  borderStrong: '#295337',
128
- shadow: 'rgba(0,0,0,.3)',
229
+ shadow: 'rgba(0,0,0,.3)'
129
230
  },
130
231
  types: {
131
232
  ...darkTheme.types,
132
- root: { icon: 'folder', color: '#7ddc92' },
133
- platform: { icon: 'aircraft', color: '#93c572' },
134
- air: { icon: 'aircraft', color: '#93c572' },
135
- ground: { icon: 'ground', color: '#c7f36f' },
136
- surface: { icon: 'surface', color: '#67e8f9' },
137
- subsurface: { icon: 'subsurface', color: '#7dd3fc' },
138
- space: { icon: 'space', color: '#d8b4fe' },
139
- munition: { icon: 'munition', color: '#ff8fab' },
140
- sensor: { icon: 'radar', color: '#45d483' },
141
- track: { icon: 'track', color: '#d6f264' },
142
- geometry: { icon: 'point', color: '#7ddc92' },
143
- control: { icon: 'control', color: '#ffd166' },
144
- situation: { icon: 'situation', color: '#8be9d9' },
145
- damage: { icon: 'damage', color: '#ff8fab' },
146
- warning: { icon: 'warning', color: '#ffd166' },
147
- error: { icon: 'error', color: '#ff5c5c' },
148
- bus: { icon: 'data-bus', color: '#67e8f9' },
149
- },
233
+ root: {
234
+ icon: 'folder',
235
+ color: '#7ddc92'
236
+ },
237
+ platform: {
238
+ icon: 'aircraft',
239
+ color: '#93c572'
240
+ },
241
+ air: {
242
+ icon: 'aircraft',
243
+ color: '#93c572'
244
+ },
245
+ ground: {
246
+ icon: 'ground',
247
+ color: '#c7f36f'
248
+ },
249
+ surface: {
250
+ icon: 'surface',
251
+ color: '#67e8f9'
252
+ },
253
+ subsurface: {
254
+ icon: 'subsurface',
255
+ color: '#7dd3fc'
256
+ },
257
+ space: {
258
+ icon: 'space',
259
+ color: '#d8b4fe'
260
+ },
261
+ munition: {
262
+ icon: 'munition',
263
+ color: '#ff8fab'
264
+ },
265
+ sensor: {
266
+ icon: 'radar',
267
+ color: '#45d483'
268
+ },
269
+ track: {
270
+ icon: 'track',
271
+ color: '#d6f264'
272
+ },
273
+ geometry: {
274
+ icon: 'point',
275
+ color: '#7ddc92'
276
+ },
277
+ control: {
278
+ icon: 'control',
279
+ color: '#ffd166'
280
+ },
281
+ situation: {
282
+ icon: 'situation',
283
+ color: '#8be9d9'
284
+ },
285
+ damage: {
286
+ icon: 'damage',
287
+ color: '#ff8fab'
288
+ },
289
+ warning: {
290
+ icon: 'warning',
291
+ color: '#ffd166'
292
+ },
293
+ error: {
294
+ icon: 'error',
295
+ color: '#ff5c5c'
296
+ },
297
+ bus: {
298
+ icon: 'data-bus',
299
+ color: '#67e8f9'
300
+ }
301
+ }
150
302
  };
151
303
 
152
304
  export const themes = {
153
305
  dark: darkTheme,
154
306
  light: lightTheme,
155
- tactical: tacticalTheme,
307
+ tactical: tacticalTheme
156
308
  };
157
309
 
158
310
  export class ThemeManager extends EventTarget {
@@ -170,7 +322,10 @@ export class ThemeManager extends EventTarget {
170
322
  return this.theme;
171
323
  }
172
324
 
173
- /** @param {import('./types.js').TreeNode} node @param {import('./types.js').NodeDynamicState} state */
325
+ /**
326
+ * @param {import('./types.js').TreeNode} node
327
+ * @param {import('./types.js').NodeDynamicState} state
328
+ */
174
329
  resolveNodeStyle(node, state = {}) {
175
330
  const typeRule = this.theme.types[node?.type ?? ''] ?? {};
176
331
  const statusRule = this.resolveStatus(state.status);
@@ -180,14 +335,20 @@ export class ThemeManager extends EventTarget {
180
335
  typeColor: typeRule.color ?? this.theme.colors.progressFill,
181
336
  status: statusRule,
182
337
  font: this.theme.font,
183
- colors: this.theme.colors,
338
+ colors: this.theme.colors
184
339
  };
185
340
  }
186
341
 
187
342
  resolveStatus(status) {
188
343
  const key = status ?? 0;
189
- return this.theme.statuses[key] ?? { label: String(key), color: this.theme.colors.textMuted };
344
+ return (
345
+ this.theme.statuses[key] ?? {
346
+ label: String(key),
347
+ color: this.theme.colors.textMuted
348
+ }
349
+ );
190
350
  }
351
+
191
352
  }
192
353
 
193
354
  export function resolveTheme(theme = {}) {
@@ -195,7 +356,8 @@ export function resolveTheme(theme = {}) {
195
356
  if (!Object.hasOwn(themes, theme)) throw new TypeError(`Unknown virtual tree theme: ${theme}`);
196
357
  return themes[theme];
197
358
  }
198
- if (!theme || typeof theme !== 'object' || Array.isArray(theme)) throw new TypeError('theme must be a theme name or object');
359
+ if (!theme || typeof theme !== 'object' || Array.isArray(theme))
360
+ throw new TypeError('theme must be a theme name or object');
199
361
  return theme;
200
362
  }
201
363
 
@@ -221,7 +383,7 @@ function normalizeTheme(theme = {}) {
221
383
  indentGuide: base.colors.guide,
222
384
  chevron: base.colors.chevron,
223
385
  chevronMuted: base.colors.textMuted,
224
- progress: base.colors.progressFill,
386
+ progress: base.colors.progressFill
225
387
  };
226
388
  }
227
389
 
@@ -229,9 +391,21 @@ function mergeTheme(base, override) {
229
391
  return {
230
392
  ...base,
231
393
  ...override,
232
- colors: { ...base.colors, ...(override.colors ?? {}) },
233
- valueColors: { ...base.valueColors, ...(override.valueColors ?? {}) },
234
- types: { ...base.types, ...(override.types ?? {}) },
235
- statuses: { ...base.statuses, ...(override.statuses ?? {}) },
394
+ colors: {
395
+ ...base.colors,
396
+ ...(override.colors ?? {})
397
+ },
398
+ valueColors: {
399
+ ...base.valueColors,
400
+ ...(override.valueColors ?? {})
401
+ },
402
+ types: {
403
+ ...base.types,
404
+ ...(override.types ?? {})
405
+ },
406
+ statuses: {
407
+ ...base.statuses,
408
+ ...(override.statuses ?? {})
409
+ }
236
410
  };
237
411
  }
@@ -1,9 +1,11 @@
1
- /** Share tree-cell positions between painting, hit testing and tooltips. */
1
+ /**
2
+ * Share tree-cell positions between painting, hit testing and tooltips.
3
+ */
2
4
  export function treeCellLayout(depth, indentWidth, hasHierarchy) {
3
5
  const indent = depth * indentWidth;
4
6
  return {
5
7
  chevronX: hasHierarchy ? indent + 10 : null,
6
8
  iconX: indent + (hasHierarchy ? 27 : 8),
7
- labelX: indent + (hasHierarchy ? 50 : 31),
9
+ labelX: indent + (hasHierarchy ? 50 : 31)
8
10
  };
9
11
  }
@@ -6,7 +6,7 @@ export const builtInColumns = {
6
6
  minWidth: 160,
7
7
  align: 'left',
8
8
  kind: 'tree',
9
- value: (node) => node.label ?? node.id,
9
+ value: (node) => node.label ?? node.id
10
10
  },
11
11
  status: {
12
12
  id: 'status',
@@ -15,7 +15,7 @@ export const builtInColumns = {
15
15
  minWidth: 64,
16
16
  align: 'center',
17
17
  kind: 'status',
18
- value: (_node, state) => state.status ?? 0,
18
+ value: (_node, state) => state.status ?? 0
19
19
  },
20
20
  value: {
21
21
  id: 'value',
@@ -24,7 +24,7 @@ export const builtInColumns = {
24
24
  minWidth: 56,
25
25
  align: 'right',
26
26
  kind: 'value',
27
- value: (_node, state) => state.value ?? '',
27
+ value: (_node, state) => state.value ?? ''
28
28
  },
29
29
  progress: {
30
30
  id: 'progress',
@@ -33,7 +33,7 @@ export const builtInColumns = {
33
33
  minWidth: 80,
34
34
  align: 'left',
35
35
  kind: 'progress',
36
- value: (_node, state) => state.progress ?? '',
36
+ value: (_node, state) => state.progress ?? ''
37
37
  },
38
38
  type: {
39
39
  id: 'type',
@@ -42,7 +42,7 @@ export const builtInColumns = {
42
42
  minWidth: 70,
43
43
  align: 'left',
44
44
  kind: 'type',
45
- value: (node) => node.type ?? '',
45
+ value: (node) => node.type ?? ''
46
46
  },
47
47
  updated: {
48
48
  id: 'updated',
@@ -51,15 +51,18 @@ export const builtInColumns = {
51
51
  minWidth: 84,
52
52
  align: 'right',
53
53
  kind: 'updated',
54
- value: (_node, state) => state.updatedAt ?? '',
55
- },
54
+ value: (_node, state) => state.updatedAt ?? ''
55
+ }
56
56
  };
57
57
 
58
58
  export class TreeColumnModel {
59
59
  constructor(columns = [builtInColumns.tree]) {
60
60
  this.columns = [];
61
61
  this.contentWidth = 0;
62
- this.sort = { columnId: null, direction: null };
62
+ this.sort = {
63
+ columnId: null,
64
+ direction: null
65
+ };
63
66
  this.setColumns(columns);
64
67
  }
65
68
 
@@ -76,23 +79,50 @@ export class TreeColumnModel {
76
79
 
77
80
  setRowReorder(enabled) {
78
81
  this.rowReorder = Boolean(enabled);
79
- this.columns = this.columns.filter(column => column.id !== '__vtc_row_order');
80
- if (this.rowReorder) this.columns.unshift(normalizeColumn({
81
- id: '__vtc_row_order', label: '', kind: 'rowOrder', width: 72, minWidth: 72,
82
- sortable: false, resizable: false
83
- }, 0));
82
+ this.columns = this.columns.filter((column) => column.id !== '__vtc_row_order');
83
+ if (this.rowReorder)
84
+ this.columns.unshift(
85
+ normalizeColumn(
86
+ {
87
+ id: '__vtc_row_order',
88
+ label: '',
89
+ kind: 'rowOrder',
90
+ width: 72,
91
+ minWidth: 72,
92
+ sortable: false,
93
+ resizable: false
94
+ },
95
+ 0
96
+ )
97
+ );
84
98
  this.#layout();
85
99
  }
86
100
 
87
101
  setRowActions(count) {
88
102
  this.rowActionCount = count;
89
- this.columns = this.columns.filter(column => column.id !== '__vtc_actions');
90
- if (count) this.columns.push(normalizeColumn({ id: '__vtc_actions', label: '', kind: 'text',
91
- width: count * 28, minWidth: count * 28, sortable: false, resizable: false, value: () => '' }, 0));
103
+ this.columns = this.columns.filter((column) => column.id !== '__vtc_actions');
104
+ if (count)
105
+ this.columns.push(
106
+ normalizeColumn(
107
+ {
108
+ id: '__vtc_actions',
109
+ label: '',
110
+ kind: 'text',
111
+ width: count * 28,
112
+ minWidth: count * 28,
113
+ sortable: false,
114
+ resizable: false,
115
+ value: () => ''
116
+ },
117
+ 0
118
+ )
119
+ );
92
120
  this.#layout();
93
121
  }
94
122
 
95
- /** @param {number} x */
123
+ /**
124
+ * @param {number} x
125
+ */
96
126
  getColumnAt(x) {
97
127
  return this.columns.find((column) => x >= column.x && x < column.x + column.width) ?? null;
98
128
  }
@@ -114,21 +144,33 @@ export class TreeColumnModel {
114
144
  const currentIndex = this.columns.findIndex((column) => column.id === id);
115
145
  if (currentIndex === -1) return false;
116
146
  const [column] = this.columns.splice(currentIndex, 1);
117
- const nextIndex = Math.max(this.rowReorder ? 1 : 0, Math.min(this.columns.length - (this.rowActionCount ? 1 : 0), targetIndex));
147
+ const nextIndex = Math.max(
148
+ this.rowReorder ? 1 : 0,
149
+ Math.min(this.columns.length - (this.rowActionCount ? 1 : 0), targetIndex)
150
+ );
118
151
  this.columns.splice(nextIndex, 0, column);
119
- if (!this.columns.some((item) => item.kind === 'tree')) this.columns.unshift(normalizeColumn(builtInColumns.tree, 0));
152
+ if (!this.columns.some((item) => item.kind === 'tree'))
153
+ this.columns.unshift(normalizeColumn(builtInColumns.tree, 0));
120
154
  this.#layout();
121
155
  return true;
122
156
  }
123
157
 
124
158
  setSort(columnId, direction) {
125
159
  if (columnId !== null && !this.getColumn(columnId)) return false;
126
- this.sort = { columnId, direction };
160
+ this.sort = {
161
+ columnId,
162
+ direction
163
+ };
127
164
  return true;
128
165
  }
129
166
 
130
167
  getResizeHandleAt(x, tolerance = 5) {
131
- return this.columns.find((column) => column.resizable !== false && Math.abs(x - (column.x + column.width)) <= tolerance) ?? null;
168
+ return (
169
+ this.columns.find(
170
+ (column) =>
171
+ column.resizable !== false && Math.abs(x - (column.x + column.width)) <= tolerance
172
+ ) ?? null
173
+ );
132
174
  }
133
175
 
134
176
  #layout() {
@@ -139,16 +181,22 @@ export class TreeColumnModel {
139
181
  }
140
182
  this.contentWidth = x;
141
183
  }
184
+
142
185
  }
143
186
 
144
187
  export function defaultTreeTableColumns() {
145
188
  return [
146
- { ...builtInColumns.tree, id: 'name', label: 'Name', width: 340 },
189
+ {
190
+ ...builtInColumns.tree,
191
+ id: 'name',
192
+ label: 'Name',
193
+ width: 340
194
+ },
147
195
  builtInColumns.type,
148
196
  builtInColumns.status,
149
197
  builtInColumns.value,
150
198
  builtInColumns.progress,
151
- builtInColumns.updated,
199
+ builtInColumns.updated
152
200
  ];
153
201
  }
154
202
 
@@ -170,6 +218,6 @@ function normalizeColumn(column, index) {
170
218
  format: source.format,
171
219
  valueType: source.valueType,
172
220
  render: source.render,
173
- x: 0,
221
+ x: 0
174
222
  };
175
223
  }
@@ -1,5 +1,5 @@
1
1
  import { TreeIndex } from './tree-index.js';
2
- import { mergeDynamicState } from './dynamic-state.js';
2
+ import { mergeDynamicStateChanged } from './dynamic-state.js';
3
3
 
4
4
  /**
5
5
  * Owns structural nodes, expanded state, and per-node dynamic state.
@@ -86,11 +86,13 @@ export class TreeModel extends EventTarget {
86
86
  * @param {Array<import('./types.js').DynamicPatch>} patches
87
87
  */
88
88
  applyDynamicPatches(patches) {
89
+ const changedIds = new Set();
89
90
  for (const patch of patches) {
90
91
  const current = this.dynamicState.get(patch.id);
91
92
  if (!current) continue;
92
- mergeDynamicState(current, patch.state);
93
+ if (mergeDynamicStateChanged(current, patch.state)) changedIds.add(patch.id);
93
94
  }
95
+ return changedIds;
94
96
  }
95
97
 
96
98
  /** @param {string} id */