neo.mjs 10.0.0-beta.6 → 10.0.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.
Files changed (51) hide show
  1. package/.github/RELEASE_NOTES/v10.0.0-beta.1.md +20 -0
  2. package/.github/RELEASE_NOTES/v10.0.0-beta.2.md +73 -0
  3. package/.github/RELEASE_NOTES/v10.0.0-beta.3.md +39 -0
  4. package/.github/RELEASE_NOTES/v10.0.0.md +52 -0
  5. package/ServiceWorker.mjs +2 -2
  6. package/apps/portal/index.html +1 -1
  7. package/apps/portal/view/ViewportController.mjs +6 -4
  8. package/apps/portal/view/examples/List.mjs +28 -19
  9. package/apps/portal/view/home/FooterContainer.mjs +1 -1
  10. package/examples/functional/button/base/MainContainer.mjs +207 -0
  11. package/examples/functional/button/base/app.mjs +6 -0
  12. package/examples/functional/button/base/index.html +11 -0
  13. package/examples/functional/button/base/neo-config.json +6 -0
  14. package/learn/blog/v10-deep-dive-functional-components.md +293 -0
  15. package/learn/blog/v10-deep-dive-reactivity.md +522 -0
  16. package/learn/blog/v10-deep-dive-state-provider.md +432 -0
  17. package/learn/blog/v10-deep-dive-vdom-revolution.md +194 -0
  18. package/learn/blog/v10-post1-love-story.md +383 -0
  19. package/learn/guides/uibuildingblocks/WorkingWithVDom.md +26 -2
  20. package/package.json +3 -3
  21. package/src/DefaultConfig.mjs +2 -2
  22. package/src/Neo.mjs +47 -45
  23. package/src/component/Abstract.mjs +412 -0
  24. package/src/component/Base.mjs +18 -380
  25. package/src/core/Base.mjs +34 -33
  26. package/src/core/Effect.mjs +30 -34
  27. package/src/core/EffectManager.mjs +101 -14
  28. package/src/core/Observable.mjs +69 -65
  29. package/src/form/field/Text.mjs +11 -5
  30. package/src/functional/button/Base.mjs +384 -0
  31. package/src/functional/component/Base.mjs +51 -145
  32. package/src/layout/Cube.mjs +8 -4
  33. package/src/manager/VDomUpdate.mjs +179 -94
  34. package/src/mixin/VdomLifecycle.mjs +4 -1
  35. package/src/state/Provider.mjs +41 -27
  36. package/src/util/VDom.mjs +11 -4
  37. package/src/util/vdom/TreeBuilder.mjs +38 -62
  38. package/src/worker/mixin/RemoteMethodAccess.mjs +1 -6
  39. package/test/siesta/siesta.js +15 -3
  40. package/test/siesta/tests/VdomCalendar.mjs +7 -7
  41. package/test/siesta/tests/VdomHelper.mjs +7 -7
  42. package/test/siesta/tests/classic/Button.mjs +113 -0
  43. package/test/siesta/tests/core/EffectBatching.mjs +46 -41
  44. package/test/siesta/tests/functional/Button.mjs +113 -0
  45. package/test/siesta/tests/state/ProviderNestedDataConfigs.mjs +59 -0
  46. package/test/siesta/tests/vdom/Advanced.mjs +14 -8
  47. package/test/siesta/tests/vdom/VdomAsymmetricUpdates.mjs +9 -9
  48. package/test/siesta/tests/vdom/VdomRealWorldUpdates.mjs +6 -6
  49. package/test/siesta/tests/vdom/layout/Cube.mjs +11 -7
  50. package/test/siesta/tests/vdom/table/Container.mjs +9 -5
  51. package/src/core/EffectBatchManager.mjs +0 -67
@@ -69,6 +69,65 @@ StartTest(t => {
69
69
  component.destroy();
70
70
  });
71
71
 
72
+ t.it('setData with a nested object should deep-merge and bubble reactivity', t => {
73
+ const component = Neo.create(MockComponent, {
74
+ stateProvider: {
75
+ data: {
76
+ user: {
77
+ firstname: 'John',
78
+ lastname : 'Doe'
79
+ }
80
+ }
81
+ }
82
+ });
83
+
84
+ const provider = component.getStateProvider();
85
+ let effectRunCount = 0;
86
+
87
+ provider.createBinding(component.id, 'user', data => {
88
+ effectRunCount++;
89
+ return data.user;
90
+ });
91
+
92
+ t.is(effectRunCount, 1, 'Effect ran initially');
93
+ t.isDeeply(proxyToObject(component.user), { firstname: 'John', lastname: 'Doe' }, 'Initial user object is correct');
94
+
95
+ // ACTION: Set data with a nested object. This should MERGE, not replace.
96
+ provider.setData({
97
+ user: { firstname: 'Jane' }
98
+ });
99
+
100
+ // ASSERT: The object was merged, and the old 'lastname' property is preserved.
101
+ t.is(effectRunCount, 2, 'Effect re-ran after setting the branch node');
102
+
103
+ const updatedUser = proxyToObject(component.user);
104
+ t.isDeeply(updatedUser, { firstname: 'Jane', lastname: 'Doe' }, 'User object should be deep-merged');
105
+ t.is(updatedUser.lastname, 'Doe', 'The "lastname" property should be preserved after merge');
106
+
107
+ // For contrast, let's show the path-based "bubbling" behavior which has the same outcome.
108
+ // First, reset the state.
109
+ provider.setData({
110
+ user: {
111
+ firstname: 'John',
112
+ lastname: 'Doe'
113
+ }
114
+ });
115
+ t.is(effectRunCount, 3, 'Effect ran after resetting state');
116
+
117
+ // ACTION: Set a leaf node using a path string.
118
+ provider.setData({
119
+ 'user.firstname': 'Robert'
120
+ });
121
+
122
+ // ASSERT: The object was updated via bubbling, preserving the 'lastname' property.
123
+ t.is(effectRunCount, 4, 'Effect re-ran after setting a leaf node via path');
124
+ const mergedUser = proxyToObject(component.user);
125
+ t.isDeeply(mergedUser, { firstname: 'Robert', lastname: 'Doe' }, 'Path-based set should merge/preserve other properties');
126
+ t.is(mergedUser.lastname, 'Doe', 'The "lastname" property should be preserved');
127
+
128
+ component.destroy();
129
+ });
130
+
72
131
  t.it('Formulas should react to leaf node changes via bubbling', t => {
73
132
  let effectRunCount = 0;
74
133
 
@@ -1,6 +1,10 @@
1
- import Neo from '../../../../src/Neo.mjs';
2
- import * as core from '../../../../src/core/_export.mjs';
3
- import VdomHelper from '../../../../src/vdom/Helper.mjs';
1
+ import Neo from '../../../../src/Neo.mjs';
2
+ import * as core from '../../../../src/core/_export.mjs';
3
+ import StringFromVnode from '../../../../src/vdom/util/StringFromVnode.mjs';
4
+ import VdomHelper from '../../../../src/vdom/Helper.mjs';
5
+
6
+ // tests are designed for this rendering mode
7
+ Neo.config.useDomApiRenderer = false;
4
8
 
5
9
  let deltas, output, vdom, vnode;
6
10
 
@@ -30,7 +34,7 @@ StartTest(t => {
30
34
  ]}
31
35
  ]};
32
36
 
33
- vnode = VdomHelper.create(vdom);
37
+ vnode = VdomHelper.create({vdom}).vnode;
34
38
 
35
39
  vdom =
36
40
  {id: 'neo-calendar-week', cn: [
@@ -136,7 +140,7 @@ StartTest(t => {
136
140
  {id: 'neo-column-2', cls: ['foo4'], cn: []}
137
141
  ]};
138
142
 
139
- vnode = VdomHelper.create(vdom);
143
+ vnode = VdomHelper.create({vdom}).vnode;
140
144
 
141
145
  vdom =
142
146
  {id: 'neo-calendar-week', cn: [
@@ -246,7 +250,7 @@ StartTest(t => {
246
250
  {id: 'neo-component-6'}
247
251
  ]};
248
252
 
249
- vnode = VdomHelper.create(vdom);
253
+ vnode = VdomHelper.create({vdom}).vnode;
250
254
 
251
255
  vdom =
252
256
  {id: 'neo-container-1', cn: [
@@ -342,9 +346,11 @@ StartTest(t => {
342
346
  ]}
343
347
  ]};
344
348
 
345
- vnode = VdomHelper.create(vdom);
349
+ const result = vnode = VdomHelper.create({vdom});
350
+ const outerHTML = result.outerHTML;
351
+ vnode = result.vnode;
346
352
 
347
- t.is(vnode.outerHTML.includes('static'), false, 'The generated DOM does not include "static"');
353
+ t.is(outerHTML.includes('static'), false, 'The generated DOM does not include "static"');
348
354
  t.isDeeplyStrict(vnode.static, undefined, 'Top-level VNode did not get the static attribute');
349
355
  t.isDeeplyStrict(vnode.childNodes[0].static, true, 'First VNode childNode got the static attribute');
350
356
 
@@ -1,15 +1,15 @@
1
- import Neo from '../../../../src/Neo.mjs';
2
- import * as core from '../../../../src/core/_export.mjs';
3
- import ComponentManager from '../../../../src/manager/Component.mjs';
4
- import TreeBuilder from '../../../../src/util/vdom/TreeBuilder.mjs';
5
- import VDomUpdate from '../../../../src/manager/VDomUpdate.mjs';
6
- import VdomLifecycle from '../../../../src/mixin/VdomLifecycle.mjs';
7
- import VdomHelper from '../../../../src/vdom/Helper.mjs';
8
- import VDomUtil from '../../../../src/util/VDom.mjs';
1
+ import Neo from '../../../../src/Neo.mjs';
2
+ import * as core from '../../../../src/core/_export.mjs';
3
+ import ComponentManager from '../../../../src/manager/Component.mjs';
4
+ import DomApiVnodeCreator from '../../../../src/vdom/util/DomApiVnodeCreator.mjs';
5
+ import TreeBuilder from '../../../../src/util/vdom/TreeBuilder.mjs';
6
+ import VDomUpdate from '../../../../src/manager/VDomUpdate.mjs';
7
+ import VdomLifecycle from '../../../../src/mixin/VdomLifecycle.mjs';
8
+ import VdomHelper from '../../../../src/vdom/Helper.mjs';
9
+ import VDomUtil from '../../../../src/util/VDom.mjs';
9
10
 
10
11
  // IMPORTANT: Test with the new standard renderer
11
12
  Neo.config.useDomApiRenderer = true;
12
- VdomHelper.onNeoConfigChange({useDomApiRenderer: true});
13
13
 
14
14
  /**
15
15
  * Creates a mock component object for testing.
@@ -1,8 +1,9 @@
1
- import Neo from '../../../../src/Neo.mjs';
2
- import * as core from '../../../../src/core/_export.mjs';
3
- import Component from '../../../../src/component/Base.mjs';
4
- import Container from '../../../../src/container/Base.mjs';
5
- import VdomHelper from '../../../../src/vdom/Helper.mjs';
1
+ import Neo from '../../../../src/Neo.mjs';
2
+ import * as core from '../../../../src/core/_export.mjs';
3
+ import Component from '../../../../src/component/Base.mjs';
4
+ import Container from '../../../../src/container/Base.mjs';
5
+ import DomApiVnodeCreator from '../../../../src/vdom/util/DomApiVnodeCreator.mjs';
6
+ import VdomHelper from '../../../../src/vdom/Helper.mjs';
6
7
 
7
8
  // IMPORTANT: This test file uses real components and expects them to render.
8
9
  // We need to enable unitTestMode for isolation, but also allow VDOM updates.
@@ -10,7 +11,6 @@ Neo.config.unitTestMode = true;
10
11
  Neo.config.allowVdomUpdatesInTests = true;
11
12
  // This ensures that the VdomHelper uses the correct renderer for the assertions.
12
13
  Neo.config.useDomApiRenderer = true;
13
- VdomHelper.onNeoConfigChange({useDomApiRenderer: true});
14
14
 
15
15
  // Create a mock application context, as the component lifecycle requires it for updates.
16
16
  const appName = 'VdomRealWorldTestApp';
@@ -1,6 +1,10 @@
1
- import Neo from '../../../../../src/Neo.mjs';
2
- import * as core from '../../../../../src/core/_export.mjs';
3
- import VdomHelper from '../../../../../src/vdom/Helper.mjs';
1
+ import Neo from '../../../../../src/Neo.mjs';
2
+ import * as core from '../../../../../src/core/_export.mjs';
3
+ import StringFromVnode from '../../../../../src/vdom/util/StringFromVnode.mjs';
4
+ import VdomHelper from '../../../../../src/vdom/Helper.mjs';
5
+
6
+ // tests are designed for this rendering mode
7
+ Neo.config.useDomApiRenderer = false;
4
8
 
5
9
  let oldVdom, oldVnode, vdom;
6
10
 
@@ -16,7 +20,7 @@ StartTest(t => {
16
20
  {id: 'neo-component-6'}
17
21
  ]};
18
22
 
19
- let oldVnode = VdomHelper.create(oldVdom);
23
+ let oldVnode = VdomHelper.create({vdom: oldVdom}).vnode;
20
24
 
21
25
  vdom =
22
26
  {id: 'neo-container-1', cn: [
@@ -67,7 +71,7 @@ StartTest(t => {
67
71
  ]}
68
72
  ]};
69
73
 
70
- oldVnode = VdomHelper.create(oldVdom);
74
+ oldVnode = VdomHelper.create({vdom: oldVdom}).vnode;
71
75
 
72
76
  vdom =
73
77
  {id: 'neo-container-1', cn: [
@@ -105,7 +109,7 @@ StartTest(t => {
105
109
  {id: 'neo-component-6'}
106
110
  ]};
107
111
 
108
- oldVnode = VdomHelper.create(oldVdom);
112
+ oldVnode = VdomHelper.create({vdom: oldVdom}).vnode;
109
113
 
110
114
  vdom =
111
115
  {id: 'neo-container-1', cn: [
@@ -158,7 +162,7 @@ StartTest(t => {
158
162
  ]}
159
163
  ]};
160
164
 
161
- oldVnode = VdomHelper.create(oldVdom);
165
+ oldVnode = VdomHelper.create({vdom: oldVdom}).vnode;
162
166
 
163
167
  vdom =
164
168
  {id: 'neo-container-1', cn: [
@@ -1,6 +1,10 @@
1
- import Neo from '../../../../../src/Neo.mjs';
2
- import * as core from '../../../../../src/core/_export.mjs';
3
- import VdomHelper from '../../../../../src/vdom/Helper.mjs';
1
+ import Neo from '../../../../../src/Neo.mjs';
2
+ import * as core from '../../../../../src/core/_export.mjs';
3
+ import StringFromVnode from '../../../../../src/vdom/util/StringFromVnode.mjs';
4
+ import VdomHelper from '../../../../../src/vdom/Helper.mjs';
5
+
6
+ // tests are designed for this rendering mode
7
+ Neo.config.useDomApiRenderer = false;
4
8
 
5
9
  let deltas, output, vdom, vnode;
6
10
 
@@ -17,7 +21,7 @@ StartTest(t => {
17
21
  ]}
18
22
  ]};
19
23
 
20
- vnode = VdomHelper.create(vdom);
24
+ vnode = VdomHelper.create({vdom}).vnode;
21
25
 
22
26
  vdom =
23
27
  {id: 'neo-table-container-1', cn: [
@@ -75,7 +79,7 @@ StartTest(t => {
75
79
  ]}
76
80
  ]};
77
81
 
78
- vnode = VdomHelper.create(vdom);
82
+ vnode = VdomHelper.create({vdom}).vnode;
79
83
 
80
84
  vdom =
81
85
  {id: 'neo-wrapper-1', cn: [
@@ -1,67 +0,0 @@
1
- /**
2
- * A singleton manager responsible for batching `Neo.core.Effect` executions.
3
- * This ensures that effects triggered by multiple config changes within a single
4
- * synchronous operation (e.g., `Neo.core.Base#set()`) are executed only once
5
- * per batch, after all changes have been applied.
6
- * @class Neo.core.EffectBatchManager
7
- * @singleton
8
- */
9
- const EffectBatchManager = {
10
- /**
11
- * The current count of active batch operations.
12
- * Incremented by `startBatch()`, decremented by `endBatch()`.
13
- * @member {Number} batchCount=0
14
- */
15
- batchCount: 0,
16
- /**
17
- * A Set of `Neo.core.Effect` instances that are pending execution within the current batch.
18
- * @member {Set<Neo.core.Effect>} pendingEffects=new Set()
19
- */
20
- pendingEffects: new Set(),
21
-
22
- /**
23
- * Decrements the batch counter. If `batchCount` reaches 0, all queued effects
24
- * are executed and the `pendingEffects` Set is cleared.
25
- */
26
- endBatch() {
27
- const me = this;
28
-
29
- me.batchCount--;
30
-
31
- if (me.batchCount === 0) {
32
- const effectsToRun = [...me.pendingEffects]; // Create a snapshot
33
- me.pendingEffects.clear();
34
-
35
- effectsToRun.forEach(effect => {
36
- effect.run()
37
- })
38
- }
39
- },
40
-
41
- /**
42
- * Checks if there is an active batch operation.
43
- * @returns {Boolean}
44
- */
45
- isBatchActive() {
46
- return this.batchCount > 0
47
- },
48
-
49
- /**
50
- * Queues an effect for execution at the end of the current batch.
51
- * If the effect is already queued, it will not be added again.
52
- * @param {Neo.core.Effect} effect The effect to queue.
53
- */
54
- queueEffect(effect) {
55
- this.pendingEffects.add(effect)
56
- },
57
-
58
- /**
59
- * Increments the batch counter. When `batchCount` is greater than 0,
60
- * effects will be queued instead of running immediately.
61
- */
62
- startBatch() {
63
- this.batchCount++
64
- }
65
- };
66
-
67
- export default Neo.gatekeep(EffectBatchManager, 'Neo.core.EffectBatchManager');