neo.mjs 10.0.0-alpha.3 → 10.0.0-alpha.4
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/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/learn/README.md +83 -0
- package/learn/guides/ApplicationBootstrap.md +354 -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/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/header/Button.mjs +1 -1
- 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/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 +2 -1
- package/src/worker/Base.mjs +87 -5
- package/src/worker/Manager.mjs +86 -28
- 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/learn/tree.json
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
{"data": [
|
2
|
+
{"name": "Using These Topics", "parentId": null, "id": "UsingTheseTopics" },
|
3
|
+
{"name": "Benefits", "parentId": null, "isLeaf": false, "id": "Benefits"},
|
4
|
+
{"name": "Introduction ", "parentId": "Benefits", "id": "benefits.Introduction"},
|
5
|
+
{"name": "Off the Main Thread", "parentId": "Benefits", "id": "benefits.OffTheMainThread"},
|
6
|
+
{"name": "4 Environments", "parentId": "Benefits", "id": "benefits.FourEnvironments"},
|
7
|
+
{"name": "Unified Config System", "parentId": "Benefits", "id": "benefits.ConfigSystem"},
|
8
|
+
{"name": "Extreme Speed", "parentId": "Benefits", "id": "benefits.Speed"},
|
9
|
+
{"name": "Multi-Window Applications", "parentId": "Benefits", "id": "benefits.MultiWindow"},
|
10
|
+
{"name": "Quick Application Development", "parentId": "Benefits", "id": "benefits.Quick"},
|
11
|
+
{"name": "Complexity and Effort", "parentId": "Benefits", "id": "benefits.Effort"},
|
12
|
+
{"name": "Forms Engine", "parentId": "Benefits", "id": "benefits.FormsEngine"},
|
13
|
+
{"name": "Features and Benefits Summary", "parentId": "Benefits", "id": "benefits.Features"},
|
14
|
+
{"name": "Getting Started", "parentId": null, "isLeaf": false, "id": "GettingStarted", "collapsed": true},
|
15
|
+
{"name": "Setup", "parentId": "GettingStarted", "id": "gettingstarted.Setup"},
|
16
|
+
{"name": "Workspaces and Applications", "parentId": "GettingStarted", "id": "gettingstarted.Workspaces"},
|
17
|
+
{"name": "Describing a View", "parentId": "GettingStarted", "id": "gettingstarted.DescribingTheUI"},
|
18
|
+
{"name": "Events", "parentId": "GettingStarted", "id": "gettingstarted.Events"},
|
19
|
+
{"name": "Component References", "parentId": "GettingStarted", "id": "gettingstarted.References"},
|
20
|
+
{"name": "Extending Classes", "parentId": "GettingStarted", "id": "gettingstarted.Extending"},
|
21
|
+
{"name": "Config", "parentId": "GettingStarted", "id": "gettingstarted.Config"},
|
22
|
+
{"name": "Shared Bindable Data", "parentId": "GettingStarted", "id": "gettingstarted.ComponentModels"},
|
23
|
+
{"name": "Guides", "parentId": null, "isLeaf": false, "id": "InDepth", "collapsed": true},
|
24
|
+
{"name": "Application Bootstrap", "parentId": "InDepth", "id": "guides.ApplicationBootstrap"},
|
25
|
+
{"name": "Declarative Component Trees VS Imperative Vdom", "parentId": "InDepth", "id": "guides.DeclarativeComponentTreesVsImperativeVdom"},
|
26
|
+
{"name": "Working with VDom", "parentId": "InDepth", "id": "guides.WorkingWithVDom"},
|
27
|
+
{"name": "Instance Lifecycle", "parentId": "InDepth", "id": "guides.InstanceLifecycle", "hidden": true},
|
28
|
+
{"name": "User Input (Forms)", "parentId": "InDepth", "id": "guides.Forms", "hidden": true},
|
29
|
+
{"name": "Component and Container Basics", "parentId": "InDepth", "id": "guides.ComponentsAndContainers"},
|
30
|
+
{"name": "Layouts", "parentId": "InDepth", "isLeaf": false, "id": "guides.Layouts", "hidden": true},
|
31
|
+
{"name": "Shared Bindable Data (State Providers)", "parentId": "InDepth", "id": "guides.StateProviders"},
|
32
|
+
{"name": "Custom Components", "parentId": "InDepth", "id": "guides.CustomComponents", "hidden": true},
|
33
|
+
{"name": "Events", "parentId": "InDepth", "isLeaf": false, "id": "GuideEvents"},
|
34
|
+
{"name": "Custom Events", "parentId": "GuideEvents", "id": "guides.events.CustomEvents"},
|
35
|
+
{"name": "DOM Events", "parentId": "GuideEvents", "id": "guides.events.DomEvents"},
|
36
|
+
{"name": "Portal App", "parentId": "InDepth", "id": "guides.PortalApp"},
|
37
|
+
{"name": "Tables (Stores)", "parentId": "InDepth", "id": "guides.Tables", "hidden": true},
|
38
|
+
{"name": "Multi-Window Applications", "parentId": "InDepth", "id": "guides.MultiWindow", "hidden": true},
|
39
|
+
{"name": "Main Thread Addons", "parentId": "InDepth", "isLeaf": false, "id": "MainThreadAddons", "hidden": true},
|
40
|
+
{"name": "Introduction", "parentId": "MainThreadAddons", "id": "guides.MainThreadAddonIntro"},
|
41
|
+
{"name": "Example", "parentId": "MainThreadAddons", "id": "guides.MainThreadAddonExample"},
|
42
|
+
{"name": "Mixins", "parentId": "InDepth", "id": "guides.Mixins", "hidden": true},
|
43
|
+
{"name": "Tutorials", "parentId": null, "isLeaf": false, "id": "Tutorials", "collapsed": true},
|
44
|
+
{"name": "Rock Scissors Paper", "parentId": "Tutorials", "id": "tutorials.RSP", "hidden": true},
|
45
|
+
{"name": "Earthquakes", "parentId": "Tutorials", "id": "tutorials.Earthquakes"},
|
46
|
+
{"name": "Todo List", "parentId": "Tutorials", "id": "tutorials.TodoList"},
|
47
|
+
{"name": "JavaScript Classes", "parentId": null, "isLeaf": false, "id": "JavaScript", "hidden": true},
|
48
|
+
{"name": "Classes, Properties, and Methods", "parentId": "JavaScript", "id": "javascript.Classes"},
|
49
|
+
{"name": "Overriding Methods", "parentId": "JavaScript", "id": "javascript.Overrides"},
|
50
|
+
{"name": "Other JavaScript Class Features", "parentId": "JavaScript", "id": "javascript.ClassFeatures"},
|
51
|
+
{"name": "Super", "parentId": "JavaScript", "id": "javascript.Super"},
|
52
|
+
{"name": "New Node", "parentId": "JavaScript", "id": "javascript.NewNode"}
|
53
|
+
]}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name" : "neo.mjs",
|
3
|
-
"version" : "10.0.0-alpha.
|
3
|
+
"version" : "10.0.0-alpha.4",
|
4
4
|
"description" : "Neo.mjs: The multi-threaded UI framework for building ultra-fast, desktop-like web applications with uncompromised responsiveness, inherent security, and a transpilation-free dev mode.",
|
5
5
|
"type" : "module",
|
6
6
|
"repository" : {
|
@@ -94,7 +94,7 @@
|
|
94
94
|
"postcss" : "^8.5.6",
|
95
95
|
"sass" : "^1.89.2",
|
96
96
|
"siesta-lite" : "5.5.2",
|
97
|
-
"terser" : "^5.43.
|
97
|
+
"terser" : "^5.43.1",
|
98
98
|
"url" : "^0.11.4",
|
99
99
|
"webpack" : "^5.99.9",
|
100
100
|
"webpack-cli" : "^6.0.1",
|
package/src/DefaultConfig.mjs
CHANGED
@@ -200,6 +200,31 @@ const DefaultConfig = {
|
|
200
200
|
* @type Boolean
|
201
201
|
*/
|
202
202
|
useCanvasWorker: false,
|
203
|
+
/**
|
204
|
+
* `true` will enable the advanced, secure, and performant direct DOM API rendering strategy (recommended).
|
205
|
+
* In this mode, `Neo.vdom.Helper` will create and send structured VNode object graphs to the Main Thread.
|
206
|
+
* `Neo.main.DeltaUpdates` will then use `Neo.main.render.DomApiRenderer` to directly manipulate the DOM.
|
207
|
+
* Crucially, `Neo.main.render.DomApiRenderer` builds new **DOM subtrees** (from the received VNode object graphs)
|
208
|
+
* as detached DocumentFragments or elements, entirely outside the live DOM tree.
|
209
|
+
* These fully constructed fragments are then inserted into the live document in a **single, atomic operation**.
|
210
|
+
* This approach inherently minimizes costly browser reflows/repaints, drastically reduces Cross-Site Scripting (XSS) risks,
|
211
|
+
* and optimizes for surgical, atomic DOM updates for unparalleled performance.
|
212
|
+
*
|
213
|
+
* `false` will enable the legacy string-based rendering strategy.
|
214
|
+
* In this mode, `Neo.vdom.Helper` will generate complete HTML strings (`outerHTML`) for VNode subtrees.
|
215
|
+
* `Neo.main.DeltaUpdates` will then use `Neo.main.render.StringBasedRenderer` to insert these
|
216
|
+
* strings into the DOM using methods like `parentNode.insertAdjacentHTML()`.
|
217
|
+
* While performant for large insertions, this mode is generally less secure due to potential XSS vectors
|
218
|
+
* and relies on browser HTML parsing, which can be less efficient for granular updates.
|
219
|
+
*
|
220
|
+
* This configuration affects both the initial painting of your applications and the creation
|
221
|
+
* of new component trees at runtime.
|
222
|
+
* @default true
|
223
|
+
* @memberOf! module:Neo
|
224
|
+
* @name config.useDomApiRenderer
|
225
|
+
* @type Boolean
|
226
|
+
*/
|
227
|
+
useDomApiRenderer: true,
|
203
228
|
/**
|
204
229
|
* Flag if vdom ids should get mapped into DOM element ids.
|
205
230
|
* false will convert them into a "data-neo-id" attribute.
|
@@ -246,18 +271,6 @@ const DefaultConfig = {
|
|
246
271
|
* @type Boolean
|
247
272
|
*/
|
248
273
|
useSharedWorkers: false,
|
249
|
-
/**
|
250
|
-
* `true` will let the `vdom.Helper` create a String-based representation of the vnode tree.
|
251
|
-
* Main will then use e.g.`parentNode.insertAdjacentHTML('beforeend', delta.outerHTML);`
|
252
|
-
* This affects the initial painting of your apps, but also the creation of new component trees at run-time.
|
253
|
-
* `false` will skip the creation of the String, and instead use DOM APIs to generate a fragment inside Main,
|
254
|
-
* into which the vnode tree will get applied.
|
255
|
-
* @default false
|
256
|
-
* @memberOf! module:Neo
|
257
|
-
* @name config.useStringBasedMounting
|
258
|
-
* @type Boolean
|
259
|
-
*/
|
260
|
-
useStringBasedMounting: false,
|
261
274
|
/**
|
262
275
|
* True will generate a new task worker, which can get filled with own expensive remote methods
|
263
276
|
* @default false
|
@@ -276,12 +289,12 @@ const DefaultConfig = {
|
|
276
289
|
useVdomWorker: true,
|
277
290
|
/**
|
278
291
|
* buildScripts/injectPackageVersion.mjs will update this value
|
279
|
-
* @default '10.0.0-alpha.
|
292
|
+
* @default '10.0.0-alpha.4'
|
280
293
|
* @memberOf! module:Neo
|
281
294
|
* @name config.version
|
282
295
|
* @type String
|
283
296
|
*/
|
284
|
-
version: '10.0.0-alpha.
|
297
|
+
version: '10.0.0-alpha.4'
|
285
298
|
};
|
286
299
|
|
287
300
|
Object.assign(DefaultConfig, {
|
package/src/Main.mjs
CHANGED
package/src/Neo.mjs
CHANGED
@@ -436,6 +436,22 @@ Neo = globalThis.Neo = Object.assign({
|
|
436
436
|
return Neo.create(className, config)
|
437
437
|
},
|
438
438
|
|
439
|
+
/**
|
440
|
+
* Updates the global Neo.config object across all active workers and connected browser windows.
|
441
|
+
*
|
442
|
+
* This is the unified entry point for changing global framework configurations.
|
443
|
+
* The framework automatically handles the complex multi-threaded and multi-window
|
444
|
+
* synchronization (via App Workers and Shared Workers, if active), ensuring
|
445
|
+
* consistency across the entire application without boilerplate.
|
446
|
+
*
|
447
|
+
* You can pass a partial config object to update specific keys.
|
448
|
+
* For nested objects, Neo.mjs performs a deep merge to preserve existing properties.
|
449
|
+
*
|
450
|
+
* @memberOf module:Neo
|
451
|
+
* @function setGlobalConfig
|
452
|
+
* @param {Object} config The partial or full Neo.config object with changes to apply.
|
453
|
+
*/
|
454
|
+
|
439
455
|
/**
|
440
456
|
* Internally used at the end of each class / module definition
|
441
457
|
* @memberOf module:Neo
|
package/src/button/Base.mjs
CHANGED
@@ -225,8 +225,8 @@ class Button extends Component {
|
|
225
225
|
afterSetBadgeText(value, oldValue) {
|
226
226
|
let {badgeNode} = this;
|
227
227
|
|
228
|
-
badgeNode.html = value;
|
229
228
|
badgeNode.removeDom = !Boolean(value);
|
229
|
+
badgeNode.text = value;
|
230
230
|
|
231
231
|
this.update()
|
232
232
|
}
|
@@ -380,7 +380,7 @@ class Button extends Component {
|
|
380
380
|
textNode.removeDom = isEmpty;
|
381
381
|
|
382
382
|
if (!isEmpty) {
|
383
|
-
textNode.
|
383
|
+
textNode.text = value
|
384
384
|
}
|
385
385
|
|
386
386
|
me.update()
|
package/src/layout/Cube.mjs
CHANGED
@@ -258,7 +258,7 @@ class Cube extends Card {
|
|
258
258
|
if (index < 6) {
|
259
259
|
wrapperCls = NeoArray.union(wrapperCls, 'neo-face', Object.keys(Cube.faces)[index]);
|
260
260
|
|
261
|
-
switch(index) {
|
261
|
+
switch (index) {
|
262
262
|
case 0:
|
263
263
|
case 1:
|
264
264
|
wrapperCls = NeoArray.union(wrapperCls, 'neo-face-z');
|
@@ -341,7 +341,7 @@ class Cube extends Card {
|
|
341
341
|
if (index < 6) {
|
342
342
|
NeoArray.remove(wrapperCls, ['neo-face', Object.keys(Cube.faces)[index]]);
|
343
343
|
|
344
|
-
switch(index) {
|
344
|
+
switch (index) {
|
345
345
|
case 0:
|
346
346
|
case 1:
|
347
347
|
NeoArray.remove(wrapperCls, 'neo-face-z');
|
@@ -54,7 +54,6 @@ class DeltaUpdates extends Base {
|
|
54
54
|
* @private
|
55
55
|
*/
|
56
56
|
#renderer = null
|
57
|
-
|
58
57
|
/**
|
59
58
|
* Private property to signal that the renderer module has been loaded.
|
60
59
|
* This will be a Promise that resolves when the module is ready.
|
@@ -87,10 +86,10 @@ class DeltaUpdates extends Base {
|
|
87
86
|
try {
|
88
87
|
let module;
|
89
88
|
|
90
|
-
if (NeoConfig.
|
91
|
-
module = await import('./render/StringBasedRenderer.mjs')
|
92
|
-
} else {
|
89
|
+
if (NeoConfig.useDomApiRenderer) {
|
93
90
|
module = await import('./render/DomApiRenderer.mjs')
|
91
|
+
} else {
|
92
|
+
module = await import('./render/StringBasedRenderer.mjs')
|
94
93
|
}
|
95
94
|
|
96
95
|
me.#renderer = module.default
|
@@ -175,7 +174,7 @@ class DeltaUpdates extends Base {
|
|
175
174
|
* - `insertAdjacentHTML()` is generally faster than creating a node via template,
|
176
175
|
* but it's only available for manipulating children (elements), not `childNodes` (all nodes).
|
177
176
|
* - For performance, in cases where there are no comment nodes (i.e., no wrapped text nodes),
|
178
|
-
* the method prioritizes `insertAdjacentHTML()` when `
|
177
|
+
* the method prioritizes `insertAdjacentHTML()` when `useDomApiRenderer` is false.
|
179
178
|
*
|
180
179
|
* @param {Object} delta
|
181
180
|
* @param {Boolean} delta.hasLeadingTextChildren Flag to honor leading comments, which require special treatment.
|
@@ -185,8 +184,10 @@ class DeltaUpdates extends Base {
|
|
185
184
|
* @param {Neo.vdom.VNode} [delta.vnode] The VNode representation of the new node (for direct DOM API mounting).
|
186
185
|
*/
|
187
186
|
insertNode({hasLeadingTextChildren, index, outerHTML, parentId, vnode}) {
|
187
|
+
let me = this;
|
188
|
+
|
188
189
|
// This method is synchronous and *expects* the renderer to be loaded
|
189
|
-
if (!
|
190
|
+
if (!me.#renderer) {
|
190
191
|
console.error('DeltaUpdates renderer not ready during insertNode!');
|
191
192
|
return
|
192
193
|
}
|
@@ -194,10 +195,10 @@ class DeltaUpdates extends Base {
|
|
194
195
|
const parentNode = DomAccess.getElementOrBody(parentId);
|
195
196
|
|
196
197
|
if (parentNode) {
|
197
|
-
if (
|
198
|
-
|
198
|
+
if (NeoConfig.useDomApiRenderer) {
|
199
|
+
me.#renderer.createDomTree({index, isRoot: true, parentNode, vnode})
|
199
200
|
} else {
|
200
|
-
|
201
|
+
me.#renderer.insertNodeAsString({hasLeadingTextChildren, index, outerHTML, parentNode})
|
201
202
|
}
|
202
203
|
}
|
203
204
|
}
|
@@ -331,7 +332,7 @@ class DeltaUpdates extends Base {
|
|
331
332
|
|
332
333
|
if (node) {
|
333
334
|
Object.entries(delta).forEach(([prop, value]) => {
|
334
|
-
switch(prop) {
|
335
|
+
switch (prop) {
|
335
336
|
case 'attributes':
|
336
337
|
Object.entries(value).forEach(([key, val]) => {
|
337
338
|
if (voidAttributes.has(key)) {
|
@@ -212,7 +212,7 @@ class Navigator extends Base {
|
|
212
212
|
let {key, target} = keyEvent,
|
213
213
|
newActiveElement;
|
214
214
|
|
215
|
-
switch(key) {
|
215
|
+
switch (key) {
|
216
216
|
// Move to the previous navigable item
|
217
217
|
case data.previousKey:
|
218
218
|
newActiveElement = me.navigateGetAdjacent(-1, data);
|
@@ -11,7 +11,7 @@ const StringBasedRenderer = {
|
|
11
11
|
|
12
12
|
/**
|
13
13
|
* Handles string-based insertion of a new node into the DOM.
|
14
|
-
* This method is called by `insertNode()` when `NeoConfig.
|
14
|
+
* This method is called by `insertNode()` when `NeoConfig.useDomApiRenderer` is false.
|
15
15
|
*
|
16
16
|
* @param {Object} data
|
17
17
|
* @param {Boolean} data.hasLeadingTextChildren Flag to honor leading comments.
|
package/src/toolbar/Base.mjs
CHANGED
package/src/util/VDom.mjs
CHANGED
package/src/util/VNode.mjs
CHANGED
@@ -43,7 +43,7 @@ class VNode extends Base {
|
|
43
43
|
|
44
44
|
optsArray.forEach(([key, value]) => {
|
45
45
|
if (vnode.hasOwnProperty(key)) {
|
46
|
-
switch(key) {
|
46
|
+
switch (key) {
|
47
47
|
case 'attributes':
|
48
48
|
if (Neo.isObject(value) && Neo.isObject(vnode[key])) {
|
49
49
|
Object.entries(value).forEach(([attrKey, attrValue]) => {
|
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});
|