neo.mjs 10.0.0-beta.6 → 10.0.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/RELEASE_NOTES/v10.0.0-beta.1.md +20 -0
- package/.github/RELEASE_NOTES/v10.0.0-beta.2.md +73 -0
- package/.github/RELEASE_NOTES/v10.0.0-beta.3.md +39 -0
- package/.github/RELEASE_NOTES/v10.0.0.md +52 -0
- package/ServiceWorker.mjs +2 -2
- package/apps/portal/index.html +1 -1
- package/apps/portal/resources/data/blog.json +24 -0
- package/apps/portal/view/ViewportController.mjs +6 -4
- package/apps/portal/view/examples/List.mjs +28 -19
- package/apps/portal/view/home/FooterContainer.mjs +1 -1
- package/examples/functional/button/base/MainContainer.mjs +207 -0
- package/examples/functional/button/base/app.mjs +6 -0
- package/examples/functional/button/base/index.html +11 -0
- package/examples/functional/button/base/neo-config.json +6 -0
- package/learn/blog/v10-deep-dive-functional-components.md +293 -0
- package/learn/blog/v10-deep-dive-reactivity.md +522 -0
- package/learn/blog/v10-deep-dive-state-provider.md +432 -0
- package/learn/blog/v10-deep-dive-vdom-revolution.md +194 -0
- package/learn/blog/v10-post1-love-story.md +383 -0
- package/learn/guides/uibuildingblocks/WorkingWithVDom.md +26 -2
- package/package.json +3 -3
- package/src/DefaultConfig.mjs +2 -2
- package/src/Neo.mjs +47 -45
- package/src/component/Abstract.mjs +412 -0
- package/src/component/Base.mjs +18 -380
- package/src/core/Base.mjs +34 -33
- package/src/core/Effect.mjs +30 -34
- package/src/core/EffectManager.mjs +101 -14
- package/src/core/Observable.mjs +69 -65
- package/src/form/field/Text.mjs +11 -5
- package/src/functional/button/Base.mjs +384 -0
- package/src/functional/component/Base.mjs +51 -145
- package/src/layout/Cube.mjs +8 -4
- package/src/manager/VDomUpdate.mjs +179 -94
- package/src/mixin/VdomLifecycle.mjs +4 -1
- package/src/state/Provider.mjs +41 -27
- package/src/util/VDom.mjs +11 -4
- package/src/util/vdom/TreeBuilder.mjs +38 -62
- package/src/worker/mixin/RemoteMethodAccess.mjs +1 -6
- package/test/siesta/siesta.js +15 -3
- package/test/siesta/tests/VdomCalendar.mjs +7 -7
- package/test/siesta/tests/VdomHelper.mjs +7 -7
- package/test/siesta/tests/classic/Button.mjs +113 -0
- package/test/siesta/tests/core/EffectBatching.mjs +46 -41
- package/test/siesta/tests/functional/Button.mjs +113 -0
- package/test/siesta/tests/state/ProviderNestedDataConfigs.mjs +59 -0
- package/test/siesta/tests/vdom/Advanced.mjs +14 -8
- package/test/siesta/tests/vdom/VdomAsymmetricUpdates.mjs +9 -9
- package/test/siesta/tests/vdom/VdomRealWorldUpdates.mjs +6 -6
- package/test/siesta/tests/vdom/layout/Cube.mjs +11 -7
- package/test/siesta/tests/vdom/table/Container.mjs +9 -5
- package/src/core/EffectBatchManager.mjs +0 -67
package/src/core/Effect.mjs
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
import Config
|
2
|
-
import EffectManager
|
3
|
-
import
|
4
|
-
import IdGenerator from './IdGenerator.mjs';
|
1
|
+
import Config from './Config.mjs';
|
2
|
+
import EffectManager from './EffectManager.mjs';
|
3
|
+
import IdGenerator from './IdGenerator.mjs';
|
5
4
|
|
6
5
|
/**
|
7
6
|
* Creates a reactive effect that automatically tracks its dependencies and re-runs when any of them change.
|
@@ -73,8 +72,6 @@ class Effect {
|
|
73
72
|
constructor(fn, options={}) {
|
74
73
|
const me = this;
|
75
74
|
|
76
|
-
// This single statement handles both (fn, options) and ({...}) signatures
|
77
|
-
// by normalizing them into a single object that we can destructure.
|
78
75
|
const {
|
79
76
|
fn: effectFn,
|
80
77
|
componentId,
|
@@ -88,16 +85,10 @@ class Effect {
|
|
88
85
|
|
89
86
|
me.isRunning = new Config(false);
|
90
87
|
|
91
|
-
// The subscriber(s) must be added *before* the first run is triggered.
|
92
|
-
// This is critical for consumers like functional components, which need to process
|
93
|
-
// the initial VDOM synchronously within the constructor lifecycle.
|
94
88
|
if (subscriber) {
|
95
|
-
// A concise way to handle both single and array subscribers.
|
96
89
|
[].concat(subscriber).forEach(sub => me.isRunning.subscribe(sub))
|
97
90
|
}
|
98
91
|
|
99
|
-
// If lazy, just store the function without running it.
|
100
|
-
// Otherwise, use the setter to trigger the initial run.
|
101
92
|
if (lazy) {
|
102
93
|
me._fn = effectFn
|
103
94
|
} else {
|
@@ -117,41 +108,53 @@ class Effect {
|
|
117
108
|
}
|
118
109
|
|
119
110
|
/**
|
120
|
-
* Executes the effect function,
|
121
|
-
*
|
122
|
-
* The dynamic re-tracking ensures the effect always reflects its current dependencies,
|
123
|
-
* even if the logic within `fn` changes conditionally.
|
111
|
+
* Executes the effect function, re-evaluating its dependencies.
|
112
|
+
* If the EffectManager is paused (e.g., inside a batch), it queues itself to be run later.
|
124
113
|
* @protected
|
125
114
|
*/
|
126
115
|
run() {
|
127
116
|
const me = this;
|
128
117
|
|
129
|
-
|
130
|
-
if (me.isDestroyed || me.isRunning.get()) {
|
131
|
-
EffectManager.resume(); // Resume if we return early
|
118
|
+
if (me.isDestroyed) {
|
132
119
|
return
|
133
120
|
}
|
134
121
|
|
135
|
-
if
|
136
|
-
|
122
|
+
// Check if already running without creating a dependency on `isRunning`.
|
123
|
+
EffectManager.pauseTracking();
|
124
|
+
const isRunning = me.isRunning.get();
|
125
|
+
EffectManager.resumeTracking();
|
126
|
+
|
127
|
+
if (isRunning) {
|
137
128
|
return
|
138
129
|
}
|
139
130
|
|
131
|
+
// If the manager is globally paused for batching, queue this effect and stop.
|
132
|
+
if (EffectManager.isPaused()) {
|
133
|
+
EffectManager.queue(me);
|
134
|
+
return
|
135
|
+
}
|
136
|
+
|
137
|
+
// Set `isRunning` to true without creating a dependency.
|
138
|
+
EffectManager.pauseTracking();
|
140
139
|
me.isRunning.set(true);
|
140
|
+
EffectManager.resumeTracking();
|
141
141
|
|
142
|
+
// Clear old dependencies and set this as the active effect.
|
142
143
|
me.dependencies.forEach(cleanup => cleanup());
|
143
144
|
me.dependencies.clear();
|
144
|
-
|
145
145
|
EffectManager.push(me);
|
146
|
-
EffectManager.resume();
|
147
146
|
|
148
147
|
try {
|
148
|
+
// Execute the function, which will collect new dependencies.
|
149
149
|
me.fn()
|
150
150
|
} finally {
|
151
|
+
// Clean up after the run.
|
151
152
|
EffectManager.pop();
|
152
|
-
|
153
|
+
|
154
|
+
// Set `isRunning` to false without creating a dependency.
|
155
|
+
EffectManager.pauseTracking();
|
153
156
|
me.isRunning.set(false);
|
154
|
-
EffectManager.
|
157
|
+
EffectManager.resumeTracking()
|
155
158
|
}
|
156
159
|
}
|
157
160
|
|
@@ -163,7 +166,6 @@ class Effect {
|
|
163
166
|
addDependency(config) {
|
164
167
|
const me = this;
|
165
168
|
|
166
|
-
// Only add if not already a dependency. Map uses strict equality (===) for object keys.
|
167
169
|
if (!me.dependencies.has(config)) {
|
168
170
|
const cleanup = config.subscribe({
|
169
171
|
id: me.id,
|
@@ -175,11 +177,7 @@ class Effect {
|
|
175
177
|
}
|
176
178
|
}
|
177
179
|
|
178
|
-
Neo.core
|
179
|
-
|
180
|
-
if (!Neo.core.Effect) {
|
181
|
-
Neo.core.Effect = Effect;
|
182
|
-
|
180
|
+
export default Neo.gatekeep(Effect, 'Neo.core.Effect', () => {
|
183
181
|
/**
|
184
182
|
* Factory shortcut to create a new Neo.core.Effect instance.
|
185
183
|
* @function Neo.effect
|
@@ -188,6 +186,4 @@ if (!Neo.core.Effect) {
|
|
188
186
|
* @returns {Neo.core.Effect}
|
189
187
|
*/
|
190
188
|
Neo.effect = (fn, options) => new Effect(fn, options)
|
191
|
-
}
|
192
|
-
|
193
|
-
export default Neo.core.Effect;
|
189
|
+
});
|
@@ -1,20 +1,47 @@
|
|
1
1
|
/**
|
2
|
-
* A singleton manager to track the currently running effect.
|
3
|
-
*
|
2
|
+
* A singleton manager to track the currently running effect and control global effect execution.
|
3
|
+
* It provides a centralized mechanism for pausing, resuming, and batching effect runs.
|
4
4
|
* @class Neo.core.EffectManager
|
5
5
|
* @singleton
|
6
6
|
*/
|
7
7
|
const EffectManager = {
|
8
|
+
/**
|
9
|
+
* A stack to keep track of the currently active effect and its predecessors.
|
10
|
+
* @member {Neo.core.Effect[]} effectStack=[]
|
11
|
+
* @protected
|
12
|
+
*/
|
8
13
|
effectStack: [],
|
9
|
-
|
14
|
+
/**
|
15
|
+
* A flag to temporarily disable dependency tracking for the active effect.
|
16
|
+
* This is used internally to prevent effects from depending on their own state, like `isRunning`.
|
17
|
+
* @member {Boolean} isTrackingPaused=false
|
18
|
+
* @protected
|
19
|
+
*/
|
20
|
+
isTrackingPaused: false,
|
21
|
+
/**
|
22
|
+
* A counter to manage nested calls to pause() and resume(). Effect execution is
|
23
|
+
* paused or batched while this counter is greater than 0.
|
24
|
+
* @member {Number} pauseCounter=0
|
25
|
+
* @protected
|
26
|
+
*/
|
27
|
+
pauseCounter: 0,
|
28
|
+
/**
|
29
|
+
* A Set to store unique effects that are triggered while the manager is paused.
|
30
|
+
* These effects will be run when resume() is called and the pauseCounter returns to 0.
|
31
|
+
* @member {Set<Neo.core.Effect>} queuedEffects=new Set()
|
32
|
+
* @protected
|
33
|
+
*/
|
34
|
+
queuedEffects: new Set(),
|
10
35
|
|
11
36
|
/**
|
12
|
-
* Adds a `Neo.core.Config` instance as a dependency for the currently active effect
|
13
|
-
*
|
37
|
+
* Adds a `Neo.core.Config` instance as a dependency for the currently active effect,
|
38
|
+
* unless dependency tracking is explicitly paused.
|
14
39
|
* @param {Neo.core.Config} config The config instance to add as a dependency.
|
15
40
|
*/
|
16
41
|
addDependency(config) {
|
17
|
-
!this.
|
42
|
+
if (!this.isTrackingPaused) {
|
43
|
+
this.getActiveEffect()?.addDependency(config)
|
44
|
+
}
|
18
45
|
},
|
19
46
|
|
20
47
|
/**
|
@@ -26,14 +53,31 @@ const EffectManager = {
|
|
26
53
|
},
|
27
54
|
|
28
55
|
/**
|
29
|
-
*
|
56
|
+
* Checks if effect execution is currently paused or batched.
|
57
|
+
* @returns {Boolean} True if the pauseCounter is greater than 0.
|
58
|
+
*/
|
59
|
+
isPaused() {
|
60
|
+
return this.pauseCounter > 0
|
61
|
+
},
|
62
|
+
|
63
|
+
/**
|
64
|
+
* Pauses effect execution and begins batching.
|
65
|
+
* Each call to pause() increments a counter, allowing for nested control.
|
30
66
|
*/
|
31
67
|
pause() {
|
32
|
-
this.
|
68
|
+
this.pauseCounter++
|
69
|
+
},
|
70
|
+
|
71
|
+
/**
|
72
|
+
* Disables dependency tracking for the currently active effect.
|
73
|
+
* @protected
|
74
|
+
*/
|
75
|
+
pauseTracking() {
|
76
|
+
this.isTrackingPaused = true
|
33
77
|
},
|
34
78
|
|
35
79
|
/**
|
36
|
-
* Pops the current effect from the stack
|
80
|
+
* Pops the current effect from the stack.
|
37
81
|
* @returns {Neo.core.Effect|null}
|
38
82
|
*/
|
39
83
|
pop() {
|
@@ -41,7 +85,7 @@ const EffectManager = {
|
|
41
85
|
},
|
42
86
|
|
43
87
|
/**
|
44
|
-
* Pushes an effect onto the stack
|
88
|
+
* Pushes an effect onto the stack.
|
45
89
|
* @param {Neo.core.Effect} effect The effect to push.
|
46
90
|
*/
|
47
91
|
push(effect) {
|
@@ -49,12 +93,55 @@ const EffectManager = {
|
|
49
93
|
},
|
50
94
|
|
51
95
|
/**
|
52
|
-
*
|
96
|
+
* Queues a unique effect to be run later.
|
97
|
+
* @param {Neo.core.Effect} effect The effect to queue.
|
98
|
+
* @protected
|
99
|
+
*/
|
100
|
+
queue(effect) {
|
101
|
+
this.queuedEffects.add(effect)
|
102
|
+
},
|
103
|
+
|
104
|
+
/**
|
105
|
+
* Resumes effect execution. If the pause counter returns to zero and effects
|
106
|
+
* have been queued, they will all be executed synchronously.
|
53
107
|
*/
|
54
108
|
resume() {
|
55
|
-
|
109
|
+
let me = this;
|
110
|
+
|
111
|
+
if (me.pauseCounter > 0) {
|
112
|
+
me.pauseCounter--;
|
113
|
+
|
114
|
+
if (me.pauseCounter === 0 && me.queuedEffects.size > 0) {
|
115
|
+
const effectsToRun = [...me.queuedEffects];
|
116
|
+
me.queuedEffects.clear();
|
117
|
+
effectsToRun.forEach(effect => effect.run())
|
118
|
+
}
|
119
|
+
}
|
120
|
+
},
|
121
|
+
|
122
|
+
/**
|
123
|
+
* Re-enables dependency tracking for the currently active effect.
|
124
|
+
* @protected
|
125
|
+
*/
|
126
|
+
resumeTracking() {
|
127
|
+
this.isTrackingPaused = false
|
56
128
|
}
|
57
129
|
};
|
58
130
|
|
59
|
-
export default Neo.gatekeep(EffectManager, 'Neo.core.EffectManager')
|
60
|
-
|
131
|
+
export default Neo.gatekeep(EffectManager, 'Neo.core.EffectManager', () => {
|
132
|
+
/**
|
133
|
+
* Wraps a function in a batch operation, ensuring that all effects triggered
|
134
|
+
* within it are run only once after the function completes.
|
135
|
+
* @function Neo.batch
|
136
|
+
* @param {Function} fn The function to execute.
|
137
|
+
*/
|
138
|
+
Neo.batch = function(fn) {
|
139
|
+
EffectManager.pause();
|
140
|
+
try {
|
141
|
+
fn()
|
142
|
+
} finally {
|
143
|
+
// The public resume() method handles running queued effects.
|
144
|
+
EffectManager.resume()
|
145
|
+
}
|
146
|
+
}
|
147
|
+
});
|
package/src/core/Observable.mjs
CHANGED
@@ -1,7 +1,16 @@
|
|
1
1
|
import Base from './Base.mjs';
|
2
2
|
import NeoArray from '../util/Array.mjs';
|
3
|
+
import {isDescriptor} from '../core/ConfigSymbols.mjs';
|
3
4
|
import {resolveCallback} from '../util/Function.mjs';
|
4
5
|
|
6
|
+
/**
|
7
|
+
* A unique, non-enumerable key for the internal event map.
|
8
|
+
* Using a Symbol prevents property name collisions on the consuming class instance,
|
9
|
+
* providing a robust way to manage private state within a mixin.
|
10
|
+
* @type {Symbol}
|
11
|
+
*/
|
12
|
+
const eventMapSymbol = Symbol('eventMap');
|
13
|
+
|
5
14
|
/**
|
6
15
|
* @class Neo.core.Observable
|
7
16
|
* @extends Neo.core.Base
|
@@ -19,12 +28,35 @@ class Observable extends Base {
|
|
19
28
|
*/
|
20
29
|
ntype: 'mixin-observable',
|
21
30
|
/**
|
22
|
-
*
|
23
|
-
*
|
31
|
+
* A declarative way to assign event listeners to an instance upon creation.
|
32
|
+
* The framework processes this config and calls `on()` to populate the
|
33
|
+
* internal event registry. This config should not be manipulated directly after
|
34
|
+
* instantiation; use `on()` and `un()` instead.
|
35
|
+
* @member {Object|null} listeners_
|
36
|
+
* @example
|
37
|
+
* listeners: {
|
38
|
+
* myEvent: 'onMyEvent',
|
39
|
+
* otherEvent: {
|
40
|
+
* fn: 'onOtherEvent',
|
41
|
+
* delay: 100,
|
42
|
+
* once: true
|
43
|
+
* },
|
44
|
+
* scope: this
|
45
|
+
* }
|
46
|
+
* @reactive
|
24
47
|
*/
|
25
|
-
|
48
|
+
listeners_: {
|
49
|
+
[isDescriptor]: true,
|
50
|
+
merge : 'deep',
|
51
|
+
value : {}
|
52
|
+
}
|
26
53
|
}
|
27
54
|
|
55
|
+
/**
|
56
|
+
* @member {Object} [eventMapSymbol]
|
57
|
+
* @private
|
58
|
+
*/
|
59
|
+
|
28
60
|
/**
|
29
61
|
* @param {Object|String} name
|
30
62
|
* @param {Object} [opts]
|
@@ -101,6 +133,12 @@ class Observable extends Base {
|
|
101
133
|
}
|
102
134
|
|
103
135
|
if (!nameObject) {
|
136
|
+
// LAZY INITIALIZATION: The key to a robust mixin.
|
137
|
+
// This ensures the private internal listener store exists on the instance.
|
138
|
+
// `eventMapSymbol` is the *actual* registry of handler arrays, and is
|
139
|
+
// intentionally separate from the public `listeners_` config.
|
140
|
+
me[eventMapSymbol] ??= {};
|
141
|
+
|
104
142
|
eventConfig = {fn: listener, id: eventId || Neo.getId('event')};
|
105
143
|
|
106
144
|
if (data) {eventConfig.data = data}
|
@@ -108,7 +146,7 @@ class Observable extends Base {
|
|
108
146
|
if (once) {eventConfig.once = once}
|
109
147
|
if (scope) {eventConfig.scope = scope}
|
110
148
|
|
111
|
-
if (existing = me
|
149
|
+
if ((existing = me[eventMapSymbol][name])) {
|
112
150
|
existing.forEach(cfg => {
|
113
151
|
if (cfg.id === eventId || (cfg.fn === listener && cfg.scope === scope)) {
|
114
152
|
console.error('Duplicate event handler attached:', name, me)
|
@@ -123,7 +161,7 @@ class Observable extends Base {
|
|
123
161
|
existing.push(eventConfig)
|
124
162
|
}
|
125
163
|
} else {
|
126
|
-
me
|
164
|
+
me[eventMapSymbol][name] = [eventConfig] // Use the private eventMapSymbol registry
|
127
165
|
}
|
128
166
|
|
129
167
|
return eventConfig.id
|
@@ -132,6 +170,25 @@ class Observable extends Base {
|
|
132
170
|
return null
|
133
171
|
}
|
134
172
|
|
173
|
+
/**
|
174
|
+
* This hook is the bridge between the declarative `listeners_` config and the
|
175
|
+
* imperative `on()`/`un()` methods. It's called automatically by the framework
|
176
|
+
* whenever the `listeners` config property is changed.
|
177
|
+
* @param {Object} value The new listeners object
|
178
|
+
* @param {Object} oldValue The old listeners object
|
179
|
+
* @protected
|
180
|
+
*/
|
181
|
+
afterSetListeners(value, oldValue) {
|
182
|
+
// Unregister any listeners from the old config object
|
183
|
+
if (oldValue && Object.keys(oldValue).length > 0) {
|
184
|
+
this.un(oldValue)
|
185
|
+
}
|
186
|
+
// Register all listeners from the new config object
|
187
|
+
if (value && Object.keys(value).length > 0) {
|
188
|
+
this.on(value)
|
189
|
+
}
|
190
|
+
}
|
191
|
+
|
135
192
|
/**
|
136
193
|
* Call the passed function, or a function by *name* which exists in the passed scope's
|
137
194
|
* or this component's ownership chain.
|
@@ -164,7 +221,7 @@ class Observable extends Base {
|
|
164
221
|
fire(name) {
|
165
222
|
let me = this,
|
166
223
|
args = [].slice.call(arguments, 1),
|
167
|
-
listeners = me.
|
224
|
+
listeners = me[eventMapSymbol], // Always use the private, structured registry for firing events.
|
168
225
|
delay, handler, handlers, i, len;
|
169
226
|
|
170
227
|
if (listeners && listeners[name]) {
|
@@ -203,50 +260,6 @@ class Observable extends Base {
|
|
203
260
|
}
|
204
261
|
}
|
205
262
|
|
206
|
-
/**
|
207
|
-
* @param {Object} config
|
208
|
-
*/
|
209
|
-
initObservable(config) {
|
210
|
-
let me = this,
|
211
|
-
proto = me.__proto__,
|
212
|
-
ctor = proto.constructor,
|
213
|
-
listeners;
|
214
|
-
|
215
|
-
if (config.listeners) {
|
216
|
-
me.listeners = config.listeners;
|
217
|
-
delete config.listeners
|
218
|
-
}
|
219
|
-
|
220
|
-
listeners = me.listeners;
|
221
|
-
|
222
|
-
me.listeners = {};
|
223
|
-
|
224
|
-
if (listeners) {
|
225
|
-
if (Neo.isObject(listeners)) {
|
226
|
-
listeners = {...listeners}
|
227
|
-
}
|
228
|
-
|
229
|
-
me.addListener(listeners);
|
230
|
-
}
|
231
|
-
|
232
|
-
while (proto?.constructor.isClass) {
|
233
|
-
ctor = proto.constructor;
|
234
|
-
|
235
|
-
if (ctor.observable && !ctor.listeners) {
|
236
|
-
Object.assign(ctor, {
|
237
|
-
addListener : me.addListener,
|
238
|
-
fire : me.fire,
|
239
|
-
listeners : {},
|
240
|
-
on : me.on,
|
241
|
-
removeListener: me.removeListener,
|
242
|
-
un : me.un
|
243
|
-
})
|
244
|
-
}
|
245
|
-
|
246
|
-
proto = proto.__proto__
|
247
|
-
}
|
248
|
-
}
|
249
|
-
|
250
263
|
/**
|
251
264
|
* Alias for addListener
|
252
265
|
* @param {Object|String} name
|
@@ -287,6 +300,9 @@ class Observable extends Base {
|
|
287
300
|
let me = this,
|
288
301
|
i, len, listener, listeners, match;
|
289
302
|
|
303
|
+
// LAZY INITIALIZATION: Ensure the internal listener store exists.
|
304
|
+
me[eventMapSymbol] ??= {};
|
305
|
+
|
290
306
|
if (Neo.isFunction(eventId)) {
|
291
307
|
me.removeListener({[name]: eventId, scope});
|
292
308
|
return
|
@@ -299,7 +315,7 @@ class Observable extends Base {
|
|
299
315
|
}
|
300
316
|
|
301
317
|
Object.entries(name).forEach(([key, value]) => {
|
302
|
-
listeners = me
|
318
|
+
listeners = me[eventMapSymbol][key] || [];
|
303
319
|
i = 0;
|
304
320
|
len = listeners.length;
|
305
321
|
|
@@ -314,9 +330,9 @@ class Observable extends Base {
|
|
314
330
|
break
|
315
331
|
}
|
316
332
|
}
|
317
|
-
})
|
333
|
+
})
|
318
334
|
} else if (Neo.isString(eventId)) {
|
319
|
-
listeners = me
|
335
|
+
listeners = me[eventMapSymbol][name];
|
320
336
|
match = false;
|
321
337
|
|
322
338
|
listeners.forEach((eventConfig, idx) => {
|
@@ -331,18 +347,6 @@ class Observable extends Base {
|
|
331
347
|
}
|
332
348
|
}
|
333
349
|
|
334
|
-
// removeAllListeners: function(name) {
|
335
|
-
|
336
|
-
// },
|
337
|
-
|
338
|
-
// suspendListeners: function(queue) {
|
339
|
-
|
340
|
-
// },
|
341
|
-
|
342
|
-
// resumeListeners: function() {
|
343
|
-
|
344
|
-
// }
|
345
|
-
|
346
350
|
/**
|
347
351
|
* Alias for removeListener
|
348
352
|
* @param {Object|String} name
|
package/src/form/field/Text.mjs
CHANGED
@@ -1391,13 +1391,19 @@ class Text extends Field {
|
|
1391
1391
|
onInputValueChange(data) {
|
1392
1392
|
let me = this,
|
1393
1393
|
oldValue = me.value,
|
1394
|
-
inputValue = data.value
|
1395
|
-
vnode = VNodeUtil.find(me.vnode, {nodeName: 'input'});
|
1394
|
+
inputValue = data.value;
|
1396
1395
|
|
1397
|
-
|
1398
|
-
|
1396
|
+
// Find the VNode for the real input element within the component's vnode tree.
|
1397
|
+
const {vnode: inputVNode} = VNodeUtil.find(me.vnode, {nodeName: 'input'}) || {};
|
1398
|
+
|
1399
|
+
if (inputVNode) {
|
1400
|
+
// This is the critical synchronization step. The user's input has changed the
|
1401
|
+
// real DOM on the Main Thread. We must manually update our "last known state"
|
1402
|
+
// (this.vnode) to match this reality *before* the next diffing cycle runs.
|
1403
|
+
// This prevents the framework from sending a redundant delta update that could
|
1404
|
+
// overwrite the user's input or cause cursor jumps.
|
1399
1405
|
// Required e.g. for validation -> revert a wrong user input
|
1400
|
-
|
1406
|
+
inputVNode.attributes.value = inputValue
|
1401
1407
|
}
|
1402
1408
|
|
1403
1409
|
if (Neo.isString(inputValue)) {
|