neo.mjs 10.2.1 → 10.3.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/.github/CONCEPT.md +2 -4
- package/.github/GETTING_STARTED.md +72 -51
- package/.github/RELEASE_NOTES/v10.3.0.md +71 -0
- package/.github/RELEASE_NOTES/v10.3.1.md +14 -0
- package/.github/epic-string-based-templates.md +690 -0
- package/ServiceWorker.mjs +2 -2
- package/apps/covid/view/MainContainer.mjs +1 -1
- package/apps/covid/view/country/Table.mjs +1 -1
- package/apps/portal/index.html +1 -1
- package/apps/portal/view/home/FooterContainer.mjs +1 -1
- package/apps/portal/view/learn/ContentComponent.mjs +1 -1
- package/apps/realworld/api/Base.mjs +2 -2
- package/apps/sharedcovid/view/MainContainer.mjs +1 -1
- package/apps/sharedcovid/view/MainContainerController.mjs +1 -1
- package/buildScripts/buildAll.mjs +4 -0
- package/buildScripts/buildESModules.mjs +23 -75
- package/buildScripts/bundleParse5.mjs +27 -0
- package/buildScripts/util/astTemplateProcessor.mjs +210 -0
- package/buildScripts/util/templateBuildProcessor.mjs +331 -0
- package/buildScripts/webpack/development/webpack.config.appworker.mjs +11 -0
- package/buildScripts/webpack/loader/template-loader.mjs +21 -0
- package/buildScripts/webpack/production/webpack.config.appworker.mjs +11 -0
- package/examples/README.md +1 -1
- package/examples/component/wrapper/googleMaps/MarkerDialog.mjs +2 -2
- package/examples/form/field/email/MainContainer.mjs +0 -1
- package/examples/form/field/number/MainContainer.mjs +0 -1
- package/examples/form/field/picker/MainContainer.mjs +0 -1
- package/examples/form/field/time/MainContainer.mjs +0 -1
- package/examples/form/field/trigger/copyToClipboard/MainContainer.mjs +0 -1
- package/examples/form/field/url/MainContainer.mjs +0 -1
- package/examples/functional/nestedTemplateComponent/Component.mjs +100 -0
- package/examples/functional/nestedTemplateComponent/MainContainer.mjs +48 -0
- package/examples/functional/nestedTemplateComponent/app.mjs +6 -0
- package/examples/functional/nestedTemplateComponent/index.html +11 -0
- package/examples/functional/nestedTemplateComponent/neo-config.json +6 -0
- package/examples/functional/templateComponent/Component.mjs +61 -0
- package/examples/functional/templateComponent/MainContainer.mjs +48 -0
- package/examples/functional/templateComponent/app.mjs +6 -0
- package/examples/functional/templateComponent/index.html +11 -0
- package/examples/functional/templateComponent/neo-config.json +6 -0
- package/learn/gettingstarted/Setup.md +29 -12
- package/learn/guides/fundamentals/ApplicationBootstrap.md +2 -2
- package/learn/guides/fundamentals/InstanceLifecycle.md +5 -5
- package/learn/guides/uibuildingblocks/HtmlTemplates.md +191 -0
- package/learn/guides/uibuildingblocks/HtmlTemplatesUnderTheHood.md +156 -0
- package/learn/guides/uibuildingblocks/WorkingWithVDom.md +1 -1
- package/learn/tree.json +2 -0
- package/package.json +61 -56
- package/src/DefaultConfig.mjs +3 -3
- package/src/calendar/view/calendars/List.mjs +1 -1
- package/src/calendar/view/month/Component.mjs +1 -1
- package/src/calendar/view/week/Component.mjs +1 -1
- package/src/component/Abstract.mjs +1 -1
- package/src/component/Base.mjs +33 -27
- package/src/container/Base.mjs +5 -5
- package/src/controller/Application.mjs +5 -5
- package/src/dialog/Base.mjs +6 -6
- package/src/draggable/DragProxyComponent.mjs +4 -4
- package/src/form/field/ComboBox.mjs +1 -1
- package/src/functional/_export.mjs +2 -1
- package/src/functional/component/Base.mjs +142 -93
- package/src/functional/util/HtmlTemplateProcessor.mjs +243 -0
- package/src/functional/util/html.mjs +24 -67
- package/src/list/Base.mjs +2 -2
- package/src/manager/Toast.mjs +1 -1
- package/src/menu/List.mjs +1 -1
- package/src/mixin/VdomLifecycle.mjs +87 -90
- package/src/tab/Container.mjs +2 -2
- package/src/tooltip/Base.mjs +1 -1
- package/src/tree/Accordion.mjs +2 -2
- package/src/worker/App.mjs +7 -7
- package/test/components/files/component/Base.mjs +1 -1
- package/test/siesta/siesta.js +2 -0
- package/test/siesta/tests/classic/Button.mjs +5 -5
- package/test/siesta/tests/functional/Button.mjs +6 -6
- package/test/siesta/tests/functional/HtmlTemplateComponent.mjs +193 -33
- package/test/siesta/tests/functional/Parse5Processor.mjs +82 -0
- package/test/siesta/tests/vdom/VdomRealWorldUpdates.mjs +5 -5
- package/.github/epic-functional-components.md +0 -498
- package/.github/ticket-asymmetric-vdom-updates.md +0 -122
package/src/menu/List.mjs
CHANGED
@@ -21,20 +21,17 @@ class VdomLifecycle extends Base {
|
|
21
21
|
*/
|
22
22
|
className: 'Neo.mixin.VdomLifecycle',
|
23
23
|
/**
|
24
|
-
* True automatically
|
24
|
+
* True automatically initializes the vnode of a component after being created inside the init call.
|
25
25
|
* Use this for the top level component of your app.
|
26
|
-
* @member {Boolean}
|
27
|
-
* @tutorial 02_ClassSystem
|
26
|
+
* @member {Boolean} autoInitVnode=false
|
28
27
|
*/
|
29
|
-
|
28
|
+
autoInitVnode: false,
|
30
29
|
/**
|
31
|
-
* True automatically
|
30
|
+
* True automatically mounts a component after being rendered.
|
32
31
|
* Use this for the top level component of your app.
|
33
|
-
* @member {Boolean}
|
34
|
-
* @see {@link Neo.component.Base#init init}
|
35
|
-
* @tutorial 02_ClassSystem
|
32
|
+
* @member {Boolean} autoMount=false
|
36
33
|
*/
|
37
|
-
|
34
|
+
autoMount: false,
|
38
35
|
/**
|
39
36
|
* Internal flag for vdom changes after a component got unmounted
|
40
37
|
* (delta updates can no longer get applied & a new render call is required before re-mounting)
|
@@ -50,6 +47,13 @@ class VdomLifecycle extends Base {
|
|
50
47
|
* @reactive
|
51
48
|
*/
|
52
49
|
isVdomUpdating_: false,
|
50
|
+
/**
|
51
|
+
* True in case the component is initializing the vnode
|
52
|
+
* @member {Boolean} isVnodeInitializing_=false
|
53
|
+
* @protected
|
54
|
+
* @reactive
|
55
|
+
*/
|
56
|
+
isVnodeInitializing_: false,
|
53
57
|
/**
|
54
58
|
* True in case the component is mounted to the DOM
|
55
59
|
* @member {Boolean} mounted_=false
|
@@ -64,13 +68,6 @@ class VdomLifecycle extends Base {
|
|
64
68
|
* @reactive
|
65
69
|
*/
|
66
70
|
needsVdomUpdate_: false,
|
67
|
-
/**
|
68
|
-
* True in case the component is rendering the vnode
|
69
|
-
* @member {Boolean} rendering_=false
|
70
|
-
* @protected
|
71
|
-
* @reactive
|
72
|
-
*/
|
73
|
-
rendering_: false,
|
74
71
|
/**
|
75
72
|
* Set this to true for bulk updates. Ensure to set it back to false afterwards.
|
76
73
|
* Internally the value will get saved as a number to ensure that child methods won't stop the silent mode too early.
|
@@ -89,7 +86,7 @@ class VdomLifecycle extends Base {
|
|
89
86
|
*/
|
90
87
|
updateDepth_: 1,
|
91
88
|
/**
|
92
|
-
* The component vnode tree. Available after the component got
|
89
|
+
* The component vnode tree. Available after the component got vnodeInitialized.
|
93
90
|
* @member {Object} vnode_=={[isDescriptor]: true, value: null, isEqual: (a, b) => a === b,}
|
94
91
|
* @protected
|
95
92
|
* @reactive
|
@@ -312,6 +309,64 @@ class VdomLifecycle extends Base {
|
|
312
309
|
return updateDepth === -1 ? true : distance < updateDepth
|
313
310
|
}
|
314
311
|
|
312
|
+
/**
|
313
|
+
* Creates the vnode tree for this component and mounts the component in case
|
314
|
+
* - you pass true for the mount param
|
315
|
+
* - or the autoMount config is set to true
|
316
|
+
* @param {Boolean} [mount] Mount the DOM after the vnode got created
|
317
|
+
* @returns {Promise<any>} If getting there, we return the data from vdom.Helper: create(), containing the vnode.
|
318
|
+
*/
|
319
|
+
async initVnode(mount) {
|
320
|
+
let me = this,
|
321
|
+
autoMount = mount || me.autoMount,
|
322
|
+
{app} = me,
|
323
|
+
{allowVdomUpdatesInTests, unitTestMode, useVdomWorker} = Neo.config;
|
324
|
+
|
325
|
+
if (unitTestMode && !allowVdomUpdatesInTests) return;
|
326
|
+
|
327
|
+
// Verify that the critical rendering path => CSS files for the new tree is in place
|
328
|
+
if (!unitTestMode && autoMount && currentWorker.countLoadingThemeFiles !== 0) {
|
329
|
+
currentWorker.on('themeFilesLoaded', function() {
|
330
|
+
!me.mounted && me.initVnode(mount)
|
331
|
+
}, me, {once: true});
|
332
|
+
|
333
|
+
return
|
334
|
+
}
|
335
|
+
|
336
|
+
me.isVnodeInitializing = true;
|
337
|
+
|
338
|
+
if (!app.vnodeInitialized) {
|
339
|
+
app.isVnodeInitializing = true
|
340
|
+
}
|
341
|
+
|
342
|
+
if (me.vdom) {
|
343
|
+
me.isVdomUpdating = true;
|
344
|
+
|
345
|
+
delete me.vdom.removeDom;
|
346
|
+
|
347
|
+
me._needsVdomUpdate = false;
|
348
|
+
me.afterSetNeedsVdomUpdate?.(false, true);
|
349
|
+
|
350
|
+
const data = await Promise.resolve(Neo.vdom.Helper.create({
|
351
|
+
appName : me.appName,
|
352
|
+
autoMount,
|
353
|
+
parentId : autoMount ? me.getMountedParentId() : undefined,
|
354
|
+
parentIndex: autoMount ? me.getMountedParentIndex() : undefined,
|
355
|
+
vdom : TreeBuilder.getVdomTree(me.vdom, -1),
|
356
|
+
windowId : me.windowId
|
357
|
+
}));
|
358
|
+
|
359
|
+
me.onInitVnode(data.vnode, useVdomWorker ? autoMount : false);
|
360
|
+
me.isVdomUpdating = false;
|
361
|
+
|
362
|
+
autoMount && !useVdomWorker && me.mount();
|
363
|
+
|
364
|
+
me.resolveVdomUpdate();
|
365
|
+
|
366
|
+
return data
|
367
|
+
}
|
368
|
+
}
|
369
|
+
|
315
370
|
/**
|
316
371
|
* Checks for vdom updates inside the parent chain and if found.
|
317
372
|
* Registers the component for a vdom update once done.
|
@@ -377,23 +432,23 @@ class VdomLifecycle extends Base {
|
|
377
432
|
}
|
378
433
|
|
379
434
|
/**
|
380
|
-
* Gets called from the
|
435
|
+
* Gets called from the initVnode() promise success handler
|
381
436
|
* @param {Object} vnode
|
382
437
|
* @param {Boolean} autoMount Mount the DOM after the vnode got created
|
383
438
|
* @protected
|
384
439
|
*/
|
385
|
-
|
440
|
+
onInitVnode(vnode, autoMount) {
|
386
441
|
let me = this,
|
387
442
|
{app} = me;
|
388
443
|
|
389
|
-
me.
|
444
|
+
me.isVnodeInitializing = false;
|
390
445
|
|
391
|
-
// if app is a check to see if the Component got destroyed while
|
446
|
+
// if app is a check to see if the Component got destroyed while vnodeInitialising => before onInitVnode got triggered
|
392
447
|
if (app) {
|
393
|
-
if (!app.
|
394
|
-
app.
|
395
|
-
app.
|
396
|
-
app.fire('
|
448
|
+
if (!app.vnodeInitialized) {
|
449
|
+
app.isVnodeInitializing = false;
|
450
|
+
app.vnodeInitialized = true;
|
451
|
+
app.fire('vnodeInitialized')
|
397
452
|
}
|
398
453
|
|
399
454
|
me.vnode = vnode;
|
@@ -407,12 +462,12 @@ class VdomLifecycle extends Base {
|
|
407
462
|
child = Neo.getComponent(childIds[i]);
|
408
463
|
|
409
464
|
if (child) {
|
410
|
-
child.
|
465
|
+
child.vnodeInitialized = true
|
411
466
|
}
|
412
467
|
}
|
413
468
|
|
414
|
-
me.
|
415
|
-
me.fire('
|
469
|
+
me._vnodeInitialized = true; // silent update
|
470
|
+
me.fire('vnodeInitialized', me.id);
|
416
471
|
|
417
472
|
if (autoMount) {
|
418
473
|
me.mounted = true;
|
@@ -435,64 +490,6 @@ class VdomLifecycle extends Base {
|
|
435
490
|
})
|
436
491
|
}
|
437
492
|
|
438
|
-
/**
|
439
|
-
* Creates the vnode tree for this component and mounts the component in case
|
440
|
-
* - you pass true for the mount param
|
441
|
-
* - or the autoMount config is set to true
|
442
|
-
* @param {Boolean} [mount] Mount the DOM after the vnode got created
|
443
|
-
* @returns {Promise<any>} If getting there, we return the data from vdom.Helper: create(), containing the vnode.
|
444
|
-
*/
|
445
|
-
async render(mount) {
|
446
|
-
let me = this,
|
447
|
-
autoMount = mount || me.autoMount,
|
448
|
-
{app} = me,
|
449
|
-
{allowVdomUpdatesInTests, unitTestMode, useVdomWorker} = Neo.config;
|
450
|
-
|
451
|
-
if (unitTestMode && !allowVdomUpdatesInTests) return;
|
452
|
-
|
453
|
-
// Verify that the critical rendering path => CSS files for the new tree is in place
|
454
|
-
if (!unitTestMode && autoMount && currentWorker.countLoadingThemeFiles !== 0) {
|
455
|
-
currentWorker.on('themeFilesLoaded', function() {
|
456
|
-
!me.mounted && me.render(mount)
|
457
|
-
}, me, {once: true});
|
458
|
-
|
459
|
-
return
|
460
|
-
}
|
461
|
-
|
462
|
-
me.rendering = true;
|
463
|
-
|
464
|
-
if (!app.rendered) {
|
465
|
-
app.rendering = true
|
466
|
-
}
|
467
|
-
|
468
|
-
if (me.vdom) {
|
469
|
-
me.isVdomUpdating = true;
|
470
|
-
|
471
|
-
delete me.vdom.removeDom;
|
472
|
-
|
473
|
-
me._needsVdomUpdate = false;
|
474
|
-
me.afterSetNeedsVdomUpdate?.(false, true);
|
475
|
-
|
476
|
-
const data = await Promise.resolve(Neo.vdom.Helper.create({
|
477
|
-
appName : me.appName,
|
478
|
-
autoMount,
|
479
|
-
parentId : autoMount ? me.getMountedParentId() : undefined,
|
480
|
-
parentIndex: autoMount ? me.getMountedParentIndex() : undefined,
|
481
|
-
vdom : TreeBuilder.getVdomTree(me.vdom, -1),
|
482
|
-
windowId : me.windowId
|
483
|
-
}));
|
484
|
-
|
485
|
-
me.onRender(data.vnode, useVdomWorker ? autoMount : false);
|
486
|
-
me.isVdomUpdating = false;
|
487
|
-
|
488
|
-
autoMount && !useVdomWorker && me.mount();
|
489
|
-
|
490
|
-
me.resolveVdomUpdate();
|
491
|
-
|
492
|
-
return data
|
493
|
-
}
|
494
|
-
}
|
495
|
-
|
496
493
|
/**
|
497
494
|
* Internal helper fn to resolve the Promise for updateVdom()
|
498
495
|
* @param {Object} [data] The return value of vdom.Helper.update()
|
@@ -529,7 +526,7 @@ class VdomLifecycle extends Base {
|
|
529
526
|
/**
|
530
527
|
* In case a component receives a new vnode, we want to do:
|
531
528
|
* - sync the vdom ids
|
532
|
-
* - setting
|
529
|
+
* - setting vnodeInitialized to true for child components
|
533
530
|
* - updating the parent component to ensure that the vnode tree stays persistent
|
534
531
|
* @param {Neo.vdom.VNode} [vnode=this.vnode]
|
535
532
|
*/
|
@@ -571,9 +568,9 @@ class VdomLifecycle extends Base {
|
|
571
568
|
// silent update
|
572
569
|
component._vnode = ComponentManager.addVnodeComponentReferences(childVnode, component.id);
|
573
570
|
|
574
|
-
if (!component.
|
575
|
-
component.
|
576
|
-
component.fire('
|
571
|
+
if (!component.vnodeInitialized) {
|
572
|
+
component._vnodeInitialized = true;
|
573
|
+
component.fire('vnodeInitialized', component.id)
|
577
574
|
}
|
578
575
|
|
579
576
|
component.mounted = true
|
package/src/tab/Container.mjs
CHANGED
@@ -30,7 +30,7 @@ class Container extends BaseContainer {
|
|
30
30
|
*/
|
31
31
|
ntype: 'tab-container',
|
32
32
|
/**
|
33
|
-
* You can use null to not
|
33
|
+
* You can use null to not mount any items initially
|
34
34
|
* @member {Number|null} activeIndex_=0
|
35
35
|
* @reactive
|
36
36
|
*/
|
@@ -197,7 +197,7 @@ class Container extends BaseContainer {
|
|
197
197
|
NeoArray.add(cls, 'neo-' + value);
|
198
198
|
me.setSilent({cls});
|
199
199
|
|
200
|
-
if (me.
|
200
|
+
if (me.vnodeInitialized) {
|
201
201
|
me.layout.setSilent(me.getLayoutConfig());
|
202
202
|
me.getTabBar().setSilent({dock: value});
|
203
203
|
me.getTabStrip().setSilent({cls: ['neo-tab-strip', 'neo-dock-' + value]});
|
package/src/tooltip/Base.mjs
CHANGED
package/src/tree/Accordion.mjs
CHANGED
@@ -130,7 +130,7 @@ class AccordionTree extends TreeList {
|
|
130
130
|
|
131
131
|
me[!value ? 'addCls' : 'removeCls']('root-not-collapsible');
|
132
132
|
|
133
|
-
if (me.
|
133
|
+
if (me.vnodeInitialized && value === false) {
|
134
134
|
let {store} = me;
|
135
135
|
|
136
136
|
store.items.forEach(record => {
|
@@ -391,7 +391,7 @@ class AccordionTree extends TreeList {
|
|
391
391
|
|
392
392
|
me.clear(false);
|
393
393
|
|
394
|
-
if (!me.mounted && me.
|
394
|
+
if (!me.mounted && me.isVnodeInitializing) {
|
395
395
|
me.on('mounted', () => {
|
396
396
|
me.createItems(null, me.getListItemsRoot(), 0);
|
397
397
|
me.update()
|
package/src/worker/App.mjs
CHANGED
@@ -97,13 +97,13 @@ class App extends Base {
|
|
97
97
|
* Remote method to use inside main threads for creating neo based class instances.
|
98
98
|
* Be aware that you can only pass configs which can get converted into pure JSON.
|
99
99
|
*
|
100
|
-
*
|
100
|
+
* Mounting a component into the document.body
|
101
101
|
* @example:
|
102
102
|
* Neo.worker.App.createNeoInstance({
|
103
|
-
* ntype
|
104
|
-
*
|
105
|
-
*
|
106
|
-
* text
|
103
|
+
* ntype : 'button',
|
104
|
+
* autoInitVnode: true,
|
105
|
+
* autoMount : true,
|
106
|
+
* text : 'Hi Nige!'
|
107
107
|
* }).then(id => console.log(id))
|
108
108
|
*
|
109
109
|
* Inserting a component into a container
|
@@ -150,8 +150,8 @@ class App extends Base {
|
|
150
150
|
}
|
151
151
|
} else {
|
152
152
|
// default parentId='document.body' => we want it to get shown
|
153
|
-
config.
|
154
|
-
config.
|
153
|
+
config.autoInitVnode = true;
|
154
|
+
config.autoMount = true;
|
155
155
|
|
156
156
|
instance = Neo[config.ntype ? 'ntype' : 'create'](config)
|
157
157
|
}
|
@@ -65,7 +65,7 @@ StartTest(t => {
|
|
65
65
|
t.is(buttonId, 'neo-button-1');
|
66
66
|
|
67
67
|
await t.waitForSelector('.neo-button');
|
68
|
-
t.diag('Button got
|
68
|
+
t.diag('Button got vnodeInitialized.');
|
69
69
|
|
70
70
|
t.diag('Child update before parent update');
|
71
71
|
Neo.worker.App.setConfigs({id: buttonId, text : 'world'});
|
package/test/siesta/siesta.js
CHANGED
@@ -15,10 +15,10 @@ Neo.config.useDomApiRenderer = true;
|
|
15
15
|
const appName = 'ClassicButtonTest';
|
16
16
|
Neo.apps = Neo.apps || {};
|
17
17
|
Neo.apps[appName] = {
|
18
|
-
name
|
19
|
-
fire
|
20
|
-
isMounted: () => true,
|
21
|
-
|
18
|
+
name : appName,
|
19
|
+
fire : Neo.emptyFn,
|
20
|
+
isMounted : () => true,
|
21
|
+
vnodeInitialising: false
|
22
22
|
};
|
23
23
|
|
24
24
|
StartTest(t => {
|
@@ -35,7 +35,7 @@ StartTest(t => {
|
|
35
35
|
text : 'Click me'
|
36
36
|
});
|
37
37
|
|
38
|
-
({vnode} = await button.
|
38
|
+
({vnode} = await button.initVnode());
|
39
39
|
button.mounted = true; // Manually mount to enable updates in the test env
|
40
40
|
});
|
41
41
|
|
@@ -15,10 +15,10 @@ Neo.config.useDomApiRenderer = true;
|
|
15
15
|
const appName = 'FunctionalButtonTest';
|
16
16
|
Neo.apps = Neo.apps || {};
|
17
17
|
Neo.apps[appName] = {
|
18
|
-
name
|
19
|
-
fire
|
20
|
-
isMounted: () => true,
|
21
|
-
|
18
|
+
name : appName,
|
19
|
+
fire : Neo.emptyFn,
|
20
|
+
isMounted : () => true,
|
21
|
+
vnodeInitialising: false
|
22
22
|
};
|
23
23
|
|
24
24
|
StartTest(t => {
|
@@ -35,7 +35,7 @@ StartTest(t => {
|
|
35
35
|
text : 'Click me'
|
36
36
|
});
|
37
37
|
|
38
|
-
({vnode} = await button.
|
38
|
+
({vnode} = await button.initVnode());
|
39
39
|
button.mounted = true; // Manually mount to enable updates in the test env
|
40
40
|
});
|
41
41
|
|
@@ -110,4 +110,4 @@ StartTest(t => {
|
|
110
110
|
t.isDeeply(delta.cls.remove, ['pressed'], 'Delta should remove "pressed" class');
|
111
111
|
t.notOk(updateData.vnode.className.includes('pressed'), 'Vnode should not have "pressed" class');
|
112
112
|
});
|
113
|
-
});
|
113
|
+
});
|