neo.mjs 9.16.0 → 10.0.0-alpha.2

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 (73) hide show
  1. package/README.md +127 -121
  2. package/ServiceWorker.mjs +2 -2
  3. package/apps/email/view/Viewport.mjs +2 -2
  4. package/apps/form/view/Viewport.mjs +1 -1
  5. package/apps/portal/index.html +1 -1
  6. package/apps/portal/view/examples/List.mjs +1 -1
  7. package/apps/portal/view/home/FooterContainer.mjs +1 -1
  8. package/apps/realworld2/view/HomeContainer.mjs +1 -1
  9. package/apps/route/view/center/CardAdministration.mjs +3 -3
  10. package/apps/route/view/center/CardAdministrationDenied.mjs +2 -2
  11. package/apps/route/view/center/CardContact.mjs +2 -2
  12. package/apps/route/view/center/CardHome.mjs +2 -2
  13. package/apps/route/view/center/CardSection1.mjs +2 -2
  14. package/apps/route/view/center/CardSection2.mjs +2 -2
  15. package/buildScripts/createApp.mjs +2 -2
  16. package/docs/app/view/classdetails/HeaderComponent.mjs +3 -3
  17. package/docs/app/view/classdetails/MembersList.mjs +43 -46
  18. package/docs/app/view/classdetails/SourceViewComponent.mjs +1 -1
  19. package/docs/app/view/classdetails/TutorialComponent.mjs +1 -1
  20. package/examples/component/toast/MainContainer.mjs +16 -16
  21. package/examples/component/wrapper/googleMaps/MarkerDialog.mjs +4 -4
  22. package/examples/fields/MainContainer.mjs +1 -1
  23. package/examples/panel/MainContainer.mjs +2 -2
  24. package/examples/tab/container/MainContainer.mjs +3 -3
  25. package/examples/tabs/MainContainer.mjs +2 -2
  26. package/examples/tabs/MainContainer2.mjs +3 -3
  27. package/examples/viewport/MainContainer.mjs +2 -2
  28. package/package.json +33 -5
  29. package/resources/data/deck/learnneo/pages/benefits/FourEnvironments.md +1 -1
  30. package/resources/data/deck/learnneo/pages/benefits/Introduction.md +4 -3
  31. package/resources/data/deck/learnneo/pages/guides/events/DomEvents.md +5 -5
  32. package/resources/data/deck/training/pages/2022-12-27T21-55-23-144Z.md +2 -2
  33. package/resources/data/deck/training/pages/2022-12-29T18-36-08-226Z.md +1 -1
  34. package/resources/data/deck/training/pages/2022-12-29T18-36-56-893Z.md +2 -2
  35. package/resources/data/deck/training/pages/2022-12-29T20-37-08-919Z.md +2 -2
  36. package/resources/data/deck/training/pages/2022-12-29T20-37-20-344Z.md +2 -2
  37. package/resources/data/deck/training/pages/2023-01-13T21-48-17-258Z.md +2 -2
  38. package/resources/data/deck/training/pages/2023-02-05T17-44-53-815Z.md +9 -9
  39. package/resources/data/deck/training/pages/2023-10-14T19-25-08-153Z.md +1 -1
  40. package/src/DefaultConfig.mjs +14 -2
  41. package/src/Main.mjs +15 -6
  42. package/src/button/Base.mjs +1 -1
  43. package/src/calendar/view/calendars/List.mjs +1 -1
  44. package/src/component/Base.mjs +11 -11
  45. package/src/component/Chip.mjs +1 -1
  46. package/src/component/Helix.mjs +3 -3
  47. package/src/component/Process.mjs +2 -2
  48. package/src/component/StatusBadge.mjs +2 -2
  49. package/src/component/Timer.mjs +1 -1
  50. package/src/component/Toast.mjs +2 -2
  51. package/src/container/Base.mjs +1 -1
  52. package/src/form/field/CheckBox.mjs +2 -2
  53. package/src/form/field/FileUpload.mjs +14 -14
  54. package/src/form/field/Range.mjs +1 -1
  55. package/src/form/field/Text.mjs +1 -1
  56. package/src/form/field/trigger/Base.mjs +2 -2
  57. package/src/form/field/trigger/SpinUpDown.mjs +2 -2
  58. package/src/grid/View.mjs +1 -1
  59. package/src/main/DeltaUpdates.mjs +442 -0
  60. package/src/main/DomAccess.mjs +13 -95
  61. package/src/main/render/DomApiRenderer.mjs +138 -0
  62. package/src/main/render/StringBasedRenderer.mjs +58 -0
  63. package/src/table/View.mjs +1 -1
  64. package/src/table/plugin/CellEditing.mjs +1 -1
  65. package/src/tree/Accordion.mjs +11 -11
  66. package/src/tree/List.mjs +12 -5
  67. package/src/vdom/Helper.mjs +179 -309
  68. package/src/vdom/VNode.mjs +47 -11
  69. package/src/vdom/domConstants.mjs +65 -0
  70. package/src/vdom/util/DomApiVnodeCreator.mjs +51 -0
  71. package/src/vdom/util/StringFromVnode.mjs +123 -0
  72. package/src/worker/mixin/RemoteMethodAccess.mjs +13 -1
  73. 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,15 +44,16 @@ 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
- compareAttributes(config) {
163
- let {deltas, oldVnode, vnode, vnodeMap} = config,
164
- attributes, delta, value, keys, styles, add, remove;
165
-
49
+ compareAttributes({deltas, oldVnode, vnode, vnodeMap}) {
50
+ // Do not compare attributes for component references
166
51
  if (oldVnode.componentId && (oldVnode.id === vnode.id || oldVnode.componentId === vnode.id)) {
167
52
  return deltas
168
53
  }
169
54
 
55
+ let attributes, delta, value, keys, styles, add, remove;
56
+
170
57
  if (vnode.vtype === 'text' && vnode.innerHTML !== oldVnode.innerHTML) {
171
58
  deltas.default.push({
172
59
  action : 'updateVtext',
@@ -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,
@@ -344,7 +265,7 @@ class Helper extends Base {
344
265
  if (me.isMovedNode(childNode, oldVnodeMap)) {
345
266
  me.moveNode({deltas, insertDelta, oldVnodeMap, vnode: childNode, vnodeMap})
346
267
  } else {
347
- me.insertNode({deltas, index: i + insertDelta, oldVnodeMap, vnode: childNode, vnodeMap});
268
+ me.insertNode({deltas, index: i + insertDelta, oldVnodeMap, vnode: childNode, vnodeMap})
348
269
  }
349
270
 
350
271
  if (oldChildNode && vnodeId === vnodeMap.get(oldChildNodeId)?.parentNode.id) {
@@ -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,42 +374,48 @@ 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
+ // Relevant for vtype='text'
384
+ if (Object.keys(node.attributes).length < 1) {
385
+ delete node.attributes
386
+ }
387
+
388
+ // 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
 
594
396
  /**
595
397
  * Creates a flat map of the tree, containing ids as keys and infos as values
596
398
  * @param {Object} config
597
- * @param {Neo.vdom.VNode} config.vnode
598
- * @param {Neo.vdom.VNode} [config.parentNode=null]
599
399
  * @param {Number} [config.index=0]
600
400
  * @param {Map} [config.map=new Map()]
401
+ * @param {Neo.vdom.VNode} [config.parentNode=null]
402
+ * @param {Neo.vdom.VNode} config.vnode
601
403
  * @returns {Map}
602
404
  * {String} id vnode.id (convenience shortcut)
603
405
  * {Number} index
604
406
  * {String} parentId
605
407
  * {Neo.vdom.VNode} vnode
408
+ * @protected
606
409
  */
607
- createVnodeMap(config) {
608
- let {vnode, parentNode=null, index=0, map=new Map()} = config,
609
- id;
610
-
410
+ createVnodeMap({index=0, map=new Map(), parentNode=null, vnode}) {
611
411
  if (vnode) {
612
- id = vnode.id || vnode.componentId;
412
+ let id = vnode.id || vnode.componentId;
613
413
 
614
414
  map.set(id, {id, index, parentNode, vnode});
615
415
 
616
416
  vnode.childNodes?.forEach((childNode, index) => {
617
- this.createVnodeMap({vnode: childNode, parentNode: vnode, index, map})
618
- });
417
+ this.createVnodeMap({index, map, parentNode: vnode, vnode: childNode})
418
+ })
619
419
  }
620
420
 
621
421
  return map
@@ -630,10 +430,10 @@ class Helper extends Base {
630
430
  * @param {Neo.vdom.VNode} config.vnode
631
431
  * @param {Map} config.vnodeMap
632
432
  * @returns {Map}
433
+ * @protected
633
434
  */
634
- findMovedNodes(config) {
635
- let {movedNodes=new Map(), oldVnodeMap, vnode, vnodeMap} = config,
636
- id = vnode?.id;
435
+ findMovedNodes({movedNodes=new Map(), oldVnodeMap, vnode, vnodeMap}) {
436
+ let id = vnode?.id;
637
437
 
638
438
  if (id) {
639
439
  if (this.isMovedNode(vnode, oldVnodeMap)) {
@@ -650,6 +450,49 @@ class Helper extends Base {
650
450
  return movedNodes
651
451
  }
652
452
 
453
+ /**
454
+ * For delta updates to work, every node inside the live DOM needs a unique ID.
455
+ * Text nodes need to get wrapped into comment nodes, which contain the ID to ensure consistency.
456
+ * As the result, we need a physical index which counts every text node as 3 nodes.
457
+ * @param {Neo.vdom.VNode} parentNode
458
+ * @param {Number} logicalIndex
459
+ * @returns {Number}
460
+ */
461
+ getPhysicalIndex(parentNode, logicalIndex) {
462
+ let physicalIndex = logicalIndex,
463
+ i = 0;
464
+
465
+ for (; i < logicalIndex; i++) {
466
+ if (parentNode.childNodes[i]?.vtype === 'text') {
467
+ physicalIndex += 2 // Accounts for <!--neo-vtext--> wrappers
468
+ }
469
+ }
470
+
471
+ return physicalIndex
472
+ }
473
+
474
+ /**
475
+ * Only import for the DOM API based mount adapter.
476
+ * @returns {Promise<void>}
477
+ * @protected
478
+ */
479
+ async importDomApiVnodeCreator() {
480
+ if (!NeoConfig.useStringBasedMounting && !Neo.vdom.util?.DomApiVnodeCreator) {
481
+ await import('./util/DomApiVnodeCreator.mjs')
482
+ }
483
+ }
484
+
485
+ /**
486
+ * Only import for the string based mount adapter.
487
+ * @returns {Promise<void>}
488
+ * @protected
489
+ */
490
+ async importStringFromVnode() {
491
+ if (NeoConfig.useStringBasedMounting && !Neo.vdom.util?.StringFromVnode) {
492
+ await import('./util/StringFromVnode.mjs')
493
+ }
494
+ }
495
+
653
496
  /**
654
497
  * @param {Object} config
655
498
  * @param {Object} config.deltas
@@ -657,16 +500,29 @@ class Helper extends Base {
657
500
  * @param {Map} config.oldVnodeMap
658
501
  * @param {Neo.vdom.VNode} config.vnode
659
502
  * @param {Map} config.vnodeMap
503
+ * @protected
660
504
  */
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);
505
+ insertNode({deltas, index, oldVnodeMap, vnode, vnodeMap}) {
506
+ let details = vnodeMap.get(vnode.id),
507
+ {parentNode} = details,
508
+ parentId = parentNode.id,
509
+ me = this,
510
+ movedNodes = me.findMovedNodes({oldVnodeMap, vnode, vnodeMap}),
511
+ delta = {action: 'insertNode', parentId},
512
+ hasLeadingTextChildren = false,
513
+ physicalIndex = me.getPhysicalIndex(parentNode, index); // Processes the children of the *NEW* parent's VNode in the *current* state
514
+
515
+ Object.assign(delta, {hasLeadingTextChildren, index: physicalIndex});
516
+
517
+ if (NeoConfig.useStringBasedMounting) {
518
+ // For string-based mounting, pass a string excluding moved nodes
519
+ delta.outerHTML = Neo.vdom.util.StringFromVnode.create(vnode, movedNodes)
520
+ } else {
521
+ // For direct DOM API mounting, pass the pruned VNode tree
522
+ delta.vnode = Neo.vdom.util.DomApiVnodeCreator.create(vnode, movedNodes)
523
+ }
668
524
 
669
- deltas.default.push({action: 'insertNode', index, outerHTML, parentId});
525
+ deltas.default.push(delta);
670
526
 
671
527
  // Insert the new node into the old tree, to simplify future OPs
672
528
  oldVnodeMap.get(parentId).vnode.childNodes.splice(index, 0, vnode);
@@ -686,6 +542,7 @@ class Helper extends Base {
686
542
  * @param {Neo.vdom.VNode} vnode
687
543
  * @param {Map} oldVnodeMap
688
544
  * @returns {Boolean}
545
+ * @protected
689
546
  */
690
547
  isMovedNode(vnode, oldVnodeMap) {
691
548
  let oldVnode = oldVnodeMap.get(vnode.id);
@@ -703,33 +560,42 @@ class Helper extends Base {
703
560
  * @param {Map} config.oldVnodeMap
704
561
  * @param {Neo.vdom.VNode} config.vnode
705
562
  * @param {Map} config.vnodeMap
563
+ * @protected
706
564
  */
707
- moveNode(config) {
708
- let {deltas, insertDelta, oldVnodeMap, vnode, vnodeMap} = config,
709
- details = vnodeMap.get(vnode.id),
565
+ moveNode({deltas, insertDelta, oldVnodeMap, vnode, vnodeMap}) {
566
+ let details = vnodeMap.get(vnode.id),
710
567
  {index, parentNode} = details,
711
568
  parentId = parentNode.id,
712
569
  movedNode = oldVnodeMap.get(vnode.id),
713
570
  movedParentNode = movedNode.parentNode,
714
- {childNodes} = movedParentNode;
571
+ {childNodes} = movedParentNode,
572
+ delta = {action: 'moveNode', id: vnode.id, parentId},
573
+ physicalIndex = this.getPhysicalIndex(parentNode, index); // Processes the children of the *NEW* parent's VNode in the *current* state (parentNode.childNodes)
715
574
 
575
+ Object.assign(delta, {index: physicalIndex + insertDelta});
576
+ deltas.default.push(delta);
577
+
578
+ // This block implements the "corrupting the old tree" optimization for performance.
579
+ // It pre-modifies the old VNode map to reflect the move, preventing redundant deltas later.
716
580
  if (parentId !== movedParentNode.id) {
717
581
  // We need to remove the node from the old parent childNodes
718
582
  // (which must not be the same as the node they got moved into)
719
583
  NeoArray.remove(childNodes, movedNode.vnode);
720
584
 
585
+ // Get the VNode representing the *new parent* from the 'old VNode map'.
586
+ // This is crucial: 'oldParentNode' here is the *old state's VNode for the new parent*.
721
587
  let oldParentNode = oldVnodeMap.get(parentId);
722
588
 
723
589
  if (oldParentNode) {
724
590
  // If moved into a new parent node, update the reference inside the flat map
725
591
  movedNode.parentNode = oldParentNode.vnode;
726
592
 
593
+ // Reassign 'childNodes' property to now point to the 'childNodes' array
594
+ // of this 'old state's VNode for the new parent'.
727
595
  childNodes = movedNode.parentNode.childNodes
728
596
  }
729
597
  }
730
598
 
731
- deltas.default.push({action: 'moveNode', id: vnode.id, index: index + insertDelta, parentId});
732
-
733
599
  // Add the node into the old vnode tree to simplify future OPs.
734
600
  // NeoArray.insert() will switch to move() in case the node already exists.
735
601
  NeoArray.insert(childNodes, index, movedNode.vnode);
@@ -742,10 +608,11 @@ class Helper extends Base {
742
608
  * @param {Object} config.deltas
743
609
  * @param {Neo.vdom.VNode} config.oldVnode
744
610
  * @param {Map} config.oldVnodeMap
611
+ * @protected
745
612
  */
746
613
  removeNode({deltas, oldVnode, oldVnodeMap}) {
747
- if (oldVnode.componentId && !oldVnode.id) {
748
- oldVnode.id = oldVnode.componentId
614
+ if (oldVnode.componentId) {
615
+ oldVnode.id ??= oldVnode.componentId
749
616
  }
750
617
 
751
618
  let delta = {action: 'removeNode', id: oldVnode.id},
@@ -766,20 +633,23 @@ class Helper extends Base {
766
633
  * @param {Object} opts
767
634
  * @param {Object} opts.vdom
768
635
  * @param {Object} opts.vnode
769
- * @returns {Object|Promise<Object>}
636
+ * @returns {Promise<Object>}
770
637
  */
771
- update(opts) {
772
- let me = this,
773
- vnode = me.createVnode(opts.vdom),
774
- deltas = me.createDeltas({oldVnode: opts.vnode, vnode});
638
+ async update(opts) {
639
+ let me = this,
640
+ deltas, vnode;
641
+
642
+ await me.importDomApiVnodeCreator();
643
+ await me.importStringFromVnode();
644
+
645
+ vnode = me.createVnode(opts.vdom);
646
+ deltas = me.createDeltas({oldVnode: opts.vnode, vnode});
775
647
 
776
648
  // Trees to remove could contain nodes which we want to re-use (move),
777
649
  // so we need to execute the removeNode OPs last.
778
650
  deltas = deltas.default.concat(deltas.remove);
779
651
 
780
- let returnObj = {deltas, updateVdom: true, vnode};
781
-
782
- return Neo.config.useVdomWorker ? returnObj : Promise.resolve(returnObj)
652
+ return {deltas, updateVdom: true, vnode}
783
653
  }
784
654
  }
785
655