lexgui 0.1.46 → 0.2.0

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.
@@ -8,10 +8,11 @@
8
8
  */
9
9
 
10
10
  var LX = {
11
- version: "0.1.46",
11
+ version: "0.2.0",
12
12
  ready: false,
13
13
  components: [], // specific pre-build components
14
- signals: {} // events and triggers
14
+ signals: {}, // events and triggers
15
+ extraCommandbarEntries: [] // user specific entries for command bar
15
16
  };
16
17
 
17
18
  LX.MOUSE_LEFT_CLICK = 0;
@@ -161,12 +162,13 @@ function getThemeColor( colorName )
161
162
  {
162
163
  const r = getComputedStyle( document.querySelector( ':root' ) );
163
164
  const value = r.getPropertyValue( '--' + colorName );
165
+ const theme = document.documentElement.getAttribute( "data-theme" );
164
166
 
165
- if( value.includes( "light-dark" ) && window.matchMedia )
167
+ if( value.includes( "light-dark" ) )
166
168
  {
167
169
  const currentScheme = r.getPropertyValue( "color-scheme" );
168
170
 
169
- if( ( window.matchMedia( "(prefers-color-scheme: light)" ).matches ) || ( currentScheme == "light" ) )
171
+ if( currentScheme == "light" )
170
172
  {
171
173
  return value.substring( value.indexOf( '(' ) + 1, value.indexOf( ',' ) ).replace( /\s/g, '' );
172
174
  }
@@ -404,6 +406,9 @@ LX.makeDraggable = makeDraggable;
404
406
  * language (String):
405
407
  * windowMode (Boolean):
406
408
  * lineNumbers (Boolean):
409
+ * firstLine (Number): TODO
410
+ * linesAdded (Array):
411
+ * linesRemoved (Array):
407
412
  * tabName (String):
408
413
  */
409
414
  function makeCodeSnippet( code, size, options = { } )
@@ -416,16 +421,15 @@ function makeCodeSnippet( code, size, options = { } )
416
421
 
417
422
  const snippet = document.createElement( "div" );
418
423
  snippet.className = "lexcodesnippet";
419
- snippet.style.width = size[ 0 ];
420
- snippet.style.height = size[ 1 ];
424
+ snippet.style.width = size ? size[ 0 ] : "auto";
425
+ snippet.style.height = size ? size[ 1 ] : "auto";
421
426
  const area = new Area( { noAppend: true } );
422
427
  let editor = new LX.CodeEditor( area, {
423
428
  skipInfo: true,
424
429
  disableEdition: true,
425
430
  allowAddScripts: false,
426
431
  name: options.tabName,
427
- // showTab: options.showTab ?? true,
428
- // lineNumbers: options.lineNumbers ?? true
432
+ // showTab: options.showTab ?? true
429
433
  } );
430
434
  editor.setText( code, options.language ?? "Plain Text" );
431
435
 
@@ -484,12 +488,30 @@ function makeCodeSnippet( code, size, options = { } )
484
488
  tabs.prepend( windowActionButtons );
485
489
  }
486
490
 
491
+ if( !( options.lineNumbers ?? true ) )
492
+ {
493
+ editor.root.classList.add( "no-gutter" );
494
+ }
495
+
487
496
  snippet.appendChild( area.root );
488
497
  return snippet;
489
498
  }
490
499
 
491
500
  LX.makeCodeSnippet = makeCodeSnippet;
492
501
 
502
+ /**
503
+ * @method registerCommandbarEntry
504
+ * @description Adds an extra command bar entry
505
+ * @param {String} name
506
+ * @param {Function} callback
507
+ */
508
+ function registerCommandbarEntry( name, callback )
509
+ {
510
+ LX.extraCommandbarEntries.push( { name, callback } );
511
+ }
512
+
513
+ LX.registerCommandbarEntry = registerCommandbarEntry;
514
+
493
515
  // Math classes
494
516
 
495
517
  class vec2 {
@@ -518,24 +540,23 @@ class vec2 {
518
540
 
519
541
  LX.vec2 = vec2;
520
542
 
521
- function create_global_searchbar( root )
543
+ function _createCommandbar( root )
522
544
  {
523
- let globalSearch = document.createElement("div");
524
- globalSearch.id = "global-search";
525
- globalSearch.className = "hidden";
526
- globalSearch.tabIndex = -1;
527
- root.appendChild( globalSearch );
545
+ let commandbar = document.createElement( "dialog" );
546
+ commandbar.className = "commandbar";
547
+ commandbar.tabIndex = -1;
548
+ root.appendChild( commandbar );
528
549
 
529
550
  let allItems = [];
530
551
  let hoverElId = null;
531
552
 
532
- globalSearch.addEventListener('keydown', function( e ) {
553
+ commandbar.addEventListener('keydown', function( e ) {
533
554
  e.stopPropagation();
534
555
  e.stopImmediatePropagation();
535
556
  hoverElId = hoverElId ?? -1;
536
557
  if( e.key == 'Escape' )
537
558
  {
538
- this.classList.add("hidden");
559
+ this.close();
539
560
  _resetBar( true );
540
561
  }
541
562
  else if( e.key == 'Enter' )
@@ -544,7 +565,7 @@ function create_global_searchbar( root )
544
565
  if( el )
545
566
  {
546
567
  const isCheckbox = (el.item.type && el.item.type === 'checkbox');
547
- this.classList.toggle('hidden');
568
+ this.close();
548
569
  if( isCheckbox )
549
570
  {
550
571
  el.item.checked = !el.item.checked;
@@ -559,7 +580,7 @@ function create_global_searchbar( root )
559
580
  else if ( e.key == 'ArrowDown' && hoverElId < (allItems.length - 1) )
560
581
  {
561
582
  hoverElId++;
562
- globalSearch.querySelectorAll(".hovered").forEach(e => e.classList.remove('hovered'));
583
+ commandbar.querySelectorAll(".hovered").forEach(e => e.classList.remove('hovered'));
563
584
  allItems[ hoverElId ].classList.add('hovered');
564
585
 
565
586
  let dt = allItems[ hoverElId ].offsetHeight * (hoverElId + 1) - itemContainer.offsetHeight;
@@ -574,19 +595,19 @@ function create_global_searchbar( root )
574
595
  } else if ( e.key == 'ArrowUp' && hoverElId > 0 )
575
596
  {
576
597
  hoverElId--;
577
- globalSearch.querySelectorAll(".hovered").forEach(e => e.classList.remove('hovered'));
598
+ commandbar.querySelectorAll(".hovered").forEach(e => e.classList.remove('hovered'));
578
599
  allItems[ hoverElId ].classList.add('hovered');
579
600
  }
580
601
  });
581
602
 
582
- globalSearch.addEventListener('focusout', function( e ) {
603
+ commandbar.addEventListener('focusout', function( e ) {
583
604
  if( e.relatedTarget == e.currentTarget )
584
605
  {
585
606
  return;
586
607
  }
587
608
  e.stopPropagation();
588
609
  e.stopImmediatePropagation();
589
- this.classList.add( "hidden" );
610
+ this.close();
590
611
  _resetBar( true );
591
612
  });
592
613
 
@@ -595,9 +616,7 @@ function create_global_searchbar( root )
595
616
  {
596
617
  e.stopImmediatePropagation();
597
618
  e.stopPropagation();
598
- globalSearch.classList.toggle('hidden');
599
- globalSearch.querySelector('input').focus();
600
- _addElements( undefined );
619
+ LX.setCommandbarState( true );
601
620
  }
602
621
  else
603
622
  {
@@ -675,22 +694,22 @@ function create_global_searchbar( root )
675
694
  const isCheckbox = (i && i.type && i.type === 'checkbox');
676
695
  if( isCheckbox )
677
696
  {
678
- searchItem.innerHTML = "<a class='fa fa-check'></a><span>" + p + t + "</span>"
697
+ searchItem.innerHTML = "<a class='fa fa-check'></a><span>" + ( p + t ) + "</span>"
679
698
  }
680
699
  else
681
700
  {
682
- searchItem.innerHTML = p + t;
701
+ searchItem.innerHTML = ( p + t );
683
702
  }
684
703
  searchItem.entry_name = t;
685
704
  searchItem.callback = c;
686
705
  searchItem.item = i;
687
706
  searchItem.addEventListener('click', function(e) {
688
- this.callback.call(window, this.entry_name);
689
- globalSearch.classList.toggle('hidden');
707
+ this.callback.call( window, this.entry_name );
708
+ LX.setCommandbarState( false );
690
709
  _resetBar( true );
691
710
  });
692
711
  searchItem.addEventListener('mouseenter', function(e) {
693
- globalSearch.querySelectorAll(".hovered").forEach(e => e.classList.remove('hovered'));
712
+ commandbar.querySelectorAll(".hovered").forEach(e => e.classList.remove('hovered'));
694
713
  this.classList.add('hovered');
695
714
  hoverElId = allItems.indexOf( this );
696
715
  });
@@ -724,7 +743,7 @@ function create_global_searchbar( root )
724
743
  _propagateAdd( c, filter, path );
725
744
  };
726
745
 
727
- const _addElements = filter => {
746
+ commandbar._addElements = filter => {
728
747
 
729
748
  _resetBar();
730
749
 
@@ -736,6 +755,16 @@ function create_global_searchbar( root )
736
755
  }
737
756
  }
738
757
 
758
+ for( let entry of LX.extraCommandbarEntries )
759
+ {
760
+ const name = entry.name;
761
+ if( !name.toLowerCase().includes( filter ) )
762
+ {
763
+ continue;
764
+ }
765
+ _addElement( name, entry.callback, "", {} );
766
+ }
767
+
739
768
  if( LX.has('CodeEditor') )
740
769
  {
741
770
  const instances = LX.CodeEditor.getInstances();
@@ -753,7 +782,7 @@ function create_global_searchbar( root )
753
782
 
754
783
  value += key + " <span class='lang-ext'>(" + languages[ l ].ext + ")</span>";
755
784
  if( key.toLowerCase().includes( filter ) ) {
756
- add_element( value, () => {
785
+ _addElement( value, () => {
757
786
  for( let i of instances ) {
758
787
  i._changeLanguage( l );
759
788
  }
@@ -764,14 +793,14 @@ function create_global_searchbar( root )
764
793
  }
765
794
 
766
795
  input.addEventListener('input', function(e) {
767
- _addElements( this.value.toLowerCase() );
796
+ commandbar._addElements( this.value.toLowerCase() );
768
797
  });
769
798
 
770
- globalSearch.appendChild( header );
771
- globalSearch.appendChild( tabArea.root );
772
- globalSearch.appendChild( itemContainer );
799
+ commandbar.appendChild( header );
800
+ commandbar.appendChild( tabArea.root );
801
+ commandbar.appendChild( itemContainer );
773
802
 
774
- return globalSearch;
803
+ return commandbar;
775
804
  }
776
805
 
777
806
  /**
@@ -781,6 +810,7 @@ function create_global_searchbar( root )
781
810
  * id: Id of the main area
782
811
  * skipRoot: Skip adding LX root container
783
812
  * skipDefaultArea: Skip creation of main area
813
+ * strictViewport: Use only window area
784
814
  */
785
815
 
786
816
  function init( options = { } )
@@ -803,16 +833,17 @@ function init( options = { } )
803
833
  this.root = root;
804
834
  this.container = document.body;
805
835
 
806
- // this.modal.toggleAttribute( 'hidden', true );
807
- // this.modal.toggle = function( force ) { this.toggleAttribute( 'hidden', force ); };
808
-
809
836
  this.modal.classList.add( 'hiddenOpacity' );
810
837
  this.modal.toggle = function( force ) { this.classList.toggle( 'hiddenOpacity', force ); };
811
838
 
812
839
  if( options.container )
840
+ {
813
841
  this.container = document.getElementById( options.container );
842
+ }
814
843
 
815
- this.globalSearch = create_global_searchbar( this.container );
844
+ document.documentElement.setAttribute( "data-strictVP", ( options.strictViewport ?? true ) ? "true" : "false" );
845
+
846
+ this.commandbar = _createCommandbar( this.container );
816
847
 
817
848
  this.container.appendChild( modal );
818
849
 
@@ -856,16 +887,48 @@ function init( options = { } )
856
887
  this.main_area = new Area( { id: options.id ?? 'mainarea' } );
857
888
  }
858
889
 
859
- window.matchMedia( "(prefers-color-scheme: dark)" ).addEventListener( "change", event => {
860
- const newColorScheme = event.matches ? "dark" : "light";
861
- LX.emit( "@on_new_color_scheme", newColorScheme );
862
- });
890
+ if( ( options.autoTheme ?? true ) && window.matchMedia && window.matchMedia( "(prefers-color-scheme: light)" ).matches )
891
+ {
892
+ LX.setTheme( "light" );
893
+
894
+ window.matchMedia( "(prefers-color-scheme: dark)" ).addEventListener( "change", event => {
895
+ LX.setTheme( event.matches ? "dark" : "light" );
896
+ });
897
+ }
863
898
 
864
899
  return this.main_area;
865
900
  }
866
901
 
867
902
  LX.init = init;
868
903
 
904
+ /**
905
+ * @method setCommandbarState
906
+ * @param {Boolean} value
907
+ * @param {Boolean} resetEntries
908
+ */
909
+
910
+ function setCommandbarState( value, resetEntries = true )
911
+ {
912
+ const cb = this.commandbar;
913
+
914
+ if( value )
915
+ {
916
+ cb.show();
917
+ cb.querySelector('input').focus();
918
+
919
+ if( resetEntries )
920
+ {
921
+ cb._addElements( undefined );
922
+ }
923
+ }
924
+ else
925
+ {
926
+ cb.close();
927
+ }
928
+ }
929
+
930
+ LX.setCommandbarState = setCommandbarState;
931
+
869
932
  /**
870
933
  * @method message
871
934
  * @param {String} text
@@ -898,9 +961,9 @@ LX.message = message;
898
961
  * @param {String} title (Optional)
899
962
  * @param {*} options
900
963
  * id: Id of the message dialog
901
- * time: (Number) Delay time before close automatically (ms). Defalut: [3000]
902
- * position: (Array) [x,y] Dialog position in screen. Default: [screen centered]
903
- * size: (Array) [width, height]
964
+ * timeout (Number): Delay time before it closes automatically (ms). Default: [3000]
965
+ * position (Array): [x,y] Dialog position in screen. Default: [screen centered]
966
+ * size (Array): [width, height]
904
967
  */
905
968
 
906
969
  function popup( text, title, options = {} )
@@ -910,7 +973,7 @@ function popup( text, title, options = {} )
910
973
  throw("No message to show");
911
974
  }
912
975
 
913
- options.size = options.size ?? [ "auto", "auto" ];
976
+ options.size = options.size ?? [ "max-content", "auto" ];
914
977
  options.class = "lexpopup";
915
978
 
916
979
  const time = options.timeout || 3000;
@@ -918,13 +981,9 @@ function popup( text, title, options = {} )
918
981
  p.addTextArea( null, text, null, { disabled: true, fitHeight: true } );
919
982
  }, options );
920
983
 
921
- dialog.root.classList.add( 'fadein' );
922
- setTimeout(() => {
923
- dialog.root.classList.remove( 'fadein' );
924
- dialog.root.classList.add( 'fadeout' );
925
- }, time - 1000 );
926
-
927
- setTimeout( dialog.close, time );
984
+ setTimeout( () => {
985
+ dialog.close();
986
+ }, Math.max( time, 150 ) );
928
987
 
929
988
  return dialog;
930
989
  }
@@ -1009,6 +1068,25 @@ function badge( text, className, options = {} )
1009
1068
 
1010
1069
  LX.badge = badge;
1011
1070
 
1071
+ /**
1072
+ * @method makeContainer
1073
+ * @param {Array} size
1074
+ * @param {String} className
1075
+ * @param {Object} overrideStyle
1076
+ */
1077
+
1078
+ function makeContainer( size, className, overrideStyle = {} )
1079
+ {
1080
+ const container = document.createElement( "div" );
1081
+ container.className = "lexcontainer " + ( className ?? "" );
1082
+ container.style.width = size && size[ 0 ] ? size[ 0 ] : "100%";
1083
+ container.style.height = size && size[ 1 ] ? size[ 1 ] : "100%";
1084
+ Object.assign( container.style, overrideStyle );
1085
+ return container;
1086
+ }
1087
+
1088
+ LX.makeContainer = makeContainer;
1089
+
1012
1090
  /*
1013
1091
  * Events and Signals
1014
1092
  */
@@ -1491,8 +1569,8 @@ class Area {
1491
1569
  }
1492
1570
 
1493
1571
  area1.root.style.width = "100%";
1494
- area1.root.style.height = "calc( " + height1 + " - " + data + " )";
1495
- area2.root.style.height = "calc( " + height2 + " - " + data + " )";
1572
+ area1.root.style.height = ( height1 == "auto" ? height1 : "calc( " + height1 + " - " + data + " )");
1573
+ area2.root.style.height = ( height2 == "auto" ? height2 : "calc( " + height2 + " - " + data + " )");
1496
1574
  }
1497
1575
  }
1498
1576
 
@@ -2092,7 +2170,7 @@ class Tabs {
2092
2170
 
2093
2171
  area.root.classList.add( "lexareatabscontainer" );
2094
2172
 
2095
- area.split({type: 'vertical', sizes: "auto", resize: false, top: 6});
2173
+ area.split({type: 'vertical', sizes: options.sizes ?? "auto", resize: false, top: 6});
2096
2174
  area.sections[0].attach( container );
2097
2175
 
2098
2176
  this.area = area.sections[1];
@@ -2958,6 +3036,7 @@ class Widget {
2958
3036
  static FORM = 27;
2959
3037
  static DIAL = 28;
2960
3038
  static COUNTER = 29;
3039
+ static TABLE = 30;
2961
3040
 
2962
3041
  static NO_CONTEXT_TYPES = [
2963
3042
  Widget.BUTTON,
@@ -3048,6 +3127,7 @@ class Widget {
3048
3127
  case Widget.FORM: return "Form";
3049
3128
  case Widget.DIAL: return "Dial";
3050
3129
  case Widget.COUNTER: return "Counter";
3130
+ case Widget.TABLE: return "Table";
3051
3131
  case Widget.CUSTOM: return this.customName;
3052
3132
  }
3053
3133
 
@@ -4070,8 +4150,8 @@ class Panel {
4070
4150
 
4071
4151
  let searchIcon = document.createElement('a');
4072
4152
  searchIcon.className = "fa-solid fa-magnifying-glass";
4073
- element.appendChild(input);
4074
4153
  element.appendChild(searchIcon);
4154
+ element.appendChild(input);
4075
4155
 
4076
4156
  input.addEventListener("input", (e) => {
4077
4157
  if(options.callback)
@@ -4123,26 +4203,28 @@ class Panel {
4123
4203
  }
4124
4204
  }
4125
4205
 
4126
- _search_options(options, value) {
4127
- // push to right container
4206
+ _filterOptions( options, value ) {
4207
+
4208
+ // Push to right container
4128
4209
  const emptyFilter = !value.length;
4129
4210
  let filteredOptions = [];
4130
- // add widgets
4131
- for( let i = 0; i < options.length; i++) {
4132
- let o = options[i];
4133
- if(!emptyFilter)
4211
+
4212
+ // Add widgets
4213
+ for( let i = 0; i < options.length; i++ )
4214
+ {
4215
+ let o = options[ i ];
4216
+ if( !emptyFilter )
4134
4217
  {
4135
- let toCompare = (typeof o == 'string') ? o : o.value;
4136
- ;
4218
+ let toCompare = ( typeof o == 'string' ) ? o : o.value;
4137
4219
  const filterWord = value.toLowerCase();
4138
4220
  const name = toCompare.toLowerCase();
4139
- if(!name.includes(filterWord)) continue;
4221
+ if( !name.includes( filterWord ) ) continue;
4140
4222
  }
4141
- // insert filtered widget
4142
- filteredOptions.push(o);
4223
+
4224
+ filteredOptions.push( o );
4143
4225
  }
4144
4226
 
4145
- this.refresh(filteredOptions);
4227
+ this.refresh( filteredOptions );
4146
4228
  }
4147
4229
 
4148
4230
  _trigger( event, callback ) {
@@ -4629,66 +4711,82 @@ class Panel {
4629
4711
  * @param {Array} values Each of the {value, callback} items
4630
4712
  * @param {*} options:
4631
4713
  * float: Justify content (left, center, right) [center]
4714
+ * selected: Selected item by default by value
4632
4715
  * noSelection: Buttons can be clicked, but they are not selectable
4633
4716
  */
4634
4717
 
4635
4718
  addComboButtons( name, values, options = {} ) {
4636
4719
 
4637
- let widget = this.create_widget(name, Widget.BUTTON, options);
4720
+ let widget = this.create_widget( name, Widget.BUTTON, options );
4638
4721
  let element = widget.domEl;
4639
4722
 
4640
4723
  let that = this;
4641
4724
  let container = document.createElement('div');
4642
4725
  container.className = "lexcombobuttons ";
4643
- if( options.float ) container.className += options.float;
4726
+
4727
+ if( options.float )
4728
+ {
4729
+ container.className += options.float;
4730
+ }
4731
+
4644
4732
  container.style.width = "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
4645
4733
 
4646
- let should_select = !(options.noSelection ?? false);
4734
+ let buttonsBox = document.createElement('div');
4735
+ buttonsBox.className = "lexcombobuttonsbox ";
4736
+
4737
+ let shouldSelect = !( options.noSelection ?? false );
4738
+
4647
4739
  for( let b of values )
4648
4740
  {
4649
- if( !b.value ) throw("Set 'value' for each button!");
4741
+ if( !b.value )
4742
+ {
4743
+ throw( "Set 'value' for each button!" );
4744
+ }
4650
4745
 
4651
4746
  let buttonEl = document.createElement('button');
4652
4747
  buttonEl.className = "lexbutton combo";
4653
4748
  buttonEl.title = b.icon ? b.value : "";
4654
- if(options.buttonClass)
4655
- buttonEl.classList.add(options.buttonClass);
4749
+ buttonEl.id = b.id ?? "";
4656
4750
 
4657
- if(options.selected == b.value)
4658
- buttonEl.classList.add("selected");
4751
+ if( options.buttonClass )
4752
+ {
4753
+ buttonEl.classList.add( options.buttonClass );
4754
+ }
4659
4755
 
4660
- if(b.id)
4661
- buttonEl.id = b.id;
4756
+ if( shouldSelect && options.selected == b.value )
4757
+ {
4758
+ buttonEl.classList.add("selected");
4759
+ }
4662
4760
 
4663
- buttonEl.innerHTML = (b.icon ? "<a class='" + b.icon +"'></a>" : "") + "<span>" + (b.icon ? "" : b.value) + "</span>";
4761
+ buttonEl.innerHTML = ( b.icon ? "<a class='" + b.icon +"'></a>" : "" ) + "<span>" + ( b.icon ? "" : b.value ) + "</span>";
4664
4762
 
4665
- if(options.disabled)
4666
- buttonEl.setAttribute("disabled", true);
4763
+ if( options.disabled )
4764
+ {
4765
+ buttonEl.setAttribute( "disabled", true );
4766
+ }
4667
4767
 
4668
- buttonEl.addEventListener("click", function(e) {
4669
- if(should_select) {
4768
+ buttonEl.addEventListener("click", function( e ) {
4769
+ if( shouldSelect )
4770
+ {
4670
4771
  container.querySelectorAll('button').forEach( s => s.classList.remove('selected'));
4671
4772
  this.classList.add('selected');
4672
4773
  }
4673
- that._trigger( new IEvent(name, b.value, e), b.callback );
4674
- });
4675
4774
 
4676
- container.appendChild(buttonEl);
4775
+ that._trigger( new IEvent( name, b.value, e ), b.callback );
4776
+ });
4677
4777
 
4678
- // Remove branch padding and margins
4679
- if(widget.name === undefined) {
4680
- buttonEl.className += " noname";
4681
- buttonEl.style.width = "100%";
4682
- }
4778
+ buttonsBox.appendChild( buttonEl );
4683
4779
  }
4684
4780
 
4685
4781
  // Remove branch padding and margins
4686
- if(widget.name !== undefined) {
4782
+ if( !widget.name)
4783
+ {
4687
4784
  element.className += " noname";
4688
4785
  container.style.width = "100%";
4689
4786
  }
4690
4787
 
4691
- element.appendChild(container);
4788
+ container.appendChild( buttonsBox );
4789
+ element.appendChild( container );
4692
4790
 
4693
4791
  return widget;
4694
4792
  }
@@ -4940,6 +5038,8 @@ class Panel {
4940
5038
  * filter: Add a search bar to the widget [false]
4941
5039
  * disabled: Make the widget disabled [false]
4942
5040
  * skipReset: Don't add the reset value button when value changes
5041
+ * placeholder: Placeholder for the filter input
5042
+ * emptyMsg: Custom message to show when no filtered results
4943
5043
  */
4944
5044
 
4945
5045
  addDropdown( name, values, value, callback, options = {} ) {
@@ -4985,17 +5085,74 @@ class Panel {
4985
5085
  let buttonName = value;
4986
5086
  buttonName += "<a class='fa-solid fa-angle-down' style='float:right; margin-right: 3px;'></a>";
4987
5087
 
4988
- this.queue(container);
5088
+ this.queue( container );
5089
+
5090
+ const _placeOptions = ( parent ) => {
5091
+
5092
+ console.log("Replacing container");
5093
+
5094
+ const overflowContainer = parent.getParentArea();
5095
+ const rect = selectedOption.getBoundingClientRect();
5096
+ const nestedDialog = parent.parentElement.closest( "dialog" );
5097
+
5098
+ // Manage vertical aspect
5099
+ {
5100
+ const listHeight = parent.offsetHeight;
5101
+ let topPosition = rect.y;
5102
+
5103
+ let maxY = window.innerHeight;
4989
5104
 
4990
- const _getMaxListWidth = () => {
5105
+ if( overflowContainer )
5106
+ {
5107
+ const parentRect = overflowContainer.getBoundingClientRect();
5108
+ maxY = parentRect.y + parentRect.height;
5109
+ }
5110
+
5111
+ if( nestedDialog )
5112
+ {
5113
+ const rect = nestedDialog.getBoundingClientRect();
5114
+ topPosition -= rect.y;
5115
+ }
5116
+
5117
+ parent.style.top = ( topPosition + selectedOption.offsetHeight ) + 'px';
5118
+
5119
+ const showAbove = ( topPosition + listHeight ) > maxY;
5120
+ if( showAbove )
5121
+ {
5122
+ parent.style.top = ( topPosition - listHeight ) + 'px';
5123
+ parent.classList.add( "place-above" );
5124
+ }
5125
+ }
4991
5126
 
4992
- let maxWidth = 0;
4993
- for( let i of values )
5127
+ // Manage horizontal aspect
4994
5128
  {
4995
- const iString = String( i );
4996
- maxWidth = Math.max( iString.length, maxWidth );
5129
+ const listWidth = parent.offsetWidth;
5130
+ let leftPosition = rect.x;
5131
+
5132
+ parent.style.minWidth = ( rect.width ) + 'px';
5133
+
5134
+ if( nestedDialog )
5135
+ {
5136
+ const rect = nestedDialog.getBoundingClientRect();
5137
+ leftPosition -= rect.x;
5138
+ }
5139
+
5140
+ parent.style.left = ( leftPosition ) + 'px';
5141
+
5142
+ let maxX = window.innerWidth;
5143
+
5144
+ if( overflowContainer )
5145
+ {
5146
+ const parentRect = overflowContainer.getBoundingClientRect();
5147
+ maxX = parentRect.x + parentRect.width;
5148
+ }
5149
+
5150
+ const showLeft = ( leftPosition + listWidth ) > maxX;
5151
+ if( showLeft )
5152
+ {
5153
+ parent.style.left = ( leftPosition - ( listWidth - rect.width ) ) + 'px';
5154
+ }
4997
5155
  }
4998
- return Math.max( maxWidth * 10, 80 );
4999
5156
  };
5000
5157
 
5001
5158
  let selectedOption = this.addButton( null, buttonName, ( value, event ) => {
@@ -5005,52 +5162,50 @@ class Panel {
5005
5162
  return;
5006
5163
  }
5007
5164
 
5008
- list.toggleAttribute( "hidden" );
5009
- list.classList.remove( "place-above" );
5165
+ listDialog.classList.remove( "place-above" );
5166
+ const opened = listDialog.hasAttribute( "open" );
5010
5167
 
5011
- const listHeight = 26 * values.length;
5012
- const rect = selectedOption.getBoundingClientRect();
5013
- const topPosition = rect.y;
5014
-
5015
- let maxY = window.innerHeight;
5016
- let overflowContainer = list.getParentArea();
5017
-
5018
- if( overflowContainer )
5168
+ if( !opened )
5019
5169
  {
5020
- const parentRect = overflowContainer.getBoundingClientRect();
5021
- maxY = parentRect.y + parentRect.height;
5170
+ listDialog.show();
5171
+ _placeOptions( listDialog );
5172
+ }
5173
+ else
5174
+ {
5175
+ listDialog.close();
5022
5176
  }
5023
5177
 
5024
- list.style.top = ( topPosition + selectedOption.offsetHeight ) + 'px';
5025
-
5026
- const showAbove = ( topPosition + listHeight ) > maxY;
5027
- if( showAbove )
5178
+ if( filter )
5028
5179
  {
5029
- list.style.top = ( topPosition - listHeight ) + 'px';
5030
- list.classList.add( "place-above" );
5180
+ filter.querySelector( "input" ).focus();
5031
5181
  }
5032
5182
 
5033
- list.style.width = (event.currentTarget.clientWidth) + 'px';
5034
- list.style.minWidth = (_getMaxListWidth()) + 'px';
5035
- list.focus();
5036
- }, { buttonClass: "array", skipInlineCount: true });
5183
+ }, { buttonClass: "array", skipInlineCount: true, disabled: options.disabled });
5037
5184
 
5038
5185
  this.clearQueue();
5039
5186
 
5040
5187
  selectedOption.style.width = "100%";
5041
5188
 
5042
5189
  selectedOption.refresh = (v) => {
5043
- if(selectedOption.querySelector("span").innerText == "")
5190
+ if( selectedOption.querySelector("span").innerText == "" )
5191
+ {
5044
5192
  selectedOption.querySelector("span").innerText = v;
5193
+ }
5045
5194
  else
5195
+ {
5046
5196
  selectedOption.querySelector("span").innerHTML = selectedOption.querySelector("span").innerHTML.replaceAll(selectedOption.querySelector("span").innerText, v);
5197
+ }
5047
5198
  }
5048
5199
 
5049
5200
  // Add dropdown options container
5201
+
5202
+ const listDialog = document.createElement( 'dialog' );
5203
+ listDialog.className = "lexdropdownoptions";
5204
+
5050
5205
  let list = document.createElement( 'ul' );
5051
5206
  list.tabIndex = -1;
5052
5207
  list.className = "lexoptions";
5053
- list.hidden = true;
5208
+ listDialog.appendChild( list )
5054
5209
 
5055
5210
  list.addEventListener( 'focusout', function( e ) {
5056
5211
  e.stopPropagation();
@@ -5068,97 +5223,118 @@ class Panel {
5068
5223
  {
5069
5224
  return;
5070
5225
  }
5071
- this.toggleAttribute( 'hidden', true );
5226
+ listDialog.close();
5072
5227
  });
5073
5228
 
5074
5229
  // Add filter options
5075
5230
  let filter = null;
5076
- if(options.filter ?? false)
5231
+ if( options.filter ?? false )
5077
5232
  {
5078
- filter = this._addFilter("Search option", {container: list, callback: this._search_options.bind(list, values)});
5079
- }
5233
+ filter = this._addFilter( options.placeholder ?? "Search...", { container: list, callback: this._filterOptions.bind( list, values )} );
5080
5234
 
5081
- // Create option list to empty it easily..
5082
- const listOptions = document.createElement('span');
5083
- list.appendChild( listOptions );
5084
-
5085
- if( filter )
5086
- {
5087
- list.prepend( filter );
5088
- listOptions.style.height = "calc(100% - 25px)";
5235
+ list.appendChild( filter );
5089
5236
 
5090
5237
  filter.addEventListener('focusout', function( e ) {
5091
5238
  if (e.relatedTarget && e.relatedTarget.tagName == "UL" && e.relatedTarget.classList.contains("lexoptions"))
5092
5239
  {
5093
5240
  return;
5094
5241
  }
5095
- list.toggleAttribute( 'hidden', true );
5242
+ listDialog.close();
5096
5243
  });
5097
5244
  }
5098
5245
 
5246
+ // Create option list to empty it easily..
5247
+ const listOptions = document.createElement('span');
5248
+ listOptions.style.height = "calc(100% - 25px)";
5249
+ list.appendChild( listOptions );
5250
+
5099
5251
  // Add dropdown options list
5100
- list.refresh = options => {
5252
+ list.refresh = ( options ) => {
5101
5253
 
5102
5254
  // Empty list
5103
5255
  listOptions.innerHTML = "";
5104
5256
 
5105
- for(let i = 0; i < options.length; i++)
5257
+ if( !options.length )
5258
+ {
5259
+ let iValue = options.emptyMsg ?? "No options found.";
5260
+
5261
+ let option = document.createElement( "div" );
5262
+ option.className = "option";
5263
+ option.style.flexDirection = "unset";
5264
+ option.innerHTML = iValue;
5265
+
5266
+ let li = document.createElement( "li" );
5267
+ li.className = "lexdropdownitem empty";
5268
+ li.appendChild( option );
5269
+
5270
+ listOptions.appendChild( li );
5271
+ return;
5272
+ }
5273
+
5274
+ for( let i = 0; i < options.length; i++ )
5106
5275
  {
5107
- let iValue = options[i];
5108
- let li = document.createElement('li');
5109
- let option = document.createElement('div');
5276
+ let iValue = options[ i ];
5277
+ let li = document.createElement( "li" );
5278
+ let option = document.createElement( "div" );
5110
5279
  option.className = "option";
5111
- li.appendChild(option);
5112
- li.addEventListener("click", (e) => {
5113
- element.querySelector(".lexoptions").toggleAttribute('hidden', true);
5114
- const currentSelected = element.querySelector(".lexoptions .selected");
5115
- if(currentSelected) currentSelected.classList.remove("selected");
5116
- value = e.currentTarget.getAttribute("value");
5117
- e.currentTarget.toggleAttribute('hidden', false);
5118
- e.currentTarget.classList.add("selected");
5280
+ li.appendChild( option );
5281
+
5282
+ li.addEventListener( "click", e => {
5283
+ listDialog.close();
5284
+ const currentSelected = element.querySelector( ".lexoptions .selected" );
5285
+ if(currentSelected) currentSelected.classList.remove( "selected" );
5286
+ value = e.currentTarget.getAttribute( "value" );
5287
+ e.currentTarget.toggleAttribute( "hidden", false );
5288
+ e.currentTarget.classList.add( "selected" );
5119
5289
  selectedOption.refresh(value);
5120
5290
 
5121
- let btn = element.querySelector(".lexwidgetname .lexicon");
5122
- if(btn) btn.style.display = (value != wValue.iValue ? "block" : "none");
5123
- that._trigger( new IEvent(name, value, null), callback );
5291
+ let btn = element.querySelector( ".lexwidgetname .lexicon" );
5292
+ if( btn ) btn.style.display = (value != wValue.iValue ? "block" : "none");
5293
+ that._trigger( new IEvent( name, value, null ), callback );
5124
5294
 
5125
5295
  // Reset filter
5126
- if(filter)
5296
+ if( filter )
5127
5297
  {
5128
- filter.querySelector('input').value = "";
5129
- this._search_options.bind(list, values, "")();
5298
+ filter.querySelector( "input" ).value = "";
5299
+ this._filterOptions.bind( list, values, "" )();
5130
5300
  }
5131
5301
  });
5132
5302
 
5133
5303
  // Add string option
5134
- if( iValue.constructor != Object ) {
5135
- option.style.flexDirection = 'unset';
5304
+ if( iValue.constructor != Object )
5305
+ {
5306
+ option.style.flexDirection = "unset";
5136
5307
  option.innerHTML = "</a><span>" + iValue + "</span><a class='fa-solid fa-check'>";
5137
5308
  option.value = iValue;
5138
- li.setAttribute("value", iValue);
5309
+ li.setAttribute( "value", iValue );
5139
5310
  li.className = "lexdropdownitem";
5140
- if( i == (options.length - 1) ) li.className += " last";
5141
- if(iValue == value) {
5142
- li.classList.add("selected");
5311
+
5312
+ if( iValue == value )
5313
+ {
5314
+ li.classList.add( "selected" );
5143
5315
  wValue.innerHTML = iValue;
5144
5316
  }
5145
5317
  }
5146
- else {
5318
+ else
5319
+ {
5147
5320
  // Add image option
5148
- let img = document.createElement("img");
5321
+ let img = document.createElement( "img" );
5149
5322
  img.src = iValue.src;
5150
- li.setAttribute("value", iValue.value);
5323
+ li.setAttribute( "value", iValue.value );
5151
5324
  li.className = "lexlistitem";
5152
5325
  option.innerText = iValue.value;
5153
5326
  option.className += " media";
5154
- option.prepend(img);
5327
+ option.prepend( img );
5328
+
5329
+ option.setAttribute( "value", iValue.value );
5330
+ option.setAttribute( "data-index", i );
5331
+ option.setAttribute( "data-src", iValue.src );
5332
+ option.setAttribute( "title", iValue.value );
5155
5333
 
5156
- option.setAttribute("value", iValue.value);
5157
- option.setAttribute("data-index", i);
5158
- option.setAttribute("data-src", iValue.src);
5159
- option.setAttribute("title", iValue.value);
5160
- if(value == iValue.value)
5161
- li.classList.add("selected");
5334
+ if( value == iValue.value )
5335
+ {
5336
+ li.classList.add( "selected" );
5337
+ }
5162
5338
  }
5163
5339
 
5164
5340
  listOptions.appendChild( li );
@@ -5167,7 +5343,7 @@ class Panel {
5167
5343
 
5168
5344
  list.refresh( values );
5169
5345
 
5170
- container.appendChild( list );
5346
+ container.appendChild( listDialog );
5171
5347
  element.appendChild( container );
5172
5348
 
5173
5349
  // Remove branch padding and margins
@@ -7346,6 +7522,228 @@ class Panel {
7346
7522
 
7347
7523
  return widget;
7348
7524
  }
7525
+
7526
+ /**
7527
+ * @method addTable
7528
+ * @param {String} name Widget name
7529
+ * @param {Number} data Table data
7530
+ * @param {*} options:
7531
+ * head: Table headers (each of the headers per column)
7532
+ * body: Table body (data per row for each column)
7533
+ * rowActions: Allow to add actions per row
7534
+ * onMenuAction: Function callback to fill the "menu" context
7535
+ * selectable: Each row can be selected
7536
+ */
7537
+
7538
+ addTable( name, data, options = { } ) {
7539
+
7540
+ if( !data )
7541
+ {
7542
+ throw( "Data is needed to create a table!" );
7543
+ }
7544
+
7545
+ let widget = this.create_widget( name, Widget.TABLE, options );
7546
+
7547
+ widget.onGetValue = () => {
7548
+
7549
+ };
7550
+
7551
+ widget.onSetValue = ( newValue, skipCallback ) => {
7552
+
7553
+ };
7554
+
7555
+ let element = widget.domEl;
7556
+
7557
+ const container = document.createElement('div');
7558
+ container.className = "lextable";
7559
+ container.style.width = "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
7560
+
7561
+ const table = document.createElement( 'table' );
7562
+ container.appendChild( table );
7563
+
7564
+ data.head = data.head ?? [];
7565
+ data.body = data.body ?? [];
7566
+ data.orderMap = { };
7567
+ data.checkMap = { };
7568
+
7569
+ function compareFn( idx, order, a, b) {
7570
+ if (a[idx] < b[idx]) return -order;
7571
+ else if (a[idx] > b[idx]) return order;
7572
+ return 0;
7573
+ }
7574
+
7575
+ widget.refreshTable = () => {
7576
+
7577
+ table.innerHTML = "";
7578
+
7579
+ // Head
7580
+ {
7581
+ const head = document.createElement( 'thead' );
7582
+ head.className = "lextablehead";
7583
+ table.appendChild( head );
7584
+
7585
+ const hrow = document.createElement( 'tr' );
7586
+
7587
+ if( options.selectable )
7588
+ {
7589
+ const th = document.createElement( 'th' );
7590
+ const input = document.createElement( 'input' );
7591
+ input.type = "checkbox";
7592
+ input.className = "lexcheckbox";
7593
+ input.checked = data.checkMap[ ":root" ] ?? false;
7594
+ th.appendChild( input );
7595
+
7596
+ input.addEventListener( 'change', function() {
7597
+
7598
+ data.checkMap[ ":root" ] = this.checked;
7599
+
7600
+ const body = table.querySelector( "tbody" );
7601
+ for( const el of body.childNodes )
7602
+ {
7603
+ data.checkMap[ el.getAttribute( "rowId" ) ] = this.checked;
7604
+ el.querySelector( "input" ).checked = this.checked;
7605
+ }
7606
+ });
7607
+
7608
+ hrow.appendChild( th );
7609
+ }
7610
+
7611
+ for( const headData of data.head )
7612
+ {
7613
+ const th = document.createElement( 'th' );
7614
+ th.innerHTML = `${ headData } <a class="fa-solid fa-sort"></a>`;
7615
+
7616
+ th.querySelector( 'a' ).addEventListener( 'click', () => {
7617
+
7618
+ if( !data.orderMap[ headData ] )
7619
+ {
7620
+ data.orderMap[ headData ] = 1;
7621
+ }
7622
+
7623
+ const idx = data.head.indexOf(headData);
7624
+ data.body = data.body.sort( compareFn.bind( this, idx,data.orderMap[ headData ] ) );
7625
+ data.orderMap[ headData ] = -data.orderMap[ headData ];
7626
+
7627
+ widget.refreshTable();
7628
+
7629
+ });
7630
+
7631
+ hrow.appendChild( th );
7632
+ }
7633
+
7634
+ // Add empty header column
7635
+ if( options.rowActions )
7636
+ {
7637
+ const th = document.createElement( 'th' );
7638
+ th.className = "sm";
7639
+ hrow.appendChild( th );
7640
+ }
7641
+
7642
+ head.appendChild( hrow );
7643
+ }
7644
+
7645
+ // Body
7646
+ {
7647
+ const body = document.createElement( 'tbody' );
7648
+ body.className = "lextablebody";
7649
+ table.appendChild( body );
7650
+
7651
+ for( let r = 0; r < data.body.length; ++r )
7652
+ {
7653
+ const bodyData = data.body[ r ];
7654
+ const row = document.createElement( 'tr' );
7655
+ const rowId = LX.getSupportedDOMName( bodyData.join( '-' ) );
7656
+ row.setAttribute( "rowId", rowId );
7657
+
7658
+ if( options.selectable )
7659
+ {
7660
+ const td = document.createElement( 'td' );
7661
+ const input = document.createElement( 'input' );
7662
+ input.type = "checkbox";
7663
+ input.className = "lexcheckbox";
7664
+ input.checked = data.checkMap[ rowId ];
7665
+ td.appendChild( input );
7666
+
7667
+ input.addEventListener( 'change', function() {
7668
+ data.checkMap[ rowId ] = this.checked;
7669
+ });
7670
+
7671
+ row.appendChild( td );
7672
+ }
7673
+
7674
+ for( const rowData of bodyData )
7675
+ {
7676
+ const td = document.createElement( 'td' );
7677
+ td.innerHTML = `${ rowData }`;
7678
+ row.appendChild( td );
7679
+ }
7680
+
7681
+ if( options.rowActions )
7682
+ {
7683
+ const td = document.createElement( 'td' );
7684
+ td.className = "sm";
7685
+
7686
+ const buttons = document.createElement( 'div' );
7687
+ buttons.className = "lextablebuttons";
7688
+ td.appendChild( buttons );
7689
+
7690
+ for( const action of options.rowActions )
7691
+ {
7692
+ const button = document.createElement( 'a' );
7693
+ button.className = "lexicon";
7694
+
7695
+ if( action == "delete" )
7696
+ {
7697
+ button.className += " fa-solid fa-trash-can";
7698
+ button.addEventListener( 'click', function() {
7699
+ // Don't need to refresh table..
7700
+ data.body.splice( r, 1 );
7701
+ row.remove();
7702
+ });
7703
+ }
7704
+ else if( action == "menu" )
7705
+ {
7706
+ button.className += " fa-solid fa-ellipsis";
7707
+ button.addEventListener( 'click', function( event ) {
7708
+ addContextMenu( null, event, c => {
7709
+ if( options.onMenuAction )
7710
+ {
7711
+ options.onMenuAction( c );
7712
+ return;
7713
+ }
7714
+ console.warn( "Using <Menu action> without action callbacks." );
7715
+ } );
7716
+ });
7717
+ }
7718
+ else // custom actions
7719
+ {
7720
+ console.assert( action.constructor == Object );
7721
+ button.className += ` ${ action.icon }`;
7722
+ }
7723
+
7724
+ buttons.appendChild( button );
7725
+ }
7726
+
7727
+ row.appendChild( td );
7728
+ }
7729
+
7730
+ body.appendChild( row );
7731
+ }
7732
+ }
7733
+ }
7734
+
7735
+ widget.refreshTable();
7736
+
7737
+ if( !widget.name )
7738
+ {
7739
+ element.className += " noname";
7740
+ container.style.width = "100%";
7741
+ }
7742
+
7743
+ element.appendChild( container );
7744
+
7745
+ return widget;
7746
+ }
7349
7747
  }
7350
7748
 
7351
7749
  LX.Panel = Panel;
@@ -7702,16 +8100,15 @@ class Dialog {
7702
8100
  draggable = options.draggable ?? true,
7703
8101
  modal = options.modal ?? false;
7704
8102
 
7705
- if( modal )
7706
- {
7707
- LX.modal.toggle( false );
7708
- }
7709
-
7710
- var root = document.createElement('div');
8103
+ var root = document.createElement('dialog');
7711
8104
  root.className = "lexdialog " + (options.class ?? "");
7712
8105
  root.id = options.id ?? "dialog" + Dialog._last_id++;
7713
8106
  LX.root.appendChild( root );
7714
8107
 
8108
+ doAsync( () => {
8109
+ modal ? root.showModal() : root.show();
8110
+ }, 10 );
8111
+
7715
8112
  let that = this;
7716
8113
 
7717
8114
  var titleDiv = document.createElement('div');
@@ -7793,18 +8190,17 @@ class Dialog {
7793
8190
 
7794
8191
  if( !options.onclose )
7795
8192
  {
7796
- that.panel.clear();
7797
- root.remove();
8193
+ root.close();
8194
+
8195
+ doAsync( () => {
8196
+ that.panel.clear();
8197
+ root.remove();
8198
+ }, 150 );
7798
8199
  }
7799
8200
  else
7800
8201
  {
7801
8202
  options.onclose( this.root );
7802
8203
  }
7803
-
7804
- if( modal )
7805
- {
7806
- LX.modal.toggle( true );
7807
- }
7808
8204
  };
7809
8205
 
7810
8206
  var closeButton = document.createElement( 'a' );
@@ -7919,21 +8315,29 @@ class PocketDialog extends Dialog {
7919
8315
  options.draggable = options.draggable ?? false;
7920
8316
  options.closable = options.closable ?? false;
7921
8317
 
8318
+ const dragMargin = 3;
8319
+
7922
8320
  super( title, callback, options );
7923
8321
 
7924
8322
  let that = this;
7925
8323
  // Update margins on branch title closes/opens
7926
8324
  LX.addSignal("@on_branch_closed", this.panel, closed => {
7927
8325
  if( this.dock_pos == PocketDialog.BOTTOM )
7928
- this.root.style.top = "calc(100% - " + (this.root.offsetHeight + 6) + "px)";
8326
+ {
8327
+ this.root.style.top = "calc(100% - " + (this.root.offsetHeight + dragMargin) + "px)";
8328
+ }
7929
8329
  });
7930
8330
 
7931
8331
  // Custom
7932
8332
  this.root.classList.add( "pocket" );
7933
- if( !options.position ) {
7934
- this.root.style.left = "calc(100% - " + (this.root.offsetWidth + 6) + "px)";
7935
- this.root.style.top = "0px";
8333
+ this.root.style.left = "unset";
8334
+
8335
+ if( !options.position )
8336
+ {
8337
+ this.root.style.right = dragMargin + "px";
8338
+ this.root.style.top = dragMargin + "px";
7936
8339
  }
8340
+
7937
8341
  this.panel.root.style.width = "calc( 100% - 12px )";
7938
8342
  this.panel.root.style.height = "calc( 100% - 40px )";
7939
8343
  this.dock_pos = PocketDialog.TOP;
@@ -7954,7 +8358,7 @@ class PocketDialog extends Dialog {
7954
8358
 
7955
8359
  if( this.dock_pos == PocketDialog.BOTTOM )
7956
8360
  that.root.style.top = this.root.classList.contains("minimized") ?
7957
- "calc(100% - " + (that.title.offsetHeight + 6) + "px)" : "calc(100% - " + (that.root.offsetHeight + 6) + "px)";
8361
+ "calc(100% - " + (that.title.offsetHeight + 6) + "px)" : "calc(100% - " + (that.root.offsetHeight + dragMargin) + "px)";
7958
8362
  });
7959
8363
 
7960
8364
  if( !options.draggable )
@@ -7969,26 +8373,42 @@ class PocketDialog extends Dialog {
7969
8373
  switch( t )
7970
8374
  {
7971
8375
  case 'b':
7972
- this.root.style.top = "calc(100% - " + (this.root.offsetHeight + 6) + "px)";
8376
+ this.root.style.top = "calc(100% - " + (this.root.offsetHeight + dragMargin) + "px)";
7973
8377
  break;
7974
8378
  case 'l':
7975
- this.root.style.left = options.position ? options.position[ 1 ] : "0px";
8379
+ this.root.style.right = "unset";
8380
+ this.root.style.left = options.position ? options.position[ 1 ] : ( dragMargin + "px" );
7976
8381
  break;
7977
8382
  }
7978
8383
  }
7979
8384
  }
7980
8385
 
7981
8386
  this.root.classList.add('dockable');
7982
- this.title.addEventListener("keydown", function(e) {
7983
- if( e.ctrlKey && e.key == 'ArrowLeft' ) {
8387
+
8388
+ this.title.addEventListener("keydown", function( e ) {
8389
+ if( !e.ctrlKey )
8390
+ {
8391
+ return;
8392
+ }
8393
+
8394
+ that.root.style.right = "unset";
8395
+
8396
+ if( e.key == 'ArrowLeft' )
8397
+ {
7984
8398
  that.root.style.left = '0px';
7985
- } else if( e.ctrlKey && e.key == 'ArrowRight' ) {
7986
- that.root.style.left = "calc(100% - " + (that.root.offsetWidth + 6) + "px)";
7987
- }else if( e.ctrlKey && e.key == 'ArrowUp' ) {
8399
+ }
8400
+ else if( e.key == 'ArrowRight' )
8401
+ {
8402
+ that.root.style.left = "calc(100% - " + (that.root.offsetWidth + dragMargin) + "px)";
8403
+ }
8404
+ else if( e.key == 'ArrowUp' )
8405
+ {
7988
8406
  that.root.style.top = "0px";
7989
8407
  that.dock_pos = PocketDialog.TOP;
7990
- }else if( e.ctrlKey && e.key == 'ArrowDown' ) {
7991
- that.root.style.top = "calc(100% - " + (that.root.offsetHeight + 6) + "px)";
8408
+ }
8409
+ else if( e.key == 'ArrowDown' )
8410
+ {
8411
+ that.root.style.top = "calc(100% - " + (that.root.offsetHeight + dragMargin) + "px)";
7992
8412
  that.dock_pos = PocketDialog.BOTTOM;
7993
8413
  }
7994
8414
  });
@@ -9287,7 +9707,7 @@ class AssetView {
9287
9707
  }
9288
9708
 
9289
9709
  this.rightPanel.sameLine();
9290
- this.rightPanel.addDropdown( "Filter", this.allowedTypes, this.allowedTypes[ 0 ], v => this._refreshContent.call(this, null, v), { width: "20%", minWidth: "128px" } );
9710
+ this.rightPanel.addDropdown( "Filter", this.allowedTypes, this.allowedTypes[ 0 ], v => this._refreshContent.call(this, null, v), { width: "30%", minWidth: "128px" } );
9291
9711
  this.rightPanel.addText( null, this.searchValue ?? "", v => this._refreshContent.call(this, v, null), { placeholder: "Search assets.." } );
9292
9712
  this.rightPanel.addButton( null, "<a class='fa fa-arrow-up-short-wide'></a>", on_sort.bind(this), { className: "micro", title: "Sort" } );
9293
9713
  this.rightPanel.addButton( null, "<a class='fa-solid fa-grip'></a>", on_change_view.bind(this), { className: "micro", title: "View" } );