virtual-tree-canvas 0.4.0 → 0.6.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.
Files changed (94) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +115 -28
  3. package/docs/icon-catalog.html +67 -0
  4. package/docs/icon-catalog.json +380 -0
  5. package/docs/icon-catalog.md +167 -0
  6. package/examples/generate-tree.js +86 -0
  7. package/examples/reorder-table.html +33 -0
  8. package/examples/units.html +43 -0
  9. package/package.json +7 -3
  10. package/resources/icons/bicycle.svg +1 -0
  11. package/resources/icons/bird.svg +1 -0
  12. package/resources/icons/box.svg +1 -0
  13. package/resources/icons/building.svg +1 -0
  14. package/resources/icons/calendar.svg +1 -0
  15. package/resources/icons/car.svg +1 -0
  16. package/resources/icons/cat.svg +1 -0
  17. package/resources/icons/child.svg +1 -0
  18. package/resources/icons/clock.svg +1 -0
  19. package/resources/icons/cloud.svg +1 -0
  20. package/resources/icons/data-bus.svg +1 -0
  21. package/resources/icons/database.svg +1 -0
  22. package/resources/icons/dog.svg +1 -0
  23. package/resources/icons/drone.svg +1 -0
  24. package/resources/icons/fish.svg +1 -0
  25. package/resources/icons/flower.svg +1 -0
  26. package/resources/icons/fog.svg +1 -0
  27. package/resources/icons/globe.svg +1 -0
  28. package/resources/icons/grip.svg +1 -0
  29. package/resources/icons/heart.svg +1 -0
  30. package/resources/icons/helicopter.svg +1 -0
  31. package/resources/icons/insect.svg +1 -0
  32. package/resources/icons/leaf.svg +1 -0
  33. package/resources/icons/link.svg +1 -0
  34. package/resources/icons/lock.svg +1 -0
  35. package/resources/icons/moon.svg +1 -0
  36. package/resources/icons/network.svg +1 -0
  37. package/resources/icons/people.svg +1 -0
  38. package/resources/icons/person.svg +1 -0
  39. package/resources/icons/pin.svg +1 -0
  40. package/resources/icons/radar.svg +1 -0
  41. package/resources/icons/rain.svg +1 -0
  42. package/resources/icons/seedling.svg +1 -0
  43. package/resources/icons/sensor.svg +1 -0
  44. package/resources/icons/server.svg +1 -0
  45. package/resources/icons/ship.svg +1 -0
  46. package/resources/icons/snow.svg +1 -0
  47. package/resources/icons/star.svg +1 -0
  48. package/resources/icons/storm.svg +1 -0
  49. package/resources/icons/sun.svg +1 -0
  50. package/resources/icons/thermometer.svg +1 -0
  51. package/resources/icons/tree.svg +1 -0
  52. package/resources/icons/wind.svg +1 -0
  53. package/src/assets/icons.js +73 -25
  54. package/src/core/icon-registry.js +6 -8
  55. package/src/core/theme-manager.js +15 -4
  56. package/src/core/tree-column-model.js +16 -3
  57. package/src/core/tree-model.js +20 -0
  58. package/src/core/view-options.js +30 -0
  59. package/src/index.d.ts +166 -5
  60. package/src/index.js +3 -0
  61. package/src/input/index.js +1 -0
  62. package/src/input/row-reorder-input.js +143 -0
  63. package/src/input/tree-filter-bar.js +55 -0
  64. package/src/input/tree-tooltip.js +55 -0
  65. package/src/input/tree-view-input-controller.js +5 -0
  66. package/src/inspector/cell-editor-manager.js +42 -22
  67. package/src/inspector/editor-resolver.js +6 -1
  68. package/src/inspector/numeric-layout.js +16 -0
  69. package/src/renderers/tree-row-renderer.js +79 -13
  70. package/src/tree-view-controller.js +261 -34
  71. package/src/tree-view.js +181 -0
  72. package/src/view/styles.js +14 -0
  73. package/src/assets/icons/air.svg +0 -1
  74. package/src/assets/icons/radar.svg +0 -1
  75. /package/{src/assets → resources}/icons/aircraft.svg +0 -0
  76. /package/{src/assets/icons/bus.svg → resources/icons/bus-vehicle.svg} +0 -0
  77. /package/{src/assets → resources}/icons/control.svg +0 -0
  78. /package/{src/assets → resources}/icons/damage.svg +0 -0
  79. /package/{src/assets → resources}/icons/error.svg +0 -0
  80. /package/{src/assets → resources}/icons/folder.svg +0 -0
  81. /package/{src/assets → resources}/icons/ground.svg +0 -0
  82. /package/{src/assets → resources}/icons/inspector-array.svg +0 -0
  83. /package/{src/assets → resources}/icons/inspector-object.svg +0 -0
  84. /package/{src/assets → resources}/icons/inspector-value.svg +0 -0
  85. /package/{src/assets → resources}/icons/munition.svg +0 -0
  86. /package/{src/assets → resources}/icons/placeholder.svg +0 -0
  87. /package/{src/assets → resources}/icons/point.svg +0 -0
  88. /package/{src/assets → resources}/icons/situation.svg +0 -0
  89. /package/{src/assets → resources}/icons/space.svg +0 -0
  90. /package/{src/assets → resources}/icons/subsurface.svg +0 -0
  91. /package/{src/assets → resources}/icons/surface.svg +0 -0
  92. /package/{src/assets → resources}/icons/task.svg +0 -0
  93. /package/{src/assets → resources}/icons/track.svg +0 -0
  94. /package/{src/assets → resources}/icons/warning.svg +0 -0
@@ -0,0 +1,380 @@
1
+ [
2
+ {
3
+ "id": "person",
4
+ "label": "Persona",
5
+ "category": "Personas y organización",
6
+ "available": true
7
+ },
8
+ {
9
+ "id": "people",
10
+ "label": "Grupo",
11
+ "category": "Personas y organización",
12
+ "available": true
13
+ },
14
+ {
15
+ "id": "child",
16
+ "label": "Infancia",
17
+ "category": "Personas y organización",
18
+ "available": true
19
+ },
20
+ {
21
+ "id": "dog",
22
+ "label": "Perro",
23
+ "category": "Animales",
24
+ "available": true
25
+ },
26
+ {
27
+ "id": "cat",
28
+ "label": "Gato",
29
+ "category": "Animales",
30
+ "available": true
31
+ },
32
+ {
33
+ "id": "bird",
34
+ "label": "Ave",
35
+ "category": "Animales",
36
+ "available": true
37
+ },
38
+ {
39
+ "id": "fish",
40
+ "label": "Pez",
41
+ "category": "Animales",
42
+ "available": true
43
+ },
44
+ {
45
+ "id": "insect",
46
+ "label": "Insecto",
47
+ "category": "Animales",
48
+ "available": true
49
+ },
50
+ {
51
+ "id": "tree",
52
+ "label": "Árbol",
53
+ "category": "Plantas y ecosistemas",
54
+ "available": true
55
+ },
56
+ {
57
+ "id": "leaf",
58
+ "label": "Hoja",
59
+ "category": "Plantas y ecosistemas",
60
+ "available": true
61
+ },
62
+ {
63
+ "id": "flower",
64
+ "label": "Flor",
65
+ "category": "Plantas y ecosistemas",
66
+ "available": true
67
+ },
68
+ {
69
+ "id": "seedling",
70
+ "label": "Brote",
71
+ "category": "Plantas y ecosistemas",
72
+ "available": true
73
+ },
74
+ {
75
+ "id": "sun",
76
+ "label": "Sol",
77
+ "category": "Tiempo atmosférico",
78
+ "available": true
79
+ },
80
+ {
81
+ "id": "moon",
82
+ "label": "Luna",
83
+ "category": "Tiempo atmosférico",
84
+ "available": true
85
+ },
86
+ {
87
+ "id": "cloud",
88
+ "label": "Nube",
89
+ "category": "Tiempo atmosférico",
90
+ "available": true
91
+ },
92
+ {
93
+ "id": "rain",
94
+ "label": "Lluvia",
95
+ "category": "Tiempo atmosférico",
96
+ "available": true
97
+ },
98
+ {
99
+ "id": "snow",
100
+ "label": "Nieve",
101
+ "category": "Tiempo atmosférico",
102
+ "available": true
103
+ },
104
+ {
105
+ "id": "wind",
106
+ "label": "Viento",
107
+ "category": "Tiempo atmosférico",
108
+ "available": true
109
+ },
110
+ {
111
+ "id": "storm",
112
+ "label": "Tormenta",
113
+ "category": "Tiempo atmosférico",
114
+ "available": true
115
+ },
116
+ {
117
+ "id": "fog",
118
+ "label": "Niebla",
119
+ "category": "Tiempo atmosférico",
120
+ "available": true
121
+ },
122
+ {
123
+ "id": "ground",
124
+ "label": "Dominio terrestre",
125
+ "category": "Terreno y agua",
126
+ "available": true
127
+ },
128
+ {
129
+ "id": "surface",
130
+ "label": "Dominio de superficie",
131
+ "category": "Terreno y agua",
132
+ "available": true
133
+ },
134
+ {
135
+ "id": "subsurface",
136
+ "label": "Dominio submarino",
137
+ "category": "Terreno y agua",
138
+ "available": true
139
+ },
140
+ {
141
+ "id": "car",
142
+ "label": "Coche",
143
+ "category": "Transporte terrestre",
144
+ "available": true
145
+ },
146
+ {
147
+ "id": "bicycle",
148
+ "label": "Bicicleta",
149
+ "category": "Transporte terrestre",
150
+ "available": true
151
+ },
152
+ {
153
+ "id": "bus-vehicle",
154
+ "label": "Autobús de pasajeros",
155
+ "category": "Transporte terrestre",
156
+ "available": true
157
+ },
158
+ {
159
+ "id": "aircraft",
160
+ "label": "Avión",
161
+ "category": "Aviación y espacio",
162
+ "available": true
163
+ },
164
+ {
165
+ "id": "helicopter",
166
+ "label": "Helicóptero",
167
+ "category": "Aviación y espacio",
168
+ "available": true
169
+ },
170
+ {
171
+ "id": "drone",
172
+ "label": "Dron",
173
+ "category": "Aviación y espacio",
174
+ "available": true
175
+ },
176
+ {
177
+ "id": "space",
178
+ "label": "Dominio espacial",
179
+ "category": "Aviación y espacio",
180
+ "available": true
181
+ },
182
+ {
183
+ "id": "ship",
184
+ "label": "Buque",
185
+ "category": "Entorno marítimo",
186
+ "available": true
187
+ },
188
+ {
189
+ "id": "box",
190
+ "label": "Caja",
191
+ "category": "Objetos e instalaciones",
192
+ "available": true
193
+ },
194
+ {
195
+ "id": "building",
196
+ "label": "Edificio",
197
+ "category": "Objetos e instalaciones",
198
+ "available": true
199
+ },
200
+ {
201
+ "id": "data-bus",
202
+ "label": "Bus de datos",
203
+ "category": "Datos y redes",
204
+ "available": true
205
+ },
206
+ {
207
+ "id": "network",
208
+ "label": "Red",
209
+ "category": "Datos y redes",
210
+ "available": true
211
+ },
212
+ {
213
+ "id": "server",
214
+ "label": "Servidor",
215
+ "category": "Datos y redes",
216
+ "available": true
217
+ },
218
+ {
219
+ "id": "database",
220
+ "label": "Base de datos",
221
+ "category": "Datos y redes",
222
+ "available": true
223
+ },
224
+ {
225
+ "id": "link",
226
+ "label": "Enlace",
227
+ "category": "Datos y redes",
228
+ "available": true
229
+ },
230
+ {
231
+ "id": "sensor",
232
+ "label": "Sensor genérico",
233
+ "category": "Sensores y mediciones",
234
+ "available": true
235
+ },
236
+ {
237
+ "id": "radar",
238
+ "label": "Radar",
239
+ "category": "Sensores y mediciones",
240
+ "available": true
241
+ },
242
+ {
243
+ "id": "thermometer",
244
+ "label": "Termómetro",
245
+ "category": "Sensores y mediciones",
246
+ "available": true
247
+ },
248
+ {
249
+ "id": "globe",
250
+ "label": "Globo terrestre",
251
+ "category": "Geografía y navegación",
252
+ "available": true
253
+ },
254
+ {
255
+ "id": "point",
256
+ "label": "Punto",
257
+ "category": "Geografía y navegación",
258
+ "available": true
259
+ },
260
+ {
261
+ "id": "track",
262
+ "label": "Traza",
263
+ "category": "Geografía y navegación",
264
+ "available": true
265
+ },
266
+ {
267
+ "id": "folder",
268
+ "label": "Carpeta",
269
+ "category": "Inspector y documentos",
270
+ "available": true
271
+ },
272
+ {
273
+ "id": "inspector-object",
274
+ "label": "Objeto de datos",
275
+ "category": "Inspector y documentos",
276
+ "available": true
277
+ },
278
+ {
279
+ "id": "inspector-array",
280
+ "label": "Array",
281
+ "category": "Inspector y documentos",
282
+ "available": true
283
+ },
284
+ {
285
+ "id": "inspector-value",
286
+ "label": "Valor escalar",
287
+ "category": "Inspector y documentos",
288
+ "available": true
289
+ },
290
+ {
291
+ "id": "warning",
292
+ "label": "Advertencia",
293
+ "category": "Estados y alertas",
294
+ "available": true
295
+ },
296
+ {
297
+ "id": "error",
298
+ "label": "Error",
299
+ "category": "Estados y alertas",
300
+ "available": true
301
+ },
302
+ {
303
+ "id": "placeholder",
304
+ "label": "Tipo desconocido",
305
+ "category": "Estados y alertas",
306
+ "available": true
307
+ },
308
+ {
309
+ "id": "damage",
310
+ "label": "Daño",
311
+ "category": "Estados y alertas",
312
+ "available": true
313
+ },
314
+ {
315
+ "id": "situation",
316
+ "label": "Situación",
317
+ "category": "Estados y alertas",
318
+ "available": true
319
+ },
320
+ {
321
+ "id": "lock",
322
+ "label": "Bloqueado",
323
+ "category": "Estados y alertas",
324
+ "available": true
325
+ },
326
+ {
327
+ "id": "control",
328
+ "label": "Control",
329
+ "category": "Acciones y controles",
330
+ "available": true
331
+ },
332
+ {
333
+ "id": "task",
334
+ "label": "Tarea",
335
+ "category": "Acciones y controles",
336
+ "available": true
337
+ },
338
+ {
339
+ "id": "star",
340
+ "label": "Favorito",
341
+ "category": "Acciones y controles",
342
+ "available": true
343
+ },
344
+ {
345
+ "id": "pin",
346
+ "label": "Fijado",
347
+ "category": "Acciones y controles",
348
+ "available": true
349
+ },
350
+ {
351
+ "id": "heart",
352
+ "label": "Preferencia afectiva",
353
+ "category": "Acciones y controles",
354
+ "available": true
355
+ },
356
+ {
357
+ "id": "grip",
358
+ "label": "Asa de arrastre",
359
+ "category": "Acciones y controles",
360
+ "available": true
361
+ },
362
+ {
363
+ "id": "clock",
364
+ "label": "Reloj",
365
+ "category": "Tiempo y simulación",
366
+ "available": true
367
+ },
368
+ {
369
+ "id": "calendar",
370
+ "label": "Calendario",
371
+ "category": "Tiempo y simulación",
372
+ "available": true
373
+ },
374
+ {
375
+ "id": "munition",
376
+ "label": "Munición",
377
+ "category": "Tiempo y simulación",
378
+ "available": true
379
+ }
380
+ ]
@@ -0,0 +1,167 @@
1
+ # Catálogo de iconos
2
+
3
+ Los 63 iconos incluidos están disponibles en `resources/icons/`.
4
+ Consulta la [galería visual](./icon-catalog.html) para verlos sobre fondos claros
5
+ u oscuros y comparar tamaños. `builtinIconNames` permite enumerar sus IDs desde JavaScript.
6
+
7
+ ## Uso
8
+
9
+ Asigna un ID a la propiedad `icon` del nodo:
10
+
11
+ ```js
12
+ view.setData([{ id: 'operator-1', label: 'Operador', icon: 'person' }]);
13
+ ```
14
+
15
+ - `data-bus` representa un bus de datos; `bus-vehicle`, un autobús.
16
+ - `placeholder` es el icono utilizado cuando no hay uno específico.
17
+ - Acompaña los iconos con etiquetas. Para estados como advertencias o errores,
18
+ proporciona también texto y no dependas únicamente del color.
19
+ - Para añadir un icono propio, usa `view.registerIcon('custom', svgStringOrUrl)`
20
+ y asigna `icon: 'custom'` al nodo.
21
+ - Configura `iconsBaseUrl` al crear la vista si los recursos se sirven desde otra URL.
22
+ Los SVG se cargan al dibujarlos o mediante `view.controller.iconRegistry.prepare()`.
23
+
24
+ ## Iconos disponibles
25
+
26
+ ### Personas y organización
27
+
28
+ | ID | Significado |
29
+ | --- | --- |
30
+ | `person` | Persona |
31
+ | `people` | Grupo |
32
+ | `child` | Infancia |
33
+
34
+ ### Animales
35
+
36
+ | ID | Significado |
37
+ | --- | --- |
38
+ | `dog` | Perro |
39
+ | `cat` | Gato |
40
+ | `bird` | Ave |
41
+ | `fish` | Pez |
42
+ | `insect` | Insecto |
43
+
44
+ ### Plantas y ecosistemas
45
+
46
+ | ID | Significado |
47
+ | --- | --- |
48
+ | `tree` | Árbol |
49
+ | `leaf` | Hoja |
50
+ | `flower` | Flor |
51
+ | `seedling` | Brote |
52
+
53
+ ### Tiempo atmosférico
54
+
55
+ | ID | Significado |
56
+ | --- | --- |
57
+ | `sun` | Sol |
58
+ | `moon` | Luna |
59
+ | `cloud` | Nube |
60
+ | `rain` | Lluvia |
61
+ | `snow` | Nieve |
62
+ | `wind` | Viento |
63
+ | `storm` | Tormenta |
64
+ | `fog` | Niebla |
65
+
66
+ ### Terreno y agua
67
+
68
+ | ID | Significado |
69
+ | --- | --- |
70
+ | `ground` | Dominio terrestre |
71
+ | `surface` | Dominio de superficie |
72
+ | `subsurface` | Dominio submarino |
73
+
74
+ ### Transporte terrestre
75
+
76
+ | ID | Significado |
77
+ | --- | --- |
78
+ | `car` | Coche |
79
+ | `bicycle` | Bicicleta |
80
+ | `bus-vehicle` | Autobús de pasajeros |
81
+
82
+ ### Aviación y espacio
83
+
84
+ | ID | Significado |
85
+ | --- | --- |
86
+ | `aircraft` | Avión |
87
+ | `helicopter` | Helicóptero |
88
+ | `drone` | Dron |
89
+ | `space` | Dominio espacial |
90
+
91
+ ### Entorno marítimo
92
+
93
+ | ID | Significado |
94
+ | --- | --- |
95
+ | `ship` | Buque |
96
+
97
+ ### Objetos e instalaciones
98
+
99
+ | ID | Significado |
100
+ | --- | --- |
101
+ | `box` | Caja |
102
+ | `building` | Edificio |
103
+
104
+ ### Datos y redes
105
+
106
+ | ID | Significado |
107
+ | --- | --- |
108
+ | `data-bus` | Bus de datos |
109
+ | `network` | Red |
110
+ | `server` | Servidor |
111
+ | `database` | Base de datos |
112
+ | `link` | Enlace |
113
+
114
+ ### Sensores y mediciones
115
+
116
+ | ID | Significado |
117
+ | --- | --- |
118
+ | `sensor` | Sensor genérico |
119
+ | `radar` | Radar |
120
+ | `thermometer` | Termómetro |
121
+
122
+ ### Geografía y navegación
123
+
124
+ | ID | Significado |
125
+ | --- | --- |
126
+ | `globe` | Globo terrestre |
127
+ | `point` | Punto |
128
+ | `track` | Traza |
129
+
130
+ ### Inspector y documentos
131
+
132
+ | ID | Significado |
133
+ | --- | --- |
134
+ | `folder` | Carpeta |
135
+ | `inspector-object` | Objeto de datos |
136
+ | `inspector-array` | Array |
137
+ | `inspector-value` | Valor escalar |
138
+
139
+ ### Estados y alertas
140
+
141
+ | ID | Significado |
142
+ | --- | --- |
143
+ | `warning` | Advertencia |
144
+ | `error` | Error |
145
+ | `placeholder` | Tipo desconocido |
146
+ | `damage` | Daño |
147
+ | `situation` | Situación |
148
+ | `lock` | Bloqueado |
149
+
150
+ ### Acciones y controles
151
+
152
+ | ID | Significado |
153
+ | --- | --- |
154
+ | `control` | Control |
155
+ | `task` | Tarea |
156
+ | `star` | Favorito |
157
+ | `pin` | Fijado |
158
+ | `heart` | Preferencia afectiva |
159
+ | `grip` | Asa de arrastre |
160
+
161
+ ### Tiempo y simulación
162
+
163
+ | ID | Significado |
164
+ | --- | --- |
165
+ | `clock` | Reloj |
166
+ | `calendar` | Calendario |
167
+ | `munition` | Munición |
@@ -0,0 +1,86 @@
1
+ /**
2
+ * @param {number} count
3
+ * @returns {Array<import('../src/core/types.js').TreeNode>}
4
+ */
5
+ export function generateTree(count = 10000) {
6
+ const nodes = [{ id: 'root', label: 'Operations Theater', type: 'root', tags: ['root', 'ops'] }];
7
+ const systems = ['Command', 'Air Defense', 'ISR', 'Logistics', 'Mission Planning'];
8
+ const platforms = [
9
+ { label: 'air', type: 'air', tags: ['air', 'platform'] },
10
+ { label: 'ground', type: 'ground', tags: ['ground', 'platform'] },
11
+ { label: 'surface', type: 'surface', tags: ['surface', 'platform'] },
12
+ { label: 'sub', type: 'subsurface', tags: ['subsurface', 'platform'] },
13
+ { label: 'space', type: 'space', tags: ['space', 'platform'] },
14
+ ];
15
+ const sensors = ['Radar', 'Emitter Beam', 'EO Camera', 'SIGINT', 'IFF', 'Telemetry'];
16
+ const tasks = ['Patrol', 'Track', 'Inspect', 'Relay', 'Intercept'];
17
+
18
+ for (const [index, label] of systems.entries()) {
19
+ nodes.push({
20
+ id: `system-${index}`,
21
+ parentId: 'root',
22
+ label: `${label} System`,
23
+ type: 'system',
24
+ tags: ['system', label.toLowerCase().replaceAll(' ', '-')],
25
+ });
26
+ }
27
+
28
+ let i = nodes.length;
29
+ let platformCursor = 0;
30
+ while (i < count) {
31
+ const system = nodes[1 + (i % systems.length)];
32
+ const platformId = `platform-${i}`;
33
+ const platform = platforms[platformCursor % platforms.length];
34
+ platformCursor++;
35
+ nodes.push({
36
+ id: platformId,
37
+ parentId: system.id,
38
+ label: `${platform.label}-${100 + (i % 900)}`,
39
+ type: platform.type,
40
+ tags: ['platform', ...platform.tags, i % 5 === 0 ? 'priority' : 'normal'],
41
+ data: { ordinal: i },
42
+ });
43
+ i++;
44
+ if (i >= count) break;
45
+
46
+ for (let s = 0; s < 3 && i < count; s++, i++) {
47
+ nodes.push({
48
+ id: `sensor-${i}`,
49
+ parentId: platformId,
50
+ label: `${sensors[(i + s) % sensors.length]} ${i}`,
51
+ type: i % 19 === 0 ? 'warning' : 'sensor',
52
+ tags: ['sensor', sensors[(i + s) % sensors.length].toLowerCase().replaceAll(' ', '-')],
53
+ data: { ordinal: i },
54
+ });
55
+ }
56
+
57
+ for (let t = 0; t < 4 && i < count; t++, i++) {
58
+ const isWarning = i % 23 === 0;
59
+ const isMunition = !isWarning && i % 17 === 0;
60
+ const isGeometry = !isWarning && !isMunition && i % 13 === 0;
61
+ nodes.push({
62
+ id: `${isWarning ? 'warning' : isMunition ? 'munition' : isGeometry ? 'point' : 'track'}-${i}`,
63
+ parentId: platformId,
64
+ label: `${isWarning ? 'Warning' : isMunition ? 'Mini Missile' : isGeometry ? 'Pointer' : 'Track'} ${String(i).padStart(5, '0')}`,
65
+ type: isWarning ? 'warning' : isMunition ? 'munition' : isGeometry ? 'geometry' : 'track',
66
+ tags: [isWarning ? 'warning' : isMunition ? 'munition' : isGeometry ? 'geometry' : 'track', i % 11 === 0 ? 'hot' : 'stable'],
67
+ data: { ordinal: i },
68
+ });
69
+ }
70
+
71
+ for (let task = 0; task < 2 && i < count; task++, i++) {
72
+ const isControl = i % 29 === 0;
73
+ const isSituation = !isControl && i % 37 === 0;
74
+ nodes.push({
75
+ id: `${isControl ? 'control' : isSituation ? 'situation' : 'task'}-${i}`,
76
+ parentId: platformId,
77
+ label: `${isControl ? 'Control' : isSituation ? 'Situation' : `${tasks[i % tasks.length]} Task`} ${i}`,
78
+ type: i % 31 === 0 ? 'error' : isControl ? 'control' : isSituation ? 'situation' : 'task',
79
+ tags: [isControl ? 'control' : isSituation ? 'situation' : 'task', tasks[i % tasks.length].toLowerCase()],
80
+ data: { ordinal: i },
81
+ });
82
+ }
83
+ }
84
+
85
+ return nodes.slice(0, count);
86
+ }
@@ -0,0 +1,33 @@
1
+ <!doctype html><html lang="es"><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Orden manual · virtual-tree-canvas</title>
2
+ <style>*{box-sizing:border-box}body{font:15px system-ui;background:#0b1020;color:#e2e8f0;padding:30px;max-width:1100px;margin:auto}h1{font-size:26px;margin-bottom:8px}p{line-height:1.5;color:#aabbd1}button,input{background:#172339;color:inherit;border:1px solid #64748b;border-radius:6px;padding:8px 12px;font:inherit}button{cursor:pointer}button:focus-visible,input:focus-visible{outline:2px solid #38bdf8;outline-offset:3px}header{display:flex;gap:10px;flex-wrap:wrap;margin:22px 0}#host{position:relative;height:390px;border:1px solid #334155;border-radius:8px;overflow:hidden}canvas{position:absolute;inset:0;width:100%;height:100%}output{display:block;min-height:28px;margin-top:16px;color:#67e8f9}code{font-size:13px}#order{color:#94a3b8;overflow-wrap:anywhere}</style>
3
+ <h1>Tabla con orden manual</h1><p>Arrastra el asa de una fila o usa sus flechas. Con la tabla enfocada, <kbd>Alt</kbd> + <kbd>↑</kbd>/<kbd>↓</kbd> mueve la fila. <kbd>Esc</kbd> cancela el arrastre. Los valores siguen actualizándose.</p>
4
+ <header><button id="clear">Quitar filtro y ordenación</button><button id="pause">Pausar valores</button><button id="reset">Restablecer orden</button></header>
5
+ <div id="host"></div><output id="status" aria-live="polite">Orden manual activo</output><p id="order"></p>
6
+ <script type="module">
7
+ import { TreeView } from '../src/index.js';
8
+ const host=document.querySelector('#host');
9
+ const rows=[
10
+ {id:'altitude',label:'Altitud',icon:'aircraft',data:{unit:'m'}},
11
+ {id:'speed',label:'Velocidad',icon:'car',data:{unit:'m/s'}},
12
+ {id:'temperature',label:'Temperatura',icon:'thermometer',data:{unit:'°C'}},
13
+ {id:'wind',label:'Viento',icon:'wind',data:{unit:'m/s'}},
14
+ {id:'power',label:'Potencia',icon:'sensor',data:{unit:'W'}},
15
+ {id:'signal',label:'Señal del bus de datos',icon:'data-bus',data:{unit:'dB'}},
16
+ {id:'people',label:'Personas detectadas',icon:'people',data:{unit:''}},
17
+ {id:'range',label:'Distancia',icon:'radar',data:{unit:'m'}}];
18
+ const view=new TreeView(host,{rowReorder:true,editable:false,iconsBaseUrl:'../resources/icons/',columns:[{id:'name',kind:'tree',label:'Propiedad',width:380,value:n=>n.label},{id:'value',label:'Valor',width:160,value:(_n,s)=>s.value??'—'},{id:'unit',label:'Unidad',width:120,value:n=>n.data?.unit??''}]});
19
+ const controller=view.controller;
20
+ window.demoView=view;window.demoController=controller;window.reorderEvents=[];
21
+ const key='vtc-demo-manual-order';
22
+ function install(order){const byId=new Map(rows.map(n=>[n.id,n]));const ordered=order.map(id=>byId.get(id)).filter(Boolean);const seen=new Set(ordered.map(n=>n.id));view.setData([...ordered,...rows.filter(n=>!seen.has(n.id))]);showOrder();}
23
+ function showOrder(){document.querySelector('#order').textContent='Orden: '+controller.getRowOrder().join(' → ');}
24
+ let saved=[];try{saved=JSON.parse(localStorage.getItem(key)||'[]');}catch{}install(Array.isArray(saved)?saved:[]);
25
+ controller.on('rowreorder',({detail})=>{window.reorderEvents.push(detail);localStorage.setItem(key,JSON.stringify(detail.order));document.querySelector('#status').textContent=`${detail.nodeId}: posición ${detail.toIndex+1}`;showOrder();});
26
+ function mode(){document.querySelector('#status').textContent=controller.canReorderRows()?'Orden manual activo':'Quita el filtro y la ordenación para mover filas.';}
27
+ controller.on('filterchange',mode);controller.on('sortchange',mode);
28
+ document.querySelector('#clear').onclick=()=>{controller.clearFilter();controller.clearSort();};
29
+ document.querySelector('#reset').onclick=()=>{localStorage.removeItem(key);install([]);};
30
+ let paused=false,tick=0;document.querySelector('#pause').onclick=e=>{paused=!paused;e.target.textContent=paused?'Reanudar valores':'Pausar valores';};
31
+ const timer=setInterval(()=>{if(paused)return;tick++;controller.setDynamicState(rows.map((n,i)=>({id:n.id,state:{value:n.id==='people'?Math.floor(tick/20)%8:(100+i*20+Math.sin(tick/8+i)*5).toFixed(2)}})));},150);
32
+ window.addEventListener('pagehide',()=>{clearInterval(timer);view.destroy();});
33
+ </script></html>