lexgui 0.6.4 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- // This is a generated file. Do not edit.
1
+ // This is a generated file. Do not edit.
2
2
  // Lexgui.js @jxarco
3
3
 
4
4
  /**
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  const LX = {
10
- version: "0.6.4",
10
+ version: "0.6.6",
11
11
  ready: false,
12
12
  components: [], // Specific pre-build components
13
13
  signals: {}, // Events and triggers
@@ -590,549 +590,408 @@ function setCommandbarState( value, resetEntries = true )
590
590
 
591
591
  LX.setCommandbarState = setCommandbarState;
592
592
 
593
- /**
594
- * @method message
595
- * @param {String} text
596
- * @param {String} title (Optional)
597
- * @param {Object} options
598
- * id: Id of the message dialog
599
- * position: Dialog position in screen [screen centered]
600
- * draggable: Dialog can be dragged [false]
601
- */
602
-
603
- function message( text, title, options = {} )
604
- {
605
- if( !text )
606
- {
607
- throw( "No message to show" );
608
- }
593
+ /*
594
+ * Events and Signals
595
+ */
609
596
 
610
- options.modal = true;
597
+ class IEvent {
611
598
 
612
- return new LX.Dialog( title, p => {
613
- p.addTextArea( null, text, null, { disabled: true, fitHeight: true } );
614
- }, options );
599
+ constructor( name, value, domEvent ) {
600
+ this.name = name;
601
+ this.value = value;
602
+ this.domEvent = domEvent;
603
+ }
615
604
  }
605
+ LX.IEvent = IEvent;
616
606
 
617
- LX.message = message;
607
+ class TreeEvent {
618
608
 
619
- /**
620
- * @method popup
621
- * @param {String} text
622
- * @param {String} title (Optional)
623
- * @param {Object} options
624
- * id: Id of the message dialog
625
- * timeout (Number): Delay time before it closes automatically (ms). Default: [3000]
626
- * position (Array): [x,y] Dialog position in screen. Default: [screen centered]
627
- * size (Array): [width, height]
628
- */
609
+ static NONE = 0;
610
+ static NODE_SELECTED = 1;
611
+ static NODE_DELETED = 2;
612
+ static NODE_DBLCLICKED = 3;
613
+ static NODE_CONTEXTMENU = 4;
614
+ static NODE_DRAGGED = 5;
615
+ static NODE_RENAMED = 6;
616
+ static NODE_VISIBILITY = 7;
617
+ static NODE_CARETCHANGED = 8;
629
618
 
630
- function popup( text, title, options = {} )
631
- {
632
- if( !text )
633
- {
634
- throw("No message to show");
619
+ constructor( type, node, value ) {
620
+ this.type = type || TreeEvent.NONE;
621
+ this.node = node;
622
+ this.value = value;
623
+ this.multiple = false; // Multiple selection
624
+ this.panel = null;
635
625
  }
636
626
 
637
- options.size = options.size ?? [ "max-content", "auto" ];
638
- options.class = "lexpopup";
639
-
640
- const time = options.timeout || 3000;
641
- const dialog = new LX.Dialog( title, p => {
642
- p.addTextArea( null, text, null, { disabled: true, fitHeight: true } );
643
- }, options );
644
-
645
- setTimeout( () => {
646
- dialog.close();
647
- }, Math.max( time, 150 ) );
648
-
649
- return dialog;
627
+ string() {
628
+ switch( this.type )
629
+ {
630
+ case TreeEvent.NONE: return "tree_event_none";
631
+ case TreeEvent.NODE_SELECTED: return "tree_event_selected";
632
+ case TreeEvent.NODE_DELETED: return "tree_event_deleted";
633
+ case TreeEvent.NODE_DBLCLICKED: return "tree_event_dblclick";
634
+ case TreeEvent.NODE_CONTEXTMENU: return "tree_event_contextmenu";
635
+ case TreeEvent.NODE_DRAGGED: return "tree_event_dragged";
636
+ case TreeEvent.NODE_RENAMED: return "tree_event_renamed";
637
+ case TreeEvent.NODE_VISIBILITY: return "tree_event_visibility";
638
+ case TreeEvent.NODE_CARETCHANGED: return "tree_event_caretchanged";
639
+ }
640
+ }
650
641
  }
642
+ LX.TreeEvent = TreeEvent;
651
643
 
652
- LX.popup = popup;
653
-
654
- /**
655
- * @method prompt
656
- * @param {String} text
657
- * @param {String} title (Optional)
658
- * @param {Object} options
659
- * id: Id of the prompt dialog
660
- * position: Dialog position in screen [screen centered]
661
- * draggable: Dialog can be dragged [false]
662
- * input: If false, no text input appears
663
- * accept: Accept text
664
- * required: Input has to be filled [true]. Default: false
665
- */
666
-
667
- function prompt( text, title, callback, options = {} )
644
+ function emit( signalName, value, options = {} )
668
645
  {
669
- options.modal = true;
670
- options.className = "prompt";
671
-
672
- let value = "";
646
+ const data = LX.signals[ signalName ];
673
647
 
674
- const dialog = new LX.Dialog( title, p => {
648
+ if( !data )
649
+ {
650
+ return;
651
+ }
675
652
 
676
- p.addTextArea( null, text, null, { disabled: true, fitHeight: true } );
653
+ const target = options.target;
677
654
 
678
- if( options.input ?? true )
655
+ if( target )
656
+ {
657
+ if( target[ signalName ])
679
658
  {
680
- p.addText( null, options.input || value, v => value = v, { placeholder: "..." } );
659
+ target[ signalName ].call( target, value );
681
660
  }
682
661
 
683
- p.sameLine( 2 );
662
+ return;
663
+ }
684
664
 
685
- p.addButton(null, "Cancel", () => {if(options.on_cancel) options.on_cancel(); dialog.close();} );
665
+ for( let obj of data )
666
+ {
667
+ if( obj instanceof LX.Widget )
668
+ {
669
+ obj.set( value, options.skipCallback ?? true );
670
+ }
671
+ else if( obj.constructor === Function )
672
+ {
673
+ const fn = obj;
674
+ fn( null, value );
675
+ }
676
+ else
677
+ {
678
+ // This is an element
679
+ const fn = obj[ signalName ];
680
+ console.assert( fn, `No callback registered with _${ signalName }_ signal` );
681
+ fn.bind( obj )( value );
682
+ }
683
+ }
684
+ }
686
685
 
687
- p.addButton( null, options.accept || "Continue", () => {
688
- if( options.required && value === '' )
689
- {
690
- text += text.includes("You must fill the input text.") ? "": "\nYou must fill the input text.";
691
- dialog.close();
692
- prompt( text, title, callback, options );
693
- }
694
- else
695
- {
696
- if( callback ) callback.call( this, value );
697
- dialog.close();
698
- }
699
- }, { buttonClass: "primary" });
686
+ LX.emit = emit;
700
687
 
701
- }, options );
688
+ function addSignal( name, obj, callback )
689
+ {
690
+ obj[ name ] = callback;
702
691
 
703
- // Focus text prompt
704
- if( options.input ?? true )
692
+ if( !LX.signals[ name ] )
705
693
  {
706
- dialog.root.querySelector( 'input' ).focus();
694
+ LX.signals[ name ] = [];
707
695
  }
708
696
 
709
- return dialog;
697
+ if( LX.signals[ name ].indexOf( obj ) > -1 )
698
+ {
699
+ return;
700
+ }
701
+
702
+ LX.signals[ name ].push( obj );
710
703
  }
711
704
 
712
- LX.prompt = prompt;
705
+ LX.addSignal = addSignal;
706
+
707
+ /*
708
+ * DOM Elements
709
+ */
713
710
 
714
711
  /**
715
- * @method toast
716
- * @param {String} title
717
- * @param {String} description (Optional)
718
- * @param {Object} options
719
- * action: Data of the custom action { name, callback }
720
- * closable: Allow closing the toast
721
- * timeout: Time in which the toast closed automatically, in ms. -1 means persistent. [3000]
712
+ * @class Popover
722
713
  */
723
714
 
724
- function toast( title, description, options = {} )
725
- {
726
- if( !title )
727
- {
728
- throw( "The toast needs at least a title!" );
729
- }
715
+ class Popover {
730
716
 
731
- console.assert( this.notifications );
717
+ static activeElement = false;
732
718
 
733
- const toast = document.createElement( "li" );
734
- toast.className = "lextoast";
735
- toast.style.translate = "0 calc(100% + 30px)";
736
- this.notifications.prepend( toast );
719
+ constructor( trigger, content, options = {} ) {
737
720
 
738
- LX.doAsync( () => {
721
+ console.assert( trigger, "Popover needs a DOM element as trigger!" );
739
722
 
740
- if( this.notifications.offsetWidth > this.notifications.iWidth )
723
+ if( Popover.activeElement )
741
724
  {
742
- this.notifications.iWidth = Math.min( this.notifications.offsetWidth, 480 );
743
- this.notifications.style.width = this.notifications.iWidth + "px";
725
+ Popover.activeElement.destroy();
726
+ return;
744
727
  }
745
728
 
746
- toast.dataset[ "open" ] = true;
747
- }, 10 );
729
+ this._trigger = trigger;
730
+ trigger.classList.add( "triggered" );
731
+ trigger.active = this;
748
732
 
749
- const content = document.createElement( "div" );
750
- content.className = "lextoastcontent";
751
- toast.appendChild( content );
733
+ this._windowPadding = 4;
734
+ this.side = options.side ?? "bottom";
735
+ this.align = options.align ?? "center";
736
+ this.avoidCollisions = options.avoidCollisions ?? true;
752
737
 
753
- const titleContent = document.createElement( "div" );
754
- titleContent.className = "title";
755
- titleContent.innerHTML = title;
756
- content.appendChild( titleContent );
738
+ this.root = document.createElement( "div" );
739
+ this.root.dataset["side"] = this.side;
740
+ this.root.tabIndex = "1";
741
+ this.root.className = "lexpopover";
742
+ LX.root.appendChild( this.root );
757
743
 
758
- if( description )
759
- {
760
- const desc = document.createElement( "div" );
761
- desc.className = "desc";
762
- desc.innerHTML = description;
763
- content.appendChild( desc );
764
- }
744
+ this.root.addEventListener( "keydown", (e) => {
745
+ if( e.key == "Escape" )
746
+ {
747
+ e.preventDefault();
748
+ e.stopPropagation();
749
+ this.destroy();
750
+ }
751
+ } );
765
752
 
766
- if( options.action )
767
- {
768
- const panel = new LX.Panel();
769
- panel.addButton(null, options.action.name ?? "Accept", options.action.callback.bind( this, toast ), { width: "auto", maxWidth: "150px", className: "right", buttonClass: "border" });
770
- toast.appendChild( panel.root.childNodes[ 0 ] );
771
- }
753
+ if( content )
754
+ {
755
+ content = [].concat( content );
756
+ content.forEach( e => {
757
+ const domNode = e.root ?? e;
758
+ this.root.appendChild( domNode );
759
+ if( e.onPopover )
760
+ {
761
+ e.onPopover();
762
+ }
763
+ } );
764
+ }
772
765
 
773
- const that = this;
766
+ Popover.activeElement = this;
774
767
 
775
- toast.close = function() {
776
- this.dataset[ "closed" ] = true;
777
768
  LX.doAsync( () => {
778
- this.remove();
779
- if( !that.notifications.childElementCount )
780
- {
781
- that.notifications.style.width = "unset";
782
- that.notifications.iWidth = 0;
783
- }
784
- }, 500 );
785
- };
769
+ this._adjustPosition();
786
770
 
787
- if( options.closable ?? true )
788
- {
789
- const closeIcon = LX.makeIcon( "X", { iconClass: "closer" } );
790
- closeIcon.addEventListener( "click", () => {
791
- toast.close();
792
- } );
793
- toast.appendChild( closeIcon );
794
- }
771
+ this.root.focus();
795
772
 
796
- const timeout = options.timeout ?? 3000;
773
+ this._onClick = e => {
774
+ if( e.target && ( this.root.contains( e.target ) || e.target == this._trigger ) )
775
+ {
776
+ return;
777
+ }
778
+ this.destroy();
779
+ };
797
780
 
798
- if( timeout != -1 )
799
- {
800
- LX.doAsync( () => {
801
- toast.close();
802
- }, timeout );
781
+ document.body.addEventListener( "mousedown", this._onClick, true );
782
+ document.body.addEventListener( "focusin", this._onClick, true );
783
+ }, 10 );
803
784
  }
804
- }
805
785
 
806
- LX.toast = toast;
807
-
808
- /**
809
- * @method badge
810
- * @param {String} text
811
- * @param {String} className
812
- * @param {Object} options
813
- * style: Style attributes to override
814
- * asElement: Returns the badge as HTMLElement [false]
815
- */
786
+ destroy() {
816
787
 
817
- function badge( text, className, options = {} )
818
- {
819
- const container = document.createElement( "div" );
820
- container.innerHTML = text;
821
- container.className = "lexbadge " + ( className ?? "" );
822
- Object.assign( container.style, options.style ?? {} );
823
- return ( options.asElement ?? false ) ? container : container.outerHTML;
824
- }
788
+ this._trigger.classList.remove( "triggered" );
825
789
 
826
- LX.badge = badge;
790
+ delete this._trigger.active;
827
791
 
828
- /**
829
- * @method makeElement
830
- * @param {String} htmlType
831
- * @param {String} className
832
- * @param {String} innerHTML
833
- * @param {HTMLElement} parent
834
- * @param {Object} overrideStyle
835
- */
792
+ document.body.removeEventListener( "mousedown", this._onClick, true );
793
+ document.body.removeEventListener( "focusin", this._onClick, true );
836
794
 
837
- function makeElement( htmlType, className, innerHTML, parent, overrideStyle = {} )
838
- {
839
- const element = document.createElement( htmlType );
840
- element.className = className ?? "";
841
- element.innerHTML = innerHTML ?? "";
842
- Object.assign( element.style, overrideStyle );
795
+ this.root.remove();
843
796
 
844
- if( parent )
845
- {
846
- if( parent.attach ) // Use attach method if possible
847
- {
848
- parent.attach( element );
849
- }
850
- else // its a native HTMLElement
851
- {
852
- parent.appendChild( element );
853
- }
797
+ Popover.activeElement = null;
854
798
  }
855
799
 
856
- return element;
857
- }
858
-
859
- LX.makeElement = makeElement;
860
-
861
- /**
862
- * @method makeContainer
863
- * @param {Array} size
864
- * @param {String} className
865
- * @param {String} innerHTML
866
- * @param {HTMLElement} parent
867
- * @param {Object} overrideStyle
868
- */
869
-
870
- function makeContainer( size, className, innerHTML, parent, overrideStyle = {} )
871
- {
872
- const container = LX.makeElement( "div", "lexcontainer " + ( className ?? "" ), innerHTML, parent, overrideStyle );
873
- container.style.width = size && size[ 0 ] ? size[ 0 ] : "100%";
874
- container.style.height = size && size[ 1 ] ? size[ 1 ] : "100%";
875
- return container;
876
- }
877
-
878
- LX.makeContainer = makeContainer;
879
-
880
- /**
881
- * @method asTooltip
882
- * @param {HTMLElement} trigger
883
- * @param {String} content
884
- * @param {Object} options
885
- * side: Side of the tooltip
886
- * offset: Tooltip margin offset
887
- * active: Tooltip active by default [true]
888
- */
889
-
890
- function asTooltip( trigger, content, options = {} )
891
- {
892
- console.assert( trigger, "You need a trigger to generate a tooltip!" );
893
-
894
- trigger.dataset[ "disableTooltip" ] = !( options.active ?? true );
895
-
896
- let tooltipDom = null;
800
+ _adjustPosition() {
897
801
 
898
- trigger.addEventListener( "mouseenter", function(e) {
802
+ const position = [ 0, 0 ];
899
803
 
900
- if( trigger.dataset[ "disableTooltip" ] == "true" )
804
+ // Place menu using trigger position and user options
901
805
  {
902
- return;
903
- }
904
-
905
- LX.root.querySelectorAll( ".lextooltip" ).forEach( e => e.remove() );
906
-
907
- tooltipDom = document.createElement( "div" );
908
- tooltipDom.className = "lextooltip";
909
- tooltipDom.innerHTML = content;
910
-
911
- LX.doAsync( () => {
806
+ const rect = this._trigger.getBoundingClientRect();
912
807
 
913
- const position = [ 0, 0 ];
914
- const rect = this.getBoundingClientRect();
915
- const offset = options.offset ?? 6;
916
808
  let alignWidth = true;
917
809
 
918
- switch( options.side ?? "top" )
810
+ switch( this.side )
919
811
  {
920
812
  case "left":
921
- position[ 0 ] += ( rect.x - tooltipDom.offsetWidth - offset );
813
+ position[ 0 ] += ( rect.x - this.root.offsetWidth );
922
814
  alignWidth = false;
923
815
  break;
924
816
  case "right":
925
- position[ 0 ] += ( rect.x + rect.width + offset );
817
+ position[ 0 ] += ( rect.x + rect.width );
926
818
  alignWidth = false;
927
819
  break;
928
820
  case "top":
929
- position[ 1 ] += ( rect.y - tooltipDom.offsetHeight - offset );
821
+ position[ 1 ] += ( rect.y - this.root.offsetHeight );
930
822
  alignWidth = true;
931
823
  break;
932
824
  case "bottom":
933
- position[ 1 ] += ( rect.y + rect.height + offset );
825
+ position[ 1 ] += ( rect.y + rect.height );
934
826
  alignWidth = true;
935
827
  break;
936
828
  }
937
829
 
938
- if( alignWidth ) { position[ 0 ] += ( rect.x + rect.width * 0.5 ) - tooltipDom.offsetWidth * 0.5; }
939
- else { position[ 1 ] += ( rect.y + rect.height * 0.5 ) - tooltipDom.offsetHeight * 0.5; }
940
-
941
- // Avoid collisions
942
- position[ 0 ] = LX.clamp( position[ 0 ], 0, window.innerWidth - tooltipDom.offsetWidth - 4 );
943
- position[ 1 ] = LX.clamp( position[ 1 ], 0, window.innerHeight - tooltipDom.offsetHeight - 4 );
944
-
945
- tooltipDom.style.left = `${ position[ 0 ] }px`;
946
- tooltipDom.style.top = `${ position[ 1 ] }px`;
947
- } );
948
-
949
- LX.root.appendChild( tooltipDom );
950
- } );
830
+ switch( this.align )
831
+ {
832
+ case "start":
833
+ if( alignWidth ) { position[ 0 ] += rect.x; }
834
+ else { position[ 1 ] += rect.y; }
835
+ break;
836
+ case "center":
837
+ if( alignWidth ) { position[ 0 ] += ( rect.x + rect.width * 0.5 ) - this.root.offsetWidth * 0.5; }
838
+ else { position[ 1 ] += ( rect.y + rect.height * 0.5 ) - this.root.offsetHeight * 0.5; }
839
+ break;
840
+ case "end":
841
+ if( alignWidth ) { position[ 0 ] += rect.x - this.root.offsetWidth + rect.width; }
842
+ else { position[ 1 ] += rect.y - this.root.offsetHeight + rect.height; }
843
+ break;
844
+ }
845
+ }
951
846
 
952
- trigger.addEventListener( "mouseleave", function(e) {
953
- if( tooltipDom )
847
+ if( this.avoidCollisions )
954
848
  {
955
- tooltipDom.remove();
849
+ position[ 0 ] = LX.clamp( position[ 0 ], 0, window.innerWidth - this.root.offsetWidth - this._windowPadding );
850
+ position[ 1 ] = LX.clamp( position[ 1 ], 0, window.innerHeight - this.root.offsetHeight - this._windowPadding );
956
851
  }
957
- } );
852
+
853
+ this.root.style.left = `${ position[ 0 ] }px`;
854
+ this.root.style.top = `${ position[ 1 ] }px`;
855
+ }
958
856
  }
857
+ LX.Popover = Popover;
959
858
 
960
- LX.asTooltip = asTooltip;
859
+ /**
860
+ * @class Sheet
861
+ */
961
862
 
962
- /*
963
- * Events and Signals
964
- */
863
+ class Sheet {
965
864
 
966
- class IEvent {
865
+ constructor( size, content, options = {} ) {
967
866
 
968
- constructor( name, value, domEvent ) {
969
- this.name = name;
970
- this.value = value;
971
- this.domEvent = domEvent;
972
- }
973
- }
974
- LX.IEvent = IEvent;
867
+ this.side = options.side ?? "left";
975
868
 
976
- class TreeEvent {
869
+ this.root = document.createElement( "div" );
870
+ this.root.dataset["side"] = this.side;
871
+ this.root.tabIndex = "1";
872
+ this.root.role = "dialog";
873
+ this.root.className = "lexsheet fixed z-100 bg-primary";
874
+ LX.root.appendChild( this.root );
977
875
 
978
- static NONE = 0;
979
- static NODE_SELECTED = 1;
980
- static NODE_DELETED = 2;
981
- static NODE_DBLCLICKED = 3;
982
- static NODE_CONTEXTMENU = 4;
983
- static NODE_DRAGGED = 5;
984
- static NODE_RENAMED = 6;
985
- static NODE_VISIBILITY = 7;
986
- static NODE_CARETCHANGED = 8;
876
+ this.root.addEventListener( "keydown", (e) => {
877
+ if( e.key == "Escape" )
878
+ {
879
+ e.preventDefault();
880
+ e.stopPropagation();
881
+ this.destroy();
882
+ }
883
+ } );
987
884
 
988
- constructor( type, node, value ) {
989
- this.type = type || TreeEvent.NONE;
990
- this.node = node;
991
- this.value = value;
992
- this.multiple = false; // Multiple selection
993
- this.panel = null;
994
- }
995
-
996
- string() {
997
- switch( this.type )
885
+ if( content )
998
886
  {
999
- case TreeEvent.NONE: return "tree_event_none";
1000
- case TreeEvent.NODE_SELECTED: return "tree_event_selected";
1001
- case TreeEvent.NODE_DELETED: return "tree_event_deleted";
1002
- case TreeEvent.NODE_DBLCLICKED: return "tree_event_dblclick";
1003
- case TreeEvent.NODE_CONTEXTMENU: return "tree_event_contextmenu";
1004
- case TreeEvent.NODE_DRAGGED: return "tree_event_dragged";
1005
- case TreeEvent.NODE_RENAMED: return "tree_event_renamed";
1006
- case TreeEvent.NODE_VISIBILITY: return "tree_event_visibility";
1007
- case TreeEvent.NODE_CARETCHANGED: return "tree_event_caretchanged";
887
+ content = [].concat( content );
888
+ content.forEach( e => {
889
+ const domNode = e.root ?? e;
890
+ this.root.appendChild( domNode );
891
+ if( e.onSheet )
892
+ {
893
+ e.onSheet();
894
+ }
895
+ } );
1008
896
  }
1009
- }
1010
- }
1011
- LX.TreeEvent = TreeEvent;
1012
897
 
1013
- function emit( signalName, value, options = {} )
1014
- {
1015
- const data = LX.signals[ signalName ];
898
+ LX.doAsync( () => {
1016
899
 
1017
- if( !data )
1018
- {
1019
- return;
1020
- }
900
+ LX.modal.toggle( false );
1021
901
 
1022
- const target = options.target;
902
+ switch( this.side )
903
+ {
904
+ case "left":
905
+ this.root.style.left = 0;
906
+ this.root.style.width = size;
907
+ this.root.style.height = "100%";
908
+ break;
909
+ case "right":
910
+ this.root.style.right = 0;
911
+ this.root.style.width = size;
912
+ this.root.style.height = "100%";
913
+ break;
914
+ case "top":
915
+ this.root.style.top = 0;
916
+ this.root.style.width = "100%";
917
+ this.root.style.height = size;
918
+ break;
919
+ case "bottom":
920
+ this.root.style.bottom = 0;
921
+ this.root.style.width = "100%";
922
+ this.root.style.height = size;
923
+ break;
924
+ }
1023
925
 
1024
- if( target )
1025
- {
1026
- if( target[ signalName ])
1027
- {
1028
- target[ signalName ].call( target, value );
1029
- }
926
+ this.root.focus();
1030
927
 
1031
- return;
1032
- }
928
+ this._onClick = e => {
929
+ if( e.target && ( this.root.contains( e.target ) ) )
930
+ {
931
+ return;
932
+ }
933
+ this.destroy();
934
+ };
1033
935
 
1034
- for( let obj of data )
1035
- {
1036
- if( obj instanceof LX.Widget )
1037
- {
1038
- obj.set( value, options.skipCallback ?? true );
1039
- }
1040
- else if( obj.constructor === Function )
1041
- {
1042
- const fn = obj;
1043
- fn( null, value );
1044
- }
1045
- else
1046
- {
1047
- // This is an element
1048
- const fn = obj[ signalName ];
1049
- console.assert( fn, `No callback registered with _${ signalName }_ signal` );
1050
- fn.bind( obj )( value );
1051
- }
936
+ document.body.addEventListener( "mousedown", this._onClick, true );
937
+ document.body.addEventListener( "focusin", this._onClick, true );
938
+ }, 10 );
1052
939
  }
1053
- }
1054
940
 
1055
- LX.emit = emit;
941
+ destroy() {
1056
942
 
1057
- function addSignal( name, obj, callback )
1058
- {
1059
- obj[ name ] = callback;
943
+ document.body.removeEventListener( "mousedown", this._onClick, true );
944
+ document.body.removeEventListener( "focusin", this._onClick, true );
1060
945
 
1061
- if( !LX.signals[ name ] )
1062
- {
1063
- LX.signals[ name ] = [];
1064
- }
946
+ this.root.remove();
1065
947
 
1066
- if( LX.signals[ name ].indexOf( obj ) > -1 )
1067
- {
1068
- return;
948
+ LX.modal.toggle( true );
1069
949
  }
1070
-
1071
- LX.signals[ name ].push( obj );
1072
950
  }
1073
-
1074
- LX.addSignal = addSignal;
1075
-
1076
- /*
1077
- * DOM Elements
1078
- */
951
+ LX.Sheet = Sheet;
1079
952
 
1080
953
  /**
1081
- * @class Popover
954
+ * @class DropdownMenu
1082
955
  */
1083
956
 
1084
- class Popover {
957
+ class DropdownMenu {
1085
958
 
1086
- static activeElement = false;
959
+ static currentMenu = false;
1087
960
 
1088
- constructor( trigger, content, options = {} ) {
961
+ constructor( trigger, items, options = {} ) {
1089
962
 
1090
- console.assert( trigger, "Popover needs a DOM element as trigger!" );
963
+ console.assert( trigger, "DropdownMenu needs a DOM element as trigger!" );
1091
964
 
1092
- if( Popover.activeElement )
965
+ if( DropdownMenu.currentMenu || !items?.length )
1093
966
  {
1094
- Popover.activeElement.destroy();
967
+ DropdownMenu.currentMenu.destroy();
968
+ this.invalid = true;
1095
969
  return;
1096
970
  }
1097
971
 
1098
972
  this._trigger = trigger;
1099
973
  trigger.classList.add( "triggered" );
1100
- trigger.active = this;
974
+ trigger.ddm = this;
975
+
976
+ this._items = items;
1101
977
 
1102
978
  this._windowPadding = 4;
1103
979
  this.side = options.side ?? "bottom";
1104
980
  this.align = options.align ?? "center";
1105
981
  this.avoidCollisions = options.avoidCollisions ?? true;
982
+ this.onBlur = options.onBlur;
983
+ this.inPlace = false;
1106
984
 
1107
985
  this.root = document.createElement( "div" );
986
+ this.root.id = "root";
1108
987
  this.root.dataset["side"] = this.side;
1109
988
  this.root.tabIndex = "1";
1110
- this.root.className = "lexpopover";
989
+ this.root.className = "lexdropdownmenu";
1111
990
  LX.root.appendChild( this.root );
1112
991
 
1113
- this.root.addEventListener( "keydown", (e) => {
1114
- if( e.key == "Escape" )
1115
- {
1116
- e.preventDefault();
1117
- e.stopPropagation();
1118
- this.destroy();
1119
- }
1120
- } );
1121
-
1122
- if( content )
1123
- {
1124
- content = [].concat( content );
1125
- content.forEach( e => {
1126
- const domNode = e.root ?? e;
1127
- this.root.appendChild( domNode );
1128
- if( e.onPopover )
1129
- {
1130
- e.onPopover();
1131
- }
1132
- } );
1133
- }
992
+ this._create( this._items );
1134
993
 
1135
- Popover.activeElement = this;
994
+ DropdownMenu.currentMenu = this;
1136
995
 
1137
996
  LX.doAsync( () => {
1138
997
  this._adjustPosition();
@@ -1140,11 +999,14 @@ class Popover {
1140
999
  this.root.focus();
1141
1000
 
1142
1001
  this._onClick = e => {
1143
- if( e.target && ( this.root.contains( e.target ) || e.target == this._trigger ) )
1002
+
1003
+ // Check if the click is inside a menu or on the trigger
1004
+ if( e.target && ( e.target.closest( ".lexdropdownmenu" ) != undefined || e.target == this._trigger ) )
1144
1005
  {
1145
1006
  return;
1146
1007
  }
1147
- this.destroy();
1008
+
1009
+ this.destroy( true );
1148
1010
  };
1149
1011
 
1150
1012
  document.body.addEventListener( "mousedown", this._onClick, true );
@@ -1152,298 +1014,67 @@ class Popover {
1152
1014
  }, 10 );
1153
1015
  }
1154
1016
 
1155
- destroy() {
1017
+ destroy( blurEvent ) {
1156
1018
 
1157
1019
  this._trigger.classList.remove( "triggered" );
1158
1020
 
1159
- delete this._trigger.active;
1021
+ delete this._trigger.ddm;
1160
1022
 
1161
1023
  document.body.removeEventListener( "mousedown", this._onClick, true );
1162
1024
  document.body.removeEventListener( "focusin", this._onClick, true );
1163
1025
 
1164
- this.root.remove();
1026
+ LX.root.querySelectorAll( ".lexdropdownmenu" ).forEach( m => { m.remove(); } );
1165
1027
 
1166
- Popover.activeElement = null;
1167
- }
1028
+ DropdownMenu.currentMenu = null;
1168
1029
 
1169
- _adjustPosition() {
1030
+ if( blurEvent && this.onBlur )
1031
+ {
1032
+ this.onBlur();
1033
+ }
1034
+ }
1170
1035
 
1171
- const position = [ 0, 0 ];
1036
+ _create( items, parentDom ) {
1172
1037
 
1173
- // Place menu using trigger position and user options
1038
+ if( !parentDom )
1174
1039
  {
1175
- const rect = this._trigger.getBoundingClientRect();
1040
+ parentDom = this.root;
1041
+ }
1042
+ else
1043
+ {
1044
+ const parentRect = parentDom.getBoundingClientRect();
1176
1045
 
1177
- let alignWidth = true;
1046
+ let newParent = document.createElement( "div" );
1047
+ newParent.tabIndex = "1";
1048
+ newParent.className = "lexdropdownmenu";
1049
+ newParent.dataset["id"] = parentDom.dataset["id"];
1050
+ newParent.dataset["side"] = "right"; // submenus always come from the right
1051
+ LX.root.appendChild( newParent );
1178
1052
 
1179
- switch( this.side )
1053
+ newParent.currentParent = parentDom;
1054
+ parentDom = newParent;
1055
+
1056
+ LX.doAsync( () => {
1057
+ const position = [ parentRect.x + parentRect.width, parentRect.y ];
1058
+
1059
+ if( this.avoidCollisions )
1060
+ {
1061
+ position[ 0 ] = LX.clamp( position[ 0 ], 0, window.innerWidth - newParent.offsetWidth - this._windowPadding );
1062
+ position[ 1 ] = LX.clamp( position[ 1 ], 0, window.innerHeight - newParent.offsetHeight - this._windowPadding );
1063
+ }
1064
+
1065
+ newParent.style.left = `${ position[ 0 ] }px`;
1066
+ newParent.style.top = `${ position[ 1 ] }px`;
1067
+ }, 10 );
1068
+ }
1069
+
1070
+ let applyIconPadding = items.filter( i => { return ( i?.icon != undefined ) || ( i?.checked != undefined ) } ).length > 0;
1071
+
1072
+ for( let item of items )
1073
+ {
1074
+ if( !item )
1180
1075
  {
1181
- case "left":
1182
- position[ 0 ] += ( rect.x - this.root.offsetWidth );
1183
- alignWidth = false;
1184
- break;
1185
- case "right":
1186
- position[ 0 ] += ( rect.x + rect.width );
1187
- alignWidth = false;
1188
- break;
1189
- case "top":
1190
- position[ 1 ] += ( rect.y - this.root.offsetHeight );
1191
- alignWidth = true;
1192
- break;
1193
- case "bottom":
1194
- position[ 1 ] += ( rect.y + rect.height );
1195
- alignWidth = true;
1196
- break;
1197
- }
1198
-
1199
- switch( this.align )
1200
- {
1201
- case "start":
1202
- if( alignWidth ) { position[ 0 ] += rect.x; }
1203
- else { position[ 1 ] += rect.y; }
1204
- break;
1205
- case "center":
1206
- if( alignWidth ) { position[ 0 ] += ( rect.x + rect.width * 0.5 ) - this.root.offsetWidth * 0.5; }
1207
- else { position[ 1 ] += ( rect.y + rect.height * 0.5 ) - this.root.offsetHeight * 0.5; }
1208
- break;
1209
- case "end":
1210
- if( alignWidth ) { position[ 0 ] += rect.x - this.root.offsetWidth + rect.width; }
1211
- else { position[ 1 ] += rect.y - this.root.offsetHeight + rect.height; }
1212
- break;
1213
- }
1214
- }
1215
-
1216
- if( this.avoidCollisions )
1217
- {
1218
- position[ 0 ] = LX.clamp( position[ 0 ], 0, window.innerWidth - this.root.offsetWidth - this._windowPadding );
1219
- position[ 1 ] = LX.clamp( position[ 1 ], 0, window.innerHeight - this.root.offsetHeight - this._windowPadding );
1220
- }
1221
-
1222
- this.root.style.left = `${ position[ 0 ] }px`;
1223
- this.root.style.top = `${ position[ 1 ] }px`;
1224
- }
1225
- }
1226
- LX.Popover = Popover;
1227
-
1228
- /**
1229
- * @class Sheet
1230
- */
1231
-
1232
- class Sheet {
1233
-
1234
- constructor( size, content, options = {} ) {
1235
-
1236
- this.side = options.side ?? "left";
1237
-
1238
- this.root = document.createElement( "div" );
1239
- this.root.dataset["side"] = this.side;
1240
- this.root.tabIndex = "1";
1241
- this.root.role = "dialog";
1242
- this.root.className = "lexsheet fixed z-100 bg-primary";
1243
- LX.root.appendChild( this.root );
1244
-
1245
- this.root.addEventListener( "keydown", (e) => {
1246
- if( e.key == "Escape" )
1247
- {
1248
- e.preventDefault();
1249
- e.stopPropagation();
1250
- this.destroy();
1251
- }
1252
- } );
1253
-
1254
- if( content )
1255
- {
1256
- content = [].concat( content );
1257
- content.forEach( e => {
1258
- const domNode = e.root ?? e;
1259
- this.root.appendChild( domNode );
1260
- if( e.onSheet )
1261
- {
1262
- e.onSheet();
1263
- }
1264
- } );
1265
- }
1266
-
1267
- LX.doAsync( () => {
1268
-
1269
- LX.modal.toggle( false );
1270
-
1271
- switch( this.side )
1272
- {
1273
- case "left":
1274
- this.root.style.left = 0;
1275
- this.root.style.width = size;
1276
- this.root.style.height = "100%";
1277
- break;
1278
- case "right":
1279
- this.root.style.right = 0;
1280
- this.root.style.width = size;
1281
- this.root.style.height = "100%";
1282
- break;
1283
- case "top":
1284
- this.root.style.top = 0;
1285
- this.root.style.width = "100%";
1286
- this.root.style.height = size;
1287
- break;
1288
- case "bottom":
1289
- this.root.style.bottom = 0;
1290
- this.root.style.width = "100%";
1291
- this.root.style.height = size;
1292
- break;
1293
- }
1294
-
1295
- this.root.focus();
1296
-
1297
- this._onClick = e => {
1298
- if( e.target && ( this.root.contains( e.target ) ) )
1299
- {
1300
- return;
1301
- }
1302
- this.destroy();
1303
- };
1304
-
1305
- document.body.addEventListener( "mousedown", this._onClick, true );
1306
- document.body.addEventListener( "focusin", this._onClick, true );
1307
- }, 10 );
1308
- }
1309
-
1310
- destroy() {
1311
-
1312
- document.body.removeEventListener( "mousedown", this._onClick, true );
1313
- document.body.removeEventListener( "focusin", this._onClick, true );
1314
-
1315
- this.root.remove();
1316
-
1317
- LX.modal.toggle( true );
1318
- }
1319
- }
1320
- LX.Sheet = Sheet;
1321
-
1322
- /**
1323
- * @class DropdownMenu
1324
- */
1325
-
1326
- class DropdownMenu {
1327
-
1328
- static currentMenu = false;
1329
-
1330
- constructor( trigger, items, options = {} ) {
1331
-
1332
- console.assert( trigger, "DropdownMenu needs a DOM element as trigger!" );
1333
-
1334
- if( DropdownMenu.currentMenu || !items?.length )
1335
- {
1336
- DropdownMenu.currentMenu.destroy();
1337
- this.invalid = true;
1338
- return;
1339
- }
1340
-
1341
- this._trigger = trigger;
1342
- trigger.classList.add( "triggered" );
1343
- trigger.ddm = this;
1344
-
1345
- this._items = items;
1346
-
1347
- this._windowPadding = 4;
1348
- this.side = options.side ?? "bottom";
1349
- this.align = options.align ?? "center";
1350
- this.avoidCollisions = options.avoidCollisions ?? true;
1351
- this.onBlur = options.onBlur;
1352
- this.inPlace = false;
1353
-
1354
- this.root = document.createElement( "div" );
1355
- this.root.id = "root";
1356
- this.root.dataset["side"] = this.side;
1357
- this.root.tabIndex = "1";
1358
- this.root.className = "lexdropdownmenu";
1359
- LX.root.appendChild( this.root );
1360
-
1361
- this._create( this._items );
1362
-
1363
- DropdownMenu.currentMenu = this;
1364
-
1365
- LX.doAsync( () => {
1366
- this._adjustPosition();
1367
-
1368
- this.root.focus();
1369
-
1370
- this._onClick = e => {
1371
-
1372
- // Check if the click is inside a menu or on the trigger
1373
- if( e.target && ( e.target.closest( ".lexdropdownmenu" ) != undefined || e.target == this._trigger ) )
1374
- {
1375
- return;
1376
- }
1377
-
1378
- this.destroy( true );
1379
- };
1380
-
1381
- document.body.addEventListener( "mousedown", this._onClick, true );
1382
- document.body.addEventListener( "focusin", this._onClick, true );
1383
- }, 10 );
1384
- }
1385
-
1386
- destroy( blurEvent ) {
1387
-
1388
- this._trigger.classList.remove( "triggered" );
1389
-
1390
- delete this._trigger.ddm;
1391
-
1392
- document.body.removeEventListener( "mousedown", this._onClick, true );
1393
- document.body.removeEventListener( "focusin", this._onClick, true );
1394
-
1395
- LX.root.querySelectorAll( ".lexdropdownmenu" ).forEach( m => { m.remove(); } );
1396
-
1397
- DropdownMenu.currentMenu = null;
1398
-
1399
- if( blurEvent && this.onBlur )
1400
- {
1401
- this.onBlur();
1402
- }
1403
- }
1404
-
1405
- _create( items, parentDom ) {
1406
-
1407
- if( !parentDom )
1408
- {
1409
- parentDom = this.root;
1410
- }
1411
- else
1412
- {
1413
- const parentRect = parentDom.getBoundingClientRect();
1414
-
1415
- let newParent = document.createElement( "div" );
1416
- newParent.tabIndex = "1";
1417
- newParent.className = "lexdropdownmenu";
1418
- newParent.dataset["id"] = parentDom.dataset["id"];
1419
- newParent.dataset["side"] = "right"; // submenus always come from the right
1420
- LX.root.appendChild( newParent );
1421
-
1422
- newParent.currentParent = parentDom;
1423
- parentDom = newParent;
1424
-
1425
- LX.doAsync( () => {
1426
- const position = [ parentRect.x + parentRect.width, parentRect.y ];
1427
-
1428
- if( this.avoidCollisions )
1429
- {
1430
- position[ 0 ] = LX.clamp( position[ 0 ], 0, window.innerWidth - newParent.offsetWidth - this._windowPadding );
1431
- position[ 1 ] = LX.clamp( position[ 1 ], 0, window.innerHeight - newParent.offsetHeight - this._windowPadding );
1432
- }
1433
-
1434
- newParent.style.left = `${ position[ 0 ] }px`;
1435
- newParent.style.top = `${ position[ 1 ] }px`;
1436
- }, 10 );
1437
- }
1438
-
1439
- let applyIconPadding = items.filter( i => { return ( i?.icon != undefined ) || ( i?.checked != undefined ) } ).length > 0;
1440
-
1441
- for( let item of items )
1442
- {
1443
- if( !item )
1444
- {
1445
- this._addSeparator( parentDom );
1446
- continue;
1076
+ this._addSeparator( parentDom );
1077
+ continue;
1447
1078
  }
1448
1079
 
1449
1080
  const key = item.name ?? item;
@@ -1513,6 +1144,7 @@ class DropdownMenu {
1513
1144
  {
1514
1145
  const checkbox = new LX.Checkbox( pKey + "_entryChecked", item.checked, (v) => {
1515
1146
  const f = item[ 'callback' ];
1147
+ item.checked = v;
1516
1148
  if( f )
1517
1149
  {
1518
1150
  f.call( this, key, v, menuItem );
@@ -2247,13 +1879,6 @@ class Calendar {
2247
1879
 
2248
1880
  LX.Calendar = Calendar;
2249
1881
 
2250
- function flushCss(element) {
2251
- // By reading the offsetHeight property, we are forcing
2252
- // the browser to flush the pending CSS changes (which it
2253
- // does to ensure the value obtained is accurate).
2254
- element.offsetHeight;
2255
- }
2256
-
2257
1882
  /**
2258
1883
  * @class Tabs
2259
1884
  */
@@ -2379,7 +2004,7 @@ class Tabs {
2379
2004
  this.thumb.style.transition = "none";
2380
2005
  this.thumb.style.transform = "translate( " + ( tabEl.childIndex * tabEl.offsetWidth ) + "px )";
2381
2006
  this.thumb.style.width = ( tabEl.offsetWidth ) + "px";
2382
- flushCss( this.thumb );
2007
+ LX.flushCss( this.thumb );
2383
2008
  this.thumb.style.transition = transition;
2384
2009
  });
2385
2010
 
@@ -3493,7 +3118,7 @@ class CanvasCurve {
3493
3118
  }
3494
3119
  else
3495
3120
  {
3496
- LX.UTILS.drawSpline( ctx, values, element.smooth );
3121
+ LX.drawSpline( ctx, values, element.smooth );
3497
3122
  }
3498
3123
 
3499
3124
  // Draw points
@@ -4435,254 +4060,6 @@ class CanvasMap2D {
4435
4060
 
4436
4061
  LX.CanvasMap2D = CanvasMap2D;
4437
4062
 
4438
- /*
4439
- * Requests
4440
- */
4441
-
4442
- Object.assign(LX, {
4443
-
4444
- /**
4445
- * Request file from url (it could be a binary, text, etc.). If you want a simplied version use
4446
- * @method request
4447
- * @param {Object} request object with all the parameters like data (for sending forms), dataType, success, error
4448
- * @param {Function} on_complete
4449
- **/
4450
- request( request ) {
4451
-
4452
- var dataType = request.dataType || "text";
4453
- if(dataType == "json") //parse it locally
4454
- dataType = "text";
4455
- else if(dataType == "xml") //parse it locally
4456
- dataType = "text";
4457
- else if (dataType == "binary")
4458
- {
4459
- //request.mimeType = "text/plain; charset=x-user-defined";
4460
- dataType = "arraybuffer";
4461
- request.mimeType = "application/octet-stream";
4462
- }
4463
-
4464
- //regular case, use AJAX call
4465
- var xhr = new XMLHttpRequest();
4466
- xhr.open( request.data ? 'POST' : 'GET', request.url, true);
4467
- if(dataType)
4468
- xhr.responseType = dataType;
4469
- if (request.mimeType)
4470
- xhr.overrideMimeType( request.mimeType );
4471
- if( request.nocache )
4472
- xhr.setRequestHeader('Cache-Control', 'no-cache');
4473
-
4474
- xhr.onload = function(load)
4475
- {
4476
- var response = this.response;
4477
- if( this.status != 200)
4478
- {
4479
- var err = "Error " + this.status;
4480
- if(request.error)
4481
- request.error(err);
4482
- return;
4483
- }
4484
-
4485
- if(request.dataType == "json") //chrome doesnt support json format
4486
- {
4487
- try
4488
- {
4489
- response = JSON.parse(response);
4490
- }
4491
- catch (err)
4492
- {
4493
- if(request.error)
4494
- request.error(err);
4495
- else
4496
- throw err;
4497
- }
4498
- }
4499
- else if(request.dataType == "xml")
4500
- {
4501
- try
4502
- {
4503
- var xmlparser = new DOMParser();
4504
- response = xmlparser.parseFromString(response,"text/xml");
4505
- }
4506
- catch (err)
4507
- {
4508
- if(request.error)
4509
- request.error(err);
4510
- else
4511
- throw err;
4512
- }
4513
- }
4514
- if(request.success)
4515
- request.success.call(this, response, this);
4516
- };
4517
- xhr.onerror = function(err) {
4518
- if(request.error)
4519
- request.error(err);
4520
- };
4521
-
4522
- var data = new FormData();
4523
- if( request.data )
4524
- {
4525
- for( var i in request.data)
4526
- data.append(i,request.data[ i ]);
4527
- }
4528
-
4529
- xhr.send( data );
4530
- return xhr;
4531
- },
4532
-
4533
- /**
4534
- * Request file from url
4535
- * @method requestText
4536
- * @param {String} url
4537
- * @param {Function} onComplete
4538
- * @param {Function} onError
4539
- **/
4540
- requestText( url, onComplete, onError ) {
4541
- return this.request({ url: url, dataType:"text", success: onComplete, error: onError });
4542
- },
4543
-
4544
- /**
4545
- * Request file from url
4546
- * @method requestJSON
4547
- * @param {String} url
4548
- * @param {Function} onComplete
4549
- * @param {Function} onError
4550
- **/
4551
- requestJSON( url, onComplete, onError ) {
4552
- return this.request({ url: url, dataType:"json", success: onComplete, error: onError });
4553
- },
4554
-
4555
- /**
4556
- * Request binary file from url
4557
- * @method requestBinary
4558
- * @param {String} url
4559
- * @param {Function} onComplete
4560
- * @param {Function} onError
4561
- **/
4562
- requestBinary( url, onComplete, onError ) {
4563
- return this.request({ url: url, dataType:"binary", success: onComplete, error: onError });
4564
- },
4565
-
4566
- /**
4567
- * Request script and inserts it in the DOM
4568
- * @method requireScript
4569
- * @param {String|Array} url the url of the script or an array containing several urls
4570
- * @param {Function} onComplete
4571
- * @param {Function} onError
4572
- * @param {Function} onProgress (if several files are required, onProgress is called after every file is added to the DOM)
4573
- **/
4574
- requireScript( url, onComplete, onError, onProgress, version ) {
4575
-
4576
- if(!url)
4577
- throw("invalid URL");
4578
-
4579
- if( url.constructor === String )
4580
- url = [url];
4581
-
4582
- var total = url.length;
4583
- var loaded_scripts = [];
4584
-
4585
- for( var i in url)
4586
- {
4587
- var script = document.createElement('script');
4588
- script.num = i;
4589
- script.type = 'text/javascript';
4590
- script.src = url[ i ] + ( version ? "?version=" + version : "" );
4591
- script.original_src = url[ i ];
4592
- script.async = false;
4593
- script.onload = function( e ) {
4594
- total--;
4595
- loaded_scripts.push(this);
4596
- if(total)
4597
- {
4598
- if( onProgress )
4599
- {
4600
- onProgress( this.original_src, this.num );
4601
- }
4602
- }
4603
- else if(onComplete)
4604
- onComplete( loaded_scripts );
4605
- };
4606
- if(onError)
4607
- script.onerror = function(err) {
4608
- onError(err, this.original_src, this.num );
4609
- };
4610
- document.getElementsByTagName('head')[ 0 ].appendChild(script);
4611
- }
4612
- },
4613
-
4614
- loadScriptSync( url ) {
4615
- return new Promise((resolve, reject) => {
4616
- const script = document.createElement( "script" );
4617
- script.src = url;
4618
- script.async = false;
4619
- script.onload = () => resolve();
4620
- script.onerror = () => reject(new Error(`Failed to load ${url}`));
4621
- document.head.appendChild( script );
4622
- });
4623
- },
4624
-
4625
- downloadURL( url, filename ) {
4626
-
4627
- const fr = new FileReader();
4628
-
4629
- const _download = function(_url) {
4630
- var link = document.createElement('a');
4631
- link.href = _url;
4632
- link.download = filename;
4633
- document.body.appendChild(link);
4634
- link.click();
4635
- document.body.removeChild(link);
4636
- };
4637
-
4638
- if( url.includes('http') )
4639
- {
4640
- LX.request({ url: url, dataType: 'blob', success: (f) => {
4641
- fr.readAsDataURL( f );
4642
- fr.onload = e => {
4643
- _download(e.currentTarget.result);
4644
- };
4645
- } });
4646
- }else
4647
- {
4648
- _download(url);
4649
- }
4650
-
4651
- },
4652
-
4653
- downloadFile: function( filename, data, dataType ) {
4654
- if(!data)
4655
- {
4656
- console.warn("No file provided to download");
4657
- return;
4658
- }
4659
-
4660
- if(!dataType)
4661
- {
4662
- if(data.constructor === String )
4663
- dataType = 'text/plain';
4664
- else
4665
- dataType = 'application/octet-stream';
4666
- }
4667
-
4668
- var file = null;
4669
- if(data.constructor !== File && data.constructor !== Blob)
4670
- file = new Blob( [ data ], {type : dataType});
4671
- else
4672
- file = data;
4673
-
4674
- var url = URL.createObjectURL( file );
4675
- var element = document.createElement("a");
4676
- element.setAttribute('href', url);
4677
- element.setAttribute('download', filename );
4678
- element.style.display = 'none';
4679
- document.body.appendChild(element);
4680
- element.click();
4681
- document.body.removeChild(element);
4682
- setTimeout( function(){ URL.revokeObjectURL( url ); }, 1000*60 ); //wait one minute to revoke url
4683
- }
4684
- });
4685
-
4686
4063
  Object.defineProperty(String.prototype, 'lastChar', {
4687
4064
  get: function() { return this[ this.length - 1 ]; },
4688
4065
  enumerable: true,
@@ -4731,8 +4108,8 @@ Element.prototype.ignore = function( eventName, callbackName ) {
4731
4108
  callbackName = callbackName ?? ( "_on" + eventName );
4732
4109
  const callback = this[ callbackName ];
4733
4110
  this.removeEventListener( eventName, callback );
4734
- };
4735
-
4111
+ };
4112
+
4736
4113
  // icons.js @jxarco
4737
4114
 
4738
4115
  const RAW_ICONS = {
@@ -4910,8 +4287,8 @@ LX.LucideIconAlias = {
4910
4287
  "RotateRight": "RotateCw",
4911
4288
  "RotateBack": "RotateCcw",
4912
4289
  "RotateLeft": "RotateCcw",
4913
- };
4914
-
4290
+ };
4291
+
4915
4292
  // utils.js @jxarco
4916
4293
 
4917
4294
  function clamp( num, min, max ) { return Math.min( Math.max( num, min ), max ); }
@@ -4960,15 +4337,39 @@ function doAsync( fn, ms ) {
4960
4337
  LX.doAsync = doAsync;
4961
4338
 
4962
4339
  /**
4963
- * @method getSupportedDOMName
4964
- * @description Convert a text string to a valid DOM name
4965
- * @param {String} text Original text
4340
+ * @method flushCss
4341
+ * @description By reading the offsetHeight property, we are forcing the browser to flush
4342
+ * the pending CSS changes (which it does to ensure the value obtained is accurate).
4343
+ * @param {HTMLElement} element
4966
4344
  */
4967
- function getSupportedDOMName( text )
4345
+ function flushCss( element )
4968
4346
  {
4969
- console.assert( typeof text == "string", "getSupportedDOMName: Text is not a string!" );
4347
+ element.offsetHeight;
4348
+ }
4970
4349
 
4971
- let name = text.trim();
4350
+ LX.flushCss = flushCss;
4351
+
4352
+ /**
4353
+ * @method deleteElement
4354
+ * @param {HTMLElement} element
4355
+ */
4356
+ function deleteElement( element )
4357
+ {
4358
+ if( element !== undefined ) element.remove();
4359
+ }
4360
+
4361
+ LX.deleteElement = deleteElement;
4362
+
4363
+ /**
4364
+ * @method getSupportedDOMName
4365
+ * @description Convert a text string to a valid DOM name
4366
+ * @param {String} text Original text
4367
+ */
4368
+ function getSupportedDOMName( text )
4369
+ {
4370
+ console.assert( typeof text == "string", "getSupportedDOMName: Text is not a string!" );
4371
+
4372
+ let name = text.trim();
4972
4373
 
4973
4374
  // Replace specific known symbols
4974
4375
  name = name.replace( /@/g, '_at_' ).replace( /\+/g, '_plus_' ).replace( /\./g, '_dot_' );
@@ -5029,9 +4430,13 @@ LX.stripHTML = stripHTML;
5029
4430
  * @param {Number|String} size
5030
4431
  * @param {Number} total
5031
4432
  */
5032
- const parsePixelSize = ( size, total ) => {
5033
-
5034
- if( size.constructor === Number ) { return size; } // Assuming pixels..
4433
+ function parsePixelSize( size, total )
4434
+ {
4435
+ // Assuming pixels..
4436
+ if( size.constructor === Number )
4437
+ {
4438
+ return size;
4439
+ }
5035
4440
 
5036
4441
  if( size.constructor === String )
5037
4442
  {
@@ -5069,7 +4474,7 @@ const parsePixelSize = ( size, total ) => {
5069
4474
  }
5070
4475
 
5071
4476
  throw( "Bad size format!" );
5072
- };
4477
+ }
5073
4478
 
5074
4479
  LX.parsePixelSize = parsePixelSize;
5075
4480
 
@@ -5332,7 +4737,7 @@ function measureRealWidth( value, paddingPlusMargin = 8 )
5332
4737
  i.innerHTML = value;
5333
4738
  document.body.appendChild( i );
5334
4739
  var rect = i.getBoundingClientRect();
5335
- LX.UTILS.deleteElement( i );
4740
+ LX.deleteElement( i );
5336
4741
  return rect.width + paddingPlusMargin;
5337
4742
  }
5338
4743
 
@@ -5761,200 +5166,846 @@ function makeIcon( iconName, options = { } )
5761
5166
  } );
5762
5167
  }
5763
5168
 
5764
- const path = document.createElement( "path" );
5765
- path.setAttribute( "fill", "currentColor" );
5766
- path.setAttribute( "d", data[ 4 ] );
5767
- svg.appendChild( path );
5169
+ const path = document.createElement( "path" );
5170
+ path.setAttribute( "fill", "currentColor" );
5171
+ path.setAttribute( "d", data[ 4 ] );
5172
+ svg.appendChild( path );
5173
+
5174
+ if( data[ 5 ] )
5175
+ {
5176
+ const classes = data[ 5 ].pathClass;
5177
+ classes?.split( ' ' ).forEach( c => {
5178
+ path.classList.add( c );
5179
+ } );
5180
+
5181
+ const attrs = data[ 5 ].pathAttributes;
5182
+ attrs?.split( ' ' ).forEach( attr => {
5183
+ const t = attr.split( '=' );
5184
+ path.setAttribute( t[ 0 ], t[ 1 ] );
5185
+ } );
5186
+ }
5187
+
5188
+ const faLicense = `<!-- This icon might belong to a collection from Iconify - https://iconify.design/ - or !Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc. -->`;
5189
+ svg.innerHTML += faLicense;
5190
+ return _createIconFromSVG( svg );
5191
+ }
5192
+ }
5193
+
5194
+ // Fallback to Lucide icon
5195
+ console.assert( lucideData, `No existing icon named _${ iconName }_` );
5196
+ svg = lucide.createElement( lucideData, options );
5197
+
5198
+ return _createIconFromSVG( svg );
5199
+ }
5200
+
5201
+ LX.makeIcon = makeIcon;
5202
+
5203
+ /**
5204
+ * @method registerIcon
5205
+ * @description Register an SVG icon to LX.ICONS
5206
+ * @param {String} iconName
5207
+ * @param {String} svgString
5208
+ * @param {String} variant
5209
+ * @param {Array} aliases
5210
+ */
5211
+ function registerIcon( iconName, svgString, variant = "none", aliases = [] )
5212
+ {
5213
+ const svg = new DOMParser().parseFromString( svgString, 'image/svg+xml' ).documentElement;
5214
+ const path = svg.querySelector( "path" );
5215
+ const viewBox = svg.getAttribute( "viewBox" ).split( ' ' );
5216
+ const pathData = path.getAttribute( 'd' );
5217
+
5218
+ let svgAttributes = [];
5219
+ let pathAttributes = [];
5220
+
5221
+ for( const attr of svg.attributes )
5222
+ {
5223
+ switch( attr.name )
5224
+ {
5225
+ case "transform":
5226
+ case "fill":
5227
+ case "stroke-width":
5228
+ case "stroke-linecap":
5229
+ case "stroke-linejoin":
5230
+ svgAttributes.push( `${ attr.name }=${ attr.value }` );
5231
+ break;
5232
+ }
5233
+ }
5234
+
5235
+ for( const attr of path.attributes )
5236
+ {
5237
+ switch( attr.name )
5238
+ {
5239
+ case "transform":
5240
+ case "fill":
5241
+ case "stroke-width":
5242
+ case "stroke-linecap":
5243
+ case "stroke-linejoin":
5244
+ pathAttributes.push( `${ attr.name }=${ attr.value }` );
5245
+ break;
5246
+ }
5247
+ }
5248
+
5249
+ const iconData = [
5250
+ parseInt( viewBox[ 2 ] ),
5251
+ parseInt( viewBox[ 3 ] ),
5252
+ aliases,
5253
+ variant,
5254
+ pathData,
5255
+ {
5256
+ svgAttributes: svgAttributes.length ? svgAttributes.join( ' ' ) : null,
5257
+ pathAttributes: pathAttributes.length ? pathAttributes.join( ' ' ) : null
5258
+ }
5259
+ ];
5260
+
5261
+ if( LX.ICONS[ iconName ] )
5262
+ {
5263
+ console.warn( `${ iconName } will be added/replaced in LX.ICONS` );
5264
+ }
5265
+
5266
+ LX.ICONS[ iconName ] = iconData;
5267
+ }
5268
+
5269
+ LX.registerIcon = registerIcon;
5270
+
5271
+ /**
5272
+ * @method registerCommandbarEntry
5273
+ * @description Adds an extra command bar entry
5274
+ * @param {String} name
5275
+ * @param {Function} callback
5276
+ */
5277
+ function registerCommandbarEntry( name, callback )
5278
+ {
5279
+ LX.extraCommandbarEntries.push( { name, callback } );
5280
+ }
5281
+
5282
+ LX.registerCommandbarEntry = registerCommandbarEntry;
5283
+
5284
+ /*
5285
+ Dialog and Notification Elements
5286
+ */
5287
+
5288
+ /**
5289
+ * @method message
5290
+ * @param {String} text
5291
+ * @param {String} title (Optional)
5292
+ * @param {Object} options
5293
+ * id: Id of the message dialog
5294
+ * position: Dialog position in screen [screen centered]
5295
+ * draggable: Dialog can be dragged [false]
5296
+ */
5297
+
5298
+ function message( text, title, options = {} )
5299
+ {
5300
+ if( !text )
5301
+ {
5302
+ throw( "No message to show" );
5303
+ }
5304
+
5305
+ options.modal = true;
5306
+
5307
+ return new LX.Dialog( title, p => {
5308
+ p.addTextArea( null, text, null, { disabled: true, fitHeight: true } );
5309
+ }, options );
5310
+ }
5311
+
5312
+ LX.message = message;
5313
+
5314
+ /**
5315
+ * @method popup
5316
+ * @param {String} text
5317
+ * @param {String} title (Optional)
5318
+ * @param {Object} options
5319
+ * id: Id of the message dialog
5320
+ * timeout (Number): Delay time before it closes automatically (ms). Default: [3000]
5321
+ * position (Array): [x,y] Dialog position in screen. Default: [screen centered]
5322
+ * size (Array): [width, height]
5323
+ */
5324
+
5325
+ function popup( text, title, options = {} )
5326
+ {
5327
+ if( !text )
5328
+ {
5329
+ throw("No message to show");
5330
+ }
5331
+
5332
+ options.size = options.size ?? [ "max-content", "auto" ];
5333
+ options.class = "lexpopup";
5334
+
5335
+ const time = options.timeout || 3000;
5336
+ const dialog = new LX.Dialog( title, p => {
5337
+ p.addTextArea( null, text, null, { disabled: true, fitHeight: true } );
5338
+ }, options );
5339
+
5340
+ setTimeout( () => {
5341
+ dialog.close();
5342
+ }, Math.max( time, 150 ) );
5343
+
5344
+ return dialog;
5345
+ }
5346
+
5347
+ LX.popup = popup;
5348
+
5349
+ /**
5350
+ * @method prompt
5351
+ * @param {String} text
5352
+ * @param {String} title (Optional)
5353
+ * @param {Object} options
5354
+ * id: Id of the prompt dialog
5355
+ * position: Dialog position in screen [screen centered]
5356
+ * draggable: Dialog can be dragged [false]
5357
+ * input: If false, no text input appears
5358
+ * accept: Accept text
5359
+ * required: Input has to be filled [true]. Default: false
5360
+ */
5361
+
5362
+ function prompt( text, title, callback, options = {} )
5363
+ {
5364
+ options.modal = true;
5365
+ options.className = "prompt";
5366
+
5367
+ let value = "";
5368
+
5369
+ const dialog = new LX.Dialog( title, p => {
5370
+
5371
+ p.addTextArea( null, text, null, { disabled: true, fitHeight: true } );
5372
+
5373
+ if( options.input ?? true )
5374
+ {
5375
+ p.addText( null, options.input || value, v => value = v, { placeholder: "..." } );
5376
+ }
5377
+
5378
+ p.sameLine( 2 );
5379
+
5380
+ p.addButton(null, "Cancel", () => {if(options.on_cancel) options.on_cancel(); dialog.close();} );
5381
+
5382
+ p.addButton( null, options.accept || "Continue", () => {
5383
+ if( options.required && value === '' )
5384
+ {
5385
+ text += text.includes("You must fill the input text.") ? "": "\nYou must fill the input text.";
5386
+ dialog.close();
5387
+ prompt( text, title, callback, options );
5388
+ }
5389
+ else
5390
+ {
5391
+ if( callback ) callback.call( this, value );
5392
+ dialog.close();
5393
+ }
5394
+ }, { buttonClass: "primary" });
5395
+
5396
+ }, options );
5397
+
5398
+ // Focus text prompt
5399
+ if( options.input ?? true )
5400
+ {
5401
+ dialog.root.querySelector( 'input' ).focus();
5402
+ }
5403
+
5404
+ return dialog;
5405
+ }
5406
+
5407
+ LX.prompt = prompt;
5408
+
5409
+ /**
5410
+ * @method toast
5411
+ * @param {String} title
5412
+ * @param {String} description (Optional)
5413
+ * @param {Object} options
5414
+ * action: Data of the custom action { name, callback }
5415
+ * closable: Allow closing the toast
5416
+ * timeout: Time in which the toast closed automatically, in ms. -1 means persistent. [3000]
5417
+ */
5418
+
5419
+ function toast( title, description, options = {} )
5420
+ {
5421
+ if( !title )
5422
+ {
5423
+ throw( "The toast needs at least a title!" );
5424
+ }
5425
+
5426
+ console.assert( this.notifications );
5427
+
5428
+ const toast = document.createElement( "li" );
5429
+ toast.className = "lextoast";
5430
+ toast.style.translate = "0 calc(100% + 30px)";
5431
+ this.notifications.prepend( toast );
5432
+
5433
+ LX.doAsync( () => {
5434
+
5435
+ if( this.notifications.offsetWidth > this.notifications.iWidth )
5436
+ {
5437
+ this.notifications.iWidth = Math.min( this.notifications.offsetWidth, 480 );
5438
+ this.notifications.style.width = this.notifications.iWidth + "px";
5439
+ }
5440
+
5441
+ toast.dataset[ "open" ] = true;
5442
+ }, 10 );
5443
+
5444
+ const content = document.createElement( "div" );
5445
+ content.className = "lextoastcontent";
5446
+ toast.appendChild( content );
5447
+
5448
+ const titleContent = document.createElement( "div" );
5449
+ titleContent.className = "title";
5450
+ titleContent.innerHTML = title;
5451
+ content.appendChild( titleContent );
5452
+
5453
+ if( description )
5454
+ {
5455
+ const desc = document.createElement( "div" );
5456
+ desc.className = "desc";
5457
+ desc.innerHTML = description;
5458
+ content.appendChild( desc );
5459
+ }
5460
+
5461
+ if( options.action )
5462
+ {
5463
+ const panel = new LX.Panel();
5464
+ panel.addButton(null, options.action.name ?? "Accept", options.action.callback.bind( this, toast ), { width: "auto", maxWidth: "150px", className: "right", buttonClass: "border" });
5465
+ toast.appendChild( panel.root.childNodes[ 0 ] );
5466
+ }
5467
+
5468
+ const that = this;
5469
+
5470
+ toast.close = function() {
5471
+ this.dataset[ "closed" ] = true;
5472
+ LX.doAsync( () => {
5473
+ this.remove();
5474
+ if( !that.notifications.childElementCount )
5475
+ {
5476
+ that.notifications.style.width = "unset";
5477
+ that.notifications.iWidth = 0;
5478
+ }
5479
+ }, 500 );
5480
+ };
5481
+
5482
+ if( options.closable ?? true )
5483
+ {
5484
+ const closeIcon = LX.makeIcon( "X", { iconClass: "closer" } );
5485
+ closeIcon.addEventListener( "click", () => {
5486
+ toast.close();
5487
+ } );
5488
+ toast.appendChild( closeIcon );
5489
+ }
5490
+
5491
+ const timeout = options.timeout ?? 3000;
5492
+
5493
+ if( timeout != -1 )
5494
+ {
5495
+ LX.doAsync( () => {
5496
+ toast.close();
5497
+ }, timeout );
5498
+ }
5499
+ }
5500
+
5501
+ LX.toast = toast;
5502
+
5503
+ /**
5504
+ * @method badge
5505
+ * @param {String} text
5506
+ * @param {String} className
5507
+ * @param {Object} options
5508
+ * style: Style attributes to override
5509
+ * asElement: Returns the badge as HTMLElement [false]
5510
+ */
5511
+
5512
+ function badge( text, className, options = {} )
5513
+ {
5514
+ const container = document.createElement( "div" );
5515
+ container.innerHTML = text;
5516
+ container.className = "lexbadge " + ( className ?? "" );
5517
+ Object.assign( container.style, options.style ?? {} );
5518
+ return ( options.asElement ?? false ) ? container : container.outerHTML;
5519
+ }
5520
+
5521
+ LX.badge = badge;
5522
+
5523
+ /**
5524
+ * @method makeElement
5525
+ * @param {String} htmlType
5526
+ * @param {String} className
5527
+ * @param {String} innerHTML
5528
+ * @param {HTMLElement} parent
5529
+ * @param {Object} overrideStyle
5530
+ */
5531
+
5532
+ function makeElement( htmlType, className, innerHTML, parent, overrideStyle = {} )
5533
+ {
5534
+ const element = document.createElement( htmlType );
5535
+ element.className = className ?? "";
5536
+ element.innerHTML = innerHTML ?? "";
5537
+ Object.assign( element.style, overrideStyle );
5538
+
5539
+ if( parent )
5540
+ {
5541
+ if( parent.attach ) // Use attach method if possible
5542
+ {
5543
+ parent.attach( element );
5544
+ }
5545
+ else // its a native HTMLElement
5546
+ {
5547
+ parent.appendChild( element );
5548
+ }
5549
+ }
5550
+
5551
+ return element;
5552
+ }
5553
+
5554
+ LX.makeElement = makeElement;
5555
+
5556
+ /**
5557
+ * @method makeContainer
5558
+ * @param {Array} size
5559
+ * @param {String} className
5560
+ * @param {String} innerHTML
5561
+ * @param {HTMLElement} parent
5562
+ * @param {Object} overrideStyle
5563
+ */
5564
+
5565
+ function makeContainer( size, className, innerHTML, parent, overrideStyle = {} )
5566
+ {
5567
+ const container = LX.makeElement( "div", "lexcontainer " + ( className ?? "" ), innerHTML, parent, overrideStyle );
5568
+ container.style.width = size && size[ 0 ] ? size[ 0 ] : "100%";
5569
+ container.style.height = size && size[ 1 ] ? size[ 1 ] : "100%";
5570
+ return container;
5571
+ }
5572
+
5573
+ LX.makeContainer = makeContainer;
5574
+
5575
+ /**
5576
+ * @method asTooltip
5577
+ * @param {HTMLElement} trigger
5578
+ * @param {String} content
5579
+ * @param {Object} options
5580
+ * side: Side of the tooltip
5581
+ * offset: Tooltip margin offset
5582
+ * active: Tooltip active by default [true]
5583
+ */
5584
+
5585
+ function asTooltip( trigger, content, options = {} )
5586
+ {
5587
+ console.assert( trigger, "You need a trigger to generate a tooltip!" );
5588
+
5589
+ trigger.dataset[ "disableTooltip" ] = !( options.active ?? true );
5590
+
5591
+ let tooltipDom = null;
5592
+
5593
+ trigger.addEventListener( "mouseenter", function(e) {
5594
+
5595
+ if( trigger.dataset[ "disableTooltip" ] == "true" )
5596
+ {
5597
+ return;
5598
+ }
5599
+
5600
+ LX.root.querySelectorAll( ".lextooltip" ).forEach( e => e.remove() );
5601
+
5602
+ tooltipDom = document.createElement( "div" );
5603
+ tooltipDom.className = "lextooltip";
5604
+ tooltipDom.innerHTML = content;
5605
+
5606
+ LX.doAsync( () => {
5607
+
5608
+ const position = [ 0, 0 ];
5609
+ const rect = this.getBoundingClientRect();
5610
+ const offset = options.offset ?? 6;
5611
+ let alignWidth = true;
5612
+
5613
+ switch( options.side ?? "top" )
5614
+ {
5615
+ case "left":
5616
+ position[ 0 ] += ( rect.x - tooltipDom.offsetWidth - offset );
5617
+ alignWidth = false;
5618
+ break;
5619
+ case "right":
5620
+ position[ 0 ] += ( rect.x + rect.width + offset );
5621
+ alignWidth = false;
5622
+ break;
5623
+ case "top":
5624
+ position[ 1 ] += ( rect.y - tooltipDom.offsetHeight - offset );
5625
+ alignWidth = true;
5626
+ break;
5627
+ case "bottom":
5628
+ position[ 1 ] += ( rect.y + rect.height + offset );
5629
+ alignWidth = true;
5630
+ break;
5631
+ }
5632
+
5633
+ if( alignWidth ) { position[ 0 ] += ( rect.x + rect.width * 0.5 ) - tooltipDom.offsetWidth * 0.5; }
5634
+ else { position[ 1 ] += ( rect.y + rect.height * 0.5 ) - tooltipDom.offsetHeight * 0.5; }
5635
+
5636
+ // Avoid collisions
5637
+ position[ 0 ] = LX.clamp( position[ 0 ], 0, window.innerWidth - tooltipDom.offsetWidth - 4 );
5638
+ position[ 1 ] = LX.clamp( position[ 1 ], 0, window.innerHeight - tooltipDom.offsetHeight - 4 );
5639
+
5640
+ tooltipDom.style.left = `${ position[ 0 ] }px`;
5641
+ tooltipDom.style.top = `${ position[ 1 ] }px`;
5642
+ } );
5643
+
5644
+ LX.root.appendChild( tooltipDom );
5645
+ } );
5646
+
5647
+ trigger.addEventListener( "mouseleave", function(e) {
5648
+ if( tooltipDom )
5649
+ {
5650
+ tooltipDom.remove();
5651
+ }
5652
+ } );
5653
+ }
5654
+
5655
+ LX.asTooltip = asTooltip;
5656
+
5657
+ /*
5658
+ * Requests
5659
+ */
5660
+
5661
+ Object.assign(LX, {
5662
+
5663
+ /**
5664
+ * Request file from url (it could be a binary, text, etc.). If you want a simplied version use
5665
+ * @method request
5666
+ * @param {Object} request object with all the parameters like data (for sending forms), dataType, success, error
5667
+ * @param {Function} on_complete
5668
+ **/
5669
+ request( request ) {
5670
+
5671
+ var dataType = request.dataType || "text";
5672
+ if(dataType == "json") //parse it locally
5673
+ dataType = "text";
5674
+ else if(dataType == "xml") //parse it locally
5675
+ dataType = "text";
5676
+ else if (dataType == "binary")
5677
+ {
5678
+ //request.mimeType = "text/plain; charset=x-user-defined";
5679
+ dataType = "arraybuffer";
5680
+ request.mimeType = "application/octet-stream";
5681
+ }
5682
+
5683
+ //regular case, use AJAX call
5684
+ var xhr = new XMLHttpRequest();
5685
+ xhr.open( request.data ? 'POST' : 'GET', request.url, true);
5686
+ if(dataType)
5687
+ xhr.responseType = dataType;
5688
+ if (request.mimeType)
5689
+ xhr.overrideMimeType( request.mimeType );
5690
+ if( request.nocache )
5691
+ xhr.setRequestHeader('Cache-Control', 'no-cache');
5692
+
5693
+ xhr.onload = function(load)
5694
+ {
5695
+ var response = this.response;
5696
+ if( this.status != 200)
5697
+ {
5698
+ var err = "Error " + this.status;
5699
+ if(request.error)
5700
+ request.error(err);
5701
+ return;
5702
+ }
5703
+
5704
+ if(request.dataType == "json") //chrome doesnt support json format
5705
+ {
5706
+ try
5707
+ {
5708
+ response = JSON.parse(response);
5709
+ }
5710
+ catch (err)
5711
+ {
5712
+ if(request.error)
5713
+ request.error(err);
5714
+ else
5715
+ throw err;
5716
+ }
5717
+ }
5718
+ else if(request.dataType == "xml")
5719
+ {
5720
+ try
5721
+ {
5722
+ var xmlparser = new DOMParser();
5723
+ response = xmlparser.parseFromString(response,"text/xml");
5724
+ }
5725
+ catch (err)
5726
+ {
5727
+ if(request.error)
5728
+ request.error(err);
5729
+ else
5730
+ throw err;
5731
+ }
5732
+ }
5733
+ if(request.success)
5734
+ request.success.call(this, response, this);
5735
+ };
5736
+ xhr.onerror = function(err) {
5737
+ if(request.error)
5738
+ request.error(err);
5739
+ };
5740
+
5741
+ var data = new FormData();
5742
+ if( request.data )
5743
+ {
5744
+ for( var i in request.data)
5745
+ data.append(i,request.data[ i ]);
5746
+ }
5747
+
5748
+ xhr.send( data );
5749
+ return xhr;
5750
+ },
5751
+
5752
+ /**
5753
+ * Request file from url
5754
+ * @method requestText
5755
+ * @param {String} url
5756
+ * @param {Function} onComplete
5757
+ * @param {Function} onError
5758
+ **/
5759
+ requestText( url, onComplete, onError ) {
5760
+ return this.request({ url: url, dataType:"text", success: onComplete, error: onError });
5761
+ },
5762
+
5763
+ /**
5764
+ * Request file from url
5765
+ * @method requestJSON
5766
+ * @param {String} url
5767
+ * @param {Function} onComplete
5768
+ * @param {Function} onError
5769
+ **/
5770
+ requestJSON( url, onComplete, onError ) {
5771
+ return this.request({ url: url, dataType:"json", success: onComplete, error: onError });
5772
+ },
5773
+
5774
+ /**
5775
+ * Request binary file from url
5776
+ * @method requestBinary
5777
+ * @param {String} url
5778
+ * @param {Function} onComplete
5779
+ * @param {Function} onError
5780
+ **/
5781
+ requestBinary( url, onComplete, onError ) {
5782
+ return this.request({ url: url, dataType:"binary", success: onComplete, error: onError });
5783
+ },
5784
+
5785
+ /**
5786
+ * Request script and inserts it in the DOM
5787
+ * @method requireScript
5788
+ * @param {String|Array} url the url of the script or an array containing several urls
5789
+ * @param {Function} onComplete
5790
+ * @param {Function} onError
5791
+ * @param {Function} onProgress (if several files are required, onProgress is called after every file is added to the DOM)
5792
+ **/
5793
+ requireScript( url, onComplete, onError, onProgress, version ) {
5768
5794
 
5769
- if( data[ 5 ] )
5770
- {
5771
- const classes = data[ 5 ].pathClass;
5772
- classes?.split( ' ' ).forEach( c => {
5773
- path.classList.add( c );
5774
- } );
5795
+ if(!url)
5796
+ throw("invalid URL");
5775
5797
 
5776
- const attrs = data[ 5 ].pathAttributes;
5777
- attrs?.split( ' ' ).forEach( attr => {
5778
- const t = attr.split( '=' );
5779
- path.setAttribute( t[ 0 ], t[ 1 ] );
5780
- } );
5781
- }
5798
+ if( url.constructor === String )
5799
+ url = [url];
5782
5800
 
5783
- const faLicense = `<!-- This icon might belong to a collection from Iconify - https://iconify.design/ - or !Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc. -->`;
5784
- svg.innerHTML += faLicense;
5785
- return _createIconFromSVG( svg );
5786
- }
5787
- }
5801
+ var total = url.length;
5802
+ var loaded_scripts = [];
5788
5803
 
5789
- // Fallback to Lucide icon
5790
- console.assert( lucideData, `No existing icon named _${ iconName }_` );
5791
- svg = lucide.createElement( lucideData, options );
5804
+ for( var i in url)
5805
+ {
5806
+ var script = document.createElement('script');
5807
+ script.num = i;
5808
+ script.type = 'text/javascript';
5809
+ script.src = url[ i ] + ( version ? "?version=" + version : "" );
5810
+ script.original_src = url[ i ];
5811
+ script.async = false;
5812
+ script.onload = function( e ) {
5813
+ total--;
5814
+ loaded_scripts.push(this);
5815
+ if(total)
5816
+ {
5817
+ if( onProgress )
5818
+ {
5819
+ onProgress( this.original_src, this.num );
5820
+ }
5821
+ }
5822
+ else if(onComplete)
5823
+ onComplete( loaded_scripts );
5824
+ };
5825
+ if(onError)
5826
+ script.onerror = function(err) {
5827
+ onError(err, this.original_src, this.num );
5828
+ };
5829
+ document.getElementsByTagName('head')[ 0 ].appendChild(script);
5830
+ }
5831
+ },
5792
5832
 
5793
- return _createIconFromSVG( svg );
5794
- }
5833
+ loadScriptSync( url ) {
5834
+ return new Promise((resolve, reject) => {
5835
+ const script = document.createElement( "script" );
5836
+ script.src = url;
5837
+ script.async = false;
5838
+ script.onload = () => resolve();
5839
+ script.onerror = () => reject(new Error(`Failed to load ${url}`));
5840
+ document.head.appendChild( script );
5841
+ });
5842
+ },
5795
5843
 
5796
- LX.makeIcon = makeIcon;
5844
+ downloadURL( url, filename ) {
5797
5845
 
5798
- /**
5799
- * @method registerIcon
5800
- * @description Register an SVG icon to LX.ICONS
5801
- * @param {String} iconName
5802
- * @param {String} svgString
5803
- * @param {String} variant
5804
- * @param {Array} aliases
5805
- */
5806
- function registerIcon( iconName, svgString, variant = "none", aliases = [] )
5807
- {
5808
- const svg = new DOMParser().parseFromString( svgString, 'image/svg+xml' ).documentElement;
5809
- const path = svg.querySelector( "path" );
5810
- const viewBox = svg.getAttribute( "viewBox" ).split( ' ' );
5811
- const pathData = path.getAttribute( 'd' );
5846
+ const fr = new FileReader();
5812
5847
 
5813
- let svgAttributes = [];
5814
- let pathAttributes = [];
5848
+ const _download = function(_url) {
5849
+ var link = document.createElement('a');
5850
+ link.href = _url;
5851
+ link.download = filename;
5852
+ document.body.appendChild(link);
5853
+ link.click();
5854
+ document.body.removeChild(link);
5855
+ };
5815
5856
 
5816
- for( const attr of svg.attributes )
5817
- {
5818
- switch( attr.name )
5857
+ if( url.includes('http') )
5819
5858
  {
5820
- case "transform":
5821
- case "fill":
5822
- case "stroke-width":
5823
- case "stroke-linecap":
5824
- case "stroke-linejoin":
5825
- svgAttributes.push( `${ attr.name }=${ attr.value }` );
5826
- break;
5859
+ LX.request({ url: url, dataType: 'blob', success: (f) => {
5860
+ fr.readAsDataURL( f );
5861
+ fr.onload = e => {
5862
+ _download(e.currentTarget.result);
5863
+ };
5864
+ } });
5865
+ }else
5866
+ {
5867
+ _download(url);
5827
5868
  }
5828
- }
5829
5869
 
5830
- for( const attr of path.attributes )
5831
- {
5832
- switch( attr.name )
5870
+ },
5871
+
5872
+ downloadFile: function( filename, data, dataType ) {
5873
+ if(!data)
5833
5874
  {
5834
- case "transform":
5835
- case "fill":
5836
- case "stroke-width":
5837
- case "stroke-linecap":
5838
- case "stroke-linejoin":
5839
- pathAttributes.push( `${ attr.name }=${ attr.value }` );
5840
- break;
5875
+ console.warn("No file provided to download");
5876
+ return;
5841
5877
  }
5842
- }
5843
5878
 
5844
- const iconData = [
5845
- parseInt( viewBox[ 2 ] ),
5846
- parseInt( viewBox[ 3 ] ),
5847
- aliases,
5848
- variant,
5849
- pathData,
5879
+ if(!dataType)
5850
5880
  {
5851
- svgAttributes: svgAttributes.length ? svgAttributes.join( ' ' ) : null,
5852
- pathAttributes: pathAttributes.length ? pathAttributes.join( ' ' ) : null
5881
+ if(data.constructor === String )
5882
+ dataType = 'text/plain';
5883
+ else
5884
+ dataType = 'application/octet-stream';
5853
5885
  }
5854
- ];
5855
5886
 
5856
- if( LX.ICONS[ iconName ] )
5857
- {
5858
- console.warn( `${ iconName } will be added/replaced in LX.ICONS` );
5887
+ var file = null;
5888
+ if(data.constructor !== File && data.constructor !== Blob)
5889
+ file = new Blob( [ data ], {type : dataType});
5890
+ else
5891
+ file = data;
5892
+
5893
+ var url = URL.createObjectURL( file );
5894
+ var element = document.createElement("a");
5895
+ element.setAttribute('href', url);
5896
+ element.setAttribute('download', filename );
5897
+ element.style.display = 'none';
5898
+ document.body.appendChild(element);
5899
+ element.click();
5900
+ document.body.removeChild(element);
5901
+ setTimeout( function(){ URL.revokeObjectURL( url ); }, 1000*60 ); //wait one minute to revoke url
5859
5902
  }
5903
+ });
5860
5904
 
5861
- LX.ICONS[ iconName ] = iconData;
5905
+ /**
5906
+ * @method compareThreshold
5907
+ * @param {String} url
5908
+ * @param {Function} onComplete
5909
+ * @param {Function} onError
5910
+ **/
5911
+ function compareThreshold( v, p, n, t )
5912
+ {
5913
+ return Math.abs( v - p ) >= t || Math.abs( v - n ) >= t;
5862
5914
  }
5863
5915
 
5864
- LX.registerIcon = registerIcon;
5916
+ LX.compareThreshold = compareThreshold;
5865
5917
 
5866
5918
  /**
5867
- * @method registerCommandbarEntry
5868
- * @description Adds an extra command bar entry
5869
- * @param {String} name
5870
- * @param {Function} callback
5871
- */
5872
- function registerCommandbarEntry( name, callback )
5919
+ * @method compareThresholdRange
5920
+ * @param {String} url
5921
+ * @param {Function} onComplete
5922
+ * @param {Function} onError
5923
+ **/
5924
+ function compareThresholdRange( v0, v1, t0, t1 )
5873
5925
  {
5874
- LX.extraCommandbarEntries.push( { name, callback } );
5926
+ return v0 >= t0 && v0 <= t1 || v1 >= t0 && v1 <= t1 || v0 <= t0 && v1 >= t1;
5875
5927
  }
5876
5928
 
5877
- LX.registerCommandbarEntry = registerCommandbarEntry;
5929
+ LX.compareThresholdRange = compareThresholdRange;
5878
5930
 
5879
5931
  /**
5880
- * Utils package
5881
- * @namespace LX.UTILS
5882
- * @description Collection of utility functions
5883
- */
5884
-
5885
- LX.UTILS = {
5886
-
5887
- compareThreshold( v, p, n, t ) { return Math.abs(v - p) >= t || Math.abs(v - n) >= t },
5888
- compareThresholdRange( v0, v1, t0, t1 ) { return v0 >= t0 && v0 <= t1 || v1 >= t0 && v1 <= t1 || v0 <= t0 && v1 >= t1},
5889
- deleteElement( el ) { if( el ) el.remove(); },
5890
- flushCss(element) {
5891
- // By reading the offsetHeight property, we are forcing
5892
- // the browser to flush the pending CSS changes (which it
5893
- // does to ensure the value obtained is accurate).
5894
- element.offsetHeight;
5895
- },
5896
- getControlPoints( x0, y0, x1, y1, x2, y2, t ) {
5897
-
5898
- // x0,y0,x1,y1 are the coordinates of the end (knot) pts of this segment
5899
- // x2,y2 is the next knot -- not connected here but needed to calculate p2
5900
- // p1 is the control point calculated here, from x1 back toward x0.
5901
- // p2 is the next control point, calculated here and returned to become the
5902
- // next segment's p1.
5903
- // t is the 'tension' which controls how far the control points spread.
5932
+ * @method getControlPoints
5933
+ * @param {String} url
5934
+ * @param {Function} onComplete
5935
+ * @param {Function} onError
5936
+ **/
5937
+ function getControlPoints( x0, y0, x1, y1, x2, y2, t )
5938
+ {
5939
+ // x0,y0,x1,y1 are the coordinates of the end (knot) pts of this segment
5940
+ // x2,y2 is the next knot -- not connected here but needed to calculate p2
5941
+ // p1 is the control point calculated here, from x1 back toward x0.
5942
+ // p2 is the next control point, calculated here and returned to become the
5943
+ // next segment's p1.
5944
+ // t is the 'tension' which controls how far the control points spread.
5904
5945
 
5905
- // Scaling factors: distances from this knot to the previous and following knots.
5906
- var d01=Math.sqrt(Math.pow(x1-x0,2)+Math.pow(y1-y0,2));
5907
- var d12=Math.sqrt(Math.pow(x2-x1,2)+Math.pow(y2-y1,2));
5946
+ // Scaling factors: distances from this knot to the previous and following knots.
5947
+ var d01=Math.sqrt(Math.pow(x1-x0,2)+Math.pow(y1-y0,2));
5948
+ var d12=Math.sqrt(Math.pow(x2-x1,2)+Math.pow(y2-y1,2));
5908
5949
 
5909
- var fa=t*d01/(d01+d12);
5910
- var fb=t-fa;
5950
+ var fa=t*d01/(d01+d12);
5951
+ var fb=t-fa;
5911
5952
 
5912
- var p1x=x1+fa*(x0-x2);
5913
- var p1y=y1+fa*(y0-y2);
5953
+ var p1x=x1+fa*(x0-x2);
5954
+ var p1y=y1+fa*(y0-y2);
5914
5955
 
5915
- var p2x=x1-fb*(x0-x2);
5916
- var p2y=y1-fb*(y0-y2);
5956
+ var p2x=x1-fb*(x0-x2);
5957
+ var p2y=y1-fb*(y0-y2);
5917
5958
 
5918
- return [p1x,p1y,p2x,p2y]
5919
- },
5920
- drawSpline( ctx, pts, t ) {
5959
+ return [p1x,p1y,p2x,p2y]
5960
+ }
5921
5961
 
5922
- ctx.save();
5923
- var cp = []; // array of control points, as x0,y0,x1,y1,...
5924
- var n = pts.length;
5962
+ LX.getControlPoints = getControlPoints;
5925
5963
 
5926
- // Draw an open curve, not connected at the ends
5927
- for( var i = 0; i < (n - 4); i += 2 )
5928
- {
5929
- cp = cp.concat(LX.UTILS.getControlPoints(pts[ i ],pts[i+1],pts[i+2],pts[i+3],pts[i+4],pts[i+5],t));
5930
- }
5964
+ /**
5965
+ * @method drawSpline
5966
+ * @param {CanvasRenderingContext2D} ctx
5967
+ * @param {Array} pts
5968
+ * @param {Number} t
5969
+ **/
5970
+ function drawSpline( ctx, pts, t )
5971
+ {
5972
+ ctx.save();
5973
+ var cp = []; // array of control points, as x0,y0,x1,y1,...
5974
+ var n = pts.length;
5931
5975
 
5932
- for( var i = 2; i < ( pts.length - 5 ); i += 2 )
5933
- {
5934
- ctx.beginPath();
5935
- ctx.moveTo(pts[ i ], pts[i+1]);
5936
- ctx.bezierCurveTo(cp[2*i-2],cp[2*i-1],cp[2*i],cp[2*i+1],pts[i+2],pts[i+3]);
5937
- ctx.stroke();
5938
- ctx.closePath();
5939
- }
5976
+ // Draw an open curve, not connected at the ends
5977
+ for( var i = 0; i < (n - 4); i += 2 )
5978
+ {
5979
+ cp = cp.concat(LX.getControlPoints(pts[ i ],pts[i+1],pts[i+2],pts[i+3],pts[i+4],pts[i+5],t));
5980
+ }
5940
5981
 
5941
- // For open curves the first and last arcs are simple quadratics.
5982
+ for( var i = 2; i < ( pts.length - 5 ); i += 2 )
5983
+ {
5942
5984
  ctx.beginPath();
5943
- ctx.moveTo( pts[ 0 ], pts[ 1 ] );
5944
- ctx.quadraticCurveTo( cp[ 0 ], cp[ 1 ], pts[ 2 ], pts[ 3 ]);
5985
+ ctx.moveTo(pts[ i ], pts[i+1]);
5986
+ ctx.bezierCurveTo(cp[2*i-2],cp[2*i-1],cp[2*i],cp[2*i+1],pts[i+2],pts[i+3]);
5945
5987
  ctx.stroke();
5946
5988
  ctx.closePath();
5989
+ }
5947
5990
 
5948
- ctx.beginPath();
5949
- ctx.moveTo( pts[ n-2 ], pts[ n-1 ] );
5950
- ctx.quadraticCurveTo( cp[ 2*n-10 ], cp[ 2*n-9 ], pts[ n-4 ], pts[ n-3 ]);
5951
- ctx.stroke();
5952
- ctx.closePath();
5991
+ // For open curves the first and last arcs are simple quadratics.
5992
+ ctx.beginPath();
5993
+ ctx.moveTo( pts[ 0 ], pts[ 1 ] );
5994
+ ctx.quadraticCurveTo( cp[ 0 ], cp[ 1 ], pts[ 2 ], pts[ 3 ]);
5995
+ ctx.stroke();
5996
+ ctx.closePath();
5953
5997
 
5954
- ctx.restore();
5955
- }
5956
- };
5998
+ ctx.beginPath();
5999
+ ctx.moveTo( pts[ n-2 ], pts[ n-1 ] );
6000
+ ctx.quadraticCurveTo( cp[ 2*n-10 ], cp[ 2*n-9 ], pts[ n-4 ], pts[ n-3 ]);
6001
+ ctx.stroke();
6002
+ ctx.closePath();
6003
+
6004
+ ctx.restore();
6005
+ }
5957
6006
 
6007
+ LX.drawSpline = drawSpline;
6008
+
5958
6009
  // area.js @jxarco
5959
6010
 
5960
6011
  class Area {
@@ -6678,7 +6729,7 @@ class Area {
6678
6729
 
6679
6730
  addSidebar( callback, options = {} ) {
6680
6731
 
6681
- let sidebar = new LX.Sidebar( options );
6732
+ let sidebar = new LX.Sidebar( { callback, ...options } );
6682
6733
 
6683
6734
  if( callback )
6684
6735
  {
@@ -7004,8 +7055,8 @@ class Area {
7004
7055
  }
7005
7056
  }
7006
7057
  }
7007
- LX.Area = Area;
7008
-
7058
+ LX.Area = Area;
7059
+
7009
7060
  // widget.js @jxarco
7010
7061
 
7011
7062
  /**
@@ -12175,8 +12226,8 @@ class Map2D extends Widget {
12175
12226
  }
12176
12227
  }
12177
12228
 
12178
- LX.Map2D = Map2D;
12179
-
12229
+ LX.Map2D = Map2D;
12230
+
12180
12231
  // panel.js @jxarco
12181
12232
 
12182
12233
  /**
@@ -13356,8 +13407,8 @@ class Panel {
13356
13407
  }
13357
13408
  }
13358
13409
 
13359
- LX.Panel = Panel;
13360
-
13410
+ LX.Panel = Panel;
13411
+
13361
13412
  // branch.js @jxarco
13362
13413
 
13363
13414
  /**
@@ -13581,8 +13632,8 @@ class Branch {
13581
13632
  }
13582
13633
  }
13583
13634
  }
13584
- LX.Branch = Branch;
13585
-
13635
+ LX.Branch = Branch;
13636
+
13586
13637
  // menubar.js @jxarco
13587
13638
 
13588
13639
  /**
@@ -13899,8 +13950,8 @@ class Menubar {
13899
13950
  }
13900
13951
  }
13901
13952
  }
13902
- LX.Menubar = Menubar;
13903
-
13953
+ LX.Menubar = Menubar;
13954
+
13904
13955
  // sidebar.js @jxarco
13905
13956
 
13906
13957
  /**
@@ -13936,6 +13987,7 @@ class Sidebar {
13936
13987
 
13937
13988
  this.root = document.createElement( "div" );
13938
13989
  this.root.className = "lexsidebar " + ( options.className ?? "" );
13990
+ this.callback = options.callback ?? null;
13939
13991
 
13940
13992
  this._displaySelected = options.displaySelected ?? false;
13941
13993
 
@@ -13952,17 +14004,19 @@ class Sidebar {
13952
14004
  configurable: true
13953
14005
  });
13954
14006
 
14007
+ const mobile = navigator && /Android|iPhone/i.test( navigator.userAgent );
14008
+
13955
14009
  this.side = options.side ?? "left";
13956
14010
  this.collapsable = options.collapsable ?? true;
13957
14011
  this._collapseWidth = ( options.collapseToIcons ?? true ) ? "58px" : "0px";
13958
- this.collapsed = false;
14012
+ this.collapsed = options.collapsed ?? mobile;
13959
14013
 
13960
14014
  this.filterString = "";
13961
14015
 
13962
14016
  LX.doAsync( () => {
13963
14017
 
13964
14018
  this.root.parentElement.ogWidth = this.root.parentElement.style.width;
13965
- this.root.parentElement.style.transition = "width 0.25s ease-out";
14019
+ this.root.parentElement.style.transition = this.collapsed ? "" : "width 0.25s ease-out";
13966
14020
 
13967
14021
  this.resizeObserver = new ResizeObserver( entries => {
13968
14022
  for ( const entry of entries )
@@ -13971,6 +14025,24 @@ class Sidebar {
13971
14025
  }
13972
14026
  });
13973
14027
 
14028
+ if( this.collapsed )
14029
+ {
14030
+ this.root.classList.toggle( "collapsed", this.collapsed );
14031
+ this.root.parentElement.style.width = this._collapseWidth;
14032
+
14033
+ if( !this.resizeObserver )
14034
+ {
14035
+ throw( "Wait until ResizeObserver has been created!" );
14036
+ }
14037
+
14038
+ this.resizeObserver.observe( this.root.parentElement );
14039
+
14040
+ LX.doAsync( () => {
14041
+ this.resizeObserver.unobserve( this.root.parentElement );
14042
+ this.root.querySelectorAll( ".lexsidebarentrycontent" ).forEach( e => e.dataset[ "disableTooltip" ] = !this.collapsed );
14043
+ }, 10 );
14044
+ }
14045
+
13974
14046
  }, 10 );
13975
14047
 
13976
14048
  // Header
@@ -13986,11 +14058,29 @@ class Sidebar {
13986
14058
  const icon = LX.makeIcon( this.side == "left" ? "PanelLeft" : "PanelRight", { title: "Toggle Sidebar", iconClass: "toggler" } );
13987
14059
  this.header.appendChild( icon );
13988
14060
 
13989
- icon.addEventListener( "click", (e) => {
13990
- e.preventDefault();
13991
- e.stopPropagation();
13992
- this.toggleCollapsed();
13993
- } );
14061
+ if( mobile )
14062
+ {
14063
+ // create an area and append a sidebar:
14064
+ const area = new LX.Area({ skipAppend: true });
14065
+ const sheetSidebarOptions = LX.deepCopy( options );
14066
+ sheetSidebarOptions.collapsed = false;
14067
+ sheetSidebarOptions.collapsable = false;
14068
+ area.addSidebar( this.callback, sheetSidebarOptions );
14069
+
14070
+ icon.addEventListener( "click", e => {
14071
+ e.preventDefault();
14072
+ e.stopPropagation();
14073
+ new LX.Sheet("256px", [ area ], { side: this.side } );
14074
+ } );
14075
+ }
14076
+ else
14077
+ {
14078
+ icon.addEventListener( "click", e => {
14079
+ e.preventDefault();
14080
+ e.stopPropagation();
14081
+ this.toggleCollapsed();
14082
+ } );
14083
+ }
13994
14084
  }
13995
14085
  }
13996
14086
 
@@ -14430,20 +14520,17 @@ class Sidebar {
14430
14520
  return;
14431
14521
  }
14432
14522
 
14523
+ const f = options.callback;
14524
+ if( f ) f.call( this, key, item.value, e );
14525
+
14433
14526
  if( isCollapsable )
14434
14527
  {
14435
14528
  itemDom.querySelector( ".collapser" ).click();
14436
14529
  }
14437
- else
14530
+ else if( item.checkbox )
14438
14531
  {
14439
- const f = options.callback;
14440
- if( f ) f.call( this, key, item.value, e );
14441
-
14442
- if( item.checkbox )
14443
- {
14444
- item.value = !item.value;
14445
- item.checkbox.set( item.value, true );
14446
- }
14532
+ item.value = !item.value;
14533
+ item.checkbox.set( item.value, true );
14447
14534
  }
14448
14535
 
14449
14536
  // Manage selected
@@ -14547,8 +14634,8 @@ class Sidebar {
14547
14634
  }
14548
14635
  }
14549
14636
  }
14550
- LX.Sidebar = Sidebar;
14551
-
14637
+ LX.Sidebar = Sidebar;
14638
+
14552
14639
  // asset_view.js @jxarco
14553
14640
 
14554
14641
  class AssetViewEvent {
@@ -15415,6 +15502,6 @@ class AssetView {
15415
15502
  }
15416
15503
  }
15417
15504
 
15418
- LX.AssetView = AssetView;
15419
-
15420
- export { ADD_CUSTOM_WIDGET, Area, AssetView, AssetViewEvent, Branch, LX, Menubar, Panel, Sidebar, Widget };
15505
+ LX.AssetView = AssetView;
15506
+
15507
+ export { ADD_CUSTOM_WIDGET, Area, AssetView, AssetViewEvent, Branch, LX, Menubar, Panel, Sidebar, Widget };