neo.mjs 10.0.0-alpha.3 → 10.0.0-alpha.5

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 (109) hide show
  1. package/.github/CODING_GUIDELINES.md +1 -1
  2. package/README.md +52 -11
  3. package/ServiceWorker.mjs +2 -2
  4. package/apps/portal/index.html +1 -1
  5. package/apps/portal/view/blog/List.mjs +1 -1
  6. package/apps/portal/view/home/FooterContainer.mjs +1 -1
  7. package/apps/portal/view/learn/ContentComponent.mjs +2 -1
  8. package/apps/portal/view/learn/MainContainerStateProvider.mjs +3 -6
  9. package/apps/realworld/view/HomeComponent.mjs +1 -1
  10. package/apps/realworld/view/user/ProfileComponent.mjs +1 -1
  11. package/apps/sharedcovid/view/MainContainerController.mjs +1 -1
  12. package/apps/shareddialog/view/MainContainerController.mjs +2 -2
  13. package/buildScripts/buildThemes.mjs +1 -1
  14. package/examples/grid/animatedRowSorting/Viewport.mjs +4 -4
  15. package/examples/grid/bigData/ControlsContainer.mjs +3 -3
  16. package/examples/grid/bigData/GridContainer.mjs +8 -8
  17. package/examples/grid/cellEditing/MainContainer.mjs +5 -5
  18. package/examples/grid/container/MainContainer.mjs +4 -4
  19. package/examples/grid/nestedRecordFields/Viewport.mjs +5 -5
  20. package/learn/README.md +83 -0
  21. package/learn/guides/ApplicationBootstrap.md +352 -0
  22. package/learn/guides/DeclarativeComponentTreesVsImperativeVdom.md +500 -0
  23. package/learn/guides/WorkingWithVDom.md +748 -0
  24. package/learn/tree.json +53 -0
  25. package/package.json +2 -2
  26. package/resources/scss/src/grid/{View.scss → Body.scss} +2 -2
  27. package/resources/scss/src/grid/VerticalScrollbar.scss +1 -1
  28. package/resources/scss/src/grid/plugin/AnimateRows.scss +1 -1
  29. package/resources/scss/src/grid/plugin/CellEditing.scss +1 -1
  30. package/resources/scss/theme-dark/grid/{View.scss → Body.scss} +1 -1
  31. package/resources/scss/theme-light/grid/{View.scss → Body.scss} +1 -1
  32. package/resources/scss/theme-neo-light/grid/{View.scss → Body.scss} +1 -1
  33. package/src/DefaultConfig.mjs +27 -14
  34. package/src/Main.mjs +1 -1
  35. package/src/Neo.mjs +16 -0
  36. package/src/button/Base.mjs +2 -2
  37. package/src/calendar/view/MainContainerStateProvider.mjs +1 -1
  38. package/src/grid/{View.mjs → Body.mjs} +17 -17
  39. package/src/grid/Container.mjs +58 -58
  40. package/src/grid/ScrollManager.mjs +56 -56
  41. package/src/grid/VerticalScrollbar.mjs +2 -2
  42. package/src/grid/_export.mjs +2 -2
  43. package/src/grid/column/AnimatedChange.mjs +5 -5
  44. package/src/grid/column/Base.mjs +1 -1
  45. package/src/grid/column/Component.mjs +6 -6
  46. package/src/grid/header/Button.mjs +1 -1
  47. package/src/grid/header/Toolbar.mjs +9 -9
  48. package/src/grid/plugin/AnimateRows.mjs +1 -2
  49. package/src/layout/Cube.mjs +2 -2
  50. package/src/main/DeltaUpdates.mjs +11 -10
  51. package/src/main/addon/Navigator.mjs +1 -1
  52. package/src/main/addon/WindowPosition.mjs +1 -1
  53. package/src/main/render/StringBasedRenderer.mjs +1 -1
  54. package/src/tab/header/Toolbar.mjs +1 -1
  55. package/src/table/header/Button.mjs +1 -1
  56. package/src/toolbar/Base.mjs +1 -1
  57. package/src/util/Style.mjs +2 -6
  58. package/src/util/VDom.mjs +1 -1
  59. package/src/util/VNode.mjs +1 -1
  60. package/src/vdom/Helper.mjs +96 -49
  61. package/src/vdom/VNode.mjs +38 -2
  62. package/src/worker/App.mjs +8 -19
  63. package/src/worker/Base.mjs +87 -5
  64. package/src/worker/Manager.mjs +90 -36
  65. package/resources/data/deck/learnneo/tree.json +0 -50
  66. package/resources/data/deck/whyneo.md +0 -80
  67. /package/{resources/data/deck/learnneo/pages → learn}/Glossary.md +0 -0
  68. /package/{resources/data/deck/learnneo/pages → learn}/UsingTheseTopics.md +0 -0
  69. /package/{resources/data/deck/learnneo/pages → learn}/benefits/ConfigSystem.md +0 -0
  70. /package/{resources/data/deck/learnneo/pages → learn}/benefits/Effort.md +0 -0
  71. /package/{resources/data/deck/learnneo/pages → learn}/benefits/Features.md +0 -0
  72. /package/{resources/data/deck/learnneo/pages → learn}/benefits/FormsEngine.md +0 -0
  73. /package/{resources/data/deck/learnneo/pages → learn}/benefits/FourEnvironments.md +0 -0
  74. /package/{resources/data/deck/learnneo/pages → learn}/benefits/Introduction.md +0 -0
  75. /package/{resources/data/deck/learnneo/pages → learn}/benefits/MultiWindow.md +0 -0
  76. /package/{resources/data/deck/learnneo/pages → learn}/benefits/OffTheMainThread.md +0 -0
  77. /package/{resources/data/deck/learnneo/pages → learn}/benefits/Quick.md +0 -0
  78. /package/{resources/data/deck/learnneo/pages → learn}/benefits/Speed.md +0 -0
  79. /package/{resources/data/deck/learnneo/pages → learn}/gettingstarted/ComponentModels.md +0 -0
  80. /package/{resources/data/deck/learnneo/pages → learn}/gettingstarted/Config.md +0 -0
  81. /package/{resources/data/deck/learnneo/pages → learn}/gettingstarted/DescribingTheUI.md +0 -0
  82. /package/{resources/data/deck/learnneo/pages → learn}/gettingstarted/Events.md +0 -0
  83. /package/{resources/data/deck/learnneo/pages → learn}/gettingstarted/Extending.md +0 -0
  84. /package/{resources/data/deck/learnneo/pages → learn}/gettingstarted/References.md +0 -0
  85. /package/{resources/data/deck/learnneo/pages → learn}/gettingstarted/Setup.md +0 -0
  86. /package/{resources/data/deck/learnneo/pages → learn}/gettingstarted/Workspaces.md +0 -0
  87. /package/{resources/data/deck/learnneo/pages → learn}/guides/ComponentsAndContainers.md +0 -0
  88. /package/{resources/data/deck/learnneo/pages → learn}/guides/CustomComponents.md +0 -0
  89. /package/{resources/data/deck/learnneo/pages → learn}/guides/Forms.md +0 -0
  90. /package/{resources/data/deck/learnneo/pages → learn}/guides/InstanceLifecycle.md +0 -0
  91. /package/{resources/data/deck/learnneo/pages → learn}/guides/Layouts.md +0 -0
  92. /package/{resources/data/deck/learnneo/pages → learn}/guides/MainThreadAddonExample.md +0 -0
  93. /package/{resources/data/deck/learnneo/pages → learn}/guides/MainThreadAddonIntro.md +0 -0
  94. /package/{resources/data/deck/learnneo/pages → learn}/guides/Mixins.md +0 -0
  95. /package/{resources/data/deck/learnneo/pages → learn}/guides/MultiWindow.md +0 -0
  96. /package/{resources/data/deck/learnneo/pages → learn}/guides/PortalApp.md +0 -0
  97. /package/{resources/data/deck/learnneo/pages → learn}/guides/StateProviders.md +0 -0
  98. /package/{resources/data/deck/learnneo/pages → learn}/guides/Tables.md +0 -0
  99. /package/{resources/data/deck/learnneo/pages → learn}/guides/events/CustomEvents.md +0 -0
  100. /package/{resources/data/deck/learnneo/pages → learn}/guides/events/DomEvents.md +0 -0
  101. /package/{resources/data/deck/learnneo/pages → learn}/javascript/ClassFeatures.md +0 -0
  102. /package/{resources/data/deck/learnneo/pages → learn}/javascript/Classes.md +0 -0
  103. /package/{resources/data/deck/learnneo/pages → learn}/javascript/NewNode.md +0 -0
  104. /package/{resources/data/deck/learnneo/pages → learn}/javascript/Overrides.md +0 -0
  105. /package/{resources/data/deck/learnneo/pages → learn}/javascript/Super.md +0 -0
  106. /package/{resources/data/deck/learnneo/pages → learn}/tutorials/Earthquakes.md +0 -0
  107. /package/{resources/data/deck/learnneo/pages → learn}/tutorials/RSP.md +0 -0
  108. /package/{resources/data/deck/learnneo/pages → learn}/tutorials/TodoList.md +0 -0
  109. /package/resources/data/{deck/learnneo/data/theBeatles.json → theBeatles.json} +0 -0
@@ -0,0 +1,748 @@
1
+ ## A Comprehensive Guide to Custom Component Development
2
+
3
+ **Target Audience**: Developers building custom Neo.mjs components who need to work directly with the VDom layer for performance optimization, complex animations, or advanced UI patterns.
4
+
5
+ **Prerequisites**: Understanding of Neo.mjs's two-tier architecture (Component Tree vs VDom). Read the "Declarative Component Trees vs Imperative VDom" guide first.
6
+
7
+ ## Overview
8
+
9
+ While 99% of Neo.mjs development happens at the Component Tree layer, creating custom components requires working with the VDom layer. This guide covers the patterns, best practices, and techniques for effective VDom manipulation in Neo.mjs.
10
+
11
+ ## VDom Fundamentals
12
+
13
+ ### VDom Structure
14
+
15
+ Neo.mjs VDom nodes are plain JavaScript objects that represent DOM elements.
16
+ **Important**: VDom only contains structure, styling, content, and attributes - **never event listeners**.
17
+
18
+ ```javascript
19
+ // Basic VDom node structure
20
+ {
21
+ tag : 'div', // HTML tag (default: 'div')
22
+ id : 'unique-id', // DOM element ID
23
+ cls : ['class1', 'class2'], // CSS classes array
24
+ style : {color: 'red'}, // Inline CSS object
25
+ html : 'Text content', // Inner HTML (exclusive with text/cn)
26
+ text : 'Text content', // Plain text content (exclusive with html/cn)
27
+ cn : [], // Child nodes array (exclusive with html/text)
28
+ vtype : 'vnode', // VNode type: 'vnode', 'text', 'root'
29
+ static : false, // Exclude from delta updates (optimization)
30
+ removeDom: false, // Hide/show element
31
+ data : {custom: 'value'}, // data-* attributes
32
+ // Standard HTML attributes
33
+ disabled: true,
34
+ tabIndex: -1,
35
+ role : 'button'
36
+ // ❌ NO EVENT LISTENERS IN VDOM
37
+ }
38
+ ```
39
+
40
+ ### Component VDom Definition
41
+
42
+ Components define their internal DOM structure via the `vdom` config:
43
+
44
+ ```javascript
45
+ import Component from './src/component/Base.mjs';
46
+
47
+ class CustomButton extends Component {
48
+ static config = {
49
+ className: 'Neo.custom.Button',
50
+ ntype : 'custom-button',
51
+
52
+ // Define internal DOM structure for this component
53
+ vdom:
54
+ {cls: ['neo-button', 'neo-custom-button'], cn: [
55
+ {tag: 'span', cls: ['neo-button-icon'], flag: 'iconNode'}, // Flag for easy access
56
+ {tag: 'span', cls: ['neo-button-text'], flag: 'textNode'},
57
+ {cls: ['neo-button-badge'], flag: 'badgeNode', removeDom: true} // Initially hidden badge
58
+ ]},
59
+
60
+ // DOM event listeners are defined in static config, not VDom
61
+ domListeners: [{
62
+ click: 'onButtonClick'
63
+ }]
64
+ }
65
+
66
+ onButtonClick(data) {
67
+ console.log('CustomButton clicked:', data);
68
+ }
69
+ }
70
+ ```
71
+
72
+ ---
73
+
74
+ ## Connecting VDom to User Interaction (DOM Events)
75
+
76
+ While VDom nodes define the visual structure and attributes of your components, they do **not** contain event listeners. Neo.mjs uses a robust, delegated global DOM event system where listeners are defined separately within your component's `domListeners` static config.
77
+
78
+ This clear separation ensures that your VDom markup remains easily serializable and performant across worker threads, while still providing flexible and powerful event handling.
79
+
80
+ For a comprehensive deep dive into all aspects of DOM event handling in Neo.mjs (including static, programmatic, string-based handlers, delegation, bubbling, and more), please refer to the dedicated **[DOM Event Handling Guide](guides.events.DomEvents)**.
81
+
82
+ Here's a simple example of how an event handler defined via `domListeners` would interact with a component's VDom:
83
+
84
+ ```javascript
85
+ import Component from './src/component/Base.mjs';
86
+ import VdomUtil from './src/util/Vdom.mjs'; // For accessing VDom nodes by flag
87
+
88
+ class InteractiveComponent extends Component {
89
+ static config = {
90
+ className: 'Neo.examples.InteractiveComponent',
91
+
92
+ vdom: {
93
+ cls: ['neo-interactive-box'],
94
+ cn: [
95
+ {tag: 'button', text: 'Click Me', cls: ['neo-button'], flag: 'myButton'},
96
+ {tag: 'div', text: 'Hover over me', cls: ['hoverable-area'], flag: 'hoverArea'}
97
+ ]
98
+ },
99
+
100
+ domListeners: [{
101
+ click : 'onButtonClick',
102
+ delegate: '.neo-button' // Event delegated to elements with this class
103
+ }, {
104
+ mouseenter: 'onHoverAreaEnter',
105
+ mouseleave: 'onHoverAreaLeave',
106
+ delegate : '.hoverable-area'
107
+ }]
108
+ }
109
+
110
+ // Access VDom nodes using VdomUtil (or flags in afterSet methods)
111
+ get myButton() {
112
+ return VdomUtil.getByFlag(this, 'myButton');
113
+ }
114
+
115
+ get hoverArea() {
116
+ return VdomUtil.getByFlag(this, 'hoverArea');
117
+ }
118
+
119
+ onButtonClick(data) {
120
+ console.log('Button clicked:', data.component.id);
121
+ // Imperatively modify VDom properties in response to event
122
+ this.myButton.text = 'Clicked!';
123
+ this.myButton.disabled = true;
124
+ this.update(); // Trigger VDom reconciliation
125
+ }
126
+
127
+ onHoverAreaEnter(data) {
128
+ this.hoverArea.style = {backgroundColor: '#f0f0f0'};
129
+ this.update();
130
+ }
131
+
132
+ onHoverAreaLeave(data) {
133
+ this.hoverArea.style = {}; // Reset style
134
+ this.update();
135
+ }
136
+ }
137
+ ```
138
+
139
+ ---
140
+
141
+ ## VDom Update Mechanisms
142
+
143
+ ### Standard Approach: `this.update()`
144
+
145
+ The typical way to sync VDom changes to the DOM is through the component's `update()` method:
146
+
147
+ ```javascript
148
+ import Component from './src/component/Base.mjs'; // Required import
149
+
150
+ class StandardComponent extends Component {
151
+ // Assuming these flags point to VDom nodes within this component's vdom config
152
+ get textNode() { return VdomUtil.getByFlag(this, 'myTextNode'); }
153
+ get iconNode() { return VdomUtil.getByFlag(this, 'myIconNode'); }
154
+
155
+ changeContent() {
156
+ // Modify VDom structure (properties of VDom nodes accessed via flags or direct object mutation)
157
+ this.textNode.text = 'New content';
158
+ this.iconNode.cls = ['fa', 'fa-star'];
159
+
160
+ // Send component's VDom to VDom worker via engine
161
+ this.update() // Engine calculates what changed
162
+ }
163
+ }
164
+ ```
165
+
166
+ **How `this.update()` works:**
167
+ 1. Sends the component's entire VDom tree to the VDom worker.
168
+ 2. VDom worker compares with previous state (diffing).
169
+ 3. Worker calculates minimal deltas needed.
170
+ 4. Deltas are sent to Main thread for efficient DOM updates.
171
+
172
+ **Use `this.update()` when:**
173
+ * Making standard component updates.
174
+ * You want the framework's VDom engine to handle diffing automatically.
175
+ * Working with complex VDom structures where manual delta calculation would be error-prone.
176
+ * This covers ~95% of VDom manipulation use cases.
177
+
178
+ ### Advanced Approach: `Neo.applyDeltas()`
179
+
180
+ For performance-critical scenarios, you can bypass the VDom worker's diffing engine and send manually crafted deltas
181
+ directly from the App Worker to the Main Thread. This offers precise control but requires careful manual delta construction.
182
+
183
+ ```javascript
184
+ import Component from './src/component/Base.mjs'; // Required import
185
+
186
+ class AdvancedComponent extends Component {
187
+ // Assume getItemId method exists to get the VDom ID of an element
188
+ // within the component's vdom structure.
189
+
190
+ optimizedUpdate() {
191
+ let me = this;
192
+ // Manually craft precise deltas. Each delta targets a VDom node by its ID.
193
+ const deltas = [{
194
+ id : me.getItemId('my-item-id-1'), // Unique VDom ID of the target element
195
+ style: {opacity: 0.5, backgroundColor: 'blue'}
196
+ }, {
197
+ id : me.getItemId('my-item-id-2'),
198
+ cls: {add: ['highlight'], remove: ['normal']}
199
+ }, {
200
+ id : me.getItemId('my-text-node-id'),
201
+ text: 'Updated text via direct delta'
202
+ }];
203
+
204
+ // Apply deltas directly to the Main Thread.
205
+ // This bypasses the VDom worker's diffing process and worker roundtrip.
206
+ Neo.applyDeltas(me.appName, deltas)
207
+ }
208
+ }
209
+ ```
210
+
211
+ **How `Neo.applyDeltas()` works:**
212
+ 1. You explicitly construct an array of delta objects (e.g., specific changes to style, classes, or text for targeted VDom nodes).
213
+ 2. These pre-calculated deltas are sent directly from the App Worker to the Main Thread.
214
+ 3. The Main Thread immediately applies these changes to the DOM, without any prior diffing or processing by the VDom worker.
215
+
216
+ **When to use `Neo.applyDeltas()` (Primary use cases):**
217
+ * **Extreme Transient Animations**: For highly frequent, localized visual updates (e.g., continuous animations of 300-600 items' transforms, reaching ~40,000 real DOM updates per second via mousewheel events). Here, the benefit of bypassing diffing and worker roundtrips is paramount.
218
+ * **Buffered Rendering / Virtualized Lists**: For scenarios like virtualized lists or data grids where elements are precisely manipulated (e.g., `shift`/`unshift` operations to recycle DOM nodes). This allows you to generate a single `move` delta directly, completely avoiding the overhead of full tree parsing and worker roundtrips that the standard VDom diffing process would incur.
219
+ * **Precise Control**: When you have full knowledge of the exact DOM changes needed and require ultimate control over the update timing.
220
+
221
+ **Important Limitations and Considerations:**
222
+ * **State Desynchronization (Critical)**: `Neo.applyDeltas()` modifies the DOM but **does not** automatically update the
223
+ component's internal VDom (`this.vdom`, your desired state) or the framework's cached `VNode` state (`this.vnode`, the
224
+ last state known by the VDom engine).
225
+ * If these internal states are not manually synchronized, subsequent standard `this.update()` calls will compare against
226
+ stale data, leading to **redundant, incorrect, or "undoing" deltas** being sent to the Main Thread.
227
+ * **Manual Synchronization for Persistent Changes (Highly Complex)**: For persistent UI changes that must remain synchronized,
228
+ manually updating both `this.vdom` (to reflect the new visual state) and `this.vnode` (to match the exact state after
229
+ `Neo.applyDeltas()`) is required. This low-level `VNode` manipulation is generally considered a **framework-internal task**
230
+ dueled to its complexity and the specific `Neo.vdom.VNode` class structure. It is **not typically recommended for
231
+ application developers** for general UI management.
232
+ * **No Automatic Diffing**: You are entirely responsible for calculating the precise deltas. Errors in delta calculation
233
+ will lead to incorrect or unexpected DOM behavior.
234
+
235
+ ---
236
+
237
+ ## VDom Manipulation Patterns
238
+
239
+ ### 1. Using Flag-Based References
240
+
241
+ Flags provide efficient, direct access to specific VDom nodes within a component's `vdom` structure, avoiding the need for DOM queries.
242
+
243
+ ```javascript
244
+ import Component from './src/component/Base.mjs';
245
+ import VdomUtil from './src/util/Vdom.mjs'; // Required import for VdomUtil
246
+ import NeoArray from './src/util/Array.mjs'; // Required import for NeoArray
247
+
248
+ class IconButton extends Component {
249
+ static config = {
250
+ vdom:
251
+ {cls: ['neo-icon-button'], cn: [
252
+ {tag: 'i', cls: ['neo-icon'], flag: 'iconNode'},
253
+ {tag: 'span', cls: ['neo-text'], flag: 'textNode'}
254
+ ]},
255
+
256
+ // domListeners are included here for context, but their detailed explanation is in the dedicated guide.
257
+ domListeners: [{
258
+ click: 'onClick'
259
+ }]
260
+ }
261
+
262
+ // Define getters for easy access to flagged VDom nodes
263
+ get iconNode() {
264
+ return VdomUtil.getByFlag(this, 'iconNode');
265
+ }
266
+
267
+ get textNode() {
268
+ return VdomUtil.getByFlag(this, 'textNode');
269
+ }
270
+
271
+ // Manipulate VDom nodes in response to config changes
272
+ afterSetIconCls(value, oldValue) {
273
+ let {iconNode} = this;
274
+
275
+ // Update CSS classes imperatively
276
+ NeoArray.remove(iconNode.cls, oldValue);
277
+ NeoArray.add(iconNode.cls, value);
278
+
279
+ // Hide/show icon based on value
280
+ iconNode.removeDom = !value;
281
+
282
+ this.update();
283
+ }
284
+
285
+ afterSetText(value, oldValue) {
286
+ let {textNode} = this;
287
+
288
+ textNode.text = value;
289
+ textNode.removeDom = !value;
290
+
291
+ this.update();
292
+ }
293
+
294
+ onClick(data) {
295
+ this.fire('buttonClick', {
296
+ iconCls: this.iconCls,
297
+ text : this.text
298
+ });
299
+ }
300
+ }
301
+ ```
302
+
303
+ ### 2. Dynamic VDom Creation (List Rendering)
304
+
305
+ Build VDom structures programmatically, often in response to data changes. This is common for lists or complex, data-driven UI fragments.
306
+
307
+ ```javascript
308
+ import Component from './src/component/Base.mjs'; // Required import
309
+
310
+ class DataList extends Component {
311
+ static config = {
312
+ data: null, // Reactive config to hold list data
313
+
314
+ vdom: {
315
+ cls: ['neo-data-list'],
316
+ cn : [] // Will be populated dynamically by createListItems
317
+ },
318
+
319
+ // domListeners are included for context
320
+ domListeners: [
321
+ { click: 'onItemClick', delegate: '.neo-list-item' },
322
+ { click: 'onAvatarClick', delegate: '.neo-avatar' }
323
+ ]
324
+ }
325
+
326
+ // Create VDom items from component's data config
327
+ createListItems() {
328
+ let {data, vdom} = this,
329
+ items = [];
330
+
331
+ // Ensure data exists before mapping
332
+ if (Array.isArray(data)) {
333
+ data.forEach((record) => {
334
+ items.push({
335
+ cls: ['neo-list-item'],
336
+ data: {recordId: record.id}, // Use data attributes for event identification
337
+ cn: [{
338
+ tag: 'img',
339
+ src: record.avatar,
340
+ cls: ['neo-avatar']
341
+ }, {
342
+ cls: ['neo-content'],
343
+ cn: [
344
+ {tag: 'h3', text: record.name},
345
+ {tag: 'p', text: record.description}
346
+ ]
347
+ }]
348
+ })
349
+ })
350
+ }
351
+
352
+ vdom.cn = items; // Assign new VDom children
353
+ this.update(); // Trigger VDom reconciliation
354
+ }
355
+
356
+ // Automatically re-render list when 'data' config changes
357
+ afterSetData(value, oldValue) {
358
+ if (value) { // Only create items if data is set
359
+ this.createListItems();
360
+ }
361
+ }
362
+
363
+ onItemClick(data) {
364
+ let recordId = data.target.closest('.neo-list-item')?.dataset.recordId; // Use optional chaining for safety
365
+ if (recordId) {
366
+ this.fire('itemSelect', {recordId});
367
+ }
368
+ }
369
+
370
+ onAvatarClick(data) {
371
+ let recordId = data.target.closest('.neo-list-item')?.dataset.recordId; // Use optional chaining
372
+ if (recordId) {
373
+ this.fire('avatarClick', {recordId});
374
+ }
375
+ }
376
+ }
377
+ ```
378
+
379
+ ### 3. Complex VDom Transformations (e.g., 3D Animations)
380
+
381
+ For sophisticated UI patterns like 3D visualizations or complex dynamic layouts, you might imperatively calculate and apply VDom properties or even use `Neo.applyDeltas()` for maximum performance.
382
+
383
+ ```javascript
384
+ import Component from './src/component/Base.mjs'; // Base component class
385
+
386
+ class Helix extends Component {
387
+ /**
388
+ * This method demonstrates complex VDom transformations by
389
+ * programmatically building VDom deltas and applying them directly
390
+ * using `Neo.applyDeltas()`.
391
+ *
392
+ * In a real application, the values for 'items', 'rotationAngle', etc.,
393
+ * would typically come from component configs, a store, or user interaction
394
+ * (e.g., via event handlers, which are covered in a separate guide).
395
+ */
396
+ transformHelixItems() {
397
+ let me = this;
398
+ let deltas = [];
399
+
400
+ // --- Simulate input data and transformation parameters for the example ---
401
+ const simulatedItems = [
402
+ { id: 'itemA', name: 'Helix Item 1' },
403
+ { id: 'itemB', name: 'Helix Item 2' },
404
+ { id: 'itemC', name: 'Helix Item 3' },
405
+ { id: 'itemD', name: 'Helix Item 4' },
406
+ { id: 'itemE', name: 'Helix Item 5' }
407
+ ];
408
+ const baseRotation = 0; // Degrees
409
+ const itemAngleIncrement = 72; // Degrees per item (360 / 5 items)
410
+ const helixRadius = 150; // Pixels
411
+ const verticalSpacing = 40; // Pixels per item
412
+
413
+ // --- Core VDom Delta Calculation Loop ---
414
+ for (let i = 0; i < simulatedItems.length; i++) {
415
+ let item = simulatedItems[i];
416
+ let currentAngle = baseRotation + i * itemAngleIncrement; // Angle for this item
417
+
418
+ // Basic 3D-like position calculation for CSS `transform`
419
+ let x = helixRadius * Math.sin(currentAngle * Math.PI / 180);
420
+ let y = i * verticalSpacing; // Stack vertically
421
+ let z = helixRadius * Math.cos(currentAngle * Math.PI / 180); // For depth effect
422
+
423
+ // Construct the CSS 3D transform string
424
+ let transformStyle = `translate3d(${x}px, ${y}px, ${z}px) rotateY(${currentAngle}deg)`;
425
+
426
+ // Calculate opacity based on depth (cosine of angle)
427
+ let opacity = (Math.cos(currentAngle * Math.PI / 180) + 1) / 2;
428
+
429
+ // Push a delta object for this specific VDom node.
430
+ // The 'id' here must correspond to the actual VDom node's ID in the Main Thread.
431
+ // This example assumes VDom nodes for helix items already exist or will be created
432
+ // by a separate render cycle; 'Neo.applyDeltas' is for *updating* existing nodes.
433
+ deltas.push({
434
+ id : `${me.id}-helix-element-${item.id}`, // Example ID convention
435
+ style: { opacity: opacity, transform: transformStyle },
436
+ text : item.name // Example: update text content
437
+ })
438
+ }
439
+
440
+ // --- ADVANCED: Directly apply calculated deltas to the DOM ---
441
+ // This bypasses the VDom worker's diffing engine, sending changes
442
+ // directly from the App Worker to the Main Thread for immediate application.
443
+ // It's used for scenarios requiring extreme performance or precise control.
444
+ Neo.applyDeltas(me.appName, deltas); // 'Neo' is globally available
445
+ }
446
+ }
447
+ ```
448
+
449
+ ---
450
+
451
+ ## Security Considerations
452
+
453
+ ### XSS Prevention
454
+
455
+ ```javascript
456
+ import Component from './src/component/Base.mjs'; // Required import
457
+ // import DOMPurify from 'dompurify'; // Example for external sanitization library
458
+
459
+ class SecureComponent extends Component {
460
+ // SECURE: Use text property for user-provided string content
461
+ setContent(userInput) {
462
+ this.textNode.text = userInput; // Automatically HTML-escaped by the framework
463
+ this.update();
464
+ }
465
+
466
+ // SECURE: Use 'tag' property for creating elements with custom names
467
+ createElement(tagName) {
468
+ return {
469
+ tag: tagName, // Safe element creation: tagName is treated as a literal tag name
470
+ cls: ['user-element']
471
+ };
472
+ }
473
+
474
+ // AVOID: Direct HTML injection using 'html' property without sanitization
475
+ unsafeSetContent(userInput) {
476
+ // This example shows a VDom node named 'containerNode' within the component's vdom.
477
+ // It directly sets innerHTML, which is a high XSS risk if 'userInput' is not sanitized.
478
+ this.containerNode.html = userInput; // XSS risk if userInput is untrusted!
479
+ this.update(); // Don't forget to update!
480
+ }
481
+
482
+ // SECURE: Validate and sanitize if HTML content is absolutely needed
483
+ setSafeHtml(content) {
484
+ // If an external library like DOMPurify is used, ensure it's imported and available.
485
+ // For simplicity, this example just shows the call.
486
+ // let sanitized = DOMPurify.sanitize(content);
487
+ // this.containerNode.html = sanitized;
488
+ // this.update();
489
+ console.warn('DOMPurify or similar sanitization library should be used here for user-provided HTML.');
490
+ this.containerNode.html = content; // Still unsafe without actual sanitization
491
+ this.update();
492
+ }
493
+ }
494
+ ```
495
+
496
+ ---
497
+
498
+ ## Performance Best Practices
499
+
500
+ ### 1. Batch VDom Updates
501
+
502
+ ```javascript
503
+ import Component from './src/component/Base.mjs'; // Required import
504
+ import Neo from './src/Neo.mjs'; // Required import for Neo.applyDeltas
505
+
506
+ class PerformantComponent extends Component {
507
+ // Assume getItemNode and getItemId methods exist to access VDom nodes/IDs
508
+
509
+ // BAD: Multiple individual updates
510
+ updateItemsBad(items) {
511
+ items.forEach(item => {
512
+ let node = this.getItemNode(item.id); // Get VDom node
513
+ if (node) { // Ensure node exists
514
+ node.text = item.name;
515
+ }
516
+ this.update(); // Too many VDom worker round-trips!
517
+ });
518
+ }
519
+
520
+ // GOOD: Single engine update with all changes
521
+ updateItemsGood(items) {
522
+ items.forEach(item => {
523
+ let node = this.getItemNode(item.id); // Get VDom node
524
+ if (node) {
525
+ node.text = item.name;
526
+ }
527
+ });
528
+
529
+ this.update(); // Single VDom diff operation, more efficient
530
+ }
531
+
532
+ // ADVANCED: Bypass engine entirely with manual deltas
533
+ updateItemsAdvanced(items) {
534
+ let deltas = [];
535
+
536
+ items.forEach(item => {
537
+ deltas.push({
538
+ id: this.getItemId(item.id), // Get VDom node ID
539
+ text: item.name
540
+ });
541
+ });
542
+
543
+ Neo.applyDeltas(this.appName, deltas); // Directly send pre-calculated deltas to Main Thread
544
+ }
545
+ }
546
+ ```
547
+
548
+ ### 2. Efficient Event Delegation
549
+
550
+ ```javascript
551
+ import Component from './src/component/Base.mjs'; // Required import
552
+
553
+ class EfficientEventComponent extends Component {
554
+ construct(config) {
555
+ super.construct(config);
556
+
557
+ // Single delegated listener handles multiple item types
558
+ this.addDomListeners({
559
+ click: this.onItemInteraction,
560
+ delegate: '.interactive-item',
561
+ scope: this
562
+ });
563
+ }
564
+
565
+ onItemInteraction(data) {
566
+ // data.target is a proxy for the DOM element, allowing direct DOM-like calls
567
+ let element = data.target.closest('.interactive-item');
568
+ if (!element) { return; } // Safety check
569
+
570
+ let itemType = element.dataset.itemType;
571
+ let itemId = element.dataset.itemId;
572
+
573
+ // Route based on item type
574
+ switch (itemType) {
575
+ case 'button':
576
+ this.handleButtonClick(itemId, data);
577
+ break;
578
+ case 'card':
579
+ this.handleCardClick(itemId, data);
580
+ break;
581
+ case 'menu-item':
582
+ this.handleMenuClick(itemId, data);
583
+ break;
584
+ default:
585
+ console.warn('Unknown interactive item type:', itemType);
586
+ }
587
+ }
588
+
589
+ // Example handlers
590
+ handleButtonClick(itemId, data) { console.log('Button clicked:', itemId); }
591
+ handleCardClick(itemId, data) { console.log('Card clicked:', itemId); }
592
+ handleMenuClick(itemId, data) { console.log('Menu item clicked:', itemId); }
593
+ }
594
+ ```
595
+
596
+ ### 3. Memory Management
597
+
598
+ ```javascript
599
+ import Component from './src/component/Base.mjs'; // Required import
600
+
601
+ class MemoryEfficientComponent extends Component {
602
+ destroy(...args) {
603
+ // Clean up internal VDom-related references if they are not automatically managed
604
+ this._cachedNodes = null;
605
+
606
+ // Clear any pending timeouts or intervals to prevent leaks
607
+ this.transitionTimeouts?.forEach(clearTimeout);
608
+ this.transitionTimeouts = null;
609
+
610
+ // Call super.destroy() last
611
+ super.destroy(...args);
612
+ }
613
+
614
+ // Avoid memory leaks in event handlers by binding scope correctly or using fat arrows
615
+ createEventHandler(itemId) {
616
+ // GOOD: Minimal closure scope, 'this' context handled by 'bind'
617
+ // This is often for passing handlers to other parts of the app, not for domListeners config
618
+ return this.processItem.bind(this, itemId);
619
+ }
620
+
621
+ processItem(itemId) {
622
+ console.log('Processing item:', itemId);
623
+ }
624
+ }
625
+ ```
626
+
627
+ ---
628
+
629
+ ## Common VDom Patterns
630
+
631
+ ### 1. Conditional Rendering
632
+
633
+ Dynamically show or hide VDom nodes by setting their `removeDom` property. This is efficient as the VDom node remains in the tree, but its corresponding DOM element is removed/added from the document flow by the framework.
634
+
635
+ ```javascript
636
+ import Component from './src/component/Base.mjs'; // Required import
637
+ import VdomUtil from './src/util/Vdom.mjs'; // Required import
638
+
639
+ class ConditionalComponent extends Component {
640
+ static config = {
641
+ vdom:
642
+ {cn: [
643
+ {tag: 'div', flag: 'contentNode', text: 'Main Content'},
644
+ {tag: 'div', flag: 'loadingNode', text: 'Loading...', removeDom: true} // Initially hidden
645
+ ]}
646
+ }
647
+
648
+ // Access VDom nodes by flag
649
+ get contentNode() { return VdomUtil.getByFlag(this, 'contentNode'); }
650
+ get loadingNode() { return VdomUtil.getByFlag(this, 'loadingNode'); }
651
+
652
+ toggleVisibility(nodeFlag, visible) {
653
+ let node = VdomUtil.getByFlag(this, nodeFlag);
654
+ node.removeDom = !visible; // true to hide, false to show
655
+ this.update(); // STANDARD: Let engine handle the DOM update
656
+ }
657
+
658
+ showLoadingState() {
659
+ this.contentNode.removeDom = true;
660
+ this.loadingNode.removeDom = false;
661
+ this.update(); // STANDARD: Single engine update for multiple changes
662
+ }
663
+
664
+ showContent() {
665
+ this.contentNode.removeDom = false;
666
+ this.loadingNode.removeDom = true;
667
+ this.update(); // STANDARD: Single engine update
668
+ }
669
+ }
670
+ ```
671
+
672
+ ### 2. List Rendering (Dynamic Children)
673
+
674
+ Programmatically create and update lists of VDom nodes, typically from data. This approach is highly efficient as the VDom diffing engine optimizes the DOM updates.
675
+
676
+ ```javascript
677
+ import Component from './src/component/Base.mjs'; // Required import
678
+
679
+ class ListComponent extends Component {
680
+ static config = {
681
+ data: [], // Example: config to hold the list data
682
+
683
+ vdom: {
684
+ cls: ['neo-list'],
685
+ cn: [] // This array will be populated with VDom nodes dynamically
686
+ },
687
+
688
+ domListeners: [{
689
+ click : 'onDeleteItem',
690
+ delegate: '.delete-button' // Delegate click to delete buttons
691
+ }]
692
+ }
693
+
694
+ // Access the VDom node where list items will be rendered
695
+ get listNode() {
696
+ // Assuming the component's root VDom is the list container
697
+ return this.vdom; // Or VdomUtil.getByFlag(this, 'listContainer') if you have a flag
698
+ }
699
+
700
+ // Method to generate VDom nodes from an array of data
701
+ renderItems(items) {
702
+ let listItems = items.map(item => ({
703
+ cls: ['list-item'],
704
+ data: {itemId: item.id}, // Use data attributes for identifying the item
705
+ cn: [
706
+ {tag: 'span', text: item.name},
707
+ {tag: 'button', text: 'Delete', cls: ['delete-button'], data: {itemId: item.id}} // Pass itemId to button
708
+ ]
709
+ }));
710
+
711
+ this.listNode.cn = listItems; // Assign new array of VDom children
712
+ this.update(); // Trigger VDom reconciliation
713
+ }
714
+
715
+ // Example handler for the delegated delete button click
716
+ onDeleteItem(data) {
717
+ let itemId = data.target.dataset.itemId; // Get itemId from the clicked button's data attribute
718
+ console.log('Delete item:', itemId);
719
+ this.fire('deleteItem', {itemId}); // Fire a component-level event
720
+ }
721
+
722
+ // Example: Trigger list rendering when 'data' config changes
723
+ afterSetData(value, oldValue) {
724
+ if (value) {
725
+ this.renderItems(value);
726
+ }
727
+ }
728
+ }
729
+ ```
730
+
731
+ ---
732
+
733
+ ## Conclusion
734
+
735
+ Working with VDom in Neo.mjs provides fine-grained control over DOM manipulation while maintaining the framework's performance benefits. Key takeaways:
736
+
737
+ * **Separate Concerns**: VDom defines structure/attributes, `domListeners` handle events.
738
+ * **Use flags for node references**: More efficient than querying the real DOM.
739
+ * **Batch VDom updates**: Minimize DOM operations with `this.update()` or `Neo.applyDeltas()`.
740
+ * **Leverage `this.update()`**: Essential for VDom-to-DOM synchronization, letting the framework optimize diffing.
741
+ * **`Neo.applyDeltas()` for advanced cases**: Bypass diffing for extreme performance needs.
742
+ * **Follow security best practices**: Use `text` over `html` (unless sanitized), use `tag` for safe element creation.
743
+ * **Optimize for performance**: Batch updates, use efficient delegation, manage memory.
744
+ * **Test thoroughly**: VDom logic benefits from comprehensive testing due to its low-level nature.
745
+
746
+ The VDom layer is where Neo.mjs's performance optimizations happen, and understanding these patterns enables you to build sophisticated, high-performance custom components that integrate seamlessly with the framework's architecture.
747
+
748
+ Remember: Most development should happen at the Component Tree layer. Only drop down to VDom manipulation when you need the additional control and performance optimization it provides.