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.
package/build/lexgui.js CHANGED
@@ -12,23 +12,26 @@ console.warn( 'Script _build/lexgui.js_ is depracated and will be removed soon.
12
12
  */
13
13
 
14
14
  var LX = {
15
- version: "0.2.0",
15
+ version: "0.4.1",
16
16
  ready: false,
17
- components: [], // specific pre-build components
18
- signals: {}, // events and triggers
19
- extraCommandbarEntries: [] // user specific entries for command bar
17
+ components: [], // Specific pre-build components
18
+ signals: {}, // Events and triggers
19
+ extraCommandbarEntries: [], // User specific entries for command bar
20
+ activeDraggable: null // Watch for the current active draggable
20
21
  };
21
22
 
22
23
  LX.MOUSE_LEFT_CLICK = 0;
23
24
  LX.MOUSE_MIDDLE_CLICK = 1;
24
25
  LX.MOUSE_RIGHT_CLICK = 2;
25
26
 
26
- LX.MOUSE_DOUBLE_CLICK = 2;
27
- LX.MOUSE_TRIPLE_CLICK = 3;
27
+ LX.MOUSE_DOUBLE_CLICK = 2;
28
+ LX.MOUSE_TRIPLE_CLICK = 3;
28
29
 
29
- LX.CURVE_MOVEOUT_CLAMP = 0;
30
+ LX.CURVE_MOVEOUT_CLAMP = 0;
30
31
  LX.CURVE_MOVEOUT_DELETE = 1;
31
32
 
33
+ LX.DRAGGABLE_Z_INDEX = 101;
34
+
32
35
  function clamp( num, min, max ) { return Math.min( Math.max( num, min ), max ); }
33
36
  function round( number, precision ) { return precision == 0 ? Math.floor( number ) : +(( number ).toFixed( precision ?? 2 ).replace( /([0-9]+(\.[0-9]+[1-9])?)(\.?0+$)/, '$1' )); }
34
37
  function remapRange( oldValue, oldMin, oldMax, newMin, newMax ) { return ((( oldValue - oldMin ) * ( newMax - newMin )) / ( oldMax - oldMin )) + newMin; }
@@ -88,7 +91,7 @@ LX.doAsync = doAsync;
88
91
  */
89
92
  function getSupportedDOMName( text )
90
93
  {
91
- return text.replace(/\s/g, '').replaceAll('@', '_').replaceAll('+', '_plus_').replaceAll('.', '');
94
+ return text.replace( /\s/g, '' ).replaceAll('@', '_').replaceAll('+', '_plus_').replaceAll( '.', '' );
92
95
  }
93
96
 
94
97
  LX.getSupportedDOMName = getSupportedDOMName;
@@ -227,7 +230,8 @@ LX.hexToRgb = hexToRgb;
227
230
  function rgbToHex( rgb )
228
231
  {
229
232
  let hex = "#";
230
- for( let c of rgb ) {
233
+ for( let c of rgb )
234
+ {
231
235
  c = Math.floor( c * 255 );
232
236
  hex += c.toString( 16 );
233
237
  }
@@ -302,7 +306,7 @@ LX.buildTextPattern = buildTextPattern;
302
306
 
303
307
  /**
304
308
  * @method makeDraggable
305
- * @description Allow an element to be dragged
309
+ * @description Allows an element to be dragged
306
310
  * @param {Element} domEl
307
311
  * @param {Object} options
308
312
  * autoAdjust (Bool): Sets in a correct position at the beggining
@@ -327,6 +331,7 @@ function makeDraggable( domEl, options = { } )
327
331
  top = top ?? e.clientY - offsetY - parentRect.y;
328
332
  domEl.style.left = clamp( left, dragMargin + fixedOffset.x, fixedOffset.x + parentRect.width - domEl.offsetWidth - dragMargin ) + 'px';
329
333
  domEl.style.top = clamp( top, dragMargin + fixedOffset.y, fixedOffset.y + parentRect.height - domEl.offsetHeight - dragMargin ) + 'px';
334
+ domEl.style.translate = "none"; // Force remove translation
330
335
  };
331
336
 
332
337
  // Initial adjustment
@@ -371,26 +376,45 @@ function makeDraggable( domEl, options = { } )
371
376
  e.preventDefault();
372
377
  e.stopPropagation();
373
378
  e.stopImmediatePropagation();
374
- if( !currentTarget ) return;
379
+
380
+ if( !currentTarget )
381
+ {
382
+ return;
383
+ }
384
+
375
385
  // Remove image when dragging
376
386
  var img = new Image();
377
387
  img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=';
378
388
  e.dataTransfer.setDragImage( img, 0, 0 );
379
389
  e.dataTransfer.effectAllowed = "move";
390
+
380
391
  const rect = e.target.getBoundingClientRect();
381
392
  const parentRect = currentTarget.parentElement.getBoundingClientRect();
382
393
  const isFixed = ( currentTarget.style.position == "fixed" );
383
394
  const fixedOffset = isFixed ? new LX.vec2( parentRect.x, parentRect.y ) : new LX.vec2();
384
395
  offsetX = e.clientX - rect.x - fixedOffset.x;
385
396
  offsetY = e.clientY - rect.y - fixedOffset.y;
397
+
386
398
  document.addEventListener( "mousemove", onMove );
399
+
400
+ currentTarget._eventCatched = true;
401
+
402
+ // Force active dialog to show on top
403
+ if( LX.activeDraggable )
404
+ {
405
+ LX.activeDraggable.style.zIndex = LX.DRAGGABLE_Z_INDEX;
406
+ }
407
+
408
+ LX.activeDraggable = domEl;
409
+ LX.activeDraggable.style.zIndex = LX.DRAGGABLE_Z_INDEX + 1;
410
+
387
411
  if( onDragStart )
388
412
  {
389
413
  onDragStart( currentTarget, e );
390
414
  }
391
415
  }, false );
392
416
 
393
- document.addEventListener( 'mouseup', () => {
417
+ document.addEventListener( 'mouseup', (e) => {
394
418
  if( currentTarget )
395
419
  {
396
420
  currentTarget = null;
@@ -401,6 +425,48 @@ function makeDraggable( domEl, options = { } )
401
425
 
402
426
  LX.makeDraggable = makeDraggable;
403
427
 
428
+ /**
429
+ * @method makeCollapsible
430
+ * @description Allows an element to be collapsed/expanded
431
+ * @param {Element} domEl: Element to be treated as collapsible
432
+ * @param {Element} content: Content to display/hide on collapse/extend
433
+ * @param {Element} parent: Element where the content will be appended (default is domEl.parent)
434
+ * @param {Object} options
435
+ */
436
+ function makeCollapsible( domEl, content, parent, options = { } )
437
+ {
438
+ domEl.classList.add( "collapsible" );
439
+
440
+ const collapsed = ( options.collapsed ?? true );
441
+ const actionIcon = LX.makeIcon( "Right" );
442
+ actionIcon.classList.add( "collapser" );
443
+ actionIcon.dataset[ "collapsed" ] = collapsed;
444
+ actionIcon.style.marginLeft = "auto";
445
+
446
+ actionIcon.addEventListener( "click", function(e) {
447
+ e.preventDefault();
448
+ e.stopPropagation();
449
+ if( this.dataset[ "collapsed" ] )
450
+ {
451
+ delete this.dataset[ "collapsed" ];
452
+ content.style.display = "block";
453
+ }
454
+ else
455
+ {
456
+ this.dataset[ "collapsed" ] = true;
457
+ content.style.display = "none";
458
+ }
459
+ } );
460
+
461
+ domEl.appendChild( actionIcon );
462
+
463
+ parent = parent ?? domEl.parentElement;
464
+
465
+ parent.appendChild( content );
466
+ }
467
+
468
+ LX.makeCollapsible = makeCollapsible;
469
+
404
470
  /**
405
471
  * @method makeCodeSnippet
406
472
  * @description Create a code snippet in a specific language
@@ -503,6 +569,23 @@ function makeCodeSnippet( code, size, options = { } )
503
569
 
504
570
  LX.makeCodeSnippet = makeCodeSnippet;
505
571
 
572
+ /**
573
+ * @method makeIcon
574
+ * @description Gets an SVG element using one of LX.ICONS
575
+ * @param {String} iconName
576
+ * @param {String} iconTitle
577
+ */
578
+ function makeIcon( iconName, iconTitle )
579
+ {
580
+ const icon = document.createElement( "a" );
581
+ icon.title = iconTitle ?? "";
582
+ icon.className = "lexicon";
583
+ icon.innerHTML = LX.ICONS[ iconName ] ?? "";
584
+ return icon;
585
+ }
586
+
587
+ LX.makeIcon = makeIcon;
588
+
506
589
  /**
507
590
  * @method registerCommandbarEntry
508
591
  * @description Adds an extra command bar entry
@@ -772,12 +855,12 @@ function _createCommandbar( root )
772
855
  if( LX.has('CodeEditor') )
773
856
  {
774
857
  const instances = LX.CodeEditor.getInstances();
775
- if(!instances.length) return;
858
+ if( !instances.length ) return;
776
859
 
777
860
  const languages = instances[ 0 ].languages;
778
861
 
779
- for( let l of Object.keys( languages ) ) {
780
-
862
+ for( let l of Object.keys( languages ) )
863
+ {
781
864
  const key = "Language: " + l;
782
865
  const icon = instances[ 0 ]._getFileIcon( null, languages[ l ].ext );
783
866
 
@@ -785,9 +868,11 @@ function _createCommandbar( root )
785
868
  "<img src='" + ( "https://raw.githubusercontent.com/jxarco/lexgui.js/master/" + icon ) + "'>";
786
869
 
787
870
  value += key + " <span class='lang-ext'>(" + languages[ l ].ext + ")</span>";
788
- if( key.toLowerCase().includes( filter ) ) {
871
+ if( key.toLowerCase().includes( filter ) )
872
+ {
789
873
  _addElement( value, () => {
790
- for( let i of instances ) {
874
+ for( let i of instances )
875
+ {
791
876
  i._changeLanguage( l );
792
877
  }
793
878
  }, "", {} );
@@ -845,7 +930,19 @@ function init( options = { } )
845
930
  this.container = document.getElementById( options.container );
846
931
  }
847
932
 
848
- document.documentElement.setAttribute( "data-strictVP", ( options.strictViewport ?? true ) ? "true" : "false" );
933
+ this.usingStrictViewport = options.strictViewport ?? true;
934
+ document.documentElement.setAttribute( "data-strictVP", ( this.usingStrictViewport ) ? "true" : "false" );
935
+
936
+ if( !this.usingStrictViewport )
937
+ {
938
+ document.addEventListener( "scroll", e => {
939
+ // Get all active menuboxes
940
+ const mbs = document.body.querySelectorAll( ".lexmenubox" );
941
+ mbs.forEach( ( mb ) => {
942
+ mb._updatePosition();
943
+ } );
944
+ } );
945
+ }
849
946
 
850
947
  this.commandbar = _createCommandbar( this.container );
851
948
 
@@ -860,6 +957,25 @@ function init( options = { } )
860
957
  this.root = document.body;
861
958
  }
862
959
 
960
+ // Notifications
961
+ {
962
+ const notifSection = document.createElement( "section" );
963
+ notifSection.className = "notifications";
964
+ this.notifications = document.createElement( "ol" );
965
+ this.notifications.className = "";
966
+ this.notifications.iWidth = 0;
967
+ notifSection.appendChild( this.notifications );
968
+ this.container.appendChild( notifSection );
969
+
970
+ this.notifications.addEventListener( "mouseenter", () => {
971
+ this.notifications.classList.add( "list" );
972
+ } );
973
+
974
+ this.notifications.addEventListener( "mouseleave", () => {
975
+ this.notifications.classList.remove( "list" );
976
+ } );
977
+ }
978
+
863
979
  // Disable drag icon
864
980
  root.addEventListener( 'dragover', function( e ) {
865
981
  e.preventDefault();
@@ -1010,6 +1126,7 @@ LX.popup = popup;
1010
1126
  function prompt( text, title, callback, options = {} )
1011
1127
  {
1012
1128
  options.modal = true;
1129
+ options.className = "prompt";
1013
1130
 
1014
1131
  let value = "";
1015
1132
 
@@ -1024,7 +1141,9 @@ function prompt( text, title, callback, options = {} )
1024
1141
 
1025
1142
  p.sameLine( 2 );
1026
1143
 
1027
- p.addButton( null, options.accept || "OK", () => {
1144
+ p.addButton(null, "Cancel", () => {if(options.on_cancel) options.on_cancel(); dialog.close();} );
1145
+
1146
+ p.addButton( null, options.accept || "Continue", () => {
1028
1147
  if( options.required && value === '' )
1029
1148
  {
1030
1149
  text += text.includes("You must fill the input text.") ? "": "\nYou must fill the input text.";
@@ -1038,8 +1157,6 @@ function prompt( text, title, callback, options = {} )
1038
1157
  }
1039
1158
  }, { buttonClass: "primary" });
1040
1159
 
1041
- p.addButton(null, "Cancel", () => {if(options.on_cancel) options.on_cancel(); dialog.close();} );
1042
-
1043
1160
  }, options );
1044
1161
 
1045
1162
  // Focus text prompt
@@ -1053,6 +1170,101 @@ function prompt( text, title, callback, options = {} )
1053
1170
 
1054
1171
  LX.prompt = prompt;
1055
1172
 
1173
+ /**
1174
+ * @method toast
1175
+ * @param {String} title
1176
+ * @param {String} description (Optional)
1177
+ * @param {*} options
1178
+ * action: Data of the custom action { name, callback }
1179
+ * closable: Allow closing the toast
1180
+ * timeout: Time in which the toast closed automatically, in ms. -1 means persistent. [3000]
1181
+ */
1182
+
1183
+ function toast( title, description, options = {} )
1184
+ {
1185
+ if( !title )
1186
+ {
1187
+ throw( "The toast needs at least a title!" );
1188
+ }
1189
+
1190
+ console.assert( this.notifications );
1191
+
1192
+ const toast = document.createElement( "li" );
1193
+ toast.className = "lextoast";
1194
+ toast.style.translate = "0 calc(100% + 30px)";
1195
+ this.notifications.prepend( toast );
1196
+
1197
+ doAsync( () => {
1198
+
1199
+ if( this.notifications.offsetWidth > this.notifications.iWidth )
1200
+ {
1201
+ this.notifications.iWidth = Math.min( this.notifications.offsetWidth, 480 );
1202
+ this.notifications.style.width = this.notifications.iWidth + "px";
1203
+ }
1204
+
1205
+ toast.dataset[ "open" ] = true;
1206
+ }, 10 );
1207
+
1208
+ const content = document.createElement( "div" );
1209
+ content.className = "lextoastcontent";
1210
+ toast.appendChild( content );
1211
+
1212
+ const titleContent = document.createElement( "div" );
1213
+ titleContent.className = "title";
1214
+ titleContent.innerHTML = title;
1215
+ content.appendChild( titleContent );
1216
+
1217
+ if( description )
1218
+ {
1219
+ const desc = document.createElement( "div" );
1220
+ desc.className = "desc";
1221
+ desc.innerHTML = description;
1222
+ content.appendChild( desc );
1223
+ }
1224
+
1225
+ if( options.action )
1226
+ {
1227
+ const panel = new Panel();
1228
+ panel.addButton(null, options.action.name ?? "Accept", options.action.callback.bind( this, toast ), { width: "auto", maxWidth: "150px", className: "right", buttonClass: "outline" });
1229
+ toast.appendChild( panel.root.childNodes[ 0 ] );
1230
+ }
1231
+
1232
+ const that = this;
1233
+
1234
+ toast.close = function() {
1235
+ this.dataset[ "closed" ] = true;
1236
+ doAsync( () => {
1237
+ this.remove();
1238
+ if( !that.notifications.childElementCount )
1239
+ {
1240
+ that.notifications.style.width = "unset";
1241
+ that.notifications.iWidth = 0;
1242
+ }
1243
+ }, 500 );
1244
+ };
1245
+
1246
+ if( options.closable ?? true )
1247
+ {
1248
+ const closeButton = document.createElement( "a" );
1249
+ closeButton.className = "fa fa-xmark lexicon closer";
1250
+ closeButton.addEventListener( "click", () => {
1251
+ toast.close();
1252
+ } );
1253
+ toast.appendChild( closeButton );
1254
+ }
1255
+
1256
+ const timeout = options.timeout ?? 3000;
1257
+
1258
+ if( timeout != -1 )
1259
+ {
1260
+ doAsync( () => {
1261
+ toast.close();
1262
+ }, timeout );
1263
+ }
1264
+ }
1265
+
1266
+ LX.toast = toast;
1267
+
1056
1268
  /**
1057
1269
  * @method badge
1058
1270
  * @param {String} text
@@ -1354,7 +1566,8 @@ class Area {
1354
1566
 
1355
1567
  function inner_mousemove( e )
1356
1568
  {
1357
- switch( that.type ) {
1569
+ switch( that.type )
1570
+ {
1358
1571
  case "right":
1359
1572
  var dt = ( lastMousePosition[ 0 ] - e.x );
1360
1573
  var size = ( that.root.offsetWidth + dt );
@@ -1549,7 +1762,8 @@ class Area {
1549
1762
 
1550
1763
  // Listen resize event on first area
1551
1764
  const resizeObserver = new ResizeObserver( entries => {
1552
- for (const entry of entries) {
1765
+ for ( const entry of entries )
1766
+ {
1553
1767
  const bb = entry.contentRect;
1554
1768
  area2.root.style.height = "calc(100% - " + ( bb.height + 4) + "px )";
1555
1769
  }
@@ -1690,9 +1904,15 @@ class Area {
1690
1904
  this.root.style.height = height;
1691
1905
  }
1692
1906
 
1693
- this.size = [ this.root.clientWidth, this.root.clientHeight ];
1907
+ if( this.onresize )
1908
+ {
1909
+ this.onresize( this.root.getBoundingClientRect() );
1910
+ }
1694
1911
 
1695
- this.propagateEvent( "onresize" );
1912
+ doAsync( () => {
1913
+ this.size = [ this.root.clientWidth, this.root.clientHeight ];
1914
+ this.propagateEvent( "onresize" );
1915
+ }, 150 );
1696
1916
  }
1697
1917
 
1698
1918
  /**
@@ -1709,7 +1929,7 @@ class Area {
1709
1929
  let [area1, area2] = this.sections;
1710
1930
  this.splitExtended = true;
1711
1931
 
1712
- if(this.type == "vertical")
1932
+ if( this.type == "vertical")
1713
1933
  {
1714
1934
  this.offset = area2.root.offsetHeight;
1715
1935
  area2.root.classList.add("fadeout-vertical");
@@ -1723,7 +1943,6 @@ class Area {
1723
1943
  this._moveSplit(-Infinity, true, 8);
1724
1944
  }
1725
1945
 
1726
- // Async resize in some ms...
1727
1946
  doAsync( () => this.propagateEvent('onresize'), 150 );
1728
1947
  }
1729
1948
 
@@ -1739,7 +1958,7 @@ class Area {
1739
1958
  this.splitExtended = false;
1740
1959
  let [area1, area2] = this.sections;
1741
1960
 
1742
- if(this.type == "vertical")
1961
+ if( this.type == "vertical")
1743
1962
  {
1744
1963
  area2.root.classList.add("fadein-vertical");
1745
1964
  this._moveSplit(this.offset);
@@ -1750,7 +1969,6 @@ class Area {
1750
1969
  this._moveSplit(this.offset);
1751
1970
  }
1752
1971
 
1753
- // Async resize in some ms...
1754
1972
  doAsync( () => this.propagateEvent('onresize'), 150 );
1755
1973
  }
1756
1974
 
@@ -1784,11 +2002,15 @@ class Area {
1784
2002
 
1785
2003
  propagateEvent( eventName ) {
1786
2004
 
1787
- for(var i = 0; i < this.sections.length; i++)
2005
+ for( var i = 0; i < this.sections.length; i++ )
1788
2006
  {
1789
- const area = this.sections[i];
1790
- if(area[ eventName ])
2007
+ const area = this.sections[ i ];
2008
+
2009
+ if( area[ eventName ] )
2010
+ {
1791
2011
  area[ eventName ].call( this, area.root.getBoundingClientRect() );
2012
+ }
2013
+
1792
2014
  area.propagateEvent( eventName );
1793
2015
  }
1794
2016
  }
@@ -1811,42 +2033,63 @@ class Area {
1811
2033
  * @param {Function} callback Function to fill the menubar
1812
2034
  * @param {*} options:
1813
2035
  * float: Justify content (left, center, right) [left]
2036
+ * sticky: Fix menubar at the top [true]
1814
2037
  */
1815
2038
 
1816
2039
  addMenubar( callback, options = {} ) {
1817
2040
 
1818
- let menubar = new Menubar(options);
2041
+ let menubar = new Menubar( options );
1819
2042
 
1820
- if(callback) callback( menubar );
2043
+ if( callback )
2044
+ {
2045
+ callback( menubar );
2046
+ }
1821
2047
 
1822
2048
  LX.menubars.push( menubar );
1823
2049
 
1824
2050
  const height = 48; // pixels
2051
+ const [ bar, content ] = this.split({ type: 'vertical', sizes: [height, null], resize: false, menubar: true });
2052
+ menubar.siblingArea = content;
1825
2053
 
1826
- const [bar, content] = this.split({type: 'vertical', sizes: [height, null], resize: false, menubar: true});
1827
2054
  bar.attach( menubar );
1828
- bar.is_menubar = true;
2055
+ bar.isMenubar = true;
2056
+
2057
+ if( options.sticky ?? true )
2058
+ {
2059
+ bar.root.classList.add( "sticky" );
2060
+ }
2061
+
1829
2062
  return menubar;
1830
2063
  }
1831
2064
 
1832
2065
  /**
1833
2066
  * @method addSidebar
1834
2067
  * @param {Function} callback Function to fill the sidebar
2068
+ * @param {Object} options: Sidebar options
2069
+ * width: Width of the sidebar [16rem]
1835
2070
  */
1836
2071
 
1837
2072
  addSidebar( callback, options = {} ) {
1838
2073
 
1839
2074
  let sidebar = new SideBar( options );
1840
2075
 
1841
- if( callback ) callback( sidebar );
2076
+ if( callback )
2077
+ {
2078
+ callback( sidebar );
2079
+ }
2080
+
2081
+ // Generate DOM elements after adding all entries
2082
+ sidebar.update();
1842
2083
 
1843
2084
  LX.menubars.push( sidebar );
1844
2085
 
1845
- const width = 64; // pixels
2086
+ const width = options.width ?? "16rem";
2087
+ const [ bar, content ] = this.split( { type: 'horizontal', sizes: [ width, null ], resize: false, sidebar: true } );
2088
+ sidebar.siblingArea = content;
1846
2089
 
1847
- const [bar, content] = this.split( { type: 'horizontal', sizes: [ width, null ], resize: false, sidebar: true } );
1848
2090
  bar.attach( sidebar );
1849
- bar.is_sidebar = true;
2091
+ bar.isSidebar = true;
2092
+
1850
2093
  return sidebar;
1851
2094
  }
1852
2095
 
@@ -2102,7 +2345,8 @@ class Area {
2102
2345
 
2103
2346
  this.size = [ rect.width, rect.height ];
2104
2347
 
2105
- for(var i = 0; i < this.sections.length; i++) {
2348
+ for( var i = 0; i < this.sections.length; i++ )
2349
+ {
2106
2350
  this.sections[i]._update();
2107
2351
  }
2108
2352
  }
@@ -2126,7 +2370,7 @@ class Tabs {
2126
2370
  static TAB_SIZE = 28;
2127
2371
  static TAB_ID = 0;
2128
2372
 
2129
- constructor( area, options = {} ) {
2373
+ constructor( area, options = {} ) {
2130
2374
 
2131
2375
  this.onclose = options.onclose;
2132
2376
 
@@ -2208,24 +2452,28 @@ class Tabs {
2208
2452
  }
2209
2453
 
2210
2454
  // debug
2211
- if(folding)
2455
+ if( folding )
2212
2456
  {
2213
2457
  this.folded = true;
2214
2458
  this.folding = folding;
2215
2459
 
2216
- if(folding == "up") area.root.insertChildAtIndex(area.sections[1].root, 0);
2460
+ if( folding == "up" )
2461
+ {
2462
+ area.root.insertChildAtIndex(area.sections[1].root, 0);
2463
+ }
2217
2464
 
2218
2465
  // Listen resize event on parent area
2219
2466
  const resizeObserver = new ResizeObserver((entries) => {
2220
- for (const entry of entries) {
2467
+ for (const entry of entries)
2468
+ {
2221
2469
  const bb = entry.contentRect;
2222
- const sibling = area.parentArea.sections[0].root;
2223
- const add_offset = true; // hardcoded...
2224
- sibling.style.height = "calc(100% - " + ((add_offset ? 42 : 0) + bb.height) + "px )";
2470
+ const sibling = area.parentArea.sections[ 0 ].root;
2471
+ const addOffset = true; // hardcoded...
2472
+ sibling.style.height = "calc(100% - " + ((addOffset ? 42 : 0) + bb.height) + "px )";
2225
2473
  }
2226
2474
  });
2227
2475
 
2228
- resizeObserver.observe(this.area.root);
2476
+ resizeObserver.observe( this.area.root );
2229
2477
  this.area.root.classList.add('folded');
2230
2478
  }
2231
2479
  }
@@ -2362,7 +2610,8 @@ class Tabs {
2362
2610
 
2363
2611
  setTimeout( () => {
2364
2612
 
2365
- if( options.onCreate ) {
2613
+ if( options.onCreate )
2614
+ {
2366
2615
  options.onCreate.call(this, this.area.root.getBoundingClientRect());
2367
2616
  }
2368
2617
 
@@ -2424,34 +2673,230 @@ LX.Tabs = Tabs;
2424
2673
 
2425
2674
  class Menubar {
2426
2675
 
2427
- constructor( options = {} ) {
2676
+ constructor( options = {} ) {
2428
2677
 
2429
- this.root = document.createElement('div');
2678
+ this.root = document.createElement( "div" );
2430
2679
  this.root.className = "lexmenubar";
2431
- if(options.float)
2680
+
2681
+ if( options.float )
2682
+ {
2432
2683
  this.root.style.justifyContent = options.float;
2433
- this.items = [];
2684
+ }
2685
+
2686
+ this.items = [ ];
2687
+ this.buttons = [ ];
2688
+ this.icons = { };
2689
+ this.shorts = { };
2690
+ }
2691
+
2692
+ _resetMenubar() {
2693
+
2694
+ // Menu entries are in the menubar..
2695
+ this.root.querySelectorAll(".lexmenuentry").forEach( _entry => {
2696
+ _entry.classList.remove( 'selected' );
2697
+ _entry.built = false;
2698
+ } );
2699
+
2700
+ // Menuboxes are in the root area!
2701
+ LX.root.querySelectorAll(".lexmenubox").forEach(e => e.remove());
2702
+
2703
+ // Next time we need to click again
2704
+ this.focused = false;
2705
+ }
2706
+
2707
+ _createSubmenu( o, k, c, d ) {
2708
+
2709
+ let menuElement = document.createElement('div');
2710
+ menuElement.className = "lexmenubox";
2711
+ menuElement.tabIndex = "0";
2712
+ c.currentMenu = menuElement;
2713
+ menuElement.parentEntry = c;
2714
+
2715
+ const isSubMenu = c.classList.contains( "lexmenuboxentry" );
2716
+ if( isSubMenu )
2717
+ {
2718
+ menuElement.dataset[ "submenu" ] = true;
2719
+ }
2720
+
2721
+ menuElement._updatePosition = () => {
2722
+
2723
+ // Remove transitions for this change..
2724
+ const transition = menuElement.style.transition;
2725
+ menuElement.style.transition = "none";
2726
+ flushCss( menuElement );
2727
+
2728
+ doAsync( () => {
2729
+ let rect = c.getBoundingClientRect();
2730
+ rect.x += document.scrollingElement.scrollLeft;
2731
+ rect.y += document.scrollingElement.scrollTop;
2732
+ menuElement.style.left = ( isSubMenu ? ( rect.x + rect.width ) : rect.x ) + "px";
2733
+ menuElement.style.top = ( isSubMenu ? rect.y : ( ( rect.y + rect.height ) ) - 4 ) + "px";
2734
+
2735
+ menuElement.style.transition = transition;
2736
+ } );
2737
+ };
2738
+
2739
+ menuElement._updatePosition();
2740
+
2741
+ doAsync( () => {
2742
+ menuElement.dataset[ "open" ] = true;
2743
+ }, 10 );
2744
+
2745
+ LX.root.appendChild( menuElement );
2746
+
2747
+ for( var i = 0; i < o[ k ].length; ++i )
2748
+ {
2749
+ const subitem = o[ k ][ i ];
2750
+ const subkey = Object.keys( subitem )[ 0 ];
2751
+ const hasSubmenu = subitem[ subkey ].length;
2752
+ const isCheckbox = subitem[ 'type' ] == 'checkbox';
2753
+ let subentry = document.createElement('div');
2754
+ subentry.className = "lexmenuboxentry";
2755
+ subentry.className += (i == o[k].length - 1 ? " last" : "") + ( subitem.disabled ? " disabled" : "" );
2756
+
2757
+ if( subkey == '' )
2758
+ {
2759
+ subentry.className = " lexseparator";
2760
+ }
2761
+ else
2762
+ {
2763
+ subentry.id = subkey;
2764
+ let subentrycont = document.createElement('div');
2765
+ subentrycont.innerHTML = "";
2766
+ subentrycont.classList = "lexmenuboxentrycontainer";
2767
+ subentry.appendChild(subentrycont);
2768
+ const icon = this.icons[ subkey ];
2769
+ if( isCheckbox )
2770
+ {
2771
+ subentrycont.innerHTML += "<input type='checkbox' >";
2772
+ }
2773
+ else if( icon )
2774
+ {
2775
+ subentrycont.innerHTML += "<a class='" + icon + " fa-sm'></a>";
2776
+ }
2777
+ else
2778
+ {
2779
+ subentrycont.innerHTML += "<a class='fa-solid fa-sm noicon'></a>";
2780
+ subentrycont.classList.add( "noicon" );
2781
+
2782
+ }
2783
+ subentrycont.innerHTML += "<div class='lexentryname'>" + subkey + "</div>";
2784
+ }
2785
+
2786
+ let checkboxInput = subentry.querySelector('input');
2787
+ if( checkboxInput )
2788
+ {
2789
+ checkboxInput.checked = subitem.checked ?? false;
2790
+ checkboxInput.addEventListener('change', e => {
2791
+ subitem.checked = checkboxInput.checked;
2792
+ const f = subitem[ 'callback' ];
2793
+ if( f )
2794
+ {
2795
+ f.call( this, subitem.checked, subkey, subentry );
2796
+ this._resetMenubar();
2797
+ }
2798
+ e.stopPropagation();
2799
+ e.stopImmediatePropagation();
2800
+ })
2801
+ }
2802
+
2803
+ menuElement.appendChild( subentry );
2804
+
2805
+ // Nothing more for separators
2806
+ if( subkey == '' )
2807
+ {
2808
+ continue;
2809
+ }
2434
2810
 
2435
- this.icons = {};
2436
- this.shorts = {};
2437
- this.buttons = [];
2811
+ menuElement.addEventListener('keydown', e => {
2812
+ e.preventDefault();
2813
+ let short = this.shorts[ subkey ];
2814
+ if(!short) return;
2815
+ // check if it's a letter or other key
2816
+ short = short.length == 1 ? short.toLowerCase() : short;
2817
+ if( short == e.key )
2818
+ {
2819
+ subentry.click()
2820
+ }
2821
+ });
2822
+
2823
+ // Add callback
2824
+ subentry.addEventListener("click", e => {
2825
+ if( checkboxInput )
2826
+ {
2827
+ subitem.checked = !subitem.checked;
2828
+ }
2829
+ const f = subitem[ 'callback' ];
2830
+ if( f )
2831
+ {
2832
+ f.call( this, checkboxInput ? subitem.checked : subkey, checkboxInput ? subkey : subentry );
2833
+ this._resetMenubar();
2834
+ }
2835
+ e.stopPropagation();
2836
+ e.stopImmediatePropagation();
2837
+ });
2838
+
2839
+ // Add icon if has submenu, else check for shortcut
2840
+ if( !hasSubmenu)
2841
+ {
2842
+ if( this.shorts[ subkey ] )
2843
+ {
2844
+ let shortEl = document.createElement('div');
2845
+ shortEl.className = "lexentryshort";
2846
+ shortEl.innerText = this.shorts[ subkey ];
2847
+ subentry.appendChild( shortEl );
2848
+ }
2849
+ continue;
2850
+ }
2851
+
2852
+ let submenuIcon = document.createElement('a');
2853
+ submenuIcon.className = "fa-solid fa-angle-right fa-xs";
2854
+ subentry.appendChild( submenuIcon );
2855
+
2856
+ subentry.addEventListener("mouseover", e => {
2857
+ if( subentry.built )
2858
+ {
2859
+ return;
2860
+ }
2861
+ subentry.built = true;
2862
+ this._createSubmenu( subitem, subkey, subentry, ++d );
2863
+ e.stopPropagation();
2864
+ });
2865
+
2866
+ subentry.addEventListener("mouseleave", e => {
2867
+ if( subentry.currentMenu && ( subentry.currentMenu != e.toElement ) )
2868
+ {
2869
+ d = -1; // Reset depth
2870
+ delete subentry.built;
2871
+ subentry.currentMenu.remove();
2872
+ delete subentry.currentMenu;
2873
+ }
2874
+ });
2875
+ }
2876
+
2877
+ // Set final width
2878
+ menuElement.style.width = menuElement.offsetWidth + "px";
2438
2879
  }
2439
2880
 
2440
2881
  /**
2441
2882
  * @method add
2442
- * @param {*} options:
2883
+ * @param {Object} options:
2443
2884
  * callback: Function to call on each item
2885
+ * icon: Entry icon
2886
+ * short: Entry shortcut name
2444
2887
  */
2445
2888
 
2446
2889
  add( path, options = {} ) {
2447
2890
 
2448
- if(options.constructor == Function)
2891
+ if( options.constructor == Function )
2892
+ {
2449
2893
  options = { callback: options };
2894
+ }
2450
2895
 
2451
- // process path
2452
- const tokens = path.split("/");
2896
+ // Process path
2897
+ const tokens = path.split( "/" );
2453
2898
 
2454
- // assign icons and shortcuts to last token in path
2899
+ // Assign icons and shortcuts to last token in path
2455
2900
  const lastPath = tokens[tokens.length - 1];
2456
2901
  this.icons[ lastPath ] = options.icon;
2457
2902
  this.shorts[ lastPath ] = options.short;
@@ -2459,211 +2904,119 @@ class Menubar {
2459
2904
  let idx = 0;
2460
2905
  let that = this;
2461
2906
 
2462
- const insert = (token, list) => {
2463
- if(token == undefined) return;
2907
+ const _insertEntry = ( token, list ) => {
2908
+ if( token == undefined )
2909
+ {
2910
+ return;
2911
+ }
2464
2912
 
2465
2913
  let found = null;
2466
2914
  list.forEach( o => {
2467
- const keys = Object.keys(o);
2915
+ const keys = Object.keys( o );
2468
2916
  const key = keys.find( t => t == token );
2469
- if(key) found = o[ key ];
2917
+ if( key ) found = o[ key ];
2470
2918
  } );
2471
2919
 
2472
- if(found) {
2473
- insert( tokens[idx++], found );
2920
+ if( found )
2921
+ {
2922
+ _insertEntry( tokens[ idx++ ], found );
2474
2923
  }
2475
- else {
2924
+ else
2925
+ {
2476
2926
  let item = {};
2477
2927
  item[ token ] = [];
2478
- const next_token = tokens[idx++];
2928
+ const nextToken = tokens[ idx++ ];
2479
2929
  // Check if last token -> add callback
2480
- if(!next_token) {
2930
+ if( !nextToken )
2931
+ {
2481
2932
  item[ 'callback' ] = options.callback;
2933
+ item[ 'disabled' ] = options.disabled;
2482
2934
  item[ 'type' ] = options.type;
2483
2935
  item[ 'checked' ] = options.checked;
2484
2936
  }
2485
2937
  list.push( item );
2486
- insert( next_token, item[ token ] );
2938
+ _insertEntry( nextToken, item[ token ] );
2487
2939
  }
2488
2940
  };
2489
2941
 
2490
- insert( tokens[idx++], this.items );
2942
+ _insertEntry( tokens[idx++], this.items );
2491
2943
 
2492
2944
  // Create elements
2493
2945
 
2494
2946
  for( let item of this.items )
2495
2947
  {
2496
- let key = Object.keys(item)[0];
2497
- let pKey = key.replace(/\s/g, '').replaceAll('.', '');
2948
+ let key = Object.keys( item )[ 0 ];
2949
+ let pKey = key.replace( /\s/g, '' ).replaceAll( '.', '' );
2498
2950
 
2499
2951
  // Item already created
2500
- if( this.root.querySelector("#" + pKey) )
2952
+ if( this.root.querySelector( "#" + pKey ) )
2953
+ {
2501
2954
  continue;
2955
+ }
2502
2956
 
2503
2957
  let entry = document.createElement('div');
2504
2958
  entry.className = "lexmenuentry";
2505
2959
  entry.id = pKey;
2506
2960
  entry.innerHTML = "<span>" + key + "</span>";
2507
- if(options.position == "left") {
2961
+ entry.tabIndex = "1";
2962
+
2963
+ if( options.position == "left" )
2964
+ {
2508
2965
  this.root.prepend( entry );
2509
2966
  }
2510
- else {
2511
- if(options.position == "right")
2967
+ else
2968
+ {
2969
+ if( options.position == "right" )
2970
+ {
2512
2971
  entry.right = true;
2513
- if(this.root.lastChild && this.root.lastChild.right) {
2972
+ }
2973
+
2974
+ if( this.root.lastChild && this.root.lastChild.right )
2975
+ {
2514
2976
  this.root.lastChild.before( entry );
2515
2977
  }
2516
- else {
2978
+ else
2979
+ {
2517
2980
  this.root.appendChild( entry );
2518
2981
  }
2519
2982
  }
2520
2983
 
2521
- const create_submenu = function( o, k, c, d ) {
2522
-
2523
- let contextmenu = document.createElement('div');
2524
- contextmenu.className = "lexcontextmenu";
2525
- contextmenu.tabIndex = "0";
2526
- const isSubMenu = c.classList.contains('lexcontextmenuentry');
2527
- var rect = c.getBoundingClientRect();
2528
- contextmenu.style.left = (isSubMenu ? rect.width : rect.left) + "px";
2529
- // Entries use css to set top relative to parent
2530
- contextmenu.style.top = (isSubMenu ? 0 : rect.bottom - 4) + "px";
2531
- c.appendChild( contextmenu );
2532
-
2533
- contextmenu.focus();
2534
-
2535
- rect = contextmenu.getBoundingClientRect();
2536
-
2537
- for( var i = 0; i < o[k].length; ++i )
2538
- {
2539
- const subitem = o[k][i];
2540
- const subkey = Object.keys(subitem)[0];
2541
- const hasSubmenu = subitem[ subkey ].length;
2542
- const isCheckbox = subitem[ 'type' ] == 'checkbox';
2543
- let subentry = document.createElement('div');
2544
- subentry.className = "lexcontextmenuentry";
2545
- subentry.className += (i == o[k].length - 1 ? " last" : "");
2546
- if(subkey == '')
2547
- subentry.className = " lexseparator";
2548
- else {
2549
-
2550
- subentry.id = subkey;
2551
- let subentrycont = document.createElement('div');
2552
- subentrycont.innerHTML = "";
2553
- subentrycont.classList = "lexcontextmenuentrycontainer";
2554
- subentry.appendChild(subentrycont);
2555
- const icon = that.icons[ subkey ];
2556
- if(isCheckbox){
2557
- subentrycont.innerHTML += "<input type='checkbox' >";
2558
- }else if(icon) {
2559
- subentrycont.innerHTML += "<a class='" + icon + " fa-sm'></a>";
2560
- }else {
2561
- subentrycont.innerHTML += "<a class='fa-solid fa-sm noicon'></a>";
2562
- subentrycont.classList.add( "noicon" );
2563
-
2564
- }
2565
- subentrycont.innerHTML += "<div class='lexentryname'>" + subkey + "</div>";
2566
- }
2567
-
2568
- let checkbox_input = subentry.querySelector('input');
2569
- if(checkbox_input) {
2570
- checkbox_input.checked = subitem.checked ?? false;
2571
- checkbox_input.addEventListener('change', (e) => {
2572
- subitem.checked = checkbox_input.checked;
2573
- const f = subitem[ 'callback' ];
2574
- if(f) {
2575
- f.call( this, subitem.checked, subkey, subentry );
2576
- that.root.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
2577
- }
2578
- e.stopPropagation();
2579
- e.stopImmediatePropagation();
2580
- })
2581
- }
2582
-
2583
- contextmenu.appendChild( subentry );
2584
-
2585
- // Nothing more for separators
2586
- if(subkey == '') continue;
2587
-
2588
- contextmenu.addEventListener('keydown', function(e) {
2589
- e.preventDefault();
2590
- let short = that.shorts[ subkey ];
2591
- if(!short) return;
2592
- // check if it's a letter or other key
2593
- short = short.length == 1 ? short.toLowerCase() : short;
2594
- if(short == e.key) {
2595
- subentry.click()
2596
- }
2597
- });
2598
-
2599
- // Add callback
2600
- subentry.addEventListener("click", e => {
2601
- if(checkbox_input) {
2602
- subitem.checked = !subitem.checked;
2603
- }
2604
- const f = subitem[ 'callback' ];
2605
- if(f) {
2606
- f.call( this, checkbox_input ? subitem.checked : subkey, checkbox_input ? subkey : subentry );
2607
- that.root.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
2608
- }
2609
- e.stopPropagation();
2610
- e.stopImmediatePropagation();
2611
- });
2612
-
2613
- // Add icon if has submenu, else check for shortcut
2614
- if( !hasSubmenu)
2615
- {
2616
- if(that.shorts[ subkey ]) {
2617
- let shortEl = document.createElement('div');
2618
- shortEl.className = "lexentryshort";
2619
- shortEl.innerText = that.shorts[ subkey ];
2620
- subentry.appendChild( shortEl );
2621
- }
2622
- continue;
2623
- }
2624
-
2625
- let submenuIcon = document.createElement('a');
2626
- submenuIcon.className = "fa-solid fa-angle-right fa-xs";
2627
- subentry.appendChild( submenuIcon );
2628
-
2629
- subentry.addEventListener("mouseover", e => {
2630
- if(subentry.built)
2631
- return;
2632
- subentry.built = true;
2633
- create_submenu( subitem, subkey, subentry, ++d );
2634
- e.stopPropagation();
2635
- });
2636
-
2637
- subentry.addEventListener("mouseleave", () => {
2638
- d = -1; // Reset depth
2639
- delete subentry.built;
2640
- contextmenu.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
2641
- });
2642
- }
2643
-
2644
- // Set final width
2645
- contextmenu.style.width = contextmenu.offsetWidth + "px";
2984
+ const _showEntry = () => {
2985
+ this._resetMenubar();
2986
+ entry.classList.add( "selected" );
2987
+ entry.built = true;
2988
+ this._createSubmenu( item, key, entry, -1 );
2646
2989
  };
2647
2990
 
2648
2991
  entry.addEventListener("click", () => {
2649
-
2650
2992
  const f = item[ 'callback' ];
2651
- if(f) {
2993
+ if( f )
2994
+ {
2652
2995
  f.call( this, key, entry );
2653
2996
  return;
2654
2997
  }
2655
2998
 
2656
- // Manage selected
2657
- this.root.querySelectorAll(".lexmenuentry").forEach( e => e.classList.remove( 'selected' ) );
2658
- entry.classList.add( "selected" );
2999
+ _showEntry();
2659
3000
 
2660
- this.root.querySelectorAll(".lexcontextmenu").forEach( e => e.remove() );
2661
- create_submenu( item, key, entry, -1 );
3001
+ this.focused = true;
2662
3002
  });
2663
3003
 
2664
- entry.addEventListener("mouseleave", () => {
2665
- this.root.querySelectorAll(".lexmenuentry").forEach( e => e.classList.remove( 'selected' ) );
2666
- this.root.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
3004
+ entry.addEventListener( "mouseover", (e) => {
3005
+
3006
+ if( this.focused && !entry.built )
3007
+ {
3008
+ _showEntry();
3009
+ }
3010
+ });
3011
+
3012
+ entry.addEventListener("blur", (e) => {
3013
+
3014
+ if( e.relatedTarget && e.relatedTarget.classList.contains( "lexmenubox" ) )
3015
+ {
3016
+ return;
3017
+ }
3018
+
3019
+ this._resetMenubar();
2667
3020
  });
2668
3021
  }
2669
3022
  }
@@ -2682,20 +3035,24 @@ class Menubar {
2682
3035
  * @param {Object} item: parent item
2683
3036
  * @param {Array} tokens: split path strings
2684
3037
  */
2685
- getSubitem(item, tokens) {
3038
+ getSubitem( item, tokens ) {
2686
3039
 
2687
3040
  let subitem = null;
2688
- let path = tokens[0];
2689
- for(let i = 0; i < item.length; i++) {
2690
- if(item[i][path]) {
3041
+ let path = tokens[ 0 ];
2691
3042
 
2692
- if(tokens.length == 1) {
2693
- subitem = item[i];
3043
+ for( let i = 0; i < item.length; i++ )
3044
+ {
3045
+ if( item[ i ][ path ] )
3046
+ {
3047
+ if( tokens.length == 1 )
3048
+ {
3049
+ subitem = item[ i ];
2694
3050
  return subitem;
2695
3051
  }
2696
- else {
2697
- tokens.splice(0,1);
2698
- return this.getSubitem(item[i][path], tokens);
3052
+ else
3053
+ {
3054
+ tokens.splice( 0, 1 );
3055
+ return this.getSubitem( item[ i ][ path ], tokens );
2699
3056
  }
2700
3057
 
2701
3058
  }
@@ -2722,11 +3079,12 @@ class Menubar {
2722
3079
  setButtonIcon( title, icon, callback, options = {} ) {
2723
3080
 
2724
3081
  const button = this.buttons[ title ];
2725
- if(button) {
2726
-
3082
+ if( button )
3083
+ {
2727
3084
  button.querySelector('a').className = "fa-solid" + " " + icon + " lexicon";
2728
3085
  }
2729
- else {
3086
+ else
3087
+ {
2730
3088
  let button = document.createElement('div');
2731
3089
  const disabled = options.disabled ?? false;
2732
3090
  button.className = "lexmenubutton" + (disabled ? " disabled" : "");
@@ -2736,15 +3094,21 @@ class Menubar {
2736
3094
  button.style.maxHeight = "calc(100% - 10px)";
2737
3095
  button.style.alignItems = "center";
2738
3096
 
2739
- if(options.float == "right")
3097
+ if( options.float == "right" )
3098
+ {
2740
3099
  button.right = true;
2741
- if(this.root.lastChild && this.root.lastChild.right) {
3100
+ }
3101
+
3102
+ if( this.root.lastChild && this.root.lastChild.right )
3103
+ {
2742
3104
  this.root.lastChild.before( button );
2743
3105
  }
2744
- else if(options.float == "left") {
2745
- this.root.prepend(button);
3106
+ else if( options.float == "left" )
3107
+ {
3108
+ this.root.prepend( button );
2746
3109
  }
2747
- else {
3110
+ else
3111
+ {
2748
3112
  this.root.appendChild( button );
2749
3113
  }
2750
3114
 
@@ -2764,11 +3128,12 @@ class Menubar {
2764
3128
 
2765
3129
  setButtonImage( title, src, callback, options = {} ) {
2766
3130
  const button = this.buttons[ title ];
2767
- if(button) {
2768
-
3131
+ if( button )
3132
+ {
2769
3133
  button.querySelector('a').className = "fa-solid" + " " + icon + " lexicon";
2770
3134
  }
2771
- else {
3135
+ else
3136
+ {
2772
3137
  let button = document.createElement('div');
2773
3138
  const disabled = options.disabled ?? false;
2774
3139
  button.className = "lexmenubutton" + (disabled ? " disabled" : "");
@@ -2777,15 +3142,21 @@ class Menubar {
2777
3142
  button.style.padding = "5px";
2778
3143
  button.style.alignItems = "center";
2779
3144
 
2780
- if(options.float == "right")
3145
+ if( options.float == "right" )
3146
+ {
2781
3147
  button.right = true;
2782
- if(this.root.lastChild && this.root.lastChild.right) {
3148
+ }
3149
+
3150
+ if( this.root.lastChild && this.root.lastChild.right )
3151
+ {
2783
3152
  this.root.lastChild.before( button );
2784
3153
  }
2785
- else if(options.float == "left") {
2786
- this.root.prepend(button);
3154
+ else if( options.float == "left" )
3155
+ {
3156
+ this.root.prepend( button );
2787
3157
  }
2788
- else {
3158
+ else
3159
+ {
2789
3160
  this.root.appendChild( button );
2790
3161
  }
2791
3162
 
@@ -2898,90 +3269,323 @@ LX.Menubar = Menubar;
2898
3269
 
2899
3270
  class SideBar {
2900
3271
 
2901
- constructor( options = {} ) {
3272
+ /**
3273
+ * @param {Object} options
3274
+ * filter: Add search bar to filter entries [false]
3275
+ * skipHeader: Do not use sidebar header [false]
3276
+ * headerImg: Image to be shown as avatar
3277
+ * headerIcon: Icon to be shown as avatar (from LX.ICONS)
3278
+ * headerTitle: Header title
3279
+ * headerSubtitle: Header subtitle
3280
+ * skipFooter: Do not use sidebar footer [false]
3281
+ * footerImg: Image to be shown as avatar
3282
+ * footerIcon: Icon to be shown as avatar (from LX.ICONS)
3283
+ * footerTitle: Footer title
3284
+ * footerSubtitle: Footer subtitle
3285
+ * collapsable: Sidebar can toggle between collapsed/expanded [true]
3286
+ * collapseToIcons: When Sidebar collapses, icons remains visible [true]
3287
+ * onHeaderPressed: Function to call when header is pressed
3288
+ * onFooterPressed: Function to call when footer is pressed
3289
+ */
3290
+
3291
+ constructor( options = {} ) {
2902
3292
 
2903
3293
  this.root = document.createElement( 'div' );
2904
3294
  this.root.className = "lexsidebar";
2905
3295
 
2906
- this.footer = document.createElement( 'div' );
2907
- this.footer.className = "lexsidebarfooter";
2908
- this.root.appendChild( this.footer );
3296
+ this.collapsable = options.collapsable ?? true;
3297
+ this._collapseWidth = ( options.collapseToIcons ?? true ) ? "58px" : "0px";
3298
+ this.collapsed = false;
3299
+
3300
+ this.filterString = "";
3301
+
3302
+ doAsync( () => {
3303
+
3304
+ this.root.parentElement.ogWidth = this.root.parentElement.style.width;
3305
+ this.root.parentElement.style.transition = "width 0.25s ease-out";
3306
+
3307
+ this.resizeObserver = new ResizeObserver( entries => {
3308
+ for ( const entry of entries )
3309
+ {
3310
+ this.siblingArea.setSize( [ "calc(100% - " + ( entry.contentRect.width ) + "px )", null ] );
3311
+ }
3312
+ });
3313
+
3314
+ }, 10 );
3315
+
3316
+ // This account for header, footer and all inner paddings
3317
+ let contentOffset = 32;
3318
+
3319
+ // Header
3320
+ if( !( options.skipHeader ?? false ) )
3321
+ {
3322
+ this.header = document.createElement( 'div' );
3323
+ this.header.className = "lexsidebarheader";
3324
+ this.root.appendChild( this.header );
3325
+
3326
+ this.header.addEventListener( "click", e => {
3327
+ if( this.collapsed )
3328
+ {
3329
+ e.preventDefault();
3330
+ e.stopPropagation();
3331
+ this.toggleCollapsed();
3332
+ }
3333
+ else if( options.onHeaderPressed )
3334
+ {
3335
+ options.onHeaderPressed( e );
3336
+ }
3337
+ } );
3338
+
3339
+ const avatar = document.createElement( 'span' );
3340
+ avatar.className = "lexavatar";
3341
+ this.header.appendChild( avatar );
3342
+
3343
+ if( options.headerImage )
3344
+ {
3345
+ const avatarImg = document.createElement( 'img' );
3346
+ avatarImg.src = options.headerImage;
3347
+ avatar.appendChild( avatarImg );
3348
+ }
3349
+ else if( options.headerIcon )
3350
+ {
3351
+ const avatarIcon = LX.makeIcon( options.headerIcon );
3352
+ avatar.appendChild( avatarIcon );
3353
+ }
3354
+
3355
+ // Info
3356
+ {
3357
+ const info = document.createElement( 'div' );
3358
+ this.header.appendChild( info );
3359
+
3360
+ const infoText = document.createElement( 'span' );
3361
+ infoText.innerHTML = options.headerTitle ?? "";
3362
+ info.appendChild( infoText );
3363
+
3364
+ const infoSubtext = document.createElement( 'span' );
3365
+ infoSubtext.innerHTML = options.headerSubtitle ?? "";
3366
+ info.appendChild( infoSubtext );
3367
+ }
3368
+
3369
+ if( this.collapsable )
3370
+ {
3371
+ const icon = LX.makeIcon( "Sidebar", "Toggle Sidebar" );
3372
+ this.header.appendChild( icon );
3373
+
3374
+ icon.addEventListener( "click", (e) => {
3375
+ e.preventDefault();
3376
+ e.stopPropagation();
3377
+ this.toggleCollapsed();
3378
+ } );
3379
+ }
3380
+
3381
+ contentOffset += 52;
3382
+ }
3383
+
3384
+ // Entry filter
3385
+ if( !( options.filter ?? false ) )
3386
+ {
3387
+ const panel = new Panel();
3388
+ panel.addText(null, "", (value, event) => {
3389
+ this.filterString = value;
3390
+ this.update();
3391
+ }, { placeholder: "Search...", icon: "fa-solid fa-magnifying-glass" });
3392
+ this.filter = panel.root.childNodes[ 0 ];
3393
+ this.root.appendChild( this.filter );
3394
+ contentOffset += 31;
3395
+ }
3396
+
3397
+ // Content
3398
+ {
3399
+ this.content = document.createElement( 'div' );
3400
+ this.content.className = "lexsidebarcontent";
3401
+ this.root.appendChild( this.content );
3402
+ }
3403
+
3404
+ // Footer
3405
+ if( !( options.skipFooter ?? false ) )
3406
+ {
3407
+ this.footer = document.createElement( 'div' );
3408
+ this.footer.className = "lexsidebarfooter";
3409
+ this.root.appendChild( this.footer );
3410
+
3411
+ this.footer.addEventListener( "click", e => {
3412
+ if( options.onFooterPressed )
3413
+ {
3414
+ options.onFooterPressed( e );
3415
+ }
3416
+ } );
3417
+
3418
+ const avatar = document.createElement( 'span' );
3419
+ avatar.className = "lexavatar";
3420
+ this.footer.appendChild( avatar );
3421
+
3422
+ if( options.footerImage )
3423
+ {
3424
+ const avatarImg = document.createElement( 'img' );
3425
+ avatarImg.src = options.footerImage;
3426
+ avatar.appendChild( avatarImg );
3427
+ }
3428
+ else if( options.footerIcon )
3429
+ {
3430
+ const avatarIcon = LX.makeIcon( options.footerIcon );
3431
+ avatar.appendChild( avatarIcon );
3432
+ }
3433
+
3434
+ // Info
3435
+ {
3436
+ const info = document.createElement( 'div' );
3437
+ this.footer.appendChild( info );
3438
+
3439
+ const infoText = document.createElement( 'span' );
3440
+ infoText.innerHTML = options.footerTitle ?? "";
3441
+ info.appendChild( infoText );
3442
+
3443
+ const infoSubtext = document.createElement( 'span' );
3444
+ infoSubtext.innerHTML = options.footerSubtitle ?? "";
3445
+ info.appendChild( infoSubtext );
3446
+ }
3447
+
3448
+ const icon = LX.makeIcon( "MenuArrows" );
3449
+ this.footer.appendChild( icon );
3450
+
3451
+ contentOffset += 52;
3452
+ }
3453
+
3454
+ // Set width depending on header/footer
3455
+ this.content.style.height = `calc(100% - ${ contentOffset }px)`;
2909
3456
 
2910
3457
  this.items = [ ];
3458
+ this.icons = { };
3459
+ this.groups = { };
2911
3460
  }
2912
3461
 
2913
3462
  /**
2914
- * @method add
2915
- * @param {*} options:
2916
- * callback: Function to call on each item
2917
- * bottom: Bool to set item at the bottom as helper button (not selectable)
2918
- * className: Add class to the entry DOM element
3463
+ * @method toggleCollapsed
3464
+ * @param {Boolean} force: Force collapsed state
2919
3465
  */
2920
3466
 
2921
- add( key, options = {} ) {
2922
-
2923
- if( options.constructor == Function )
2924
- options = { callback: options };
3467
+ toggleCollapsed( force ) {
2925
3468
 
2926
- let pKey = key.replace( /\s/g, '' ).replaceAll( '.', '' );
2927
-
2928
- if( this.items.findIndex( (v, i) => v.key == pKey ) > -1 )
3469
+ if( !this.collapsable )
2929
3470
  {
2930
- console.warn( `'${key}' already created in Sidebar` );
2931
3471
  return;
2932
3472
  }
2933
3473
 
2934
- let entry = document.createElement( 'div' );
2935
- entry.className = "lexsidebarentry " + ( options.className ?? "" );
2936
- entry.id = pKey;
3474
+ this.collapsed = force ?? !this.collapsed;
2937
3475
 
2938
- if( options.bottom )
3476
+ if( this.collapsed )
2939
3477
  {
2940
- this.footer.appendChild( entry );
3478
+ this.root.classList.add( "collapsing" );
3479
+ this.root.parentElement.style.width = this._collapseWidth;
2941
3480
  }
2942
3481
  else
2943
3482
  {
2944
- this.root.appendChild( entry );
3483
+ this.root.classList.remove( "collapsing" );
3484
+ this.root.classList.remove( "collapsed" );
3485
+ this.root.parentElement.style.width = this.root.parentElement.ogWidth;
2945
3486
  }
2946
3487
 
2947
- // Reappend footer in root
2948
- this.root.appendChild( this.footer );
3488
+ if( !this.resizeObserver )
3489
+ {
3490
+ throw( "Wait until ResizeObserver has been created!" );
3491
+ }
2949
3492
 
2950
- let button = document.createElement( 'button' );
2951
- button.innerHTML = "<i class='"+ (options.icon ?? "") + "'></i>";
2952
- entry.appendChild( button );
3493
+ this.resizeObserver.observe( this.root.parentElement );
2953
3494
 
2954
- let desc = document.createElement( 'span' );
2955
- desc.className = 'lexsidebarentrydesc';
2956
- desc.innerHTML = key;
2957
- entry.appendChild( desc );
3495
+ doAsync( () => {
2958
3496
 
2959
- button.addEventListener("mouseenter", () => {
2960
- setTimeout( () => {
2961
- desc.style.display = "unset";
2962
- }, 100 );
2963
- });
3497
+ this.root.classList.toggle( "collapsed", this.collapsed );
3498
+ this.resizeObserver.unobserve( this.root.parentElement );
2964
3499
 
2965
- button.addEventListener("mouseleave", () => {
2966
- setTimeout( () => {
2967
- desc.style.display = "none";
2968
- }, 100 );
2969
- });
3500
+ }, 250 );
3501
+ }
2970
3502
 
2971
- entry.addEventListener("click", () => {
3503
+ /**
3504
+ * @method separator
3505
+ */
3506
+
3507
+ separator() {
2972
3508
 
2973
- const f = options.callback;
2974
- if( f ) f.call( this, key, entry );
3509
+ this.currentGroup = null;
2975
3510
 
2976
- // Manage selected
2977
- if( !options.bottom )
3511
+ this.add( "" );
3512
+ }
3513
+
3514
+ /**
3515
+ * @method group
3516
+ * @param {String} groupName
3517
+ * @param {Object} action: { icon, callback }
3518
+ */
3519
+
3520
+ group( groupName, action ) {
3521
+
3522
+ this.currentGroup = groupName;
3523
+
3524
+ this.groups[ groupName ] = action;
3525
+ }
3526
+
3527
+ /**
3528
+ * @method add
3529
+ * @param {String} path
3530
+ * @param {Object} options:
3531
+ * callback: Function to call on each item
3532
+ * icon: Entry icon
3533
+ * collapsable: Add entry as a collapsable section
3534
+ * className: Add class to the entry DOM element
3535
+ */
3536
+
3537
+ add( path, options = {} ) {
3538
+
3539
+ if( options.constructor == Function )
3540
+ {
3541
+ options = { callback: options };
3542
+ }
3543
+
3544
+ // Process path
3545
+ const tokens = path.split( "/" );
3546
+
3547
+ // Assign icons and shortcuts to last token in path
3548
+ const lastPath = tokens[tokens.length - 1];
3549
+ this.icons[ lastPath ] = options.icon;
3550
+
3551
+ let idx = 0;
3552
+
3553
+ const _insertEntry = ( token, list ) => {
3554
+
3555
+ if( token == undefined )
2978
3556
  {
2979
- this.root.querySelectorAll(".lexsidebarentry").forEach( e => e.classList.remove( 'selected' ) );
2980
- entry.classList.add( "selected" );
3557
+ return;
2981
3558
  }
2982
- });
2983
3559
 
2984
- this.items.push( { name: pKey, domEl: entry, callback: options.callback } );
3560
+ let found = null;
3561
+ list.forEach( o => {
3562
+ const keys = Object.keys( o );
3563
+ const key = keys.find( t => t == token );
3564
+ if( key ) found = o[ key ];
3565
+ } );
3566
+
3567
+ if( found )
3568
+ {
3569
+ _insertEntry( tokens[ idx++ ], found );
3570
+ }
3571
+ else
3572
+ {
3573
+ let item = {};
3574
+ item[ token ] = [];
3575
+ const nextToken = tokens[ idx++ ];
3576
+ // Check if last token -> add callback
3577
+ if( !nextToken )
3578
+ {
3579
+ item[ 'callback' ] = options.callback;
3580
+ item[ 'group' ] = this.currentGroup;
3581
+ item[ 'options' ] = options;
3582
+ }
3583
+ list.push( item );
3584
+ _insertEntry( nextToken, item[ token ] );
3585
+ }
3586
+ };
3587
+
3588
+ _insertEntry( tokens[idx++], this.items );
2985
3589
  }
2986
3590
 
2987
3591
  /**
@@ -2998,7 +3602,267 @@ class SideBar {
2998
3602
  if( !entry )
2999
3603
  return;
3000
3604
 
3001
- entry.domEl.click();
3605
+ entry.dom.click();
3606
+ }
3607
+
3608
+ update() {
3609
+
3610
+ // Reset first
3611
+
3612
+ this.content.innerHTML = "";
3613
+
3614
+ for( let item of this.items )
3615
+ {
3616
+ delete item.dom;
3617
+ }
3618
+
3619
+ for( let item of this.items )
3620
+ {
3621
+ const options = item.options ?? { };
3622
+
3623
+ // Item already created
3624
+ if( item.dom )
3625
+ {
3626
+ continue;
3627
+ }
3628
+
3629
+ let key = Object.keys( item )[ 0 ];
3630
+
3631
+ if( this.filterString.length && !key.toLowerCase().includes( this.filterString.toLowerCase() ) )
3632
+ {
3633
+ continue;
3634
+ }
3635
+
3636
+ let pKey = key.replace( /\s/g, '' ).replaceAll( '.', '' );
3637
+ let currentGroup = null;
3638
+
3639
+ let entry = document.createElement( 'div' );
3640
+ entry.className = "lexsidebarentry " + ( options.className ?? "" );
3641
+ entry.id = item.name = pKey;
3642
+
3643
+ if( item.group )
3644
+ {
3645
+ const pGroupKey = item.group.replace( /\s/g, '' ).replaceAll( '.', '' );
3646
+ currentGroup = this.content.querySelector( "#" + pGroupKey );
3647
+
3648
+ if( !currentGroup )
3649
+ {
3650
+ currentGroup = document.createElement( 'div' );
3651
+ currentGroup.id = pGroupKey;
3652
+ currentGroup.className = "lexsidebargroup";
3653
+ this.content.appendChild( currentGroup );
3654
+
3655
+ let groupEntry = document.createElement( 'div' );
3656
+ groupEntry.className = "lexsidebargrouptitle";
3657
+ currentGroup.appendChild( groupEntry );
3658
+
3659
+ let groupLabel = document.createElement( 'div' );
3660
+ groupLabel.innerHTML = item.group;
3661
+ groupEntry.appendChild( groupLabel );
3662
+
3663
+ if( this.groups[ item.group ] != null )
3664
+ {
3665
+ let groupAction = document.createElement( 'a' );
3666
+ groupAction.className = ( this.groups[ item.group ].icon ?? "" ) + " lexicon";
3667
+ groupEntry.appendChild( groupAction );
3668
+ groupAction.addEventListener( "click", (e) => {
3669
+ if( this.groups[ item.group ].callback )
3670
+ {
3671
+ this.groups[ item.group ].callback( item.group, e );
3672
+ }
3673
+ } );
3674
+ }
3675
+
3676
+ }
3677
+ else if( !currentGroup.classList.contains( "lexsidebargroup" ) )
3678
+ {
3679
+ throw( "Bad id: " + item.group );
3680
+ }
3681
+ }
3682
+
3683
+ if( pKey == "" )
3684
+ {
3685
+ let separatorDom = document.createElement( 'div' );
3686
+ separatorDom.className = "lexsidebarseparator";
3687
+ this.content.appendChild( separatorDom );
3688
+ continue;
3689
+ }
3690
+
3691
+ if( this.collapseContainer )
3692
+ {
3693
+ this.collapseContainer.appendChild( entry );
3694
+
3695
+ this.collapseQueue--;
3696
+ if( !this.collapseQueue )
3697
+ {
3698
+ delete this.collapseContainer;
3699
+ }
3700
+ }
3701
+ else if( currentGroup )
3702
+ {
3703
+ currentGroup.appendChild( entry );
3704
+ }
3705
+ else
3706
+ {
3707
+ this.content.appendChild( entry );
3708
+ }
3709
+
3710
+ let itemDom = document.createElement( 'div' );
3711
+ entry.appendChild( itemDom );
3712
+ item.dom = entry;
3713
+
3714
+ if( options.type == "checkbox" )
3715
+ {
3716
+ item.value = options.value ?? false;
3717
+ const panel = new Panel();
3718
+ item.checkbox = panel.addCheckbox(null, item.value, (value, event) => {
3719
+ event.preventDefault();
3720
+ event.stopPropagation();
3721
+ const f = options.callback;
3722
+ item.value = value;
3723
+ if( f ) f.call( this, key, value, event );
3724
+ }, { label: key, signal: ( "@checkbox_" + key ) });
3725
+ itemDom.appendChild( panel.root.childNodes[ 0 ] );
3726
+ }
3727
+ else
3728
+ {
3729
+ if( options.icon )
3730
+ {
3731
+ let itemIcon = document.createElement( 'i' );
3732
+ itemIcon.className = options.icon;
3733
+ itemDom.appendChild( itemIcon );
3734
+ }
3735
+
3736
+ let itemName = document.createElement( 'a' );
3737
+ itemName.innerHTML = key;
3738
+ itemDom.appendChild( itemName );
3739
+ }
3740
+
3741
+ entry.addEventListener("click", ( e ) => {
3742
+ if( e.target && e.target.classList.contains( "lexcheckbox" ) )
3743
+ {
3744
+ return;
3745
+ }
3746
+
3747
+ if( options.collapsable )
3748
+ {
3749
+ itemDom.querySelector( ".collapser" ).click();
3750
+ }
3751
+ else
3752
+ {
3753
+ const f = options.callback;
3754
+ if( f ) f.call( this, key, item.value, e );
3755
+
3756
+ if( item.checkbox )
3757
+ {
3758
+ item.value = !item.value;
3759
+ item.checkbox.set( item.value, true );
3760
+ }
3761
+ }
3762
+
3763
+ // Manage selected
3764
+ this.root.querySelectorAll(".lexsidebarentry").forEach( e => e.classList.remove( 'selected' ) );
3765
+ entry.classList.add( "selected" );
3766
+ });
3767
+
3768
+ if( options.action )
3769
+ {
3770
+ const actionIcon = LX.makeIcon( options.action.icon ?? "MoreHorizontal", options.action.name );
3771
+ itemDom.appendChild( actionIcon );
3772
+
3773
+ actionIcon.addEventListener( "click", (e) => {
3774
+ e.preventDefault();
3775
+ e.stopImmediatePropagation();
3776
+ const f = options.action.callback;
3777
+ if( f ) f.call( this, key, e );
3778
+ } );
3779
+ }
3780
+ else if( options.collapsable )
3781
+ {
3782
+ const collapsableContent = document.createElement( 'div' );
3783
+ Object.assign( collapsableContent.style, { width: "100%", display: "none" } );
3784
+ LX.makeCollapsible( itemDom, collapsableContent, currentGroup ?? this.content );
3785
+ this.collapseQueue = options.collapsable;
3786
+ this.collapseContainer = collapsableContent;
3787
+ }
3788
+
3789
+ let desc = document.createElement( 'span' );
3790
+ desc.className = 'lexsidebarentrydesc';
3791
+ desc.innerHTML = key;
3792
+ entry.appendChild( desc );
3793
+
3794
+ itemDom.addEventListener("mouseenter", () => {
3795
+ setTimeout( () => {
3796
+ desc.style.display = "unset";
3797
+ }, 150 );
3798
+ });
3799
+
3800
+ itemDom.addEventListener("mouseleave", () => {
3801
+ setTimeout( () => {
3802
+ desc.style.display = "none";
3803
+ }, 150 );
3804
+ });
3805
+
3806
+ // Subentries
3807
+ if( !item[ key ].length )
3808
+ {
3809
+ continue;
3810
+ }
3811
+
3812
+ let subentryContainer = document.createElement( 'div' );
3813
+ subentryContainer.className = "lexsidebarsubentrycontainer";
3814
+
3815
+ if( currentGroup )
3816
+ {
3817
+ currentGroup.appendChild( subentryContainer );
3818
+ }
3819
+ else
3820
+ {
3821
+ this.content.appendChild( subentryContainer );
3822
+ }
3823
+
3824
+ for( let i = 0; i < item[ key ].length; ++i )
3825
+ {
3826
+ const subitem = item[ key ][ i ];
3827
+ const suboptions = subitem.options ?? {};
3828
+ const subkey = Object.keys( subitem )[ 0 ];
3829
+
3830
+ if( this.filterString.length && !subkey.toLowerCase().includes( this.filterString.toLowerCase() ) )
3831
+ {
3832
+ continue;
3833
+ }
3834
+
3835
+ let subentry = document.createElement( 'div' );
3836
+ subentry.innerHTML = `<span>${ subkey }</span>`;
3837
+
3838
+ if( suboptions.action )
3839
+ {
3840
+ const actionIcon = LX.makeIcon( suboptions.action.icon ?? "MoreHorizontal", suboptions.action.name );
3841
+ subentry.appendChild( actionIcon );
3842
+
3843
+ actionIcon.addEventListener( "click", (e) => {
3844
+ e.preventDefault();
3845
+ e.stopImmediatePropagation();
3846
+ const f = suboptions.action.callback;
3847
+ if( f ) f.call( this, subkey, e );
3848
+ } );
3849
+ }
3850
+
3851
+ subentry.className = "lexsidebarentry";
3852
+ subentry.id = subkey;
3853
+ subentryContainer.appendChild( subentry );
3854
+
3855
+ subentry.addEventListener("click", (e) => {
3856
+
3857
+ const f = suboptions.callback;
3858
+ if( f ) f.call( this, subkey, subentry, e );
3859
+
3860
+ // Manage selected
3861
+ this.root.querySelectorAll(".lexsidebarentry").forEach( e => e.classList.remove( 'selected' ) );
3862
+ entry.classList.add( "selected" );
3863
+ });
3864
+ }
3865
+ }
3002
3866
  }
3003
3867
  };
3004
3868
 
@@ -3017,30 +3881,32 @@ class Widget {
3017
3881
  static DROPDOWN = 4;
3018
3882
  static CHECKBOX = 5;
3019
3883
  static TOGGLE = 6;
3020
- static COLOR = 7;
3021
- static NUMBER = 8;
3022
- static TITLE = 9;
3023
- static VECTOR = 10;
3024
- static TREE = 11;
3025
- static PROGRESS = 12;
3026
- static FILE = 13;
3027
- static LAYERS = 14;
3028
- static ARRAY = 15;
3029
- static LIST = 16;
3030
- static TAGS = 17;
3031
- static CURVE = 18;
3032
- static CARD = 19;
3033
- static IMAGE = 20;
3034
- static CONTENT = 21;
3035
- static CUSTOM = 22;
3036
- static SEPARATOR = 23;
3037
- static KNOB = 24;
3038
- static SIZE = 25;
3039
- static PAD = 26;
3040
- static FORM = 27;
3041
- static DIAL = 28;
3042
- static COUNTER = 29;
3043
- static TABLE = 30;
3884
+ static RADIO = 7;
3885
+ static COLOR = 8;
3886
+ static RANGE = 9;
3887
+ static NUMBER = 10;
3888
+ static TITLE = 11;
3889
+ static VECTOR = 12;
3890
+ static TREE = 13;
3891
+ static PROGRESS = 14;
3892
+ static FILE = 15;
3893
+ static LAYERS = 16;
3894
+ static ARRAY = 17;
3895
+ static LIST = 18;
3896
+ static TAGS = 19;
3897
+ static CURVE = 20;
3898
+ static CARD = 21;
3899
+ static IMAGE = 22;
3900
+ static CONTENT = 23;
3901
+ static CUSTOM = 24;
3902
+ static SEPARATOR = 25;
3903
+ static KNOB = 26;
3904
+ static SIZE = 27;
3905
+ static PAD = 28;
3906
+ static FORM = 29;
3907
+ static DIAL = 30;
3908
+ static COUNTER = 31;
3909
+ static TABLE = 32;
3044
3910
 
3045
3911
  static NO_CONTEXT_TYPES = [
3046
3912
  Widget.BUTTON,
@@ -3068,7 +3934,9 @@ class Widget {
3068
3934
  set( value, skipCallback = false, signalName = "" ) {
3069
3935
 
3070
3936
  if( this.onSetValue )
3937
+ {
3071
3938
  return this.onSetValue( value, skipCallback );
3939
+ }
3072
3940
 
3073
3941
  console.warn("Can't set value of " + this.typeName());
3074
3942
  }
@@ -3107,14 +3975,17 @@ class Widget {
3107
3975
 
3108
3976
  typeName() {
3109
3977
 
3110
- switch( this.type ) {
3978
+ switch( this.type )
3979
+ {
3111
3980
  case Widget.TEXT: return "Text";
3112
3981
  case Widget.TEXTAREA: return "TextArea";
3113
3982
  case Widget.BUTTON: return "Button";
3114
3983
  case Widget.DROPDOWN: return "Dropdown";
3115
3984
  case Widget.CHECKBOX: return "Checkbox";
3116
3985
  case Widget.TOGGLE: return "Toggle";
3986
+ case Widget.RADIO: return "Radio";
3117
3987
  case Widget.COLOR: return "Color";
3988
+ case Widget.RANGE: return "Range";
3118
3989
  case Widget.NUMBER: return "Number";
3119
3990
  case Widget.VECTOR: return "Vector";
3120
3991
  case Widget.TREE: return "Tree";
@@ -3192,11 +4063,12 @@ function ADD_CUSTOM_WIDGET( custom_widget_name, options = {} )
3192
4063
  buttonName += "<a class='fa-solid " + (instance ? "fa-bars-staggered" : " ") + " menu' style='float:right; width:5%;'></a>";
3193
4064
 
3194
4065
  let buttonEl = this.addButton(null, buttonName, (value, event) => {
3195
-
3196
- if( instance ) {
4066
+ if( instance )
4067
+ {
3197
4068
  element.querySelector(".lexcustomitems").toggleAttribute('hidden');
3198
4069
  }
3199
- else {
4070
+ else
4071
+ {
3200
4072
  addContextMenu(null, event, c => {
3201
4073
  c.add("New " + custom_widget_name, () => {
3202
4074
  instance = {};
@@ -3383,16 +4255,20 @@ class NodeTree {
3383
4255
 
3384
4256
  // Add or remove
3385
4257
  const idx = this.selected.indexOf( node );
3386
- if( idx > -1 ) {
4258
+ if( idx > -1 )
4259
+ {
3387
4260
  item.classList.remove( 'selected' );
3388
4261
  this.selected.splice( idx, 1 );
3389
- }else {
4262
+ }
4263
+ else
4264
+ {
3390
4265
  item.classList.add( 'selected' );
3391
4266
  this.selected.push( node );
3392
4267
  }
3393
4268
 
3394
4269
  // Only Show children...
3395
- if( isParent && node.id.length > 1 /* Strange case... */) {
4270
+ if( isParent && node.id.length > 1 /* Strange case... */)
4271
+ {
3396
4272
  node.closed = false;
3397
4273
  if( that.onevent )
3398
4274
  {
@@ -3430,7 +4306,7 @@ class NodeTree {
3430
4306
 
3431
4307
  e.preventDefault();
3432
4308
 
3433
- if( that.onevent )
4309
+ if( !that.onevent )
3434
4310
  {
3435
4311
  return;
3436
4312
  }
@@ -3486,7 +4362,8 @@ class NodeTree {
3486
4362
  return;
3487
4363
  }
3488
4364
 
3489
- if( that.onevent ) {
4365
+ if( that.onevent )
4366
+ {
3490
4367
  const event = new TreeEvent( TreeEvent.NODE_DELETED, node, e );
3491
4368
  that.onevent( event );
3492
4369
  }
@@ -3513,7 +4390,8 @@ class NodeTree {
3513
4390
  if( e.key == "Delete" )
3514
4391
  {
3515
4392
  // Send event now so we have the info in selected array..
3516
- if( that.onevent ) {
4393
+ if( that.onevent )
4394
+ {
3517
4395
  const event = new TreeEvent( TreeEvent.NODE_DELETED, this.selected.length > 1 ? this.selected : node, e );
3518
4396
  event.multiple = this.selected.length > 1;
3519
4397
  that.onevent( event );
@@ -3544,22 +4422,24 @@ class NodeTree {
3544
4422
 
3545
4423
  // Node rename
3546
4424
 
3547
- let name_input = document.createElement('input');
3548
- name_input.toggleAttribute('hidden', !node.rename);
3549
- name_input.value = node.id;
3550
- item.appendChild(name_input);
4425
+ const nameInput = document.createElement( "input" );
4426
+ nameInput.toggleAttribute( "hidden", !node.rename );
4427
+ nameInput.value = node.id;
4428
+ item.appendChild(nameInput);
3551
4429
 
3552
- if(node.rename) {
4430
+ if( node.rename )
4431
+ {
3553
4432
  item.classList.add('selected');
3554
- name_input.focus();
4433
+ nameInput.focus();
3555
4434
  }
3556
4435
 
3557
- name_input.addEventListener("keyup", function(e){
3558
- if(e.key == 'Enter') {
3559
-
4436
+ nameInput.addEventListener("keyup", function(e){
4437
+ if( e.key == "Enter" )
4438
+ {
3560
4439
  this.value = this.value.replace(/\s/g, '_');
3561
4440
 
3562
- if(that.onevent) {
4441
+ if( that.onevent )
4442
+ {
3563
4443
  const event = new TreeEvent(TreeEvent.NODE_RENAMED, node, this.value);
3564
4444
  that.onevent( event );
3565
4445
  }
@@ -3569,18 +4449,20 @@ class NodeTree {
3569
4449
  that.frefresh( node.id );
3570
4450
  list.querySelector("#" + node.id).classList.add('selected');
3571
4451
  }
3572
- if(e.key == 'Escape') {
4452
+ else if(e.key == "Escape")
4453
+ {
3573
4454
  delete node.rename;
3574
4455
  that.frefresh( node.id );
3575
4456
  }
3576
4457
  });
3577
4458
 
3578
- name_input.addEventListener("blur", function(e){
4459
+ nameInput.addEventListener("blur", function(e){
3579
4460
  delete node.rename;
3580
4461
  that.refresh();
3581
4462
  });
3582
4463
 
3583
- if(this.options.draggable ?? true) {
4464
+ if( this.options.draggable ?? true )
4465
+ {
3584
4466
  // Drag nodes
3585
4467
  if(parent) // Root doesn't move!
3586
4468
  {
@@ -3606,29 +4488,32 @@ class NodeTree {
3606
4488
  return;
3607
4489
  let target = node;
3608
4490
  // Can't drop to same node
3609
- if(dragged.id == target.id) {
4491
+ if( dragged.id == target.id )
4492
+ {
3610
4493
  console.warn("Cannot parent node to itself!");
3611
4494
  return;
3612
4495
  }
3613
4496
 
3614
4497
  // Can't drop to child node
3615
- const isChild = function(new_parent, node) {
4498
+ const isChild = function( newParent, node ) {
3616
4499
  var result = false;
3617
- for( var c of node.children ) {
3618
- if( c.id == new_parent.id )
3619
- return true;
3620
- result |= isChild(new_parent, c);
4500
+ for( var c of node.children )
4501
+ {
4502
+ if( c.id == newParent.id ) return true;
4503
+ result |= isChild( newParent, c );
3621
4504
  }
3622
4505
  return result;
3623
4506
  };
3624
4507
 
3625
- if(isChild(target, dragged)) {
4508
+ if( isChild( target, dragged ))
4509
+ {
3626
4510
  console.warn("Cannot parent node to a current child!");
3627
4511
  return;
3628
4512
  }
3629
4513
 
3630
4514
  // Trigger node dragger event
3631
- if(that.onevent) {
4515
+ if( that.onevent )
4516
+ {
3632
4517
  const event = new TreeEvent(TreeEvent.NODE_DRAGGED, dragged, target);
3633
4518
  that.onevent( event );
3634
4519
  }
@@ -3644,7 +4529,8 @@ class NodeTree {
3644
4529
  let handled = false;
3645
4530
 
3646
4531
  // Show/hide children
3647
- if(isParent) {
4532
+ if( isParent )
4533
+ {
3648
4534
  item.querySelector('a.hierarchy').addEventListener("click", function(e) {
3649
4535
 
3650
4536
  handled = true;
@@ -3652,7 +4538,8 @@ class NodeTree {
3652
4538
  e.stopPropagation();
3653
4539
 
3654
4540
  node.closed = !node.closed;
3655
- if(that.onevent) {
4541
+ if( that.onevent )
4542
+ {
3656
4543
  const event = new TreeEvent(TreeEvent.NODE_CARETCHANGED, node, node.closed);
3657
4544
  that.onevent( event );
3658
4545
  }
@@ -3672,7 +4559,8 @@ class NodeTree {
3672
4559
  node.visible = node.visible === undefined ? false : !node.visible;
3673
4560
  this.className = "itemicon fa-solid fa-eye" + (!node.visible ? "-slash" : "");
3674
4561
  // Trigger visibility event
3675
- if(that.onevent) {
4562
+ if( that.onevent )
4563
+ {
3676
4564
  const event = new TreeEvent(TreeEvent.NODE_VISIBILITY, node, node.visible);
3677
4565
  that.onevent( event );
3678
4566
  }
@@ -3681,9 +4569,10 @@ class NodeTree {
3681
4569
  item.appendChild(visibility);
3682
4570
  }
3683
4571
 
3684
- if(node.actions)
4572
+ if( node.actions )
3685
4573
  {
3686
- for(var i = 0; i < node.actions.length; ++i) {
4574
+ for( var i = 0; i < node.actions.length; ++i )
4575
+ {
3687
4576
  let a = node.actions[i];
3688
4577
  var actionEl = document.createElement('a');
3689
4578
  actionEl.className = "itemicon " + a.icon;
@@ -3696,20 +4585,25 @@ class NodeTree {
3696
4585
  }
3697
4586
  }
3698
4587
 
3699
- if(selectedId != undefined && node.id == selectedId) {
3700
- this.selected = [node];
4588
+ if( selectedId != undefined && node.id == selectedId )
4589
+ {
4590
+ this.selected = [ node ];
3701
4591
  item.click();
3702
4592
  }
3703
4593
 
3704
- if(node.closed )
4594
+ if( node.closed )
4595
+ {
3705
4596
  return;
4597
+ }
3706
4598
 
3707
4599
  for( var i = 0; i < node.children.length; ++i )
3708
4600
  {
3709
- let child = node.children[i];
4601
+ let child = node.children[ i ];
3710
4602
 
3711
- if( this.options.onlyFolders && child.type != 'folder')
4603
+ if( this.options.onlyFolders && child.type != 'folder' )
4604
+ {
3712
4605
  continue;
4606
+ }
3713
4607
 
3714
4608
  this._create_item( node, child, level + 1 );
3715
4609
  }
@@ -3748,12 +4642,12 @@ class Panel {
3748
4642
  * style: CSS Style object to be applied to the panel
3749
4643
  */
3750
4644
 
3751
- constructor( options = {} ) {
4645
+ constructor( options = {} ) {
3752
4646
  var root = document.createElement('div');
3753
4647
  root.className = "lexpanel";
3754
- if(options.id)
4648
+ if( options.id )
3755
4649
  root.id = options.id;
3756
- if(options.className)
4650
+ if( options.className )
3757
4651
  root.className += " " + options.className;
3758
4652
 
3759
4653
  root.style.width = options.width || "calc( 100% - 6px )";
@@ -3830,23 +4724,31 @@ class Panel {
3830
4724
  this.branches = [];
3831
4725
  this.current_branch = null;
3832
4726
 
3833
- for(let w in this.widgets) {
3834
- if(this.widgets[w].options && this.widgets[w].options.signal) {
4727
+ for( let w in this.widgets )
4728
+ {
4729
+ if( this.widgets[w].options && this.widgets[w].options.signal )
4730
+ {
3835
4731
  const signal = this.widgets[w].options.signal;
3836
- for(let i = 0; i < LX.signals[signal].length; i++) {
3837
- if(LX.signals[signal][i] == this.widgets[w]) {
4732
+ for( let i = 0; i < LX.signals[signal].length; i++ )
4733
+ {
4734
+ if( LX.signals[signal][i] == this.widgets[w] )
4735
+ {
3838
4736
  LX.signals[signal] = [...LX.signals[signal].slice(0, i), ...LX.signals[signal].slice(i+1)];
3839
4737
  }
3840
4738
  }
3841
4739
  }
3842
4740
  }
3843
4741
 
3844
- if(this.signals) {
3845
- for(let w = 0; w < this.signals.length; w++) {
4742
+ if( this.signals )
4743
+ {
4744
+ for( let w = 0; w < this.signals.length; w++ )
4745
+ {
3846
4746
  let widget = Object.values(this.signals[w])[0];
3847
4747
  let signal = widget.options.signal;
3848
- for(let i = 0; i < LX.signals[signal].length; i++) {
3849
- if(LX.signals[signal][i] == widget) {
4748
+ for( let i = 0; i < LX.signals[signal].length; i++ )
4749
+ {
4750
+ if( LX.signals[signal][i] == widget )
4751
+ {
3850
4752
  LX.signals[signal] = [...LX.signals[signal].slice(0, i), ...LX.signals[signal].slice(i+1)];
3851
4753
  }
3852
4754
  }
@@ -3884,10 +4786,12 @@ class Panel {
3884
4786
 
3885
4787
  this._inline_widgets_left = -1;
3886
4788
 
3887
- if(!this._inlineContainer) {
4789
+ if( !this._inlineContainer )
4790
+ {
3888
4791
  this._inlineContainer = document.createElement('div');
3889
4792
  this._inlineContainer.className = "lexinlinewidgets";
3890
- if(justifyContent)
4793
+
4794
+ if( justifyContent )
3891
4795
  {
3892
4796
  this._inlineContainer.style.justifyContent = justifyContent;
3893
4797
  }
@@ -3901,7 +4805,7 @@ class Panel {
3901
4805
  if(is_pair)
3902
4806
  {
3903
4807
  // eg. an array, inline items appended later to
3904
- if(this._inline_queued_container)
4808
+ if( this._inline_queued_container)
3905
4809
  this._inlineContainer.appendChild( item[0] );
3906
4810
  // eg. a dropdown, item is appended to parent, not to inline cont.
3907
4811
  else
@@ -3913,7 +4817,7 @@ class Panel {
3913
4817
 
3914
4818
  if(!this._inline_queued_container)
3915
4819
  {
3916
- if(this.current_branch)
4820
+ if( this.current_branch)
3917
4821
  this.current_branch.content.appendChild( this._inlineContainer );
3918
4822
  else
3919
4823
  this.root.appendChild( this._inlineContainer );
@@ -3952,7 +4856,7 @@ class Panel {
3952
4856
  this.current_branch = branch;
3953
4857
 
3954
4858
  // Append to panel
3955
- if(this.branches.length == 0)
4859
+ if( this.branches.length == 0)
3956
4860
  branch.root.classList.add('first');
3957
4861
 
3958
4862
  // This is the last!
@@ -3963,7 +4867,8 @@ class Panel {
3963
4867
  this.root.appendChild( branch.root );
3964
4868
 
3965
4869
  // Add widget filter
3966
- if(options.filter) {
4870
+ if( options.filter )
4871
+ {
3967
4872
  this._addFilter( options.filter, {callback: this._searchWidgets.bind(this, branch.name)} );
3968
4873
  }
3969
4874
 
@@ -4080,14 +4985,18 @@ class Panel {
4080
4985
  element.jsInstance = widget;
4081
4986
 
4082
4987
  const insert_widget = el => {
4083
- if(options.container)
4084
- options.container.appendChild(el);
4085
- else if(!this.queuedContainer) {
4086
-
4087
- if(this.current_branch)
4988
+ if( options.container )
4989
+ {
4990
+ options.container.appendChild( el );
4991
+ }
4992
+ else if( !this.queuedContainer )
4993
+ {
4994
+ if( this.current_branch )
4088
4995
  {
4089
- if(!options.skipWidget)
4996
+ if( !options.skipWidget )
4997
+ {
4090
4998
  this.current_branch.widgets.push( widget );
4999
+ }
4091
5000
  this.current_branch.content.appendChild( el );
4092
5001
  }
4093
5002
  else
@@ -4097,40 +5006,47 @@ class Panel {
4097
5006
  }
4098
5007
  }
4099
5008
  // Append content to queued tab container
4100
- else {
5009
+ else
5010
+ {
4101
5011
  this.queuedContainer.appendChild( el );
4102
5012
  }
4103
5013
  };
4104
5014
 
4105
5015
  const store_widget = el => {
4106
5016
 
4107
- if(!this.queuedContainer) {
5017
+ if( !this.queuedContainer )
5018
+ {
4108
5019
  this._inlineWidgets.push( el );
4109
5020
  }
4110
5021
  // Append content to queued tab container
4111
- else {
5022
+ else
5023
+ {
4112
5024
  this._inlineWidgets.push( [el, this.queuedContainer] );
4113
5025
  }
4114
5026
  };
4115
5027
 
4116
5028
  // Process inline widgets
4117
- if(this._inline_widgets_left > 0 && !options.skipInlineCount)
5029
+ if( this._inline_widgets_left > 0 && !options.skipInlineCount )
4118
5030
  {
4119
- if(!this._inlineWidgets) {
5031
+ if( !this._inlineWidgets )
5032
+ {
4120
5033
  this._inlineWidgets = [];
4121
5034
  }
4122
5035
 
4123
5036
  // Store widget and its container
4124
- store_widget(element);
5037
+ store_widget( element );
4125
5038
 
4126
5039
  this._inline_widgets_left--;
4127
5040
 
4128
5041
  // Last widget
4129
- if(!this._inline_widgets_left) {
5042
+ if( !this._inline_widgets_left )
5043
+ {
4130
5044
  this.endLine();
4131
5045
  }
4132
- }else {
4133
- insert_widget(element);
5046
+ }
5047
+ else
5048
+ {
5049
+ insert_widget( element );
4134
5050
  }
4135
5051
 
4136
5052
  return widget;
@@ -4167,15 +5083,20 @@ class Panel {
4167
5083
 
4168
5084
  _searchWidgets(branchName, value) {
4169
5085
 
4170
- for( let b of this.branches ) {
4171
-
4172
- if(b.name !== branchName)
5086
+ for( let b of this.branches )
5087
+ {
5088
+ if( b.name !== branchName )
5089
+ {
4173
5090
  continue;
5091
+ }
4174
5092
 
4175
5093
  // remove all widgets
4176
- for( let w of b.widgets ) {
4177
- if(w.domEl.classList.contains('lexfilter'))
5094
+ for( let w of b.widgets )
5095
+ {
5096
+ if( w.domEl.classList.contains('lexfilter') )
5097
+ {
4178
5098
  continue;
5099
+ }
4179
5100
  w.domEl.remove();
4180
5101
  }
4181
5102
 
@@ -4185,9 +5106,9 @@ class Panel {
4185
5106
  const emptyFilter = !value.length;
4186
5107
 
4187
5108
  // add widgets
4188
- for( let w of b.widgets ) {
4189
-
4190
- if(!emptyFilter)
5109
+ for( let w of b.widgets )
5110
+ {
5111
+ if( !emptyFilter )
4191
5112
  {
4192
5113
  if(!w.name) continue;
4193
5114
  const filterWord = value.toLowerCase();
@@ -4281,7 +5202,7 @@ class Panel {
4281
5202
 
4282
5203
  clearQueue() {
4283
5204
 
4284
- if(this._queue && this._queue.length)
5205
+ if( this._queue && this._queue.length)
4285
5206
  {
4286
5207
  this.queuedContainer = this._queue.pop();
4287
5208
  return;
@@ -4441,7 +5362,7 @@ class Panel {
4441
5362
 
4442
5363
  var resolve = ( function( val, event ) {
4443
5364
 
4444
- if( !widget.valid() )
5365
+ if( !widget.valid() || ( this._lastValueTriggered == val ) )
4445
5366
  {
4446
5367
  return;
4447
5368
  }
@@ -4454,6 +5375,8 @@ class Panel {
4454
5375
  this._trigger( new IEvent( name, val, event ), callback );
4455
5376
  }
4456
5377
 
5378
+ this._lastValueTriggered = val;
5379
+
4457
5380
  }).bind( this );
4458
5381
 
4459
5382
  const trigger = options.trigger ?? 'default';
@@ -4507,7 +5430,8 @@ class Panel {
4507
5430
  element.appendChild( container );
4508
5431
 
4509
5432
  // Remove branch padding and margins
4510
- if( !widget.name ) {
5433
+ if( !widget.name )
5434
+ {
4511
5435
  element.className += " noname";
4512
5436
  container.style.width = "100%";
4513
5437
  }
@@ -4607,7 +5531,8 @@ class Panel {
4607
5531
  element.appendChild(container);
4608
5532
 
4609
5533
  // Remove branch padding and margins
4610
- if(!widget.name) {
5534
+ if( !widget.name )
5535
+ {
4611
5536
  element.className += " noname";
4612
5537
  container.style.width = "100%";
4613
5538
  }
@@ -4712,11 +5637,12 @@ class Panel {
4712
5637
  /**
4713
5638
  * @method addComboButtons
4714
5639
  * @param {String} name Widget name
4715
- * @param {Array} values Each of the {value, callback} items
5640
+ * @param {Array} values Each of the {value, callback, selected, disabled} items
4716
5641
  * @param {*} options:
4717
5642
  * float: Justify content (left, center, right) [center]
4718
- * selected: Selected item by default by value
5643
+ * @legacy selected: Selected item by default by value
4719
5644
  * noSelection: Buttons can be clicked, but they are not selectable
5645
+ * toggle: Buttons can be toggled insted of selecting only one
4720
5646
  */
4721
5647
 
4722
5648
  addComboButtons( name, values, options = {} ) {
@@ -4738,7 +5664,8 @@ class Panel {
4738
5664
  let buttonsBox = document.createElement('div');
4739
5665
  buttonsBox.className = "lexcombobuttonsbox ";
4740
5666
 
4741
- let shouldSelect = !( options.noSelection ?? false );
5667
+ const shouldSelect = !( options.noSelection ?? false );
5668
+ const shouldToggle = shouldSelect && ( options.toggle ?? false );
4742
5669
 
4743
5670
  for( let b of values )
4744
5671
  {
@@ -4757,14 +5684,14 @@ class Panel {
4757
5684
  buttonEl.classList.add( options.buttonClass );
4758
5685
  }
4759
5686
 
4760
- if( shouldSelect && options.selected == b.value )
5687
+ if( shouldSelect && ( b.selected || options.selected == b.value ) )
4761
5688
  {
4762
5689
  buttonEl.classList.add("selected");
4763
5690
  }
4764
5691
 
4765
5692
  buttonEl.innerHTML = ( b.icon ? "<a class='" + b.icon +"'></a>" : "" ) + "<span>" + ( b.icon ? "" : b.value ) + "</span>";
4766
5693
 
4767
- if( options.disabled )
5694
+ if( b.disabled )
4768
5695
  {
4769
5696
  buttonEl.setAttribute( "disabled", true );
4770
5697
  }
@@ -4772,8 +5699,15 @@ class Panel {
4772
5699
  buttonEl.addEventListener("click", function( e ) {
4773
5700
  if( shouldSelect )
4774
5701
  {
4775
- container.querySelectorAll('button').forEach( s => s.classList.remove('selected'));
4776
- this.classList.add('selected');
5702
+ if( shouldToggle )
5703
+ {
5704
+ this.classList.toggle('selected');
5705
+ }
5706
+ else
5707
+ {
5708
+ container.querySelectorAll('button').forEach( s => s.classList.remove('selected'));
5709
+ this.classList.add('selected');
5710
+ }
4777
5711
  }
4778
5712
 
4779
5713
  that._trigger( new IEvent( name, b.value, e ), b.callback );
@@ -4783,7 +5717,7 @@ class Panel {
4783
5717
  }
4784
5718
 
4785
5719
  // Remove branch padding and margins
4786
- if( !widget.name)
5720
+ if( !widget.name )
4787
5721
  {
4788
5722
  element.className += " noname";
4789
5723
  container.style.width = "100%";
@@ -4957,7 +5891,8 @@ class Panel {
4957
5891
 
4958
5892
  element.appendChild( container );
4959
5893
 
4960
- if( !widget.name || options.hideName ) {
5894
+ if( !widget.name || options.hideName )
5895
+ {
4961
5896
  element.className += " noname";
4962
5897
  container.style.width = "100%";
4963
5898
  }
@@ -5093,8 +6028,6 @@ class Panel {
5093
6028
 
5094
6029
  const _placeOptions = ( parent ) => {
5095
6030
 
5096
- console.log("Replacing container");
5097
-
5098
6031
  const overflowContainer = parent.getParentArea();
5099
6032
  const rect = selectedOption.getBoundingClientRect();
5100
6033
  const nestedDialog = parent.parentElement.closest( "dialog" );
@@ -5378,15 +6311,16 @@ class Panel {
5378
6311
 
5379
6312
  addCurve( name, values, callback, options = {} ) {
5380
6313
 
5381
- if(!name) {
5382
- throw("Set Widget Name!");
6314
+ if( !name )
6315
+ {
6316
+ throw( "Set Widget Name!" );
5383
6317
  }
5384
6318
 
5385
6319
  let that = this;
5386
- let widget = this.create_widget(name, Widget.CURVE, options);
6320
+ let widget = this.create_widget( name, Widget.CURVE, options );
5387
6321
 
5388
6322
  widget.onGetValue = () => {
5389
- return JSON.parse(JSON.stringify(curveInstance.element.value));
6323
+ return JSON.parse(JSON.stringify( curveInstance.element.value ));
5390
6324
  };
5391
6325
 
5392
6326
  widget.onSetValue = ( newValue, skipCallback ) => {
@@ -5531,7 +6465,8 @@ class Panel {
5531
6465
 
5532
6466
  addLayers( name, value, callback, options = {} ) {
5533
6467
 
5534
- if(!name) {
6468
+ if( !name )
6469
+ {
5535
6470
  throw("Set Widget Name!");
5536
6471
  }
5537
6472
 
@@ -5573,7 +6508,8 @@ class Panel {
5573
6508
  let binary = value.toString( 2 );
5574
6509
  let nbits = binary.length;
5575
6510
  // fill zeros
5576
- for(var i = 0; i < (16 - nbits); ++i) {
6511
+ for( var i = 0; i < (16 - nbits); ++i )
6512
+ {
5577
6513
  binary = '0' + binary;
5578
6514
  }
5579
6515
 
@@ -5626,8 +6562,9 @@ class Panel {
5626
6562
 
5627
6563
  addArray( name, values = [], callback, options = {} ) {
5628
6564
 
5629
- if(!name) {
5630
- throw("Set Widget Name!");
6565
+ if( !name )
6566
+ {
6567
+ throw( "Set Widget Name!" );
5631
6568
  }
5632
6569
 
5633
6570
  let widget = this.create_widget(name, Widget.ARRAY, options);
@@ -5808,7 +6745,8 @@ class Panel {
5808
6745
  widget.updateValues( values );
5809
6746
 
5810
6747
  // Remove branch padding and margins
5811
- if( !widget.name ) {
6748
+ if( !widget.name )
6749
+ {
5812
6750
  element.className += " noname";
5813
6751
  listContainer.style.width = "100%";
5814
6752
  }
@@ -5895,7 +6833,7 @@ class Panel {
5895
6833
  tagsContainer.appendChild( tagInput );
5896
6834
 
5897
6835
  tagInput.onkeydown = function( e ) {
5898
- const val = this.value.replace(/\s/g, '');
6836
+ const val = this.value.replace( /\s/g, '' );
5899
6837
  if( e.key == ' ' || e.key == 'Enter' )
5900
6838
  {
5901
6839
  e.preventDefault();
@@ -5933,15 +6871,16 @@ class Panel {
5933
6871
  * @param {Function} callback Callback function on change
5934
6872
  * @param {*} options:
5935
6873
  * disabled: Make the widget disabled [false]
6874
+ * label: Checkbox label
5936
6875
  * suboptions: Callback to add widgets in case of TRUE value
5937
- * className: Customize colors
6876
+ * className: Extra classes to customize style
5938
6877
  */
5939
6878
 
5940
6879
  addCheckbox( name, value, callback, options = {} ) {
5941
6880
 
5942
- if( !name )
6881
+ if( !name && !options.label )
5943
6882
  {
5944
- throw( "Set Widget Name!" );
6883
+ throw( "Set Widget Name or at least a label!" );
5945
6884
  }
5946
6885
 
5947
6886
  let widget = this.create_widget( name, Widget.CHECKBOX, options );
@@ -5961,10 +6900,13 @@ class Panel {
5961
6900
  let element = widget.domEl;
5962
6901
 
5963
6902
  // Add reset functionality
5964
- Panel._add_reset_property( element.domName, function() {
5965
- checkbox.checked = !checkbox.checked;
5966
- Panel._dispatch_event( checkbox, "change" );
5967
- });
6903
+ if( name )
6904
+ {
6905
+ Panel._add_reset_property( element.domName, function() {
6906
+ checkbox.checked = !checkbox.checked;
6907
+ Panel._dispatch_event( checkbox, "change" );
6908
+ });
6909
+ }
5968
6910
 
5969
6911
  // Add widget value
5970
6912
 
@@ -5980,7 +6922,7 @@ class Panel {
5980
6922
 
5981
6923
  let valueName = document.createElement( 'span' );
5982
6924
  valueName.className = "checkboxtext";
5983
- valueName.innerHTML = "On";
6925
+ valueName.innerHTML = options.label ?? "On";
5984
6926
 
5985
6927
  container.appendChild( checkbox );
5986
6928
  container.appendChild( valueName );
@@ -6118,6 +7060,96 @@ class Panel {
6118
7060
  return widget;
6119
7061
  }
6120
7062
 
7063
+ /**
7064
+ * @method addRadioGroup
7065
+ * @param {String} label Radio label
7066
+ * @param {Array} values Radio options
7067
+ * @param {Function} callback Callback function on change
7068
+ * @param {*} options:
7069
+ * disabled: Make the widget disabled [false]
7070
+ * className: Customize colors
7071
+ */
7072
+
7073
+ addRadioGroup( label, values, callback, options = {} ) {
7074
+
7075
+ let widget = this.create_widget( null, Widget.RADIO, options );
7076
+
7077
+ widget.onGetValue = () => {
7078
+ const items = container.querySelectorAll( 'button' );
7079
+ for( let i = 0; i < items.length; ++i )
7080
+ {
7081
+ const optionItem = items[ i ];
7082
+ if( optionItem.checked )
7083
+ {
7084
+ return [ i, values[ i ] ];
7085
+ }
7086
+ }
7087
+ };
7088
+
7089
+ widget.onSetValue = ( newValue, skipCallback ) => {
7090
+ const items = container.querySelectorAll( 'button' );
7091
+ for( let i = 0; i < items.length; ++i )
7092
+ {
7093
+ const optionItem = items[ i ];
7094
+ if( newValue == i )
7095
+ {
7096
+ Panel._dispatch_event( optionItem, "click", skipCallback );
7097
+ }
7098
+ }
7099
+ };
7100
+
7101
+ let element = widget.domEl;
7102
+
7103
+ // Add widget value
7104
+ var container = document.createElement( 'div' );
7105
+ container.className = "lexradiogroup " + ( options.className ?? "" );
7106
+
7107
+ let labelSpan = document.createElement( 'span' );
7108
+ labelSpan.innerHTML = label;
7109
+ container.appendChild( labelSpan );
7110
+
7111
+ const that = this;
7112
+
7113
+ for( let i = 0; i < values.length; ++i )
7114
+ {
7115
+ const optionItem = document.createElement( 'div' );
7116
+ optionItem.className = "lexradiogroupitem";
7117
+ container.appendChild( optionItem );
7118
+
7119
+ const optionButton = document.createElement( 'button' );
7120
+ optionButton.className = "lexbutton";
7121
+ optionButton.disabled = options.disabled ?? false;
7122
+ optionItem.appendChild( optionButton );
7123
+
7124
+ optionButton.addEventListener( "click", function( e ) {
7125
+ const skipCallback = ( e.detail?.constructor == Number ? null : e.detail );
7126
+ container.querySelectorAll( 'button' ).forEach( e => { e.checked = false; e.classList.remove( "checked" ) } );
7127
+ this.checked = !this.checked;
7128
+ this.classList.toggle( "checked" );
7129
+ if( !skipCallback ) that._trigger( new IEvent( null, [ i, values[ i ] ], e ), callback );
7130
+ } );
7131
+
7132
+ {
7133
+ const checkedSpan = document.createElement( 'span' );
7134
+ optionButton.appendChild( checkedSpan );
7135
+ }
7136
+
7137
+ const optionLabel = document.createElement( 'span' );
7138
+ optionLabel.innerHTML = values[ i ];
7139
+ optionItem.appendChild( optionLabel );
7140
+ }
7141
+
7142
+ if( options.selected )
7143
+ {
7144
+ console.assert( options.selected.constructor == Number );
7145
+ widget.set( options.selected, true );
7146
+ }
7147
+
7148
+ element.appendChild( container );
7149
+
7150
+ return widget;
7151
+ }
7152
+
6121
7153
  /**
6122
7154
  * @method addColor
6123
7155
  * @param {String} name Widget name
@@ -6130,7 +7162,8 @@ class Panel {
6130
7162
 
6131
7163
  addColor( name, value, callback, options = {} ) {
6132
7164
 
6133
- if( !name ) {
7165
+ if( !name )
7166
+ {
6134
7167
  throw( "Set Widget Name!" );
6135
7168
  }
6136
7169
 
@@ -6168,7 +7201,8 @@ class Panel {
6168
7201
  color.useRGB = options.useRGB ?? false;
6169
7202
  color.value = color.iValue = value.constructor === Array ? rgbToHex( value ) : value;
6170
7203
 
6171
- if( options.disabled ) {
7204
+ if( options.disabled )
7205
+ {
6172
7206
  color.disabled = true;
6173
7207
  }
6174
7208
 
@@ -6213,6 +7247,137 @@ class Panel {
6213
7247
  return widget;
6214
7248
  }
6215
7249
 
7250
+ /**
7251
+ * @method addRange
7252
+ * @param {String} name Widget name
7253
+ * @param {Number} value Default number value
7254
+ * @param {Function} callback Callback function on change
7255
+ * @param {*} options:
7256
+ * className: Extra classes to customize style
7257
+ * disabled: Make the widget disabled [false]
7258
+ * left: The slider goes to the left instead of the right
7259
+ * fill: Fill slider progress [true]
7260
+ * step: Step of the input
7261
+ * min, max: Min and Max values for the input
7262
+ */
7263
+
7264
+ addRange( name, value, callback, options = {} ) {
7265
+
7266
+ let widget = this.create_widget( name, Widget.RANGE, options );
7267
+
7268
+ widget.onGetValue = () => {
7269
+ return +slider.value;
7270
+ };
7271
+
7272
+ widget.onSetValue = ( newValue, skipCallback ) => {
7273
+ slider.value = newValue;
7274
+ Panel._dispatch_event( slider, "input", skipCallback );
7275
+ };
7276
+
7277
+ let element = widget.domEl;
7278
+
7279
+ // add reset functionality
7280
+ if( widget.name )
7281
+ {
7282
+ Panel._add_reset_property( element.domName, function() {
7283
+ this.style.display = "none";
7284
+ slider.value = slider.iValue;
7285
+ Panel._dispatch_event( slider, "input" );
7286
+ });
7287
+ }
7288
+
7289
+ // add widget value
7290
+
7291
+ var container = document.createElement( 'div' );
7292
+ container.className = "lexrange";
7293
+ container.style.width = options.inputWidth || "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
7294
+
7295
+ let slider = document.createElement( 'input' );
7296
+ slider.className = "lexrangeslider " + ( options.className ?? "" );
7297
+ slider.value = slider.iValue = value;
7298
+ slider.min = options.min;
7299
+ slider.max = options.max;
7300
+ slider.step = options.step ?? 1;
7301
+ slider.type = "range";
7302
+ slider.disabled = options.disabled ?? false;
7303
+
7304
+ if( options.left ?? false )
7305
+ {
7306
+ slider.classList.add( "left" );
7307
+ }
7308
+
7309
+ if( !( options.fill ?? true ) )
7310
+ {
7311
+ slider.classList.add( "no-fill" );
7312
+ }
7313
+
7314
+ slider.addEventListener( "input", e => {
7315
+
7316
+ if( isNaN( e.target.valueAsNumber ) )
7317
+ {
7318
+ return;
7319
+ }
7320
+
7321
+ const skipCallback = e.detail;
7322
+
7323
+ let val = e.target.value = clamp( +e.target.valueAsNumber, +slider.min, +slider.max );
7324
+ slider.value = val;
7325
+
7326
+ // Reset button (default value)
7327
+ if( !skipCallback )
7328
+ {
7329
+ let btn = element.querySelector( ".lexwidgetname .lexicon" );
7330
+ if( btn ) btn.style.display = val != slider.iValue ? "block": "none";
7331
+ }
7332
+
7333
+ if( options.left )
7334
+ {
7335
+ val = ( +slider.max ) - val + ( +slider.min );
7336
+ }
7337
+
7338
+ if( !skipCallback ) this._trigger( new IEvent( name, val, e ), callback );
7339
+ }, { passive: false });
7340
+
7341
+ slider.addEventListener( "mousedown", function( e ) {
7342
+ if( options.onPress )
7343
+ {
7344
+ options.onPress.bind( slider )( e, slider );
7345
+ }
7346
+ }, false );
7347
+
7348
+ slider.addEventListener( "mouseup", function( e ) {
7349
+ if( options.onRelease )
7350
+ {
7351
+ options.onRelease.bind( slider )( e, slider );
7352
+ }
7353
+ }, false );
7354
+
7355
+ // Method to change min, max, step parameters
7356
+ widget.setLimits = ( newMin, newMax, newStep ) => {
7357
+ slider.min = newMin ?? slider.min;
7358
+ slider.max = newMax ?? slider.max;
7359
+ slider.step = newStep ?? slider.step;
7360
+ Panel._dispatch_event( slider, "input", true );
7361
+ };
7362
+
7363
+ if( value.constructor == Number )
7364
+ {
7365
+ value = clamp( value, +slider.min, +slider.max );
7366
+ }
7367
+
7368
+ container.appendChild( slider );
7369
+ element.appendChild( container );
7370
+
7371
+ // Remove branch padding and margins
7372
+ if( !widget.name )
7373
+ {
7374
+ element.className += " noname";
7375
+ container.style.width = "100%";
7376
+ }
7377
+
7378
+ return widget;
7379
+ }
7380
+
6216
7381
  /**
6217
7382
  * @method addNumber
6218
7383
  * @param {String} name Widget name
@@ -6245,7 +7410,8 @@ class Panel {
6245
7410
  let element = widget.domEl;
6246
7411
 
6247
7412
  // add reset functionality
6248
- if( widget.name ) {
7413
+ if( widget.name )
7414
+ {
6249
7415
  Panel._add_reset_property( element.domName, function() {
6250
7416
  this.style.display = "none";
6251
7417
  vecinput.value = vecinput.iValue;
@@ -6513,7 +7679,8 @@ class Panel {
6513
7679
  return;
6514
7680
  }
6515
7681
 
6516
- for( let i = 0; i < inputs.length; ++i ) {
7682
+ for( let i = 0; i < inputs.length; ++i )
7683
+ {
6517
7684
  let value = newValue[ i ];
6518
7685
  inputs[ i ].value = round( value, options.precision ) ?? 0;
6519
7686
  Panel._dispatch_event( inputs[ i ], "change", skipCallback );
@@ -6525,7 +7692,8 @@ class Panel {
6525
7692
  // Add reset functionality
6526
7693
  Panel._add_reset_property( element.domName, function() {
6527
7694
  this.style.display = "none";
6528
- for( let v of element.querySelectorAll( ".vecinput" ) ) {
7695
+ for( let v of element.querySelectorAll( ".vecinput" ) )
7696
+ {
6529
7697
  v.value = v.iValue;
6530
7698
  Panel._dispatch_event( v, "change" );
6531
7699
  }
@@ -6537,8 +7705,8 @@ class Panel {
6537
7705
  container.className = "lexvector";
6538
7706
  container.style.width = "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
6539
7707
 
6540
- for( let i = 0; i < num_components; ++i ) {
6541
-
7708
+ for( let i = 0; i < num_components; ++i )
7709
+ {
6542
7710
  let box = document.createElement( 'div' );
6543
7711
  box.className = "vecbox";
6544
7712
  box.innerHTML = "<span class='" + Panel.VECTOR_COMPONENTS[ i ] + "'></span>";
@@ -6614,7 +7782,8 @@ class Panel {
6614
7782
 
6615
7783
  if( locker.locked )
6616
7784
  {
6617
- for( let v of element.querySelectorAll( ".vecinput" ) ) {
7785
+ for( let v of element.querySelectorAll( ".vecinput" ) )
7786
+ {
6618
7787
  v.value = val;
6619
7788
  value[ v.idx ] = val;
6620
7789
  }
@@ -6672,7 +7841,8 @@ class Panel {
6672
7841
 
6673
7842
  if( locker.locked )
6674
7843
  {
6675
- for( let v of element.querySelectorAll( ".vecinput" ) ) {
7844
+ for( let v of element.querySelectorAll( ".vecinput" ) )
7845
+ {
6676
7846
  v.value = round( +v.valueAsNumber + mult * dt, options.precision );
6677
7847
  Panel._dispatch_event( v, "change" );
6678
7848
  }
@@ -6798,7 +7968,7 @@ class Panel {
6798
7968
  const value = [];
6799
7969
  for( let i = 0; i < element.dimensions.length; ++i )
6800
7970
  {
6801
- value.push( element.dimensions[ i ].onGetValue() );
7971
+ value.push( element.dimensions[ i ].value() );
6802
7972
  }
6803
7973
  return value;
6804
7974
  };
@@ -6806,7 +7976,7 @@ class Panel {
6806
7976
  widget.onSetValue = ( newValue, skipCallback ) => {
6807
7977
  for( let i = 0; i < element.dimensions.length; ++i )
6808
7978
  {
6809
- element.dimensions[ i ].onSetValue( newValue[ i ], skipCallback );
7979
+ element.dimensions[ i ].set( newValue[ i ], skipCallback );
6810
7980
  }
6811
7981
  };
6812
7982
 
@@ -6821,14 +7991,14 @@ class Panel {
6821
7991
  {
6822
7992
  element.dimensions[ i ] = this.addNumber( null, value[ i ], ( v ) => {
6823
7993
 
6824
- const value = widget.onGetValue();
7994
+ const value = widget.value();
6825
7995
 
6826
7996
  if( element.locked )
6827
7997
  {
6828
7998
  const ar = ( i == 0 ? 1.0 / element.aspectRatio : element.aspectRatio );
6829
7999
  const index = ( 1 + i ) % 2;
6830
8000
  value[ index ] = v * ar;
6831
- element.dimensions[ index ].onSetValue( value[ index ], true );
8001
+ element.dimensions[ index ].set( value[ index ], true );
6832
8002
  }
6833
8003
 
6834
8004
  if( callback )
@@ -6871,7 +8041,7 @@ class Panel {
6871
8041
  this.classList.remove( "fa-lock-open" );
6872
8042
 
6873
8043
  // Recompute ratio
6874
- const value = widget.onGetValue();
8044
+ const value = widget.value();
6875
8045
  element.aspectRatio = value[ 0 ] / value[ 1 ];
6876
8046
  }
6877
8047
  else
@@ -7268,7 +8438,8 @@ class Panel {
7268
8438
  let container = document.createElement('div');
7269
8439
  container.className = "lextree";
7270
8440
 
7271
- if(name) {
8441
+ if( name )
8442
+ {
7272
8443
  let title = document.createElement('span');
7273
8444
  title.innerHTML = name;
7274
8445
  container.appendChild(title);
@@ -7280,8 +8451,8 @@ class Panel {
7280
8451
  toolsDiv.className += " notitle";
7281
8452
 
7282
8453
  // Tree icons
7283
- if(options.icons) {
7284
-
8454
+ if( options.icons )
8455
+ {
7285
8456
  for( let data of options.icons )
7286
8457
  {
7287
8458
  let iconEl = document.createElement('a');
@@ -7341,11 +8512,15 @@ class Panel {
7341
8512
  let widget = new Widget( null, Widget.SEPARATOR );
7342
8513
  widget.domEl = element;
7343
8514
 
7344
- if(this.current_branch) {
8515
+ if( this.current_branch )
8516
+ {
7345
8517
  this.current_branch.content.appendChild( element );
7346
8518
  this.current_branch.widgets.push( widget );
7347
- } else
7348
- this.root.appendChild(element);
8519
+ }
8520
+ else
8521
+ {
8522
+ this.root.appendChild( element );
8523
+ }
7349
8524
  }
7350
8525
 
7351
8526
  /**
@@ -7670,6 +8845,12 @@ class Panel {
7670
8845
 
7671
8846
  input.addEventListener( 'change', function() {
7672
8847
  data.checkMap[ rowId ] = this.checked;
8848
+
8849
+ if( !this.checked )
8850
+ {
8851
+ const input = table.querySelector( "thead input[type='checkbox']" );
8852
+ input.checked = data.checkMap[ ":root" ] = false;
8853
+ }
7673
8854
  });
7674
8855
 
7675
8856
  row.appendChild( td );
@@ -7795,7 +8976,7 @@ class Branch {
7795
8976
  root.appendChild( title );
7796
8977
 
7797
8978
  var branchContent = document.createElement( 'div' );
7798
- branchContent.id = name.replace(/\s/g, '');
8979
+ branchContent.id = name.replace( /\s/g, '' );
7799
8980
  branchContent.className = "lexbranchcontent";
7800
8981
  root.appendChild(branchContent);
7801
8982
  this.content = branchContent;
@@ -7851,7 +9032,8 @@ class Branch {
7851
9032
 
7852
9033
  const dialog = new Dialog(this.name, p => {
7853
9034
  // add widgets
7854
- for( let w of this.widgets ) {
9035
+ for( let w of this.widgets )
9036
+ {
7855
9037
  p.root.appendChild( w.domEl );
7856
9038
  }
7857
9039
  });
@@ -7944,8 +9126,8 @@ class Branch {
7944
9126
  var size = this.grabber.style.marginLeft;
7945
9127
 
7946
9128
  // Update sizes of widgets inside
7947
- for(var i = 0; i < this.widgets.length; i++) {
7948
-
9129
+ for( var i = 0; i < this.widgets.length; i++ )
9130
+ {
7949
9131
  let widget = this.widgets[ i ];
7950
9132
  let element = widget.domEl;
7951
9133
 
@@ -7964,9 +9146,6 @@ class Branch {
7964
9146
  case Widget.FILE:
7965
9147
  padding = "10%";
7966
9148
  break;
7967
- case Widget.TEXT:
7968
- padding = "8px";
7969
- break;
7970
9149
  };
7971
9150
 
7972
9151
  value.style.width = "-moz-calc( 100% - " + size + " - " + padding + " )";
@@ -8105,7 +9284,7 @@ class Dialog {
8105
9284
  modal = options.modal ?? false;
8106
9285
 
8107
9286
  var root = document.createElement('dialog');
8108
- root.className = "lexdialog " + (options.class ?? "");
9287
+ root.className = "lexdialog " + (options.className ?? "");
8109
9288
  root.id = options.id ?? "dialog" + Dialog._last_id++;
8110
9289
  LX.root.appendChild( root );
8111
9290
 
@@ -8263,15 +9442,15 @@ class Dialog {
8263
9442
 
8264
9443
  root.style.width = size[ 0 ] ? (size[ 0 ]) : "25%";
8265
9444
  root.style.height = size[ 1 ] ? (size[ 1 ]) : "auto";
9445
+ root.style.translate = options.position ? "unset" : "-50% -50%";
8266
9446
 
8267
9447
  if( options.size )
8268
9448
  {
8269
9449
  this.size = size;
8270
9450
  }
8271
9451
 
8272
- let rect = root.getBoundingClientRect();
8273
- root.style.left = position[ 0 ] ? (position[ 0 ]) : "calc( 50% - " + ( rect.width * 0.5 ) + "px )";
8274
- root.style.top = position[ 1 ] ? (position[ 1 ]) : "calc( 50% - " + ( rect.height * 0.5 ) + "px )";
9452
+ root.style.left = position[ 0 ] ?? "50%";
9453
+ root.style.top = position[ 1 ] ?? "50%";
8275
9454
 
8276
9455
  panel.root.style.width = "calc( 100% - 30px )";
8277
9456
  panel.root.style.height = title ? "calc( 100% - " + ( titleDiv.offsetHeight + 30 ) + "px )" : "calc( 100% - 51px )";
@@ -8288,7 +9467,7 @@ class Dialog {
8288
9467
  this._oncreate.call(this, this.panel);
8289
9468
  }
8290
9469
 
8291
- setPosition(x, y) {
9470
+ setPosition( x, y ) {
8292
9471
 
8293
9472
  this.root.style.left = x + "px";
8294
9473
  this.root.style.top = y + "px";
@@ -8334,6 +9513,9 @@ class PocketDialog extends Dialog {
8334
9513
 
8335
9514
  // Custom
8336
9515
  this.root.classList.add( "pocket" );
9516
+
9517
+ this.root.style.translate = "none";
9518
+ this.root.style.top = "0";
8337
9519
  this.root.style.left = "unset";
8338
9520
 
8339
9521
  if( !options.position )
@@ -8349,6 +9531,11 @@ class PocketDialog extends Dialog {
8349
9531
  this.minimized = false;
8350
9532
  this.title.tabIndex = -1;
8351
9533
  this.title.addEventListener("click", e => {
9534
+ if( this.title._eventCatched )
9535
+ {
9536
+ this.title._eventCatched = false;
9537
+ return;
9538
+ }
8352
9539
 
8353
9540
  // Sized dialogs have to keep their size
8354
9541
  if( this.size )
@@ -8431,12 +9618,12 @@ class ContextMenu {
8431
9618
  constructor( event, title, options = {} ) {
8432
9619
 
8433
9620
  // remove all context menus
8434
- document.body.querySelectorAll(".lexcontextmenubox").forEach(e => e.remove());
9621
+ document.body.querySelectorAll( ".lexcontextmenu" ).forEach( e => e.remove() );
8435
9622
 
8436
- this.root = document.createElement('div');
8437
- this.root.className = "lexcontextmenubox";
8438
- this.root.style.left = (event.x - 48 + document.scrollingElement.scrollLeft) + "px";
8439
- this.root.style.top = (event.y - 8 + document.scrollingElement.scrollTop) + "px";
9623
+ this.root = document.createElement( "div" );
9624
+ this.root.className = "lexcontextmenu";
9625
+ this.root.style.left = ( event.x - 48 + document.scrollingElement.scrollLeft ) + "px";
9626
+ this.root.style.top = ( event.y - 8 + document.scrollingElement.scrollTop ) + "px";
8440
9627
 
8441
9628
  this.root.addEventListener("mouseleave", function() {
8442
9629
  this.remove();
@@ -8449,13 +9636,13 @@ class ContextMenu {
8449
9636
  {
8450
9637
  const item = {};
8451
9638
  item[ title ] = [];
8452
- item[ 'className' ] = "cmtitle";
8453
- item[ 'icon' ] = options.icon;
9639
+ item[ "className" ] = "cmtitle";
9640
+ item[ "icon" ] = options.icon;
8454
9641
  this.items.push( item );
8455
9642
  }
8456
9643
  }
8457
9644
 
8458
- _adjust_position( div, margin, useAbsolute = false ) {
9645
+ _adjustPosition( div, margin, useAbsolute = false ) {
8459
9646
 
8460
9647
  let rect = div.getBoundingClientRect();
8461
9648
 
@@ -8496,19 +9683,19 @@ class ContextMenu {
8496
9683
  }
8497
9684
  }
8498
9685
 
8499
- _create_submenu( o, k, c, d ) {
9686
+ _createSubmenu( o, k, c, d ) {
8500
9687
 
8501
- this.root.querySelectorAll(".lexcontextmenubox").forEach( cm => cm.remove() );
9688
+ this.root.querySelectorAll( ".lexcontextmenu" ).forEach( cm => cm.remove() );
8502
9689
 
8503
9690
  let contextmenu = document.createElement('div');
8504
- contextmenu.className = "lexcontextmenubox";
9691
+ contextmenu.className = "lexcontextmenu";
8505
9692
  c.appendChild( contextmenu );
8506
9693
 
8507
9694
  for( var i = 0; i < o[k].length; ++i )
8508
9695
  {
8509
- const subitem = o[k][i];
8510
- const subkey = Object.keys(subitem)[0];
8511
- this._create_entry(subitem, subkey, contextmenu, d);
9696
+ const subitem = o[ k ][ i ];
9697
+ const subkey = Object.keys( subitem )[ 0 ];
9698
+ this._createEntry(subitem, subkey, contextmenu, d);
8512
9699
  }
8513
9700
 
8514
9701
  var rect = c.getBoundingClientRect();
@@ -8516,29 +9703,32 @@ class ContextMenu {
8516
9703
  contextmenu.style.marginTop = 3.5 - c.offsetHeight + "px";
8517
9704
 
8518
9705
  // Set final width
8519
- this._adjust_position( contextmenu, 6, true );
9706
+ this._adjustPosition( contextmenu, 6, true );
8520
9707
  }
8521
9708
 
8522
- _create_entry( o, k, c, d ) {
9709
+ _createEntry( o, k, c, d ) {
8523
9710
 
8524
9711
  const hasSubmenu = o[ k ].length;
8525
9712
  let entry = document.createElement('div');
8526
- entry.className = "lexcontextmenuentry" + (o[ 'className' ] ? " " + o[ 'className' ] : "" );
9713
+ entry.className = "lexmenuboxentry" + (o[ 'className' ] ? " " + o[ 'className' ] : "" );
8527
9714
  entry.id = o.id ?? ("eId" + getSupportedDOMName( k ));
8528
9715
  entry.innerHTML = "";
8529
9716
  const icon = o[ 'icon' ];
8530
- if(icon) {
9717
+ if( icon )
9718
+ {
8531
9719
  entry.innerHTML += "<a class='" + icon + " fa-sm'></a>";
8532
9720
  }
8533
9721
  const disabled = o['disabled'];
8534
9722
  entry.innerHTML += "<div class='lexentryname" + (disabled ? " disabled" : "") + "'>" + k + "</div>";
8535
9723
  c.appendChild( entry );
8536
9724
 
8537
- if( this.colors[ k ] ) {
9725
+ if( this.colors[ k ] )
9726
+ {
8538
9727
  entry.style.borderColor = this.colors[ k ];
8539
9728
  }
8540
9729
 
8541
- if( k == "" ) {
9730
+ if( k == "" )
9731
+ {
8542
9732
  entry.className += " cmseparator";
8543
9733
  return;
8544
9734
  }
@@ -8551,7 +9741,8 @@ class ContextMenu {
8551
9741
  if(disabled) return;
8552
9742
 
8553
9743
  const f = o[ 'callback' ];
8554
- if(f) {
9744
+ if( f )
9745
+ {
8555
9746
  f.call( this, k, entry );
8556
9747
  this.root.remove();
8557
9748
  }
@@ -8560,7 +9751,7 @@ class ContextMenu {
8560
9751
  return;
8561
9752
 
8562
9753
  if( LX.OPEN_CONTEXTMENU_ENTRY == 'click' )
8563
- this._create_submenu( o, k, entry, ++d );
9754
+ this._createSubmenu( o, k, entry, ++d );
8564
9755
  });
8565
9756
 
8566
9757
  if( !hasSubmenu )
@@ -8576,20 +9767,19 @@ class ContextMenu {
8576
9767
  if(entry.built)
8577
9768
  return;
8578
9769
  entry.built = true;
8579
- this._create_submenu( o, k, entry, ++d );
9770
+ this._createSubmenu( o, k, entry, ++d );
8580
9771
  e.stopPropagation();
8581
9772
  });
8582
9773
  }
8583
9774
 
8584
9775
  entry.addEventListener("mouseleave", () => {
8585
9776
  d = -1; // Reset depth
8586
- // delete entry.built;
8587
- c.querySelectorAll(".lexcontextmenubox").forEach(e => e.remove());
9777
+ c.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
8588
9778
  });
8589
9779
  }
8590
9780
 
8591
9781
  onCreate() {
8592
- doAsync( () => this._adjust_position( this.root, 6 ) );
9782
+ doAsync( () => this._adjustPosition( this.root, 6 ) );
8593
9783
  }
8594
9784
 
8595
9785
  add( path, options = {} ) {
@@ -8617,22 +9807,25 @@ class ContextMenu {
8617
9807
  if(key) found = o[ key ];
8618
9808
  } );
8619
9809
 
8620
- if(found) {
8621
- insert( tokens[idx++], found );
9810
+ if( found )
9811
+ {
9812
+ insert( tokens[ idx++ ], found );
8622
9813
  }
8623
- else {
9814
+ else
9815
+ {
8624
9816
  let item = {};
8625
9817
  item[ token ] = [];
8626
- const next_token = tokens[idx++];
9818
+ const nextToken = tokens[ idx++ ];
8627
9819
  // Check if last token -> add callback
8628
- if(!next_token) {
9820
+ if( !nextToken )
9821
+ {
8629
9822
  item[ 'id' ] = options.id;
8630
9823
  item[ 'callback' ] = options.callback;
8631
9824
  item[ 'disabled' ] = options.disabled ?? false;
8632
9825
  }
8633
9826
 
8634
9827
  list.push( item );
8635
- insert( next_token, item[ token ] );
9828
+ insert( nextToken, item[ token ] );
8636
9829
  }
8637
9830
  };
8638
9831
 
@@ -8642,13 +9835,15 @@ class ContextMenu {
8642
9835
 
8643
9836
  const setParent = _item => {
8644
9837
 
8645
- let key = Object.keys(_item)[0];
9838
+ let key = Object.keys( _item )[ 0 ];
8646
9839
  let children = _item[ key ];
8647
9840
 
8648
- if(!children.length)
9841
+ if( !children.length )
9842
+ {
8649
9843
  return;
9844
+ }
8650
9845
 
8651
- if(children.find( c => Object.keys(c)[0] == key ) == null)
9846
+ if( children.find( c => Object.keys(c)[0] == key ) == null )
8652
9847
  {
8653
9848
  const parent = {};
8654
9849
  parent[ key ] = [];
@@ -8656,27 +9851,34 @@ class ContextMenu {
8656
9851
  _item[ key ].unshift( parent );
8657
9852
  }
8658
9853
 
8659
- for( var child of _item[ key ] ) {
8660
- let k = Object.keys(child)[0];
8661
- for( var i = 0; i < child[k].length; ++i )
8662
- setParent(child);
9854
+ for( var child of _item[ key ] )
9855
+ {
9856
+ let k = Object.keys( child )[ 0 ];
9857
+ for( var i = 0; i < child[ k ].length; ++i )
9858
+ {
9859
+ setParent( child );
9860
+ }
8663
9861
  }
8664
9862
  };
8665
9863
 
8666
9864
  for( let item of this.items )
8667
- setParent(item);
9865
+ {
9866
+ setParent( item );
9867
+ }
8668
9868
 
8669
9869
  // Create elements
8670
9870
 
8671
9871
  for( let item of this.items )
8672
9872
  {
8673
- let key = Object.keys(item)[0];
9873
+ let key = Object.keys( item )[ 0 ];
8674
9874
  let pKey = "eId" + getSupportedDOMName( key );
8675
9875
 
8676
9876
  // Item already created
8677
- const id = "#" + (item.id ?? pKey);
8678
- if( !this.root.querySelector(id) )
8679
- this._create_entry(item, key, this.root, -1);
9877
+ const id = "#" + ( item.id ?? pKey );
9878
+ if( !this.root.querySelector( id ) )
9879
+ {
9880
+ this._createEntry( item, key, this.root, -1 );
9881
+ }
8680
9882
  }
8681
9883
  }
8682
9884
 
@@ -8787,7 +9989,7 @@ class Curve {
8787
9989
 
8788
9990
  var r = [];
8789
9991
  var dx = (element.xrange[1] - element.xrange[ 0 ]) / samples;
8790
- for(var i = element.xrange[0]; i <= element.xrange[1]; i += dx)
9992
+ for( var i = element.xrange[0]; i <= element.xrange[1]; i += dx )
8791
9993
  {
8792
9994
  r.push( element.getValueAt(i) );
8793
9995
  }
@@ -8796,7 +9998,8 @@ class Curve {
8796
9998
 
8797
9999
  element.addValue = function(v) {
8798
10000
 
8799
- for(var i = 0; i < element.value; i++) {
10001
+ for( var i = 0; i < element.value; i++ )
10002
+ {
8800
10003
  var value = element.value[i];
8801
10004
  if(value[0] < v[0]) continue;
8802
10005
  element.value.splice(i,0,v);
@@ -8822,7 +10025,7 @@ class Curve {
8822
10025
 
8823
10026
  var selected = -1;
8824
10027
 
8825
- element.redraw = function( o = {} ) {
10028
+ element.redraw = function( o = {} ) {
8826
10029
 
8827
10030
  if( o.value ) element.value = o.value;
8828
10031
  if( o.xrange ) element.xrange = o.xrange;
@@ -8851,13 +10054,16 @@ class Curve {
8851
10054
  ctx.moveTo( pos[ 0 ], pos[ 1 ] );
8852
10055
  let values = [pos[ 0 ], pos[ 1 ]];
8853
10056
 
8854
- for(var i in element.value) {
8855
- var value = element.value[i];
8856
- pos = convert(value);
8857
- values.push(pos[ 0 ]);
8858
- values.push(pos[ 1 ]);
8859
- if(!element.smooth)
10057
+ for( var i in element.value )
10058
+ {
10059
+ var value = element.value[ i ];
10060
+ pos = convert( value );
10061
+ values.push( pos[ 0 ] );
10062
+ values.push( pos[ 1 ] );
10063
+ if( !element.smooth )
10064
+ {
8860
10065
  ctx.lineTo( pos[ 0 ], pos[ 1 ] );
10066
+ }
8861
10067
  }
8862
10068
 
8863
10069
  pos = convert([ element.xrange[ 1 ], element.defaulty ]);
@@ -8874,7 +10080,8 @@ class Curve {
8874
10080
  }
8875
10081
 
8876
10082
  // Draw points
8877
- for( var i = 0; i < element.value.length; i += 1 ) {
10083
+ for( var i = 0; i < element.value.length; i += 1 )
10084
+ {
8878
10085
  var value = element.value[ i ];
8879
10086
  pos = convert( value );
8880
10087
  if( selected == i )
@@ -8886,10 +10093,11 @@ class Curve {
8886
10093
  ctx.fill();
8887
10094
  }
8888
10095
 
8889
- if(element.show_samples) {
10096
+ if( element.show_samples )
10097
+ {
8890
10098
  var samples = element.resample(element.show_samples);
8891
10099
  ctx.fillStyle = "#888";
8892
- for(var i = 0; i < samples.length; i += 1)
10100
+ for( var i = 0; i < samples.length; i += 1)
8893
10101
  {
8894
10102
  var value = [ i * ((element.xrange[ 1 ] - element.xrange[ 0 ]) / element.show_samples) + element.xrange[ 0 ], samples[ i ] ];
8895
10103
  pos = convert(value);
@@ -8912,7 +10120,8 @@ class Curve {
8912
10120
 
8913
10121
  selected = computeSelected( mousex, canvas.height - mousey );
8914
10122
 
8915
- if( e.button == LX.MOUSE_LEFT_CLICK && selected == -1 && element.allow_add_values ) {
10123
+ if( e.button == LX.MOUSE_LEFT_CLICK && selected == -1 && element.allow_add_values )
10124
+ {
8916
10125
  var v = unconvert([ mousex, canvas.height - mousey ]);
8917
10126
  element.value.push( v );
8918
10127
  sortValues();
@@ -8960,7 +10169,8 @@ class Curve {
8960
10169
  var dy = element.draggable_y ? last_mouse[ 1 ] - mousey : 0;
8961
10170
  var delta = unconvert([ -dx, dy ]);
8962
10171
 
8963
- if( selected != -1 ) {
10172
+ if( selected != -1 )
10173
+ {
8964
10174
  var minx = element.xrange[ 0 ];
8965
10175
  var maxx = element.xrange[ 1 ];
8966
10176
 
@@ -9117,7 +10327,7 @@ class Dial {
9117
10327
 
9118
10328
  var r = [];
9119
10329
  var dx = (element.xrange[1] - element.xrange[ 0 ]) / samples;
9120
- for(var i = element.xrange[0]; i <= element.xrange[1]; i += dx)
10330
+ for( var i = element.xrange[0]; i <= element.xrange[1]; i += dx)
9121
10331
  {
9122
10332
  r.push( element.getValueAt(i) );
9123
10333
  }
@@ -9126,15 +10336,16 @@ class Dial {
9126
10336
 
9127
10337
  element.addValue = function(v) {
9128
10338
 
9129
- for(var i = 0; i < element.value; i++) {
9130
- var value = element.value[i];
9131
- if(value[0] < v[0]) continue;
9132
- element.value.splice(i,0,v);
10339
+ for( var i = 0; i < element.value; i++ )
10340
+ {
10341
+ var value = element.value[ i ];
10342
+ if(value[ 0 ] < v[ 0 ]) continue;
10343
+ element.value.splice( i, 0, v );
9133
10344
  redraw();
9134
10345
  return;
9135
10346
  }
9136
10347
 
9137
- element.value.push(v);
10348
+ element.value.push( v );
9138
10349
  redraw();
9139
10350
  }
9140
10351
 
@@ -9154,7 +10365,7 @@ class Dial {
9154
10365
 
9155
10366
  var selected = -1;
9156
10367
 
9157
- element.redraw = function( o = {} ) {
10368
+ element.redraw = function( o = {} ) {
9158
10369
 
9159
10370
  if( o.value ) element.value = o.value;
9160
10371
  if( o.xrange ) element.xrange = o.xrange;
@@ -9183,17 +10394,17 @@ class Dial {
9183
10394
  ctx.moveTo( pos[ 0 ], pos[ 1 ] );
9184
10395
  let values = [pos[ 0 ], pos[ 1 ]];
9185
10396
 
9186
- for(var i in element.value) {
9187
- var value = element.value[i];
9188
- pos = convert(value);
9189
- values.push(pos[ 0 ]);
9190
- values.push(pos[ 1 ]);
9191
-
10397
+ for( var i in element.value)
10398
+ {
10399
+ var value = element.value[ i ];
10400
+ pos = convert( value );
10401
+ values.push( pos[ 0 ] );
10402
+ values.push( pos[ 1 ] );
9192
10403
  }
9193
10404
 
9194
10405
  pos = convert([ element.xrange[ 1 ], element.defaulty ]);
9195
- values.push(pos[ 0 ]);
9196
- values.push(pos[ 1 ]);
10406
+ values.push( pos[ 0 ] );
10407
+ values.push( pos[ 1 ] );
9197
10408
 
9198
10409
  // Draw points
9199
10410
  const center = [0,0];
@@ -9203,7 +10414,8 @@ class Dial {
9203
10414
  ctx.arc( pos[ 0 ], pos[ 1 ], 3, 0, Math.PI * 2);
9204
10415
  ctx.fill();
9205
10416
 
9206
- for( var i = 0; i < element.value.length; i += 1 ) {
10417
+ for( var i = 0; i < element.value.length; i += 1 )
10418
+ {
9207
10419
  var value = element.value[ i ];
9208
10420
  pos = convert( value );
9209
10421
  if( selected == i )
@@ -9215,10 +10427,11 @@ class Dial {
9215
10427
  ctx.fill();
9216
10428
  }
9217
10429
 
9218
- if(element.show_samples) {
10430
+ if( element.show_samples )
10431
+ {
9219
10432
  var samples = element.resample(element.show_samples);
9220
10433
  ctx.fillStyle = "#888";
9221
- for(var i = 0; i < samples.length; i += 1)
10434
+ for( var i = 0; i < samples.length; i += 1)
9222
10435
  {
9223
10436
  var value = [ i * ((element.xrange[ 1 ] - element.xrange[ 0 ]) / element.show_samples) + element.xrange[ 0 ], samples[ i ] ];
9224
10437
  pos = convert(value);
@@ -9241,7 +10454,8 @@ class Dial {
9241
10454
 
9242
10455
  selected = computeSelected( mousex, canvas.height - mousey );
9243
10456
 
9244
- if( e.button == LX.MOUSE_LEFT_CLICK && selected == -1 && element.allow_add_values ) {
10457
+ if( e.button == LX.MOUSE_LEFT_CLICK && selected == -1 && element.allow_add_values )
10458
+ {
9245
10459
  var v = unconvert([ mousex, canvas.height - mousey ]);
9246
10460
  element.value.push( v );
9247
10461
  sortValues();
@@ -9289,7 +10503,8 @@ class Dial {
9289
10503
  var dy = element.draggable_y ? last_mouse[ 1 ] - mousey : 0;
9290
10504
  var delta = unconvert([ -dx, dy ]);
9291
10505
 
9292
- if( selected != -1 ) {
10506
+ if( selected != -1 )
10507
+ {
9293
10508
  var minx = element.xrange[ 0 ];
9294
10509
  var maxx = element.xrange[ 1 ];
9295
10510
 
@@ -9397,7 +10612,8 @@ class AssetViewEvent {
9397
10612
  }
9398
10613
 
9399
10614
  string() {
9400
- switch(this.type) {
10615
+ switch(this.type)
10616
+ {
9401
10617
  case AssetViewEvent.NONE: return "assetview_event_none";
9402
10618
  case AssetViewEvent.ASSET_SELECTED: return "assetview_event_selected";
9403
10619
  case AssetViewEvent.ASSET_DELETED: return "assetview_event_deleted";
@@ -9751,8 +10967,8 @@ class AssetView {
9751
10967
  icon: "fa-solid fa-arrows-rotate",
9752
10968
  callback: domEl => { this._refreshContent(); }
9753
10969
  }
9754
- ], { width: "auto", noSelection: true } );
9755
- this.rightPanel.addText(null, this.path.join('/'), null, { disabled: true, signal: "@on_folder_change", style: { fontWeight: "bolder", fontSize: "16px", color: "#aaa" } });
10970
+ ], { width: "20%", minWidth: "164px", noSelection: true } );
10971
+ 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" } });
9756
10972
  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"})
9757
10973
  this.rightPanel.endLine();
9758
10974
  }
@@ -9878,8 +11094,8 @@ class AssetView {
9878
11094
  title.innerText = item.id;
9879
11095
  itemEl.appendChild( title );
9880
11096
 
9881
- if( !that.skipPreview ) {
9882
-
11097
+ if( !that.skipPreview )
11098
+ {
9883
11099
  let preview = null;
9884
11100
  const hasImage = item.src && (['png', 'jpg'].indexOf( getExtension( item.src ) ) > -1 || item.src.includes("data:image/") ); // Support b64 image as src
9885
11101
 
@@ -9906,7 +11122,8 @@ class AssetView {
9906
11122
  var newEmSize = charsPerLine / newLength;
9907
11123
  var textBaseSize = 64;
9908
11124
 
9909
- if(newEmSize < 1) {
11125
+ if( newEmSize < 1 )
11126
+ {
9910
11127
  var newFontSize = newEmSize * textBaseSize;
9911
11128
  textEl.style.fontSize = newFontSize + "px";
9912
11129
  preview.style.paddingTop = "calc(50% - " + (textEl.offsetHeight * 0.5 + 10) + "px)"
@@ -10135,7 +11352,8 @@ class AssetView {
10135
11352
 
10136
11353
  this.currentData.push( item );
10137
11354
 
10138
- if(i == (num_files - 1)) {
11355
+ if( i == (num_files - 1) )
11356
+ {
10139
11357
  this._refreshContent();
10140
11358
  if( !this.skipBrowser )
10141
11359
  this.tree.refresh();
@@ -10187,7 +11405,7 @@ class AssetView {
10187
11405
  this.currentData.splice( idx, 1 );
10188
11406
  this._refreshContent( this.searchValue, this.filter );
10189
11407
 
10190
- if(this.onevent)
11408
+ if( this.onevent)
10191
11409
  {
10192
11410
  const event = new AssetViewEvent( AssetViewEvent.ASSET_DELETED, item );
10193
11411
  this.onevent( event );
@@ -10263,7 +11481,7 @@ Object.assign(LX, {
10263
11481
  xhr.onload = function(load)
10264
11482
  {
10265
11483
  var response = this.response;
10266
- if(this.status != 200)
11484
+ if( this.status != 200)
10267
11485
  {
10268
11486
  var err = "Error " + this.status;
10269
11487
  if(request.error)
@@ -10311,7 +11529,7 @@ Object.assign(LX, {
10311
11529
  var data = new FormData();
10312
11530
  if( request.data )
10313
11531
  {
10314
- for(var i in request.data)
11532
+ for( var i in request.data)
10315
11533
  data.append(i,request.data[i]);
10316
11534
  }
10317
11535
 
@@ -10372,7 +11590,7 @@ Object.assign(LX, {
10372
11590
  var size = total;
10373
11591
  var loaded_scripts = [];
10374
11592
 
10375
- for(var i in url)
11593
+ for( var i in url)
10376
11594
  {
10377
11595
  var script = document.createElement('script');
10378
11596
  script.num = i;
@@ -10497,6 +11715,18 @@ Element.prototype.getParentArea = function() {
10497
11715
  }
10498
11716
  }
10499
11717
 
11718
+ LX.ICONS = {
11719
+ "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>`,
11720
+ "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>`,
11721
+ "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>`,
11722
+ "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>`,
11723
+ "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>`,
11724
+ "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>`,
11725
+ "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>`,
11726
+ "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>`,
11727
+ "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>`,
11728
+ }
11729
+
10500
11730
  LX.UTILS = {
10501
11731
  getTime() { return new Date().getTime() },
10502
11732
  compareThreshold( v, p, n, t ) { return Math.abs(v - p) >= t || Math.abs(v - n) >= t },
@@ -10536,17 +11766,19 @@ LX.UTILS = {
10536
11766
  drawSpline( ctx, pts, t ) {
10537
11767
 
10538
11768
  ctx.save();
10539
- var cp=[]; // array of control points, as x0,y0,x1,y1,...
10540
- var n=pts.length;
11769
+ var cp = []; // array of control points, as x0,y0,x1,y1,...
11770
+ var n = pts.length;
10541
11771
 
10542
11772
  // Draw an open curve, not connected at the ends
10543
- for(var i=0;i<n-4;i+=2) {
10544
- cp=cp.concat(LX.UTILS.getControlPoints(pts[i],pts[i+1],pts[i+2],pts[i+3],pts[i+4],pts[i+5],t));
11773
+ for( var i = 0; i < (n - 4); i += 2 )
11774
+ {
11775
+ cp = cp.concat(LX.UTILS.getControlPoints(pts[i],pts[i+1],pts[i+2],pts[i+3],pts[i+4],pts[i+5],t));
10545
11776
  }
10546
11777
 
10547
- for(var i=2;i<pts.length-5;i+=2) {
11778
+ for( var i = 2; i < ( pts.length - 5 ); i += 2 )
11779
+ {
10548
11780
  ctx.beginPath();
10549
- ctx.moveTo(pts[i],pts[i+1]);
11781
+ ctx.moveTo(pts[i], pts[i+1]);
10550
11782
  ctx.bezierCurveTo(cp[2*i-2],cp[2*i-1],cp[2*i],cp[2*i+1],pts[i+2],pts[i+3]);
10551
11783
  ctx.stroke();
10552
11784
  ctx.closePath();
@@ -10554,14 +11786,14 @@ LX.UTILS = {
10554
11786
 
10555
11787
  // For open curves the first and last arcs are simple quadratics.
10556
11788
  ctx.beginPath();
10557
- ctx.moveTo(pts[0],pts[1]);
10558
- ctx.quadraticCurveTo(cp[0],cp[1],pts[2],pts[3]);
11789
+ ctx.moveTo( pts[ 0 ], pts[ 1 ] );
11790
+ ctx.quadraticCurveTo( cp[ 0 ], cp[ 1 ], pts[ 2 ], pts[ 3 ]);
10559
11791
  ctx.stroke();
10560
11792
  ctx.closePath();
10561
11793
 
10562
11794
  ctx.beginPath();
10563
- ctx.moveTo(pts[n-2],pts[n-1]);
10564
- ctx.quadraticCurveTo(cp[2*n-10],cp[2*n-9],pts[n-4],pts[n-3]);
11795
+ ctx.moveTo( pts[ n-2 ], pts[ n-1 ] );
11796
+ ctx.quadraticCurveTo( cp[ 2*n-10 ], cp[ 2*n-9 ], pts[ n-4 ], pts[ n-3 ]);
10565
11797
  ctx.stroke();
10566
11798
  ctx.closePath();
10567
11799