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.
@@ -20,10 +20,14 @@ export class TreeRowRenderer {
20
20
  this.stopIconListener = this.iconRegistry.onChange?.(() => this.#scheduleIconRender()) ?? null;
21
21
  }
22
22
 
23
- /** @param {HTMLCanvasElement} canvas */
23
+ /**
24
+ * @param {HTMLCanvasElement} canvas
25
+ */
24
26
  initialize(canvas) {
25
27
  this.canvas = canvas;
26
- this.ctx = canvas.getContext('2d', { alpha: false });
28
+ this.ctx = canvas.getContext('2d', {
29
+ alpha: false
30
+ });
27
31
  if (!this.ctx) throw new Error('Canvas2D context is not available');
28
32
  }
29
33
 
@@ -31,7 +35,9 @@ export class TreeRowRenderer {
31
35
  this.scene = scene;
32
36
  }
33
37
 
34
- setInvalidationHandler(invalidate) { this.invalidate = invalidate; }
38
+ setInvalidationHandler(invalidate) {
39
+ this.invalidate = invalidate;
40
+ }
35
41
 
36
42
  destroy() {
37
43
  if (this.iconRenderFrame !== null) {
@@ -87,7 +93,8 @@ export class TreeRowRenderer {
87
93
  if (this.invalidate) return this.invalidate();
88
94
  if (this.iconRenderFrame !== null) return;
89
95
  const view = this.canvas.ownerDocument?.defaultView ?? globalThis;
90
- const schedule = view.requestAnimationFrame?.bind(view) ?? (callback => setTimeout(callback, 0));
96
+ const schedule =
97
+ view.requestAnimationFrame?.bind(view) ?? ((callback) => setTimeout(callback, 0));
91
98
  this.iconRenderFrame = schedule(() => {
92
99
  this.iconRenderFrame = null;
93
100
  this.render();
@@ -103,7 +110,12 @@ export class TreeRowRenderer {
103
110
  if (key === this.preparedIconThemeKey) return;
104
111
  this.preparedIconThemeKey = key;
105
112
  for (const [icon, color] of iconColors) {
106
- this.iconRegistry.prepare?.({ icons: [icon], size: 15, color, pixelRatio });
113
+ this.iconRegistry.prepare?.({
114
+ icons: [icon],
115
+ size: 15,
116
+ color,
117
+ pixelRatio
118
+ });
107
119
  }
108
120
  }
109
121
 
@@ -123,14 +135,29 @@ export class TreeRowRenderer {
123
135
  ctx.textBaseline = 'middle';
124
136
 
125
137
  for (const column of columns) {
126
- if (headerFilter && column === columns.find(item => item.kind !== 'rowOrder' && item.id !== '__vtc_actions')) {
138
+ if (
139
+ headerFilter &&
140
+ column === columns.find((item) => item.kind !== 'rowOrder' && item.id !== '__vtc_actions')
141
+ ) {
127
142
  this.#drawHeaderFilter(ctx, column, viewport, theme, filterQuery);
128
143
  } else {
129
144
  ctx.fillStyle = colors.textMuted;
130
- drawTruncatedText(ctx, column.label, column.x + 10, viewport.headerHeight / 2, column.width - 20);
145
+ drawTruncatedText(
146
+ ctx,
147
+ column.label,
148
+ column.x + 10,
149
+ viewport.headerHeight / 2,
150
+ column.width - 20
151
+ );
131
152
  }
132
153
  if (sort?.columnId === column.id && sort.direction) {
133
- this.#drawSortIndicator(ctx, column.x + column.width - 16, viewport.headerHeight / 2, sort.direction, theme);
154
+ this.#drawSortIndicator(
155
+ ctx,
156
+ column.x + column.width - 16,
157
+ viewport.headerHeight / 2,
158
+ sort.direction,
159
+ theme
160
+ );
134
161
  }
135
162
  ctx.strokeStyle = colors.border;
136
163
  ctx.beginPath();
@@ -151,7 +178,8 @@ export class TreeRowRenderer {
151
178
  const colors = theme.colors;
152
179
  const x = column.x + 8;
153
180
  const y = 5;
154
- const visibleRight = viewport.scrollX + (viewport.contentViewportWidth ?? viewport.viewportWidth);
181
+ const visibleRight =
182
+ viewport.scrollX + (viewport.contentViewportWidth ?? viewport.viewportWidth);
155
183
  const visibleWidth = Math.max(1, Math.min(column.x + column.width, visibleRight) - column.x);
156
184
  const width = Math.max(40, visibleWidth - 16);
157
185
  const height = Math.max(18, viewport.headerHeight - 10);
@@ -161,7 +189,13 @@ export class TreeRowRenderer {
161
189
  ctx.strokeStyle = colors.border;
162
190
  ctx.stroke();
163
191
  ctx.fillStyle = filterQuery ? colors.text : colors.textMuted;
164
- drawTruncatedText(ctx, filterQuery || 'Filter rows', x + 8, viewport.headerHeight / 2, width - 16);
192
+ drawTruncatedText(
193
+ ctx,
194
+ filterQuery || 'Filter rows',
195
+ x + 8,
196
+ viewport.headerHeight / 2,
197
+ width - 16
198
+ );
165
199
  }
166
200
 
167
201
  #drawSortIndicator(ctx, x, y, direction, theme) {
@@ -202,7 +236,10 @@ export class TreeRowRenderer {
202
236
  const visibleWidth = viewport.contentViewportWidth ?? viewport.viewportWidth;
203
237
  const height = Math.min(
204
238
  viewport.rowViewportHeight,
205
- stickyRows.reduce((bottom, row, index) => Math.max(bottom, (row.stickyY ?? index * row.height) + row.height), 0)
239
+ stickyRows.reduce(
240
+ (bottom, row, index) => Math.max(bottom, (row.stickyY ?? index * row.height) + row.height),
241
+ 0
242
+ )
206
243
  );
207
244
 
208
245
  ctx.save();
@@ -212,11 +249,14 @@ export class TreeRowRenderer {
212
249
  ctx.translate(-viewport.scrollX, viewport.headerHeight);
213
250
  for (let i = 0; i < stickyRows.length; i++) {
214
251
  const row = stickyRows[i];
215
- this.#drawRow(ctx, { ...row, y: row.stickyY ?? i * row.height });
252
+ this.#drawRow(ctx, {
253
+ ...row,
254
+ y: row.stickyY ?? i * row.height
255
+ });
216
256
  }
217
257
  ctx.restore();
218
258
 
219
- if (!stickyRows.some(row => row.stickyY > row.y - viewport.scrollY)) return;
259
+ if (!stickyRows.some((row) => row.stickyY > row.y - viewport.scrollY)) return;
220
260
  const bottom = viewport.headerHeight + height;
221
261
  ctx.strokeStyle = theme.colors.border;
222
262
  ctx.beginPath();
@@ -226,7 +266,20 @@ export class TreeRowRenderer {
226
266
  }
227
267
 
228
268
  #drawRow(ctx, row) {
229
- const { columns, nodes, dynamicState, selection, hoverNodeId, hoverPart, activeNodeId, activePart, focusNodeId, searchMatches, theme, viewport } = this.scene;
269
+ const {
270
+ columns,
271
+ nodes,
272
+ dynamicState,
273
+ selection,
274
+ hoverNodeId,
275
+ hoverPart,
276
+ activeNodeId,
277
+ activePart,
278
+ focusNodeId,
279
+ searchMatches,
280
+ theme,
281
+ viewport
282
+ } = this.scene;
230
283
  const node = nodes[row.nodeIndex];
231
284
  const state = dynamicState.get(row.nodeId) ?? {};
232
285
  const style = resolveNodeStyle(theme, node, state);
@@ -239,14 +292,25 @@ export class TreeRowRenderer {
239
292
  const visibleX = viewport.scrollX;
240
293
  const visibleWidth = viewport.contentViewportWidth ?? viewport.viewportWidth;
241
294
 
242
- const background = selected ? colors.rowSelected : highlighted ? colors.rowHighlighted : hovered ? colors.rowHover : colors.row;
295
+ const background = selected
296
+ ? colors.rowSelected
297
+ : highlighted
298
+ ? colors.rowHighlighted
299
+ : hovered
300
+ ? colors.rowHover
301
+ : colors.row;
243
302
  ctx.fillStyle = background;
244
303
  ctx.fillRect(visibleX, y, visibleWidth, row.height);
245
304
 
246
305
  this.#drawIndentGuides(ctx, row, colors);
247
306
 
248
307
  for (const column of columns) {
249
- const rect = { x: column.x, y, width: column.width, height: row.height };
308
+ const rect = {
309
+ x: column.x,
310
+ y,
311
+ width: column.width,
312
+ height: row.height
313
+ };
250
314
  this.#drawCell(ctx, {
251
315
  node,
252
316
  state,
@@ -258,7 +322,7 @@ export class TreeRowRenderer {
258
322
  selected,
259
323
  hovered,
260
324
  hoverPart: hoverNodeId === row.nodeId ? hoverPart : null,
261
- activePart: activeNodeId === row.nodeId ? activePart : null,
325
+ activePart: activeNodeId === row.nodeId ? activePart : null
262
326
  });
263
327
  ctx.strokeStyle = colors.border;
264
328
  ctx.beginPath();
@@ -267,10 +331,15 @@ export class TreeRowRenderer {
267
331
  ctx.stroke();
268
332
  }
269
333
 
270
- const actions = columns.find(column => column.id === '__vtc_actions');
334
+ const actions = columns.find((column) => column.id === '__vtc_actions');
271
335
  if (actions) {
272
336
  ctx.fillStyle = background;
273
- ctx.fillRect(visibleX + Math.max(0, visibleWidth - actions.width), y, actions.width, row.height);
337
+ ctx.fillRect(
338
+ visibleX + Math.max(0, visibleWidth - actions.width),
339
+ y,
340
+ actions.width,
341
+ row.height
342
+ );
274
343
  }
275
344
 
276
345
  ctx.strokeStyle = colors.border;
@@ -297,7 +366,8 @@ export class TreeRowRenderer {
297
366
  else if (cell.column.kind === 'inspectorPane') this.#drawInspectorPaneCell(ctx, cell);
298
367
  else if (cell.column.kind === 'inspectorValue') this.#drawInspectorValueCell(ctx, cell);
299
368
  else if (cell.column.kind === 'inspectorType') this.#drawInspectorTypeCell(ctx, cell);
300
- else if (cell.column.kind === 'inspectorDescription') this.#drawInspectorDescriptionCell(ctx, cell);
369
+ else if (cell.column.kind === 'inspectorDescription')
370
+ this.#drawInspectorDescriptionCell(ctx, cell);
301
371
  else if (cell.column.kind === 'status') this.#drawStatusCell(ctx, cell);
302
372
  else if (cell.column.kind === 'progress') this.#drawProgressCell(ctx, cell);
303
373
  else this.#drawTextCell(ctx, cell);
@@ -312,9 +382,16 @@ export class TreeRowRenderer {
312
382
  ctx.strokeStyle = theme.colors.textMuted;
313
383
  ctx.fillStyle = theme.colors.textMuted;
314
384
  ctx.lineWidth = 1.5;
315
- ctx.globalAlpha = enabled || this.scene.rowDrag?.(node, this.scene.dynamicState.get(node.id) ?? {}) != null ? 1 : 0.3;
316
- for (const dx of [10, 14]) for (const dy of [-4, 0, 4]) ctx.fillRect(rect.x + dx, cy + dy, 1.5, 1.5);
317
- for (const [center, direction, available] of [[36, -1, siblings[0] !== node.id], [60, 1, siblings.at(-1) !== node.id]]) {
385
+ ctx.globalAlpha =
386
+ enabled || this.scene.rowDrag?.(node, this.scene.dynamicState.get(node.id) ?? {}) != null
387
+ ? 1
388
+ : 0.3;
389
+ for (const dx of [10, 14])
390
+ for (const dy of [-4, 0, 4]) ctx.fillRect(rect.x + dx, cy + dy, 1.5, 1.5);
391
+ for (const [center, direction, available] of [
392
+ [36, -1, siblings[0] !== node.id],
393
+ [60, 1, siblings.at(-1) !== node.id]
394
+ ]) {
318
395
  ctx.globalAlpha = enabled && available ? 1 : 0.3;
319
396
  ctx.beginPath();
320
397
  ctx.moveTo(rect.x + center - 4, cy - direction * 2);
@@ -341,8 +418,13 @@ export class TreeRowRenderer {
341
418
  }
342
419
 
343
420
  #drawInspectorPaneCell(ctx, { node, row, rect, theme, style, hovered, hoverPart, activePart }) {
344
- const visibleRight = this.scene.viewport.scrollX + (this.scene.viewport.contentViewportWidth ?? this.scene.viewport.viewportWidth);
345
- rect = { ...rect, width: Math.max(1, Math.min(rect.x + rect.width, visibleRight) - rect.x) };
421
+ const visibleRight =
422
+ this.scene.viewport.scrollX +
423
+ (this.scene.viewport.contentViewportWidth ?? this.scene.viewport.viewportWidth);
424
+ rect = {
425
+ ...rect,
426
+ width: Math.max(1, Math.min(rect.x + rect.width, visibleRight) - rect.x)
427
+ };
346
428
  const data = node.data ?? {};
347
429
  const colors = theme.colors;
348
430
  const indentX = rect.x + row.depth * theme.indentWidth;
@@ -360,52 +442,109 @@ export class TreeRowRenderer {
360
442
  ctx.textBaseline = 'middle';
361
443
  ctx.textAlign = 'left';
362
444
  ctx.globalAlpha = data.disabled ? DISABLED_ALPHA : 1;
363
- ctx.fillStyle = data.valueType === 'object' || data.valueType === 'array' ? colors.text : colors.textMuted;
445
+ ctx.fillStyle =
446
+ data.valueType === 'object' || data.valueType === 'array' ? colors.text : colors.textMuted;
364
447
 
365
448
  if (data.valueType === 'object') {
366
- drawTruncatedText(ctx, node.label ?? node.id, labelX, cy, Math.max(20, rect.x + rect.width - labelX - 8));
449
+ drawTruncatedText(
450
+ ctx,
451
+ node.label ?? node.id,
452
+ labelX,
453
+ cy,
454
+ Math.max(20, rect.x + rect.width - labelX - 8)
455
+ );
367
456
  if (data.meta?.updated) this.#drawUpdatedMarker(ctx, row, theme);
368
457
  ctx.globalAlpha = 1;
369
458
  return;
370
459
  }
371
460
 
372
- const layout = inspectorPaneLayout(rect.width, row.depth, theme.indentWidth, data.editorType, this.scene.inspectorPaneLabelEnd);
373
- drawTruncatedText(ctx, node.label ?? node.id, labelX, cy, Math.max(20, rect.x + layout.editorLeft - labelX - 8));
461
+ const layout = inspectorPaneLayout(
462
+ rect.width,
463
+ row.depth,
464
+ theme.indentWidth,
465
+ data.editorType,
466
+ this.scene.inspectorPaneLabelEnd
467
+ );
468
+ drawTruncatedText(
469
+ ctx,
470
+ node.label ?? node.id,
471
+ labelX,
472
+ cy,
473
+ Math.max(20, rect.x + layout.editorLeft - labelX - 8)
474
+ );
374
475
 
375
476
  const editorX = rect.x + layout.editorLeft;
376
477
  const editorWidth = layout.editorWidth;
377
478
  if (data.valueType === 'array') {
378
479
  ctx.fillStyle = colors.textMuted;
379
480
  drawTruncatedText(ctx, data.valueText, editorX, cy, Math.max(20, editorWidth - 58));
380
- this.#drawSmallButton(ctx, rect.x + rect.width - 54, rect.y + 5, 22, rect.height - 10, '+', theme, {
381
- hovered: hovered && hoverPart === 'arrayAdd',
382
- active: activePart === 'arrayAdd',
383
- });
384
- this.#drawSmallButton(ctx, rect.x + rect.width - 28, rect.y + 5, 22, rect.height - 10, '-', theme, {
385
- hovered: hovered && hoverPart === 'arrayRemove',
386
- active: activePart === 'arrayRemove',
387
- });
481
+ this.#drawSmallButton(
482
+ ctx,
483
+ rect.x + rect.width - 54,
484
+ rect.y + 5,
485
+ 22,
486
+ rect.height - 10,
487
+ '+',
488
+ theme,
489
+ {
490
+ hovered: hovered && hoverPart === 'arrayAdd',
491
+ active: activePart === 'arrayAdd'
492
+ }
493
+ );
494
+ this.#drawSmallButton(
495
+ ctx,
496
+ rect.x + rect.width - 28,
497
+ rect.y + 5,
498
+ 22,
499
+ rect.height - 10,
500
+ '-',
501
+ theme,
502
+ {
503
+ hovered: hovered && hoverPart === 'arrayRemove',
504
+ active: activePart === 'arrayRemove'
505
+ }
506
+ );
388
507
  } else {
389
508
  this.#drawInspectorValueCell(ctx, {
390
509
  node,
391
- rect: { x: editorX, y: rect.y, width: editorWidth, height: rect.height },
510
+ rect: {
511
+ x: editorX,
512
+ y: rect.y,
513
+ width: editorWidth,
514
+ height: rect.height
515
+ },
392
516
  theme,
393
517
  hovered,
394
518
  hoverPart,
395
519
  activePart,
396
- suppressUpdatedMarker: true,
520
+ suppressUpdatedMarker: true
397
521
  });
398
522
  }
399
523
  if (data.meta?.updated) this.#drawUpdatedMarker(ctx, row, theme);
400
524
  ctx.globalAlpha = 1;
401
525
  }
402
526
 
403
- #drawInspectorValueCell(ctx, { node, rect, theme, hovered = false, hoverPart = null, activePart = null, suppressUpdatedMarker = false }) {
527
+ #drawInspectorValueCell(
528
+ ctx,
529
+ {
530
+ node,
531
+ rect,
532
+ theme,
533
+ hovered = false,
534
+ hoverPart = null,
535
+ activePart = null,
536
+ suppressUpdatedMarker = false
537
+ }
538
+ ) {
404
539
  const data = node.data ?? {};
405
540
  const meta = data.meta ?? {};
406
541
  const disabled = data.disabled;
407
542
  const readonly = data.readonly;
408
- const valueColor = resolveValueColor(theme, data.value, data.editorType === 'select' ? 'enum' : data.valueType);
543
+ const valueColor = resolveValueColor(
544
+ theme,
545
+ data.value,
546
+ data.editorType === 'select' ? 'enum' : data.valueType
547
+ );
409
548
  const x = rect.x + 10;
410
549
  const y = rect.y + 5;
411
550
  const fullWidth = Math.max(24, rect.width - 20);
@@ -420,7 +559,13 @@ export class TreeRowRenderer {
420
559
  if (numeric.unit && numeric.unitWidth) {
421
560
  ctx.font = theme.unitFont ?? theme.monoFont ?? theme.font;
422
561
  ctx.fillStyle = theme.colors.unit ?? theme.colors.textMuted;
423
- drawTruncatedText(ctx, numeric.unit, x + numeric.unitLeft, rect.y + rect.height / 2, numeric.unitWidth);
562
+ drawTruncatedText(
563
+ ctx,
564
+ numeric.unit,
565
+ x + numeric.unitLeft,
566
+ rect.y + rect.height / 2,
567
+ numeric.unitWidth
568
+ );
424
569
  ctx.font = theme.font;
425
570
  }
426
571
 
@@ -429,33 +574,59 @@ export class TreeRowRenderer {
429
574
  } else if (data.editorType === 'range') {
430
575
  this.#drawInspectorRange(ctx, x, rect.y + rect.height / 2 - 4, width, data, theme, {
431
576
  hoveredNumber: hovered && hoverPart === 'number',
432
- activeNumber: activePart === 'number',
577
+ activeNumber: activePart === 'number'
433
578
  });
434
579
  } else if (data.editorType === 'color') {
435
580
  ctx.fillStyle = String(data.value || '#000000');
436
581
  ctx.fillRect(x, y + 2, 28, height - 4);
437
582
  ctx.strokeStyle = theme.colors.border;
438
583
  ctx.strokeRect(x + 0.5, y + 2.5, 28, height - 4);
439
- this.#drawMutedText(ctx, String(data.value ?? ''), x + 38, rect.y + rect.height / 2, width - 38, theme, false, null, valueColor);
584
+ this.#drawMutedText(
585
+ ctx,
586
+ String(data.value ?? ''),
587
+ x + 38,
588
+ rect.y + rect.height / 2,
589
+ width - 38,
590
+ theme,
591
+ false,
592
+ null,
593
+ valueColor
594
+ );
440
595
  } else if (data.editorType === 'button') {
441
596
  const buttonWidth = meta.fullWidthButton ? width : Math.min(width, 140);
442
597
  this.#drawControlSurface(ctx, x, y, buttonWidth, height, theme, {
443
598
  hovered: hovered && hoverPart === 'button',
444
599
  active: activePart === 'button',
445
- disabled: readonly || disabled,
600
+ disabled: readonly || disabled
446
601
  });
447
602
  ctx.fillStyle = theme.colors.text;
448
603
  ctx.textAlign = 'center';
449
- drawTruncatedText(ctx, meta.button ?? node.label, x + buttonWidth / 2, rect.y + rect.height / 2, Math.max(10, buttonWidth - 12));
604
+ drawTruncatedText(
605
+ ctx,
606
+ meta.button ?? node.label,
607
+ x + buttonWidth / 2,
608
+ rect.y + rect.height / 2,
609
+ Math.max(10, buttonWidth - 12)
610
+ );
450
611
  ctx.textAlign = 'left';
451
612
  } else if (data.editorType === 'select') {
452
613
  const selectWidth = Math.min(width, 180);
453
614
  this.#drawControlSurface(ctx, x, y, selectWidth, height, theme, {
454
615
  hovered: hovered && hoverPart === 'editor',
455
616
  active: activePart === 'editor',
456
- disabled: readonly || disabled,
617
+ disabled: readonly || disabled
457
618
  });
458
- this.#drawMutedText(ctx, data.valueText, x + 8, rect.y + rect.height / 2, selectWidth - 30, theme, false, null, valueColor);
619
+ this.#drawMutedText(
620
+ ctx,
621
+ data.valueText,
622
+ x + 8,
623
+ rect.y + rect.height / 2,
624
+ selectWidth - 30,
625
+ theme,
626
+ false,
627
+ null,
628
+ valueColor
629
+ );
459
630
  this.#drawSelectChevron(ctx, x + selectWidth - 18, rect.y + rect.height / 2, theme, disabled);
460
631
  } else {
461
632
  if (numeric.unit) {
@@ -465,7 +636,17 @@ export class TreeRowRenderer {
465
636
  drawTruncatedText(ctx, data.valueText, x + width, rect.y + rect.height / 2, width);
466
637
  ctx.textAlign = 'left';
467
638
  } else {
468
- this.#drawMutedText(ctx, data.valueText, x, rect.y + rect.height / 2, width, theme, false, null, valueColor);
639
+ this.#drawMutedText(
640
+ ctx,
641
+ data.valueText,
642
+ x,
643
+ rect.y + rect.height / 2,
644
+ width,
645
+ theme,
646
+ false,
647
+ null,
648
+ valueColor
649
+ );
469
650
  }
470
651
  }
471
652
 
@@ -488,14 +669,29 @@ export class TreeRowRenderer {
488
669
  }
489
670
 
490
671
  #drawInspectorTypeCell(ctx, { node, rect, theme }) {
491
- this.#drawMutedText(ctx, node.data?.valueType ?? '', rect.x + 10, rect.y + rect.height / 2, rect.width - 20, theme, false, theme.monoFont);
672
+ this.#drawMutedText(
673
+ ctx,
674
+ node.data?.valueType ?? '',
675
+ rect.x + 10,
676
+ rect.y + rect.height / 2,
677
+ rect.width - 20,
678
+ theme,
679
+ false,
680
+ theme.monoFont
681
+ );
492
682
  }
493
683
 
494
684
  #drawInspectorDescriptionCell(ctx, { node, rect, theme }) {
495
- this.#drawMutedText(ctx, node.data?.meta?.description ?? '', rect.x + 10, rect.y + rect.height / 2, rect.width - 20, theme);
685
+ this.#drawMutedText(
686
+ ctx,
687
+ node.data?.meta?.description ?? '',
688
+ rect.x + 10,
689
+ rect.y + rect.height / 2,
690
+ rect.width - 20,
691
+ theme
692
+ );
496
693
  }
497
694
 
498
-
499
695
  #drawInspectorRange(ctx, x, y, width, data, theme, state = {}) {
500
696
  const meta = data.meta ?? {};
501
697
  const min = meta.min ?? 0;
@@ -507,12 +703,18 @@ export class TreeRowRenderer {
507
703
  this.#drawMeterBar(ctx, x, y + 0.5, barWidth, 7, ratio, theme.colors.progressFill, theme);
508
704
  this.#drawControlSurface(ctx, x + barWidth + gap, y - 6, valueWidth, 20, theme, {
509
705
  hovered: Boolean(state.hoveredNumber),
510
- active: Boolean(state.activeNumber),
706
+ active: Boolean(state.activeNumber)
511
707
  });
512
708
  ctx.fillStyle = resolveValueColor(theme, data.value);
513
709
  ctx.font = theme.monoFont ?? theme.font;
514
710
  ctx.textAlign = 'right';
515
- drawTruncatedText(ctx, String(data.valueText ?? ''), x + barWidth + gap + valueWidth - 6, y + 4, valueWidth - 10);
711
+ drawTruncatedText(
712
+ ctx,
713
+ String(data.valueText ?? ''),
714
+ x + barWidth + gap + valueWidth - 6,
715
+ y + 4,
716
+ valueWidth - 10
717
+ );
516
718
  ctx.textAlign = 'left';
517
719
  ctx.font = theme.font;
518
720
  }
@@ -526,7 +728,15 @@ export class TreeRowRenderer {
526
728
  ctx.textAlign = 'left';
527
729
  }
528
730
 
529
- #drawControlSurface(ctx, x, y, width, height, theme, { hovered = false, active = false, disabled = false } = {}) {
731
+ #drawControlSurface(
732
+ ctx,
733
+ x,
734
+ y,
735
+ width,
736
+ height,
737
+ theme,
738
+ { hovered = false, active = false, disabled = false } = {}
739
+ ) {
530
740
  const colors = theme.colors;
531
741
  ctx.fillStyle = disabled
532
742
  ? colors.progressTrack
@@ -537,7 +747,11 @@ export class TreeRowRenderer {
537
747
  : colors.progressTrack;
538
748
  roundRect(ctx, x, y, width, height, 5);
539
749
  ctx.fill();
540
- ctx.strokeStyle = active ? colors.focus : hovered ? mixColor(colors.border, colors.focus, 0.5) : colors.border;
750
+ ctx.strokeStyle = active
751
+ ? colors.focus
752
+ : hovered
753
+ ? mixColor(colors.border, colors.focus, 0.5)
754
+ : colors.border;
541
755
  ctx.stroke();
542
756
  }
543
757
 
@@ -561,7 +775,11 @@ export class TreeRowRenderer {
561
775
 
562
776
  #drawTreeCell(ctx, { node, row, rect, theme, style }) {
563
777
  const colors = theme.colors;
564
- const layout = treeCellLayout(row.depth, theme.indentWidth, this.scene.childrenByParent.size > 1);
778
+ const layout = treeCellLayout(
779
+ row.depth,
780
+ theme.indentWidth,
781
+ this.scene.childrenByParent.size > 1
782
+ );
565
783
  const cy = rect.y + rect.height / 2;
566
784
  if (row.hasChildren) this.#drawChevron(ctx, rect.x + layout.chevronX, cy, row, colors);
567
785
  this.iconRegistry.draw(ctx, style.icon, rect.x + layout.iconX, rect.y + 6, 15, style.color);
@@ -569,12 +787,21 @@ export class TreeRowRenderer {
569
787
  ctx.font = theme.font;
570
788
  ctx.textBaseline = 'middle';
571
789
  ctx.textAlign = 'left';
572
- drawTruncatedText(ctx, node.label ?? node.id, rect.x + layout.labelX, cy, Math.max(0, rect.width - layout.labelX - 6));
790
+ drawTruncatedText(
791
+ ctx,
792
+ node.label ?? node.id,
793
+ rect.x + layout.labelX,
794
+ cy,
795
+ Math.max(0, rect.width - layout.labelX - 6)
796
+ );
573
797
  }
574
798
 
575
799
  #drawStatusCell(ctx, { rect, style, theme }) {
576
800
  ctx.font = theme.font;
577
- const badgeWidth = Math.max(0, Math.min(ctx.measureText(style.status.label).width + 18, rect.width - 12));
801
+ const badgeWidth = Math.max(
802
+ 0,
803
+ Math.min(ctx.measureText(style.status.label).width + 18, rect.width - 12)
804
+ );
578
805
  const x = rect.x + (rect.width - badgeWidth) / 2;
579
806
  const y = rect.y + (rect.height - 17) / 2;
580
807
  const statusColor = style.status.color;
@@ -593,7 +820,18 @@ export class TreeRowRenderer {
593
820
  }
594
821
 
595
822
  #drawProgressCell(ctx, { state, rect, theme, style }) {
596
- if (state.progress === undefined) return this.#drawTextCell(ctx, { state, rect, theme, style, column: { align: 'right', value: () => '' }, node: {} });
823
+ if (state.progress === undefined)
824
+ return this.#drawTextCell(ctx, {
825
+ state,
826
+ rect,
827
+ theme,
828
+ style,
829
+ column: {
830
+ align: 'right',
831
+ value: () => ''
832
+ },
833
+ node: {}
834
+ });
597
835
  const ratio = clamp01(state.progress);
598
836
  const barX = rect.x + 10;
599
837
  const barHeight = 7;
@@ -628,16 +866,32 @@ export class TreeRowRenderer {
628
866
  #drawTextCell(ctx, { node, state, rect, column, theme }) {
629
867
  let value = column.value(node, state);
630
868
  const numeric = typeof value === 'number';
631
- const valueType = typeof column.valueType === 'function' ? column.valueType(value, node, state) : column.valueType;
869
+ const valueType =
870
+ typeof column.valueType === 'function'
871
+ ? column.valueType(value, node, state)
872
+ : column.valueType;
632
873
  const valueColor = resolveValueColor(theme, value, valueType);
633
874
  if (column.format) value = column.format(value, node, state);
634
875
  else if (column.kind === 'updated' && numeric) value = formatTime(value);
635
876
  else if (numeric) value = formatInspectorValue(value);
636
- ctx.fillStyle = column.kind === 'type' ? resolveNodeStyle(theme, node, state).color : column.kind === 'value' ? valueColor : theme.colors.textMuted;
637
- ctx.font = column.kind === 'type' || column.kind === 'updated' || numeric ? theme.monoFont ?? theme.font : theme.font;
877
+ ctx.fillStyle =
878
+ column.kind === 'type'
879
+ ? resolveNodeStyle(theme, node, state).color
880
+ : column.kind === 'value'
881
+ ? valueColor
882
+ : theme.colors.textMuted;
883
+ ctx.font =
884
+ column.kind === 'type' || column.kind === 'updated' || numeric
885
+ ? (theme.monoFont ?? theme.font)
886
+ : theme.font;
638
887
  ctx.textBaseline = 'middle';
639
888
  ctx.textAlign = column.align;
640
- const x = column.align === 'right' ? rect.x + rect.width - 10 : column.align === 'center' ? rect.x + rect.width / 2 : rect.x + 10;
889
+ const x =
890
+ column.align === 'right'
891
+ ? rect.x + rect.width - 10
892
+ : column.align === 'center'
893
+ ? rect.x + rect.width / 2
894
+ : rect.x + 10;
641
895
  drawTruncatedText(ctx, String(value ?? ''), x, rect.y + rect.height / 2, rect.width - 20);
642
896
  ctx.textAlign = 'left';
643
897
  }
@@ -671,20 +925,23 @@ export class TreeRowRenderer {
671
925
  ctx.closePath();
672
926
  ctx.fill();
673
927
  }
928
+
674
929
  }
675
930
 
676
931
  function resolveNodeStyle(theme, node, state = {}) {
677
932
  const typeRule = theme.types[node?.type ?? ''] ?? {};
678
- const status = theme.statuses[state.status ?? 0] ?? { label: String(state.status ?? ''), color: theme.colors.textMuted };
933
+ const status = theme.statuses[state.status ?? 0] ?? {
934
+ label: String(state.status ?? ''),
935
+ color: theme.colors.textMuted
936
+ };
679
937
  return {
680
938
  icon: state.icon ?? node?.icon ?? typeRule.icon ?? 'placeholder',
681
939
  color: state.color ?? typeRule.color ?? theme.colors.progressFill,
682
940
  typeColor: typeRule.color ?? theme.colors.progressFill,
683
- status,
941
+ status
684
942
  };
685
943
  }
686
944
 
687
-
688
945
  function clamp01(value) {
689
946
  return Math.max(0, Math.min(1, value));
690
947
  }
@@ -773,7 +1030,7 @@ function setTextFitCache(key, value) {
773
1030
  const timeFormatter = new Intl.DateTimeFormat([], {
774
1031
  hour: '2-digit',
775
1032
  minute: '2-digit',
776
- second: '2-digit',
1033
+ second: '2-digit'
777
1034
  });
778
1035
 
779
1036
  function formatTime(value) {