neo.mjs 9.16.0 → 10.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/ServiceWorker.mjs +2 -2
  2. package/apps/email/view/Viewport.mjs +2 -2
  3. package/apps/form/view/Viewport.mjs +1 -1
  4. package/apps/portal/index.html +1 -1
  5. package/apps/portal/view/examples/List.mjs +1 -1
  6. package/apps/portal/view/home/FooterContainer.mjs +1 -1
  7. package/apps/realworld2/view/HomeContainer.mjs +1 -1
  8. package/apps/route/view/center/CardAdministration.mjs +3 -3
  9. package/apps/route/view/center/CardAdministrationDenied.mjs +2 -2
  10. package/apps/route/view/center/CardContact.mjs +2 -2
  11. package/apps/route/view/center/CardHome.mjs +2 -2
  12. package/apps/route/view/center/CardSection1.mjs +2 -2
  13. package/apps/route/view/center/CardSection2.mjs +2 -2
  14. package/buildScripts/createApp.mjs +2 -2
  15. package/docs/app/view/classdetails/HeaderComponent.mjs +3 -3
  16. package/docs/app/view/classdetails/MembersList.mjs +43 -46
  17. package/docs/app/view/classdetails/SourceViewComponent.mjs +1 -1
  18. package/docs/app/view/classdetails/TutorialComponent.mjs +1 -1
  19. package/examples/component/toast/MainContainer.mjs +16 -16
  20. package/examples/component/wrapper/googleMaps/MarkerDialog.mjs +4 -4
  21. package/examples/fields/MainContainer.mjs +1 -1
  22. package/examples/panel/MainContainer.mjs +2 -2
  23. package/examples/tab/container/MainContainer.mjs +3 -3
  24. package/examples/tabs/MainContainer.mjs +2 -2
  25. package/examples/tabs/MainContainer2.mjs +3 -3
  26. package/examples/viewport/MainContainer.mjs +2 -2
  27. package/package.json +3 -3
  28. package/resources/data/deck/learnneo/pages/benefits/FourEnvironments.md +1 -1
  29. package/resources/data/deck/learnneo/pages/benefits/Introduction.md +4 -3
  30. package/resources/data/deck/learnneo/pages/guides/events/DomEvents.md +5 -5
  31. package/resources/data/deck/training/pages/2022-12-27T21-55-23-144Z.md +2 -2
  32. package/resources/data/deck/training/pages/2022-12-29T18-36-08-226Z.md +1 -1
  33. package/resources/data/deck/training/pages/2022-12-29T18-36-56-893Z.md +2 -2
  34. package/resources/data/deck/training/pages/2022-12-29T20-37-08-919Z.md +2 -2
  35. package/resources/data/deck/training/pages/2022-12-29T20-37-20-344Z.md +2 -2
  36. package/resources/data/deck/training/pages/2023-01-13T21-48-17-258Z.md +2 -2
  37. package/resources/data/deck/training/pages/2023-02-05T17-44-53-815Z.md +9 -9
  38. package/resources/data/deck/training/pages/2023-10-14T19-25-08-153Z.md +1 -1
  39. package/src/DefaultConfig.mjs +14 -2
  40. package/src/Main.mjs +14 -5
  41. package/src/button/Base.mjs +1 -1
  42. package/src/calendar/view/calendars/List.mjs +1 -1
  43. package/src/component/Base.mjs +11 -11
  44. package/src/component/Chip.mjs +1 -1
  45. package/src/component/Helix.mjs +3 -3
  46. package/src/component/Process.mjs +2 -2
  47. package/src/component/StatusBadge.mjs +2 -2
  48. package/src/component/Timer.mjs +1 -1
  49. package/src/component/Toast.mjs +2 -2
  50. package/src/container/Base.mjs +1 -1
  51. package/src/form/field/CheckBox.mjs +2 -2
  52. package/src/form/field/FileUpload.mjs +14 -14
  53. package/src/form/field/Range.mjs +1 -1
  54. package/src/form/field/Text.mjs +1 -1
  55. package/src/form/field/trigger/Base.mjs +2 -2
  56. package/src/form/field/trigger/SpinUpDown.mjs +2 -2
  57. package/src/grid/View.mjs +1 -1
  58. package/src/main/DeltaUpdates.mjs +382 -0
  59. package/src/main/DomAccess.mjs +13 -36
  60. package/src/main/render/DomApiRenderer.mjs +138 -0
  61. package/src/main/render/StringBasedRenderer.mjs +58 -0
  62. package/src/table/View.mjs +1 -1
  63. package/src/table/plugin/CellEditing.mjs +1 -1
  64. package/src/tree/Accordion.mjs +11 -11
  65. package/src/tree/List.mjs +12 -5
  66. package/src/vdom/Helper.mjs +174 -292
  67. package/src/vdom/VNode.mjs +47 -11
  68. package/src/vdom/domConstants.mjs +65 -0
  69. package/src/vdom/util/DomApiVnodeCreator.mjs +51 -0
  70. package/src/vdom/util/StringFromVnode.mjs +123 -0
  71. package/src/worker/mixin/RemoteMethodAccess.mjs +13 -1
  72. package/src/main/mixin/DeltaUpdates.mjs +0 -352
@@ -1,8 +1,10 @@
1
- import Base from '../core/Base.mjs';
2
- import NeoArray from '../util/Array.mjs';
3
- import NeoString from '../util/String.mjs';
4
- import Style from '../util/Style.mjs';
5
- import VNode from './VNode.mjs';
1
+ import Base from '../core/Base.mjs';
2
+ import NeoArray from '../util/Array.mjs';
3
+ import Style from '../util/Style.mjs';
4
+ import {rawDimensionTags} from './domConstants.mjs';
5
+ import VNode from './VNode.mjs';
6
+
7
+ const NeoConfig = Neo.config;
6
8
 
7
9
  /**
8
10
  * The central class for the VDom worker to create vnodes & delta updates.
@@ -35,122 +37,6 @@ class Helper extends Base {
35
37
  singleton: true
36
38
  }
37
39
 
38
- /**
39
- * The following top-level attributes will get converted into styles:
40
- * height, maxHeight, maxWidth, minHeight, minWidth, width
41
- *
42
- * Some tags must not do the transformation, so we add them here.
43
- * @member {Set} rawDimensionTags
44
- */
45
- rawDimensionTags = new Set([
46
- 'circle',
47
- 'clipPath',
48
- 'ellipse',
49
- 'filter',
50
- 'foreignObject',
51
- 'image',
52
- 'marker',
53
- 'mask',
54
- 'pattern',
55
- 'rect',
56
- 'svg',
57
- 'use'
58
- ])
59
- /**
60
- * @member {Boolean} returnChildNodeOuterHtml=false
61
- */
62
- returnChildNodeOuterHtml = false
63
- /**
64
- * Void attributes inside html tags
65
- * @member {Set} voidAttributes
66
- * @protected
67
- */
68
- voidAttributes = new Set([
69
- 'checked',
70
- 'defer',
71
- 'disabled',
72
- 'ismap',
73
- 'multiple',
74
- 'nohref',
75
- 'noresize',
76
- 'noshade',
77
- 'nowrap',
78
- 'open',
79
- 'readonly',
80
- 'required',
81
- 'reversed',
82
- 'selected'
83
- ])
84
- /**
85
- * Void html tags
86
- * @member {Set} voidElements
87
- * @protected
88
- */
89
- voidElements = new Set([
90
- 'area',
91
- 'base',
92
- 'br',
93
- 'col',
94
- 'embed',
95
- 'hr',
96
- 'img',
97
- 'input',
98
- 'link',
99
- 'meta',
100
- 'param',
101
- 'source',
102
- 'track',
103
- 'wbr'
104
- ])
105
-
106
- /**
107
- * Creates a Neo.vdom.VNode tree for the given vdom template.
108
- * The top level vnode contains the outerHTML as a string.
109
- * @param {Object} opts
110
- * @param {String} opts.appName
111
- * @param {Boolean} [opts.autoMount]
112
- * @param {String} opts.parentId
113
- * @param {Number} opts.parentIndex
114
- * @param {Object} opts.vdom
115
- * @param {Number} opts.windowId
116
- * @returns {Neo.vdom.VNode|Promise<Neo.vdom.VNode>}
117
- */
118
- create(opts) {
119
- let me = this,
120
- autoMount = opts.autoMount === true,
121
- {appName, parentId, parentIndex, windowId} = opts,
122
- node;
123
-
124
- delete opts.appName;
125
- delete opts.autoMount;
126
- delete opts.parentId;
127
- delete opts.parentIndex;
128
- delete opts.windowId;
129
-
130
- node = me.createVnode(opts);
131
- node.outerHTML = me.createStringFromVnode(node);
132
-
133
- if (autoMount) {
134
- Object.assign(node, {
135
- appName,
136
- autoMount: true,
137
- parentId,
138
- parentIndex,
139
- windowId
140
- })
141
- }
142
-
143
- return Neo.config.useVdomWorker ? node : Promise.resolve(node)
144
- }
145
-
146
- /**
147
- * @param {Object} vnode
148
- * @protected
149
- */
150
- createCloseTag(vnode) {
151
- return this.voidElements.has(vnode.nodeName) ? '' : '</' + vnode.nodeName + '>'
152
- }
153
-
154
40
  /**
155
41
  * @param {Object} config
156
42
  * @param {Object} config.deltas
@@ -158,6 +44,7 @@ class Helper extends Base {
158
44
  * @param {Neo.vdom.VNode} config.vnode
159
45
  * @param {Map} config.vnodeMap
160
46
  * @returns {Object} deltas
47
+ * @protected
161
48
  */
162
49
  compareAttributes(config) {
163
50
  let {deltas, oldVnode, vnode, vnodeMap} = config,
@@ -219,6 +106,7 @@ class Helper extends Base {
219
106
  break
220
107
  case 'nodeName':
221
108
  case 'innerHTML':
109
+ case 'textContent':
222
110
  if (value !== oldVnode[prop]) {
223
111
  delta[prop] = value
224
112
  }
@@ -258,13 +146,46 @@ class Helper extends Base {
258
146
  }
259
147
 
260
148
  /**
261
- * @param {Object} config
262
- * @param {Object} [config.deltas={default: [], remove: []}]
263
- * @param {Neo.vdom.VNode} config.oldVnode
264
- * @param {Map} [config.oldVnodeMap]
265
- * @param {Neo.vdom.VNode} config.vnode
266
- * @param {Map} [config.vnodeMap]
149
+ * Creates a Neo.vdom.VNode tree for the given vdom template.
150
+ * The top level vnode contains the outerHTML as a string,
151
+ * in case Neo.config.useStringBasedMounting === true
152
+ * @param {Object} opts
153
+ * @param {String} opts.appName
154
+ * @param {Boolean} [opts.autoMount]
155
+ * @param {String} opts.parentId
156
+ * @param {Number} opts.parentIndex
157
+ * @param {Object} opts.vdom
158
+ * @param {Number} opts.windowId
159
+ * @returns {Promise<Object>}
160
+ */
161
+ async create(opts) {
162
+ let me = this,
163
+ returnValue, vnode;
164
+
165
+ await me.importDomApiVnodeCreator();
166
+ await me.importStringFromVnode();
167
+
168
+ vnode = me.createVnode(opts.vdom);
169
+ returnValue = {...opts, vnode};
170
+
171
+ delete returnValue.vdom;
172
+
173
+ if (NeoConfig.useStringBasedMounting) {
174
+ returnValue.outerHTML = Neo.vdom.util.StringFromVnode.create(vnode)
175
+ }
176
+
177
+ return returnValue
178
+ }
179
+
180
+ /**
181
+ * @param {Object} config
182
+ * @param {Object} [config.deltas={default: [], remove: []}]
183
+ * @param {Neo.vdom.VNode|Object} config.oldVnode
184
+ * @param {Map} [config.oldVnodeMap]
185
+ * @param {Neo.vdom.VNode|Object} config.vnode
186
+ * @param {Map} [config.vnodeMap]
267
187
  * @returns {Object} deltas
188
+ * @protected
268
189
  */
269
190
  createDeltas(config) {
270
191
  let {deltas={default: [], remove: []}, oldVnode, vnode} = config,
@@ -356,124 +277,16 @@ class Helper extends Base {
356
277
  return deltas
357
278
  }
358
279
 
359
- /**
360
- * @param {Object} vnode
361
- * @protected
362
- */
363
- createOpenTag(vnode) {
364
- let string = '<' + vnode.nodeName,
365
- {attributes} = vnode,
366
- cls = vnode.className,
367
- style;
368
-
369
- if (vnode.style) {
370
- style = Neo.createStyles(vnode.style);
371
-
372
- if (style !== '') {
373
- string += ` style="${style}"`
374
- }
375
- }
376
-
377
- if (cls) {
378
- if (Array.isArray(cls)) {
379
- cls = cls.join(' ')
380
- }
381
-
382
- if (cls !== '') {
383
- string += ` class="${cls}"`
384
- }
385
- }
386
-
387
- if (vnode.id) {
388
- if (Neo.config.useDomIds) {
389
- string += ` id="${vnode.id}"`
390
- } else {
391
- string += ` data-neo-id="${vnode.id}"`
392
- }
393
- }
394
-
395
- Object.entries(attributes).forEach(([key, value]) => {
396
- if (this.voidAttributes.has(key)) {
397
- if (value === 'true') { // vnode attribute values get converted into strings
398
- string += ` ${key}`
399
- }
400
- } else if (key !== 'removeDom') {
401
- if (key === 'value') {
402
- value = NeoString.escapeHtml(value)
403
- }
404
-
405
- string += ` ${key}="${value?.replaceAll?.('"', '&quot;') ?? value}"`
406
- }
407
- });
408
-
409
- return string + '>'
410
- }
411
-
412
- /**
413
- * @param {Neo.vdom.VNode} vnode
414
- * @param {Map} [movedNodes]
415
- */
416
- createStringFromVnode(vnode, movedNodes) {
417
- let me = this,
418
- id = vnode?.id;
419
-
420
- if (id && movedNodes?.get(id)) {
421
- return ''
422
- }
423
-
424
- switch (vnode.vtype) {
425
- case 'root':
426
- return me.createStringFromVnode(vnode.childNodes[0], movedNodes)
427
- case 'text':
428
- return vnode.innerHTML === undefined ? '' : String(vnode.innerHTML)
429
- case 'vnode':
430
- return me.createOpenTag(vnode) + me.createTagContent(vnode, movedNodes) + me.createCloseTag(vnode)
431
- default:
432
- return ''
433
- }
434
- }
435
-
436
- /**
437
- * @param {Neo.vdom.VNode} vnode
438
- * @param {Map} [movedNodes]
439
- * @protected
440
- */
441
- createTagContent(vnode, movedNodes) {
442
- if (vnode.innerHTML) {
443
- return vnode.innerHTML
444
- }
445
-
446
- let string = '',
447
- len = vnode.childNodes ? vnode.childNodes.length : 0,
448
- i = 0,
449
- childNode, outerHTML;
450
-
451
- for (; i < len; i++) {
452
- childNode = vnode.childNodes[i];
453
- outerHTML = this.createStringFromVnode(childNode, movedNodes);
454
-
455
- if (childNode.innerHTML !== outerHTML) {
456
- if (this.returnChildNodeOuterHtml) {
457
- childNode.outerHTML = outerHTML
458
- }
459
- }
460
-
461
- string += outerHTML
462
- }
463
-
464
- return string
465
- }
466
-
467
280
  /**
468
281
  * @param {Object} opts
469
282
  * @returns {Object|Neo.vdom.VNode|null}
283
+ * @protected
470
284
  */
471
285
  createVnode(opts) {
472
286
  // do not create vnode instances for component reference objects
473
287
  if (opts.componentId) {
474
- if (!opts.id) {
475
- opts.id = opts.componentId
476
- }
288
+ opts.childNodes ??= []; // Consistency: Every VNode has a childNodes array
289
+ opts.id ??= opts.componentId
477
290
 
478
291
  return opts
479
292
  }
@@ -482,43 +295,24 @@ class Helper extends Base {
482
295
  return null
483
296
  }
484
297
 
485
- if (typeof opts === 'string') {
486
-
487
- }
488
-
489
- if (opts.vtype === 'text') {
490
- if (!opts.id) {
491
- opts.id = Neo.getId('vtext') // adding an id to be able to find vtype='text' items inside the vnode tree
492
- }
493
-
494
- opts.innerHTML = `<!-- ${opts.id} -->${opts.html || ''}<!-- /neo-vtext -->`;
495
- delete opts.html;
496
- return opts
497
- }
498
-
499
298
  let me = this,
500
- node = {attributes: {}, childNodes: [], style: {}},
299
+ node = {attributes: {}, style: {}},
501
300
  potentialNode;
502
301
 
503
- if (!opts.tag) {
504
- opts.tag = 'div'
505
- }
506
-
507
302
  Object.entries(opts).forEach(([key, value]) => {
508
- let hasUnit, newValue, style;
303
+ if (value !== undefined && value !== null && key !== 'flag' && key !== 'removeDom') {
304
+ let hasUnit, newValue, style;
509
305
 
510
- if (value !== undefined && value !== null && key !== 'flag') {
511
- switch (key) {
306
+ switch(key) {
512
307
  case 'tag':
513
- case 'nodeName':
514
308
  node.nodeName = value;
515
309
  break
516
310
  case 'html':
517
- case 'innerHTML':
518
311
  node.innerHTML = value.toString(); // support for numbers
519
312
  break
520
- case 'children':
521
- case 'childNodes':
313
+ case 'text':
314
+ node.textContent = value
315
+ break
522
316
  case 'cn':
523
317
  if (!Array.isArray(value)) {
524
318
  value = [value]
@@ -559,7 +353,7 @@ class Helper extends Base {
559
353
  case 'minHeight':
560
354
  case 'minWidth':
561
355
  case 'width':
562
- if (me.rawDimensionTags.has(node.nodeName)) {
356
+ if (rawDimensionTags.has(node.nodeName)) {
563
357
  node.attributes[key] = value + ''
564
358
  } else {
565
359
  hasUnit = value != parseInt(value);
@@ -580,14 +374,22 @@ class Helper extends Base {
580
374
  }
581
375
  break
582
376
  default:
583
- if (key !== 'removeDom') { // could be set to false
584
- node.attributes[key] = value + ''
585
- }
377
+ node.attributes[key] = value + '';
586
378
  break
587
379
  }
588
380
  }
589
381
  });
590
382
 
383
+ // Especially relevant for vtype='text'
384
+ if (Object.keys(node.attributes).length < 1) {
385
+ delete node.attributes
386
+ }
387
+
388
+ // Especially relevant for vtype='text'
389
+ if (Object.keys(node.style).length < 1) {
390
+ delete node.style
391
+ }
392
+
591
393
  return new VNode(node)
592
394
  }
593
395
 
@@ -603,6 +405,7 @@ class Helper extends Base {
603
405
  * {Number} index
604
406
  * {String} parentId
605
407
  * {Neo.vdom.VNode} vnode
408
+ * @protected
606
409
  */
607
410
  createVnodeMap(config) {
608
411
  let {vnode, parentNode=null, index=0, map=new Map()} = config,
@@ -630,6 +433,7 @@ class Helper extends Base {
630
433
  * @param {Neo.vdom.VNode} config.vnode
631
434
  * @param {Map} config.vnodeMap
632
435
  * @returns {Map}
436
+ * @protected
633
437
  */
634
438
  findMovedNodes(config) {
635
439
  let {movedNodes=new Map(), oldVnodeMap, vnode, vnodeMap} = config,
@@ -650,6 +454,28 @@ class Helper extends Base {
650
454
  return movedNodes
651
455
  }
652
456
 
457
+ /**
458
+ * Only import for the DOM API based mount adapter.
459
+ * @returns {Promise<void>}
460
+ * @protected
461
+ */
462
+ async importDomApiVnodeCreator() {
463
+ if (!NeoConfig.useStringBasedMounting && !Neo.vdom.util?.DomApiVnodeCreator) {
464
+ await import('./util/DomApiVnodeCreator.mjs')
465
+ }
466
+ }
467
+
468
+ /**
469
+ * Only import for the string based mount adapter.
470
+ * @returns {Promise<void>}
471
+ * @protected
472
+ */
473
+ async importStringFromVnode() {
474
+ if (NeoConfig.useStringBasedMounting && !Neo.vdom.util?.StringFromVnode) {
475
+ await import('./util/StringFromVnode.mjs')
476
+ }
477
+ }
478
+
653
479
  /**
654
480
  * @param {Object} config
655
481
  * @param {Object} config.deltas
@@ -657,16 +483,44 @@ class Helper extends Base {
657
483
  * @param {Map} config.oldVnodeMap
658
484
  * @param {Neo.vdom.VNode} config.vnode
659
485
  * @param {Map} config.vnodeMap
486
+ * @protected
660
487
  */
661
- insertNode(config) {
662
- let {deltas, index, oldVnodeMap, vnode, vnodeMap} = config,
663
- details = vnodeMap.get(vnode.id),
664
- parentId = details.parentNode.id,
665
- me = this,
666
- movedNodes = me.findMovedNodes({oldVnodeMap, vnode, vnodeMap}),
667
- outerHTML = me.createStringFromVnode(vnode, movedNodes);
488
+ insertNode({deltas, index, oldVnodeMap, vnode, vnodeMap}) {
489
+ let details = vnodeMap.get(vnode.id),
490
+ {parentNode} = details,
491
+ parentId = parentNode.id,
492
+ me = this,
493
+ movedNodes = me.findMovedNodes({oldVnodeMap, vnode, vnodeMap}),
494
+ delta = {action: 'insertNode', parentId},
495
+ hasLeadingTextChildren = false,
496
+ physicalIndex = index, // Start with the logical index
497
+ i = 0,
498
+ siblingVnode;
499
+
500
+ // Calculate physicalIndex for DOM insertion and hasLeadingTextChildren flag
501
+ // This loop processes the children of the *NEW* parent's VNode in the *current* state (parentNode.childNodes)
502
+ // up to the logical insertion point.
503
+ for (; i < index; i++) {
504
+ siblingVnode = parentNode.childNodes[i];
505
+
506
+ // If we encounter a text VNode before the insertion point, adjust physicalIndex
507
+ if (siblingVnode?.vtype === 'text') {
508
+ physicalIndex += 2; // Each text VNode adds 2 comment nodes to the physical count
509
+ hasLeadingTextChildren = true
510
+ }
511
+ }
668
512
 
669
- deltas.default.push({action: 'insertNode', index, outerHTML, parentId});
513
+ Object.assign(delta, {hasLeadingTextChildren, index: physicalIndex});
514
+
515
+ if (NeoConfig.useStringBasedMounting) {
516
+ // For string-based mounting, pass a string excluding moved nodes
517
+ delta.outerHTML = Neo.vdom.util.StringFromVnode.create(vnode, movedNodes)
518
+ } else {
519
+ // For direct DOM API mounting, pass the pruned VNode tree
520
+ delta.vnode = Neo.vdom.util.DomApiVnodeCreator.create(vnode, movedNodes)
521
+ }
522
+
523
+ deltas.default.push(delta);
670
524
 
671
525
  // Insert the new node into the old tree, to simplify future OPs
672
526
  oldVnodeMap.get(parentId).vnode.childNodes.splice(index, 0, vnode);
@@ -686,6 +540,7 @@ class Helper extends Base {
686
540
  * @param {Neo.vdom.VNode} vnode
687
541
  * @param {Map} oldVnodeMap
688
542
  * @returns {Boolean}
543
+ * @protected
689
544
  */
690
545
  isMovedNode(vnode, oldVnodeMap) {
691
546
  let oldVnode = oldVnodeMap.get(vnode.id);
@@ -703,33 +558,56 @@ class Helper extends Base {
703
558
  * @param {Map} config.oldVnodeMap
704
559
  * @param {Neo.vdom.VNode} config.vnode
705
560
  * @param {Map} config.vnodeMap
561
+ * @protected
706
562
  */
707
- moveNode(config) {
708
- let {deltas, insertDelta, oldVnodeMap, vnode, vnodeMap} = config,
709
- details = vnodeMap.get(vnode.id),
563
+ moveNode({deltas, insertDelta, oldVnodeMap, vnode, vnodeMap}) {
564
+ let details = vnodeMap.get(vnode.id),
710
565
  {index, parentNode} = details,
711
566
  parentId = parentNode.id,
712
567
  movedNode = oldVnodeMap.get(vnode.id),
713
568
  movedParentNode = movedNode.parentNode,
714
- {childNodes} = movedParentNode;
569
+ {childNodes} = movedParentNode,
570
+ delta = {action: 'moveNode', id: vnode.id, parentId},
571
+ physicalIndex = index, // Start with the logical index
572
+ i = 0,
573
+ siblingVnode;
574
+
575
+ // Calculate physicalIndex for DOM insertion.
576
+ // This loop processes the children of the *NEW* parent's VNode in the *current* state (parentNode.childNodes)
577
+ // up to the logical insertion point.
578
+ for (; i < index; i++) {
579
+ siblingVnode = parentNode.childNodes[i];
580
+
581
+ if (siblingVnode?.vtype === 'text') {
582
+ // Each text VNode adds 2 comment nodes to the physical count
583
+ physicalIndex += 2
584
+ }
585
+ }
715
586
 
587
+ Object.assign(delta, {index: physicalIndex + insertDelta});
588
+ deltas.default.push(delta);
589
+
590
+ // This block implements the "corrupting the old tree" optimization for performance.
591
+ // It pre-modifies the old VNode map to reflect the move, preventing redundant deltas later.
716
592
  if (parentId !== movedParentNode.id) {
717
593
  // We need to remove the node from the old parent childNodes
718
594
  // (which must not be the same as the node they got moved into)
719
595
  NeoArray.remove(childNodes, movedNode.vnode);
720
596
 
597
+ // Get the VNode representing the *new parent* from the 'old VNode map'.
598
+ // This is crucial: 'oldParentNode' here is the *old state's VNode for the new parent*.
721
599
  let oldParentNode = oldVnodeMap.get(parentId);
722
600
 
723
601
  if (oldParentNode) {
724
602
  // If moved into a new parent node, update the reference inside the flat map
725
603
  movedNode.parentNode = oldParentNode.vnode;
726
604
 
605
+ // Reassign 'childNodes' property to now point to the 'childNodes' array
606
+ // of this 'old state's VNode for the new parent'.
727
607
  childNodes = movedNode.parentNode.childNodes
728
608
  }
729
609
  }
730
610
 
731
- deltas.default.push({action: 'moveNode', id: vnode.id, index: index + insertDelta, parentId});
732
-
733
611
  // Add the node into the old vnode tree to simplify future OPs.
734
612
  // NeoArray.insert() will switch to move() in case the node already exists.
735
613
  NeoArray.insert(childNodes, index, movedNode.vnode);
@@ -742,10 +620,11 @@ class Helper extends Base {
742
620
  * @param {Object} config.deltas
743
621
  * @param {Neo.vdom.VNode} config.oldVnode
744
622
  * @param {Map} config.oldVnodeMap
623
+ * @protected
745
624
  */
746
625
  removeNode({deltas, oldVnode, oldVnodeMap}) {
747
- if (oldVnode.componentId && !oldVnode.id) {
748
- oldVnode.id = oldVnode.componentId
626
+ if (oldVnode.componentId) {
627
+ oldVnode.id ??= oldVnode.componentId
749
628
  }
750
629
 
751
630
  let delta = {action: 'removeNode', id: oldVnode.id},
@@ -766,20 +645,23 @@ class Helper extends Base {
766
645
  * @param {Object} opts
767
646
  * @param {Object} opts.vdom
768
647
  * @param {Object} opts.vnode
769
- * @returns {Object|Promise<Object>}
648
+ * @returns {Promise<Object>}
770
649
  */
771
- update(opts) {
772
- let me = this,
773
- vnode = me.createVnode(opts.vdom),
774
- deltas = me.createDeltas({oldVnode: opts.vnode, vnode});
650
+ async update(opts) {
651
+ let me = this,
652
+ deltas, vnode;
653
+
654
+ await me.importDomApiVnodeCreator();
655
+ await me.importStringFromVnode();
656
+
657
+ vnode = me.createVnode(opts.vdom);
658
+ deltas = me.createDeltas({oldVnode: opts.vnode, vnode});
775
659
 
776
660
  // Trees to remove could contain nodes which we want to re-use (move),
777
661
  // so we need to execute the removeNode OPs last.
778
662
  deltas = deltas.default.concat(deltas.remove);
779
663
 
780
- let returnObj = {deltas, updateVdom: true, vnode};
781
-
782
- return Neo.config.useVdomWorker ? returnObj : Promise.resolve(returnObj)
664
+ return {deltas, updateVdom: true, vnode}
783
665
  }
784
666
  }
785
667