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.
- package/README.md +127 -121
- package/ServiceWorker.mjs +2 -2
- package/apps/email/view/Viewport.mjs +2 -2
- package/apps/form/view/Viewport.mjs +1 -1
- package/apps/portal/index.html +1 -1
- package/apps/portal/view/examples/List.mjs +1 -1
- package/apps/portal/view/home/FooterContainer.mjs +1 -1
- package/apps/realworld2/view/HomeContainer.mjs +1 -1
- package/apps/route/view/center/CardAdministration.mjs +3 -3
- package/apps/route/view/center/CardAdministrationDenied.mjs +2 -2
- package/apps/route/view/center/CardContact.mjs +2 -2
- package/apps/route/view/center/CardHome.mjs +2 -2
- package/apps/route/view/center/CardSection1.mjs +2 -2
- package/apps/route/view/center/CardSection2.mjs +2 -2
- package/buildScripts/createApp.mjs +2 -2
- package/docs/app/view/classdetails/HeaderComponent.mjs +3 -3
- package/docs/app/view/classdetails/MembersList.mjs +43 -46
- package/docs/app/view/classdetails/SourceViewComponent.mjs +1 -1
- package/docs/app/view/classdetails/TutorialComponent.mjs +1 -1
- package/examples/component/toast/MainContainer.mjs +16 -16
- package/examples/component/wrapper/googleMaps/MarkerDialog.mjs +4 -4
- package/examples/fields/MainContainer.mjs +1 -1
- package/examples/panel/MainContainer.mjs +2 -2
- package/examples/tab/container/MainContainer.mjs +3 -3
- package/examples/tabs/MainContainer.mjs +2 -2
- package/examples/tabs/MainContainer2.mjs +3 -3
- package/examples/viewport/MainContainer.mjs +2 -2
- package/package.json +33 -5
- package/resources/data/deck/learnneo/pages/benefits/FourEnvironments.md +1 -1
- package/resources/data/deck/learnneo/pages/benefits/Introduction.md +4 -3
- package/resources/data/deck/learnneo/pages/guides/events/DomEvents.md +5 -5
- package/resources/data/deck/training/pages/2022-12-27T21-55-23-144Z.md +2 -2
- package/resources/data/deck/training/pages/2022-12-29T18-36-08-226Z.md +1 -1
- package/resources/data/deck/training/pages/2022-12-29T18-36-56-893Z.md +2 -2
- package/resources/data/deck/training/pages/2022-12-29T20-37-08-919Z.md +2 -2
- package/resources/data/deck/training/pages/2022-12-29T20-37-20-344Z.md +2 -2
- package/resources/data/deck/training/pages/2023-01-13T21-48-17-258Z.md +2 -2
- package/resources/data/deck/training/pages/2023-02-05T17-44-53-815Z.md +9 -9
- package/resources/data/deck/training/pages/2023-10-14T19-25-08-153Z.md +1 -1
- package/src/DefaultConfig.mjs +14 -2
- package/src/Main.mjs +15 -6
- package/src/button/Base.mjs +1 -1
- package/src/calendar/view/calendars/List.mjs +1 -1
- package/src/component/Base.mjs +11 -11
- package/src/component/Chip.mjs +1 -1
- package/src/component/Helix.mjs +3 -3
- package/src/component/Process.mjs +2 -2
- package/src/component/StatusBadge.mjs +2 -2
- package/src/component/Timer.mjs +1 -1
- package/src/component/Toast.mjs +2 -2
- package/src/container/Base.mjs +1 -1
- package/src/form/field/CheckBox.mjs +2 -2
- package/src/form/field/FileUpload.mjs +14 -14
- package/src/form/field/Range.mjs +1 -1
- package/src/form/field/Text.mjs +1 -1
- package/src/form/field/trigger/Base.mjs +2 -2
- package/src/form/field/trigger/SpinUpDown.mjs +2 -2
- package/src/grid/View.mjs +1 -1
- package/src/main/DeltaUpdates.mjs +442 -0
- package/src/main/DomAccess.mjs +13 -95
- package/src/main/render/DomApiRenderer.mjs +138 -0
- package/src/main/render/StringBasedRenderer.mjs +58 -0
- package/src/table/View.mjs +1 -1
- package/src/table/plugin/CellEditing.mjs +1 -1
- package/src/tree/Accordion.mjs +11 -11
- package/src/tree/List.mjs +12 -5
- package/src/vdom/Helper.mjs +179 -309
- package/src/vdom/VNode.mjs +47 -11
- package/src/vdom/domConstants.mjs +65 -0
- package/src/vdom/util/DomApiVnodeCreator.mjs +51 -0
- package/src/vdom/util/StringFromVnode.mjs +123 -0
- package/src/worker/mixin/RemoteMethodAccess.mjs +13 -1
- package/src/main/mixin/DeltaUpdates.mjs +0 -352
package/src/vdom/Helper.mjs
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
-
import Base
|
2
|
-
import NeoArray
|
3
|
-
import
|
4
|
-
import
|
5
|
-
import VNode
|
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(
|
163
|
-
|
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
|
-
*
|
262
|
-
*
|
263
|
-
*
|
264
|
-
* @param {
|
265
|
-
* @param {
|
266
|
-
* @param {
|
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?.('"', '"') ?? 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
|
-
|
475
|
-
|
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: {},
|
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
|
-
|
303
|
+
if (value !== undefined && value !== null && key !== 'flag' && key !== 'removeDom') {
|
304
|
+
let hasUnit, newValue, style;
|
509
305
|
|
510
|
-
|
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 '
|
521
|
-
|
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 (
|
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
|
-
|
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(
|
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({
|
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(
|
635
|
-
let
|
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(
|
662
|
-
let
|
663
|
-
|
664
|
-
parentId
|
665
|
-
me
|
666
|
-
movedNodes
|
667
|
-
|
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(
|
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(
|
708
|
-
let
|
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
|
748
|
-
oldVnode.id
|
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 {
|
636
|
+
* @returns {Promise<Object>}
|
770
637
|
*/
|
771
|
-
update(opts) {
|
772
|
-
let me
|
773
|
-
vnode
|
774
|
-
|
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
|
-
|
781
|
-
|
782
|
-
return Neo.config.useVdomWorker ? returnObj : Promise.resolve(returnObj)
|
652
|
+
return {deltas, updateVdom: true, vnode}
|
783
653
|
}
|
784
654
|
}
|
785
655
|
|