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.
- package/README.md +173 -225
- package/docs/inspector-demo.png +0 -0
- package/docs/tree-demo.png +0 -0
- package/package.json +2 -1
- package/src/assets/icons/air.svg +1 -0
- package/src/assets/icons/aircraft.svg +1 -0
- package/src/assets/icons/bus.svg +1 -0
- package/src/assets/icons/control.svg +1 -0
- package/src/assets/icons/damage.svg +1 -0
- package/src/assets/icons/error.svg +1 -0
- package/src/assets/icons/folder.svg +1 -0
- package/src/assets/icons/ground.svg +1 -0
- package/src/assets/icons/inspector-array.svg +1 -0
- package/src/assets/icons/inspector-object.svg +1 -0
- package/src/assets/icons/inspector-value.svg +1 -0
- package/src/assets/icons/munition.svg +1 -0
- package/src/assets/icons/placeholder.svg +1 -0
- package/src/assets/icons/point.svg +1 -0
- package/src/assets/icons/radar.svg +1 -0
- package/src/assets/icons/situation.svg +1 -0
- package/src/assets/icons/space.svg +1 -0
- package/src/assets/icons/subsurface.svg +1 -0
- package/src/assets/icons/surface.svg +1 -0
- package/src/assets/icons/task.svg +1 -0
- package/src/assets/icons/track.svg +1 -0
- package/src/assets/icons/warning.svg +1 -0
- package/src/assets/icons.js +25 -0
- package/src/core/icon-registry.js +173 -495
- package/src/core/theme-manager.js +1 -0
- package/src/core/tree-worker-client.js +17 -0
- package/src/index.d.ts +17 -0
- package/src/inspector/cell-editor-manager.js +23 -18
- package/src/inspector/editor-resolver.js +6 -1
- package/src/inspector/numeric-layout.js +16 -0
- package/src/renderers/tree-row-renderer.js +56 -4
- package/src/tree-view-controller.js +13 -5
|
@@ -2,6 +2,7 @@ export const darkTheme = {
|
|
|
2
2
|
rowHeight: 28,
|
|
3
3
|
indentWidth: 18,
|
|
4
4
|
font: '12px Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif',
|
|
5
|
+
unitFont: '11px ui-monospace, SFMono-Regular, Consolas, monospace',
|
|
5
6
|
monoFont: '12px "JetBrains Mono", "Cascadia Mono", "Fira Code", ui-monospace, SFMono-Regular, Consolas, monospace',
|
|
6
7
|
colors: {
|
|
7
8
|
background: '#0a0f1c',
|
|
@@ -6,6 +6,8 @@ export class TreeWorkerClient {
|
|
|
6
6
|
this.pending = new Map();
|
|
7
7
|
this.ready = Promise.resolve();
|
|
8
8
|
this.worker.addEventListener('message', this.#onMessage);
|
|
9
|
+
this.worker.addEventListener('error', this.#onError);
|
|
10
|
+
this.worker.addEventListener('messageerror', this.#onMessageError);
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
setData(nodes) {
|
|
@@ -27,6 +29,8 @@ export class TreeWorkerClient {
|
|
|
27
29
|
for (const { reject } of this.pending.values()) reject(new Error('Tree worker destroyed'));
|
|
28
30
|
this.pending.clear();
|
|
29
31
|
this.worker.removeEventListener('message', this.#onMessage);
|
|
32
|
+
this.worker.removeEventListener('error', this.#onError);
|
|
33
|
+
this.worker.removeEventListener('messageerror', this.#onMessageError);
|
|
30
34
|
this.worker.terminate();
|
|
31
35
|
}
|
|
32
36
|
|
|
@@ -47,4 +51,17 @@ export class TreeWorkerClient {
|
|
|
47
51
|
if (ok) pending.resolve(result);
|
|
48
52
|
else pending.reject(new Error(error || 'Tree worker request failed'));
|
|
49
53
|
};
|
|
54
|
+
|
|
55
|
+
#onError = (event) => {
|
|
56
|
+
this.#rejectPending(new Error(event.message || 'Tree worker failed'));
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
#onMessageError = () => {
|
|
60
|
+
this.#rejectPending(new Error('Tree worker returned an unreadable message'));
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
#rejectPending(error) {
|
|
64
|
+
for (const { reject } of this.pending.values()) reject(error);
|
|
65
|
+
this.pending.clear();
|
|
66
|
+
}
|
|
50
67
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
export type TreeViewAlign = 'start' | 'center' | 'end' | 'nearest';
|
|
2
2
|
|
|
3
|
+
export type IconDrawFunction = (ctx: CanvasRenderingContext2D, x: number, y: number, size: number, color: string) => void;
|
|
4
|
+
export type IconSource = string | CanvasImageSource | IconDrawFunction;
|
|
5
|
+
|
|
6
|
+
export class IconRegistry {
|
|
7
|
+
constructor(options?: { pixelRatio?: number });
|
|
8
|
+
register(name: string, icon: IconSource): any;
|
|
9
|
+
get(name: string): any;
|
|
10
|
+
onChange(listener: () => void): () => void;
|
|
11
|
+
prepare(options?: { icons?: Iterable<string>; size?: number; color?: string; pixelRatio?: number }): Promise<any[]>;
|
|
12
|
+
draw(ctx: CanvasRenderingContext2D, name: string, x: number, y: number, size: number, color: string): void;
|
|
13
|
+
}
|
|
14
|
+
|
|
3
15
|
export type TreeNode = {
|
|
4
16
|
id: string;
|
|
5
17
|
parentId?: string | null;
|
|
@@ -21,6 +33,10 @@ export type MetaRule = {
|
|
|
21
33
|
max?: number;
|
|
22
34
|
step?: number;
|
|
23
35
|
integer?: boolean;
|
|
36
|
+
/** Display-only suffix for numeric values. Never appended to the model value. */
|
|
37
|
+
unit?: string;
|
|
38
|
+
/** Display decimal places (0–20). Editing retains the original precision. */
|
|
39
|
+
precision?: number;
|
|
24
40
|
readonly?: boolean;
|
|
25
41
|
disabled?: boolean;
|
|
26
42
|
updated?: boolean;
|
|
@@ -86,6 +102,7 @@ export class TreeViewController {
|
|
|
86
102
|
setDynamicState(patches: DynamicPatch[]): void;
|
|
87
103
|
setTheme(theme: any): void;
|
|
88
104
|
setLayoutMetrics(options?: { rowHeight?: number; indentWidth?: number; headerHeight?: number }): void;
|
|
105
|
+
registerIcon(name: string, icon: IconSource): any;
|
|
89
106
|
resize(width: number, height: number): void;
|
|
90
107
|
render(time?: number): void;
|
|
91
108
|
renderMeasured(time?: number): any;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { numericLayout } from './numeric-layout.js';
|
|
2
|
+
|
|
1
3
|
export class CellEditorManager {
|
|
2
4
|
constructor({ controller, host = null }) {
|
|
3
5
|
if (!controller) throw new TypeError('CellEditorManager requires a TreeViewController');
|
|
@@ -81,6 +83,7 @@ export class CellEditorManager {
|
|
|
81
83
|
const hostRect = this.host.getBoundingClientRect();
|
|
82
84
|
const element = createEditorElement(data);
|
|
83
85
|
element.className = `vtc-editor vtc-editor-${data.editorType}`;
|
|
86
|
+
element.setAttribute('aria-label', [node.label, numericLayout(rect.width, data).unit].filter(Boolean).join(' '));
|
|
84
87
|
Object.assign(element.style, {
|
|
85
88
|
position: 'absolute',
|
|
86
89
|
left: `${rect.x - hostRect.left}px`,
|
|
@@ -208,34 +211,36 @@ export class CellEditorManager {
|
|
|
208
211
|
}
|
|
209
212
|
|
|
210
213
|
#isEditableHit(hit) {
|
|
211
|
-
return hit?.area === 'row' && (hit.column?.kind === 'inspectorValue' || hit.column?.kind === 'inspectorPane');
|
|
214
|
+
return hit?.area === 'row' && hit.part !== 'unit' && (hit.column?.kind === 'inspectorValue' || hit.column?.kind === 'inspectorPane');
|
|
212
215
|
}
|
|
213
216
|
|
|
214
|
-
#
|
|
215
|
-
if (hit.column?.kind !== 'inspectorPane') return this.controller.getCellClientRect(hit);
|
|
217
|
+
#editorContentRect(hit) {
|
|
216
218
|
const rect = this.controller.getCellClientRect(hit);
|
|
219
|
+
if (hit.column?.kind !== 'inspectorPane') {
|
|
220
|
+
return { x: rect.x + 10, y: rect.y + 4, width: Math.max(24, rect.width - 20), height: rect.height - 8 };
|
|
221
|
+
}
|
|
217
222
|
const visibleWidth = Math.max(1, Math.min(rect.width, this.controller.viewport.contentViewportWidth));
|
|
218
223
|
const data = this.controller.model.nodes[hit.row.nodeIndex]?.data ?? {};
|
|
219
224
|
const { editorLeft, editorWidth } = this.controller.getInspectorPaneLayout(visibleWidth, hit.row, data.editorType);
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
225
|
+
return { x: rect.x + editorLeft + 10, y: rect.y + 4, width: Math.max(24, editorWidth - 20), height: rect.height - 8 };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
#overlayRect(hit) {
|
|
229
|
+
const data = this.controller.model.nodes[hit.row.nodeIndex]?.data ?? {};
|
|
230
|
+
const rect = this.#editorContentRect(hit);
|
|
231
|
+
const layout = numericLayout(rect.width, data);
|
|
232
|
+
if (hit.part === 'number' && data.editorType === 'range') {
|
|
233
|
+
return { ...rect, x: rect.x + layout.numberLeft, width: layout.valueWidth };
|
|
223
234
|
}
|
|
224
|
-
|
|
235
|
+
if (layout.unit) return { ...rect, width: layout.contentWidth };
|
|
236
|
+
if (hit.column?.kind !== 'inspectorPane') return this.controller.getCellClientRect(hit);
|
|
237
|
+
return rect;
|
|
225
238
|
}
|
|
226
239
|
|
|
227
240
|
#rangeBarRect(hit) {
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const data = this.controller.model.nodes[hit.row.nodeIndex]?.data ?? {};
|
|
232
|
-
const { editorLeft, editorWidth } = this.controller.getInspectorPaneLayout(visibleWidth, hit.row, data.editorType);
|
|
233
|
-
const valueWidth = Math.min(64, Math.max(42, (editorWidth - 20) * 0.28));
|
|
234
|
-
const barWidth = Math.max(24, editorWidth - 20 - valueWidth - 8);
|
|
235
|
-
return { x: rect.x + editorLeft + 10, y: rect.y + rect.height / 2 - 4, width: barWidth, height: 8 };
|
|
236
|
-
}
|
|
237
|
-
const valueWidth = Math.min(64, Math.max(42, (rect.width - 20) * 0.28));
|
|
238
|
-
return { x: rect.x + 10, y: rect.y + rect.height / 2 - 4, width: Math.max(24, rect.width - 20 - valueWidth - 8), height: 8 };
|
|
241
|
+
const data = this.controller.model.nodes[hit.row.nodeIndex]?.data ?? {};
|
|
242
|
+
const rect = this.#editorContentRect(hit);
|
|
243
|
+
return { ...rect, y: rect.y + rect.height / 2 - 4, width: numericLayout(rect.width, data).barWidth, height: 8 };
|
|
239
244
|
}
|
|
240
245
|
|
|
241
246
|
#clampRectToHost(rect, inset = 0) {
|
|
@@ -28,6 +28,11 @@ export function formatInspectorValue(value, meta = {}) {
|
|
|
28
28
|
if (value === null) return 'null';
|
|
29
29
|
if (value === undefined) return 'undefined';
|
|
30
30
|
if (typeof value === 'object') return Array.isArray(value) ? `Array(${value.length})` : 'Object';
|
|
31
|
-
if (typeof value === 'number')
|
|
31
|
+
if (typeof value === 'number') {
|
|
32
|
+
if (Number.isFinite(value) && Number.isInteger(meta.precision) && meta.precision >= 0 && meta.precision <= 20) {
|
|
33
|
+
return value.toFixed(meta.precision);
|
|
34
|
+
}
|
|
35
|
+
return Number.isInteger(value) ? String(value) : String(Math.round(value * 1000) / 1000);
|
|
36
|
+
}
|
|
32
37
|
return String(value);
|
|
33
38
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Shared geometry for canvas drawing, hit testing and the numeric editor. */
|
|
2
|
+
export function numericLayout(width, data = {}) {
|
|
3
|
+
width = Math.max(0, width);
|
|
4
|
+
const numeric = typeof data.value === 'number' && !data.meta?.options && !data.meta?.button;
|
|
5
|
+
const unit = numeric && typeof data.meta?.unit === 'string' ? data.meta.unit.trim() : '';
|
|
6
|
+
// Reserve a stable slot: changes to the number must not move the unit.
|
|
7
|
+
const gap = unit ? 6 : 0;
|
|
8
|
+
const unitWidth = unit ? Math.min(Math.max(24, [...unit].length * 7), 96, Math.max(0, width - 48)) : 0;
|
|
9
|
+
const contentWidth = Math.max(0, width - unitWidth - gap);
|
|
10
|
+
const valueWidth = Math.min(64, Math.max(42, contentWidth * 0.28), Math.max(0, contentWidth - 32));
|
|
11
|
+
return {
|
|
12
|
+
unit, unitWidth, unitLeft: contentWidth + gap, contentWidth,
|
|
13
|
+
valueWidth, numberLeft: contentWidth - valueWidth,
|
|
14
|
+
barWidth: Math.max(0, contentWidth - valueWidth - 8),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { numericLayout } from '../inspector/numeric-layout.js';
|
|
1
2
|
import { IconRegistry } from '../core/icon-registry.js';
|
|
2
3
|
|
|
3
4
|
const DISABLED_ALPHA = 0.72;
|
|
@@ -9,6 +10,9 @@ export class TreeRowRenderer {
|
|
|
9
10
|
this.scene = null;
|
|
10
11
|
this.iconRegistry = iconRegistry ?? new IconRegistry();
|
|
11
12
|
this.renderedRows = 0;
|
|
13
|
+
this.iconRenderQueued = false;
|
|
14
|
+
this.preparedIconThemeKey = null;
|
|
15
|
+
this.stopIconListener = this.iconRegistry.onChange?.(() => this.#scheduleIconRender()) ?? null;
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
/** @param {HTMLCanvasElement} canvas */
|
|
@@ -22,6 +26,14 @@ export class TreeRowRenderer {
|
|
|
22
26
|
this.scene = scene;
|
|
23
27
|
}
|
|
24
28
|
|
|
29
|
+
destroy() {
|
|
30
|
+
this.stopIconListener?.();
|
|
31
|
+
this.stopIconListener = null;
|
|
32
|
+
this.canvas = null;
|
|
33
|
+
this.ctx = null;
|
|
34
|
+
this.scene = null;
|
|
35
|
+
}
|
|
36
|
+
|
|
25
37
|
updateDynamicState(_patches) {
|
|
26
38
|
// Canvas2D reads the state map directly; patches still stay on the hot path.
|
|
27
39
|
}
|
|
@@ -43,6 +55,7 @@ export class TreeRowRenderer {
|
|
|
43
55
|
|
|
44
56
|
const ctx = this.ctx;
|
|
45
57
|
const colors = theme.colors;
|
|
58
|
+
this.#prepareThemeIcons(theme, dpr);
|
|
46
59
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
47
60
|
ctx.fillStyle = colors.background;
|
|
48
61
|
ctx.fillRect(0, 0, viewportWidth, viewportHeight);
|
|
@@ -55,6 +68,29 @@ export class TreeRowRenderer {
|
|
|
55
68
|
ctx.restore();
|
|
56
69
|
}
|
|
57
70
|
|
|
71
|
+
#scheduleIconRender() {
|
|
72
|
+
if (this.iconRenderQueued) return;
|
|
73
|
+
this.iconRenderQueued = true;
|
|
74
|
+
const schedule = globalThis.requestAnimationFrame ?? ((callback) => setTimeout(callback, 0));
|
|
75
|
+
schedule(() => {
|
|
76
|
+
this.iconRenderQueued = false;
|
|
77
|
+
this.render();
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
#prepareThemeIcons(theme, pixelRatio) {
|
|
82
|
+
const iconColors = new Map([['placeholder', theme.colors.textMuted]]);
|
|
83
|
+
for (const style of Object.values(theme.types ?? {})) {
|
|
84
|
+
if (style?.icon) iconColors.set(style.icon, style.color ?? theme.colors.progressFill);
|
|
85
|
+
}
|
|
86
|
+
const key = `${pixelRatio}|${[...iconColors].map(([icon, color]) => `${icon}:${color}`).join(',')}`;
|
|
87
|
+
if (key === this.preparedIconThemeKey) return;
|
|
88
|
+
this.preparedIconThemeKey = key;
|
|
89
|
+
for (const [icon, color] of iconColors) {
|
|
90
|
+
this.iconRegistry.prepare?.({ icons: [icon], size: 15, color, pixelRatio });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
58
94
|
#drawHeader(ctx) {
|
|
59
95
|
const { viewport, columns, theme, sort, headerFilter, filterQuery } = this.scene;
|
|
60
96
|
if (viewport.headerHeight <= 0) return;
|
|
@@ -309,13 +345,22 @@ export class TreeRowRenderer {
|
|
|
309
345
|
const readonly = data.readonly;
|
|
310
346
|
const x = rect.x + 10;
|
|
311
347
|
const y = rect.y + 5;
|
|
312
|
-
const
|
|
348
|
+
const fullWidth = Math.max(24, rect.width - 20);
|
|
349
|
+
const numeric = numericLayout(fullWidth, data);
|
|
350
|
+
const width = numeric.contentWidth;
|
|
313
351
|
const height = rect.height - 10;
|
|
314
352
|
ctx.font = theme.font;
|
|
315
353
|
ctx.textBaseline = 'middle';
|
|
316
354
|
ctx.textAlign = 'left';
|
|
317
355
|
ctx.globalAlpha = disabled ? DISABLED_ALPHA : 1;
|
|
318
356
|
|
|
357
|
+
if (numeric.unit && numeric.unitWidth) {
|
|
358
|
+
ctx.font = theme.unitFont ?? theme.monoFont ?? theme.font;
|
|
359
|
+
ctx.fillStyle = theme.colors.unit ?? theme.colors.textMuted;
|
|
360
|
+
drawTruncatedText(ctx, numeric.unit, x + numeric.unitLeft, rect.y + rect.height / 2, numeric.unitWidth);
|
|
361
|
+
ctx.font = theme.font;
|
|
362
|
+
}
|
|
363
|
+
|
|
319
364
|
if (data.editorType === 'checkbox') {
|
|
320
365
|
this.#drawCheckbox(ctx, x, rect.y + rect.height / 2 - 8, Boolean(data.value), theme);
|
|
321
366
|
} else if (data.editorType === 'range') {
|
|
@@ -350,7 +395,15 @@ export class TreeRowRenderer {
|
|
|
350
395
|
this.#drawMutedText(ctx, data.valueText, x + 8, rect.y + rect.height / 2, selectWidth - 30, theme);
|
|
351
396
|
this.#drawSelectChevron(ctx, x + selectWidth - 18, rect.y + rect.height / 2, theme, disabled);
|
|
352
397
|
} else {
|
|
353
|
-
|
|
398
|
+
if (numeric.unit) {
|
|
399
|
+
ctx.font = theme.monoFont ?? theme.font;
|
|
400
|
+
ctx.fillStyle = readonly ? theme.colors.textMuted : theme.colors.text;
|
|
401
|
+
ctx.textAlign = 'right';
|
|
402
|
+
drawTruncatedText(ctx, data.valueText, x + width, rect.y + rect.height / 2, width);
|
|
403
|
+
ctx.textAlign = 'left';
|
|
404
|
+
} else {
|
|
405
|
+
this.#drawMutedText(ctx, data.valueText, x, rect.y + rect.height / 2, width, theme, readonly);
|
|
406
|
+
}
|
|
354
407
|
}
|
|
355
408
|
|
|
356
409
|
if (meta.updated && !suppressUpdatedMarker) {
|
|
@@ -397,9 +450,8 @@ export class TreeRowRenderer {
|
|
|
397
450
|
const max = meta.max ?? 100;
|
|
398
451
|
const value = typeof data.value === 'number' ? data.value : min;
|
|
399
452
|
const ratio = max === min ? 0 : clamp01((value - min) / (max - min));
|
|
400
|
-
const valueWidth =
|
|
453
|
+
const { valueWidth, barWidth } = numericLayout(width);
|
|
401
454
|
const gap = 8;
|
|
402
|
-
const barWidth = Math.max(24, width - valueWidth - gap);
|
|
403
455
|
this.#drawMeterBar(ctx, x, y + 0.5, barWidth, 7, ratio, theme.colors.progressFill, theme);
|
|
404
456
|
this.#drawControlSurface(ctx, x + barWidth + gap, y - 6, valueWidth, 20, theme, {
|
|
405
457
|
hovered: Boolean(state.hoveredNumber),
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
TreeViewViewport,
|
|
13
13
|
VisibleRowModel,
|
|
14
14
|
} from './core/index.js';
|
|
15
|
+
import { numericLayout } from './inspector/numeric-layout.js';
|
|
15
16
|
import { CellEditorManager } from './inspector/cell-editor-manager.js';
|
|
16
17
|
import { formatInspectorValue, getAtPath, inspectorColumns, inspectorPaneColumns, ModelInspectorBuilder, setAtPath } from './inspector/index.js';
|
|
17
18
|
import { TreeViewInputController } from './input/tree-view-input-controller.js';
|
|
@@ -113,6 +114,7 @@ export class TreeViewController {
|
|
|
113
114
|
this.#nativeScroll = null;
|
|
114
115
|
this.inputController = null;
|
|
115
116
|
this.cellEditor = null;
|
|
117
|
+
this.renderer?.destroy?.();
|
|
116
118
|
this.canvas = null;
|
|
117
119
|
}
|
|
118
120
|
|
|
@@ -212,8 +214,14 @@ export class TreeViewController {
|
|
|
212
214
|
|
|
213
215
|
enableWorkers(workerUrl) {
|
|
214
216
|
if (this.workerClient) return Promise.resolve(this);
|
|
215
|
-
|
|
216
|
-
|
|
217
|
+
const client = new TreeWorkerClient(workerUrl);
|
|
218
|
+
this.workerClient = client;
|
|
219
|
+
return client.setData(this.model.nodes)
|
|
220
|
+
.then(() => this)
|
|
221
|
+
.catch((error) => {
|
|
222
|
+
if (this.workerClient === client) this.disableWorkers();
|
|
223
|
+
throw error;
|
|
224
|
+
});
|
|
217
225
|
}
|
|
218
226
|
|
|
219
227
|
disableWorkers() {
|
|
@@ -782,11 +790,11 @@ export class TreeViewController {
|
|
|
782
790
|
const node = this.model.nodes[row.nodeIndex];
|
|
783
791
|
const data = node?.data;
|
|
784
792
|
if (!data?.inspector) return 'cell';
|
|
793
|
+
const numeric = numericLayout(Math.max(24, editorWidth - 20), data);
|
|
794
|
+
if (numeric.unit && localEditorX >= 10 + numeric.unitLeft) return 'unit';
|
|
785
795
|
if (data.editorType === 'checkbox') return 'checkbox';
|
|
786
796
|
if (data.editorType === 'range') {
|
|
787
|
-
const
|
|
788
|
-
const valueWidth = Math.min(64, Math.max(42, width * 0.28));
|
|
789
|
-
const numberLeft = editorWidth - valueWidth - 10;
|
|
797
|
+
const numberLeft = 10 + numeric.numberLeft;
|
|
790
798
|
return localEditorX >= numberLeft ? 'number' : 'range';
|
|
791
799
|
}
|
|
792
800
|
if (data.editorType === 'button') return 'button';
|