native-document 1.0.94 → 1.0.98

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/{src/devtools/hrm → devtools}/ComponentRegistry.js +2 -2
  2. package/devtools/index.js +8 -0
  3. package/{src/devtools/plugin.js → devtools/plugin/dev-tools-plugin.js} +2 -2
  4. package/{src/devtools/hrm/nd-vite-hot-reload.js → devtools/transformers/nd-vite-devtools.js} +16 -6
  5. package/devtools/transformers/src/transformComponentForHrm.js +74 -0
  6. package/devtools/transformers/src/transformJsFile.js +9 -0
  7. package/devtools/transformers/src/utils.js +79 -0
  8. package/devtools/widget/Widget.js +48 -0
  9. package/devtools/widget/widget.css +81 -0
  10. package/devtools/widget.js +23 -0
  11. package/dist/native-document.components.min.js +2441 -1191
  12. package/dist/native-document.dev.js +2710 -1359
  13. package/dist/native-document.dev.js.map +1 -1
  14. package/dist/native-document.devtools.min.js +1 -1
  15. package/dist/native-document.min.js +1 -1
  16. package/docs/cache.md +1 -1
  17. package/docs/core-concepts.md +1 -1
  18. package/docs/native-document-element.md +51 -15
  19. package/docs/observables.md +310 -306
  20. package/docs/state-management.md +198 -193
  21. package/index.def.js +762 -26
  22. package/package.json +1 -1
  23. package/readme.md +1 -1
  24. package/src/core/data/ObservableChecker.js +2 -0
  25. package/src/core/data/ObservableItem.js +97 -0
  26. package/src/core/data/ObservableObject.js +182 -0
  27. package/src/core/data/Store.js +364 -34
  28. package/src/core/data/observable-helpers/object.js +2 -166
  29. package/src/core/elements/anchor.js +28 -20
  30. package/src/core/elements/control/for-each.js +1 -1
  31. package/src/core/utils/formatters.js +91 -0
  32. package/src/core/utils/localstorage.js +57 -0
  33. package/src/core/utils/validator.js +0 -2
  34. package/src/core/wrappers/DocumentObserver.js +102 -31
  35. package/src/core/wrappers/ElementCreator.js +5 -0
  36. package/src/core/wrappers/NDElement.js +32 -1
  37. package/src/core/wrappers/prototypes/nd-element.transition.extensions.js +83 -0
  38. package/src/devtools.js +9 -0
  39. package/src/fetch/NativeFetch.js +5 -2
  40. package/types/elements.d.ts +580 -2
  41. package/types/nd-element.d.ts +6 -0
  42. package/types/observable.d.ts +71 -15
  43. package/types/plugins-manager.d.ts +1 -1
  44. package/types/store.d.ts +33 -6
  45. package/hrm.js +0 -7
  46. package/src/devtools/app/App.js +0 -66
  47. package/src/devtools/app/app.css +0 -0
  48. package/src/devtools/hrm/transformComponent.js +0 -129
  49. package/src/devtools/index.js +0 -18
  50. package/src/devtools/widget/DevToolsWidget.js +0 -26
  51. /package/{src/devtools/hrm → devtools/transformers/templates}/hrm.hook.template.js +0 -0
  52. /package/{src/devtools/hrm → devtools/transformers/templates}/hrm.orbservable.hook.template.js +0 -0
@@ -374,59 +374,6 @@ var NativeComponents = (function (exports) {
374
374
  }
375
375
  var DebugManager = DebugManager$1;
376
376
 
377
- const MemoryManager = (function() {
378
-
379
- let $nextObserverId = 0;
380
- const $observables = new Map();
381
-
382
- return {
383
- /**
384
- * Register an observable and return an id.
385
- *
386
- * @param {ObservableItem} observable
387
- * @param {Function} getListeners
388
- * @returns {number}
389
- */
390
- register(observable) {
391
- const id = ++$nextObserverId;
392
- $observables.set(id, new WeakRef(observable));
393
- return id;
394
- },
395
- unregister(id) {
396
- $observables.delete(id);
397
- },
398
- getObservableById(id) {
399
- return $observables.get(id)?.deref();
400
- },
401
- cleanup() {
402
- for (const [_, weakObservableRef] of $observables) {
403
- const observable = weakObservableRef.deref();
404
- if (observable) {
405
- observable.cleanup();
406
- }
407
- }
408
- $observables.clear();
409
- },
410
- /**
411
- * Clean observables that are not referenced anymore.
412
- * @param {number} threshold
413
- */
414
- cleanObservables(threshold) {
415
- if($observables.size < threshold) return;
416
- let cleanedCount = 0;
417
- for (const [id, weakObservableRef] of $observables) {
418
- if (!weakObservableRef.deref()) {
419
- $observables.delete(id);
420
- cleanedCount++;
421
- }
422
- }
423
- if (cleanedCount > 0) {
424
- DebugManager.log('Memory Auto Clean', `🧹 Cleaned ${cleanedCount} orphaned observables`);
425
- }
426
- }
427
- };
428
- }());
429
-
430
377
  /**
431
378
  *
432
379
  * @param {ObservableItem} $observable
@@ -519,1191 +466,1993 @@ var NativeComponents = (function (exports) {
519
466
  return this.observable.cleanup();
520
467
  };
521
468
 
522
- /**
523
- * Creates an ObservableWhen that tracks whether an observable equals a specific value.
524
- *
525
- * @param {ObservableItem} observer - The observable to watch
526
- * @param {*} value - The value to compare against
527
- * @class ObservableWhen
528
- */
529
- const ObservableWhen = function(observer, value) {
530
- this.$target = value;
531
- this.$observer = observer;
532
- };
533
-
534
- ObservableWhen.prototype.__$isObservableWhen = true;
469
+ const DocumentObserver = {
470
+ mounted: new WeakMap(),
471
+ beforeUnmount: new WeakMap(),
472
+ mountedSupposedSize: 0,
473
+ unmounted: new WeakMap(),
474
+ unmountedSupposedSize: 0,
475
+ observer: null,
535
476
 
536
- /**
537
- * Subscribes to changes in the match status (true when observable equals target value).
538
- *
539
- * @param {Function} callback - Function called with boolean indicating if values match
540
- * @returns {Function} Unsubscribe function
541
- * @example
542
- * const status = Observable('idle');
543
- * const isLoading = status.when('loading');
544
- * isLoading.subscribe(active => console.log('Loading:', active));
545
- */
546
- ObservableWhen.prototype.subscribe = function(callback) {
547
- return this.$observer.on(this.$target, callback);
548
- };
477
+ executeMountedCallback(node) {
478
+ const data = DocumentObserver.mounted.get(node);
479
+ if(!data) {
480
+ return;
481
+ }
482
+ data.inDom = true;
483
+ if(!data.mounted) {
484
+ return;
485
+ }
486
+ if(Array.isArray(data.mounted)) {
487
+ for(const cb of data.mounted) {
488
+ cb(node);
489
+ }
490
+ return;
491
+ }
492
+ data.mounted(node);
493
+ },
549
494
 
550
- /**
551
- * Returns true if the observable's current value equals the target value.
552
- *
553
- * @returns {boolean} True if observable value matches target value
554
- */
555
- ObservableWhen.prototype.val = function() {
556
- return this.$observer.$currentValue === this.$target;
557
- };
495
+ executeUnmountedCallback(node) {
496
+ const data = DocumentObserver.unmounted.get(node);
497
+ if(!data) {
498
+ return;
499
+ }
500
+ data.inDom = false;
501
+ if(!data.unmounted) {
502
+ return;
503
+ }
558
504
 
559
- /**
560
- * Returns true if the observable's current value equals the target value.
561
- * Alias for val().
562
- *
563
- * @returns {boolean} True if observable value matches target value
564
- */
565
- ObservableWhen.prototype.isMatch = ObservableWhen.prototype.val;
505
+ let shouldRemove = false;
506
+ if(Array.isArray(data.unmounted)) {
507
+ for(const cb of data.unmounted) {
508
+ if(cb(node) === true) {
509
+ shouldRemove = true;
510
+ }
511
+ }
512
+ } else {
513
+ shouldRemove = data.unmounted(node) === true;
514
+ }
566
515
 
567
- /**
568
- * Returns true if the observable's current value equals the target value.
569
- * Alias for val().
570
- *
571
- * @returns {boolean} True if observable value matches target value
572
- */
573
- ObservableWhen.prototype.isActive = ObservableWhen.prototype.val;
516
+ if(shouldRemove) {
517
+ data.disconnect();
518
+ node.nd?.remove();
519
+ }
520
+ },
574
521
 
575
- const invoke = function(fn, args, context) {
576
- if(context) {
577
- fn.apply(context, args);
578
- } else {
579
- fn(...args);
580
- }
581
- };
582
- /**
583
- *
584
- * @param {Function} fn
585
- * @param {number} delay
586
- * @param {{leading?:Boolean, trailing?:Boolean, debounce?:Boolean, check: Function}}options
587
- * @returns {(function(...[*]): void)|*}
588
- */
589
- const debounce = function(fn, delay, options = {}) {
590
- let timer = null;
591
- let lastArgs = null;
522
+ checkMutation: function(mutationsList) {
523
+ for(const mutation of mutationsList) {
524
+ if(DocumentObserver.mountedSupposedSize > 0) {
525
+ for(const node of mutation.addedNodes) {
526
+ DocumentObserver.executeMountedCallback(node);
527
+ if(!node.querySelectorAll) {
528
+ continue;
529
+ }
530
+ const children = node.querySelectorAll('[data--nd-mounted]');
531
+ for(const child of children) {
532
+ DocumentObserver.executeMountedCallback(child);
533
+ }
534
+ }
535
+ }
592
536
 
593
- return function(...args) {
594
- const context = options.context === true ? this : null;
595
- if(options.check) {
596
- options.check(...args);
537
+ if (DocumentObserver.unmountedSupposedSize > 0) {
538
+ for (const node of mutation.removedNodes) {
539
+ DocumentObserver.executeUnmountedCallback(node);
540
+ if(!node.querySelectorAll) {
541
+ continue;
542
+ }
543
+ const children = node.querySelectorAll('[data--nd-unmounted]');
544
+ for(const child of children) {
545
+ DocumentObserver.executeUnmountedCallback(child);
546
+ }
547
+ }
548
+ }
597
549
  }
598
- lastArgs = args;
550
+ },
599
551
 
600
- // debounce mode: reset the timer for each call
601
- clearTimeout(timer);
602
- timer = setTimeout(() => invoke(fn, lastArgs, context), delay);
603
- }
604
- };
552
+ /**
553
+ * @param {HTMLElement} element
554
+ * @param {boolean} inDom
555
+ * @returns {{ disconnect: Function, mounted: Function, unmounted: Function, off: Function }}
556
+ */
557
+ watch: function(element, inDom = false) {
558
+ let mountedRegistered = false;
559
+ let unmountedRegistered = false;
605
560
 
606
- const nextTick = function(fn) {
607
- let pending = false;
608
- return function(...args) {
609
- if (pending) return;
610
- pending = true;
561
+ let data = {
562
+ inDom,
563
+ mounted: null,
564
+ unmounted: null,
565
+ disconnect: () => {
566
+ if (mountedRegistered) {
567
+ DocumentObserver.mounted.delete(element);
568
+ DocumentObserver.mountedSupposedSize--;
569
+ }
570
+ if (unmountedRegistered) {
571
+ DocumentObserver.unmounted.delete(element);
572
+ DocumentObserver.unmountedSupposedSize--;
573
+ }
574
+ data = null;
575
+ }
576
+ };
611
577
 
612
- Promise.resolve().then(() => {
613
- fn.apply(this, args);
614
- pending = false;
615
- });
616
- };
617
- };
578
+ const addListener = (type, callback) => {
579
+ if (!data[type]) {
580
+ data[type] = callback;
581
+ return;
582
+ }
583
+ if (!Array.isArray(data[type])) {
584
+ data[type] = [data[type], callback];
585
+ return;
586
+ }
587
+ data[type].push(callback);
588
+ };
618
589
 
619
- const deepClone = (value, onObservableFound) => {
620
- // Primitives
621
- if (value === null || typeof value !== 'object') {
622
- return value;
623
- }
590
+ const removeListener = (type, callback) => {
591
+ if(!data?.[type]) {
592
+ return;
593
+ }
594
+ if(Array.isArray(data[type])) {
595
+ const index = data[type].indexOf(callback);
596
+ if(index > -1) {
597
+ data[type].splice(index, 1);
598
+ }
599
+ if(data[type].length === 1) {
600
+ data[type] = data[type][0];
601
+ }
602
+ if(data[type].length === 0) {
603
+ data[type] = null;
604
+ }
605
+ return;
606
+ }
607
+ data[type] = null;
608
+ };
624
609
 
625
- // Dates
626
- if (value instanceof Date) {
627
- return new Date(value.getTime());
628
- }
610
+ return {
611
+ disconnect: () => data?.disconnect(),
629
612
 
630
- // Arrays
631
- if (Array.isArray(value)) {
632
- return value.map(item => deepClone(item));
633
- }
613
+ mounted: (callback) => {
614
+ addListener('mounted', callback);
615
+ DocumentObserver.mounted.set(element, data);
616
+ if (!mountedRegistered) {
617
+ DocumentObserver.mountedSupposedSize++;
618
+ mountedRegistered = true;
619
+ }
620
+ },
634
621
 
635
- // Observables - keep the référence
636
- if (Validator.isObservable(value)) {
637
- onObservableFound && onObservableFound(value);
638
- return value;
639
- }
622
+ unmounted: (callback) => {
623
+ addListener('unmounted', callback);
624
+ DocumentObserver.unmounted.set(element, data);
625
+ if (!unmountedRegistered) {
626
+ DocumentObserver.unmountedSupposedSize++;
627
+ unmountedRegistered = true;
628
+ }
629
+ },
640
630
 
641
- // Objects
642
- const cloned = {};
643
- for (const key in value) {
644
- if (value.hasOwnProperty(key)) {
645
- cloned[key] = deepClone(value[key]);
646
- }
631
+ off: (type, callback) => {
632
+ removeListener(type, callback);
633
+ }
634
+ };
647
635
  }
648
- return cloned;
649
636
  };
650
637
 
651
- /**
652
- *
653
- * @param {*} value
654
- * @param {{ propagation: boolean, reset: boolean} | null} configs
655
- * @class ObservableItem
656
- */
657
- function ObservableItem(value, configs = null) {
658
- value = Validator.isObservable(value) ? value.val() : value;
659
-
660
- this.$previousValue = null;
661
- this.$currentValue = value;
662
-
663
- this.$firstListener = null;
664
- this.$listeners = null;
665
- this.$watchers = null;
666
-
667
- this.$memoryId = null;
638
+ DocumentObserver.observer = new MutationObserver(DocumentObserver.checkMutation);
639
+ DocumentObserver.observer.observe(document.body, {
640
+ childList: true,
641
+ subtree: true,
642
+ });
668
643
 
669
- if(configs) {
670
- this.configs = configs;
671
- if(configs.reset) {
672
- this.$initialValue = Validator.isObject(value) ? deepClone(value) : value;
673
- }
674
- }
644
+ function NDElement(element) {
645
+ this.$element = element;
646
+ this.$observer = null;
675
647
  }
676
648
 
677
- Object.defineProperty(ObservableItem.prototype, '$value', {
678
- get() {
679
- return this.$currentValue;
680
- },
681
- set(value) {
682
- this.set(value);
683
- },
684
- configurable: true,
685
- });
649
+ NDElement.prototype.__$isNDElement = true;
686
650
 
687
- ObservableItem.prototype.__$isObservable = true;
688
- const noneTrigger = function() {};
651
+ NDElement.prototype.valueOf = function() {
652
+ return this.$element;
653
+ };
689
654
 
690
- /**
691
- * Intercepts and transforms values before they are set on the observable.
692
- * The interceptor can modify the value or return undefined to use the original value.
693
- *
694
- * @param {(value) => any} callback - Interceptor function that receives (newValue, currentValue) and returns the transformed value or undefined
695
- * @returns {ObservableItem} The observable instance for chaining
696
- * @example
697
- * const count = Observable(0);
698
- * count.intercept((newVal, oldVal) => Math.max(0, newVal)); // Prevent negative values
699
- */
700
- ObservableItem.prototype.intercept = function(callback) {
701
- this.$interceptor = callback;
702
- this.set = this.$setWithInterceptor;
655
+ NDElement.prototype.ref = function(target, name) {
656
+ target[name] = this.$element;
703
657
  return this;
704
658
  };
705
659
 
706
- ObservableItem.prototype.triggerFirstListener = function(operations) {
707
- this.$firstListener(this.$currentValue, this.$previousValue, operations);
660
+ NDElement.prototype.refSelf = function(target, name) {
661
+ target[name] = this;
662
+ return this;
708
663
  };
709
664
 
710
- ObservableItem.prototype.triggerListeners = function(operations) {
711
- const $listeners = this.$listeners;
712
- const $previousValue = this.$previousValue;
713
- const $currentValue = this.$currentValue;
714
-
715
- for(let i = 0, length = $listeners.length; i < length; i++) {
716
- $listeners[i]($currentValue, $previousValue, operations);
665
+ NDElement.prototype.unmountChildren = function() {
666
+ let element = this.$element;
667
+ for(let i = 0, length = element.children.length; i < length; i++) {
668
+ let elementChildren = element.children[i];
669
+ if(!elementChildren.$ndProx) {
670
+ elementChildren.nd?.remove();
671
+ }
672
+ elementChildren = null;
717
673
  }
674
+ element = null;
675
+ return this;
718
676
  };
719
677
 
720
- ObservableItem.prototype.triggerWatchers = function(operations) {
721
- const $watchers = this.$watchers;
722
- const $previousValue = this.$previousValue;
723
- const $currentValue = this.$currentValue;
678
+ NDElement.prototype.remove = function() {
679
+ let element = this.$element;
680
+ element.nd.unmountChildren();
681
+ element.$ndProx = null;
682
+ delete element.nd?.on?.prevent;
683
+ delete element.nd?.on;
684
+ delete element.nd;
685
+ element = null;
686
+ return this;
687
+ };
724
688
 
725
- const $currentValueCallbacks = $watchers.get($currentValue);
726
- const $previousValueCallbacks = $watchers.get($previousValue);
727
- if($currentValueCallbacks) {
728
- $currentValueCallbacks(true, $previousValue, operations);
689
+ NDElement.prototype.lifecycle = function(states) {
690
+ this.$observer = this.$observer || DocumentObserver.watch(this.$element);
691
+
692
+ if(states.mounted) {
693
+ this.$element.setAttribute('data--nd-mounted', '1');
694
+ this.$observer.mounted(states.mounted);
729
695
  }
730
- if($previousValueCallbacks) {
731
- $previousValueCallbacks(false, $currentValue, operations);
696
+ if(states.unmounted) {
697
+ this.$element.setAttribute('data--nd-unmounted', '1');
698
+ this.$observer.unmounted(states.unmounted);
732
699
  }
700
+ return this;
733
701
  };
734
702
 
735
- ObservableItem.prototype.triggerAll = function(operations) {
736
- this.triggerWatchers(operations);
737
- this.triggerListeners(operations);
703
+ NDElement.prototype.mounted = function(callback) {
704
+ return this.lifecycle({ mounted: callback });
738
705
  };
739
706
 
740
- ObservableItem.prototype.triggerWatchersAndFirstListener = function(operations) {
741
- this.triggerWatchers(operations);
742
- this.triggerFirstListener(operations);
707
+ NDElement.prototype.unmounted = function(callback) {
708
+ return this.lifecycle({ unmounted: callback });
743
709
  };
744
710
 
745
- ObservableItem.prototype.assocTrigger = function() {
746
- this.$firstListener = null;
747
- if(this.$watchers?.size && this.$listeners?.length) {
748
- this.trigger = (this.$listeners.length === 1) ? this.triggerWatchersAndFirstListener : this.triggerAll;
749
- return;
750
- }
751
- if(this.$listeners?.length) {
752
- if(this.$listeners.length === 1) {
753
- this.$firstListener = this.$listeners[0];
754
- this.trigger = this.triggerFirstListener;
755
- }
756
- else {
757
- this.trigger = this.triggerListeners;
758
- }
759
- return;
760
- }
761
- if(this.$watchers?.size) {
762
- this.trigger = this.triggerWatchers;
763
- return;
764
- }
765
- this.trigger = noneTrigger;
766
- };
767
- ObservableItem.prototype.trigger = noneTrigger;
711
+ NDElement.prototype.beforeUnmount = function(id, callback) {
712
+ const el = this.$element;
768
713
 
769
- ObservableItem.prototype.$updateWithNewValue = function(newValue) {
770
- newValue = newValue?.__$isObservable ? newValue.val() : newValue;
771
- if(this.$currentValue === newValue) {
772
- return;
773
- }
774
- this.$previousValue = this.$currentValue;
775
- this.$currentValue = newValue;
776
- this.trigger();
777
- this.$previousValue = null;
778
- };
714
+ if(!DocumentObserver.beforeUnmount.has(el)) {
715
+ DocumentObserver.beforeUnmount.set(el, new Map());
716
+ const originalRemove = el.remove.bind(el);
779
717
 
780
- /**
781
- * @param {*} data
782
- */
783
- ObservableItem.prototype.$setWithInterceptor = function(data) {
784
- let newValue = (typeof data === 'function') ? data(this.$currentValue) : data;
785
- const result = this.$interceptor(newValue, this.$currentValue);
718
+ let $isUnmounting = false;
786
719
 
787
- if (result !== undefined) {
788
- newValue = result;
720
+ el.remove = async () => {
721
+ if($isUnmounting) {
722
+ return;
723
+ }
724
+ $isUnmounting = true;
725
+
726
+ try {
727
+ const callbacks = DocumentObserver.beforeUnmount.get(el);
728
+ for (const cb of callbacks.values()) {
729
+ await cb.call(this, el);
730
+ }
731
+ } finally {
732
+ originalRemove();
733
+ $isUnmounting = false;
734
+ }
735
+ };
789
736
  }
790
737
 
791
- this.$updateWithNewValue(newValue);
738
+ DocumentObserver.beforeUnmount.get(el).set(id, callback);
739
+ return this;
792
740
  };
793
741
 
794
- /**
795
- * @param {*} data
796
- */
797
- ObservableItem.prototype.$basicSet = function(data) {
798
- let newValue = (typeof data === 'function') ? data(this.$currentValue) : data;
799
- this.$updateWithNewValue(newValue);
742
+ NDElement.prototype.htmlElement = function() {
743
+ return this.$element;
800
744
  };
801
745
 
802
- ObservableItem.prototype.set = ObservableItem.prototype.$basicSet;
803
-
804
- ObservableItem.prototype.val = function() {
805
- return this.$currentValue;
806
- };
746
+ NDElement.prototype.node = NDElement.prototype.htmlElement;
807
747
 
808
- ObservableItem.prototype.disconnectAll = function() {
809
- this.$listeners?.splice(0);
810
- this.$previousValue = null;
811
- this.$currentValue = null;
812
- if(this.$watchers) {
813
- for (const [_, watchValueList] of this.$watchers) {
814
- if(Validator.isArray(watchValueList)) {
815
- watchValueList.splice(0);
816
- }
817
- }
748
+ NDElement.prototype.shadow = function(mode, style = null) {
749
+ const $element = this.$element;
750
+ const children = Array.from($element.childNodes);
751
+ const shadowRoot = $element.attachShadow({ mode });
752
+ if(style) {
753
+ const styleNode = document.createElement("style");
754
+ styleNode.textContent = style;
755
+ shadowRoot.appendChild(styleNode);
818
756
  }
819
- this.$watchers?.clear();
820
- this.$listeners = null;
821
- this.$watchers = null;
822
- this.trigger = noneTrigger;
757
+ $element.append = shadowRoot.append.bind(shadowRoot);
758
+ $element.appendChild = shadowRoot.appendChild.bind(shadowRoot);
759
+ shadowRoot.append(...children);
760
+
761
+ return this;
823
762
  };
824
763
 
825
- /**
826
- * Registers a cleanup callback that will be executed when the observable is cleaned up.
827
- * Useful for disposing resources, removing event listeners, or other cleanup tasks.
828
- *
829
- * @param {Function} callback - Cleanup function to execute on observable disposal
830
- * @example
831
- * const obs = Observable(0);
832
- * obs.onCleanup(() => console.log('Cleaned up!'));
833
- * obs.cleanup(); // Logs: "Cleaned up!"
834
- */
835
- ObservableItem.prototype.onCleanup = function(callback) {
836
- this.$cleanupListeners = this.$cleanupListeners ?? [];
837
- this.$cleanupListeners.push(callback);
764
+ NDElement.prototype.openShadow = function(style = null) {
765
+ return this.shadow('open', style);
838
766
  };
839
767
 
840
- ObservableItem.prototype.cleanup = function() {
841
- if (this.$cleanupListeners) {
842
- for (let i = 0; i < this.$cleanupListeners.length; i++) {
843
- this.$cleanupListeners[i]();
844
- }
845
- this.$cleanupListeners = null;
846
- }
847
- MemoryManager.unregister(this.$memoryId);
848
- this.disconnectAll();
849
- delete this.$value;
768
+ NDElement.prototype.closedShadow = function(style = null) {
769
+ return this.shadow('closed', style);
850
770
  };
851
771
 
852
772
  /**
773
+ * Attaches a template binding to the element by hydrating it with the specified method.
853
774
  *
854
- * @param {Function} callback
855
- * @returns {(function(): void)}
775
+ * @param {string} methodName - Name of the hydration method to call
776
+ * @param {BindingHydrator} bindingHydrator - Template binding with $hydrate method
777
+ * @returns {HTMLElement} The underlying HTML element
778
+ * @example
779
+ * const onClick = $binder.attach((event, data) => console.log(data));
780
+ * element.nd.attach('onClick', onClick);
856
781
  */
857
- ObservableItem.prototype.subscribe = function(callback) {
858
- this.$listeners = this.$listeners ?? [];
859
-
860
- this.$listeners.push(callback);
861
- this.assocTrigger();
782
+ NDElement.prototype.attach = function(methodName, bindingHydrator) {
783
+ bindingHydrator.$hydrate(this.$element, methodName);
784
+ return this.$element;
862
785
  };
863
786
 
864
787
  /**
865
- * Watches for a specific value and executes callback when the observable equals that value.
866
- * Creates a watcher that only triggers when the observable changes to the specified value.
788
+ * Extends the current NDElement instance with custom methods.
789
+ * Methods are bound to the instance and available for chaining.
867
790
  *
868
- * @param {*} value - The value to watch for
869
- * @param {(value) => void|ObservableItem} callback - Callback function or observable to set when value matches
791
+ * @param {Object} methods - Object containing method definitions
792
+ * @returns {this} The NDElement instance with added methods for chaining
870
793
  * @example
871
- * const status = Observable('idle');
872
- * status.on('loading', () => console.log('Started loading'));
873
- * status.on('error', isError); // Set another observable
794
+ * element.nd.with({
795
+ * highlight() {
796
+ * this.$element.style.background = 'yellow';
797
+ * return this;
798
+ * }
799
+ * }).highlight().onClick(() => console.log('Clicked'));
874
800
  */
875
- ObservableItem.prototype.on = function(value, callback) {
876
- this.$watchers = this.$watchers ?? new Map();
801
+ NDElement.prototype.with = function(methods) {
802
+ if (!methods || typeof methods !== 'object') {
803
+ throw new NativeDocumentError('extend() requires an object of methods');
804
+ }
877
805
 
878
- let watchValueList = this.$watchers.get(value);
806
+ for (const name in methods) {
807
+ const method = methods[name];
879
808
 
880
- if(callback.__$isObservable) {
881
- callback = callback.set.bind(callback);
882
- }
809
+ if (typeof method !== 'function') {
810
+ console.warn(`⚠️ extends(): "${name}" is not a function, skipping`);
811
+ continue;
812
+ }
883
813
 
884
- if(!watchValueList) {
885
- watchValueList = callback;
886
- this.$watchers.set(value, callback);
887
- } else if(!Validator.isArray(watchValueList.list)) {
888
- watchValueList = [watchValueList, callback];
889
- callback = (value) => {
890
- for(let i = 0, length = watchValueList.length; i < length; i++) {
891
- watchValueList[i](value);
892
- }
893
- };
894
- callback.list = watchValueList;
895
- this.$watchers.set(value, callback);
896
- } else {
897
- watchValueList.list.push(callback);
814
+ this[name] = method.bind(this);
898
815
  }
899
816
 
900
- this.assocTrigger();
817
+ return this;
901
818
  };
902
819
 
903
820
  /**
904
- * Removes a watcher for a specific value. If no callback is provided, removes all watchers for that value.
821
+ * Extends the NDElement prototype with new methods available to all NDElement instances.
822
+ * Use this to add global methods to all NDElements.
905
823
  *
906
- * @param {*} value - The value to stop watching
907
- * @param {Function} [callback] - Specific callback to remove. If omitted, removes all watchers for this value
824
+ * @param {Object} methods - Object containing method definitions to add to prototype
825
+ * @returns {typeof NDElement} The NDElement constructor
826
+ * @throws {NativeDocumentError} If methods is not an object or contains non-function values
908
827
  * @example
909
- * const status = Observable('idle');
910
- * const handler = () => console.log('Loading');
911
- * status.on('loading', handler);
912
- * status.off('loading', handler); // Remove specific handler
913
- * status.off('loading'); // Remove all handlers for 'loading'
828
+ * NDElement.extend({
829
+ * fadeIn() {
830
+ * this.$element.style.opacity = '1';
831
+ * return this;
832
+ * }
833
+ * });
834
+ * // Now all NDElements have .fadeIn() method
835
+ * Div().nd.fadeIn();
914
836
  */
915
- ObservableItem.prototype.off = function(value, callback) {
916
- if(!this.$watchers) return;
917
-
918
- const watchValueList = this.$watchers.get(value);
919
- if(!watchValueList) return;
920
-
921
- if(!callback || !Array.isArray(watchValueList.list)) {
922
- this.$watchers?.delete(value);
923
- this.assocTrigger();
924
- return;
925
- }
926
- const index = watchValueList.indexOf(callback);
927
- watchValueList?.splice(index, 1);
928
- if(watchValueList.length === 1) {
929
- this.$watchers.set(value, watchValueList[0]);
837
+ NDElement.extend = function(methods) {
838
+ if (!methods || typeof methods !== 'object') {
839
+ throw new NativeDocumentError('NDElement.extend() requires an object of methods');
930
840
  }
931
- else if(watchValueList.length === 0) {
932
- this.$watchers?.delete(value);
841
+
842
+ if (Array.isArray(methods)) {
843
+ throw new NativeDocumentError('NDElement.extend() requires an object, not an array');
933
844
  }
934
- this.assocTrigger();
935
- };
936
845
 
937
- /**
938
- * Subscribes to the observable but automatically unsubscribes after the first time the predicate matches.
939
- *
940
- * @param {(value) => Boolean|any} predicate - Value to match or function that returns true when condition is met
941
- * @param {(value) => void} callback - Callback to execute when predicate matches, receives the matched value
942
- * @example
943
- * const status = Observable('loading');
944
- * status.once('ready', (val) => console.log('Ready!'));
945
- * status.once(val => val === 'error', (val) => console.log('Error occurred'));
946
- */
947
- ObservableItem.prototype.once = function(predicate, callback) {
948
- const fn = typeof predicate === 'function' ? predicate : (v) => v === predicate;
846
+ const protectedMethods = new Set([
847
+ 'constructor', 'valueOf', '$element', '$observer',
848
+ 'ref', 'remove', 'cleanup', 'with', 'extend', 'attach',
849
+ 'lifecycle', 'mounted', 'unmounted', 'unmountChildren'
850
+ ]);
949
851
 
950
- const handler = (val) => {
951
- if (fn(val)) {
952
- this.unsubscribe(handler);
953
- callback(val);
852
+ for (const name in methods) {
853
+ if (!Object.hasOwn(methods, name)) {
854
+ continue;
954
855
  }
955
- };
956
- this.subscribe(handler);
957
- };
958
856
 
959
- /**
960
- * Unsubscribe from an observable.
961
- * @param {Function} callback
962
- */
963
- ObservableItem.prototype.unsubscribe = function(callback) {
964
- if(!this.$listeners) return;
965
- const index = this.$listeners.indexOf(callback);
966
- if (index > -1) {
967
- this.$listeners.splice(index, 1);
968
- }
969
- this.assocTrigger();
970
- };
857
+ const method = methods[name];
971
858
 
972
- /**
973
- * Create an Observable checker instance
974
- * @param callback
975
- * @returns {ObservableChecker}
976
- */
977
- ObservableItem.prototype.check = function(callback) {
978
- return new ObservableChecker(this, callback)
979
- };
859
+ if (typeof method !== 'function') {
860
+ DebugManager.warn('NDElement.extend', `"${name}" is not a function, skipping`);
861
+ continue;
862
+ }
980
863
 
981
- /**
982
- * Gets a property value from the observable's current value.
983
- * If the property is an observable, returns its value.
984
- *
985
- * @param {string|number} key - Property key to retrieve
986
- * @returns {*} The value of the property, unwrapped if it's an observable
987
- * @example
988
- * const user = Observable({ name: 'John', age: Observable(25) });
989
- * user.get('name'); // 'John'
990
- * user.get('age'); // 25 (unwrapped from observable)
991
- */
992
- ObservableItem.prototype.get = function(key) {
993
- const item = this.$currentValue[key];
994
- return Validator.isObservable(item) ? item.val() : item;
995
- };
864
+ if (protectedMethods.has(name)) {
865
+ DebugManager.error('NDElement.extend', `Cannot override protected method "${name}"`);
866
+ throw new NativeDocumentError(`Cannot override protected method "${name}"`);
867
+ }
996
868
 
997
- /**
998
- * Creates an ObservableWhen that represents whether the observable equals a specific value.
999
- * Returns an object that can be subscribed to and will emit true/false.
1000
- *
1001
- * @param {*} value - The value to compare against
1002
- * @returns {ObservableWhen} An ObservableWhen instance that tracks when the observable equals the value
1003
- * @example
1004
- * const status = Observable('idle');
1005
- * const isLoading = status.when('loading');
1006
- * isLoading.subscribe(active => console.log('Loading:', active));
1007
- * status.set('loading'); // Logs: "Loading: true"
1008
- */
1009
- ObservableItem.prototype.when = function(value) {
1010
- return new ObservableWhen(this, value);
1011
- };
869
+ if (NDElement.prototype[name]) {
870
+ DebugManager.warn('NDElement.extend', `Overwriting existing prototype method "${name}"`);
871
+ }
1012
872
 
1013
- /**
1014
- * Compares the observable's current value with another value or observable.
1015
- *
1016
- * @param {*|ObservableItem} other - Value or observable to compare against
1017
- * @returns {boolean} True if values are equal
1018
- * @example
1019
- * const a = Observable(5);
1020
- * const b = Observable(5);
1021
- * a.equals(5); // true
1022
- * a.equals(b); // true
1023
- * a.equals(10); // false
1024
- */
1025
- ObservableItem.prototype.equals = function(other) {
1026
- if(Validator.isObservable(other)) {
1027
- return this.$currentValue === other.$currentValue;
873
+ NDElement.prototype[name] = method;
1028
874
  }
1029
- return this.$currentValue === other;
1030
- };
1031
-
1032
- /**
1033
- * Converts the observable's current value to a boolean.
1034
- *
1035
- * @returns {boolean} The boolean representation of the current value
1036
- * @example
1037
- * const count = Observable(0);
1038
- * count.toBool(); // false
1039
- * count.set(5);
1040
- * count.toBool(); // true
1041
- */
1042
- ObservableItem.prototype.toBool = function() {
1043
- return !!this.$currentValue;
1044
- };
1045
875
 
1046
- /**
1047
- * Toggles the boolean value of the observable (false becomes true, true becomes false).
1048
- *
1049
- * @example
1050
- * const isOpen = Observable(false);
1051
- * isOpen.toggle(); // Now true
1052
- * isOpen.toggle(); // Now false
1053
- */
1054
- ObservableItem.prototype.toggle = function() {
1055
- this.set(!this.$currentValue);
876
+ return NDElement;
1056
877
  };
1057
878
 
1058
- /**
1059
- * Resets the observable to its initial value.
1060
- * Only works if the observable was created with { reset: true } config.
1061
- *
1062
- * @example
1063
- * const count = Observable(0, { reset: true });
1064
- * count.set(10);
1065
- * count.reset(); // Back to 0
1066
- */
1067
- ObservableItem.prototype.reset = function() {
1068
- if(!this.configs?.reset) {
1069
- return;
1070
- }
1071
- const resetValue = (Validator.isObject(this.$initialValue))
1072
- ? deepClone(this.$initialValue, (observable) => {
1073
- observable.reset();
1074
- })
1075
- : this.$initialValue;
1076
- this.set(resetValue);
879
+ const COMMON_NODE_TYPES = {
880
+ ELEMENT: 1,
881
+ TEXT: 3,
882
+ COMMENT: 8,
883
+ DOCUMENT_FRAGMENT: 11
1077
884
  };
1078
885
 
1079
- /**
1080
- * Returns a string representation of the observable's current value.
1081
- *
1082
- * @returns {string} String representation of the current value
1083
- */
1084
- ObservableItem.prototype.toString = function() {
1085
- return String(this.$currentValue);
1086
- };
1087
-
1088
- /**
1089
- * Returns the primitive value of the observable (its current value).
1090
- * Called automatically in type coercion contexts.
1091
- *
1092
- * @returns {*} The current value of the observable
1093
- */
1094
- ObservableItem.prototype.valueOf = function() {
1095
- return this.$currentValue;
1096
- };
1097
-
1098
- const DocumentObserver = {
1099
- mounted: new WeakMap(),
1100
- mountedSupposedSize: 0,
1101
- unmounted: new WeakMap(),
1102
- unmountedSupposedSize: 0,
1103
- observer: null,
1104
- executeMountedCallback(node) {
1105
- const data = DocumentObserver.mounted.get(node);
1106
- if(!data) {
1107
- return;
886
+ const Validator = {
887
+ isObservable(value) {
888
+ return value?.__$isObservable;
889
+ },
890
+ isTemplateBinding(value) {
891
+ return value?.__$isTemplateBinding;
892
+ },
893
+ isObservableWhenResult(value) {
894
+ return value && (value.__$isObservableWhen || (typeof value === 'object' && '$target' in value && '$observer' in value));
895
+ },
896
+ isArrayObservable(value) {
897
+ return value?.__$isObservableArray;
898
+ },
899
+ isProxy(value) {
900
+ return value?.__isProxy__
901
+ },
902
+ isObservableOrProxy(value) {
903
+ return Validator.isObservable(value) || Validator.isProxy(value);
904
+ },
905
+ isAnchor(value) {
906
+ return value?.__Anchor__
907
+ },
908
+ isObservableChecker(value) {
909
+ return value?.__$isObservableChecker || value instanceof ObservableChecker;
910
+ },
911
+ isArray(value) {
912
+ return Array.isArray(value);
913
+ },
914
+ isString(value) {
915
+ return typeof value === 'string';
916
+ },
917
+ isNumber(value) {
918
+ return typeof value === 'number';
919
+ },
920
+ isBoolean(value) {
921
+ return typeof value === 'boolean';
922
+ },
923
+ isFunction(value) {
924
+ return typeof value === 'function';
925
+ },
926
+ isAsyncFunction(value) {
927
+ return typeof value === 'function' && value.constructor.name === 'AsyncFunction';
928
+ },
929
+ isObject(value) {
930
+ return typeof value === 'object' && value !== null;
931
+ },
932
+ isJson(value) {
933
+ return !(typeof value !== 'object' || value === null || Array.isArray(value) || value.constructor.name !== 'Object')
934
+ },
935
+ isElement(value) {
936
+ return value && (
937
+ value.nodeType === COMMON_NODE_TYPES.ELEMENT ||
938
+ value.nodeType === COMMON_NODE_TYPES.TEXT ||
939
+ value.nodeType === COMMON_NODE_TYPES.DOCUMENT_FRAGMENT ||
940
+ value.nodeType === COMMON_NODE_TYPES.COMMENT
941
+ );
942
+ },
943
+ isFragment(value) {
944
+ return value?.nodeType === COMMON_NODE_TYPES.DOCUMENT_FRAGMENT;
945
+ },
946
+ isStringOrObservable(value) {
947
+ return this.isString(value) || this.isObservable(value);
948
+ },
949
+ isValidChild(child) {
950
+ return child === null ||
951
+ this.isElement(child) ||
952
+ this.isObservable(child) ||
953
+ this.isNDElement(child) ||
954
+ ['string', 'number', 'boolean'].includes(typeof child);
955
+ },
956
+ isNDElement(child) {
957
+ return child?.__$isNDElement || child instanceof NDElement;
958
+ },
959
+ isValidChildren(children) {
960
+ if (!Array.isArray(children)) {
961
+ children = [children];
1108
962
  }
1109
- data.inDom = true;
1110
- data.mounted && data.mounted(node);
963
+
964
+ const invalid = children.filter(child => !this.isValidChild(child));
965
+ return invalid.length === 0;
1111
966
  },
1112
- executeUnmountedCallback(node) {
1113
- const data = DocumentObserver.unmounted.get(node);
1114
- if(!data) {
1115
- return;
967
+ validateChildren(children) {
968
+ if (!Array.isArray(children)) {
969
+ children = [children];
1116
970
  }
1117
971
 
1118
- data.inDom = false;
1119
- if(data.unmounted && data.unmounted(node) === true) {
1120
- data.disconnect();
1121
- node.nd?.remove();
972
+ const invalid = children.filter(child => !this.isValidChild(child));
973
+ if (invalid.length > 0) {
974
+ throw new NativeDocumentError(`Invalid children detected: ${invalid.map(i => typeof i).join(', ')}`);
1122
975
  }
1123
- },
1124
- checkMutation: function(mutationsList) {
1125
- for(const mutation of mutationsList) {
1126
- if(DocumentObserver.mountedSupposedSize > 0 ) {
1127
- for(const node of mutation.addedNodes) {
1128
- DocumentObserver.executeMountedCallback(node);
1129
- if(!node.querySelectorAll) {
1130
- return;
1131
- }
1132
- const children = node.querySelectorAll('[data--nd-mounted]');
1133
- if(!children.length) {
1134
- return;
1135
- }
1136
- for(const node of children) {
1137
- DocumentObserver.executeMountedCallback(node);
1138
- }
1139
- }
1140
- }
1141
976
 
1142
- if(DocumentObserver.unmountedSupposedSize > 0 ) {
1143
- for(const node of mutation.removedNodes) {
1144
- DocumentObserver.executeUnmountedCallback(node);
1145
- if(!node.querySelectorAll) {
1146
- return;
1147
- }
1148
- const children = node.querySelectorAll('[data--nd-unmounted]');
1149
- if(!children.length) {
1150
- return;
1151
- }
1152
- for(const node of children) {
1153
- DocumentObserver.executeUnmountedCallback(node);
1154
- }
1155
- }
1156
- }
977
+ return children;
978
+ },
979
+ /**
980
+ * Check if the data contains observables.
981
+ * @param {Array|Object} data
982
+ * @returns {boolean}
983
+ */
984
+ containsObservables(data) {
985
+ if(!data) {
986
+ return false;
1157
987
  }
988
+ return Validator.isObject(data)
989
+ && Object.values(data).some(value => Validator.isObservable(value));
1158
990
  },
1159
991
  /**
1160
- *
1161
- * @param {HTMLElement} element
1162
- * @param {boolean} inDom
1163
- * @returns {{watch: (function(): Map<any, any>), disconnect: (function(): boolean), mounted: (function(*): Set<any>), unmounted: (function(*): Set<any>)}}
992
+ * Check if the data contains an observable reference.
993
+ * @param {string} data
994
+ * @returns {boolean}
1164
995
  */
1165
- watch: function(element, inDom = false) {
1166
- let data = {
1167
- inDom,
1168
- mounted: null,
1169
- unmounted: null,
1170
- disconnect: () => {
1171
- DocumentObserver.mounted.delete(element);
1172
- DocumentObserver.unmounted.delete(element);
1173
- DocumentObserver.mountedSupposedSize--;
1174
- DocumentObserver.unmountedSupposedSize--;
1175
- data = null;
1176
- }
1177
- };
996
+ containsObservableReference(data) {
997
+ if(!data || typeof data !== 'string') {
998
+ return false;
999
+ }
1000
+ return /\{\{#ObItem::\([0-9]+\)\}\}/.test(data);
1001
+ },
1002
+ validateAttributes(attributes) {},
1178
1003
 
1179
- return {
1180
- disconnect: data.disconnect,
1181
- mounted: (callback) => {
1182
- data.mounted = callback;
1183
- DocumentObserver.mounted.set(element, data);
1184
- DocumentObserver.mountedSupposedSize++;
1185
- },
1186
- unmounted: (callback) => {
1187
- data.unmounted = callback;
1188
- DocumentObserver.unmounted.set(element, data);
1189
- DocumentObserver.unmountedSupposedSize++;
1190
- }
1191
- };
1004
+ validateEventCallback(callback) {
1005
+ if (typeof callback !== 'function') {
1006
+ throw new NativeDocumentError('Event callback must be a function');
1007
+ }
1192
1008
  }
1193
1009
  };
1194
1010
 
1195
- DocumentObserver.observer = new MutationObserver(DocumentObserver.checkMutation);
1196
- DocumentObserver.observer.observe(document.body, {
1197
- childList: true,
1198
- subtree: true,
1199
- });
1200
-
1201
- function NDElement(element) {
1202
- this.$element = element;
1203
- this.$observer = null;
1204
- }
1011
+ function Anchor(name, isUniqueChild = false) {
1012
+ const anchorFragment = document.createDocumentFragment();
1013
+ anchorFragment.__Anchor__ = true;
1205
1014
 
1206
- NDElement.prototype.__$isNDElement = true;
1015
+ const anchorStart = document.createComment('Anchor Start : '+name);
1016
+ const anchorEnd = document.createComment('/ Anchor End '+name);
1207
1017
 
1208
- NDElement.prototype.valueOf = function() {
1209
- return this.$element;
1210
- };
1018
+ anchorFragment.appendChild(anchorStart);
1019
+ anchorFragment.appendChild(anchorEnd);
1211
1020
 
1212
- NDElement.prototype.ref = function(target, name) {
1213
- target[name] = this.$element;
1214
- return this;
1215
- };
1021
+ anchorFragment.nativeInsertBefore = anchorFragment.insertBefore;
1022
+ anchorFragment.nativeAppendChild = anchorFragment.appendChild;
1023
+ anchorFragment.nativeAppend = anchorFragment.append;
1216
1024
 
1217
- NDElement.prototype.refSelf = function(target, name) {
1218
- target[name] = this;
1219
- return this;
1220
- };
1025
+ const isParentUniqueChild = (parent) => (isUniqueChild || (parent.firstChild === anchorStart && parent.lastChild === anchorEnd));
1221
1026
 
1222
- NDElement.prototype.unmountChildren = function() {
1223
- let element = this.$element;
1224
- for(let i = 0, length = element.children.length; i < length; i++) {
1225
- let elementChildren = element.children[i];
1226
- if(!elementChildren.$ndProx) {
1227
- elementChildren.nd?.remove();
1027
+ const insertBefore = function(parent, child, target) {
1028
+ const childElement = Validator.isElement(child) ? child : ElementCreator.getChild(child);
1029
+ if(parent === anchorFragment) {
1030
+ parent.nativeInsertBefore(childElement, target);
1031
+ return;
1228
1032
  }
1229
- elementChildren = null;
1230
- }
1231
- element = null;
1232
- return this;
1233
- };
1033
+ if(isParentUniqueChild(parent) && target === anchorEnd) {
1034
+ parent.append(childElement, target);
1035
+ return;
1036
+ }
1037
+ parent.insertBefore(childElement, target);
1038
+ };
1234
1039
 
1235
- NDElement.prototype.remove = function() {
1236
- let element = this.$element;
1237
- element.nd.unmountChildren();
1238
- element.$ndProx = null;
1239
- delete element.nd?.on?.prevent;
1240
- delete element.nd?.on;
1241
- delete element.nd;
1242
- element = null;
1243
- return this;
1244
- };
1040
+ anchorFragment.appendElement = function(child, before = null) {
1041
+ const parentNode = anchorStart.parentNode;
1042
+ const targetBefore = before || anchorEnd;
1043
+ if(parentNode === anchorFragment) {
1044
+ parentNode.nativeInsertBefore(child, targetBefore);
1045
+ return;
1046
+ }
1047
+ parentNode?.insertBefore(child, targetBefore);
1048
+ };
1245
1049
 
1246
- NDElement.prototype.lifecycle = function(states) {
1247
- this.$observer = this.$observer || DocumentObserver.watch(this.$element);
1050
+ anchorFragment.appendChild = function(child, before = null) {
1051
+ const parent = anchorEnd.parentNode;
1052
+ if(!parent) {
1053
+ DebugManager.error('Anchor', 'Anchor : parent not found', child);
1054
+ return;
1055
+ }
1056
+ before = before ?? anchorEnd;
1057
+ insertBefore(parent, child, before);
1058
+ };
1248
1059
 
1249
- if(states.mounted) {
1250
- this.$element.setAttribute('data--nd-mounted', '1');
1251
- this.$observer.mounted(states.mounted);
1252
- }
1253
- if(states.unmounted) {
1254
- this.$element.setAttribute('data--nd-unmounted', '1');
1255
- this.$observer.unmounted(states.unmounted);
1256
- }
1257
- return this;
1258
- };
1060
+ anchorFragment.append = function(...args ) {
1061
+ return anchorFragment.appendChild(args);
1062
+ };
1259
1063
 
1260
- NDElement.prototype.mounted = function(callback) {
1261
- return this.lifecycle({ mounted: callback });
1262
- };
1064
+ anchorFragment.removeChildren = async function() {
1065
+ const parent = anchorEnd.parentNode;
1066
+ if(parent === anchorFragment) {
1067
+ return;
1068
+ }
1069
+ // if(isParentUniqueChild(parent)) {
1070
+ // parent.replaceChildren(anchorStart, anchorEnd);
1071
+ // return;
1072
+ // }
1263
1073
 
1264
- NDElement.prototype.unmounted = function(callback) {
1265
- return this.lifecycle({ unmounted: callback });
1266
- };
1074
+ let itemToRemove = anchorStart.nextSibling, tempItem;
1075
+ const removes = [];
1076
+ while(itemToRemove && itemToRemove !== anchorEnd) {
1077
+ tempItem = itemToRemove.nextSibling;
1078
+ removes.push(itemToRemove.remove());
1079
+ itemToRemove = tempItem;
1080
+ }
1081
+ await Promise.all(removes);
1082
+ };
1267
1083
 
1268
- NDElement.prototype.htmlElement = function() {
1269
- return this.$element;
1270
- };
1084
+ anchorFragment.remove = async function() {
1085
+ const parent = anchorEnd.parentNode;
1086
+ if(parent === anchorFragment) {
1087
+ return;
1088
+ }
1089
+ let itemToRemove = anchorStart.nextSibling, tempItem;
1090
+ const allItemToRemove = [];
1091
+ const removes = [];
1092
+ while(itemToRemove && itemToRemove !== anchorEnd) {
1093
+ tempItem = itemToRemove.nextSibling;
1094
+ allItemToRemove.push(itemToRemove);
1095
+ removes.push(itemToRemove.remove());
1096
+ itemToRemove = tempItem;
1097
+ }
1098
+ await Promise.all(removes);
1099
+ anchorFragment.nativeAppend(...allItemToRemove);
1100
+ };
1271
1101
 
1272
- NDElement.prototype.node = NDElement.prototype.htmlElement;
1102
+ anchorFragment.removeWithAnchors = async function() {
1103
+ await anchorFragment.removeChildren();
1104
+ anchorStart.remove();
1105
+ anchorEnd.remove();
1106
+ };
1273
1107
 
1274
- NDElement.prototype.shadow = function(mode, style = null) {
1275
- const $element = this.$element;
1276
- const children = Array.from($element.childNodes);
1277
- const shadowRoot = $element.attachShadow({ mode });
1278
- if(style) {
1279
- const styleNode = document.createElement("style");
1280
- styleNode.textContent = style;
1281
- shadowRoot.appendChild(styleNode);
1282
- }
1283
- $element.append = shadowRoot.append.bind(shadowRoot);
1284
- $element.appendChild = shadowRoot.appendChild.bind(shadowRoot);
1285
- shadowRoot.append(...children);
1108
+ anchorFragment.replaceContent = async function(child) {
1109
+ const childElement = Validator.isElement(child) ? child : ElementCreator.getChild(child);
1110
+ const parent = anchorEnd.parentNode;
1111
+ if(!parent) {
1112
+ return;
1113
+ }
1114
+ // if(isParentUniqueChild(parent)) {
1115
+ // parent.replaceChildren(anchorStart, childElement, anchorEnd);
1116
+ // return;
1117
+ // }
1118
+ await anchorFragment.removeChildren();
1119
+ parent.insertBefore(childElement, anchorEnd);
1120
+ };
1286
1121
 
1287
- return this;
1288
- };
1122
+ anchorFragment.setContent = anchorFragment.replaceContent;
1289
1123
 
1290
- NDElement.prototype.openShadow = function(style = null) {
1291
- return this.shadow('open', style);
1292
- };
1124
+ anchorFragment.insertBefore = function(child, anchor = null) {
1125
+ anchorFragment.appendChild(child, anchor);
1126
+ };
1293
1127
 
1294
- NDElement.prototype.closedShadow = function(style = null) {
1295
- return this.shadow('closed', style);
1296
- };
1128
+
1129
+ anchorFragment.endElement = function() {
1130
+ return anchorEnd;
1131
+ };
1132
+
1133
+ anchorFragment.startElement = function() {
1134
+ return anchorStart;
1135
+ };
1136
+ anchorFragment.restore = function() {
1137
+ anchorFragment.appendChild(anchorFragment);
1138
+ };
1139
+ anchorFragment.clear = anchorFragment.remove;
1140
+ anchorFragment.detach = anchorFragment.remove;
1141
+
1142
+ anchorFragment.getByIndex = function(index) {
1143
+ let currentNode = anchorStart;
1144
+ for(let i = 0; i <= index; i++) {
1145
+ if(!currentNode.nextSibling) {
1146
+ return null;
1147
+ }
1148
+ currentNode = currentNode.nextSibling;
1149
+ }
1150
+ return currentNode !== anchorStart ? currentNode : null;
1151
+ };
1152
+
1153
+ return anchorFragment;
1154
+ }
1155
+ DocumentFragment.prototype.setAttribute = () => {};
1156
+
1157
+ const BOOLEAN_ATTRIBUTES = new Set([
1158
+ 'checked',
1159
+ 'selected',
1160
+ 'disabled',
1161
+ 'readonly',
1162
+ 'required',
1163
+ 'autofocus',
1164
+ 'multiple',
1165
+ 'autocomplete',
1166
+ 'hidden',
1167
+ 'contenteditable',
1168
+ 'spellcheck',
1169
+ 'translate',
1170
+ 'draggable',
1171
+ 'async',
1172
+ 'defer',
1173
+ 'autoplay',
1174
+ 'controls',
1175
+ 'loop',
1176
+ 'muted',
1177
+ 'download',
1178
+ 'reversed',
1179
+ 'open',
1180
+ 'default',
1181
+ 'formnovalidate',
1182
+ 'novalidate',
1183
+ 'scoped',
1184
+ 'itemscope',
1185
+ 'allowfullscreen',
1186
+ 'allowpaymentrequest',
1187
+ 'playsinline'
1188
+ ]);
1189
+
1190
+ const MemoryManager = (function() {
1191
+
1192
+ let $nextObserverId = 0;
1193
+ const $observables = new Map();
1194
+
1195
+ return {
1196
+ /**
1197
+ * Register an observable and return an id.
1198
+ *
1199
+ * @param {ObservableItem} observable
1200
+ * @param {Function} getListeners
1201
+ * @returns {number}
1202
+ */
1203
+ register(observable) {
1204
+ const id = ++$nextObserverId;
1205
+ $observables.set(id, new WeakRef(observable));
1206
+ return id;
1207
+ },
1208
+ unregister(id) {
1209
+ $observables.delete(id);
1210
+ },
1211
+ getObservableById(id) {
1212
+ return $observables.get(id)?.deref();
1213
+ },
1214
+ cleanup() {
1215
+ for (const [_, weakObservableRef] of $observables) {
1216
+ const observable = weakObservableRef.deref();
1217
+ if (observable) {
1218
+ observable.cleanup();
1219
+ }
1220
+ }
1221
+ $observables.clear();
1222
+ },
1223
+ /**
1224
+ * Clean observables that are not referenced anymore.
1225
+ * @param {number} threshold
1226
+ */
1227
+ cleanObservables(threshold) {
1228
+ if($observables.size < threshold) return;
1229
+ let cleanedCount = 0;
1230
+ for (const [id, weakObservableRef] of $observables) {
1231
+ if (!weakObservableRef.deref()) {
1232
+ $observables.delete(id);
1233
+ cleanedCount++;
1234
+ }
1235
+ }
1236
+ if (cleanedCount > 0) {
1237
+ DebugManager.log('Memory Auto Clean', `🧹 Cleaned ${cleanedCount} orphaned observables`);
1238
+ }
1239
+ }
1240
+ };
1241
+ }());
1297
1242
 
1298
1243
  /**
1299
- * Attaches a template binding to the element by hydrating it with the specified method.
1244
+ * Creates an ObservableWhen that tracks whether an observable equals a specific value.
1300
1245
  *
1301
- * @param {string} methodName - Name of the hydration method to call
1302
- * @param {BindingHydrator} bindingHydrator - Template binding with $hydrate method
1303
- * @returns {HTMLElement} The underlying HTML element
1304
- * @example
1305
- * const onClick = $binder.attach((event, data) => console.log(data));
1306
- * element.nd.attach('onClick', onClick);
1246
+ * @param {ObservableItem} observer - The observable to watch
1247
+ * @param {*} value - The value to compare against
1248
+ * @class ObservableWhen
1307
1249
  */
1308
- NDElement.prototype.attach = function(methodName, bindingHydrator) {
1309
- bindingHydrator.$hydrate(this.$element, methodName);
1310
- return this.$element;
1250
+ const ObservableWhen = function(observer, value) {
1251
+ this.$target = value;
1252
+ this.$observer = observer;
1311
1253
  };
1312
1254
 
1255
+ ObservableWhen.prototype.__$isObservableWhen = true;
1256
+
1313
1257
  /**
1314
- * Extends the current NDElement instance with custom methods.
1315
- * Methods are bound to the instance and available for chaining.
1258
+ * Subscribes to changes in the match status (true when observable equals target value).
1316
1259
  *
1317
- * @param {Object} methods - Object containing method definitions
1318
- * @returns {this} The NDElement instance with added methods for chaining
1260
+ * @param {Function} callback - Function called with boolean indicating if values match
1261
+ * @returns {Function} Unsubscribe function
1319
1262
  * @example
1320
- * element.nd.with({
1321
- * highlight() {
1322
- * this.$element.style.background = 'yellow';
1323
- * return this;
1324
- * }
1325
- * }).highlight().onClick(() => console.log('Clicked'));
1263
+ * const status = Observable('idle');
1264
+ * const isLoading = status.when('loading');
1265
+ * isLoading.subscribe(active => console.log('Loading:', active));
1326
1266
  */
1327
- NDElement.prototype.with = function(methods) {
1328
- if (!methods || typeof methods !== 'object') {
1329
- throw new NativeDocumentError('extend() requires an object of methods');
1330
- }
1331
-
1332
- for (const name in methods) {
1333
- const method = methods[name];
1334
-
1335
- if (typeof method !== 'function') {
1336
- console.warn(`⚠️ extends(): "${name}" is not a function, skipping`);
1337
- continue;
1338
- }
1339
-
1340
- this[name] = method.bind(this);
1341
- }
1342
-
1343
- return this;
1267
+ ObservableWhen.prototype.subscribe = function(callback) {
1268
+ return this.$observer.on(this.$target, callback);
1344
1269
  };
1345
1270
 
1346
1271
  /**
1347
- * Extends the NDElement prototype with new methods available to all NDElement instances.
1348
- * Use this to add global methods to all NDElements.
1272
+ * Returns true if the observable's current value equals the target value.
1349
1273
  *
1350
- * @param {Object} methods - Object containing method definitions to add to prototype
1351
- * @returns {typeof NDElement} The NDElement constructor
1352
- * @throws {NativeDocumentError} If methods is not an object or contains non-function values
1353
- * @example
1354
- * NDElement.extend({
1355
- * fadeIn() {
1356
- * this.$element.style.opacity = '1';
1357
- * return this;
1358
- * }
1359
- * });
1360
- * // Now all NDElements have .fadeIn() method
1361
- * Div().nd.fadeIn();
1274
+ * @returns {boolean} True if observable value matches target value
1362
1275
  */
1363
- NDElement.extend = function(methods) {
1364
- if (!methods || typeof methods !== 'object') {
1365
- throw new NativeDocumentError('NDElement.extend() requires an object of methods');
1366
- }
1276
+ ObservableWhen.prototype.val = function() {
1277
+ return this.$observer.$currentValue === this.$target;
1278
+ };
1367
1279
 
1368
- if (Array.isArray(methods)) {
1369
- throw new NativeDocumentError('NDElement.extend() requires an object, not an array');
1370
- }
1280
+ /**
1281
+ * Returns true if the observable's current value equals the target value.
1282
+ * Alias for val().
1283
+ *
1284
+ * @returns {boolean} True if observable value matches target value
1285
+ */
1286
+ ObservableWhen.prototype.isMatch = ObservableWhen.prototype.val;
1371
1287
 
1372
- const protectedMethods = new Set([
1373
- 'constructor', 'valueOf', '$element', '$observer',
1374
- 'ref', 'remove', 'cleanup', 'with', 'extend', 'attach',
1375
- 'lifecycle', 'mounted', 'unmounted', 'unmountChildren'
1376
- ]);
1288
+ /**
1289
+ * Returns true if the observable's current value equals the target value.
1290
+ * Alias for val().
1291
+ *
1292
+ * @returns {boolean} True if observable value matches target value
1293
+ */
1294
+ ObservableWhen.prototype.isActive = ObservableWhen.prototype.val;
1377
1295
 
1378
- for (const name in methods) {
1379
- if (!methods.hasOwnProperty(name)) {
1380
- continue;
1296
+ const invoke = function(fn, args, context) {
1297
+ if(context) {
1298
+ fn.apply(context, args);
1299
+ } else {
1300
+ fn(...args);
1301
+ }
1302
+ };
1303
+ /**
1304
+ *
1305
+ * @param {Function} fn
1306
+ * @param {number} delay
1307
+ * @param {{leading?:Boolean, trailing?:Boolean, debounce?:Boolean, check: Function}}options
1308
+ * @returns {(function(...[*]): void)|*}
1309
+ */
1310
+ const debounce = function(fn, delay, options = {}) {
1311
+ let timer = null;
1312
+ let lastArgs = null;
1313
+
1314
+ return function(...args) {
1315
+ const context = options.context === true ? this : null;
1316
+ if(options.check) {
1317
+ options.check(...args);
1381
1318
  }
1319
+ lastArgs = args;
1382
1320
 
1383
- const method = methods[name];
1321
+ // debounce mode: reset the timer for each call
1322
+ clearTimeout(timer);
1323
+ timer = setTimeout(() => invoke(fn, lastArgs, context), delay);
1324
+ }
1325
+ };
1384
1326
 
1385
- if (typeof method !== 'function') {
1386
- DebugManager.warn('NDElement.extend', `"${name}" is not a function, skipping`);
1387
- continue;
1388
- }
1327
+ const nextTick = function(fn) {
1328
+ let pending = false;
1329
+ return function(...args) {
1330
+ if (pending) return;
1331
+ pending = true;
1389
1332
 
1390
- if (protectedMethods.has(name)) {
1391
- DebugManager.error('NDElement.extend', `Cannot override protected method "${name}"`);
1392
- throw new NativeDocumentError(`Cannot override protected method "${name}"`);
1393
- }
1333
+ Promise.resolve().then(() => {
1334
+ fn.apply(this, args);
1335
+ pending = false;
1336
+ });
1337
+ };
1338
+ };
1394
1339
 
1395
- if (NDElement.prototype[name]) {
1396
- DebugManager.warn('NDElement.extend', `Overwriting existing prototype method "${name}"`);
1340
+ const deepClone = (value, onObservableFound) => {
1341
+ try {
1342
+ if(window.structuredClone !== undefined) {
1343
+ return window.structuredClone(value);
1397
1344
  }
1345
+ } catch (e){}
1398
1346
 
1399
- NDElement.prototype[name] = method;
1347
+ if (value === null || typeof value !== 'object') {
1348
+ return value;
1400
1349
  }
1401
1350
 
1402
- return NDElement;
1403
- };
1351
+ // Dates
1352
+ if (value instanceof Date) {
1353
+ return new Date(value.getTime());
1354
+ }
1404
1355
 
1405
- const COMMON_NODE_TYPES = {
1406
- ELEMENT: 1,
1407
- TEXT: 3,
1408
- COMMENT: 8,
1409
- DOCUMENT_FRAGMENT: 11
1356
+ // Arrays
1357
+ if (Array.isArray(value)) {
1358
+ return value.map(item => deepClone(item));
1359
+ }
1360
+
1361
+ // Observables - keep the référence
1362
+ if (Validator.isObservable(value)) {
1363
+ onObservableFound && onObservableFound(value);
1364
+ return value;
1365
+ }
1366
+
1367
+ // Objects
1368
+ const cloned = {};
1369
+ for (const key in value) {
1370
+ if (Object.hasOwn(value, key)) {
1371
+ cloned[key] = deepClone(value[key]);
1372
+ }
1373
+ }
1374
+ return cloned;
1410
1375
  };
1411
1376
 
1412
- const Validator = {
1413
- isObservable(value) {
1414
- return value?.__$isObservable;
1415
- },
1416
- isTemplateBinding(value) {
1417
- return value?.__$isTemplateBinding;
1418
- },
1419
- isObservableWhenResult(value) {
1420
- return value && (value.__$isObservableWhen || (typeof value === 'object' && '$target' in value && '$observer' in value));
1421
- },
1422
- isArrayObservable(value) {
1423
- return value?.__$isObservableArray;
1424
- },
1425
- isProxy(value) {
1426
- return value?.__isProxy__
1427
- },
1428
- isObservableOrProxy(value) {
1429
- return Validator.isObservable(value) || Validator.isProxy(value);
1430
- },
1431
- isAnchor(value) {
1432
- return value?.__Anchor__
1433
- },
1434
- isObservableChecker(value) {
1435
- return value?.__$isObservableChecker || value instanceof ObservableChecker;
1436
- },
1437
- isArray(value) {
1438
- return Array.isArray(value);
1439
- },
1440
- isString(value) {
1441
- return typeof value === 'string';
1442
- },
1443
- isNumber(value) {
1444
- return typeof value === 'number';
1445
- },
1446
- isBoolean(value) {
1447
- return typeof value === 'boolean';
1448
- },
1449
- isFunction(value) {
1450
- return typeof value === 'function';
1451
- },
1452
- isAsyncFunction(value) {
1453
- return typeof value === 'function' && value.constructor.name === 'AsyncFunction';
1377
+ const LocalStorage$1 = {
1378
+ getJson(key) {
1379
+ let value = localStorage.getItem(key);
1380
+ try {
1381
+ return JSON.parse(value);
1382
+ } catch (e) {
1383
+ throw new NativeDocumentError('invalid_json:'+key);
1384
+ }
1454
1385
  },
1455
- isObject(value) {
1456
- return typeof value === 'object' && value !== null;
1386
+ getNumber(key) {
1387
+ return Number(this.get(key));
1457
1388
  },
1458
- isJson(value) {
1459
- return !(typeof value !== 'object' || value === null || Array.isArray(value) || value.constructor.name !== 'Object')
1389
+ getBool(key) {
1390
+ const value = this.get(key);
1391
+ return value === 'true' || value === '1';
1460
1392
  },
1461
- isElement(value) {
1462
- return value && (
1463
- value.nodeType === COMMON_NODE_TYPES.ELEMENT ||
1464
- value.nodeType === COMMON_NODE_TYPES.TEXT ||
1465
- value.nodeType === COMMON_NODE_TYPES.DOCUMENT_FRAGMENT ||
1466
- value.nodeType === COMMON_NODE_TYPES.COMMENT
1467
- );
1393
+ setJson(key, value) {
1394
+ localStorage.setItem(key, JSON.stringify(value));
1468
1395
  },
1469
- isFragment(value) {
1470
- return value?.nodeType === COMMON_NODE_TYPES.DOCUMENT_FRAGMENT;
1396
+ setBool(key, value) {
1397
+ localStorage.setItem(key, value ? 'true' : 'false');
1471
1398
  },
1472
- isStringOrObservable(value) {
1473
- return this.isString(value) || this.isObservable(value);
1399
+ get(key, defaultValue = null) {
1400
+ return localStorage.getItem(key) || defaultValue;
1474
1401
  },
1475
- isValidChild(child) {
1476
- return child === null ||
1477
- this.isElement(child) ||
1478
- this.isObservable(child) ||
1479
- this.isNDElement(child) ||
1480
- ['string', 'number', 'boolean'].includes(typeof child);
1402
+ set(key, value) {
1403
+ return localStorage.setItem(key, value);
1481
1404
  },
1482
- isNDElement(child) {
1483
- return child?.__$isNDElement || child instanceof NDElement;
1405
+ remove(key) {
1406
+ localStorage.removeItem(key);
1484
1407
  },
1485
- isValidChildren(children) {
1486
- if (!Array.isArray(children)) {
1487
- children = [children];
1488
- }
1408
+ has(key) {
1409
+ return localStorage.getItem(key) != null;
1410
+ }
1411
+ };
1489
1412
 
1490
- const invalid = children.filter(child => !this.isValidChild(child));
1491
- return invalid.length === 0;
1492
- },
1493
- validateChildren(children) {
1494
- if (!Array.isArray(children)) {
1495
- children = [children];
1496
- }
1413
+ const $getFromStorage$1 = (key, value) => {
1414
+ if(!LocalStorage$1.has(key)) {
1415
+ return value;
1416
+ }
1417
+ switch (typeof value) {
1418
+ case 'object': return LocalStorage$1.getJson(key) ?? value;
1419
+ case 'boolean': return LocalStorage$1.getBool(key) ?? value;
1420
+ case 'number': return LocalStorage$1.getNumber(key) ?? value;
1421
+ default: return LocalStorage$1.get(key, value) ?? value;
1422
+ }
1423
+ };
1497
1424
 
1498
- const invalid = children.filter(child => !this.isValidChild(child));
1499
- if (invalid.length > 0) {
1500
- throw new NativeDocumentError(`Invalid children detected: ${invalid.map(i => typeof i).join(', ')}`);
1501
- }
1425
+ const $saveToStorage$1 = (value) => {
1426
+ switch (typeof value) {
1427
+ case 'object': return LocalStorage$1.setJson;
1428
+ case 'boolean': return LocalStorage$1.setBool;
1429
+ default: return LocalStorage$1.set;
1430
+ }
1431
+ };
1432
+
1433
+ const StoreFactory = function() {
1434
+
1435
+ const $stores = new Map();
1436
+ const $followersCache = new Map();
1502
1437
 
1503
- return children;
1504
- },
1505
1438
  /**
1506
- * Check if the data contains observables.
1507
- * @param {Array|Object} data
1508
- * @returns {boolean}
1439
+ * Internal helper retrieves a store entry or throws if not found.
1509
1440
  */
1510
- containsObservables(data) {
1511
- if(!data) {
1512
- return false;
1441
+ const $getStoreOrThrow = (method, name) => {
1442
+ const item = $stores.get(name);
1443
+ if (!item) {
1444
+ DebugManager.error('Store', `Store.${method}('${name}') : store not found. Did you call Store.create('${name}') first?`);
1445
+ throw new NativeDocumentError(
1446
+ `Store.${method}('${name}') : store not found.`
1447
+ );
1513
1448
  }
1514
- return Validator.isObject(data)
1515
- && Object.values(data).some(value => Validator.isObservable(value));
1516
- },
1449
+ return item;
1450
+ };
1451
+
1517
1452
  /**
1518
- * Check if the data contains an observable reference.
1519
- * @param {string} data
1520
- * @returns {boolean}
1453
+ * Internal helper blocks write operations on a read-only observer.
1521
1454
  */
1522
- containsObservableReference(data) {
1523
- if(!data || typeof data !== 'string') {
1524
- return false;
1525
- }
1526
- return /\{\{#ObItem::\([0-9]+\)\}\}/.test(data);
1527
- },
1528
- validateAttributes(attributes) {},
1455
+ const $applyReadOnly = (observer, name, context) => {
1456
+ const readOnlyError = (method) => () => {
1457
+ DebugManager.error('Store', `Store.${context}('${name}') is read-only. '${method}()' is not allowed.`);
1458
+ throw new NativeDocumentError(
1459
+ `Store.${context}('${name}') is read-only.`
1460
+ );
1461
+ };
1462
+ observer.set = readOnlyError('set');
1463
+ observer.toggle = readOnlyError('toggle');
1464
+ observer.reset = readOnlyError('reset');
1465
+ };
1529
1466
 
1530
- validateEventCallback(callback) {
1531
- if (typeof callback !== 'function') {
1532
- throw new NativeDocumentError('Event callback must be a function');
1467
+ const $createObservable = (value, options = {}) => {
1468
+ if(Array.isArray(value)) {
1469
+ return Observable$1.array(value, options);
1533
1470
  }
1534
- }
1535
- };
1471
+ if(typeof value === 'object') {
1472
+ return Observable$1.object(value, options);
1473
+ }
1474
+ return Observable$1(value, options);
1475
+ };
1536
1476
 
1537
- function Anchor(name, isUniqueChild = false) {
1538
- const anchorFragment = document.createDocumentFragment();
1539
- anchorFragment.__Anchor__ = true;
1477
+ const $api = {
1478
+ /**
1479
+ * Create a new state and return the observer.
1480
+ * Throws if a store with the same name already exists.
1481
+ *
1482
+ * @param {string} name
1483
+ * @param {*} value
1484
+ * @returns {ObservableItem}
1485
+ */
1486
+ create(name, value) {
1487
+ if ($stores.has(name)) {
1488
+ DebugManager.warn('Store', `Store.create('${name}') : a store with this name already exists. Use Store.get('${name}') to retrieve it.`);
1489
+ throw new NativeDocumentError(
1490
+ `Store.create('${name}') : a store with this name already exists.`
1491
+ );
1492
+ }
1493
+ const observer = $createObservable(value);
1494
+ $stores.set(name, { observer, subscribers: new Set(), resettable: false, composed: false });
1495
+ return observer;
1496
+ },
1540
1497
 
1541
- const anchorStart = document.createComment('Anchor Start : '+name);
1542
- const anchorEnd = document.createComment('/ Anchor End '+name);
1498
+ /**
1499
+ * Create a new resettable state and return the observer.
1500
+ * The store can be reset to its initial value via Store.reset(name).
1501
+ * Throws if a store with the same name already exists.
1502
+ *
1503
+ * @param {string} name
1504
+ * @param {*} value
1505
+ * @returns {ObservableItem}
1506
+ */
1507
+ createResettable(name, value) {
1508
+ if ($stores.has(name)) {
1509
+ DebugManager.warn('Store', `Store.createResettable('${name}') : a store with this name already exists.`);
1510
+ throw new NativeDocumentError(
1511
+ `Store.createResettable('${name}') : a store with this name already exists.`
1512
+ );
1513
+ }
1514
+ const observer = $createObservable(value, { reset: true });
1515
+ $stores.set(name, { observer, subscribers: new Set(), resettable: true, composed: false });
1516
+ return observer;
1517
+ },
1543
1518
 
1544
- anchorFragment.appendChild(anchorStart);
1545
- anchorFragment.appendChild(anchorEnd);
1519
+ /**
1520
+ * Create a computed store derived from other stores.
1521
+ * The value is automatically recalculated when any dependency changes.
1522
+ * This store is read-only — Store.use() and Store.set() will throw.
1523
+ * Throws if a store with the same name already exists.
1524
+ *
1525
+ * @param {string} name
1526
+ * @param {() => *} computation - Function that returns the computed value
1527
+ * @param {string[]} dependencies - Names of the stores to watch
1528
+ * @returns {ObservableItem}
1529
+ *
1530
+ * @example
1531
+ * Store.create('products', [{ id: 1, price: 10 }]);
1532
+ * Store.create('cart', [{ productId: 1, quantity: 2 }]);
1533
+ *
1534
+ * Store.createComposed('total', () => {
1535
+ * const products = Store.get('products').val();
1536
+ * const cart = Store.get('cart').val();
1537
+ * return cart.reduce((sum, item) => {
1538
+ * const product = products.find(p => p.id === item.productId);
1539
+ * return sum + (product.price * item.quantity);
1540
+ * }, 0);
1541
+ * }, ['products', 'cart']);
1542
+ */
1543
+ createComposed(name, computation, dependencies) {
1544
+ if ($stores.has(name)) {
1545
+ DebugManager.warn('Store', `Store.createComposed('${name}') : a store with this name already exists.`);
1546
+ throw new NativeDocumentError(
1547
+ `Store.createComposed('${name}') : a store with this name already exists.`
1548
+ );
1549
+ }
1550
+ if (typeof computation !== 'function') {
1551
+ throw new NativeDocumentError(
1552
+ `Store.createComposed('${name}') : computation must be a function.`
1553
+ );
1554
+ }
1555
+ if (!Array.isArray(dependencies) || dependencies.length === 0) {
1556
+ throw new NativeDocumentError(
1557
+ `Store.createComposed('${name}') : dependencies must be a non-empty array of store names.`
1558
+ );
1559
+ }
1546
1560
 
1547
- anchorFragment.nativeInsertBefore = anchorFragment.insertBefore;
1548
- anchorFragment.nativeAppendChild = anchorFragment.appendChild;
1561
+ // Resolve dependency observers
1562
+ const depObservers = dependencies.map(depName => {
1563
+ if(typeof depName !== 'string') {
1564
+ return depName;
1565
+ }
1566
+ const depItem = $stores.get(depName);
1567
+ if (!depItem) {
1568
+ DebugManager.error('Store', `Store.createComposed('${name}') : dependency '${depName}' not found. Create it first.`);
1569
+ throw new NativeDocumentError(
1570
+ `Store.createComposed('${name}') : dependency store '${depName}' not found.`
1571
+ );
1572
+ }
1573
+ return depItem.observer;
1574
+ });
1549
1575
 
1550
- const isParentUniqueChild = (parent) => (isUniqueChild || (parent.firstChild === anchorStart && parent.lastChild === anchorEnd));
1576
+ // Create computed observable from dependency observers
1577
+ const observer = Observable$1.computed(computation, depObservers);
1551
1578
 
1552
- const insertBefore = function(parent, child, target) {
1553
- const childElement = Validator.isElement(child) ? child : ElementCreator.getChild(child);
1554
- if(parent === anchorFragment) {
1555
- parent.nativeInsertBefore(childElement, target);
1556
- return;
1557
- }
1558
- if(isParentUniqueChild(parent) && target === anchorEnd) {
1559
- parent.append(childElement, target);
1560
- return;
1561
- }
1562
- parent.insertBefore(childElement, target);
1563
- };
1579
+ $stores.set(name, { observer, subscribers: new Set(), resettable: false, composed: true });
1580
+ return observer;
1581
+ },
1564
1582
 
1565
- anchorFragment.appendElement = function(child, before = null) {
1566
- const parentNode = anchorStart.parentNode;
1567
- const targetBefore = before || anchorEnd;
1568
- if(parentNode === anchorFragment) {
1569
- parentNode.nativeInsertBefore(child, targetBefore);
1570
- return;
1583
+ /**
1584
+ * Returns true if a store with the given name exists.
1585
+ *
1586
+ * @param {string} name
1587
+ * @returns {boolean}
1588
+ */
1589
+ has(name) {
1590
+ return $stores.has(name);
1591
+ },
1592
+
1593
+ /**
1594
+ * Resets a resettable store to its initial value and notifies all subscribers.
1595
+ * Throws if the store was not created with createResettable().
1596
+ *
1597
+ * @param {string} name
1598
+ */
1599
+ reset(name) {
1600
+ const item = $getStoreOrThrow('reset', name);
1601
+ if (item.composed) {
1602
+ DebugManager.error('Store', `Store.reset('${name}') : composed stores cannot be reset. Their value is derived from dependencies.`);
1603
+ throw new NativeDocumentError(
1604
+ `Store.reset('${name}') : composed stores cannot be reset.`
1605
+ );
1606
+ }
1607
+ if (!item.resettable) {
1608
+ DebugManager.error('Store', `Store.reset('${name}') : this store is not resettable. Use Store.createResettable('${name}', value) instead of Store.create().`);
1609
+ throw new NativeDocumentError(
1610
+ `Store.reset('${name}') : this store is not resettable. Use Store.createResettable('${name}', value) instead of Store.create().`
1611
+ );
1612
+ }
1613
+ item.observer.reset();
1614
+ },
1615
+
1616
+ /**
1617
+ * Returns a two-way synchronized follower of the store.
1618
+ * Writing to the follower propagates the value back to the store and all its subscribers.
1619
+ * Throws if called on a composed store — use Store.follow() instead.
1620
+ * Call follower.destroy() or follower.dispose() to unsubscribe.
1621
+ *
1622
+ * @param {string} name
1623
+ * @returns {ObservableItem}
1624
+ */
1625
+ use(name) {
1626
+ const item = $getStoreOrThrow('use', name);
1627
+
1628
+ if (item.composed) {
1629
+ DebugManager.error('Store', `Store.use('${name}') : composed stores are read-only. Use Store.follow('${name}') instead.`);
1630
+ throw new NativeDocumentError(
1631
+ `Store.use('${name}') : composed stores are read-only. Use Store.follow('${name}') instead.`
1632
+ );
1633
+ }
1634
+
1635
+ const { observer: originalObserver, subscribers } = item;
1636
+ const observerFollower = $createObservable(originalObserver.val());
1637
+
1638
+ const onStoreChange = value => observerFollower.set(value);
1639
+ const onFollowerChange = value => originalObserver.set(value);
1640
+
1641
+ originalObserver.subscribe(onStoreChange);
1642
+ observerFollower.subscribe(onFollowerChange);
1643
+
1644
+ observerFollower.destroy = () => {
1645
+ originalObserver.unsubscribe(onStoreChange);
1646
+ observerFollower.unsubscribe(onFollowerChange);
1647
+ subscribers.delete(observerFollower);
1648
+ observerFollower.cleanup();
1649
+ };
1650
+ observerFollower.dispose = observerFollower.destroy;
1651
+
1652
+ subscribers.add(observerFollower);
1653
+ return observerFollower;
1654
+ },
1655
+
1656
+ /**
1657
+ * Returns a read-only follower of the store.
1658
+ * The follower reflects store changes but cannot write back to the store.
1659
+ * Any attempt to call .set(), .toggle() or .reset() will throw.
1660
+ * Call follower.destroy() or follower.dispose() to unsubscribe.
1661
+ *
1662
+ * @param {string} name
1663
+ * @returns {ObservableItem}
1664
+ */
1665
+ follow(name) {
1666
+ const { observer: originalObserver, subscribers } = $getStoreOrThrow('follow', name);
1667
+ const observerFollower = $createObservable(originalObserver.val());
1668
+
1669
+ const onStoreChange = value => observerFollower.set(value);
1670
+ originalObserver.subscribe(onStoreChange);
1671
+
1672
+ $applyReadOnly(observerFollower, name, 'follow');
1673
+
1674
+ observerFollower.destroy = () => {
1675
+ originalObserver.unsubscribe(onStoreChange);
1676
+ subscribers.delete(observerFollower);
1677
+ observerFollower.cleanup();
1678
+ };
1679
+ observerFollower.dispose = observerFollower.destroy;
1680
+
1681
+ subscribers.add(observerFollower);
1682
+ return observerFollower;
1683
+ },
1684
+
1685
+ /**
1686
+ * Returns the raw store observer directly (no follower, no cleanup contract).
1687
+ * Use this for direct read access when you don't need to unsubscribe.
1688
+ * WARNING : mutations on this observer impact all subscribers immediately.
1689
+ *
1690
+ * @param {string} name
1691
+ * @returns {ObservableItem|null}
1692
+ */
1693
+ get(name) {
1694
+ const item = $stores.get(name);
1695
+ if (!item) {
1696
+ DebugManager.warn('Store', `Store.get('${name}') : store not found.`);
1697
+ return null;
1698
+ }
1699
+ return item.observer;
1700
+ },
1701
+
1702
+ /**
1703
+ * @param {string} name
1704
+ * @returns {{ observer: ObservableItem, subscribers: Set } | null}
1705
+ */
1706
+ getWithSubscribers(name) {
1707
+ return $stores.get(name) ?? null;
1708
+ },
1709
+
1710
+ /**
1711
+ * Destroys a store : cleans up the observer, destroys all followers, and removes the entry.
1712
+ *
1713
+ * @param {string} name
1714
+ */
1715
+ delete(name) {
1716
+ const item = $stores.get(name);
1717
+ if (!item) {
1718
+ DebugManager.warn('Store', `Store.delete('${name}') : store not found, nothing to delete.`);
1719
+ return;
1720
+ }
1721
+ item.subscribers.forEach(follower => follower.destroy());
1722
+ item.subscribers.clear();
1723
+ item.observer.cleanup();
1724
+ $stores.delete(name);
1725
+ },
1726
+ /**
1727
+ * Creates an isolated store group with its own state namespace.
1728
+ * Each group is a fully independent StoreFactory instance —
1729
+ * no key conflicts, no shared state with the parent store.
1730
+ *
1731
+ * @param {string | ((group: ReturnType<typeof StoreFactory>) => void)} name - Group name for debugging, or setup callback if no name is provided
1732
+ * @param {((group: ReturnType<typeof StoreFactory>) => void)} [callback] - Setup function receiving the isolated store instance
1733
+ * @returns {ReturnType<typeof StoreFactory>}
1734
+ *
1735
+ * @example
1736
+ * // With name (recommended)
1737
+ * const EventStore = Store.group('events', (group) => {
1738
+ * group.create('catalog', []);
1739
+ * group.create('filters', { category: null, date: null });
1740
+ * group.createResettable('selected', null);
1741
+ * group.createComposed('filtered', () => {
1742
+ * const catalog = EventStore.get('catalog').val();
1743
+ * const filters = EventStore.get('filters').val();
1744
+ * return catalog.filter(event => {
1745
+ * if (filters.category && event.category !== filters.category) return false;
1746
+ * return true;
1747
+ * });
1748
+ * }, ['catalog', 'filters']);
1749
+ * });
1750
+ *
1751
+ * // Without name
1752
+ * const CartStore = Store.group((group) => {
1753
+ * group.create('items', []);
1754
+ * });
1755
+ *
1756
+ * // Usage
1757
+ * EventStore.use('catalog'); // two-way follower
1758
+ * EventStore.follow('filtered'); // read-only follower
1759
+ * EventStore.get('filters'); // raw observable
1760
+ *
1761
+ * // Cross-group composed
1762
+ * const OrderStore = Store.group('orders', (group) => {
1763
+ * group.createComposed('summary', () => {
1764
+ * const items = CartStore.get('items').val();
1765
+ * const events = EventStore.get('catalog').val();
1766
+ * return { items, events };
1767
+ * }, [CartStore.get('items'), EventStore.get('catalog')]);
1768
+ * });
1769
+ */
1770
+ group(name, callback) {
1771
+ if (typeof name === 'function') {
1772
+ callback = name;
1773
+ name = 'anonymous';
1774
+ }
1775
+ const store = StoreFactory();
1776
+ callback && callback(store);
1777
+ return store;
1778
+ },
1779
+ createPersistent(name, value, localstorage_key) {
1780
+ localstorage_key = localstorage_key || name;
1781
+ const observer = this.create(name, $getFromStorage$1(localstorage_key, value));
1782
+ const saver = $saveToStorage$1(value);
1783
+
1784
+ observer.subscribe((val) => saver(localstorage_key, val));
1785
+ return observer;
1786
+ },
1787
+ createPersistentResettable(name, value, localstorage_key) {
1788
+ localstorage_key = localstorage_key || name;
1789
+ const observer = this.createResettable(name, $getFromStorage$1(localstorage_key, value));
1790
+ const saver = $saveToStorage$1(value);
1791
+ observer.subscribe((val) => saver(localstorage_key, val));
1792
+
1793
+ const originalReset = observer.reset.bind(observer);
1794
+ observer.reset = () => {
1795
+ LocalStorage$1.remove(localstorage_key);
1796
+ originalReset();
1797
+ };
1798
+
1799
+ return observer;
1571
1800
  }
1572
- parentNode?.insertBefore(child, targetBefore);
1573
1801
  };
1574
1802
 
1575
- anchorFragment.appendChild = function(child, before = null) {
1576
- const parent = anchorEnd.parentNode;
1577
- if(!parent) {
1578
- DebugManager.error('Anchor', 'Anchor : parent not found', child);
1579
- return;
1803
+
1804
+ return new Proxy($api, {
1805
+ get(target, prop) {
1806
+ if (typeof prop === 'symbol' || prop.startsWith('$') || prop in target) {
1807
+ return target[prop];
1808
+ }
1809
+ if (target.has(prop)) {
1810
+ if ($followersCache.has(prop)) {
1811
+ return $followersCache.get(prop);
1812
+ }
1813
+ const follower = target.follow(prop);
1814
+ $followersCache.set(prop, follower);
1815
+ return follower;
1816
+ }
1817
+ return undefined;
1818
+ },
1819
+ set(target, prop, value) {
1820
+ DebugManager.error('Store', `Forbidden: You cannot overwrite the store key '${String(prop)}'. Use .use('${String(prop)}').set(value) instead.`);
1821
+ throw new NativeDocumentError(`Store structure is immutable. Use .set() on the observable.`);
1822
+ },
1823
+ deleteProperty(target, prop) {
1824
+ throw new NativeDocumentError(`Store keys cannot be deleted.`);
1580
1825
  }
1581
- before = before ?? anchorEnd;
1582
- insertBefore(parent, child, before);
1583
- };
1584
- anchorFragment.append = function(...args ) {
1585
- return anchorFragment.appendChild(args);
1826
+ });
1827
+ };
1828
+
1829
+ const Store = StoreFactory();
1830
+
1831
+ Store.create('locale', 'fr');
1832
+
1833
+ const $parseDateParts = (value, locale) => {
1834
+ const d = new Date(value);
1835
+ return {
1836
+ d,
1837
+ parts: new Intl.DateTimeFormat(locale, {
1838
+ year: 'numeric',
1839
+ month: 'long',
1840
+ day: '2-digit',
1841
+ hour: '2-digit',
1842
+ minute: '2-digit',
1843
+ second: '2-digit',
1844
+ }).formatToParts(d).reduce((acc, { type, value }) => {
1845
+ acc[type] = value;
1846
+ return acc;
1847
+ }, {})
1586
1848
  };
1849
+ };
1587
1850
 
1588
- anchorFragment.removeChildren = function() {
1589
- const parent = anchorEnd.parentNode;
1590
- if(parent === anchorFragment) {
1591
- return;
1592
- }
1593
- if(isParentUniqueChild(parent)) {
1594
- parent.replaceChildren(anchorStart, anchorEnd);
1595
- return;
1851
+ const $applyDatePattern = (pattern, d, parts) => {
1852
+ const pad = n => String(n).padStart(2, '0');
1853
+ return pattern
1854
+ .replace('YYYY', parts.year)
1855
+ .replace('YY', parts.year.slice(-2))
1856
+ .replace('MMMM', parts.month)
1857
+ .replace('MMM', parts.month.slice(0, 3))
1858
+ .replace('MM', pad(d.getMonth() + 1))
1859
+ .replace('DD', pad(d.getDate()))
1860
+ .replace('D', d.getDate())
1861
+ .replace('HH', parts.hour)
1862
+ .replace('mm', parts.minute)
1863
+ .replace('ss', parts.second);
1864
+ };
1865
+
1866
+ const Formatters = {
1867
+
1868
+ currency: (value, locale, { currency = 'XOF', notation, minimumFractionDigits, maximumFractionDigits } = {}) =>
1869
+ new Intl.NumberFormat(locale, {
1870
+ style: 'currency',
1871
+ currency,
1872
+ notation,
1873
+ minimumFractionDigits,
1874
+ maximumFractionDigits
1875
+ }).format(value),
1876
+
1877
+ number: (value, locale, { notation, minimumFractionDigits, maximumFractionDigits } = {}) =>
1878
+ new Intl.NumberFormat(locale, {
1879
+ notation,
1880
+ minimumFractionDigits,
1881
+ maximumFractionDigits
1882
+ }).format(value),
1883
+
1884
+ percent: (value, locale, { decimals = 1 } = {}) =>
1885
+ new Intl.NumberFormat(locale, {
1886
+ style: 'percent',
1887
+ maximumFractionDigits: decimals
1888
+ }).format(value),
1889
+
1890
+ date: (value, locale, { format, dateStyle = 'long' } = {}) => {
1891
+ if (format) {
1892
+ const { d, parts } = $parseDateParts(value, locale);
1893
+ return $applyDatePattern(format, d, parts);
1596
1894
  }
1895
+ return new Intl.DateTimeFormat(locale, { dateStyle }).format(new Date(value));
1896
+ },
1597
1897
 
1598
- let itemToRemove = anchorStart.nextSibling, tempItem;
1599
- const fragment = document.createDocumentFragment();
1600
- while(itemToRemove && itemToRemove !== anchorEnd) {
1601
- tempItem = itemToRemove.nextSibling;
1602
- fragment.append(itemToRemove);
1603
- itemToRemove = tempItem;
1898
+ time: (value, locale, { format, hour = '2-digit', minute = '2-digit', second } = {}) => {
1899
+ if (format) {
1900
+ const { d, parts } = $parseDateParts(value, locale);
1901
+ return $applyDatePattern(format, d, parts);
1604
1902
  }
1605
- fragment.replaceChildren();
1606
- };
1607
- anchorFragment.remove = function() {
1608
- const parent = anchorEnd.parentNode;
1609
- if(parent === anchorFragment) {
1610
- return;
1903
+ return new Intl.DateTimeFormat(locale, { hour, minute, second }).format(new Date(value));
1904
+ },
1905
+
1906
+ datetime: (value, locale, { format, dateStyle = 'long', hour = '2-digit', minute = '2-digit', second } = {}) => {
1907
+ if (format) {
1908
+ const { d, parts } = $parseDateParts(value, locale);
1909
+ return $applyDatePattern(format, d, parts);
1611
1910
  }
1612
- let itemToRemove = anchorStart.nextSibling, tempItem;
1613
- while(itemToRemove && itemToRemove !== anchorEnd) {
1614
- tempItem = itemToRemove.nextSibling;
1615
- anchorFragment.nativeAppendChild(itemToRemove);
1616
- itemToRemove = tempItem;
1911
+ return new Intl.DateTimeFormat(locale, { dateStyle, hour, minute, second }).format(new Date(value));
1912
+ },
1913
+
1914
+ relative: (value, locale, { unit = 'day', numeric = 'auto' } = {}) => {
1915
+ const diff = Math.round((value - Date.now()) / (1000 * 60 * 60 * 24));
1916
+ return new Intl.RelativeTimeFormat(locale, { numeric }).format(diff, unit);
1917
+ },
1918
+
1919
+ plural: (value, locale, { singular, plural } = {}) => {
1920
+ const rule = new Intl.PluralRules(locale).select(value);
1921
+ return `${value} ${rule === 'one' ? singular : plural}`;
1922
+ },
1923
+ };
1924
+
1925
+ /**
1926
+ *
1927
+ * @param {*} value
1928
+ * @param {{ propagation: boolean, reset: boolean} | null} configs
1929
+ * @class ObservableItem
1930
+ */
1931
+ function ObservableItem(value, configs = null) {
1932
+ value = Validator.isObservable(value) ? value.val() : value;
1933
+
1934
+ this.$previousValue = null;
1935
+ this.$currentValue = value;
1936
+
1937
+ this.$firstListener = null;
1938
+ this.$listeners = null;
1939
+ this.$watchers = null;
1940
+
1941
+ this.$memoryId = null;
1942
+
1943
+ if(configs) {
1944
+ this.configs = configs;
1945
+ if(configs.reset) {
1946
+ this.$initialValue = Validator.isObject(value) ? deepClone(value) : value;
1617
1947
  }
1618
- };
1948
+ }
1949
+ }
1619
1950
 
1620
- anchorFragment.removeWithAnchors = function() {
1621
- anchorFragment.removeChildren();
1622
- anchorStart.remove();
1623
- anchorEnd.remove();
1624
- };
1951
+ Object.defineProperty(ObservableItem.prototype, '$value', {
1952
+ get() {
1953
+ return this.$currentValue;
1954
+ },
1955
+ set(value) {
1956
+ this.set(value);
1957
+ },
1958
+ configurable: true,
1959
+ });
1960
+
1961
+ ObservableItem.prototype.__$isObservable = true;
1962
+ const noneTrigger = function() {};
1963
+
1964
+ /**
1965
+ * Intercepts and transforms values before they are set on the observable.
1966
+ * The interceptor can modify the value or return undefined to use the original value.
1967
+ *
1968
+ * @param {(value) => any} callback - Interceptor function that receives (newValue, currentValue) and returns the transformed value or undefined
1969
+ * @returns {ObservableItem} The observable instance for chaining
1970
+ * @example
1971
+ * const count = Observable(0);
1972
+ * count.intercept((newVal, oldVal) => Math.max(0, newVal)); // Prevent negative values
1973
+ */
1974
+ ObservableItem.prototype.intercept = function(callback) {
1975
+ this.$interceptor = callback;
1976
+ this.set = this.$setWithInterceptor;
1977
+ return this;
1978
+ };
1979
+
1980
+ ObservableItem.prototype.triggerFirstListener = function(operations) {
1981
+ this.$firstListener(this.$currentValue, this.$previousValue, operations);
1982
+ };
1983
+
1984
+ ObservableItem.prototype.triggerListeners = function(operations) {
1985
+ const $listeners = this.$listeners;
1986
+ const $previousValue = this.$previousValue;
1987
+ const $currentValue = this.$currentValue;
1988
+
1989
+ for(let i = 0, length = $listeners.length; i < length; i++) {
1990
+ $listeners[i]($currentValue, $previousValue, operations);
1991
+ }
1992
+ };
1993
+
1994
+ ObservableItem.prototype.triggerWatchers = function(operations) {
1995
+ const $watchers = this.$watchers;
1996
+ const $previousValue = this.$previousValue;
1997
+ const $currentValue = this.$currentValue;
1998
+
1999
+ const $currentValueCallbacks = $watchers.get($currentValue);
2000
+ const $previousValueCallbacks = $watchers.get($previousValue);
2001
+ if($currentValueCallbacks) {
2002
+ $currentValueCallbacks(true, $previousValue, operations);
2003
+ }
2004
+ if($previousValueCallbacks) {
2005
+ $previousValueCallbacks(false, $currentValue, operations);
2006
+ }
2007
+ };
2008
+
2009
+ ObservableItem.prototype.triggerAll = function(operations) {
2010
+ this.triggerWatchers(operations);
2011
+ this.triggerListeners(operations);
2012
+ };
2013
+
2014
+ ObservableItem.prototype.triggerWatchersAndFirstListener = function(operations) {
2015
+ this.triggerWatchers(operations);
2016
+ this.triggerFirstListener(operations);
2017
+ };
2018
+
2019
+ ObservableItem.prototype.assocTrigger = function() {
2020
+ this.$firstListener = null;
2021
+ if(this.$watchers?.size && this.$listeners?.length) {
2022
+ this.trigger = (this.$listeners.length === 1) ? this.triggerWatchersAndFirstListener : this.triggerAll;
2023
+ return;
2024
+ }
2025
+ if(this.$listeners?.length) {
2026
+ if(this.$listeners.length === 1) {
2027
+ this.$firstListener = this.$listeners[0];
2028
+ this.trigger = this.triggerFirstListener;
2029
+ }
2030
+ else {
2031
+ this.trigger = this.triggerListeners;
2032
+ }
2033
+ return;
2034
+ }
2035
+ if(this.$watchers?.size) {
2036
+ this.trigger = this.triggerWatchers;
2037
+ return;
2038
+ }
2039
+ this.trigger = noneTrigger;
2040
+ };
2041
+ ObservableItem.prototype.trigger = noneTrigger;
2042
+
2043
+ ObservableItem.prototype.$updateWithNewValue = function(newValue) {
2044
+ newValue = newValue?.__$isObservable ? newValue.val() : newValue;
2045
+ if(this.$currentValue === newValue) {
2046
+ return;
2047
+ }
2048
+ this.$previousValue = this.$currentValue;
2049
+ this.$currentValue = newValue;
2050
+ this.trigger();
2051
+ this.$previousValue = null;
2052
+ };
2053
+
2054
+ /**
2055
+ * @param {*} data
2056
+ */
2057
+ ObservableItem.prototype.$setWithInterceptor = function(data) {
2058
+ let newValue = (typeof data === 'function') ? data(this.$currentValue) : data;
2059
+ const result = this.$interceptor(newValue, this.$currentValue);
2060
+
2061
+ if (result !== undefined) {
2062
+ newValue = result;
2063
+ }
2064
+
2065
+ this.$updateWithNewValue(newValue);
2066
+ };
2067
+
2068
+ /**
2069
+ * @param {*} data
2070
+ */
2071
+ ObservableItem.prototype.$basicSet = function(data) {
2072
+ let newValue = (typeof data === 'function') ? data(this.$currentValue) : data;
2073
+ this.$updateWithNewValue(newValue);
2074
+ };
2075
+
2076
+ ObservableItem.prototype.set = ObservableItem.prototype.$basicSet;
2077
+
2078
+ ObservableItem.prototype.val = function() {
2079
+ return this.$currentValue;
2080
+ };
2081
+
2082
+ ObservableItem.prototype.disconnectAll = function() {
2083
+ this.$listeners?.splice(0);
2084
+ this.$previousValue = null;
2085
+ this.$currentValue = null;
2086
+ if(this.$watchers) {
2087
+ for (const [_, watchValueList] of this.$watchers) {
2088
+ if(Validator.isArray(watchValueList)) {
2089
+ watchValueList.splice(0);
2090
+ }
2091
+ }
2092
+ }
2093
+ this.$watchers?.clear();
2094
+ this.$listeners = null;
2095
+ this.$watchers = null;
2096
+ this.trigger = noneTrigger;
2097
+ };
2098
+
2099
+ /**
2100
+ * Registers a cleanup callback that will be executed when the observable is cleaned up.
2101
+ * Useful for disposing resources, removing event listeners, or other cleanup tasks.
2102
+ *
2103
+ * @param {Function} callback - Cleanup function to execute on observable disposal
2104
+ * @example
2105
+ * const obs = Observable(0);
2106
+ * obs.onCleanup(() => console.log('Cleaned up!'));
2107
+ * obs.cleanup(); // Logs: "Cleaned up!"
2108
+ */
2109
+ ObservableItem.prototype.onCleanup = function(callback) {
2110
+ this.$cleanupListeners = this.$cleanupListeners ?? [];
2111
+ this.$cleanupListeners.push(callback);
2112
+ };
2113
+
2114
+ ObservableItem.prototype.cleanup = function() {
2115
+ if (this.$cleanupListeners) {
2116
+ for (let i = 0; i < this.$cleanupListeners.length; i++) {
2117
+ this.$cleanupListeners[i]();
2118
+ }
2119
+ this.$cleanupListeners = null;
2120
+ }
2121
+ MemoryManager.unregister(this.$memoryId);
2122
+ this.disconnectAll();
2123
+ delete this.$value;
2124
+ };
2125
+
2126
+ /**
2127
+ *
2128
+ * @param {Function} callback
2129
+ * @returns {(function(): void)}
2130
+ */
2131
+ ObservableItem.prototype.subscribe = function(callback) {
2132
+ this.$listeners = this.$listeners ?? [];
2133
+
2134
+ this.$listeners.push(callback);
2135
+ this.assocTrigger();
2136
+ };
2137
+
2138
+ /**
2139
+ * Watches for a specific value and executes callback when the observable equals that value.
2140
+ * Creates a watcher that only triggers when the observable changes to the specified value.
2141
+ *
2142
+ * @param {*} value - The value to watch for
2143
+ * @param {(value) => void|ObservableItem} callback - Callback function or observable to set when value matches
2144
+ * @example
2145
+ * const status = Observable('idle');
2146
+ * status.on('loading', () => console.log('Started loading'));
2147
+ * status.on('error', isError); // Set another observable
2148
+ */
2149
+ ObservableItem.prototype.on = function(value, callback) {
2150
+ this.$watchers = this.$watchers ?? new Map();
2151
+
2152
+ let watchValueList = this.$watchers.get(value);
2153
+
2154
+ if(callback.__$isObservable) {
2155
+ callback = callback.set.bind(callback);
2156
+ }
2157
+
2158
+ if(!watchValueList) {
2159
+ watchValueList = callback;
2160
+ this.$watchers.set(value, callback);
2161
+ } else if(!Validator.isArray(watchValueList.list)) {
2162
+ watchValueList = [watchValueList, callback];
2163
+ callback = (value) => {
2164
+ for(let i = 0, length = watchValueList.length; i < length; i++) {
2165
+ watchValueList[i](value);
2166
+ }
2167
+ };
2168
+ callback.list = watchValueList;
2169
+ this.$watchers.set(value, callback);
2170
+ } else {
2171
+ watchValueList.list.push(callback);
2172
+ }
2173
+
2174
+ this.assocTrigger();
2175
+ };
2176
+
2177
+ /**
2178
+ * Removes a watcher for a specific value. If no callback is provided, removes all watchers for that value.
2179
+ *
2180
+ * @param {*} value - The value to stop watching
2181
+ * @param {Function} [callback] - Specific callback to remove. If omitted, removes all watchers for this value
2182
+ * @example
2183
+ * const status = Observable('idle');
2184
+ * const handler = () => console.log('Loading');
2185
+ * status.on('loading', handler);
2186
+ * status.off('loading', handler); // Remove specific handler
2187
+ * status.off('loading'); // Remove all handlers for 'loading'
2188
+ */
2189
+ ObservableItem.prototype.off = function(value, callback) {
2190
+ if(!this.$watchers) return;
2191
+
2192
+ const watchValueList = this.$watchers.get(value);
2193
+ if(!watchValueList) return;
2194
+
2195
+ if(!callback || !Array.isArray(watchValueList.list)) {
2196
+ this.$watchers?.delete(value);
2197
+ this.assocTrigger();
2198
+ return;
2199
+ }
2200
+ const index = watchValueList.indexOf(callback);
2201
+ watchValueList?.splice(index, 1);
2202
+ if(watchValueList.length === 1) {
2203
+ this.$watchers.set(value, watchValueList[0]);
2204
+ }
2205
+ else if(watchValueList.length === 0) {
2206
+ this.$watchers?.delete(value);
2207
+ }
2208
+ this.assocTrigger();
2209
+ };
2210
+
2211
+ /**
2212
+ * Subscribes to the observable but automatically unsubscribes after the first time the predicate matches.
2213
+ *
2214
+ * @param {(value) => Boolean|any} predicate - Value to match or function that returns true when condition is met
2215
+ * @param {(value) => void} callback - Callback to execute when predicate matches, receives the matched value
2216
+ * @example
2217
+ * const status = Observable('loading');
2218
+ * status.once('ready', (val) => console.log('Ready!'));
2219
+ * status.once(val => val === 'error', (val) => console.log('Error occurred'));
2220
+ */
2221
+ ObservableItem.prototype.once = function(predicate, callback) {
2222
+ const fn = typeof predicate === 'function' ? predicate : (v) => v === predicate;
2223
+
2224
+ const handler = (val) => {
2225
+ if (fn(val)) {
2226
+ this.unsubscribe(handler);
2227
+ callback(val);
2228
+ }
2229
+ };
2230
+ this.subscribe(handler);
2231
+ };
2232
+
2233
+ /**
2234
+ * Unsubscribe from an observable.
2235
+ * @param {Function} callback
2236
+ */
2237
+ ObservableItem.prototype.unsubscribe = function(callback) {
2238
+ if(!this.$listeners) return;
2239
+ const index = this.$listeners.indexOf(callback);
2240
+ if (index > -1) {
2241
+ this.$listeners.splice(index, 1);
2242
+ }
2243
+ this.assocTrigger();
2244
+ };
2245
+
2246
+ /**
2247
+ * Create an Observable checker instance
2248
+ * @param callback
2249
+ * @returns {ObservableChecker}
2250
+ */
2251
+ ObservableItem.prototype.check = function(callback) {
2252
+ return new ObservableChecker(this, callback)
2253
+ };
2254
+
2255
+ ObservableItem.prototype.transform = ObservableItem.prototype.check;
2256
+ ObservableItem.prototype.pluck = ObservableItem.prototype.check;
2257
+ ObservableItem.prototype.is = ObservableItem.prototype.check;
2258
+ ObservableItem.prototype.select = ObservableItem.prototype.check;
2259
+
2260
+ /**
2261
+ * Gets a property value from the observable's current value.
2262
+ * If the property is an observable, returns its value.
2263
+ *
2264
+ * @param {string|number} key - Property key to retrieve
2265
+ * @returns {*} The value of the property, unwrapped if it's an observable
2266
+ * @example
2267
+ * const user = Observable({ name: 'John', age: Observable(25) });
2268
+ * user.get('name'); // 'John'
2269
+ * user.get('age'); // 25 (unwrapped from observable)
2270
+ */
2271
+ ObservableItem.prototype.get = function(key) {
2272
+ const item = this.$currentValue[key];
2273
+ return Validator.isObservable(item) ? item.val() : item;
2274
+ };
2275
+
2276
+ /**
2277
+ * Creates an ObservableWhen that represents whether the observable equals a specific value.
2278
+ * Returns an object that can be subscribed to and will emit true/false.
2279
+ *
2280
+ * @param {*} value - The value to compare against
2281
+ * @returns {ObservableWhen} An ObservableWhen instance that tracks when the observable equals the value
2282
+ * @example
2283
+ * const status = Observable('idle');
2284
+ * const isLoading = status.when('loading');
2285
+ * isLoading.subscribe(active => console.log('Loading:', active));
2286
+ * status.set('loading'); // Logs: "Loading: true"
2287
+ */
2288
+ ObservableItem.prototype.when = function(value) {
2289
+ return new ObservableWhen(this, value);
2290
+ };
2291
+
2292
+ /**
2293
+ * Compares the observable's current value with another value or observable.
2294
+ *
2295
+ * @param {*|ObservableItem} other - Value or observable to compare against
2296
+ * @returns {boolean} True if values are equal
2297
+ * @example
2298
+ * const a = Observable(5);
2299
+ * const b = Observable(5);
2300
+ * a.equals(5); // true
2301
+ * a.equals(b); // true
2302
+ * a.equals(10); // false
2303
+ */
2304
+ ObservableItem.prototype.equals = function(other) {
2305
+ if(Validator.isObservable(other)) {
2306
+ return this.$currentValue === other.$currentValue;
2307
+ }
2308
+ return this.$currentValue === other;
2309
+ };
2310
+
2311
+ /**
2312
+ * Converts the observable's current value to a boolean.
2313
+ *
2314
+ * @returns {boolean} The boolean representation of the current value
2315
+ * @example
2316
+ * const count = Observable(0);
2317
+ * count.toBool(); // false
2318
+ * count.set(5);
2319
+ * count.toBool(); // true
2320
+ */
2321
+ ObservableItem.prototype.toBool = function() {
2322
+ return !!this.$currentValue;
2323
+ };
1625
2324
 
1626
- anchorFragment.replaceContent = function(child) {
1627
- const childElement = Validator.isElement(child) ? child : ElementCreator.getChild(child);
1628
- const parent = anchorEnd.parentNode;
1629
- if(!parent) {
1630
- return;
1631
- }
1632
- if(isParentUniqueChild(parent)) {
1633
- parent.replaceChildren(anchorStart, childElement, anchorEnd);
1634
- return;
1635
- }
1636
- anchorFragment.removeChildren();
1637
- parent.insertBefore(childElement, anchorEnd);
1638
- };
2325
+ /**
2326
+ * Toggles the boolean value of the observable (false becomes true, true becomes false).
2327
+ *
2328
+ * @example
2329
+ * const isOpen = Observable(false);
2330
+ * isOpen.toggle(); // Now true
2331
+ * isOpen.toggle(); // Now false
2332
+ */
2333
+ ObservableItem.prototype.toggle = function() {
2334
+ this.set(!this.$currentValue);
2335
+ };
1639
2336
 
1640
- anchorFragment.setContent = anchorFragment.replaceContent;
2337
+ /**
2338
+ * Resets the observable to its initial value.
2339
+ * Only works if the observable was created with { reset: true } config.
2340
+ *
2341
+ * @example
2342
+ * const count = Observable(0, { reset: true });
2343
+ * count.set(10);
2344
+ * count.reset(); // Back to 0
2345
+ */
2346
+ ObservableItem.prototype.reset = function() {
2347
+ if(!this.configs?.reset) {
2348
+ return;
2349
+ }
2350
+ const resetValue = (Validator.isObject(this.$initialValue))
2351
+ ? deepClone(this.$initialValue, (observable) => {
2352
+ observable.reset();
2353
+ })
2354
+ : this.$initialValue;
2355
+ this.set(resetValue);
2356
+ };
1641
2357
 
1642
- anchorFragment.insertBefore = function(child, anchor = null) {
1643
- anchorFragment.appendChild(child, anchor);
1644
- };
2358
+ /**
2359
+ * Returns a string representation of the observable's current value.
2360
+ *
2361
+ * @returns {string} String representation of the current value
2362
+ */
2363
+ ObservableItem.prototype.toString = function() {
2364
+ return String(this.$currentValue);
2365
+ };
1645
2366
 
2367
+ /**
2368
+ * Returns the primitive value of the observable (its current value).
2369
+ * Called automatically in type coercion contexts.
2370
+ *
2371
+ * @returns {*} The current value of the observable
2372
+ */
2373
+ ObservableItem.prototype.valueOf = function() {
2374
+ return this.$currentValue;
2375
+ };
1646
2376
 
1647
- anchorFragment.endElement = function() {
1648
- return anchorEnd;
1649
- };
1650
2377
 
1651
- anchorFragment.startElement = function() {
1652
- return anchorStart;
1653
- };
1654
- anchorFragment.restore = function() {
1655
- anchorFragment.appendChild(anchorFragment);
1656
- };
1657
- anchorFragment.clear = anchorFragment.remove;
1658
- anchorFragment.detach = anchorFragment.remove;
2378
+ /**
2379
+ * Creates a derived observable that formats the current value using Intl.
2380
+ * Automatically reacts to both value changes and locale changes (Store.__nd.locale).
2381
+ *
2382
+ * @param {string | Function} type - Format type or custom formatter function
2383
+ * @param {Object} [options={}] - Options passed to the formatter
2384
+ * @returns {ObservableItem<string>}
2385
+ *
2386
+ * @example
2387
+ * // Currency
2388
+ * price.format('currency') // "15 000 FCFA"
2389
+ * price.format('currency', { currency: 'EUR' }) // "15 000,00 €"
2390
+ * price.format('currency', { notation: 'compact' }) // "15 K FCFA"
2391
+ *
2392
+ * // Number
2393
+ * count.format('number') // "15 000"
2394
+ *
2395
+ * // Percent
2396
+ * rate.format('percent') // "15,0 %"
2397
+ * rate.format('percent', { decimals: 2 }) // "15,00 %"
2398
+ *
2399
+ * // Date
2400
+ * date.format('date') // "3 mars 2026"
2401
+ * date.format('date', { dateStyle: 'full' }) // "mardi 3 mars 2026"
2402
+ * date.format('date', { format: 'DD/MM/YYYY' }) // "03/03/2026"
2403
+ * date.format('date', { format: 'DD MMM YYYY' }) // "03 mar 2026"
2404
+ * date.format('date', { format: 'DD MMMM YYYY' }) // "03 mars 2026"
2405
+ *
2406
+ * // Time
2407
+ * date.format('time') // "20:30"
2408
+ * date.format('time', { second: '2-digit' }) // "20:30:00"
2409
+ * date.format('time', { format: 'HH:mm:ss' }) // "20:30:00"
2410
+ *
2411
+ * // Datetime
2412
+ * date.format('datetime') // "3 mars 2026, 20:30"
2413
+ * date.format('datetime', { dateStyle: 'full' }) // "mardi 3 mars 2026, 20:30"
2414
+ * date.format('datetime', { format: 'DD/MM/YYYY HH:mm' }) // "03/03/2026 20:30"
2415
+ *
2416
+ * // Relative
2417
+ * date.format('relative') // "dans 11 jours"
2418
+ * date.format('relative', { unit: 'month' }) // "dans 1 mois"
2419
+ *
2420
+ * // Plural
2421
+ * count.format('plural', { singular: 'billet', plural: 'billets' }) // "3 billets"
2422
+ *
2423
+ * // Custom formatter
2424
+ * price.format(value => `${value.toLocaleString()} FCFA`)
2425
+ *
2426
+ * // Reacts to locale changes automatically
2427
+ * Store.setLocale('en-US');
2428
+ */
2429
+ ObservableItem.prototype.format = function(type, options = {}) {
2430
+ const self = this;
1659
2431
 
1660
- anchorFragment.getByIndex = function(index) {
1661
- let currentNode = anchorStart;
1662
- for(let i = 0; i <= index; i++) {
1663
- if(!currentNode.nextSibling) {
1664
- return null;
1665
- }
1666
- currentNode = currentNode.nextSibling;
1667
- }
1668
- return currentNode !== anchorStart ? currentNode : null;
1669
- };
2432
+ if (typeof type === 'function') {
2433
+ return new ObservableChecker(self, type);
2434
+ }
1670
2435
 
1671
- return anchorFragment;
1672
- }
1673
- DocumentFragment.prototype.setAttribute = () => {};
2436
+ const formatter = Formatters[type];
2437
+ const localeObservable = Store.follow('locale');
1674
2438
 
1675
- const BOOLEAN_ATTRIBUTES = new Set([
1676
- 'checked',
1677
- 'selected',
1678
- 'disabled',
1679
- 'readonly',
1680
- 'required',
1681
- 'autofocus',
1682
- 'multiple',
1683
- 'autocomplete',
1684
- 'hidden',
1685
- 'contenteditable',
1686
- 'spellcheck',
1687
- 'translate',
1688
- 'draggable',
1689
- 'async',
1690
- 'defer',
1691
- 'autoplay',
1692
- 'controls',
1693
- 'loop',
1694
- 'muted',
1695
- 'download',
1696
- 'reversed',
1697
- 'open',
1698
- 'default',
1699
- 'formnovalidate',
1700
- 'novalidate',
1701
- 'scoped',
1702
- 'itemscope',
1703
- 'allowfullscreen',
1704
- 'allowpaymentrequest',
1705
- 'playsinline'
1706
- ]);
2439
+ return Observable$1.computed(() => formatter(self.val(), localeObservable.val(), options),
2440
+ [self, localeObservable]
2441
+ );
2442
+ };
2443
+
2444
+ ObservableItem.prototype.persist = function(key, options = {}) {
2445
+ let value = $getFromStorage$1(key, this.$currentValue);
2446
+ if(options.get) {
2447
+ value = options.get(value);
2448
+ }
2449
+ this.set(value);
2450
+ const saver = $saveToStorage$1(this.$currentValue);
2451
+ this.subscribe((newValue) => {
2452
+ saver(key, options.set ? options.set(newValue) : newValue);
2453
+ });
2454
+ return this;
2455
+ };
1707
2456
 
1708
2457
  /**
1709
2458
  *
@@ -1712,17 +2461,17 @@ var NativeComponents = (function (exports) {
1712
2461
  * @returns {ObservableItem}
1713
2462
  * @constructor
1714
2463
  */
1715
- function Observable(value, configs = null) {
2464
+ function Observable$1(value, configs = null) {
1716
2465
  return new ObservableItem(value, configs);
1717
2466
  }
1718
2467
 
1719
- const $$1 = Observable;
2468
+ const $$1 = Observable$1;
1720
2469
 
1721
2470
  /**
1722
2471
  *
1723
2472
  * @param {string} propertyName
1724
2473
  */
1725
- Observable.useValueProperty = function(propertyName = 'value') {
2474
+ Observable$1.useValueProperty = function(propertyName = 'value') {
1726
2475
  Object.defineProperty(ObservableItem.prototype, propertyName, {
1727
2476
  get() {
1728
2477
  return this.$currentValue;
@@ -1740,7 +2489,7 @@ var NativeComponents = (function (exports) {
1740
2489
  * @param id
1741
2490
  * @returns {ObservableItem|null}
1742
2491
  */
1743
- Observable.getById = function(id) {
2492
+ Observable$1.getById = function(id) {
1744
2493
  const item = MemoryManager.getObservableById(parseInt(id));
1745
2494
  if(!item) {
1746
2495
  throw new NativeDocumentError('Observable.getById : No observable found with id ' + id);
@@ -1752,7 +2501,7 @@ var NativeComponents = (function (exports) {
1752
2501
  *
1753
2502
  * @param {ObservableItem} observable
1754
2503
  */
1755
- Observable.cleanup = function(observable) {
2504
+ Observable$1.cleanup = function(observable) {
1756
2505
  observable.cleanup();
1757
2506
  };
1758
2507
 
@@ -1761,7 +2510,7 @@ var NativeComponents = (function (exports) {
1761
2510
  * @param {Boolean} enable
1762
2511
  * @param {{interval:Boolean, threshold:number}} options
1763
2512
  */
1764
- Observable.autoCleanup = function(enable = false, options = {}) {
2513
+ Observable$1.autoCleanup = function(enable = false, options = {}) {
1765
2514
  if(!enable) {
1766
2515
  return;
1767
2516
  }
@@ -1953,6 +2702,88 @@ var NativeComponents = (function (exports) {
1953
2702
  return ElementCreator.getChild(child());
1954
2703
  };
1955
2704
 
2705
+ /**
2706
+ * @param {HTMLElement} el
2707
+ * @param {number} timeout
2708
+ */
2709
+ const waitForVisualEnd = (el, timeout = 1000) => {
2710
+ return new Promise((resolve) => {
2711
+ let isResolved = false;
2712
+
2713
+ const cleanupAndResolve = (e) => {
2714
+ if (e && e.target !== el) return;
2715
+ if (isResolved) return;
2716
+
2717
+ isResolved = true;
2718
+ el.removeEventListener('transitionend', cleanupAndResolve);
2719
+ el.removeEventListener('animationend', cleanupAndResolve);
2720
+ clearTimeout(timer);
2721
+ resolve();
2722
+ };
2723
+
2724
+ el.addEventListener('transitionend', cleanupAndResolve);
2725
+ el.addEventListener('animationend', cleanupAndResolve);
2726
+
2727
+ const timer = setTimeout(cleanupAndResolve, timeout);
2728
+
2729
+ const style = window.getComputedStyle(el);
2730
+ const hasTransition = style.transitionDuration !== '0s';
2731
+ const hasAnimation = style.animationDuration !== '0s';
2732
+
2733
+ if (!hasTransition && !hasAnimation) {
2734
+ cleanupAndResolve();
2735
+ }
2736
+ });
2737
+ };
2738
+
2739
+ NDElement.prototype.transitionOut = function(transitionName) {
2740
+ const exitClass = transitionName + '-exit';
2741
+ this.beforeUnmount('transition-exit', async function() {
2742
+ this.$element.classes.add(exitClass);
2743
+ await waitForVisualEnd(this.$element);
2744
+ this.$element.classes.remove(exitClass);
2745
+ });
2746
+ return this;
2747
+ };
2748
+
2749
+ NDElement.prototype.transitionIn = function(transitionName) {
2750
+ const startClass = transitionName + '-enter-from';
2751
+ const endClass = transitionName + '-enter-to';
2752
+
2753
+ this.$element.classes.add(startClass);
2754
+
2755
+ this.mounted(() => {
2756
+ requestAnimationFrame(() => {
2757
+ requestAnimationFrame(() => {
2758
+ this.$element.classes.remove(startClass);
2759
+ this.$element.classes.add(endClass);
2760
+
2761
+ waitForVisualEnd(this.$element).then(() => {
2762
+ this.$element.classes.remove(endClass);
2763
+ });
2764
+ });
2765
+ });
2766
+ });
2767
+ return this;
2768
+ };
2769
+
2770
+
2771
+ NDElement.prototype.transition = function (transitionName) {
2772
+ this.transitionIn(transitionName);
2773
+ this.transitionOut(transitionName);
2774
+ return this;
2775
+ };
2776
+
2777
+ NDElement.prototype.animate = function(animationName) {
2778
+ this.$element.classes.add(animationName);
2779
+
2780
+ waitForVisualEnd(this.$element).then(() => {
2781
+ this.$element.classes.remove(animationName);
2782
+ });
2783
+
2784
+ return this;
2785
+ };
2786
+
1956
2787
  String.prototype.handleNdAttribute = function(element, attributeName) {
1957
2788
  element.setAttribute(attributeName, this);
1958
2789
  };
@@ -2050,6 +2881,10 @@ var NativeComponents = (function (exports) {
2050
2881
  parent.appendChild(child);
2051
2882
  }
2052
2883
  },
2884
+ async safeRemove(element) {
2885
+ await element.remove();
2886
+
2887
+ },
2053
2888
  getChild(child) {
2054
2889
  if(child == null) {
2055
2890
  return null;
@@ -2390,10 +3225,10 @@ var NativeComponents = (function (exports) {
2390
3225
  /**
2391
3226
  *
2392
3227
  * @param {string} name
2393
- * @param {?Function} customWrapper
3228
+ * @param {?Function=} customWrapper
2394
3229
  * @returns {Function}
2395
3230
  */
2396
- function HtmlElementWrapper(name, customWrapper) {
3231
+ function HtmlElementWrapper(name, customWrapper = null) {
2397
3232
  return createHtmlElement.bind(null, name, customWrapper);
2398
3233
  }
2399
3234
 
@@ -2433,7 +3268,7 @@ var NativeComponents = (function (exports) {
2433
3268
  String.prototype.use = function(args) {
2434
3269
  const value = this;
2435
3270
 
2436
- return Observable.computed(() => {
3271
+ return Observable$1.computed(() => {
2437
3272
  return value.replace(/\$\{(.*?)}/g, (match, key) => {
2438
3273
  const data = args[key];
2439
3274
  if(Validator.isObservable(data)) {
@@ -2453,7 +3288,7 @@ var NativeComponents = (function (exports) {
2453
3288
  return value;
2454
3289
  }
2455
3290
  const [_, id] = value.match(/\{\{#ObItem::\(([0-9]+)\)\}\}/);
2456
- return Observable.getById(id);
3291
+ return Observable$1.getById(id);
2457
3292
  });
2458
3293
  };
2459
3294
 
@@ -2726,7 +3561,7 @@ var NativeComponents = (function (exports) {
2726
3561
  }
2727
3562
  }
2728
3563
 
2729
- const viewArray = Observable.array();
3564
+ const viewArray = Observable$1.array();
2730
3565
 
2731
3566
  const filters = Object.entries(filterCallbacks);
2732
3567
  const updateView = () => {
@@ -2812,7 +3647,7 @@ var NativeComponents = (function (exports) {
2812
3647
  * items.push(4); // Triggers update
2813
3648
  * items.subscribe((arr) => console.log(arr));
2814
3649
  */
2815
- Observable.array = function(target = [], configs = null) {
3650
+ Observable$1.array = function(target = [], configs = null) {
2816
3651
  return new ObservableArray(target, configs);
2817
3652
  };
2818
3653
 
@@ -2821,8 +3656,8 @@ var NativeComponents = (function (exports) {
2821
3656
  * @param {Function} callback
2822
3657
  * @returns {Function}
2823
3658
  */
2824
- Observable.batch = function(callback) {
2825
- const $observer = Observable(0);
3659
+ Observable$1.batch = function(callback) {
3660
+ const $observer = Observable$1(0);
2826
3661
  const batch = function() {
2827
3662
  if(Validator.isAsyncFunction(callback)) {
2828
3663
  return (callback(...arguments)).then(() => {
@@ -2836,10 +3671,71 @@ var NativeComponents = (function (exports) {
2836
3671
  return batch;
2837
3672
  };
2838
3673
 
2839
- const ObservableObjectValue = function(data) {
3674
+ const ObservableObject = function(target, configs) {
3675
+ ObservableItem.call(this, target);
3676
+ this.$observables = {};
3677
+ this.configs = configs;
3678
+
3679
+ this.$load(target);
3680
+
3681
+ for(const name in target) {
3682
+ if(!Object.hasOwn(this, name)) {
3683
+ Object.defineProperty(this, name, {
3684
+ get: () => this.$observables[name],
3685
+ set: (value) => this.$observables[name].set(value)
3686
+ });
3687
+ }
3688
+ }
3689
+
3690
+ };
3691
+
3692
+ ObservableObject.prototype = Object.create(ObservableItem.prototype);
3693
+
3694
+ Object.defineProperty(ObservableObject, '$value', {
3695
+ get() {
3696
+ return this.val();
3697
+ },
3698
+ set(value) {
3699
+ this.set(value);
3700
+ }
3701
+ });
3702
+
3703
+ ObservableObject.prototype.__$isObservableObject = true;
3704
+ ObservableObject.prototype.__isProxy__ = true;
3705
+
3706
+ ObservableObject.prototype.$load = function(initialValue) {
3707
+ const configs = this.configs;
3708
+ for(const key in initialValue) {
3709
+ const itemValue = initialValue[key];
3710
+ if(Array.isArray(itemValue)) {
3711
+ if(configs?.deep !== false) {
3712
+ const mappedItemValue = itemValue.map(item => {
3713
+ if(Validator.isJson(item)) {
3714
+ return Observable$1.json(item, configs);
3715
+ }
3716
+ if(Validator.isArray(item)) {
3717
+ return Observable$1.array(item, configs);
3718
+ }
3719
+ return Observable$1(item, configs);
3720
+ });
3721
+ this.$observables[key] = Observable$1.array(mappedItemValue, configs);
3722
+ continue;
3723
+ }
3724
+ this.$observables[key] = Observable$1.array(itemValue, configs);
3725
+ continue;
3726
+ }
3727
+ if(Validator.isObservable(itemValue) || Validator.isProxy(itemValue)) {
3728
+ this.$observables[key] = itemValue;
3729
+ continue;
3730
+ }
3731
+ this.$observables[key] = Observable$1(itemValue, configs);
3732
+ }
3733
+ };
3734
+
3735
+ ObservableObject.prototype.val = function() {
2840
3736
  const result = {};
2841
- for(const key in data) {
2842
- const dataItem = data[key];
3737
+ for(const key in this.$observables) {
3738
+ const dataItem = this.$observables[key];
2843
3739
  if(Validator.isObservable(dataItem)) {
2844
3740
  let value = dataItem.val();
2845
3741
  if(Array.isArray(value)) {
@@ -2862,9 +3758,10 @@ var NativeComponents = (function (exports) {
2862
3758
  }
2863
3759
  return result;
2864
3760
  };
3761
+ ObservableObject.prototype.$val = ObservableObject.prototype.val;
2865
3762
 
2866
- const ObservableGet = function(target, property) {
2867
- const item = target[property];
3763
+ ObservableObject.prototype.get = function(property) {
3764
+ const item = this.$observables[property];
2868
3765
  if(Validator.isObservable(item)) {
2869
3766
  return item.val();
2870
3767
  }
@@ -2873,100 +3770,87 @@ var NativeComponents = (function (exports) {
2873
3770
  }
2874
3771
  return item;
2875
3772
  };
3773
+ ObservableObject.prototype.$get = ObservableObject.prototype.get;
2876
3774
 
2877
- /**
2878
- * Creates an observable proxy for an object where each property becomes an observable.
2879
- * Properties can be accessed directly or via getter methods.
2880
- *
2881
- * @param {Object} initialValue - Initial object value
2882
- * @param {Object|null} [configs=null] - Configuration options
2883
- * // @param {boolean} [configs.propagation=true] - Whether changes propagate to parent
2884
- * @param {boolean} [configs.deep=false] - Whether to make nested objects observable
2885
- * @param {boolean} [configs.reset=false] - Whether to enable reset() method
2886
- * @returns {ObservableProxy} A proxy where each property is an observable
2887
- * @example
2888
- * const user = Observable.init({
2889
- * name: 'John',
2890
- * age: 25,
2891
- * address: { city: 'NYC' }
2892
- * }, { deep: true });
2893
- *
2894
- * user.name.val(); // 'John'
2895
- * user.name.set('Jane');
2896
- * user.name = 'Jane X'
2897
- * user.age.subscribe(val => console.log('Age:', val));
2898
- */
2899
- Observable.init = function(initialValue, configs = null) {
2900
- const data = {};
2901
- for(const key in initialValue) {
2902
- const itemValue = initialValue[key];
2903
- if(Array.isArray(itemValue)) {
2904
- if(configs?.deep !== false) {
2905
- const mappedItemValue = itemValue.map(item => {
2906
- if(Validator.isJson(item)) {
2907
- return Observable.json(item, configs);
2908
- }
2909
- if(Validator.isArray(item)) {
2910
- return Observable.array(item, configs);
3775
+ ObservableObject.prototype.set = function(newData) {
3776
+ const data = Validator.isProxy(newData) ? newData.$value : newData;
3777
+ const configs = this.configs;
3778
+
3779
+ for(const key in data) {
3780
+ const targetItem = this.$observables[key];
3781
+ const newValueOrigin = newData[key];
3782
+ const newValue = data[key];
3783
+
3784
+ if(Validator.isObservable(targetItem)) {
3785
+ if(!Validator.isArray(newValue)) {
3786
+ targetItem.set(newValue);
3787
+ continue;
3788
+ }
3789
+ const firstElementFromOriginalValue = newValueOrigin.at(0);
3790
+ if(Validator.isObservable(firstElementFromOriginalValue) || Validator.isProxy(firstElementFromOriginalValue)) {
3791
+ const newValues = newValue.map(item => {
3792
+ if(Validator.isProxy(firstElementFromOriginalValue)) {
3793
+ return Observable$1.init(item, configs);
2911
3794
  }
2912
- return Observable(item, configs);
3795
+ return Observable$1(item, configs);
2913
3796
  });
2914
- data[key] = Observable.array(mappedItemValue, configs);
3797
+ targetItem.set(newValues);
2915
3798
  continue;
2916
3799
  }
2917
- data[key] = Observable.array(itemValue, configs);
3800
+ targetItem.set([...newValue]);
2918
3801
  continue;
2919
3802
  }
2920
- if(Validator.isObservable(itemValue) || Validator.isProxy(itemValue)) {
2921
- data[key] = itemValue;
3803
+ if(Validator.isProxy(targetItem)) {
3804
+ targetItem.update(newValue);
2922
3805
  continue;
2923
3806
  }
2924
- data[key] = Observable(itemValue, configs);
3807
+ this[key] = newValue;
2925
3808
  }
3809
+ };
3810
+ ObservableObject.prototype.$set = ObservableObject.prototype.set;
3811
+ ObservableObject.prototype.$updateWith = ObservableObject.prototype.set;
2926
3812
 
2927
- const $reset = () => {
2928
- for(const key in data) {
2929
- const item = data[key];
2930
- item.reset();
2931
- }
2932
- };
3813
+ ObservableObject.prototype.observables = function() {
3814
+ return Object.values(this.$observables);
3815
+ };
3816
+ ObservableObject.prototype.$observables = ObservableObject.prototype.observables;
2933
3817
 
2934
- const $val = () => ObservableObjectValue(data);
3818
+ ObservableObject.prototype.keys = function() {
3819
+ return Object.keys(this.$observables);
3820
+ };
3821
+ ObservableObject.prototype.$keys = ObservableObject.prototype.keys;
3822
+ ObservableObject.prototype.clone = function() {
3823
+ return Observable$1.init(this.val(), this.configs);
3824
+ };
3825
+ ObservableObject.prototype.$clone = ObservableObject.prototype.clone;
3826
+ ObservableObject.prototype.reset = function() {
3827
+ for(const key in this.$observables) {
3828
+ this.$observables[key].reset();
3829
+ }
3830
+ };
3831
+ ObservableObject.prototype.originalSubscribe = ObservableObject.prototype.subscribe;
3832
+ ObservableObject.prototype.subscribe = function(callback) {
3833
+ const observables = this.observables();
3834
+ const updatedValue = nextTick(() => {
3835
+ this.$currentValue = this.val();
3836
+ this.trigger();
3837
+ });
2935
3838
 
2936
- const $clone = () => Observable.init($val(), configs);
3839
+ this.originalSubscribe(callback);
2937
3840
 
2938
- const $updateWith = (values) => {
2939
- Observable.update(proxy, values);
2940
- };
3841
+ for(let i = 0, length = observables.length; i < length; i++) {
3842
+ const observable = observables[i];
3843
+ observable.subscribe(updatedValue);
3844
+ }
3845
+ };
3846
+ ObservableObject.prototype.configs = function() {
3847
+ return this.configs;
3848
+ };
2941
3849
 
2942
- const $get = (key) => ObservableGet(data, key);
2943
-
2944
- const proxy = new Proxy(data, {
2945
- get(target, property) {
2946
- if(property === '__isProxy__') { return true; }
2947
- if(property === '$value') { return $val() }
2948
- if(property === 'get' || property === '$get') { return $get; }
2949
- if(property === 'val' || property === '$val') { return $val; }
2950
- if(property === 'set' || property === '$set' || property === '$updateWith') { return $updateWith; }
2951
- if(property === 'observables' || property === '$observables') { return Object.values(target); }
2952
- if(property === 'keys'|| property === '$keys') { return Object.keys(initialValue); }
2953
- if(property === 'clone' || property === '$clone') { return $clone; }
2954
- if(property === 'reset') { return $reset; }
2955
- if(property === 'configs') { return configs; }
2956
- return target[property];
2957
- },
2958
- set(target, prop, newValue) {
2959
- if(target[prop] !== undefined) {
2960
- Validator.isObservable(newValue)
2961
- ? target[prop].set(newValue.val())
2962
- : target[prop].set(newValue);
2963
- return true;
2964
- }
2965
- return true;
2966
- }
2967
- });
3850
+ ObservableObject.prototype.update = ObservableObject.prototype.set;
2968
3851
 
2969
- return proxy;
3852
+ Observable$1.init = function(initialValue, configs = null) {
3853
+ return new ObservableObject(initialValue, configs)
2970
3854
  };
2971
3855
 
2972
3856
  /**
@@ -2974,8 +3858,8 @@ var NativeComponents = (function (exports) {
2974
3858
  * @param {any[]} data
2975
3859
  * @return Proxy[]
2976
3860
  */
2977
- Observable.arrayOfObject = function(data) {
2978
- return data.map(item => Observable.object(item));
3861
+ Observable$1.arrayOfObject = function(data) {
3862
+ return data.map(item => Observable$1.object(item));
2979
3863
  };
2980
3864
 
2981
3865
  /**
@@ -2983,7 +3867,7 @@ var NativeComponents = (function (exports) {
2983
3867
  * @param {ObservableItem|Object<ObservableItem>} data
2984
3868
  * @returns {{}|*|null}
2985
3869
  */
2986
- Observable.value = function(data) {
3870
+ Observable$1.value = function(data) {
2987
3871
  if(Validator.isObservable(data)) {
2988
3872
  return data.val();
2989
3873
  }
@@ -2994,52 +3878,15 @@ var NativeComponents = (function (exports) {
2994
3878
  const result = [];
2995
3879
  for(let i = 0, length = data.length; i < length; i++) {
2996
3880
  const item = data[i];
2997
- result.push(Observable.value(item));
3881
+ result.push(Observable$1.value(item));
2998
3882
  }
2999
3883
  return result;
3000
3884
  }
3001
3885
  return data;
3002
3886
  };
3003
3887
 
3004
-
3005
- Observable.update = function($target, newData) {
3006
- const data = Validator.isProxy(newData) ? newData.$value : newData;
3007
- const configs = $target.configs;
3008
-
3009
- for(const key in data) {
3010
- const targetItem = $target[key];
3011
- const newValueOrigin = newData[key];
3012
- const newValue = data[key];
3013
-
3014
- if(Validator.isObservable(targetItem)) {
3015
- if(Validator.isArray(newValue)) {
3016
- const firstElementFromOriginalValue = newValueOrigin.at(0);
3017
- if(Validator.isObservable(firstElementFromOriginalValue) || Validator.isProxy(firstElementFromOriginalValue)) {
3018
- const newValues = newValue.map(item => {
3019
- if(Validator.isProxy(firstElementFromOriginalValue)) {
3020
- return Observable.init(item, configs);
3021
- }
3022
- return Observable(item, configs);
3023
- });
3024
- targetItem.set(newValues);
3025
- continue;
3026
- }
3027
- targetItem.set([...newValue]);
3028
- continue;
3029
- }
3030
- targetItem.set(newValue);
3031
- continue;
3032
- }
3033
- if(Validator.isProxy(targetItem)) {
3034
- Observable.update(targetItem, newValue);
3035
- continue;
3036
- }
3037
- $target[key] = newValue;
3038
- }
3039
- };
3040
-
3041
- Observable.object = Observable.init;
3042
- Observable.json = Observable.init;
3888
+ Observable$1.object = Observable$1.init;
3889
+ Observable$1.json = Observable$1.init;
3043
3890
 
3044
3891
  /**
3045
3892
  * Creates a computed observable that automatically updates when its dependencies change.
@@ -3060,7 +3907,7 @@ var NativeComponents = (function (exports) {
3060
3907
  * const batch = Observable.batch(() => { ... });
3061
3908
  * const computed = Observable.computed(() => { ... }, batch);
3062
3909
  */
3063
- Observable.computed = function(callback, dependencies = []) {
3910
+ Observable$1.computed = function(callback, dependencies = []) {
3064
3911
  const initialValue = callback();
3065
3912
  const observable = new ObservableItem(initialValue);
3066
3913
  const updatedValue = nextTick(() => observable.set(callback()));
@@ -3086,40 +3933,196 @@ var NativeComponents = (function (exports) {
3086
3933
  return observable;
3087
3934
  };
3088
3935
 
3936
+ /**
3937
+ * Creates a `<div>` element.
3938
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLDivElement}
3939
+ */
3089
3940
  HtmlElementWrapper('div');
3941
+
3942
+ /**
3943
+ * Creates a `<span>` element.
3944
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLSpanElement}
3945
+ */
3090
3946
  HtmlElementWrapper('span');
3947
+
3948
+ /**
3949
+ * Creates a `<label>` element.
3950
+ * @type {function(LabelAttributes=, NdChild|NdChild[]=): HTMLLabelElement}
3951
+ */
3091
3952
  HtmlElementWrapper('label');
3953
+
3954
+ /**
3955
+ * Creates a `<p>` element.
3956
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLParagraphElement}
3957
+ */
3092
3958
  HtmlElementWrapper('p');
3959
+
3960
+ /**
3961
+ * Creates a `<strong>` element.
3962
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLElement}
3963
+ */
3093
3964
  HtmlElementWrapper('strong');
3965
+
3966
+ /**
3967
+ * Creates a `<h1>` element.
3968
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLHeadingElement}
3969
+ */
3094
3970
  HtmlElementWrapper('h1');
3971
+
3972
+ /**
3973
+ * Creates a `<h2>` element.
3974
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLHeadingElement}
3975
+ */
3095
3976
  HtmlElementWrapper('h2');
3977
+
3978
+ /**
3979
+ * Creates a `<h3>` element.
3980
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLHeadingElement}
3981
+ */
3096
3982
  HtmlElementWrapper('h3');
3983
+
3984
+ /**
3985
+ * Creates a `<h4>` element.
3986
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLHeadingElement}
3987
+ */
3097
3988
  HtmlElementWrapper('h4');
3989
+
3990
+ /**
3991
+ * Creates a `<h5>` element.
3992
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLHeadingElement}
3993
+ */
3098
3994
  HtmlElementWrapper('h5');
3995
+
3996
+ /**
3997
+ * Creates a `<h6>` element.
3998
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLHeadingElement}
3999
+ */
3099
4000
  HtmlElementWrapper('h6');
3100
4001
 
4002
+ /**
4003
+ * Creates a `<br>` element.
4004
+ * @type {function(GlobalAttributes=): HTMLBRElement}
4005
+ */
3101
4006
  HtmlElementWrapper('br');
3102
4007
 
4008
+ /**
4009
+ * Creates an `<a>` element.
4010
+ * @type {function(AnchorAttributes=, NdChild|NdChild[]=): HTMLAnchorElement}
4011
+ */
3103
4012
  HtmlElementWrapper('a');
4013
+
4014
+ /**
4015
+ * Creates a `<pre>` element.
4016
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLPreElement}
4017
+ */
3104
4018
  HtmlElementWrapper('pre');
4019
+
4020
+ /**
4021
+ * Creates a `<code>` element.
4022
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLElement}
4023
+ */
3105
4024
  HtmlElementWrapper('code');
4025
+
4026
+ /**
4027
+ * Creates a `<blockquote>` element.
4028
+ * @type {function(GlobalAttributes & { cite?: string }=, NdChild|NdChild[]=): HTMLQuoteElement}
4029
+ */
3106
4030
  HtmlElementWrapper('blockquote');
4031
+
4032
+ /**
4033
+ * Creates an `<hr>` element.
4034
+ * @type {function(GlobalAttributes=): HTMLHRElement}
4035
+ */
3107
4036
  HtmlElementWrapper('hr');
4037
+
4038
+ /**
4039
+ * Creates an `<em>` element.
4040
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLElement}
4041
+ */
3108
4042
  HtmlElementWrapper('em');
4043
+
4044
+ /**
4045
+ * Creates a `<small>` element.
4046
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLElement}
4047
+ */
3109
4048
  HtmlElementWrapper('small');
4049
+
4050
+ /**
4051
+ * Creates a `<mark>` element.
4052
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLElement}
4053
+ */
3110
4054
  HtmlElementWrapper('mark');
4055
+
4056
+ /**
4057
+ * Creates a `<del>` element.
4058
+ * @type {function(ModAttributes=, NdChild|NdChild[]=): HTMLModElement}
4059
+ */
3111
4060
  HtmlElementWrapper('del');
4061
+
4062
+ /**
4063
+ * Creates an `<ins>` element.
4064
+ * @type {function(ModAttributes=, NdChild|NdChild[]=): HTMLModElement}
4065
+ */
3112
4066
  HtmlElementWrapper('ins');
4067
+
4068
+ /**
4069
+ * Creates a `<sub>` element.
4070
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLElement}
4071
+ */
3113
4072
  HtmlElementWrapper('sub');
4073
+
4074
+ /**
4075
+ * Creates a `<sup>` element.
4076
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLElement}
4077
+ */
3114
4078
  HtmlElementWrapper('sup');
4079
+
4080
+ /**
4081
+ * Creates an `<abbr>` element.
4082
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLElement}
4083
+ */
3115
4084
  HtmlElementWrapper('abbr');
4085
+
4086
+ /**
4087
+ * Creates a `<cite>` element.
4088
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLElement}
4089
+ */
3116
4090
  HtmlElementWrapper('cite');
4091
+
4092
+ /**
4093
+ * Creates a `<q>` element.
4094
+ * @type {function(GlobalAttributes & { cite?: string }=, NdChild|NdChild[]=): HTMLQuoteElement}
4095
+ */
3117
4096
  HtmlElementWrapper('q');
3118
4097
 
4098
+ /**
4099
+ * Creates a `<dl>` element.
4100
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLDListElement}
4101
+ */
3119
4102
  HtmlElementWrapper('dl');
4103
+
4104
+ /**
4105
+ * Creates a `<dt>` element.
4106
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLElement}
4107
+ */
3120
4108
  HtmlElementWrapper('dt');
4109
+
4110
+ /**
4111
+ * Creates a `<dd>` element.
4112
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLElement}
4113
+ */
3121
4114
  HtmlElementWrapper('dd');
3122
4115
 
4116
+ /**
4117
+ * Creates a `<form>` element.
4118
+ * Extended with fluent methods: `.submit()`, `.post()`, `.get()`, `.multipartFormData()`.
4119
+ * @type {function(FormAttributes=, NdChild|NdChild[]=): HTMLFormElement & {
4120
+ * submit: (actionOrFn: string | ((e: SubmitEvent) => void)) => HTMLFormElement,
4121
+ * post: (action: string) => HTMLFormElement,
4122
+ * get: (action: string) => HTMLFormElement,
4123
+ * multipartFormData: () => HTMLFormElement,
4124
+ * }}
4125
+ */
3123
4126
  HtmlElementWrapper('form', function(el) {
3124
4127
 
3125
4128
  el.submit = function(action) {
@@ -3149,70 +4152,317 @@ var NativeComponents = (function (exports) {
3149
4152
  return el;
3150
4153
  });
3151
4154
 
4155
+ /**
4156
+ * Creates an `<input>` element.
4157
+ * @type {function(InputAttributes=): HTMLInputElement}
4158
+ */
3152
4159
  HtmlElementWrapper('input');
3153
4160
 
4161
+ /**
4162
+ * Creates a `<textarea>` element.
4163
+ * @type {function(TextAreaAttributes=, NdChild|NdChild[]=): HTMLTextAreaElement}
4164
+ */
3154
4165
  HtmlElementWrapper('textarea');
3155
4166
 
4167
+ /**
4168
+ * Creates a `<select>` element.
4169
+ * @type {function(SelectAttributes=, NdChild|NdChild[]=): HTMLSelectElement}
4170
+ */
3156
4171
  HtmlElementWrapper('select');
3157
- HtmlElementWrapper('fieldset', );
4172
+
4173
+ /**
4174
+ * Creates a `<fieldset>` element.
4175
+ * @type {function(GlobalAttributes & { disabled?: Observable<boolean>|boolean }=, NdChild|NdChild[]=): HTMLFieldSetElement}
4176
+ */
4177
+ HtmlElementWrapper('fieldset');
4178
+
4179
+ /**
4180
+ * Creates an `<option>` element.
4181
+ * @type {function(OptionAttributes=, NdChild|NdChild[]=): HTMLOptionElement}
4182
+ */
3158
4183
  HtmlElementWrapper('option');
4184
+
4185
+ /**
4186
+ * Creates a `<legend>` element.
4187
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLLegendElement}
4188
+ */
3159
4189
  HtmlElementWrapper('legend');
4190
+
4191
+ /**
4192
+ * Creates a `<datalist>` element.
4193
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLDataListElement}
4194
+ */
3160
4195
  HtmlElementWrapper('datalist');
4196
+
4197
+ /**
4198
+ * Creates an `<output>` element.
4199
+ * @type {function(OutputAttributes=, NdChild|NdChild[]=): HTMLOutputElement}
4200
+ */
3161
4201
  HtmlElementWrapper('output');
4202
+
4203
+ /**
4204
+ * Creates a `<progress>` element.
4205
+ * @type {function(ProgressAttributes=, NdChild|NdChild[]=): HTMLProgressElement}
4206
+ */
3162
4207
  HtmlElementWrapper('progress');
3163
- HtmlElementWrapper('meter');
3164
4208
 
4209
+ /**
4210
+ * Creates a `<meter>` element.
4211
+ * @type {function(MeterAttributes=, NdChild|NdChild[]=): HTMLMeterElement}
4212
+ */
4213
+ HtmlElementWrapper('meter');
3165
4214
 
4215
+ /**
4216
+ * Creates a `<button>` element.
4217
+ * @type {function(ButtonAttributes=, NdChild|NdChild[]=): HTMLButtonElement}
4218
+ */
3166
4219
  const Button$1 = HtmlElementWrapper('button');
3167
4220
 
4221
+ /**
4222
+ * Creates a `<main>` element.
4223
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLElement}
4224
+ */
3168
4225
  HtmlElementWrapper('main');
4226
+
4227
+ /**
4228
+ * Creates a `<section>` element.
4229
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLElement}
4230
+ */
3169
4231
  HtmlElementWrapper('section');
4232
+
4233
+ /**
4234
+ * Creates an `<article>` element.
4235
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLElement}
4236
+ */
3170
4237
  HtmlElementWrapper('article');
4238
+
4239
+ /**
4240
+ * Creates an `<aside>` element.
4241
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLElement}
4242
+ */
3171
4243
  HtmlElementWrapper('aside');
4244
+
4245
+ /**
4246
+ * Creates a `<nav>` element.
4247
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLElement}
4248
+ */
3172
4249
  HtmlElementWrapper('nav');
4250
+
4251
+ /**
4252
+ * Creates a `<figure>` element.
4253
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLElement}
4254
+ */
3173
4255
  HtmlElementWrapper('figure');
4256
+
4257
+ /**
4258
+ * Creates a `<figcaption>` element.
4259
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLElement}
4260
+ */
3174
4261
  HtmlElementWrapper('figcaption');
3175
4262
 
4263
+ /**
4264
+ * Creates a `<header>` element.
4265
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLElement}
4266
+ */
3176
4267
  HtmlElementWrapper('header');
4268
+
4269
+ /**
4270
+ * Creates a `<footer>` element.
4271
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLElement}
4272
+ */
3177
4273
  HtmlElementWrapper('footer');
3178
4274
 
4275
+ /**
4276
+ * Creates an `<img>` element.
4277
+ * @type {function(ImgAttributes=): HTMLImageElement}
4278
+ */
3179
4279
  HtmlElementWrapper('img');
3180
4280
 
4281
+ /**
4282
+ * Creates a `<details>` element.
4283
+ * @type {function(DetailsAttributes=, NdChild|NdChild[]=): HTMLDetailsElement}
4284
+ */
3181
4285
  HtmlElementWrapper('details');
4286
+
4287
+ /**
4288
+ * Creates a `<summary>` element.
4289
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLElement}
4290
+ */
3182
4291
  HtmlElementWrapper('summary');
4292
+
4293
+ /**
4294
+ * Creates a `<dialog>` element.
4295
+ * @type {function(DialogAttributes=, NdChild|NdChild[]=): HTMLDialogElement}
4296
+ */
3183
4297
  HtmlElementWrapper('dialog');
4298
+
4299
+ /**
4300
+ * Creates a `<menu>` element.
4301
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLMenuElement}
4302
+ */
3184
4303
  HtmlElementWrapper('menu');
3185
4304
 
4305
+ /**
4306
+ * Creates an `<ol>` element.
4307
+ * @type {function(OlAttributes=, NdChild|NdChild[]=): HTMLOListElement}
4308
+ */
3186
4309
  HtmlElementWrapper('ol');
4310
+
4311
+ /**
4312
+ * Creates a `<ul>` element.
4313
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLUListElement}
4314
+ */
3187
4315
  HtmlElementWrapper('ul');
4316
+
4317
+ /**
4318
+ * Creates a `<li>` element.
4319
+ * @type {function(GlobalAttributes & { value?: number }=, NdChild|NdChild[]=): HTMLLIElement}
4320
+ */
3188
4321
  HtmlElementWrapper('li');
3189
4322
 
4323
+ /**
4324
+ * Creates an `<audio>` element.
4325
+ * @type {function(AudioAttributes=, NdChild|NdChild[]=): HTMLAudioElement}
4326
+ */
3190
4327
  HtmlElementWrapper('audio');
4328
+
4329
+ /**
4330
+ * Creates a `<video>` element.
4331
+ * @type {function(VideoAttributes=, NdChild|NdChild[]=): HTMLVideoElement}
4332
+ */
3191
4333
  HtmlElementWrapper('video');
4334
+
4335
+ /**
4336
+ * Creates a `<source>` element.
4337
+ * @type {function(SourceAttributes=): HTMLSourceElement}
4338
+ */
3192
4339
  HtmlElementWrapper('source');
4340
+
4341
+ /**
4342
+ * Creates a `<track>` element.
4343
+ * @type {function(TrackAttributes=): HTMLTrackElement}
4344
+ */
3193
4345
  HtmlElementWrapper('track');
4346
+
4347
+ /**
4348
+ * Creates a `<canvas>` element.
4349
+ * @type {function(CanvasAttributes=, NdChild|NdChild[]=): HTMLCanvasElement}
4350
+ */
3194
4351
  HtmlElementWrapper('canvas');
4352
+
4353
+ /**
4354
+ * Creates an `<svg>` element.
4355
+ * @type {function(SvgAttributes=, NdChild|NdChild[]=): SVGSVGElement}
4356
+ */
3195
4357
  HtmlElementWrapper('svg');
3196
4358
 
4359
+ /**
4360
+ * Creates a `<time>` element.
4361
+ * @type {function(TimeAttributes=, NdChild|NdChild[]=): HTMLTimeElement}
4362
+ */
3197
4363
  HtmlElementWrapper('time');
4364
+
4365
+ /**
4366
+ * Creates a `<data>` element.
4367
+ * @type {function(GlobalAttributes & { value?: Observable<string>|string }=, NdChild|NdChild[]=): HTMLDataElement}
4368
+ */
3198
4369
  HtmlElementWrapper('data');
4370
+
4371
+ /**
4372
+ * Creates an `<address>` element.
4373
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLElement}
4374
+ */
3199
4375
  HtmlElementWrapper('address');
4376
+
4377
+ /**
4378
+ * Creates a `<kbd>` element.
4379
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLElement}
4380
+ */
3200
4381
  HtmlElementWrapper('kbd');
4382
+
4383
+ /**
4384
+ * Creates a `<samp>` element.
4385
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLElement}
4386
+ */
3201
4387
  HtmlElementWrapper('samp');
4388
+
4389
+ /**
4390
+ * Creates a `<var>` element.
4391
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLElement}
4392
+ */
3202
4393
  HtmlElementWrapper('var');
4394
+
4395
+ /**
4396
+ * Creates a `<wbr>` element.
4397
+ * @type {function(GlobalAttributes=): HTMLElement}
4398
+ */
3203
4399
  HtmlElementWrapper('wbr');
3204
4400
 
4401
+ /**
4402
+ * Creates a `<caption>` element.
4403
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLTableCaptionElement}
4404
+ */
3205
4405
  HtmlElementWrapper('caption');
4406
+
4407
+ /**
4408
+ * Creates a `<table>` element.
4409
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLTableElement}
4410
+ */
3206
4411
  const Table = HtmlElementWrapper('table');
4412
+
4413
+ /**
4414
+ * Creates a `<thead>` element.
4415
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLTableSectionElement}
4416
+ */
3207
4417
  HtmlElementWrapper('thead');
4418
+
4419
+ /**
4420
+ * Creates a `<tfoot>` element.
4421
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLTableSectionElement}
4422
+ */
3208
4423
  HtmlElementWrapper('tfoot');
4424
+
4425
+ /**
4426
+ * Creates a `<tbody>` element.
4427
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLTableSectionElement}
4428
+ */
3209
4429
  HtmlElementWrapper('tbody');
4430
+
4431
+ /**
4432
+ * Creates a `<tr>` element.
4433
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLTableRowElement}
4434
+ */
3210
4435
  const Tr = HtmlElementWrapper('tr');
4436
+
4437
+ /**
4438
+ * Alias for {@link Tr}.
4439
+ * @type {typeof Tr}
4440
+ */
3211
4441
  const TRow = Tr;
4442
+
4443
+ /**
4444
+ * Creates a `<th>` element.
4445
+ * @type {function(ThAttributes=, NdChild|NdChild[]=): HTMLTableCellElement}
4446
+ */
3212
4447
  const Th = HtmlElementWrapper('th');
4448
+
4449
+ /**
4450
+ * Alias for {@link Th}.
4451
+ * @type {typeof Th}
4452
+ */
3213
4453
  const THeadCell$1 = Th;
4454
+
4455
+ /**
4456
+ * Creates a `<td>` element.
4457
+ * @type {function(TdAttributes=, NdChild|NdChild[]=): HTMLTableCellElement}
4458
+ */
3214
4459
  HtmlElementWrapper('td');
3215
4460
 
4461
+ /**
4462
+ * Creates an empty `DocumentFragment` wrapper.
4463
+ * Useful for grouping elements without adding a DOM node.
4464
+ * @type {function(GlobalAttributes=, NdChild|NdChild[]=): DocumentFragment}
4465
+ */
3216
4466
  HtmlElementWrapper('');
3217
4467
 
3218
4468
  /**
@@ -6326,10 +7576,10 @@ var NativeComponents = (function (exports) {
6326
7576
  this.$element = null;
6327
7577
  this.$configs = configs;
6328
7578
  this.$fields = new Map();
6329
- this.$submitting = Observable(false);
6330
- this.$errors = Observable(null);
6331
- this.$isDirty = Observable(false);
6332
- this.$isValid = Observable(false);
7579
+ this.$submitting = Observable$1(false);
7580
+ this.$errors = Observable$1(null);
7581
+ this.$isDirty = Observable$1(false);
7582
+ this.$isValid = Observable$1(false);
6333
7583
  }
6334
7584
 
6335
7585
  FormControl.defaultLayoutTemplate = null;
@@ -7650,7 +8900,7 @@ var NativeComponents = (function (exports) {
7650
8900
  defaultItem: null,
7651
8901
  value: (config?.data && Validator.isObservable(config.data))
7652
8902
  ? config.data
7653
- : Observable.array(config?.data || []),
8903
+ : Observable$1.array(config?.data || []),
7654
8904
  rules: null,
7655
8905
  layout: null,
7656
8906
  template: null,
@@ -7747,8 +8997,8 @@ var NativeComponents = (function (exports) {
7747
8997
  defaultItemData = Validator.isObservable(defaultItemData)
7748
8998
  ? defaultItemData
7749
8999
  : Validator.isObject(defaultItemData)
7750
- ? Observable.init(defaultItemData)
7751
- : Observable(defaultItemData);
9000
+ ? Observable$1.init(defaultItemData)
9001
+ : Observable$1(defaultItemData);
7752
9002
 
7753
9003
  this.$items = this.$items || new WeakMap();
7754
9004
 
@@ -10230,9 +11480,9 @@ var NativeComponents = (function (exports) {
10230
11480
  ...configs
10231
11481
  });
10232
11482
 
10233
- this.$currentPage = Observable(1);
10234
- this.$selectedRows = Observable.array();
10235
- this.$expandedRows = Observable.array();
11483
+ this.$currentPage = Observable$1(1);
11484
+ this.$selectedRows = Observable$1.array();
11485
+ this.$expandedRows = Observable$1.array();
10236
11486
  }
10237
11487
 
10238
11488
  DataTable.defaultToolbarTemplate = null;