neo.mjs 6.18.3 → 6.19.0
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 +89 -8
- package/apps/portal/view/blog/Container.mjs +8 -8
- package/apps/portal/view/blog/List.mjs +6 -6
- package/apps/portal/view/home/MainContainer.mjs +3 -2
- 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/package.json +6 -6
- 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/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/component/Base.mjs +18 -1
- package/src/container/Base.mjs +3 -1
- package/src/dialog/Base.mjs +1 -2
- package/src/layout/Base.mjs +43 -6
- package/src/layout/Card.mjs +21 -59
- package/src/layout/Cube.mjs +428 -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/main/mixin/DeltaUpdates.mjs +16 -3
- package/src/util/Array.mjs +36 -0
- package/src/vdom/Helper.mjs +328 -445
- package/src/vdom/VNode.mjs +12 -1
- package/test/siesta/siesta.js +16 -1
- package/test/siesta/tests/VdomCalendar.mjs +2111 -37
- package/test/siesta/tests/VdomHelper.mjs +283 -47
- package/test/siesta/tests/vdom/Advanced.mjs +367 -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,414 +121,186 @@ 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
|
+
len = Math.max(childNodes.length, oldChildNodes.length),
|
261
|
+
childNode, nodeInNewTree, oldChildNode;
|
262
|
+
|
263
|
+
me.compareAttributes({deltas, oldVnode, vnode, vnodeMap});
|
264
|
+
|
265
|
+
if (childNodes.length === 0 && oldChildNodes.length > 1) {
|
266
|
+
deltas.remove.push({action: 'removeAll', parentId: vnodeId});
|
267
|
+
return deltas
|
268
|
+
}
|
378
269
|
|
379
|
-
|
270
|
+
for (; i < len; i++) {
|
271
|
+
childNode = childNodes[i];
|
272
|
+
oldChildNode = oldChildNodes[i + indexDelta];
|
380
273
|
|
381
|
-
|
274
|
+
if (!childNode && !oldChildNode) {
|
275
|
+
break
|
276
|
+
}
|
382
277
|
|
383
|
-
|
278
|
+
// Same node, continue recursively
|
279
|
+
if (childNode && childNode.id === oldChildNode?.id) {
|
280
|
+
me.createDeltas({deltas, oldVnode: oldChildNode, oldVnodeMap, vnode: childNode, vnodeMap});
|
281
|
+
continue
|
282
|
+
}
|
384
283
|
|
385
|
-
|
386
|
-
|
387
|
-
// todo: needs testing => index gaps > 1
|
388
|
-
indexDelta = newVnodeDetails.index - movedNode.index
|
389
|
-
}
|
390
|
-
}
|
284
|
+
if (oldChildNode) {
|
285
|
+
nodeInNewTree = vnodeMap.get(oldChildNode.id);
|
391
286
|
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
})
|
399
|
-
}
|
287
|
+
// Remove node, if no longer inside the new tree
|
288
|
+
if (!nodeInNewTree) {
|
289
|
+
me.removeNode({deltas, oldVnode: oldChildNode, oldVnodeMap});
|
290
|
+
i--;
|
291
|
+
continue
|
292
|
+
}
|
400
293
|
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
oldVnodeRoot,
|
407
|
-
parentId: movedNode.parentNode.id
|
408
|
-
});
|
409
|
-
|
410
|
-
return {
|
411
|
-
indexDelta: 0
|
412
|
-
}
|
294
|
+
// The old child node got moved into a different not processed array. It will get picked up there.
|
295
|
+
if (childNode && vnodeId !== nodeInNewTree.parentNode.id) {
|
296
|
+
i--;
|
297
|
+
indexDelta++;
|
298
|
+
continue
|
413
299
|
}
|
414
300
|
}
|
415
301
|
|
416
|
-
if (
|
417
|
-
|
418
|
-
deltas.push({
|
419
|
-
action : 'updateVtext',
|
420
|
-
id : newVnode.id,
|
421
|
-
parentId: VNodeUtil.findChildVnode(newVnodeRoot, newVnode.id).parentNode.id,
|
422
|
-
value : newVnode.innerHTML
|
423
|
-
})
|
424
|
-
} else {
|
425
|
-
keys = Object.keys(newVnode);
|
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
|
-
}
|
526
|
-
|
527
|
-
if (Object.keys(delta).length > 0) {
|
528
|
-
delta.id = newVnode.id;
|
529
|
-
deltas.push(delta)
|
530
|
-
}
|
531
|
-
})
|
532
|
-
}
|
302
|
+
if (childNode) {
|
303
|
+
me[oldVnodeMap.get(childNode.id) ? 'moveNode' : 'insertNode']({deltas, oldVnodeMap, vnode: childNode, vnodeMap})
|
533
304
|
}
|
534
305
|
}
|
535
306
|
|
@@ -590,28 +361,35 @@ class Helper extends Base {
|
|
590
361
|
}
|
591
362
|
|
592
363
|
/**
|
593
|
-
* @param {
|
364
|
+
* @param {Neo.vdom.VNode} vnode
|
365
|
+
* @param {Map} [movedNodes]
|
594
366
|
*/
|
595
|
-
createStringFromVnode(vnode) {
|
596
|
-
let me = this
|
367
|
+
createStringFromVnode(vnode, movedNodes) {
|
368
|
+
let me = this,
|
369
|
+
id = vnode?.id;
|
370
|
+
|
371
|
+
if (id && movedNodes?.get(id)) {
|
372
|
+
return ''
|
373
|
+
}
|
597
374
|
|
598
375
|
switch (vnode.vtype) {
|
599
376
|
case 'root':
|
600
|
-
return me.createStringFromVnode(vnode.childNodes[0])
|
377
|
+
return me.createStringFromVnode(vnode.childNodes[0], movedNodes)
|
601
378
|
case 'text':
|
602
379
|
return vnode.innerHTML === undefined ? '' : String(vnode.innerHTML)
|
603
380
|
case 'vnode':
|
604
|
-
return me.createOpenTag(vnode) + me.createTagContent(vnode) + me.createCloseTag(vnode)
|
381
|
+
return me.createOpenTag(vnode) + me.createTagContent(vnode, movedNodes) + me.createCloseTag(vnode)
|
605
382
|
default:
|
606
383
|
return ''
|
607
384
|
}
|
608
385
|
}
|
609
386
|
|
610
387
|
/**
|
611
|
-
* @param {
|
388
|
+
* @param {Neo.vdom.VNode} vnode
|
389
|
+
* @param {Map} [movedNodes]
|
612
390
|
* @protected
|
613
391
|
*/
|
614
|
-
createTagContent(vnode) {
|
392
|
+
createTagContent(vnode, movedNodes) {
|
615
393
|
if (vnode.innerHTML) {
|
616
394
|
return vnode.innerHTML
|
617
395
|
}
|
@@ -623,7 +401,7 @@ class Helper extends Base {
|
|
623
401
|
|
624
402
|
for (; i < len; i++) {
|
625
403
|
childNode = vnode.childNodes[i];
|
626
|
-
outerHTML = this.createStringFromVnode(childNode);
|
404
|
+
outerHTML = this.createStringFromVnode(childNode, movedNodes);
|
627
405
|
|
628
406
|
if (childNode.innerHTML !== outerHTML) {
|
629
407
|
if (this.returnChildNodeOuterHtml) {
|
@@ -637,53 +415,11 @@ class Helper extends Base {
|
|
637
415
|
return string
|
638
416
|
}
|
639
417
|
|
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
418
|
/**
|
683
419
|
* @param {Object} opts
|
684
420
|
* @returns {Object|Neo.vdom.VNode|null}
|
685
421
|
*/
|
686
|
-
|
422
|
+
createVnode(opts) {
|
687
423
|
if (opts.removeDom === true) {
|
688
424
|
return null
|
689
425
|
}
|
@@ -735,7 +471,7 @@ class Helper extends Base {
|
|
735
471
|
value.forEach(item => {
|
736
472
|
if (item.removeDom !== true) {
|
737
473
|
delete item.removeDom; // could be false
|
738
|
-
potentialNode = me.
|
474
|
+
potentialNode = me.createVnode(item);
|
739
475
|
|
740
476
|
if (potentialNode) { // don't add null values
|
741
477
|
newValue.push(potentialNode)
|
@@ -771,6 +507,9 @@ class Helper extends Base {
|
|
771
507
|
case 'id':
|
772
508
|
node.id = value;
|
773
509
|
break
|
510
|
+
case 'static':
|
511
|
+
node.static = value;
|
512
|
+
break
|
774
513
|
case 'style':
|
775
514
|
style = node.style;
|
776
515
|
if (Neo.isString(value)) {
|
@@ -791,6 +530,154 @@ class Helper extends Base {
|
|
791
530
|
return new VNode(node)
|
792
531
|
}
|
793
532
|
|
533
|
+
/**
|
534
|
+
* Creates a flap map of the tree, containing ids as keys and infos as values
|
535
|
+
* @param {Object} config
|
536
|
+
* @param {Neo.vdom.VNode} config.vnode
|
537
|
+
* @param {Neo.vdom.VNode} [config.parentNode=null]
|
538
|
+
* @param {Number} [config.index=0]
|
539
|
+
* @param {Map} [config.map=new Map()]
|
540
|
+
* @returns {Map}
|
541
|
+
* {String} id vnode.id (convenience shortcut)
|
542
|
+
* {Number} index
|
543
|
+
* {String} parentId
|
544
|
+
* {Neo.vdom.VNode} vnode
|
545
|
+
*/
|
546
|
+
createVnodeMap(config) {
|
547
|
+
let {vnode, parentNode=null, index=0, map=new Map()} = config,
|
548
|
+
id = vnode?.id;
|
549
|
+
|
550
|
+
map.set(id, {id, index, parentNode, vnode});
|
551
|
+
|
552
|
+
vnode?.childNodes?.forEach((childNode, index) => {
|
553
|
+
this.createVnodeMap({vnode: childNode, parentNode: vnode, index, map})
|
554
|
+
});
|
555
|
+
|
556
|
+
return map
|
557
|
+
}
|
558
|
+
|
559
|
+
/**
|
560
|
+
* The logic will parse the vnode (tree) to find existing items inside a given map.
|
561
|
+
* It will not search for further childNodes inside an already found vnode.
|
562
|
+
* @param {Object} config
|
563
|
+
* @param {Map} [config.movedNodes=new Map()]
|
564
|
+
* @param {Map} config.oldVnodeMap
|
565
|
+
* @param {Neo.vdom.VNode} config.vnode
|
566
|
+
* @param {Map} config.vnodeMap
|
567
|
+
* @returns {Map}
|
568
|
+
*/
|
569
|
+
findMovedNodes(config) {
|
570
|
+
let {movedNodes=new Map(), oldVnodeMap, vnode, vnodeMap} = config,
|
571
|
+
id = vnode?.id;
|
572
|
+
|
573
|
+
if (id) {
|
574
|
+
let currentNode = oldVnodeMap.get(id)
|
575
|
+
|
576
|
+
if (currentNode) {
|
577
|
+
movedNodes.set(id, vnodeMap.get(id))
|
578
|
+
} else {
|
579
|
+
vnode.childNodes.forEach(childNode => {
|
580
|
+
if (childNode.vtype !== 'text') {
|
581
|
+
this.findMovedNodes({movedNodes, oldVnodeMap, vnode: childNode, vnodeMap})
|
582
|
+
}
|
583
|
+
})
|
584
|
+
}
|
585
|
+
}
|
586
|
+
|
587
|
+
return movedNodes
|
588
|
+
}
|
589
|
+
|
590
|
+
/**
|
591
|
+
* @param {Object} config
|
592
|
+
* @param {Object} config.deltas
|
593
|
+
* @param {Map} config.oldVnodeMap
|
594
|
+
* @param {Neo.vdom.VNode} config.vnode
|
595
|
+
* @param {Map} config.vnodeMap
|
596
|
+
*/
|
597
|
+
insertNode(config) {
|
598
|
+
let {deltas, oldVnodeMap, vnode, vnodeMap} = config,
|
599
|
+
details = vnodeMap.get(vnode.id),
|
600
|
+
{index} = details,
|
601
|
+
parentId = details.parentNode.id,
|
602
|
+
me = this,
|
603
|
+
movedNodes = me.findMovedNodes({oldVnodeMap, vnode, vnodeMap}),
|
604
|
+
outerHTML = me.createStringFromVnode(vnode, movedNodes);
|
605
|
+
|
606
|
+
deltas.default.push({action: 'insertNode', index, outerHTML, parentId});
|
607
|
+
|
608
|
+
// Insert the new node into the old tree, to simplify future OPs
|
609
|
+
oldVnodeMap.get(parentId).vnode.childNodes.splice(index, 0, vnode);
|
610
|
+
|
611
|
+
movedNodes.forEach(details => {
|
612
|
+
let {id} = details,
|
613
|
+
parentId = details.parentNode.id;
|
614
|
+
|
615
|
+
deltas.default.push({action: 'moveNode', id, index: details.index, parentId});
|
616
|
+
|
617
|
+
me.createDeltas({deltas, oldVnode: oldVnodeMap.get(id).vnode, oldVnodeMap, vnode: details.vnode, vnodeMap})
|
618
|
+
})
|
619
|
+
}
|
620
|
+
|
621
|
+
/**
|
622
|
+
* @param {Object} config
|
623
|
+
* @param {Object} config.deltas
|
624
|
+
* @param {Map} config.oldVnodeMap
|
625
|
+
* @param {Neo.vdom.VNode} config.vnode
|
626
|
+
* @param {Map} config.vnodeMap
|
627
|
+
*/
|
628
|
+
moveNode(config) {
|
629
|
+
let {deltas, oldVnodeMap, vnode, vnodeMap} = config,
|
630
|
+
details = vnodeMap.get(vnode.id),
|
631
|
+
{index, parentNode} = details,
|
632
|
+
parentId = parentNode.id,
|
633
|
+
movedNode = oldVnodeMap.get(vnode.id),
|
634
|
+
movedParentNode = movedNode.parentNode,
|
635
|
+
{childNodes} = movedParentNode;
|
636
|
+
|
637
|
+
if (parentId !== movedParentNode.id) {
|
638
|
+
// We need to remove the node from the old parent childNodes
|
639
|
+
// (which must not be the same as the node they got moved into)
|
640
|
+
NeoArray.remove(childNodes, movedNode.vnode);
|
641
|
+
|
642
|
+
let oldParentNode = oldVnodeMap.get(parentId);
|
643
|
+
|
644
|
+
if (oldParentNode) {
|
645
|
+
// If moved into a new parent node, update the reference inside the flat map
|
646
|
+
movedNode.parentNode = oldParentNode.vnode;
|
647
|
+
|
648
|
+
childNodes = movedNode.parentNode.childNodes
|
649
|
+
}
|
650
|
+
}
|
651
|
+
|
652
|
+
deltas.default.push({action: 'moveNode', id: vnode.id, index, parentId});
|
653
|
+
|
654
|
+
// Add the node into the old vnode tree to simplify future OPs.
|
655
|
+
// NeoArray.insert() will switch to move() in case the node already exists.
|
656
|
+
NeoArray.insert(childNodes, index, movedNode.vnode);
|
657
|
+
|
658
|
+
this.createDeltas({deltas, oldVnode: movedNode.vnode, oldVnodeMap, vnode, vnodeMap})
|
659
|
+
}
|
660
|
+
|
661
|
+
/**
|
662
|
+
* @param {Object} config
|
663
|
+
* @param {Object} config.deltas
|
664
|
+
* @param {Neo.vdom.VNode} config.oldVnode
|
665
|
+
* @param {Map} config.oldVnodeMap
|
666
|
+
*/
|
667
|
+
removeNode(config) {
|
668
|
+
let {deltas, oldVnode, oldVnodeMap} = config,
|
669
|
+
delta = {action: 'removeNode', id: oldVnode.id},
|
670
|
+
{parentNode} = oldVnodeMap.get(oldVnode.id);
|
671
|
+
|
672
|
+
if (oldVnode.vtype === 'text') {
|
673
|
+
delta.parentId = parentNode.id
|
674
|
+
}
|
675
|
+
|
676
|
+
deltas.remove.push(delta);
|
677
|
+
|
678
|
+
NeoArray.remove(parentNode.childNodes, oldVnode)
|
679
|
+
}
|
680
|
+
|
794
681
|
/**
|
795
682
|
* Creates a Neo.vdom.VNode tree for the given vdom template and compares the new vnode with the current one
|
796
683
|
* to calculate the vdom deltas.
|
@@ -800,19 +687,15 @@ class Helper extends Base {
|
|
800
687
|
* @returns {Object|Promise<Object>}
|
801
688
|
*/
|
802
689
|
update(opts) {
|
803
|
-
let me
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
returnObj = {
|
812
|
-
deltas,
|
813
|
-
updateVdom: true,
|
814
|
-
vnode : node
|
815
|
-
};
|
690
|
+
let me = this,
|
691
|
+
vnode = me.createVnode(opts.vdom),
|
692
|
+
deltas = me.createDeltas({oldVnode: opts.vnode, vnode});
|
693
|
+
|
694
|
+
// Trees to remove could contain nodes which we want to re-use (move),
|
695
|
+
// so we need to execute the removeNode OPs last.
|
696
|
+
deltas = deltas.default.concat(deltas.remove);
|
697
|
+
|
698
|
+
let returnObj = {deltas, updateVdom: true, vnode};
|
816
699
|
|
817
700
|
return Neo.config.useVdomWorker ? returnObj : Promise.resolve(returnObj)
|
818
701
|
}
|