native-document 1.0.91 → 1.0.93
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/dist/native-document.components.min.js +1168 -138
- package/dist/native-document.dev.js +792 -217
- package/dist/native-document.dev.js.map +1 -1
- package/dist/native-document.devtools.min.js +1 -1
- package/dist/native-document.min.js +1 -1
- package/docs/advanced-components.md +814 -0
- package/docs/anchor.md +71 -11
- package/docs/cache.md +888 -0
- package/docs/conditional-rendering.md +91 -1
- package/docs/core-concepts.md +9 -2
- package/docs/elements.md +127 -2
- package/docs/extending-native-document-element.md +7 -1
- package/docs/filters.md +1216 -0
- package/docs/getting-started.md +12 -3
- package/docs/lifecycle-events.md +10 -2
- package/docs/list-rendering.md +453 -54
- package/docs/memory-management.md +9 -7
- package/docs/native-document-element.md +30 -9
- package/docs/native-fetch.md +744 -0
- package/docs/observables.md +135 -6
- package/docs/routing.md +7 -1
- package/docs/state-management.md +7 -1
- package/docs/validation.md +8 -1
- package/eslint.config.js +3 -3
- package/package.json +3 -2
- package/readme.md +53 -14
- package/src/components/$traits/HasItems.js +42 -1
- package/src/components/BaseComponent.js +4 -1
- package/src/components/accordion/Accordion.js +112 -8
- package/src/components/accordion/AccordionItem.js +93 -4
- package/src/components/alert/Alert.js +164 -4
- package/src/components/avatar/Avatar.js +236 -22
- package/src/components/menu/index.js +1 -2
- package/src/core/data/ObservableArray.js +120 -2
- package/src/core/data/ObservableChecker.js +50 -0
- package/src/core/data/ObservableItem.js +223 -80
- package/src/core/data/ObservableWhen.js +36 -6
- package/src/core/data/observable-helpers/array.js +12 -3
- package/src/core/data/observable-helpers/computed.js +17 -4
- package/src/core/data/observable-helpers/object.js +19 -3
- package/src/core/elements/control/for-each-array.js +21 -3
- package/src/core/elements/control/for-each.js +17 -5
- package/src/core/elements/control/show-if.js +31 -15
- package/src/core/elements/control/show-when.js +23 -0
- package/src/core/elements/control/switch.js +40 -10
- package/src/core/utils/cache.js +5 -0
- package/src/core/utils/memoize.js +25 -16
- package/src/core/utils/prototypes.js +3 -2
- package/src/core/wrappers/AttributesWrapper.js +1 -1
- package/src/core/wrappers/NDElement.js +41 -1
- package/src/core/wrappers/NdPrototype.js +4 -0
- package/src/core/wrappers/TemplateCloner.js +13 -10
- package/src/core/wrappers/prototypes/bind-class-extensions.js +1 -1
- package/src/core/wrappers/prototypes/nd-element-extensions.js +3 -0
- package/src/router/Route.js +9 -4
- package/src/router/Router.js +28 -9
- package/src/router/errors/RouterError.js +0 -1
- package/types/control-flow.d.ts +9 -6
- package/types/elements.d.ts +6 -3
- package/types/filters/index.d.ts +4 -0
- package/types/nd-element.d.ts +5 -238
- package/types/observable.d.ts +9 -3
- package/types/router.d.ts +5 -1
- package/types/template-cloner.ts +1 -0
- package/types/validator.ts +11 -1
- package/utils.d.ts +2 -1
- package/utils.js +4 -4
- package/src/core/utils/service.js +0 -6
|
@@ -18,7 +18,9 @@ export default function ObservableItem(value, configs = null) {
|
|
|
18
18
|
|
|
19
19
|
this.$previousValue = null;
|
|
20
20
|
this.$currentValue = value;
|
|
21
|
-
|
|
21
|
+
if(process.env.NODE_ENV === 'development') {
|
|
22
|
+
this.$isCleanedUp = false;
|
|
23
|
+
}
|
|
22
24
|
|
|
23
25
|
this.$firstListener = null;
|
|
24
26
|
this.$listeners = null;
|
|
@@ -51,13 +53,24 @@ ObservableItem.prototype.__$isObservable = true;
|
|
|
51
53
|
const DEFAULT_OPERATIONS = {};
|
|
52
54
|
const noneTrigger = function() {};
|
|
53
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Intercepts and transforms values before they are set on the observable.
|
|
58
|
+
* The interceptor can modify the value or return undefined to use the original value.
|
|
59
|
+
*
|
|
60
|
+
* @param {(value) => any} callback - Interceptor function that receives (newValue, currentValue) and returns the transformed value or undefined
|
|
61
|
+
* @returns {ObservableItem} The observable instance for chaining
|
|
62
|
+
* @example
|
|
63
|
+
* const count = Observable(0);
|
|
64
|
+
* count.intercept((newVal, oldVal) => Math.max(0, newVal)); // Prevent negative values
|
|
65
|
+
*/
|
|
54
66
|
ObservableItem.prototype.intercept = function(callback) {
|
|
55
67
|
this.$interceptor = callback;
|
|
68
|
+
this.set = this.$setWithInterceptor;
|
|
56
69
|
return this;
|
|
57
70
|
};
|
|
58
71
|
|
|
59
72
|
ObservableItem.prototype.triggerFirstListener = function(operations) {
|
|
60
|
-
this.$firstListener(this.$currentValue, this.$previousValue, operations
|
|
73
|
+
this.$firstListener(this.$currentValue, this.$previousValue, operations);
|
|
61
74
|
};
|
|
62
75
|
|
|
63
76
|
ObservableItem.prototype.triggerListeners = function(operations) {
|
|
@@ -65,33 +78,12 @@ ObservableItem.prototype.triggerListeners = function(operations) {
|
|
|
65
78
|
const $previousValue = this.$previousValue;
|
|
66
79
|
const $currentValue = this.$currentValue;
|
|
67
80
|
|
|
68
|
-
operations = operations || DEFAULT_OPERATIONS;
|
|
69
81
|
for(let i = 0, length = $listeners.length; i < length; i++) {
|
|
70
82
|
$listeners[i]($currentValue, $previousValue, operations);
|
|
71
83
|
}
|
|
72
84
|
};
|
|
73
85
|
|
|
74
|
-
|
|
75
|
-
if(typeof callbacks === "function") {
|
|
76
|
-
callbacks(value);
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
if (callbacks.set) {
|
|
80
|
-
callbacks.set(value);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
for(let i = 0, length = callbacks.length; i < length; i++) {
|
|
84
|
-
const callback = callbacks[i];
|
|
85
|
-
callback.set ? callback.set(value) : callback(value);
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
ObservableItem.prototype.triggerWatchers = function() {
|
|
91
|
-
if(!this.$watchers) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
86
|
+
ObservableItem.prototype.triggerWatchers = function(operations) {
|
|
95
87
|
const $watchers = this.$watchers;
|
|
96
88
|
const $previousValue = this.$previousValue;
|
|
97
89
|
const $currentValue = this.$currentValue;
|
|
@@ -99,20 +91,20 @@ ObservableItem.prototype.triggerWatchers = function() {
|
|
|
99
91
|
const $currentValueCallbacks = $watchers.get($currentValue);
|
|
100
92
|
const $previousValueCallbacks = $watchers.get($previousValue);
|
|
101
93
|
if($currentValueCallbacks) {
|
|
102
|
-
|
|
94
|
+
$currentValueCallbacks(true, $previousValue, operations);
|
|
103
95
|
}
|
|
104
96
|
if($previousValueCallbacks) {
|
|
105
|
-
|
|
97
|
+
$previousValueCallbacks(false, $currentValue, operations);
|
|
106
98
|
}
|
|
107
99
|
};
|
|
108
100
|
|
|
109
101
|
ObservableItem.prototype.triggerAll = function(operations) {
|
|
110
|
-
this.triggerWatchers();
|
|
102
|
+
this.triggerWatchers(operations);
|
|
111
103
|
this.triggerListeners(operations);
|
|
112
104
|
};
|
|
113
105
|
|
|
114
106
|
ObservableItem.prototype.triggerWatchersAndFirstListener = function(operations) {
|
|
115
|
-
this.triggerWatchers();
|
|
107
|
+
this.triggerWatchers(operations);
|
|
116
108
|
this.triggerFirstListener(operations);
|
|
117
109
|
};
|
|
118
110
|
|
|
@@ -140,21 +132,8 @@ ObservableItem.prototype.assocTrigger = function() {
|
|
|
140
132
|
};
|
|
141
133
|
ObservableItem.prototype.trigger = noneTrigger;
|
|
142
134
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
*/
|
|
146
|
-
ObservableItem.prototype.set = function(data) {
|
|
147
|
-
let newValue = (typeof data === 'function') ? data(this.$currentValue) : data;
|
|
148
|
-
newValue = Validator.isObservable(newValue) ? newValue.val() : newValue;
|
|
149
|
-
|
|
150
|
-
if (this.$interceptor) {
|
|
151
|
-
const result = this.$interceptor(newValue, this.$currentValue);
|
|
152
|
-
|
|
153
|
-
if (result !== undefined) {
|
|
154
|
-
newValue = result;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
135
|
+
ObservableItem.prototype.$updateWithNewValue = function(newValue) {
|
|
136
|
+
newValue = newValue?.__$isObservable ? newValue.val() : newValue;
|
|
158
137
|
if(this.$currentValue === newValue) {
|
|
159
138
|
return;
|
|
160
139
|
}
|
|
@@ -170,6 +149,30 @@ ObservableItem.prototype.set = function(data) {
|
|
|
170
149
|
}
|
|
171
150
|
};
|
|
172
151
|
|
|
152
|
+
/**
|
|
153
|
+
* @param {*} data
|
|
154
|
+
*/
|
|
155
|
+
ObservableItem.prototype.$setWithInterceptor = function(data) {
|
|
156
|
+
let newValue = (typeof data === 'function') ? data(this.$currentValue) : data;
|
|
157
|
+
const result = this.$interceptor(newValue, this.$currentValue);
|
|
158
|
+
|
|
159
|
+
if (result !== undefined) {
|
|
160
|
+
newValue = result;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
this.$updateWithNewValue(newValue);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* @param {*} data
|
|
168
|
+
*/
|
|
169
|
+
ObservableItem.prototype.$basicSet = function(data) {
|
|
170
|
+
let newValue = (typeof data === 'function') ? data(this.$currentValue) : data;
|
|
171
|
+
this.$updateWithNewValue(newValue);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
ObservableItem.prototype.set = ObservableItem.prototype.$basicSet;
|
|
175
|
+
|
|
173
176
|
ObservableItem.prototype.val = function() {
|
|
174
177
|
return this.$currentValue;
|
|
175
178
|
};
|
|
@@ -191,6 +194,16 @@ ObservableItem.prototype.disconnectAll = function() {
|
|
|
191
194
|
this.trigger = noneTrigger;
|
|
192
195
|
};
|
|
193
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Registers a cleanup callback that will be executed when the observable is cleaned up.
|
|
199
|
+
* Useful for disposing resources, removing event listeners, or other cleanup tasks.
|
|
200
|
+
*
|
|
201
|
+
* @param {Function} callback - Cleanup function to execute on observable disposal
|
|
202
|
+
* @example
|
|
203
|
+
* const obs = Observable(0);
|
|
204
|
+
* obs.onCleanup(() => console.log('Cleaned up!'));
|
|
205
|
+
* obs.cleanup(); // Logs: "Cleaned up!"
|
|
206
|
+
*/
|
|
194
207
|
ObservableItem.prototype.onCleanup = function(callback) {
|
|
195
208
|
this.$cleanupListeners = this.$cleanupListeners ?? [];
|
|
196
209
|
this.$cleanupListeners.push(callback);
|
|
@@ -205,79 +218,129 @@ ObservableItem.prototype.cleanup = function() {
|
|
|
205
218
|
}
|
|
206
219
|
MemoryManager.unregister(this.$memoryId);
|
|
207
220
|
this.disconnectAll();
|
|
208
|
-
|
|
221
|
+
if(process.env.NODE_ENV === 'development') {
|
|
222
|
+
this.$isCleanedUp = true;
|
|
223
|
+
}
|
|
209
224
|
delete this.$value;
|
|
210
225
|
};
|
|
211
226
|
|
|
212
227
|
/**
|
|
213
228
|
*
|
|
214
229
|
* @param {Function} callback
|
|
215
|
-
* @param {any} target
|
|
216
230
|
* @returns {(function(): void)}
|
|
217
231
|
*/
|
|
218
|
-
ObservableItem.prototype.subscribe = function(callback
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
232
|
+
ObservableItem.prototype.subscribe = function(callback) {
|
|
233
|
+
if(process.env.NODE_ENV === 'development') {
|
|
234
|
+
if (this.$isCleanedUp) {
|
|
235
|
+
DebugManager.warn('Observable subscription', '⚠️ Attempted to subscribe to a cleaned up observable.');
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (typeof callback !== 'function') {
|
|
239
|
+
throw new NativeDocumentError('Callback must be a function');
|
|
240
|
+
}
|
|
226
241
|
}
|
|
242
|
+
this.$listeners = this.$listeners ?? [];
|
|
227
243
|
|
|
228
244
|
this.$listeners.push(callback);
|
|
229
245
|
this.assocTrigger();
|
|
230
246
|
if(process.env.NODE_ENV === 'development') {
|
|
231
|
-
PluginsManager.emit('ObservableSubscribe', this
|
|
247
|
+
PluginsManager.emit('ObservableSubscribe', this);
|
|
232
248
|
}
|
|
233
|
-
return () => {
|
|
234
|
-
this.unsubscribe(callback);
|
|
235
|
-
this.assocTrigger();
|
|
236
|
-
if(process.env.NODE_ENV === 'development') {
|
|
237
|
-
PluginsManager.emit('ObservableUnsubscribe', this);
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
249
|
};
|
|
241
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Watches for a specific value and executes callback when the observable equals that value.
|
|
253
|
+
* Creates a watcher that only triggers when the observable changes to the specified value.
|
|
254
|
+
*
|
|
255
|
+
* @param {*} value - The value to watch for
|
|
256
|
+
* @param {(value) => void|ObservableItem} callback - Callback function or observable to set when value matches
|
|
257
|
+
* @example
|
|
258
|
+
* const status = Observable('idle');
|
|
259
|
+
* status.on('loading', () => console.log('Started loading'));
|
|
260
|
+
* status.on('error', isError); // Set another observable
|
|
261
|
+
*/
|
|
242
262
|
ObservableItem.prototype.on = function(value, callback) {
|
|
243
263
|
this.$watchers = this.$watchers ?? new Map();
|
|
244
264
|
|
|
245
265
|
let watchValueList = this.$watchers.get(value);
|
|
246
266
|
|
|
267
|
+
if(callback.__$isObservable) {
|
|
268
|
+
callback = callback.set.bind(callback);
|
|
269
|
+
}
|
|
270
|
+
|
|
247
271
|
if(!watchValueList) {
|
|
272
|
+
watchValueList = callback;
|
|
248
273
|
this.$watchers.set(value, callback);
|
|
249
|
-
} else if(!Validator.isArray(watchValueList)) {
|
|
274
|
+
} else if(!Validator.isArray(watchValueList.list)) {
|
|
250
275
|
watchValueList = [watchValueList, callback];
|
|
251
|
-
|
|
276
|
+
callback = (value) => {
|
|
277
|
+
for(let i = 0, length = watchValueList.length; i < length; i++) {
|
|
278
|
+
watchValueList[i](value);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
callback.list = watchValueList;
|
|
282
|
+
this.$watchers.set(value, callback);
|
|
252
283
|
} else {
|
|
253
|
-
watchValueList.push(callback);
|
|
284
|
+
watchValueList.list.push(callback);
|
|
254
285
|
}
|
|
255
286
|
|
|
256
287
|
this.assocTrigger();
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Removes a watcher for a specific value. If no callback is provided, removes all watchers for that value.
|
|
292
|
+
*
|
|
293
|
+
* @param {*} value - The value to stop watching
|
|
294
|
+
* @param {Function} [callback] - Specific callback to remove. If omitted, removes all watchers for this value
|
|
295
|
+
* @example
|
|
296
|
+
* const status = Observable('idle');
|
|
297
|
+
* const handler = () => console.log('Loading');
|
|
298
|
+
* status.on('loading', handler);
|
|
299
|
+
* status.off('loading', handler); // Remove specific handler
|
|
300
|
+
* status.off('loading'); // Remove all handlers for 'loading'
|
|
301
|
+
*/
|
|
302
|
+
ObservableItem.prototype.off = function(value, callback) {
|
|
303
|
+
if(!this.$watchers) return;
|
|
304
|
+
|
|
305
|
+
const watchValueList = this.$watchers.get(value);
|
|
306
|
+
if(!watchValueList) return;
|
|
307
|
+
|
|
308
|
+
if(!callback || !Array.isArray(watchValueList.list)) {
|
|
309
|
+
this.$watchers?.delete(value);
|
|
267
310
|
this.assocTrigger();
|
|
268
|
-
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const index = watchValueList.indexOf(callback);
|
|
314
|
+
watchValueList?.splice(index, 1);
|
|
315
|
+
if(watchValueList.length === 1) {
|
|
316
|
+
this.$watchers.set(value, watchValueList[0]);
|
|
317
|
+
}
|
|
318
|
+
else if(watchValueList.length === 0) {
|
|
319
|
+
this.$watchers?.delete(value);
|
|
320
|
+
}
|
|
321
|
+
this.assocTrigger();
|
|
269
322
|
};
|
|
270
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Subscribes to the observable but automatically unsubscribes after the first time the predicate matches.
|
|
326
|
+
*
|
|
327
|
+
* @param {(value) => Boolean|any} predicate - Value to match or function that returns true when condition is met
|
|
328
|
+
* @param {(value) => void} callback - Callback to execute when predicate matches, receives the matched value
|
|
329
|
+
* @example
|
|
330
|
+
* const status = Observable('loading');
|
|
331
|
+
* status.once('ready', (val) => console.log('Ready!'));
|
|
332
|
+
* status.once(val => val === 'error', (val) => console.log('Error occurred'));
|
|
333
|
+
*/
|
|
271
334
|
ObservableItem.prototype.once = function(predicate, callback) {
|
|
272
335
|
const fn = typeof predicate === 'function' ? predicate : (v) => v === predicate;
|
|
273
336
|
|
|
274
|
-
const
|
|
337
|
+
const handler = (val) => {
|
|
275
338
|
if (fn(val)) {
|
|
276
|
-
|
|
339
|
+
this.unsubscribe(handler);
|
|
277
340
|
callback(val);
|
|
278
341
|
}
|
|
279
|
-
}
|
|
280
|
-
|
|
342
|
+
};
|
|
343
|
+
this.subscribe(handler);
|
|
281
344
|
};
|
|
282
345
|
|
|
283
346
|
/**
|
|
@@ -285,11 +348,15 @@ ObservableItem.prototype.once = function(predicate, callback) {
|
|
|
285
348
|
* @param {Function} callback
|
|
286
349
|
*/
|
|
287
350
|
ObservableItem.prototype.unsubscribe = function(callback) {
|
|
351
|
+
if(!this.$listeners) return;
|
|
288
352
|
const index = this.$listeners.indexOf(callback);
|
|
289
353
|
if (index > -1) {
|
|
290
354
|
this.$listeners.splice(index, 1);
|
|
291
355
|
}
|
|
292
356
|
this.assocTrigger();
|
|
357
|
+
if(process.env.NODE_ENV === 'development') {
|
|
358
|
+
PluginsManager.emit('ObservableUnsubscribe', this);
|
|
359
|
+
}
|
|
293
360
|
};
|
|
294
361
|
|
|
295
362
|
/**
|
|
@@ -301,15 +368,50 @@ ObservableItem.prototype.check = function(callback) {
|
|
|
301
368
|
return new ObservableChecker(this, callback)
|
|
302
369
|
};
|
|
303
370
|
|
|
371
|
+
/**
|
|
372
|
+
* Gets a property value from the observable's current value.
|
|
373
|
+
* If the property is an observable, returns its value.
|
|
374
|
+
*
|
|
375
|
+
* @param {string|number} key - Property key to retrieve
|
|
376
|
+
* @returns {*} The value of the property, unwrapped if it's an observable
|
|
377
|
+
* @example
|
|
378
|
+
* const user = Observable({ name: 'John', age: Observable(25) });
|
|
379
|
+
* user.get('name'); // 'John'
|
|
380
|
+
* user.get('age'); // 25 (unwrapped from observable)
|
|
381
|
+
*/
|
|
304
382
|
ObservableItem.prototype.get = function(key) {
|
|
305
383
|
const item = this.$currentValue[key];
|
|
306
384
|
return Validator.isObservable(item) ? item.val() : item;
|
|
307
385
|
};
|
|
308
386
|
|
|
387
|
+
/**
|
|
388
|
+
* Creates an ObservableWhen that represents whether the observable equals a specific value.
|
|
389
|
+
* Returns an object that can be subscribed to and will emit true/false.
|
|
390
|
+
*
|
|
391
|
+
* @param {*} value - The value to compare against
|
|
392
|
+
* @returns {ObservableWhen} An ObservableWhen instance that tracks when the observable equals the value
|
|
393
|
+
* @example
|
|
394
|
+
* const status = Observable('idle');
|
|
395
|
+
* const isLoading = status.when('loading');
|
|
396
|
+
* isLoading.subscribe(active => console.log('Loading:', active));
|
|
397
|
+
* status.set('loading'); // Logs: "Loading: true"
|
|
398
|
+
*/
|
|
309
399
|
ObservableItem.prototype.when = function(value) {
|
|
310
400
|
return new ObservableWhen(this, value);
|
|
311
401
|
};
|
|
312
402
|
|
|
403
|
+
/**
|
|
404
|
+
* Compares the observable's current value with another value or observable.
|
|
405
|
+
*
|
|
406
|
+
* @param {*|ObservableItem} other - Value or observable to compare against
|
|
407
|
+
* @returns {boolean} True if values are equal
|
|
408
|
+
* @example
|
|
409
|
+
* const a = Observable(5);
|
|
410
|
+
* const b = Observable(5);
|
|
411
|
+
* a.equals(5); // true
|
|
412
|
+
* a.equals(b); // true
|
|
413
|
+
* a.equals(10); // false
|
|
414
|
+
*/
|
|
313
415
|
ObservableItem.prototype.equals = function(other) {
|
|
314
416
|
if(Validator.isObservable(other)) {
|
|
315
417
|
return this.$currentValue === other.$currentValue;
|
|
@@ -317,14 +419,41 @@ ObservableItem.prototype.equals = function(other) {
|
|
|
317
419
|
return this.$currentValue === other;
|
|
318
420
|
};
|
|
319
421
|
|
|
422
|
+
/**
|
|
423
|
+
* Converts the observable's current value to a boolean.
|
|
424
|
+
*
|
|
425
|
+
* @returns {boolean} The boolean representation of the current value
|
|
426
|
+
* @example
|
|
427
|
+
* const count = Observable(0);
|
|
428
|
+
* count.toBool(); // false
|
|
429
|
+
* count.set(5);
|
|
430
|
+
* count.toBool(); // true
|
|
431
|
+
*/
|
|
320
432
|
ObservableItem.prototype.toBool = function() {
|
|
321
433
|
return !!this.$currentValue;
|
|
322
434
|
};
|
|
323
435
|
|
|
436
|
+
/**
|
|
437
|
+
* Toggles the boolean value of the observable (false becomes true, true becomes false).
|
|
438
|
+
*
|
|
439
|
+
* @example
|
|
440
|
+
* const isOpen = Observable(false);
|
|
441
|
+
* isOpen.toggle(); // Now true
|
|
442
|
+
* isOpen.toggle(); // Now false
|
|
443
|
+
*/
|
|
324
444
|
ObservableItem.prototype.toggle = function() {
|
|
325
445
|
this.set(!this.$currentValue);
|
|
326
446
|
};
|
|
327
447
|
|
|
448
|
+
/**
|
|
449
|
+
* Resets the observable to its initial value.
|
|
450
|
+
* Only works if the observable was created with { reset: true } config.
|
|
451
|
+
*
|
|
452
|
+
* @example
|
|
453
|
+
* const count = Observable(0, { reset: true });
|
|
454
|
+
* count.set(10);
|
|
455
|
+
* count.reset(); // Back to 0
|
|
456
|
+
*/
|
|
328
457
|
ObservableItem.prototype.reset = function() {
|
|
329
458
|
if(!this.configs?.reset) {
|
|
330
459
|
return;
|
|
@@ -337,7 +466,21 @@ ObservableItem.prototype.reset = function() {
|
|
|
337
466
|
this.set(resetValue)
|
|
338
467
|
};
|
|
339
468
|
|
|
340
|
-
|
|
469
|
+
/**
|
|
470
|
+
* Returns a string representation of the observable's current value.
|
|
471
|
+
*
|
|
472
|
+
* @returns {string} String representation of the current value
|
|
473
|
+
*/
|
|
341
474
|
ObservableItem.prototype.toString = function() {
|
|
342
475
|
return String(this.$currentValue);
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Returns the primitive value of the observable (its current value).
|
|
480
|
+
* Called automatically in type coercion contexts.
|
|
481
|
+
*
|
|
482
|
+
* @returns {*} The current value of the observable
|
|
483
|
+
*/
|
|
484
|
+
ObservableItem.prototype.valueOf = function() {
|
|
485
|
+
return this.$currentValue;
|
|
343
486
|
};
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
|
|
2
|
+
/**
|
|
3
|
+
* Creates an ObservableWhen that tracks whether an observable equals a specific value.
|
|
4
|
+
*
|
|
5
|
+
* @param {ObservableItem} observer - The observable to watch
|
|
6
|
+
* @param {*} value - The value to compare against
|
|
7
|
+
* @class ObservableWhen
|
|
8
|
+
*/
|
|
2
9
|
export const ObservableWhen = function(observer, value) {
|
|
3
10
|
this.$target = value;
|
|
4
11
|
this.$observer = observer;
|
|
@@ -6,18 +13,41 @@ export const ObservableWhen = function(observer, value) {
|
|
|
6
13
|
|
|
7
14
|
ObservableWhen.prototype.__$isObservableWhen = true;
|
|
8
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Subscribes to changes in the match status (true when observable equals target value).
|
|
18
|
+
*
|
|
19
|
+
* @param {Function} callback - Function called with boolean indicating if values match
|
|
20
|
+
* @returns {Function} Unsubscribe function
|
|
21
|
+
* @example
|
|
22
|
+
* const status = Observable('idle');
|
|
23
|
+
* const isLoading = status.when('loading');
|
|
24
|
+
* isLoading.subscribe(active => console.log('Loading:', active));
|
|
25
|
+
*/
|
|
9
26
|
ObservableWhen.prototype.subscribe = function(callback) {
|
|
10
27
|
return this.$observer.on(this.$target, callback);
|
|
11
28
|
};
|
|
12
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Returns true if the observable's current value equals the target value.
|
|
32
|
+
*
|
|
33
|
+
* @returns {boolean} True if observable value matches target value
|
|
34
|
+
*/
|
|
13
35
|
ObservableWhen.prototype.val = function() {
|
|
14
36
|
return this.$observer.$currentValue === this.$target;
|
|
15
37
|
};
|
|
16
38
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Returns true if the observable's current value equals the target value.
|
|
41
|
+
* Alias for val().
|
|
42
|
+
*
|
|
43
|
+
* @returns {boolean} True if observable value matches target value
|
|
44
|
+
*/
|
|
45
|
+
ObservableWhen.prototype.isMatch = ObservableWhen.prototype.val;
|
|
20
46
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Returns true if the observable's current value equals the target value.
|
|
49
|
+
* Alias for val().
|
|
50
|
+
*
|
|
51
|
+
* @returns {boolean} True if observable value matches target value
|
|
52
|
+
*/
|
|
53
|
+
ObservableWhen.prototype.isActive = ObservableWhen.prototype.val;
|
|
@@ -3,10 +3,19 @@ import ObservableArray from "../ObservableArray";
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
+
* Creates an observable array with reactive array methods.
|
|
7
|
+
* All mutations trigger updates automatically.
|
|
6
8
|
*
|
|
7
|
-
* @param {Array} target
|
|
8
|
-
* @param {
|
|
9
|
-
* @
|
|
9
|
+
* @param {Array} [target=[]] - Initial array value
|
|
10
|
+
* @param {Object|null} [configs=null] - Configuration options
|
|
11
|
+
* // @param {boolean} [configs.propagation=true] - Whether to propagate changes to parent observables
|
|
12
|
+
* // @param {boolean} [configs.deep=false] - Whether to make nested objects observable
|
|
13
|
+
* @param {boolean} [configs.reset=false] - Whether to store initial value for reset()
|
|
14
|
+
* @returns {ObservableArray} An observable array with reactive methods
|
|
15
|
+
* @example
|
|
16
|
+
* const items = Observable.array([1, 2, 3]);
|
|
17
|
+
* items.push(4); // Triggers update
|
|
18
|
+
* items.subscribe((arr) => console.log(arr));
|
|
10
19
|
*/
|
|
11
20
|
Observable.array = function(target = [], configs = null) {
|
|
12
21
|
return new ObservableArray(target, configs);
|
|
@@ -6,11 +6,24 @@ import PluginsManager from "../../utils/plugins-manager";
|
|
|
6
6
|
import {nextTick} from "../../utils/helpers";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
+
* Creates a computed observable that automatically updates when its dependencies change.
|
|
10
|
+
* The callback is re-executed whenever any dependency observable changes.
|
|
9
11
|
*
|
|
10
|
-
* @param {Function} callback
|
|
11
|
-
* @param {Array|Function} dependencies
|
|
12
|
-
* @returns {ObservableItem}
|
|
13
|
-
|
|
12
|
+
* @param {Function} callback - Function that returns the computed value
|
|
13
|
+
* @param {Array<ObservableItem|ObservableChecker|ObservableProxy>|Function} [dependencies=[]] - Array of observables to watch, or batch function
|
|
14
|
+
* @returns {ObservableItem} A new observable that updates automatically
|
|
15
|
+
* @example
|
|
16
|
+
* const firstName = Observable('John');
|
|
17
|
+
* const lastName = Observable('Doe');
|
|
18
|
+
* const fullName = Observable.computed(
|
|
19
|
+
* () => `${firstName.val()} ${lastName.val()}`,
|
|
20
|
+
* [firstName, lastName]
|
|
21
|
+
* );
|
|
22
|
+
*
|
|
23
|
+
* // With batch function
|
|
24
|
+
* const batch = Observable.batch(() => { ... });
|
|
25
|
+
* const computed = Observable.computed(() => { ... }, batch);
|
|
26
|
+
*/
|
|
14
27
|
Observable.computed = function(callback, dependencies = []) {
|
|
15
28
|
const initialValue = callback();
|
|
16
29
|
const observable = new ObservableItem(initialValue);
|
|
@@ -40,10 +40,26 @@ const ObservableGet = function(target, property) {
|
|
|
40
40
|
};
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
+
* Creates an observable proxy for an object where each property becomes an observable.
|
|
44
|
+
* Properties can be accessed directly or via getter methods.
|
|
43
45
|
*
|
|
44
|
-
* @param {Object} initialValue
|
|
45
|
-
* @param {
|
|
46
|
-
* @
|
|
46
|
+
* @param {Object} initialValue - Initial object value
|
|
47
|
+
* @param {Object|null} [configs=null] - Configuration options
|
|
48
|
+
* // @param {boolean} [configs.propagation=true] - Whether changes propagate to parent
|
|
49
|
+
* @param {boolean} [configs.deep=false] - Whether to make nested objects observable
|
|
50
|
+
* @param {boolean} [configs.reset=false] - Whether to enable reset() method
|
|
51
|
+
* @returns {ObservableProxy} A proxy where each property is an observable
|
|
52
|
+
* @example
|
|
53
|
+
* const user = Observable.init({
|
|
54
|
+
* name: 'John',
|
|
55
|
+
* age: 25,
|
|
56
|
+
* address: { city: 'NYC' }
|
|
57
|
+
* }, { deep: true });
|
|
58
|
+
*
|
|
59
|
+
* user.name.val(); // 'John'
|
|
60
|
+
* user.name.set('Jane');
|
|
61
|
+
* user.name = 'Jane X'
|
|
62
|
+
* user.age.subscribe(val => console.log('Age:', val));
|
|
47
63
|
*/
|
|
48
64
|
Observable.init = function(initialValue, configs = null) {
|
|
49
65
|
const data = {};
|
|
@@ -4,8 +4,26 @@ import Validator from "../../utils/validator";
|
|
|
4
4
|
import { ElementCreator } from "../../wrappers/ElementCreator";
|
|
5
5
|
import NativeDocumentError from "../../errors/NativeDocumentError";
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Renders items from an ObservableArray with optimized array-specific updates.
|
|
9
|
+
* Provides index observables and handles array mutations efficiently.
|
|
10
|
+
*
|
|
11
|
+
* @param {ObservableArray} data - ObservableArray to iterate over
|
|
12
|
+
* @param {Function} callback - Function that renders each item (item, indexObservable) => ValidChild
|
|
13
|
+
* @param {Object} [configs={}] - Configuration options
|
|
14
|
+
* @param {boolean} [configs.shouldKeepItemsInCache] - Whether to cache rendered items
|
|
15
|
+
* @param {boolean} [configs.isParentUniqueChild] - When it's the only child of the parent
|
|
16
|
+
* @returns {AnchorDocumentFragment} Fragment managing the list rendering
|
|
17
|
+
* @example
|
|
18
|
+
* const items = Observable.array([1, 2, 3]);
|
|
19
|
+
* ForEachArray(items, (item, index) =>
|
|
20
|
+
* Div({}, `Item ${item} at index ${index.val()}`)
|
|
21
|
+
* );
|
|
22
|
+
*
|
|
23
|
+
* items.push(4); // Automatically updates DOM
|
|
24
|
+
*/
|
|
7
25
|
export function ForEachArray(data, callback, configs = {}) {
|
|
8
|
-
const element = Anchor('ForEach Array');
|
|
26
|
+
const element = Anchor('ForEach Array', configs.isParentUniqueChild);
|
|
9
27
|
const blockEnd = element.endElement();
|
|
10
28
|
const blockStart = element.startElement();
|
|
11
29
|
|
|
@@ -180,7 +198,7 @@ export function ForEachArray(data, callback, configs = {}) {
|
|
|
180
198
|
elementBeforeFirst = firstChildRemoved?.previousSibling;
|
|
181
199
|
|
|
182
200
|
for(let i = 0; i < deleted.length; i++) {
|
|
183
|
-
|
|
201
|
+
removeByItem(deleted[i], garbageFragment);
|
|
184
202
|
}
|
|
185
203
|
}
|
|
186
204
|
} else {
|
|
@@ -226,7 +244,7 @@ export function ForEachArray(data, callback, configs = {}) {
|
|
|
226
244
|
};
|
|
227
245
|
|
|
228
246
|
const buildContent = (items, _, operations) => {
|
|
229
|
-
if(operations
|
|
247
|
+
if(operations?.action === 'clear' || !items.length) {
|
|
230
248
|
if(lastNumberOfItems === 0) {
|
|
231
249
|
return;
|
|
232
250
|
}
|
|
@@ -7,12 +7,24 @@ import { ElementCreator } from "../../wrappers/ElementCreator";
|
|
|
7
7
|
import NativeDocumentError from "../../errors/NativeDocumentError";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
+
* Renders a list of items from an observable array or object, automatically updating when data changes.
|
|
11
|
+
* Efficiently manages DOM updates by tracking items with keys.
|
|
10
12
|
*
|
|
11
|
-
* @param {Array|Object
|
|
12
|
-
* @param {Function} callback
|
|
13
|
-
* @param {
|
|
14
|
-
* @param {{
|
|
15
|
-
* @
|
|
13
|
+
* @param {ObservableItem<Array|Object>} data - Observable containing array or object to iterate over
|
|
14
|
+
* @param {Function} callback - Function that renders each item (item, index) => ValidChild
|
|
15
|
+
* @param {string|Function} [key] - Property name or function to generate unique keys for items
|
|
16
|
+
* @param {Object} [options={}] - Configuration options
|
|
17
|
+
* @param {boolean} [options.shouldKeepItemsInCache=false] - Whether to cache rendered items
|
|
18
|
+
* @returns {AnchorDocumentFragment} Fragment managing the list rendering
|
|
19
|
+
* @example
|
|
20
|
+
* const users = Observable([
|
|
21
|
+
* { id: 1, name: 'John' },
|
|
22
|
+
* { id: 2, name: 'Jane' }
|
|
23
|
+
* ]);
|
|
24
|
+
* ForEach(users, (user) => Div({}, user.name), 'id');
|
|
25
|
+
*
|
|
26
|
+
* // With function key
|
|
27
|
+
* ForEach(items, (item) => Div({}, item), (item) => item.id);
|
|
16
28
|
*/
|
|
17
29
|
export function ForEach(data, callback, key, { shouldKeepItemsInCache = false } = {}) {
|
|
18
30
|
const element = Anchor('ForEach');
|