native-document 1.0.14 → 1.0.16

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 (39) hide show
  1. package/dist/native-document.dev.js +1262 -842
  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 +25 -24
  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 +87 -110
  29. package/src/elements/form.js +1 -1
  30. package/src/elements/index.js +1 -0
  31. package/src/elements/list.js +4 -0
  32. package/src/router/link.js +2 -2
  33. package/src/utils/helpers.js +44 -21
  34. package/src/wrappers/AttributesWrapper.js +5 -18
  35. package/src/wrappers/DocumentObserver.js +58 -29
  36. package/src/wrappers/ElementCreator.js +114 -0
  37. package/src/wrappers/HtmlElementEventsWrapper.js +52 -65
  38. package/src/wrappers/HtmlElementWrapper.js +11 -167
  39. package/src/wrappers/NdPrototype.js +106 -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,35 +102,36 @@ var NativeDocument = (function (exports) {
193
102
  function ObservableChecker($observable, $checker) {
194
103
  this.observable = $observable;
195
104
  this.checker = $checker;
196
- const $unSubscriptions = [];
105
+ this.unSubscriptions = [];
106
+ }
197
107
 
198
- this.subscribe = function(callback) {
199
- const unSubscribe = $observable.subscribe((value) => {
200
- callback && callback($checker(value));
201
- });
202
- $unSubscriptions.push(unSubscribe);
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
+ };
203
115
 
204
- return unSubscribe;
205
- };
116
+ ObservableChecker.prototype.check = function(callback) {
117
+ return this.observable.check(() => callback(this.val()));
118
+ };
206
119
 
207
- this.val = function() {
208
- return $checker && $checker($observable.val());
209
- };
210
- this.check = function(callback) {
211
- return $observable.check(() => callback(this.val()));
212
- };
120
+ ObservableChecker.prototype.val = function() {
121
+ return this.checker && this.checker(this.observable.val());
122
+ };
213
123
 
214
- this.set = function(value) {
215
- return $observable.set(value);
216
- };
217
- this.trigger = function() {
218
- return $observable.trigger();
219
- };
124
+ ObservableChecker.prototype.set = function(value) {
125
+ return this.observable.set(value);
126
+ };
220
127
 
221
- this.cleanup = function() {
222
- $unSubscriptions.forEach(unSubscription => unSubscription());
223
- };
224
- }
128
+ ObservableChecker.prototype.trigger = function() {
129
+ return this.observable.trigger();
130
+ };
131
+
132
+ ObservableChecker.prototype.cleanup = function() {
133
+ return this.observable.cleanup();
134
+ };
225
135
 
226
136
  /**
227
137
  *
@@ -236,104 +146,183 @@ var NativeDocument = (function (exports) {
236
146
  throw new NativeDocumentError('ObservableItem cannot be an Observable');
237
147
  }
238
148
 
239
- const $initialValue = (typeof value === 'object') ? JSON.parse(JSON.stringify(value)) : value;
149
+ this.$previousValue = value;
150
+ this.$currentValue = value;
151
+ this.$isCleanedUp = false;
240
152
 
241
- let $previousValue = value;
242
- let $currentValue = value;
243
- let $isCleanedUp = false;
153
+ this.$listeners = null;
154
+ this.$watchers = null;
244
155
 
245
- const $listeners = [];
156
+ this.$memoryId = MemoryManager.register(this);
157
+ }
246
158
 
247
- const $memoryId = MemoryManager.register(this, $listeners);
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
+ });
248
168
 
249
- this.trigger = () => {
250
- $listeners.forEach(listener => {
251
- try {
252
- listener($currentValue, $previousValue);
253
- } catch (error) {
254
- DebugManager.error('Listener Undefined', 'Error in observable listener:', error);
255
- this.unsubscribe(listener);
169
+ ObservableItem.prototype.triggerListeners = function(operations) {
170
+ const $listeners = this.$listeners;
171
+ const $previousValue = this.$previousValue;
172
+ const $currentValue = this.$currentValue;
173
+
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;
256
206
  }
207
+ itemValue.else.callback();
208
+ itemValue.ifTrue.called = false;
257
209
  });
210
+ }
211
+ };
258
212
 
259
- };
213
+ ObservableItem.prototype.trigger = function(operations) {
214
+ this.triggerListeners(operations);
215
+ this.triggerWatchers();
216
+ };
260
217
 
261
- this.originalValue = () => $initialValue;
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
+ };
262
230
 
263
- /**
264
- * @param {*} data
265
- */
266
- this.set = (data) => {
267
- const newValue = (typeof data === 'function') ? data($currentValue) : data;
268
- if($currentValue === newValue) {
269
- return;
231
+ ObservableItem.prototype.val = function() {
232
+ return this.$currentValue;
233
+ };
234
+
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();
270
246
  }
271
- $previousValue = $currentValue;
272
- $currentValue = newValue;
273
- this.trigger();
274
- };
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
+ };
275
258
 
276
- this.val = () => $currentValue;
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
+ }
277
273
 
278
- this.cleanup = function() {
279
- $listeners.splice(0);
280
- $isCleanedUp = true;
281
- };
274
+ this.$listeners.push(callback);
275
+ return () => this.unsubscribe(callback);
276
+ };
282
277
 
283
- /**
284
- *
285
- * @param {Function} callback
286
- * @returns {(function(): void)}
287
- */
288
- this.subscribe = (callback) => {
289
- if ($isCleanedUp) {
290
- DebugManager.warn('Observable subscription', '⚠️ Attempted to subscribe to a cleaned up observable.');
291
- return () => {};
292
- }
293
- if (typeof callback !== 'function') {
294
- throw new NativeDocumentError('Callback must be a function');
295
- }
278
+ ObservableItem.prototype.on = function(value, callback, elseCallback) {
279
+ this.$watchers = this.$watchers ?? new Map();
296
280
 
297
- $listeners.push(callback);
298
- return () => this.unsubscribe(callback);
299
- };
281
+ let watchValueList = this.$watchers.get(value);
282
+ if(!watchValueList) {
283
+ watchValueList = new Set();
284
+ this.$watchers.set(value, watchValueList);
285
+ }
300
286
 
301
- /**
302
- * Unsubscribe from an observable.
303
- * @param {Function} callback
304
- */
305
- this.unsubscribe = (callback) => {
306
- const index = $listeners.indexOf(callback);
307
- if (index > -1) {
308
- $listeners.splice(index, 1);
309
- }
287
+ let itemValue = {
288
+ ifTrue: { callback, called: false },
289
+ else: { callback: elseCallback, called: false }
310
290
  };
311
-
312
- /**
313
- * Create an Observable checker instance
314
- * @param callback
315
- * @returns {ObservableChecker}
316
- */
317
- this.check = function(callback) {
318
- return new ObservableChecker(this, callback)
291
+ watchValueList.add(itemValue);
292
+ return () => {
293
+ watchValueList?.delete(itemValue);
294
+ if(watchValueList.size === 0) {
295
+ this.$watchers?.delete(value);
296
+ }
297
+ watchValueList = null;
298
+ itemValue = null;
319
299
  };
300
+ };
320
301
 
321
- const $object = this;
322
- Object.defineProperty($object, '$value', {
323
- get() {
324
- return $object.val();
325
- },
326
- set(value) {
327
- $object.set(value);
328
- return $object;
329
- }
330
- });
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
+ };
331
312
 
332
- this.toString = function() {
333
- return '{{#ObItem::(' +$memoryId+ ')}}';
334
- };
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;
335
322
 
336
- }
323
+ ObservableItem.prototype.toString = function() {
324
+ return '{{#ObItem::(' +this.$memoryId+ ')}}';
325
+ };
337
326
 
338
327
  const Validator = {
339
328
  isObservable(value) {
@@ -450,40 +439,182 @@ var NativeDocument = (function (exports) {
450
439
  }
451
440
  };
452
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
+
453
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'];
454
568
 
569
+ const invoke = function(fn, args, context) {
570
+ if(context) {
571
+ fn.apply(context, args);
572
+ } else {
573
+ fn(...args);
574
+ }
575
+ };
455
576
  /**
456
577
  *
457
578
  * @param {Function} fn
458
579
  * @param {number} delay
459
- * @param {{leading?:Boolean, trailing?:Boolean, debounce?:Boolean}}options
580
+ * @param {{leading?:Boolean, trailing?:Boolean, debounce?:Boolean, check: Function}}options
460
581
  * @returns {(function(...[*]): void)|*}
461
582
  */
462
- const throttle = function(fn, delay, options = {}) {
583
+ const debounce = function(fn, delay, options = {}) {
463
584
  let timer = null;
464
- let lastExecTime = 0;
465
- const { leading = true, trailing = true, debounce = false } = options;
585
+ let lastArgs = null;
466
586
 
467
587
  return function(...args) {
468
- const now = Date.now();
469
- if (debounce) {
470
- // debounce mode: reset the timer for each call
471
- clearTimeout(timer);
472
- timer = setTimeout(() => fn.apply(this, args), delay);
473
- return;
474
- }
475
- if (leading && now - lastExecTime >= delay) {
476
- fn.apply(this, args);
477
- lastExecTime = now;
478
- }
479
- if (trailing && !timer) {
480
- timer = setTimeout(() => {
481
- fn.apply(this, args);
482
- lastExecTime = Date.now();
483
- timer = null;
484
- }, delay - (now - lastExecTime));
588
+ const context = options.context === true ? this : null;
589
+ if(options.check) {
590
+ options.check(...args);
485
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;
613
+ }
614
+ if(!Validator.isObject(item)) {
615
+ return item;
486
616
  }
617
+ return item[key] ?? defaultKey;
487
618
  };
488
619
 
489
620
  const trim = function(str, char) {
@@ -500,45 +631,6 @@ var NativeDocument = (function (exports) {
500
631
  return new ObservableItem(value);
501
632
  }
502
633
 
503
- /**
504
- *
505
- * @param {Function} callback
506
- * @param {Array|Function} dependencies
507
- * @returns {ObservableItem}
508
- */
509
- Observable.computed = function(callback, dependencies = []) {
510
- const initialValue = callback();
511
- const observable = new ObservableItem(initialValue);
512
- const updatedValue = () => observable.set(callback());
513
-
514
- if(Validator.isFunction(dependencies)) {
515
- if(!Validator.isObservable(dependencies.$observer)) {
516
- throw new NativeDocumentError('Observable.computed : dependencies must be valid batch function');
517
- }
518
- dependencies.$observer.subscribe(updatedValue);
519
- return observable;
520
- }
521
-
522
- dependencies.forEach(dependency => dependency.subscribe(updatedValue));
523
-
524
- return observable;
525
- };
526
-
527
- Observable.batch = function(callback) {
528
- const $observer = Observable(0);
529
- const batch = function() {
530
- if(Validator.isAsyncFunction(callback)) {
531
- return (callback(...arguments)).then(() => {
532
- $observer.trigger();
533
- }).catch(error => { throw error; });
534
- }
535
- callback(...arguments);
536
- $observer.trigger();
537
- };
538
- batch.$observer = $observer;
539
- return batch;
540
- };
541
-
542
634
  /**
543
635
  *
544
636
  * @param id
@@ -552,7 +644,6 @@ var NativeDocument = (function (exports) {
552
644
  return item;
553
645
  };
554
646
 
555
-
556
647
  /**
557
648
  *
558
649
  * @param {ObservableItem} observable
@@ -561,144 +652,6 @@ var NativeDocument = (function (exports) {
561
652
  observable.cleanup();
562
653
  };
563
654
 
564
- /**
565
- * Get the value of an observable or an object of observables.
566
- * @param {ObservableItem|Object<ObservableItem>} object
567
- * @returns {{}|*|null}
568
- */
569
- Observable.value = function(data) {
570
- if(Validator.isObservable(data)) {
571
- return data.val();
572
- }
573
- if(Validator.isProxy(data)) {
574
- return data.$val();
575
- }
576
- if(Validator.isArray(data)) {
577
- const result = [];
578
- data.forEach(item => {
579
- result.push(Observable.value(item));
580
- });
581
- return result;
582
- }
583
- return data;
584
- };
585
-
586
- /**
587
- *
588
- * @param {Object} value
589
- * @returns {Proxy}
590
- */
591
- Observable.init = function(value) {
592
- const data = {};
593
- for(const key in value) {
594
- const itemValue = value[key];
595
- if(Validator.isJson(itemValue)) {
596
- data[key] = Observable.init(itemValue);
597
- continue;
598
- }
599
- else if(Validator.isArray(itemValue)) {
600
- data[key] = Observable.array(itemValue);
601
- continue;
602
- }
603
- data[key] = Observable(itemValue);
604
- }
605
-
606
- const $val = function() {
607
- const result = {};
608
- for(const key in data) {
609
- const dataItem = data[key];
610
- if(Validator.isObservable(dataItem)) {
611
- result[key] = dataItem.val();
612
- } else if(Validator.isProxy(dataItem)) {
613
- result[key] = dataItem.$val();
614
- } else {
615
- result[key] = dataItem;
616
- }
617
- }
618
- return result;
619
- };
620
- const $clone = function() {
621
-
622
- };
623
-
624
- return new Proxy(data, {
625
- get(target, property) {
626
- if(property === '__isProxy__') {
627
- return true;
628
- }
629
- if(property === '$val') {
630
- return $val;
631
- }
632
- if(property === '$clone') {
633
- return $clone;
634
- }
635
- if(target[property] !== undefined) {
636
- return target[property];
637
- }
638
- return undefined;
639
- },
640
- set(target, prop, newValue) {
641
- if(target[prop] !== undefined) {
642
- target[prop].set(newValue);
643
- }
644
- }
645
- })
646
- };
647
-
648
- Observable.object = Observable.init;
649
- Observable.json = Observable.init;
650
- Observable.update = function($target, data) {
651
- for(const key in data) {
652
- const targetItem = $target[key];
653
- const newValue = data[key];
654
-
655
- if(Validator.isObservable(targetItem)) {
656
- if(Validator.isArray(newValue)) {
657
- Observable.update(targetItem, newValue);
658
- continue;
659
- }
660
- targetItem.set(newValue);
661
- continue;
662
- }
663
- if(Validator.isProxy(targetItem)) {
664
- Observable.update(targetItem, newValue);
665
- continue;
666
- }
667
- $target[key] = newValue;
668
- }
669
- };
670
- /**
671
- *
672
- * @param {Array} target
673
- * @returns {ObservableItem}
674
- */
675
- Observable.array = function(target) {
676
- if(!Array.isArray(target)) {
677
- throw new NativeDocumentError('Observable.array : target must be an array');
678
- }
679
- const observer = Observable(target);
680
-
681
- const methods = ['push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice'];
682
-
683
- methods.forEach((method) => {
684
- observer[method] = function(...values) {
685
- const target = observer.val();
686
- const result = target[method].apply(target, arguments);
687
- observer.trigger();
688
- return result;
689
- };
690
- });
691
-
692
- const overrideMethods = ['map', 'filter', 'reduce', 'some', 'every', 'find', 'findIndex'];
693
- overrideMethods.forEach((method) => {
694
- observer[method] = function(callback) {
695
- return observer.val()[method](callback);
696
- };
697
- });
698
-
699
- return observer;
700
- };
701
-
702
655
  /**
703
656
  * Enable auto cleanup of observables.
704
657
  * @param {Boolean} enable
@@ -717,20 +670,6 @@ var NativeDocument = (function (exports) {
717
670
  setInterval(() => MemoryManager.cleanObservables(threshold), interval);
718
671
  };
719
672
 
720
- /**
721
- *
722
- * @param {HTMLElement} element
723
- * @param {string} className
724
- * @param {string} value
725
- */
726
- const toggleClassItem = function(element, className, value) {
727
- if(value) {
728
- element.classList.add(className);
729
- } else {
730
- element.classList.remove(className);
731
- }
732
- };
733
-
734
673
  /**
735
674
  *
736
675
  * @param {HTMLElement} element
@@ -740,11 +679,11 @@ var NativeDocument = (function (exports) {
740
679
  for(let className in data) {
741
680
  const value = data[className];
742
681
  if(Validator.isObservable(value)) {
743
- toggleClassItem(element, className, value.val());
744
- value.subscribe(newValue => toggleClassItem(element, className, newValue));
682
+ element.classList.toggle(className, value.val());
683
+ value.subscribe(newValue => element.classList.toggle(className, newValue));
745
684
  continue;
746
685
  }
747
- toggleClassItem(element, className, value);
686
+ element.classList.toggle(className, value);
748
687
  }
749
688
  }
750
689
 
@@ -816,8 +755,8 @@ var NativeDocument = (function (exports) {
816
755
  }
817
756
  element.setAttribute(attributeName, newValue);
818
757
  };
819
- value.subscribe(applyValue);
820
758
  applyValue(value.val());
759
+ value.subscribe(applyValue);
821
760
 
822
761
  if(attributeName === 'value') {
823
762
  element.addEventListener('input', () => value.set(element.value));
@@ -866,228 +805,302 @@ var NativeDocument = (function (exports) {
866
805
  continue;
867
806
  }
868
807
  element.setAttribute(attributeName, value);
808
+
869
809
  }
870
810
  return element;
871
811
  }
872
812
 
873
- const DocumentObserver = {
874
- elements: new Map(),
875
- observer: null,
876
- checkMutation: throttle(function() {
877
- for(const [element, data] of DocumentObserver.elements.entries()) {
878
- const isCurrentlyInDom = document.body.contains(element);
879
- if(isCurrentlyInDom && !data.inDom) {
880
- data.inDom = true;
881
- data.mounted.forEach(callback => callback(element));
882
- } else if(!isCurrentlyInDom && data.inDom) {
883
- data.inDom = false;
884
- 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);
885
898
  }
886
899
  }
887
- }, 10, { debounce: true }),
900
+ },
888
901
  /**
889
902
  *
890
903
  * @param {HTMLElement} element
891
- * @returns {{watch: (function(): Map<any, any>), disconnect: (function(): boolean), mounted: (function(*): Set<any>), unmounted: (function(*): Set<any>)}}
904
+ * @param {Object} attributes
892
905
  */
893
- watch: function(element) {
894
- let data = {};
895
- if(DocumentObserver.elements.has(element)) {
896
- data = DocumentObserver.elements.get(element);
897
- } else {
898
- const inDom = document.body.contains(element);
899
- data = {
900
- inDom,
901
- mounted: new Set(),
902
- unmounted: new Set(),
903
- };
904
- DocumentObserver.elements.set(element, data);
906
+ processAttributes(element, attributes) {
907
+ if(Validator.isFragment(element)) return;
908
+ if (attributes) {
909
+ AttributesWrapper(element, attributes);
905
910
  }
906
-
907
- return {
908
- watch: () => DocumentObserver.elements.set(element, data),
909
- disconnect: () => DocumentObserver.elements.delete(element),
910
- mounted: (callback) => data.mounted.add(callback),
911
- unmounted: (callback) => data.unmounted.add(callback)
912
- };
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;
913
921
  }
914
922
  };
915
923
 
916
- DocumentObserver.observer = new MutationObserver(DocumentObserver.checkMutation);
917
- DocumentObserver.observer.observe(document.body, {
918
- childList: true,
919
- subtree: true,
920
- });
921
-
922
- const getChildAsNode = (child) => {
923
- if(Validator.isFunction(child)) {
924
- return getChildAsNode(child());
925
- }
926
- if(Validator.isElement(child)) {
927
- return child;
928
- }
929
- return createTextNode(child)
930
- };
931
-
932
- function Anchor(name) {
933
- const element = document.createDocumentFragment();
934
-
935
- const anchorStart = document.createComment('Anchor Start : '+name);
936
- const anchorEnd = document.createComment('/ Anchor End '+name);
937
-
938
- element.appendChild(anchorStart);
939
- element.appendChild(anchorEnd);
940
-
941
- element.nativeInsertBefore = element.insertBefore;
942
- element.nativeAppendChild = element.appendChild;
943
-
944
- const insertBefore = function(parent, child, target) {
945
- if(parent === element) {
946
- parent.nativeInsertBefore(getChildAsNode(child), target);
947
- return;
948
- }
949
- parent.insertBefore(getChildAsNode(child), target);
950
- };
951
-
952
- element.appendChild = function(child, before = null) {
953
- const parent = anchorEnd.parentNode;
954
- if(!parent) {
955
- DebugManager.error('Anchor', 'Anchor : parent not found', child);
956
- return;
957
- }
958
- before = before ?? anchorEnd;
959
- if(Validator.isArray(child)) {
960
- child.forEach((element) => {
961
- insertBefore(parent, element, before);
962
- });
963
- return element;
964
- }
965
- insertBefore(parent, child, before);
966
- };
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
+ }
967
942
 
968
- element.remove = function(trueRemove) {
969
- if(anchorEnd.parentNode === element) {
970
- return;
971
- }
972
- let itemToRemove = anchorStart.nextSibling, tempItem;
973
- while(itemToRemove !== anchorEnd) {
974
- tempItem = itemToRemove.nextSibling;
975
- trueRemove ? itemToRemove.remove() : element.nativeAppendChild(itemToRemove);
976
- itemToRemove = tempItem;
977
- }
978
- if(trueRemove) {
979
- anchorEnd.remove();
980
- 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
+ }
981
957
  }
982
- };
983
-
984
- element.insertBefore = function(child, anchor = null) {
985
- element.appendChild(child, anchor);
986
- };
987
-
988
- element.clear = function() {
989
- element.remove();
990
- };
991
-
992
- element.endElement = function() {
993
- return anchorEnd;
994
- };
995
- element.startElement = function() {
996
- return anchorStart;
997
- };
998
-
999
- return element;
1000
- }
1001
-
1002
- const pluginsManager = (function() {
1003
-
1004
- const $plugins = [];
1005
-
1006
- return {
1007
- list : () => $plugins,
1008
- add : (plugin) => $plugins.push(plugin)
1009
- };
1010
- }());
1011
-
1012
- /**
1013
- *
1014
- * @param {HTMLElement|DocumentFragment} parent
1015
- * @param {ObservableItem} observable
1016
- * @returns {Text}
1017
- */
1018
- const createObservableNode = function(parent, observable) {
1019
- const text = document.createTextNode('');
1020
- observable.subscribe(value => text.textContent = String(value));
1021
- text.textContent = observable.val();
1022
- parent && parent.appendChild(text);
1023
- return text;
1024
- };
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
+ };
1025
978
 
1026
- /**
1027
- *
1028
- * @param {HTMLElement|DocumentFragment} parent
1029
- * @param {*} value
1030
- * @returns {Text}
1031
- */
1032
- const createStaticTextNode = function(parent, value) {
1033
- const text = document.createTextNode('');
1034
- text.textContent = String(value);
1035
- parent && parent.appendChild(text);
1036
- 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
+ }
1037
993
  };
1038
994
 
1039
- /**
1040
- *
1041
- * @param {HTMLElement} element
1042
- */
1043
- const addUtilsMethods = function(element) {
1044
- element.nd.wrap = (callback) => {
1045
- if(!Validator.isFunction(callback)) {
1046
- throw new NativeDocumentError('Callback must be a function');
1047
- }
1048
- callback && callback(element);
1049
- return element;
1050
- };
1051
- element.nd.ref = (target, name) => {
1052
- target[name] = element;
1053
- return element;
1054
- };
1055
-
1056
- let $observer = null;
1057
-
1058
- element.nd.appendChild = function(child) {
1059
- if(Validator.isArray(child)) {
1060
- ElementCreator.processChildren(child, element);
1061
- return;
1062
- }
1063
- if(Validator.isFunction(child)) {
1064
- child = child();
1065
- ElementCreator.processChildren(child(), element);
1066
- }
1067
- if(Validator.isElement(child)) {
1068
- ElementCreator.processChildren(child, element);
1069
- }
1070
- };
1071
-
1072
- element.nd.lifecycle = function(states) {
1073
- $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
+ });
1074
1000
 
1075
- states.mounted && $observer.mounted(states.mounted);
1076
- states.unmounted && $observer.unmounted(states.unmounted);
1077
- return element;
1078
- };
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
+ return function(target, name) {
1047
+ target[name] = element;
1048
+ return element;
1049
+ };
1050
+ }
1051
+ if(property === 'unmountChildren') {
1052
+ return () => {
1053
+ for(let i = 0, length = element.children.length; i < length; i++) {
1054
+ let elementchildren = element.children[i];
1055
+ if(!elementchildren.$ndProx) {
1056
+ elementchildren.nd?.remove();
1057
+ }
1058
+ elementchildren = null;
1059
+ }
1060
+ };
1061
+ }
1062
+ if(property === 'remove') {
1063
+ return function() {
1064
+ element.nd.unmountChildren();
1065
+ lifecycle = null;
1066
+ element.$ndProx = null;
1067
+ delete element.nd?.on?.prevent;
1068
+ delete element.nd?.on;
1069
+ delete element.nd;
1070
+ };
1071
+ }
1072
+ if(property === 'hasLifecycle') {
1073
+ return lifecycle !== null;
1074
+ }
1075
+ if(property === 'lifecycle') {
1076
+ if(lifecycle) {
1077
+ return lifecycle;
1078
+ }
1079
+ let $observer = null;
1080
+ lifecycle = function(states) {
1081
+ $observer = $observer || DocumentObserver.watch(element);
1082
+
1083
+ states.mounted && $observer.mounted(states.mounted);
1084
+ states.unmounted && $observer.unmounted(states.unmounted);
1085
+ return element;
1086
+ };
1087
+ return lifecycle;
1088
+ }
1089
+ if(property === 'mounted' || property === 'unmounted') {
1090
+ return function(callback) {
1091
+ element.nd.lifecycle({ [property]: callback});
1092
+ return element;
1093
+ };
1094
+ }
1095
+ },
1096
+ set(target, p, newValue, receiver) {
1079
1097
 
1080
- element.nd.mounted = (callback) => {
1081
- $observer = $observer || DocumentObserver.watch(element);
1082
- $observer.mounted(callback);
1083
- return element;
1084
- };
1085
- element.nd.unmounted = (callback) => {
1086
- $observer = $observer || DocumentObserver.watch(element);
1087
- $observer.unmounted(callback);
1088
- return element;
1089
- };
1090
- };
1098
+ },
1099
+ configurable: true
1100
+ });
1101
+ return this.$ndProx;
1102
+ }
1103
+ });
1091
1104
 
1092
1105
  /**
1093
1106
  *
@@ -1096,84 +1109,10 @@ var NativeDocument = (function (exports) {
1096
1109
  */
1097
1110
  const createTextNode = function(value) {
1098
1111
  return (Validator.isObservable(value))
1099
- ? createObservableNode(null, value)
1100
- : createStaticTextNode(null, value);
1112
+ ? ElementCreator.createObservableNode(null, value)
1113
+ : ElementCreator.createStaticTextNode(null, value);
1101
1114
  };
1102
1115
 
1103
- const ElementCreator = {
1104
- /**
1105
- *
1106
- * @param {string} name
1107
- * @returns {HTMLElement|DocumentFragment}
1108
- */
1109
- createElement(name) {
1110
- return name ? document.createElement(name) : new Anchor('Fragment');
1111
- },
1112
- /**
1113
- *
1114
- * @param {*} children
1115
- * @param {HTMLElement|DocumentFragment} parent
1116
- */
1117
- processChildren(children, parent) {
1118
- if(children === null) return;
1119
- const childrenArray = Array.isArray(children) ? children : [children];
1120
- childrenArray.forEach(child => {
1121
- if (child === null) return;
1122
- if(Validator.isString(child) && Validator.isFunction(child.resolveObservableTemplate)) {
1123
- child = child.resolveObservableTemplate();
1124
- }
1125
- if(Validator.isFunction(child)) {
1126
- this.processChildren(child(), parent);
1127
- return;
1128
- }
1129
- if(Validator.isArray(child)) {
1130
- this.processChildren(child, parent);
1131
- return;
1132
- }
1133
- if (Validator.isElement(child)) {
1134
- parent.appendChild(child);
1135
- return;
1136
- }
1137
- if (Validator.isObservable(child)) {
1138
- createObservableNode(parent, child);
1139
- return;
1140
- }
1141
- if (child) {
1142
- createStaticTextNode(parent, child);
1143
- }
1144
- });
1145
- },
1146
- /**
1147
- *
1148
- * @param {HTMLElement} element
1149
- * @param {Object} attributes
1150
- */
1151
- processAttributes(element, attributes) {
1152
- if(Validator.isFragment(element)) return;
1153
- if (attributes) {
1154
- AttributesWrapper(element, attributes);
1155
- }
1156
- },
1157
- /**
1158
- *
1159
- * @param {HTMLElement} element
1160
- * @param {Object} attributes
1161
- * @param {?Function} customWrapper
1162
- * @returns {HTMLElement|DocumentFragment}
1163
- */
1164
- setup(element, attributes, customWrapper) {
1165
- element.nd = {};
1166
- HtmlElementEventsWrapper(element);
1167
- const item = (typeof customWrapper === 'function') ? customWrapper(element) : element;
1168
- addUtilsMethods(item);
1169
-
1170
- pluginsManager.list().forEach(plugin => {
1171
- plugin?.element?.setup && plugin.element.setup(item, attributes);
1172
- });
1173
-
1174
- return item;
1175
- }
1176
- };
1177
1116
 
1178
1117
  /**
1179
1118
  *
@@ -1182,7 +1121,7 @@ var NativeDocument = (function (exports) {
1182
1121
  * @returns {Function}
1183
1122
  */
1184
1123
  function HtmlElementWrapper(name, customWrapper) {
1185
- const $tagName = name.toLowerCase().trim();
1124
+ const $tagName = name.toLowerCase();
1186
1125
 
1187
1126
  const builder = function(attributes, children = null) {
1188
1127
  try {
@@ -1192,11 +1131,12 @@ var NativeDocument = (function (exports) {
1192
1131
  attributes = tempChildren;
1193
1132
  }
1194
1133
  const element = ElementCreator.createElement($tagName);
1134
+ const finalElement = (typeof customWrapper === 'function') ? customWrapper(element) : element;
1195
1135
 
1196
- ElementCreator.processAttributes(element, attributes);
1197
- ElementCreator.processChildren(children, element);
1136
+ ElementCreator.processAttributes(finalElement, attributes);
1137
+ ElementCreator.processChildren(children, finalElement);
1198
1138
 
1199
- return ElementCreator.setup(element, attributes, customWrapper);
1139
+ return ElementCreator.setup(finalElement, attributes, customWrapper);
1200
1140
  } catch (error) {
1201
1141
  DebugManager.error('ElementCreation', `Error creating ${$tagName}`, error);
1202
1142
  }
@@ -1304,51 +1244,274 @@ var NativeDocument = (function (exports) {
1304
1244
  if(!Validator.isArray(argSchema)) {
1305
1245
  throw new NativeDocumentError('withValidation : argSchema must be an array');
1306
1246
  }
1307
- return function(...args) {
1308
- validateArgs(args, argSchema, fn.name || fnName);
1309
- return fn.apply(this, args);
1310
- };
1247
+ return function(...args) {
1248
+ validateArgs(args, argSchema, fn.name || fnName);
1249
+ return fn.apply(this, args);
1250
+ };
1251
+ };
1252
+
1253
+ Function.prototype.args = function(...args) {
1254
+ return withValidation(this, args);
1255
+ };
1256
+
1257
+ Function.prototype.errorBoundary = function(callback) {
1258
+ return (...args) => {
1259
+ try {
1260
+ return this.apply(this, args);
1261
+ } catch(e) {
1262
+ return callback(e);
1263
+ }
1264
+ };
1265
+ };
1266
+
1267
+ String.prototype.use = function(args) {
1268
+ const value = this;
1269
+
1270
+ return Observable.computed(() => {
1271
+ return value.replace(/\$\{(.*?)}/g, (match, key) => {
1272
+ const data = args[key];
1273
+ if(Validator.isObservable(data)) {
1274
+ return data.val();
1275
+ }
1276
+ return data;
1277
+ });
1278
+ }, Object.values(args));
1279
+ };
1280
+
1281
+ String.prototype.resolveObservableTemplate = function() {
1282
+ if(!Validator.containsObservableReference(this)) {
1283
+ return this;
1284
+ }
1285
+ return this.split(/(\{\{#ObItem::\([0-9]+\)\}\})/g).filter(Boolean).map((value) => {
1286
+ if(!Validator.containsObservableReference(value)) {
1287
+ return value;
1288
+ }
1289
+ const [_, id] = value.match(/\{\{#ObItem::\(([0-9]+)\)\}\}/);
1290
+ return Observable.getById(id);
1291
+ });
1292
+ };
1293
+
1294
+ const methods = ['push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice'];
1295
+
1296
+ /**
1297
+ *
1298
+ * @param {Array} target
1299
+ * @returns {ObservableItem}
1300
+ */
1301
+ Observable.array = function(target) {
1302
+ if(!Array.isArray(target)) {
1303
+ throw new NativeDocumentError('Observable.array : target must be an array');
1304
+ }
1305
+ const observer = Observable(target);
1306
+
1307
+ methods.forEach((method) => {
1308
+ observer[method] = function(...values) {
1309
+ const result = observer.val()[method](...values);
1310
+ observer.trigger({ action: method, args: values, result });
1311
+ return result;
1312
+ };
1313
+ });
1314
+
1315
+ observer.clear = function() {
1316
+ observer.$value.length = 0;
1317
+ observer.trigger({ action: 'clear' });
1318
+ return true;
1319
+ };
1320
+
1321
+ observer.remove = function(index) {
1322
+ const deleted = observer.$value.splice(index, 1);
1323
+ if(deleted.length === 0) {
1324
+ return [];
1325
+ }
1326
+ observer.trigger({ action: 'remove', args: [index], result: deleted[0] });
1327
+ return deleted;
1328
+ };
1329
+
1330
+ observer.swap = function(indexA, indexB) {
1331
+ const value = observer.$value;
1332
+ const length = value.length;
1333
+ if(length < indexA || length < indexB) {
1334
+ return false;
1335
+ }
1336
+ if(indexB < indexA) {
1337
+ const temp = indexA;
1338
+ indexA = indexB;
1339
+ indexB = temp;
1340
+ }
1341
+ const elementA = value[indexA];
1342
+ const elementB = value[indexB];
1343
+
1344
+ value[indexA] = elementB;
1345
+ value[indexB] = elementA;
1346
+ observer.trigger({ action: 'swap', args: [indexA, indexB], result: [elementA, elementB] });
1347
+ return true;
1348
+ };
1349
+
1350
+ observer.length = function() {
1351
+ return observer.$value.length;
1352
+ };
1353
+
1354
+ const overrideMethods = ['map', 'filter', 'reduce', 'some', 'every', 'find', 'findIndex', 'concat'];
1355
+ overrideMethods.forEach((method) => {
1356
+ observer[method] = function(...args) {
1357
+ return observer.val()[method](...args);
1358
+ };
1359
+ });
1360
+
1361
+ return observer;
1362
+ };
1363
+
1364
+ /**
1365
+ *
1366
+ * @param {Function} callback
1367
+ * @returns {Function}
1368
+ */
1369
+ Observable.batch = function(callback) {
1370
+ const $observer = Observable(0);
1371
+ const batch = function() {
1372
+ if(Validator.isAsyncFunction(callback)) {
1373
+ return (callback(...arguments)).then(() => {
1374
+ $observer.trigger();
1375
+ }).catch(error => { throw error; });
1376
+ }
1377
+ callback(...arguments);
1378
+ $observer.trigger();
1379
+ };
1380
+ batch.$observer = $observer;
1381
+ return batch;
1382
+ };
1383
+
1384
+ /**
1385
+ *
1386
+ * @param {Object} value
1387
+ * @returns {Proxy}
1388
+ */
1389
+ Observable.init = function(value) {
1390
+ const data = {};
1391
+ for(const key in value) {
1392
+ const itemValue = value[key];
1393
+ if(Validator.isJson(itemValue)) {
1394
+ data[key] = Observable.init(itemValue);
1395
+ continue;
1396
+ }
1397
+ else if(Validator.isArray(itemValue)) {
1398
+ data[key] = Observable.array(itemValue);
1399
+ continue;
1400
+ }
1401
+ data[key] = Observable(itemValue);
1402
+ }
1403
+
1404
+ const $val = function() {
1405
+ const result = {};
1406
+ for(const key in data) {
1407
+ const dataItem = data[key];
1408
+ if(Validator.isObservable(dataItem)) {
1409
+ result[key] = dataItem.val();
1410
+ } else if(Validator.isProxy(dataItem)) {
1411
+ result[key] = dataItem.$value;
1412
+ } else {
1413
+ result[key] = dataItem;
1414
+ }
1415
+ }
1416
+ return result;
1417
+ };
1418
+ const $clone = function() {
1419
+
1420
+ };
1421
+
1422
+ return new Proxy(data, {
1423
+ get(target, property) {
1424
+ if(property === '__isProxy__') {
1425
+ return true;
1426
+ }
1427
+ if(property === '$value') {
1428
+ return $val();
1429
+ }
1430
+ if(property === '$clone') {
1431
+ return $clone;
1432
+ }
1433
+ if(target[property] !== undefined) {
1434
+ return target[property];
1435
+ }
1436
+ return undefined;
1437
+ },
1438
+ set(target, prop, newValue) {
1439
+ if(target[prop] !== undefined) {
1440
+ target[prop].set(newValue);
1441
+ }
1442
+ }
1443
+ })
1444
+ };
1445
+
1446
+ /**
1447
+ * Get the value of an observable or an object of observables.
1448
+ * @param {ObservableItem|Object<ObservableItem>} data
1449
+ * @returns {{}|*|null}
1450
+ */
1451
+ Observable.value = function(data) {
1452
+ if(Validator.isObservable(data)) {
1453
+ return data.val();
1454
+ }
1455
+ if(Validator.isProxy(data)) {
1456
+ return data.$value;
1457
+ }
1458
+ if(Validator.isArray(data)) {
1459
+ const result = [];
1460
+ data.forEach(item => {
1461
+ result.push(Observable.value(item));
1462
+ });
1463
+ return result;
1464
+ }
1465
+ return data;
1311
1466
  };
1312
1467
 
1313
- Function.prototype.args = function(...args) {
1314
- return withValidation(this, args);
1315
- };
1316
1468
 
1317
- Function.prototype.errorBoundary = function(callback) {
1318
- return (...args) => {
1319
- try {
1320
- return this.apply(this, args);
1321
- } catch(e) {
1322
- return callback(e);
1469
+ Observable.update = function($target, data) {
1470
+ for(const key in data) {
1471
+ const targetItem = $target[key];
1472
+ const newValue = data[key];
1473
+
1474
+ if(Validator.isObservable(targetItem)) {
1475
+ if(Validator.isArray(newValue)) {
1476
+ Observable.update(targetItem, newValue);
1477
+ continue;
1478
+ }
1479
+ targetItem.set(newValue);
1480
+ continue;
1323
1481
  }
1324
- };
1482
+ if(Validator.isProxy(targetItem)) {
1483
+ Observable.update(targetItem, newValue);
1484
+ continue;
1485
+ }
1486
+ $target[key] = newValue;
1487
+ }
1325
1488
  };
1326
1489
 
1327
- String.prototype.use = function(args) {
1328
- const value = this;
1490
+ Observable.object = Observable.init;
1491
+ Observable.json = Observable.init;
1329
1492
 
1330
- return Observable.computed(() => {
1331
- return value.replace(/\$\{(.*?)}/g, (match, key) => {
1332
- const data = args[key];
1333
- if(Validator.isObservable(data)) {
1334
- return data.val();
1335
- }
1336
- return data;
1337
- });
1338
- }, Object.values(args));
1339
- };
1493
+ /**
1494
+ *
1495
+ * @param {Function} callback
1496
+ * @param {Array|Function} dependencies
1497
+ * @returns {ObservableItem}
1498
+ */
1499
+ Observable.computed = function(callback, dependencies = []) {
1500
+ const initialValue = callback();
1501
+ const observable = new ObservableItem(initialValue);
1502
+ const updatedValue = () => observable.set(callback());
1340
1503
 
1341
- String.prototype.resolveObservableTemplate = function() {
1342
- if(!Validator.containsObservableReference(this)) {
1343
- return this;
1344
- }
1345
- return this.split(/(\{\{#ObItem::\([0-9]+\)\}\})/g).filter(Boolean).map((value) => {
1346
- if(!Validator.containsObservableReference(value)) {
1347
- return value;
1504
+ if(Validator.isFunction(dependencies)) {
1505
+ if(!Validator.isObservable(dependencies.$observer)) {
1506
+ throw new NativeDocumentError('Observable.computed : dependencies must be valid batch function');
1348
1507
  }
1349
- const [_, id] = value.match(/\{\{#ObItem::\(([0-9]+)\)\}\}/);
1350
- return Observable.getById(id);
1351
- });
1508
+ dependencies.$observer.subscribe(updatedValue);
1509
+ return observable;
1510
+ }
1511
+
1512
+ dependencies.forEach(dependency => dependency.subscribe(updatedValue));
1513
+
1514
+ return observable;
1352
1515
  };
1353
1516
 
1354
1517
  const Store = (function() {
@@ -1424,45 +1587,6 @@ var NativeDocument = (function (exports) {
1424
1587
  };
1425
1588
  }());
1426
1589
 
1427
- /**
1428
- *
1429
- * @param {*} item
1430
- * @param {string|null} defaultKey
1431
- * @param {?Function} key
1432
- * @returns {*}
1433
- */
1434
- const getKey = (item, defaultKey, key) => {
1435
- if(Validator.isFunction(key)) return key(item, defaultKey);
1436
- if(Validator.isObservable(item)) {
1437
- const val = item.val();
1438
- return (val && key) ? val[key] : defaultKey;
1439
- }
1440
- return item[key] ?? defaultKey;
1441
- };
1442
-
1443
- /**
1444
- *
1445
- * @param {Map} cache
1446
- * @param {Set} keyIds
1447
- */
1448
- const cleanBlockByCache = (cache, keyIds) => {
1449
- const toRemove = [];
1450
- for(const [key, cacheItem] of cache.entries()) {
1451
- if(keyIds.has(key)) {
1452
- continue;
1453
- }
1454
- toRemove.push({ key, cacheItem });
1455
- }
1456
- if(toRemove.length === 0) {
1457
- return;
1458
- }
1459
- toRemove.forEach(({ key, cacheItem }) => {
1460
- cacheItem.child.remove();
1461
- cacheItem.indexObserver.cleanup();
1462
- cache.delete(key);
1463
- });
1464
- };
1465
-
1466
1590
  /**
1467
1591
  *
1468
1592
  * @param {Array|Object|ObservableItem} data
@@ -1473,120 +1597,408 @@ var NativeDocument = (function (exports) {
1473
1597
  function ForEach(data, callback, key) {
1474
1598
  const element = new Anchor('ForEach');
1475
1599
  const blockEnd = element.endElement();
1476
- const blockStart = element.startElement();
1600
+ element.startElement();
1477
1601
 
1478
1602
  let cache = new Map();
1603
+ let lastKeyOrder = null;
1479
1604
  const keyIds = new Set();
1480
1605
 
1606
+ const clear = () => {
1607
+ element.removeChildren();
1608
+ cleanCache();
1609
+ };
1610
+
1611
+ const cleanCache = (parent) => {
1612
+ for(const [keyId, cacheItem] of cache.entries()) {
1613
+ if(keyIds.has(keyId)) {
1614
+ continue;
1615
+ }
1616
+ const child = cacheItem.child?.deref();
1617
+ if(parent && child) {
1618
+ parent.removeChild(child);
1619
+ }
1620
+ cacheItem.indexObserver?.cleanup();
1621
+ cacheItem.child = null;
1622
+ cacheItem.indexObserver = null;
1623
+ cache.delete(cacheItem.keyId);
1624
+ lastKeyOrder && lastKeyOrder.delete(cacheItem.keyId);
1625
+ }
1626
+ };
1627
+
1481
1628
  const handleContentItem = (item, indexKey) => {
1482
1629
  const keyId = getKey(item, indexKey, key);
1483
1630
 
1484
1631
  if(cache.has(keyId)) {
1485
1632
  const cacheItem = cache.get(keyId);
1486
- cacheItem.indexObserver.set(indexKey);
1633
+ cacheItem.indexObserver?.set(indexKey);
1487
1634
  cacheItem.isNew = false;
1635
+ if(cacheItem.child?.deref()) {
1636
+ return keyId;
1637
+ }
1638
+ cache.delete(keyId);
1488
1639
  }
1489
- else {
1490
1640
 
1491
- try {
1492
- const indexObserver = Observable(indexKey);
1493
- let child = callback(item, indexObserver);
1494
- if(Validator.isStringOrObservable(child)) {
1495
- child = createTextNode(child);
1496
- }
1497
- cache.set(keyId, { isNew: true, child, indexObserver});
1498
- } catch (e) {
1499
- DebugManager.error('ForEach', `Error creating element for key ${keyId}` , e);
1500
- throw e;
1641
+ try {
1642
+ const indexObserver = callback.length >= 2 ? Observable(indexKey) : null;
1643
+ let child = callback(item, indexObserver);
1644
+ if(Validator.isStringOrObservable(child)) {
1645
+ child = createTextNode(child);
1501
1646
  }
1647
+ cache.set(keyId, { keyId, isNew: true, child: new WeakRef(child), indexObserver});
1648
+ } catch (e) {
1649
+ DebugManager.error('ForEach', `Error creating element for key ${keyId}` , e);
1650
+ throw e;
1502
1651
  }
1503
1652
  return keyId;
1504
1653
  };
1505
1654
 
1506
- const batchDOMUpdates = () => {
1655
+ const batchDOMUpdates = (parent) => {
1656
+ const fragment = document.createDocumentFragment();
1657
+ for(const itemKey of keyIds) {
1658
+ const cacheItem = cache.get(itemKey);
1659
+ if(!cacheItem) {
1660
+ continue;
1661
+ }
1662
+ const child = cacheItem.child?.deref();
1663
+ child && fragment.appendChild(child);
1664
+ }
1665
+ parent.insertBefore(fragment, blockEnd);
1666
+ };
1667
+
1668
+ const diffingDOMUpdates = (parent) => {
1669
+ let fragment = document.createDocumentFragment();
1670
+ const newKeys = Array.from(keyIds);
1671
+ Array.from(lastKeyOrder);
1672
+
1673
+ for(const index in newKeys) {
1674
+ const itemKey = newKeys[index];
1675
+ const cacheItem = cache.get(itemKey);
1676
+ if(!cacheItem) {
1677
+ continue;
1678
+ }
1679
+ const child = cacheItem.child.deref();
1680
+ if(!child) {
1681
+ continue;
1682
+ }
1683
+ fragment.appendChild(child);
1684
+ }
1685
+ element.replaceContent(fragment);
1686
+ };
1687
+
1688
+ const buildContent = () => {
1507
1689
  const parent = blockEnd.parentNode;
1508
1690
  if(!parent) {
1509
1691
  return;
1510
1692
  }
1511
1693
 
1512
- let previousElementSibling = blockStart;
1513
- const elementsToInsert = [];
1514
- const elementsToMove = [];
1515
- let fragment = null;
1516
-
1517
- let saveFragment = (beforeTarget) => {
1518
- if(fragment) {
1519
- elementsToInsert.push({ child: fragment, before: beforeTarget });
1520
- fragment = null;
1694
+ const items = (Validator.isObservable(data)) ? data.val() : data;
1695
+ keyIds.clear();
1696
+ if(Array.isArray(items)) {
1697
+ for(let i = 0, length = items.length; i < length; i++) {
1698
+ const keyId= handleContentItem(items[i], i);
1699
+ keyIds.add(keyId);
1521
1700
  }
1522
- };
1701
+ } else {
1702
+ for(const indexKey in items) {
1703
+ const keyId = handleContentItem(items[indexKey], indexKey);
1704
+ keyIds.add(keyId);
1705
+ }
1706
+ }
1523
1707
 
1524
- const keyIdsArray = Array.from(keyIds);
1525
- for(let i = 0; i < keyIdsArray.length; i++) {
1526
- const itemKey = keyIdsArray[i];
1527
- const cacheItem = cache.get(itemKey);
1708
+ if(keyIds.size === 0) {
1709
+ clear();
1710
+ lastKeyOrder?.clear();
1711
+ return;
1712
+ }
1713
+
1714
+ cleanCache(parent);
1715
+ if(!lastKeyOrder || lastKeyOrder.size === 0) {
1716
+ batchDOMUpdates(parent);
1717
+ } else {
1718
+ diffingDOMUpdates();
1719
+ }
1720
+ lastKeyOrder?.clear();
1721
+ lastKeyOrder = new Set([...keyIds]);
1722
+ };
1723
+
1724
+ buildContent();
1725
+ if(Validator.isObservable(data)) {
1726
+ data.subscribe(buildContent);
1727
+ }
1728
+ return element;
1729
+ }
1730
+
1731
+ function ForEachArray(data, callback, key, configs = {}) {
1732
+ const element = new Anchor('ForEach Array');
1733
+ const blockEnd = element.endElement();
1734
+ const blockStart = element.startElement();
1735
+
1736
+ let cache = new Map();
1737
+ let nodeCacheByElement = new WeakMap();
1738
+ let lastNumberOfItems = 0;
1739
+
1740
+ const keysCache = new WeakMap();
1741
+
1742
+ const clear = () => {
1743
+ element.removeChildren();
1744
+ cleanCache();
1745
+ lastNumberOfItems = 0;
1746
+ };
1747
+ const getItemKey = (item, indexKey) => {
1748
+ if(keysCache.has(item)) {
1749
+ return keysCache.get(item);
1750
+ }
1751
+ return getKey(item, indexKey, key);
1752
+ };
1753
+
1754
+ const updateIndexObservers = (items, startFrom = 0) => {
1755
+ if(callback.length < 2) {
1756
+ return;
1757
+ }
1758
+ let index = startFrom;
1759
+ for(let i = startFrom, length = items?.length; i < length; i++) {
1760
+ const cacheItem = cache.get(getItemKey(items[i], i));
1528
1761
  if(!cacheItem) {
1529
1762
  continue;
1530
1763
  }
1764
+ cacheItem.indexObserver?.deref()?.set(index);
1765
+ index++;
1766
+ }
1767
+ };
1531
1768
 
1532
- if(previousElementSibling && previousElementSibling.nextSibling === cacheItem.child) {
1533
- previousElementSibling = cacheItem.child;
1534
- saveFragment(cacheItem.child);
1535
- continue;
1769
+ const removeCacheItem = (cacheItem, removeChild = true) => {
1770
+ if(!cacheItem) {
1771
+ return;
1772
+ }
1773
+ const child = cacheItem.child?.deref();
1774
+ cacheItem.indexObserver?.deref()?.cleanup();
1775
+ cacheItem.child = null;
1776
+ cacheItem.indexObserver = null;
1777
+ nodeCacheByElement.delete(cacheItem.item);
1778
+ keysCache.delete(cacheItem.item);
1779
+ cacheItem.item = null;
1780
+ if(removeChild) {
1781
+ child?.remove();
1782
+ cache.delete(cacheItem.keyId);
1783
+ }
1784
+ };
1785
+
1786
+ const removeCacheItemByKey = (keyId, removeChild = true) => {
1787
+ removeCacheItem(cache.get(keyId), removeChild);
1788
+ };
1789
+
1790
+ const cleanCache = () => {
1791
+ for (const [keyId, cacheItem] of cache.entries()) {
1792
+ removeCacheItem(cacheItem, false);
1793
+ }
1794
+ cache.clear();
1795
+ };
1796
+
1797
+ const buildItem = (item, indexKey) => {
1798
+ const keyId = getItemKey(item, indexKey);
1799
+
1800
+ if(cache.has(keyId)) {
1801
+ const cacheItem = cache.get(keyId);
1802
+ cacheItem.indexObserver?.deref()?.set(indexKey);
1803
+ cacheItem.isNew = false;
1804
+ const child = cacheItem.child?.deref();
1805
+ if(child) {
1806
+ return child;
1536
1807
  }
1537
- if(cacheItem.isNew) {
1538
- fragment = fragment || document.createDocumentFragment();
1539
- fragment.append(cacheItem.child);
1540
- cacheItem.isNew = false;
1541
- continue;
1808
+ cache.delete(keyId);
1809
+ }
1810
+
1811
+ try {
1812
+ const indexObserver = callback.length >= 2 ? Observable(indexKey) : null;
1813
+ let child = callback(item, indexObserver);
1814
+ if(Validator.isStringOrObservable(child)) {
1815
+ child = createTextNode(child);
1542
1816
  }
1543
- saveFragment(cacheItem.child);
1544
- const nextChild = cache.get(keyIdsArray[i + 1])?.child;
1545
- if(nextChild) {
1546
- if(cacheItem.child.nextSibling !== nextChild) {
1547
- elementsToMove.push({ child: cacheItem.child, before: nextChild });
1548
- }
1817
+ cache.set(keyId, {
1818
+ keyId,
1819
+ isNew: true,
1820
+ item,
1821
+ child: new WeakRef(child),
1822
+ indexObserver: (indexObserver ? new WeakRef(indexObserver) : null)
1823
+ });
1824
+ keysCache.set(item, keyId);
1825
+ if(Validator.isObject(item)) {
1826
+ nodeCacheByElement.set(item, child);
1549
1827
  }
1828
+ return child;
1829
+ } catch (e) {
1830
+ DebugManager.error('ForEach', `Error creating element for key ${keyId}` , e);
1831
+ throw e;
1832
+ }
1833
+ };
1834
+ const getChildByKey = function(keyId, fragment) {
1835
+ const cacheItem = cache.get(keyId);
1836
+ if(!cacheItem) {
1837
+ return null;
1838
+ }
1839
+ const child = cacheItem.child?.deref();
1840
+ if(!child) {
1841
+ removeCacheItem(cacheItem, false);
1842
+ return null;
1843
+ }
1844
+ return child;
1845
+ };
1846
+
1847
+ const removeByKey = function(keyId, fragment) {
1848
+ const cacheItem = cache.get(keyId);
1849
+ if(!cacheItem) {
1850
+ return null;
1851
+ }
1852
+ const child = cacheItem.child?.deref();
1853
+ if(!child) {
1854
+ return null;
1855
+ }
1550
1856
 
1551
- previousElementSibling = cacheItem.child;
1857
+ if(fragment) {
1858
+ fragment.appendChild(child);
1859
+ return;
1552
1860
  }
1553
- saveFragment(blockEnd);
1861
+ child.remove();
1862
+ };
1554
1863
 
1555
- elementsToInsert.forEach(({ child, before }) => {
1556
- if(before) {
1557
- parent.insertBefore(child, before);
1864
+ const Actions = {
1865
+ toFragment(items, startIndexFrom = 0){
1866
+ const fragment = document.createDocumentFragment();
1867
+ for(let i = 0, length = items.length; i < length; i++) {
1868
+ fragment.append(buildItem(items[i], lastNumberOfItems));
1869
+ lastNumberOfItems++;
1870
+ }
1871
+ return fragment;
1872
+ },
1873
+ add(items, delay = 0) {
1874
+ setTimeout(() => {
1875
+ element.appendElement(Actions.toFragment(items));
1876
+ }, delay);
1877
+ },
1878
+ replace(items) {
1879
+ clear();
1880
+ Actions.add(items);
1881
+ },
1882
+ reOrder(items) {
1883
+ let child = null;
1884
+ const fragment = document.createDocumentFragment();
1885
+ for(const item of items) {
1886
+ child = nodeCacheByElement.get(item);
1887
+ if(child) {
1888
+ fragment.appendChild(child);
1889
+ }
1890
+ }
1891
+ child = null;
1892
+ element.appendElement(fragment, blockEnd);
1893
+ },
1894
+ removeOne(element, index) {
1895
+ let child = nodeCacheByElement.get(element);
1896
+ if(child) {
1897
+ child.remove();
1898
+ nodeCacheByElement.delete(element);
1899
+ removeCacheItemByKey(getItemKey(element, index));
1900
+ }
1901
+ child = null;
1902
+ },
1903
+ clear,
1904
+ push(items) {
1905
+ let delay = 0;
1906
+ if(configs.pushDelay) {
1907
+ delay = configs.pushDelay(items) ?? 0;
1558
1908
  } else {
1559
- element.appendChild(child);
1909
+ delay = (items.length >= 1000) ? 10 : 0;
1560
1910
  }
1561
- });
1911
+ Actions.add(items, delay);
1912
+ },
1913
+ unshift(values){
1914
+ element.insertBefore(Actions.toFragment(values), blockStart.nextSibling);
1915
+ },
1916
+ splice(args, deleted) {
1917
+ const [start, deleteCount, ...values] = args;
1918
+ let elementBeforeFirst = null;
1919
+ const garbageFragment = document.createDocumentFragment();
1920
+
1921
+ if(deleted.length > 0) {
1922
+ let firstKey = getItemKey(deleted[0], start);
1923
+ if(deleted.length === 1) {
1924
+ removeByKey(firstKey, garbageFragment);
1925
+ } else if(deleted.length > 1) {
1926
+ const firstChildRemoved = getChildByKey(firstKey);
1927
+ elementBeforeFirst = firstChildRemoved?.previousSibling;
1928
+
1929
+ for(let i = 0; i < deleted.length; i++) {
1930
+ const keyId = getItemKey(deleted[i], start + i);
1931
+ removeByKey(keyId, garbageFragment);
1932
+ }
1933
+ }
1934
+ } else {
1935
+ elementBeforeFirst = blockEnd;
1936
+ }
1937
+ garbageFragment.replaceChildren();
1562
1938
 
1563
- elementsToMove.forEach(({ child, before }) => {
1564
- parent.insertBefore(child, before);
1565
- });
1566
- saveFragment = null;
1939
+ if(values && values.length && elementBeforeFirst) {
1940
+ element.insertBefore(Actions.toFragment(values), elementBeforeFirst.nextSibling);
1941
+ }
1567
1942
 
1568
- };
1943
+ },
1944
+ reverse(_, reversed) {
1945
+ Actions.reOrder(reversed);
1946
+ },
1947
+ sort(_, sorted) {
1948
+ Actions.reOrder(sorted);
1949
+ },
1950
+ remove(_, deleted) {
1951
+ Actions.removeOne(deleted);
1952
+ },
1953
+ pop(_, deleted) {
1954
+ Actions.removeOne(deleted);
1955
+ },
1956
+ shift(_, deleted) {
1957
+ Actions.removeOne(deleted);
1958
+ },
1959
+ swap(args, elements) {
1960
+ const parent = blockEnd.parentNode;
1569
1961
 
1570
- const buildContent = () => {
1571
- const items = (Validator.isObservable(data)) ? data.val() : data;
1572
- keyIds.clear();
1573
- if(Array.isArray(items)) {
1574
- items.forEach((item, index) => keyIds.add(handleContentItem(item, index)));
1575
- } else {
1576
- for(const indexKey in items) {
1577
- keyIds.add(handleContentItem(items[indexKey], indexKey));
1962
+ let childA = nodeCacheByElement.get(elements[0]);
1963
+ let childB = nodeCacheByElement.get(elements[1]);
1964
+ if(!childA || !childB) {
1965
+ return;
1578
1966
  }
1967
+
1968
+ const childBNext = childB.nextSibling;
1969
+ parent.insertBefore(childB, childA);
1970
+ parent.insertBefore(childA, childBNext);
1971
+ childA = null;
1972
+ childB = null;
1579
1973
  }
1974
+ };
1580
1975
 
1581
- cleanBlockByCache(cache, keyIds);
1976
+ const buildContent = (items, _, operations) => {
1977
+ if(operations.action === 'clear' || !items.length) {
1978
+ if(lastNumberOfItems === 0) {
1979
+ return;
1980
+ }
1981
+ clear();
1982
+ }
1582
1983
 
1583
- batchDOMUpdates();
1984
+ if(!operations?.action) {
1985
+ if(lastNumberOfItems === 0) {
1986
+ Actions.add(items);
1987
+ return;
1988
+ }
1989
+ Actions.replace(items);
1990
+ }
1991
+ else if(Actions[operations.action]) {
1992
+ Actions[operations.action](operations.args, operations.result);
1993
+ }
1994
+ updateIndexObservers(items, 0);
1584
1995
  };
1585
1996
 
1586
- buildContent();
1997
+ buildContent(data.val(), null, {action: null});
1587
1998
  if(Validator.isObservable(data)) {
1588
1999
  data.subscribe(buildContent);
1589
2000
  }
2001
+
1590
2002
  return element;
1591
2003
  }
1592
2004
 
@@ -1795,7 +2207,7 @@ var NativeDocument = (function (exports) {
1795
2207
 
1796
2208
  el.submit = function(action) {
1797
2209
  if(typeof action === 'function') {
1798
- el.on.submit((e) => {
2210
+ el.onSubmit((e) => {
1799
2211
  e.preventDefault();
1800
2212
  action(e);
1801
2213
  });
@@ -1921,6 +2333,10 @@ var NativeDocument = (function (exports) {
1921
2333
  const UnorderedList = HtmlElementWrapper('ul');
1922
2334
  const ListItem = HtmlElementWrapper('li');
1923
2335
 
2336
+ const Li = ListItem;
2337
+ const Ol = OrderedList;
2338
+ const Ul = UnorderedList;
2339
+
1924
2340
  const Audio = HtmlElementWrapper('audio');
1925
2341
  const Video = HtmlElementWrapper('video');
1926
2342
  const Source = HtmlElementWrapper('source');
@@ -1989,6 +2405,7 @@ var NativeDocument = (function (exports) {
1989
2405
  FileInput: FileInput,
1990
2406
  Footer: Footer,
1991
2407
  ForEach: ForEach,
2408
+ ForEachArray: ForEachArray,
1992
2409
  Form: Form,
1993
2410
  Fragment: Fragment,
1994
2411
  H1: H1,
@@ -2009,6 +2426,7 @@ var NativeDocument = (function (exports) {
2009
2426
  Label: Label,
2010
2427
  LazyImg: LazyImg,
2011
2428
  Legend: Legend,
2429
+ Li: Li,
2012
2430
  Link: Link$1,
2013
2431
  ListItem: ListItem,
2014
2432
  Main: Main,
@@ -2020,6 +2438,7 @@ var NativeDocument = (function (exports) {
2020
2438
  NativeDocumentFragment: Anchor,
2021
2439
  Nav: Nav,
2022
2440
  NumberInput: NumberInput,
2441
+ Ol: Ol,
2023
2442
  Option: Option,
2024
2443
  OrderedList: OrderedList,
2025
2444
  Output: Output,
@@ -2065,6 +2484,7 @@ var NativeDocument = (function (exports) {
2065
2484
  TimeInput: TimeInput,
2066
2485
  Tr: Tr,
2067
2486
  Track: Track,
2487
+ Ul: Ul,
2068
2488
  UnorderedList: UnorderedList,
2069
2489
  UrlInput: UrlInput,
2070
2490
  Var: Var,
@@ -2732,7 +3152,7 @@ var NativeDocument = (function (exports) {
2732
3152
  const target = to || href;
2733
3153
  if(Validator.isString(target)) {
2734
3154
  const router = Router.get();
2735
- return Link$1({ ...attributes, href: target}, children).nd.on.prevent.click(() => {
3155
+ return Link$1({ ...attributes, href: target}, children).nd.onPreventClick(() => {
2736
3156
  router.push(target);
2737
3157
  });
2738
3158
  }
@@ -2743,7 +3163,7 @@ var NativeDocument = (function (exports) {
2743
3163
  throw new RouterError('Router not found "'+routerName+'" for link "'+target.name+'"');
2744
3164
  }
2745
3165
  const url = router.generateUrl(target.name, target.params, target.query);
2746
- return Link$1({ ...attributes, href: url }, children).nd.on.prevent.click(() => {
3166
+ return Link$1({ ...attributes, href: url }, children).nd.onPreventClick(() => {
2747
3167
  router.push(url);
2748
3168
  });
2749
3169
  }