neo.mjs 6.18.3 → 6.19.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.
- package/README.md +28 -214
- package/apps/ServiceWorker.mjs +2 -2
- package/apps/colors/view/ViewportController.mjs +7 -3
- package/apps/portal/data/blog.json +13 -0
- package/apps/portal/view/HeaderToolbar.mjs +2 -2
- package/apps/portal/view/Viewport.mjs +4 -2
- package/apps/portal/view/ViewportController.mjs +88 -7
- package/apps/portal/view/blog/Container.mjs +8 -8
- package/apps/portal/view/blog/List.mjs +6 -6
- package/apps/portal/view/home/FooterContainer.mjs +123 -0
- package/apps/portal/view/home/MainContainer.mjs +3 -2
- package/apps/portal/view/home/parts/AfterMath.mjs +17 -24
- package/apps/portal/view/home/parts/Colors.mjs +2 -2
- package/apps/portal/view/home/parts/How.mjs +3 -3
- package/apps/portal/view/home/parts/MainNeo.mjs +6 -7
- package/apps/portal/view/home/parts/References.mjs +88 -0
- package/apps/portal/view/learn/MainContainer.mjs +3 -2
- package/apps/portal/view/learn/MainContainerController.mjs +11 -0
- package/apps/portal/view/learn/PageContainer.mjs +5 -3
- package/apps/portal/view/services/Component.mjs +73 -0
- package/apps/website/data/blog.json +13 -0
- package/examples/ServiceWorker.mjs +2 -2
- package/examples/component/carousel/MainContainer.mjs +42 -33
- package/examples/layout/cube/MainContainer.mjs +217 -0
- package/examples/layout/cube/app.mjs +6 -0
- package/examples/layout/cube/index.html +11 -0
- package/examples/layout/cube/neo-config.json +6 -0
- package/examples/model/twoWay/MainContainer.mjs +76 -0
- package/examples/model/twoWay/app.mjs +6 -0
- package/examples/model/twoWay/index.html +11 -0
- package/examples/model/twoWay/neo-config.json +6 -0
- package/package.json +7 -7
- package/resources/data/deck/learnneo/pages/Earthquakes-01-goals.md +32 -0
- package/resources/data/deck/learnneo/pages/Earthquakes-Lab-01-generate-a-workspace.md +47 -0
- package/resources/data/deck/learnneo/pages/Earthquakes-Lab-02-generate-the-starter-app.md +150 -0
- package/resources/data/deck/learnneo/pages/Earthquakes-Lab-03-debugging.md +136 -0
- package/resources/data/deck/learnneo/pages/Earthquakes-Lab-04-fetch-data.md +146 -0
- package/resources/data/deck/learnneo/pages/Earthquakes-Lab-05-refactor-the-table.md +146 -0
- package/resources/data/deck/learnneo/pages/Earthquakes-Lab-06-use-a-view-model.md +301 -0
- package/resources/data/deck/learnneo/pages/Earthquakes-Lab-07-use-the-google-maps-addon.md +175 -0
- package/resources/data/deck/learnneo/pages/Earthquakes-Lab-08-events.md +38 -0
- package/resources/data/deck/learnneo/pages/Earthquakes.md +8 -8
- package/resources/data/deck/learnneo/pages/Glossary.md +0 -0
- package/resources/data/deck/learnneo/pages/GuideEvents.md +80 -1
- package/resources/data/deck/learnneo/tree.json +2 -1
- package/resources/images/apps/portal/neo-references.png +0 -0
- package/resources/scss/src/apps/portal/Viewport.scss +18 -0
- package/resources/scss/src/apps/portal/blog/Container.scss +7 -7
- package/resources/scss/src/apps/portal/blog/List.scss +20 -16
- package/resources/scss/src/apps/portal/home/FooterContainer.scss +31 -0
- package/resources/scss/src/apps/portal/home/parts/AfterMath.scss +5 -0
- package/resources/scss/src/apps/portal/home/parts/MainNeo.scss +4 -5
- package/resources/scss/src/apps/portal/home/parts/References.scss +46 -0
- package/resources/scss/src/apps/portal/learn/ContentTreeList.scss +20 -0
- package/resources/scss/src/apps/portal/learn/ContentView.scss +4 -0
- package/resources/scss/src/apps/portal/learn/MainContainer.scss +1 -1
- package/resources/scss/src/apps/portal/learn/PageContainer.scss +22 -16
- package/resources/scss/src/apps/portal/services/Component.scss +20 -0
- package/resources/scss/src/component/Carousel.scss +21 -0
- package/resources/scss/src/examples/layout/cube/MainContainer.scss +7 -0
- package/resources/scss/src/layout/Cube.scss +80 -0
- package/resources/scss/src/tab/Container.scss +10 -10
- package/resources/scss/theme-neo-light/apps/portal/blog/Container.scss +3 -0
- package/resources/scss/theme-neo-light/form/field/Search.scss +1 -1
- package/resources/scss/theme-neo-light/tooltip/Base.scss +1 -1
- package/src/DefaultConfig.mjs +2 -2
- package/src/Main.mjs +15 -1
- package/src/Neo.mjs +14 -3
- package/src/calendar/view/MainContainer.mjs +8 -7
- package/src/component/Base.mjs +28 -8
- package/src/component/DateSelector.mjs +2 -2
- package/src/container/Base.mjs +3 -1
- package/src/dialog/Base.mjs +1 -2
- package/src/form/field/Time.mjs +18 -16
- package/src/layout/Base.mjs +43 -6
- package/src/layout/Card.mjs +21 -59
- package/src/layout/Cube.mjs +432 -0
- package/src/layout/Fit.mjs +9 -38
- package/src/layout/Flexbox.mjs +16 -17
- package/src/layout/Form.mjs +13 -70
- package/src/layout/Grid.mjs +6 -18
- package/src/list/Base.mjs +3 -3
- package/src/main/mixin/DeltaUpdates.mjs +16 -3
- package/src/model/Component.mjs +25 -6
- package/src/util/Array.mjs +36 -0
- package/src/vdom/Helper.mjs +338 -442
- package/src/vdom/VNode.mjs +12 -1
- package/test/siesta/siesta.js +16 -1
- package/test/siesta/tests/VdomCalendar.mjs +2193 -37
- package/test/siesta/tests/VdomHelper.mjs +287 -48
- package/test/siesta/tests/vdom/Advanced.mjs +368 -0
- package/test/siesta/tests/vdom/layout/Cube.mjs +189 -0
- package/test/siesta/tests/vdom/table/Container.mjs +133 -0
- package/resources/scss/theme-neo-light/apps/portal/learn/ContentTreeList.scss +0 -23
package/src/vdom/Helper.mjs
CHANGED
@@ -3,7 +3,6 @@ import NeoArray from '../util/Array.mjs';
|
|
3
3
|
import NeoString from '../util/String.mjs';
|
4
4
|
import Style from '../util/Style.mjs';
|
5
5
|
import VNode from './VNode.mjs';
|
6
|
-
import VNodeUtil from '../util/VNode.mjs';
|
7
6
|
|
8
7
|
/**
|
9
8
|
* The central class for the VDom worker to create vnodes & delta updates.
|
@@ -97,7 +96,7 @@ class Helper extends Base {
|
|
97
96
|
delete opts.parentIndex;
|
98
97
|
delete opts.windowId;
|
99
98
|
|
100
|
-
node = me.
|
99
|
+
node = me.createVnode(opts);
|
101
100
|
node.outerHTML = me.createStringFromVnode(node);
|
102
101
|
|
103
102
|
if (autoMount) {
|
@@ -122,413 +121,197 @@ class Helper extends Base {
|
|
122
121
|
}
|
123
122
|
|
124
123
|
/**
|
125
|
-
* @param {Object}
|
126
|
-
* @param {
|
127
|
-
* @param {
|
128
|
-
* @param {
|
129
|
-
* @param {
|
130
|
-
* @
|
131
|
-
* @param {Object} config.oldVnodeRoot
|
132
|
-
* @param {String} config.parentId
|
133
|
-
* @returns {Array} deltas
|
124
|
+
* @param {Object} config
|
125
|
+
* @param {Object} config.deltas
|
126
|
+
* @param {Neo.vdom.VNode} config.oldVnode
|
127
|
+
* @param {Neo.vdom.VNode} config.vnode
|
128
|
+
* @param {Map} config.vnodeMap
|
129
|
+
* @returns {Object} deltas
|
134
130
|
*/
|
135
|
-
|
136
|
-
let {deltas
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
movedOldNode = me.findVnode(oldVnodeRoot, newVnode.id, oldVnode)
|
147
|
-
}
|
148
|
-
|
149
|
-
if (!movedOldNode) {
|
150
|
-
// console.log('insertNode', newVnode);
|
151
|
-
|
152
|
-
deltas.push({
|
153
|
-
action : 'insertNode',
|
154
|
-
id : newVnode.id,
|
155
|
-
index,
|
156
|
-
outerHTML: me.createStringFromVnode(newVnode),
|
157
|
-
parentId
|
158
|
-
})
|
159
|
-
}
|
160
|
-
} else if (!newVnode && oldVnode) {
|
161
|
-
if (newVnodeRoot) {
|
162
|
-
movedNode = me.findVnode(newVnodeRoot, oldVnode.id, newVnode)
|
163
|
-
}
|
164
|
-
|
165
|
-
// use case: calendar week view => move an event into a column on the right side
|
166
|
-
|
167
|
-
if (movedNode) {
|
168
|
-
deltas.push({
|
169
|
-
action: 'moveNode',
|
170
|
-
id : oldVnode.id,
|
171
|
-
index : movedNode.index,
|
172
|
-
parentId: movedNode.parentNode.id
|
173
|
-
});
|
174
|
-
|
175
|
-
movedOldNode = me.findVnode(oldVnodeRoot, movedNode.parentNode.id);
|
176
|
-
|
177
|
-
me.createDeltas({
|
178
|
-
deltas,
|
179
|
-
newVnode: movedNode.vnode,
|
180
|
-
newVnodeRoot,
|
181
|
-
oldVnode,
|
182
|
-
oldVnodeRoot,
|
183
|
-
parentId: movedNode.parentNode.id
|
184
|
-
});
|
185
|
-
|
186
|
-
movedOldNode.vnode.childNodes.splice(movedNode.index, 0, movedNode.vnode)
|
187
|
-
} else {
|
188
|
-
// console.log('top level removed node', oldVnode.id, oldVnode);
|
189
|
-
|
190
|
-
delta = {
|
191
|
-
action: 'removeNode',
|
192
|
-
id : oldVnode.id
|
193
|
-
};
|
194
|
-
|
195
|
-
// We only need a parentId for vtype text
|
196
|
-
if (oldVnode.vtype === 'text') {
|
197
|
-
let removedNodeDetails = me.findVnode(oldVnodeRoot, oldVnode.id);
|
198
|
-
|
199
|
-
delta.parentId = removedNodeDetails?.parentNode.id
|
200
|
-
}
|
201
|
-
|
202
|
-
deltas.push(delta)
|
203
|
-
}
|
131
|
+
compareAttributes(config) {
|
132
|
+
let {deltas, oldVnode, vnode, vnodeMap} = config,
|
133
|
+
attributes, delta, value, keys, styles, add, remove;
|
134
|
+
|
135
|
+
if (vnode.vtype === 'text' && vnode.innerHTML !== oldVnode.innerHTML) {
|
136
|
+
deltas.default.push({
|
137
|
+
action : 'updateVtext',
|
138
|
+
id : vnode.id,
|
139
|
+
parentId: vnodeMap.get(vnode.id).parentNode.id,
|
140
|
+
value : vnode.innerHTML
|
141
|
+
})
|
204
142
|
} else {
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
action: 'removeNode',
|
217
|
-
id : oldVnode.id,
|
218
|
-
});
|
219
|
-
|
220
|
-
deltas.push({
|
221
|
-
action : 'insertNode',
|
222
|
-
id : newVnode.id,
|
223
|
-
index,
|
224
|
-
outerHTML: me.createStringFromVnode(newVnode),
|
225
|
-
parentId
|
226
|
-
});
|
227
|
-
|
228
|
-
return {
|
229
|
-
indexDelta: 0
|
230
|
-
}
|
143
|
+
keys = Object.keys(vnode);
|
144
|
+
|
145
|
+
Object.keys(oldVnode).forEach(prop => {
|
146
|
+
if (!vnode.hasOwnProperty(prop)) {
|
147
|
+
keys.push(prop)
|
148
|
+
} else if (prop === 'attributes') { // find removed attributes
|
149
|
+
Object.keys(oldVnode[prop]).forEach(attr => {
|
150
|
+
if (!vnode[prop].hasOwnProperty(attr)) {
|
151
|
+
vnode[prop][attr] = null
|
152
|
+
}
|
153
|
+
})
|
231
154
|
}
|
155
|
+
});
|
232
156
|
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
action: 'moveNode',
|
237
|
-
id : movedOldNode.vnode.id,
|
238
|
-
index,
|
239
|
-
parentId
|
240
|
-
});
|
241
|
-
|
242
|
-
me.createDeltas({
|
243
|
-
deltas,
|
244
|
-
newVnode,
|
245
|
-
newVnodeRoot,
|
246
|
-
oldVnode: movedOldNode.vnode,
|
247
|
-
oldVnodeRoot,
|
248
|
-
parentId: movedNode.parentNode.id
|
249
|
-
});
|
250
|
-
|
251
|
-
// see: https://github.com/neomjs/neo/issues/3116
|
252
|
-
movedOldNode.parentNode.childNodes.splice(index, 0, movedOldNode)
|
253
|
-
} else if (!movedNode && movedOldNode) {
|
254
|
-
if (newVnode.id === movedOldNode.vnode.id) {
|
255
|
-
indexDelta = 0;
|
256
|
-
|
257
|
-
if (VNodeUtil.findChildVnodeById(oldVnode, newVnode.id)) {
|
258
|
-
// the old vnode replaced a parent vnode
|
259
|
-
// e.g.: vdom.cn[1] = vdom.cn[1].cn[0];
|
260
|
-
|
261
|
-
deltas.push({
|
262
|
-
action: 'replaceChild',
|
263
|
-
fromId: oldVnode.id,
|
264
|
-
parentId,
|
265
|
-
toId : newVnode.id
|
266
|
-
})
|
267
|
-
} else {
|
268
|
-
// the old vnode got moved into a different higher level branch
|
269
|
-
// and its parent got removed
|
270
|
-
// e.g.:
|
271
|
-
// vdom.cn[1] = vdom.cn[2].cn[0];
|
272
|
-
// vdom.cn.splice(2, 1);
|
273
|
-
|
274
|
-
let movedOldNodeDetails = VNodeUtil.findChildVnode(oldVnodeRoot, movedOldNode.vnode.id),
|
275
|
-
oldVnodeDetails = VNodeUtil.findChildVnode(oldVnodeRoot, oldVnode.id);
|
157
|
+
keys.forEach(prop => {
|
158
|
+
delta = {};
|
159
|
+
value = vnode[prop];
|
276
160
|
|
277
|
-
|
161
|
+
switch (prop) {
|
162
|
+
case 'attributes':
|
163
|
+
attributes = {};
|
278
164
|
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
// console.log(newVnodeDetails.parentNode);
|
286
|
-
|
287
|
-
i = index + 1;
|
288
|
-
tmp = oldVnodeDetails.parentNode.childNodes;
|
289
|
-
len = movedOldNodeDetails.index;
|
290
|
-
|
291
|
-
for (; i < len; i++) {
|
292
|
-
// console.log(tmp[i]);
|
293
|
-
if (!VNodeUtil.findChildVnode(newVnodeDetails.parentNode, tmp[i].id)) {
|
294
|
-
// console.log('not found');
|
295
|
-
targetIndex ++
|
296
|
-
}
|
165
|
+
Object.entries(value).forEach(([key, value]) => {
|
166
|
+
if (!(oldVnode.attributes.hasOwnProperty(key) && oldVnode.attributes[key] === value)) {
|
167
|
+
if (value !== null && !Neo.isString(value) && Neo.isEmpty(value)) {
|
168
|
+
// ignore empty arrays & objects
|
169
|
+
} else {
|
170
|
+
attributes[key] = value
|
297
171
|
}
|
172
|
+
}
|
173
|
+
});
|
298
174
|
|
299
|
-
|
300
|
-
|
301
|
-
movedOldNodeDetails.parentNode.childNodes.splice(movedOldNodeDetails.index, 1);
|
175
|
+
if (Object.keys(attributes).length > 0) {
|
176
|
+
delta.attributes = attributes;
|
302
177
|
|
303
|
-
|
304
|
-
if (
|
305
|
-
|
306
|
-
action: 'moveNode',
|
307
|
-
id : movedOldNode.vnode.id,
|
308
|
-
index,
|
309
|
-
parentId
|
310
|
-
})
|
178
|
+
Object.entries(attributes).forEach(([key, value]) => {
|
179
|
+
if (value === null || value === '') {
|
180
|
+
delete vnode.attributes[key]
|
311
181
|
}
|
312
|
-
|
313
|
-
// console.log(movedOldNodeDetails);
|
314
|
-
|
315
|
-
indexDelta = 0
|
316
|
-
}
|
317
|
-
|
318
|
-
deltas.push({
|
319
|
-
action: 'removeNode',
|
320
|
-
id : oldVnode.id,
|
321
|
-
parentId
|
322
182
|
})
|
323
183
|
}
|
184
|
+
break
|
185
|
+
case 'nodeName':
|
186
|
+
case 'innerHTML':
|
187
|
+
if (value !== oldVnode[prop]) {
|
188
|
+
delta[prop] = value
|
189
|
+
}
|
190
|
+
break
|
191
|
+
case 'style':
|
192
|
+
styles = Style.compareStyles(value, oldVnode.style);
|
193
|
+
if (styles) {
|
194
|
+
delta.style = styles
|
195
|
+
}
|
196
|
+
break
|
197
|
+
case 'className':
|
198
|
+
if (oldVnode.className) {
|
199
|
+
add = NeoArray.difference(value, oldVnode.className);
|
200
|
+
remove = NeoArray.difference(oldVnode.className, value)
|
201
|
+
} else {
|
202
|
+
add = value;
|
203
|
+
remove = []
|
204
|
+
}
|
324
205
|
|
325
|
-
|
326
|
-
|
327
|
-
newVnode,
|
328
|
-
newVnodeRoot,
|
329
|
-
oldVnode: movedOldNode.vnode,
|
330
|
-
oldVnodeRoot,
|
331
|
-
parentId
|
332
|
-
});
|
333
|
-
|
334
|
-
return {indexDelta}
|
335
|
-
} else {
|
336
|
-
// console.log('removed node', oldVnode.id, '('+newVnode.id+')');
|
337
|
-
|
338
|
-
deltas.push({
|
339
|
-
action: 'removeNode',
|
340
|
-
id : oldVnode.id
|
341
|
-
});
|
206
|
+
if (add.length > 0 || remove.length > 0) {
|
207
|
+
delta.cls = {};
|
342
208
|
|
343
|
-
|
344
|
-
|
209
|
+
if (add .length > 0) {delta.cls.add = add}
|
210
|
+
if (remove.length > 0) {delta.cls.remove = remove}
|
345
211
|
}
|
346
|
-
|
347
|
-
}
|
348
|
-
// new node inside of a child array
|
349
|
-
// console.log('new node', index, parentId, newVnode);
|
212
|
+
break
|
213
|
+
}
|
350
214
|
|
351
|
-
|
215
|
+
if (Object.keys(delta).length > 0) {
|
216
|
+
delta.id = vnode.id;
|
217
|
+
deltas.default.push(delta)
|
218
|
+
}
|
219
|
+
})
|
220
|
+
}
|
352
221
|
|
353
|
-
|
354
|
-
|
355
|
-
// => we need to remove the old one, since it will get recreated
|
222
|
+
return deltas
|
223
|
+
}
|
356
224
|
|
357
|
-
|
225
|
+
/**
|
226
|
+
* @param {Object} config
|
227
|
+
* @param {Object} [config.deltas={default: [], remove: []}]
|
228
|
+
* @param {Neo.vdom.VNode} config.oldVnode
|
229
|
+
* @param {Map} [config.oldVnodeMap]
|
230
|
+
* @param {Neo.vdom.VNode} config.vnode
|
231
|
+
* @param {Map} [config.vnodeMap]
|
232
|
+
* @returns {Object} deltas
|
233
|
+
*/
|
234
|
+
createDeltas(config) {
|
235
|
+
let {deltas={default: [], remove: []}, oldVnode, vnode} = config,
|
236
|
+
oldVnodeId = oldVnode?.id,
|
237
|
+
vnodeId = vnode?.id;
|
238
|
+
|
239
|
+
// Edge case: setting `removeDom: true` on a top-level vdom node
|
240
|
+
if (!vnode && oldVnodeId) {
|
241
|
+
deltas.remove.push({action: 'removeNode', id: oldVnodeId});
|
242
|
+
return deltas
|
243
|
+
}
|
358
244
|
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
})
|
363
|
-
}
|
245
|
+
if (vnode.static) {
|
246
|
+
return deltas
|
247
|
+
}
|
364
248
|
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
index,
|
369
|
-
outerHTML: me.createStringFromVnode(newVnode),
|
370
|
-
parentId
|
371
|
-
});
|
249
|
+
if (vnodeId !== oldVnodeId) {
|
250
|
+
throw new Error(`createDeltas() must get called for the same node. ${vnodeId}, ${oldVnodeId}`);
|
251
|
+
}
|
372
252
|
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
253
|
+
let me = this,
|
254
|
+
oldVnodeMap = config.oldVnodeMap || me.createVnodeMap({vnode: oldVnode}),
|
255
|
+
vnodeMap = config.vnodeMap || me.createVnodeMap({vnode}),
|
256
|
+
childNodes = vnode .childNodes || [],
|
257
|
+
oldChildNodes = oldVnode.childNodes || [],
|
258
|
+
i = 0,
|
259
|
+
indexDelta = 0,
|
260
|
+
insertDelta = 0,
|
261
|
+
len = Math.max(childNodes.length, oldChildNodes.length),
|
262
|
+
childNode, nodeInNewTree, oldChildNode;
|
263
|
+
|
264
|
+
me.compareAttributes({deltas, oldVnode, vnode, vnodeMap});
|
265
|
+
|
266
|
+
if (childNodes.length === 0 && oldChildNodes.length > 1) {
|
267
|
+
deltas.remove.push({action: 'removeAll', parentId: vnodeId});
|
268
|
+
return deltas
|
269
|
+
}
|
378
270
|
|
379
|
-
|
271
|
+
for (; i < len; i++) {
|
272
|
+
childNode = childNodes[i];
|
273
|
+
oldChildNode = oldChildNodes[i + indexDelta];
|
380
274
|
|
381
|
-
|
275
|
+
// console.log(childNode?.id, oldChildNode?.id);
|
382
276
|
|
383
|
-
|
277
|
+
if (!childNode && !oldChildNode) {
|
278
|
+
break
|
279
|
+
}
|
384
280
|
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
}
|
281
|
+
// Same node, continue recursively
|
282
|
+
if (childNode && childNode.id === oldChildNode?.id) {
|
283
|
+
me.createDeltas({deltas, oldVnode: oldChildNode, oldVnodeMap, vnode: childNode, vnodeMap});
|
284
|
+
continue
|
285
|
+
}
|
391
286
|
|
392
|
-
|
393
|
-
|
394
|
-
action: 'moveNode',
|
395
|
-
id : movedNode.vnode.id,
|
396
|
-
index : movedNode.index,
|
397
|
-
parentId: movedNode.parentNode.id
|
398
|
-
})
|
399
|
-
}
|
287
|
+
if (oldChildNode) {
|
288
|
+
nodeInNewTree = vnodeMap.get(oldChildNode.id);
|
400
289
|
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
290
|
+
// Remove node, if no longer inside the new tree
|
291
|
+
if (!nodeInNewTree) {
|
292
|
+
me.removeNode({deltas, oldVnode: oldChildNode, oldVnodeMap});
|
293
|
+
i--;
|
294
|
+
insertDelta++;
|
295
|
+
continue
|
296
|
+
}
|
297
|
+
|
298
|
+
// The old child node got moved into a different not processed array. It will get picked up there.
|
299
|
+
if (childNode && vnodeId !== nodeInNewTree.parentNode.id) {
|
300
|
+
i--;
|
301
|
+
indexDelta++;
|
302
|
+
continue
|
413
303
|
}
|
414
304
|
}
|
415
305
|
|
416
|
-
if (
|
417
|
-
if (
|
418
|
-
|
419
|
-
action : 'updateVtext',
|
420
|
-
id : newVnode.id,
|
421
|
-
parentId: VNodeUtil.findChildVnode(newVnodeRoot, newVnode.id).parentNode.id,
|
422
|
-
value : newVnode.innerHTML
|
423
|
-
})
|
306
|
+
if (childNode) {
|
307
|
+
if (oldVnodeMap.get(childNode.id)) {
|
308
|
+
me.moveNode({deltas, insertDelta, oldVnodeMap, vnode: childNode, vnodeMap});
|
424
309
|
} else {
|
425
|
-
|
426
|
-
|
427
|
-
Object.keys(oldVnode).forEach(prop => {
|
428
|
-
if (!newVnode.hasOwnProperty(prop)) {
|
429
|
-
keys.push(prop)
|
430
|
-
} else if (prop === 'attributes') { // find removed attributes
|
431
|
-
Object.keys(oldVnode[prop]).forEach(attr => {
|
432
|
-
if (!newVnode[prop].hasOwnProperty(attr)) {
|
433
|
-
newVnode[prop][attr] = null;
|
434
|
-
}
|
435
|
-
})
|
436
|
-
}
|
437
|
-
});
|
438
|
-
|
439
|
-
keys.forEach(prop => {
|
440
|
-
delta = {};
|
441
|
-
value = newVnode[prop];
|
442
|
-
|
443
|
-
switch (prop) {
|
444
|
-
case 'attributes':
|
445
|
-
attributes = {};
|
446
|
-
|
447
|
-
Object.entries(value).forEach(([key, value]) => {
|
448
|
-
if (!(oldVnode.attributes.hasOwnProperty(key) && oldVnode.attributes[key] === value)) {
|
449
|
-
if (value !== null && !Neo.isString(value) && Neo.isEmpty(value)) {
|
450
|
-
// ignore empty arrays & objects
|
451
|
-
} else {
|
452
|
-
attributes[key] = value
|
453
|
-
}
|
454
|
-
}
|
455
|
-
});
|
456
|
-
|
457
|
-
if (Object.keys(attributes).length > 0) {
|
458
|
-
delta.attributes = attributes;
|
459
|
-
|
460
|
-
Object.entries(attributes).forEach(([key, value]) => {
|
461
|
-
if (value === null || value === '') {
|
462
|
-
delete newVnode.attributes[key]
|
463
|
-
}
|
464
|
-
})
|
465
|
-
}
|
466
|
-
break
|
467
|
-
case 'childNodes':
|
468
|
-
i = 0;
|
469
|
-
indexDelta = 0;
|
470
|
-
len = Math.max(value.length, oldVnode.childNodes.length);
|
471
|
-
|
472
|
-
for (; i < len; i++) {
|
473
|
-
returnValue = me.createDeltas({
|
474
|
-
deltas,
|
475
|
-
index : i,
|
476
|
-
newVnode: value[i],
|
477
|
-
newVnodeRoot,
|
478
|
-
oldVnode: oldVnode.childNodes[i + indexDelta],
|
479
|
-
oldVnodeRoot,
|
480
|
-
parentId: newVnode.id
|
481
|
-
});
|
482
|
-
|
483
|
-
if (returnValue && returnValue.indexDelta) {
|
484
|
-
indexDelta += returnValue.indexDelta
|
485
|
-
}
|
486
|
-
}
|
487
|
-
|
488
|
-
if (indexDelta < 0) {
|
489
|
-
// this case happens for infinite scrolling upwards:
|
490
|
-
// add new nodes at the start, remove nodes at the end
|
491
|
-
for (i=value.length + indexDelta; i < oldVnode.childNodes.length; i++) {
|
492
|
-
deltas.push({
|
493
|
-
action: 'removeNode',
|
494
|
-
id : oldVnode.childNodes[i].id
|
495
|
-
})
|
496
|
-
}
|
497
|
-
}
|
498
|
-
|
499
|
-
break
|
500
|
-
case 'nodeName':
|
501
|
-
case 'innerHTML':
|
502
|
-
if (value !== oldVnode[prop]) {
|
503
|
-
delta[prop] = value
|
504
|
-
}
|
505
|
-
break
|
506
|
-
case 'style':
|
507
|
-
styles = Style.compareStyles(value, oldVnode.style);
|
508
|
-
if (styles) {
|
509
|
-
delta.style = styles
|
510
|
-
}
|
511
|
-
break
|
512
|
-
case 'className':
|
513
|
-
if (oldVnode.className) {
|
514
|
-
add = NeoArray.difference(value, oldVnode.className);
|
515
|
-
remove = NeoArray.difference(oldVnode.className, value)
|
516
|
-
} else {
|
517
|
-
add = value;
|
518
|
-
remove = []
|
519
|
-
}
|
520
|
-
|
521
|
-
if (add.length > 0 || remove.length > 0) {
|
522
|
-
delta.cls = {add, remove}
|
523
|
-
}
|
524
|
-
break
|
525
|
-
}
|
310
|
+
me.insertNode({deltas, index: i + insertDelta, oldVnodeMap, vnode: childNode, vnodeMap});
|
311
|
+
}
|
526
312
|
|
527
|
-
|
528
|
-
|
529
|
-
deltas.push(delta)
|
530
|
-
}
|
531
|
-
})
|
313
|
+
if (oldChildNode && vnodeId === vnodeMap.get(oldChildNode.id)?.parentNode.id) {
|
314
|
+
len++;
|
532
315
|
}
|
533
316
|
}
|
534
317
|
}
|
@@ -590,28 +373,35 @@ class Helper extends Base {
|
|
590
373
|
}
|
591
374
|
|
592
375
|
/**
|
593
|
-
* @param {
|
376
|
+
* @param {Neo.vdom.VNode} vnode
|
377
|
+
* @param {Map} [movedNodes]
|
594
378
|
*/
|
595
|
-
createStringFromVnode(vnode) {
|
596
|
-
let me = this
|
379
|
+
createStringFromVnode(vnode, movedNodes) {
|
380
|
+
let me = this,
|
381
|
+
id = vnode?.id;
|
382
|
+
|
383
|
+
if (id && movedNodes?.get(id)) {
|
384
|
+
return ''
|
385
|
+
}
|
597
386
|
|
598
387
|
switch (vnode.vtype) {
|
599
388
|
case 'root':
|
600
|
-
return me.createStringFromVnode(vnode.childNodes[0])
|
389
|
+
return me.createStringFromVnode(vnode.childNodes[0], movedNodes)
|
601
390
|
case 'text':
|
602
391
|
return vnode.innerHTML === undefined ? '' : String(vnode.innerHTML)
|
603
392
|
case 'vnode':
|
604
|
-
return me.createOpenTag(vnode) + me.createTagContent(vnode) + me.createCloseTag(vnode)
|
393
|
+
return me.createOpenTag(vnode) + me.createTagContent(vnode, movedNodes) + me.createCloseTag(vnode)
|
605
394
|
default:
|
606
395
|
return ''
|
607
396
|
}
|
608
397
|
}
|
609
398
|
|
610
399
|
/**
|
611
|
-
* @param {
|
400
|
+
* @param {Neo.vdom.VNode} vnode
|
401
|
+
* @param {Map} [movedNodes]
|
612
402
|
* @protected
|
613
403
|
*/
|
614
|
-
createTagContent(vnode) {
|
404
|
+
createTagContent(vnode, movedNodes) {
|
615
405
|
if (vnode.innerHTML) {
|
616
406
|
return vnode.innerHTML
|
617
407
|
}
|
@@ -623,7 +413,7 @@ class Helper extends Base {
|
|
623
413
|
|
624
414
|
for (; i < len; i++) {
|
625
415
|
childNode = vnode.childNodes[i];
|
626
|
-
outerHTML = this.createStringFromVnode(childNode);
|
416
|
+
outerHTML = this.createStringFromVnode(childNode, movedNodes);
|
627
417
|
|
628
418
|
if (childNode.innerHTML !== outerHTML) {
|
629
419
|
if (this.returnChildNodeOuterHtml) {
|
@@ -637,53 +427,11 @@ class Helper extends Base {
|
|
637
427
|
return string
|
638
428
|
}
|
639
429
|
|
640
|
-
/**
|
641
|
-
* @param {Neo.vdom.VNode} vnode
|
642
|
-
* @param {String} id
|
643
|
-
* @param {Neo.vdom.VNode} parentNode
|
644
|
-
* @param {Number} index
|
645
|
-
* @returns {Object}
|
646
|
-
* {Number} index
|
647
|
-
* {String} parentId
|
648
|
-
* {Neo.vdom.VNode} vnode
|
649
|
-
*/
|
650
|
-
findVnode(vnode, id, parentNode, index) {
|
651
|
-
if (!index) {
|
652
|
-
index = 0
|
653
|
-
}
|
654
|
-
|
655
|
-
let returnValue = null,
|
656
|
-
children, childValue, i, len;
|
657
|
-
|
658
|
-
if (vnode.id === id) {
|
659
|
-
returnValue = {index, parentNode, vnode}
|
660
|
-
} else if (vnode.vtype !== 'text') {
|
661
|
-
children = vnode.childNodes;
|
662
|
-
i = 0;
|
663
|
-
len = children?.length || 0;
|
664
|
-
|
665
|
-
for (; i < len; i++) {
|
666
|
-
childValue = this.findVnode(children[i], id, vnode, i);
|
667
|
-
|
668
|
-
if (childValue && childValue.vnode.id === id) {
|
669
|
-
returnValue = childValue;
|
670
|
-
break
|
671
|
-
}
|
672
|
-
}
|
673
|
-
}
|
674
|
-
|
675
|
-
if (returnValue && returnValue.parentId === 'root') {
|
676
|
-
returnValue.index = null
|
677
|
-
}
|
678
|
-
|
679
|
-
return returnValue;
|
680
|
-
}
|
681
|
-
|
682
430
|
/**
|
683
431
|
* @param {Object} opts
|
684
432
|
* @returns {Object|Neo.vdom.VNode|null}
|
685
433
|
*/
|
686
|
-
|
434
|
+
createVnode(opts) {
|
687
435
|
if (opts.removeDom === true) {
|
688
436
|
return null
|
689
437
|
}
|
@@ -735,7 +483,7 @@ class Helper extends Base {
|
|
735
483
|
value.forEach(item => {
|
736
484
|
if (item.removeDom !== true) {
|
737
485
|
delete item.removeDom; // could be false
|
738
|
-
potentialNode = me.
|
486
|
+
potentialNode = me.createVnode(item);
|
739
487
|
|
740
488
|
if (potentialNode) { // don't add null values
|
741
489
|
newValue.push(potentialNode)
|
@@ -771,6 +519,9 @@ class Helper extends Base {
|
|
771
519
|
case 'id':
|
772
520
|
node.id = value;
|
773
521
|
break
|
522
|
+
case 'static':
|
523
|
+
node.static = value;
|
524
|
+
break
|
774
525
|
case 'style':
|
775
526
|
style = node.style;
|
776
527
|
if (Neo.isString(value)) {
|
@@ -791,6 +542,155 @@ class Helper extends Base {
|
|
791
542
|
return new VNode(node)
|
792
543
|
}
|
793
544
|
|
545
|
+
/**
|
546
|
+
* Creates a flap map of the tree, containing ids as keys and infos as values
|
547
|
+
* @param {Object} config
|
548
|
+
* @param {Neo.vdom.VNode} config.vnode
|
549
|
+
* @param {Neo.vdom.VNode} [config.parentNode=null]
|
550
|
+
* @param {Number} [config.index=0]
|
551
|
+
* @param {Map} [config.map=new Map()]
|
552
|
+
* @returns {Map}
|
553
|
+
* {String} id vnode.id (convenience shortcut)
|
554
|
+
* {Number} index
|
555
|
+
* {String} parentId
|
556
|
+
* {Neo.vdom.VNode} vnode
|
557
|
+
*/
|
558
|
+
createVnodeMap(config) {
|
559
|
+
let {vnode, parentNode=null, index=0, map=new Map()} = config,
|
560
|
+
id = vnode?.id;
|
561
|
+
|
562
|
+
map.set(id, {id, index, parentNode, vnode});
|
563
|
+
|
564
|
+
vnode?.childNodes?.forEach((childNode, index) => {
|
565
|
+
this.createVnodeMap({vnode: childNode, parentNode: vnode, index, map})
|
566
|
+
});
|
567
|
+
|
568
|
+
return map
|
569
|
+
}
|
570
|
+
|
571
|
+
/**
|
572
|
+
* The logic will parse the vnode (tree) to find existing items inside a given map.
|
573
|
+
* It will not search for further childNodes inside an already found vnode.
|
574
|
+
* @param {Object} config
|
575
|
+
* @param {Map} [config.movedNodes=new Map()]
|
576
|
+
* @param {Map} config.oldVnodeMap
|
577
|
+
* @param {Neo.vdom.VNode} config.vnode
|
578
|
+
* @param {Map} config.vnodeMap
|
579
|
+
* @returns {Map}
|
580
|
+
*/
|
581
|
+
findMovedNodes(config) {
|
582
|
+
let {movedNodes=new Map(), oldVnodeMap, vnode, vnodeMap} = config,
|
583
|
+
id = vnode?.id;
|
584
|
+
|
585
|
+
if (id) {
|
586
|
+
let currentNode = oldVnodeMap.get(id)
|
587
|
+
|
588
|
+
if (currentNode) {
|
589
|
+
movedNodes.set(id, vnodeMap.get(id))
|
590
|
+
} else {
|
591
|
+
vnode.childNodes.forEach(childNode => {
|
592
|
+
if (childNode.vtype !== 'text') {
|
593
|
+
this.findMovedNodes({movedNodes, oldVnodeMap, vnode: childNode, vnodeMap})
|
594
|
+
}
|
595
|
+
})
|
596
|
+
}
|
597
|
+
}
|
598
|
+
|
599
|
+
return movedNodes
|
600
|
+
}
|
601
|
+
|
602
|
+
/**
|
603
|
+
* @param {Object} config
|
604
|
+
* @param {Object} config.deltas
|
605
|
+
* @param {Number} config.index
|
606
|
+
* @param {Map} config.oldVnodeMap
|
607
|
+
* @param {Neo.vdom.VNode} config.vnode
|
608
|
+
* @param {Map} config.vnodeMap
|
609
|
+
*/
|
610
|
+
insertNode(config) {
|
611
|
+
let {deltas, index, oldVnodeMap, vnode, vnodeMap} = config,
|
612
|
+
details = vnodeMap.get(vnode.id),
|
613
|
+
parentId = details.parentNode.id,
|
614
|
+
me = this,
|
615
|
+
movedNodes = me.findMovedNodes({oldVnodeMap, vnode, vnodeMap}),
|
616
|
+
outerHTML = me.createStringFromVnode(vnode, movedNodes);
|
617
|
+
|
618
|
+
deltas.default.push({action: 'insertNode', index, outerHTML, parentId});
|
619
|
+
|
620
|
+
// Insert the new node into the old tree, to simplify future OPs
|
621
|
+
oldVnodeMap.get(parentId).vnode.childNodes.splice(index, 0, vnode);
|
622
|
+
|
623
|
+
movedNodes.forEach(details => {
|
624
|
+
let {id} = details,
|
625
|
+
parentId = details.parentNode.id;
|
626
|
+
|
627
|
+
deltas.default.push({action: 'moveNode', id, index: details.index, parentId});
|
628
|
+
|
629
|
+
me.createDeltas({deltas, oldVnode: oldVnodeMap.get(id).vnode, oldVnodeMap, vnode: details.vnode, vnodeMap})
|
630
|
+
})
|
631
|
+
}
|
632
|
+
|
633
|
+
/**
|
634
|
+
* @param {Object} config
|
635
|
+
* @param {Object} config.deltas
|
636
|
+
* @param {Number} config.insertDelta
|
637
|
+
* @param {Map} config.oldVnodeMap
|
638
|
+
* @param {Neo.vdom.VNode} config.vnode
|
639
|
+
* @param {Map} config.vnodeMap
|
640
|
+
*/
|
641
|
+
moveNode(config) {
|
642
|
+
let {deltas, insertDelta, oldVnodeMap, vnode, vnodeMap} = config,
|
643
|
+
details = vnodeMap.get(vnode.id),
|
644
|
+
{index, parentNode} = details,
|
645
|
+
parentId = parentNode.id,
|
646
|
+
movedNode = oldVnodeMap.get(vnode.id),
|
647
|
+
movedParentNode = movedNode.parentNode,
|
648
|
+
{childNodes} = movedParentNode;
|
649
|
+
|
650
|
+
if (parentId !== movedParentNode.id) {
|
651
|
+
// We need to remove the node from the old parent childNodes
|
652
|
+
// (which must not be the same as the node they got moved into)
|
653
|
+
NeoArray.remove(childNodes, movedNode.vnode);
|
654
|
+
|
655
|
+
let oldParentNode = oldVnodeMap.get(parentId);
|
656
|
+
|
657
|
+
if (oldParentNode) {
|
658
|
+
// If moved into a new parent node, update the reference inside the flat map
|
659
|
+
movedNode.parentNode = oldParentNode.vnode;
|
660
|
+
|
661
|
+
childNodes = movedNode.parentNode.childNodes
|
662
|
+
}
|
663
|
+
}
|
664
|
+
|
665
|
+
deltas.default.push({action: 'moveNode', id: vnode.id, index: index + insertDelta, parentId});
|
666
|
+
|
667
|
+
// Add the node into the old vnode tree to simplify future OPs.
|
668
|
+
// NeoArray.insert() will switch to move() in case the node already exists.
|
669
|
+
NeoArray.insert(childNodes, index, movedNode.vnode);
|
670
|
+
|
671
|
+
this.createDeltas({deltas, oldVnode: movedNode.vnode, oldVnodeMap, vnode, vnodeMap})
|
672
|
+
}
|
673
|
+
|
674
|
+
/**
|
675
|
+
* @param {Object} config
|
676
|
+
* @param {Object} config.deltas
|
677
|
+
* @param {Neo.vdom.VNode} config.oldVnode
|
678
|
+
* @param {Map} config.oldVnodeMap
|
679
|
+
*/
|
680
|
+
removeNode(config) {
|
681
|
+
let {deltas, oldVnode, oldVnodeMap} = config,
|
682
|
+
delta = {action: 'removeNode', id: oldVnode.id},
|
683
|
+
{parentNode} = oldVnodeMap.get(oldVnode.id);
|
684
|
+
|
685
|
+
if (oldVnode.vtype === 'text') {
|
686
|
+
delta.parentId = parentNode.id
|
687
|
+
}
|
688
|
+
|
689
|
+
deltas.remove.push(delta);
|
690
|
+
|
691
|
+
NeoArray.remove(parentNode.childNodes, oldVnode)
|
692
|
+
}
|
693
|
+
|
794
694
|
/**
|
795
695
|
* Creates a Neo.vdom.VNode tree for the given vdom template and compares the new vnode with the current one
|
796
696
|
* to calculate the vdom deltas.
|
@@ -800,19 +700,15 @@ class Helper extends Base {
|
|
800
700
|
* @returns {Object|Promise<Object>}
|
801
701
|
*/
|
802
702
|
update(opts) {
|
803
|
-
let me
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
returnObj = {
|
812
|
-
deltas,
|
813
|
-
updateVdom: true,
|
814
|
-
vnode : node
|
815
|
-
};
|
703
|
+
let me = this,
|
704
|
+
vnode = me.createVnode(opts.vdom),
|
705
|
+
deltas = me.createDeltas({oldVnode: opts.vnode, vnode});
|
706
|
+
|
707
|
+
// Trees to remove could contain nodes which we want to re-use (move),
|
708
|
+
// so we need to execute the removeNode OPs last.
|
709
|
+
deltas = deltas.default.concat(deltas.remove);
|
710
|
+
|
711
|
+
let returnObj = {deltas, updateVdom: true, vnode};
|
816
712
|
|
817
713
|
return Neo.config.useVdomWorker ? returnObj : Promise.resolve(returnObj)
|
818
714
|
}
|