virtual-tree-canvas 0.1.0 → 0.3.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.
- package/package.json +6 -2
- package/src/core/icon-registry.js +249 -0
- package/src/core/theme-manager.js +20 -1
- package/src/core/tree-worker-operations.js +5 -3
- package/src/core/visible-row-model.js +15 -3
- package/src/index.d.ts +108 -0
- package/src/input/tree-view-input-controller.js +1 -0
- package/src/inspector/cell-editor-manager.js +57 -15
- package/src/inspector/model-inspector-builder.js +2 -1
- package/src/renderers/tree-row-renderer.js +171 -37
- package/src/tree-view-controller.js +164 -9
package/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "virtual-tree-canvas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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",
|
|
7
|
+
"types": "./src/index.d.ts",
|
|
7
8
|
"sideEffects": false,
|
|
8
9
|
"exports": {
|
|
9
|
-
".":
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./src/index.d.ts",
|
|
12
|
+
"default": "./src/index.js"
|
|
13
|
+
},
|
|
10
14
|
"./core": "./src/core/index.js",
|
|
11
15
|
"./inspector": "./src/inspector/index.js",
|
|
12
16
|
"./renderers": "./src/renderers/index.js",
|
|
@@ -74,6 +74,16 @@ export class IconRegistry {
|
|
|
74
74
|
this.register('error', drawError);
|
|
75
75
|
this.register('task', drawTask);
|
|
76
76
|
this.register('track', drawTrack);
|
|
77
|
+
this.register('point', drawPoint);
|
|
78
|
+
this.register('munition', drawMunition);
|
|
79
|
+
this.register('air', drawAircraft);
|
|
80
|
+
this.register('ground', drawGroundVehicle);
|
|
81
|
+
this.register('surface', drawSurfaceVehicle);
|
|
82
|
+
this.register('subsurface', drawSubsurfaceVehicle);
|
|
83
|
+
this.register('space', drawSpaceVehicle);
|
|
84
|
+
this.register('control', drawControl);
|
|
85
|
+
this.register('situation', drawSituation);
|
|
86
|
+
this.register('damage', drawDamage);
|
|
77
87
|
}
|
|
78
88
|
}
|
|
79
89
|
|
|
@@ -171,3 +181,242 @@ function drawTrack(ctx, x, y, size, color) {
|
|
|
171
181
|
ctx.lineTo(x + size - 1, y + size / 2);
|
|
172
182
|
ctx.stroke();
|
|
173
183
|
}
|
|
184
|
+
|
|
185
|
+
function drawPoint(ctx, x, y, size, color) {
|
|
186
|
+
const cx = x + size / 2;
|
|
187
|
+
const cy = y + size / 2;
|
|
188
|
+
ctx.strokeStyle = color;
|
|
189
|
+
ctx.fillStyle = color;
|
|
190
|
+
ctx.lineWidth = 1.4;
|
|
191
|
+
ctx.beginPath();
|
|
192
|
+
ctx.arc(cx, cy, size * 0.22, 0, Math.PI * 2);
|
|
193
|
+
ctx.fill();
|
|
194
|
+
ctx.beginPath();
|
|
195
|
+
ctx.arc(cx, cy, size * 0.42, 0, Math.PI * 2);
|
|
196
|
+
ctx.stroke();
|
|
197
|
+
ctx.beginPath();
|
|
198
|
+
ctx.moveTo(cx, y + 1);
|
|
199
|
+
ctx.lineTo(cx, y + size * 0.22);
|
|
200
|
+
ctx.moveTo(cx, y + size * 0.78);
|
|
201
|
+
ctx.lineTo(cx, y + size - 1);
|
|
202
|
+
ctx.moveTo(x + 1, cy);
|
|
203
|
+
ctx.lineTo(x + size * 0.22, cy);
|
|
204
|
+
ctx.moveTo(x + size * 0.78, cy);
|
|
205
|
+
ctx.lineTo(x + size - 1, cy);
|
|
206
|
+
ctx.stroke();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function drawMunition(ctx, x, y, size, color) {
|
|
210
|
+
const cx = x + size * 0.5;
|
|
211
|
+
const bodyTop = y + size * 0.25;
|
|
212
|
+
const bodyBottom = y + size * 0.76;
|
|
213
|
+
ctx.fillStyle = color;
|
|
214
|
+
|
|
215
|
+
ctx.beginPath();
|
|
216
|
+
ctx.moveTo(cx, y + 1);
|
|
217
|
+
ctx.lineTo(cx + size * 0.17, bodyTop);
|
|
218
|
+
ctx.lineTo(cx - size * 0.17, bodyTop);
|
|
219
|
+
ctx.closePath();
|
|
220
|
+
ctx.fill();
|
|
221
|
+
|
|
222
|
+
ctx.fillRect(cx - size * 0.11, bodyTop, size * 0.22, bodyBottom - bodyTop);
|
|
223
|
+
|
|
224
|
+
ctx.beginPath();
|
|
225
|
+
ctx.moveTo(cx - size * 0.11, y + size * 0.63);
|
|
226
|
+
ctx.lineTo(x + 1, y + size - 2);
|
|
227
|
+
ctx.lineTo(cx - size * 0.11, y + size * 0.82);
|
|
228
|
+
ctx.closePath();
|
|
229
|
+
ctx.fill();
|
|
230
|
+
|
|
231
|
+
ctx.beginPath();
|
|
232
|
+
ctx.moveTo(cx + size * 0.11, y + size * 0.63);
|
|
233
|
+
ctx.lineTo(x + size - 1, y + size - 2);
|
|
234
|
+
ctx.lineTo(cx + size * 0.11, y + size * 0.82);
|
|
235
|
+
ctx.closePath();
|
|
236
|
+
ctx.fill();
|
|
237
|
+
|
|
238
|
+
ctx.strokeStyle = 'rgba(255,255,255,.35)';
|
|
239
|
+
ctx.lineWidth = 1;
|
|
240
|
+
ctx.beginPath();
|
|
241
|
+
ctx.moveTo(cx, bodyTop + 1);
|
|
242
|
+
ctx.lineTo(cx, bodyBottom - 1);
|
|
243
|
+
ctx.stroke();
|
|
244
|
+
|
|
245
|
+
ctx.strokeStyle = color;
|
|
246
|
+
ctx.beginPath();
|
|
247
|
+
ctx.moveTo(cx - size * 0.12, bodyBottom);
|
|
248
|
+
ctx.lineTo(cx, y + size - 1);
|
|
249
|
+
ctx.lineTo(cx + size * 0.12, bodyBottom);
|
|
250
|
+
ctx.stroke();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function drawGroundVehicle(ctx, x, y, size, color) {
|
|
254
|
+
ctx.fillStyle = color;
|
|
255
|
+
const bodyY = y + size * 0.42;
|
|
256
|
+
ctx.fillRect(x + size * 0.16, bodyY, size * 0.68, size * 0.26);
|
|
257
|
+
ctx.fillRect(x + size * 0.34, y + size * 0.26, size * 0.28, size * 0.2);
|
|
258
|
+
ctx.fillRect(x + size * 0.62, y + size * 0.34, size * 0.3, size * 0.06);
|
|
259
|
+
ctx.beginPath();
|
|
260
|
+
ctx.arc(x + size * 0.28, y + size * 0.76, size * 0.09, 0, Math.PI * 2);
|
|
261
|
+
ctx.arc(x + size * 0.5, y + size * 0.76, size * 0.09, 0, Math.PI * 2);
|
|
262
|
+
ctx.arc(x + size * 0.72, y + size * 0.76, size * 0.09, 0, Math.PI * 2);
|
|
263
|
+
ctx.fill();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function drawSurfaceVehicle(ctx, x, y, size, color) {
|
|
267
|
+
ctx.fillStyle = color;
|
|
268
|
+
ctx.beginPath();
|
|
269
|
+
ctx.moveTo(x + 1, y + size * 0.58);
|
|
270
|
+
ctx.lineTo(x + size * 0.82, y + size * 0.58);
|
|
271
|
+
ctx.lineTo(x + size - 1, y + size * 0.72);
|
|
272
|
+
ctx.lineTo(x + size * 0.18, y + size * 0.82);
|
|
273
|
+
ctx.closePath();
|
|
274
|
+
ctx.fill();
|
|
275
|
+
|
|
276
|
+
ctx.fillRect(x + size * 0.34, y + size * 0.34, size * 0.24, size * 0.2);
|
|
277
|
+
ctx.fillRect(x + size * 0.46, y + size * 0.18, size * 0.08, size * 0.18);
|
|
278
|
+
ctx.strokeStyle = color;
|
|
279
|
+
ctx.lineWidth = 1.2;
|
|
280
|
+
ctx.beginPath();
|
|
281
|
+
ctx.moveTo(x + size * 0.18, y + size * 0.88);
|
|
282
|
+
ctx.quadraticCurveTo(x + size * 0.34, y + size * 0.78, x + size * 0.5, y + size * 0.88);
|
|
283
|
+
ctx.quadraticCurveTo(x + size * 0.66, y + size * 0.98, x + size * 0.82, y + size * 0.88);
|
|
284
|
+
ctx.stroke();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function drawSubsurfaceVehicle(ctx, x, y, size, color) {
|
|
288
|
+
ctx.fillStyle = color;
|
|
289
|
+
ctx.beginPath();
|
|
290
|
+
ctx.ellipse(x + size * 0.5, y + size * 0.58, size * 0.38, size * 0.18, 0, 0, Math.PI * 2);
|
|
291
|
+
ctx.fill();
|
|
292
|
+
ctx.fillRect(x + size * 0.44, y + size * 0.28, size * 0.12, size * 0.18);
|
|
293
|
+
ctx.fillRect(x + size * 0.38, y + size * 0.26, size * 0.24, size * 0.06);
|
|
294
|
+
ctx.strokeStyle = color;
|
|
295
|
+
ctx.lineWidth = 1.2;
|
|
296
|
+
ctx.beginPath();
|
|
297
|
+
ctx.moveTo(x + size * 0.16, y + size * 0.86);
|
|
298
|
+
ctx.quadraticCurveTo(x + size * 0.32, y + size * 0.78, x + size * 0.5, y + size * 0.86);
|
|
299
|
+
ctx.quadraticCurveTo(x + size * 0.68, y + size * 0.94, x + size * 0.84, y + size * 0.86);
|
|
300
|
+
ctx.stroke();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function drawSpaceVehicle(ctx, x, y, size, color) {
|
|
304
|
+
const cx = x + size * 0.5;
|
|
305
|
+
const cy = y + size * 0.5;
|
|
306
|
+
ctx.strokeStyle = color;
|
|
307
|
+
ctx.fillStyle = color;
|
|
308
|
+
ctx.lineWidth = 1.2;
|
|
309
|
+
ctx.beginPath();
|
|
310
|
+
ctx.arc(cx, cy, size * 0.16, 0, Math.PI * 2);
|
|
311
|
+
ctx.fill();
|
|
312
|
+
ctx.strokeRect(x + size * 0.12, y + size * 0.32, size * 0.24, size * 0.36);
|
|
313
|
+
ctx.strokeRect(x + size * 0.64, y + size * 0.32, size * 0.24, size * 0.36);
|
|
314
|
+
ctx.beginPath();
|
|
315
|
+
ctx.moveTo(x + size * 0.36, cy);
|
|
316
|
+
ctx.lineTo(x + size * 0.64, cy);
|
|
317
|
+
ctx.stroke();
|
|
318
|
+
ctx.beginPath();
|
|
319
|
+
ctx.ellipse(cx, cy, size * 0.42, size * 0.18, -0.45, 0, Math.PI * 2);
|
|
320
|
+
ctx.stroke();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function drawControl(ctx, x, y, size, color) {
|
|
324
|
+
ctx.strokeStyle = color;
|
|
325
|
+
ctx.fillStyle = color;
|
|
326
|
+
ctx.lineWidth = 1.3;
|
|
327
|
+
const cx = x + size * 0.5;
|
|
328
|
+
const baseY = y + size * 0.68;
|
|
329
|
+
|
|
330
|
+
ctx.beginPath();
|
|
331
|
+
ctx.moveTo(x + size * 0.22, baseY);
|
|
332
|
+
ctx.lineTo(x + size * 0.78, baseY);
|
|
333
|
+
ctx.lineTo(x + size * 0.88, y + size * 0.88);
|
|
334
|
+
ctx.lineTo(x + size * 0.12, y + size * 0.88);
|
|
335
|
+
ctx.closePath();
|
|
336
|
+
ctx.stroke();
|
|
337
|
+
|
|
338
|
+
ctx.beginPath();
|
|
339
|
+
ctx.moveTo(cx, baseY);
|
|
340
|
+
ctx.lineTo(x + size * 0.42, y + size * 0.32);
|
|
341
|
+
ctx.stroke();
|
|
342
|
+
|
|
343
|
+
ctx.beginPath();
|
|
344
|
+
ctx.arc(x + size * 0.4, y + size * 0.28, size * 0.13, 0, Math.PI * 2);
|
|
345
|
+
ctx.fill();
|
|
346
|
+
|
|
347
|
+
ctx.beginPath();
|
|
348
|
+
ctx.arc(x + size * 0.66, y + size * 0.78, size * 0.055, 0, Math.PI * 2);
|
|
349
|
+
ctx.arc(x + size * 0.78, y + size * 0.78, size * 0.055, 0, Math.PI * 2);
|
|
350
|
+
ctx.fill();
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function drawSituation(ctx, x, y, size, color) {
|
|
354
|
+
const cx = x + size * 0.5;
|
|
355
|
+
const cy = y + size * 0.5;
|
|
356
|
+
ctx.strokeStyle = color;
|
|
357
|
+
ctx.fillStyle = color;
|
|
358
|
+
ctx.lineWidth = 1.25;
|
|
359
|
+
|
|
360
|
+
ctx.beginPath();
|
|
361
|
+
ctx.arc(cx, cy, size * 0.42, 0, Math.PI * 2);
|
|
362
|
+
ctx.moveTo(cx - size * 0.42, cy);
|
|
363
|
+
ctx.lineTo(cx + size * 0.42, cy);
|
|
364
|
+
ctx.moveTo(cx, cy - size * 0.42);
|
|
365
|
+
ctx.lineTo(cx, cy + size * 0.42);
|
|
366
|
+
ctx.stroke();
|
|
367
|
+
|
|
368
|
+
ctx.globalAlpha = 0.55;
|
|
369
|
+
ctx.beginPath();
|
|
370
|
+
ctx.arc(cx, cy, size * 0.25, 0, Math.PI * 2);
|
|
371
|
+
ctx.stroke();
|
|
372
|
+
ctx.globalAlpha = 1;
|
|
373
|
+
|
|
374
|
+
ctx.beginPath();
|
|
375
|
+
ctx.moveTo(cx, cy);
|
|
376
|
+
ctx.lineTo(x + size * 0.82, y + size * 0.26);
|
|
377
|
+
ctx.stroke();
|
|
378
|
+
|
|
379
|
+
ctx.beginPath();
|
|
380
|
+
ctx.arc(x + size * 0.68, y + size * 0.38, size * 0.055, 0, Math.PI * 2);
|
|
381
|
+
ctx.arc(x + size * 0.36, y + size * 0.62, size * 0.045, 0, Math.PI * 2);
|
|
382
|
+
ctx.fill();
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function drawDamage(ctx, x, y, size, color) {
|
|
386
|
+
const cx = x + size * 0.5;
|
|
387
|
+
const cy = y + size * 0.52;
|
|
388
|
+
const r = size * 0.42;
|
|
389
|
+
|
|
390
|
+
ctx.beginPath();
|
|
391
|
+
ctx.moveTo(cx, y + 1);
|
|
392
|
+
ctx.lineTo(cx + r * 0.28, cy - r * 0.28);
|
|
393
|
+
ctx.lineTo(x + size - 1, y + size * 0.2);
|
|
394
|
+
ctx.lineTo(cx + r * 0.55, cy + r * 0.05);
|
|
395
|
+
ctx.lineTo(x + size * 0.92, y + size * 0.76);
|
|
396
|
+
ctx.lineTo(cx + r * 0.22, cy + r * 0.32);
|
|
397
|
+
ctx.lineTo(cx + r * 0.08, y + size - 1);
|
|
398
|
+
ctx.lineTo(cx - r * 0.18, cy + r * 0.34);
|
|
399
|
+
ctx.lineTo(x + size * 0.14, y + size * 0.86);
|
|
400
|
+
ctx.lineTo(cx - r * 0.42, cy + r * 0.12);
|
|
401
|
+
ctx.lineTo(x + 1, y + size * 0.38);
|
|
402
|
+
ctx.lineTo(cx - r * 0.3, cy - r * 0.18);
|
|
403
|
+
ctx.closePath();
|
|
404
|
+
ctx.fillStyle = color;
|
|
405
|
+
ctx.fill();
|
|
406
|
+
|
|
407
|
+
ctx.fillStyle = '#fff7ed';
|
|
408
|
+
ctx.beginPath();
|
|
409
|
+
ctx.arc(cx, cy, size * 0.13, 0, Math.PI * 2);
|
|
410
|
+
ctx.fill();
|
|
411
|
+
|
|
412
|
+
ctx.strokeStyle = color;
|
|
413
|
+
ctx.lineWidth = 1.1;
|
|
414
|
+
ctx.beginPath();
|
|
415
|
+
ctx.moveTo(x + size * 0.08, y + size * 0.08);
|
|
416
|
+
ctx.lineTo(x + size * 0.2, y + size * 0.2);
|
|
417
|
+
ctx.moveTo(x + size * 0.84, y + size * 0.88);
|
|
418
|
+
ctx.lineTo(x + size * 0.94, y + size * 0.98);
|
|
419
|
+
ctx.moveTo(x + size * 0.9, y + size * 0.06);
|
|
420
|
+
ctx.lineTo(x + size * 0.82, y + size * 0.18);
|
|
421
|
+
ctx.stroke();
|
|
422
|
+
}
|
|
@@ -22,8 +22,18 @@ export const darkTheme = {
|
|
|
22
22
|
root: { icon: 'folder', color: '#38bdf8' },
|
|
23
23
|
system: { icon: 'folder', color: '#818cf8' },
|
|
24
24
|
platform: { icon: 'aircraft', color: '#60a5fa' },
|
|
25
|
+
air: { icon: 'air', color: '#60a5fa' },
|
|
26
|
+
ground: { icon: 'ground', color: '#a3e635' },
|
|
27
|
+
surface: { icon: 'surface', color: '#22d3ee' },
|
|
28
|
+
subsurface: { icon: 'subsurface', color: '#38bdf8' },
|
|
29
|
+
space: { icon: 'space', color: '#c084fc' },
|
|
30
|
+
munition: { icon: 'munition', color: '#fb7185' },
|
|
25
31
|
sensor: { icon: 'radar', color: '#34d399' },
|
|
26
32
|
track: { icon: 'track', color: '#a78bfa' },
|
|
33
|
+
geometry: { icon: 'point', color: '#38bdf8' },
|
|
34
|
+
control: { icon: 'control', color: '#f59e0b' },
|
|
35
|
+
situation: { icon: 'situation', color: '#2dd4bf' },
|
|
36
|
+
damage: { icon: 'damage', color: '#fb7185' },
|
|
27
37
|
warning: { icon: 'warning', color: '#facc15' },
|
|
28
38
|
error: { icon: 'error', color: '#ef4444' },
|
|
29
39
|
task: { icon: 'task', color: '#f97316' },
|
|
@@ -77,8 +87,18 @@ export const tacticalTheme = {
|
|
|
77
87
|
...darkTheme.types,
|
|
78
88
|
root: { icon: 'folder', color: '#7ddc92' },
|
|
79
89
|
platform: { icon: 'aircraft', color: '#93c572' },
|
|
90
|
+
air: { icon: 'air', color: '#93c572' },
|
|
91
|
+
ground: { icon: 'ground', color: '#c7f36f' },
|
|
92
|
+
surface: { icon: 'surface', color: '#67e8f9' },
|
|
93
|
+
subsurface: { icon: 'subsurface', color: '#7dd3fc' },
|
|
94
|
+
space: { icon: 'space', color: '#d8b4fe' },
|
|
95
|
+
munition: { icon: 'munition', color: '#ff8fab' },
|
|
80
96
|
sensor: { icon: 'radar', color: '#45d483' },
|
|
81
97
|
track: { icon: 'track', color: '#d6f264' },
|
|
98
|
+
geometry: { icon: 'point', color: '#7ddc92' },
|
|
99
|
+
control: { icon: 'control', color: '#ffd166' },
|
|
100
|
+
situation: { icon: 'situation', color: '#8be9d9' },
|
|
101
|
+
damage: { icon: 'damage', color: '#ff8fab' },
|
|
82
102
|
warning: { icon: 'warning', color: '#ffd166' },
|
|
83
103
|
error: { icon: 'error', color: '#ff5c5c' },
|
|
84
104
|
},
|
|
@@ -159,4 +179,3 @@ function mergeTheme(base, override) {
|
|
|
159
179
|
statuses: { ...base.statuses, ...(override.statuses ?? {}) },
|
|
160
180
|
};
|
|
161
181
|
}
|
|
162
|
-
|
|
@@ -59,6 +59,7 @@ export function rebuildWorkerRows(state, options = {}) {
|
|
|
59
59
|
const rowHeight = options.rowHeight ?? 28;
|
|
60
60
|
const indentWidth = options.indentWidth ?? 18;
|
|
61
61
|
const expanded = new Set(options.expandedIds ?? []);
|
|
62
|
+
const filterCollapsed = new Set(options.filterCollapsedIds ?? []);
|
|
62
63
|
const query = normalize(options.filterQuery ?? '');
|
|
63
64
|
const sort = options.sort ?? { columnId: null, direction: null };
|
|
64
65
|
const sortValues = options.sortValues ? new Map(options.sortValues) : null;
|
|
@@ -81,7 +82,8 @@ export function rebuildWorkerRows(state, options = {}) {
|
|
|
81
82
|
if (!isIncluded(id)) return;
|
|
82
83
|
const nodeIndex = state.idToIndex.get(id);
|
|
83
84
|
if (nodeIndex === undefined) return;
|
|
84
|
-
const
|
|
85
|
+
const children = sortIds(state.childrenByParent.get(id) ?? []);
|
|
86
|
+
const expandedRow = (expanded.has(id) || Boolean(query && children.length > 0)) && !filterCollapsed.has(id);
|
|
85
87
|
const rowIndex = rows.length;
|
|
86
88
|
rows.push({
|
|
87
89
|
nodeId: id,
|
|
@@ -94,8 +96,8 @@ export function rebuildWorkerRows(state, options = {}) {
|
|
|
94
96
|
hasChildren: hasChildren(id),
|
|
95
97
|
});
|
|
96
98
|
maxDepth = Math.max(maxDepth, depth);
|
|
97
|
-
if (!expandedRow
|
|
98
|
-
for (const childId of
|
|
99
|
+
if (!expandedRow) return;
|
|
100
|
+
for (const childId of children) visit(childId, depth + 1);
|
|
99
101
|
};
|
|
100
102
|
|
|
101
103
|
for (const rootId of sortIds(state.roots)) visit(rootId, 0);
|
|
@@ -33,6 +33,7 @@ export class VisibleRowModel extends EventTarget {
|
|
|
33
33
|
this.contentHeight = 0;
|
|
34
34
|
this.sortComparator = null;
|
|
35
35
|
this.filterPredicate = null;
|
|
36
|
+
this.filterCollapsed = new Set();
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
setSortComparator(comparator) {
|
|
@@ -41,6 +42,15 @@ export class VisibleRowModel extends EventTarget {
|
|
|
41
42
|
|
|
42
43
|
setFilterPredicate(predicate) {
|
|
43
44
|
this.filterPredicate = predicate;
|
|
45
|
+
this.filterCollapsed.clear();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
collapseFilterBranch(id) {
|
|
49
|
+
this.filterCollapsed.add(id);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
expandFilterBranch(id) {
|
|
53
|
+
return this.filterCollapsed.delete(id);
|
|
44
54
|
}
|
|
45
55
|
|
|
46
56
|
applyRows({ rows, contentHeight, contentWidth }) {
|
|
@@ -84,8 +94,10 @@ export class VisibleRowModel extends EventTarget {
|
|
|
84
94
|
if (!subtreeIncluded(id)) return;
|
|
85
95
|
const nodeIndex = this.model.index.idToIndex.get(id);
|
|
86
96
|
if (nodeIndex === undefined) return;
|
|
97
|
+
const children = sortedChildren(id);
|
|
87
98
|
const hasChildren = this.expansion.hasChildren(id);
|
|
88
|
-
const
|
|
99
|
+
const autoExpanded = Boolean(this.filterPredicate && children.length > 0);
|
|
100
|
+
const expanded = (this.expansion.isExpanded(id) || autoExpanded) && !this.filterCollapsed.has(id);
|
|
89
101
|
const rowIndex = this.rows.length;
|
|
90
102
|
this.rows.push({
|
|
91
103
|
nodeId: id,
|
|
@@ -99,8 +111,8 @@ export class VisibleRowModel extends EventTarget {
|
|
|
99
111
|
});
|
|
100
112
|
this.rowIndexById.set(id, rowIndex);
|
|
101
113
|
maxDepth = Math.max(maxDepth, depth);
|
|
102
|
-
if (!expanded
|
|
103
|
-
for (const childId of
|
|
114
|
+
if (!expanded) return;
|
|
115
|
+
for (const childId of children) visit(childId, depth + 1);
|
|
104
116
|
};
|
|
105
117
|
|
|
106
118
|
const roots = this.model.index.roots.filter((rootId) => subtreeIncluded(rootId));
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export type TreeViewAlign = 'start' | 'center' | 'end' | 'nearest';
|
|
2
|
+
|
|
3
|
+
export type TreeNode = {
|
|
4
|
+
id: string;
|
|
5
|
+
parentId?: string | null;
|
|
6
|
+
label?: string;
|
|
7
|
+
type?: string;
|
|
8
|
+
icon?: string;
|
|
9
|
+
image?: string;
|
|
10
|
+
tags?: string[];
|
|
11
|
+
data?: any;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type DynamicPatch = {
|
|
15
|
+
id: string;
|
|
16
|
+
state?: Record<string, any>;
|
|
17
|
+
} & Record<string, any>;
|
|
18
|
+
|
|
19
|
+
export type MetaRule = {
|
|
20
|
+
min?: number;
|
|
21
|
+
max?: number;
|
|
22
|
+
step?: number;
|
|
23
|
+
integer?: boolean;
|
|
24
|
+
readonly?: boolean;
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
updated?: boolean;
|
|
27
|
+
description?: string;
|
|
28
|
+
color?: boolean;
|
|
29
|
+
options?: Record<string, string | number | boolean>;
|
|
30
|
+
button?: string;
|
|
31
|
+
label?: string;
|
|
32
|
+
fullWidthButton?: boolean;
|
|
33
|
+
itemType?: 'object' | 'number' | 'string' | 'boolean';
|
|
34
|
+
itemTitle?: (i: number, item: any) => string;
|
|
35
|
+
itemFactory?: () => any;
|
|
36
|
+
type?: string;
|
|
37
|
+
icon?: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type Column = {
|
|
41
|
+
id: string;
|
|
42
|
+
label?: string;
|
|
43
|
+
width?: number;
|
|
44
|
+
minWidth?: number;
|
|
45
|
+
align?: 'left' | 'center' | 'right';
|
|
46
|
+
kind?: string;
|
|
47
|
+
sortable?: boolean;
|
|
48
|
+
value?: (node: TreeNode, state: Record<string, any>) => string | number | boolean;
|
|
49
|
+
render?: (ctx: CanvasRenderingContext2D, cell: any) => void;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const themes: Record<string, any>;
|
|
53
|
+
|
|
54
|
+
export class TreeRowRenderer {
|
|
55
|
+
renderedRows: number;
|
|
56
|
+
constructor(options?: Record<string, any>);
|
|
57
|
+
initialize(canvas: HTMLCanvasElement): void;
|
|
58
|
+
setScene(scene: any): void;
|
|
59
|
+
updateDynamicState(patches: DynamicPatch[]): void;
|
|
60
|
+
render(scene?: any, time?: number): void;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export class TreeViewController {
|
|
64
|
+
canvas?: HTMLCanvasElement;
|
|
65
|
+
viewport: {
|
|
66
|
+
viewportWidth: number;
|
|
67
|
+
viewportHeight: number;
|
|
68
|
+
[key: string]: any;
|
|
69
|
+
};
|
|
70
|
+
filterQuery: string;
|
|
71
|
+
rowModel: any;
|
|
72
|
+
expansion: any;
|
|
73
|
+
selection: any;
|
|
74
|
+
constructor(options?: Record<string, any>);
|
|
75
|
+
on(type: string, listener: (event: any) => void): any;
|
|
76
|
+
off(type: string, listener: (event: any) => void): void;
|
|
77
|
+
setData(nodes: TreeNode[]): void;
|
|
78
|
+
setModel(model: any, meta?: Record<string, MetaRule>, options?: Record<string, any>): void;
|
|
79
|
+
setColumns(columns: Column[]): void;
|
|
80
|
+
setDynamicState(patches: DynamicPatch[]): void;
|
|
81
|
+
setTheme(theme: any): void;
|
|
82
|
+
resize(width: number, height: number): void;
|
|
83
|
+
render(time?: number): void;
|
|
84
|
+
renderMeasured(time?: number): any;
|
|
85
|
+
hitTest(clientX: number, clientY: number): any;
|
|
86
|
+
getTooltipForHit(hit: any): any;
|
|
87
|
+
search(query: string, options?: Record<string, any>): any;
|
|
88
|
+
setFilter(queryOrPredicate?: string | ((node: any, state: any) => boolean)): void;
|
|
89
|
+
clearFilter(): void;
|
|
90
|
+
focusNode(nodeId: string, options?: Record<string, any>): boolean;
|
|
91
|
+
scrollToNode(nodeId: string, align?: TreeViewAlign): boolean;
|
|
92
|
+
getSelection(): string[];
|
|
93
|
+
setSelection(ids: string[]): void;
|
|
94
|
+
clearSelection(): void;
|
|
95
|
+
expandAll(): void;
|
|
96
|
+
collapseAll(): void;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export class TreeViewInputController {
|
|
100
|
+
constructor(options?: Record<string, any>);
|
|
101
|
+
destroy(): void;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export class CellEditorManager {
|
|
105
|
+
constructor(options?: Record<string, any>);
|
|
106
|
+
destroy(): void;
|
|
107
|
+
close(): void;
|
|
108
|
+
}
|
|
@@ -15,11 +15,18 @@ export class CellEditorManager {
|
|
|
15
15
|
window.removeEventListener('mouseup', this.onMouseUp);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
close() {
|
|
19
|
+
this.#removeOverlay();
|
|
20
|
+
this.rangeDrag = null;
|
|
21
|
+
window.removeEventListener('mousemove', this.onMouseMove);
|
|
22
|
+
window.removeEventListener('mouseup', this.onMouseUp);
|
|
23
|
+
}
|
|
24
|
+
|
|
18
25
|
handlePointerDown(event, hit) {
|
|
19
26
|
if (!this.#isEditableHit(hit)) return false;
|
|
20
27
|
const data = hit.row ? this.controller.model.nodes[hit.row.nodeIndex]?.data : null;
|
|
21
28
|
if (!data || data.readonly || data.disabled) return false;
|
|
22
|
-
if (data.editorType === 'range' && hit.part
|
|
29
|
+
if (data.editorType === 'range' && hit.part === 'range') {
|
|
23
30
|
this.rangeDrag = { hit, data };
|
|
24
31
|
this.#updateRangeFromEvent(event);
|
|
25
32
|
window.addEventListener('mousemove', this.onMouseMove);
|
|
@@ -47,7 +54,13 @@ export class CellEditorManager {
|
|
|
47
54
|
this.controller.updateInspectorValue(node.id, !data.value, 'checkbox');
|
|
48
55
|
return true;
|
|
49
56
|
}
|
|
50
|
-
if (data.editorType === 'range'
|
|
57
|
+
if (data.editorType === 'range') {
|
|
58
|
+
if (hit.part === 'range') return true;
|
|
59
|
+
if (hit.part !== 'number') {
|
|
60
|
+
this.#toggleRangeMinMax(node, data);
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
51
64
|
if (this.overlay) return true;
|
|
52
65
|
this.#showOverlay(node, hit);
|
|
53
66
|
return true;
|
|
@@ -62,7 +75,7 @@ export class CellEditorManager {
|
|
|
62
75
|
#showOverlay(node, hit, options = {}) {
|
|
63
76
|
this.#removeOverlay();
|
|
64
77
|
const data = node.data;
|
|
65
|
-
const rect = this.#overlayRect(hit);
|
|
78
|
+
const rect = this.#clampRectToHost(this.#overlayRect(hit), 4);
|
|
66
79
|
const hostRect = this.host.getBoundingClientRect();
|
|
67
80
|
const element = createEditorElement(data);
|
|
68
81
|
Object.assign(element.style, {
|
|
@@ -98,7 +111,7 @@ export class CellEditorManager {
|
|
|
98
111
|
ensureOverlayHost(this.host);
|
|
99
112
|
this.host.append(element);
|
|
100
113
|
this.overlay = element;
|
|
101
|
-
element.focus();
|
|
114
|
+
element.focus({ preventScroll: true });
|
|
102
115
|
element.select?.();
|
|
103
116
|
if (options.showPicker && element.showPicker) {
|
|
104
117
|
requestAnimationFrame(() => {
|
|
@@ -113,7 +126,13 @@ export class CellEditorManager {
|
|
|
113
126
|
|
|
114
127
|
#showHeaderFilterOverlay(hit) {
|
|
115
128
|
this.#removeOverlay();
|
|
116
|
-
const
|
|
129
|
+
const headerRect = this.controller.getHeaderClientRect(hit);
|
|
130
|
+
const rect = this.#clampRectToHost({
|
|
131
|
+
x: headerRect.x + 8,
|
|
132
|
+
y: headerRect.y + 5,
|
|
133
|
+
width: Math.max(24, Math.min(headerRect.width, this.controller.viewport.viewportWidth) - 16),
|
|
134
|
+
height: Math.max(20, headerRect.height - 10),
|
|
135
|
+
}, 0);
|
|
117
136
|
const hostRect = this.host.getBoundingClientRect();
|
|
118
137
|
const element = document.createElement('input');
|
|
119
138
|
element.type = 'search';
|
|
@@ -121,12 +140,12 @@ export class CellEditorManager {
|
|
|
121
140
|
element.placeholder = 'Filter inspector';
|
|
122
141
|
Object.assign(element.style, {
|
|
123
142
|
position: 'absolute',
|
|
124
|
-
left: `${rect.x - hostRect.left
|
|
125
|
-
top: `${rect.y - hostRect.top
|
|
126
|
-
width: `${Math.max(24, rect.width
|
|
127
|
-
height: `${Math.max(20, rect.height
|
|
143
|
+
left: `${rect.x - hostRect.left}px`,
|
|
144
|
+
top: `${rect.y - hostRect.top}px`,
|
|
145
|
+
width: `${Math.max(24, rect.width)}px`,
|
|
146
|
+
height: `${Math.max(20, rect.height)}px`,
|
|
128
147
|
minWidth: '0',
|
|
129
|
-
maxWidth: `${Math.max(24, rect.width
|
|
148
|
+
maxWidth: `${Math.max(24, rect.width)}px`,
|
|
130
149
|
zIndex: 20,
|
|
131
150
|
boxSizing: 'border-box',
|
|
132
151
|
margin: '0',
|
|
@@ -147,7 +166,7 @@ export class CellEditorManager {
|
|
|
147
166
|
ensureOverlayHost(this.host);
|
|
148
167
|
this.host.append(element);
|
|
149
168
|
this.overlay = element;
|
|
150
|
-
element.focus();
|
|
169
|
+
element.focus({ preventScroll: true });
|
|
151
170
|
element.select();
|
|
152
171
|
}
|
|
153
172
|
|
|
@@ -184,8 +203,9 @@ export class CellEditorManager {
|
|
|
184
203
|
#overlayRect(hit) {
|
|
185
204
|
if (hit.column?.kind !== 'inspectorPane') return this.controller.getCellClientRect(hit);
|
|
186
205
|
const rect = this.controller.getCellClientRect(hit);
|
|
187
|
-
const
|
|
188
|
-
const
|
|
206
|
+
const visibleWidth = Math.max(1, Math.min(rect.width, this.controller.viewport.viewportWidth));
|
|
207
|
+
const data = this.controller.model.nodes[hit.row.nodeIndex]?.data ?? {};
|
|
208
|
+
const { editorLeft, editorWidth } = this.controller.getInspectorPaneLayout(visibleWidth, hit.row, data.editorType);
|
|
189
209
|
if (hit.part === 'number') {
|
|
190
210
|
const valueWidth = Math.min(64, Math.max(42, (editorWidth - 20) * 0.28));
|
|
191
211
|
return { x: rect.x + editorLeft + editorWidth - valueWidth - 10, y: rect.y + 4, width: valueWidth, height: rect.height - 8 };
|
|
@@ -196,8 +216,9 @@ export class CellEditorManager {
|
|
|
196
216
|
#rangeBarRect(hit) {
|
|
197
217
|
const rect = this.controller.getCellClientRect(hit);
|
|
198
218
|
if (hit.column?.kind === 'inspectorPane') {
|
|
199
|
-
const
|
|
200
|
-
const
|
|
219
|
+
const visibleWidth = Math.max(1, Math.min(rect.width, this.controller.viewport.viewportWidth));
|
|
220
|
+
const data = this.controller.model.nodes[hit.row.nodeIndex]?.data ?? {};
|
|
221
|
+
const { editorLeft, editorWidth } = this.controller.getInspectorPaneLayout(visibleWidth, hit.row, data.editorType);
|
|
201
222
|
const valueWidth = Math.min(64, Math.max(42, (editorWidth - 20) * 0.28));
|
|
202
223
|
const barWidth = Math.max(24, editorWidth - 20 - valueWidth - 8);
|
|
203
224
|
return { x: rect.x + editorLeft + 10, y: rect.y + rect.height / 2 - 4, width: barWidth, height: 8 };
|
|
@@ -206,10 +227,31 @@ export class CellEditorManager {
|
|
|
206
227
|
return { x: rect.x + 10, y: rect.y + rect.height / 2 - 4, width: Math.max(24, rect.width - 20 - valueWidth - 8), height: 8 };
|
|
207
228
|
}
|
|
208
229
|
|
|
230
|
+
#clampRectToHost(rect, inset = 0) {
|
|
231
|
+
const hostRect = this.host.getBoundingClientRect();
|
|
232
|
+
const minX = hostRect.left + inset;
|
|
233
|
+
const minY = hostRect.top + inset;
|
|
234
|
+
const maxX = hostRect.right - inset;
|
|
235
|
+
const maxY = hostRect.bottom - inset;
|
|
236
|
+
const width = Math.max(24, Math.min(rect.width, maxX - minX));
|
|
237
|
+
const height = Math.max(18, Math.min(rect.height, maxY - minY));
|
|
238
|
+
const x = Math.max(minX, Math.min(rect.x, maxX - width));
|
|
239
|
+
const y = Math.max(minY, Math.min(rect.y, maxY - height));
|
|
240
|
+
return { x, y, width, height };
|
|
241
|
+
}
|
|
242
|
+
|
|
209
243
|
#removeOverlay() {
|
|
210
244
|
this.overlay?.remove();
|
|
211
245
|
this.overlay = null;
|
|
212
246
|
}
|
|
247
|
+
|
|
248
|
+
#toggleRangeMinMax(node, data) {
|
|
249
|
+
const meta = data.meta ?? {};
|
|
250
|
+
const min = Number.isFinite(meta.min) ? meta.min : 0;
|
|
251
|
+
const max = Number.isFinite(meta.max) ? meta.max : 100;
|
|
252
|
+
const current = typeof data.value === 'number' && Number.isFinite(data.value) ? data.value : min;
|
|
253
|
+
this.controller.updateInspectorValue(node.id, current <= min ? max : min, 'range');
|
|
254
|
+
}
|
|
213
255
|
}
|
|
214
256
|
|
|
215
257
|
function createEditorElement(data) {
|