neo.mjs 10.0.0-alpha.3 → 10.0.0-alpha.5
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/CODING_GUIDELINES.md +1 -1
- package/README.md +52 -11
- package/ServiceWorker.mjs +2 -2
- package/apps/portal/index.html +1 -1
- package/apps/portal/view/blog/List.mjs +1 -1
- package/apps/portal/view/home/FooterContainer.mjs +1 -1
- package/apps/portal/view/learn/ContentComponent.mjs +2 -1
- package/apps/portal/view/learn/MainContainerStateProvider.mjs +3 -6
- package/apps/realworld/view/HomeComponent.mjs +1 -1
- package/apps/realworld/view/user/ProfileComponent.mjs +1 -1
- package/apps/sharedcovid/view/MainContainerController.mjs +1 -1
- package/apps/shareddialog/view/MainContainerController.mjs +2 -2
- package/buildScripts/buildThemes.mjs +1 -1
- package/examples/grid/animatedRowSorting/Viewport.mjs +4 -4
- package/examples/grid/bigData/ControlsContainer.mjs +3 -3
- package/examples/grid/bigData/GridContainer.mjs +8 -8
- package/examples/grid/cellEditing/MainContainer.mjs +5 -5
- package/examples/grid/container/MainContainer.mjs +4 -4
- package/examples/grid/nestedRecordFields/Viewport.mjs +5 -5
- package/learn/README.md +83 -0
- package/learn/guides/ApplicationBootstrap.md +352 -0
- package/learn/guides/DeclarativeComponentTreesVsImperativeVdom.md +500 -0
- package/learn/guides/WorkingWithVDom.md +748 -0
- package/learn/tree.json +53 -0
- package/package.json +2 -2
- package/resources/scss/src/grid/{View.scss → Body.scss} +2 -2
- package/resources/scss/src/grid/VerticalScrollbar.scss +1 -1
- package/resources/scss/src/grid/plugin/AnimateRows.scss +1 -1
- package/resources/scss/src/grid/plugin/CellEditing.scss +1 -1
- package/resources/scss/theme-dark/grid/{View.scss → Body.scss} +1 -1
- package/resources/scss/theme-light/grid/{View.scss → Body.scss} +1 -1
- package/resources/scss/theme-neo-light/grid/{View.scss → Body.scss} +1 -1
- package/src/DefaultConfig.mjs +27 -14
- package/src/Main.mjs +1 -1
- package/src/Neo.mjs +16 -0
- package/src/button/Base.mjs +2 -2
- package/src/calendar/view/MainContainerStateProvider.mjs +1 -1
- package/src/grid/{View.mjs → Body.mjs} +17 -17
- package/src/grid/Container.mjs +58 -58
- package/src/grid/ScrollManager.mjs +56 -56
- package/src/grid/VerticalScrollbar.mjs +2 -2
- package/src/grid/_export.mjs +2 -2
- package/src/grid/column/AnimatedChange.mjs +5 -5
- package/src/grid/column/Base.mjs +1 -1
- package/src/grid/column/Component.mjs +6 -6
- package/src/grid/header/Button.mjs +1 -1
- package/src/grid/header/Toolbar.mjs +9 -9
- package/src/grid/plugin/AnimateRows.mjs +1 -2
- package/src/layout/Cube.mjs +2 -2
- package/src/main/DeltaUpdates.mjs +11 -10
- package/src/main/addon/Navigator.mjs +1 -1
- package/src/main/addon/WindowPosition.mjs +1 -1
- package/src/main/render/StringBasedRenderer.mjs +1 -1
- package/src/tab/header/Toolbar.mjs +1 -1
- package/src/table/header/Button.mjs +1 -1
- package/src/toolbar/Base.mjs +1 -1
- package/src/util/Style.mjs +2 -6
- package/src/util/VDom.mjs +1 -1
- package/src/util/VNode.mjs +1 -1
- package/src/vdom/Helper.mjs +96 -49
- package/src/vdom/VNode.mjs +38 -2
- package/src/worker/App.mjs +8 -19
- package/src/worker/Base.mjs +87 -5
- package/src/worker/Manager.mjs +90 -36
- package/resources/data/deck/learnneo/tree.json +0 -50
- package/resources/data/deck/whyneo.md +0 -80
- /package/{resources/data/deck/learnneo/pages → learn}/Glossary.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/UsingTheseTopics.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/benefits/ConfigSystem.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/benefits/Effort.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/benefits/Features.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/benefits/FormsEngine.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/benefits/FourEnvironments.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/benefits/Introduction.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/benefits/MultiWindow.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/benefits/OffTheMainThread.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/benefits/Quick.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/benefits/Speed.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/gettingstarted/ComponentModels.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/gettingstarted/Config.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/gettingstarted/DescribingTheUI.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/gettingstarted/Events.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/gettingstarted/Extending.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/gettingstarted/References.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/gettingstarted/Setup.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/gettingstarted/Workspaces.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/guides/ComponentsAndContainers.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/guides/CustomComponents.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/guides/Forms.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/guides/InstanceLifecycle.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/guides/Layouts.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/guides/MainThreadAddonExample.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/guides/MainThreadAddonIntro.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/guides/Mixins.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/guides/MultiWindow.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/guides/PortalApp.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/guides/StateProviders.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/guides/Tables.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/guides/events/CustomEvents.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/guides/events/DomEvents.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/javascript/ClassFeatures.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/javascript/Classes.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/javascript/NewNode.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/javascript/Overrides.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/javascript/Super.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/tutorials/Earthquakes.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/tutorials/RSP.md +0 -0
- /package/{resources/data/deck/learnneo/pages → learn}/tutorials/TodoList.md +0 -0
- /package/resources/data/{deck/learnneo/data/theBeatles.json → theBeatles.json} +0 -0
package/src/vdom/Helper.mjs
CHANGED
@@ -37,6 +37,26 @@ class Helper extends Base {
|
|
37
37
|
singleton: true
|
38
38
|
}
|
39
39
|
|
40
|
+
/**
|
41
|
+
* @param {Object} config
|
42
|
+
*/
|
43
|
+
construct(config) {
|
44
|
+
super.construct(config);
|
45
|
+
|
46
|
+
let me = this;
|
47
|
+
|
48
|
+
// Ensure Neo.currentWorker is defined before attaching listeners
|
49
|
+
Promise.resolve().then(async () => {
|
50
|
+
// Subscribe to global Neo.config changes for dynamic renderer switching.
|
51
|
+
Neo.currentWorker.on({
|
52
|
+
neoConfigChange: me.onNeoConfigChange,
|
53
|
+
scope : me
|
54
|
+
});
|
55
|
+
|
56
|
+
await me.importUtil()
|
57
|
+
})
|
58
|
+
}
|
59
|
+
|
40
60
|
/**
|
41
61
|
* @param {Object} config
|
42
62
|
* @param {Object} config.deltas
|
@@ -65,11 +85,11 @@ class Helper extends Base {
|
|
65
85
|
keys = Object.keys(vnode);
|
66
86
|
|
67
87
|
Object.keys(oldVnode).forEach(prop => {
|
68
|
-
if (!
|
88
|
+
if (!Object.hasOwn(vnode, prop)) {
|
69
89
|
keys.push(prop)
|
70
|
-
} else if (prop === 'attributes') { //
|
90
|
+
} else if (prop === 'attributes') { // Find removed attributes
|
71
91
|
Object.keys(oldVnode[prop]).forEach(attr => {
|
72
|
-
if (!vnode[prop]
|
92
|
+
if (!Object.hasOwn(vnode[prop], attr)) {
|
73
93
|
vnode[prop][attr] = null
|
74
94
|
}
|
75
95
|
})
|
@@ -85,13 +105,22 @@ class Helper extends Base {
|
|
85
105
|
attributes = {};
|
86
106
|
|
87
107
|
Object.entries(value).forEach(([key, value]) => {
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
108
|
+
const
|
109
|
+
oldValue = oldVnode.attributes[key],
|
110
|
+
hasOldValue = Object.hasOwn(oldVnode.attributes, key);
|
111
|
+
|
112
|
+
// If the attribute has an old value AND the value hasn't changed, skip.
|
113
|
+
if (hasOldValue && oldValue === value) {
|
114
|
+
return
|
115
|
+
}
|
116
|
+
|
117
|
+
// If the current value is null, or it's a non-string empty value (e.g., [], {}), skip.
|
118
|
+
// Note: An empty string ('') is a valid value and should NOT be skipped here.
|
119
|
+
if (value !== null && !Neo.isString(value) && Neo.isEmpty(value)) {
|
120
|
+
return
|
94
121
|
}
|
122
|
+
|
123
|
+
attributes[key] = value
|
95
124
|
});
|
96
125
|
|
97
126
|
if (Object.keys(attributes).length > 0) {
|
@@ -148,7 +177,7 @@ class Helper extends Base {
|
|
148
177
|
/**
|
149
178
|
* Creates a Neo.vdom.VNode tree for the given vdom template.
|
150
179
|
* The top level vnode contains the outerHTML as a string,
|
151
|
-
* in case Neo.config.
|
180
|
+
* in case Neo.config.useDomApiRenderer === false
|
152
181
|
* @param {Object} opts
|
153
182
|
* @param {String} opts.appName
|
154
183
|
* @param {Boolean} [opts.autoMount]
|
@@ -156,22 +185,24 @@ class Helper extends Base {
|
|
156
185
|
* @param {Number} opts.parentIndex
|
157
186
|
* @param {Object} opts.vdom
|
158
187
|
* @param {Number} opts.windowId
|
159
|
-
* @returns {
|
188
|
+
* @returns {Object}
|
160
189
|
*/
|
161
|
-
|
162
|
-
let me
|
190
|
+
create(opts) {
|
191
|
+
let me = this,
|
192
|
+
{util} = Neo.vdom,
|
163
193
|
returnValue, vnode;
|
164
194
|
|
165
|
-
await me.importDomApiVnodeCreator();
|
166
|
-
await me.importStringFromVnode();
|
167
|
-
|
168
195
|
vnode = me.createVnode(opts.vdom);
|
169
196
|
returnValue = {...opts, vnode};
|
170
197
|
|
171
198
|
delete returnValue.vdom;
|
172
199
|
|
173
|
-
if (NeoConfig.
|
174
|
-
|
200
|
+
if (!NeoConfig.useDomApiRenderer) {
|
201
|
+
if (!util.StringFromVnode) {
|
202
|
+
throw new Error('VDom Helper render utilities are not loaded yet!')
|
203
|
+
}
|
204
|
+
|
205
|
+
returnValue.outerHTML = util.StringFromVnode.create(vnode)
|
175
206
|
}
|
176
207
|
|
177
208
|
return returnValue
|
@@ -303,10 +334,13 @@ class Helper extends Base {
|
|
303
334
|
if (value !== undefined && value !== null && key !== 'flag' && key !== 'removeDom') {
|
304
335
|
let hasUnit, newValue, style;
|
305
336
|
|
306
|
-
switch(key) {
|
337
|
+
switch (key) {
|
307
338
|
case 'tag':
|
308
339
|
node.nodeName = value;
|
309
340
|
break
|
341
|
+
case 'cls':
|
342
|
+
node.className = value;
|
343
|
+
break
|
310
344
|
case 'html':
|
311
345
|
node.innerHTML = value.toString(); // support for numbers
|
312
346
|
break
|
@@ -333,13 +367,7 @@ class Helper extends Base {
|
|
333
367
|
|
334
368
|
node.childNodes = newValue;
|
335
369
|
break
|
336
|
-
|
337
|
-
if (value && !Array.isArray(value)) {
|
338
|
-
node.className = [value]
|
339
|
-
} else if (!(Array.isArray(value) && value.length < 1)) {
|
340
|
-
node.className = value
|
341
|
-
}
|
342
|
-
break
|
370
|
+
|
343
371
|
case 'data':
|
344
372
|
if (value && Neo.typeOf(value) === 'Object') {
|
345
373
|
Object.entries(value).forEach(([key, val]) => {
|
@@ -472,24 +500,23 @@ class Helper extends Base {
|
|
472
500
|
}
|
473
501
|
|
474
502
|
/**
|
475
|
-
*
|
503
|
+
* Imports either (if not already imported):
|
504
|
+
* `Neo.vdom.util.DomApiVnodeCreator` if Neo.config.useDomApiRenderer === true
|
505
|
+
* `Neo.vdom.util.StringFromVnode` if Neo.config.useDomApiRenderer === false
|
476
506
|
* @returns {Promise<void>}
|
477
507
|
* @protected
|
478
508
|
*/
|
479
|
-
async
|
480
|
-
|
481
|
-
await import('./util/DomApiVnodeCreator.mjs')
|
482
|
-
}
|
483
|
-
}
|
509
|
+
async importUtil() {
|
510
|
+
const {util} = Neo.vdom;
|
484
511
|
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
512
|
+
if (NeoConfig.useDomApiRenderer) {
|
513
|
+
if (!util?.DomApiVnodeCreator) {
|
514
|
+
await import('./util/DomApiVnodeCreator.mjs')
|
515
|
+
}
|
516
|
+
} else {
|
517
|
+
if (!util?.StringFromVnode) {
|
518
|
+
await import('./util/StringFromVnode.mjs')
|
519
|
+
}
|
493
520
|
}
|
494
521
|
}
|
495
522
|
|
@@ -514,12 +541,12 @@ class Helper extends Base {
|
|
514
541
|
|
515
542
|
Object.assign(delta, {hasLeadingTextChildren, index: physicalIndex});
|
516
543
|
|
517
|
-
if (NeoConfig.
|
518
|
-
// For string-based mounting, pass a string excluding moved nodes
|
519
|
-
delta.outerHTML = Neo.vdom.util.StringFromVnode.create(vnode, movedNodes)
|
520
|
-
} else {
|
544
|
+
if (NeoConfig.useDomApiRenderer) {
|
521
545
|
// For direct DOM API mounting, pass the pruned VNode tree
|
522
546
|
delta.vnode = Neo.vdom.util.DomApiVnodeCreator.create(vnode, movedNodes)
|
547
|
+
} else {
|
548
|
+
// For string-based mounting, pass a string excluding moved nodes
|
549
|
+
delta.outerHTML = Neo.vdom.util.StringFromVnode.create(vnode, movedNodes)
|
523
550
|
}
|
524
551
|
|
525
552
|
deltas.default.push(delta);
|
@@ -603,6 +630,18 @@ class Helper extends Base {
|
|
603
630
|
this.createDeltas({deltas, oldVnode: movedNode.vnode, oldVnodeMap, vnode, vnodeMap})
|
604
631
|
}
|
605
632
|
|
633
|
+
/**
|
634
|
+
* Handler for global Neo.config changes.
|
635
|
+
* If 'useDomApiRenderer' property changes, this method dynamically loads/clears the renderer utilities.
|
636
|
+
* @param {Object} config
|
637
|
+
* @return {Promise<void>}
|
638
|
+
*/
|
639
|
+
async onNeoConfigChange(config) {
|
640
|
+
if(Object.hasOwn(config, 'useDomApiRenderer')) {
|
641
|
+
await this.importUtil()
|
642
|
+
}
|
643
|
+
}
|
644
|
+
|
606
645
|
/**
|
607
646
|
* @param {Object} config
|
608
647
|
* @param {Object} config.deltas
|
@@ -633,14 +672,22 @@ class Helper extends Base {
|
|
633
672
|
* @param {Object} opts
|
634
673
|
* @param {Object} opts.vdom
|
635
674
|
* @param {Object} opts.vnode
|
636
|
-
* @returns {
|
675
|
+
* @returns {Object}
|
637
676
|
*/
|
638
|
-
|
639
|
-
let me
|
677
|
+
update(opts) {
|
678
|
+
let me = this,
|
679
|
+
{util} = Neo.vdom,
|
640
680
|
deltas, vnode;
|
641
681
|
|
642
|
-
|
643
|
-
|
682
|
+
if (NeoConfig.useDomApiRenderer) {
|
683
|
+
if (!util.DomApiVnodeCreator) {
|
684
|
+
throw new Error('Neo.vdom.Helper: DomApiVnodeCreator is not loaded yet for updates!')
|
685
|
+
}
|
686
|
+
} else {
|
687
|
+
if (!util.StringFromVnode) {
|
688
|
+
throw new Error('Neo.vdom.Helper: StringFromVnode is not loaded yet for updates!');
|
689
|
+
}
|
690
|
+
}
|
644
691
|
|
645
692
|
vnode = me.createVnode(opts.vdom);
|
646
693
|
deltas = me.createDeltas({oldVnode: opts.vnode, vnode});
|
package/src/vdom/VNode.mjs
CHANGED
@@ -76,7 +76,7 @@ class VNode {
|
|
76
76
|
} else {
|
77
77
|
Object.assign(me, {
|
78
78
|
attributes: config.attributes || {},
|
79
|
-
className : config.className
|
79
|
+
className : normalizeClassName(config.className),
|
80
80
|
nodeName : config.nodeName || 'div',
|
81
81
|
style : config.style
|
82
82
|
});
|
@@ -88,7 +88,7 @@ class VNode {
|
|
88
88
|
|
89
89
|
// We only apply textContent, in case it has content
|
90
90
|
else if (Object.hasOwn(config, 'textContent')) {
|
91
|
-
me.textContent = Neo.config.
|
91
|
+
me.textContent = Neo.config.useDomApiRenderer ? textContent : StringUtil.escapeHtml(textContent)
|
92
92
|
}
|
93
93
|
}
|
94
94
|
|
@@ -99,6 +99,42 @@ class VNode {
|
|
99
99
|
}
|
100
100
|
}
|
101
101
|
|
102
|
+
/**
|
103
|
+
* vdom cls definitions might contain spaces, especially when it comes to iconCls.
|
104
|
+
* @example: myVdom = {cls: ['my-button', 'fa fa-user']}
|
105
|
+
*
|
106
|
+
* On DOM level, classList.add() will throw, in case it gets an input containing a space.
|
107
|
+
*
|
108
|
+
* This is a module-scoped utility function, not a method of the VNode class.
|
109
|
+
* VNodes are transferred via structured cloning (e.g., in postMessage()), which strips methods.
|
110
|
+
* Keeping this logic separate from the VNode class itself ensures conceptual purity and a cleaner data model,
|
111
|
+
* as methods defined on the VNode instance would be lost during transfer anyway.
|
112
|
+
*
|
113
|
+
* @param {String|String[]} classNameInput
|
114
|
+
* @returns {String[]}
|
115
|
+
* @private
|
116
|
+
*/
|
117
|
+
function normalizeClassName(classNameInput) {
|
118
|
+
let normalizedClasses = [];
|
119
|
+
|
120
|
+
if (Neo.isString(classNameInput)) {
|
121
|
+
normalizedClasses = classNameInput.split(' ').filter(Boolean)
|
122
|
+
} else if (Array.isArray(classNameInput)) {
|
123
|
+
classNameInput.forEach(cls => {
|
124
|
+
if (Neo.isString(cls)) {
|
125
|
+
if (cls.includes(' ')) {
|
126
|
+
normalizedClasses.push(...cls.split(' ').filter(Boolean))
|
127
|
+
} else if (cls !== '') {
|
128
|
+
normalizedClasses.push(cls)
|
129
|
+
}
|
130
|
+
}
|
131
|
+
})
|
132
|
+
}
|
133
|
+
|
134
|
+
// Remove duplicates if necessary
|
135
|
+
return [...new Set(normalizedClasses)]
|
136
|
+
}
|
137
|
+
|
102
138
|
const ns = Neo.ns('Neo.vdom', true);
|
103
139
|
ns.VNode = VNode;
|
104
140
|
|
package/src/worker/App.mjs
CHANGED
@@ -35,7 +35,8 @@ class App extends Base {
|
|
35
35
|
'destroyNeoInstance',
|
36
36
|
'fireEvent',
|
37
37
|
'getConfigs',
|
38
|
-
'setConfigs'
|
38
|
+
'setConfigs',
|
39
|
+
'setGlobalConfig' // points to worker.Base: setGlobalConfig()
|
39
40
|
]
|
40
41
|
},
|
41
42
|
/**
|
@@ -45,11 +46,6 @@ class App extends Base {
|
|
45
46
|
singleton: true
|
46
47
|
}
|
47
48
|
|
48
|
-
/**
|
49
|
-
* @member {Object|null} data=null
|
50
|
-
* @protected
|
51
|
-
*/
|
52
|
-
data = null
|
53
49
|
/**
|
54
50
|
* @member {Boolean} isUsingStateProviders=false
|
55
51
|
* @protected
|
@@ -387,23 +383,16 @@ class App extends Base {
|
|
387
383
|
* @param {Object} data
|
388
384
|
*/
|
389
385
|
onLoadApplication(data) {
|
390
|
-
let me
|
391
|
-
{config}
|
392
|
-
|
393
|
-
|
394
|
-
if (data) {
|
395
|
-
me.data = data;
|
396
|
-
config.resourcesPath = data.resourcesPath
|
397
|
-
}
|
398
|
-
|
399
|
-
path = me.data.path;
|
386
|
+
let me = this,
|
387
|
+
{config} = Neo,
|
388
|
+
{appPath} = config;
|
400
389
|
|
401
390
|
if (config.environment !== 'development') {
|
402
|
-
|
391
|
+
appPath = appPath.startsWith('/') ? appPath.substring(1) : appPath
|
403
392
|
}
|
404
393
|
|
405
|
-
me.importApp(
|
406
|
-
|
394
|
+
me.importApp(appPath).then(module => {
|
395
|
+
module.onStart();
|
407
396
|
|
408
397
|
// short delay to ensure Component Controllers are ready
|
409
398
|
config.hash && me.timeout(5).then(() => {
|
package/src/worker/Base.mjs
CHANGED
@@ -71,8 +71,9 @@ class Worker extends Base {
|
|
71
71
|
gt.onmessage = me.onMessage.bind(me)
|
72
72
|
}
|
73
73
|
|
74
|
-
Neo.currentWorker
|
75
|
-
Neo.
|
74
|
+
Neo.currentWorker = me;
|
75
|
+
Neo.setGlobalConfig = me.setGlobalConfig.bind(me);
|
76
|
+
Neo.workerId = me.workerId
|
76
77
|
}
|
77
78
|
|
78
79
|
/**
|
@@ -220,10 +221,17 @@ class Worker extends Base {
|
|
220
221
|
}
|
221
222
|
|
222
223
|
/**
|
223
|
-
*
|
224
|
+
* Handles the initial registration of the `Neo.config` for this worker's realm.
|
225
|
+
* Triggered when receiving a worker message with `{action: 'registerNeoConfig'}` from the Main Thread's `Neo.worker.Manager`.
|
226
|
+
* This method is primarily responsible for setting the initial global `Neo.config` object in this worker's scope
|
227
|
+
* upon its creation. It also handles associating `windowId` with `MessagePort`s for Shared Workers.
|
228
|
+
*
|
229
|
+
* @param {Object} msg The incoming message object.
|
230
|
+
* @param {Object} msg.data The initial global Neo.config data object.
|
231
|
+
* @param {Number} msg.data.windowId The unique ID of the window/tab (relevant for SharedWorkers).
|
224
232
|
*/
|
225
233
|
onRegisterNeoConfig(msg) {
|
226
|
-
Neo.
|
234
|
+
Neo.ns('Neo.config', true);
|
227
235
|
|
228
236
|
let me = this,
|
229
237
|
{windowId} = msg.data,
|
@@ -236,7 +244,29 @@ class Worker extends Base {
|
|
236
244
|
}
|
237
245
|
}
|
238
246
|
|
239
|
-
|
247
|
+
Neo.merge(Neo.config, msg.data)
|
248
|
+
}
|
249
|
+
|
250
|
+
/**
|
251
|
+
* Handles runtime updates to the global `Neo.config` for this worker's realm.
|
252
|
+
* This method is triggered when receiving a worker message with `{action: 'setNeoConfig'}`
|
253
|
+
* from the Main Thread's `Neo.worker.Manager`. This message signifies a global config change
|
254
|
+
* that originated either from this worker's Main Thread or was broadcast from another
|
255
|
+
* connected browser window via a Shared Worker.
|
256
|
+
*
|
257
|
+
* It merges the incoming configuration changes into this worker's local `Neo.config`
|
258
|
+
* and fires a local `neoConfigChange` event, allowing other instances within this worker
|
259
|
+
* to react to the updated configuration.
|
260
|
+
*
|
261
|
+
* @param {Object} msg The destructured arguments from the message payload.
|
262
|
+
* @param {Object} msg.config The partial or full `Neo.config` object to merge.
|
263
|
+
*/
|
264
|
+
onSetNeoConfig({config}) {
|
265
|
+
let me = this;
|
266
|
+
|
267
|
+
Neo.merge(Neo.config, config);
|
268
|
+
|
269
|
+
me.fire('neoConfigChange', config)
|
240
270
|
}
|
241
271
|
|
242
272
|
/**
|
@@ -307,6 +337,58 @@ class Worker extends Base {
|
|
307
337
|
|
308
338
|
return message
|
309
339
|
}
|
340
|
+
|
341
|
+
/**
|
342
|
+
* Initiates a global Neo.config change from a worker's context.
|
343
|
+
* This method is exposed globally as `Neo.setGlobalConfig` within each worker realm.
|
344
|
+
*
|
345
|
+
* It orchestrates the propagation of the config change to the Main Thread
|
346
|
+
* and, if a Shared Worker is active, across all connected browser windows,
|
347
|
+
* ensuring a single, consistent Neo.config state everywhere.
|
348
|
+
*
|
349
|
+
* You can pass a partial config object to update specific keys.
|
350
|
+
* For nested objects, Neo.mjs performs a deep merge.
|
351
|
+
*
|
352
|
+
* @param {Object} config The partial or full Neo.config object with changes to apply.
|
353
|
+
*/
|
354
|
+
setGlobalConfig(config) {
|
355
|
+
const
|
356
|
+
me = this,
|
357
|
+
{Manager} = Neo.worker; // Remote access proxy object
|
358
|
+
|
359
|
+
// Apply the config change locally to this worker's Neo.config and
|
360
|
+
// trigger its local change events immediately. This ensures immediate
|
361
|
+
// feedback and an updated state for the worker that initiated the change.
|
362
|
+
me.onSetNeoConfig({config});
|
363
|
+
|
364
|
+
if (me.isSharedWorker) {
|
365
|
+
// This block executes when the calling worker instance is a Shared Worker.
|
366
|
+
// This happens if `Neo.config.useSharedWorkers` is true, meaning App, VDom,
|
367
|
+
// Data, Canvas, and Task workers are all SharedWorker instances.
|
368
|
+
// This Shared Worker (the one where setGlobalConfig was called) acts as the
|
369
|
+
// central point to inform all connected Main Threads (browser windows).
|
370
|
+
me.ports.forEach((port, index) => {
|
371
|
+
// Send the config change to each connected Main Thread.
|
372
|
+
// The `broadcast` flag is crucial here for the *receiving* Main Thread:
|
373
|
+
// - `broadcast: true` (for the first port/Main Thread in the list): This Main Thread
|
374
|
+
// will apply the config locally and is then responsible for propagating it to *all*
|
375
|
+
// its own associated Shared Workers connected to that Main Thread),
|
376
|
+
// **excluding the worker that originated this change**.
|
377
|
+
// - `broadcast: false` (for all other ports/Main Threads): These Main Threads
|
378
|
+
// will simply apply the config locally and stop. They are passive recipients
|
379
|
+
// of the broadcast, synchronizing their state without initiating further actions back.
|
380
|
+
// The `excludeOrigin` parameter ensures the originating worker doesn't receive a redundant broadcast.
|
381
|
+
Manager.setNeoConfig({broadcast: index < 1, config, excludeOrigin: me.workerId, windowId: port.windowId})
|
382
|
+
})
|
383
|
+
} else {
|
384
|
+
// This Dedicated Worker (the one where setGlobalConfig was called) informs
|
385
|
+
// its single, connected Main Thread. The Main Thread will then:
|
386
|
+
// 1. Apply the config locally.
|
387
|
+
// 2. Broadcast this change to *all* other Dedicated Workers connected to
|
388
|
+
// *that same Main Thread*, **excluding the sender worker itself**.
|
389
|
+
Manager.setNeoConfig({broadcast: true, config, excludeOrigin: me.workerId})
|
390
|
+
}
|
391
|
+
}
|
310
392
|
}
|
311
393
|
|
312
394
|
export default Neo.setupClass(Worker);
|