virtual-tree-canvas 0.6.0 → 0.7.1
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/CHANGELOG.md +32 -0
- package/README.md +90 -3
- package/docs/icon-catalog.html +68 -66
- package/docs/icon-catalog.json +12 -0
- package/docs/icon-catalog.md +111 -107
- package/docs/performance/dynamic-updates-0.7.1.md +38 -0
- package/package.json +3 -2
- package/resources/icons/star-filled.svg +1 -0
- package/resources/icons/trash.svg +1 -0
- package/src/assets/icons.js +12 -4
- package/src/core/dynamic-state.js +19 -29
- package/src/core/patch-batcher.js +4 -0
- package/src/core/theme-manager.js +238 -61
- package/src/core/tree-cell-layout.js +11 -0
- package/src/core/tree-column-model.js +81 -22
- package/src/core/tree-model.js +4 -2
- package/src/core/types.js +1 -0
- package/src/core/view-options.js +98 -18
- package/src/core/visible-row-model.js +51 -21
- package/src/index.d.ts +70 -1
- package/src/input/row-actions.js +248 -0
- package/src/input/row-reorder-input.js +134 -36
- package/src/input/tree-tooltip.js +39 -5
- package/src/input/tree-view-input-controller.js +35 -10
- package/src/inspector/cell-editor-manager.js +128 -35
- package/src/inspector/pane-layout.js +20 -0
- package/src/renderers/checkbox.js +13 -0
- package/src/renderers/tree-row-renderer.js +356 -107
- package/src/tree-view-controller.js +968 -236
- package/src/tree-view.js +218 -54
- package/src/view/styles.js +83 -12
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export class TreeViewInputController {
|
|
2
|
+
|
|
2
3
|
/**
|
|
3
4
|
* @param {{
|
|
4
5
|
* controller: import('../tree-view-controller.js').TreeViewController,
|
|
@@ -6,8 +7,10 @@ export class TreeViewInputController {
|
|
|
6
7
|
* }} options
|
|
7
8
|
*/
|
|
8
9
|
constructor(options) {
|
|
9
|
-
if (!options?.controller)
|
|
10
|
-
|
|
10
|
+
if (!options?.controller)
|
|
11
|
+
throw new TypeError('TreeViewInputController requires a TreeViewController');
|
|
12
|
+
if (!options.controller.canvas)
|
|
13
|
+
throw new Error('TreeViewInputController requires an initialized TreeViewController canvas');
|
|
11
14
|
this.controller = options.controller;
|
|
12
15
|
this.canvas = options.controller.canvas;
|
|
13
16
|
this.hoveredId = null;
|
|
@@ -15,10 +18,12 @@ export class TreeViewInputController {
|
|
|
15
18
|
this.cellEditor = options.cellEditor ?? null;
|
|
16
19
|
this.canvas.tabIndex = 0;
|
|
17
20
|
|
|
18
|
-
this.canvas.addEventListener('wheel', this.#onWheel, {
|
|
21
|
+
this.canvas.addEventListener('wheel', this.#onWheel, {
|
|
22
|
+
passive: false
|
|
23
|
+
});
|
|
19
24
|
this.canvas.addEventListener('mousedown', this.#onMouseDown);
|
|
20
25
|
this.canvas.addEventListener('mousemove', this.#onMouseMove);
|
|
21
|
-
window.addEventListener('mouseup', this.#onMouseUp);
|
|
26
|
+
(this.canvas.ownerDocument.defaultView ?? window).addEventListener('mouseup', this.#onMouseUp);
|
|
22
27
|
this.canvas.addEventListener('mouseleave', this.#onMouseLeave);
|
|
23
28
|
this.canvas.addEventListener('click', this.#onClick);
|
|
24
29
|
this.canvas.addEventListener('dblclick', this.#onDoubleClick);
|
|
@@ -29,7 +34,10 @@ export class TreeViewInputController {
|
|
|
29
34
|
this.canvas.removeEventListener('wheel', this.#onWheel);
|
|
30
35
|
this.canvas.removeEventListener('mousedown', this.#onMouseDown);
|
|
31
36
|
this.canvas.removeEventListener('mousemove', this.#onMouseMove);
|
|
32
|
-
window.removeEventListener(
|
|
37
|
+
(this.canvas.ownerDocument.defaultView ?? window).removeEventListener(
|
|
38
|
+
'mouseup',
|
|
39
|
+
this.#onMouseUp
|
|
40
|
+
);
|
|
33
41
|
this.canvas.removeEventListener('mouseleave', this.#onMouseLeave);
|
|
34
42
|
this.canvas.removeEventListener('click', this.#onClick);
|
|
35
43
|
this.canvas.removeEventListener('dblclick', this.#onDoubleClick);
|
|
@@ -71,13 +79,18 @@ export class TreeViewInputController {
|
|
|
71
79
|
};
|
|
72
80
|
|
|
73
81
|
#onClick = (event) => {
|
|
74
|
-
this.
|
|
82
|
+
if (this.controller.rowReorderInput?.suppressClick) {
|
|
83
|
+
this.controller.rowReorderInput.suppressClick = false;
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
75
87
|
const hit = this.#hitTest(event);
|
|
76
88
|
if (!hit) return;
|
|
77
89
|
if (['rowDrag', 'rowUp', 'rowDown'].includes(hit.part)) return;
|
|
78
90
|
if (hit.area === 'header') {
|
|
79
91
|
if (hit.part === 'filter' && this.cellEditor?.handleHeaderClick(event, hit)) return;
|
|
80
|
-
if (!this.resizeDrag && hit.part === 'label' && hit.column)
|
|
92
|
+
if (!this.resizeDrag && hit.part === 'label' && hit.column)
|
|
93
|
+
this.controller.sortBy(hit.column.id);
|
|
81
94
|
return;
|
|
82
95
|
}
|
|
83
96
|
if (hit.part === 'chevron' && hit.row.hasChildren) {
|
|
@@ -85,11 +98,14 @@ export class TreeViewInputController {
|
|
|
85
98
|
return;
|
|
86
99
|
}
|
|
87
100
|
if (this.cellEditor?.handleClick(event, hit)) return;
|
|
101
|
+
this.canvas.focus({
|
|
102
|
+
preventScroll: true
|
|
103
|
+
});
|
|
88
104
|
this.controller.clickNode(hit.row.nodeId, {
|
|
89
105
|
shiftKey: event.shiftKey,
|
|
90
106
|
ctrlKey: event.ctrlKey,
|
|
91
107
|
metaKey: event.metaKey,
|
|
92
|
-
multi: this.canvas.dataset.multi === 'true'
|
|
108
|
+
multi: this.canvas.dataset.multi === 'true'
|
|
93
109
|
});
|
|
94
110
|
};
|
|
95
111
|
|
|
@@ -99,6 +115,7 @@ export class TreeViewInputController {
|
|
|
99
115
|
};
|
|
100
116
|
|
|
101
117
|
#onMouseDown = (event) => {
|
|
118
|
+
if (this.controller.rowReorderInput?.gesture) return;
|
|
102
119
|
const hit = this.#hitTest(event);
|
|
103
120
|
if (['rowDrag', 'rowUp', 'rowDown'].includes(hit?.part)) return;
|
|
104
121
|
this.controller.setActiveHit(hit);
|
|
@@ -111,7 +128,7 @@ export class TreeViewInputController {
|
|
|
111
128
|
this.resizeDrag = {
|
|
112
129
|
columnId: hit.column.id,
|
|
113
130
|
startX: event.clientX - rect.left,
|
|
114
|
-
startWidth: hit.column.width
|
|
131
|
+
startWidth: hit.column.width
|
|
115
132
|
};
|
|
116
133
|
this.canvas.style.cursor = 'col-resize';
|
|
117
134
|
event.preventDefault();
|
|
@@ -135,6 +152,7 @@ export class TreeViewInputController {
|
|
|
135
152
|
const clientY = event.clientY - rect.top;
|
|
136
153
|
return this.controller.hitTest(clientX, clientY);
|
|
137
154
|
}
|
|
155
|
+
|
|
138
156
|
}
|
|
139
157
|
|
|
140
158
|
function cursorForHit(hit) {
|
|
@@ -143,7 +161,14 @@ function cursorForHit(hit) {
|
|
|
143
161
|
if (hit?.area === 'header' && hit.part === 'resize') return 'col-resize';
|
|
144
162
|
if (hit?.area === 'header' && hit.part === 'filter') return 'text';
|
|
145
163
|
if (hit?.area !== 'row') return '';
|
|
146
|
-
if (
|
|
164
|
+
if (
|
|
165
|
+
hit.part === 'button' ||
|
|
166
|
+
hit.part === 'checkbox' ||
|
|
167
|
+
hit.part === 'arrayAdd' ||
|
|
168
|
+
hit.part === 'arrayRemove' ||
|
|
169
|
+
hit.part === 'chevron'
|
|
170
|
+
)
|
|
171
|
+
return 'pointer';
|
|
147
172
|
if (hit.part === 'editor' || hit.part === 'number') return 'text';
|
|
148
173
|
if (hit.part === 'range') return 'ew-resize';
|
|
149
174
|
return '';
|
|
@@ -3,14 +3,17 @@ import { numericLayout } from './numeric-layout.js';
|
|
|
3
3
|
export class CellEditorManager {
|
|
4
4
|
constructor({ controller, host = null }) {
|
|
5
5
|
if (!controller) throw new TypeError('CellEditorManager requires a TreeViewController');
|
|
6
|
-
if (!controller.canvas)
|
|
6
|
+
if (!controller.canvas)
|
|
7
|
+
throw new Error('CellEditorManager requires an initialized TreeViewController canvas');
|
|
7
8
|
this.controller = controller;
|
|
8
9
|
this.canvas = controller.canvas;
|
|
10
|
+
this.view = this.canvas.ownerDocument.defaultView;
|
|
9
11
|
this.host = host ?? controller.canvas.parentElement ?? document.body;
|
|
10
12
|
this.editable = true;
|
|
11
13
|
this.overlay = null;
|
|
12
14
|
this.rangeDrag = null;
|
|
13
15
|
this.overlayKind = null;
|
|
16
|
+
this.overlayNode = null;
|
|
14
17
|
this.stopFilter = controller.on('filterchange', ({ detail }) => {
|
|
15
18
|
if (this.overlayKind === 'filter' && this.overlay) this.overlay.value = detail.query;
|
|
16
19
|
});
|
|
@@ -26,15 +29,32 @@ export class CellEditorManager {
|
|
|
26
29
|
destroy() {
|
|
27
30
|
this.stopFilter();
|
|
28
31
|
this.#removeOverlay();
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
this.view.removeEventListener('mousemove', this.onMouseMove);
|
|
33
|
+
this.view.removeEventListener('mouseup', this.onMouseUp);
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
close() {
|
|
34
37
|
this.#removeOverlay();
|
|
35
38
|
this.rangeDrag = null;
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
this.view.removeEventListener('mousemove', this.onMouseMove);
|
|
40
|
+
this.view.removeEventListener('mouseup', this.onMouseUp);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Keep an in-progress edit while values refresh; cancel if its editor changes.
|
|
45
|
+
*/
|
|
46
|
+
reconcile(nodes) {
|
|
47
|
+
const previous = this.overlayNode ?? this.rangeDrag?.node;
|
|
48
|
+
if (!previous) return;
|
|
49
|
+
const next = nodes.find((node) => node.id === previous.id);
|
|
50
|
+
const signature = (data) =>
|
|
51
|
+
JSON.stringify([
|
|
52
|
+
data.editorType,
|
|
53
|
+
data.readonly,
|
|
54
|
+
data.disabled,
|
|
55
|
+
...['min', 'max', 'step', 'integer', 'options', 'unit'].map((key) => data.meta?.[key])
|
|
56
|
+
]);
|
|
57
|
+
if (!next || signature(previous.data) !== signature(next.data)) this.close();
|
|
38
58
|
}
|
|
39
59
|
|
|
40
60
|
handlePointerDown(event, hit) {
|
|
@@ -42,15 +62,21 @@ export class CellEditorManager {
|
|
|
42
62
|
const data = hit.row ? this.controller.model.nodes[hit.row.nodeIndex]?.data : null;
|
|
43
63
|
if (!data || data.readonly || data.disabled) return false;
|
|
44
64
|
if (data.editorType === 'range' && hit.part === 'range') {
|
|
45
|
-
this.rangeDrag = {
|
|
65
|
+
this.rangeDrag = {
|
|
66
|
+
hit,
|
|
67
|
+
data,
|
|
68
|
+
node: this.controller.model.nodes[hit.row.nodeIndex]
|
|
69
|
+
};
|
|
46
70
|
this.#updateRangeFromEvent(event);
|
|
47
|
-
|
|
48
|
-
|
|
71
|
+
this.view.addEventListener('mousemove', this.onMouseMove);
|
|
72
|
+
this.view.addEventListener('mouseup', this.onMouseUp);
|
|
49
73
|
return true;
|
|
50
74
|
}
|
|
51
75
|
if (shouldOpenOverlayOnPointerDown(data, hit)) {
|
|
52
76
|
const node = this.controller.model.nodes[hit.row.nodeIndex];
|
|
53
|
-
this.#showOverlay(node, hit, {
|
|
77
|
+
this.#showOverlay(node, hit, {
|
|
78
|
+
showPicker: true
|
|
79
|
+
});
|
|
54
80
|
return true;
|
|
55
81
|
}
|
|
56
82
|
return false;
|
|
@@ -94,7 +120,10 @@ export class CellEditorManager {
|
|
|
94
120
|
const hostRect = this.host.getBoundingClientRect();
|
|
95
121
|
const element = createEditorElement(data);
|
|
96
122
|
element.className = `vtc-editor vtc-editor-${data.editorType}`;
|
|
97
|
-
element.setAttribute(
|
|
123
|
+
element.setAttribute(
|
|
124
|
+
'aria-label',
|
|
125
|
+
[node.label, numericLayout(rect.width, data).unit].filter(Boolean).join(' ')
|
|
126
|
+
);
|
|
98
127
|
Object.assign(element.style, {
|
|
99
128
|
position: 'absolute',
|
|
100
129
|
left: `${rect.x - hostRect.left}px`,
|
|
@@ -114,8 +143,13 @@ export class CellEditorManager {
|
|
|
114
143
|
color: '#e5e7eb',
|
|
115
144
|
boxShadow: '0 0 0 1px rgba(15, 23, 42, 0.8), 0 8px 20px rgba(0, 0, 0, 0.28)',
|
|
116
145
|
font: editorFont(data),
|
|
117
|
-
padding:
|
|
118
|
-
|
|
146
|
+
padding:
|
|
147
|
+
data.editorType === 'color'
|
|
148
|
+
? '0 2px'
|
|
149
|
+
: data.editorType === 'select'
|
|
150
|
+
? '0 22px 0 8px'
|
|
151
|
+
: '0 8px',
|
|
152
|
+
textAlign: data.editorType === 'number' || data.editorType === 'range' ? 'right' : 'left'
|
|
119
153
|
});
|
|
120
154
|
let committed = false;
|
|
121
155
|
const commit = () => {
|
|
@@ -137,7 +171,10 @@ export class CellEditorManager {
|
|
|
137
171
|
this.host.append(element);
|
|
138
172
|
this.overlay = element;
|
|
139
173
|
this.overlayKind = 'value';
|
|
140
|
-
|
|
174
|
+
this.overlayNode = node;
|
|
175
|
+
element.focus({
|
|
176
|
+
preventScroll: true
|
|
177
|
+
});
|
|
141
178
|
element.select?.();
|
|
142
179
|
if (options.showPicker && element.showPicker) {
|
|
143
180
|
requestAnimationFrame(() => {
|
|
@@ -153,12 +190,18 @@ export class CellEditorManager {
|
|
|
153
190
|
#showHeaderFilterOverlay(hit) {
|
|
154
191
|
this.#removeOverlay();
|
|
155
192
|
const headerRect = this.controller.getHeaderClientRect(hit);
|
|
156
|
-
const rect = this.#clampRectToHost(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
193
|
+
const rect = this.#clampRectToHost(
|
|
194
|
+
{
|
|
195
|
+
x: headerRect.x + 8,
|
|
196
|
+
y: headerRect.y + 5,
|
|
197
|
+
width: Math.max(
|
|
198
|
+
24,
|
|
199
|
+
Math.min(headerRect.width, this.controller.viewport.contentViewportWidth) - 16
|
|
200
|
+
),
|
|
201
|
+
height: Math.max(20, headerRect.height - 10)
|
|
202
|
+
},
|
|
203
|
+
0
|
|
204
|
+
);
|
|
162
205
|
const hostRect = this.host.getBoundingClientRect();
|
|
163
206
|
const element = document.createElement('input');
|
|
164
207
|
element.type = 'search';
|
|
@@ -181,7 +224,7 @@ export class CellEditorManager {
|
|
|
181
224
|
background: '#0b1020',
|
|
182
225
|
color: '#e5e7eb',
|
|
183
226
|
font: SANS_FONT,
|
|
184
|
-
padding: '0 8px'
|
|
227
|
+
padding: '0 8px'
|
|
185
228
|
});
|
|
186
229
|
element.addEventListener('input', () => this.controller.setFilter(element.value));
|
|
187
230
|
element.addEventListener('keydown', (event) => {
|
|
@@ -193,7 +236,9 @@ export class CellEditorManager {
|
|
|
193
236
|
this.host.append(element);
|
|
194
237
|
this.overlay = element;
|
|
195
238
|
this.overlayKind = 'filter';
|
|
196
|
-
element.focus({
|
|
239
|
+
element.focus({
|
|
240
|
+
preventScroll: true
|
|
241
|
+
});
|
|
197
242
|
element.select();
|
|
198
243
|
}
|
|
199
244
|
|
|
@@ -219,23 +264,44 @@ export class CellEditorManager {
|
|
|
219
264
|
|
|
220
265
|
#onMouseUp() {
|
|
221
266
|
this.rangeDrag = null;
|
|
222
|
-
|
|
223
|
-
|
|
267
|
+
this.view.removeEventListener('mousemove', this.onMouseMove);
|
|
268
|
+
this.view.removeEventListener('mouseup', this.onMouseUp);
|
|
224
269
|
}
|
|
225
270
|
|
|
226
271
|
#isEditableHit(hit) {
|
|
227
|
-
return
|
|
272
|
+
return (
|
|
273
|
+
hit?.area === 'row' &&
|
|
274
|
+
hit.part !== 'unit' &&
|
|
275
|
+
(hit.column?.kind === 'inspectorValue' || hit.column?.kind === 'inspectorPane')
|
|
276
|
+
);
|
|
228
277
|
}
|
|
229
278
|
|
|
230
279
|
#editorContentRect(hit) {
|
|
231
280
|
const rect = this.controller.getCellClientRect(hit);
|
|
232
281
|
if (hit.column?.kind !== 'inspectorPane') {
|
|
233
|
-
return {
|
|
282
|
+
return {
|
|
283
|
+
x: rect.x + 10,
|
|
284
|
+
y: rect.y + 4,
|
|
285
|
+
width: Math.max(24, rect.width - 20),
|
|
286
|
+
height: rect.height - 8
|
|
287
|
+
};
|
|
234
288
|
}
|
|
235
|
-
const visibleWidth = Math.max(
|
|
289
|
+
const visibleWidth = Math.max(
|
|
290
|
+
1,
|
|
291
|
+
Math.min(rect.width, this.controller.viewport.contentViewportWidth)
|
|
292
|
+
);
|
|
236
293
|
const data = this.controller.model.nodes[hit.row.nodeIndex]?.data ?? {};
|
|
237
|
-
const { editorLeft, editorWidth } = this.controller.getInspectorPaneLayout(
|
|
238
|
-
|
|
294
|
+
const { editorLeft, editorWidth } = this.controller.getInspectorPaneLayout(
|
|
295
|
+
visibleWidth,
|
|
296
|
+
hit.row,
|
|
297
|
+
data.editorType
|
|
298
|
+
);
|
|
299
|
+
return {
|
|
300
|
+
x: rect.x + editorLeft + 10,
|
|
301
|
+
y: rect.y + 4,
|
|
302
|
+
width: Math.max(24, editorWidth - 20),
|
|
303
|
+
height: rect.height - 8
|
|
304
|
+
};
|
|
239
305
|
}
|
|
240
306
|
|
|
241
307
|
#overlayRect(hit) {
|
|
@@ -243,9 +309,17 @@ export class CellEditorManager {
|
|
|
243
309
|
const rect = this.#editorContentRect(hit);
|
|
244
310
|
const layout = numericLayout(rect.width, data);
|
|
245
311
|
if (hit.part === 'number' && data.editorType === 'range') {
|
|
246
|
-
return {
|
|
312
|
+
return {
|
|
313
|
+
...rect,
|
|
314
|
+
x: rect.x + layout.numberLeft,
|
|
315
|
+
width: layout.valueWidth
|
|
316
|
+
};
|
|
247
317
|
}
|
|
248
|
-
if (layout.unit)
|
|
318
|
+
if (layout.unit)
|
|
319
|
+
return {
|
|
320
|
+
...rect,
|
|
321
|
+
width: layout.contentWidth
|
|
322
|
+
};
|
|
249
323
|
if (hit.column?.kind !== 'inspectorPane') return this.controller.getCellClientRect(hit);
|
|
250
324
|
return rect;
|
|
251
325
|
}
|
|
@@ -253,7 +327,12 @@ export class CellEditorManager {
|
|
|
253
327
|
#rangeBarRect(hit) {
|
|
254
328
|
const data = this.controller.model.nodes[hit.row.nodeIndex]?.data ?? {};
|
|
255
329
|
const rect = this.#editorContentRect(hit);
|
|
256
|
-
return {
|
|
330
|
+
return {
|
|
331
|
+
...rect,
|
|
332
|
+
y: rect.y + rect.height / 2 - 4,
|
|
333
|
+
width: numericLayout(rect.width, data).barWidth,
|
|
334
|
+
height: 8
|
|
335
|
+
};
|
|
257
336
|
}
|
|
258
337
|
|
|
259
338
|
#clampRectToHost(rect, inset = 0) {
|
|
@@ -266,13 +345,19 @@ export class CellEditorManager {
|
|
|
266
345
|
const height = Math.max(18, Math.min(rect.height, maxY - minY));
|
|
267
346
|
const x = Math.max(minX, Math.min(rect.x, maxX - width));
|
|
268
347
|
const y = Math.max(minY, Math.min(rect.y, maxY - height));
|
|
269
|
-
return {
|
|
348
|
+
return {
|
|
349
|
+
x,
|
|
350
|
+
y,
|
|
351
|
+
width,
|
|
352
|
+
height
|
|
353
|
+
};
|
|
270
354
|
}
|
|
271
355
|
|
|
272
356
|
#removeOverlay() {
|
|
273
357
|
const overlay = this.overlay;
|
|
274
358
|
this.overlay = null;
|
|
275
359
|
this.overlayKind = null;
|
|
360
|
+
this.overlayNode = null;
|
|
276
361
|
overlay?.remove();
|
|
277
362
|
}
|
|
278
363
|
|
|
@@ -280,9 +365,11 @@ export class CellEditorManager {
|
|
|
280
365
|
const meta = data.meta ?? {};
|
|
281
366
|
const min = Number.isFinite(meta.min) ? meta.min : 0;
|
|
282
367
|
const max = Number.isFinite(meta.max) ? meta.max : 100;
|
|
283
|
-
const current =
|
|
368
|
+
const current =
|
|
369
|
+
typeof data.value === 'number' && Number.isFinite(data.value) ? data.value : min;
|
|
284
370
|
this.controller.updateInspectorValue(node.id, current <= min ? max : min, 'range');
|
|
285
371
|
}
|
|
372
|
+
|
|
286
373
|
}
|
|
287
374
|
|
|
288
375
|
function createEditorElement(data) {
|
|
@@ -312,7 +399,12 @@ function createEditorElement(data) {
|
|
|
312
399
|
function shouldOpenOverlayOnPointerDown(data, hit) {
|
|
313
400
|
if (data.readonly || data.disabled) return false;
|
|
314
401
|
if (data.editorType === 'range') return hit.part === 'number';
|
|
315
|
-
return
|
|
402
|
+
return (
|
|
403
|
+
data.editorType === 'text' ||
|
|
404
|
+
data.editorType === 'number' ||
|
|
405
|
+
data.editorType === 'select' ||
|
|
406
|
+
data.editorType === 'color'
|
|
407
|
+
);
|
|
316
408
|
}
|
|
317
409
|
|
|
318
410
|
function ensureOverlayHost(host) {
|
|
@@ -338,7 +430,8 @@ function parseEditorValue(element, data) {
|
|
|
338
430
|
}
|
|
339
431
|
|
|
340
432
|
const SANS_FONT = '12px Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif';
|
|
341
|
-
const MONO_FONT =
|
|
433
|
+
const MONO_FONT =
|
|
434
|
+
'12px "JetBrains Mono", "Cascadia Mono", "Fira Code", ui-monospace, SFMono-Regular, Consolas, monospace';
|
|
342
435
|
|
|
343
436
|
function editorFont(data) {
|
|
344
437
|
return data.editorType === 'number' || data.editorType === 'range' ? MONO_FONT : SANS_FONT;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function inspectorPaneLayout(
|
|
2
|
+
width,
|
|
3
|
+
depth = 0,
|
|
4
|
+
indentWidth = 18,
|
|
5
|
+
editorType = '',
|
|
6
|
+
labelEnd = 0
|
|
7
|
+
) {
|
|
8
|
+
const safeWidth = Math.max(1, width);
|
|
9
|
+
const rightPadding = 14;
|
|
10
|
+
const minEditor = Math.min(170, Math.max(96, safeWidth * 0.45));
|
|
11
|
+
const minLabelEnd = Math.max(88, depth * indentWidth + 104);
|
|
12
|
+
const preferredLeft = Math.max(minLabelEnd, labelEnd, safeWidth * 0.32);
|
|
13
|
+
const maxLeft = Math.max(64, safeWidth - rightPadding - minEditor);
|
|
14
|
+
const editorLeft = Math.max(64, Math.min(preferredLeft, maxLeft));
|
|
15
|
+
const editorWidth = Math.max(56, safeWidth - rightPadding - editorLeft);
|
|
16
|
+
return {
|
|
17
|
+
editorLeft,
|
|
18
|
+
editorWidth
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function drawCheckbox(ctx, x, y, checked, theme) {
|
|
2
|
+
ctx.beginPath();
|
|
3
|
+
ctx.roundRect(x, y, 16, 16, 3);
|
|
4
|
+
ctx.fillStyle = theme.colors.progressTrack;
|
|
5
|
+
ctx.fill();
|
|
6
|
+
ctx.strokeStyle = checked ? theme.colors.progressFill : theme.colors.textMuted;
|
|
7
|
+
ctx.stroke();
|
|
8
|
+
if (!checked) return;
|
|
9
|
+
ctx.fillStyle = theme.colors.progressFill;
|
|
10
|
+
ctx.beginPath();
|
|
11
|
+
ctx.roundRect(x + 4, y + 4, 8, 8, 1.5);
|
|
12
|
+
ctx.fill();
|
|
13
|
+
}
|