lexgui 0.2.0 → 0.4.1

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,23 +8,26 @@
8
8
  */
9
9
 
10
10
  var LX = {
11
- version: "0.2.0",
11
+ version: "0.4.1",
12
12
  ready: false,
13
- components: [], // specific pre-build components
14
- signals: {}, // events and triggers
15
- extraCommandbarEntries: [] // user specific entries for command bar
13
+ components: [], // Specific pre-build components
14
+ signals: {}, // Events and triggers
15
+ extraCommandbarEntries: [], // User specific entries for command bar
16
+ activeDraggable: null // Watch for the current active draggable
16
17
  };
17
18
 
18
19
  LX.MOUSE_LEFT_CLICK = 0;
19
20
  LX.MOUSE_MIDDLE_CLICK = 1;
20
21
  LX.MOUSE_RIGHT_CLICK = 2;
21
22
 
22
- LX.MOUSE_DOUBLE_CLICK = 2;
23
- LX.MOUSE_TRIPLE_CLICK = 3;
23
+ LX.MOUSE_DOUBLE_CLICK = 2;
24
+ LX.MOUSE_TRIPLE_CLICK = 3;
24
25
 
25
- LX.CURVE_MOVEOUT_CLAMP = 0;
26
+ LX.CURVE_MOVEOUT_CLAMP = 0;
26
27
  LX.CURVE_MOVEOUT_DELETE = 1;
27
28
 
29
+ LX.DRAGGABLE_Z_INDEX = 101;
30
+
28
31
  function clamp( num, min, max ) { return Math.min( Math.max( num, min ), max ); }
29
32
  function round( number, precision ) { return precision == 0 ? Math.floor( number ) : +(( number ).toFixed( precision ?? 2 ).replace( /([0-9]+(\.[0-9]+[1-9])?)(\.?0+$)/, '$1' )); }
30
33
  function remapRange( oldValue, oldMin, oldMax, newMin, newMax ) { return ((( oldValue - oldMin ) * ( newMax - newMin )) / ( oldMax - oldMin )) + newMin; }
@@ -84,7 +87,7 @@ LX.doAsync = doAsync;
84
87
  */
85
88
  function getSupportedDOMName( text )
86
89
  {
87
- return text.replace(/\s/g, '').replaceAll('@', '_').replaceAll('+', '_plus_').replaceAll('.', '');
90
+ return text.replace( /\s/g, '' ).replaceAll('@', '_').replaceAll('+', '_plus_').replaceAll( '.', '' );
88
91
  }
89
92
 
90
93
  LX.getSupportedDOMName = getSupportedDOMName;
@@ -223,7 +226,8 @@ LX.hexToRgb = hexToRgb;
223
226
  function rgbToHex( rgb )
224
227
  {
225
228
  let hex = "#";
226
- for( let c of rgb ) {
229
+ for( let c of rgb )
230
+ {
227
231
  c = Math.floor( c * 255 );
228
232
  hex += c.toString( 16 );
229
233
  }
@@ -298,7 +302,7 @@ LX.buildTextPattern = buildTextPattern;
298
302
 
299
303
  /**
300
304
  * @method makeDraggable
301
- * @description Allow an element to be dragged
305
+ * @description Allows an element to be dragged
302
306
  * @param {Element} domEl
303
307
  * @param {Object} options
304
308
  * autoAdjust (Bool): Sets in a correct position at the beggining
@@ -323,6 +327,7 @@ function makeDraggable( domEl, options = { } )
323
327
  top = top ?? e.clientY - offsetY - parentRect.y;
324
328
  domEl.style.left = clamp( left, dragMargin + fixedOffset.x, fixedOffset.x + parentRect.width - domEl.offsetWidth - dragMargin ) + 'px';
325
329
  domEl.style.top = clamp( top, dragMargin + fixedOffset.y, fixedOffset.y + parentRect.height - domEl.offsetHeight - dragMargin ) + 'px';
330
+ domEl.style.translate = "none"; // Force remove translation
326
331
  };
327
332
 
328
333
  // Initial adjustment
@@ -367,26 +372,45 @@ function makeDraggable( domEl, options = { } )
367
372
  e.preventDefault();
368
373
  e.stopPropagation();
369
374
  e.stopImmediatePropagation();
370
- if( !currentTarget ) return;
375
+
376
+ if( !currentTarget )
377
+ {
378
+ return;
379
+ }
380
+
371
381
  // Remove image when dragging
372
382
  var img = new Image();
373
383
  img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=';
374
384
  e.dataTransfer.setDragImage( img, 0, 0 );
375
385
  e.dataTransfer.effectAllowed = "move";
386
+
376
387
  const rect = e.target.getBoundingClientRect();
377
388
  const parentRect = currentTarget.parentElement.getBoundingClientRect();
378
389
  const isFixed = ( currentTarget.style.position == "fixed" );
379
390
  const fixedOffset = isFixed ? new LX.vec2( parentRect.x, parentRect.y ) : new LX.vec2();
380
391
  offsetX = e.clientX - rect.x - fixedOffset.x;
381
392
  offsetY = e.clientY - rect.y - fixedOffset.y;
393
+
382
394
  document.addEventListener( "mousemove", onMove );
395
+
396
+ currentTarget._eventCatched = true;
397
+
398
+ // Force active dialog to show on top
399
+ if( LX.activeDraggable )
400
+ {
401
+ LX.activeDraggable.style.zIndex = LX.DRAGGABLE_Z_INDEX;
402
+ }
403
+
404
+ LX.activeDraggable = domEl;
405
+ LX.activeDraggable.style.zIndex = LX.DRAGGABLE_Z_INDEX + 1;
406
+
383
407
  if( onDragStart )
384
408
  {
385
409
  onDragStart( currentTarget, e );
386
410
  }
387
411
  }, false );
388
412
 
389
- document.addEventListener( 'mouseup', () => {
413
+ document.addEventListener( 'mouseup', (e) => {
390
414
  if( currentTarget )
391
415
  {
392
416
  currentTarget = null;
@@ -397,6 +421,48 @@ function makeDraggable( domEl, options = { } )
397
421
 
398
422
  LX.makeDraggable = makeDraggable;
399
423
 
424
+ /**
425
+ * @method makeCollapsible
426
+ * @description Allows an element to be collapsed/expanded
427
+ * @param {Element} domEl: Element to be treated as collapsible
428
+ * @param {Element} content: Content to display/hide on collapse/extend
429
+ * @param {Element} parent: Element where the content will be appended (default is domEl.parent)
430
+ * @param {Object} options
431
+ */
432
+ function makeCollapsible( domEl, content, parent, options = { } )
433
+ {
434
+ domEl.classList.add( "collapsible" );
435
+
436
+ const collapsed = ( options.collapsed ?? true );
437
+ const actionIcon = LX.makeIcon( "Right" );
438
+ actionIcon.classList.add( "collapser" );
439
+ actionIcon.dataset[ "collapsed" ] = collapsed;
440
+ actionIcon.style.marginLeft = "auto";
441
+
442
+ actionIcon.addEventListener( "click", function(e) {
443
+ e.preventDefault();
444
+ e.stopPropagation();
445
+ if( this.dataset[ "collapsed" ] )
446
+ {
447
+ delete this.dataset[ "collapsed" ];
448
+ content.style.display = "block";
449
+ }
450
+ else
451
+ {
452
+ this.dataset[ "collapsed" ] = true;
453
+ content.style.display = "none";
454
+ }
455
+ } );
456
+
457
+ domEl.appendChild( actionIcon );
458
+
459
+ parent = parent ?? domEl.parentElement;
460
+
461
+ parent.appendChild( content );
462
+ }
463
+
464
+ LX.makeCollapsible = makeCollapsible;
465
+
400
466
  /**
401
467
  * @method makeCodeSnippet
402
468
  * @description Create a code snippet in a specific language
@@ -499,6 +565,23 @@ function makeCodeSnippet( code, size, options = { } )
499
565
 
500
566
  LX.makeCodeSnippet = makeCodeSnippet;
501
567
 
568
+ /**
569
+ * @method makeIcon
570
+ * @description Gets an SVG element using one of LX.ICONS
571
+ * @param {String} iconName
572
+ * @param {String} iconTitle
573
+ */
574
+ function makeIcon( iconName, iconTitle )
575
+ {
576
+ const icon = document.createElement( "a" );
577
+ icon.title = iconTitle ?? "";
578
+ icon.className = "lexicon";
579
+ icon.innerHTML = LX.ICONS[ iconName ] ?? "";
580
+ return icon;
581
+ }
582
+
583
+ LX.makeIcon = makeIcon;
584
+
502
585
  /**
503
586
  * @method registerCommandbarEntry
504
587
  * @description Adds an extra command bar entry
@@ -768,12 +851,12 @@ function _createCommandbar( root )
768
851
  if( LX.has('CodeEditor') )
769
852
  {
770
853
  const instances = LX.CodeEditor.getInstances();
771
- if(!instances.length) return;
854
+ if( !instances.length ) return;
772
855
 
773
856
  const languages = instances[ 0 ].languages;
774
857
 
775
- for( let l of Object.keys( languages ) ) {
776
-
858
+ for( let l of Object.keys( languages ) )
859
+ {
777
860
  const key = "Language: " + l;
778
861
  const icon = instances[ 0 ]._getFileIcon( null, languages[ l ].ext );
779
862
 
@@ -781,9 +864,11 @@ function _createCommandbar( root )
781
864
  "<img src='" + ( "https://raw.githubusercontent.com/jxarco/lexgui.js/master/" + icon ) + "'>";
782
865
 
783
866
  value += key + " <span class='lang-ext'>(" + languages[ l ].ext + ")</span>";
784
- if( key.toLowerCase().includes( filter ) ) {
867
+ if( key.toLowerCase().includes( filter ) )
868
+ {
785
869
  _addElement( value, () => {
786
- for( let i of instances ) {
870
+ for( let i of instances )
871
+ {
787
872
  i._changeLanguage( l );
788
873
  }
789
874
  }, "", {} );
@@ -841,7 +926,19 @@ function init( options = { } )
841
926
  this.container = document.getElementById( options.container );
842
927
  }
843
928
 
844
- document.documentElement.setAttribute( "data-strictVP", ( options.strictViewport ?? true ) ? "true" : "false" );
929
+ this.usingStrictViewport = options.strictViewport ?? true;
930
+ document.documentElement.setAttribute( "data-strictVP", ( this.usingStrictViewport ) ? "true" : "false" );
931
+
932
+ if( !this.usingStrictViewport )
933
+ {
934
+ document.addEventListener( "scroll", e => {
935
+ // Get all active menuboxes
936
+ const mbs = document.body.querySelectorAll( ".lexmenubox" );
937
+ mbs.forEach( ( mb ) => {
938
+ mb._updatePosition();
939
+ } );
940
+ } );
941
+ }
845
942
 
846
943
  this.commandbar = _createCommandbar( this.container );
847
944
 
@@ -856,6 +953,25 @@ function init( options = { } )
856
953
  this.root = document.body;
857
954
  }
858
955
 
956
+ // Notifications
957
+ {
958
+ const notifSection = document.createElement( "section" );
959
+ notifSection.className = "notifications";
960
+ this.notifications = document.createElement( "ol" );
961
+ this.notifications.className = "";
962
+ this.notifications.iWidth = 0;
963
+ notifSection.appendChild( this.notifications );
964
+ this.container.appendChild( notifSection );
965
+
966
+ this.notifications.addEventListener( "mouseenter", () => {
967
+ this.notifications.classList.add( "list" );
968
+ } );
969
+
970
+ this.notifications.addEventListener( "mouseleave", () => {
971
+ this.notifications.classList.remove( "list" );
972
+ } );
973
+ }
974
+
859
975
  // Disable drag icon
860
976
  root.addEventListener( 'dragover', function( e ) {
861
977
  e.preventDefault();
@@ -1006,6 +1122,7 @@ LX.popup = popup;
1006
1122
  function prompt( text, title, callback, options = {} )
1007
1123
  {
1008
1124
  options.modal = true;
1125
+ options.className = "prompt";
1009
1126
 
1010
1127
  let value = "";
1011
1128
 
@@ -1020,7 +1137,9 @@ function prompt( text, title, callback, options = {} )
1020
1137
 
1021
1138
  p.sameLine( 2 );
1022
1139
 
1023
- p.addButton( null, options.accept || "OK", () => {
1140
+ p.addButton(null, "Cancel", () => {if(options.on_cancel) options.on_cancel(); dialog.close();} );
1141
+
1142
+ p.addButton( null, options.accept || "Continue", () => {
1024
1143
  if( options.required && value === '' )
1025
1144
  {
1026
1145
  text += text.includes("You must fill the input text.") ? "": "\nYou must fill the input text.";
@@ -1034,8 +1153,6 @@ function prompt( text, title, callback, options = {} )
1034
1153
  }
1035
1154
  }, { buttonClass: "primary" });
1036
1155
 
1037
- p.addButton(null, "Cancel", () => {if(options.on_cancel) options.on_cancel(); dialog.close();} );
1038
-
1039
1156
  }, options );
1040
1157
 
1041
1158
  // Focus text prompt
@@ -1049,6 +1166,101 @@ function prompt( text, title, callback, options = {} )
1049
1166
 
1050
1167
  LX.prompt = prompt;
1051
1168
 
1169
+ /**
1170
+ * @method toast
1171
+ * @param {String} title
1172
+ * @param {String} description (Optional)
1173
+ * @param {*} options
1174
+ * action: Data of the custom action { name, callback }
1175
+ * closable: Allow closing the toast
1176
+ * timeout: Time in which the toast closed automatically, in ms. -1 means persistent. [3000]
1177
+ */
1178
+
1179
+ function toast( title, description, options = {} )
1180
+ {
1181
+ if( !title )
1182
+ {
1183
+ throw( "The toast needs at least a title!" );
1184
+ }
1185
+
1186
+ console.assert( this.notifications );
1187
+
1188
+ const toast = document.createElement( "li" );
1189
+ toast.className = "lextoast";
1190
+ toast.style.translate = "0 calc(100% + 30px)";
1191
+ this.notifications.prepend( toast );
1192
+
1193
+ doAsync( () => {
1194
+
1195
+ if( this.notifications.offsetWidth > this.notifications.iWidth )
1196
+ {
1197
+ this.notifications.iWidth = Math.min( this.notifications.offsetWidth, 480 );
1198
+ this.notifications.style.width = this.notifications.iWidth + "px";
1199
+ }
1200
+
1201
+ toast.dataset[ "open" ] = true;
1202
+ }, 10 );
1203
+
1204
+ const content = document.createElement( "div" );
1205
+ content.className = "lextoastcontent";
1206
+ toast.appendChild( content );
1207
+
1208
+ const titleContent = document.createElement( "div" );
1209
+ titleContent.className = "title";
1210
+ titleContent.innerHTML = title;
1211
+ content.appendChild( titleContent );
1212
+
1213
+ if( description )
1214
+ {
1215
+ const desc = document.createElement( "div" );
1216
+ desc.className = "desc";
1217
+ desc.innerHTML = description;
1218
+ content.appendChild( desc );
1219
+ }
1220
+
1221
+ if( options.action )
1222
+ {
1223
+ const panel = new Panel();
1224
+ panel.addButton(null, options.action.name ?? "Accept", options.action.callback.bind( this, toast ), { width: "auto", maxWidth: "150px", className: "right", buttonClass: "outline" });
1225
+ toast.appendChild( panel.root.childNodes[ 0 ] );
1226
+ }
1227
+
1228
+ const that = this;
1229
+
1230
+ toast.close = function() {
1231
+ this.dataset[ "closed" ] = true;
1232
+ doAsync( () => {
1233
+ this.remove();
1234
+ if( !that.notifications.childElementCount )
1235
+ {
1236
+ that.notifications.style.width = "unset";
1237
+ that.notifications.iWidth = 0;
1238
+ }
1239
+ }, 500 );
1240
+ };
1241
+
1242
+ if( options.closable ?? true )
1243
+ {
1244
+ const closeButton = document.createElement( "a" );
1245
+ closeButton.className = "fa fa-xmark lexicon closer";
1246
+ closeButton.addEventListener( "click", () => {
1247
+ toast.close();
1248
+ } );
1249
+ toast.appendChild( closeButton );
1250
+ }
1251
+
1252
+ const timeout = options.timeout ?? 3000;
1253
+
1254
+ if( timeout != -1 )
1255
+ {
1256
+ doAsync( () => {
1257
+ toast.close();
1258
+ }, timeout );
1259
+ }
1260
+ }
1261
+
1262
+ LX.toast = toast;
1263
+
1052
1264
  /**
1053
1265
  * @method badge
1054
1266
  * @param {String} text
@@ -1350,7 +1562,8 @@ class Area {
1350
1562
 
1351
1563
  function inner_mousemove( e )
1352
1564
  {
1353
- switch( that.type ) {
1565
+ switch( that.type )
1566
+ {
1354
1567
  case "right":
1355
1568
  var dt = ( lastMousePosition[ 0 ] - e.x );
1356
1569
  var size = ( that.root.offsetWidth + dt );
@@ -1545,7 +1758,8 @@ class Area {
1545
1758
 
1546
1759
  // Listen resize event on first area
1547
1760
  const resizeObserver = new ResizeObserver( entries => {
1548
- for (const entry of entries) {
1761
+ for ( const entry of entries )
1762
+ {
1549
1763
  const bb = entry.contentRect;
1550
1764
  area2.root.style.height = "calc(100% - " + ( bb.height + 4) + "px )";
1551
1765
  }
@@ -1686,9 +1900,15 @@ class Area {
1686
1900
  this.root.style.height = height;
1687
1901
  }
1688
1902
 
1689
- this.size = [ this.root.clientWidth, this.root.clientHeight ];
1903
+ if( this.onresize )
1904
+ {
1905
+ this.onresize( this.root.getBoundingClientRect() );
1906
+ }
1690
1907
 
1691
- this.propagateEvent( "onresize" );
1908
+ doAsync( () => {
1909
+ this.size = [ this.root.clientWidth, this.root.clientHeight ];
1910
+ this.propagateEvent( "onresize" );
1911
+ }, 150 );
1692
1912
  }
1693
1913
 
1694
1914
  /**
@@ -1705,7 +1925,7 @@ class Area {
1705
1925
  let [area1, area2] = this.sections;
1706
1926
  this.splitExtended = true;
1707
1927
 
1708
- if(this.type == "vertical")
1928
+ if( this.type == "vertical")
1709
1929
  {
1710
1930
  this.offset = area2.root.offsetHeight;
1711
1931
  area2.root.classList.add("fadeout-vertical");
@@ -1719,7 +1939,6 @@ class Area {
1719
1939
  this._moveSplit(-Infinity, true, 8);
1720
1940
  }
1721
1941
 
1722
- // Async resize in some ms...
1723
1942
  doAsync( () => this.propagateEvent('onresize'), 150 );
1724
1943
  }
1725
1944
 
@@ -1735,7 +1954,7 @@ class Area {
1735
1954
  this.splitExtended = false;
1736
1955
  let [area1, area2] = this.sections;
1737
1956
 
1738
- if(this.type == "vertical")
1957
+ if( this.type == "vertical")
1739
1958
  {
1740
1959
  area2.root.classList.add("fadein-vertical");
1741
1960
  this._moveSplit(this.offset);
@@ -1746,7 +1965,6 @@ class Area {
1746
1965
  this._moveSplit(this.offset);
1747
1966
  }
1748
1967
 
1749
- // Async resize in some ms...
1750
1968
  doAsync( () => this.propagateEvent('onresize'), 150 );
1751
1969
  }
1752
1970
 
@@ -1780,11 +1998,15 @@ class Area {
1780
1998
 
1781
1999
  propagateEvent( eventName ) {
1782
2000
 
1783
- for(var i = 0; i < this.sections.length; i++)
2001
+ for( var i = 0; i < this.sections.length; i++ )
1784
2002
  {
1785
- const area = this.sections[i];
1786
- if(area[ eventName ])
2003
+ const area = this.sections[ i ];
2004
+
2005
+ if( area[ eventName ] )
2006
+ {
1787
2007
  area[ eventName ].call( this, area.root.getBoundingClientRect() );
2008
+ }
2009
+
1788
2010
  area.propagateEvent( eventName );
1789
2011
  }
1790
2012
  }
@@ -1807,42 +2029,63 @@ class Area {
1807
2029
  * @param {Function} callback Function to fill the menubar
1808
2030
  * @param {*} options:
1809
2031
  * float: Justify content (left, center, right) [left]
2032
+ * sticky: Fix menubar at the top [true]
1810
2033
  */
1811
2034
 
1812
2035
  addMenubar( callback, options = {} ) {
1813
2036
 
1814
- let menubar = new Menubar(options);
2037
+ let menubar = new Menubar( options );
1815
2038
 
1816
- if(callback) callback( menubar );
2039
+ if( callback )
2040
+ {
2041
+ callback( menubar );
2042
+ }
1817
2043
 
1818
2044
  LX.menubars.push( menubar );
1819
2045
 
1820
2046
  const height = 48; // pixels
2047
+ const [ bar, content ] = this.split({ type: 'vertical', sizes: [height, null], resize: false, menubar: true });
2048
+ menubar.siblingArea = content;
1821
2049
 
1822
- const [bar, content] = this.split({type: 'vertical', sizes: [height, null], resize: false, menubar: true});
1823
2050
  bar.attach( menubar );
1824
- bar.is_menubar = true;
2051
+ bar.isMenubar = true;
2052
+
2053
+ if( options.sticky ?? true )
2054
+ {
2055
+ bar.root.classList.add( "sticky" );
2056
+ }
2057
+
1825
2058
  return menubar;
1826
2059
  }
1827
2060
 
1828
2061
  /**
1829
2062
  * @method addSidebar
1830
2063
  * @param {Function} callback Function to fill the sidebar
2064
+ * @param {Object} options: Sidebar options
2065
+ * width: Width of the sidebar [16rem]
1831
2066
  */
1832
2067
 
1833
2068
  addSidebar( callback, options = {} ) {
1834
2069
 
1835
2070
  let sidebar = new SideBar( options );
1836
2071
 
1837
- if( callback ) callback( sidebar );
2072
+ if( callback )
2073
+ {
2074
+ callback( sidebar );
2075
+ }
2076
+
2077
+ // Generate DOM elements after adding all entries
2078
+ sidebar.update();
1838
2079
 
1839
2080
  LX.menubars.push( sidebar );
1840
2081
 
1841
- const width = 64; // pixels
2082
+ const width = options.width ?? "16rem";
2083
+ const [ bar, content ] = this.split( { type: 'horizontal', sizes: [ width, null ], resize: false, sidebar: true } );
2084
+ sidebar.siblingArea = content;
1842
2085
 
1843
- const [bar, content] = this.split( { type: 'horizontal', sizes: [ width, null ], resize: false, sidebar: true } );
1844
2086
  bar.attach( sidebar );
1845
- bar.is_sidebar = true;
2087
+ bar.isSidebar = true;
2088
+
1846
2089
  return sidebar;
1847
2090
  }
1848
2091
 
@@ -2098,7 +2341,8 @@ class Area {
2098
2341
 
2099
2342
  this.size = [ rect.width, rect.height ];
2100
2343
 
2101
- for(var i = 0; i < this.sections.length; i++) {
2344
+ for( var i = 0; i < this.sections.length; i++ )
2345
+ {
2102
2346
  this.sections[i]._update();
2103
2347
  }
2104
2348
  }
@@ -2122,7 +2366,7 @@ class Tabs {
2122
2366
  static TAB_SIZE = 28;
2123
2367
  static TAB_ID = 0;
2124
2368
 
2125
- constructor( area, options = {} ) {
2369
+ constructor( area, options = {} ) {
2126
2370
 
2127
2371
  this.onclose = options.onclose;
2128
2372
 
@@ -2204,24 +2448,28 @@ class Tabs {
2204
2448
  }
2205
2449
 
2206
2450
  // debug
2207
- if(folding)
2451
+ if( folding )
2208
2452
  {
2209
2453
  this.folded = true;
2210
2454
  this.folding = folding;
2211
2455
 
2212
- if(folding == "up") area.root.insertChildAtIndex(area.sections[1].root, 0);
2456
+ if( folding == "up" )
2457
+ {
2458
+ area.root.insertChildAtIndex(area.sections[1].root, 0);
2459
+ }
2213
2460
 
2214
2461
  // Listen resize event on parent area
2215
2462
  const resizeObserver = new ResizeObserver((entries) => {
2216
- for (const entry of entries) {
2463
+ for (const entry of entries)
2464
+ {
2217
2465
  const bb = entry.contentRect;
2218
- const sibling = area.parentArea.sections[0].root;
2219
- const add_offset = true; // hardcoded...
2220
- sibling.style.height = "calc(100% - " + ((add_offset ? 42 : 0) + bb.height) + "px )";
2466
+ const sibling = area.parentArea.sections[ 0 ].root;
2467
+ const addOffset = true; // hardcoded...
2468
+ sibling.style.height = "calc(100% - " + ((addOffset ? 42 : 0) + bb.height) + "px )";
2221
2469
  }
2222
2470
  });
2223
2471
 
2224
- resizeObserver.observe(this.area.root);
2472
+ resizeObserver.observe( this.area.root );
2225
2473
  this.area.root.classList.add('folded');
2226
2474
  }
2227
2475
  }
@@ -2358,7 +2606,8 @@ class Tabs {
2358
2606
 
2359
2607
  setTimeout( () => {
2360
2608
 
2361
- if( options.onCreate ) {
2609
+ if( options.onCreate )
2610
+ {
2362
2611
  options.onCreate.call(this, this.area.root.getBoundingClientRect());
2363
2612
  }
2364
2613
 
@@ -2420,34 +2669,230 @@ LX.Tabs = Tabs;
2420
2669
 
2421
2670
  class Menubar {
2422
2671
 
2423
- constructor( options = {} ) {
2672
+ constructor( options = {} ) {
2424
2673
 
2425
- this.root = document.createElement('div');
2674
+ this.root = document.createElement( "div" );
2426
2675
  this.root.className = "lexmenubar";
2427
- if(options.float)
2676
+
2677
+ if( options.float )
2678
+ {
2428
2679
  this.root.style.justifyContent = options.float;
2429
- this.items = [];
2680
+ }
2681
+
2682
+ this.items = [ ];
2683
+ this.buttons = [ ];
2684
+ this.icons = { };
2685
+ this.shorts = { };
2686
+ }
2687
+
2688
+ _resetMenubar() {
2689
+
2690
+ // Menu entries are in the menubar..
2691
+ this.root.querySelectorAll(".lexmenuentry").forEach( _entry => {
2692
+ _entry.classList.remove( 'selected' );
2693
+ _entry.built = false;
2694
+ } );
2695
+
2696
+ // Menuboxes are in the root area!
2697
+ LX.root.querySelectorAll(".lexmenubox").forEach(e => e.remove());
2698
+
2699
+ // Next time we need to click again
2700
+ this.focused = false;
2701
+ }
2702
+
2703
+ _createSubmenu( o, k, c, d ) {
2704
+
2705
+ let menuElement = document.createElement('div');
2706
+ menuElement.className = "lexmenubox";
2707
+ menuElement.tabIndex = "0";
2708
+ c.currentMenu = menuElement;
2709
+ menuElement.parentEntry = c;
2710
+
2711
+ const isSubMenu = c.classList.contains( "lexmenuboxentry" );
2712
+ if( isSubMenu )
2713
+ {
2714
+ menuElement.dataset[ "submenu" ] = true;
2715
+ }
2716
+
2717
+ menuElement._updatePosition = () => {
2718
+
2719
+ // Remove transitions for this change..
2720
+ const transition = menuElement.style.transition;
2721
+ menuElement.style.transition = "none";
2722
+ flushCss( menuElement );
2723
+
2724
+ doAsync( () => {
2725
+ let rect = c.getBoundingClientRect();
2726
+ rect.x += document.scrollingElement.scrollLeft;
2727
+ rect.y += document.scrollingElement.scrollTop;
2728
+ menuElement.style.left = ( isSubMenu ? ( rect.x + rect.width ) : rect.x ) + "px";
2729
+ menuElement.style.top = ( isSubMenu ? rect.y : ( ( rect.y + rect.height ) ) - 4 ) + "px";
2730
+
2731
+ menuElement.style.transition = transition;
2732
+ } );
2733
+ };
2734
+
2735
+ menuElement._updatePosition();
2736
+
2737
+ doAsync( () => {
2738
+ menuElement.dataset[ "open" ] = true;
2739
+ }, 10 );
2740
+
2741
+ LX.root.appendChild( menuElement );
2742
+
2743
+ for( var i = 0; i < o[ k ].length; ++i )
2744
+ {
2745
+ const subitem = o[ k ][ i ];
2746
+ const subkey = Object.keys( subitem )[ 0 ];
2747
+ const hasSubmenu = subitem[ subkey ].length;
2748
+ const isCheckbox = subitem[ 'type' ] == 'checkbox';
2749
+ let subentry = document.createElement('div');
2750
+ subentry.className = "lexmenuboxentry";
2751
+ subentry.className += (i == o[k].length - 1 ? " last" : "") + ( subitem.disabled ? " disabled" : "" );
2752
+
2753
+ if( subkey == '' )
2754
+ {
2755
+ subentry.className = " lexseparator";
2756
+ }
2757
+ else
2758
+ {
2759
+ subentry.id = subkey;
2760
+ let subentrycont = document.createElement('div');
2761
+ subentrycont.innerHTML = "";
2762
+ subentrycont.classList = "lexmenuboxentrycontainer";
2763
+ subentry.appendChild(subentrycont);
2764
+ const icon = this.icons[ subkey ];
2765
+ if( isCheckbox )
2766
+ {
2767
+ subentrycont.innerHTML += "<input type='checkbox' >";
2768
+ }
2769
+ else if( icon )
2770
+ {
2771
+ subentrycont.innerHTML += "<a class='" + icon + " fa-sm'></a>";
2772
+ }
2773
+ else
2774
+ {
2775
+ subentrycont.innerHTML += "<a class='fa-solid fa-sm noicon'></a>";
2776
+ subentrycont.classList.add( "noicon" );
2777
+
2778
+ }
2779
+ subentrycont.innerHTML += "<div class='lexentryname'>" + subkey + "</div>";
2780
+ }
2781
+
2782
+ let checkboxInput = subentry.querySelector('input');
2783
+ if( checkboxInput )
2784
+ {
2785
+ checkboxInput.checked = subitem.checked ?? false;
2786
+ checkboxInput.addEventListener('change', e => {
2787
+ subitem.checked = checkboxInput.checked;
2788
+ const f = subitem[ 'callback' ];
2789
+ if( f )
2790
+ {
2791
+ f.call( this, subitem.checked, subkey, subentry );
2792
+ this._resetMenubar();
2793
+ }
2794
+ e.stopPropagation();
2795
+ e.stopImmediatePropagation();
2796
+ })
2797
+ }
2798
+
2799
+ menuElement.appendChild( subentry );
2800
+
2801
+ // Nothing more for separators
2802
+ if( subkey == '' )
2803
+ {
2804
+ continue;
2805
+ }
2430
2806
 
2431
- this.icons = {};
2432
- this.shorts = {};
2433
- this.buttons = [];
2807
+ menuElement.addEventListener('keydown', e => {
2808
+ e.preventDefault();
2809
+ let short = this.shorts[ subkey ];
2810
+ if(!short) return;
2811
+ // check if it's a letter or other key
2812
+ short = short.length == 1 ? short.toLowerCase() : short;
2813
+ if( short == e.key )
2814
+ {
2815
+ subentry.click()
2816
+ }
2817
+ });
2818
+
2819
+ // Add callback
2820
+ subentry.addEventListener("click", e => {
2821
+ if( checkboxInput )
2822
+ {
2823
+ subitem.checked = !subitem.checked;
2824
+ }
2825
+ const f = subitem[ 'callback' ];
2826
+ if( f )
2827
+ {
2828
+ f.call( this, checkboxInput ? subitem.checked : subkey, checkboxInput ? subkey : subentry );
2829
+ this._resetMenubar();
2830
+ }
2831
+ e.stopPropagation();
2832
+ e.stopImmediatePropagation();
2833
+ });
2834
+
2835
+ // Add icon if has submenu, else check for shortcut
2836
+ if( !hasSubmenu)
2837
+ {
2838
+ if( this.shorts[ subkey ] )
2839
+ {
2840
+ let shortEl = document.createElement('div');
2841
+ shortEl.className = "lexentryshort";
2842
+ shortEl.innerText = this.shorts[ subkey ];
2843
+ subentry.appendChild( shortEl );
2844
+ }
2845
+ continue;
2846
+ }
2847
+
2848
+ let submenuIcon = document.createElement('a');
2849
+ submenuIcon.className = "fa-solid fa-angle-right fa-xs";
2850
+ subentry.appendChild( submenuIcon );
2851
+
2852
+ subentry.addEventListener("mouseover", e => {
2853
+ if( subentry.built )
2854
+ {
2855
+ return;
2856
+ }
2857
+ subentry.built = true;
2858
+ this._createSubmenu( subitem, subkey, subentry, ++d );
2859
+ e.stopPropagation();
2860
+ });
2861
+
2862
+ subentry.addEventListener("mouseleave", e => {
2863
+ if( subentry.currentMenu && ( subentry.currentMenu != e.toElement ) )
2864
+ {
2865
+ d = -1; // Reset depth
2866
+ delete subentry.built;
2867
+ subentry.currentMenu.remove();
2868
+ delete subentry.currentMenu;
2869
+ }
2870
+ });
2871
+ }
2872
+
2873
+ // Set final width
2874
+ menuElement.style.width = menuElement.offsetWidth + "px";
2434
2875
  }
2435
2876
 
2436
2877
  /**
2437
2878
  * @method add
2438
- * @param {*} options:
2879
+ * @param {Object} options:
2439
2880
  * callback: Function to call on each item
2881
+ * icon: Entry icon
2882
+ * short: Entry shortcut name
2440
2883
  */
2441
2884
 
2442
2885
  add( path, options = {} ) {
2443
2886
 
2444
- if(options.constructor == Function)
2887
+ if( options.constructor == Function )
2888
+ {
2445
2889
  options = { callback: options };
2890
+ }
2446
2891
 
2447
- // process path
2448
- const tokens = path.split("/");
2892
+ // Process path
2893
+ const tokens = path.split( "/" );
2449
2894
 
2450
- // assign icons and shortcuts to last token in path
2895
+ // Assign icons and shortcuts to last token in path
2451
2896
  const lastPath = tokens[tokens.length - 1];
2452
2897
  this.icons[ lastPath ] = options.icon;
2453
2898
  this.shorts[ lastPath ] = options.short;
@@ -2455,211 +2900,119 @@ class Menubar {
2455
2900
  let idx = 0;
2456
2901
  let that = this;
2457
2902
 
2458
- const insert = (token, list) => {
2459
- if(token == undefined) return;
2903
+ const _insertEntry = ( token, list ) => {
2904
+ if( token == undefined )
2905
+ {
2906
+ return;
2907
+ }
2460
2908
 
2461
2909
  let found = null;
2462
2910
  list.forEach( o => {
2463
- const keys = Object.keys(o);
2911
+ const keys = Object.keys( o );
2464
2912
  const key = keys.find( t => t == token );
2465
- if(key) found = o[ key ];
2913
+ if( key ) found = o[ key ];
2466
2914
  } );
2467
2915
 
2468
- if(found) {
2469
- insert( tokens[idx++], found );
2916
+ if( found )
2917
+ {
2918
+ _insertEntry( tokens[ idx++ ], found );
2470
2919
  }
2471
- else {
2920
+ else
2921
+ {
2472
2922
  let item = {};
2473
2923
  item[ token ] = [];
2474
- const next_token = tokens[idx++];
2924
+ const nextToken = tokens[ idx++ ];
2475
2925
  // Check if last token -> add callback
2476
- if(!next_token) {
2926
+ if( !nextToken )
2927
+ {
2477
2928
  item[ 'callback' ] = options.callback;
2929
+ item[ 'disabled' ] = options.disabled;
2478
2930
  item[ 'type' ] = options.type;
2479
2931
  item[ 'checked' ] = options.checked;
2480
2932
  }
2481
2933
  list.push( item );
2482
- insert( next_token, item[ token ] );
2934
+ _insertEntry( nextToken, item[ token ] );
2483
2935
  }
2484
2936
  };
2485
2937
 
2486
- insert( tokens[idx++], this.items );
2938
+ _insertEntry( tokens[idx++], this.items );
2487
2939
 
2488
2940
  // Create elements
2489
2941
 
2490
2942
  for( let item of this.items )
2491
2943
  {
2492
- let key = Object.keys(item)[0];
2493
- let pKey = key.replace(/\s/g, '').replaceAll('.', '');
2944
+ let key = Object.keys( item )[ 0 ];
2945
+ let pKey = key.replace( /\s/g, '' ).replaceAll( '.', '' );
2494
2946
 
2495
2947
  // Item already created
2496
- if( this.root.querySelector("#" + pKey) )
2948
+ if( this.root.querySelector( "#" + pKey ) )
2949
+ {
2497
2950
  continue;
2951
+ }
2498
2952
 
2499
2953
  let entry = document.createElement('div');
2500
2954
  entry.className = "lexmenuentry";
2501
2955
  entry.id = pKey;
2502
2956
  entry.innerHTML = "<span>" + key + "</span>";
2503
- if(options.position == "left") {
2957
+ entry.tabIndex = "1";
2958
+
2959
+ if( options.position == "left" )
2960
+ {
2504
2961
  this.root.prepend( entry );
2505
2962
  }
2506
- else {
2507
- if(options.position == "right")
2963
+ else
2964
+ {
2965
+ if( options.position == "right" )
2966
+ {
2508
2967
  entry.right = true;
2509
- if(this.root.lastChild && this.root.lastChild.right) {
2968
+ }
2969
+
2970
+ if( this.root.lastChild && this.root.lastChild.right )
2971
+ {
2510
2972
  this.root.lastChild.before( entry );
2511
2973
  }
2512
- else {
2974
+ else
2975
+ {
2513
2976
  this.root.appendChild( entry );
2514
2977
  }
2515
2978
  }
2516
2979
 
2517
- const create_submenu = function( o, k, c, d ) {
2518
-
2519
- let contextmenu = document.createElement('div');
2520
- contextmenu.className = "lexcontextmenu";
2521
- contextmenu.tabIndex = "0";
2522
- const isSubMenu = c.classList.contains('lexcontextmenuentry');
2523
- var rect = c.getBoundingClientRect();
2524
- contextmenu.style.left = (isSubMenu ? rect.width : rect.left) + "px";
2525
- // Entries use css to set top relative to parent
2526
- contextmenu.style.top = (isSubMenu ? 0 : rect.bottom - 4) + "px";
2527
- c.appendChild( contextmenu );
2528
-
2529
- contextmenu.focus();
2530
-
2531
- rect = contextmenu.getBoundingClientRect();
2532
-
2533
- for( var i = 0; i < o[k].length; ++i )
2534
- {
2535
- const subitem = o[k][i];
2536
- const subkey = Object.keys(subitem)[0];
2537
- const hasSubmenu = subitem[ subkey ].length;
2538
- const isCheckbox = subitem[ 'type' ] == 'checkbox';
2539
- let subentry = document.createElement('div');
2540
- subentry.className = "lexcontextmenuentry";
2541
- subentry.className += (i == o[k].length - 1 ? " last" : "");
2542
- if(subkey == '')
2543
- subentry.className = " lexseparator";
2544
- else {
2545
-
2546
- subentry.id = subkey;
2547
- let subentrycont = document.createElement('div');
2548
- subentrycont.innerHTML = "";
2549
- subentrycont.classList = "lexcontextmenuentrycontainer";
2550
- subentry.appendChild(subentrycont);
2551
- const icon = that.icons[ subkey ];
2552
- if(isCheckbox){
2553
- subentrycont.innerHTML += "<input type='checkbox' >";
2554
- }else if(icon) {
2555
- subentrycont.innerHTML += "<a class='" + icon + " fa-sm'></a>";
2556
- }else {
2557
- subentrycont.innerHTML += "<a class='fa-solid fa-sm noicon'></a>";
2558
- subentrycont.classList.add( "noicon" );
2559
-
2560
- }
2561
- subentrycont.innerHTML += "<div class='lexentryname'>" + subkey + "</div>";
2562
- }
2563
-
2564
- let checkbox_input = subentry.querySelector('input');
2565
- if(checkbox_input) {
2566
- checkbox_input.checked = subitem.checked ?? false;
2567
- checkbox_input.addEventListener('change', (e) => {
2568
- subitem.checked = checkbox_input.checked;
2569
- const f = subitem[ 'callback' ];
2570
- if(f) {
2571
- f.call( this, subitem.checked, subkey, subentry );
2572
- that.root.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
2573
- }
2574
- e.stopPropagation();
2575
- e.stopImmediatePropagation();
2576
- })
2577
- }
2578
-
2579
- contextmenu.appendChild( subentry );
2580
-
2581
- // Nothing more for separators
2582
- if(subkey == '') continue;
2583
-
2584
- contextmenu.addEventListener('keydown', function(e) {
2585
- e.preventDefault();
2586
- let short = that.shorts[ subkey ];
2587
- if(!short) return;
2588
- // check if it's a letter or other key
2589
- short = short.length == 1 ? short.toLowerCase() : short;
2590
- if(short == e.key) {
2591
- subentry.click()
2592
- }
2593
- });
2594
-
2595
- // Add callback
2596
- subentry.addEventListener("click", e => {
2597
- if(checkbox_input) {
2598
- subitem.checked = !subitem.checked;
2599
- }
2600
- const f = subitem[ 'callback' ];
2601
- if(f) {
2602
- f.call( this, checkbox_input ? subitem.checked : subkey, checkbox_input ? subkey : subentry );
2603
- that.root.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
2604
- }
2605
- e.stopPropagation();
2606
- e.stopImmediatePropagation();
2607
- });
2608
-
2609
- // Add icon if has submenu, else check for shortcut
2610
- if( !hasSubmenu)
2611
- {
2612
- if(that.shorts[ subkey ]) {
2613
- let shortEl = document.createElement('div');
2614
- shortEl.className = "lexentryshort";
2615
- shortEl.innerText = that.shorts[ subkey ];
2616
- subentry.appendChild( shortEl );
2617
- }
2618
- continue;
2619
- }
2620
-
2621
- let submenuIcon = document.createElement('a');
2622
- submenuIcon.className = "fa-solid fa-angle-right fa-xs";
2623
- subentry.appendChild( submenuIcon );
2624
-
2625
- subentry.addEventListener("mouseover", e => {
2626
- if(subentry.built)
2627
- return;
2628
- subentry.built = true;
2629
- create_submenu( subitem, subkey, subentry, ++d );
2630
- e.stopPropagation();
2631
- });
2632
-
2633
- subentry.addEventListener("mouseleave", () => {
2634
- d = -1; // Reset depth
2635
- delete subentry.built;
2636
- contextmenu.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
2637
- });
2638
- }
2639
-
2640
- // Set final width
2641
- contextmenu.style.width = contextmenu.offsetWidth + "px";
2980
+ const _showEntry = () => {
2981
+ this._resetMenubar();
2982
+ entry.classList.add( "selected" );
2983
+ entry.built = true;
2984
+ this._createSubmenu( item, key, entry, -1 );
2642
2985
  };
2643
2986
 
2644
2987
  entry.addEventListener("click", () => {
2645
-
2646
2988
  const f = item[ 'callback' ];
2647
- if(f) {
2989
+ if( f )
2990
+ {
2648
2991
  f.call( this, key, entry );
2649
2992
  return;
2650
2993
  }
2651
2994
 
2652
- // Manage selected
2653
- this.root.querySelectorAll(".lexmenuentry").forEach( e => e.classList.remove( 'selected' ) );
2654
- entry.classList.add( "selected" );
2995
+ _showEntry();
2655
2996
 
2656
- this.root.querySelectorAll(".lexcontextmenu").forEach( e => e.remove() );
2657
- create_submenu( item, key, entry, -1 );
2997
+ this.focused = true;
2658
2998
  });
2659
2999
 
2660
- entry.addEventListener("mouseleave", () => {
2661
- this.root.querySelectorAll(".lexmenuentry").forEach( e => e.classList.remove( 'selected' ) );
2662
- this.root.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
3000
+ entry.addEventListener( "mouseover", (e) => {
3001
+
3002
+ if( this.focused && !entry.built )
3003
+ {
3004
+ _showEntry();
3005
+ }
3006
+ });
3007
+
3008
+ entry.addEventListener("blur", (e) => {
3009
+
3010
+ if( e.relatedTarget && e.relatedTarget.classList.contains( "lexmenubox" ) )
3011
+ {
3012
+ return;
3013
+ }
3014
+
3015
+ this._resetMenubar();
2663
3016
  });
2664
3017
  }
2665
3018
  }
@@ -2678,20 +3031,24 @@ class Menubar {
2678
3031
  * @param {Object} item: parent item
2679
3032
  * @param {Array} tokens: split path strings
2680
3033
  */
2681
- getSubitem(item, tokens) {
3034
+ getSubitem( item, tokens ) {
2682
3035
 
2683
3036
  let subitem = null;
2684
- let path = tokens[0];
2685
- for(let i = 0; i < item.length; i++) {
2686
- if(item[i][path]) {
3037
+ let path = tokens[ 0 ];
2687
3038
 
2688
- if(tokens.length == 1) {
2689
- subitem = item[i];
3039
+ for( let i = 0; i < item.length; i++ )
3040
+ {
3041
+ if( item[ i ][ path ] )
3042
+ {
3043
+ if( tokens.length == 1 )
3044
+ {
3045
+ subitem = item[ i ];
2690
3046
  return subitem;
2691
3047
  }
2692
- else {
2693
- tokens.splice(0,1);
2694
- return this.getSubitem(item[i][path], tokens);
3048
+ else
3049
+ {
3050
+ tokens.splice( 0, 1 );
3051
+ return this.getSubitem( item[ i ][ path ], tokens );
2695
3052
  }
2696
3053
 
2697
3054
  }
@@ -2718,11 +3075,12 @@ class Menubar {
2718
3075
  setButtonIcon( title, icon, callback, options = {} ) {
2719
3076
 
2720
3077
  const button = this.buttons[ title ];
2721
- if(button) {
2722
-
3078
+ if( button )
3079
+ {
2723
3080
  button.querySelector('a').className = "fa-solid" + " " + icon + " lexicon";
2724
3081
  }
2725
- else {
3082
+ else
3083
+ {
2726
3084
  let button = document.createElement('div');
2727
3085
  const disabled = options.disabled ?? false;
2728
3086
  button.className = "lexmenubutton" + (disabled ? " disabled" : "");
@@ -2732,15 +3090,21 @@ class Menubar {
2732
3090
  button.style.maxHeight = "calc(100% - 10px)";
2733
3091
  button.style.alignItems = "center";
2734
3092
 
2735
- if(options.float == "right")
3093
+ if( options.float == "right" )
3094
+ {
2736
3095
  button.right = true;
2737
- if(this.root.lastChild && this.root.lastChild.right) {
3096
+ }
3097
+
3098
+ if( this.root.lastChild && this.root.lastChild.right )
3099
+ {
2738
3100
  this.root.lastChild.before( button );
2739
3101
  }
2740
- else if(options.float == "left") {
2741
- this.root.prepend(button);
3102
+ else if( options.float == "left" )
3103
+ {
3104
+ this.root.prepend( button );
2742
3105
  }
2743
- else {
3106
+ else
3107
+ {
2744
3108
  this.root.appendChild( button );
2745
3109
  }
2746
3110
 
@@ -2760,11 +3124,12 @@ class Menubar {
2760
3124
 
2761
3125
  setButtonImage( title, src, callback, options = {} ) {
2762
3126
  const button = this.buttons[ title ];
2763
- if(button) {
2764
-
3127
+ if( button )
3128
+ {
2765
3129
  button.querySelector('a').className = "fa-solid" + " " + icon + " lexicon";
2766
3130
  }
2767
- else {
3131
+ else
3132
+ {
2768
3133
  let button = document.createElement('div');
2769
3134
  const disabled = options.disabled ?? false;
2770
3135
  button.className = "lexmenubutton" + (disabled ? " disabled" : "");
@@ -2773,15 +3138,21 @@ class Menubar {
2773
3138
  button.style.padding = "5px";
2774
3139
  button.style.alignItems = "center";
2775
3140
 
2776
- if(options.float == "right")
3141
+ if( options.float == "right" )
3142
+ {
2777
3143
  button.right = true;
2778
- if(this.root.lastChild && this.root.lastChild.right) {
3144
+ }
3145
+
3146
+ if( this.root.lastChild && this.root.lastChild.right )
3147
+ {
2779
3148
  this.root.lastChild.before( button );
2780
3149
  }
2781
- else if(options.float == "left") {
2782
- this.root.prepend(button);
3150
+ else if( options.float == "left" )
3151
+ {
3152
+ this.root.prepend( button );
2783
3153
  }
2784
- else {
3154
+ else
3155
+ {
2785
3156
  this.root.appendChild( button );
2786
3157
  }
2787
3158
 
@@ -2894,90 +3265,323 @@ LX.Menubar = Menubar;
2894
3265
 
2895
3266
  class SideBar {
2896
3267
 
2897
- constructor( options = {} ) {
3268
+ /**
3269
+ * @param {Object} options
3270
+ * filter: Add search bar to filter entries [false]
3271
+ * skipHeader: Do not use sidebar header [false]
3272
+ * headerImg: Image to be shown as avatar
3273
+ * headerIcon: Icon to be shown as avatar (from LX.ICONS)
3274
+ * headerTitle: Header title
3275
+ * headerSubtitle: Header subtitle
3276
+ * skipFooter: Do not use sidebar footer [false]
3277
+ * footerImg: Image to be shown as avatar
3278
+ * footerIcon: Icon to be shown as avatar (from LX.ICONS)
3279
+ * footerTitle: Footer title
3280
+ * footerSubtitle: Footer subtitle
3281
+ * collapsable: Sidebar can toggle between collapsed/expanded [true]
3282
+ * collapseToIcons: When Sidebar collapses, icons remains visible [true]
3283
+ * onHeaderPressed: Function to call when header is pressed
3284
+ * onFooterPressed: Function to call when footer is pressed
3285
+ */
3286
+
3287
+ constructor( options = {} ) {
2898
3288
 
2899
3289
  this.root = document.createElement( 'div' );
2900
3290
  this.root.className = "lexsidebar";
2901
3291
 
2902
- this.footer = document.createElement( 'div' );
2903
- this.footer.className = "lexsidebarfooter";
2904
- this.root.appendChild( this.footer );
3292
+ this.collapsable = options.collapsable ?? true;
3293
+ this._collapseWidth = ( options.collapseToIcons ?? true ) ? "58px" : "0px";
3294
+ this.collapsed = false;
3295
+
3296
+ this.filterString = "";
3297
+
3298
+ doAsync( () => {
3299
+
3300
+ this.root.parentElement.ogWidth = this.root.parentElement.style.width;
3301
+ this.root.parentElement.style.transition = "width 0.25s ease-out";
3302
+
3303
+ this.resizeObserver = new ResizeObserver( entries => {
3304
+ for ( const entry of entries )
3305
+ {
3306
+ this.siblingArea.setSize( [ "calc(100% - " + ( entry.contentRect.width ) + "px )", null ] );
3307
+ }
3308
+ });
3309
+
3310
+ }, 10 );
3311
+
3312
+ // This account for header, footer and all inner paddings
3313
+ let contentOffset = 32;
3314
+
3315
+ // Header
3316
+ if( !( options.skipHeader ?? false ) )
3317
+ {
3318
+ this.header = document.createElement( 'div' );
3319
+ this.header.className = "lexsidebarheader";
3320
+ this.root.appendChild( this.header );
3321
+
3322
+ this.header.addEventListener( "click", e => {
3323
+ if( this.collapsed )
3324
+ {
3325
+ e.preventDefault();
3326
+ e.stopPropagation();
3327
+ this.toggleCollapsed();
3328
+ }
3329
+ else if( options.onHeaderPressed )
3330
+ {
3331
+ options.onHeaderPressed( e );
3332
+ }
3333
+ } );
3334
+
3335
+ const avatar = document.createElement( 'span' );
3336
+ avatar.className = "lexavatar";
3337
+ this.header.appendChild( avatar );
3338
+
3339
+ if( options.headerImage )
3340
+ {
3341
+ const avatarImg = document.createElement( 'img' );
3342
+ avatarImg.src = options.headerImage;
3343
+ avatar.appendChild( avatarImg );
3344
+ }
3345
+ else if( options.headerIcon )
3346
+ {
3347
+ const avatarIcon = LX.makeIcon( options.headerIcon );
3348
+ avatar.appendChild( avatarIcon );
3349
+ }
3350
+
3351
+ // Info
3352
+ {
3353
+ const info = document.createElement( 'div' );
3354
+ this.header.appendChild( info );
3355
+
3356
+ const infoText = document.createElement( 'span' );
3357
+ infoText.innerHTML = options.headerTitle ?? "";
3358
+ info.appendChild( infoText );
3359
+
3360
+ const infoSubtext = document.createElement( 'span' );
3361
+ infoSubtext.innerHTML = options.headerSubtitle ?? "";
3362
+ info.appendChild( infoSubtext );
3363
+ }
3364
+
3365
+ if( this.collapsable )
3366
+ {
3367
+ const icon = LX.makeIcon( "Sidebar", "Toggle Sidebar" );
3368
+ this.header.appendChild( icon );
3369
+
3370
+ icon.addEventListener( "click", (e) => {
3371
+ e.preventDefault();
3372
+ e.stopPropagation();
3373
+ this.toggleCollapsed();
3374
+ } );
3375
+ }
3376
+
3377
+ contentOffset += 52;
3378
+ }
3379
+
3380
+ // Entry filter
3381
+ if( !( options.filter ?? false ) )
3382
+ {
3383
+ const panel = new Panel();
3384
+ panel.addText(null, "", (value, event) => {
3385
+ this.filterString = value;
3386
+ this.update();
3387
+ }, { placeholder: "Search...", icon: "fa-solid fa-magnifying-glass" });
3388
+ this.filter = panel.root.childNodes[ 0 ];
3389
+ this.root.appendChild( this.filter );
3390
+ contentOffset += 31;
3391
+ }
3392
+
3393
+ // Content
3394
+ {
3395
+ this.content = document.createElement( 'div' );
3396
+ this.content.className = "lexsidebarcontent";
3397
+ this.root.appendChild( this.content );
3398
+ }
3399
+
3400
+ // Footer
3401
+ if( !( options.skipFooter ?? false ) )
3402
+ {
3403
+ this.footer = document.createElement( 'div' );
3404
+ this.footer.className = "lexsidebarfooter";
3405
+ this.root.appendChild( this.footer );
3406
+
3407
+ this.footer.addEventListener( "click", e => {
3408
+ if( options.onFooterPressed )
3409
+ {
3410
+ options.onFooterPressed( e );
3411
+ }
3412
+ } );
3413
+
3414
+ const avatar = document.createElement( 'span' );
3415
+ avatar.className = "lexavatar";
3416
+ this.footer.appendChild( avatar );
3417
+
3418
+ if( options.footerImage )
3419
+ {
3420
+ const avatarImg = document.createElement( 'img' );
3421
+ avatarImg.src = options.footerImage;
3422
+ avatar.appendChild( avatarImg );
3423
+ }
3424
+ else if( options.footerIcon )
3425
+ {
3426
+ const avatarIcon = LX.makeIcon( options.footerIcon );
3427
+ avatar.appendChild( avatarIcon );
3428
+ }
3429
+
3430
+ // Info
3431
+ {
3432
+ const info = document.createElement( 'div' );
3433
+ this.footer.appendChild( info );
3434
+
3435
+ const infoText = document.createElement( 'span' );
3436
+ infoText.innerHTML = options.footerTitle ?? "";
3437
+ info.appendChild( infoText );
3438
+
3439
+ const infoSubtext = document.createElement( 'span' );
3440
+ infoSubtext.innerHTML = options.footerSubtitle ?? "";
3441
+ info.appendChild( infoSubtext );
3442
+ }
3443
+
3444
+ const icon = LX.makeIcon( "MenuArrows" );
3445
+ this.footer.appendChild( icon );
3446
+
3447
+ contentOffset += 52;
3448
+ }
3449
+
3450
+ // Set width depending on header/footer
3451
+ this.content.style.height = `calc(100% - ${ contentOffset }px)`;
2905
3452
 
2906
3453
  this.items = [ ];
3454
+ this.icons = { };
3455
+ this.groups = { };
2907
3456
  }
2908
3457
 
2909
3458
  /**
2910
- * @method add
2911
- * @param {*} options:
2912
- * callback: Function to call on each item
2913
- * bottom: Bool to set item at the bottom as helper button (not selectable)
2914
- * className: Add class to the entry DOM element
3459
+ * @method toggleCollapsed
3460
+ * @param {Boolean} force: Force collapsed state
2915
3461
  */
2916
3462
 
2917
- add( key, options = {} ) {
2918
-
2919
- if( options.constructor == Function )
2920
- options = { callback: options };
3463
+ toggleCollapsed( force ) {
2921
3464
 
2922
- let pKey = key.replace( /\s/g, '' ).replaceAll( '.', '' );
2923
-
2924
- if( this.items.findIndex( (v, i) => v.key == pKey ) > -1 )
3465
+ if( !this.collapsable )
2925
3466
  {
2926
- console.warn( `'${key}' already created in Sidebar` );
2927
3467
  return;
2928
3468
  }
2929
3469
 
2930
- let entry = document.createElement( 'div' );
2931
- entry.className = "lexsidebarentry " + ( options.className ?? "" );
2932
- entry.id = pKey;
3470
+ this.collapsed = force ?? !this.collapsed;
2933
3471
 
2934
- if( options.bottom )
3472
+ if( this.collapsed )
2935
3473
  {
2936
- this.footer.appendChild( entry );
3474
+ this.root.classList.add( "collapsing" );
3475
+ this.root.parentElement.style.width = this._collapseWidth;
2937
3476
  }
2938
3477
  else
2939
3478
  {
2940
- this.root.appendChild( entry );
3479
+ this.root.classList.remove( "collapsing" );
3480
+ this.root.classList.remove( "collapsed" );
3481
+ this.root.parentElement.style.width = this.root.parentElement.ogWidth;
2941
3482
  }
2942
3483
 
2943
- // Reappend footer in root
2944
- this.root.appendChild( this.footer );
3484
+ if( !this.resizeObserver )
3485
+ {
3486
+ throw( "Wait until ResizeObserver has been created!" );
3487
+ }
2945
3488
 
2946
- let button = document.createElement( 'button' );
2947
- button.innerHTML = "<i class='"+ (options.icon ?? "") + "'></i>";
2948
- entry.appendChild( button );
3489
+ this.resizeObserver.observe( this.root.parentElement );
2949
3490
 
2950
- let desc = document.createElement( 'span' );
2951
- desc.className = 'lexsidebarentrydesc';
2952
- desc.innerHTML = key;
2953
- entry.appendChild( desc );
3491
+ doAsync( () => {
2954
3492
 
2955
- button.addEventListener("mouseenter", () => {
2956
- setTimeout( () => {
2957
- desc.style.display = "unset";
2958
- }, 100 );
2959
- });
3493
+ this.root.classList.toggle( "collapsed", this.collapsed );
3494
+ this.resizeObserver.unobserve( this.root.parentElement );
2960
3495
 
2961
- button.addEventListener("mouseleave", () => {
2962
- setTimeout( () => {
2963
- desc.style.display = "none";
2964
- }, 100 );
2965
- });
3496
+ }, 250 );
3497
+ }
2966
3498
 
2967
- entry.addEventListener("click", () => {
3499
+ /**
3500
+ * @method separator
3501
+ */
3502
+
3503
+ separator() {
2968
3504
 
2969
- const f = options.callback;
2970
- if( f ) f.call( this, key, entry );
3505
+ this.currentGroup = null;
2971
3506
 
2972
- // Manage selected
2973
- if( !options.bottom )
3507
+ this.add( "" );
3508
+ }
3509
+
3510
+ /**
3511
+ * @method group
3512
+ * @param {String} groupName
3513
+ * @param {Object} action: { icon, callback }
3514
+ */
3515
+
3516
+ group( groupName, action ) {
3517
+
3518
+ this.currentGroup = groupName;
3519
+
3520
+ this.groups[ groupName ] = action;
3521
+ }
3522
+
3523
+ /**
3524
+ * @method add
3525
+ * @param {String} path
3526
+ * @param {Object} options:
3527
+ * callback: Function to call on each item
3528
+ * icon: Entry icon
3529
+ * collapsable: Add entry as a collapsable section
3530
+ * className: Add class to the entry DOM element
3531
+ */
3532
+
3533
+ add( path, options = {} ) {
3534
+
3535
+ if( options.constructor == Function )
3536
+ {
3537
+ options = { callback: options };
3538
+ }
3539
+
3540
+ // Process path
3541
+ const tokens = path.split( "/" );
3542
+
3543
+ // Assign icons and shortcuts to last token in path
3544
+ const lastPath = tokens[tokens.length - 1];
3545
+ this.icons[ lastPath ] = options.icon;
3546
+
3547
+ let idx = 0;
3548
+
3549
+ const _insertEntry = ( token, list ) => {
3550
+
3551
+ if( token == undefined )
2974
3552
  {
2975
- this.root.querySelectorAll(".lexsidebarentry").forEach( e => e.classList.remove( 'selected' ) );
2976
- entry.classList.add( "selected" );
3553
+ return;
2977
3554
  }
2978
- });
2979
3555
 
2980
- this.items.push( { name: pKey, domEl: entry, callback: options.callback } );
3556
+ let found = null;
3557
+ list.forEach( o => {
3558
+ const keys = Object.keys( o );
3559
+ const key = keys.find( t => t == token );
3560
+ if( key ) found = o[ key ];
3561
+ } );
3562
+
3563
+ if( found )
3564
+ {
3565
+ _insertEntry( tokens[ idx++ ], found );
3566
+ }
3567
+ else
3568
+ {
3569
+ let item = {};
3570
+ item[ token ] = [];
3571
+ const nextToken = tokens[ idx++ ];
3572
+ // Check if last token -> add callback
3573
+ if( !nextToken )
3574
+ {
3575
+ item[ 'callback' ] = options.callback;
3576
+ item[ 'group' ] = this.currentGroup;
3577
+ item[ 'options' ] = options;
3578
+ }
3579
+ list.push( item );
3580
+ _insertEntry( nextToken, item[ token ] );
3581
+ }
3582
+ };
3583
+
3584
+ _insertEntry( tokens[idx++], this.items );
2981
3585
  }
2982
3586
 
2983
3587
  /**
@@ -2994,7 +3598,267 @@ class SideBar {
2994
3598
  if( !entry )
2995
3599
  return;
2996
3600
 
2997
- entry.domEl.click();
3601
+ entry.dom.click();
3602
+ }
3603
+
3604
+ update() {
3605
+
3606
+ // Reset first
3607
+
3608
+ this.content.innerHTML = "";
3609
+
3610
+ for( let item of this.items )
3611
+ {
3612
+ delete item.dom;
3613
+ }
3614
+
3615
+ for( let item of this.items )
3616
+ {
3617
+ const options = item.options ?? { };
3618
+
3619
+ // Item already created
3620
+ if( item.dom )
3621
+ {
3622
+ continue;
3623
+ }
3624
+
3625
+ let key = Object.keys( item )[ 0 ];
3626
+
3627
+ if( this.filterString.length && !key.toLowerCase().includes( this.filterString.toLowerCase() ) )
3628
+ {
3629
+ continue;
3630
+ }
3631
+
3632
+ let pKey = key.replace( /\s/g, '' ).replaceAll( '.', '' );
3633
+ let currentGroup = null;
3634
+
3635
+ let entry = document.createElement( 'div' );
3636
+ entry.className = "lexsidebarentry " + ( options.className ?? "" );
3637
+ entry.id = item.name = pKey;
3638
+
3639
+ if( item.group )
3640
+ {
3641
+ const pGroupKey = item.group.replace( /\s/g, '' ).replaceAll( '.', '' );
3642
+ currentGroup = this.content.querySelector( "#" + pGroupKey );
3643
+
3644
+ if( !currentGroup )
3645
+ {
3646
+ currentGroup = document.createElement( 'div' );
3647
+ currentGroup.id = pGroupKey;
3648
+ currentGroup.className = "lexsidebargroup";
3649
+ this.content.appendChild( currentGroup );
3650
+
3651
+ let groupEntry = document.createElement( 'div' );
3652
+ groupEntry.className = "lexsidebargrouptitle";
3653
+ currentGroup.appendChild( groupEntry );
3654
+
3655
+ let groupLabel = document.createElement( 'div' );
3656
+ groupLabel.innerHTML = item.group;
3657
+ groupEntry.appendChild( groupLabel );
3658
+
3659
+ if( this.groups[ item.group ] != null )
3660
+ {
3661
+ let groupAction = document.createElement( 'a' );
3662
+ groupAction.className = ( this.groups[ item.group ].icon ?? "" ) + " lexicon";
3663
+ groupEntry.appendChild( groupAction );
3664
+ groupAction.addEventListener( "click", (e) => {
3665
+ if( this.groups[ item.group ].callback )
3666
+ {
3667
+ this.groups[ item.group ].callback( item.group, e );
3668
+ }
3669
+ } );
3670
+ }
3671
+
3672
+ }
3673
+ else if( !currentGroup.classList.contains( "lexsidebargroup" ) )
3674
+ {
3675
+ throw( "Bad id: " + item.group );
3676
+ }
3677
+ }
3678
+
3679
+ if( pKey == "" )
3680
+ {
3681
+ let separatorDom = document.createElement( 'div' );
3682
+ separatorDom.className = "lexsidebarseparator";
3683
+ this.content.appendChild( separatorDom );
3684
+ continue;
3685
+ }
3686
+
3687
+ if( this.collapseContainer )
3688
+ {
3689
+ this.collapseContainer.appendChild( entry );
3690
+
3691
+ this.collapseQueue--;
3692
+ if( !this.collapseQueue )
3693
+ {
3694
+ delete this.collapseContainer;
3695
+ }
3696
+ }
3697
+ else if( currentGroup )
3698
+ {
3699
+ currentGroup.appendChild( entry );
3700
+ }
3701
+ else
3702
+ {
3703
+ this.content.appendChild( entry );
3704
+ }
3705
+
3706
+ let itemDom = document.createElement( 'div' );
3707
+ entry.appendChild( itemDom );
3708
+ item.dom = entry;
3709
+
3710
+ if( options.type == "checkbox" )
3711
+ {
3712
+ item.value = options.value ?? false;
3713
+ const panel = new Panel();
3714
+ item.checkbox = panel.addCheckbox(null, item.value, (value, event) => {
3715
+ event.preventDefault();
3716
+ event.stopPropagation();
3717
+ const f = options.callback;
3718
+ item.value = value;
3719
+ if( f ) f.call( this, key, value, event );
3720
+ }, { label: key, signal: ( "@checkbox_" + key ) });
3721
+ itemDom.appendChild( panel.root.childNodes[ 0 ] );
3722
+ }
3723
+ else
3724
+ {
3725
+ if( options.icon )
3726
+ {
3727
+ let itemIcon = document.createElement( 'i' );
3728
+ itemIcon.className = options.icon;
3729
+ itemDom.appendChild( itemIcon );
3730
+ }
3731
+
3732
+ let itemName = document.createElement( 'a' );
3733
+ itemName.innerHTML = key;
3734
+ itemDom.appendChild( itemName );
3735
+ }
3736
+
3737
+ entry.addEventListener("click", ( e ) => {
3738
+ if( e.target && e.target.classList.contains( "lexcheckbox" ) )
3739
+ {
3740
+ return;
3741
+ }
3742
+
3743
+ if( options.collapsable )
3744
+ {
3745
+ itemDom.querySelector( ".collapser" ).click();
3746
+ }
3747
+ else
3748
+ {
3749
+ const f = options.callback;
3750
+ if( f ) f.call( this, key, item.value, e );
3751
+
3752
+ if( item.checkbox )
3753
+ {
3754
+ item.value = !item.value;
3755
+ item.checkbox.set( item.value, true );
3756
+ }
3757
+ }
3758
+
3759
+ // Manage selected
3760
+ this.root.querySelectorAll(".lexsidebarentry").forEach( e => e.classList.remove( 'selected' ) );
3761
+ entry.classList.add( "selected" );
3762
+ });
3763
+
3764
+ if( options.action )
3765
+ {
3766
+ const actionIcon = LX.makeIcon( options.action.icon ?? "MoreHorizontal", options.action.name );
3767
+ itemDom.appendChild( actionIcon );
3768
+
3769
+ actionIcon.addEventListener( "click", (e) => {
3770
+ e.preventDefault();
3771
+ e.stopImmediatePropagation();
3772
+ const f = options.action.callback;
3773
+ if( f ) f.call( this, key, e );
3774
+ } );
3775
+ }
3776
+ else if( options.collapsable )
3777
+ {
3778
+ const collapsableContent = document.createElement( 'div' );
3779
+ Object.assign( collapsableContent.style, { width: "100%", display: "none" } );
3780
+ LX.makeCollapsible( itemDom, collapsableContent, currentGroup ?? this.content );
3781
+ this.collapseQueue = options.collapsable;
3782
+ this.collapseContainer = collapsableContent;
3783
+ }
3784
+
3785
+ let desc = document.createElement( 'span' );
3786
+ desc.className = 'lexsidebarentrydesc';
3787
+ desc.innerHTML = key;
3788
+ entry.appendChild( desc );
3789
+
3790
+ itemDom.addEventListener("mouseenter", () => {
3791
+ setTimeout( () => {
3792
+ desc.style.display = "unset";
3793
+ }, 150 );
3794
+ });
3795
+
3796
+ itemDom.addEventListener("mouseleave", () => {
3797
+ setTimeout( () => {
3798
+ desc.style.display = "none";
3799
+ }, 150 );
3800
+ });
3801
+
3802
+ // Subentries
3803
+ if( !item[ key ].length )
3804
+ {
3805
+ continue;
3806
+ }
3807
+
3808
+ let subentryContainer = document.createElement( 'div' );
3809
+ subentryContainer.className = "lexsidebarsubentrycontainer";
3810
+
3811
+ if( currentGroup )
3812
+ {
3813
+ currentGroup.appendChild( subentryContainer );
3814
+ }
3815
+ else
3816
+ {
3817
+ this.content.appendChild( subentryContainer );
3818
+ }
3819
+
3820
+ for( let i = 0; i < item[ key ].length; ++i )
3821
+ {
3822
+ const subitem = item[ key ][ i ];
3823
+ const suboptions = subitem.options ?? {};
3824
+ const subkey = Object.keys( subitem )[ 0 ];
3825
+
3826
+ if( this.filterString.length && !subkey.toLowerCase().includes( this.filterString.toLowerCase() ) )
3827
+ {
3828
+ continue;
3829
+ }
3830
+
3831
+ let subentry = document.createElement( 'div' );
3832
+ subentry.innerHTML = `<span>${ subkey }</span>`;
3833
+
3834
+ if( suboptions.action )
3835
+ {
3836
+ const actionIcon = LX.makeIcon( suboptions.action.icon ?? "MoreHorizontal", suboptions.action.name );
3837
+ subentry.appendChild( actionIcon );
3838
+
3839
+ actionIcon.addEventListener( "click", (e) => {
3840
+ e.preventDefault();
3841
+ e.stopImmediatePropagation();
3842
+ const f = suboptions.action.callback;
3843
+ if( f ) f.call( this, subkey, e );
3844
+ } );
3845
+ }
3846
+
3847
+ subentry.className = "lexsidebarentry";
3848
+ subentry.id = subkey;
3849
+ subentryContainer.appendChild( subentry );
3850
+
3851
+ subentry.addEventListener("click", (e) => {
3852
+
3853
+ const f = suboptions.callback;
3854
+ if( f ) f.call( this, subkey, subentry, e );
3855
+
3856
+ // Manage selected
3857
+ this.root.querySelectorAll(".lexsidebarentry").forEach( e => e.classList.remove( 'selected' ) );
3858
+ entry.classList.add( "selected" );
3859
+ });
3860
+ }
3861
+ }
2998
3862
  }
2999
3863
  };
3000
3864
 
@@ -3013,30 +3877,32 @@ class Widget {
3013
3877
  static DROPDOWN = 4;
3014
3878
  static CHECKBOX = 5;
3015
3879
  static TOGGLE = 6;
3016
- static COLOR = 7;
3017
- static NUMBER = 8;
3018
- static TITLE = 9;
3019
- static VECTOR = 10;
3020
- static TREE = 11;
3021
- static PROGRESS = 12;
3022
- static FILE = 13;
3023
- static LAYERS = 14;
3024
- static ARRAY = 15;
3025
- static LIST = 16;
3026
- static TAGS = 17;
3027
- static CURVE = 18;
3028
- static CARD = 19;
3029
- static IMAGE = 20;
3030
- static CONTENT = 21;
3031
- static CUSTOM = 22;
3032
- static SEPARATOR = 23;
3033
- static KNOB = 24;
3034
- static SIZE = 25;
3035
- static PAD = 26;
3036
- static FORM = 27;
3037
- static DIAL = 28;
3038
- static COUNTER = 29;
3039
- static TABLE = 30;
3880
+ static RADIO = 7;
3881
+ static COLOR = 8;
3882
+ static RANGE = 9;
3883
+ static NUMBER = 10;
3884
+ static TITLE = 11;
3885
+ static VECTOR = 12;
3886
+ static TREE = 13;
3887
+ static PROGRESS = 14;
3888
+ static FILE = 15;
3889
+ static LAYERS = 16;
3890
+ static ARRAY = 17;
3891
+ static LIST = 18;
3892
+ static TAGS = 19;
3893
+ static CURVE = 20;
3894
+ static CARD = 21;
3895
+ static IMAGE = 22;
3896
+ static CONTENT = 23;
3897
+ static CUSTOM = 24;
3898
+ static SEPARATOR = 25;
3899
+ static KNOB = 26;
3900
+ static SIZE = 27;
3901
+ static PAD = 28;
3902
+ static FORM = 29;
3903
+ static DIAL = 30;
3904
+ static COUNTER = 31;
3905
+ static TABLE = 32;
3040
3906
 
3041
3907
  static NO_CONTEXT_TYPES = [
3042
3908
  Widget.BUTTON,
@@ -3064,7 +3930,9 @@ class Widget {
3064
3930
  set( value, skipCallback = false, signalName = "" ) {
3065
3931
 
3066
3932
  if( this.onSetValue )
3933
+ {
3067
3934
  return this.onSetValue( value, skipCallback );
3935
+ }
3068
3936
 
3069
3937
  console.warn("Can't set value of " + this.typeName());
3070
3938
  }
@@ -3103,14 +3971,17 @@ class Widget {
3103
3971
 
3104
3972
  typeName() {
3105
3973
 
3106
- switch( this.type ) {
3974
+ switch( this.type )
3975
+ {
3107
3976
  case Widget.TEXT: return "Text";
3108
3977
  case Widget.TEXTAREA: return "TextArea";
3109
3978
  case Widget.BUTTON: return "Button";
3110
3979
  case Widget.DROPDOWN: return "Dropdown";
3111
3980
  case Widget.CHECKBOX: return "Checkbox";
3112
3981
  case Widget.TOGGLE: return "Toggle";
3982
+ case Widget.RADIO: return "Radio";
3113
3983
  case Widget.COLOR: return "Color";
3984
+ case Widget.RANGE: return "Range";
3114
3985
  case Widget.NUMBER: return "Number";
3115
3986
  case Widget.VECTOR: return "Vector";
3116
3987
  case Widget.TREE: return "Tree";
@@ -3188,11 +4059,12 @@ function ADD_CUSTOM_WIDGET( custom_widget_name, options = {} )
3188
4059
  buttonName += "<a class='fa-solid " + (instance ? "fa-bars-staggered" : " ") + " menu' style='float:right; width:5%;'></a>";
3189
4060
 
3190
4061
  let buttonEl = this.addButton(null, buttonName, (value, event) => {
3191
-
3192
- if( instance ) {
4062
+ if( instance )
4063
+ {
3193
4064
  element.querySelector(".lexcustomitems").toggleAttribute('hidden');
3194
4065
  }
3195
- else {
4066
+ else
4067
+ {
3196
4068
  addContextMenu(null, event, c => {
3197
4069
  c.add("New " + custom_widget_name, () => {
3198
4070
  instance = {};
@@ -3379,16 +4251,20 @@ class NodeTree {
3379
4251
 
3380
4252
  // Add or remove
3381
4253
  const idx = this.selected.indexOf( node );
3382
- if( idx > -1 ) {
4254
+ if( idx > -1 )
4255
+ {
3383
4256
  item.classList.remove( 'selected' );
3384
4257
  this.selected.splice( idx, 1 );
3385
- }else {
4258
+ }
4259
+ else
4260
+ {
3386
4261
  item.classList.add( 'selected' );
3387
4262
  this.selected.push( node );
3388
4263
  }
3389
4264
 
3390
4265
  // Only Show children...
3391
- if( isParent && node.id.length > 1 /* Strange case... */) {
4266
+ if( isParent && node.id.length > 1 /* Strange case... */)
4267
+ {
3392
4268
  node.closed = false;
3393
4269
  if( that.onevent )
3394
4270
  {
@@ -3426,7 +4302,7 @@ class NodeTree {
3426
4302
 
3427
4303
  e.preventDefault();
3428
4304
 
3429
- if( that.onevent )
4305
+ if( !that.onevent )
3430
4306
  {
3431
4307
  return;
3432
4308
  }
@@ -3482,7 +4358,8 @@ class NodeTree {
3482
4358
  return;
3483
4359
  }
3484
4360
 
3485
- if( that.onevent ) {
4361
+ if( that.onevent )
4362
+ {
3486
4363
  const event = new TreeEvent( TreeEvent.NODE_DELETED, node, e );
3487
4364
  that.onevent( event );
3488
4365
  }
@@ -3509,7 +4386,8 @@ class NodeTree {
3509
4386
  if( e.key == "Delete" )
3510
4387
  {
3511
4388
  // Send event now so we have the info in selected array..
3512
- if( that.onevent ) {
4389
+ if( that.onevent )
4390
+ {
3513
4391
  const event = new TreeEvent( TreeEvent.NODE_DELETED, this.selected.length > 1 ? this.selected : node, e );
3514
4392
  event.multiple = this.selected.length > 1;
3515
4393
  that.onevent( event );
@@ -3540,22 +4418,24 @@ class NodeTree {
3540
4418
 
3541
4419
  // Node rename
3542
4420
 
3543
- let name_input = document.createElement('input');
3544
- name_input.toggleAttribute('hidden', !node.rename);
3545
- name_input.value = node.id;
3546
- item.appendChild(name_input);
4421
+ const nameInput = document.createElement( "input" );
4422
+ nameInput.toggleAttribute( "hidden", !node.rename );
4423
+ nameInput.value = node.id;
4424
+ item.appendChild(nameInput);
3547
4425
 
3548
- if(node.rename) {
4426
+ if( node.rename )
4427
+ {
3549
4428
  item.classList.add('selected');
3550
- name_input.focus();
4429
+ nameInput.focus();
3551
4430
  }
3552
4431
 
3553
- name_input.addEventListener("keyup", function(e){
3554
- if(e.key == 'Enter') {
3555
-
4432
+ nameInput.addEventListener("keyup", function(e){
4433
+ if( e.key == "Enter" )
4434
+ {
3556
4435
  this.value = this.value.replace(/\s/g, '_');
3557
4436
 
3558
- if(that.onevent) {
4437
+ if( that.onevent )
4438
+ {
3559
4439
  const event = new TreeEvent(TreeEvent.NODE_RENAMED, node, this.value);
3560
4440
  that.onevent( event );
3561
4441
  }
@@ -3565,18 +4445,20 @@ class NodeTree {
3565
4445
  that.frefresh( node.id );
3566
4446
  list.querySelector("#" + node.id).classList.add('selected');
3567
4447
  }
3568
- if(e.key == 'Escape') {
4448
+ else if(e.key == "Escape")
4449
+ {
3569
4450
  delete node.rename;
3570
4451
  that.frefresh( node.id );
3571
4452
  }
3572
4453
  });
3573
4454
 
3574
- name_input.addEventListener("blur", function(e){
4455
+ nameInput.addEventListener("blur", function(e){
3575
4456
  delete node.rename;
3576
4457
  that.refresh();
3577
4458
  });
3578
4459
 
3579
- if(this.options.draggable ?? true) {
4460
+ if( this.options.draggable ?? true )
4461
+ {
3580
4462
  // Drag nodes
3581
4463
  if(parent) // Root doesn't move!
3582
4464
  {
@@ -3602,29 +4484,32 @@ class NodeTree {
3602
4484
  return;
3603
4485
  let target = node;
3604
4486
  // Can't drop to same node
3605
- if(dragged.id == target.id) {
4487
+ if( dragged.id == target.id )
4488
+ {
3606
4489
  console.warn("Cannot parent node to itself!");
3607
4490
  return;
3608
4491
  }
3609
4492
 
3610
4493
  // Can't drop to child node
3611
- const isChild = function(new_parent, node) {
4494
+ const isChild = function( newParent, node ) {
3612
4495
  var result = false;
3613
- for( var c of node.children ) {
3614
- if( c.id == new_parent.id )
3615
- return true;
3616
- result |= isChild(new_parent, c);
4496
+ for( var c of node.children )
4497
+ {
4498
+ if( c.id == newParent.id ) return true;
4499
+ result |= isChild( newParent, c );
3617
4500
  }
3618
4501
  return result;
3619
4502
  };
3620
4503
 
3621
- if(isChild(target, dragged)) {
4504
+ if( isChild( target, dragged ))
4505
+ {
3622
4506
  console.warn("Cannot parent node to a current child!");
3623
4507
  return;
3624
4508
  }
3625
4509
 
3626
4510
  // Trigger node dragger event
3627
- if(that.onevent) {
4511
+ if( that.onevent )
4512
+ {
3628
4513
  const event = new TreeEvent(TreeEvent.NODE_DRAGGED, dragged, target);
3629
4514
  that.onevent( event );
3630
4515
  }
@@ -3640,7 +4525,8 @@ class NodeTree {
3640
4525
  let handled = false;
3641
4526
 
3642
4527
  // Show/hide children
3643
- if(isParent) {
4528
+ if( isParent )
4529
+ {
3644
4530
  item.querySelector('a.hierarchy').addEventListener("click", function(e) {
3645
4531
 
3646
4532
  handled = true;
@@ -3648,7 +4534,8 @@ class NodeTree {
3648
4534
  e.stopPropagation();
3649
4535
 
3650
4536
  node.closed = !node.closed;
3651
- if(that.onevent) {
4537
+ if( that.onevent )
4538
+ {
3652
4539
  const event = new TreeEvent(TreeEvent.NODE_CARETCHANGED, node, node.closed);
3653
4540
  that.onevent( event );
3654
4541
  }
@@ -3668,7 +4555,8 @@ class NodeTree {
3668
4555
  node.visible = node.visible === undefined ? false : !node.visible;
3669
4556
  this.className = "itemicon fa-solid fa-eye" + (!node.visible ? "-slash" : "");
3670
4557
  // Trigger visibility event
3671
- if(that.onevent) {
4558
+ if( that.onevent )
4559
+ {
3672
4560
  const event = new TreeEvent(TreeEvent.NODE_VISIBILITY, node, node.visible);
3673
4561
  that.onevent( event );
3674
4562
  }
@@ -3677,9 +4565,10 @@ class NodeTree {
3677
4565
  item.appendChild(visibility);
3678
4566
  }
3679
4567
 
3680
- if(node.actions)
4568
+ if( node.actions )
3681
4569
  {
3682
- for(var i = 0; i < node.actions.length; ++i) {
4570
+ for( var i = 0; i < node.actions.length; ++i )
4571
+ {
3683
4572
  let a = node.actions[i];
3684
4573
  var actionEl = document.createElement('a');
3685
4574
  actionEl.className = "itemicon " + a.icon;
@@ -3692,20 +4581,25 @@ class NodeTree {
3692
4581
  }
3693
4582
  }
3694
4583
 
3695
- if(selectedId != undefined && node.id == selectedId) {
3696
- this.selected = [node];
4584
+ if( selectedId != undefined && node.id == selectedId )
4585
+ {
4586
+ this.selected = [ node ];
3697
4587
  item.click();
3698
4588
  }
3699
4589
 
3700
- if(node.closed )
4590
+ if( node.closed )
4591
+ {
3701
4592
  return;
4593
+ }
3702
4594
 
3703
4595
  for( var i = 0; i < node.children.length; ++i )
3704
4596
  {
3705
- let child = node.children[i];
4597
+ let child = node.children[ i ];
3706
4598
 
3707
- if( this.options.onlyFolders && child.type != 'folder')
4599
+ if( this.options.onlyFolders && child.type != 'folder' )
4600
+ {
3708
4601
  continue;
4602
+ }
3709
4603
 
3710
4604
  this._create_item( node, child, level + 1 );
3711
4605
  }
@@ -3744,12 +4638,12 @@ class Panel {
3744
4638
  * style: CSS Style object to be applied to the panel
3745
4639
  */
3746
4640
 
3747
- constructor( options = {} ) {
4641
+ constructor( options = {} ) {
3748
4642
  var root = document.createElement('div');
3749
4643
  root.className = "lexpanel";
3750
- if(options.id)
4644
+ if( options.id )
3751
4645
  root.id = options.id;
3752
- if(options.className)
4646
+ if( options.className )
3753
4647
  root.className += " " + options.className;
3754
4648
 
3755
4649
  root.style.width = options.width || "calc( 100% - 6px )";
@@ -3826,23 +4720,31 @@ class Panel {
3826
4720
  this.branches = [];
3827
4721
  this.current_branch = null;
3828
4722
 
3829
- for(let w in this.widgets) {
3830
- if(this.widgets[w].options && this.widgets[w].options.signal) {
4723
+ for( let w in this.widgets )
4724
+ {
4725
+ if( this.widgets[w].options && this.widgets[w].options.signal )
4726
+ {
3831
4727
  const signal = this.widgets[w].options.signal;
3832
- for(let i = 0; i < LX.signals[signal].length; i++) {
3833
- if(LX.signals[signal][i] == this.widgets[w]) {
4728
+ for( let i = 0; i < LX.signals[signal].length; i++ )
4729
+ {
4730
+ if( LX.signals[signal][i] == this.widgets[w] )
4731
+ {
3834
4732
  LX.signals[signal] = [...LX.signals[signal].slice(0, i), ...LX.signals[signal].slice(i+1)];
3835
4733
  }
3836
4734
  }
3837
4735
  }
3838
4736
  }
3839
4737
 
3840
- if(this.signals) {
3841
- for(let w = 0; w < this.signals.length; w++) {
4738
+ if( this.signals )
4739
+ {
4740
+ for( let w = 0; w < this.signals.length; w++ )
4741
+ {
3842
4742
  let widget = Object.values(this.signals[w])[0];
3843
4743
  let signal = widget.options.signal;
3844
- for(let i = 0; i < LX.signals[signal].length; i++) {
3845
- if(LX.signals[signal][i] == widget) {
4744
+ for( let i = 0; i < LX.signals[signal].length; i++ )
4745
+ {
4746
+ if( LX.signals[signal][i] == widget )
4747
+ {
3846
4748
  LX.signals[signal] = [...LX.signals[signal].slice(0, i), ...LX.signals[signal].slice(i+1)];
3847
4749
  }
3848
4750
  }
@@ -3880,10 +4782,12 @@ class Panel {
3880
4782
 
3881
4783
  this._inline_widgets_left = -1;
3882
4784
 
3883
- if(!this._inlineContainer) {
4785
+ if( !this._inlineContainer )
4786
+ {
3884
4787
  this._inlineContainer = document.createElement('div');
3885
4788
  this._inlineContainer.className = "lexinlinewidgets";
3886
- if(justifyContent)
4789
+
4790
+ if( justifyContent )
3887
4791
  {
3888
4792
  this._inlineContainer.style.justifyContent = justifyContent;
3889
4793
  }
@@ -3897,7 +4801,7 @@ class Panel {
3897
4801
  if(is_pair)
3898
4802
  {
3899
4803
  // eg. an array, inline items appended later to
3900
- if(this._inline_queued_container)
4804
+ if( this._inline_queued_container)
3901
4805
  this._inlineContainer.appendChild( item[0] );
3902
4806
  // eg. a dropdown, item is appended to parent, not to inline cont.
3903
4807
  else
@@ -3909,7 +4813,7 @@ class Panel {
3909
4813
 
3910
4814
  if(!this._inline_queued_container)
3911
4815
  {
3912
- if(this.current_branch)
4816
+ if( this.current_branch)
3913
4817
  this.current_branch.content.appendChild( this._inlineContainer );
3914
4818
  else
3915
4819
  this.root.appendChild( this._inlineContainer );
@@ -3948,7 +4852,7 @@ class Panel {
3948
4852
  this.current_branch = branch;
3949
4853
 
3950
4854
  // Append to panel
3951
- if(this.branches.length == 0)
4855
+ if( this.branches.length == 0)
3952
4856
  branch.root.classList.add('first');
3953
4857
 
3954
4858
  // This is the last!
@@ -3959,7 +4863,8 @@ class Panel {
3959
4863
  this.root.appendChild( branch.root );
3960
4864
 
3961
4865
  // Add widget filter
3962
- if(options.filter) {
4866
+ if( options.filter )
4867
+ {
3963
4868
  this._addFilter( options.filter, {callback: this._searchWidgets.bind(this, branch.name)} );
3964
4869
  }
3965
4870
 
@@ -4076,14 +4981,18 @@ class Panel {
4076
4981
  element.jsInstance = widget;
4077
4982
 
4078
4983
  const insert_widget = el => {
4079
- if(options.container)
4080
- options.container.appendChild(el);
4081
- else if(!this.queuedContainer) {
4082
-
4083
- if(this.current_branch)
4984
+ if( options.container )
4985
+ {
4986
+ options.container.appendChild( el );
4987
+ }
4988
+ else if( !this.queuedContainer )
4989
+ {
4990
+ if( this.current_branch )
4084
4991
  {
4085
- if(!options.skipWidget)
4992
+ if( !options.skipWidget )
4993
+ {
4086
4994
  this.current_branch.widgets.push( widget );
4995
+ }
4087
4996
  this.current_branch.content.appendChild( el );
4088
4997
  }
4089
4998
  else
@@ -4093,40 +5002,47 @@ class Panel {
4093
5002
  }
4094
5003
  }
4095
5004
  // Append content to queued tab container
4096
- else {
5005
+ else
5006
+ {
4097
5007
  this.queuedContainer.appendChild( el );
4098
5008
  }
4099
5009
  };
4100
5010
 
4101
5011
  const store_widget = el => {
4102
5012
 
4103
- if(!this.queuedContainer) {
5013
+ if( !this.queuedContainer )
5014
+ {
4104
5015
  this._inlineWidgets.push( el );
4105
5016
  }
4106
5017
  // Append content to queued tab container
4107
- else {
5018
+ else
5019
+ {
4108
5020
  this._inlineWidgets.push( [el, this.queuedContainer] );
4109
5021
  }
4110
5022
  };
4111
5023
 
4112
5024
  // Process inline widgets
4113
- if(this._inline_widgets_left > 0 && !options.skipInlineCount)
5025
+ if( this._inline_widgets_left > 0 && !options.skipInlineCount )
4114
5026
  {
4115
- if(!this._inlineWidgets) {
5027
+ if( !this._inlineWidgets )
5028
+ {
4116
5029
  this._inlineWidgets = [];
4117
5030
  }
4118
5031
 
4119
5032
  // Store widget and its container
4120
- store_widget(element);
5033
+ store_widget( element );
4121
5034
 
4122
5035
  this._inline_widgets_left--;
4123
5036
 
4124
5037
  // Last widget
4125
- if(!this._inline_widgets_left) {
5038
+ if( !this._inline_widgets_left )
5039
+ {
4126
5040
  this.endLine();
4127
5041
  }
4128
- }else {
4129
- insert_widget(element);
5042
+ }
5043
+ else
5044
+ {
5045
+ insert_widget( element );
4130
5046
  }
4131
5047
 
4132
5048
  return widget;
@@ -4163,15 +5079,20 @@ class Panel {
4163
5079
 
4164
5080
  _searchWidgets(branchName, value) {
4165
5081
 
4166
- for( let b of this.branches ) {
4167
-
4168
- if(b.name !== branchName)
5082
+ for( let b of this.branches )
5083
+ {
5084
+ if( b.name !== branchName )
5085
+ {
4169
5086
  continue;
5087
+ }
4170
5088
 
4171
5089
  // remove all widgets
4172
- for( let w of b.widgets ) {
4173
- if(w.domEl.classList.contains('lexfilter'))
5090
+ for( let w of b.widgets )
5091
+ {
5092
+ if( w.domEl.classList.contains('lexfilter') )
5093
+ {
4174
5094
  continue;
5095
+ }
4175
5096
  w.domEl.remove();
4176
5097
  }
4177
5098
 
@@ -4181,9 +5102,9 @@ class Panel {
4181
5102
  const emptyFilter = !value.length;
4182
5103
 
4183
5104
  // add widgets
4184
- for( let w of b.widgets ) {
4185
-
4186
- if(!emptyFilter)
5105
+ for( let w of b.widgets )
5106
+ {
5107
+ if( !emptyFilter )
4187
5108
  {
4188
5109
  if(!w.name) continue;
4189
5110
  const filterWord = value.toLowerCase();
@@ -4277,7 +5198,7 @@ class Panel {
4277
5198
 
4278
5199
  clearQueue() {
4279
5200
 
4280
- if(this._queue && this._queue.length)
5201
+ if( this._queue && this._queue.length)
4281
5202
  {
4282
5203
  this.queuedContainer = this._queue.pop();
4283
5204
  return;
@@ -4437,7 +5358,7 @@ class Panel {
4437
5358
 
4438
5359
  var resolve = ( function( val, event ) {
4439
5360
 
4440
- if( !widget.valid() )
5361
+ if( !widget.valid() || ( this._lastValueTriggered == val ) )
4441
5362
  {
4442
5363
  return;
4443
5364
  }
@@ -4450,6 +5371,8 @@ class Panel {
4450
5371
  this._trigger( new IEvent( name, val, event ), callback );
4451
5372
  }
4452
5373
 
5374
+ this._lastValueTriggered = val;
5375
+
4453
5376
  }).bind( this );
4454
5377
 
4455
5378
  const trigger = options.trigger ?? 'default';
@@ -4503,7 +5426,8 @@ class Panel {
4503
5426
  element.appendChild( container );
4504
5427
 
4505
5428
  // Remove branch padding and margins
4506
- if( !widget.name ) {
5429
+ if( !widget.name )
5430
+ {
4507
5431
  element.className += " noname";
4508
5432
  container.style.width = "100%";
4509
5433
  }
@@ -4603,7 +5527,8 @@ class Panel {
4603
5527
  element.appendChild(container);
4604
5528
 
4605
5529
  // Remove branch padding and margins
4606
- if(!widget.name) {
5530
+ if( !widget.name )
5531
+ {
4607
5532
  element.className += " noname";
4608
5533
  container.style.width = "100%";
4609
5534
  }
@@ -4708,11 +5633,12 @@ class Panel {
4708
5633
  /**
4709
5634
  * @method addComboButtons
4710
5635
  * @param {String} name Widget name
4711
- * @param {Array} values Each of the {value, callback} items
5636
+ * @param {Array} values Each of the {value, callback, selected, disabled} items
4712
5637
  * @param {*} options:
4713
5638
  * float: Justify content (left, center, right) [center]
4714
- * selected: Selected item by default by value
5639
+ * @legacy selected: Selected item by default by value
4715
5640
  * noSelection: Buttons can be clicked, but they are not selectable
5641
+ * toggle: Buttons can be toggled insted of selecting only one
4716
5642
  */
4717
5643
 
4718
5644
  addComboButtons( name, values, options = {} ) {
@@ -4734,7 +5660,8 @@ class Panel {
4734
5660
  let buttonsBox = document.createElement('div');
4735
5661
  buttonsBox.className = "lexcombobuttonsbox ";
4736
5662
 
4737
- let shouldSelect = !( options.noSelection ?? false );
5663
+ const shouldSelect = !( options.noSelection ?? false );
5664
+ const shouldToggle = shouldSelect && ( options.toggle ?? false );
4738
5665
 
4739
5666
  for( let b of values )
4740
5667
  {
@@ -4753,14 +5680,14 @@ class Panel {
4753
5680
  buttonEl.classList.add( options.buttonClass );
4754
5681
  }
4755
5682
 
4756
- if( shouldSelect && options.selected == b.value )
5683
+ if( shouldSelect && ( b.selected || options.selected == b.value ) )
4757
5684
  {
4758
5685
  buttonEl.classList.add("selected");
4759
5686
  }
4760
5687
 
4761
5688
  buttonEl.innerHTML = ( b.icon ? "<a class='" + b.icon +"'></a>" : "" ) + "<span>" + ( b.icon ? "" : b.value ) + "</span>";
4762
5689
 
4763
- if( options.disabled )
5690
+ if( b.disabled )
4764
5691
  {
4765
5692
  buttonEl.setAttribute( "disabled", true );
4766
5693
  }
@@ -4768,8 +5695,15 @@ class Panel {
4768
5695
  buttonEl.addEventListener("click", function( e ) {
4769
5696
  if( shouldSelect )
4770
5697
  {
4771
- container.querySelectorAll('button').forEach( s => s.classList.remove('selected'));
4772
- this.classList.add('selected');
5698
+ if( shouldToggle )
5699
+ {
5700
+ this.classList.toggle('selected');
5701
+ }
5702
+ else
5703
+ {
5704
+ container.querySelectorAll('button').forEach( s => s.classList.remove('selected'));
5705
+ this.classList.add('selected');
5706
+ }
4773
5707
  }
4774
5708
 
4775
5709
  that._trigger( new IEvent( name, b.value, e ), b.callback );
@@ -4779,7 +5713,7 @@ class Panel {
4779
5713
  }
4780
5714
 
4781
5715
  // Remove branch padding and margins
4782
- if( !widget.name)
5716
+ if( !widget.name )
4783
5717
  {
4784
5718
  element.className += " noname";
4785
5719
  container.style.width = "100%";
@@ -4953,7 +5887,8 @@ class Panel {
4953
5887
 
4954
5888
  element.appendChild( container );
4955
5889
 
4956
- if( !widget.name || options.hideName ) {
5890
+ if( !widget.name || options.hideName )
5891
+ {
4957
5892
  element.className += " noname";
4958
5893
  container.style.width = "100%";
4959
5894
  }
@@ -5089,8 +6024,6 @@ class Panel {
5089
6024
 
5090
6025
  const _placeOptions = ( parent ) => {
5091
6026
 
5092
- console.log("Replacing container");
5093
-
5094
6027
  const overflowContainer = parent.getParentArea();
5095
6028
  const rect = selectedOption.getBoundingClientRect();
5096
6029
  const nestedDialog = parent.parentElement.closest( "dialog" );
@@ -5374,15 +6307,16 @@ class Panel {
5374
6307
 
5375
6308
  addCurve( name, values, callback, options = {} ) {
5376
6309
 
5377
- if(!name) {
5378
- throw("Set Widget Name!");
6310
+ if( !name )
6311
+ {
6312
+ throw( "Set Widget Name!" );
5379
6313
  }
5380
6314
 
5381
6315
  let that = this;
5382
- let widget = this.create_widget(name, Widget.CURVE, options);
6316
+ let widget = this.create_widget( name, Widget.CURVE, options );
5383
6317
 
5384
6318
  widget.onGetValue = () => {
5385
- return JSON.parse(JSON.stringify(curveInstance.element.value));
6319
+ return JSON.parse(JSON.stringify( curveInstance.element.value ));
5386
6320
  };
5387
6321
 
5388
6322
  widget.onSetValue = ( newValue, skipCallback ) => {
@@ -5527,7 +6461,8 @@ class Panel {
5527
6461
 
5528
6462
  addLayers( name, value, callback, options = {} ) {
5529
6463
 
5530
- if(!name) {
6464
+ if( !name )
6465
+ {
5531
6466
  throw("Set Widget Name!");
5532
6467
  }
5533
6468
 
@@ -5569,7 +6504,8 @@ class Panel {
5569
6504
  let binary = value.toString( 2 );
5570
6505
  let nbits = binary.length;
5571
6506
  // fill zeros
5572
- for(var i = 0; i < (16 - nbits); ++i) {
6507
+ for( var i = 0; i < (16 - nbits); ++i )
6508
+ {
5573
6509
  binary = '0' + binary;
5574
6510
  }
5575
6511
 
@@ -5622,8 +6558,9 @@ class Panel {
5622
6558
 
5623
6559
  addArray( name, values = [], callback, options = {} ) {
5624
6560
 
5625
- if(!name) {
5626
- throw("Set Widget Name!");
6561
+ if( !name )
6562
+ {
6563
+ throw( "Set Widget Name!" );
5627
6564
  }
5628
6565
 
5629
6566
  let widget = this.create_widget(name, Widget.ARRAY, options);
@@ -5804,7 +6741,8 @@ class Panel {
5804
6741
  widget.updateValues( values );
5805
6742
 
5806
6743
  // Remove branch padding and margins
5807
- if( !widget.name ) {
6744
+ if( !widget.name )
6745
+ {
5808
6746
  element.className += " noname";
5809
6747
  listContainer.style.width = "100%";
5810
6748
  }
@@ -5891,7 +6829,7 @@ class Panel {
5891
6829
  tagsContainer.appendChild( tagInput );
5892
6830
 
5893
6831
  tagInput.onkeydown = function( e ) {
5894
- const val = this.value.replace(/\s/g, '');
6832
+ const val = this.value.replace( /\s/g, '' );
5895
6833
  if( e.key == ' ' || e.key == 'Enter' )
5896
6834
  {
5897
6835
  e.preventDefault();
@@ -5929,15 +6867,16 @@ class Panel {
5929
6867
  * @param {Function} callback Callback function on change
5930
6868
  * @param {*} options:
5931
6869
  * disabled: Make the widget disabled [false]
6870
+ * label: Checkbox label
5932
6871
  * suboptions: Callback to add widgets in case of TRUE value
5933
- * className: Customize colors
6872
+ * className: Extra classes to customize style
5934
6873
  */
5935
6874
 
5936
6875
  addCheckbox( name, value, callback, options = {} ) {
5937
6876
 
5938
- if( !name )
6877
+ if( !name && !options.label )
5939
6878
  {
5940
- throw( "Set Widget Name!" );
6879
+ throw( "Set Widget Name or at least a label!" );
5941
6880
  }
5942
6881
 
5943
6882
  let widget = this.create_widget( name, Widget.CHECKBOX, options );
@@ -5957,10 +6896,13 @@ class Panel {
5957
6896
  let element = widget.domEl;
5958
6897
 
5959
6898
  // Add reset functionality
5960
- Panel._add_reset_property( element.domName, function() {
5961
- checkbox.checked = !checkbox.checked;
5962
- Panel._dispatch_event( checkbox, "change" );
5963
- });
6899
+ if( name )
6900
+ {
6901
+ Panel._add_reset_property( element.domName, function() {
6902
+ checkbox.checked = !checkbox.checked;
6903
+ Panel._dispatch_event( checkbox, "change" );
6904
+ });
6905
+ }
5964
6906
 
5965
6907
  // Add widget value
5966
6908
 
@@ -5976,7 +6918,7 @@ class Panel {
5976
6918
 
5977
6919
  let valueName = document.createElement( 'span' );
5978
6920
  valueName.className = "checkboxtext";
5979
- valueName.innerHTML = "On";
6921
+ valueName.innerHTML = options.label ?? "On";
5980
6922
 
5981
6923
  container.appendChild( checkbox );
5982
6924
  container.appendChild( valueName );
@@ -6114,6 +7056,96 @@ class Panel {
6114
7056
  return widget;
6115
7057
  }
6116
7058
 
7059
+ /**
7060
+ * @method addRadioGroup
7061
+ * @param {String} label Radio label
7062
+ * @param {Array} values Radio options
7063
+ * @param {Function} callback Callback function on change
7064
+ * @param {*} options:
7065
+ * disabled: Make the widget disabled [false]
7066
+ * className: Customize colors
7067
+ */
7068
+
7069
+ addRadioGroup( label, values, callback, options = {} ) {
7070
+
7071
+ let widget = this.create_widget( null, Widget.RADIO, options );
7072
+
7073
+ widget.onGetValue = () => {
7074
+ const items = container.querySelectorAll( 'button' );
7075
+ for( let i = 0; i < items.length; ++i )
7076
+ {
7077
+ const optionItem = items[ i ];
7078
+ if( optionItem.checked )
7079
+ {
7080
+ return [ i, values[ i ] ];
7081
+ }
7082
+ }
7083
+ };
7084
+
7085
+ widget.onSetValue = ( newValue, skipCallback ) => {
7086
+ const items = container.querySelectorAll( 'button' );
7087
+ for( let i = 0; i < items.length; ++i )
7088
+ {
7089
+ const optionItem = items[ i ];
7090
+ if( newValue == i )
7091
+ {
7092
+ Panel._dispatch_event( optionItem, "click", skipCallback );
7093
+ }
7094
+ }
7095
+ };
7096
+
7097
+ let element = widget.domEl;
7098
+
7099
+ // Add widget value
7100
+ var container = document.createElement( 'div' );
7101
+ container.className = "lexradiogroup " + ( options.className ?? "" );
7102
+
7103
+ let labelSpan = document.createElement( 'span' );
7104
+ labelSpan.innerHTML = label;
7105
+ container.appendChild( labelSpan );
7106
+
7107
+ const that = this;
7108
+
7109
+ for( let i = 0; i < values.length; ++i )
7110
+ {
7111
+ const optionItem = document.createElement( 'div' );
7112
+ optionItem.className = "lexradiogroupitem";
7113
+ container.appendChild( optionItem );
7114
+
7115
+ const optionButton = document.createElement( 'button' );
7116
+ optionButton.className = "lexbutton";
7117
+ optionButton.disabled = options.disabled ?? false;
7118
+ optionItem.appendChild( optionButton );
7119
+
7120
+ optionButton.addEventListener( "click", function( e ) {
7121
+ const skipCallback = ( e.detail?.constructor == Number ? null : e.detail );
7122
+ container.querySelectorAll( 'button' ).forEach( e => { e.checked = false; e.classList.remove( "checked" ) } );
7123
+ this.checked = !this.checked;
7124
+ this.classList.toggle( "checked" );
7125
+ if( !skipCallback ) that._trigger( new IEvent( null, [ i, values[ i ] ], e ), callback );
7126
+ } );
7127
+
7128
+ {
7129
+ const checkedSpan = document.createElement( 'span' );
7130
+ optionButton.appendChild( checkedSpan );
7131
+ }
7132
+
7133
+ const optionLabel = document.createElement( 'span' );
7134
+ optionLabel.innerHTML = values[ i ];
7135
+ optionItem.appendChild( optionLabel );
7136
+ }
7137
+
7138
+ if( options.selected )
7139
+ {
7140
+ console.assert( options.selected.constructor == Number );
7141
+ widget.set( options.selected, true );
7142
+ }
7143
+
7144
+ element.appendChild( container );
7145
+
7146
+ return widget;
7147
+ }
7148
+
6117
7149
  /**
6118
7150
  * @method addColor
6119
7151
  * @param {String} name Widget name
@@ -6126,7 +7158,8 @@ class Panel {
6126
7158
 
6127
7159
  addColor( name, value, callback, options = {} ) {
6128
7160
 
6129
- if( !name ) {
7161
+ if( !name )
7162
+ {
6130
7163
  throw( "Set Widget Name!" );
6131
7164
  }
6132
7165
 
@@ -6164,7 +7197,8 @@ class Panel {
6164
7197
  color.useRGB = options.useRGB ?? false;
6165
7198
  color.value = color.iValue = value.constructor === Array ? rgbToHex( value ) : value;
6166
7199
 
6167
- if( options.disabled ) {
7200
+ if( options.disabled )
7201
+ {
6168
7202
  color.disabled = true;
6169
7203
  }
6170
7204
 
@@ -6209,6 +7243,137 @@ class Panel {
6209
7243
  return widget;
6210
7244
  }
6211
7245
 
7246
+ /**
7247
+ * @method addRange
7248
+ * @param {String} name Widget name
7249
+ * @param {Number} value Default number value
7250
+ * @param {Function} callback Callback function on change
7251
+ * @param {*} options:
7252
+ * className: Extra classes to customize style
7253
+ * disabled: Make the widget disabled [false]
7254
+ * left: The slider goes to the left instead of the right
7255
+ * fill: Fill slider progress [true]
7256
+ * step: Step of the input
7257
+ * min, max: Min and Max values for the input
7258
+ */
7259
+
7260
+ addRange( name, value, callback, options = {} ) {
7261
+
7262
+ let widget = this.create_widget( name, Widget.RANGE, options );
7263
+
7264
+ widget.onGetValue = () => {
7265
+ return +slider.value;
7266
+ };
7267
+
7268
+ widget.onSetValue = ( newValue, skipCallback ) => {
7269
+ slider.value = newValue;
7270
+ Panel._dispatch_event( slider, "input", skipCallback );
7271
+ };
7272
+
7273
+ let element = widget.domEl;
7274
+
7275
+ // add reset functionality
7276
+ if( widget.name )
7277
+ {
7278
+ Panel._add_reset_property( element.domName, function() {
7279
+ this.style.display = "none";
7280
+ slider.value = slider.iValue;
7281
+ Panel._dispatch_event( slider, "input" );
7282
+ });
7283
+ }
7284
+
7285
+ // add widget value
7286
+
7287
+ var container = document.createElement( 'div' );
7288
+ container.className = "lexrange";
7289
+ container.style.width = options.inputWidth || "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
7290
+
7291
+ let slider = document.createElement( 'input' );
7292
+ slider.className = "lexrangeslider " + ( options.className ?? "" );
7293
+ slider.value = slider.iValue = value;
7294
+ slider.min = options.min;
7295
+ slider.max = options.max;
7296
+ slider.step = options.step ?? 1;
7297
+ slider.type = "range";
7298
+ slider.disabled = options.disabled ?? false;
7299
+
7300
+ if( options.left ?? false )
7301
+ {
7302
+ slider.classList.add( "left" );
7303
+ }
7304
+
7305
+ if( !( options.fill ?? true ) )
7306
+ {
7307
+ slider.classList.add( "no-fill" );
7308
+ }
7309
+
7310
+ slider.addEventListener( "input", e => {
7311
+
7312
+ if( isNaN( e.target.valueAsNumber ) )
7313
+ {
7314
+ return;
7315
+ }
7316
+
7317
+ const skipCallback = e.detail;
7318
+
7319
+ let val = e.target.value = clamp( +e.target.valueAsNumber, +slider.min, +slider.max );
7320
+ slider.value = val;
7321
+
7322
+ // Reset button (default value)
7323
+ if( !skipCallback )
7324
+ {
7325
+ let btn = element.querySelector( ".lexwidgetname .lexicon" );
7326
+ if( btn ) btn.style.display = val != slider.iValue ? "block": "none";
7327
+ }
7328
+
7329
+ if( options.left )
7330
+ {
7331
+ val = ( +slider.max ) - val + ( +slider.min );
7332
+ }
7333
+
7334
+ if( !skipCallback ) this._trigger( new IEvent( name, val, e ), callback );
7335
+ }, { passive: false });
7336
+
7337
+ slider.addEventListener( "mousedown", function( e ) {
7338
+ if( options.onPress )
7339
+ {
7340
+ options.onPress.bind( slider )( e, slider );
7341
+ }
7342
+ }, false );
7343
+
7344
+ slider.addEventListener( "mouseup", function( e ) {
7345
+ if( options.onRelease )
7346
+ {
7347
+ options.onRelease.bind( slider )( e, slider );
7348
+ }
7349
+ }, false );
7350
+
7351
+ // Method to change min, max, step parameters
7352
+ widget.setLimits = ( newMin, newMax, newStep ) => {
7353
+ slider.min = newMin ?? slider.min;
7354
+ slider.max = newMax ?? slider.max;
7355
+ slider.step = newStep ?? slider.step;
7356
+ Panel._dispatch_event( slider, "input", true );
7357
+ };
7358
+
7359
+ if( value.constructor == Number )
7360
+ {
7361
+ value = clamp( value, +slider.min, +slider.max );
7362
+ }
7363
+
7364
+ container.appendChild( slider );
7365
+ element.appendChild( container );
7366
+
7367
+ // Remove branch padding and margins
7368
+ if( !widget.name )
7369
+ {
7370
+ element.className += " noname";
7371
+ container.style.width = "100%";
7372
+ }
7373
+
7374
+ return widget;
7375
+ }
7376
+
6212
7377
  /**
6213
7378
  * @method addNumber
6214
7379
  * @param {String} name Widget name
@@ -6241,7 +7406,8 @@ class Panel {
6241
7406
  let element = widget.domEl;
6242
7407
 
6243
7408
  // add reset functionality
6244
- if( widget.name ) {
7409
+ if( widget.name )
7410
+ {
6245
7411
  Panel._add_reset_property( element.domName, function() {
6246
7412
  this.style.display = "none";
6247
7413
  vecinput.value = vecinput.iValue;
@@ -6509,7 +7675,8 @@ class Panel {
6509
7675
  return;
6510
7676
  }
6511
7677
 
6512
- for( let i = 0; i < inputs.length; ++i ) {
7678
+ for( let i = 0; i < inputs.length; ++i )
7679
+ {
6513
7680
  let value = newValue[ i ];
6514
7681
  inputs[ i ].value = round( value, options.precision ) ?? 0;
6515
7682
  Panel._dispatch_event( inputs[ i ], "change", skipCallback );
@@ -6521,7 +7688,8 @@ class Panel {
6521
7688
  // Add reset functionality
6522
7689
  Panel._add_reset_property( element.domName, function() {
6523
7690
  this.style.display = "none";
6524
- for( let v of element.querySelectorAll( ".vecinput" ) ) {
7691
+ for( let v of element.querySelectorAll( ".vecinput" ) )
7692
+ {
6525
7693
  v.value = v.iValue;
6526
7694
  Panel._dispatch_event( v, "change" );
6527
7695
  }
@@ -6533,8 +7701,8 @@ class Panel {
6533
7701
  container.className = "lexvector";
6534
7702
  container.style.width = "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
6535
7703
 
6536
- for( let i = 0; i < num_components; ++i ) {
6537
-
7704
+ for( let i = 0; i < num_components; ++i )
7705
+ {
6538
7706
  let box = document.createElement( 'div' );
6539
7707
  box.className = "vecbox";
6540
7708
  box.innerHTML = "<span class='" + Panel.VECTOR_COMPONENTS[ i ] + "'></span>";
@@ -6610,7 +7778,8 @@ class Panel {
6610
7778
 
6611
7779
  if( locker.locked )
6612
7780
  {
6613
- for( let v of element.querySelectorAll( ".vecinput" ) ) {
7781
+ for( let v of element.querySelectorAll( ".vecinput" ) )
7782
+ {
6614
7783
  v.value = val;
6615
7784
  value[ v.idx ] = val;
6616
7785
  }
@@ -6668,7 +7837,8 @@ class Panel {
6668
7837
 
6669
7838
  if( locker.locked )
6670
7839
  {
6671
- for( let v of element.querySelectorAll( ".vecinput" ) ) {
7840
+ for( let v of element.querySelectorAll( ".vecinput" ) )
7841
+ {
6672
7842
  v.value = round( +v.valueAsNumber + mult * dt, options.precision );
6673
7843
  Panel._dispatch_event( v, "change" );
6674
7844
  }
@@ -6794,7 +7964,7 @@ class Panel {
6794
7964
  const value = [];
6795
7965
  for( let i = 0; i < element.dimensions.length; ++i )
6796
7966
  {
6797
- value.push( element.dimensions[ i ].onGetValue() );
7967
+ value.push( element.dimensions[ i ].value() );
6798
7968
  }
6799
7969
  return value;
6800
7970
  };
@@ -6802,7 +7972,7 @@ class Panel {
6802
7972
  widget.onSetValue = ( newValue, skipCallback ) => {
6803
7973
  for( let i = 0; i < element.dimensions.length; ++i )
6804
7974
  {
6805
- element.dimensions[ i ].onSetValue( newValue[ i ], skipCallback );
7975
+ element.dimensions[ i ].set( newValue[ i ], skipCallback );
6806
7976
  }
6807
7977
  };
6808
7978
 
@@ -6817,14 +7987,14 @@ class Panel {
6817
7987
  {
6818
7988
  element.dimensions[ i ] = this.addNumber( null, value[ i ], ( v ) => {
6819
7989
 
6820
- const value = widget.onGetValue();
7990
+ const value = widget.value();
6821
7991
 
6822
7992
  if( element.locked )
6823
7993
  {
6824
7994
  const ar = ( i == 0 ? 1.0 / element.aspectRatio : element.aspectRatio );
6825
7995
  const index = ( 1 + i ) % 2;
6826
7996
  value[ index ] = v * ar;
6827
- element.dimensions[ index ].onSetValue( value[ index ], true );
7997
+ element.dimensions[ index ].set( value[ index ], true );
6828
7998
  }
6829
7999
 
6830
8000
  if( callback )
@@ -6867,7 +8037,7 @@ class Panel {
6867
8037
  this.classList.remove( "fa-lock-open" );
6868
8038
 
6869
8039
  // Recompute ratio
6870
- const value = widget.onGetValue();
8040
+ const value = widget.value();
6871
8041
  element.aspectRatio = value[ 0 ] / value[ 1 ];
6872
8042
  }
6873
8043
  else
@@ -7264,7 +8434,8 @@ class Panel {
7264
8434
  let container = document.createElement('div');
7265
8435
  container.className = "lextree";
7266
8436
 
7267
- if(name) {
8437
+ if( name )
8438
+ {
7268
8439
  let title = document.createElement('span');
7269
8440
  title.innerHTML = name;
7270
8441
  container.appendChild(title);
@@ -7276,8 +8447,8 @@ class Panel {
7276
8447
  toolsDiv.className += " notitle";
7277
8448
 
7278
8449
  // Tree icons
7279
- if(options.icons) {
7280
-
8450
+ if( options.icons )
8451
+ {
7281
8452
  for( let data of options.icons )
7282
8453
  {
7283
8454
  let iconEl = document.createElement('a');
@@ -7337,11 +8508,15 @@ class Panel {
7337
8508
  let widget = new Widget( null, Widget.SEPARATOR );
7338
8509
  widget.domEl = element;
7339
8510
 
7340
- if(this.current_branch) {
8511
+ if( this.current_branch )
8512
+ {
7341
8513
  this.current_branch.content.appendChild( element );
7342
8514
  this.current_branch.widgets.push( widget );
7343
- } else
7344
- this.root.appendChild(element);
8515
+ }
8516
+ else
8517
+ {
8518
+ this.root.appendChild( element );
8519
+ }
7345
8520
  }
7346
8521
 
7347
8522
  /**
@@ -7666,6 +8841,12 @@ class Panel {
7666
8841
 
7667
8842
  input.addEventListener( 'change', function() {
7668
8843
  data.checkMap[ rowId ] = this.checked;
8844
+
8845
+ if( !this.checked )
8846
+ {
8847
+ const input = table.querySelector( "thead input[type='checkbox']" );
8848
+ input.checked = data.checkMap[ ":root" ] = false;
8849
+ }
7669
8850
  });
7670
8851
 
7671
8852
  row.appendChild( td );
@@ -7791,7 +8972,7 @@ class Branch {
7791
8972
  root.appendChild( title );
7792
8973
 
7793
8974
  var branchContent = document.createElement( 'div' );
7794
- branchContent.id = name.replace(/\s/g, '');
8975
+ branchContent.id = name.replace( /\s/g, '' );
7795
8976
  branchContent.className = "lexbranchcontent";
7796
8977
  root.appendChild(branchContent);
7797
8978
  this.content = branchContent;
@@ -7847,7 +9028,8 @@ class Branch {
7847
9028
 
7848
9029
  const dialog = new Dialog(this.name, p => {
7849
9030
  // add widgets
7850
- for( let w of this.widgets ) {
9031
+ for( let w of this.widgets )
9032
+ {
7851
9033
  p.root.appendChild( w.domEl );
7852
9034
  }
7853
9035
  });
@@ -7940,8 +9122,8 @@ class Branch {
7940
9122
  var size = this.grabber.style.marginLeft;
7941
9123
 
7942
9124
  // Update sizes of widgets inside
7943
- for(var i = 0; i < this.widgets.length; i++) {
7944
-
9125
+ for( var i = 0; i < this.widgets.length; i++ )
9126
+ {
7945
9127
  let widget = this.widgets[ i ];
7946
9128
  let element = widget.domEl;
7947
9129
 
@@ -7960,9 +9142,6 @@ class Branch {
7960
9142
  case Widget.FILE:
7961
9143
  padding = "10%";
7962
9144
  break;
7963
- case Widget.TEXT:
7964
- padding = "8px";
7965
- break;
7966
9145
  };
7967
9146
 
7968
9147
  value.style.width = "-moz-calc( 100% - " + size + " - " + padding + " )";
@@ -8101,7 +9280,7 @@ class Dialog {
8101
9280
  modal = options.modal ?? false;
8102
9281
 
8103
9282
  var root = document.createElement('dialog');
8104
- root.className = "lexdialog " + (options.class ?? "");
9283
+ root.className = "lexdialog " + (options.className ?? "");
8105
9284
  root.id = options.id ?? "dialog" + Dialog._last_id++;
8106
9285
  LX.root.appendChild( root );
8107
9286
 
@@ -8259,15 +9438,15 @@ class Dialog {
8259
9438
 
8260
9439
  root.style.width = size[ 0 ] ? (size[ 0 ]) : "25%";
8261
9440
  root.style.height = size[ 1 ] ? (size[ 1 ]) : "auto";
9441
+ root.style.translate = options.position ? "unset" : "-50% -50%";
8262
9442
 
8263
9443
  if( options.size )
8264
9444
  {
8265
9445
  this.size = size;
8266
9446
  }
8267
9447
 
8268
- let rect = root.getBoundingClientRect();
8269
- root.style.left = position[ 0 ] ? (position[ 0 ]) : "calc( 50% - " + ( rect.width * 0.5 ) + "px )";
8270
- root.style.top = position[ 1 ] ? (position[ 1 ]) : "calc( 50% - " + ( rect.height * 0.5 ) + "px )";
9448
+ root.style.left = position[ 0 ] ?? "50%";
9449
+ root.style.top = position[ 1 ] ?? "50%";
8271
9450
 
8272
9451
  panel.root.style.width = "calc( 100% - 30px )";
8273
9452
  panel.root.style.height = title ? "calc( 100% - " + ( titleDiv.offsetHeight + 30 ) + "px )" : "calc( 100% - 51px )";
@@ -8284,7 +9463,7 @@ class Dialog {
8284
9463
  this._oncreate.call(this, this.panel);
8285
9464
  }
8286
9465
 
8287
- setPosition(x, y) {
9466
+ setPosition( x, y ) {
8288
9467
 
8289
9468
  this.root.style.left = x + "px";
8290
9469
  this.root.style.top = y + "px";
@@ -8330,6 +9509,9 @@ class PocketDialog extends Dialog {
8330
9509
 
8331
9510
  // Custom
8332
9511
  this.root.classList.add( "pocket" );
9512
+
9513
+ this.root.style.translate = "none";
9514
+ this.root.style.top = "0";
8333
9515
  this.root.style.left = "unset";
8334
9516
 
8335
9517
  if( !options.position )
@@ -8345,6 +9527,11 @@ class PocketDialog extends Dialog {
8345
9527
  this.minimized = false;
8346
9528
  this.title.tabIndex = -1;
8347
9529
  this.title.addEventListener("click", e => {
9530
+ if( this.title._eventCatched )
9531
+ {
9532
+ this.title._eventCatched = false;
9533
+ return;
9534
+ }
8348
9535
 
8349
9536
  // Sized dialogs have to keep their size
8350
9537
  if( this.size )
@@ -8427,12 +9614,12 @@ class ContextMenu {
8427
9614
  constructor( event, title, options = {} ) {
8428
9615
 
8429
9616
  // remove all context menus
8430
- document.body.querySelectorAll(".lexcontextmenubox").forEach(e => e.remove());
9617
+ document.body.querySelectorAll( ".lexcontextmenu" ).forEach( e => e.remove() );
8431
9618
 
8432
- this.root = document.createElement('div');
8433
- this.root.className = "lexcontextmenubox";
8434
- this.root.style.left = (event.x - 48 + document.scrollingElement.scrollLeft) + "px";
8435
- this.root.style.top = (event.y - 8 + document.scrollingElement.scrollTop) + "px";
9619
+ this.root = document.createElement( "div" );
9620
+ this.root.className = "lexcontextmenu";
9621
+ this.root.style.left = ( event.x - 48 + document.scrollingElement.scrollLeft ) + "px";
9622
+ this.root.style.top = ( event.y - 8 + document.scrollingElement.scrollTop ) + "px";
8436
9623
 
8437
9624
  this.root.addEventListener("mouseleave", function() {
8438
9625
  this.remove();
@@ -8445,13 +9632,13 @@ class ContextMenu {
8445
9632
  {
8446
9633
  const item = {};
8447
9634
  item[ title ] = [];
8448
- item[ 'className' ] = "cmtitle";
8449
- item[ 'icon' ] = options.icon;
9635
+ item[ "className" ] = "cmtitle";
9636
+ item[ "icon" ] = options.icon;
8450
9637
  this.items.push( item );
8451
9638
  }
8452
9639
  }
8453
9640
 
8454
- _adjust_position( div, margin, useAbsolute = false ) {
9641
+ _adjustPosition( div, margin, useAbsolute = false ) {
8455
9642
 
8456
9643
  let rect = div.getBoundingClientRect();
8457
9644
 
@@ -8492,19 +9679,19 @@ class ContextMenu {
8492
9679
  }
8493
9680
  }
8494
9681
 
8495
- _create_submenu( o, k, c, d ) {
9682
+ _createSubmenu( o, k, c, d ) {
8496
9683
 
8497
- this.root.querySelectorAll(".lexcontextmenubox").forEach( cm => cm.remove() );
9684
+ this.root.querySelectorAll( ".lexcontextmenu" ).forEach( cm => cm.remove() );
8498
9685
 
8499
9686
  let contextmenu = document.createElement('div');
8500
- contextmenu.className = "lexcontextmenubox";
9687
+ contextmenu.className = "lexcontextmenu";
8501
9688
  c.appendChild( contextmenu );
8502
9689
 
8503
9690
  for( var i = 0; i < o[k].length; ++i )
8504
9691
  {
8505
- const subitem = o[k][i];
8506
- const subkey = Object.keys(subitem)[0];
8507
- this._create_entry(subitem, subkey, contextmenu, d);
9692
+ const subitem = o[ k ][ i ];
9693
+ const subkey = Object.keys( subitem )[ 0 ];
9694
+ this._createEntry(subitem, subkey, contextmenu, d);
8508
9695
  }
8509
9696
 
8510
9697
  var rect = c.getBoundingClientRect();
@@ -8512,29 +9699,32 @@ class ContextMenu {
8512
9699
  contextmenu.style.marginTop = 3.5 - c.offsetHeight + "px";
8513
9700
 
8514
9701
  // Set final width
8515
- this._adjust_position( contextmenu, 6, true );
9702
+ this._adjustPosition( contextmenu, 6, true );
8516
9703
  }
8517
9704
 
8518
- _create_entry( o, k, c, d ) {
9705
+ _createEntry( o, k, c, d ) {
8519
9706
 
8520
9707
  const hasSubmenu = o[ k ].length;
8521
9708
  let entry = document.createElement('div');
8522
- entry.className = "lexcontextmenuentry" + (o[ 'className' ] ? " " + o[ 'className' ] : "" );
9709
+ entry.className = "lexmenuboxentry" + (o[ 'className' ] ? " " + o[ 'className' ] : "" );
8523
9710
  entry.id = o.id ?? ("eId" + getSupportedDOMName( k ));
8524
9711
  entry.innerHTML = "";
8525
9712
  const icon = o[ 'icon' ];
8526
- if(icon) {
9713
+ if( icon )
9714
+ {
8527
9715
  entry.innerHTML += "<a class='" + icon + " fa-sm'></a>";
8528
9716
  }
8529
9717
  const disabled = o['disabled'];
8530
9718
  entry.innerHTML += "<div class='lexentryname" + (disabled ? " disabled" : "") + "'>" + k + "</div>";
8531
9719
  c.appendChild( entry );
8532
9720
 
8533
- if( this.colors[ k ] ) {
9721
+ if( this.colors[ k ] )
9722
+ {
8534
9723
  entry.style.borderColor = this.colors[ k ];
8535
9724
  }
8536
9725
 
8537
- if( k == "" ) {
9726
+ if( k == "" )
9727
+ {
8538
9728
  entry.className += " cmseparator";
8539
9729
  return;
8540
9730
  }
@@ -8547,7 +9737,8 @@ class ContextMenu {
8547
9737
  if(disabled) return;
8548
9738
 
8549
9739
  const f = o[ 'callback' ];
8550
- if(f) {
9740
+ if( f )
9741
+ {
8551
9742
  f.call( this, k, entry );
8552
9743
  this.root.remove();
8553
9744
  }
@@ -8556,7 +9747,7 @@ class ContextMenu {
8556
9747
  return;
8557
9748
 
8558
9749
  if( LX.OPEN_CONTEXTMENU_ENTRY == 'click' )
8559
- this._create_submenu( o, k, entry, ++d );
9750
+ this._createSubmenu( o, k, entry, ++d );
8560
9751
  });
8561
9752
 
8562
9753
  if( !hasSubmenu )
@@ -8572,20 +9763,19 @@ class ContextMenu {
8572
9763
  if(entry.built)
8573
9764
  return;
8574
9765
  entry.built = true;
8575
- this._create_submenu( o, k, entry, ++d );
9766
+ this._createSubmenu( o, k, entry, ++d );
8576
9767
  e.stopPropagation();
8577
9768
  });
8578
9769
  }
8579
9770
 
8580
9771
  entry.addEventListener("mouseleave", () => {
8581
9772
  d = -1; // Reset depth
8582
- // delete entry.built;
8583
- c.querySelectorAll(".lexcontextmenubox").forEach(e => e.remove());
9773
+ c.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
8584
9774
  });
8585
9775
  }
8586
9776
 
8587
9777
  onCreate() {
8588
- doAsync( () => this._adjust_position( this.root, 6 ) );
9778
+ doAsync( () => this._adjustPosition( this.root, 6 ) );
8589
9779
  }
8590
9780
 
8591
9781
  add( path, options = {} ) {
@@ -8613,22 +9803,25 @@ class ContextMenu {
8613
9803
  if(key) found = o[ key ];
8614
9804
  } );
8615
9805
 
8616
- if(found) {
8617
- insert( tokens[idx++], found );
9806
+ if( found )
9807
+ {
9808
+ insert( tokens[ idx++ ], found );
8618
9809
  }
8619
- else {
9810
+ else
9811
+ {
8620
9812
  let item = {};
8621
9813
  item[ token ] = [];
8622
- const next_token = tokens[idx++];
9814
+ const nextToken = tokens[ idx++ ];
8623
9815
  // Check if last token -> add callback
8624
- if(!next_token) {
9816
+ if( !nextToken )
9817
+ {
8625
9818
  item[ 'id' ] = options.id;
8626
9819
  item[ 'callback' ] = options.callback;
8627
9820
  item[ 'disabled' ] = options.disabled ?? false;
8628
9821
  }
8629
9822
 
8630
9823
  list.push( item );
8631
- insert( next_token, item[ token ] );
9824
+ insert( nextToken, item[ token ] );
8632
9825
  }
8633
9826
  };
8634
9827
 
@@ -8638,13 +9831,15 @@ class ContextMenu {
8638
9831
 
8639
9832
  const setParent = _item => {
8640
9833
 
8641
- let key = Object.keys(_item)[0];
9834
+ let key = Object.keys( _item )[ 0 ];
8642
9835
  let children = _item[ key ];
8643
9836
 
8644
- if(!children.length)
9837
+ if( !children.length )
9838
+ {
8645
9839
  return;
9840
+ }
8646
9841
 
8647
- if(children.find( c => Object.keys(c)[0] == key ) == null)
9842
+ if( children.find( c => Object.keys(c)[0] == key ) == null )
8648
9843
  {
8649
9844
  const parent = {};
8650
9845
  parent[ key ] = [];
@@ -8652,27 +9847,34 @@ class ContextMenu {
8652
9847
  _item[ key ].unshift( parent );
8653
9848
  }
8654
9849
 
8655
- for( var child of _item[ key ] ) {
8656
- let k = Object.keys(child)[0];
8657
- for( var i = 0; i < child[k].length; ++i )
8658
- setParent(child);
9850
+ for( var child of _item[ key ] )
9851
+ {
9852
+ let k = Object.keys( child )[ 0 ];
9853
+ for( var i = 0; i < child[ k ].length; ++i )
9854
+ {
9855
+ setParent( child );
9856
+ }
8659
9857
  }
8660
9858
  };
8661
9859
 
8662
9860
  for( let item of this.items )
8663
- setParent(item);
9861
+ {
9862
+ setParent( item );
9863
+ }
8664
9864
 
8665
9865
  // Create elements
8666
9866
 
8667
9867
  for( let item of this.items )
8668
9868
  {
8669
- let key = Object.keys(item)[0];
9869
+ let key = Object.keys( item )[ 0 ];
8670
9870
  let pKey = "eId" + getSupportedDOMName( key );
8671
9871
 
8672
9872
  // Item already created
8673
- const id = "#" + (item.id ?? pKey);
8674
- if( !this.root.querySelector(id) )
8675
- this._create_entry(item, key, this.root, -1);
9873
+ const id = "#" + ( item.id ?? pKey );
9874
+ if( !this.root.querySelector( id ) )
9875
+ {
9876
+ this._createEntry( item, key, this.root, -1 );
9877
+ }
8676
9878
  }
8677
9879
  }
8678
9880
 
@@ -8783,7 +9985,7 @@ class Curve {
8783
9985
 
8784
9986
  var r = [];
8785
9987
  var dx = (element.xrange[1] - element.xrange[ 0 ]) / samples;
8786
- for(var i = element.xrange[0]; i <= element.xrange[1]; i += dx)
9988
+ for( var i = element.xrange[0]; i <= element.xrange[1]; i += dx )
8787
9989
  {
8788
9990
  r.push( element.getValueAt(i) );
8789
9991
  }
@@ -8792,7 +9994,8 @@ class Curve {
8792
9994
 
8793
9995
  element.addValue = function(v) {
8794
9996
 
8795
- for(var i = 0; i < element.value; i++) {
9997
+ for( var i = 0; i < element.value; i++ )
9998
+ {
8796
9999
  var value = element.value[i];
8797
10000
  if(value[0] < v[0]) continue;
8798
10001
  element.value.splice(i,0,v);
@@ -8818,7 +10021,7 @@ class Curve {
8818
10021
 
8819
10022
  var selected = -1;
8820
10023
 
8821
- element.redraw = function( o = {} ) {
10024
+ element.redraw = function( o = {} ) {
8822
10025
 
8823
10026
  if( o.value ) element.value = o.value;
8824
10027
  if( o.xrange ) element.xrange = o.xrange;
@@ -8847,13 +10050,16 @@ class Curve {
8847
10050
  ctx.moveTo( pos[ 0 ], pos[ 1 ] );
8848
10051
  let values = [pos[ 0 ], pos[ 1 ]];
8849
10052
 
8850
- for(var i in element.value) {
8851
- var value = element.value[i];
8852
- pos = convert(value);
8853
- values.push(pos[ 0 ]);
8854
- values.push(pos[ 1 ]);
8855
- if(!element.smooth)
10053
+ for( var i in element.value )
10054
+ {
10055
+ var value = element.value[ i ];
10056
+ pos = convert( value );
10057
+ values.push( pos[ 0 ] );
10058
+ values.push( pos[ 1 ] );
10059
+ if( !element.smooth )
10060
+ {
8856
10061
  ctx.lineTo( pos[ 0 ], pos[ 1 ] );
10062
+ }
8857
10063
  }
8858
10064
 
8859
10065
  pos = convert([ element.xrange[ 1 ], element.defaulty ]);
@@ -8870,7 +10076,8 @@ class Curve {
8870
10076
  }
8871
10077
 
8872
10078
  // Draw points
8873
- for( var i = 0; i < element.value.length; i += 1 ) {
10079
+ for( var i = 0; i < element.value.length; i += 1 )
10080
+ {
8874
10081
  var value = element.value[ i ];
8875
10082
  pos = convert( value );
8876
10083
  if( selected == i )
@@ -8882,10 +10089,11 @@ class Curve {
8882
10089
  ctx.fill();
8883
10090
  }
8884
10091
 
8885
- if(element.show_samples) {
10092
+ if( element.show_samples )
10093
+ {
8886
10094
  var samples = element.resample(element.show_samples);
8887
10095
  ctx.fillStyle = "#888";
8888
- for(var i = 0; i < samples.length; i += 1)
10096
+ for( var i = 0; i < samples.length; i += 1)
8889
10097
  {
8890
10098
  var value = [ i * ((element.xrange[ 1 ] - element.xrange[ 0 ]) / element.show_samples) + element.xrange[ 0 ], samples[ i ] ];
8891
10099
  pos = convert(value);
@@ -8908,7 +10116,8 @@ class Curve {
8908
10116
 
8909
10117
  selected = computeSelected( mousex, canvas.height - mousey );
8910
10118
 
8911
- if( e.button == LX.MOUSE_LEFT_CLICK && selected == -1 && element.allow_add_values ) {
10119
+ if( e.button == LX.MOUSE_LEFT_CLICK && selected == -1 && element.allow_add_values )
10120
+ {
8912
10121
  var v = unconvert([ mousex, canvas.height - mousey ]);
8913
10122
  element.value.push( v );
8914
10123
  sortValues();
@@ -8956,7 +10165,8 @@ class Curve {
8956
10165
  var dy = element.draggable_y ? last_mouse[ 1 ] - mousey : 0;
8957
10166
  var delta = unconvert([ -dx, dy ]);
8958
10167
 
8959
- if( selected != -1 ) {
10168
+ if( selected != -1 )
10169
+ {
8960
10170
  var minx = element.xrange[ 0 ];
8961
10171
  var maxx = element.xrange[ 1 ];
8962
10172
 
@@ -9113,7 +10323,7 @@ class Dial {
9113
10323
 
9114
10324
  var r = [];
9115
10325
  var dx = (element.xrange[1] - element.xrange[ 0 ]) / samples;
9116
- for(var i = element.xrange[0]; i <= element.xrange[1]; i += dx)
10326
+ for( var i = element.xrange[0]; i <= element.xrange[1]; i += dx)
9117
10327
  {
9118
10328
  r.push( element.getValueAt(i) );
9119
10329
  }
@@ -9122,15 +10332,16 @@ class Dial {
9122
10332
 
9123
10333
  element.addValue = function(v) {
9124
10334
 
9125
- for(var i = 0; i < element.value; i++) {
9126
- var value = element.value[i];
9127
- if(value[0] < v[0]) continue;
9128
- element.value.splice(i,0,v);
10335
+ for( var i = 0; i < element.value; i++ )
10336
+ {
10337
+ var value = element.value[ i ];
10338
+ if(value[ 0 ] < v[ 0 ]) continue;
10339
+ element.value.splice( i, 0, v );
9129
10340
  redraw();
9130
10341
  return;
9131
10342
  }
9132
10343
 
9133
- element.value.push(v);
10344
+ element.value.push( v );
9134
10345
  redraw();
9135
10346
  }
9136
10347
 
@@ -9150,7 +10361,7 @@ class Dial {
9150
10361
 
9151
10362
  var selected = -1;
9152
10363
 
9153
- element.redraw = function( o = {} ) {
10364
+ element.redraw = function( o = {} ) {
9154
10365
 
9155
10366
  if( o.value ) element.value = o.value;
9156
10367
  if( o.xrange ) element.xrange = o.xrange;
@@ -9179,17 +10390,17 @@ class Dial {
9179
10390
  ctx.moveTo( pos[ 0 ], pos[ 1 ] );
9180
10391
  let values = [pos[ 0 ], pos[ 1 ]];
9181
10392
 
9182
- for(var i in element.value) {
9183
- var value = element.value[i];
9184
- pos = convert(value);
9185
- values.push(pos[ 0 ]);
9186
- values.push(pos[ 1 ]);
9187
-
10393
+ for( var i in element.value)
10394
+ {
10395
+ var value = element.value[ i ];
10396
+ pos = convert( value );
10397
+ values.push( pos[ 0 ] );
10398
+ values.push( pos[ 1 ] );
9188
10399
  }
9189
10400
 
9190
10401
  pos = convert([ element.xrange[ 1 ], element.defaulty ]);
9191
- values.push(pos[ 0 ]);
9192
- values.push(pos[ 1 ]);
10402
+ values.push( pos[ 0 ] );
10403
+ values.push( pos[ 1 ] );
9193
10404
 
9194
10405
  // Draw points
9195
10406
  const center = [0,0];
@@ -9199,7 +10410,8 @@ class Dial {
9199
10410
  ctx.arc( pos[ 0 ], pos[ 1 ], 3, 0, Math.PI * 2);
9200
10411
  ctx.fill();
9201
10412
 
9202
- for( var i = 0; i < element.value.length; i += 1 ) {
10413
+ for( var i = 0; i < element.value.length; i += 1 )
10414
+ {
9203
10415
  var value = element.value[ i ];
9204
10416
  pos = convert( value );
9205
10417
  if( selected == i )
@@ -9211,10 +10423,11 @@ class Dial {
9211
10423
  ctx.fill();
9212
10424
  }
9213
10425
 
9214
- if(element.show_samples) {
10426
+ if( element.show_samples )
10427
+ {
9215
10428
  var samples = element.resample(element.show_samples);
9216
10429
  ctx.fillStyle = "#888";
9217
- for(var i = 0; i < samples.length; i += 1)
10430
+ for( var i = 0; i < samples.length; i += 1)
9218
10431
  {
9219
10432
  var value = [ i * ((element.xrange[ 1 ] - element.xrange[ 0 ]) / element.show_samples) + element.xrange[ 0 ], samples[ i ] ];
9220
10433
  pos = convert(value);
@@ -9237,7 +10450,8 @@ class Dial {
9237
10450
 
9238
10451
  selected = computeSelected( mousex, canvas.height - mousey );
9239
10452
 
9240
- if( e.button == LX.MOUSE_LEFT_CLICK && selected == -1 && element.allow_add_values ) {
10453
+ if( e.button == LX.MOUSE_LEFT_CLICK && selected == -1 && element.allow_add_values )
10454
+ {
9241
10455
  var v = unconvert([ mousex, canvas.height - mousey ]);
9242
10456
  element.value.push( v );
9243
10457
  sortValues();
@@ -9285,7 +10499,8 @@ class Dial {
9285
10499
  var dy = element.draggable_y ? last_mouse[ 1 ] - mousey : 0;
9286
10500
  var delta = unconvert([ -dx, dy ]);
9287
10501
 
9288
- if( selected != -1 ) {
10502
+ if( selected != -1 )
10503
+ {
9289
10504
  var minx = element.xrange[ 0 ];
9290
10505
  var maxx = element.xrange[ 1 ];
9291
10506
 
@@ -9393,7 +10608,8 @@ class AssetViewEvent {
9393
10608
  }
9394
10609
 
9395
10610
  string() {
9396
- switch(this.type) {
10611
+ switch(this.type)
10612
+ {
9397
10613
  case AssetViewEvent.NONE: return "assetview_event_none";
9398
10614
  case AssetViewEvent.ASSET_SELECTED: return "assetview_event_selected";
9399
10615
  case AssetViewEvent.ASSET_DELETED: return "assetview_event_deleted";
@@ -9747,8 +10963,8 @@ class AssetView {
9747
10963
  icon: "fa-solid fa-arrows-rotate",
9748
10964
  callback: domEl => { this._refreshContent(); }
9749
10965
  }
9750
- ], { width: "auto", noSelection: true } );
9751
- this.rightPanel.addText(null, this.path.join('/'), null, { disabled: true, signal: "@on_folder_change", style: { fontWeight: "bolder", fontSize: "16px", color: "#aaa" } });
10966
+ ], { width: "20%", minWidth: "164px", noSelection: true } );
10967
+ this.rightPanel.addText(null, this.path.join('/'), null, { width: "70%", maxWidth: "calc(70% - 64px)", minWidth: "164px", disabled: true, signal: "@on_folder_change", style: { fontWeight: "bolder", fontSize: "16px", color: "#aaa" } });
9752
10968
  this.rightPanel.addText(null, "Page " + this.contentPage + " / " + ((((this.currentData.length - 1) / AssetView.MAX_PAGE_ELEMENTS )|0) + 1), null, {disabled: true, signal: "@on_page_change", width: "fit-content"})
9753
10969
  this.rightPanel.endLine();
9754
10970
  }
@@ -9874,8 +11090,8 @@ class AssetView {
9874
11090
  title.innerText = item.id;
9875
11091
  itemEl.appendChild( title );
9876
11092
 
9877
- if( !that.skipPreview ) {
9878
-
11093
+ if( !that.skipPreview )
11094
+ {
9879
11095
  let preview = null;
9880
11096
  const hasImage = item.src && (['png', 'jpg'].indexOf( getExtension( item.src ) ) > -1 || item.src.includes("data:image/") ); // Support b64 image as src
9881
11097
 
@@ -9902,7 +11118,8 @@ class AssetView {
9902
11118
  var newEmSize = charsPerLine / newLength;
9903
11119
  var textBaseSize = 64;
9904
11120
 
9905
- if(newEmSize < 1) {
11121
+ if( newEmSize < 1 )
11122
+ {
9906
11123
  var newFontSize = newEmSize * textBaseSize;
9907
11124
  textEl.style.fontSize = newFontSize + "px";
9908
11125
  preview.style.paddingTop = "calc(50% - " + (textEl.offsetHeight * 0.5 + 10) + "px)"
@@ -10131,7 +11348,8 @@ class AssetView {
10131
11348
 
10132
11349
  this.currentData.push( item );
10133
11350
 
10134
- if(i == (num_files - 1)) {
11351
+ if( i == (num_files - 1) )
11352
+ {
10135
11353
  this._refreshContent();
10136
11354
  if( !this.skipBrowser )
10137
11355
  this.tree.refresh();
@@ -10183,7 +11401,7 @@ class AssetView {
10183
11401
  this.currentData.splice( idx, 1 );
10184
11402
  this._refreshContent( this.searchValue, this.filter );
10185
11403
 
10186
- if(this.onevent)
11404
+ if( this.onevent)
10187
11405
  {
10188
11406
  const event = new AssetViewEvent( AssetViewEvent.ASSET_DELETED, item );
10189
11407
  this.onevent( event );
@@ -10259,7 +11477,7 @@ Object.assign(LX, {
10259
11477
  xhr.onload = function(load)
10260
11478
  {
10261
11479
  var response = this.response;
10262
- if(this.status != 200)
11480
+ if( this.status != 200)
10263
11481
  {
10264
11482
  var err = "Error " + this.status;
10265
11483
  if(request.error)
@@ -10307,7 +11525,7 @@ Object.assign(LX, {
10307
11525
  var data = new FormData();
10308
11526
  if( request.data )
10309
11527
  {
10310
- for(var i in request.data)
11528
+ for( var i in request.data)
10311
11529
  data.append(i,request.data[i]);
10312
11530
  }
10313
11531
 
@@ -10368,7 +11586,7 @@ Object.assign(LX, {
10368
11586
  var size = total;
10369
11587
  var loaded_scripts = [];
10370
11588
 
10371
- for(var i in url)
11589
+ for( var i in url)
10372
11590
  {
10373
11591
  var script = document.createElement('script');
10374
11592
  script.num = i;
@@ -10493,6 +11711,18 @@ Element.prototype.getParentArea = function() {
10493
11711
  }
10494
11712
  }
10495
11713
 
11714
+ LX.ICONS = {
11715
+ "Sidebar": `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_iconCarrier"> <g id="Complete"> <g id="sidebar-left"> <g> <rect id="Square-2" data-name="Square" x="3" y="3" width="18" height="18" rx="2" ry="2" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="2"></rect> <line x1="9" y1="21" x2="9" y2="3" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="2"></line> </g> </g> </g> </g></svg>`,
11716
+ "More": `<svg fill="#000000" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_iconCarrier"> <g id="Complete"> <g id="F-More"> <path id="Vertical" d="M12,16a2,2,0,1,1-2,2A2,2,0,0,1,12,16ZM10,6a2,2,0,1,0,2-2A2,2,0,0,0,10,6Zm0,6a2,2,0,1,0,2-2A2,2,0,0,0,10,12Z"></path> </g> </g> </g></svg>`,
11717
+ "MoreHorizontal": `<svg fill="#000000" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_iconCarrier"> <g id="Complete"> <g id="F-More"> <path id="Horizontal" d="M8,12a2,2,0,1,1-2-2A2,2,0,0,1,8,12Zm10-2a2,2,0,1,0,2,2A2,2,0,0,0,18,10Zm-6,0a2,2,0,1,0,2,2A2,2,0,0,0,12,10Z"></path> </g> </g> </g></svg>`,
11718
+ "MenuArrows": `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="#000000" transform="rotate(90)"><g id="SVGRepo_iconCarrier"> <g id="Complete"> <g id="Code"> <g> <polyline id="Right-2" data-name="Right" points="15.5 7 20.5 12 15.5 17" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></polyline> <polyline id="Left-2" data-name="Left" points="8.5 7 3.5 12 8.5 17" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></polyline> </g> </g> </g> </g></svg>`,
11719
+ "Plus": `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="#000000"<g id="SVGRepo_iconCarrier"> <g id="Complete"> <g id="add-2" data-name="add"> <g> <line x1="12" y1="19" x2="12" y2="5" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></line> <line x1="5" y1="12" x2="19" y2="12" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></line> </g> </g> </g> </g></svg>`,
11720
+ "Down": `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="#000000"><g id="SVGRepo_iconCarrier"> <title>i</title> <g id="Complete"> <g id="F-Chevron"> <polyline id="Down" points="5 8.5 12 15.5 19 8.5" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></polyline> </g> </g> </g></svg>`,
11721
+ "Up": `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="#000000"><g id="SVGRepo_iconCarrier"> <title>i</title> <g id="Complete"> <g id="F-Chevron"> <polyline id="Up" points="5 15.5 12 8.5 19 15.5" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></polyline> </g> </g> </g></svg>`,
11722
+ "Right": `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="#000000"><g id="SVGRepo_iconCarrier"> <title>i</title> <g id="Complete"> <g id="F-Chevron"> <polyline id="Right" points="8.5 5 15.5 12 8.5 19" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></polyline> </g> </g> </g></svg>`,
11723
+ "Left": `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="#000000"><g id="SVGRepo_iconCarrier"> <title>i</title> <g id="Complete"> <g id="F-Chevron"> <polyline id="Left" points="15.5 5 8.5 12 15.5 19" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></polyline> </g> </g> </g></svg>`,
11724
+ }
11725
+
10496
11726
  LX.UTILS = {
10497
11727
  getTime() { return new Date().getTime() },
10498
11728
  compareThreshold( v, p, n, t ) { return Math.abs(v - p) >= t || Math.abs(v - n) >= t },
@@ -10532,17 +11762,19 @@ LX.UTILS = {
10532
11762
  drawSpline( ctx, pts, t ) {
10533
11763
 
10534
11764
  ctx.save();
10535
- var cp=[]; // array of control points, as x0,y0,x1,y1,...
10536
- var n=pts.length;
11765
+ var cp = []; // array of control points, as x0,y0,x1,y1,...
11766
+ var n = pts.length;
10537
11767
 
10538
11768
  // Draw an open curve, not connected at the ends
10539
- for(var i=0;i<n-4;i+=2) {
10540
- cp=cp.concat(LX.UTILS.getControlPoints(pts[i],pts[i+1],pts[i+2],pts[i+3],pts[i+4],pts[i+5],t));
11769
+ for( var i = 0; i < (n - 4); i += 2 )
11770
+ {
11771
+ cp = cp.concat(LX.UTILS.getControlPoints(pts[i],pts[i+1],pts[i+2],pts[i+3],pts[i+4],pts[i+5],t));
10541
11772
  }
10542
11773
 
10543
- for(var i=2;i<pts.length-5;i+=2) {
11774
+ for( var i = 2; i < ( pts.length - 5 ); i += 2 )
11775
+ {
10544
11776
  ctx.beginPath();
10545
- ctx.moveTo(pts[i],pts[i+1]);
11777
+ ctx.moveTo(pts[i], pts[i+1]);
10546
11778
  ctx.bezierCurveTo(cp[2*i-2],cp[2*i-1],cp[2*i],cp[2*i+1],pts[i+2],pts[i+3]);
10547
11779
  ctx.stroke();
10548
11780
  ctx.closePath();
@@ -10550,14 +11782,14 @@ LX.UTILS = {
10550
11782
 
10551
11783
  // For open curves the first and last arcs are simple quadratics.
10552
11784
  ctx.beginPath();
10553
- ctx.moveTo(pts[0],pts[1]);
10554
- ctx.quadraticCurveTo(cp[0],cp[1],pts[2],pts[3]);
11785
+ ctx.moveTo( pts[ 0 ], pts[ 1 ] );
11786
+ ctx.quadraticCurveTo( cp[ 0 ], cp[ 1 ], pts[ 2 ], pts[ 3 ]);
10555
11787
  ctx.stroke();
10556
11788
  ctx.closePath();
10557
11789
 
10558
11790
  ctx.beginPath();
10559
- ctx.moveTo(pts[n-2],pts[n-1]);
10560
- ctx.quadraticCurveTo(cp[2*n-10],cp[2*n-9],pts[n-4],pts[n-3]);
11791
+ ctx.moveTo( pts[ n-2 ], pts[ n-1 ] );
11792
+ ctx.quadraticCurveTo( cp[ 2*n-10 ], cp[ 2*n-9 ], pts[ n-4 ], pts[ n-3 ]);
10561
11793
  ctx.stroke();
10562
11794
  ctx.closePath();
10563
11795