lexgui 0.6.8 → 0.6.10

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.
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  const LX = {
10
- version: "0.6.8",
10
+ version: "0.6.10",
11
11
  ready: false,
12
12
  components: [], // Specific pre-build components
13
13
  signals: {}, // Events and triggers
@@ -311,7 +311,7 @@ function _createCommandbar( root )
311
311
 
312
312
  const _propagateAdd = ( item, filter, path, skipPropagation ) => {
313
313
 
314
- if( !item )
314
+ if( !item || ( item.constructor != Object ) )
315
315
  {
316
316
  return;
317
317
  }
@@ -1530,7 +1530,7 @@ class ColorPicker {
1530
1530
  copyButtonWidget.root.querySelector( "input[type='checkbox']" ).style.pointerEvents = "none";
1531
1531
 
1532
1532
  LX.doAsync( () => {
1533
- copyButtonWidget.root.swap( true );
1533
+ copyButtonWidget.swap( true );
1534
1534
  copyButtonWidget.root.querySelector( "input[type='checkbox']" ).style.pointerEvents = "auto";
1535
1535
  }, 3000 );
1536
1536
 
@@ -5070,7 +5070,7 @@ LX.makeCodeSnippet = makeCodeSnippet;
5070
5070
  * @param {Array} keys
5071
5071
  * @param {String} extraClass
5072
5072
  */
5073
- function makeKbd( keys, extraClass = "" )
5073
+ function makeKbd( keys, useSpecialKeys = true, extraClass = "" )
5074
5074
  {
5075
5075
  const specialKeys = {
5076
5076
  "Ctrl": '⌃',
@@ -5092,7 +5092,7 @@ function makeKbd( keys, extraClass = "" )
5092
5092
 
5093
5093
  for( const k of keys )
5094
5094
  {
5095
- LX.makeContainer( ["auto", "auto"], "self-center text-xs fg-secondary select-none", specialKeys[ k ] ?? k, kbd );
5095
+ LX.makeContainer( ["auto", "auto"], "self-center text-xs fg-secondary select-none " + extraClass, useSpecialKeys ? specialKeys[ k ] ?? k : k, kbd );
5096
5096
  }
5097
5097
 
5098
5098
  return kbd;
@@ -6013,6 +6013,176 @@ LX.drawSpline = drawSpline;
6013
6013
 
6014
6014
  // area.js @jxarco
6015
6015
 
6016
+ class AreaOverlayButtons {
6017
+
6018
+ /**
6019
+ * @constructor AreaOverlayButtons
6020
+ */
6021
+
6022
+ constructor( area, buttonsArray, options = {} ) {
6023
+
6024
+ this.area = area;
6025
+ this.options = options;
6026
+
6027
+ this.buttons = {};
6028
+
6029
+ this._buildButtons( buttonsArray, options );
6030
+ }
6031
+
6032
+ _buildButtons( buttonsArray, options ) {
6033
+
6034
+ options.className = "lexoverlaybuttons";
6035
+
6036
+ let overlayPanel = this.area.addPanel( options );
6037
+ let overlayGroup = null;
6038
+
6039
+ const container = document.createElement( "div" );
6040
+ container.className = "lexoverlaybuttonscontainer";
6041
+ container.appendChild( overlayPanel.root );
6042
+ this.area.attach( container );
6043
+
6044
+ const float = options.float;
6045
+ let floatClass = "";
6046
+
6047
+ if( float )
6048
+ {
6049
+ for( let i = 0; i < float.length; i++ )
6050
+ {
6051
+ const t = float[ i ];
6052
+ switch( t )
6053
+ {
6054
+ case 'h': break;
6055
+ case 'v': floatClass += " vertical"; break;
6056
+ case 't': break;
6057
+ case 'm': floatClass += " middle"; break;
6058
+ case 'b': floatClass += " bottom"; break;
6059
+ case 'l': break;
6060
+ case 'c': floatClass += " center"; break;
6061
+ case 'r': floatClass += " right"; break;
6062
+ }
6063
+ }
6064
+
6065
+ container.className += ` ${ floatClass }`;
6066
+ }
6067
+
6068
+ const _addButton = ( b, group, last ) => {
6069
+
6070
+ const _options = {
6071
+ width: "auto",
6072
+ selectable: b.selectable,
6073
+ selected: b.selected,
6074
+ icon: b.icon,
6075
+ img: b.img,
6076
+ className: b.class ?? "",
6077
+ title: b.name,
6078
+ overflowContainerX: overlayPanel.root,
6079
+ swap: b.swap
6080
+ };
6081
+
6082
+ if( group )
6083
+ {
6084
+ if( !overlayGroup )
6085
+ {
6086
+ overlayGroup = document.createElement('div');
6087
+ overlayGroup.className = "lexoverlaygroup";
6088
+ overlayPanel.queuedContainer = overlayGroup;
6089
+ }
6090
+
6091
+ _options.parent = overlayGroup;
6092
+ }
6093
+
6094
+ let callback = b.callback;
6095
+ let widget = null;
6096
+
6097
+ if( b.options )
6098
+ {
6099
+ widget = overlayPanel.addSelect( null, b.options, b.value ?? b.name, callback, _options );
6100
+ }
6101
+ else
6102
+ {
6103
+ widget = overlayPanel.addButton( null, b.name, function( value, event ) {
6104
+ if( b.selectable )
6105
+ {
6106
+ if( b.group )
6107
+ {
6108
+ let _prev = b.selected;
6109
+ b.group.forEach( sub => sub.selected = false );
6110
+ b.selected = !_prev;
6111
+ }
6112
+ else
6113
+ {
6114
+ b.selected = !b.selected;
6115
+ }
6116
+ }
6117
+
6118
+ if( callback )
6119
+ {
6120
+ callback( value, event, widget.root );
6121
+ }
6122
+
6123
+ }, _options );
6124
+ }
6125
+
6126
+ this.buttons[ b.name ] = widget;
6127
+
6128
+ // ends the group
6129
+ if( overlayGroup && last )
6130
+ {
6131
+ overlayPanel.root.appendChild( overlayGroup );
6132
+ overlayGroup = null;
6133
+ overlayPanel.clearQueue();
6134
+ }
6135
+ };
6136
+
6137
+ const _refreshPanel = function() {
6138
+
6139
+ overlayPanel.clear();
6140
+
6141
+ for( let b of buttonsArray )
6142
+ {
6143
+ if( b === null )
6144
+ {
6145
+ // Add a separator
6146
+ const separator = document.createElement("div");
6147
+ separator.className = "lexoverlayseparator" + floatClass;
6148
+ overlayPanel.root.appendChild( separator );
6149
+ continue;
6150
+ }
6151
+
6152
+ if( b.constructor === Array )
6153
+ {
6154
+ for( let i = 0; i < b.length; ++i )
6155
+ {
6156
+ let sub = b[ i ];
6157
+ sub.group = b;
6158
+ _addButton( sub, true, i == ( b.length - 1 ) );
6159
+ }
6160
+ }
6161
+ else
6162
+ {
6163
+ _addButton( b );
6164
+ }
6165
+ }
6166
+
6167
+ // Add floating info
6168
+ if( float )
6169
+ {
6170
+ var height = 0;
6171
+ overlayPanel.root.childNodes.forEach( c => { height += c.offsetHeight; } );
6172
+
6173
+ if( container.className.includes( "middle" ) )
6174
+ {
6175
+ container.style.top = "-moz-calc( 50% - " + (height * 0.5) + "px )";
6176
+ container.style.top = "-webkit-calc( 50% - " + (height * 0.5) + "px )";
6177
+ container.style.top = "calc( 50% - " + (height * 0.5) + "px )";
6178
+ }
6179
+ }
6180
+ };
6181
+
6182
+ _refreshPanel();
6183
+ }
6184
+ }
6185
+
6016
6186
  class Area {
6017
6187
 
6018
6188
  /**
@@ -6778,8 +6948,7 @@ class Area {
6778
6948
  // Add to last split section if area has been split
6779
6949
  if( this.sections.length )
6780
6950
  {
6781
- this.sections[ 1 ].addOverlayButtons( buttons, options );
6782
- return;
6951
+ return this.sections[ 1 ].addOverlayButtons( buttons, options );
6783
6952
  }
6784
6953
 
6785
6954
  console.assert( buttons.constructor == Array && buttons.length );
@@ -6787,140 +6956,10 @@ class Area {
6787
6956
  // Set area to relative to use local position
6788
6957
  this.root.style.position = "relative";
6789
6958
 
6790
- options.className = "lexoverlaybuttons";
6791
-
6792
- let overlayPanel = this.addPanel( options );
6793
- let overlayGroup = null;
6794
-
6795
- const container = document.createElement("div");
6796
- container.className = "lexoverlaybuttonscontainer";
6797
- container.appendChild( overlayPanel.root );
6798
- this.attach( container );
6799
-
6800
- const float = options.float;
6959
+ // Reset if already exists
6960
+ this.overlayButtons = new AreaOverlayButtons( this, buttons, options );
6801
6961
 
6802
- if( float )
6803
- {
6804
- for( let i = 0; i < float.length; i++ )
6805
- {
6806
- const t = float[ i ];
6807
- switch( t )
6808
- {
6809
- case 'h': break;
6810
- case 'v': container.className += " vertical"; break;
6811
- case 't': break;
6812
- case 'm': container.className += " middle"; break;
6813
- case 'b': container.className += " bottom"; break;
6814
- case 'l': break;
6815
- case 'c': container.className += " center"; break;
6816
- case 'r': container.className += " right"; break;
6817
- }
6818
- }
6819
- }
6820
-
6821
- const _addButton = function( b, group, last ) {
6822
-
6823
- const _options = {
6824
- width: "auto",
6825
- selectable: b.selectable,
6826
- selected: b.selected,
6827
- icon: b.icon,
6828
- img: b.img,
6829
- className: b.class ?? "",
6830
- title: b.name,
6831
- overflowContainerX: overlayPanel.root,
6832
- swap: b.swap
6833
- };
6834
-
6835
- if( group )
6836
- {
6837
- if( !overlayGroup )
6838
- {
6839
- overlayGroup = document.createElement('div');
6840
- overlayGroup.className = "lexoverlaygroup";
6841
- overlayPanel.queuedContainer = overlayGroup;
6842
- }
6843
-
6844
- _options.parent = overlayGroup;
6845
- }
6846
-
6847
- let callback = b.callback;
6848
-
6849
- if( b.options )
6850
- {
6851
- overlayPanel.addSelect( null, b.options, b.name, callback, _options );
6852
- }
6853
- else
6854
- {
6855
- const button = overlayPanel.addButton( null, b.name, function( value, event ) {
6856
- if( b.selectable )
6857
- {
6858
- if( b.group )
6859
- {
6860
- let _prev = b.selected;
6861
- b.group.forEach( sub => sub.selected = false );
6862
- b.selected = !_prev;
6863
- }
6864
- else
6865
- {
6866
- b.selected = !b.selected;
6867
- }
6868
- }
6869
-
6870
- if( callback )
6871
- {
6872
- callback( value, event, button.root );
6873
- }
6874
-
6875
- }, _options );
6876
- }
6877
-
6878
- // ends the group
6879
- if( overlayGroup && last )
6880
- {
6881
- overlayPanel.root.appendChild( overlayGroup );
6882
- overlayGroup = null;
6883
- overlayPanel.clearQueue();
6884
- }
6885
- };
6886
-
6887
- const _refreshPanel = function() {
6888
-
6889
- overlayPanel.clear();
6890
-
6891
- for( let b of buttons )
6892
- {
6893
- if( b.constructor === Array )
6894
- {
6895
- for( let i = 0; i < b.length; ++i )
6896
- {
6897
- let sub = b[ i ];
6898
- sub.group = b;
6899
- _addButton(sub, true, i == ( b.length - 1 ));
6900
- }
6901
- }
6902
- else
6903
- {
6904
- _addButton( b );
6905
- }
6906
- }
6907
-
6908
- // Add floating info
6909
- if( float )
6910
- {
6911
- var height = 0;
6912
- overlayPanel.root.childNodes.forEach( c => { height += c.offsetHeight; } );
6913
-
6914
- if( container.className.includes( "middle" ) )
6915
- {
6916
- container.style.top = "-moz-calc( 50% - " + (height * 0.5) + "px )";
6917
- container.style.top = "-webkit-calc( 50% - " + (height * 0.5) + "px )";
6918
- container.style.top = "calc( 50% - " + (height * 0.5) + "px )";
6919
- }
6920
- }
6921
- };
6922
-
6923
- _refreshPanel();
6962
+ return this.overlayButtons;
6924
6963
  }
6925
6964
 
6926
6965
  /**
@@ -7421,8 +7460,7 @@ function ADD_CUSTOM_WIDGET( customWidgetName, options = {} )
7421
7460
  if( customWidgetsDom ) customWidgetsDom.remove();
7422
7461
 
7423
7462
  container = document.createElement('div');
7424
- container.className = "lexcustomcontainer";
7425
- container.style.width = "100%";
7463
+ container.className = "lexcustomcontainer w-full";
7426
7464
  element.appendChild( container );
7427
7465
  element.dataset["opened"] = false;
7428
7466
 
@@ -7616,9 +7654,9 @@ class NodeTree {
7616
7654
 
7617
7655
  if( this.options.onlyFolders )
7618
7656
  {
7619
- let has_folders = false;
7620
- node.children.forEach( c => has_folders |= (c.type == 'folder') );
7621
- isParent = !!has_folders;
7657
+ let hasFolders = false;
7658
+ node.children.forEach( c => hasFolders |= (c.type == 'folder') );
7659
+ isParent = !!hasFolders;
7622
7660
  }
7623
7661
 
7624
7662
  let item = document.createElement('li');
@@ -7783,23 +7821,15 @@ class NodeTree {
7783
7821
  } );
7784
7822
 
7785
7823
  event.panel.add( "Delete", { callback: () => {
7786
- // It's the root node
7787
- if( !node.parent )
7788
- {
7789
- return;
7790
- }
7791
7824
 
7792
- if( that.onevent )
7825
+ const ok = that.deleteNode( node );
7826
+
7827
+ if( ok && that.onevent )
7793
7828
  {
7794
7829
  const event = new LX.TreeEvent( LX.TreeEvent.NODE_DELETED, node, e );
7795
7830
  that.onevent( event );
7796
7831
  }
7797
7832
 
7798
- // Delete nodes now
7799
- let childs = node.parent.children;
7800
- const index = childs.indexOf( node );
7801
- childs.splice( index, 1 );
7802
-
7803
7833
  this.refresh();
7804
7834
  } } );
7805
7835
  }
@@ -7816,23 +7846,26 @@ class NodeTree {
7816
7846
 
7817
7847
  if( e.key == "Delete" )
7818
7848
  {
7819
- // Send event now so we have the info in selected array..
7820
- if( that.onevent )
7849
+ const nodesDeleted = [];
7850
+
7851
+ for( let _node of this.selected )
7821
7852
  {
7822
- const event = new LX.TreeEvent( LX.TreeEvent.NODE_DELETED, this.selected.length > 1 ? this.selected : node, e );
7823
- event.multiple = this.selected.length > 1;
7824
- that.onevent( event );
7853
+ if( that.deleteNode( _node ) )
7854
+ {
7855
+ nodesDeleted.push( _node );
7856
+ }
7825
7857
  }
7826
7858
 
7827
- // Delete nodes now
7828
- for( let _node of this.selected )
7859
+ // Send event now so we have the info in selected array..
7860
+ if( nodesDeleted.length && that.onevent )
7829
7861
  {
7830
- let childs = _node.parent.children;
7831
- const index = childs.indexOf( _node );
7832
- childs.splice( index, 1 );
7862
+ const event = new LX.TreeEvent( LX.TreeEvent.NODE_DELETED, nodesDeleted.length > 1 ? nodesDeleted : node, e );
7863
+ event.multiple = nodesDeleted.length > 1;
7864
+ that.onevent( event );
7833
7865
  }
7834
7866
 
7835
7867
  this.selected.length = 0;
7868
+
7836
7869
  this.refresh();
7837
7870
  }
7838
7871
  else if( e.key == "ArrowUp" || e.key == "ArrowDown" ) // Unique or zero selected
@@ -8101,6 +8134,35 @@ class NodeTree {
8101
8134
  this.selected = [ el.treeData ];
8102
8135
  el.focus();
8103
8136
  }
8137
+
8138
+ deleteNode( node ) {
8139
+
8140
+ const dataAsArray = ( this.data.constructor === Array );
8141
+
8142
+ // Can be either Array or Object type data
8143
+ if( node.parent )
8144
+ {
8145
+ let childs = node.parent.children;
8146
+ const index = childs.indexOf( node );
8147
+ childs.splice( index, 1 );
8148
+ }
8149
+ else
8150
+ {
8151
+ if( dataAsArray )
8152
+ {
8153
+ const index = this.data.indexOf( node );
8154
+ console.assert( index > -1, "NodeTree: Can't delete root node " + node.id + " from data array!" );
8155
+ this.data.splice( index, 1 );
8156
+ }
8157
+ else
8158
+ {
8159
+ console.warn( "NodeTree: Can't delete root node from object data!" );
8160
+ return false;
8161
+ }
8162
+ }
8163
+
8164
+ return true;
8165
+ }
8104
8166
  }
8105
8167
 
8106
8168
  LX.NodeTree = NodeTree;
@@ -8420,14 +8482,15 @@ class Button extends Widget {
8420
8482
  super( Widget.BUTTON, name, null, options );
8421
8483
 
8422
8484
  this.onGetValue = () => {
8423
- return wValue.querySelector( "input" )?.checked;
8485
+ const swapInput = wValue.querySelector( "input" );
8486
+ return swapInput ? swapInput.checked : value
8424
8487
  };
8425
8488
 
8426
8489
  this.onSetValue = ( newValue, skipCallback, event ) => {
8427
8490
 
8428
8491
  if( ( options.swap ?? false ) )
8429
8492
  {
8430
- this.root.setState( newValue, skipCallback );
8493
+ this.setState( newValue, skipCallback );
8431
8494
  return;
8432
8495
  }
8433
8496
 
@@ -8457,6 +8520,30 @@ class Button extends Widget {
8457
8520
  wValue.style.width = `calc( 100% - ${ realNameWidth })`;
8458
8521
  };
8459
8522
 
8523
+ // In case of swap, set if a change has to be performed
8524
+ this.setState = function( v, skipCallback ) {
8525
+ const swapInput = wValue.querySelector( "input" );
8526
+
8527
+ if( swapInput )
8528
+ {
8529
+ swapInput.checked = v;
8530
+ }
8531
+ else if( options.selectable )
8532
+ {
8533
+ if( options.parent )
8534
+ {
8535
+ options.parent.querySelectorAll(".lexbutton.selected").forEach( b => { if( b == wValue ) return; b.classList.remove( "selected" ); } );
8536
+ }
8537
+
8538
+ wValue.classList.toggle( "selected", v );
8539
+ }
8540
+
8541
+ if( !skipCallback )
8542
+ {
8543
+ this._trigger( new LX.IEvent( name, swapInput ? swapInput.checked : ( options.selectable ? v : value ), null ), callback );
8544
+ }
8545
+ };
8546
+
8460
8547
  var wValue = document.createElement( 'button' );
8461
8548
  wValue.title = options.tooltip ? "" : ( options.title ?? "" );
8462
8549
  wValue.className = "lexbutton p-1 " + ( options.buttonClass ?? "" );
@@ -8468,18 +8555,38 @@ class Button extends Widget {
8468
8555
  wValue.classList.add( "selected" );
8469
8556
  }
8470
8557
 
8471
- if( options.icon )
8472
- {
8473
- const icon = LX.makeIcon( options.icon );
8474
- wValue.prepend( icon );
8475
- wValue.classList.add( "justify-center" );
8476
- }
8477
- else if( options.img )
8558
+ if( options.img )
8478
8559
  {
8479
8560
  let img = document.createElement( 'img' );
8480
8561
  img.src = options.img;
8481
8562
  wValue.prepend( img );
8482
8563
  }
8564
+ else if( options.icon )
8565
+ {
8566
+ const icon = LX.makeIcon( options.icon, { iconClass: options.iconClass, svgClass: options.svgClass } );
8567
+ const iconPosition = options.iconPosition ?? "cover";
8568
+
8569
+ // Default
8570
+ if( iconPosition == "cover" || ( options.swap !== undefined ) )
8571
+ {
8572
+ wValue.prepend( icon );
8573
+ }
8574
+ else
8575
+ {
8576
+ wValue.innerHTML = `<span>${ ( value || "" ) }</span>`;
8577
+
8578
+ if( iconPosition == "start" )
8579
+ {
8580
+ wValue.querySelector( "span" ).prepend( icon );
8581
+ }
8582
+ else // "end"
8583
+ {
8584
+ wValue.querySelector( "span" ).appendChild( icon );
8585
+ }
8586
+ }
8587
+
8588
+ wValue.classList.add( "justify-center" );
8589
+ }
8483
8590
  else
8484
8591
  {
8485
8592
  wValue.innerHTML = `<span>${ ( value || "" ) }</span>`;
@@ -8527,7 +8634,7 @@ class Button extends Widget {
8527
8634
  const swapIcon = LX.makeIcon( options.swap, { iconClass: "swap-on" } );
8528
8635
  wValue.appendChild( swapIcon );
8529
8636
 
8530
- this.root.swap = function( skipCallback ) {
8637
+ this.swap = function( skipCallback ) {
8531
8638
  const swapInput = wValue.querySelector( "input" );
8532
8639
  swapInput.checked = !swapInput.checked;
8533
8640
  if( !skipCallback )
@@ -8535,19 +8642,10 @@ class Button extends Widget {
8535
8642
  trigger.click();
8536
8643
  }
8537
8644
  };
8538
-
8539
- // Set if swap has to be performed
8540
- this.root.setState = function( v, skipCallback ) {
8541
- const swapInput = wValue.querySelector( "input" );
8542
- swapInput.checked = v;
8543
- if( !skipCallback )
8544
- {
8545
- trigger.click();
8546
- }
8547
- };
8548
8645
  }
8549
8646
 
8550
8647
  trigger.addEventListener( "click", e => {
8648
+ let isSelected;
8551
8649
  if( options.selectable )
8552
8650
  {
8553
8651
  if( options.parent )
@@ -8555,7 +8653,7 @@ class Button extends Widget {
8555
8653
  options.parent.querySelectorAll(".lexbutton.selected").forEach( b => { if( b == wValue ) return; b.classList.remove( "selected" ); } );
8556
8654
  }
8557
8655
 
8558
- wValue.classList.toggle('selected');
8656
+ isSelected = wValue.classList.toggle('selected');
8559
8657
  }
8560
8658
 
8561
8659
  if( options.fileInput )
@@ -8565,7 +8663,7 @@ class Button extends Widget {
8565
8663
  else
8566
8664
  {
8567
8665
  const swapInput = wValue.querySelector( "input" );
8568
- this._trigger( new LX.IEvent( name, swapInput?.checked ?? value, e ), callback );
8666
+ this._trigger( new LX.IEvent( name, swapInput?.checked ?? ( options.selectable ? isSelected : value ), e ), callback );
8569
8667
  }
8570
8668
  });
8571
8669
 
@@ -8935,8 +9033,8 @@ class Select extends Widget {
8935
9033
  value = newValue;
8936
9034
 
8937
9035
  let item = null;
8938
- const options = listOptions.childNodes;
8939
- options.forEach( e => {
9036
+ const listOptionsNodes = listOptions.childNodes;
9037
+ listOptionsNodes.forEach( e => {
8940
9038
  e.classList.remove( "selected" );
8941
9039
  if( e.getAttribute( "value" ) == newValue )
8942
9040
  {
@@ -8956,6 +9054,23 @@ class Select extends Widget {
8956
9054
  list.refresh( filteredOptions );
8957
9055
  }
8958
9056
 
9057
+ // Update suboptions menu
9058
+ const suboptions = this.root.querySelector( ".lexcustomcontainer" );
9059
+ const suboptionsFunc = options[ `on_${ value }` ];
9060
+ suboptions.toggleAttribute( "hidden", !suboptionsFunc );
9061
+
9062
+ if( suboptionsFunc )
9063
+ {
9064
+ suboptions.innerHTML = "";
9065
+ const suboptionsPanel = new LX.Panel();
9066
+ suboptionsPanel.queue( suboptions );
9067
+ suboptionsFunc.call(this, suboptionsPanel);
9068
+ suboptionsPanel.clearQueue();
9069
+ }
9070
+
9071
+ this.root.dataset["opened"] = ( !!suboptionsFunc );
9072
+ list.style.height = ""; // set auto height by default
9073
+
8959
9074
  if( !skipCallback )
8960
9075
  {
8961
9076
  this._trigger( new LX.IEvent( name, value, event ), callback );
@@ -8976,7 +9091,7 @@ class Select extends Widget {
8976
9091
  wValue.name = name;
8977
9092
  wValue.iValue = value;
8978
9093
 
8979
- if( options.overflowContainer )
9094
+ if( options.overflowContainer !== undefined )
8980
9095
  {
8981
9096
  options.overflowContainerX = options.overflowContainerY = options.overflowContainer;
8982
9097
  }
@@ -8989,7 +9104,7 @@ class Select extends Widget {
8989
9104
 
8990
9105
  // Manage vertical aspect
8991
9106
  {
8992
- const overflowContainer = options.overflowContainerY ?? parent.getParentArea();
9107
+ const overflowContainer = options.overflowContainerY !== undefined ? options.overflowContainerY : parent.getParentArea();
8993
9108
  const listHeight = parent.offsetHeight;
8994
9109
  let topPosition = rect.y;
8995
9110
 
@@ -9009,18 +9124,25 @@ class Select extends Widget {
9009
9124
  }
9010
9125
 
9011
9126
  parent.style.top = ( topPosition + selectRoot.offsetHeight ) + 'px';
9127
+ list.style.height = ""; // set auto height by default
9012
9128
 
9013
- const showAbove = ( topPosition + listHeight ) > maxY;
9014
- if( showAbove )
9129
+ const failBelow = ( topPosition + listHeight ) > maxY;
9130
+ const failAbove = ( topPosition - listHeight ) < 0;
9131
+ if( failBelow && !failAbove )
9015
9132
  {
9016
9133
  parent.style.top = ( topPosition - listHeight ) + 'px';
9017
9134
  parent.classList.add( "place-above" );
9018
9135
  }
9136
+ // If does not fit in any direction, put it below but limit height..
9137
+ else if( failBelow && failAbove )
9138
+ {
9139
+ list.style.height = `${ maxY - topPosition - 32 }px`; // 32px margin
9140
+ }
9019
9141
  }
9020
9142
 
9021
9143
  // Manage horizontal aspect
9022
9144
  {
9023
- const overflowContainer = options.overflowContainerX ?? parent.getParentArea();
9145
+ const overflowContainer = options.overflowContainerX !== undefined ? options.overflowContainerX : parent.getParentArea();
9024
9146
  const listWidth = parent.offsetWidth;
9025
9147
  let leftPosition = rect.x;
9026
9148
 
@@ -9252,6 +9374,25 @@ class Select extends Widget {
9252
9374
 
9253
9375
  container.appendChild( listDialog );
9254
9376
 
9377
+ // Element suboptions
9378
+ let suboptions = document.createElement( "div" );
9379
+ suboptions.className = "lexcustomcontainer w-full";
9380
+
9381
+ const suboptionsFunc = options[ `on_${ value }` ];
9382
+ suboptions.toggleAttribute( "hidden", !suboptionsFunc );
9383
+
9384
+ if( suboptionsFunc )
9385
+ {
9386
+ suboptions.innerHTML = "";
9387
+ const suboptionsPanel = new LX.Panel();
9388
+ suboptionsPanel.queue( suboptions );
9389
+ suboptionsFunc.call( this, suboptionsPanel );
9390
+ suboptionsPanel.clearQueue();
9391
+ }
9392
+
9393
+ this.root.appendChild( suboptions );
9394
+ this.root.dataset["opened"] = ( !!suboptionsFunc );
9395
+
9255
9396
  LX.doAsync( this.onResize.bind( this ) );
9256
9397
  }
9257
9398
 
@@ -9429,10 +9570,12 @@ class Layers extends Widget {
9429
9570
  container.style.width = `calc( 100% - ${ realNameWidth })`;
9430
9571
  };
9431
9572
 
9432
- var container = document.createElement( "div" );
9573
+ const container = document.createElement( "div" );
9433
9574
  container.className = "lexlayers";
9434
9575
  this.root.appendChild( container );
9435
9576
 
9577
+ const maxBits = options.maxBits ?? 16;
9578
+
9436
9579
  this.setLayers = ( val ) => {
9437
9580
 
9438
9581
  container.innerHTML = "";
@@ -9441,19 +9584,19 @@ class Layers extends Widget {
9441
9584
  let nbits = binary.length;
9442
9585
 
9443
9586
  // fill zeros
9444
- for( let i = 0; i < ( 16 - nbits ); ++i )
9587
+ for( let i = 0; i < ( maxBits - nbits ); ++i )
9445
9588
  {
9446
9589
  binary = '0' + binary;
9447
9590
  }
9448
9591
 
9449
- for( let bit = 0; bit < 16; ++bit )
9592
+ for( let bit = 0; bit < maxBits; ++bit )
9450
9593
  {
9451
9594
  let layer = document.createElement( "div" );
9452
9595
  layer.className = "lexlayer";
9453
9596
 
9454
9597
  if( val != undefined )
9455
9598
  {
9456
- const valueBit = binary[ 16 - bit - 1 ];
9599
+ const valueBit = binary[ maxBits - bit - 1 ];
9457
9600
  if( valueBit != undefined && valueBit == '1' )
9458
9601
  {
9459
9602
  layer.classList.add( "selected" );
@@ -9461,7 +9604,7 @@ class Layers extends Widget {
9461
9604
  }
9462
9605
 
9463
9606
  layer.innerText = bit + 1;
9464
- layer.title = "Bit " + bit + ", value " + (1 << bit);
9607
+ layer.title = "Bit " + bit + ", value " + ( 1 << bit );
9465
9608
  container.appendChild( layer );
9466
9609
 
9467
9610
  layer.addEventListener( "click", e => {
@@ -10108,9 +10251,7 @@ class ColorInput extends Widget {
10108
10251
  colorModel: options.useRGB ? "RGB" : "Hex",
10109
10252
  useAlpha,
10110
10253
  onChange: ( color ) => {
10111
- this._fromColorPicker = true;
10112
10254
  this.set( color.hex );
10113
- delete this._fromColorPicker;
10114
10255
  }
10115
10256
  } );
10116
10257
 
@@ -10148,6 +10289,7 @@ class ColorInput extends Widget {
10148
10289
  this._skipTextUpdate = true;
10149
10290
  this.set( v );
10150
10291
  delete this._skipTextUpdate;
10292
+ this.picker.fromHexColor( v );
10151
10293
  }, { width: "calc( 100% - 24px )", disabled: options.disabled });
10152
10294
 
10153
10295
  textWidget.root.style.marginLeft = "6px";
@@ -10355,6 +10497,7 @@ class NumberInput extends Widget {
10355
10497
  slider.step = options.step ?? 1;
10356
10498
  slider.type = "range";
10357
10499
  slider.value = value;
10500
+ slider.disabled = this.disabled;
10358
10501
 
10359
10502
  slider.addEventListener( "input", ( e ) => {
10360
10503
  this.set( slider.valueAsNumber, false, e );
@@ -11419,9 +11562,16 @@ class TabSections extends Widget {
11419
11562
  throw( "Param @tabs must be an Array!" );
11420
11563
  }
11421
11564
 
11565
+ if( !tabs.length )
11566
+ {
11567
+ throw( "Tab list cannot be empty!" );
11568
+ }
11569
+
11422
11570
  const vertical = options.vertical ?? true;
11423
11571
  const showNames = !vertical && ( options.showNames ?? false );
11424
11572
 
11573
+ this.tabDOMs = {};
11574
+
11425
11575
  let container = document.createElement( 'div' );
11426
11576
  container.className = "lextabscontainer";
11427
11577
  if( !vertical )
@@ -11434,25 +11584,25 @@ class TabSections extends Widget {
11434
11584
  container.appendChild( tabContainer );
11435
11585
  this.root.appendChild( container );
11436
11586
 
11437
- for( let i = 0; i < tabs.length; ++i )
11587
+ // Check at least 1 is selected
11588
+ if( tabs.findIndex( e => e.selected === true ) < 0 )
11589
+ {
11590
+ tabs[ 0 ].selected = true;
11591
+ }
11592
+
11593
+ for( let tab of tabs )
11438
11594
  {
11439
- const tab = tabs[ i ];
11440
11595
  console.assert( tab.name );
11441
- const isSelected = ( i == 0 );
11442
11596
  let tabEl = document.createElement( "div" );
11443
- tabEl.className = "lextab " + (i == tabs.length - 1 ? "last" : "") + ( isSelected ? "selected" : "" );
11597
+ tabEl.className = "lextab " + ( ( tab.selected ?? false ) ? "selected" : "" );
11444
11598
  tabEl.innerHTML = ( showNames ? tab.name : "" );
11445
- tabEl.appendChild( LX.makeIcon( tab.icon ?? "Hash", { title: tab.name } ) );
11599
+ tabEl.appendChild( LX.makeIcon( tab.icon ?? "Hash", { title: tab.name, iconClass: tab.iconClass, svgClass: tab.svgClass } ) );
11600
+ this.tabDOMs[ tab.name ] = tabEl;
11446
11601
 
11447
11602
  let infoContainer = document.createElement( "div" );
11448
11603
  infoContainer.id = tab.name.replace( /\s/g, '' );
11449
11604
  infoContainer.className = "widgets";
11450
-
11451
- if( !isSelected )
11452
- {
11453
- infoContainer.toggleAttribute( "hidden", true );
11454
- }
11455
-
11605
+ infoContainer.toggleAttribute( "hidden", !( tab.selected ?? false ) );
11456
11606
  container.appendChild( infoContainer );
11457
11607
 
11458
11608
  tabEl.addEventListener( "click", e => {
@@ -11478,10 +11628,24 @@ class TabSections extends Widget {
11478
11628
  // Push to tab space
11479
11629
  const creationPanel = new LX.Panel();
11480
11630
  creationPanel.queue( infoContainer );
11481
- tab.onCreate.call(this, creationPanel);
11631
+ tab.onCreate.call( this, creationPanel, infoContainer );
11482
11632
  creationPanel.clearQueue();
11483
11633
  }
11484
11634
  }
11635
+
11636
+ this.tabs = tabs;
11637
+ }
11638
+
11639
+ select( name ) {
11640
+
11641
+ const tabEl = this.tabDOMs[ name ];
11642
+
11643
+ if( !tabEl )
11644
+ {
11645
+ return;
11646
+ }
11647
+
11648
+ tabEl.click();
11485
11649
  }
11486
11650
  }
11487
11651
 
@@ -11797,7 +11961,9 @@ class Table extends Widget {
11797
11961
  const body = table.querySelector( "tbody" );
11798
11962
  for( const el of body.childNodes )
11799
11963
  {
11800
- data.checkMap[ el.getAttribute( "rowId" ) ] = this.checked;
11964
+ const rowId = el.getAttribute( "rowId" );
11965
+ if( !rowId ) continue;
11966
+ data.checkMap[ rowId ] = this.checked;
11801
11967
  el.querySelector( "input[type='checkbox']" ).checked = this.checked;
11802
11968
  }
11803
11969
  });
@@ -12008,8 +12174,8 @@ class Table extends Widget {
12008
12174
  }
12009
12175
 
12010
12176
  const row = document.createElement( 'tr' );
12011
- const rowId = LX.getSupportedDOMName( bodyData.join( '-' ) );
12012
- row.setAttribute( "rowId", rowId.substr(0, 32) );
12177
+ const rowId = LX.getSupportedDOMName( bodyData.join( '-' ) ).substr(0, 32);
12178
+ row.setAttribute( "rowId", rowId );
12013
12179
 
12014
12180
  if( options.sortable ?? false )
12015
12181
  {
@@ -12125,7 +12291,7 @@ class Table extends Widget {
12125
12291
  }
12126
12292
  else if( action == "menu" )
12127
12293
  {
12128
- button = LX.makeIcon( "Ellipsis", { title: "Menu" } );
12294
+ button = LX.makeIcon( "EllipsisVertical", { title: "Menu" } );
12129
12295
  button.addEventListener( 'click', function( event ) {
12130
12296
  if( !options.onMenuAction )
12131
12297
  {
@@ -12164,6 +12330,17 @@ class Table extends Widget {
12164
12330
 
12165
12331
  body.appendChild( row );
12166
12332
  }
12333
+
12334
+ if( body.childNodes.length == 0 )
12335
+ {
12336
+ const row = document.createElement( 'tr' );
12337
+ const td = document.createElement( 'td' );
12338
+ td.setAttribute( "colspan", data.head.length + this.rowOffsetCount + 1 ); // +1 for rowActions
12339
+ td.className = "empty-row";
12340
+ td.innerHTML = "No results.";
12341
+ row.appendChild( td );
12342
+ body.appendChild( row );
12343
+ }
12167
12344
  }
12168
12345
 
12169
12346
  for( const v in data.colVisibilityMap )
@@ -12948,6 +13125,7 @@ class Panel {
12948
13125
  * hideName: Don't use name as label [false]
12949
13126
  * disabled: Make the widget disabled [false]
12950
13127
  * icon: Icon class to show as button value
13128
+ * iconPosition: Icon position (cover|start|end)
12951
13129
  * fileInput: Button click requests a file
12952
13130
  * fileInputType: Type of the requested file
12953
13131
  * img: Path to image to show as button value
@@ -13439,6 +13617,8 @@ class Panel {
13439
13617
  * @param {Array} tabs Contains objects with {
13440
13618
  * name: Name of the tab (if icon, use as title)
13441
13619
  * icon: Icon to be used as the tab icon (optional)
13620
+ * iconClass: Class to be added to the icon (optional)
13621
+ * svgClass: Class to be added to the inner SVG of the icon (optional)
13442
13622
  * onCreate: Func to be called at tab creation
13443
13623
  * onSelect: Func to be called on select tab (optional)
13444
13624
  * }
@@ -13917,46 +14097,43 @@ class Menubar {
13917
14097
  }
13918
14098
 
13919
14099
  let button = this.buttons[ name ];
14100
+ // If the button already exists, delete it
14101
+ // since only one button of this type can exist
13920
14102
  if( button )
13921
14103
  {
13922
- button.innerHTML = "";
13923
- button.appendChild( LX.makeIcon( icon, { svgClass: "xl" } ) );
13924
- return;
14104
+ delete this.buttons[ name ];
14105
+ LX.deleteElement( button.root );
13925
14106
  }
13926
14107
 
13927
14108
  // Otherwise, create it
13928
- button = document.createElement('div');
13929
- const disabled = options.disabled ?? false;
13930
- button.className = "lexmenubutton main" + (disabled ? " disabled" : "");
13931
- button.title = name;
13932
- button.appendChild( LX.makeIcon( icon, { svgClass: "xl" } ) );
14109
+ button = new LX.Button( name, null, callback, {
14110
+ title: name,
14111
+ buttonClass: "lexmenubutton main bg-none",
14112
+ disabled: options.disabled,
14113
+ icon,
14114
+ svgClass: "xl",
14115
+ hideName: true,
14116
+ swap: options.swap
14117
+ } );
13933
14118
 
13934
14119
  if( options.float == "right" )
13935
14120
  {
13936
- button.right = true;
14121
+ button.root.right = true;
13937
14122
  }
13938
14123
 
13939
14124
  if( this.root.lastChild && this.root.lastChild.right )
13940
14125
  {
13941
- this.root.lastChild.before( button );
14126
+ this.root.lastChild.before( button.root );
13942
14127
  }
13943
14128
  else if( options.float == "left" )
13944
14129
  {
13945
- this.root.prepend( button );
14130
+ this.root.prepend( button.root );
13946
14131
  }
13947
14132
  else
13948
14133
  {
13949
- this.root.appendChild( button );
14134
+ this.root.appendChild( button.root );
13950
14135
  }
13951
14136
 
13952
- const _b = button.querySelector('a');
13953
- _b.addEventListener("click", (e) => {
13954
- if( callback && !disabled )
13955
- {
13956
- callback.call( this, _b, e );
13957
- }
13958
- });
13959
-
13960
14137
  this.buttons[ name ] = button;
13961
14138
  }
13962
14139
 
@@ -14038,7 +14215,7 @@ class Menubar {
14038
14215
  this.buttonContainer.className = "lexmenubuttons";
14039
14216
  this.buttonContainer.classList.add( options.float ?? "center" );
14040
14217
 
14041
- if( options.position == "right" )
14218
+ if( options.float == "right" )
14042
14219
  {
14043
14220
  this.buttonContainer.right = true;
14044
14221
  }
@@ -14057,12 +14234,12 @@ class Menubar {
14057
14234
  {
14058
14235
  const data = buttons[ i ];
14059
14236
  const title = data.title;
14060
- const button = new LX.Button( title, "", data.callback, { title, buttonClass: "bg-none", disabled: data.disabled, icon: data.icon, hideName: true, swap: data.swap } );
14237
+ const button = new LX.Button( title, data.label, data.callback, { title, buttonClass: "bg-none", disabled: data.disabled, icon: data.icon, hideName: true, swap: data.swap, iconPosition: "start" } );
14061
14238
  this.buttonContainer.appendChild( button.root );
14062
14239
 
14063
14240
  if( title )
14064
14241
  {
14065
- this.buttons[ title ] = button.root;
14242
+ this.buttons[ title ] = button;
14066
14243
  }
14067
14244
  }
14068
14245
  }
@@ -14823,10 +15000,14 @@ class AssetView {
14823
15000
 
14824
15001
  if( options.rootPath )
14825
15002
  {
14826
- if(options.rootPath.constructor !== String)
14827
- console.warn("Asset Root Path must be a String (now is " + path.constructor.name + ")");
15003
+ if( options.rootPath.constructor !== String )
15004
+ {
15005
+ console.warn( `Asset Root Path must be a String (now is a ${ path.constructor.name })` );
15006
+ }
14828
15007
  else
15008
+ {
14829
15009
  this.rootPath = options.rootPath;
15010
+ }
14830
15011
  }
14831
15012
 
14832
15013
  let div = document.createElement('div');
@@ -15195,7 +15376,7 @@ class AssetView {
15195
15376
  this.content.className = (isContentLayout ? "lexassetscontent" : "lexassetscontent list");
15196
15377
  let that = this;
15197
15378
 
15198
- const add_item = function(item) {
15379
+ const _addItem = function(item) {
15199
15380
 
15200
15381
  const type = item.type.charAt( 0 ).toUpperCase() + item.type.slice( 1 );
15201
15382
  const extension = LX.getExtension( item.id );
@@ -15220,20 +15401,27 @@ class AssetView {
15220
15401
  return;
15221
15402
  }
15222
15403
 
15404
+ const dialog = itemEl.closest('dialog');
15223
15405
  const rect = itemEl.getBoundingClientRect();
15224
15406
  const targetRect = e.target.getBoundingClientRect();
15225
- const parentRect = desc.parentElement.getBoundingClientRect();
15226
15407
 
15227
- let localOffsetX = targetRect.x - parentRect.x - ( targetRect.x - rect.x );
15228
- let localOffsetY = targetRect.y - parentRect.y - ( targetRect.y - rect.y );
15408
+ let localOffsetX = rect.x + e.offsetX;
15409
+ let localOffsetY = rect.y + e.offsetY;
15410
+
15411
+ if( dialog )
15412
+ {
15413
+ const dialogRect = dialog.getBoundingClientRect();
15414
+ localOffsetX -= dialogRect.x;
15415
+ localOffsetY -= dialogRect.y;
15416
+ }
15229
15417
 
15230
15418
  if( e.target.classList.contains( "lexassettitle" ) )
15231
15419
  {
15232
15420
  localOffsetY += ( targetRect.y - rect.y );
15233
15421
  }
15234
15422
 
15235
- desc.style.left = (localOffsetX + e.offsetX + 12) + "px";
15236
- desc.style.top = (localOffsetY + e.offsetY) + "px";
15423
+ desc.style.left = ( localOffsetX ) + "px";
15424
+ desc.style.top = ( localOffsetY - 36 ) + "px";
15237
15425
  });
15238
15426
 
15239
15427
  itemEl.addEventListener("mouseenter", () => {
@@ -15431,7 +15619,7 @@ class AssetView {
15431
15619
  } });
15432
15620
  }else
15433
15621
  {
15434
- item.domEl = add_item( item );
15622
+ item.domEl = _addItem( item );
15435
15623
  }
15436
15624
  }
15437
15625