virtual-tree-canvas 0.5.0 → 0.7.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 (100) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +181 -27
  3. package/docs/icon-catalog.html +67 -0
  4. package/docs/icon-catalog.json +392 -0
  5. package/docs/icon-catalog.md +171 -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-filled.svg +1 -0
  48. package/resources/icons/star.svg +1 -0
  49. package/resources/icons/storm.svg +1 -0
  50. package/resources/icons/sun.svg +1 -0
  51. package/resources/icons/thermometer.svg +1 -0
  52. package/resources/icons/trash.svg +1 -0
  53. package/resources/icons/tree.svg +1 -0
  54. package/resources/icons/wind.svg +1 -0
  55. package/src/assets/icons.js +75 -25
  56. package/src/core/icon-registry.js +6 -8
  57. package/src/core/theme-manager.js +17 -4
  58. package/src/core/tree-cell-layout.js +9 -0
  59. package/src/core/tree-column-model.js +27 -3
  60. package/src/core/tree-model.js +20 -0
  61. package/src/core/types.js +1 -0
  62. package/src/core/view-options.js +37 -0
  63. package/src/core/visible-row-model.js +1 -1
  64. package/src/index.d.ts +209 -5
  65. package/src/index.js +3 -0
  66. package/src/input/index.js +1 -0
  67. package/src/input/row-actions.js +115 -0
  68. package/src/input/row-reorder-input.js +168 -0
  69. package/src/input/tree-filter-bar.js +55 -0
  70. package/src/input/tree-tooltip.js +55 -0
  71. package/src/input/tree-view-input-controller.js +10 -3
  72. package/src/inspector/cell-editor-manager.js +42 -13
  73. package/src/inspector/pane-layout.js +12 -0
  74. package/src/renderers/checkbox.js +14 -0
  75. package/src/renderers/tree-row-renderer.js +103 -62
  76. package/src/tree-view-controller.js +309 -57
  77. package/src/tree-view.js +183 -0
  78. package/src/view/styles.js +14 -0
  79. package/src/assets/icons/air.svg +0 -1
  80. package/src/assets/icons/radar.svg +0 -1
  81. /package/{src/assets → resources}/icons/aircraft.svg +0 -0
  82. /package/{src/assets/icons/bus.svg → resources/icons/bus-vehicle.svg} +0 -0
  83. /package/{src/assets → resources}/icons/control.svg +0 -0
  84. /package/{src/assets → resources}/icons/damage.svg +0 -0
  85. /package/{src/assets → resources}/icons/error.svg +0 -0
  86. /package/{src/assets → resources}/icons/folder.svg +0 -0
  87. /package/{src/assets → resources}/icons/ground.svg +0 -0
  88. /package/{src/assets → resources}/icons/inspector-array.svg +0 -0
  89. /package/{src/assets → resources}/icons/inspector-object.svg +0 -0
  90. /package/{src/assets → resources}/icons/inspector-value.svg +0 -0
  91. /package/{src/assets → resources}/icons/munition.svg +0 -0
  92. /package/{src/assets → resources}/icons/placeholder.svg +0 -0
  93. /package/{src/assets → resources}/icons/point.svg +0 -0
  94. /package/{src/assets → resources}/icons/situation.svg +0 -0
  95. /package/{src/assets → resources}/icons/space.svg +0 -0
  96. /package/{src/assets → resources}/icons/subsurface.svg +0 -0
  97. /package/{src/assets → resources}/icons/surface.svg +0 -0
  98. /package/{src/assets → resources}/icons/task.svg +0 -0
  99. /package/{src/assets → resources}/icons/track.svg +0 -0
  100. /package/{src/assets → resources}/icons/warning.svg +0 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,65 @@
1
+ # Changelog
2
+
3
+ ## 0.7.0 — 2026-09-10
4
+
5
+ ### Added
6
+
7
+ - Configurable right-aligned row actions with icons, checkboxes, tooltips, disabled and pressed states, and keyboard activation.
8
+ - Optional row data dragging with start, move, end and cancel events for application drop targets.
9
+ - `preloadIcons` prepares action icon variants before the first interaction. Includes new trash and filled-star icons.
10
+ - `showHeader` hides column headings independently of the filter.
11
+ - `reorderable: false` prevents manual movement of individual rows while allowing their parent subtree to move.
12
+ - Value formatting and semantic type colors for table columns, plus configurable status pills.
13
+
14
+ ### Fixed
15
+
16
+ - Row actions preserve selection backgrounds and remain available on partially visible rows. Hover highlights the icon without covering the row.
17
+ - Action icons render at their displayed size and current screen pixel density.
18
+ - Canvas resizing repaints immediately after measurement; keyboard focus uses a subtle outer border.
19
+ - Inspector checkboxes align with other value controls. Sliders and text inputs remain editable when row dragging is enabled.
20
+ - Live refreshes preserve active editors, row drags, expansion state and action-button focus.
21
+ - Flat lists no longer reserve unnecessary leaf-marker space. Sticky ancestor separators remain hidden before scrolling.
22
+ - Focusing a row during dragging no longer scrolls ancestor panels.
23
+ - Sorted and filtered tables can export rows while keeping manual reordering disabled.
24
+ - Pointer cancellation and disposal release drag state.
25
+
26
+ ## 0.6.0 — 2026-09-10
27
+
28
+ ### Added
29
+
30
+ - `TreeView(host, options)` mounts a complete tree or inspector with a canvas, filter, tooltip and editor. Use `configure()` to update its options.
31
+ - Manual row ordering with drag handles, Up/Down buttons, Alt+Arrow shortcuts, edge scrolling and cancellation. `rowreorder` reports changes for applications that save the order.
32
+ - `getRowOrder`, `moveRow`, `moveRowBy` and `setRowReorder` APIs. Manual ordering is available for data rows when filtering and column sorting are inactive.
33
+ - `autoRender`, `TreeTooltip`, configurable `iconsBaseUrl` and application-defined `iconResolver`.
34
+ - `setEditable`, `setInitialExpandDepth`, `setIconResolver`, `setHeaderFilter` and inspector writes by path with optional event suppression.
35
+ - Expanded SVG catalogue with 63 icons. Includes a visual catalogue and examples of units and manual ordering.
36
+
37
+ ### Changed
38
+
39
+ - Built-in SVGs are served from `resources/icons/` and loaded on demand. SVG sources and raster variants are cached.
40
+
41
+ ### Fixed
42
+
43
+ - Changing editability, filter visibility or icons preserves row order, selection and live values.
44
+ - Explicit row dimensions survive theme changes.
45
+ - Explicit data and model setters refresh mutated objects even when their reference is unchanged.
46
+ - Mode and expansion-depth options are applied; `columns: null` restores the current presentation's default columns.
47
+ - Filter controls reflect programmatic changes, including clearing the filter. Header filtering works in readonly tables.
48
+ - Cancelling an editor does not commit its value. Layout changes close editors whose position is no longer valid.
49
+ - Switching from inspector mode to data rows clears inspector-specific columns.
50
+
51
+ ### Migration
52
+
53
+ Applications that bundle the library must serve its resources and configure their public URL:
54
+
55
+ ```js
56
+ const view = new TreeView(host, {
57
+ iconsBaseUrl: '/node_modules/virtual-tree-canvas/resources/icons/'
58
+ });
59
+ ```
60
+
61
+ Update references to `src/assets/icons/` to use `resources/icons/`.
62
+
63
+ Existing `TreeViewController` integrations and explicit render loops remain supported.
64
+ Manual ordering and controller automatic rendering are opt-in. Numeric units and
65
+ display precision remain supported.
package/README.md CHANGED
@@ -19,50 +19,80 @@ grow.
19
19
  - **Easy to style.** Built-in dark, light and tactical themes, type-based styling, and editable SVG icons.
20
20
  - **Ready for inspectors.** Turn plain JavaScript objects into compact editable forms or property tables.
21
21
 
22
+ See [the changelog](./CHANGELOG.md) for migration notes and additions.
23
+
22
24
  ## Install
23
25
 
24
26
  ```bash
25
27
  npm install virtual-tree-canvas
26
28
  ```
27
29
 
28
- ## Quick start
30
+ ## Quick start: complete DOM view
29
31
 
30
- ```html
31
- <canvas id="assets-tree"></canvas>
32
- ```
32
+ Give the host an explicit height. `TreeView` mounts its canvas, filter, tooltip
33
+ and editors, observes size changes and schedules rendering itself.
33
34
 
34
- ```css
35
- #assets-tree {
36
- display: block;
37
- width: 100%;
38
- height: 480px;
39
- }
35
+ ```html
36
+ <div id="assets-tree" style="height:480px"></div>
40
37
  ```
41
38
 
42
39
  ```js
43
- import { TreeViewController } from 'virtual-tree-canvas';
40
+ import { TreeView } from 'virtual-tree-canvas';
44
41
 
45
- const tree = new TreeViewController({
46
- canvas: document.querySelector('#assets-tree'),
42
+ const view = new TreeView(document.querySelector('#assets-tree'), {
47
43
  initialExpandDepth: 2,
44
+ rowReorder: true,
45
+ iconsBaseUrl: '/node_modules/virtual-tree-canvas/resources/icons/'
48
46
  });
49
-
50
- tree.setData([
51
- { id: 'fleet', label: 'Fleet', type: 'root' },
52
- { id: 'radar-1', parentId: 'fleet', label: 'Forward radar', type: 'sensor' },
53
- { id: 'track-100', parentId: 'radar-1', label: 'Track T-100', type: 'track' },
47
+ view.setData([
48
+ { id: 'fleet', label: 'Fleet', icon: 'folder' },
49
+ { id: 'radar-1', parentId: 'fleet', label: 'Forward radar', icon: 'radar' },
50
+ { id: 'track-100', parentId: 'radar-1', label: 'Track T-100', icon: 'track' }
54
51
  ]);
52
+ view.setDynamicState([{ id: 'track-100', state: { value: 430 } }]);
53
+ view.configure({ editable: false, rowHeight: 32 });
54
+ view.on('rowreorder', ({ detail }) => console.log(detail.order));
55
+ const tree = view.controller; // optional lower-level operations
56
+ // On permanent unmount: view.destroy();
57
+ ```
55
58
 
56
- tree.setDynamicState([
57
- { id: 'radar-1', state: { status: 0, progress: 0.72 } },
58
- { id: 'track-100', state: { status: 1, value: 430 } },
59
- ]);
59
+ `configure(patch)` batches changes in a microtask. Public operations and reading
60
+ `view.controller` flush pending configuration first; `view.flush()` is also
61
+ available. Editability, theme, filter visibility and icon resolution preserve
62
+ manual order, selection, expansion and live values. `setData` and `setModel`
63
+ explicitly reinstall data even when the same mutated reference is supplied.
64
+
65
+ - `mode`: `tree` (default) or `inspector`; `presentation`: `pane` or `table`.
66
+ - `filterPlacement`: `auto` (bar in tree/pane, first-column header in inspector
67
+ table), `bar` or `header`. Set `filter: false` to hide it. Hiding the control
68
+ preserves its query; call `clearFilter()` to clear the query.
69
+ - `columns: null` restores defaults for the current presentation.
70
+ - Explicit `rowHeight` and `indentWidth` override the theme; configure them as
71
+ `undefined` to resume theme defaults. `initialExpandDepth` applies to future
72
+ data installations; it does not expand or collapse the current tree.
73
+ - `fontFamily: 'inherit'` follows the host font and refreshes after fonts load.
74
+ - `iconsBaseUrl`, `iconRegistry` and `nativeScrollbars` are construction options.
75
+ - `setInspectorValue(path, value, {emit: false})` updates a value without emitting
76
+ `valuechange` or `modelchange`. Normal writes include `source: 'api'`; editor
77
+ writes use `source: 'user'`. Both include the model in their event details.
78
+
79
+ Use `view.configure` for view configuration. Direct controller operations are
80
+ available for interaction state (filtering, selection, sorting, navigation);
81
+ changing data or presentation through the view keeps its configuration coherent.
82
+
83
+ ## Low-level canvas integration
84
+
85
+ Existing applications can keep `TreeViewController` and manage their own DOM:
60
86
 
61
- tree.render();
87
+ ```js
88
+ import { TreeViewController } from 'virtual-tree-canvas';
89
+ const tree = new TreeViewController({ canvas, host, autoRender: true });
90
+ tree.setData(rows);
62
91
  ```
63
92
 
64
- The controller observes the canvas size. Call `tree.render()` from your own
65
- animation loop when the data is live, or after changing data in a static view.
93
+ The controller owns state and editing, including `setEditable`,
94
+ `setInspectorValue`, `closeEditor` and layout precedence. With `autoRender`
95
+ omitted, use your existing explicit `tree.render()` loop.
66
96
 
67
97
  ![Virtual tree-table with status, progress and SVG icons](./docs/tree-demo.png)
68
98
 
@@ -105,8 +135,16 @@ and may rebuild the visible row list. For very large datasets,
105
135
  `enableWorkers()` moves search and filtered row rebuilds off the main thread
106
136
  when Workers are available.
107
137
 
138
+ Row actions can also use checkboxes styled like inspector controls. Set `kind: 'checkbox'` and provide
139
+ `checked` as a boolean or a function of `(node, state)`. The `rowaction` event
140
+ includes `checked`; update your application state in response. Checkbox actions
141
+ support `visible` and `disabled` and do not select or drag the row.
142
+
108
143
  ## Columns
109
144
 
145
+ Use `showHeader: false` to hide column headings while keeping the filter available.
146
+ The configured `headerHeight` is preserved when the header is shown again.
147
+
110
148
  ```js
111
149
  tree.setColumns([
112
150
  {
@@ -141,6 +179,11 @@ Available column kinds are `tree`, `status`, `value`, `progress`, `type`,
141
179
  Canvas2D content. Import `builtInColumns` or `defaultTreeTableColumns` to start
142
180
  from the default set.
143
181
 
182
+ Text columns accept `format(value, node, state)` to customize displayed text and
183
+ tooltips while sorting by the original `value`. For example,
184
+ `format: value => value.toFixed(2)` displays two decimal places. By default,
185
+ numeric text uses the same precision as inspector values (up to three decimals).
186
+
144
187
  ## Themes and icons
145
188
 
146
189
  Three themes are included:
@@ -151,6 +194,18 @@ import { darkTheme, lightTheme, tacticalTheme } from 'virtual-tree-canvas';
151
194
  tree.setTheme(tacticalTheme);
152
195
  ```
153
196
 
197
+ Inspector values and columns with `kind: 'value'` use `theme.valueColors` to
198
+ distinguish numbers, strings, booleans, enums and empty values. Read-only values
199
+ retain their contrast; disabled controls remain dimmed. Override individual
200
+ colors, for example `tree.setTheme({ valueColors: { number: '#8bd5ee' } })`.
201
+ A value column can specify `valueType: 'enum'` or a
202
+ `valueType(value, node, state)` callback when its semantic type differs from the
203
+ raw value. Formatting and sorting are unaffected.
204
+
205
+ Columns with `kind: 'status'` display compact pills using `theme.statuses`, for
206
+ example `{ Live: { label: 'Live', color: '#56ba95' } }`. Their width adapts to
207
+ the label within the column.
208
+
154
209
  Themes can map domain types to a colour and icon:
155
210
 
156
211
  ```js
@@ -166,11 +221,24 @@ tree.setTheme({
166
221
  });
167
222
  ```
168
223
 
169
- Built-in icons are editable SVG files in [`src/assets/icons`](./src/assets/icons).
170
- They are preloaded and rasterized once per icon, colour, CSS size and device
224
+ Built-in icons are editable SVG files in [`resources/icons`](./resources/icons).
225
+ They are loaded on demand and rasterized once per icon, colour, CSS size and device
171
226
  pixel ratio. Rendering rows then uses a cached `drawImage`, not SVG parsing or
172
227
  path drawing.
173
228
 
229
+ When bundling the library, configure the public directory containing its built-in SVGs:
230
+
231
+ ```js
232
+ const tree = new TreeViewController({
233
+ canvas,
234
+ iconsBaseUrl: '/node_modules/virtual-tree-canvas/resources/icons/'
235
+ });
236
+ ```
237
+
238
+ `iconsBaseUrl` also works with `new IconRegistry({ iconsBaseUrl })`. It accepts an absolute URL (including a CDN), a root-relative path, or a document-relative path. A trailing slash is optional. Supply it when constructing the controller or registry, before built-in icons begin loading. A custom `iconRegistry` takes precedence over the controller's `iconsBaseUrl`.
239
+
240
+ Without this option, unbundled modules load the icons from the package's `resources/icons/` directory. Publish that directory alongside the package; the bundler does not need to extract or rename its SVGs.
241
+
174
242
  Register application icons from an SVG URL, inline SVG or an image:
175
243
 
176
244
  ```js
@@ -282,3 +350,89 @@ Open <http://localhost:4173/demo/> for the large-tree benchmark, or
282
350
  ```bash
283
351
  npm test
284
352
  ```
353
+
354
+ ## Manual row order
355
+
356
+ `rowReorder: true` adds a dedicated handle and Up/Down controls to a data table. Set `reorderable: false` on derived or fixed nodes to hide their ordering controls and prevent moving them manually; their parent can still move with its subtree. Dragging commits only on drop; Escape or dropping outside cancels. Alt+Up / Alt+Down moves the focused row. Edge scrolling works while dragging.
357
+
358
+ ```js
359
+ const tree = new TreeViewController({
360
+ canvas, host,
361
+ iconsBaseUrl: '/node_modules/virtual-tree-canvas/resources/icons/',
362
+ rowReorder: true,
363
+ autoRender: true,
364
+ tooltip: true,
365
+ columns: [
366
+ { id: 'name', kind: 'tree', label: 'Property', width: 300 },
367
+ { id: 'value', label: 'Value', width: 120, value: (_node, state) => state.value }
368
+ ]
369
+ });
370
+ tree.setData(favoriteRows);
371
+ tree.on('rowreorder', ({ detail }) => saveOrder(detail.order));
372
+ ```
373
+
374
+ - `moveRow(id, targetIndex)` uses a zero-based index among siblings. `moveRowBy(id, offset)` and `getRowOrder(parentId = null)` are also available.
375
+ - Reordering preserves IDs, parent relationships, expansion, selected IDs and live dynamic state. `setDynamicState()` does not change order. `setData()` intentionally installs the order supplied by the host; restore a saved order before calling it.
376
+ - Reordering is disabled during sorting/filtering and for object-inspector models (`setModel`). It does not reorder JavaScript object properties or mutate inspected arrays. Use data rows (`setData`) for favorites, with stable IDs and dynamic value columns.
377
+ - The `rowreorder` detail contains `nodeId`, `parentId`, `fromIndex`, `toIndex`, `siblingOrder`, `order` and `source`. The host owns persistence and live subscriptions. One row is moved at a time, including its subtree, without reparenting.
378
+ - The order column is fixed at 72 CSS px (three 24 px targets) and cannot be sorted, resized or moved. The mode can be toggled with `setRowReorder(enabled)`. Sticky ancestor rows are omitted in this mode.
379
+ - `autoRender` coalesces rendering into an animation frame and cancels pending work on `destroy()`. It is opt-in so existing hosts with their own render loop continue to work.
380
+ - `tooltip: true` creates a reusable tooltip in `host` (or the canvas parent). The host must provide a positioned container. `attachTooltip({ host })` is also available.
381
+ - `setData(nodes, { iconResolver })` allows host-independent visual mapping without mutating the input nodes.
382
+
383
+ Open [the reorder demo](./examples/reorder-table.html) through a local HTTP server to try a table with live values. The demo's localStorage is illustrative; the library never persists user data itself.
384
+
385
+ ## Icon catalogue
386
+
387
+ 65 SVGs are included, with a [visual sheet](./docs/icon-catalog.html) and a [reference of available icons](./docs/icon-catalog.md). `builtinIconNames` exposes available IDs.
388
+
389
+ ## Row actions and data dragging
390
+
391
+ Actions are optional accessible buttons on the right of visible rows. Their icon names use
392
+ `IconRegistry`, including registered custom icons. The host handles the action;
393
+ clicking a button does not select or expand the row.
394
+
395
+ ```js
396
+ const favorites = new Set();
397
+ const view = new TreeView(host, {
398
+ nodes,
399
+ rowActions: [{
400
+ id: 'favorite', preloadIcons: ['star', 'star-filled'], icon: node => favorites.has(node.id) ? 'star-filled' : 'star', label: 'Toggle favorite',
401
+ visible: node => !node.data?.group,
402
+ disabled: node => node.data?.unavailable === true,
403
+ pressed: node => favorites.has(node.id)
404
+ }],
405
+ rowDrag: node => node.data?.reference ?? null
406
+ });
407
+ view.on('rowaction', ({detail}) => {
408
+ if (favorites.has(detail.nodeId)) favorites.delete(detail.nodeId);
409
+ else favorites.add(detail.nodeId);
410
+ view.controller.requestRender();
411
+ });
412
+ ```
413
+
414
+ `rowActions` accepts unique `id` and accessible `label` strings. `icon`, `visible`,
415
+ `disabled` and `pressed` may be functions receiving `(node, dynamicState)`.
416
+ `visible`, `disabled` and `pressed` also accept booleans. Buttons remain focusable
417
+ with Tab and work with Enter/Space. Only visible-row buttons are mounted; live
418
+ value updates preserve their identity and focus.
419
+
420
+ `rowDrag(node, dynamicState)` returns a payload, or `null` to disable dragging
421
+ that row. Drag from a row label or the row-order handle. Editors, checkboxes,
422
+ action buttons and expand/collapse controls retain their normal behavior.
423
+
424
+ The controller emits `rowdragstart`, `rowdragmove`, `rowdragend` and
425
+ `rowdragcancel`. Details contain `nodeId`, `payload`, `label` and, except on
426
+ cancellation, `originalEvent` with client coordinates. Connect these events to
427
+ your application's drop manager to highlight destinations and handle the drop.
428
+ The payload is captured at pointer-down; use a stable reference instead of a
429
+ snapshot of a live value. The library never moves or deletes exported data.
430
+
431
+ Dragging uses Pointer Events within a document. Escape, pointer cancellation,
432
+ loss of pointer capture, removal of the source row and destruction cancel the
433
+ gesture. Live value refreshes preserve an ongoing drag. Sorting and filtering
434
+ disable manual row reordering but still allow exporting rows. Clear the filter
435
+ and cycle the column header through ascending, descending and unsorted to
436
+ restore manual ordering. With `rowReorder: true`, Alt+Up/Down also reorders rows.
437
+
438
+ For actions whose `icon` callback changes between variants, supply `preloadIcons: ['star', 'star-filled']`. The visible action layer prepares these icons in both normal and pressed colors at the current pixel ratio before interaction.
@@ -0,0 +1,67 @@
1
+ <!doctype html><html lang="es"><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Catálogo de iconos · virtual-tree-canvas</title>
2
+ <style>body{font:15px system-ui;margin:32px;background:#0f172a;color:#e2e8f0}body.light{background:#f8fafc;color:#162033}h1{font-size:26px}header{display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin:24px 0}input,button,select{font:inherit;padding:9px;border:1px solid #64748b;border-radius:6px;background:transparent;color:inherit}main{display:grid;grid-template-columns:repeat(auto-fill,minmax(155px,1fr));gap:12px}article{border:1px solid #64748b55;border-radius:9px;display:flex;flex-direction:column;gap:6px;padding:16px;min-height:118px}article[hidden]{display:none}.glyph{height:40px;display:flex;align-items:center;color:#38bdf8}body.light .glyph{color:#0369a1}svg{width:var(--icon-size,24px);height:var(--icon-size,24px)}small{font-size:11px;opacity:.75}code{font-size:12px}p{max-width:850px;line-height:1.5}option{color:#162033}</style>
3
+ <h1>Iconos · virtual-tree-canvas</h1><p>63 SVG disponibles. Bus de datos y autobús son símbolos diferentes. Prueba el tamaño real de una fila y ambos fondos; la etiqueta siempre acompaña al icono.</p>
4
+ <header><input aria-label="Buscar iconos" id="search" type="search" placeholder="Buscar nombre o familia"><label>Tamaño <select id="size"><option>16</option><option>20</option><option selected>24</option><option>32</option></select></label><button id="theme">Cambiar fondo</button></header><main><article data-search="person persona personas y organización"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="7" r="4"/><path d="M4 21v-2a8 8 0 0 1 16 0v2"/></svg>
5
+ </div><strong>Persona</strong><code>person</code><small>Personas y organización</small></article><article data-search="people grupo personas y organización"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="7" r="3"/><path d="M2 21v-3a7 7 0 0 1 14 0v3M17 4a3 3 0 0 1 0 6m2 4a6 6 0 0 1 3 5v2"/></svg>
6
+ </div><strong>Grupo</strong><code>people</code><small>Personas y organización</small></article><article data-search="child infancia personas y organización"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="3"/><path d="M12 8v8m-6-5 6 2 6-2m-6 5-4 5m4-5 4 5"/></svg>
7
+ </div><strong>Infancia</strong><code>child</code><small>Personas y organización</small></article><article data-search="dog perro animales"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M8 6q4-3 8 0l2 10q-6 8-12 0ZM8 6 4 5 2 13l4 3M16 6l4-1 2 8-4 3M9 11h.01M15 11h.01m-4 4 1 1 1-1"/></svg>
8
+ </div><strong>Perro</strong><code>dog</code><small>Animales</small></article><article data-search="cat gato animales"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="m5 9-1-6 6 4h4l6-4-1 6a7 7 0 1 1-14 0ZM2 13h5m10 0h5M9 11h.01M15 11h.01m-4 4 1 1 1-1"/></svg>
9
+ </div><strong>Gato</strong><code>cat</code><small>Animales</small></article><article data-search="bird ave animales"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 13q6 0 8-7 3-4 6 0l4 2-4 2q0 9-11 9l-3-6Zm5 2 5-4M10 19v3m4-4v4"/><circle cx="15" cy="6" r=".6" fill="currentColor"/></svg>
10
+ </div><strong>Ave</strong><code>bird</code><small>Animales</small></article><article data-search="fish pez animales"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12q7-12 15-2l4-5v14l-4-5Q10 24 3 12Zm10-7v14"/><circle cx="8" cy="11" r=".7" fill="currentColor"/></svg>
11
+ </div><strong>Pez</strong><code>fish</code><small>Animales</small></article><article data-search="insect insecto animales"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="14" rx="5" ry="7"/><path d="M9 7V5l-2-2m8 4V5l2-2M7 11 3 8m4 7H2m5 3-4 3m14-10 4-3m-4 7h5m-5 3 4 3M12 7v14"/></svg>
12
+ </div><strong>Insecto</strong><code>insect</code><small>Animales</small></article><article data-search="tree árbol plantas y ecosistemas"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="m12 2-6 8h3l-6 7h7v5h4v-5h7l-6-7h3Z"/></svg>
13
+ </div><strong>Árbol</strong><code>tree</code><small>Plantas y ecosistemas</small></article><article data-search="leaf hoja plantas y ecosistemas"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M20 3Q3 1 3 13q0 8 8 8 12-1 9-18ZM3 21 16 8"/></svg>
14
+ </div><strong>Hoja</strong><code>leaf</code><small>Plantas y ecosistemas</small></article><article data-search="flower flor plantas y ecosistemas"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 14v8m0-4q-7 1-7-4 5-1 7 4m0 2q7 0 7-4-5-1-7 4M9 6C5 0 1 7 7 9c-5 5 3 9 5 3 3 6 10 1 5-3 6-2 1-9-3-3 1-7-7-7-5 0Z"/><circle cx="12" cy="8" r="2"/></svg>
15
+ </div><strong>Flor</strong><code>flower</code><small>Plantas y ecosistemas</small></article><article data-search="seedling brote plantas y ecosistemas"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22V11M12 15C3 15 2 10 3 5c6-1 10 2 9 10Zm0-4c0-7 4-9 9-8 1 5-2 9-9 8Z"/></svg>
16
+ </div><strong>Brote</strong><code>seedling</code><small>Plantas y ecosistemas</small></article><article data-search="sun sol tiempo atmosférico"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2m0 16v2M2 12h2m16 0h2M5 5l1 1m12 12 1 1M5 19l1-1M18 6l1-1"/></svg>
17
+ </div><strong>Sol</strong><code>sun</code><small>Tiempo atmosférico</small></article><article data-search="moon luna tiempo atmosférico"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M20 15A9 9 0 0 1 9 3a9 9 0 1 0 11 12Z"/></svg>
18
+ </div><strong>Luna</strong><code>moon</code><small>Tiempo atmosférico</small></article><article data-search="cloud nube tiempo atmosférico"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M6 18a5 5 0 1 1 1-10 6 6 0 0 1 11 1 4.5 4.5 0 0 1 0 9Z"/></svg>
19
+ </div><strong>Nube</strong><code>cloud</code><small>Tiempo atmosférico</small></article><article data-search="rain lluvia tiempo atmosférico"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M5 15a4 4 0 1 1 1-8 5 5 0 0 1 10 1 3.5 3.5 0 0 1 2 7M7 18l-1 3m6-3-1 3m6-3-1 3"/></svg>
20
+ </div><strong>Lluvia</strong><code>rain</code><small>Tiempo atmosférico</small></article><article data-search="snow nieve tiempo atmosférico"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v20M3 7l18 10M3 17 21 7M9 4l3 3 3-3m-6 16 3-3 3 3M3 10l4-1-1-4m12 14-1-4 4-1M3 14l4 1-1 4M18 5l-1 4 4 1"/></svg>
21
+ </div><strong>Nieve</strong><code>snow</code><small>Tiempo atmosférico</small></article><article data-search="wind viento tiempo atmosférico"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 8h12a3 3 0 1 0-3-3M2 12h17a3 3 0 1 1-3 3M4 16h5a3 3 0 1 1-3 3"/></svg>
22
+ </div><strong>Viento</strong><code>wind</code><small>Tiempo atmosférico</small></article><article data-search="storm tormenta tiempo atmosférico"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M6 15a4 4 0 1 1 0-8 5 5 0 0 1 10 1 3.5 3.5 0 0 1 2 7m-5-4-5 7h5l-2 5 6-8h-5Z"/></svg>
23
+ </div><strong>Tormenta</strong><code>storm</code><small>Tiempo atmosférico</small></article><article data-search="fog niebla tiempo atmosférico"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M6 11a3.5 3.5 0 0 1-.6-6.95A5 5 0 0 1 15 5h2a3 3 0 0 1 0 6H6Z"/><path d="M4 15h16M2 19h7m4 0h9"/></svg>
24
+ </div><strong>Niebla</strong><code>fog</code><small>Tiempo atmosférico</small></article><article data-search="ground dominio terrestre terreno y agua"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M4 10h12l3 3v5H4zm4-3h7v3H8zM6.5 17a2 2 0 1 0 0 4 2 2 0 0 0 0-4m10 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4"/></svg>
25
+ </div><strong>Dominio terrestre</strong><code>ground</code><small>Terreno y agua</small></article><article data-search="surface dominio de superficie terreno y agua"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"><path d="M3 14h14l4 3-16 2zM9 8h5v6H9zM12 4v4"/><path d="M4 21c2-1.5 4-1.5 6 0s4 1.5 6 0" fill="none" stroke-linecap="round"/></svg>
26
+ </div><strong>Dominio de superficie</strong><code>surface</code><small>Terreno y agua</small></article><article data-search="subsurface dominio submarino terreno y agua"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"><path d="M3 14c2-3 16-3 18 0-2 3-16 3-18 0M10 8h4v5h-4zM9 7h6"/><path d="M4 20c2-1.5 4-1.5 6 0s4 1.5 6 0" fill="none" stroke-linecap="round"/></svg>
27
+ </div><strong>Dominio submarino</strong><code>subsurface</code><small>Terreno y agua</small></article><article data-search="car coche transporte terrestre"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="m4 10 2-6h12l2 6M3 10h18v8H3Zm2 8v3m14-3v3M6 14h2m8 0h2"/></svg>
28
+ </div><strong>Coche</strong><code>car</code><small>Transporte terrestre</small></article><article data-search="bicycle bicicleta transporte terrestre"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="5" cy="16" r="4"/><circle cx="19" cy="16" r="4"/><path d="m5 16 5-9 5 9H5m10 0 3-12h-4M8 7h5m5-3h3"/></svg>
29
+ </div><strong>Bicicleta</strong><code>bicycle</code><small>Transporte terrestre</small></article><article data-search="bus-vehicle autobús de pasajeros transporte terrestre"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 4h12v13H6zM4 8h2m12 0h2M6 17l-2 3m14-3 2 3"/><path d="M8 7h3v4H8zm5 0h3v4h-3zM8 15h.01M16 15h.01"/></svg>
30
+ </div><strong>Autobús de pasajeros</strong><code>bus-vehicle</code><small>Transporte terrestre</small></article><article data-search="aircraft avión aviación y espacio"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2.5l1.4 8.1 7.2 4.2-7.9-1.3L12 21.5l-.7-8-7.9 1.3 7.2-4.2z"/><path d="M9.8 18.8L6.5 21M14.2 18.8l3.3 2.2" fill="none"/></svg>
31
+ </div><strong>Avión</strong><code>aircraft</code><small>Aviación y espacio</small></article><article data-search="helicopter helicóptero aviación y espacio"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M6 17V9h8q6 0 6 7l-5 2H6L2 8m4 5H2m9-4V4M3 4h17M7 18v3m9-3v3M4 21h16M14 9v5h5"/></svg>
32
+ </div><strong>Helicóptero</strong><code>helicopter</code><small>Aviación y espacio</small></article><article data-search="drone dron aviación y espacio"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="6" height="6" rx="2"/><path d="m9 9-4-4m10 4 4-4M9 15l-4 4m10-4 4 4"/><circle cx="5" cy="5" r="3"/><circle cx="19" cy="5" r="3"/><circle cx="5" cy="19" r="3"/><circle cx="19" cy="19" r="3"/></svg>
33
+ </div><strong>Dron</strong><code>drone</code><small>Aviación y espacio</small></article><article data-search="space dominio espacial aviación y espacio"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"><circle cx="12" cy="12" r="3" fill="currentColor"/><path d="M3 9h5v6H3zm13 0h5v6h-5zM8 12h8M4 18c4-3 12-3 16 0"/></svg>
34
+ </div><strong>Dominio espacial</strong><code>space</code><small>Aviación y espacio</small></article><article data-search="ship buque entorno marítimo"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="m3 13 9-4 9 4-3 6M6 19l-3-6m4-2V5h10v6M12 5V2M2 21q3-4 6 0 4-4 8 0 3-4 6 0"/></svg>
35
+ </div><strong>Buque</strong><code>ship</code><small>Entorno marítimo</small></article><article data-search="box caja objetos e instalaciones"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="m3 7 9-5 9 5v10l-9 5-9-5Zm0 0 9 5 9-5m-9 5v10M8 4l9 5"/></svg>
36
+ </div><strong>Caja</strong><code>box</code><small>Objetos e instalaciones</small></article><article data-search="building edificio objetos e instalaciones"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 22V3h16v19M2 22h20M9 22v-5h6v5M8 7h1m6 0h1M8 12h1m6 0h1"/></svg>
37
+ </div><strong>Edificio</strong><code>building</code><small>Objetos e instalaciones</small></article><article data-search="data-bus bus de datos datos y redes"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12h18M6 8v4m6 0v4m6-8v4"/><rect x="3" y="3" width="6" height="5" rx="1"/><rect x="15" y="3" width="6" height="5" rx="1"/><rect x="9" y="16" width="6" height="5" rx="1"/></svg>
38
+ </div><strong>Bus de datos</strong><code>data-bus</code><small>Datos y redes</small></article><article data-search="network red datos y redes"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="2" width="6" height="5" rx="1"/><rect x="2" y="17" width="6" height="5" rx="1"/><rect x="16" y="17" width="6" height="5" rx="1"/><path d="M12 7v5M5 17v-5h14v5"/></svg>
39
+ </div><strong>Red</strong><code>network</code><small>Datos y redes</small></article><article data-search="server servidor datos y redes"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="7" rx="2"/><rect x="3" y="14" width="18" height="7" rx="2"/><path d="M7 6.5h.01M7 17.5h.01M12 6.5h5m-5 11h5"/></svg>
40
+ </div><strong>Servidor</strong><code>server</code><small>Datos y redes</small></article><article data-search="database base de datos datos y redes"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14c0 4 18 4 18 0V5M3 12c0 4 18 4 18 0"/></svg>
41
+ </div><strong>Base de datos</strong><code>database</code><small>Datos y redes</small></article><article data-search="link enlace datos y redes"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="m9 15 6-6m-4-4 2-2a5 5 0 0 1 7 7l-3 3a5 5 0 0 1-6 1m2 5-2 2a5 5 0 0 1-7-7l3-3a5 5 0 0 1 6-1"/></svg>
42
+ </div><strong>Enlace</strong><code>link</code><small>Datos y redes</small></article><article data-search="sensor sensor genérico sensores y mediciones"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="2"/><path d="M7 7a7 7 0 0 0 0 10m10-10a7 7 0 0 1 0 10M4 4a11 11 0 0 0 0 16M20 4a11 11 0 0 1 0 16"/></svg>
43
+ </div><strong>Sensor genérico</strong><code>sensor</code><small>Sensores y mediciones</small></article><article data-search="radar radar sensores y mediciones"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="4.5"/><path d="m12 12 6.4-6.4"/><g fill="currentColor" stroke="none"><circle cx="12" cy="12" r="1.1"/><circle cx="7" cy="15" r="1.1"/></g></svg>
44
+ </div><strong>Radar</strong><code>radar</code><small>Sensores y mediciones</small></article><article data-search="thermometer termómetro sensores y mediciones"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M9 14V5a3 3 0 0 1 6 0v9a5 5 0 1 1-6 0ZM12 8v9"/></svg>
45
+ </div><strong>Termómetro</strong><code>thermometer</code><small>Sensores y mediciones</small></article><article data-search="globe globo terrestre geografía y navegación"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><ellipse cx="12" cy="12" rx="4" ry="10"/><path d="M2 12h20"/></svg>
46
+ </div><strong>Globo terrestre</strong><code>globe</code><small>Geografía y navegación</small></article><article data-search="point punto geografía y navegación"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="3" fill="currentColor"/><circle cx="12" cy="12" r="8"/><path d="M12 2v3m0 14v3M2 12h3m14 0h3"/></svg>
47
+ </div><strong>Punto</strong><code>point</code><small>Geografía y navegación</small></article><article data-search="track traza geografía y navegación"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="7"/><path d="M12 3v18M3 12h18"/></svg>
48
+ </div><strong>Traza</strong><code>track</code><small>Geografía y navegación</small></article><article data-search="folder carpeta inspector y documentos"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M3 6.5A2.5 2.5 0 0 1 5.5 4H10l2.1 2H18.5A2.5 2.5 0 0 1 21 8.5v8A2.5 2.5 0 0 1 18.5 19h-13A2.5 2.5 0 0 1 3 16.5z"/></svg>
49
+ </div><strong>Carpeta</strong><code>folder</code><small>Inspector y documentos</small></article><article data-search="inspector-object objeto de datos inspector y documentos"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"><rect x="5" y="5" width="14" height="14" rx="1"/><path d="M10 5v14m4-14v14"/></svg>
50
+ </div><strong>Objeto de datos</strong><code>inspector-object</code><small>Inspector y documentos</small></article><article data-search="inspector-array array inspector y documentos"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="8" cy="12" r="4"/><circle cx="16" cy="12" r="4"/></svg>
51
+ </div><strong>Array</strong><code>inspector-array</code><small>Inspector y documentos</small></article><article data-search="inspector-value valor escalar inspector y documentos"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="7"/><circle cx="12" cy="12" r="2.5" fill="currentColor" stroke="none"/></svg>
52
+ </div><strong>Valor escalar</strong><code>inspector-value</code><small>Inspector y documentos</small></article><article data-search="warning advertencia estados y alertas"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M10.27 3.3a2 2 0 0 1 3.46 0l8.02 13.9A2 2 0 0 1 20.02 20H3.98a2 2 0 0 1-1.73-2.8zM11 9v5h2V9zm0 7v2h2v-2z"/></svg>
53
+ </div><strong>Advertencia</strong><code>warning</code><small>Estados y alertas</small></article><article data-search="error error estados y alertas"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"><circle cx="12" cy="12" r="8.5"/><path d="m9 9 6 6m0-6-6 6"/></svg>
54
+ </div><strong>Error</strong><code>error</code><small>Estados y alertas</small></article><article data-search="placeholder tipo desconocido estados y alertas"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"><rect x="4" y="4" width="16" height="16" rx="2"/></svg>
55
+ </div><strong>Tipo desconocido</strong><code>placeholder</code><small>Estados y alertas</small></article><article data-search="damage daño estados y alertas"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-linejoin="round"><path d="m12 2 2.5 5L21 5l-3 5 4 3-5.5 1L17 21l-5-3-5 3 .5-7L2 13l4-3-3-5 6.5 2z"/><circle cx="12" cy="12" r="2.2" fill="#fff"/></svg>
56
+ </div><strong>Daño</strong><code>damage</code><small>Estados y alertas</small></article><article data-search="situation situación estados y alertas"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="4" opacity=".55"/><path d="M3 12h18M12 3v18m0-9 6-5"/><circle cx="16" cy="8.5" r="1" fill="currentColor" stroke="none"/></svg>
57
+ </div><strong>Situación</strong><code>situation</code><small>Estados y alertas</small></article><article data-search="lock bloqueado estados y alertas"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="10" width="16" height="12" rx="2"/><path d="M7 10V7a5 5 0 0 1 10 0v3M12 15v3"/></svg>
58
+ </div><strong>Bloqueado</strong><code>lock</code><small>Estados y alertas</small></article><article data-search="control control acciones y controles"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round" stroke-linecap="round"><path d="M4 17h16l2 3H2zM12 17l-2-8"/><circle cx="9.5" cy="7" r="2" fill="currentColor"/><circle cx="16" cy="19" r=".8" fill="currentColor"/><circle cx="19" cy="19" r=".8" fill="currentColor"/></svg>
59
+ </div><strong>Control</strong><code>control</code><small>Acciones y controles</small></article><article data-search="task tarea acciones y controles"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="4" width="14" height="16" rx="2"/><path d="M9 4.5h6M8.5 11.5l2 2 4-4M9 16h6"/></svg>
60
+ </div><strong>Tarea</strong><code>task</code><small>Acciones y controles</small></article><article data-search="star favorito acciones y controles"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="m12 2 3 6 7 1-5 5 1 7-6-3-6 3 1-7-5-5 7-1Z"/></svg>
61
+ </div><strong>Favorito</strong><code>star</code><small>Acciones y controles</small></article><article data-search="pin fijado acciones y controles"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="m8 3 8 0-1 6 4 4v2H5v-2l4-4-1-6ZM12 15v7"/></svg>
62
+ </div><strong>Fijado</strong><code>pin</code><small>Acciones y controles</small></article><article data-search="heart preferencia afectiva acciones y controles"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 21 3 12C-2 5 7-1 12 6c5-7 14-1 9 6Z"/></svg>
63
+ </div><strong>Preferencia afectiva</strong><code>heart</code><small>Acciones y controles</small></article><article data-search="grip asa de arrastre acciones y controles"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="5" r="1"/><circle cx="15" cy="5" r="1"/><circle cx="9" cy="12" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="9" cy="19" r="1"/><circle cx="15" cy="19" r="1"/></svg>
64
+ </div><strong>Asa de arrastre</strong><code>grip</code><small>Acciones y controles</small></article><article data-search="clock reloj tiempo y simulación"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
65
+ </div><strong>Reloj</strong><code>clock</code><small>Tiempo y simulación</small></article><article data-search="calendar calendario tiempo y simulación"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M3 10h18M8 2v5m8-5v5M7 14h2m3 0h2m3 0h1M7 18h2m3 0h2"/></svg>
66
+ </div><strong>Calendario</strong><code>calendar</code><small>Tiempo y simulación</small></article><article data-search="munition munición tiempo y simulación"><div class="glyph"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-linejoin="round"><path d="m12 2 3 5v10l-3 5-3-5V7z"/><path d="m9 14-6 5 6-1m6-4 6 5-6-1" fill="none" stroke-width="2" stroke-linecap="round"/></svg>
67
+ </div><strong>Munición</strong><code>munition</code><small>Tiempo y simulación</small></article></main><script>search.oninput=()=>document.querySelectorAll('article').forEach(el=>el.hidden=!el.dataset.search.includes(search.value.toLowerCase()));size.onchange=()=>document.documentElement.style.setProperty('--icon-size',size.value+'px');theme.onclick=()=>document.body.classList.toggle('light');</script></html>