native-document 1.0.13 → 1.0.15

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 (37) hide show
  1. package/dist/native-document.dev.js +1297 -804
  2. package/dist/native-document.min.js +1 -1
  3. package/docs/anchor.md +216 -53
  4. package/docs/conditional-rendering.md +25 -24
  5. package/docs/core-concepts.md +20 -19
  6. package/docs/elements.md +21 -20
  7. package/docs/getting-started.md +28 -27
  8. package/docs/lifecycle-events.md +2 -2
  9. package/docs/list-rendering.md +607 -0
  10. package/docs/memory-management.md +1 -1
  11. package/docs/observables.md +15 -14
  12. package/docs/routing.md +22 -22
  13. package/docs/state-management.md +8 -8
  14. package/docs/validation.md +0 -2
  15. package/index.js +6 -1
  16. package/package.json +1 -1
  17. package/readme.md +5 -4
  18. package/src/data/MemoryManager.js +8 -20
  19. package/src/data/Observable.js +2 -180
  20. package/src/data/ObservableChecker.js +26 -21
  21. package/src/data/ObservableItem.js +158 -79
  22. package/src/data/observable-helpers/array.js +74 -0
  23. package/src/data/observable-helpers/batch.js +22 -0
  24. package/src/data/observable-helpers/computed.js +28 -0
  25. package/src/data/observable-helpers/object.js +111 -0
  26. package/src/elements/anchor.js +54 -9
  27. package/src/elements/control/for-each-array.js +280 -0
  28. package/src/elements/control/for-each.js +100 -56
  29. package/src/elements/index.js +1 -0
  30. package/src/elements/list.js +4 -0
  31. package/src/utils/helpers.js +44 -21
  32. package/src/wrappers/AttributesWrapper.js +5 -18
  33. package/src/wrappers/DocumentObserver.js +58 -29
  34. package/src/wrappers/ElementCreator.js +114 -0
  35. package/src/wrappers/HtmlElementEventsWrapper.js +52 -65
  36. package/src/wrappers/HtmlElementWrapper.js +11 -167
  37. package/src/wrappers/NdPrototype.js +109 -0
@@ -1,84 +1,6 @@
1
1
  var NativeDocument = (function (exports) {
2
2
  'use strict';
3
3
 
4
- function eventWrapper(element, name, callback) {
5
- element.addEventListener(name, callback);
6
- return element;
7
- }
8
-
9
- /**
10
- *
11
- * @param {HTMLElement} element
12
- * @returns {HTMLElement}
13
- */
14
- function HtmlElementEventsWrapper(element) {
15
-
16
- if(!element.nd) {
17
- element.nd = {};
18
- }
19
-
20
- /**
21
- * @param {Object<string,Function>} events
22
- */
23
- element.nd.on = function(events) {
24
- for(const event in events) {
25
- const callback = events[event];
26
- eventWrapper(element, event, callback);
27
- }
28
- return element;
29
- };
30
- element.nd.on.prevent = function(events) {
31
- for(const event in events) {
32
- const callback = events[event];
33
- eventWrapper(element, event, (event) => {
34
- event.preventDefault();
35
- callback && callback(event);
36
- return element;
37
- });
38
- }
39
- return element;
40
- };
41
- const events = {
42
- click: (callback) => eventWrapper(element, 'click', callback),
43
- focus: (callback) => eventWrapper(element, 'focus', callback),
44
- blur: (callback) => eventWrapper(element, 'blur', callback),
45
- input: (callback) => eventWrapper(element, 'input', callback),
46
- change: (callback) => eventWrapper(element, 'change', callback),
47
- keyup: (callback) => eventWrapper(element, 'keyup', callback),
48
- keydown: (callback) => eventWrapper(element, 'keydown', callback),
49
- beforeInput: (callback) => eventWrapper(element, 'beforeinput', callback),
50
- mouseOver: (callback) => eventWrapper(element, 'mouseover', callback),
51
- mouseOut: (callback) => eventWrapper(element, 'mouseout', callback),
52
- mouseDown: (callback) => eventWrapper(element, 'mousedown', callback),
53
- mouseUp: (callback) => eventWrapper(element, 'mouseup', callback),
54
- mouseMove: (callback) => eventWrapper(element, 'mousemove', callback),
55
- hover: (mouseInCallback, mouseOutCallback) => {
56
- element.addEventListener('mouseover', mouseInCallback);
57
- element.addEventListener('mouseout', mouseOutCallback);
58
- },
59
- dropped: (callback) => eventWrapper(element, 'drop', callback),
60
- submit: (callback) => eventWrapper(element, 'submit', callback),
61
- dragEnd: (callback) => eventWrapper(element, 'dragend', callback),
62
- dragStart: (callback) => eventWrapper(element, 'dragstart', callback),
63
- drop: (callback) => eventWrapper(element, 'drop', callback),
64
- dragOver: (callback) => eventWrapper(element, 'dragover', callback),
65
- dragEnter: (callback) => eventWrapper(element, 'dragenter', callback),
66
- dragLeave: (callback) => eventWrapper(element, 'dragleave', callback),
67
- };
68
- for(let event in events) {
69
- element.nd.on[event] = events[event];
70
- element.nd.on.prevent[event] = function(callback) {
71
- eventWrapper(element, event.toLowerCase(), (event) => {
72
- event.preventDefault();
73
- callback && callback(event);
74
- });
75
- return element;
76
- };
77
- }
78
-
79
- return element;
80
- }
81
-
82
4
  const DebugManager = {
83
5
  enabled: false,
84
6
 
@@ -111,38 +33,25 @@ var NativeDocument = (function (exports) {
111
33
 
112
34
  const MemoryManager = (function() {
113
35
 
114
- let $nexObserverId = 0;
36
+ let $nextObserverId = 0;
115
37
  const $observables = new Map();
116
- let $registry = null;
117
- try {
118
- $registry = new FinalizationRegistry((heldValue) => {
119
- DebugManager.log('MemoryManager', '🧹 Auto-cleanup observable:', heldValue);
120
- heldValue.listeners.splice(0);
121
- });
122
- } catch (e) {
123
- DebugManager.warn('MemoryManager', 'FinalizationRegistry not supported, observables will not be cleaned automatically');
124
- }
125
38
 
126
39
  return {
127
40
  /**
128
41
  * Register an observable and return an id.
129
42
  *
130
43
  * @param {ObservableItem} observable
131
- * @param {Function[]} listeners
44
+ * @param {Function} getListeners
132
45
  * @returns {number}
133
46
  */
134
- register(observable, listeners) {
135
- const id = ++$nexObserverId;
136
- const heldValue = {
137
- id: id,
138
- listeners
139
- };
140
- if($registry) {
141
- $registry.register(observable, heldValue);
142
- }
47
+ register(observable) {
48
+ const id = ++$nextObserverId;
143
49
  $observables.set(id, new WeakRef(observable));
144
50
  return id;
145
51
  },
52
+ unregister(id) {
53
+ $observables.delete(id);
54
+ },
146
55
  getObservableById(id) {
147
56
  return $observables.get(id)?.deref();
148
57
  },
@@ -193,31 +102,36 @@ var NativeDocument = (function (exports) {
193
102
  function ObservableChecker($observable, $checker) {
194
103
  this.observable = $observable;
195
104
  this.checker = $checker;
105
+ this.unSubscriptions = [];
106
+ }
196
107
 
197
- this.subscribe = function(callback) {
198
- return $observable.subscribe((value) => {
199
- callback && callback($checker(value));
200
- });
201
- };
108
+ ObservableChecker.prototype.subscribe = function(callback) {
109
+ const unSubscribe = this.observable.subscribe((value) => {
110
+ callback && callback(this.checker(value));
111
+ });
112
+ this.unSubscriptions.push(unSubscribe);
113
+ return unSubscribe;
114
+ };
202
115
 
203
- this.val = function() {
204
- return $checker && $checker($observable.val());
205
- };
206
- this.check = function(callback) {
207
- return $observable.check(() => callback(this.val()));
208
- };
116
+ ObservableChecker.prototype.check = function(callback) {
117
+ return this.observable.check(() => callback(this.val()));
118
+ };
209
119
 
210
- this.set = function(value) {
211
- return $observable.set(value);
212
- };
213
- this.trigger = function() {
214
- return $observable.trigger();
215
- };
120
+ ObservableChecker.prototype.val = function() {
121
+ return this.checker && this.checker(this.observable.val());
122
+ };
216
123
 
217
- this.cleanup = function() {
218
- return $observable.cleanup();
219
- };
220
- }
124
+ ObservableChecker.prototype.set = function(value) {
125
+ return this.observable.set(value);
126
+ };
127
+
128
+ ObservableChecker.prototype.trigger = function() {
129
+ return this.observable.trigger();
130
+ };
131
+
132
+ ObservableChecker.prototype.cleanup = function() {
133
+ return this.observable.cleanup();
134
+ };
221
135
 
222
136
  /**
223
137
  *
@@ -232,104 +146,183 @@ var NativeDocument = (function (exports) {
232
146
  throw new NativeDocumentError('ObservableItem cannot be an Observable');
233
147
  }
234
148
 
235
- const $initialValue = (typeof value === 'object') ? JSON.parse(JSON.stringify(value)) : value;
149
+ this.$previousValue = value;
150
+ this.$currentValue = value;
151
+ this.$isCleanedUp = false;
236
152
 
237
- let $previousValue = value;
238
- let $currentValue = value;
239
- let $isCleanedUp = false;
153
+ this.$listeners = null;
154
+ this.$watchers = null;
240
155
 
241
- const $listeners = [];
156
+ this.$memoryId = MemoryManager.register(this);
157
+ }
158
+
159
+ Object.defineProperty(ObservableItem.prototype, '$value', {
160
+ get() {
161
+ return this.$currentValue;
162
+ },
163
+ set(value) {
164
+ this.set(value);
165
+ },
166
+ configurable: true,
167
+ });
242
168
 
243
- const $memoryId = MemoryManager.register(this, $listeners);
169
+ ObservableItem.prototype.triggerListeners = function(operations) {
170
+ const $listeners = this.$listeners;
171
+ const $previousValue = this.$previousValue;
172
+ const $currentValue = this.$currentValue;
244
173
 
245
- this.trigger = () => {
246
- $listeners.forEach(listener => {
247
- try {
248
- listener($currentValue, $previousValue);
249
- } catch (error) {
250
- DebugManager.error('Listener Undefined', 'Error in observable listener:', error);
251
- this.unsubscribe(listener);
174
+ operations = operations || {};
175
+ if($listeners?.length) {
176
+ for(let i = 0, length = $listeners.length; i < length; i++) {
177
+ $listeners[i]($currentValue, $previousValue, operations);
178
+ }
179
+ }
180
+ };
181
+
182
+ ObservableItem.prototype.triggerWatchers = function() {
183
+ if(!this.$watchers) {
184
+ return;
185
+ }
186
+
187
+ const $watchers = this.$watchers;
188
+ const $previousValue = this.$previousValue;
189
+ const $currentValue = this.$currentValue;
190
+
191
+ if($watchers.has($currentValue)) {
192
+ const watchValueList = $watchers.get($currentValue);
193
+ watchValueList.forEach(itemValue => {
194
+ if(itemValue.ifTrue.called) {
195
+ return;
196
+ }
197
+ itemValue.ifTrue.callback();
198
+ itemValue.else.called = false;
199
+ });
200
+ }
201
+ if($watchers.has($previousValue)) {
202
+ const watchValueList = $watchers.get($previousValue);
203
+ watchValueList.forEach(itemValue => {
204
+ if(itemValue.else.called) {
205
+ return;
252
206
  }
207
+ itemValue.else.callback();
208
+ itemValue.ifTrue.called = false;
253
209
  });
210
+ }
211
+ };
254
212
 
255
- };
213
+ ObservableItem.prototype.trigger = function(operations) {
214
+ this.triggerListeners(operations);
215
+ this.triggerWatchers();
216
+ };
217
+
218
+ /**
219
+ * @param {*} data
220
+ */
221
+ ObservableItem.prototype.set = function(data) {
222
+ const newValue = (typeof data === 'function') ? data(this.$currentValue) : data;
223
+ if(this.$currentValue === newValue) {
224
+ return;
225
+ }
226
+ this.$previousValue = this.$currentValue;
227
+ this.$currentValue = newValue;
228
+ this.trigger();
229
+ };
256
230
 
257
- this.originalValue = () => $initialValue;
231
+ ObservableItem.prototype.val = function() {
232
+ return this.$currentValue;
233
+ };
258
234
 
259
- /**
260
- * @param {*} data
261
- */
262
- this.set = (data) => {
263
- const newValue = (typeof data === 'function') ? data($currentValue) : data;
264
- if($currentValue === newValue) {
265
- return;
235
+ ObservableItem.prototype.disconnectAll = function() {
236
+ this.$listeners?.splice(0);
237
+ this.$previousValue = null;
238
+ this.$currentValue = null;
239
+ if(this.$watchers) {
240
+ for (const [_, watchValueList] of this.$watchers) {
241
+ for (const itemValue of watchValueList) {
242
+ itemValue.ifTrue.callback = null;
243
+ itemValue.else.callback = null;
244
+ }
245
+ watchValueList.clear();
266
246
  }
267
- $previousValue = $currentValue;
268
- $currentValue = newValue;
269
- this.trigger();
270
- };
247
+ }
248
+ this.$watchers?.clear();
249
+ this.$listeners = null;
250
+ this.$watchers = null;
251
+ };
252
+ ObservableItem.prototype.cleanup = function() {
253
+ MemoryManager.unregister(this.$memoryId);
254
+ this.disconnectAll();
255
+ this.$isCleanedUp = true;
256
+ delete this.$value;
257
+ };
258
+
259
+ /**
260
+ *
261
+ * @param {Function} callback
262
+ * @returns {(function(): void)}
263
+ */
264
+ ObservableItem.prototype.subscribe = function(callback) {
265
+ this.$listeners = this.$listeners ?? [];
266
+ if (this.$isCleanedUp) {
267
+ DebugManager.warn('Observable subscription', '⚠️ Attempted to subscribe to a cleaned up observable.');
268
+ return () => {};
269
+ }
270
+ if (typeof callback !== 'function') {
271
+ throw new NativeDocumentError('Callback must be a function');
272
+ }
271
273
 
272
- this.val = () => $currentValue;
274
+ this.$listeners.push(callback);
275
+ return () => this.unsubscribe(callback);
276
+ };
273
277
 
274
- this.cleanup = function() {
275
- $listeners.splice(0);
276
- $isCleanedUp = true;
277
- };
278
+ ObservableItem.prototype.on = function(value, callback, elseCallback) {
279
+ this.$watchers = this.$watchers ?? new Map();
278
280
 
279
- /**
280
- *
281
- * @param {Function} callback
282
- * @returns {(function(): void)}
283
- */
284
- this.subscribe = (callback) => {
285
- if ($isCleanedUp) {
286
- DebugManager.warn('Observable subscription', '⚠️ Attempted to subscribe to a cleaned up observable.');
287
- return () => {};
288
- }
289
- if (typeof callback !== 'function') {
290
- throw new NativeDocumentError('Callback must be a function');
291
- }
281
+ let watchValueList = this.$watchers.get(value);
282
+ if(!watchValueList) {
283
+ watchValueList = new Set();
284
+ this.$watchers.set(value, watchValueList);
285
+ }
292
286
 
293
- $listeners.push(callback);
294
- return () => this.unsubscribe(callback);
287
+ let itemValue = {
288
+ ifTrue: { callback, called: false },
289
+ else: { callback: elseCallback, called: false }
295
290
  };
296
-
297
- /**
298
- * Unsubscribe from an observable.
299
- * @param {Function} callback
300
- */
301
- this.unsubscribe = (callback) => {
302
- const index = $listeners.indexOf(callback);
303
- if (index > -1) {
304
- $listeners.splice(index, 1);
291
+ watchValueList.add(itemValue);
292
+ return () => {
293
+ watchValueList?.delete(itemValue);
294
+ if(watchValueList.size === 0) {
295
+ this.$watchers?.delete(value);
305
296
  }
297
+ watchValueList = null;
298
+ itemValue = null;
306
299
  };
300
+ };
307
301
 
308
- /**
309
- * Create an Observable checker instance
310
- * @param callback
311
- * @returns {ObservableChecker}
312
- */
313
- this.check = function(callback) {
314
- return new ObservableChecker(this, callback)
315
- };
316
-
317
- const $object = this;
318
- Object.defineProperty($object, '$value', {
319
- get() {
320
- return $object.val();
321
- },
322
- set(value) {
323
- $object.set(value);
324
- return $object;
325
- }
326
- });
302
+ /**
303
+ * Unsubscribe from an observable.
304
+ * @param {Function} callback
305
+ */
306
+ ObservableItem.prototype.unsubscribe = function(callback) {
307
+ const index = this.$listeners.indexOf(callback);
308
+ if (index > -1) {
309
+ this.$listeners.splice(index, 1);
310
+ }
311
+ };
327
312
 
328
- this.toString = function() {
329
- return '{{#ObItem::(' +$memoryId+ ')}}';
330
- };
313
+ /**
314
+ * Create an Observable checker instance
315
+ * @param callback
316
+ * @returns {ObservableChecker}
317
+ */
318
+ ObservableItem.prototype.check = function(callback) {
319
+ return new ObservableChecker(this, callback)
320
+ };
321
+ ObservableItem.prototype.get = ObservableItem.prototype.check;
331
322
 
332
- }
323
+ ObservableItem.prototype.toString = function() {
324
+ return '{{#ObItem::(' +this.$memoryId+ ')}}';
325
+ };
333
326
 
334
327
  const Validator = {
335
328
  isObservable(value) {
@@ -446,40 +439,182 @@ var NativeDocument = (function (exports) {
446
439
  }
447
440
  };
448
441
 
442
+ const getChildAsNode = (child) => {
443
+ if(Validator.isFunction(child)) {
444
+ return getChildAsNode(child());
445
+ }
446
+ if(Validator.isElement(child)) {
447
+ return child;
448
+ }
449
+ return createTextNode(child)
450
+ };
451
+
452
+ function Anchor(name) {
453
+ const element = document.createDocumentFragment();
454
+
455
+ const anchorStart = document.createComment('Anchor Start : '+name);
456
+ const anchorEnd = document.createComment('/ Anchor End '+name);
457
+
458
+ element.appendChild(anchorStart);
459
+ element.appendChild(anchorEnd);
460
+
461
+ element.nativeInsertBefore = element.insertBefore;
462
+ element.nativeAppendChild = element.appendChild;
463
+
464
+ const insertBefore = function(parent, child, target) {
465
+ if(parent === element) {
466
+ parent.nativeInsertBefore(getChildAsNode(child), target);
467
+ return;
468
+ }
469
+ parent.insertBefore(getChildAsNode(child), target);
470
+ };
471
+
472
+ element.appendElement = function(child, before = null) {
473
+ if(anchorEnd.parentNode === element) {
474
+ anchorEnd.parentNode.nativeInsertBefore(child, before || anchorEnd);
475
+ return;
476
+ }
477
+ anchorEnd.parentNode?.insertBefore(child, before || anchorEnd);
478
+ };
479
+
480
+ element.appendChild = function(child, before = null) {
481
+ const parent = anchorEnd.parentNode;
482
+ if(!parent) {
483
+ DebugManager.error('Anchor', 'Anchor : parent not found', child);
484
+ return;
485
+ }
486
+ before = before ?? anchorEnd;
487
+ if(Validator.isArray(child)) {
488
+ const fragment = document.createDocumentFragment();
489
+ for(let i = 0, length = child.length; i < length; i++) {
490
+ fragment.appendChild(getChildAsNode(child[i]));
491
+ }
492
+ insertBefore(parent, fragment, before);
493
+ return element;
494
+ }
495
+ insertBefore(parent, child, before);
496
+ };
497
+
498
+ element.removeChildren = function() {
499
+ const parent = anchorEnd.parentNode;
500
+ if(parent === element) {
501
+ return;
502
+ }
503
+ if(parent.firstChild === anchorStart && parent.lastChild === anchorEnd) {
504
+ parent.replaceChildren(anchorStart, anchorEnd);
505
+ return;
506
+ }
507
+
508
+ let itemToRemove = anchorStart.nextSibling, tempItem;
509
+ const fragment = document.createDocumentFragment();
510
+ while(itemToRemove && itemToRemove !== anchorEnd) {
511
+ tempItem = itemToRemove.nextSibling;
512
+ fragment.append(itemToRemove);
513
+ itemToRemove = tempItem;
514
+ }
515
+ fragment.replaceChildren();
516
+ };
517
+ element.remove = function() {
518
+ const parent = anchorEnd.parentNode;
519
+ if(parent === element) {
520
+ return;
521
+ }
522
+ let itemToRemove = anchorStart.nextSibling, tempItem;
523
+ while(itemToRemove !== anchorEnd) {
524
+ tempItem = itemToRemove.nextSibling;
525
+ element.nativeAppendChild(itemToRemove);
526
+ itemToRemove = tempItem;
527
+ }
528
+ };
529
+
530
+ element.removeWithAnchors = function() {
531
+ element.removeChildren();
532
+ anchorStart.remove();
533
+ anchorEnd.remove();
534
+ };
535
+
536
+ element.replaceContent = function(child) {
537
+ const parent = anchorEnd.parentNode;
538
+ if(!parent) {
539
+ return;
540
+ }
541
+ if(parent.firstChild === anchorStart && parent.lastChild === anchorEnd) {
542
+ parent.replaceChildren(anchorStart, child, anchorEnd);
543
+ return;
544
+ }
545
+ element.removeChildren();
546
+ parent.insertBefore(child, anchorEnd);
547
+ };
548
+
549
+ element.insertBefore = function(child, anchor = null) {
550
+ element.appendChild(child, anchor);
551
+ };
552
+
553
+ element.clear = function() {
554
+ element.remove();
555
+ };
556
+
557
+ element.endElement = function() {
558
+ return anchorEnd;
559
+ };
560
+ element.startElement = function() {
561
+ return anchorStart;
562
+ };
563
+
564
+ return element;
565
+ }
566
+
449
567
  const BOOLEAN_ATTRIBUTES = ['checked', 'selected', 'disabled', 'readonly', 'required', 'autofocus', 'multiple', 'autocomplete', 'hidden', 'contenteditable', 'spellcheck', 'translate', 'draggable', 'async', 'defer', 'autoplay', 'controls', 'loop', 'muted', 'download', 'reversed', 'open', 'default', 'formnovalidate', 'novalidate', 'scoped', 'itemscope', 'allowfullscreen', 'allowpaymentrequest', 'playsinline'];
450
568
 
569
+ const invoke = function(fn, args, context) {
570
+ if(context) {
571
+ fn.apply(context, args);
572
+ } else {
573
+ fn(...args);
574
+ }
575
+ };
451
576
  /**
452
577
  *
453
578
  * @param {Function} fn
454
579
  * @param {number} delay
455
- * @param {{leading?:Boolean, trailing?:Boolean, debounce?:Boolean}}options
580
+ * @param {{leading?:Boolean, trailing?:Boolean, debounce?:Boolean, check: Function}}options
456
581
  * @returns {(function(...[*]): void)|*}
457
582
  */
458
- const throttle = function(fn, delay, options = {}) {
583
+ const debounce = function(fn, delay, options = {}) {
459
584
  let timer = null;
460
- let lastExecTime = 0;
461
- const { leading = true, trailing = true, debounce = false } = options;
585
+ let lastArgs = null;
462
586
 
463
587
  return function(...args) {
464
- const now = Date.now();
465
- if (debounce) {
466
- // debounce mode: reset the timer for each call
467
- clearTimeout(timer);
468
- timer = setTimeout(() => fn.apply(this, args), delay);
469
- return;
470
- }
471
- if (leading && now - lastExecTime >= delay) {
472
- fn.apply(this, args);
473
- lastExecTime = now;
474
- }
475
- if (trailing && !timer) {
476
- timer = setTimeout(() => {
477
- fn.apply(this, args);
478
- lastExecTime = Date.now();
479
- timer = null;
480
- }, delay - (now - lastExecTime));
588
+ const context = options.context === true ? this : null;
589
+ if(options.check) {
590
+ options.check(...args);
481
591
  }
592
+ lastArgs = args;
593
+
594
+ // debounce mode: reset the timer for each call
595
+ clearTimeout(timer);
596
+ timer = setTimeout(() => invoke(fn, lastArgs, context), delay);
597
+ }
598
+ };
599
+
600
+
601
+ /**
602
+ *
603
+ * @param {*} item
604
+ * @param {string|null} defaultKey
605
+ * @param {?Function} key
606
+ * @returns {*}
607
+ */
608
+ const getKey = (item, defaultKey, key) => {
609
+ if(Validator.isFunction(key)) return key(item, defaultKey);
610
+ if(Validator.isObservable(item)) {
611
+ const val = item.val();
612
+ return (val && key) ? val[key] : defaultKey;
482
613
  }
614
+ if(!Validator.isObject(item)) {
615
+ return item;
616
+ }
617
+ return item[key] ?? defaultKey;
483
618
  };
484
619
 
485
620
  const trim = function(str, char) {
@@ -496,45 +631,6 @@ var NativeDocument = (function (exports) {
496
631
  return new ObservableItem(value);
497
632
  }
498
633
 
499
- /**
500
- *
501
- * @param {Function} callback
502
- * @param {Array|Function} dependencies
503
- * @returns {ObservableItem}
504
- */
505
- Observable.computed = function(callback, dependencies = []) {
506
- const initialValue = callback();
507
- const observable = new ObservableItem(initialValue);
508
- const updatedValue = () => observable.set(callback());
509
-
510
- if(Validator.isFunction(dependencies)) {
511
- if(!Validator.isObservable(dependencies.$observer)) {
512
- throw new NativeDocumentError('Observable.computed : dependencies must be valid batch function');
513
- }
514
- dependencies.$observer.subscribe(updatedValue);
515
- return observable;
516
- }
517
-
518
- dependencies.forEach(dependency => dependency.subscribe(updatedValue));
519
-
520
- return observable;
521
- };
522
-
523
- Observable.batch = function(callback) {
524
- const $observer = Observable(0);
525
- const batch = function() {
526
- if(Validator.isAsyncFunction(callback)) {
527
- return (callback(...arguments)).then(() => {
528
- $observer.trigger();
529
- }).catch(error => { throw error; });
530
- }
531
- callback(...arguments);
532
- $observer.trigger();
533
- };
534
- batch.$observer = $observer;
535
- return batch;
536
- };
537
-
538
634
  /**
539
635
  *
540
636
  * @param id
@@ -548,7 +644,6 @@ var NativeDocument = (function (exports) {
548
644
  return item;
549
645
  };
550
646
 
551
-
552
647
  /**
553
648
  *
554
649
  * @param {ObservableItem} observable
@@ -557,144 +652,6 @@ var NativeDocument = (function (exports) {
557
652
  observable.cleanup();
558
653
  };
559
654
 
560
- /**
561
- * Get the value of an observable or an object of observables.
562
- * @param {ObservableItem|Object<ObservableItem>} object
563
- * @returns {{}|*|null}
564
- */
565
- Observable.value = function(data) {
566
- if(Validator.isObservable(data)) {
567
- return data.val();
568
- }
569
- if(Validator.isProxy(data)) {
570
- return data.$val();
571
- }
572
- if(Validator.isArray(data)) {
573
- const result = [];
574
- data.forEach(item => {
575
- result.push(Observable.value(item));
576
- });
577
- return result;
578
- }
579
- return data;
580
- };
581
-
582
- /**
583
- *
584
- * @param {Object} value
585
- * @returns {Proxy}
586
- */
587
- Observable.init = function(value) {
588
- const data = {};
589
- for(const key in value) {
590
- const itemValue = value[key];
591
- if(Validator.isJson(itemValue)) {
592
- data[key] = Observable.init(itemValue);
593
- continue;
594
- }
595
- else if(Validator.isArray(itemValue)) {
596
- data[key] = Observable.array(itemValue);
597
- continue;
598
- }
599
- data[key] = Observable(itemValue);
600
- }
601
-
602
- const $val = function() {
603
- const result = {};
604
- for(const key in data) {
605
- const dataItem = data[key];
606
- if(Validator.isObservable(dataItem)) {
607
- result[key] = dataItem.val();
608
- } else if(Validator.isProxy(dataItem)) {
609
- result[key] = dataItem.$val();
610
- } else {
611
- result[key] = dataItem;
612
- }
613
- }
614
- return result;
615
- };
616
- const $clone = function() {
617
-
618
- };
619
-
620
- return new Proxy(data, {
621
- get(target, property) {
622
- if(property === '__isProxy__') {
623
- return true;
624
- }
625
- if(property === '$val') {
626
- return $val;
627
- }
628
- if(property === '$clone') {
629
- return $clone;
630
- }
631
- if(target[property] !== undefined) {
632
- return target[property];
633
- }
634
- return undefined;
635
- },
636
- set(target, prop, newValue) {
637
- if(target[prop] !== undefined) {
638
- target[prop].set(newValue);
639
- }
640
- }
641
- })
642
- };
643
-
644
- Observable.object = Observable.init;
645
- Observable.json = Observable.init;
646
- Observable.update = function($target, data) {
647
- for(const key in data) {
648
- const targetItem = $target[key];
649
- const newValue = data[key];
650
-
651
- if(Validator.isObservable(targetItem)) {
652
- if(Validator.isArray(newValue)) {
653
- Observable.update(targetItem, newValue);
654
- continue;
655
- }
656
- targetItem.set(newValue);
657
- continue;
658
- }
659
- if(Validator.isProxy(targetItem)) {
660
- Observable.update(targetItem, newValue);
661
- continue;
662
- }
663
- $target[key] = newValue;
664
- }
665
- };
666
- /**
667
- *
668
- * @param {Array} target
669
- * @returns {ObservableItem}
670
- */
671
- Observable.array = function(target) {
672
- if(!Array.isArray(target)) {
673
- throw new NativeDocumentError('Observable.array : target must be an array');
674
- }
675
- const observer = Observable(target);
676
-
677
- const methods = ['push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice'];
678
-
679
- methods.forEach((method) => {
680
- observer[method] = function(...values) {
681
- const target = observer.val();
682
- const result = target[method].apply(target, arguments);
683
- observer.trigger();
684
- return result;
685
- };
686
- });
687
-
688
- const overrideMethods = ['map', 'filter', 'reduce', 'some', 'every', 'find'];
689
- overrideMethods.forEach((method) => {
690
- observer[method] = function(callback) {
691
- return observer.val()[method](callback);
692
- };
693
- });
694
-
695
- return observer;
696
- };
697
-
698
655
  /**
699
656
  * Enable auto cleanup of observables.
700
657
  * @param {Boolean} enable
@@ -713,20 +670,6 @@ var NativeDocument = (function (exports) {
713
670
  setInterval(() => MemoryManager.cleanObservables(threshold), interval);
714
671
  };
715
672
 
716
- /**
717
- *
718
- * @param {HTMLElement} element
719
- * @param {string} className
720
- * @param {string} value
721
- */
722
- const toggleClassItem = function(element, className, value) {
723
- if(value) {
724
- element.classList.add(className);
725
- } else {
726
- element.classList.remove(className);
727
- }
728
- };
729
-
730
673
  /**
731
674
  *
732
675
  * @param {HTMLElement} element
@@ -736,11 +679,11 @@ var NativeDocument = (function (exports) {
736
679
  for(let className in data) {
737
680
  const value = data[className];
738
681
  if(Validator.isObservable(value)) {
739
- toggleClassItem(element, className, value.val());
740
- value.subscribe(newValue => toggleClassItem(element, className, newValue));
682
+ element.classList.toggle(className, value.val());
683
+ value.subscribe(newValue => element.classList.toggle(className, newValue));
741
684
  continue;
742
685
  }
743
- toggleClassItem(element, className, value);
686
+ element.classList.toggle(className, value);
744
687
  }
745
688
  }
746
689
 
@@ -812,8 +755,8 @@ var NativeDocument = (function (exports) {
812
755
  }
813
756
  element.setAttribute(attributeName, newValue);
814
757
  };
815
- value.subscribe(applyValue);
816
758
  applyValue(value.val());
759
+ value.subscribe(applyValue);
817
760
 
818
761
  if(attributeName === 'value') {
819
762
  element.addEventListener('input', () => value.set(element.value));
@@ -862,228 +805,305 @@ var NativeDocument = (function (exports) {
862
805
  continue;
863
806
  }
864
807
  element.setAttribute(attributeName, value);
808
+
865
809
  }
866
810
  return element;
867
811
  }
868
812
 
869
- const DocumentObserver = {
870
- elements: new Map(),
871
- observer: null,
872
- checkMutation: throttle(function() {
873
- for(const [element, data] of DocumentObserver.elements.entries()) {
874
- const isCurrentlyInDom = document.body.contains(element);
875
- if(isCurrentlyInDom && !data.inDom) {
876
- data.inDom = true;
877
- data.mounted.forEach(callback => callback(element));
878
- } else if(!isCurrentlyInDom && data.inDom) {
879
- data.inDom = false;
880
- data.unmounted.forEach(callback => callback(element));
813
+ const $nodeCache = new Map();
814
+ let $textNodeCache = null;
815
+
816
+ const ElementCreator = {
817
+ createTextNode() {
818
+ if(!$textNodeCache) {
819
+ $textNodeCache = document.createTextNode('');
820
+ }
821
+ return $textNodeCache.cloneNode();
822
+ },
823
+ /**
824
+ *
825
+ * @param {HTMLElement|DocumentFragment} parent
826
+ * @param {ObservableItem} observable
827
+ * @returns {Text}
828
+ */
829
+ createObservableNode(parent, observable) {
830
+ const text = ElementCreator.createTextNode();
831
+ observable.subscribe(value => text.nodeValue = String(value));
832
+ text.nodeValue = observable.val();
833
+ parent && parent.appendChild(text);
834
+ return text;
835
+ },
836
+
837
+ /**
838
+ *
839
+ * @param {HTMLElement|DocumentFragment} parent
840
+ * @param {*} value
841
+ * @returns {Text}
842
+ */
843
+ createStaticTextNode(parent, value) {
844
+ let text = ElementCreator.createTextNode();
845
+ text.nodeValue = String(value);
846
+ parent && parent.appendChild(text);
847
+ return text;
848
+ },
849
+ /**
850
+ *
851
+ * @param {string} name
852
+ * @returns {HTMLElement|DocumentFragment}
853
+ */
854
+ createElement(name) {
855
+ if(name) {
856
+ if($nodeCache.has(name)) {
857
+ return $nodeCache.get(name).cloneNode();
858
+ }
859
+ const node = document.createElement(name);
860
+ $nodeCache.set(name, node);
861
+ return node.cloneNode();
862
+ }
863
+ return new Anchor('Fragment');
864
+ },
865
+ /**
866
+ *
867
+ * @param {*} children
868
+ * @param {HTMLElement|DocumentFragment} parent
869
+ */
870
+ processChildren(children, parent) {
871
+ if(children === null) return;
872
+ const childrenArray = Array.isArray(children) ? children : [children];
873
+
874
+ for(let i = 0, length = childrenArray.length; i < length; i++) {
875
+ let child = childrenArray[i];
876
+ if (child === null) continue;
877
+ if(Validator.isString(child) && Validator.isFunction(child.resolveObservableTemplate)) {
878
+ child = child.resolveObservableTemplate();
879
+ }
880
+ if(Validator.isFunction(child)) {
881
+ this.processChildren(child(), parent);
882
+ continue;
883
+ }
884
+ if(Validator.isArray(child)) {
885
+ this.processChildren(child, parent);
886
+ continue;
887
+ }
888
+ if (Validator.isElement(child)) {
889
+ parent.appendChild(child);
890
+ continue;
891
+ }
892
+ if (Validator.isObservable(child)) {
893
+ ElementCreator.createObservableNode(parent, child);
894
+ continue;
895
+ }
896
+ if (child) {
897
+ ElementCreator.createStaticTextNode(parent, child);
881
898
  }
882
899
  }
883
- }, 10, { debounce: true }),
900
+ },
884
901
  /**
885
902
  *
886
903
  * @param {HTMLElement} element
887
- * @returns {{watch: (function(): Map<any, any>), disconnect: (function(): boolean), mounted: (function(*): Set<any>), unmounted: (function(*): Set<any>)}}
904
+ * @param {Object} attributes
888
905
  */
889
- watch: function(element) {
890
- let data = {};
891
- if(DocumentObserver.elements.has(element)) {
892
- data = DocumentObserver.elements.get(element);
893
- } else {
894
- const inDom = document.body.contains(element);
895
- data = {
896
- inDom,
897
- mounted: new Set(),
898
- unmounted: new Set(),
899
- };
900
- DocumentObserver.elements.set(element, data);
906
+ processAttributes(element, attributes) {
907
+ if(Validator.isFragment(element)) return;
908
+ if (attributes) {
909
+ AttributesWrapper(element, attributes);
901
910
  }
902
-
903
- return {
904
- watch: () => DocumentObserver.elements.set(element, data),
905
- disconnect: () => DocumentObserver.elements.delete(element),
906
- mounted: (callback) => data.mounted.add(callback),
907
- unmounted: (callback) => data.unmounted.add(callback)
908
- };
911
+ },
912
+ /**
913
+ *
914
+ * @param {HTMLElement} element
915
+ * @param {Object} attributes
916
+ * @param {?Function} customWrapper
917
+ * @returns {HTMLElement|DocumentFragment}
918
+ */
919
+ setup(element, attributes, customWrapper) {
920
+ return element;
909
921
  }
910
922
  };
911
923
 
912
- DocumentObserver.observer = new MutationObserver(DocumentObserver.checkMutation);
913
- DocumentObserver.observer.observe(document.body, {
914
- childList: true,
915
- subtree: true,
916
- });
917
-
918
- const getChildAsNode = (child) => {
919
- if(Validator.isFunction(child)) {
920
- return getChildAsNode(child());
921
- }
922
- if(Validator.isElement(child)) {
923
- return child;
924
- }
925
- return createTextNode(child)
926
- };
927
-
928
- function Anchor(name) {
929
- const element = document.createDocumentFragment();
930
-
931
- const anchorStart = document.createComment('Anchor Start : '+name);
932
- const anchorEnd = document.createComment('/ Anchor End '+name);
933
-
934
- element.appendChild(anchorStart);
935
- element.appendChild(anchorEnd);
936
-
937
- element.nativeInsertBefore = element.insertBefore;
938
- element.nativeAppendChild = element.appendChild;
939
-
940
- const insertBefore = function(parent, child, target) {
941
- if(parent === element) {
942
- parent.nativeInsertBefore(getChildAsNode(child), target);
943
- return;
944
- }
945
- parent.insertBefore(getChildAsNode(child), target);
946
- };
947
-
948
- element.appendChild = function(child, before = null) {
949
- const parent = anchorEnd.parentNode;
950
- if(!parent) {
951
- DebugManager.error('Anchor', 'Anchor : parent not found', child);
952
- return;
953
- }
954
- before = before ?? anchorEnd;
955
- if(Validator.isArray(child)) {
956
- child.forEach((element) => {
957
- insertBefore(parent, element, before);
958
- });
959
- return element;
960
- }
961
- insertBefore(parent, child, before);
962
- };
924
+ const DocumentObserver = {
925
+ mounted: new WeakMap(),
926
+ mountedSupposedSize: 0,
927
+ unmounted: new WeakMap(),
928
+ unmountedSupposedSize: 0,
929
+ observer: null,
930
+ checkMutation: debounce(function(mutationsList) {
931
+ for(const mutation of mutationsList) {
932
+ if(DocumentObserver.mountedSupposedSize > 0 ) {
933
+ for(const node of mutation.addedNodes) {
934
+ const data = DocumentObserver.mounted.get(node);
935
+ if(!data) {
936
+ continue;
937
+ }
938
+ data.inDom = true;
939
+ data.mounted && data.mounted(node);
940
+ }
941
+ }
963
942
 
964
- element.remove = function(trueRemove) {
965
- if(anchorEnd.parentNode === element) {
966
- return;
967
- }
968
- let itemToRemove = anchorStart.nextSibling, tempItem;
969
- while(itemToRemove !== anchorEnd) {
970
- tempItem = itemToRemove.nextSibling;
971
- trueRemove ? itemToRemove.remove() : element.nativeAppendChild(itemToRemove);
972
- itemToRemove = tempItem;
973
- }
974
- if(trueRemove) {
975
- anchorEnd.remove();
976
- anchorStart.remove();
943
+ if(DocumentObserver.unmountedSupposedSize > 0 ) {
944
+ for(const node of mutation.removedNodes) {
945
+ const data = DocumentObserver.unmounted.get(node);
946
+ if(!data) {
947
+ continue;
948
+ }
949
+
950
+ data.inDom = false;
951
+ if(data.unmounted && data.unmounted(node) === true) {
952
+ data.disconnect();
953
+ node.nd?.remove();
954
+ }
955
+ }
956
+ }
977
957
  }
978
- };
979
-
980
- element.insertBefore = function(child, anchor = null) {
981
- element.appendChild(child, anchor);
982
- };
983
-
984
- element.clear = function() {
985
- element.remove();
986
- };
987
-
988
- element.endElement = function() {
989
- return anchorEnd;
990
- };
991
- element.startElement = function() {
992
- return anchorStart;
993
- };
994
-
995
- return element;
996
- }
997
-
998
- const pluginsManager = (function() {
999
-
1000
- const $plugins = [];
1001
-
1002
- return {
1003
- list : () => $plugins,
1004
- add : (plugin) => $plugins.push(plugin)
1005
- };
1006
- }());
1007
-
1008
- /**
1009
- *
1010
- * @param {HTMLElement|DocumentFragment} parent
1011
- * @param {ObservableItem} observable
1012
- * @returns {Text}
1013
- */
1014
- const createObservableNode = function(parent, observable) {
1015
- const text = document.createTextNode('');
1016
- observable.subscribe(value => text.textContent = String(value));
1017
- text.textContent = observable.val();
1018
- parent && parent.appendChild(text);
1019
- return text;
1020
- };
958
+ }, 16),
959
+ /**
960
+ *
961
+ * @param {HTMLElement} element
962
+ * @param {boolean} inDom
963
+ * @returns {{watch: (function(): Map<any, any>), disconnect: (function(): boolean), mounted: (function(*): Set<any>), unmounted: (function(*): Set<any>)}}
964
+ */
965
+ watch: function(element, inDom = false) {
966
+ let data = {
967
+ inDom,
968
+ mounted: null,
969
+ unmounted: null,
970
+ disconnect: () => {
971
+ DocumentObserver.mounted.delete(element);
972
+ DocumentObserver.unmounted.delete(element);
973
+ DocumentObserver.mountedSupposedSize--;
974
+ DocumentObserver.unmountedSupposedSize--;
975
+ data = null;
976
+ }
977
+ };
1021
978
 
1022
- /**
1023
- *
1024
- * @param {HTMLElement|DocumentFragment} parent
1025
- * @param {*} value
1026
- * @returns {Text}
1027
- */
1028
- const createStaticTextNode = function(parent, value) {
1029
- const text = document.createTextNode('');
1030
- text.textContent = String(value);
1031
- parent && parent.appendChild(text);
1032
- return text;
979
+ return {
980
+ disconnect: data.disconnect,
981
+ mounted: (callback) => {
982
+ data.mounted = callback;
983
+ DocumentObserver.mounted.set(element, data);
984
+ DocumentObserver.mountedSupposedSize++;
985
+ },
986
+ unmounted: (callback) => {
987
+ data.unmounted = callback;
988
+ DocumentObserver.unmounted.set(element, data);
989
+ DocumentObserver.unmountedSupposedSize++;
990
+ }
991
+ };
992
+ }
1033
993
  };
1034
994
 
1035
- /**
1036
- *
1037
- * @param {HTMLElement} element
1038
- */
1039
- const addUtilsMethods = function(element) {
1040
- element.nd.wrap = (callback) => {
1041
- if(!Validator.isFunction(callback)) {
1042
- throw new NativeDocumentError('Callback must be a function');
1043
- }
1044
- callback && callback(element);
1045
- return element;
1046
- };
1047
- element.nd.ref = (target, name) => {
1048
- target[name] = element;
1049
- return element;
1050
- };
1051
-
1052
- let $observer = null;
1053
-
1054
- element.nd.appendChild = function(child) {
1055
- if(Validator.isArray(child)) {
1056
- ElementCreator.processChildren(child, element);
1057
- return;
1058
- }
1059
- if(Validator.isFunction(child)) {
1060
- child = child();
1061
- ElementCreator.processChildren(child(), element);
1062
- }
1063
- if(Validator.isElement(child)) {
1064
- ElementCreator.processChildren(child, element);
1065
- }
1066
- };
1067
-
1068
- element.nd.lifecycle = function(states) {
1069
- $observer = $observer || DocumentObserver.watch(element);
995
+ DocumentObserver.observer = new MutationObserver(DocumentObserver.checkMutation);
996
+ DocumentObserver.observer.observe(document.body, {
997
+ childList: true,
998
+ subtree: true,
999
+ });
1070
1000
 
1071
- states.mounted && $observer.mounted(states.mounted);
1072
- states.unmounted && $observer.unmounted(states.unmounted);
1073
- return element;
1074
- };
1001
+ Object.defineProperty(HTMLElement.prototype, 'nd', {
1002
+ get() {
1003
+ if(this.$ndProx) {
1004
+ return this.$ndProx;
1005
+ }
1006
+ let element = this;
1007
+ let lifecycle = null;
1008
+
1009
+ this.$ndProx = new Proxy({}, {
1010
+ get(target, property) {
1011
+ if(/^on[A-Z]/.test(property)) {
1012
+ const event = property.replace(/^on/, '').toLowerCase();
1013
+ const shouldPrevent = event.toLowerCase().startsWith('prevent');
1014
+ let eventName = event.replace(/^prevent/i, '');
1015
+ const shouldStop = event.toLowerCase().startsWith('stop');
1016
+ eventName = eventName.replace(/^stop/i, '');
1017
+
1018
+ return function(callback) {
1019
+ if(shouldPrevent && !shouldStop) {
1020
+ element.addEventListener(eventName, function(event) {
1021
+ event.preventDefault();
1022
+ callback(event);
1023
+ });
1024
+ return element;
1025
+ }
1026
+ if(!shouldPrevent && shouldStop) {
1027
+ element.addEventListener(eventName, function(event) {
1028
+ event.stopPropagation();
1029
+ callback(event);
1030
+ });
1031
+ return element;
1032
+ }
1033
+ if(shouldPrevent && shouldStop) {
1034
+ element.addEventListener(eventName, function(event) {
1035
+ event.preventDefault();
1036
+ event.stopPropagation();
1037
+ callback(event);
1038
+ });
1039
+ return element;
1040
+ }
1041
+ element.addEventListener(eventName, callback);
1042
+ return element;
1043
+ };
1044
+ }
1045
+ if(property === 'ref') {
1046
+ if(ref) {
1047
+ return ref;
1048
+ }
1049
+ return function(target, name) {
1050
+ target[name] = element;
1051
+ return element;
1052
+ };
1053
+ }
1054
+ if(property === 'unmountChildren') {
1055
+ return () => {
1056
+ for(let i = 0, length = element.children.length; i < length; i++) {
1057
+ let elementchildren = element.children[i];
1058
+ if(!elementchildren.$ndProx) {
1059
+ elementchildren.nd?.remove();
1060
+ }
1061
+ elementchildren = null;
1062
+ }
1063
+ };
1064
+ }
1065
+ if(property === 'remove') {
1066
+ return function() {
1067
+ element.nd.unmountChildren();
1068
+ lifecycle = null;
1069
+ element.$ndProx = null;
1070
+ delete element.nd?.on?.prevent;
1071
+ delete element.nd?.on;
1072
+ delete element.nd;
1073
+ };
1074
+ }
1075
+ if(property === 'hasLifecycle') {
1076
+ return lifecycle !== null;
1077
+ }
1078
+ if(property === 'lifecycle') {
1079
+ if(lifecycle) {
1080
+ return lifecycle;
1081
+ }
1082
+ let $observer = null;
1083
+ lifecycle = function(states) {
1084
+ $observer = $observer || DocumentObserver.watch(element);
1085
+
1086
+ states.mounted && $observer.mounted(states.mounted);
1087
+ states.unmounted && $observer.unmounted(states.unmounted);
1088
+ return element;
1089
+ };
1090
+ return lifecycle;
1091
+ }
1092
+ if(property === 'mounted' || property === 'unmounted') {
1093
+ return function(callback) {
1094
+ element.nd.lifecycle({ [property]: callback});
1095
+ return element;
1096
+ };
1097
+ }
1098
+ },
1099
+ set(target, p, newValue, receiver) {
1075
1100
 
1076
- element.nd.mounted = (callback) => {
1077
- $observer = $observer || DocumentObserver.watch(element);
1078
- $observer.mounted(callback);
1079
- return element;
1080
- };
1081
- element.nd.unmounted = (callback) => {
1082
- $observer = $observer || DocumentObserver.watch(element);
1083
- $observer.unmounted(callback);
1084
- return element;
1085
- };
1086
- };
1101
+ },
1102
+ configurable: true
1103
+ });
1104
+ return this.$ndProx;
1105
+ }
1106
+ });
1087
1107
 
1088
1108
  /**
1089
1109
  *
@@ -1092,84 +1112,10 @@ var NativeDocument = (function (exports) {
1092
1112
  */
1093
1113
  const createTextNode = function(value) {
1094
1114
  return (Validator.isObservable(value))
1095
- ? createObservableNode(null, value)
1096
- : createStaticTextNode(null, value);
1115
+ ? ElementCreator.createObservableNode(null, value)
1116
+ : ElementCreator.createStaticTextNode(null, value);
1097
1117
  };
1098
1118
 
1099
- const ElementCreator = {
1100
- /**
1101
- *
1102
- * @param {string} name
1103
- * @returns {HTMLElement|DocumentFragment}
1104
- */
1105
- createElement(name) {
1106
- return name ? document.createElement(name) : new Anchor('Fragment');
1107
- },
1108
- /**
1109
- *
1110
- * @param {*} children
1111
- * @param {HTMLElement|DocumentFragment} parent
1112
- */
1113
- processChildren(children, parent) {
1114
- if(children === null) return;
1115
- const childrenArray = Array.isArray(children) ? children : [children];
1116
- childrenArray.forEach(child => {
1117
- if (child === null) return;
1118
- if(Validator.isString(child) && Validator.isFunction(child.resolveObservableTemplate)) {
1119
- child = child.resolveObservableTemplate();
1120
- }
1121
- if(Validator.isFunction(child)) {
1122
- this.processChildren(child(), parent);
1123
- return;
1124
- }
1125
- if(Validator.isArray(child)) {
1126
- this.processChildren(child, parent);
1127
- return;
1128
- }
1129
- if (Validator.isElement(child)) {
1130
- parent.appendChild(child);
1131
- return;
1132
- }
1133
- if (Validator.isObservable(child)) {
1134
- createObservableNode(parent, child);
1135
- return;
1136
- }
1137
- if (child) {
1138
- createStaticTextNode(parent, child);
1139
- }
1140
- });
1141
- },
1142
- /**
1143
- *
1144
- * @param {HTMLElement} element
1145
- * @param {Object} attributes
1146
- */
1147
- processAttributes(element, attributes) {
1148
- if(Validator.isFragment(element)) return;
1149
- if (attributes) {
1150
- AttributesWrapper(element, attributes);
1151
- }
1152
- },
1153
- /**
1154
- *
1155
- * @param {HTMLElement} element
1156
- * @param {Object} attributes
1157
- * @param {?Function} customWrapper
1158
- * @returns {HTMLElement|DocumentFragment}
1159
- */
1160
- setup(element, attributes, customWrapper) {
1161
- element.nd = {};
1162
- HtmlElementEventsWrapper(element);
1163
- const item = (typeof customWrapper === 'function') ? customWrapper(element) : element;
1164
- addUtilsMethods(item);
1165
-
1166
- pluginsManager.list().forEach(plugin => {
1167
- plugin?.element?.setup && plugin.element.setup(item, attributes);
1168
- });
1169
-
1170
- return item;
1171
- }
1172
- };
1173
1119
 
1174
1120
  /**
1175
1121
  *
@@ -1178,7 +1124,7 @@ var NativeDocument = (function (exports) {
1178
1124
  * @returns {Function}
1179
1125
  */
1180
1126
  function HtmlElementWrapper(name, customWrapper) {
1181
- const $tagName = name.toLowerCase().trim();
1127
+ const $tagName = name.toLowerCase();
1182
1128
 
1183
1129
  const builder = function(attributes, children = null) {
1184
1130
  try {
@@ -1188,11 +1134,12 @@ var NativeDocument = (function (exports) {
1188
1134
  attributes = tempChildren;
1189
1135
  }
1190
1136
  const element = ElementCreator.createElement($tagName);
1137
+ const finalElement = (typeof customWrapper === 'function') ? customWrapper(element) : element;
1191
1138
 
1192
- ElementCreator.processAttributes(element, attributes);
1193
- ElementCreator.processChildren(children, element);
1139
+ ElementCreator.processAttributes(finalElement, attributes);
1140
+ ElementCreator.processChildren(children, finalElement);
1194
1141
 
1195
- return ElementCreator.setup(element, attributes, customWrapper);
1142
+ return ElementCreator.setup(finalElement, attributes, customWrapper);
1196
1143
  } catch (error) {
1197
1144
  DebugManager.error('ElementCreation', `Error creating ${$tagName}`, error);
1198
1145
  }
@@ -1276,75 +1223,298 @@ var NativeDocument = (function (exports) {
1276
1223
  if (!schema.optional) {
1277
1224
  errors.push(`${fnName}: Missing required argument '${schema.name}' at position ${position}`);
1278
1225
  }
1279
- return;
1226
+ return;
1227
+ }
1228
+
1229
+ if (!schema.validate(value)) {
1230
+ const valueTypeOf = value?.constructor?.name || typeof value;
1231
+ errors.push(`${fnName}: Invalid argument '${schema.name}' at position ${position}, expected ${schema.type}, got ${valueTypeOf}`);
1232
+ }
1233
+ });
1234
+
1235
+ if (errors.length > 0) {
1236
+ throw new ArgTypesError(`Argument validation failed`, errors);
1237
+ }
1238
+ };
1239
+
1240
+ /**
1241
+ * @param {Function} fn
1242
+ * @param {Array} argSchema
1243
+ * @param {string} fnName
1244
+ * @returns {Function}
1245
+ */
1246
+ const withValidation = (fn, argSchema, fnName = 'Function') => {
1247
+ if(!Validator.isArray(argSchema)) {
1248
+ throw new NativeDocumentError('withValidation : argSchema must be an array');
1249
+ }
1250
+ return function(...args) {
1251
+ validateArgs(args, argSchema, fn.name || fnName);
1252
+ return fn.apply(this, args);
1253
+ };
1254
+ };
1255
+
1256
+ Function.prototype.args = function(...args) {
1257
+ return withValidation(this, args);
1258
+ };
1259
+
1260
+ Function.prototype.errorBoundary = function(callback) {
1261
+ return (...args) => {
1262
+ try {
1263
+ return this.apply(this, args);
1264
+ } catch(e) {
1265
+ return callback(e);
1266
+ }
1267
+ };
1268
+ };
1269
+
1270
+ String.prototype.use = function(args) {
1271
+ const value = this;
1272
+
1273
+ return Observable.computed(() => {
1274
+ return value.replace(/\$\{(.*?)}/g, (match, key) => {
1275
+ const data = args[key];
1276
+ if(Validator.isObservable(data)) {
1277
+ return data.val();
1278
+ }
1279
+ return data;
1280
+ });
1281
+ }, Object.values(args));
1282
+ };
1283
+
1284
+ String.prototype.resolveObservableTemplate = function() {
1285
+ if(!Validator.containsObservableReference(this)) {
1286
+ return this;
1287
+ }
1288
+ return this.split(/(\{\{#ObItem::\([0-9]+\)\}\})/g).filter(Boolean).map((value) => {
1289
+ if(!Validator.containsObservableReference(value)) {
1290
+ return value;
1291
+ }
1292
+ const [_, id] = value.match(/\{\{#ObItem::\(([0-9]+)\)\}\}/);
1293
+ return Observable.getById(id);
1294
+ });
1295
+ };
1296
+
1297
+ const methods = ['push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice'];
1298
+
1299
+ /**
1300
+ *
1301
+ * @param {Array} target
1302
+ * @returns {ObservableItem}
1303
+ */
1304
+ Observable.array = function(target) {
1305
+ if(!Array.isArray(target)) {
1306
+ throw new NativeDocumentError('Observable.array : target must be an array');
1307
+ }
1308
+ const observer = Observable(target);
1309
+
1310
+ methods.forEach((method) => {
1311
+ observer[method] = function(...values) {
1312
+ const result = observer.val()[method](...values);
1313
+ observer.trigger({ action: method, args: values, result });
1314
+ return result;
1315
+ };
1316
+ });
1317
+
1318
+ observer.clear = function() {
1319
+ observer.$value.length = 0;
1320
+ observer.trigger({ action: 'clear' });
1321
+ return true;
1322
+ };
1323
+
1324
+ observer.remove = function(index) {
1325
+ const deleted = observer.$value.splice(index, 1);
1326
+ if(deleted.length === 0) {
1327
+ return [];
1328
+ }
1329
+ observer.trigger({ action: 'remove', args: [index], result: deleted[0] });
1330
+ return deleted;
1331
+ };
1332
+
1333
+ observer.swap = function(indexA, indexB) {
1334
+ const value = observer.$value;
1335
+ const length = value.length;
1336
+ if(length < indexA || length < indexB) {
1337
+ return false;
1338
+ }
1339
+ if(indexB < indexA) {
1340
+ const temp = indexA;
1341
+ indexA = indexB;
1342
+ indexB = temp;
1343
+ }
1344
+ const elementA = value[indexA];
1345
+ const elementB = value[indexB];
1346
+
1347
+ value[indexA] = elementB;
1348
+ value[indexB] = elementA;
1349
+ observer.trigger({ action: 'swap', args: [indexA, indexB], result: [elementA, elementB] });
1350
+ return true;
1351
+ };
1352
+
1353
+ observer.length = function() {
1354
+ return observer.$value.length;
1355
+ };
1356
+
1357
+ const overrideMethods = ['map', 'filter', 'reduce', 'some', 'every', 'find', 'findIndex', 'concat'];
1358
+ overrideMethods.forEach((method) => {
1359
+ observer[method] = function(...args) {
1360
+ return observer.val()[method](...args);
1361
+ };
1362
+ });
1363
+
1364
+ return observer;
1365
+ };
1366
+
1367
+ /**
1368
+ *
1369
+ * @param {Function} callback
1370
+ * @returns {Function}
1371
+ */
1372
+ Observable.batch = function(callback) {
1373
+ const $observer = Observable(0);
1374
+ const batch = function() {
1375
+ if(Validator.isAsyncFunction(callback)) {
1376
+ return (callback(...arguments)).then(() => {
1377
+ $observer.trigger();
1378
+ }).catch(error => { throw error; });
1379
+ }
1380
+ callback(...arguments);
1381
+ $observer.trigger();
1382
+ };
1383
+ batch.$observer = $observer;
1384
+ return batch;
1385
+ };
1386
+
1387
+ /**
1388
+ *
1389
+ * @param {Object} value
1390
+ * @returns {Proxy}
1391
+ */
1392
+ Observable.init = function(value) {
1393
+ const data = {};
1394
+ for(const key in value) {
1395
+ const itemValue = value[key];
1396
+ if(Validator.isJson(itemValue)) {
1397
+ data[key] = Observable.init(itemValue);
1398
+ continue;
1399
+ }
1400
+ else if(Validator.isArray(itemValue)) {
1401
+ data[key] = Observable.array(itemValue);
1402
+ continue;
1403
+ }
1404
+ data[key] = Observable(itemValue);
1405
+ }
1406
+
1407
+ const $val = function() {
1408
+ const result = {};
1409
+ for(const key in data) {
1410
+ const dataItem = data[key];
1411
+ if(Validator.isObservable(dataItem)) {
1412
+ result[key] = dataItem.val();
1413
+ } else if(Validator.isProxy(dataItem)) {
1414
+ result[key] = dataItem.$value;
1415
+ } else {
1416
+ result[key] = dataItem;
1417
+ }
1418
+ }
1419
+ return result;
1420
+ };
1421
+ const $clone = function() {
1422
+
1423
+ };
1424
+
1425
+ return new Proxy(data, {
1426
+ get(target, property) {
1427
+ if(property === '__isProxy__') {
1428
+ return true;
1429
+ }
1430
+ if(property === '$value') {
1431
+ return $val();
1432
+ }
1433
+ if(property === '$clone') {
1434
+ return $clone;
1435
+ }
1436
+ if(target[property] !== undefined) {
1437
+ return target[property];
1438
+ }
1439
+ return undefined;
1440
+ },
1441
+ set(target, prop, newValue) {
1442
+ if(target[prop] !== undefined) {
1443
+ target[prop].set(newValue);
1444
+ }
1445
+ }
1446
+ })
1447
+ };
1448
+
1449
+ /**
1450
+ * Get the value of an observable or an object of observables.
1451
+ * @param {ObservableItem|Object<ObservableItem>} data
1452
+ * @returns {{}|*|null}
1453
+ */
1454
+ Observable.value = function(data) {
1455
+ if(Validator.isObservable(data)) {
1456
+ return data.val();
1457
+ }
1458
+ if(Validator.isProxy(data)) {
1459
+ return data.$value;
1460
+ }
1461
+ if(Validator.isArray(data)) {
1462
+ const result = [];
1463
+ data.forEach(item => {
1464
+ result.push(Observable.value(item));
1465
+ });
1466
+ return result;
1467
+ }
1468
+ return data;
1469
+ };
1470
+
1471
+
1472
+ Observable.update = function($target, data) {
1473
+ for(const key in data) {
1474
+ const targetItem = $target[key];
1475
+ const newValue = data[key];
1476
+
1477
+ if(Validator.isObservable(targetItem)) {
1478
+ if(Validator.isArray(newValue)) {
1479
+ Observable.update(targetItem, newValue);
1480
+ continue;
1481
+ }
1482
+ targetItem.set(newValue);
1483
+ continue;
1280
1484
  }
1281
-
1282
- if (!schema.validate(value)) {
1283
- const valueTypeOf = value?.constructor?.name || typeof value;
1284
- errors.push(`${fnName}: Invalid argument '${schema.name}' at position ${position}, expected ${schema.type}, got ${valueTypeOf}`);
1485
+ if(Validator.isProxy(targetItem)) {
1486
+ Observable.update(targetItem, newValue);
1487
+ continue;
1285
1488
  }
1286
- });
1287
-
1288
- if (errors.length > 0) {
1289
- throw new ArgTypesError(`Argument validation failed`, errors);
1489
+ $target[key] = newValue;
1290
1490
  }
1291
1491
  };
1292
1492
 
1493
+ Observable.object = Observable.init;
1494
+ Observable.json = Observable.init;
1495
+
1293
1496
  /**
1294
- * @param {Function} fn
1295
- * @param {Array} argSchema
1296
- * @param {string} fnName
1297
- * @returns {Function}
1497
+ *
1498
+ * @param {Function} callback
1499
+ * @param {Array|Function} dependencies
1500
+ * @returns {ObservableItem}
1298
1501
  */
1299
- const withValidation = (fn, argSchema, fnName = 'Function') => {
1300
- if(!Validator.isArray(argSchema)) {
1301
- throw new NativeDocumentError('withValidation : argSchema must be an array');
1302
- }
1303
- return function(...args) {
1304
- validateArgs(args, argSchema, fn.name || fnName);
1305
- return fn.apply(this, args);
1306
- };
1307
- };
1308
-
1309
- Function.prototype.args = function(...args) {
1310
- return withValidation(this, args);
1311
- };
1502
+ Observable.computed = function(callback, dependencies = []) {
1503
+ const initialValue = callback();
1504
+ const observable = new ObservableItem(initialValue);
1505
+ const updatedValue = () => observable.set(callback());
1312
1506
 
1313
- Function.prototype.errorBoundary = function(callback) {
1314
- return (...args) => {
1315
- try {
1316
- return this.apply(this, args);
1317
- } catch(e) {
1318
- return callback(e);
1507
+ if(Validator.isFunction(dependencies)) {
1508
+ if(!Validator.isObservable(dependencies.$observer)) {
1509
+ throw new NativeDocumentError('Observable.computed : dependencies must be valid batch function');
1319
1510
  }
1320
- };
1321
- };
1322
-
1323
- String.prototype.use = function(args) {
1324
- const value = this;
1511
+ dependencies.$observer.subscribe(updatedValue);
1512
+ return observable;
1513
+ }
1325
1514
 
1326
- return Observable.computed(() => {
1327
- return value.replace(/\$\{(.*?)}/g, (match, key) => {
1328
- const data = args[key];
1329
- if(Validator.isObservable(data)) {
1330
- return data.val();
1331
- }
1332
- return data;
1333
- });
1334
- }, Object.values(args));
1335
- };
1515
+ dependencies.forEach(dependency => dependency.subscribe(updatedValue));
1336
1516
 
1337
- String.prototype.resolveObservableTemplate = function() {
1338
- if(!Validator.containsObservableReference(this)) {
1339
- return this;
1340
- }
1341
- return this.split(/(\{\{#ObItem::\([0-9]+\)\}\})/g).filter(Boolean).map((value) => {
1342
- if(!Validator.containsObservableReference(value)) {
1343
- return value;
1344
- }
1345
- const [_, id] = value.match(/\{\{#ObItem::\(([0-9]+)\)\}\}/);
1346
- return Observable.getById(id);
1347
- });
1517
+ return observable;
1348
1518
  };
1349
1519
 
1350
1520
  const Store = (function() {
@@ -1420,36 +1590,6 @@ var NativeDocument = (function (exports) {
1420
1590
  };
1421
1591
  }());
1422
1592
 
1423
- /**
1424
- *
1425
- * @param {*} item
1426
- * @param {string|null} defaultKey
1427
- * @param {?Function} key
1428
- * @returns {*}
1429
- */
1430
- const getKey = (item, defaultKey, key) => {
1431
- if(Validator.isFunction(key)) return key(item, defaultKey);
1432
- if(Validator.isObservable(item)) {
1433
- const val = item.val();
1434
- return (val && key) ? val[key] : defaultKey;
1435
- }
1436
- return item[key] ?? defaultKey;
1437
- };
1438
-
1439
- /**
1440
- *
1441
- * @param {Map} cache
1442
- * @param {Set} keyIds
1443
- */
1444
- const cleanBlockByCache = (cache, keyIds) => {
1445
- for(const [key, {child}] of cache.entries()) {
1446
- if(keyIds.has(key)) {
1447
- continue;
1448
- }
1449
- child.remove();
1450
- }
1451
- };
1452
-
1453
1593
  /**
1454
1594
  *
1455
1595
  * @param {Array|Object|ObservableItem} data
@@ -1460,63 +1600,408 @@ var NativeDocument = (function (exports) {
1460
1600
  function ForEach(data, callback, key) {
1461
1601
  const element = new Anchor('ForEach');
1462
1602
  const blockEnd = element.endElement();
1603
+ element.startElement();
1463
1604
 
1464
1605
  let cache = new Map();
1606
+ let lastKeyOrder = null;
1607
+ const keyIds = new Set();
1608
+
1609
+ const clear = () => {
1610
+ element.removeChildren();
1611
+ cleanCache();
1612
+ };
1613
+
1614
+ const cleanCache = (parent) => {
1615
+ for(const [keyId, cacheItem] of cache.entries()) {
1616
+ if(keyIds.has(keyId)) {
1617
+ continue;
1618
+ }
1619
+ const child = cacheItem.child?.deref();
1620
+ if(parent && child) {
1621
+ parent.removeChild(child);
1622
+ }
1623
+ cacheItem.indexObserver?.cleanup();
1624
+ cacheItem.child = null;
1625
+ cacheItem.indexObserver = null;
1626
+ cache.delete(cacheItem.keyId);
1627
+ lastKeyOrder && lastKeyOrder.delete(cacheItem.keyId);
1628
+ }
1629
+ };
1465
1630
 
1466
1631
  const handleContentItem = (item, indexKey) => {
1467
1632
  const keyId = getKey(item, indexKey, key);
1468
1633
 
1469
1634
  if(cache.has(keyId)) {
1470
- cache.get(keyId).indexObserver.set(indexKey);
1635
+ const cacheItem = cache.get(keyId);
1636
+ cacheItem.indexObserver?.set(indexKey);
1637
+ cacheItem.isNew = false;
1638
+ if(cacheItem.child?.deref()) {
1639
+ return keyId;
1640
+ }
1641
+ cache.delete(keyId);
1471
1642
  }
1472
- else {
1473
- const indexObserver = Observable(indexKey);
1643
+
1644
+ try {
1645
+ const indexObserver = callback.length >= 2 ? Observable(indexKey) : null;
1474
1646
  let child = callback(item, indexObserver);
1475
1647
  if(Validator.isStringOrObservable(child)) {
1476
1648
  child = createTextNode(child);
1477
1649
  }
1478
- cache.set(keyId, { child, indexObserver});
1650
+ cache.set(keyId, { keyId, isNew: true, child: new WeakRef(child), indexObserver});
1651
+ } catch (e) {
1652
+ DebugManager.error('ForEach', `Error creating element for key ${keyId}` , e);
1653
+ throw e;
1479
1654
  }
1480
1655
  return keyId;
1481
1656
  };
1482
- const keyIds = new Set();
1657
+
1658
+ const batchDOMUpdates = (parent) => {
1659
+ const fragment = document.createDocumentFragment();
1660
+ for(const itemKey of keyIds) {
1661
+ const cacheItem = cache.get(itemKey);
1662
+ if(!cacheItem) {
1663
+ continue;
1664
+ }
1665
+ const child = cacheItem.child?.deref();
1666
+ child && fragment.appendChild(child);
1667
+ }
1668
+ parent.insertBefore(fragment, blockEnd);
1669
+ };
1670
+
1671
+ const diffingDOMUpdates = (parent) => {
1672
+ let fragment = document.createDocumentFragment();
1673
+ const newKeys = Array.from(keyIds);
1674
+ Array.from(lastKeyOrder);
1675
+
1676
+ for(const index in newKeys) {
1677
+ const itemKey = newKeys[index];
1678
+ const cacheItem = cache.get(itemKey);
1679
+ if(!cacheItem) {
1680
+ continue;
1681
+ }
1682
+ const child = cacheItem.child.deref();
1683
+ if(!child) {
1684
+ continue;
1685
+ }
1686
+ fragment.appendChild(child);
1687
+ }
1688
+ element.replaceContent(fragment);
1689
+ };
1483
1690
 
1484
1691
  const buildContent = () => {
1485
- const items = (Validator.isObservable(data)) ? data.val() : data;
1486
1692
  const parent = blockEnd.parentNode;
1487
1693
  if(!parent) {
1488
1694
  return;
1489
1695
  }
1696
+
1697
+ const items = (Validator.isObservable(data)) ? data.val() : data;
1490
1698
  keyIds.clear();
1491
1699
  if(Array.isArray(items)) {
1492
- items.forEach((item, index) => keyIds.add(handleContentItem(item, index)));
1700
+ for(let i = 0, length = items.length; i < length; i++) {
1701
+ const keyId= handleContentItem(items[i], i);
1702
+ keyIds.add(keyId);
1703
+ }
1493
1704
  } else {
1494
1705
  for(const indexKey in items) {
1495
- keyIds.add(handleContentItem(items[indexKey], indexKey));
1706
+ const keyId = handleContentItem(items[indexKey], indexKey);
1707
+ keyIds.add(keyId);
1708
+ }
1709
+ }
1710
+
1711
+ if(keyIds.size === 0) {
1712
+ clear();
1713
+ lastKeyOrder?.clear();
1714
+ return;
1715
+ }
1716
+
1717
+ cleanCache(parent);
1718
+ if(!lastKeyOrder || lastKeyOrder.size === 0) {
1719
+ batchDOMUpdates(parent);
1720
+ } else {
1721
+ diffingDOMUpdates();
1722
+ }
1723
+ lastKeyOrder?.clear();
1724
+ lastKeyOrder = new Set([...keyIds]);
1725
+ };
1726
+
1727
+ buildContent();
1728
+ if(Validator.isObservable(data)) {
1729
+ data.subscribe(buildContent);
1730
+ }
1731
+ return element;
1732
+ }
1733
+
1734
+ function ForEachArray(data, callback, key, configs = {}) {
1735
+ const element = new Anchor('ForEach Array');
1736
+ const blockEnd = element.endElement();
1737
+ const blockStart = element.startElement();
1738
+
1739
+ let cache = new Map();
1740
+ let nodeCacheByElement = new WeakMap();
1741
+ let lastNumberOfItems = 0;
1742
+
1743
+ const keysCache = new WeakMap();
1744
+
1745
+ const clear = () => {
1746
+ element.removeChildren();
1747
+ cleanCache();
1748
+ lastNumberOfItems = 0;
1749
+ };
1750
+ const getItemKey = (item, indexKey) => {
1751
+ if(keysCache.has(item)) {
1752
+ return keysCache.get(item);
1753
+ }
1754
+ return getKey(item, indexKey, key);
1755
+ };
1756
+
1757
+ const updateIndexObservers = (items, startFrom = 0) => {
1758
+ if(callback.length < 2) {
1759
+ return;
1760
+ }
1761
+ let index = startFrom;
1762
+ for(let i = startFrom, length = items?.length; i < length; i++) {
1763
+ const cacheItem = cache.get(getItemKey(items[i], i));
1764
+ if(!cacheItem) {
1765
+ continue;
1766
+ }
1767
+ cacheItem.indexObserver?.deref()?.set(index);
1768
+ index++;
1769
+ }
1770
+ };
1771
+
1772
+ const removeCacheItem = (cacheItem, removeChild = true) => {
1773
+ if(!cacheItem) {
1774
+ return;
1775
+ }
1776
+ const child = cacheItem.child?.deref();
1777
+ cacheItem.indexObserver?.deref()?.cleanup();
1778
+ cacheItem.child = null;
1779
+ cacheItem.indexObserver = null;
1780
+ nodeCacheByElement.delete(cacheItem.item);
1781
+ keysCache.delete(cacheItem.item);
1782
+ cacheItem.item = null;
1783
+ if(removeChild) {
1784
+ child?.remove();
1785
+ cache.delete(cacheItem.keyId);
1786
+ }
1787
+ };
1788
+
1789
+ const removeCacheItemByKey = (keyId, removeChild = true) => {
1790
+ removeCacheItem(cache.get(keyId), removeChild);
1791
+ };
1792
+
1793
+ const cleanCache = () => {
1794
+ for (const [keyId, cacheItem] of cache.entries()) {
1795
+ removeCacheItem(cacheItem, false);
1796
+ }
1797
+ cache.clear();
1798
+ };
1799
+
1800
+ const buildItem = (item, indexKey) => {
1801
+ const keyId = getItemKey(item, indexKey);
1802
+
1803
+ if(cache.has(keyId)) {
1804
+ const cacheItem = cache.get(keyId);
1805
+ cacheItem.indexObserver?.deref()?.set(indexKey);
1806
+ cacheItem.isNew = false;
1807
+ const child = cacheItem.child?.deref();
1808
+ if(child) {
1809
+ return child;
1810
+ }
1811
+ cache.delete(keyId);
1812
+ }
1813
+
1814
+ try {
1815
+ const indexObserver = callback.length >= 2 ? Observable(indexKey) : null;
1816
+ let child = callback(item, indexObserver);
1817
+ if(Validator.isStringOrObservable(child)) {
1818
+ child = createTextNode(child);
1496
1819
  }
1820
+ cache.set(keyId, {
1821
+ keyId,
1822
+ isNew: true,
1823
+ item,
1824
+ child: new WeakRef(child),
1825
+ indexObserver: (indexObserver ? new WeakRef(indexObserver) : null)
1826
+ });
1827
+ keysCache.set(item, keyId);
1828
+ if(Validator.isObject(item)) {
1829
+ nodeCacheByElement.set(item, child);
1830
+ }
1831
+ return child;
1832
+ } catch (e) {
1833
+ DebugManager.error('ForEach', `Error creating element for key ${keyId}` , e);
1834
+ throw e;
1835
+ }
1836
+ };
1837
+ const getChildByKey = function(keyId, fragment) {
1838
+ const cacheItem = cache.get(keyId);
1839
+ if(!cacheItem) {
1840
+ return null;
1841
+ }
1842
+ const child = cacheItem.child?.deref();
1843
+ if(!child) {
1844
+ removeCacheItem(cacheItem, false);
1845
+ return null;
1846
+ }
1847
+ return child;
1848
+ };
1849
+
1850
+ const removeByKey = function(keyId, fragment) {
1851
+ const cacheItem = cache.get(keyId);
1852
+ if(!cacheItem) {
1853
+ return null;
1854
+ }
1855
+ const child = cacheItem.child?.deref();
1856
+ if(!child) {
1857
+ return null;
1858
+ }
1859
+
1860
+ if(fragment) {
1861
+ fragment.appendChild(child);
1862
+ return;
1497
1863
  }
1864
+ child.remove();
1865
+ };
1498
1866
 
1499
- cleanBlockByCache(cache, keyIds);
1500
- let nextElementSibling = blockEnd;
1501
- for(const item of [...keyIds].reverse()) {
1502
- const { child } = cache.get(item);
1867
+ const Actions = {
1868
+ toFragment(items, startIndexFrom = 0){
1869
+ const fragment = document.createDocumentFragment();
1870
+ for(let i = 0, length = items.length; i < length; i++) {
1871
+ fragment.append(buildItem(items[i], lastNumberOfItems));
1872
+ lastNumberOfItems++;
1873
+ }
1874
+ return fragment;
1875
+ },
1876
+ add(items, delay = 0) {
1877
+ setTimeout(() => {
1878
+ element.appendElement(Actions.toFragment(items));
1879
+ }, delay);
1880
+ },
1881
+ replace(items) {
1882
+ clear();
1883
+ Actions.add(items);
1884
+ },
1885
+ reOrder(items) {
1886
+ let child = null;
1887
+ const fragment = document.createDocumentFragment();
1888
+ for(const item of items) {
1889
+ child = nodeCacheByElement.get(item);
1890
+ if(child) {
1891
+ fragment.appendChild(child);
1892
+ }
1893
+ }
1894
+ child = null;
1895
+ element.appendElement(fragment, blockEnd);
1896
+ },
1897
+ removeOne(element, index) {
1898
+ let child = nodeCacheByElement.get(element);
1503
1899
  if(child) {
1504
- if(nextElementSibling && nextElementSibling.previousSibling === child) {
1505
- nextElementSibling = child;
1506
- continue;
1900
+ child.remove();
1901
+ nodeCacheByElement.delete(element);
1902
+ removeCacheItemByKey(getItemKey(element, index));
1903
+ }
1904
+ child = null;
1905
+ },
1906
+ clear,
1907
+ push(items) {
1908
+ let delay = 0;
1909
+ if(configs.pushDelay) {
1910
+ delay = configs.pushDelay(items) ?? 0;
1911
+ } else {
1912
+ delay = (items.length >= 1000) ? 10 : 0;
1913
+ }
1914
+ Actions.add(items, delay);
1915
+ },
1916
+ unshift(values){
1917
+ element.insertBefore(Actions.toFragment(values), blockStart.nextSibling);
1918
+ },
1919
+ splice(args, deleted) {
1920
+ const [start, deleteCount, ...values] = args;
1921
+ let elementBeforeFirst = null;
1922
+ const garbageFragment = document.createDocumentFragment();
1923
+
1924
+ if(deleted.length > 0) {
1925
+ let firstKey = getItemKey(deleted[0], start);
1926
+ if(deleted.length === 1) {
1927
+ removeByKey(firstKey, garbageFragment);
1928
+ } else if(deleted.length > 1) {
1929
+ const firstChildRemoved = getChildByKey(firstKey);
1930
+ elementBeforeFirst = firstChildRemoved?.previousSibling;
1931
+
1932
+ for(let i = 0; i < deleted.length; i++) {
1933
+ const keyId = getItemKey(deleted[i], start + i);
1934
+ removeByKey(keyId, garbageFragment);
1935
+ }
1507
1936
  }
1508
- parent.insertBefore(child, nextElementSibling);
1509
- nextElementSibling = child;
1937
+ } else {
1938
+ elementBeforeFirst = blockEnd;
1939
+ }
1940
+ garbageFragment.replaceChildren();
1941
+
1942
+ if(values && values.length && elementBeforeFirst) {
1943
+ element.insertBefore(Actions.toFragment(values), elementBeforeFirst.nextSibling);
1944
+ }
1945
+
1946
+ },
1947
+ reverse(_, reversed) {
1948
+ Actions.reOrder(reversed);
1949
+ },
1950
+ sort(_, sorted) {
1951
+ Actions.reOrder(sorted);
1952
+ },
1953
+ remove(_, deleted) {
1954
+ Actions.removeOne(deleted);
1955
+ },
1956
+ pop(_, deleted) {
1957
+ Actions.removeOne(deleted);
1958
+ },
1959
+ shift(_, deleted) {
1960
+ Actions.removeOne(deleted);
1961
+ },
1962
+ swap(args, elements) {
1963
+ const parent = blockEnd.parentNode;
1964
+
1965
+ let childA = nodeCacheByElement.get(elements[0]);
1966
+ let childB = nodeCacheByElement.get(elements[1]);
1967
+ if(!childA || !childB) {
1968
+ return;
1510
1969
  }
1970
+
1971
+ const childBNext = childB.nextSibling;
1972
+ parent.insertBefore(childB, childA);
1973
+ parent.insertBefore(childA, childBNext);
1974
+ childA = null;
1975
+ childB = null;
1511
1976
  }
1512
1977
  };
1513
1978
 
1514
- buildContent();
1979
+ const buildContent = (items, _, operations) => {
1980
+ if(operations.action === 'clear' || !items.length) {
1981
+ if(lastNumberOfItems === 0) {
1982
+ return;
1983
+ }
1984
+ clear();
1985
+ }
1986
+
1987
+ if(!operations?.action) {
1988
+ if(lastNumberOfItems === 0) {
1989
+ Actions.add(items);
1990
+ return;
1991
+ }
1992
+ Actions.replace(items);
1993
+ }
1994
+ else if(Actions[operations.action]) {
1995
+ Actions[operations.action](operations.args, operations.result);
1996
+ }
1997
+ updateIndexObservers(items, 0);
1998
+ };
1999
+
2000
+ buildContent(data.val(), null, {action: null});
1515
2001
  if(Validator.isObservable(data)) {
1516
- data.subscribe(throttle((newValue, oldValue) => {
1517
- buildContent();
1518
- }, 50, { debounce: true }));
2002
+ data.subscribe(buildContent);
1519
2003
  }
2004
+
1520
2005
  return element;
1521
2006
  }
1522
2007
 
@@ -1851,6 +2336,10 @@ var NativeDocument = (function (exports) {
1851
2336
  const UnorderedList = HtmlElementWrapper('ul');
1852
2337
  const ListItem = HtmlElementWrapper('li');
1853
2338
 
2339
+ const Li = ListItem;
2340
+ const Ol = OrderedList;
2341
+ const Ul = UnorderedList;
2342
+
1854
2343
  const Audio = HtmlElementWrapper('audio');
1855
2344
  const Video = HtmlElementWrapper('video');
1856
2345
  const Source = HtmlElementWrapper('source');
@@ -1919,6 +2408,7 @@ var NativeDocument = (function (exports) {
1919
2408
  FileInput: FileInput,
1920
2409
  Footer: Footer,
1921
2410
  ForEach: ForEach,
2411
+ ForEachArray: ForEachArray,
1922
2412
  Form: Form,
1923
2413
  Fragment: Fragment,
1924
2414
  H1: H1,
@@ -1939,6 +2429,7 @@ var NativeDocument = (function (exports) {
1939
2429
  Label: Label,
1940
2430
  LazyImg: LazyImg,
1941
2431
  Legend: Legend,
2432
+ Li: Li,
1942
2433
  Link: Link$1,
1943
2434
  ListItem: ListItem,
1944
2435
  Main: Main,
@@ -1950,6 +2441,7 @@ var NativeDocument = (function (exports) {
1950
2441
  NativeDocumentFragment: Anchor,
1951
2442
  Nav: Nav,
1952
2443
  NumberInput: NumberInput,
2444
+ Ol: Ol,
1953
2445
  Option: Option,
1954
2446
  OrderedList: OrderedList,
1955
2447
  Output: Output,
@@ -1995,6 +2487,7 @@ var NativeDocument = (function (exports) {
1995
2487
  TimeInput: TimeInput,
1996
2488
  Tr: Tr,
1997
2489
  Track: Track,
2490
+ Ul: Ul,
1998
2491
  UnorderedList: UnorderedList,
1999
2492
  UrlInput: UrlInput,
2000
2493
  Var: Var,