native-document 1.0.9 → 1.0.11

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.
@@ -0,0 +1,2671 @@
1
+ var NativeDocument = (function (exports) {
2
+ 'use strict';
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
+ const DebugManager = {
83
+ enabled: false,
84
+
85
+ enable() {
86
+ this.enabled = true;
87
+ console.log('🔍 NativeDocument Debug Mode enabled');
88
+ },
89
+
90
+ disable() {
91
+ this.enabled = false;
92
+ },
93
+
94
+ log(category, message, data) {
95
+ if (!this.enabled) return;
96
+ console.group(`🔍 [${category}] ${message}`);
97
+ if (data) console.log(data);
98
+ console.trace();
99
+ console.groupEnd();
100
+ },
101
+
102
+ warn(category, message, data) {
103
+ if (!this.enabled) return;
104
+ console.warn(`⚠️ [${category}] ${message}`, data);
105
+ },
106
+
107
+ error(category, message, error) {
108
+ console.error(`❌ [${category}] ${message}`, error);
109
+ }
110
+ };
111
+
112
+ const MemoryManager = (function() {
113
+
114
+ let $nexObserverId = 0;
115
+ 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
+
126
+ return {
127
+ /**
128
+ * Register an observable and return an id.
129
+ *
130
+ * @param {ObservableItem} observable
131
+ * @param {Function[]} listeners
132
+ * @returns {number}
133
+ */
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
+ }
143
+ $observables.set(id, new WeakRef(observable));
144
+ return id;
145
+ },
146
+ getObservableById(id) {
147
+ return $observables.get(id)?.deref();
148
+ },
149
+ cleanup() {
150
+ for (const [_, weakObservableRef] of $observables) {
151
+ const observable = weakObservableRef.deref();
152
+ if (observable) {
153
+ observable.cleanup();
154
+ }
155
+ }
156
+ $observables.clear();
157
+ },
158
+ /**
159
+ * Clean observables that are not referenced anymore.
160
+ * @param {number} threshold
161
+ */
162
+ cleanObservables(threshold) {
163
+ if($observables.size < threshold) return;
164
+ let cleanedCount = 0;
165
+ for (const [id, weakObservableRef] of $observables) {
166
+ if (!weakObservableRef.deref()) {
167
+ $observables.delete(id);
168
+ cleanedCount++;
169
+ }
170
+ }
171
+ if (cleanedCount > 0) {
172
+ DebugManager.log('Memory Auto Clean', `🧹 Cleaned ${cleanedCount} orphaned observables`);
173
+ }
174
+ }
175
+ };
176
+ }());
177
+
178
+ class NativeDocumentError extends Error {
179
+ constructor(message, context = {}) {
180
+ super(message);
181
+ this.name = 'NativeDocumentError';
182
+ this.context = context;
183
+ this.timestamp = new Date().toISOString();
184
+ }
185
+ }
186
+
187
+ /**
188
+ *
189
+ * @param {ObservableItem} $observable
190
+ * @param {Function} $checker
191
+ * @class ObservableChecker
192
+ */
193
+ function ObservableChecker($observable, $checker) {
194
+ this.observable = $observable;
195
+ this.checker = $checker;
196
+
197
+ this.subscribe = function(callback) {
198
+ return $observable.subscribe((value) => {
199
+ callback && callback($checker(value));
200
+ });
201
+ };
202
+
203
+ this.val = function() {
204
+ return $checker && $checker($observable.val());
205
+ };
206
+ this.check = function(callback) {
207
+ return $observable.check(() => callback(this.val()));
208
+ };
209
+
210
+ this.set = function(value) {
211
+ return $observable.set(value);
212
+ };
213
+ this.trigger = function() {
214
+ return $observable.trigger();
215
+ };
216
+
217
+ this.cleanup = function() {
218
+ return $observable.cleanup();
219
+ };
220
+ }
221
+
222
+ /**
223
+ *
224
+ * @param {*} value
225
+ * @class ObservableItem
226
+ */
227
+ function ObservableItem(value) {
228
+ if (value === undefined) {
229
+ throw new NativeDocumentError('ObservableItem requires an initial value');
230
+ }
231
+ if(value instanceof ObservableItem) {
232
+ throw new NativeDocumentError('ObservableItem cannot be an Observable');
233
+ }
234
+
235
+ const $initialValue = (typeof value === 'object') ? JSON.parse(JSON.stringify(value)) : value;
236
+
237
+ let $previousValue = value;
238
+ let $currentValue = value;
239
+ let $isCleanedUp = false;
240
+
241
+ const $listeners = [];
242
+
243
+ const $memoryId = MemoryManager.register(this, $listeners);
244
+
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);
252
+ }
253
+ });
254
+
255
+ };
256
+
257
+ this.originalValue = () => $initialValue;
258
+
259
+ /**
260
+ * @param {*} data
261
+ */
262
+ this.set = (data) => {
263
+ const newValue = (typeof data === 'function') ? data($currentValue) : data;
264
+ if($currentValue === newValue) {
265
+ return;
266
+ }
267
+ $previousValue = $currentValue;
268
+ $currentValue = newValue;
269
+ this.trigger();
270
+ };
271
+
272
+ this.val = () => $currentValue;
273
+
274
+ this.cleanup = function() {
275
+ $listeners.splice(0);
276
+ $isCleanedUp = true;
277
+ };
278
+
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
+ }
292
+
293
+ $listeners.push(callback);
294
+ return () => this.unsubscribe(callback);
295
+ };
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);
305
+ }
306
+ };
307
+
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
+ });
327
+
328
+ this.toString = function() {
329
+ return '{{#ObItem::(' +$memoryId+ ')}}';
330
+ };
331
+
332
+ }
333
+
334
+ const Validator = {
335
+ isObservable(value) {
336
+ return value instanceof ObservableItem || value instanceof ObservableChecker;
337
+ },
338
+ isProxy(value) {
339
+ return value?.__isProxy__
340
+ },
341
+ isObservableChecker(value) {
342
+ return value instanceof ObservableChecker;
343
+ },
344
+ isArray(value) {
345
+ return Array.isArray(value);
346
+ },
347
+ isString(value) {
348
+ return typeof value === 'string';
349
+ },
350
+ isNumber(value) {
351
+ return typeof value === 'number';
352
+ },
353
+ isBoolean(value) {
354
+ return typeof value === 'boolean';
355
+ },
356
+ isFunction(value) {
357
+ return typeof value === 'function';
358
+ },
359
+ isObject(value) {
360
+ return typeof value === 'object';
361
+ },
362
+ isJson(value) {
363
+ return typeof value === 'object' && value !== null && value.constructor.name === 'Object' && !Array.isArray(value);
364
+ },
365
+ isElement(value) {
366
+ return value instanceof HTMLElement || value instanceof DocumentFragment || value instanceof Text;
367
+ },
368
+ isFragment(value) {
369
+ return value instanceof DocumentFragment;
370
+ },
371
+ isStringOrObservable(value) {
372
+ return this.isString(value) || this.isObservable(value);
373
+ },
374
+
375
+ isValidChild(child) {
376
+ return child === null ||
377
+ this.isElement(child) ||
378
+ this.isObservable(child) ||
379
+ ['string', 'number', 'boolean'].includes(typeof child);
380
+ },
381
+ isValidChildren(children) {
382
+ if (!Array.isArray(children)) {
383
+ children = [children];
384
+ }
385
+
386
+ const invalid = children.filter(child => !this.isValidChild(child));
387
+ return invalid.length === 0;
388
+ },
389
+ validateChildren(children) {
390
+ if (!Array.isArray(children)) {
391
+ children = [children];
392
+ }
393
+
394
+ const invalid = children.filter(child => !this.isValidChild(child));
395
+ if (invalid.length > 0) {
396
+ throw new NativeDocumentError(`Invalid children detected: ${invalid.map(i => typeof i).join(', ')}`);
397
+ }
398
+
399
+ return children;
400
+ },
401
+ /**
402
+ * Check if the data contains observables.
403
+ * @param {Array|Object} data
404
+ * @returns {boolean}
405
+ */
406
+ containsObservables(data) {
407
+ if(!data) {
408
+ return false;
409
+ }
410
+ return Validator.isObject(data)
411
+ && Object.values(data).some(value => Validator.isObservable(value));
412
+ },
413
+ /**
414
+ * Check if the data contains an observable reference.
415
+ * @param {string} data
416
+ * @returns {boolean}
417
+ */
418
+ containsObservableReference(data) {
419
+ if(!data || typeof data !== 'string') {
420
+ return false;
421
+ }
422
+ return /\{\{#ObItem::\([0-9]+\)\}\}/.test(data);
423
+ },
424
+ validateAttributes(attributes) {
425
+ if (!attributes || typeof attributes !== 'object') {
426
+ return attributes;
427
+ }
428
+
429
+ const reserved = [];
430
+ const foundReserved = Object.keys(attributes).filter(key => reserved.includes(key));
431
+
432
+ if (foundReserved.length > 0) {
433
+ DebugManager.warn('Validator', `Reserved attributes found: ${foundReserved.join(', ')}`);
434
+ }
435
+
436
+ return attributes;
437
+ },
438
+
439
+ validateEventCallback(callback) {
440
+ if (typeof callback !== 'function') {
441
+ throw new NativeDocumentError('Event callback must be a function');
442
+ }
443
+ }
444
+ };
445
+
446
+ 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'];
447
+
448
+ /**
449
+ *
450
+ * @param {Function} fn
451
+ * @param {number} delay
452
+ * @param {{leading?:Boolean, trailing?:Boolean, debounce?:Boolean}}options
453
+ * @returns {(function(...[*]): void)|*}
454
+ */
455
+ const throttle = function(fn, delay, options = {}) {
456
+ let timer = null;
457
+ let lastExecTime = 0;
458
+ const { leading = true, trailing = true, debounce = false } = options;
459
+
460
+ return function(...args) {
461
+ const now = Date.now();
462
+ if (debounce) {
463
+ // debounce mode: reset the timer for each call
464
+ clearTimeout(timer);
465
+ timer = setTimeout(() => fn.apply(this, args), delay);
466
+ return;
467
+ }
468
+ if (leading && now - lastExecTime >= delay) {
469
+ fn.apply(this, args);
470
+ lastExecTime = now;
471
+ }
472
+ if (trailing && !timer) {
473
+ timer = setTimeout(() => {
474
+ fn.apply(this, args);
475
+ lastExecTime = Date.now();
476
+ timer = null;
477
+ }, delay - (now - lastExecTime));
478
+ }
479
+ }
480
+ };
481
+
482
+ const trim = function(str, char) {
483
+ return str.replace(new RegExp(`^[${char}]+|[${char}]+$`, 'g'), '');
484
+ };
485
+
486
+ /**
487
+ *
488
+ * @param {*} value
489
+ * @returns {ObservableItem}
490
+ * @constructor
491
+ */
492
+ function Observable(value) {
493
+ return new ObservableItem(value);
494
+ }
495
+
496
+ Observable.computed = function(callback, dependencies = []) {
497
+ const initialValue = callback();
498
+ const observable = new ObservableItem(initialValue);
499
+
500
+ const updatedValue = () => observable.set(callback());
501
+
502
+ dependencies.forEach(dependency => dependency.subscribe(updatedValue));
503
+
504
+ return observable;
505
+ };
506
+
507
+ /**
508
+ *
509
+ * @param id
510
+ * @returns {ObservableItem|null}
511
+ */
512
+ Observable.getById = function(id) {
513
+ const item = MemoryManager.getObservableById(parseInt(id));
514
+ if(!item) {
515
+ throw new NativeDocumentError('Observable.getById : No observable found with id ' + id);
516
+ }
517
+ return item;
518
+ };
519
+
520
+
521
+ /**
522
+ *
523
+ * @param {ObservableItem} observable
524
+ */
525
+ Observable.cleanup = function(observable) {
526
+ observable.cleanup();
527
+ };
528
+
529
+ /**
530
+ * Get the value of an observable or an object of observables.
531
+ * @param {ObservableItem|Object<ObservableItem>} object
532
+ * @returns {{}|*|null}
533
+ */
534
+ Observable.value = function(data) {
535
+ if(Validator.isObservable(data)) {
536
+ return data.val();
537
+ }
538
+ if(Validator.isProxy(data)) {
539
+ return data.$val();
540
+ }
541
+ if(Validator.isArray(data)) {
542
+ const result = [];
543
+ data.forEach(item => {
544
+ result.push(Observable.value(item));
545
+ });
546
+ return result;
547
+ }
548
+ return data;
549
+ };
550
+
551
+ /**
552
+ *
553
+ * @param {Object} value
554
+ * @returns {Proxy}
555
+ */
556
+ Observable.init = function(value) {
557
+ const data = {};
558
+ for(const key in value) {
559
+ const itemValue = value[key];
560
+ if(Validator.isJson(itemValue)) {
561
+ data[key] = Observable.init(itemValue);
562
+ continue;
563
+ }
564
+ else if(Validator.isArray(itemValue)) {
565
+ data[key] = Observable.array(itemValue);
566
+ continue;
567
+ }
568
+ data[key] = Observable(itemValue);
569
+ }
570
+
571
+ const $val = function() {
572
+ const result = {};
573
+ for(const key in data) {
574
+ const dataItem = data[key];
575
+ if(Validator.isObservable(dataItem)) {
576
+ result[key] = dataItem.val();
577
+ } else if(Validator.isProxy(dataItem)) {
578
+ result[key] = dataItem.$val();
579
+ } else {
580
+ result[key] = dataItem;
581
+ }
582
+ }
583
+ return result;
584
+ };
585
+ const $clone = function() {
586
+
587
+ };
588
+
589
+ return new Proxy(data, {
590
+ get(target, property) {
591
+ if(property === '__isProxy__') {
592
+ return true;
593
+ }
594
+ if(property === '$val') {
595
+ return $val;
596
+ }
597
+ if(property === '$clone') {
598
+ return $clone;
599
+ }
600
+ if(target[property] !== undefined) {
601
+ return target[property];
602
+ }
603
+ return undefined;
604
+ },
605
+ set(target, prop, newValue) {
606
+ if(target[prop] !== undefined) {
607
+ target[prop].set(newValue);
608
+ }
609
+ }
610
+ })
611
+ };
612
+
613
+ Observable.object = Observable.init;
614
+ Observable.json = Observable.init;
615
+ Observable.update = function($target, data) {
616
+ for(const key in data) {
617
+ const targetItem = $target[key];
618
+ const newValue = data[key];
619
+
620
+ if(Validator.isObservable(targetItem)) {
621
+ if(Validator.isArray(newValue)) {
622
+ Observable.update(targetItem, newValue);
623
+ continue;
624
+ }
625
+ targetItem.set(newValue);
626
+ continue;
627
+ }
628
+ if(Validator.isProxy(targetItem)) {
629
+ Observable.update(targetItem, newValue);
630
+ continue;
631
+ }
632
+ $target[key] = newValue;
633
+ }
634
+ };
635
+ /**
636
+ *
637
+ * @param {Array} target
638
+ * @returns {ObservableItem}
639
+ */
640
+ Observable.array = function(target) {
641
+ if(!Array.isArray(target)) {
642
+ throw new NativeDocumentError('Observable.array : target must be an array');
643
+ }
644
+ const observer = Observable(target);
645
+
646
+ const methods = ['push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice'];
647
+
648
+ methods.forEach((method) => {
649
+ observer[method] = function(...values) {
650
+ const target = observer.val();
651
+ const result = target[method].apply(target, arguments);
652
+ observer.trigger();
653
+ return result;
654
+ };
655
+ });
656
+
657
+ const overrideMethods = ['map', 'filter', 'reduce', 'some', 'every', 'find'];
658
+ overrideMethods.forEach((method) => {
659
+ observer[method] = function(callback) {
660
+ return observer.val()[method](callback);
661
+ };
662
+ });
663
+
664
+ return observer;
665
+ };
666
+
667
+ /**
668
+ * Enable auto cleanup of observables.
669
+ * @param {Boolean} enable
670
+ * @param {{interval:Boolean, threshold:number}} options
671
+ */
672
+ Observable.autoCleanup = function(enable = false, options = {}) {
673
+ if(!enable) {
674
+ return;
675
+ }
676
+ const { interval = 60000, threshold = 100 } = options;
677
+
678
+ window.addEventListener('beforeunload', () => {
679
+ MemoryManager.cleanup();
680
+ });
681
+
682
+ setInterval(() => MemoryManager.cleanObservables(threshold), interval);
683
+ };
684
+
685
+ /**
686
+ *
687
+ * @param {HTMLElement} element
688
+ * @param {string} className
689
+ * @param {string} value
690
+ */
691
+ const toggleClassItem = function(element, className, value) {
692
+ if(value) {
693
+ element.classList.add(className);
694
+ } else {
695
+ element.classList.remove(className);
696
+ }
697
+ };
698
+
699
+ /**
700
+ *
701
+ * @param {HTMLElement} element
702
+ * @param {Object} data
703
+ */
704
+ function bindClassAttribute(element, data) {
705
+ for(let className in data) {
706
+ const value = data[className];
707
+ if(Validator.isObservable(value)) {
708
+ toggleClassItem(element, className, value.val());
709
+ value.subscribe(newValue => toggleClassItem(element, className, newValue));
710
+ continue;
711
+ }
712
+ toggleClassItem(element, className, value);
713
+ }
714
+ }
715
+
716
+ /**
717
+ *
718
+ * @param {HTMLElement} element
719
+ * @param {Object} data
720
+ */
721
+ function bindStyleAttribute(element, data) {
722
+ for(let styleName in data) {
723
+ const value = data[styleName];
724
+ if(Validator.isObservable(value)) {
725
+ element.style[styleName] = value.val();
726
+ value.subscribe(newValue => {
727
+ element.style[styleName] = newValue;
728
+ });
729
+ continue;
730
+ }
731
+ element.style[styleName] = value;
732
+ }
733
+ }
734
+
735
+ /**
736
+ *
737
+ * @param {HTMLElement} element
738
+ * @param {string} attributeName
739
+ * @param {boolean|number|Observable} value
740
+ */
741
+ function bindBooleanAttribute(element, attributeName, value) {
742
+ const defaultValue = Validator.isObservable(value) ? value.val() : value;
743
+ if(Validator.isBoolean(defaultValue)) {
744
+ element[attributeName] = defaultValue;
745
+ }
746
+ else {
747
+ element[attributeName] = defaultValue === element.value;
748
+ }
749
+ if(Validator.isObservable(value)) {
750
+ if(['checked'].includes(attributeName)) {
751
+ element.addEventListener('input', () => {
752
+ if(Validator.isBoolean(defaultValue)) {
753
+ value.set(element[attributeName]);
754
+ return;
755
+ }
756
+ value.set(element.value);
757
+ });
758
+ }
759
+ value.subscribe(newValue => {
760
+ if(Validator.isBoolean(newValue)) {
761
+ element[attributeName] = newValue;
762
+ return;
763
+ }
764
+ element[attributeName] = newValue === element.value;
765
+ });
766
+ }
767
+ }
768
+
769
+
770
+ /**
771
+ *
772
+ * @param {HTMLElement} element
773
+ * @param {string} attributeName
774
+ * @param {Observable} value
775
+ */
776
+ function bindAttributeWithObservable(element, attributeName, value) {
777
+ const applyValue = (newValue) => {
778
+ if(attributeName === 'value') {
779
+ element.value = newValue;
780
+ return;
781
+ }
782
+ element.setAttribute(attributeName, newValue);
783
+ };
784
+ value.subscribe(applyValue);
785
+ applyValue(value.val());
786
+
787
+ if(attributeName === 'value') {
788
+ element.addEventListener('input', () => value.set(element.value));
789
+ }
790
+ }
791
+
792
+ /**
793
+ *
794
+ * @param {HTMLElement} element
795
+ * @param {Object} attributes
796
+ */
797
+ function AttributesWrapper(element, attributes) {
798
+
799
+ Validator.validateAttributes(attributes);
800
+
801
+ if(!Validator.isObject(attributes)) {
802
+ throw new NativeDocumentError('Attributes must be an object');
803
+ }
804
+
805
+ for(let key in attributes) {
806
+ const attributeName = key.toLowerCase();
807
+ let value = attributes[attributeName];
808
+ if(Validator.isString(value) && Validator.isFunction(value.resolveObservableTemplate)) {
809
+ value = value.resolveObservableTemplate();
810
+ if(Validator.isArray(value)) {
811
+ const observables = value.filter(item => Validator.isObservable(item));
812
+ value = Observable.computed(() => {
813
+ return value.map(item => Validator.isObservable(item) ? item.val() : item).join(' ') || ' ';
814
+ }, observables);
815
+ }
816
+ }
817
+ if(BOOLEAN_ATTRIBUTES.includes(attributeName)) {
818
+ bindBooleanAttribute(element, attributeName, value);
819
+ continue;
820
+ }
821
+ if(Validator.isObservable(value)) {
822
+ bindAttributeWithObservable(element, attributeName, value);
823
+ continue;
824
+ }
825
+ if(attributeName === 'class' && Validator.isJson(value)) {
826
+ bindClassAttribute(element, value);
827
+ continue;
828
+ }
829
+ if(attributeName === 'style' && Validator.isJson(value)) {
830
+ bindStyleAttribute(element, value);
831
+ continue;
832
+ }
833
+ element.setAttribute(attributeName, value);
834
+ }
835
+ return element;
836
+ }
837
+
838
+ const DocumentObserver = {
839
+ elements: new Map(),
840
+ observer: null,
841
+ checkMutation: throttle(function() {
842
+ for(const [element, data] of DocumentObserver.elements.entries()) {
843
+ const isCurrentlyInDom = document.body.contains(element);
844
+ if(isCurrentlyInDom && !data.inDom) {
845
+ data.inDom = true;
846
+ data.mounted.forEach(callback => callback(element));
847
+ } else if(!isCurrentlyInDom && data.inDom) {
848
+ data.inDom = false;
849
+ data.unmounted.forEach(callback => callback(element));
850
+ }
851
+ }
852
+ }, 10, { debounce: true }),
853
+ /**
854
+ *
855
+ * @param {HTMLElement} element
856
+ * @returns {{watch: (function(): Map<any, any>), disconnect: (function(): boolean), mounted: (function(*): Set<any>), unmounted: (function(*): Set<any>)}}
857
+ */
858
+ watch: function(element) {
859
+ let data = {};
860
+ if(DocumentObserver.elements.has(element)) {
861
+ data = DocumentObserver.elements.get(element);
862
+ } else {
863
+ const inDom = document.body.contains(element);
864
+ data = {
865
+ inDom,
866
+ mounted: new Set(),
867
+ unmounted: new Set(),
868
+ };
869
+ DocumentObserver.elements.set(element, data);
870
+ }
871
+
872
+ return {
873
+ watch: () => DocumentObserver.elements.set(element, data),
874
+ disconnect: () => DocumentObserver.elements.delete(element),
875
+ mounted: (callback) => data.mounted.add(callback),
876
+ unmounted: (callback) => data.unmounted.add(callback)
877
+ };
878
+ }
879
+ };
880
+
881
+ DocumentObserver.observer = new MutationObserver(DocumentObserver.checkMutation);
882
+ DocumentObserver.observer.observe(document.body, {
883
+ childList: true,
884
+ subtree: true,
885
+ });
886
+
887
+ const getChildAsNode = (child) => {
888
+ if(Validator.isFunction(child)) {
889
+ return getChildAsNode(child());
890
+ }
891
+ if(Validator.isElement(child)) {
892
+ return child;
893
+ }
894
+ return createTextNode(String(child))
895
+ };
896
+
897
+ function Anchor(name) {
898
+ const element = document.createDocumentFragment();
899
+
900
+ const anchorStart = document.createComment('Anchor Start : '+name);
901
+ const anchorEnd = document.createComment('/ Anchor End '+name);
902
+
903
+ element.appendChild(anchorStart);
904
+ element.appendChild(anchorEnd);
905
+
906
+ element.nativeInsertBefore = element.insertBefore;
907
+ element.nativeAppendChild = element.appendChild;
908
+
909
+ const insertBefore = function(parent, child, target) {
910
+ if(parent === element) {
911
+ parent.nativeInsertBefore(getChildAsNode(child), target);
912
+ return;
913
+ }
914
+ parent.insertBefore(getChildAsNode(child), target);
915
+ };
916
+
917
+ element.appendChild = function(child, before = null) {
918
+ const parent = anchorEnd.parentNode;
919
+ if(!parent) {
920
+ DebugManager.error('Anchor', 'Anchor : parent not found', child);
921
+ return;
922
+ }
923
+ before = before ?? anchorEnd;
924
+ if(Validator.isArray(child)) {
925
+ child.forEach((element) => {
926
+ insertBefore(parent, element, before);
927
+ });
928
+ return element;
929
+ }
930
+ insertBefore(parent, child, before);
931
+ };
932
+
933
+ element.remove = function(trueRemove) {
934
+ if(anchorEnd.parentNode === element) {
935
+ return;
936
+ }
937
+ let itemToRemove = anchorStart.nextSibling, tempItem;
938
+ while(itemToRemove !== anchorEnd) {
939
+ tempItem = itemToRemove.nextSibling;
940
+ trueRemove ? itemToRemove.remove() : element.nativeAppendChild(itemToRemove);
941
+ itemToRemove = tempItem;
942
+ }
943
+ if(trueRemove) {
944
+ anchorEnd.remove();
945
+ anchorStart.remove();
946
+ }
947
+ };
948
+
949
+ element.insertBefore = function(child, anchor = null) {
950
+ element.appendChild(child, anchor);
951
+ };
952
+
953
+ element.clear = function() {
954
+ element.remove();
955
+ };
956
+
957
+ element.endElement = function() {
958
+ return anchorEnd;
959
+ };
960
+ element.startElement = function() {
961
+ return anchorStart;
962
+ };
963
+
964
+ return element;
965
+ }
966
+
967
+ const pluginsManager = (function() {
968
+
969
+ const $plugins = [];
970
+
971
+ return {
972
+ list : () => $plugins,
973
+ add : (plugin) => $plugins.push(plugin)
974
+ };
975
+ }());
976
+
977
+ /**
978
+ *
979
+ * @param {HTMLElement|DocumentFragment} parent
980
+ * @param {ObservableItem} observable
981
+ * @returns {Text}
982
+ */
983
+ const createObservableNode = function(parent, observable) {
984
+ const text = document.createTextNode('');
985
+ observable.subscribe(value => text.textContent = String(value));
986
+ text.textContent = observable.val();
987
+ parent && parent.appendChild(text);
988
+ return text;
989
+ };
990
+
991
+ /**
992
+ *
993
+ * @param {HTMLElement|DocumentFragment} parent
994
+ * @param {*} value
995
+ * @returns {Text}
996
+ */
997
+ const createStaticTextNode = function(parent, value) {
998
+ const text = document.createTextNode('');
999
+ text.textContent = String(value);
1000
+ parent && parent.appendChild(text);
1001
+ return text;
1002
+ };
1003
+
1004
+ /**
1005
+ *
1006
+ * @param {HTMLElement} element
1007
+ */
1008
+ const addUtilsMethods = function(element) {
1009
+ element.nd.wrap = (callback) => {
1010
+ if(!Validator.isFunction(callback)) {
1011
+ throw new NativeDocumentError('Callback must be a function');
1012
+ }
1013
+ callback && callback(element);
1014
+ return element;
1015
+ };
1016
+ element.nd.ref = (target, name) => {
1017
+ target[name] = element;
1018
+ return element;
1019
+ };
1020
+
1021
+ let $observer = null;
1022
+
1023
+ element.nd.appendChild = function(child) {
1024
+ if(Validator.isArray(child)) {
1025
+ ElementCreator.processChildren(child, element);
1026
+ return;
1027
+ }
1028
+ if(Validator.isFunction(child)) {
1029
+ child = child();
1030
+ ElementCreator.processChildren(child(), element);
1031
+ }
1032
+ if(Validator.isElement(child)) {
1033
+ ElementCreator.processChildren(child, element);
1034
+ }
1035
+ };
1036
+
1037
+ element.nd.lifecycle = function(states) {
1038
+ $observer = $observer || DocumentObserver.watch(element);
1039
+
1040
+ states.mounted && $observer.mounted(states.mounted);
1041
+ states.unmounted && $observer.unmounted(states.unmounted);
1042
+ return element;
1043
+ };
1044
+
1045
+ element.nd.mounted = (callback) => {
1046
+ $observer = $observer || DocumentObserver.watch(element);
1047
+ $observer.mounted(callback);
1048
+ return element;
1049
+ };
1050
+ element.nd.unmounted = (callback) => {
1051
+ $observer = $observer || DocumentObserver.watch(element);
1052
+ $observer.unmounted(callback);
1053
+ return element;
1054
+ };
1055
+ };
1056
+
1057
+ /**
1058
+ *
1059
+ * @param {*} value
1060
+ * @returns {Text}
1061
+ */
1062
+ const createTextNode = function(value) {
1063
+ return (Validator.isObservable(value))
1064
+ ? createObservableNode(null, value)
1065
+ : createStaticTextNode(null, value);
1066
+ };
1067
+
1068
+ const ElementCreator = {
1069
+ /**
1070
+ *
1071
+ * @param {string} name
1072
+ * @returns {HTMLElement|DocumentFragment}
1073
+ */
1074
+ createElement(name) {
1075
+ return name ? document.createElement(name) : new Anchor('Fragment');
1076
+ },
1077
+ /**
1078
+ *
1079
+ * @param {*} children
1080
+ * @param {HTMLElement|DocumentFragment} parent
1081
+ */
1082
+ processChildren(children, parent) {
1083
+ if(children === null) return;
1084
+ const childrenArray = Array.isArray(children) ? children : [children];
1085
+ childrenArray.forEach(child => {
1086
+ if (child === null) return;
1087
+ if(Validator.isString(child) && Validator.isFunction(child.resolveObservableTemplate)) {
1088
+ child = child.resolveObservableTemplate();
1089
+ }
1090
+ if(Validator.isFunction(child)) {
1091
+ this.processChildren(child(), parent);
1092
+ return;
1093
+ }
1094
+ if(Validator.isArray(child)) {
1095
+ this.processChildren(child, parent);
1096
+ return;
1097
+ }
1098
+ if (Validator.isElement(child)) {
1099
+ parent.appendChild(child);
1100
+ return;
1101
+ }
1102
+ if (Validator.isObservable(child)) {
1103
+ createObservableNode(parent, child);
1104
+ return;
1105
+ }
1106
+ if (child) {
1107
+ createStaticTextNode(parent, child);
1108
+ }
1109
+ });
1110
+ },
1111
+ /**
1112
+ *
1113
+ * @param {HTMLElement} element
1114
+ * @param {Object} attributes
1115
+ */
1116
+ processAttributes(element, attributes) {
1117
+ if(Validator.isFragment(element)) return;
1118
+ if (attributes) {
1119
+ AttributesWrapper(element, attributes);
1120
+ }
1121
+ },
1122
+ /**
1123
+ *
1124
+ * @param {HTMLElement} element
1125
+ * @param {Object} attributes
1126
+ * @param {?Function} customWrapper
1127
+ * @returns {HTMLElement|DocumentFragment}
1128
+ */
1129
+ setup(element, attributes, customWrapper) {
1130
+ element.nd = {};
1131
+ HtmlElementEventsWrapper(element);
1132
+ const item = (typeof customWrapper === 'function') ? customWrapper(element) : element;
1133
+ addUtilsMethods(item);
1134
+
1135
+ pluginsManager.list().forEach(plugin => {
1136
+ plugin?.element?.setup && plugin.element.setup(item, attributes);
1137
+ });
1138
+
1139
+ return item;
1140
+ }
1141
+ };
1142
+
1143
+ /**
1144
+ *
1145
+ * @param {string} name
1146
+ * @param {?Function} customWrapper
1147
+ * @returns {Function}
1148
+ */
1149
+ function HtmlElementWrapper(name, customWrapper) {
1150
+ const $tagName = name.toLowerCase().trim();
1151
+
1152
+ const builder = function(attributes, children = null) {
1153
+ try {
1154
+ if(Validator.isValidChildren(attributes)) {
1155
+ const tempChildren = children;
1156
+ children = attributes;
1157
+ attributes = tempChildren;
1158
+ }
1159
+ const element = ElementCreator.createElement($tagName);
1160
+
1161
+ ElementCreator.processAttributes(element, attributes);
1162
+ ElementCreator.processChildren(children, element);
1163
+
1164
+ return ElementCreator.setup(element, attributes, customWrapper);
1165
+ } catch (error) {
1166
+ DebugManager.error('ElementCreation', `Error creating ${$tagName}`, error);
1167
+ }
1168
+ };
1169
+
1170
+ builder.hold = (children, attributes) => (() => builder(children, attributes));
1171
+
1172
+ return builder;
1173
+ }
1174
+
1175
+ class ArgTypesError extends Error {
1176
+ constructor(message, errors) {
1177
+ super(`${message}\n\n${errors.join("\n")}\n\n`);
1178
+ }
1179
+ }
1180
+
1181
+ /**
1182
+ *
1183
+ * @type {{string: (function(*): {name: *, type: string, validate: function(*): boolean}),
1184
+ * number: (function(*): {name: *, type: string, validate: function(*): boolean}),
1185
+ * boolean: (function(*): {name: *, type: string, validate: function(*): boolean}),
1186
+ * observable: (function(*): {name: *, type: string, validate: function(*): boolean}),
1187
+ * element: (function(*): {name: *, type: string, validate: function(*): *}),
1188
+ * function: (function(*): {name: *, type: string, validate: function(*): boolean}),
1189
+ * object: (function(*): {name: *, type: string, validate: function(*): boolean}),
1190
+ * objectNotNull: (function(*): {name: *, type: string, validate: function(*): *}),
1191
+ * children: (function(*): {name: *, type: string, validate: function(*): *}),
1192
+ * attributes: (function(*): {name: *, type: string, validate: function(*): *}),
1193
+ * optional: (function(*): *&{optional: boolean}),
1194
+ * oneOf: (function(*, ...[*]): {name: *, type: string, types: *[],
1195
+ * validate: function(*): boolean})
1196
+ * }}
1197
+ */
1198
+ const ArgTypes = {
1199
+ string: (name) => ({ name, type: 'string', validate: (v) => Validator.isString(v) }),
1200
+ number: (name) => ({ name, type: 'number', validate: (v) => Validator.isNumber(v) }),
1201
+ boolean: (name) => ({ name, type: 'boolean', validate: (v) => Validator.isBoolean(v) }),
1202
+ observable: (name) => ({ name, type: 'observable', validate: (v) => Validator.isObservable(v) }),
1203
+ element: (name) => ({ name, type: 'element', validate: (v) => Validator.isElement(v) }),
1204
+ function: (name) => ({ name, type: 'function', validate: (v) => Validator.isFunction(v) }),
1205
+ object: (name) => ({ name, type: 'object', validate: (v) => (Validator.isObject(v)) }),
1206
+ objectNotNull: (name) => ({ name, type: 'object', validate: (v) => (Validator.isObject(v) && v !== null) }),
1207
+ children: (name) => ({ name, type: 'children', validate: (v) => Validator.validateChildren(v) }),
1208
+ attributes: (name) => ({ name, type: 'attributes', validate: (v) => Validator.validateAttributes(v) }),
1209
+
1210
+ // Optional arguments
1211
+ optional: (argType) => ({ ...argType, optional: true }),
1212
+
1213
+ // Union types
1214
+ oneOf: (name, ...argTypes) => ({
1215
+ name,
1216
+ type: 'oneOf',
1217
+ types: argTypes,
1218
+ validate: (v) => argTypes.some(type => type.validate(v))
1219
+ })
1220
+ };
1221
+
1222
+ /**
1223
+ *
1224
+ * @param {Array} args
1225
+ * @param {Array} argSchema
1226
+ * @param {string} fnName
1227
+ */
1228
+ const validateArgs = (args, argSchema, fnName = 'Function') => {
1229
+ if (!argSchema) return;
1230
+
1231
+ const errors = [];
1232
+
1233
+ // Check the number of arguments
1234
+ const requiredCount = argSchema.filter(arg => !arg.optional).length;
1235
+ if (args.length < requiredCount) {
1236
+ errors.push(`${fnName}: Expected at least ${requiredCount} arguments, got ${args.length}`);
1237
+ }
1238
+
1239
+ // Validate each argument
1240
+ argSchema.forEach((schema, index) => {
1241
+ const position = index + 1;
1242
+ const value = args[index];
1243
+
1244
+ if (value === undefined) {
1245
+ if (!schema.optional) {
1246
+ errors.push(`${fnName}: Missing required argument '${schema.name}' at position ${position}`);
1247
+ }
1248
+ return;
1249
+ }
1250
+
1251
+ if (!schema.validate(value)) {
1252
+ const valueTypeOf = value?.constructor?.name || typeof value;
1253
+ errors.push(`${fnName}: Invalid argument '${schema.name}' at position ${position}, expected ${schema.type}, got ${valueTypeOf}`);
1254
+ }
1255
+ });
1256
+
1257
+ if (errors.length > 0) {
1258
+ throw new ArgTypesError(`Argument validation failed`, errors);
1259
+ }
1260
+ };
1261
+
1262
+ /**
1263
+ * @param {Function} fn
1264
+ * @param {Array} argSchema
1265
+ * @param {string} fnName
1266
+ * @returns {Function}
1267
+ */
1268
+ const withValidation = (fn, argSchema, fnName = 'Function') => {
1269
+ if(!Validator.isArray(argSchema)) {
1270
+ throw new NativeDocumentError('withValidation : argSchema must be an array');
1271
+ }
1272
+ return function(...args) {
1273
+ validateArgs(args, argSchema, fn.name || fnName);
1274
+ return fn.apply(this, args);
1275
+ };
1276
+ };
1277
+
1278
+ Function.prototype.args = function(...args) {
1279
+ return withValidation(this, args);
1280
+ };
1281
+
1282
+ Function.prototype.errorBoundary = function(callback) {
1283
+ return (...args) => {
1284
+ try {
1285
+ return this.apply(this, args);
1286
+ } catch(e) {
1287
+ return callback(e);
1288
+ }
1289
+ };
1290
+ };
1291
+
1292
+ String.prototype.use = function(args) {
1293
+ const value = this;
1294
+
1295
+ return Observable.computed(() => {
1296
+ return value.replace(/\$\{(.*?)}/g, (match, key) => {
1297
+ const data = args[key];
1298
+ if(Validator.isObservable(data)) {
1299
+ return data.val();
1300
+ }
1301
+ return data;
1302
+ });
1303
+ }, Object.values(args));
1304
+ };
1305
+
1306
+ String.prototype.resolveObservableTemplate = function() {
1307
+ if(!Validator.containsObservableReference(this)) {
1308
+ return this;
1309
+ }
1310
+ return this.split(/(\{\{#ObItem::\([0-9]+\)\}\})/g).filter(Boolean).map((value) => {
1311
+ if(!Validator.containsObservableReference(value)) {
1312
+ return value;
1313
+ }
1314
+ const [_, id] = value.match(/\{\{#ObItem::\(([0-9]+)\)\}\}/);
1315
+ return Observable.getById(id);
1316
+ });
1317
+ };
1318
+
1319
+ const Store = (function() {
1320
+
1321
+ const $stores = new Map();
1322
+
1323
+ return {
1324
+ /**
1325
+ * Create a new state follower and return it.
1326
+ * @param {string} name
1327
+ * @returns {ObservableItem}
1328
+ */
1329
+ use(name) {
1330
+ const {observer: originalObserver, subscribers } = $stores.get(name);
1331
+ const observerFollower = Observable(originalObserver.val());
1332
+ const unSubscriber = originalObserver.subscribe(value => observerFollower.set(value));
1333
+ const updaterUnsubscriber = observerFollower.subscribe(value => originalObserver.set(value));
1334
+ observerFollower.destroy = () => {
1335
+ unSubscriber();
1336
+ updaterUnsubscriber();
1337
+ observerFollower.cleanup();
1338
+ };
1339
+ subscribers.add(observerFollower);
1340
+
1341
+ return observerFollower;
1342
+ },
1343
+ /**
1344
+ * @param {string} name
1345
+ * @returns {ObservableItem}
1346
+ */
1347
+ follow(name) {
1348
+ return this.use(name);
1349
+ },
1350
+ /**
1351
+ * Create a new state and return the observer.
1352
+ * @param {string} name
1353
+ * @param {*} value
1354
+ * @returns {ObservableItem}
1355
+ */
1356
+ create(name, value) {
1357
+ const observer = Observable(value);
1358
+ $stores.set(name, { observer, subscribers: new Set()});
1359
+ return observer;
1360
+ },
1361
+ /**
1362
+ * Get the observer for a state.
1363
+ * @param {string} name
1364
+ * @returns {null|ObservableItem}
1365
+ */
1366
+ get(name) {
1367
+ const item = $stores.get(name);
1368
+ return item ? item.observer : null;
1369
+ },
1370
+ /**
1371
+ *
1372
+ * @param {string} name
1373
+ * @returns {{observer: ObservableItem, subscribers: Set}}
1374
+ */
1375
+ getWithSubscribers(name) {
1376
+ return $stores.get(name);
1377
+ },
1378
+ /**
1379
+ * Delete a state.
1380
+ * @param {string} name
1381
+ */
1382
+ delete(name) {
1383
+ const item = $stores.get(name);
1384
+ if(!item) return;
1385
+ item.observer.cleanup();
1386
+ item.subscribers.forEach(follower => follower.destroy());
1387
+ item.observer.clear();
1388
+ }
1389
+ };
1390
+ }());
1391
+
1392
+ /**
1393
+ *
1394
+ * @param {*} item
1395
+ * @param {string|null} defaultKey
1396
+ * @param {?Function} key
1397
+ * @returns {*}
1398
+ */
1399
+ const getKey = (item, defaultKey, key) => {
1400
+ if(Validator.isFunction(key)) return key(item, defaultKey);
1401
+ if(Validator.isObservable(item)) {
1402
+ const val = item.val();
1403
+ return (val && key) ? val[key] : defaultKey;
1404
+ }
1405
+ return item[key] ?? defaultKey;
1406
+ };
1407
+
1408
+ /**
1409
+ *
1410
+ * @param {Map} cache
1411
+ * @param {Set} keyIds
1412
+ */
1413
+ const cleanBlockByCache = (cache, keyIds) => {
1414
+ for(const [key, {child}] of cache.entries()) {
1415
+ if(keyIds.has(key)) {
1416
+ continue;
1417
+ }
1418
+ child.remove();
1419
+ }
1420
+ };
1421
+
1422
+ /**
1423
+ *
1424
+ * @param {Array|Object|ObservableItem} data
1425
+ * @param {Function} callback
1426
+ * @param {?Function} key
1427
+ * @returns {DocumentFragment}
1428
+ */
1429
+ function ForEach(data, callback, key) {
1430
+ const element = new Anchor('ForEach');
1431
+ const blockEnd = element.endElement();
1432
+
1433
+ let cache = new Map();
1434
+
1435
+ const handleContentItem = (item, indexKey) => {
1436
+ const keyId = getKey(item, indexKey, key);
1437
+
1438
+ if(cache.has(keyId)) {
1439
+ cache.get(keyId).indexObserver.set(indexKey);
1440
+ }
1441
+ else {
1442
+ const indexObserver = Observable(indexKey);
1443
+ let child = callback(item, indexObserver);
1444
+ if(Validator.isStringOrObservable(child)) {
1445
+ child = createTextNode(child);
1446
+ }
1447
+ cache.set(keyId, { child, indexObserver});
1448
+ }
1449
+ return keyId;
1450
+ };
1451
+ const keyIds = new Set();
1452
+
1453
+ const buildContent = () => {
1454
+ const items = (Validator.isObservable(data)) ? data.val() : data;
1455
+ const parent = blockEnd.parentNode;
1456
+ if(!parent) {
1457
+ return;
1458
+ }
1459
+ keyIds.clear();
1460
+ if(Array.isArray(items)) {
1461
+ items.forEach((item, index) => keyIds.add(handleContentItem(item, index)));
1462
+ } else {
1463
+ for(const indexKey in items) {
1464
+ keyIds.add(handleContentItem(items[indexKey], indexKey));
1465
+ }
1466
+ }
1467
+
1468
+ cleanBlockByCache(cache, keyIds);
1469
+ let nextElementSibling = blockEnd;
1470
+ for(const item of [...keyIds].reverse()) {
1471
+ const { child } = cache.get(item);
1472
+ if(child) {
1473
+ if(nextElementSibling && nextElementSibling.previousSibling === child) {
1474
+ nextElementSibling = child;
1475
+ continue;
1476
+ }
1477
+ parent.insertBefore(child, nextElementSibling);
1478
+ nextElementSibling = child;
1479
+ }
1480
+ }
1481
+ };
1482
+
1483
+ buildContent();
1484
+ if(Validator.isObservable(data)) {
1485
+ data.subscribe(throttle((newValue, oldValue) => {
1486
+ buildContent();
1487
+ }, 50, { debounce: true }));
1488
+ }
1489
+ return element;
1490
+ }
1491
+
1492
+ /**
1493
+ * Show the element if the condition is true
1494
+ *
1495
+ * @param {ObservableItem|ObservableChecker} condition
1496
+ * @param {*} child
1497
+ * @param {string|null} comment
1498
+ * @returns {DocumentFragment}
1499
+ */
1500
+ const ShowIf = function(condition, child, comment = null) {
1501
+ if(!(Validator.isObservable(condition))) {
1502
+ return DebugManager.warn('ShowIf', "ShowIf : condition must be an Observable / "+comment, condition);
1503
+ }
1504
+ const element = new Anchor('Show if : '+(comment || ''));
1505
+
1506
+ let childElement = null;
1507
+ const getChildElement = () => {
1508
+ if(childElement) {
1509
+ return childElement;
1510
+ }
1511
+ if(typeof child === 'function') {
1512
+ childElement = child();
1513
+ }
1514
+ else {
1515
+ childElement = child;
1516
+ }
1517
+ if(Validator.isStringOrObservable(childElement)) {
1518
+ childElement = createTextNode(childElement);
1519
+ }
1520
+ return childElement;
1521
+ };
1522
+
1523
+ const currentValue = condition.val();
1524
+
1525
+ if(currentValue) {
1526
+ element.appendChild(getChildElement());
1527
+ }
1528
+ condition.subscribe(value => {
1529
+ if(value) {
1530
+ element.appendChild(getChildElement());
1531
+ } else {
1532
+ element.remove();
1533
+ }
1534
+ });
1535
+
1536
+ return element;
1537
+ };
1538
+
1539
+ /**
1540
+ * Hide the element if the condition is true
1541
+ * @param {ObservableItem|ObservableChecker} condition
1542
+ * @param child
1543
+ * @param comment
1544
+ * @returns {DocumentFragment}
1545
+ */
1546
+ const HideIf = function(condition, child, comment) {
1547
+
1548
+ const hideCondition = Observable(!condition.val());
1549
+ condition.subscribe(value => hideCondition.set(!value));
1550
+
1551
+ return ShowIf(hideCondition, child, comment);
1552
+ };
1553
+
1554
+ /**
1555
+ * Hide the element if the condition is false
1556
+ *
1557
+ * @param {ObservableItem|ObservableChecker} condition
1558
+ * @param {*} child
1559
+ * @param {string|null} comment
1560
+ * @returns {DocumentFragment}
1561
+ */
1562
+ const HideIfNot = function(condition, child, comment) {
1563
+ return ShowIf(condition, child, comment);
1564
+ };
1565
+
1566
+ /**
1567
+ *
1568
+ * @param {ObservableItem|ObservableChecker} $condition
1569
+ * @param {{[key]: *}} values
1570
+ * @returns {DocumentFragment}
1571
+ */
1572
+ const Match = function($condition, values) {
1573
+
1574
+ if(!Validator.isObservable($condition)) {
1575
+ throw new NativeDocumentError("Toggle : condition must be an Observable");
1576
+ }
1577
+
1578
+ const anchor = new Anchor();
1579
+ const cache = new Map();
1580
+
1581
+ const getItem = function(key) {
1582
+ if(cache.has(key)) {
1583
+ return cache.get(key);
1584
+ }
1585
+ let item = values[key];
1586
+ if(!item) {
1587
+ return null;
1588
+ }
1589
+ if(Validator.isFunction(item)) {
1590
+ item = item();
1591
+ }
1592
+ cache.set(key, item);
1593
+ return item;
1594
+ };
1595
+
1596
+ const defaultValue = $condition.val();
1597
+ const defaultContent = getItem(defaultValue);
1598
+ if(defaultContent) {
1599
+ anchor.appendChild(defaultContent);
1600
+ }
1601
+
1602
+ $condition.subscribe(value => {
1603
+ const content = getItem(value);
1604
+ anchor.remove();
1605
+ if(content) {
1606
+ anchor.appendChild(content);
1607
+ }
1608
+ });
1609
+
1610
+ return anchor;
1611
+ };
1612
+
1613
+
1614
+ /**
1615
+ *
1616
+ * @param {ObservableItem|ObservableChecker} $condition
1617
+ * @param {*} onTrue
1618
+ * @param {*} onFalse
1619
+ * @returns {DocumentFragment}
1620
+ */
1621
+ const Switch = function ($condition, onTrue, onFalse) {
1622
+
1623
+ if(!Validator.isObservable($condition)) {
1624
+ throw new NativeDocumentError("Toggle : condition must be an Observable");
1625
+ }
1626
+
1627
+ return Match($condition, {
1628
+ true: onTrue,
1629
+ false: onFalse,
1630
+ });
1631
+ };
1632
+
1633
+ /**
1634
+ *
1635
+ * @param {ObservableItem|ObservableChecker} $condition
1636
+ * @returns {{show: Function, otherwise: (((*) => {}):DocumentFragment)}
1637
+ */
1638
+ const When = function($condition) {
1639
+ if(!Validator.isObservable($condition)) {
1640
+ throw new NativeDocumentError("When : condition must be an Observable");
1641
+ }
1642
+
1643
+ let $onTrue = null;
1644
+ let $onFalse = null;
1645
+
1646
+ return {
1647
+ show(onTrue) {
1648
+ $onTrue = onTrue;
1649
+ return this;
1650
+ },
1651
+ otherwise(onFalse) {
1652
+ $onFalse = onFalse;
1653
+ return Switch($condition, $onTrue, $onFalse);
1654
+ }
1655
+ }
1656
+ };
1657
+
1658
+ const Div = HtmlElementWrapper('div');
1659
+ const Span = HtmlElementWrapper('span');
1660
+ const Label = HtmlElementWrapper('label');
1661
+ const P = HtmlElementWrapper('p');
1662
+ const Paragraph = P;
1663
+ const Strong = HtmlElementWrapper('strong');
1664
+ const H1 = HtmlElementWrapper('h1');
1665
+ const H2 = HtmlElementWrapper('h2');
1666
+ const H3 = HtmlElementWrapper('h3');
1667
+ const H4 = HtmlElementWrapper('h4');
1668
+ const H5 = HtmlElementWrapper('h5');
1669
+ const H6 = HtmlElementWrapper('h6');
1670
+
1671
+ const Br = HtmlElementWrapper('br');
1672
+
1673
+ const Link$1 = HtmlElementWrapper('a');
1674
+ const Pre = HtmlElementWrapper('pre');
1675
+ const Code = HtmlElementWrapper('code');
1676
+ const Blockquote = HtmlElementWrapper('blockquote');
1677
+ const Hr = HtmlElementWrapper('hr');
1678
+ const Em = HtmlElementWrapper('em');
1679
+ const Small = HtmlElementWrapper('small');
1680
+ const Mark = HtmlElementWrapper('mark');
1681
+ const Del = HtmlElementWrapper('del');
1682
+ const Ins = HtmlElementWrapper('ins');
1683
+ const Sub = HtmlElementWrapper('sub');
1684
+ const Sup = HtmlElementWrapper('sup');
1685
+ const Abbr = HtmlElementWrapper('abbr');
1686
+ const Cite = HtmlElementWrapper('cite');
1687
+ const Quote = HtmlElementWrapper('q');
1688
+
1689
+ const Dl = HtmlElementWrapper('dl');
1690
+ const Dt = HtmlElementWrapper('dt');
1691
+ const Dd = HtmlElementWrapper('dd');
1692
+
1693
+ const Form = HtmlElementWrapper('form', function(el) {
1694
+
1695
+ el.submit = function(action) {
1696
+ if(typeof action === 'function') {
1697
+ el.on.submit((e) => {
1698
+ e.preventDefault();
1699
+ action(e);
1700
+ });
1701
+ return el;
1702
+ }
1703
+ this.setAttribute('action', action);
1704
+ return el;
1705
+ };
1706
+ el.multipartFormData = function() {
1707
+ this.setAttribute('enctype', 'multipart/form-data');
1708
+ return el;
1709
+ };
1710
+ el.post = function(action) {
1711
+ this.setAttribute('method', 'post');
1712
+ this.setAttribute('action', action);
1713
+ return el;
1714
+ };
1715
+ el.get = function(action) {
1716
+ this.setAttribute('method', 'get');
1717
+ this.setAttribute('action', action);
1718
+ };
1719
+ return el;
1720
+ });
1721
+
1722
+ const Input = HtmlElementWrapper('input');
1723
+
1724
+ const TextArea = HtmlElementWrapper('textarea');
1725
+ const TextInput = TextArea;
1726
+
1727
+ const Select = HtmlElementWrapper('select');
1728
+ const FieldSet = HtmlElementWrapper('fieldset', );
1729
+ const Option = HtmlElementWrapper('option');
1730
+ const Legend = HtmlElementWrapper('legend');
1731
+ const Datalist = HtmlElementWrapper('datalist');
1732
+ const Output = HtmlElementWrapper('output');
1733
+ const Progress = HtmlElementWrapper('progress');
1734
+ const Meter = HtmlElementWrapper('meter');
1735
+
1736
+ const ReadonlyInput = (attributes) => Input({ readonly: true, ...attributes });
1737
+ const HiddenInput = (attributes) => Input({type: 'hidden', ...attributes });
1738
+ const FileInput = (attributes) => Input({ type: 'file', ...attributes });
1739
+ const PasswordInput = (attributes) => Input({ type: 'password', ...attributes });
1740
+ const Checkbox = (attributes) => Input({ type: 'checkbox', ...attributes });
1741
+ const Radio = (attributes) => Input({ type: 'radio', ...attributes });
1742
+
1743
+ const RangeInput = (attributes) => Input({ type: 'range', ...attributes });
1744
+ const ColorInput = (attributes) => Input({ type: 'color', ...attributes });
1745
+ const DateInput = (attributes) => Input({ type: 'date', ...attributes });
1746
+ const TimeInput = (attributes) => Input({ type: 'time', ...attributes });
1747
+ const DateTimeInput = (attributes) => Input({ type: 'datetime-local', ...attributes });
1748
+ const WeekInput = (attributes) => Input({ type: 'week', ...attributes });
1749
+ const MonthInput = (attributes) => Input({ type: 'month', ...attributes });
1750
+ const SearchInput = (attributes) => Input({ type: 'search', ...attributes });
1751
+ const TelInput = (attributes) => Input({ type: 'tel', ...attributes });
1752
+ const UrlInput = (attributes) => Input({ type: 'url', ...attributes });
1753
+ const EmailInput = (attributes) => Input({ type: 'email', ...attributes });
1754
+ const NumberInput = (attributes) => Input({ type: 'number', ...attributes });
1755
+
1756
+
1757
+ const Button = HtmlElementWrapper('button');
1758
+ const SimpleButton = (child, attributes) => Button(child, { type: 'button', ...attributes });
1759
+ const SubmitButton = (child, attributes) => Button(child, { type: 'submit', ...attributes });
1760
+
1761
+ const Main = HtmlElementWrapper('main');
1762
+ const Section = HtmlElementWrapper('section');
1763
+ const Article = HtmlElementWrapper('article');
1764
+ const Aside = HtmlElementWrapper('aside');
1765
+ const Nav = HtmlElementWrapper('nav');
1766
+ const Figure = HtmlElementWrapper('figure');
1767
+ const FigCaption = HtmlElementWrapper('figcaption');
1768
+
1769
+ const Header = HtmlElementWrapper('header');
1770
+ const Footer = HtmlElementWrapper('footer');
1771
+
1772
+ const BaseImage = HtmlElementWrapper('img');
1773
+ const Img = function(src, attributes) {
1774
+ return BaseImage({ src, ...attributes });
1775
+ };
1776
+
1777
+ /**
1778
+ *
1779
+ * @param {string} src
1780
+ * @param {string|null} defaultImage
1781
+ * @param {Object} attributes
1782
+ * @param {?Function} callback
1783
+ * @returns {Image}
1784
+ */
1785
+ const AsyncImg = function(src, defaultImage, attributes, callback) {
1786
+ const image = Img(defaultImage || src, attributes);
1787
+ const img = new Image();
1788
+ img.onload = () => {
1789
+ Validator.isFunction(callback) && callback(null, image);
1790
+ image.src = src;
1791
+ };
1792
+ img.onerror = () => {
1793
+ Validator.isFunction(callback) && callback(new NativeDocumentError('Image not found'));
1794
+ };
1795
+ if(Validator.isObservable(src)) {
1796
+ src.subscribe(newSrc => {
1797
+ img.src = newSrc;
1798
+ });
1799
+ }
1800
+ img.src = src;
1801
+ return image;
1802
+ };
1803
+
1804
+ /**
1805
+ *
1806
+ * @param {string} src
1807
+ * @param {Object} attributes
1808
+ * @returns {Image}
1809
+ */
1810
+ const LazyImg = function(src, attributes) {
1811
+ return Img(src, { ...attributes, loading: 'lazy' });
1812
+ };
1813
+
1814
+ const Details = HtmlElementWrapper('details');
1815
+ const Summary = HtmlElementWrapper('summary');
1816
+ const Dialog = HtmlElementWrapper('dialog');
1817
+ const Menu = HtmlElementWrapper('menu');
1818
+
1819
+ const OrderedList = HtmlElementWrapper('ol');
1820
+ const UnorderedList = HtmlElementWrapper('ul');
1821
+ const ListItem = HtmlElementWrapper('li');
1822
+
1823
+ const Audio = HtmlElementWrapper('audio');
1824
+ const Video = HtmlElementWrapper('video');
1825
+ const Source = HtmlElementWrapper('source');
1826
+ const Track = HtmlElementWrapper('track');
1827
+ const Canvas = HtmlElementWrapper('canvas');
1828
+ const Svg = HtmlElementWrapper('svg');
1829
+
1830
+ const Time = HtmlElementWrapper('time');
1831
+ const Data = HtmlElementWrapper('data');
1832
+ const Address = HtmlElementWrapper('address');
1833
+ const Kbd = HtmlElementWrapper('kbd');
1834
+ const Samp = HtmlElementWrapper('samp');
1835
+ const Var = HtmlElementWrapper('var');
1836
+ const Wbr = HtmlElementWrapper('wbr');
1837
+
1838
+ const Caption = HtmlElementWrapper('caption');
1839
+ const Table = HtmlElementWrapper('table');
1840
+ const THead = HtmlElementWrapper('thead');
1841
+ const TFoot = HtmlElementWrapper('tfoot');
1842
+ const TBody = HtmlElementWrapper('tbody');
1843
+ const Tr = HtmlElementWrapper('tr');
1844
+ const TRow = Tr;
1845
+ const Th = HtmlElementWrapper('th');
1846
+ const THeadCell = Th;
1847
+ const TFootCell = Th;
1848
+ const Td = HtmlElementWrapper('td');
1849
+ const TBodyCell = Td;
1850
+
1851
+ const Fragment = HtmlElementWrapper('');
1852
+
1853
+ var elements = /*#__PURE__*/Object.freeze({
1854
+ __proto__: null,
1855
+ Abbr: Abbr,
1856
+ Address: Address,
1857
+ Anchor: Anchor,
1858
+ Article: Article,
1859
+ Aside: Aside,
1860
+ AsyncImg: AsyncImg,
1861
+ Audio: Audio,
1862
+ BaseImage: BaseImage,
1863
+ Blockquote: Blockquote,
1864
+ Br: Br,
1865
+ Button: Button,
1866
+ Canvas: Canvas,
1867
+ Caption: Caption,
1868
+ Checkbox: Checkbox,
1869
+ Cite: Cite,
1870
+ Code: Code,
1871
+ ColorInput: ColorInput,
1872
+ Data: Data,
1873
+ Datalist: Datalist,
1874
+ DateInput: DateInput,
1875
+ DateTimeInput: DateTimeInput,
1876
+ Dd: Dd,
1877
+ Del: Del,
1878
+ Details: Details,
1879
+ Dialog: Dialog,
1880
+ Div: Div,
1881
+ Dl: Dl,
1882
+ Dt: Dt,
1883
+ Em: Em,
1884
+ EmailInput: EmailInput,
1885
+ FieldSet: FieldSet,
1886
+ FigCaption: FigCaption,
1887
+ Figure: Figure,
1888
+ FileInput: FileInput,
1889
+ Footer: Footer,
1890
+ ForEach: ForEach,
1891
+ Form: Form,
1892
+ Fragment: Fragment,
1893
+ H1: H1,
1894
+ H2: H2,
1895
+ H3: H3,
1896
+ H4: H4,
1897
+ H5: H5,
1898
+ H6: H6,
1899
+ Header: Header,
1900
+ HiddenInput: HiddenInput,
1901
+ HideIf: HideIf,
1902
+ HideIfNot: HideIfNot,
1903
+ Hr: Hr,
1904
+ Img: Img,
1905
+ Input: Input,
1906
+ Ins: Ins,
1907
+ Kbd: Kbd,
1908
+ Label: Label,
1909
+ LazyImg: LazyImg,
1910
+ Legend: Legend,
1911
+ Link: Link$1,
1912
+ ListItem: ListItem,
1913
+ Main: Main,
1914
+ Mark: Mark,
1915
+ Match: Match,
1916
+ Menu: Menu,
1917
+ Meter: Meter,
1918
+ MonthInput: MonthInput,
1919
+ Nav: Nav,
1920
+ NumberInput: NumberInput,
1921
+ Option: Option,
1922
+ OrderedList: OrderedList,
1923
+ Output: Output,
1924
+ P: P,
1925
+ Paragraph: Paragraph,
1926
+ PasswordInput: PasswordInput,
1927
+ Pre: Pre,
1928
+ Progress: Progress,
1929
+ Quote: Quote,
1930
+ Radio: Radio,
1931
+ RangeInput: RangeInput,
1932
+ ReadonlyInput: ReadonlyInput,
1933
+ Samp: Samp,
1934
+ SearchInput: SearchInput,
1935
+ Section: Section,
1936
+ Select: Select,
1937
+ ShowIf: ShowIf,
1938
+ SimpleButton: SimpleButton,
1939
+ Small: Small,
1940
+ Source: Source,
1941
+ Span: Span,
1942
+ Strong: Strong,
1943
+ Sub: Sub,
1944
+ SubmitButton: SubmitButton,
1945
+ Summary: Summary,
1946
+ Sup: Sup,
1947
+ Svg: Svg,
1948
+ Switch: Switch,
1949
+ TBody: TBody,
1950
+ TBodyCell: TBodyCell,
1951
+ TFoot: TFoot,
1952
+ TFootCell: TFootCell,
1953
+ THead: THead,
1954
+ THeadCell: THeadCell,
1955
+ TRow: TRow,
1956
+ Table: Table,
1957
+ Td: Td,
1958
+ TelInput: TelInput,
1959
+ TextArea: TextArea,
1960
+ TextInput: TextInput,
1961
+ Th: Th,
1962
+ Time: Time,
1963
+ TimeInput: TimeInput,
1964
+ Tr: Tr,
1965
+ Track: Track,
1966
+ UnorderedList: UnorderedList,
1967
+ UrlInput: UrlInput,
1968
+ Var: Var,
1969
+ Video: Video,
1970
+ Wbr: Wbr,
1971
+ WeekInput: WeekInput,
1972
+ When: When
1973
+ });
1974
+
1975
+ const RouteParamPatterns = {
1976
+
1977
+ };
1978
+
1979
+ /**
1980
+ *
1981
+ * @param {string} $path
1982
+ * @param {Function} $component
1983
+ * @param {{name:?string, middlewares:Function[], shouldRebuild:Boolean, with: Object }}$options
1984
+ * @class
1985
+ */
1986
+ function Route($path, $component, $options = {}) {
1987
+
1988
+ $path = '/'+trim($path, '/');
1989
+
1990
+ let $pattern = null;
1991
+ let $name = $options.name || null;
1992
+
1993
+ const $middlewares = $options.middlewares || [];
1994
+ const $shouldRebuild = $options.shouldRebuild || false;
1995
+ const $paramsValidators = $options.with || {};
1996
+
1997
+ const $params = {};
1998
+ const $paramsNames = [];
1999
+
2000
+
2001
+ const paramsExtractor = (description) => {
2002
+ if(!description) return null;
2003
+ const [name, type] = description.split(':');
2004
+
2005
+ let pattern = $paramsValidators[name];
2006
+ if(!pattern && type) {
2007
+ pattern = RouteParamPatterns[type];
2008
+ }
2009
+ if(!pattern) {
2010
+ pattern = '[^/]+';
2011
+ }
2012
+
2013
+ pattern = pattern.replace('(', '(?:');
2014
+
2015
+ return { name, pattern: `(${pattern})` };
2016
+ };
2017
+
2018
+ const getPattern = () => {
2019
+ if($pattern) {
2020
+ return $pattern;
2021
+ }
2022
+
2023
+ const patternDescription = $path.replace(/\{(.*?)}/ig, (block, definition) => {
2024
+ const description = paramsExtractor(definition);
2025
+ if(!description || !description.pattern) return block;
2026
+ $params[description.name] = description.pattern;
2027
+ $paramsNames.push(description.name);
2028
+ return description.pattern;
2029
+ });
2030
+
2031
+ $pattern = new RegExp('^'+patternDescription+'$');
2032
+ return $pattern;
2033
+ };
2034
+
2035
+ this.name = () => $name;
2036
+ this.component = () => $component;
2037
+ this.middlewares = () => $middlewares;
2038
+ this.shouldRebuild = () => $shouldRebuild;
2039
+ this.path = () => $path;
2040
+
2041
+ /**
2042
+ *
2043
+ * @param {string} path
2044
+ */
2045
+ this.match = function(path) {
2046
+ path = '/'+trim(path, '/');
2047
+ const match = getPattern().exec(path);
2048
+ if(!match) return false;
2049
+ const params = {};
2050
+
2051
+ getPattern().exec(path).forEach((value, index) => {
2052
+ if(index < 1) return;
2053
+ const name = $paramsNames[index - 1];
2054
+ params[name] = value;
2055
+ });
2056
+
2057
+ return params;
2058
+ };
2059
+ /**
2060
+ * @param {{params: ?Object, query: ?Object, basePath: ?string}} configs
2061
+ */
2062
+ this.url = function(configs) {
2063
+ const path = $path.replace(/\{(.*?)}/ig, (block, definition) => {
2064
+ const description = paramsExtractor(definition);
2065
+ if(configs.params && configs.params[description.name]) {
2066
+ return configs.params[description.name];
2067
+ }
2068
+ throw new Error(`Missing parameter '${description.name}'`);
2069
+ });
2070
+
2071
+ const queryString = (typeof configs.query === 'object') ? (new URLSearchParams(configs.query)).toString() : null;
2072
+ return (configs.basePath ? configs.basePath : '') + (queryString ? `${path}?${queryString}` : path);
2073
+ };
2074
+ }
2075
+
2076
+ class RouterError extends Error {
2077
+ constructor(message, context) {
2078
+ super(message);
2079
+ this.context = context;
2080
+ }
2081
+
2082
+ }
2083
+
2084
+ const RouteGroupHelper = {
2085
+ /**
2086
+ *
2087
+ * @param {{suffix: string, options: {middlewares: Function[], name: string}}[]} $groupTree
2088
+ * @param {string} path
2089
+ * @returns {string}
2090
+ */
2091
+ fullPath: ($groupTree, path) => {
2092
+ const fullPath = [];
2093
+ $groupTree.forEach(group => {
2094
+ fullPath.push(trim(group.suffix, '/'));
2095
+ });
2096
+ fullPath.push(trim(path, '/'));
2097
+ return fullPath.join('/');
2098
+ },
2099
+ /**
2100
+ *
2101
+ * @param {{suffix: string, options: {middlewares: Function[], name: string}}[]} $groupTree
2102
+ * @param {Function[]} middlewares
2103
+ * @returns {Function[]}
2104
+ */
2105
+ fullMiddlewares: ($groupTree, middlewares) => {
2106
+ const fullMiddlewares = [];
2107
+ $groupTree.forEach(group => {
2108
+ if(group.options.middlewares) {
2109
+ fullMiddlewares.push(...group.options.middlewares);
2110
+ }
2111
+ });
2112
+ if(middlewares) {
2113
+ fullMiddlewares.push(...middlewares);
2114
+ }
2115
+ return fullMiddlewares;
2116
+ },
2117
+ /**
2118
+ *
2119
+ * @param {{suffix: string, options: {middlewares: Function[], name: string}}[]} $groupTree
2120
+ * @param {string} name
2121
+ * @returns {string}
2122
+ */
2123
+ fullName: ($groupTree, name) => {
2124
+ const fullName = [];
2125
+ $groupTree.forEach(group => {
2126
+ if(group.options?.name) {
2127
+ fullName.push(group.options.name);
2128
+ }
2129
+ });
2130
+ name && fullName.push(name);
2131
+ return fullName.join('.');
2132
+ }
2133
+ };
2134
+
2135
+ function HashRouter() {
2136
+
2137
+ const $history = [];
2138
+ let $currentIndex = 0;
2139
+
2140
+ /**
2141
+ *
2142
+ * @param {number} delta
2143
+ */
2144
+ const go = (delta) => {
2145
+ const index = $currentIndex + delta;
2146
+ if(!$history[index]) {
2147
+ return;
2148
+ }
2149
+ $currentIndex = index;
2150
+ const { route, params, query, path } = $history[index];
2151
+ setHash(path);
2152
+ };
2153
+
2154
+ const canGoBack = function() {
2155
+ return $currentIndex > 0;
2156
+ };
2157
+ const canGoForward = function() {
2158
+ return $currentIndex < $history.length - 1;
2159
+ };
2160
+
2161
+ /**
2162
+ *
2163
+ * @param {string} path
2164
+ */
2165
+ const setHash = (path) => {
2166
+ window.location.replace(`${window.location.pathname}${window.location.search}#${path}`);
2167
+ };
2168
+
2169
+ const getCurrentHash = () => window.location.hash.slice(1);
2170
+
2171
+ /**
2172
+ * @param {string|{name:string,params?:Object, query?:Object }} target
2173
+ */
2174
+ this.push = function(target) {
2175
+ const { route, params, query, path } = this.resolve(target);
2176
+ if(path === getCurrentHash()) {
2177
+ return;
2178
+ }
2179
+ $history.splice($currentIndex + 1);
2180
+ $history.push({ route, params, query, path });
2181
+ $currentIndex++;
2182
+ setHash(path);
2183
+ };
2184
+ /**
2185
+ *
2186
+ * @param {string|{name:string,params?:Object, query?:Object }} target
2187
+ */
2188
+ this.replace = function(target) {
2189
+ const { route, params, query, path } = this.resolve(target);
2190
+ if(path === getCurrentHash()) {
2191
+ return;
2192
+ }
2193
+ $history[$currentIndex] = { route, params, query, path };
2194
+ };
2195
+ this.forward = function() {
2196
+ return canGoForward() && go(1);
2197
+ };
2198
+ this.back = function() {
2199
+ return canGoBack() && go(-1);
2200
+ };
2201
+
2202
+ /**
2203
+ * @param {string} defaultPath
2204
+ */
2205
+ this.init = function(defaultPath) {
2206
+ window.addEventListener('hashchange', () => {
2207
+ const { route, params, query, path } = this.resolve(getCurrentHash());
2208
+ this.handleRouteChange(route, params, query, path);
2209
+ });
2210
+ const { route, params, query, path } = this.resolve(defaultPath || getCurrentHash());
2211
+ $history.push({ route, params, query, path });
2212
+ $currentIndex = 0;
2213
+ this.handleRouteChange(route, params, query, path);
2214
+ };
2215
+ }
2216
+
2217
+ function HistoryRouter() {
2218
+
2219
+ /**
2220
+ *
2221
+ * @param {string|{name:string,params?:Object, query?:Object }} target
2222
+ */
2223
+ this.push = function(target) {
2224
+ try {
2225
+ const { route, path, params, query } = this.resolve(target);
2226
+ if(window.history.state && window.history.state.path === path) {
2227
+ return;
2228
+ }
2229
+ window.history.pushState({ name: route.name(), params, path}, route.name() || path , path);
2230
+ this.handleRouteChange(route, params, query, path);
2231
+ } catch (e) {
2232
+ DebugManager.error('HistoryRouter', 'Error in pushState', e);
2233
+ }
2234
+ };
2235
+ /**
2236
+ *
2237
+ * @param {string|{name:string,params?:Object, query?:Object }} target
2238
+ */
2239
+ this.replace = function(target) {
2240
+ const { route, path, params } = this.resolve(target);
2241
+ try {
2242
+ window.history.replaceState({ name: route.name(), params, path}, route.name() || path , path);
2243
+ this.handleRouteChange(route, params, {}, path);
2244
+ } catch(e) {
2245
+ DebugManager.error('HistoryRouter', 'Error in replaceState', e);
2246
+ }
2247
+ };
2248
+ this.forward = function() {
2249
+ window.history.forward();
2250
+ };
2251
+
2252
+ this.back = function() {
2253
+ window.history.back();
2254
+ };
2255
+
2256
+ /**
2257
+ * @param {string} defaultPath
2258
+ */
2259
+ this.init = function(defaultPath) {
2260
+ window.addEventListener('popstate', (event) => {
2261
+ try {
2262
+ if(!event.state || !event.state.path) {
2263
+ return;
2264
+ }
2265
+ const statePath = event.state.path;
2266
+ const {route, params, query, path} = this.resolve(statePath);
2267
+ if(!route) {
2268
+ return;
2269
+ }
2270
+ this.handleRouteChange(route, params, query, path);
2271
+ } catch(e) {
2272
+ DebugManager.error('HistoryRouter', 'Error in popstate event', e);
2273
+ }
2274
+ });
2275
+ const { route, params, query, path } = this.resolve(defaultPath || (window.location.pathname+window.location.search));
2276
+ this.handleRouteChange(route, params, query, path);
2277
+ };
2278
+
2279
+ }
2280
+
2281
+ function MemoryRouter() {
2282
+ const $history = [];
2283
+ let $currentIndex = 0;
2284
+
2285
+ /**
2286
+ *
2287
+ * @param {number} delta
2288
+ */
2289
+ const go = (delta) => {
2290
+ const index = $currentIndex + delta;
2291
+ if(!$history[index]) {
2292
+ return;
2293
+ }
2294
+ $currentIndex = index;
2295
+ const { route, params, query, path } = $history[index];
2296
+ this.handleRouteChange(route, params, query, path);
2297
+ };
2298
+
2299
+ const canGoBack = function() {
2300
+ return $currentIndex > 0;
2301
+ };
2302
+ const canGoForward = function() {
2303
+ return $currentIndex < $history.length - 1;
2304
+ };
2305
+
2306
+ /**
2307
+ *
2308
+ * @param {string|{name:string,params?:Object, query?:Object }} target
2309
+ */
2310
+ this.push = function(target) {
2311
+ const { route, params, query, path} = this.resolve(target);
2312
+ if($history[$currentIndex] && $history[$currentIndex].path === path) {
2313
+ return;
2314
+ }
2315
+ $history.splice($currentIndex + 1);
2316
+ $history.push({ route, params, query, path });
2317
+ $currentIndex++;
2318
+ this.handleRouteChange(route, params, query, path);
2319
+ };
2320
+
2321
+ /**
2322
+ *
2323
+ * @param {string|{name:string,params?:Object, query?:Object }} target
2324
+ */
2325
+ this.replace = function(target) {
2326
+ const { route, params, query, path} = this.resolve(target);
2327
+ $history[$currentIndex] = { route, params, query, path };
2328
+ this.handleRouteChange(route, params, query, path);
2329
+ };
2330
+
2331
+ this.forward = function() {
2332
+ return canGoForward() && go(1);
2333
+ };
2334
+
2335
+ this.back = function() {
2336
+ return canGoBack() && go(-1);
2337
+ };
2338
+
2339
+ /**
2340
+ * @param {string} defaultPath
2341
+ */
2342
+ this.init = function(defaultPath) {
2343
+ const currentPath = defaultPath || (window.location.pathname + window.location.search);
2344
+ const { route, params, query, path } = this.resolve(currentPath);
2345
+ $history.push({ route, params, query, path });
2346
+ $currentIndex = 0;
2347
+
2348
+ this.handleRouteChange(route, params, query, path);
2349
+ };
2350
+ }
2351
+
2352
+ /**
2353
+ *
2354
+ * @param {Router} router
2355
+ * @param {?HTMLElement} container
2356
+ */
2357
+ function RouterComponent(router, container) {
2358
+
2359
+ const $cache = new Map();
2360
+
2361
+ const updateContainer = function(node) {
2362
+ container.innerHTML = '';
2363
+ container.appendChild(node);
2364
+ };
2365
+
2366
+ const handleCurrentRouterState = function(state) {
2367
+ if(!state.route) {
2368
+ return;
2369
+ }
2370
+ const { route, params, query, path } = state;
2371
+ if($cache.has(path)) {
2372
+ const cacheNode = $cache.get(path);
2373
+ updateContainer(cacheNode);
2374
+ return;
2375
+ }
2376
+ const Component = route.component();
2377
+ const node = Component({ params, query });
2378
+ $cache.set(path, node);
2379
+ updateContainer(node);
2380
+ };
2381
+
2382
+ router.subscribe(handleCurrentRouterState);
2383
+
2384
+ handleCurrentRouterState(router.currentState());
2385
+ return container;
2386
+ }
2387
+
2388
+ const DEFAULT_ROUTER_NAME = 'default';
2389
+
2390
+ /**
2391
+ *
2392
+ * @param {{mode: 'memory'|'history'|'hash'}} $options
2393
+ * @constructor
2394
+ */
2395
+ function Router($options = {}) {
2396
+
2397
+ /** @type {Route[]} */
2398
+ const $routes = [];
2399
+ /** @type {{[string]: Route}} */
2400
+ const $routesByName = {};
2401
+ const $groupTree = [];
2402
+ const $listeners = [];
2403
+ const $currentState = { route: null, params: null, query: null, path: null, hash: null };
2404
+
2405
+ if($options.mode === 'hash') {
2406
+ HashRouter.apply(this, []);
2407
+ } else if($options.mode === 'history') {
2408
+ HistoryRouter.apply(this, []);
2409
+ } else if($options.mode === 'memory') {
2410
+ MemoryRouter.apply(this, []);
2411
+ } else {
2412
+ throw new RouterError('Invalid router mode '+$options.mode);
2413
+ }
2414
+
2415
+ const trigger = function(request, next) {
2416
+ for(const listener of $listeners) {
2417
+ try {
2418
+ listener(request);
2419
+ next && next(request);
2420
+ } catch (e) {
2421
+ DebugManager.warn('Route Listener', 'Error in listener:', e);
2422
+ }
2423
+ }
2424
+ };
2425
+
2426
+ this.routes = () => [...$routes];
2427
+ this.currentState = () => ({ ...$currentState });
2428
+
2429
+ /**
2430
+ *
2431
+ * @param {string} path
2432
+ * @param {Function} component
2433
+ * @param {{name:?string, middlewares:Function[], shouldRebuild:Boolean, with: Object }} options
2434
+ * @returns {this}
2435
+ */
2436
+ this.add = function(path, component, options) {
2437
+ const route = new Route(RouteGroupHelper.fullPath($groupTree, path), component, {
2438
+ ...options,
2439
+ middlewares: RouteGroupHelper.fullMiddlewares($groupTree, options?.middlewares || []),
2440
+ name: options?.name ? RouteGroupHelper.fullName($groupTree, options.name) : null,
2441
+ });
2442
+ $routes.push(route);
2443
+ if(route.name()) {
2444
+ $routesByName[route.name()] = route;
2445
+ }
2446
+ return this;
2447
+ };
2448
+
2449
+ /**
2450
+ *
2451
+ * @param {string} suffix
2452
+ * @param {{ middlewares: Function[], name: string}} options
2453
+ * @param {Function} callback
2454
+ * @returns {this}
2455
+ */
2456
+ this.group = function(suffix, options, callback) {
2457
+ if(!Validator.isFunction(callback)) {
2458
+ throw new RouterError('Callback must be a function');
2459
+ }
2460
+ $groupTree.push({suffix, options});
2461
+ callback();
2462
+ $groupTree.pop();
2463
+ return this;
2464
+ };
2465
+
2466
+ /**
2467
+ *
2468
+ * @param {string} name
2469
+ * @param {Object}params
2470
+ * @param {Object} query
2471
+ * @returns {*}
2472
+ */
2473
+ this.generateUrl = function(name, params = {}, query = {}) {
2474
+ const route = $routesByName[name];
2475
+ if(!route) {
2476
+ throw new RouterError(`Route not found for name: ${name}`);
2477
+ }
2478
+ return route.url({ params, query });
2479
+ };
2480
+
2481
+ /**
2482
+ *
2483
+ * @param {string|{name:string,params?:Object, query?:Object }} target
2484
+ * @returns {{route:Route, params:Object, query:Object, path:string}}
2485
+ */
2486
+ this.resolve = function(target) {
2487
+ if(Validator.isJson(target)) {
2488
+ const route = $routesByName[target.name];
2489
+ if(!route) {
2490
+ throw new RouterError(`Route not found for name: ${target.name}`);
2491
+ }
2492
+ return {
2493
+ route,
2494
+ params: target.params,
2495
+ query: target.query,
2496
+ path: route.url({ ...target })
2497
+ };
2498
+ }
2499
+
2500
+ const [urlPath, urlQuery] = target.split('?');
2501
+ const path = '/'+trim(urlPath, '/');
2502
+ let routeFound = null, params;
2503
+
2504
+ for(const route of $routes) {
2505
+ params = route.match(path);
2506
+ if(params) {
2507
+ routeFound = route;
2508
+ break;
2509
+ }
2510
+ }
2511
+ if(!routeFound) {
2512
+ throw new RouterError(`Route not found for url: ${urlPath}`);
2513
+ }
2514
+ const queryParams = {};
2515
+ if(urlQuery) {
2516
+ const queries = new URLSearchParams(urlQuery).entries();
2517
+ for (const [key, value] of queries) {
2518
+ queryParams[key] = value;
2519
+ }
2520
+ }
2521
+
2522
+ return { route: routeFound, params, query: queryParams, path: target };
2523
+ };
2524
+
2525
+ /**
2526
+ *
2527
+ * @param {Function} listener
2528
+ * @returns {(function(): void)|*}
2529
+ */
2530
+ this.subscribe = function(listener) {
2531
+ if(!Validator.isFunction(listener)) {
2532
+ throw new RouterError('Listener must be a function');
2533
+ }
2534
+ $listeners.push(listener);
2535
+ return () => {
2536
+ $listeners.splice($listeners.indexOf(listener), 1);
2537
+ };
2538
+ };
2539
+
2540
+ /**
2541
+ *
2542
+ * @param {Route} route
2543
+ * @param {Object} params
2544
+ * @param {Object} query
2545
+ * @param {string} path
2546
+ */
2547
+ this.handleRouteChange = function(route, params, query, path) {
2548
+ $currentState.route = route;
2549
+ $currentState.params = params;
2550
+ $currentState.query = query;
2551
+ $currentState.path = path;
2552
+
2553
+ const middlewares = [...route.middlewares(), trigger];
2554
+ let currentIndex = 0;
2555
+ const request = { ...$currentState };
2556
+
2557
+ const next = (editableRequest) => {
2558
+ currentIndex++;
2559
+ if(currentIndex >= middlewares.length) {
2560
+ return;
2561
+ }
2562
+ return middlewares[currentIndex](editableRequest || request, next);
2563
+ };
2564
+ return middlewares[currentIndex](request, next);
2565
+ };
2566
+
2567
+ }
2568
+
2569
+ Router.routers = {};
2570
+
2571
+ /**
2572
+ *
2573
+ * @param {{mode: 'memory'|'history'|'hash', name?:string, entry?: string}} options
2574
+ * @param {Function} callback
2575
+ * @param {Element} container
2576
+ */
2577
+ Router.create = function(options, callback) {
2578
+ if(!Validator.isFunction(callback)) {
2579
+ DebugManager.error('Router', 'Callback must be a function', e);
2580
+ throw new RouterError('Callback must be a function');
2581
+ }
2582
+ const router = new Router(options);
2583
+ Router.routers[options.name || DEFAULT_ROUTER_NAME] = router;
2584
+ callback(router);
2585
+
2586
+ router.init(options.entry);
2587
+
2588
+ router.mount = function(container) {
2589
+ if(Validator.isString(container)) {
2590
+ const mountContainer = document.querySelector(container);
2591
+ if(!mountContainer) {
2592
+ throw new RouterError(`Container not found for selector: ${container}`);
2593
+ }
2594
+ container = mountContainer;
2595
+ } else if(!Validator.isElement(container)) {
2596
+ throw new RouterError('Container must be a string or an Element');
2597
+ }
2598
+
2599
+ return RouterComponent(router, container);
2600
+ };
2601
+
2602
+ return router;
2603
+ };
2604
+
2605
+ Router.get = function(name) {
2606
+ const router = Router.routers[name || DEFAULT_ROUTER_NAME];
2607
+ if(!router) {
2608
+ throw new RouterError(`Router not found for name: ${name}`);
2609
+ }
2610
+ return router;
2611
+ };
2612
+
2613
+ Router.push = function(target, name = null) {
2614
+ return Router.get(name).push(target);
2615
+ };
2616
+
2617
+ Router.replace = function(target, name = null) {
2618
+ return Router.get(name).replace(target);
2619
+ };
2620
+
2621
+ Router.forward = function(name = null) {
2622
+ return Router.get(name).forward();
2623
+ };
2624
+ Router.back = function(name = null) {
2625
+ return Router.get(name).back();
2626
+ };
2627
+
2628
+ function Link(options, children){
2629
+ const { to, href, ...attributes } = options;
2630
+ const target = to || href;
2631
+ if(Validator.isString(target)) {
2632
+ const router = Router.get();
2633
+ return Link$1({ ...attributes, href: target}, children).nd.on.prevent.click(() => {
2634
+ router.push(target);
2635
+ });
2636
+ }
2637
+ const routerName = target.router || DEFAULT_ROUTER_NAME;
2638
+ const router = Router.get(routerName);
2639
+ console.log(routerName);
2640
+ if(!router) {
2641
+ throw new RouterError('Router not found "'+routerName+'" for link "'+target.name+'"');
2642
+ }
2643
+ const url = router.generateUrl(target.name, target.params, target.query);
2644
+ return Link$1({ ...attributes, href: url }, children).nd.on.prevent.click(() => {
2645
+ router.push(url);
2646
+ });
2647
+ }
2648
+
2649
+ Link.blank = function(attributes, children){
2650
+ return Link$1({ ...attributes, target: '_blank'}, children);
2651
+ };
2652
+
2653
+ var router = /*#__PURE__*/Object.freeze({
2654
+ __proto__: null,
2655
+ Link: Link,
2656
+ RouteParamPatterns: RouteParamPatterns,
2657
+ Router: Router
2658
+ });
2659
+
2660
+ exports.ArgTypes = ArgTypes;
2661
+ exports.ElementCreator = ElementCreator;
2662
+ exports.HtmlElementWrapper = HtmlElementWrapper;
2663
+ exports.Observable = Observable;
2664
+ exports.Store = Store;
2665
+ exports.elements = elements;
2666
+ exports.router = router;
2667
+ exports.withValidation = withValidation;
2668
+
2669
+ return exports;
2670
+
2671
+ })({});