native-document 1.0.0 โ†’ 1.0.1

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