lexgui 0.6.4 → 0.6.5

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.5",
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;
@@ -2247,13 +1878,6 @@ class Calendar {
2247
1878
 
2248
1879
  LX.Calendar = Calendar;
2249
1880
 
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
1881
  /**
2258
1882
  * @class Tabs
2259
1883
  */
@@ -2379,7 +2003,7 @@ class Tabs {
2379
2003
  this.thumb.style.transition = "none";
2380
2004
  this.thumb.style.transform = "translate( " + ( tabEl.childIndex * tabEl.offsetWidth ) + "px )";
2381
2005
  this.thumb.style.width = ( tabEl.offsetWidth ) + "px";
2382
- flushCss( this.thumb );
2006
+ LX.flushCss( this.thumb );
2383
2007
  this.thumb.style.transition = transition;
2384
2008
  });
2385
2009
 
@@ -3493,7 +3117,7 @@ class CanvasCurve {
3493
3117
  }
3494
3118
  else
3495
3119
  {
3496
- LX.UTILS.drawSpline( ctx, values, element.smooth );
3120
+ LX.drawSpline( ctx, values, element.smooth );
3497
3121
  }
3498
3122
 
3499
3123
  // Draw points
@@ -4435,254 +4059,6 @@ class CanvasMap2D {
4435
4059
 
4436
4060
  LX.CanvasMap2D = CanvasMap2D;
4437
4061
 
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
4062
  Object.defineProperty(String.prototype, 'lastChar', {
4687
4063
  get: function() { return this[ this.length - 1 ]; },
4688
4064
  enumerable: true,
@@ -4731,8 +4107,8 @@ Element.prototype.ignore = function( eventName, callbackName ) {
4731
4107
  callbackName = callbackName ?? ( "_on" + eventName );
4732
4108
  const callback = this[ callbackName ];
4733
4109
  this.removeEventListener( eventName, callback );
4734
- };
4735
-
4110
+ };
4111
+
4736
4112
  // icons.js @jxarco
4737
4113
 
4738
4114
  const RAW_ICONS = {
@@ -4910,8 +4286,8 @@ LX.LucideIconAlias = {
4910
4286
  "RotateRight": "RotateCw",
4911
4287
  "RotateBack": "RotateCcw",
4912
4288
  "RotateLeft": "RotateCcw",
4913
- };
4914
-
4289
+ };
4290
+
4915
4291
  // utils.js @jxarco
4916
4292
 
4917
4293
  function clamp( num, min, max ) { return Math.min( Math.max( num, min ), max ); }
@@ -4960,15 +4336,39 @@ function doAsync( fn, ms ) {
4960
4336
  LX.doAsync = doAsync;
4961
4337
 
4962
4338
  /**
4963
- * @method getSupportedDOMName
4964
- * @description Convert a text string to a valid DOM name
4965
- * @param {String} text Original text
4339
+ * @method flushCss
4340
+ * @description By reading the offsetHeight property, we are forcing the browser to flush
4341
+ * the pending CSS changes (which it does to ensure the value obtained is accurate).
4342
+ * @param {HTMLElement} element
4966
4343
  */
4967
- function getSupportedDOMName( text )
4344
+ function flushCss( element )
4968
4345
  {
4969
- console.assert( typeof text == "string", "getSupportedDOMName: Text is not a string!" );
4346
+ element.offsetHeight;
4347
+ }
4970
4348
 
4971
- let name = text.trim();
4349
+ LX.flushCss = flushCss;
4350
+
4351
+ /**
4352
+ * @method deleteElement
4353
+ * @param {HTMLElement} element
4354
+ */
4355
+ function deleteElement( element )
4356
+ {
4357
+ if( element !== undefined ) element.remove();
4358
+ }
4359
+
4360
+ LX.deleteElement = deleteElement;
4361
+
4362
+ /**
4363
+ * @method getSupportedDOMName
4364
+ * @description Convert a text string to a valid DOM name
4365
+ * @param {String} text Original text
4366
+ */
4367
+ function getSupportedDOMName( text )
4368
+ {
4369
+ console.assert( typeof text == "string", "getSupportedDOMName: Text is not a string!" );
4370
+
4371
+ let name = text.trim();
4972
4372
 
4973
4373
  // Replace specific known symbols
4974
4374
  name = name.replace( /@/g, '_at_' ).replace( /\+/g, '_plus_' ).replace( /\./g, '_dot_' );
@@ -5029,9 +4429,13 @@ LX.stripHTML = stripHTML;
5029
4429
  * @param {Number|String} size
5030
4430
  * @param {Number} total
5031
4431
  */
5032
- const parsePixelSize = ( size, total ) => {
5033
-
5034
- if( size.constructor === Number ) { return size; } // Assuming pixels..
4432
+ function parsePixelSize( size, total )
4433
+ {
4434
+ // Assuming pixels..
4435
+ if( size.constructor === Number )
4436
+ {
4437
+ return size;
4438
+ }
5035
4439
 
5036
4440
  if( size.constructor === String )
5037
4441
  {
@@ -5069,7 +4473,7 @@ const parsePixelSize = ( size, total ) => {
5069
4473
  }
5070
4474
 
5071
4475
  throw( "Bad size format!" );
5072
- };
4476
+ }
5073
4477
 
5074
4478
  LX.parsePixelSize = parsePixelSize;
5075
4479
 
@@ -5332,7 +4736,7 @@ function measureRealWidth( value, paddingPlusMargin = 8 )
5332
4736
  i.innerHTML = value;
5333
4737
  document.body.appendChild( i );
5334
4738
  var rect = i.getBoundingClientRect();
5335
- LX.UTILS.deleteElement( i );
4739
+ LX.deleteElement( i );
5336
4740
  return rect.width + paddingPlusMargin;
5337
4741
  }
5338
4742
 
@@ -5754,207 +5158,853 @@ function makeIcon( iconName, options = { } )
5754
5158
  svg.classList.add( c );
5755
5159
  } );
5756
5160
 
5757
- const attrs = data[ 5 ].svgAttributes;
5758
- attrs?.split( ' ' ).forEach( attr => {
5759
- const t = attr.split( '=' );
5760
- svg.setAttribute( t[ 0 ], t[ 1 ] );
5761
- } );
5762
- }
5161
+ const attrs = data[ 5 ].svgAttributes;
5162
+ attrs?.split( ' ' ).forEach( attr => {
5163
+ const t = attr.split( '=' );
5164
+ svg.setAttribute( t[ 0 ], t[ 1 ] );
5165
+ } );
5166
+ }
5167
+
5168
+ const path = document.createElement( "path" );
5169
+ path.setAttribute( "fill", "currentColor" );
5170
+ path.setAttribute( "d", data[ 4 ] );
5171
+ svg.appendChild( path );
5172
+
5173
+ if( data[ 5 ] )
5174
+ {
5175
+ const classes = data[ 5 ].pathClass;
5176
+ classes?.split( ' ' ).forEach( c => {
5177
+ path.classList.add( c );
5178
+ } );
5179
+
5180
+ const attrs = data[ 5 ].pathAttributes;
5181
+ attrs?.split( ' ' ).forEach( attr => {
5182
+ const t = attr.split( '=' );
5183
+ path.setAttribute( t[ 0 ], t[ 1 ] );
5184
+ } );
5185
+ }
5186
+
5187
+ 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. -->`;
5188
+ svg.innerHTML += faLicense;
5189
+ return _createIconFromSVG( svg );
5190
+ }
5191
+ }
5192
+
5193
+ // Fallback to Lucide icon
5194
+ console.assert( lucideData, `No existing icon named _${ iconName }_` );
5195
+ svg = lucide.createElement( lucideData, options );
5196
+
5197
+ return _createIconFromSVG( svg );
5198
+ }
5199
+
5200
+ LX.makeIcon = makeIcon;
5201
+
5202
+ /**
5203
+ * @method registerIcon
5204
+ * @description Register an SVG icon to LX.ICONS
5205
+ * @param {String} iconName
5206
+ * @param {String} svgString
5207
+ * @param {String} variant
5208
+ * @param {Array} aliases
5209
+ */
5210
+ function registerIcon( iconName, svgString, variant = "none", aliases = [] )
5211
+ {
5212
+ const svg = new DOMParser().parseFromString( svgString, 'image/svg+xml' ).documentElement;
5213
+ const path = svg.querySelector( "path" );
5214
+ const viewBox = svg.getAttribute( "viewBox" ).split( ' ' );
5215
+ const pathData = path.getAttribute( 'd' );
5216
+
5217
+ let svgAttributes = [];
5218
+ let pathAttributes = [];
5219
+
5220
+ for( const attr of svg.attributes )
5221
+ {
5222
+ switch( attr.name )
5223
+ {
5224
+ case "transform":
5225
+ case "fill":
5226
+ case "stroke-width":
5227
+ case "stroke-linecap":
5228
+ case "stroke-linejoin":
5229
+ svgAttributes.push( `${ attr.name }=${ attr.value }` );
5230
+ break;
5231
+ }
5232
+ }
5233
+
5234
+ for( const attr of path.attributes )
5235
+ {
5236
+ switch( attr.name )
5237
+ {
5238
+ case "transform":
5239
+ case "fill":
5240
+ case "stroke-width":
5241
+ case "stroke-linecap":
5242
+ case "stroke-linejoin":
5243
+ pathAttributes.push( `${ attr.name }=${ attr.value }` );
5244
+ break;
5245
+ }
5246
+ }
5247
+
5248
+ const iconData = [
5249
+ parseInt( viewBox[ 2 ] ),
5250
+ parseInt( viewBox[ 3 ] ),
5251
+ aliases,
5252
+ variant,
5253
+ pathData,
5254
+ {
5255
+ svgAttributes: svgAttributes.length ? svgAttributes.join( ' ' ) : null,
5256
+ pathAttributes: pathAttributes.length ? pathAttributes.join( ' ' ) : null
5257
+ }
5258
+ ];
5259
+
5260
+ if( LX.ICONS[ iconName ] )
5261
+ {
5262
+ console.warn( `${ iconName } will be added/replaced in LX.ICONS` );
5263
+ }
5264
+
5265
+ LX.ICONS[ iconName ] = iconData;
5266
+ }
5267
+
5268
+ LX.registerIcon = registerIcon;
5269
+
5270
+ /**
5271
+ * @method registerCommandbarEntry
5272
+ * @description Adds an extra command bar entry
5273
+ * @param {String} name
5274
+ * @param {Function} callback
5275
+ */
5276
+ function registerCommandbarEntry( name, callback )
5277
+ {
5278
+ LX.extraCommandbarEntries.push( { name, callback } );
5279
+ }
5280
+
5281
+ LX.registerCommandbarEntry = registerCommandbarEntry;
5282
+
5283
+ /*
5284
+ Dialog and Notification Elements
5285
+ */
5286
+
5287
+ /**
5288
+ * @method message
5289
+ * @param {String} text
5290
+ * @param {String} title (Optional)
5291
+ * @param {Object} options
5292
+ * id: Id of the message dialog
5293
+ * position: Dialog position in screen [screen centered]
5294
+ * draggable: Dialog can be dragged [false]
5295
+ */
5296
+
5297
+ function message( text, title, options = {} )
5298
+ {
5299
+ if( !text )
5300
+ {
5301
+ throw( "No message to show" );
5302
+ }
5303
+
5304
+ options.modal = true;
5305
+
5306
+ return new LX.Dialog( title, p => {
5307
+ p.addTextArea( null, text, null, { disabled: true, fitHeight: true } );
5308
+ }, options );
5309
+ }
5310
+
5311
+ LX.message = message;
5312
+
5313
+ /**
5314
+ * @method popup
5315
+ * @param {String} text
5316
+ * @param {String} title (Optional)
5317
+ * @param {Object} options
5318
+ * id: Id of the message dialog
5319
+ * timeout (Number): Delay time before it closes automatically (ms). Default: [3000]
5320
+ * position (Array): [x,y] Dialog position in screen. Default: [screen centered]
5321
+ * size (Array): [width, height]
5322
+ */
5323
+
5324
+ function popup( text, title, options = {} )
5325
+ {
5326
+ if( !text )
5327
+ {
5328
+ throw("No message to show");
5329
+ }
5330
+
5331
+ options.size = options.size ?? [ "max-content", "auto" ];
5332
+ options.class = "lexpopup";
5333
+
5334
+ const time = options.timeout || 3000;
5335
+ const dialog = new LX.Dialog( title, p => {
5336
+ p.addTextArea( null, text, null, { disabled: true, fitHeight: true } );
5337
+ }, options );
5338
+
5339
+ setTimeout( () => {
5340
+ dialog.close();
5341
+ }, Math.max( time, 150 ) );
5342
+
5343
+ return dialog;
5344
+ }
5345
+
5346
+ LX.popup = popup;
5347
+
5348
+ /**
5349
+ * @method prompt
5350
+ * @param {String} text
5351
+ * @param {String} title (Optional)
5352
+ * @param {Object} options
5353
+ * id: Id of the prompt dialog
5354
+ * position: Dialog position in screen [screen centered]
5355
+ * draggable: Dialog can be dragged [false]
5356
+ * input: If false, no text input appears
5357
+ * accept: Accept text
5358
+ * required: Input has to be filled [true]. Default: false
5359
+ */
5360
+
5361
+ function prompt( text, title, callback, options = {} )
5362
+ {
5363
+ options.modal = true;
5364
+ options.className = "prompt";
5365
+
5366
+ let value = "";
5367
+
5368
+ const dialog = new LX.Dialog( title, p => {
5369
+
5370
+ p.addTextArea( null, text, null, { disabled: true, fitHeight: true } );
5371
+
5372
+ if( options.input ?? true )
5373
+ {
5374
+ p.addText( null, options.input || value, v => value = v, { placeholder: "..." } );
5375
+ }
5376
+
5377
+ p.sameLine( 2 );
5378
+
5379
+ p.addButton(null, "Cancel", () => {if(options.on_cancel) options.on_cancel(); dialog.close();} );
5380
+
5381
+ p.addButton( null, options.accept || "Continue", () => {
5382
+ if( options.required && value === '' )
5383
+ {
5384
+ text += text.includes("You must fill the input text.") ? "": "\nYou must fill the input text.";
5385
+ dialog.close();
5386
+ prompt( text, title, callback, options );
5387
+ }
5388
+ else
5389
+ {
5390
+ if( callback ) callback.call( this, value );
5391
+ dialog.close();
5392
+ }
5393
+ }, { buttonClass: "primary" });
5394
+
5395
+ }, options );
5396
+
5397
+ // Focus text prompt
5398
+ if( options.input ?? true )
5399
+ {
5400
+ dialog.root.querySelector( 'input' ).focus();
5401
+ }
5402
+
5403
+ return dialog;
5404
+ }
5405
+
5406
+ LX.prompt = prompt;
5407
+
5408
+ /**
5409
+ * @method toast
5410
+ * @param {String} title
5411
+ * @param {String} description (Optional)
5412
+ * @param {Object} options
5413
+ * action: Data of the custom action { name, callback }
5414
+ * closable: Allow closing the toast
5415
+ * timeout: Time in which the toast closed automatically, in ms. -1 means persistent. [3000]
5416
+ */
5417
+
5418
+ function toast( title, description, options = {} )
5419
+ {
5420
+ if( !title )
5421
+ {
5422
+ throw( "The toast needs at least a title!" );
5423
+ }
5424
+
5425
+ console.assert( this.notifications );
5426
+
5427
+ const toast = document.createElement( "li" );
5428
+ toast.className = "lextoast";
5429
+ toast.style.translate = "0 calc(100% + 30px)";
5430
+ this.notifications.prepend( toast );
5431
+
5432
+ LX.doAsync( () => {
5433
+
5434
+ if( this.notifications.offsetWidth > this.notifications.iWidth )
5435
+ {
5436
+ this.notifications.iWidth = Math.min( this.notifications.offsetWidth, 480 );
5437
+ this.notifications.style.width = this.notifications.iWidth + "px";
5438
+ }
5439
+
5440
+ toast.dataset[ "open" ] = true;
5441
+ }, 10 );
5442
+
5443
+ const content = document.createElement( "div" );
5444
+ content.className = "lextoastcontent";
5445
+ toast.appendChild( content );
5446
+
5447
+ const titleContent = document.createElement( "div" );
5448
+ titleContent.className = "title";
5449
+ titleContent.innerHTML = title;
5450
+ content.appendChild( titleContent );
5451
+
5452
+ if( description )
5453
+ {
5454
+ const desc = document.createElement( "div" );
5455
+ desc.className = "desc";
5456
+ desc.innerHTML = description;
5457
+ content.appendChild( desc );
5458
+ }
5459
+
5460
+ if( options.action )
5461
+ {
5462
+ const panel = new LX.Panel();
5463
+ panel.addButton(null, options.action.name ?? "Accept", options.action.callback.bind( this, toast ), { width: "auto", maxWidth: "150px", className: "right", buttonClass: "border" });
5464
+ toast.appendChild( panel.root.childNodes[ 0 ] );
5465
+ }
5466
+
5467
+ const that = this;
5468
+
5469
+ toast.close = function() {
5470
+ this.dataset[ "closed" ] = true;
5471
+ LX.doAsync( () => {
5472
+ this.remove();
5473
+ if( !that.notifications.childElementCount )
5474
+ {
5475
+ that.notifications.style.width = "unset";
5476
+ that.notifications.iWidth = 0;
5477
+ }
5478
+ }, 500 );
5479
+ };
5480
+
5481
+ if( options.closable ?? true )
5482
+ {
5483
+ const closeIcon = LX.makeIcon( "X", { iconClass: "closer" } );
5484
+ closeIcon.addEventListener( "click", () => {
5485
+ toast.close();
5486
+ } );
5487
+ toast.appendChild( closeIcon );
5488
+ }
5489
+
5490
+ const timeout = options.timeout ?? 3000;
5491
+
5492
+ if( timeout != -1 )
5493
+ {
5494
+ LX.doAsync( () => {
5495
+ toast.close();
5496
+ }, timeout );
5497
+ }
5498
+ }
5499
+
5500
+ LX.toast = toast;
5501
+
5502
+ /**
5503
+ * @method badge
5504
+ * @param {String} text
5505
+ * @param {String} className
5506
+ * @param {Object} options
5507
+ * style: Style attributes to override
5508
+ * asElement: Returns the badge as HTMLElement [false]
5509
+ */
5510
+
5511
+ function badge( text, className, options = {} )
5512
+ {
5513
+ const container = document.createElement( "div" );
5514
+ container.innerHTML = text;
5515
+ container.className = "lexbadge " + ( className ?? "" );
5516
+ Object.assign( container.style, options.style ?? {} );
5517
+ return ( options.asElement ?? false ) ? container : container.outerHTML;
5518
+ }
5519
+
5520
+ LX.badge = badge;
5521
+
5522
+ /**
5523
+ * @method makeElement
5524
+ * @param {String} htmlType
5525
+ * @param {String} className
5526
+ * @param {String} innerHTML
5527
+ * @param {HTMLElement} parent
5528
+ * @param {Object} overrideStyle
5529
+ */
5530
+
5531
+ function makeElement( htmlType, className, innerHTML, parent, overrideStyle = {} )
5532
+ {
5533
+ const element = document.createElement( htmlType );
5534
+ element.className = className ?? "";
5535
+ element.innerHTML = innerHTML ?? "";
5536
+ Object.assign( element.style, overrideStyle );
5537
+
5538
+ if( parent )
5539
+ {
5540
+ if( parent.attach ) // Use attach method if possible
5541
+ {
5542
+ parent.attach( element );
5543
+ }
5544
+ else // its a native HTMLElement
5545
+ {
5546
+ parent.appendChild( element );
5547
+ }
5548
+ }
5549
+
5550
+ return element;
5551
+ }
5552
+
5553
+ LX.makeElement = makeElement;
5554
+
5555
+ /**
5556
+ * @method makeContainer
5557
+ * @param {Array} size
5558
+ * @param {String} className
5559
+ * @param {String} innerHTML
5560
+ * @param {HTMLElement} parent
5561
+ * @param {Object} overrideStyle
5562
+ */
5563
+
5564
+ function makeContainer( size, className, innerHTML, parent, overrideStyle = {} )
5565
+ {
5566
+ const container = LX.makeElement( "div", "lexcontainer " + ( className ?? "" ), innerHTML, parent, overrideStyle );
5567
+ container.style.width = size && size[ 0 ] ? size[ 0 ] : "100%";
5568
+ container.style.height = size && size[ 1 ] ? size[ 1 ] : "100%";
5569
+ return container;
5570
+ }
5571
+
5572
+ LX.makeContainer = makeContainer;
5573
+
5574
+ /**
5575
+ * @method asTooltip
5576
+ * @param {HTMLElement} trigger
5577
+ * @param {String} content
5578
+ * @param {Object} options
5579
+ * side: Side of the tooltip
5580
+ * offset: Tooltip margin offset
5581
+ * active: Tooltip active by default [true]
5582
+ */
5583
+
5584
+ function asTooltip( trigger, content, options = {} )
5585
+ {
5586
+ console.assert( trigger, "You need a trigger to generate a tooltip!" );
5587
+
5588
+ trigger.dataset[ "disableTooltip" ] = !( options.active ?? true );
5589
+
5590
+ let tooltipDom = null;
5591
+
5592
+ trigger.addEventListener( "mouseenter", function(e) {
5593
+
5594
+ if( trigger.dataset[ "disableTooltip" ] == "true" )
5595
+ {
5596
+ return;
5597
+ }
5598
+
5599
+ LX.root.querySelectorAll( ".lextooltip" ).forEach( e => e.remove() );
5600
+
5601
+ tooltipDom = document.createElement( "div" );
5602
+ tooltipDom.className = "lextooltip";
5603
+ tooltipDom.innerHTML = content;
5604
+
5605
+ LX.doAsync( () => {
5606
+
5607
+ const position = [ 0, 0 ];
5608
+ const rect = this.getBoundingClientRect();
5609
+ const offset = options.offset ?? 6;
5610
+ let alignWidth = true;
5611
+
5612
+ switch( options.side ?? "top" )
5613
+ {
5614
+ case "left":
5615
+ position[ 0 ] += ( rect.x - tooltipDom.offsetWidth - offset );
5616
+ alignWidth = false;
5617
+ break;
5618
+ case "right":
5619
+ position[ 0 ] += ( rect.x + rect.width + offset );
5620
+ alignWidth = false;
5621
+ break;
5622
+ case "top":
5623
+ position[ 1 ] += ( rect.y - tooltipDom.offsetHeight - offset );
5624
+ alignWidth = true;
5625
+ break;
5626
+ case "bottom":
5627
+ position[ 1 ] += ( rect.y + rect.height + offset );
5628
+ alignWidth = true;
5629
+ break;
5630
+ }
5631
+
5632
+ if( alignWidth ) { position[ 0 ] += ( rect.x + rect.width * 0.5 ) - tooltipDom.offsetWidth * 0.5; }
5633
+ else { position[ 1 ] += ( rect.y + rect.height * 0.5 ) - tooltipDom.offsetHeight * 0.5; }
5634
+
5635
+ // Avoid collisions
5636
+ position[ 0 ] = LX.clamp( position[ 0 ], 0, window.innerWidth - tooltipDom.offsetWidth - 4 );
5637
+ position[ 1 ] = LX.clamp( position[ 1 ], 0, window.innerHeight - tooltipDom.offsetHeight - 4 );
5638
+
5639
+ tooltipDom.style.left = `${ position[ 0 ] }px`;
5640
+ tooltipDom.style.top = `${ position[ 1 ] }px`;
5641
+ } );
5642
+
5643
+ LX.root.appendChild( tooltipDom );
5644
+ } );
5645
+
5646
+ trigger.addEventListener( "mouseleave", function(e) {
5647
+ if( tooltipDom )
5648
+ {
5649
+ tooltipDom.remove();
5650
+ }
5651
+ } );
5652
+ }
5653
+
5654
+ LX.asTooltip = asTooltip;
5655
+
5656
+ /*
5657
+ * Requests
5658
+ */
5659
+
5660
+ Object.assign(LX, {
5661
+
5662
+ /**
5663
+ * Request file from url (it could be a binary, text, etc.). If you want a simplied version use
5664
+ * @method request
5665
+ * @param {Object} request object with all the parameters like data (for sending forms), dataType, success, error
5666
+ * @param {Function} on_complete
5667
+ **/
5668
+ request( request ) {
5669
+
5670
+ var dataType = request.dataType || "text";
5671
+ if(dataType == "json") //parse it locally
5672
+ dataType = "text";
5673
+ else if(dataType == "xml") //parse it locally
5674
+ dataType = "text";
5675
+ else if (dataType == "binary")
5676
+ {
5677
+ //request.mimeType = "text/plain; charset=x-user-defined";
5678
+ dataType = "arraybuffer";
5679
+ request.mimeType = "application/octet-stream";
5680
+ }
5681
+
5682
+ //regular case, use AJAX call
5683
+ var xhr = new XMLHttpRequest();
5684
+ xhr.open( request.data ? 'POST' : 'GET', request.url, true);
5685
+ if(dataType)
5686
+ xhr.responseType = dataType;
5687
+ if (request.mimeType)
5688
+ xhr.overrideMimeType( request.mimeType );
5689
+ if( request.nocache )
5690
+ xhr.setRequestHeader('Cache-Control', 'no-cache');
5691
+
5692
+ xhr.onload = function(load)
5693
+ {
5694
+ var response = this.response;
5695
+ if( this.status != 200)
5696
+ {
5697
+ var err = "Error " + this.status;
5698
+ if(request.error)
5699
+ request.error(err);
5700
+ return;
5701
+ }
5702
+
5703
+ if(request.dataType == "json") //chrome doesnt support json format
5704
+ {
5705
+ try
5706
+ {
5707
+ response = JSON.parse(response);
5708
+ }
5709
+ catch (err)
5710
+ {
5711
+ if(request.error)
5712
+ request.error(err);
5713
+ else
5714
+ throw err;
5715
+ }
5716
+ }
5717
+ else if(request.dataType == "xml")
5718
+ {
5719
+ try
5720
+ {
5721
+ var xmlparser = new DOMParser();
5722
+ response = xmlparser.parseFromString(response,"text/xml");
5723
+ }
5724
+ catch (err)
5725
+ {
5726
+ if(request.error)
5727
+ request.error(err);
5728
+ else
5729
+ throw err;
5730
+ }
5731
+ }
5732
+ if(request.success)
5733
+ request.success.call(this, response, this);
5734
+ };
5735
+ xhr.onerror = function(err) {
5736
+ if(request.error)
5737
+ request.error(err);
5738
+ };
5739
+
5740
+ var data = new FormData();
5741
+ if( request.data )
5742
+ {
5743
+ for( var i in request.data)
5744
+ data.append(i,request.data[ i ]);
5745
+ }
5746
+
5747
+ xhr.send( data );
5748
+ return xhr;
5749
+ },
5750
+
5751
+ /**
5752
+ * Request file from url
5753
+ * @method requestText
5754
+ * @param {String} url
5755
+ * @param {Function} onComplete
5756
+ * @param {Function} onError
5757
+ **/
5758
+ requestText( url, onComplete, onError ) {
5759
+ return this.request({ url: url, dataType:"text", success: onComplete, error: onError });
5760
+ },
5761
+
5762
+ /**
5763
+ * Request file from url
5764
+ * @method requestJSON
5765
+ * @param {String} url
5766
+ * @param {Function} onComplete
5767
+ * @param {Function} onError
5768
+ **/
5769
+ requestJSON( url, onComplete, onError ) {
5770
+ return this.request({ url: url, dataType:"json", success: onComplete, error: onError });
5771
+ },
5772
+
5773
+ /**
5774
+ * Request binary file from url
5775
+ * @method requestBinary
5776
+ * @param {String} url
5777
+ * @param {Function} onComplete
5778
+ * @param {Function} onError
5779
+ **/
5780
+ requestBinary( url, onComplete, onError ) {
5781
+ return this.request({ url: url, dataType:"binary", success: onComplete, error: onError });
5782
+ },
5783
+
5784
+ /**
5785
+ * Request script and inserts it in the DOM
5786
+ * @method requireScript
5787
+ * @param {String|Array} url the url of the script or an array containing several urls
5788
+ * @param {Function} onComplete
5789
+ * @param {Function} onError
5790
+ * @param {Function} onProgress (if several files are required, onProgress is called after every file is added to the DOM)
5791
+ **/
5792
+ requireScript( url, onComplete, onError, onProgress, version ) {
5763
5793
 
5764
- const path = document.createElement( "path" );
5765
- path.setAttribute( "fill", "currentColor" );
5766
- path.setAttribute( "d", data[ 4 ] );
5767
- svg.appendChild( path );
5794
+ if(!url)
5795
+ throw("invalid URL");
5768
5796
 
5769
- if( data[ 5 ] )
5770
- {
5771
- const classes = data[ 5 ].pathClass;
5772
- classes?.split( ' ' ).forEach( c => {
5773
- path.classList.add( c );
5774
- } );
5797
+ if( url.constructor === String )
5798
+ url = [url];
5775
5799
 
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
- }
5800
+ var total = url.length;
5801
+ var loaded_scripts = [];
5782
5802
 
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 );
5803
+ for( var i in url)
5804
+ {
5805
+ var script = document.createElement('script');
5806
+ script.num = i;
5807
+ script.type = 'text/javascript';
5808
+ script.src = url[ i ] + ( version ? "?version=" + version : "" );
5809
+ script.original_src = url[ i ];
5810
+ script.async = false;
5811
+ script.onload = function( e ) {
5812
+ total--;
5813
+ loaded_scripts.push(this);
5814
+ if(total)
5815
+ {
5816
+ if( onProgress )
5817
+ {
5818
+ onProgress( this.original_src, this.num );
5819
+ }
5820
+ }
5821
+ else if(onComplete)
5822
+ onComplete( loaded_scripts );
5823
+ };
5824
+ if(onError)
5825
+ script.onerror = function(err) {
5826
+ onError(err, this.original_src, this.num );
5827
+ };
5828
+ document.getElementsByTagName('head')[ 0 ].appendChild(script);
5786
5829
  }
5787
- }
5788
-
5789
- // Fallback to Lucide icon
5790
- console.assert( lucideData, `No existing icon named _${ iconName }_` );
5791
- svg = lucide.createElement( lucideData, options );
5830
+ },
5792
5831
 
5793
- return _createIconFromSVG( svg );
5794
- }
5832
+ loadScriptSync( url ) {
5833
+ return new Promise((resolve, reject) => {
5834
+ const script = document.createElement( "script" );
5835
+ script.src = url;
5836
+ script.async = false;
5837
+ script.onload = () => resolve();
5838
+ script.onerror = () => reject(new Error(`Failed to load ${url}`));
5839
+ document.head.appendChild( script );
5840
+ });
5841
+ },
5795
5842
 
5796
- LX.makeIcon = makeIcon;
5843
+ downloadURL( url, filename ) {
5797
5844
 
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' );
5845
+ const fr = new FileReader();
5812
5846
 
5813
- let svgAttributes = [];
5814
- let pathAttributes = [];
5847
+ const _download = function(_url) {
5848
+ var link = document.createElement('a');
5849
+ link.href = _url;
5850
+ link.download = filename;
5851
+ document.body.appendChild(link);
5852
+ link.click();
5853
+ document.body.removeChild(link);
5854
+ };
5815
5855
 
5816
- for( const attr of svg.attributes )
5817
- {
5818
- switch( attr.name )
5856
+ if( url.includes('http') )
5819
5857
  {
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;
5858
+ LX.request({ url: url, dataType: 'blob', success: (f) => {
5859
+ fr.readAsDataURL( f );
5860
+ fr.onload = e => {
5861
+ _download(e.currentTarget.result);
5862
+ };
5863
+ } });
5864
+ }else
5865
+ {
5866
+ _download(url);
5827
5867
  }
5828
- }
5829
5868
 
5830
- for( const attr of path.attributes )
5831
- {
5832
- switch( attr.name )
5869
+ },
5870
+
5871
+ downloadFile: function( filename, data, dataType ) {
5872
+ if(!data)
5833
5873
  {
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;
5874
+ console.warn("No file provided to download");
5875
+ return;
5841
5876
  }
5842
- }
5843
5877
 
5844
- const iconData = [
5845
- parseInt( viewBox[ 2 ] ),
5846
- parseInt( viewBox[ 3 ] ),
5847
- aliases,
5848
- variant,
5849
- pathData,
5878
+ if(!dataType)
5850
5879
  {
5851
- svgAttributes: svgAttributes.length ? svgAttributes.join( ' ' ) : null,
5852
- pathAttributes: pathAttributes.length ? pathAttributes.join( ' ' ) : null
5880
+ if(data.constructor === String )
5881
+ dataType = 'text/plain';
5882
+ else
5883
+ dataType = 'application/octet-stream';
5853
5884
  }
5854
- ];
5855
5885
 
5856
- if( LX.ICONS[ iconName ] )
5857
- {
5858
- console.warn( `${ iconName } will be added/replaced in LX.ICONS` );
5886
+ var file = null;
5887
+ if(data.constructor !== File && data.constructor !== Blob)
5888
+ file = new Blob( [ data ], {type : dataType});
5889
+ else
5890
+ file = data;
5891
+
5892
+ var url = URL.createObjectURL( file );
5893
+ var element = document.createElement("a");
5894
+ element.setAttribute('href', url);
5895
+ element.setAttribute('download', filename );
5896
+ element.style.display = 'none';
5897
+ document.body.appendChild(element);
5898
+ element.click();
5899
+ document.body.removeChild(element);
5900
+ setTimeout( function(){ URL.revokeObjectURL( url ); }, 1000*60 ); //wait one minute to revoke url
5859
5901
  }
5902
+ });
5860
5903
 
5861
- LX.ICONS[ iconName ] = iconData;
5904
+ /**
5905
+ * @method compareThreshold
5906
+ * @param {String} url
5907
+ * @param {Function} onComplete
5908
+ * @param {Function} onError
5909
+ **/
5910
+ function compareThreshold( v, p, n, t )
5911
+ {
5912
+ return Math.abs( v - p ) >= t || Math.abs( v - n ) >= t;
5862
5913
  }
5863
5914
 
5864
- LX.registerIcon = registerIcon;
5915
+ LX.compareThreshold = compareThreshold;
5865
5916
 
5866
5917
  /**
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 )
5918
+ * @method compareThresholdRange
5919
+ * @param {String} url
5920
+ * @param {Function} onComplete
5921
+ * @param {Function} onError
5922
+ **/
5923
+ function compareThresholdRange( v0, v1, t0, t1 )
5873
5924
  {
5874
- LX.extraCommandbarEntries.push( { name, callback } );
5925
+ return v0 >= t0 && v0 <= t1 || v1 >= t0 && v1 <= t1 || v0 <= t0 && v1 >= t1;
5875
5926
  }
5876
5927
 
5877
- LX.registerCommandbarEntry = registerCommandbarEntry;
5928
+ LX.compareThresholdRange = compareThresholdRange;
5878
5929
 
5879
5930
  /**
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.
5931
+ * @method getControlPoints
5932
+ * @param {String} url
5933
+ * @param {Function} onComplete
5934
+ * @param {Function} onError
5935
+ **/
5936
+ function getControlPoints( x0, y0, x1, y1, x2, y2, t )
5937
+ {
5938
+ // x0,y0,x1,y1 are the coordinates of the end (knot) pts of this segment
5939
+ // x2,y2 is the next knot -- not connected here but needed to calculate p2
5940
+ // p1 is the control point calculated here, from x1 back toward x0.
5941
+ // p2 is the next control point, calculated here and returned to become the
5942
+ // next segment's p1.
5943
+ // t is the 'tension' which controls how far the control points spread.
5904
5944
 
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));
5945
+ // Scaling factors: distances from this knot to the previous and following knots.
5946
+ var d01=Math.sqrt(Math.pow(x1-x0,2)+Math.pow(y1-y0,2));
5947
+ var d12=Math.sqrt(Math.pow(x2-x1,2)+Math.pow(y2-y1,2));
5908
5948
 
5909
- var fa=t*d01/(d01+d12);
5910
- var fb=t-fa;
5949
+ var fa=t*d01/(d01+d12);
5950
+ var fb=t-fa;
5911
5951
 
5912
- var p1x=x1+fa*(x0-x2);
5913
- var p1y=y1+fa*(y0-y2);
5952
+ var p1x=x1+fa*(x0-x2);
5953
+ var p1y=y1+fa*(y0-y2);
5914
5954
 
5915
- var p2x=x1-fb*(x0-x2);
5916
- var p2y=y1-fb*(y0-y2);
5955
+ var p2x=x1-fb*(x0-x2);
5956
+ var p2y=y1-fb*(y0-y2);
5917
5957
 
5918
- return [p1x,p1y,p2x,p2y]
5919
- },
5920
- drawSpline( ctx, pts, t ) {
5958
+ return [p1x,p1y,p2x,p2y]
5959
+ }
5921
5960
 
5922
- ctx.save();
5923
- var cp = []; // array of control points, as x0,y0,x1,y1,...
5924
- var n = pts.length;
5961
+ LX.getControlPoints = getControlPoints;
5925
5962
 
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
- }
5963
+ /**
5964
+ * @method drawSpline
5965
+ * @param {CanvasRenderingContext2D} ctx
5966
+ * @param {Array} pts
5967
+ * @param {Number} t
5968
+ **/
5969
+ function drawSpline( ctx, pts, t )
5970
+ {
5971
+ ctx.save();
5972
+ var cp = []; // array of control points, as x0,y0,x1,y1,...
5973
+ var n = pts.length;
5931
5974
 
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
- }
5975
+ // Draw an open curve, not connected at the ends
5976
+ for( var i = 0; i < (n - 4); i += 2 )
5977
+ {
5978
+ cp = cp.concat(LX.getControlPoints(pts[ i ],pts[i+1],pts[i+2],pts[i+3],pts[i+4],pts[i+5],t));
5979
+ }
5940
5980
 
5941
- // For open curves the first and last arcs are simple quadratics.
5981
+ for( var i = 2; i < ( pts.length - 5 ); i += 2 )
5982
+ {
5942
5983
  ctx.beginPath();
5943
- ctx.moveTo( pts[ 0 ], pts[ 1 ] );
5944
- ctx.quadraticCurveTo( cp[ 0 ], cp[ 1 ], pts[ 2 ], pts[ 3 ]);
5984
+ ctx.moveTo(pts[ i ], pts[i+1]);
5985
+ ctx.bezierCurveTo(cp[2*i-2],cp[2*i-1],cp[2*i],cp[2*i+1],pts[i+2],pts[i+3]);
5945
5986
  ctx.stroke();
5946
5987
  ctx.closePath();
5988
+ }
5947
5989
 
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();
5990
+ // For open curves the first and last arcs are simple quadratics.
5991
+ ctx.beginPath();
5992
+ ctx.moveTo( pts[ 0 ], pts[ 1 ] );
5993
+ ctx.quadraticCurveTo( cp[ 0 ], cp[ 1 ], pts[ 2 ], pts[ 3 ]);
5994
+ ctx.stroke();
5995
+ ctx.closePath();
5953
5996
 
5954
- ctx.restore();
5955
- }
5956
- };
5997
+ ctx.beginPath();
5998
+ ctx.moveTo( pts[ n-2 ], pts[ n-1 ] );
5999
+ ctx.quadraticCurveTo( cp[ 2*n-10 ], cp[ 2*n-9 ], pts[ n-4 ], pts[ n-3 ]);
6000
+ ctx.stroke();
6001
+ ctx.closePath();
6002
+
6003
+ ctx.restore();
6004
+ }
5957
6005
 
6006
+ LX.drawSpline = drawSpline;
6007
+
5958
6008
  // area.js @jxarco
5959
6009
 
5960
6010
  class Area {
@@ -6678,7 +6728,7 @@ class Area {
6678
6728
 
6679
6729
  addSidebar( callback, options = {} ) {
6680
6730
 
6681
- let sidebar = new LX.Sidebar( options );
6731
+ let sidebar = new LX.Sidebar( { callback, ...options } );
6682
6732
 
6683
6733
  if( callback )
6684
6734
  {
@@ -7004,8 +7054,8 @@ class Area {
7004
7054
  }
7005
7055
  }
7006
7056
  }
7007
- LX.Area = Area;
7008
-
7057
+ LX.Area = Area;
7058
+
7009
7059
  // widget.js @jxarco
7010
7060
 
7011
7061
  /**
@@ -12175,8 +12225,8 @@ class Map2D extends Widget {
12175
12225
  }
12176
12226
  }
12177
12227
 
12178
- LX.Map2D = Map2D;
12179
-
12228
+ LX.Map2D = Map2D;
12229
+
12180
12230
  // panel.js @jxarco
12181
12231
 
12182
12232
  /**
@@ -13356,8 +13406,8 @@ class Panel {
13356
13406
  }
13357
13407
  }
13358
13408
 
13359
- LX.Panel = Panel;
13360
-
13409
+ LX.Panel = Panel;
13410
+
13361
13411
  // branch.js @jxarco
13362
13412
 
13363
13413
  /**
@@ -13581,8 +13631,8 @@ class Branch {
13581
13631
  }
13582
13632
  }
13583
13633
  }
13584
- LX.Branch = Branch;
13585
-
13634
+ LX.Branch = Branch;
13635
+
13586
13636
  // menubar.js @jxarco
13587
13637
 
13588
13638
  /**
@@ -13899,8 +13949,8 @@ class Menubar {
13899
13949
  }
13900
13950
  }
13901
13951
  }
13902
- LX.Menubar = Menubar;
13903
-
13952
+ LX.Menubar = Menubar;
13953
+
13904
13954
  // sidebar.js @jxarco
13905
13955
 
13906
13956
  /**
@@ -13936,6 +13986,7 @@ class Sidebar {
13936
13986
 
13937
13987
  this.root = document.createElement( "div" );
13938
13988
  this.root.className = "lexsidebar " + ( options.className ?? "" );
13989
+ this.callback = options.callback ?? null;
13939
13990
 
13940
13991
  this._displaySelected = options.displaySelected ?? false;
13941
13992
 
@@ -13952,17 +14003,19 @@ class Sidebar {
13952
14003
  configurable: true
13953
14004
  });
13954
14005
 
14006
+ const mobile = navigator && /Android|iPhone/i.test( navigator.userAgent );
14007
+
13955
14008
  this.side = options.side ?? "left";
13956
14009
  this.collapsable = options.collapsable ?? true;
13957
14010
  this._collapseWidth = ( options.collapseToIcons ?? true ) ? "58px" : "0px";
13958
- this.collapsed = false;
14011
+ this.collapsed = options.collapsed ?? mobile;
13959
14012
 
13960
14013
  this.filterString = "";
13961
14014
 
13962
14015
  LX.doAsync( () => {
13963
14016
 
13964
14017
  this.root.parentElement.ogWidth = this.root.parentElement.style.width;
13965
- this.root.parentElement.style.transition = "width 0.25s ease-out";
14018
+ this.root.parentElement.style.transition = this.collapsed ? "" : "width 0.25s ease-out";
13966
14019
 
13967
14020
  this.resizeObserver = new ResizeObserver( entries => {
13968
14021
  for ( const entry of entries )
@@ -13971,6 +14024,24 @@ class Sidebar {
13971
14024
  }
13972
14025
  });
13973
14026
 
14027
+ if( this.collapsed )
14028
+ {
14029
+ this.root.classList.toggle( "collapsed", this.collapsed );
14030
+ this.root.parentElement.style.width = this._collapseWidth;
14031
+
14032
+ if( !this.resizeObserver )
14033
+ {
14034
+ throw( "Wait until ResizeObserver has been created!" );
14035
+ }
14036
+
14037
+ this.resizeObserver.observe( this.root.parentElement );
14038
+
14039
+ LX.doAsync( () => {
14040
+ this.resizeObserver.unobserve( this.root.parentElement );
14041
+ this.root.querySelectorAll( ".lexsidebarentrycontent" ).forEach( e => e.dataset[ "disableTooltip" ] = !this.collapsed );
14042
+ }, 10 );
14043
+ }
14044
+
13974
14045
  }, 10 );
13975
14046
 
13976
14047
  // Header
@@ -13986,11 +14057,29 @@ class Sidebar {
13986
14057
  const icon = LX.makeIcon( this.side == "left" ? "PanelLeft" : "PanelRight", { title: "Toggle Sidebar", iconClass: "toggler" } );
13987
14058
  this.header.appendChild( icon );
13988
14059
 
13989
- icon.addEventListener( "click", (e) => {
13990
- e.preventDefault();
13991
- e.stopPropagation();
13992
- this.toggleCollapsed();
13993
- } );
14060
+ if( mobile )
14061
+ {
14062
+ // create an area and append a sidebar:
14063
+ const area = new LX.Area({ skipAppend: true });
14064
+ const sheetSidebarOptions = LX.deepCopy( options );
14065
+ sheetSidebarOptions.collapsed = false;
14066
+ sheetSidebarOptions.collapsable = false;
14067
+ area.addSidebar( this.callback, sheetSidebarOptions );
14068
+
14069
+ icon.addEventListener( "click", e => {
14070
+ e.preventDefault();
14071
+ e.stopPropagation();
14072
+ new LX.Sheet("256px", [ area ], { side: this.side } );
14073
+ } );
14074
+ }
14075
+ else
14076
+ {
14077
+ icon.addEventListener( "click", e => {
14078
+ e.preventDefault();
14079
+ e.stopPropagation();
14080
+ this.toggleCollapsed();
14081
+ } );
14082
+ }
13994
14083
  }
13995
14084
  }
13996
14085
 
@@ -14547,8 +14636,8 @@ class Sidebar {
14547
14636
  }
14548
14637
  }
14549
14638
  }
14550
- LX.Sidebar = Sidebar;
14551
-
14639
+ LX.Sidebar = Sidebar;
14640
+
14552
14641
  // asset_view.js @jxarco
14553
14642
 
14554
14643
  class AssetViewEvent {
@@ -15415,6 +15504,6 @@ class AssetView {
15415
15504
  }
15416
15505
  }
15417
15506
 
15418
- LX.AssetView = AssetView;
15419
-
15420
- export { ADD_CUSTOM_WIDGET, Area, AssetView, AssetViewEvent, Branch, LX, Menubar, Panel, Sidebar, Widget };
15507
+ LX.AssetView = AssetView;
15508
+
15509
+ export { ADD_CUSTOM_WIDGET, Area, AssetView, AssetViewEvent, Branch, LX, Menubar, Panel, Sidebar, Widget };