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.
Files changed (68) hide show
  1. package/dist/native-document.components.min.js +1168 -138
  2. package/dist/native-document.dev.js +792 -217
  3. package/dist/native-document.dev.js.map +1 -1
  4. package/dist/native-document.devtools.min.js +1 -1
  5. package/dist/native-document.min.js +1 -1
  6. package/docs/advanced-components.md +814 -0
  7. package/docs/anchor.md +71 -11
  8. package/docs/cache.md +888 -0
  9. package/docs/conditional-rendering.md +91 -1
  10. package/docs/core-concepts.md +9 -2
  11. package/docs/elements.md +127 -2
  12. package/docs/extending-native-document-element.md +7 -1
  13. package/docs/filters.md +1216 -0
  14. package/docs/getting-started.md +12 -3
  15. package/docs/lifecycle-events.md +10 -2
  16. package/docs/list-rendering.md +453 -54
  17. package/docs/memory-management.md +9 -7
  18. package/docs/native-document-element.md +30 -9
  19. package/docs/native-fetch.md +744 -0
  20. package/docs/observables.md +135 -6
  21. package/docs/routing.md +7 -1
  22. package/docs/state-management.md +7 -1
  23. package/docs/validation.md +8 -1
  24. package/eslint.config.js +3 -3
  25. package/package.json +3 -2
  26. package/readme.md +53 -14
  27. package/src/components/$traits/HasItems.js +42 -1
  28. package/src/components/BaseComponent.js +4 -1
  29. package/src/components/accordion/Accordion.js +112 -8
  30. package/src/components/accordion/AccordionItem.js +93 -4
  31. package/src/components/alert/Alert.js +164 -4
  32. package/src/components/avatar/Avatar.js +236 -22
  33. package/src/components/menu/index.js +1 -2
  34. package/src/core/data/ObservableArray.js +120 -2
  35. package/src/core/data/ObservableChecker.js +50 -0
  36. package/src/core/data/ObservableItem.js +223 -80
  37. package/src/core/data/ObservableWhen.js +36 -6
  38. package/src/core/data/observable-helpers/array.js +12 -3
  39. package/src/core/data/observable-helpers/computed.js +17 -4
  40. package/src/core/data/observable-helpers/object.js +19 -3
  41. package/src/core/elements/control/for-each-array.js +21 -3
  42. package/src/core/elements/control/for-each.js +17 -5
  43. package/src/core/elements/control/show-if.js +31 -15
  44. package/src/core/elements/control/show-when.js +23 -0
  45. package/src/core/elements/control/switch.js +40 -10
  46. package/src/core/utils/cache.js +5 -0
  47. package/src/core/utils/memoize.js +25 -16
  48. package/src/core/utils/prototypes.js +3 -2
  49. package/src/core/wrappers/AttributesWrapper.js +1 -1
  50. package/src/core/wrappers/NDElement.js +41 -1
  51. package/src/core/wrappers/NdPrototype.js +4 -0
  52. package/src/core/wrappers/TemplateCloner.js +13 -10
  53. package/src/core/wrappers/prototypes/bind-class-extensions.js +1 -1
  54. package/src/core/wrappers/prototypes/nd-element-extensions.js +3 -0
  55. package/src/router/Route.js +9 -4
  56. package/src/router/Router.js +28 -9
  57. package/src/router/errors/RouterError.js +0 -1
  58. package/types/control-flow.d.ts +9 -6
  59. package/types/elements.d.ts +6 -3
  60. package/types/filters/index.d.ts +4 -0
  61. package/types/nd-element.d.ts +5 -238
  62. package/types/observable.d.ts +9 -3
  63. package/types/router.d.ts +5 -1
  64. package/types/template-cloner.ts +1 -0
  65. package/types/validator.ts +11 -1
  66. package/utils.d.ts +2 -1
  67. package/utils.js +4 -4
  68. 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
- this.$isCleanedUp = false;
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
- const handleWatcherCallback = function(callbacks, value) {
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
- handleWatcherCallback($currentValueCallbacks, true);
94
+ $currentValueCallbacks(true, $previousValue, operations);
103
95
  }
104
96
  if($previousValueCallbacks) {
105
- handleWatcherCallback($previousValueCallbacks, false);
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
- * @param {*} data
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
- this.$isCleanedUp = true;
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, target = null) {
219
- this.$listeners = this.$listeners ?? [];
220
- if (this.$isCleanedUp) {
221
- DebugManager.warn('Observable subscription', '⚠️ Attempted to subscribe to a cleaned up observable.');
222
- return () => {};
223
- }
224
- if (typeof callback !== 'function') {
225
- throw new NativeDocumentError('Callback must be a function');
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, target);
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
- this.$watchers.set(value, watchValueList);
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
- return () => {
258
- const index = watchValueList.indexOf(callback);
259
- watchValueList?.splice(index, 1);
260
- if(watchValueList.size === 1) {
261
- this.$watchers.set(value, watchValueList[0]);
262
- }
263
- else if(watchValueList.size === 0) {
264
- this.$watchers?.delete(value);
265
- watchValueList = null;
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 unsub = this.subscribe((val) => {
337
+ const handler = (val) => {
275
338
  if (fn(val)) {
276
- unsub();
339
+ this.unsubscribe(handler);
277
340
  callback(val);
278
341
  }
279
- });
280
- return unsub;
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
- ObservableWhen.prototype.isMath = function() {
18
- return this.$observer.$currentValue === this.$target;
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
- ObservableWhen.prototype.isActive = function() {
22
- return this.$observer.$currentValue === this.$target;
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 {{propagation: boolean, deep: boolean, reset: boolean}|null} configs
9
- * @returns {ObservableArray}
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 {{propagation: boolean, deep: boolean, reset: boolean}|null} configs
46
- * @returns {Proxy}
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
- firstItem(deleted[i], garbageFragment);
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.action === 'clear' || !items.length) {
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|ObservableItem} data
12
- * @param {Function} callback
13
- * @param {?Function|?string} key
14
- * @param {{shouldKeepItemsInCache: boolean}?} configs
15
- * @returns {DocumentFragment}
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');