lexgui 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/lexgui.js CHANGED
@@ -12,11 +12,12 @@ 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.0",
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;
@@ -29,6 +30,8 @@ LX.MOUSE_TRIPLE_CLICK = 3;
29
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
  }, "", {} );
@@ -860,6 +945,25 @@ function init( options = { } )
860
945
  this.root = document.body;
861
946
  }
862
947
 
948
+ // Notifications
949
+ {
950
+ const notifSection = document.createElement( "section" );
951
+ notifSection.className = "notifications";
952
+ this.notifications = document.createElement( "ol" );
953
+ this.notifications.className = "";
954
+ this.notifications.iWidth = 0;
955
+ notifSection.appendChild( this.notifications );
956
+ this.container.appendChild( notifSection );
957
+
958
+ this.notifications.addEventListener( "mouseenter", () => {
959
+ this.notifications.classList.add( "list" );
960
+ } );
961
+
962
+ this.notifications.addEventListener( "mouseleave", () => {
963
+ this.notifications.classList.remove( "list" );
964
+ } );
965
+ }
966
+
863
967
  // Disable drag icon
864
968
  root.addEventListener( 'dragover', function( e ) {
865
969
  e.preventDefault();
@@ -1010,6 +1114,7 @@ LX.popup = popup;
1010
1114
  function prompt( text, title, callback, options = {} )
1011
1115
  {
1012
1116
  options.modal = true;
1117
+ options.className = "prompt";
1013
1118
 
1014
1119
  let value = "";
1015
1120
 
@@ -1024,7 +1129,9 @@ function prompt( text, title, callback, options = {} )
1024
1129
 
1025
1130
  p.sameLine( 2 );
1026
1131
 
1027
- p.addButton( null, options.accept || "OK", () => {
1132
+ p.addButton(null, "Cancel", () => {if(options.on_cancel) options.on_cancel(); dialog.close();} );
1133
+
1134
+ p.addButton( null, options.accept || "Continue", () => {
1028
1135
  if( options.required && value === '' )
1029
1136
  {
1030
1137
  text += text.includes("You must fill the input text.") ? "": "\nYou must fill the input text.";
@@ -1038,8 +1145,6 @@ function prompt( text, title, callback, options = {} )
1038
1145
  }
1039
1146
  }, { buttonClass: "primary" });
1040
1147
 
1041
- p.addButton(null, "Cancel", () => {if(options.on_cancel) options.on_cancel(); dialog.close();} );
1042
-
1043
1148
  }, options );
1044
1149
 
1045
1150
  // Focus text prompt
@@ -1053,6 +1158,101 @@ function prompt( text, title, callback, options = {} )
1053
1158
 
1054
1159
  LX.prompt = prompt;
1055
1160
 
1161
+ /**
1162
+ * @method toast
1163
+ * @param {String} title
1164
+ * @param {String} description (Optional)
1165
+ * @param {*} options
1166
+ * action: Data of the custom action { name, callback }
1167
+ * closable: Allow closing the toast
1168
+ * timeout: Time in which the toast closed automatically, in ms. -1 means persistent. [3000]
1169
+ */
1170
+
1171
+ function toast( title, description, options = {} )
1172
+ {
1173
+ if( !title )
1174
+ {
1175
+ throw( "The toast needs at least a title!" );
1176
+ }
1177
+
1178
+ console.assert( this.notifications );
1179
+
1180
+ const toast = document.createElement( "li" );
1181
+ toast.className = "lextoast";
1182
+ toast.style.translate = "0 calc(100% + 30px)";
1183
+ this.notifications.prepend( toast );
1184
+
1185
+ doAsync( () => {
1186
+
1187
+ if( this.notifications.offsetWidth > this.notifications.iWidth )
1188
+ {
1189
+ this.notifications.iWidth = Math.min( this.notifications.offsetWidth, 480 );
1190
+ this.notifications.style.width = this.notifications.iWidth + "px";
1191
+ }
1192
+
1193
+ toast.dataset[ "open" ] = true;
1194
+ }, 10 );
1195
+
1196
+ const content = document.createElement( "div" );
1197
+ content.className = "lextoastcontent";
1198
+ toast.appendChild( content );
1199
+
1200
+ const titleContent = document.createElement( "div" );
1201
+ titleContent.className = "title";
1202
+ titleContent.innerHTML = title;
1203
+ content.appendChild( titleContent );
1204
+
1205
+ if( description )
1206
+ {
1207
+ const desc = document.createElement( "div" );
1208
+ desc.className = "desc";
1209
+ desc.innerHTML = description;
1210
+ content.appendChild( desc );
1211
+ }
1212
+
1213
+ if( options.action )
1214
+ {
1215
+ const panel = new Panel();
1216
+ panel.addButton(null, options.action.name ?? "Accept", options.action.callback.bind( this, toast ), { width: "auto", maxWidth: "150px", className: "right", buttonClass: "outline" });
1217
+ toast.appendChild( panel.root.childNodes[ 0 ] );
1218
+ }
1219
+
1220
+ const that = this;
1221
+
1222
+ toast.close = function() {
1223
+ this.dataset[ "closed" ] = true;
1224
+ doAsync( () => {
1225
+ this.remove();
1226
+ if( !that.notifications.childElementCount )
1227
+ {
1228
+ that.notifications.style.width = "unset";
1229
+ that.notifications.iWidth = 0;
1230
+ }
1231
+ }, 500 );
1232
+ };
1233
+
1234
+ if( options.closable ?? true )
1235
+ {
1236
+ const closeButton = document.createElement( "a" );
1237
+ closeButton.className = "fa fa-xmark lexicon closer";
1238
+ closeButton.addEventListener( "click", () => {
1239
+ toast.close();
1240
+ } );
1241
+ toast.appendChild( closeButton );
1242
+ }
1243
+
1244
+ const timeout = options.timeout ?? 3000;
1245
+
1246
+ if( timeout != -1 )
1247
+ {
1248
+ doAsync( () => {
1249
+ toast.close();
1250
+ }, timeout );
1251
+ }
1252
+ }
1253
+
1254
+ LX.toast = toast;
1255
+
1056
1256
  /**
1057
1257
  * @method badge
1058
1258
  * @param {String} text
@@ -1354,7 +1554,8 @@ class Area {
1354
1554
 
1355
1555
  function inner_mousemove( e )
1356
1556
  {
1357
- switch( that.type ) {
1557
+ switch( that.type )
1558
+ {
1358
1559
  case "right":
1359
1560
  var dt = ( lastMousePosition[ 0 ] - e.x );
1360
1561
  var size = ( that.root.offsetWidth + dt );
@@ -1549,7 +1750,8 @@ class Area {
1549
1750
 
1550
1751
  // Listen resize event on first area
1551
1752
  const resizeObserver = new ResizeObserver( entries => {
1552
- for (const entry of entries) {
1753
+ for ( const entry of entries )
1754
+ {
1553
1755
  const bb = entry.contentRect;
1554
1756
  area2.root.style.height = "calc(100% - " + ( bb.height + 4) + "px )";
1555
1757
  }
@@ -1690,9 +1892,15 @@ class Area {
1690
1892
  this.root.style.height = height;
1691
1893
  }
1692
1894
 
1693
- this.size = [ this.root.clientWidth, this.root.clientHeight ];
1895
+ if( this.onresize )
1896
+ {
1897
+ this.onresize( this.root.getBoundingClientRect() );
1898
+ }
1694
1899
 
1695
- this.propagateEvent( "onresize" );
1900
+ doAsync( () => {
1901
+ this.size = [ this.root.clientWidth, this.root.clientHeight ];
1902
+ this.propagateEvent( "onresize" );
1903
+ }, 150 );
1696
1904
  }
1697
1905
 
1698
1906
  /**
@@ -1709,7 +1917,7 @@ class Area {
1709
1917
  let [area1, area2] = this.sections;
1710
1918
  this.splitExtended = true;
1711
1919
 
1712
- if(this.type == "vertical")
1920
+ if( this.type == "vertical")
1713
1921
  {
1714
1922
  this.offset = area2.root.offsetHeight;
1715
1923
  area2.root.classList.add("fadeout-vertical");
@@ -1723,7 +1931,6 @@ class Area {
1723
1931
  this._moveSplit(-Infinity, true, 8);
1724
1932
  }
1725
1933
 
1726
- // Async resize in some ms...
1727
1934
  doAsync( () => this.propagateEvent('onresize'), 150 );
1728
1935
  }
1729
1936
 
@@ -1739,7 +1946,7 @@ class Area {
1739
1946
  this.splitExtended = false;
1740
1947
  let [area1, area2] = this.sections;
1741
1948
 
1742
- if(this.type == "vertical")
1949
+ if( this.type == "vertical")
1743
1950
  {
1744
1951
  area2.root.classList.add("fadein-vertical");
1745
1952
  this._moveSplit(this.offset);
@@ -1750,7 +1957,6 @@ class Area {
1750
1957
  this._moveSplit(this.offset);
1751
1958
  }
1752
1959
 
1753
- // Async resize in some ms...
1754
1960
  doAsync( () => this.propagateEvent('onresize'), 150 );
1755
1961
  }
1756
1962
 
@@ -1784,11 +1990,15 @@ class Area {
1784
1990
 
1785
1991
  propagateEvent( eventName ) {
1786
1992
 
1787
- for(var i = 0; i < this.sections.length; i++)
1993
+ for( var i = 0; i < this.sections.length; i++ )
1788
1994
  {
1789
- const area = this.sections[i];
1790
- if(area[ eventName ])
1995
+ const area = this.sections[ i ];
1996
+
1997
+ if( area[ eventName ] )
1998
+ {
1791
1999
  area[ eventName ].call( this, area.root.getBoundingClientRect() );
2000
+ }
2001
+
1792
2002
  area.propagateEvent( eventName );
1793
2003
  }
1794
2004
  }
@@ -1811,42 +2021,63 @@ class Area {
1811
2021
  * @param {Function} callback Function to fill the menubar
1812
2022
  * @param {*} options:
1813
2023
  * float: Justify content (left, center, right) [left]
2024
+ * sticky: Fix menubar at the top [true]
1814
2025
  */
1815
2026
 
1816
2027
  addMenubar( callback, options = {} ) {
1817
2028
 
1818
- let menubar = new Menubar(options);
2029
+ let menubar = new Menubar( options );
1819
2030
 
1820
- if(callback) callback( menubar );
2031
+ if( callback )
2032
+ {
2033
+ callback( menubar );
2034
+ }
1821
2035
 
1822
2036
  LX.menubars.push( menubar );
1823
2037
 
1824
2038
  const height = 48; // pixels
2039
+ const [ bar, content ] = this.split({ type: 'vertical', sizes: [height, null], resize: false, menubar: true });
2040
+ menubar.siblingArea = content;
1825
2041
 
1826
- const [bar, content] = this.split({type: 'vertical', sizes: [height, null], resize: false, menubar: true});
1827
2042
  bar.attach( menubar );
1828
- bar.is_menubar = true;
2043
+ bar.isMenubar = true;
2044
+
2045
+ if( options.sticky ?? true )
2046
+ {
2047
+ bar.root.classList.add( "sticky" );
2048
+ }
2049
+
1829
2050
  return menubar;
1830
2051
  }
1831
2052
 
1832
2053
  /**
1833
2054
  * @method addSidebar
1834
2055
  * @param {Function} callback Function to fill the sidebar
2056
+ * @param {Object} options: Sidebar options
2057
+ * width: Width of the sidebar [16rem]
1835
2058
  */
1836
2059
 
1837
2060
  addSidebar( callback, options = {} ) {
1838
2061
 
1839
2062
  let sidebar = new SideBar( options );
1840
2063
 
1841
- if( callback ) callback( sidebar );
2064
+ if( callback )
2065
+ {
2066
+ callback( sidebar );
2067
+ }
2068
+
2069
+ // Generate DOM elements after adding all entries
2070
+ sidebar._build();
1842
2071
 
1843
2072
  LX.menubars.push( sidebar );
1844
2073
 
1845
- const width = 64; // pixels
2074
+ const width = options.width ?? "16rem";
2075
+ const [ bar, content ] = this.split( { type: 'horizontal', sizes: [ width, null ], resize: false, sidebar: true } );
2076
+ sidebar.siblingArea = content;
1846
2077
 
1847
- const [bar, content] = this.split( { type: 'horizontal', sizes: [ width, null ], resize: false, sidebar: true } );
1848
2078
  bar.attach( sidebar );
1849
- bar.is_sidebar = true;
2079
+ bar.isSidebar = true;
2080
+
1850
2081
  return sidebar;
1851
2082
  }
1852
2083
 
@@ -2102,7 +2333,8 @@ class Area {
2102
2333
 
2103
2334
  this.size = [ rect.width, rect.height ];
2104
2335
 
2105
- for(var i = 0; i < this.sections.length; i++) {
2336
+ for( var i = 0; i < this.sections.length; i++ )
2337
+ {
2106
2338
  this.sections[i]._update();
2107
2339
  }
2108
2340
  }
@@ -2126,7 +2358,7 @@ class Tabs {
2126
2358
  static TAB_SIZE = 28;
2127
2359
  static TAB_ID = 0;
2128
2360
 
2129
- constructor( area, options = {} ) {
2361
+ constructor( area, options = {} ) {
2130
2362
 
2131
2363
  this.onclose = options.onclose;
2132
2364
 
@@ -2208,24 +2440,28 @@ class Tabs {
2208
2440
  }
2209
2441
 
2210
2442
  // debug
2211
- if(folding)
2443
+ if( folding )
2212
2444
  {
2213
2445
  this.folded = true;
2214
2446
  this.folding = folding;
2215
2447
 
2216
- if(folding == "up") area.root.insertChildAtIndex(area.sections[1].root, 0);
2448
+ if( folding == "up" )
2449
+ {
2450
+ area.root.insertChildAtIndex(area.sections[1].root, 0);
2451
+ }
2217
2452
 
2218
2453
  // Listen resize event on parent area
2219
2454
  const resizeObserver = new ResizeObserver((entries) => {
2220
- for (const entry of entries) {
2455
+ for (const entry of entries)
2456
+ {
2221
2457
  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 )";
2458
+ const sibling = area.parentArea.sections[ 0 ].root;
2459
+ const addOffset = true; // hardcoded...
2460
+ sibling.style.height = "calc(100% - " + ((addOffset ? 42 : 0) + bb.height) + "px )";
2225
2461
  }
2226
2462
  });
2227
2463
 
2228
- resizeObserver.observe(this.area.root);
2464
+ resizeObserver.observe( this.area.root );
2229
2465
  this.area.root.classList.add('folded');
2230
2466
  }
2231
2467
  }
@@ -2362,7 +2598,8 @@ class Tabs {
2362
2598
 
2363
2599
  setTimeout( () => {
2364
2600
 
2365
- if( options.onCreate ) {
2601
+ if( options.onCreate )
2602
+ {
2366
2603
  options.onCreate.call(this, this.area.root.getBoundingClientRect());
2367
2604
  }
2368
2605
 
@@ -2424,34 +2661,41 @@ LX.Tabs = Tabs;
2424
2661
 
2425
2662
  class Menubar {
2426
2663
 
2427
- constructor( options = {} ) {
2664
+ constructor( options = {} ) {
2428
2665
 
2429
- this.root = document.createElement('div');
2666
+ this.root = document.createElement( "div" );
2430
2667
  this.root.className = "lexmenubar";
2431
- if(options.float)
2668
+
2669
+ if( options.float )
2670
+ {
2432
2671
  this.root.style.justifyContent = options.float;
2433
- this.items = [];
2672
+ }
2434
2673
 
2435
- this.icons = {};
2436
- this.shorts = {};
2437
- this.buttons = [];
2674
+ this.items = [ ];
2675
+ this.buttons = [ ];
2676
+ this.icons = { };
2677
+ this.shorts = { };
2438
2678
  }
2439
2679
 
2440
2680
  /**
2441
2681
  * @method add
2442
- * @param {*} options:
2682
+ * @param {Object} options:
2443
2683
  * callback: Function to call on each item
2684
+ * icon: Entry icon
2685
+ * short: Entry shortcut name
2444
2686
  */
2445
2687
 
2446
2688
  add( path, options = {} ) {
2447
2689
 
2448
- if(options.constructor == Function)
2690
+ if( options.constructor == Function )
2691
+ {
2449
2692
  options = { callback: options };
2693
+ }
2450
2694
 
2451
- // process path
2452
- const tokens = path.split("/");
2695
+ // Process path
2696
+ const tokens = path.split( "/" );
2453
2697
 
2454
- // assign icons and shortcuts to last token in path
2698
+ // Assign icons and shortcuts to last token in path
2455
2699
  const lastPath = tokens[tokens.length - 1];
2456
2700
  this.icons[ lastPath ] = options.icon;
2457
2701
  this.shorts[ lastPath ] = options.short;
@@ -2459,105 +2703,146 @@ class Menubar {
2459
2703
  let idx = 0;
2460
2704
  let that = this;
2461
2705
 
2462
- const insert = (token, list) => {
2463
- if(token == undefined) return;
2706
+ const _insertEntry = ( token, list ) => {
2707
+ if( token == undefined )
2708
+ {
2709
+ return;
2710
+ }
2464
2711
 
2465
2712
  let found = null;
2466
2713
  list.forEach( o => {
2467
- const keys = Object.keys(o);
2714
+ const keys = Object.keys( o );
2468
2715
  const key = keys.find( t => t == token );
2469
- if(key) found = o[ key ];
2716
+ if( key ) found = o[ key ];
2470
2717
  } );
2471
2718
 
2472
- if(found) {
2473
- insert( tokens[idx++], found );
2719
+ if( found )
2720
+ {
2721
+ _insertEntry( tokens[ idx++ ], found );
2474
2722
  }
2475
- else {
2723
+ else
2724
+ {
2476
2725
  let item = {};
2477
2726
  item[ token ] = [];
2478
- const next_token = tokens[idx++];
2727
+ const nextToken = tokens[ idx++ ];
2479
2728
  // Check if last token -> add callback
2480
- if(!next_token) {
2729
+ if( !nextToken )
2730
+ {
2481
2731
  item[ 'callback' ] = options.callback;
2732
+ item[ 'disabled' ] = options.disabled;
2482
2733
  item[ 'type' ] = options.type;
2483
2734
  item[ 'checked' ] = options.checked;
2484
2735
  }
2485
2736
  list.push( item );
2486
- insert( next_token, item[ token ] );
2737
+ _insertEntry( nextToken, item[ token ] );
2487
2738
  }
2488
2739
  };
2489
2740
 
2490
- insert( tokens[idx++], this.items );
2741
+ _insertEntry( tokens[idx++], this.items );
2491
2742
 
2492
2743
  // Create elements
2493
2744
 
2494
2745
  for( let item of this.items )
2495
2746
  {
2496
- let key = Object.keys(item)[0];
2497
- let pKey = key.replace(/\s/g, '').replaceAll('.', '');
2747
+ let key = Object.keys( item )[ 0 ];
2748
+ let pKey = key.replace( /\s/g, '' ).replaceAll( '.', '' );
2498
2749
 
2499
2750
  // Item already created
2500
- if( this.root.querySelector("#" + pKey) )
2751
+ if( this.root.querySelector( "#" + pKey ) )
2752
+ {
2501
2753
  continue;
2754
+ }
2502
2755
 
2503
2756
  let entry = document.createElement('div');
2504
2757
  entry.className = "lexmenuentry";
2505
2758
  entry.id = pKey;
2506
2759
  entry.innerHTML = "<span>" + key + "</span>";
2507
- if(options.position == "left") {
2760
+ entry.tabIndex = "1";
2761
+
2762
+ if( options.position == "left" )
2763
+ {
2508
2764
  this.root.prepend( entry );
2509
2765
  }
2510
- else {
2511
- if(options.position == "right")
2766
+ else
2767
+ {
2768
+ if( options.position == "right" )
2769
+ {
2512
2770
  entry.right = true;
2513
- if(this.root.lastChild && this.root.lastChild.right) {
2771
+ }
2772
+
2773
+ if( this.root.lastChild && this.root.lastChild.right )
2774
+ {
2514
2775
  this.root.lastChild.before( entry );
2515
2776
  }
2516
- else {
2777
+ else
2778
+ {
2517
2779
  this.root.appendChild( entry );
2518
2780
  }
2519
2781
  }
2520
2782
 
2783
+ const _resetMenubar = function() {
2784
+ // Menu entries are in the menubar..
2785
+ that.root.querySelectorAll(".lexmenuentry").forEach( _entry => {
2786
+ _entry.classList.remove( 'selected' );
2787
+ _entry.built = false;
2788
+ } );
2789
+ // Menuboxes are in the root area!
2790
+ LX.root.querySelectorAll(".lexmenubox").forEach(e => e.remove());
2791
+ // Next time we need to click again
2792
+ that.focused = false;
2793
+ };
2794
+
2521
2795
  const create_submenu = function( o, k, c, d ) {
2522
2796
 
2523
- let contextmenu = document.createElement('div');
2524
- contextmenu.className = "lexcontextmenu";
2525
- contextmenu.tabIndex = "0";
2526
- const isSubMenu = c.classList.contains('lexcontextmenuentry');
2797
+ let menuElement = document.createElement('div');
2798
+ menuElement.className = "lexmenubox";
2799
+ menuElement.tabIndex = "0";
2800
+ c.currentMenu = menuElement;
2801
+ const isSubMenu = c.classList.contains( "lexmenuboxentry" );
2802
+ if( isSubMenu ) menuElement.dataset[ "submenu" ] = true;
2527
2803
  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 );
2804
+ menuElement.style.left = ( isSubMenu ? ( rect.x + rect.width ) : rect.left ) + "px";
2805
+ menuElement.style.top = ( isSubMenu ? rect.y : rect.bottom - 4 ) + "px";
2806
+ rect = menuElement.getBoundingClientRect();
2532
2807
 
2533
- contextmenu.focus();
2808
+ doAsync( () => {
2809
+ menuElement.dataset[ "open" ] = true;
2810
+ }, 10 );
2534
2811
 
2535
- rect = contextmenu.getBoundingClientRect();
2812
+ LX.root.appendChild( menuElement );
2536
2813
 
2537
- for( var i = 0; i < o[k].length; ++i )
2814
+ for( var i = 0; i < o[ k ].length; ++i )
2538
2815
  {
2539
- const subitem = o[k][i];
2540
- const subkey = Object.keys(subitem)[0];
2816
+ const subitem = o[ k ][ i ];
2817
+ const subkey = Object.keys( subitem )[ 0 ];
2541
2818
  const hasSubmenu = subitem[ subkey ].length;
2542
2819
  const isCheckbox = subitem[ 'type' ] == 'checkbox';
2543
2820
  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 {
2821
+ subentry.className = "lexmenuboxentry";
2822
+ subentry.className += (i == o[k].length - 1 ? " last" : "") + ( subitem.disabled ? " disabled" : "" );
2549
2823
 
2824
+ if( subkey == '' )
2825
+ {
2826
+ subentry.className = " lexseparator";
2827
+ }
2828
+ else
2829
+ {
2550
2830
  subentry.id = subkey;
2551
2831
  let subentrycont = document.createElement('div');
2552
2832
  subentrycont.innerHTML = "";
2553
- subentrycont.classList = "lexcontextmenuentrycontainer";
2833
+ subentrycont.classList = "lexmenuboxentrycontainer";
2554
2834
  subentry.appendChild(subentrycont);
2555
2835
  const icon = that.icons[ subkey ];
2556
- if(isCheckbox){
2836
+ if( isCheckbox )
2837
+ {
2557
2838
  subentrycont.innerHTML += "<input type='checkbox' >";
2558
- }else if(icon) {
2839
+ }
2840
+ else if( icon )
2841
+ {
2559
2842
  subentrycont.innerHTML += "<a class='" + icon + " fa-sm'></a>";
2560
- }else {
2843
+ }
2844
+ else
2845
+ {
2561
2846
  subentrycont.innerHTML += "<a class='fa-solid fa-sm noicon'></a>";
2562
2847
  subentrycont.classList.add( "noicon" );
2563
2848
 
@@ -2565,46 +2850,54 @@ class Menubar {
2565
2850
  subentrycont.innerHTML += "<div class='lexentryname'>" + subkey + "</div>";
2566
2851
  }
2567
2852
 
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;
2853
+ let checkboxInput = subentry.querySelector('input');
2854
+ if( checkboxInput )
2855
+ {
2856
+ checkboxInput.checked = subitem.checked ?? false;
2857
+ checkboxInput.addEventListener('change', e => {
2858
+ subitem.checked = checkboxInput.checked;
2573
2859
  const f = subitem[ 'callback' ];
2574
- if(f) {
2860
+ if( f )
2861
+ {
2575
2862
  f.call( this, subitem.checked, subkey, subentry );
2576
- that.root.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
2863
+ _resetMenubar();
2577
2864
  }
2578
2865
  e.stopPropagation();
2579
2866
  e.stopImmediatePropagation();
2580
2867
  })
2581
2868
  }
2582
2869
 
2583
- contextmenu.appendChild( subentry );
2870
+ menuElement.appendChild( subentry );
2584
2871
 
2585
2872
  // Nothing more for separators
2586
- if(subkey == '') continue;
2873
+ if( subkey == '' )
2874
+ {
2875
+ continue;
2876
+ }
2587
2877
 
2588
- contextmenu.addEventListener('keydown', function(e) {
2878
+ menuElement.addEventListener('keydown', function(e) {
2589
2879
  e.preventDefault();
2590
2880
  let short = that.shorts[ subkey ];
2591
2881
  if(!short) return;
2592
2882
  // check if it's a letter or other key
2593
2883
  short = short.length == 1 ? short.toLowerCase() : short;
2594
- if(short == e.key) {
2884
+ if( short == e.key )
2885
+ {
2595
2886
  subentry.click()
2596
2887
  }
2597
2888
  });
2598
2889
 
2599
2890
  // Add callback
2600
2891
  subentry.addEventListener("click", e => {
2601
- if(checkbox_input) {
2892
+ if( checkboxInput )
2893
+ {
2602
2894
  subitem.checked = !subitem.checked;
2603
2895
  }
2604
2896
  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());
2897
+ if( f )
2898
+ {
2899
+ f.call( this, checkboxInput ? subitem.checked : subkey, checkboxInput ? subkey : subentry );
2900
+ _resetMenubar();
2608
2901
  }
2609
2902
  e.stopPropagation();
2610
2903
  e.stopImmediatePropagation();
@@ -2613,7 +2906,8 @@ class Menubar {
2613
2906
  // Add icon if has submenu, else check for shortcut
2614
2907
  if( !hasSubmenu)
2615
2908
  {
2616
- if(that.shorts[ subkey ]) {
2909
+ if( that.shorts[ subkey ] )
2910
+ {
2617
2911
  let shortEl = document.createElement('div');
2618
2912
  shortEl.className = "lexentryshort";
2619
2913
  shortEl.innerText = that.shorts[ subkey ];
@@ -2627,43 +2921,66 @@ class Menubar {
2627
2921
  subentry.appendChild( submenuIcon );
2628
2922
 
2629
2923
  subentry.addEventListener("mouseover", e => {
2630
- if(subentry.built)
2631
- return;
2924
+ if( subentry.built )
2925
+ {
2926
+ return;
2927
+ }
2632
2928
  subentry.built = true;
2633
2929
  create_submenu( subitem, subkey, subentry, ++d );
2634
2930
  e.stopPropagation();
2635
2931
  });
2636
2932
 
2637
- subentry.addEventListener("mouseleave", () => {
2638
- d = -1; // Reset depth
2639
- delete subentry.built;
2640
- contextmenu.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
2933
+ subentry.addEventListener("mouseleave", (e) => {
2934
+ if( subentry.currentMenu && ( subentry.currentMenu != e.toElement ) )
2935
+ {
2936
+ d = -1; // Reset depth
2937
+ delete subentry.built;
2938
+ subentry.currentMenu.remove();
2939
+ delete subentry.currentMenu;
2940
+ }
2641
2941
  });
2642
2942
  }
2643
2943
 
2644
2944
  // Set final width
2645
- contextmenu.style.width = contextmenu.offsetWidth + "px";
2945
+ menuElement.style.width = menuElement.offsetWidth + "px";
2646
2946
  };
2647
2947
 
2648
- entry.addEventListener("click", () => {
2948
+ const _showEntry = () => {
2949
+ _resetMenubar();
2950
+ entry.classList.add( "selected" );
2951
+ entry.built = true;
2952
+ create_submenu( item, key, entry, -1 );
2953
+ };
2649
2954
 
2955
+ entry.addEventListener("click", () => {
2650
2956
  const f = item[ 'callback' ];
2651
- if(f) {
2957
+ if( f )
2958
+ {
2652
2959
  f.call( this, key, entry );
2653
2960
  return;
2654
2961
  }
2655
2962
 
2656
- // Manage selected
2657
- this.root.querySelectorAll(".lexmenuentry").forEach( e => e.classList.remove( 'selected' ) );
2658
- entry.classList.add( "selected" );
2963
+ _showEntry();
2659
2964
 
2660
- this.root.querySelectorAll(".lexcontextmenu").forEach( e => e.remove() );
2661
- create_submenu( item, key, entry, -1 );
2965
+ this.focused = true;
2966
+ });
2967
+
2968
+ entry.addEventListener( "mouseover", (e) => {
2969
+
2970
+ if( this.focused && !entry.built )
2971
+ {
2972
+ _showEntry();
2973
+ }
2662
2974
  });
2663
2975
 
2664
- entry.addEventListener("mouseleave", () => {
2665
- this.root.querySelectorAll(".lexmenuentry").forEach( e => e.classList.remove( 'selected' ) );
2666
- this.root.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
2976
+ entry.addEventListener("blur", (e) => {
2977
+
2978
+ if( e.relatedTarget && e.relatedTarget.classList.contains( "lexmenubox" ) )
2979
+ {
2980
+ return;
2981
+ }
2982
+
2983
+ _resetMenubar();
2667
2984
  });
2668
2985
  }
2669
2986
  }
@@ -2682,20 +2999,24 @@ class Menubar {
2682
2999
  * @param {Object} item: parent item
2683
3000
  * @param {Array} tokens: split path strings
2684
3001
  */
2685
- getSubitem(item, tokens) {
3002
+ getSubitem( item, tokens ) {
2686
3003
 
2687
3004
  let subitem = null;
2688
- let path = tokens[0];
2689
- for(let i = 0; i < item.length; i++) {
2690
- if(item[i][path]) {
3005
+ let path = tokens[ 0 ];
2691
3006
 
2692
- if(tokens.length == 1) {
2693
- subitem = item[i];
3007
+ for( let i = 0; i < item.length; i++ )
3008
+ {
3009
+ if( item[ i ][ path ] )
3010
+ {
3011
+ if( tokens.length == 1 )
3012
+ {
3013
+ subitem = item[ i ];
2694
3014
  return subitem;
2695
3015
  }
2696
- else {
2697
- tokens.splice(0,1);
2698
- return this.getSubitem(item[i][path], tokens);
3016
+ else
3017
+ {
3018
+ tokens.splice( 0, 1 );
3019
+ return this.getSubitem( item[ i ][ path ], tokens );
2699
3020
  }
2700
3021
 
2701
3022
  }
@@ -2722,11 +3043,12 @@ class Menubar {
2722
3043
  setButtonIcon( title, icon, callback, options = {} ) {
2723
3044
 
2724
3045
  const button = this.buttons[ title ];
2725
- if(button) {
2726
-
3046
+ if( button )
3047
+ {
2727
3048
  button.querySelector('a').className = "fa-solid" + " " + icon + " lexicon";
2728
3049
  }
2729
- else {
3050
+ else
3051
+ {
2730
3052
  let button = document.createElement('div');
2731
3053
  const disabled = options.disabled ?? false;
2732
3054
  button.className = "lexmenubutton" + (disabled ? " disabled" : "");
@@ -2736,15 +3058,21 @@ class Menubar {
2736
3058
  button.style.maxHeight = "calc(100% - 10px)";
2737
3059
  button.style.alignItems = "center";
2738
3060
 
2739
- if(options.float == "right")
3061
+ if( options.float == "right" )
3062
+ {
2740
3063
  button.right = true;
2741
- if(this.root.lastChild && this.root.lastChild.right) {
3064
+ }
3065
+
3066
+ if( this.root.lastChild && this.root.lastChild.right )
3067
+ {
2742
3068
  this.root.lastChild.before( button );
2743
3069
  }
2744
- else if(options.float == "left") {
2745
- this.root.prepend(button);
3070
+ else if( options.float == "left" )
3071
+ {
3072
+ this.root.prepend( button );
2746
3073
  }
2747
- else {
3074
+ else
3075
+ {
2748
3076
  this.root.appendChild( button );
2749
3077
  }
2750
3078
 
@@ -2764,11 +3092,12 @@ class Menubar {
2764
3092
 
2765
3093
  setButtonImage( title, src, callback, options = {} ) {
2766
3094
  const button = this.buttons[ title ];
2767
- if(button) {
2768
-
3095
+ if( button )
3096
+ {
2769
3097
  button.querySelector('a').className = "fa-solid" + " " + icon + " lexicon";
2770
3098
  }
2771
- else {
3099
+ else
3100
+ {
2772
3101
  let button = document.createElement('div');
2773
3102
  const disabled = options.disabled ?? false;
2774
3103
  button.className = "lexmenubutton" + (disabled ? " disabled" : "");
@@ -2777,15 +3106,21 @@ class Menubar {
2777
3106
  button.style.padding = "5px";
2778
3107
  button.style.alignItems = "center";
2779
3108
 
2780
- if(options.float == "right")
3109
+ if( options.float == "right" )
3110
+ {
2781
3111
  button.right = true;
2782
- if(this.root.lastChild && this.root.lastChild.right) {
3112
+ }
3113
+
3114
+ if( this.root.lastChild && this.root.lastChild.right )
3115
+ {
2783
3116
  this.root.lastChild.before( button );
2784
3117
  }
2785
- else if(options.float == "left") {
2786
- this.root.prepend(button);
3118
+ else if( options.float == "left" )
3119
+ {
3120
+ this.root.prepend( button );
2787
3121
  }
2788
- else {
3122
+ else
3123
+ {
2789
3124
  this.root.appendChild( button );
2790
3125
  }
2791
3126
 
@@ -2898,90 +3233,306 @@ LX.Menubar = Menubar;
2898
3233
 
2899
3234
  class SideBar {
2900
3235
 
2901
- constructor( options = {} ) {
3236
+ /**
3237
+ * @param {Object} options
3238
+ * inset: TODO
3239
+ * filter: TODO
3240
+ * skipHeader: Do not use sidebar header [false]
3241
+ * headerImg: Image to be shown as avatar
3242
+ * headerIcon: Icon to be shown as avatar (from LX.ICONS)
3243
+ * headerTitle
3244
+ * headerSubtitle
3245
+ * skipFooter: Do not use sidebar footer [false]
3246
+ * footerImg: Image to be shown as avatar
3247
+ * footerIcon: Icon to be shown as avatar (from LX.ICONS)
3248
+ * footerTitle
3249
+ * footerSubtitle
3250
+ * collapsable: Sidebar can toggle between collapsed/expanded [true]
3251
+ * collapseToIcons: When Sidebar collapses, icons remains visible [true]
3252
+ * onHeaderPressed: Function to call when header is pressed
3253
+ * onFooterPressed: Function to call when footer is pressed
3254
+ */
3255
+
3256
+ constructor( options = {} ) {
2902
3257
 
2903
3258
  this.root = document.createElement( 'div' );
2904
3259
  this.root.className = "lexsidebar";
2905
3260
 
2906
- this.footer = document.createElement( 'div' );
2907
- this.footer.className = "lexsidebarfooter";
2908
- this.root.appendChild( this.footer );
3261
+ window.sidebar = this;
2909
3262
 
2910
- this.items = [ ];
2911
- }
3263
+ this.collapsable = options.collapsable ?? true;
3264
+ this.collapseWidth = ( options.collapseToIcons ?? true ) ? "58px" : "0px";
3265
+ this.collapsed = false;
2912
3266
 
2913
- /**
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
2919
- */
3267
+ doAsync( () => {
2920
3268
 
2921
- add( key, options = {} ) {
3269
+ this.root.parentElement.ogWidth = this.root.parentElement.style.width;
3270
+ this.root.parentElement.style.transition = "width 0.25s ease-out";
2922
3271
 
2923
- if( options.constructor == Function )
2924
- options = { callback: options };
3272
+ this.resizeObserver = new ResizeObserver( entries => {
3273
+ for ( const entry of entries )
3274
+ {
3275
+ this.siblingArea.setSize( [ "calc(100% - " + ( entry.contentRect.width ) + "px )", null ] );
3276
+ }
3277
+ });
3278
+
3279
+ }, 100 );
3280
+
3281
+ // This account for header, footer and all inner paddings
3282
+ let contentOffset = 32;
3283
+
3284
+ // Header
3285
+ if( !( options.skipHeader ?? false ) )
3286
+ {
3287
+ this.header = document.createElement( 'div' );
3288
+ this.header.className = "lexsidebarheader";
3289
+ this.root.appendChild( this.header );
3290
+
3291
+ this.header.addEventListener( "click", e => {
3292
+ if( this.collapsed )
3293
+ {
3294
+ e.preventDefault();
3295
+ e.stopPropagation();
3296
+ this.toggleCollapsed();
3297
+ }
3298
+ else if( options.onHeaderPressed )
3299
+ {
3300
+ options.onHeaderPressed( e );
3301
+ }
3302
+ } );
3303
+
3304
+ const avatar = document.createElement( 'span' );
3305
+ avatar.className = "lexavatar";
3306
+ this.header.appendChild( avatar );
3307
+
3308
+ if( options.headerImage )
3309
+ {
3310
+ const avatarImg = document.createElement( 'img' );
3311
+ avatarImg.src = options.headerImage;
3312
+ avatar.appendChild( avatarImg );
3313
+ }
3314
+ else if( options.headerIcon )
3315
+ {
3316
+ const avatarIcon = LX.makeIcon( options.headerIcon );
3317
+ avatar.appendChild( avatarIcon );
3318
+ }
3319
+
3320
+ // Info
3321
+ {
3322
+ const info = document.createElement( 'div' );
3323
+ this.header.appendChild( info );
3324
+
3325
+ const infoText = document.createElement( 'span' );
3326
+ infoText.innerHTML = options.headerTitle ?? "";
3327
+ info.appendChild( infoText );
3328
+
3329
+ const infoSubtext = document.createElement( 'span' );
3330
+ infoSubtext.innerHTML = options.headerSubtitle ?? "";
3331
+ info.appendChild( infoSubtext );
3332
+ }
3333
+
3334
+ if( this.collapsable )
3335
+ {
3336
+ const icon = LX.makeIcon( "Sidebar", "Toggle Sidebar" );
3337
+ this.header.appendChild( icon );
3338
+
3339
+ icon.addEventListener( "click", (e) => {
3340
+ e.preventDefault();
3341
+ e.stopPropagation();
3342
+ this.toggleCollapsed();
3343
+ } );
3344
+ }
3345
+
3346
+ contentOffset += 52;
3347
+ }
3348
+
3349
+ // Content
3350
+ {
3351
+ this.content = document.createElement( 'div' );
3352
+ this.content.className = "lexsidebarcontent";
3353
+ this.root.appendChild( this.content );
3354
+ }
3355
+
3356
+ // Footer
3357
+ if( !( options.skipFooter ?? false ) )
3358
+ {
3359
+ this.footer = document.createElement( 'div' );
3360
+ this.footer.className = "lexsidebarfooter";
3361
+ this.root.appendChild( this.footer );
3362
+
3363
+ this.footer.addEventListener( "click", e => {
3364
+ if( options.onFooterPressed )
3365
+ {
3366
+ options.onFooterPressed( e );
3367
+ }
3368
+ } );
2925
3369
 
2926
- let pKey = key.replace( /\s/g, '' ).replaceAll( '.', '' );
3370
+ const avatar = document.createElement( 'span' );
3371
+ avatar.className = "lexavatar";
3372
+ this.footer.appendChild( avatar );
2927
3373
 
2928
- if( this.items.findIndex( (v, i) => v.key == pKey ) > -1 )
3374
+ if( options.footerImage )
3375
+ {
3376
+ const avatarImg = document.createElement( 'img' );
3377
+ avatarImg.src = options.footerImage;
3378
+ avatar.appendChild( avatarImg );
3379
+ }
3380
+ else if( options.footerIcon )
3381
+ {
3382
+ const avatarIcon = LX.makeIcon( options.footerIcon );
3383
+ avatar.appendChild( avatarIcon );
3384
+ }
3385
+
3386
+ // Info
3387
+ {
3388
+ const info = document.createElement( 'div' );
3389
+ this.footer.appendChild( info );
3390
+
3391
+ const infoText = document.createElement( 'span' );
3392
+ infoText.innerHTML = options.footerTitle ?? "";
3393
+ info.appendChild( infoText );
3394
+
3395
+ const infoSubtext = document.createElement( 'span' );
3396
+ infoSubtext.innerHTML = options.footerSubtitle ?? "";
3397
+ info.appendChild( infoSubtext );
3398
+ }
3399
+
3400
+ const icon = LX.makeIcon( "MenuArrows" );
3401
+ this.footer.appendChild( icon );
3402
+
3403
+ contentOffset += 52;
3404
+ }
3405
+
3406
+ // Set width depending on header/footer
3407
+ this.content.style.height = `calc(100% - ${ contentOffset }px)`;
3408
+
3409
+ this.items = [ ];
3410
+ this.icons = { };
3411
+ this.groups = { };
3412
+ }
3413
+
3414
+ /**
3415
+ * @method toggleCollapsed
3416
+ */
3417
+
3418
+ toggleCollapsed() {
3419
+
3420
+ if( !this.collapsable )
2929
3421
  {
2930
- console.warn( `'${key}' already created in Sidebar` );
2931
3422
  return;
2932
3423
  }
2933
3424
 
2934
- let entry = document.createElement( 'div' );
2935
- entry.className = "lexsidebarentry " + ( options.className ?? "" );
2936
- entry.id = pKey;
3425
+ this.collapsed = !this.collapsed;
2937
3426
 
2938
- if( options.bottom )
3427
+ if( this.collapsed )
2939
3428
  {
2940
- this.footer.appendChild( entry );
3429
+ this.root.classList.add( "collapsing" );
3430
+ this.root.parentElement.style.width = this.collapseWidth;
2941
3431
  }
2942
3432
  else
2943
3433
  {
2944
- this.root.appendChild( entry );
3434
+ this.root.classList.remove( "collapsing" );
3435
+ this.root.classList.remove( "collapsed" );
3436
+ this.root.parentElement.style.width = this.root.parentElement.ogWidth;
2945
3437
  }
2946
3438
 
2947
- // Reappend footer in root
2948
- this.root.appendChild( this.footer );
3439
+ this.resizeObserver.observe( this.root.parentElement );
2949
3440
 
2950
- let button = document.createElement( 'button' );
2951
- button.innerHTML = "<i class='"+ (options.icon ?? "") + "'></i>";
2952
- entry.appendChild( button );
3441
+ doAsync( () => {
2953
3442
 
2954
- let desc = document.createElement( 'span' );
2955
- desc.className = 'lexsidebarentrydesc';
2956
- desc.innerHTML = key;
2957
- entry.appendChild( desc );
3443
+ this.root.classList.toggle( "collapsed", this.collapsed );
3444
+ this.resizeObserver.unobserve( this.root.parentElement );
2958
3445
 
2959
- button.addEventListener("mouseenter", () => {
2960
- setTimeout( () => {
2961
- desc.style.display = "unset";
2962
- }, 100 );
2963
- });
3446
+ }, 250 );
3447
+ }
2964
3448
 
2965
- button.addEventListener("mouseleave", () => {
2966
- setTimeout( () => {
2967
- desc.style.display = "none";
2968
- }, 100 );
2969
- });
3449
+ /**
3450
+ * @method separator
3451
+ */
3452
+
3453
+ separator() {
3454
+
3455
+ this.currentGroup = null;
3456
+
3457
+ this.add( "" );
3458
+ }
2970
3459
 
2971
- entry.addEventListener("click", () => {
3460
+ /**
3461
+ * @method group
3462
+ * @param {String} groupName
3463
+ * @param {Object} action: { icon, callback }
3464
+ */
3465
+
3466
+ group( groupName, action ) {
2972
3467
 
2973
- const f = options.callback;
2974
- if( f ) f.call( this, key, entry );
3468
+ this.currentGroup = groupName;
3469
+
3470
+ this.groups[ groupName ] = action;
3471
+ }
2975
3472
 
2976
- // Manage selected
2977
- if( !options.bottom )
3473
+ /**
3474
+ * @method add
3475
+ * @param {String} path
3476
+ * @param {Object} options:
3477
+ * callback: Function to call on each item
3478
+ * icon: Entry icon
3479
+ * collapsable: Add entry as a collapsable section
3480
+ * className: Add class to the entry DOM element
3481
+ */
3482
+
3483
+ add( path, options = {} ) {
3484
+
3485
+ if( options.constructor == Function )
3486
+ {
3487
+ options = { callback: options };
3488
+ }
3489
+
3490
+ // Process path
3491
+ const tokens = path.split( "/" );
3492
+
3493
+ // Assign icons and shortcuts to last token in path
3494
+ const lastPath = tokens[tokens.length - 1];
3495
+ this.icons[ lastPath ] = options.icon;
3496
+
3497
+
3498
+ let idx = 0;
3499
+
3500
+ const _insertEntry = ( token, list ) => {
3501
+
3502
+ if( token == undefined )
2978
3503
  {
2979
- this.root.querySelectorAll(".lexsidebarentry").forEach( e => e.classList.remove( 'selected' ) );
2980
- entry.classList.add( "selected" );
3504
+ return;
2981
3505
  }
2982
- });
2983
3506
 
2984
- this.items.push( { name: pKey, domEl: entry, callback: options.callback } );
3507
+ let found = null;
3508
+ list.forEach( o => {
3509
+ const keys = Object.keys( o );
3510
+ const key = keys.find( t => t == token );
3511
+ if( key ) found = o[ key ];
3512
+ } );
3513
+
3514
+ if( found )
3515
+ {
3516
+ _insertEntry( tokens[ idx++ ], found );
3517
+ }
3518
+ else
3519
+ {
3520
+ let item = {};
3521
+ item[ token ] = [];
3522
+ const nextToken = tokens[ idx++ ];
3523
+ // Check if last token -> add callback
3524
+ if( !nextToken )
3525
+ {
3526
+ item[ 'callback' ] = options.callback;
3527
+ item[ 'group' ] = this.currentGroup;
3528
+ item[ 'options' ] = options;
3529
+ }
3530
+ list.push( item );
3531
+ _insertEntry( nextToken, item[ token ] );
3532
+ }
3533
+ };
3534
+
3535
+ _insertEntry( tokens[idx++], this.items );
2985
3536
  }
2986
3537
 
2987
3538
  /**
@@ -3000,6 +3551,246 @@ class SideBar {
3000
3551
 
3001
3552
  entry.domEl.click();
3002
3553
  }
3554
+
3555
+ _build() {
3556
+
3557
+ for( let item of this.items )
3558
+ {
3559
+ const options = item.options ?? { };
3560
+
3561
+ // Item already created
3562
+ if( item.dom )
3563
+ {
3564
+ continue;
3565
+ }
3566
+
3567
+ let key = Object.keys( item )[ 0 ];
3568
+ let pKey = key.replace( /\s/g, '' ).replaceAll( '.', '' );
3569
+ let currentGroup = null;
3570
+
3571
+ let entry = document.createElement( 'div' );
3572
+ entry.className = "lexsidebarentry " + ( options.className ?? "" );
3573
+ entry.id = pKey;
3574
+
3575
+ if( item.group )
3576
+ {
3577
+ const pGroupKey = item.group.replace( /\s/g, '' ).replaceAll( '.', '' );
3578
+ currentGroup = this.content.querySelector( "#" + pGroupKey );
3579
+
3580
+ if( !currentGroup )
3581
+ {
3582
+ currentGroup = document.createElement( 'div' );
3583
+ currentGroup.id = pGroupKey;
3584
+ currentGroup.className = "lexsidebargroup";
3585
+ this.content.appendChild( currentGroup );
3586
+
3587
+ let groupEntry = document.createElement( 'div' );
3588
+ groupEntry.className = "lexsidebargrouptitle";
3589
+ currentGroup.appendChild( groupEntry );
3590
+
3591
+ let groupLabel = document.createElement( 'div' );
3592
+ groupLabel.innerHTML = item.group;
3593
+ groupEntry.appendChild( groupLabel );
3594
+
3595
+ if( this.groups[ item.group ] != null )
3596
+ {
3597
+ let groupAction = document.createElement( 'a' );
3598
+ groupAction.className = ( this.groups[ item.group ].icon ?? "" ) + " lexicon";
3599
+ groupEntry.appendChild( groupAction );
3600
+ groupAction.addEventListener( "click", (e) => {
3601
+ if( this.groups[ item.group ].callback )
3602
+ {
3603
+ this.groups[ item.group ].callback( item.group, e );
3604
+ }
3605
+ } );
3606
+ }
3607
+
3608
+ }
3609
+ else if( !currentGroup.classList.contains( "lexsidebargroup" ) )
3610
+ {
3611
+ throw( "Bad id: " + item.group );
3612
+ }
3613
+ }
3614
+
3615
+ if( pKey == "" )
3616
+ {
3617
+ let separatorDom = document.createElement( 'div' );
3618
+ separatorDom.className = "lexsidebarseparator";
3619
+ this.content.appendChild( separatorDom );
3620
+ continue;
3621
+ }
3622
+
3623
+ if( this.collapseContainer )
3624
+ {
3625
+ this.collapseContainer.appendChild( entry );
3626
+
3627
+ this.collapseQueue--;
3628
+ if( !this.collapseQueue )
3629
+ {
3630
+ delete this.collapseContainer;
3631
+ }
3632
+ }
3633
+ else if( currentGroup )
3634
+ {
3635
+ currentGroup.appendChild( entry );
3636
+ }
3637
+ else
3638
+ {
3639
+ this.content.appendChild( entry );
3640
+ }
3641
+
3642
+ let itemDom = document.createElement( 'div' );
3643
+ entry.appendChild( itemDom );
3644
+ item.dom = itemDom;
3645
+
3646
+ if( options.type == "checkbox" )
3647
+ {
3648
+ item.value = options.value ?? false;
3649
+ const panel = new Panel();
3650
+ item.checkbox = panel.addCheckbox(null, item.value, (value, event) => {
3651
+ event.preventDefault();
3652
+ event.stopPropagation();
3653
+ const f = options.callback;
3654
+ item.value = value;
3655
+ if( f ) f.call( this, key, value, event );
3656
+ }, { label: key, signal: ( "@checkbox_" + key ) });
3657
+ itemDom.appendChild( panel.root.childNodes[ 0 ] );
3658
+ }
3659
+ else
3660
+ {
3661
+ if( options.icon )
3662
+ {
3663
+ let itemIcon = document.createElement( 'i' );
3664
+ itemIcon.className = options.icon;
3665
+ itemDom.appendChild( itemIcon );
3666
+ }
3667
+
3668
+ let itemName = document.createElement( 'a' );
3669
+ itemName.innerHTML = key;
3670
+ itemDom.appendChild( itemName );
3671
+ }
3672
+
3673
+ entry.addEventListener("click", ( e ) => {
3674
+ if( e.target && e.target.classList.contains( "lexcheckbox" ) )
3675
+ {
3676
+ return;
3677
+ }
3678
+
3679
+ if( options.collapsable )
3680
+ {
3681
+ itemDom.querySelector( ".collapser" ).click();
3682
+ }
3683
+ else
3684
+ {
3685
+ const f = options.callback;
3686
+ if( f ) f.call( this, key, item.value, e );
3687
+
3688
+ if( item.checkbox )
3689
+ {
3690
+ item.value = !item.value;
3691
+ item.checkbox.set( item.value, true );
3692
+ }
3693
+ }
3694
+
3695
+ // Manage selected
3696
+ this.root.querySelectorAll(".lexsidebarentry").forEach( e => e.classList.remove( 'selected' ) );
3697
+ entry.classList.add( "selected" );
3698
+ });
3699
+
3700
+ if( options.action )
3701
+ {
3702
+ const actionIcon = LX.makeIcon( options.action.icon ?? "MoreHorizontal", options.action.name );
3703
+ itemDom.appendChild( actionIcon );
3704
+
3705
+ actionIcon.addEventListener( "click", (e) => {
3706
+ e.preventDefault();
3707
+ e.stopImmediatePropagation();
3708
+ const f = options.action.callback;
3709
+ if( f ) f.call( this, key, e );
3710
+ } );
3711
+ }
3712
+ else if( options.collapsable )
3713
+ {
3714
+ const collapsableContent = document.createElement( 'div' );
3715
+ Object.assign( collapsableContent.style, { width: "100%", display: "none" } );
3716
+ LX.makeCollapsible( itemDom, collapsableContent, currentGroup ?? this.content );
3717
+ this.collapseQueue = options.collapsable;
3718
+ this.collapseContainer = collapsableContent;
3719
+ }
3720
+
3721
+ let desc = document.createElement( 'span' );
3722
+ desc.className = 'lexsidebarentrydesc';
3723
+ desc.innerHTML = key;
3724
+ entry.appendChild( desc );
3725
+
3726
+ itemDom.addEventListener("mouseenter", () => {
3727
+ setTimeout( () => {
3728
+ desc.style.display = "unset";
3729
+ }, 150 );
3730
+ });
3731
+
3732
+ itemDom.addEventListener("mouseleave", () => {
3733
+ setTimeout( () => {
3734
+ desc.style.display = "none";
3735
+ }, 150 );
3736
+ });
3737
+
3738
+ // Subentries
3739
+ if( !item[ key ].length )
3740
+ {
3741
+ continue;
3742
+ }
3743
+
3744
+ let subentryContainer = document.createElement( 'div' );
3745
+ subentryContainer.className = "lexsidebarsubentrycontainer";
3746
+
3747
+ if( currentGroup )
3748
+ {
3749
+ currentGroup.appendChild( subentryContainer );
3750
+ }
3751
+ else
3752
+ {
3753
+ this.content.appendChild( subentryContainer );
3754
+ }
3755
+
3756
+ for( var i = 0; i < item[ key ].length; ++i )
3757
+ {
3758
+ const subitem = item[ key ][ i ];
3759
+ const suboptions = subitem.options ?? {};
3760
+ const subkey = Object.keys( subitem )[ 0 ];
3761
+
3762
+ let subentry = document.createElement( 'div' );
3763
+ subentry.innerHTML = `<span>${ subkey }</span>`;
3764
+
3765
+ if( suboptions.action )
3766
+ {
3767
+ const actionIcon = LX.makeIcon( suboptions.action.icon ?? "MoreHorizontal", suboptions.action.name );
3768
+ subentry.appendChild( actionIcon );
3769
+
3770
+ actionIcon.addEventListener( "click", (e) => {
3771
+ e.preventDefault();
3772
+ e.stopImmediatePropagation();
3773
+ const f = suboptions.action.callback;
3774
+ if( f ) f.call( this, subkey, e );
3775
+ } );
3776
+ }
3777
+
3778
+ subentry.className = "lexsidebarentry";
3779
+ subentry.id = subkey;
3780
+ subentryContainer.appendChild( subentry );
3781
+
3782
+ subentry.addEventListener("click", (e) => {
3783
+
3784
+ const f = suboptions.callback;
3785
+ if( f ) f.call( this, subkey, subentry, e );
3786
+
3787
+ // Manage selected
3788
+ this.root.querySelectorAll(".lexsidebarentry").forEach( e => e.classList.remove( 'selected' ) );
3789
+ entry.classList.add( "selected" );
3790
+ });
3791
+ }
3792
+ }
3793
+ }
3003
3794
  };
3004
3795
 
3005
3796
  LX.SideBar = SideBar;
@@ -3017,30 +3808,32 @@ class Widget {
3017
3808
  static DROPDOWN = 4;
3018
3809
  static CHECKBOX = 5;
3019
3810
  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;
3811
+ static RADIO = 7;
3812
+ static COLOR = 8;
3813
+ static RANGE = 9;
3814
+ static NUMBER = 10;
3815
+ static TITLE = 11;
3816
+ static VECTOR = 12;
3817
+ static TREE = 13;
3818
+ static PROGRESS = 14;
3819
+ static FILE = 15;
3820
+ static LAYERS = 16;
3821
+ static ARRAY = 17;
3822
+ static LIST = 18;
3823
+ static TAGS = 19;
3824
+ static CURVE = 20;
3825
+ static CARD = 21;
3826
+ static IMAGE = 22;
3827
+ static CONTENT = 23;
3828
+ static CUSTOM = 24;
3829
+ static SEPARATOR = 25;
3830
+ static KNOB = 26;
3831
+ static SIZE = 27;
3832
+ static PAD = 28;
3833
+ static FORM = 29;
3834
+ static DIAL = 30;
3835
+ static COUNTER = 31;
3836
+ static TABLE = 32;
3044
3837
 
3045
3838
  static NO_CONTEXT_TYPES = [
3046
3839
  Widget.BUTTON,
@@ -3068,7 +3861,9 @@ class Widget {
3068
3861
  set( value, skipCallback = false, signalName = "" ) {
3069
3862
 
3070
3863
  if( this.onSetValue )
3864
+ {
3071
3865
  return this.onSetValue( value, skipCallback );
3866
+ }
3072
3867
 
3073
3868
  console.warn("Can't set value of " + this.typeName());
3074
3869
  }
@@ -3107,14 +3902,17 @@ class Widget {
3107
3902
 
3108
3903
  typeName() {
3109
3904
 
3110
- switch( this.type ) {
3905
+ switch( this.type )
3906
+ {
3111
3907
  case Widget.TEXT: return "Text";
3112
3908
  case Widget.TEXTAREA: return "TextArea";
3113
3909
  case Widget.BUTTON: return "Button";
3114
3910
  case Widget.DROPDOWN: return "Dropdown";
3115
3911
  case Widget.CHECKBOX: return "Checkbox";
3116
3912
  case Widget.TOGGLE: return "Toggle";
3913
+ case Widget.RADIO: return "Radio";
3117
3914
  case Widget.COLOR: return "Color";
3915
+ case Widget.RANGE: return "Range";
3118
3916
  case Widget.NUMBER: return "Number";
3119
3917
  case Widget.VECTOR: return "Vector";
3120
3918
  case Widget.TREE: return "Tree";
@@ -3192,11 +3990,12 @@ function ADD_CUSTOM_WIDGET( custom_widget_name, options = {} )
3192
3990
  buttonName += "<a class='fa-solid " + (instance ? "fa-bars-staggered" : " ") + " menu' style='float:right; width:5%;'></a>";
3193
3991
 
3194
3992
  let buttonEl = this.addButton(null, buttonName, (value, event) => {
3195
-
3196
- if( instance ) {
3993
+ if( instance )
3994
+ {
3197
3995
  element.querySelector(".lexcustomitems").toggleAttribute('hidden');
3198
3996
  }
3199
- else {
3997
+ else
3998
+ {
3200
3999
  addContextMenu(null, event, c => {
3201
4000
  c.add("New " + custom_widget_name, () => {
3202
4001
  instance = {};
@@ -3383,16 +4182,20 @@ class NodeTree {
3383
4182
 
3384
4183
  // Add or remove
3385
4184
  const idx = this.selected.indexOf( node );
3386
- if( idx > -1 ) {
4185
+ if( idx > -1 )
4186
+ {
3387
4187
  item.classList.remove( 'selected' );
3388
4188
  this.selected.splice( idx, 1 );
3389
- }else {
4189
+ }
4190
+ else
4191
+ {
3390
4192
  item.classList.add( 'selected' );
3391
4193
  this.selected.push( node );
3392
4194
  }
3393
4195
 
3394
4196
  // Only Show children...
3395
- if( isParent && node.id.length > 1 /* Strange case... */) {
4197
+ if( isParent && node.id.length > 1 /* Strange case... */)
4198
+ {
3396
4199
  node.closed = false;
3397
4200
  if( that.onevent )
3398
4201
  {
@@ -3430,7 +4233,7 @@ class NodeTree {
3430
4233
 
3431
4234
  e.preventDefault();
3432
4235
 
3433
- if( that.onevent )
4236
+ if( !that.onevent )
3434
4237
  {
3435
4238
  return;
3436
4239
  }
@@ -3486,7 +4289,8 @@ class NodeTree {
3486
4289
  return;
3487
4290
  }
3488
4291
 
3489
- if( that.onevent ) {
4292
+ if( that.onevent )
4293
+ {
3490
4294
  const event = new TreeEvent( TreeEvent.NODE_DELETED, node, e );
3491
4295
  that.onevent( event );
3492
4296
  }
@@ -3513,7 +4317,8 @@ class NodeTree {
3513
4317
  if( e.key == "Delete" )
3514
4318
  {
3515
4319
  // Send event now so we have the info in selected array..
3516
- if( that.onevent ) {
4320
+ if( that.onevent )
4321
+ {
3517
4322
  const event = new TreeEvent( TreeEvent.NODE_DELETED, this.selected.length > 1 ? this.selected : node, e );
3518
4323
  event.multiple = this.selected.length > 1;
3519
4324
  that.onevent( event );
@@ -3544,22 +4349,24 @@ class NodeTree {
3544
4349
 
3545
4350
  // Node rename
3546
4351
 
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);
4352
+ const nameInput = document.createElement( "input" );
4353
+ nameInput.toggleAttribute( "hidden", !node.rename );
4354
+ nameInput.value = node.id;
4355
+ item.appendChild(nameInput);
3551
4356
 
3552
- if(node.rename) {
4357
+ if( node.rename )
4358
+ {
3553
4359
  item.classList.add('selected');
3554
- name_input.focus();
4360
+ nameInput.focus();
3555
4361
  }
3556
4362
 
3557
- name_input.addEventListener("keyup", function(e){
3558
- if(e.key == 'Enter') {
3559
-
4363
+ nameInput.addEventListener("keyup", function(e){
4364
+ if( e.key == "Enter" )
4365
+ {
3560
4366
  this.value = this.value.replace(/\s/g, '_');
3561
4367
 
3562
- if(that.onevent) {
4368
+ if( that.onevent )
4369
+ {
3563
4370
  const event = new TreeEvent(TreeEvent.NODE_RENAMED, node, this.value);
3564
4371
  that.onevent( event );
3565
4372
  }
@@ -3569,18 +4376,20 @@ class NodeTree {
3569
4376
  that.frefresh( node.id );
3570
4377
  list.querySelector("#" + node.id).classList.add('selected');
3571
4378
  }
3572
- if(e.key == 'Escape') {
4379
+ else if(e.key == "Escape")
4380
+ {
3573
4381
  delete node.rename;
3574
4382
  that.frefresh( node.id );
3575
4383
  }
3576
4384
  });
3577
4385
 
3578
- name_input.addEventListener("blur", function(e){
4386
+ nameInput.addEventListener("blur", function(e){
3579
4387
  delete node.rename;
3580
4388
  that.refresh();
3581
4389
  });
3582
4390
 
3583
- if(this.options.draggable ?? true) {
4391
+ if( this.options.draggable ?? true )
4392
+ {
3584
4393
  // Drag nodes
3585
4394
  if(parent) // Root doesn't move!
3586
4395
  {
@@ -3606,29 +4415,32 @@ class NodeTree {
3606
4415
  return;
3607
4416
  let target = node;
3608
4417
  // Can't drop to same node
3609
- if(dragged.id == target.id) {
4418
+ if( dragged.id == target.id )
4419
+ {
3610
4420
  console.warn("Cannot parent node to itself!");
3611
4421
  return;
3612
4422
  }
3613
4423
 
3614
4424
  // Can't drop to child node
3615
- const isChild = function(new_parent, node) {
4425
+ const isChild = function( newParent, node ) {
3616
4426
  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);
4427
+ for( var c of node.children )
4428
+ {
4429
+ if( c.id == newParent.id ) return true;
4430
+ result |= isChild( newParent, c );
3621
4431
  }
3622
4432
  return result;
3623
4433
  };
3624
4434
 
3625
- if(isChild(target, dragged)) {
4435
+ if( isChild( target, dragged ))
4436
+ {
3626
4437
  console.warn("Cannot parent node to a current child!");
3627
4438
  return;
3628
4439
  }
3629
4440
 
3630
4441
  // Trigger node dragger event
3631
- if(that.onevent) {
4442
+ if( that.onevent )
4443
+ {
3632
4444
  const event = new TreeEvent(TreeEvent.NODE_DRAGGED, dragged, target);
3633
4445
  that.onevent( event );
3634
4446
  }
@@ -3644,7 +4456,8 @@ class NodeTree {
3644
4456
  let handled = false;
3645
4457
 
3646
4458
  // Show/hide children
3647
- if(isParent) {
4459
+ if( isParent )
4460
+ {
3648
4461
  item.querySelector('a.hierarchy').addEventListener("click", function(e) {
3649
4462
 
3650
4463
  handled = true;
@@ -3652,7 +4465,8 @@ class NodeTree {
3652
4465
  e.stopPropagation();
3653
4466
 
3654
4467
  node.closed = !node.closed;
3655
- if(that.onevent) {
4468
+ if( that.onevent )
4469
+ {
3656
4470
  const event = new TreeEvent(TreeEvent.NODE_CARETCHANGED, node, node.closed);
3657
4471
  that.onevent( event );
3658
4472
  }
@@ -3672,7 +4486,8 @@ class NodeTree {
3672
4486
  node.visible = node.visible === undefined ? false : !node.visible;
3673
4487
  this.className = "itemicon fa-solid fa-eye" + (!node.visible ? "-slash" : "");
3674
4488
  // Trigger visibility event
3675
- if(that.onevent) {
4489
+ if( that.onevent )
4490
+ {
3676
4491
  const event = new TreeEvent(TreeEvent.NODE_VISIBILITY, node, node.visible);
3677
4492
  that.onevent( event );
3678
4493
  }
@@ -3681,9 +4496,10 @@ class NodeTree {
3681
4496
  item.appendChild(visibility);
3682
4497
  }
3683
4498
 
3684
- if(node.actions)
4499
+ if( node.actions )
3685
4500
  {
3686
- for(var i = 0; i < node.actions.length; ++i) {
4501
+ for( var i = 0; i < node.actions.length; ++i )
4502
+ {
3687
4503
  let a = node.actions[i];
3688
4504
  var actionEl = document.createElement('a');
3689
4505
  actionEl.className = "itemicon " + a.icon;
@@ -3696,20 +4512,25 @@ class NodeTree {
3696
4512
  }
3697
4513
  }
3698
4514
 
3699
- if(selectedId != undefined && node.id == selectedId) {
3700
- this.selected = [node];
4515
+ if( selectedId != undefined && node.id == selectedId )
4516
+ {
4517
+ this.selected = [ node ];
3701
4518
  item.click();
3702
4519
  }
3703
4520
 
3704
- if(node.closed )
4521
+ if( node.closed )
4522
+ {
3705
4523
  return;
4524
+ }
3706
4525
 
3707
4526
  for( var i = 0; i < node.children.length; ++i )
3708
4527
  {
3709
- let child = node.children[i];
4528
+ let child = node.children[ i ];
3710
4529
 
3711
- if( this.options.onlyFolders && child.type != 'folder')
4530
+ if( this.options.onlyFolders && child.type != 'folder' )
4531
+ {
3712
4532
  continue;
4533
+ }
3713
4534
 
3714
4535
  this._create_item( node, child, level + 1 );
3715
4536
  }
@@ -3748,12 +4569,12 @@ class Panel {
3748
4569
  * style: CSS Style object to be applied to the panel
3749
4570
  */
3750
4571
 
3751
- constructor( options = {} ) {
4572
+ constructor( options = {} ) {
3752
4573
  var root = document.createElement('div');
3753
4574
  root.className = "lexpanel";
3754
- if(options.id)
4575
+ if( options.id )
3755
4576
  root.id = options.id;
3756
- if(options.className)
4577
+ if( options.className )
3757
4578
  root.className += " " + options.className;
3758
4579
 
3759
4580
  root.style.width = options.width || "calc( 100% - 6px )";
@@ -3830,23 +4651,31 @@ class Panel {
3830
4651
  this.branches = [];
3831
4652
  this.current_branch = null;
3832
4653
 
3833
- for(let w in this.widgets) {
3834
- if(this.widgets[w].options && this.widgets[w].options.signal) {
4654
+ for( let w in this.widgets )
4655
+ {
4656
+ if( this.widgets[w].options && this.widgets[w].options.signal )
4657
+ {
3835
4658
  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]) {
4659
+ for( let i = 0; i < LX.signals[signal].length; i++ )
4660
+ {
4661
+ if( LX.signals[signal][i] == this.widgets[w] )
4662
+ {
3838
4663
  LX.signals[signal] = [...LX.signals[signal].slice(0, i), ...LX.signals[signal].slice(i+1)];
3839
4664
  }
3840
4665
  }
3841
4666
  }
3842
4667
  }
3843
4668
 
3844
- if(this.signals) {
3845
- for(let w = 0; w < this.signals.length; w++) {
4669
+ if( this.signals )
4670
+ {
4671
+ for( let w = 0; w < this.signals.length; w++ )
4672
+ {
3846
4673
  let widget = Object.values(this.signals[w])[0];
3847
4674
  let signal = widget.options.signal;
3848
- for(let i = 0; i < LX.signals[signal].length; i++) {
3849
- if(LX.signals[signal][i] == widget) {
4675
+ for( let i = 0; i < LX.signals[signal].length; i++ )
4676
+ {
4677
+ if( LX.signals[signal][i] == widget )
4678
+ {
3850
4679
  LX.signals[signal] = [...LX.signals[signal].slice(0, i), ...LX.signals[signal].slice(i+1)];
3851
4680
  }
3852
4681
  }
@@ -3884,10 +4713,12 @@ class Panel {
3884
4713
 
3885
4714
  this._inline_widgets_left = -1;
3886
4715
 
3887
- if(!this._inlineContainer) {
4716
+ if( !this._inlineContainer )
4717
+ {
3888
4718
  this._inlineContainer = document.createElement('div');
3889
4719
  this._inlineContainer.className = "lexinlinewidgets";
3890
- if(justifyContent)
4720
+
4721
+ if( justifyContent )
3891
4722
  {
3892
4723
  this._inlineContainer.style.justifyContent = justifyContent;
3893
4724
  }
@@ -3901,7 +4732,7 @@ class Panel {
3901
4732
  if(is_pair)
3902
4733
  {
3903
4734
  // eg. an array, inline items appended later to
3904
- if(this._inline_queued_container)
4735
+ if( this._inline_queued_container)
3905
4736
  this._inlineContainer.appendChild( item[0] );
3906
4737
  // eg. a dropdown, item is appended to parent, not to inline cont.
3907
4738
  else
@@ -3913,7 +4744,7 @@ class Panel {
3913
4744
 
3914
4745
  if(!this._inline_queued_container)
3915
4746
  {
3916
- if(this.current_branch)
4747
+ if( this.current_branch)
3917
4748
  this.current_branch.content.appendChild( this._inlineContainer );
3918
4749
  else
3919
4750
  this.root.appendChild( this._inlineContainer );
@@ -3952,7 +4783,7 @@ class Panel {
3952
4783
  this.current_branch = branch;
3953
4784
 
3954
4785
  // Append to panel
3955
- if(this.branches.length == 0)
4786
+ if( this.branches.length == 0)
3956
4787
  branch.root.classList.add('first');
3957
4788
 
3958
4789
  // This is the last!
@@ -3963,7 +4794,8 @@ class Panel {
3963
4794
  this.root.appendChild( branch.root );
3964
4795
 
3965
4796
  // Add widget filter
3966
- if(options.filter) {
4797
+ if( options.filter )
4798
+ {
3967
4799
  this._addFilter( options.filter, {callback: this._searchWidgets.bind(this, branch.name)} );
3968
4800
  }
3969
4801
 
@@ -4080,14 +4912,18 @@ class Panel {
4080
4912
  element.jsInstance = widget;
4081
4913
 
4082
4914
  const insert_widget = el => {
4083
- if(options.container)
4084
- options.container.appendChild(el);
4085
- else if(!this.queuedContainer) {
4086
-
4087
- if(this.current_branch)
4915
+ if( options.container )
4916
+ {
4917
+ options.container.appendChild( el );
4918
+ }
4919
+ else if( !this.queuedContainer )
4920
+ {
4921
+ if( this.current_branch )
4088
4922
  {
4089
- if(!options.skipWidget)
4923
+ if( !options.skipWidget )
4924
+ {
4090
4925
  this.current_branch.widgets.push( widget );
4926
+ }
4091
4927
  this.current_branch.content.appendChild( el );
4092
4928
  }
4093
4929
  else
@@ -4097,40 +4933,47 @@ class Panel {
4097
4933
  }
4098
4934
  }
4099
4935
  // Append content to queued tab container
4100
- else {
4936
+ else
4937
+ {
4101
4938
  this.queuedContainer.appendChild( el );
4102
4939
  }
4103
4940
  };
4104
4941
 
4105
4942
  const store_widget = el => {
4106
4943
 
4107
- if(!this.queuedContainer) {
4944
+ if( !this.queuedContainer )
4945
+ {
4108
4946
  this._inlineWidgets.push( el );
4109
4947
  }
4110
4948
  // Append content to queued tab container
4111
- else {
4949
+ else
4950
+ {
4112
4951
  this._inlineWidgets.push( [el, this.queuedContainer] );
4113
4952
  }
4114
4953
  };
4115
4954
 
4116
4955
  // Process inline widgets
4117
- if(this._inline_widgets_left > 0 && !options.skipInlineCount)
4956
+ if( this._inline_widgets_left > 0 && !options.skipInlineCount )
4118
4957
  {
4119
- if(!this._inlineWidgets) {
4958
+ if( !this._inlineWidgets )
4959
+ {
4120
4960
  this._inlineWidgets = [];
4121
4961
  }
4122
4962
 
4123
4963
  // Store widget and its container
4124
- store_widget(element);
4964
+ store_widget( element );
4125
4965
 
4126
4966
  this._inline_widgets_left--;
4127
4967
 
4128
4968
  // Last widget
4129
- if(!this._inline_widgets_left) {
4969
+ if( !this._inline_widgets_left )
4970
+ {
4130
4971
  this.endLine();
4131
4972
  }
4132
- }else {
4133
- insert_widget(element);
4973
+ }
4974
+ else
4975
+ {
4976
+ insert_widget( element );
4134
4977
  }
4135
4978
 
4136
4979
  return widget;
@@ -4167,15 +5010,20 @@ class Panel {
4167
5010
 
4168
5011
  _searchWidgets(branchName, value) {
4169
5012
 
4170
- for( let b of this.branches ) {
4171
-
4172
- if(b.name !== branchName)
5013
+ for( let b of this.branches )
5014
+ {
5015
+ if( b.name !== branchName )
5016
+ {
4173
5017
  continue;
5018
+ }
4174
5019
 
4175
5020
  // remove all widgets
4176
- for( let w of b.widgets ) {
4177
- if(w.domEl.classList.contains('lexfilter'))
5021
+ for( let w of b.widgets )
5022
+ {
5023
+ if( w.domEl.classList.contains('lexfilter') )
5024
+ {
4178
5025
  continue;
5026
+ }
4179
5027
  w.domEl.remove();
4180
5028
  }
4181
5029
 
@@ -4185,9 +5033,9 @@ class Panel {
4185
5033
  const emptyFilter = !value.length;
4186
5034
 
4187
5035
  // add widgets
4188
- for( let w of b.widgets ) {
4189
-
4190
- if(!emptyFilter)
5036
+ for( let w of b.widgets )
5037
+ {
5038
+ if( !emptyFilter )
4191
5039
  {
4192
5040
  if(!w.name) continue;
4193
5041
  const filterWord = value.toLowerCase();
@@ -4281,7 +5129,7 @@ class Panel {
4281
5129
 
4282
5130
  clearQueue() {
4283
5131
 
4284
- if(this._queue && this._queue.length)
5132
+ if( this._queue && this._queue.length)
4285
5133
  {
4286
5134
  this.queuedContainer = this._queue.pop();
4287
5135
  return;
@@ -4507,7 +5355,8 @@ class Panel {
4507
5355
  element.appendChild( container );
4508
5356
 
4509
5357
  // Remove branch padding and margins
4510
- if( !widget.name ) {
5358
+ if( !widget.name )
5359
+ {
4511
5360
  element.className += " noname";
4512
5361
  container.style.width = "100%";
4513
5362
  }
@@ -4607,7 +5456,8 @@ class Panel {
4607
5456
  element.appendChild(container);
4608
5457
 
4609
5458
  // Remove branch padding and margins
4610
- if(!widget.name) {
5459
+ if( !widget.name )
5460
+ {
4611
5461
  element.className += " noname";
4612
5462
  container.style.width = "100%";
4613
5463
  }
@@ -4712,11 +5562,12 @@ class Panel {
4712
5562
  /**
4713
5563
  * @method addComboButtons
4714
5564
  * @param {String} name Widget name
4715
- * @param {Array} values Each of the {value, callback} items
5565
+ * @param {Array} values Each of the {value, callback, selected, disabled} items
4716
5566
  * @param {*} options:
4717
5567
  * float: Justify content (left, center, right) [center]
4718
- * selected: Selected item by default by value
5568
+ * @legacy selected: Selected item by default by value
4719
5569
  * noSelection: Buttons can be clicked, but they are not selectable
5570
+ * toggle: Buttons can be toggled insted of selecting only one
4720
5571
  */
4721
5572
 
4722
5573
  addComboButtons( name, values, options = {} ) {
@@ -4738,7 +5589,8 @@ class Panel {
4738
5589
  let buttonsBox = document.createElement('div');
4739
5590
  buttonsBox.className = "lexcombobuttonsbox ";
4740
5591
 
4741
- let shouldSelect = !( options.noSelection ?? false );
5592
+ const shouldSelect = !( options.noSelection ?? false );
5593
+ const shouldToggle = shouldSelect && ( options.toggle ?? false );
4742
5594
 
4743
5595
  for( let b of values )
4744
5596
  {
@@ -4757,14 +5609,14 @@ class Panel {
4757
5609
  buttonEl.classList.add( options.buttonClass );
4758
5610
  }
4759
5611
 
4760
- if( shouldSelect && options.selected == b.value )
5612
+ if( shouldSelect && ( b.selected || options.selected == b.value ) )
4761
5613
  {
4762
5614
  buttonEl.classList.add("selected");
4763
5615
  }
4764
5616
 
4765
5617
  buttonEl.innerHTML = ( b.icon ? "<a class='" + b.icon +"'></a>" : "" ) + "<span>" + ( b.icon ? "" : b.value ) + "</span>";
4766
5618
 
4767
- if( options.disabled )
5619
+ if( b.disabled )
4768
5620
  {
4769
5621
  buttonEl.setAttribute( "disabled", true );
4770
5622
  }
@@ -4772,8 +5624,15 @@ class Panel {
4772
5624
  buttonEl.addEventListener("click", function( e ) {
4773
5625
  if( shouldSelect )
4774
5626
  {
4775
- container.querySelectorAll('button').forEach( s => s.classList.remove('selected'));
4776
- this.classList.add('selected');
5627
+ if( shouldToggle )
5628
+ {
5629
+ this.classList.toggle('selected');
5630
+ }
5631
+ else
5632
+ {
5633
+ container.querySelectorAll('button').forEach( s => s.classList.remove('selected'));
5634
+ this.classList.add('selected');
5635
+ }
4777
5636
  }
4778
5637
 
4779
5638
  that._trigger( new IEvent( name, b.value, e ), b.callback );
@@ -4783,7 +5642,7 @@ class Panel {
4783
5642
  }
4784
5643
 
4785
5644
  // Remove branch padding and margins
4786
- if( !widget.name)
5645
+ if( !widget.name )
4787
5646
  {
4788
5647
  element.className += " noname";
4789
5648
  container.style.width = "100%";
@@ -4957,7 +5816,8 @@ class Panel {
4957
5816
 
4958
5817
  element.appendChild( container );
4959
5818
 
4960
- if( !widget.name || options.hideName ) {
5819
+ if( !widget.name || options.hideName )
5820
+ {
4961
5821
  element.className += " noname";
4962
5822
  container.style.width = "100%";
4963
5823
  }
@@ -5093,8 +5953,6 @@ class Panel {
5093
5953
 
5094
5954
  const _placeOptions = ( parent ) => {
5095
5955
 
5096
- console.log("Replacing container");
5097
-
5098
5956
  const overflowContainer = parent.getParentArea();
5099
5957
  const rect = selectedOption.getBoundingClientRect();
5100
5958
  const nestedDialog = parent.parentElement.closest( "dialog" );
@@ -5378,15 +6236,16 @@ class Panel {
5378
6236
 
5379
6237
  addCurve( name, values, callback, options = {} ) {
5380
6238
 
5381
- if(!name) {
5382
- throw("Set Widget Name!");
6239
+ if( !name )
6240
+ {
6241
+ throw( "Set Widget Name!" );
5383
6242
  }
5384
6243
 
5385
6244
  let that = this;
5386
- let widget = this.create_widget(name, Widget.CURVE, options);
6245
+ let widget = this.create_widget( name, Widget.CURVE, options );
5387
6246
 
5388
6247
  widget.onGetValue = () => {
5389
- return JSON.parse(JSON.stringify(curveInstance.element.value));
6248
+ return JSON.parse(JSON.stringify( curveInstance.element.value ));
5390
6249
  };
5391
6250
 
5392
6251
  widget.onSetValue = ( newValue, skipCallback ) => {
@@ -5531,7 +6390,8 @@ class Panel {
5531
6390
 
5532
6391
  addLayers( name, value, callback, options = {} ) {
5533
6392
 
5534
- if(!name) {
6393
+ if( !name )
6394
+ {
5535
6395
  throw("Set Widget Name!");
5536
6396
  }
5537
6397
 
@@ -5573,7 +6433,8 @@ class Panel {
5573
6433
  let binary = value.toString( 2 );
5574
6434
  let nbits = binary.length;
5575
6435
  // fill zeros
5576
- for(var i = 0; i < (16 - nbits); ++i) {
6436
+ for( var i = 0; i < (16 - nbits); ++i )
6437
+ {
5577
6438
  binary = '0' + binary;
5578
6439
  }
5579
6440
 
@@ -5626,8 +6487,9 @@ class Panel {
5626
6487
 
5627
6488
  addArray( name, values = [], callback, options = {} ) {
5628
6489
 
5629
- if(!name) {
5630
- throw("Set Widget Name!");
6490
+ if( !name )
6491
+ {
6492
+ throw( "Set Widget Name!" );
5631
6493
  }
5632
6494
 
5633
6495
  let widget = this.create_widget(name, Widget.ARRAY, options);
@@ -5808,7 +6670,8 @@ class Panel {
5808
6670
  widget.updateValues( values );
5809
6671
 
5810
6672
  // Remove branch padding and margins
5811
- if( !widget.name ) {
6673
+ if( !widget.name )
6674
+ {
5812
6675
  element.className += " noname";
5813
6676
  listContainer.style.width = "100%";
5814
6677
  }
@@ -5895,7 +6758,7 @@ class Panel {
5895
6758
  tagsContainer.appendChild( tagInput );
5896
6759
 
5897
6760
  tagInput.onkeydown = function( e ) {
5898
- const val = this.value.replace(/\s/g, '');
6761
+ const val = this.value.replace( /\s/g, '' );
5899
6762
  if( e.key == ' ' || e.key == 'Enter' )
5900
6763
  {
5901
6764
  e.preventDefault();
@@ -5933,15 +6796,16 @@ class Panel {
5933
6796
  * @param {Function} callback Callback function on change
5934
6797
  * @param {*} options:
5935
6798
  * disabled: Make the widget disabled [false]
6799
+ * label: Checkbox label
5936
6800
  * suboptions: Callback to add widgets in case of TRUE value
5937
- * className: Customize colors
6801
+ * className: Extra classes to customize style
5938
6802
  */
5939
6803
 
5940
6804
  addCheckbox( name, value, callback, options = {} ) {
5941
6805
 
5942
- if( !name )
6806
+ if( !name && !options.label )
5943
6807
  {
5944
- throw( "Set Widget Name!" );
6808
+ throw( "Set Widget Name or at least a label!" );
5945
6809
  }
5946
6810
 
5947
6811
  let widget = this.create_widget( name, Widget.CHECKBOX, options );
@@ -5961,10 +6825,13 @@ class Panel {
5961
6825
  let element = widget.domEl;
5962
6826
 
5963
6827
  // Add reset functionality
5964
- Panel._add_reset_property( element.domName, function() {
5965
- checkbox.checked = !checkbox.checked;
5966
- Panel._dispatch_event( checkbox, "change" );
5967
- });
6828
+ if( name )
6829
+ {
6830
+ Panel._add_reset_property( element.domName, function() {
6831
+ checkbox.checked = !checkbox.checked;
6832
+ Panel._dispatch_event( checkbox, "change" );
6833
+ });
6834
+ }
5968
6835
 
5969
6836
  // Add widget value
5970
6837
 
@@ -5980,7 +6847,7 @@ class Panel {
5980
6847
 
5981
6848
  let valueName = document.createElement( 'span' );
5982
6849
  valueName.className = "checkboxtext";
5983
- valueName.innerHTML = "On";
6850
+ valueName.innerHTML = options.label ?? "On";
5984
6851
 
5985
6852
  container.appendChild( checkbox );
5986
6853
  container.appendChild( valueName );
@@ -6118,6 +6985,96 @@ class Panel {
6118
6985
  return widget;
6119
6986
  }
6120
6987
 
6988
+ /**
6989
+ * @method addRadioGroup
6990
+ * @param {String} label Radio label
6991
+ * @param {Array} values Radio options
6992
+ * @param {Function} callback Callback function on change
6993
+ * @param {*} options:
6994
+ * disabled: Make the widget disabled [false]
6995
+ * className: Customize colors
6996
+ */
6997
+
6998
+ addRadioGroup( label, values, callback, options = {} ) {
6999
+
7000
+ let widget = this.create_widget( null, Widget.RADIO, options );
7001
+
7002
+ widget.onGetValue = () => {
7003
+ const items = container.querySelectorAll( 'button' );
7004
+ for( let i = 0; i < items.length; ++i )
7005
+ {
7006
+ const optionItem = items[ i ];
7007
+ if( optionItem.checked )
7008
+ {
7009
+ return [ i, values[ i ] ];
7010
+ }
7011
+ }
7012
+ };
7013
+
7014
+ widget.onSetValue = ( newValue, skipCallback ) => {
7015
+ const items = container.querySelectorAll( 'button' );
7016
+ for( let i = 0; i < items.length; ++i )
7017
+ {
7018
+ const optionItem = items[ i ];
7019
+ if( newValue == i )
7020
+ {
7021
+ Panel._dispatch_event( optionItem, "click", skipCallback );
7022
+ }
7023
+ }
7024
+ };
7025
+
7026
+ let element = widget.domEl;
7027
+
7028
+ // Add widget value
7029
+ var container = document.createElement( 'div' );
7030
+ container.className = "lexradiogroup " + ( options.className ?? "" );
7031
+
7032
+ let labelSpan = document.createElement( 'span' );
7033
+ labelSpan.innerHTML = label;
7034
+ container.appendChild( labelSpan );
7035
+
7036
+ const that = this;
7037
+
7038
+ for( let i = 0; i < values.length; ++i )
7039
+ {
7040
+ const optionItem = document.createElement( 'div' );
7041
+ optionItem.className = "lexradiogroupitem";
7042
+ container.appendChild( optionItem );
7043
+
7044
+ const optionButton = document.createElement( 'button' );
7045
+ optionButton.className = "lexbutton";
7046
+ optionButton.disabled = options.disabled ?? false;
7047
+ optionItem.appendChild( optionButton );
7048
+
7049
+ optionButton.addEventListener( "click", function( e ) {
7050
+ const skipCallback = ( e.detail?.constructor == Number ? null : e.detail );
7051
+ container.querySelectorAll( 'button' ).forEach( e => { e.checked = false; e.classList.remove( "checked" ) } );
7052
+ this.checked = !this.checked;
7053
+ this.classList.toggle( "checked" );
7054
+ if( !skipCallback ) that._trigger( new IEvent( null, [ i, values[ i ] ], e ), callback );
7055
+ } );
7056
+
7057
+ {
7058
+ const checkedSpan = document.createElement( 'span' );
7059
+ optionButton.appendChild( checkedSpan );
7060
+ }
7061
+
7062
+ const optionLabel = document.createElement( 'span' );
7063
+ optionLabel.innerHTML = values[ i ];
7064
+ optionItem.appendChild( optionLabel );
7065
+ }
7066
+
7067
+ if( options.selected )
7068
+ {
7069
+ console.assert( options.selected.constructor == Number );
7070
+ widget.set( options.selected, true );
7071
+ }
7072
+
7073
+ element.appendChild( container );
7074
+
7075
+ return widget;
7076
+ }
7077
+
6121
7078
  /**
6122
7079
  * @method addColor
6123
7080
  * @param {String} name Widget name
@@ -6130,7 +7087,8 @@ class Panel {
6130
7087
 
6131
7088
  addColor( name, value, callback, options = {} ) {
6132
7089
 
6133
- if( !name ) {
7090
+ if( !name )
7091
+ {
6134
7092
  throw( "Set Widget Name!" );
6135
7093
  }
6136
7094
 
@@ -6168,7 +7126,8 @@ class Panel {
6168
7126
  color.useRGB = options.useRGB ?? false;
6169
7127
  color.value = color.iValue = value.constructor === Array ? rgbToHex( value ) : value;
6170
7128
 
6171
- if( options.disabled ) {
7129
+ if( options.disabled )
7130
+ {
6172
7131
  color.disabled = true;
6173
7132
  }
6174
7133
 
@@ -6213,6 +7172,137 @@ class Panel {
6213
7172
  return widget;
6214
7173
  }
6215
7174
 
7175
+ /**
7176
+ * @method addRange
7177
+ * @param {String} name Widget name
7178
+ * @param {Number} value Default number value
7179
+ * @param {Function} callback Callback function on change
7180
+ * @param {*} options:
7181
+ * className: Extra classes to customize style
7182
+ * disabled: Make the widget disabled [false]
7183
+ * left: The slider goes to the left instead of the right
7184
+ * fill: Fill slider progress [true]
7185
+ * step: Step of the input
7186
+ * min, max: Min and Max values for the input
7187
+ */
7188
+
7189
+ addRange( name, value, callback, options = {} ) {
7190
+
7191
+ let widget = this.create_widget( name, Widget.RANGE, options );
7192
+
7193
+ widget.onGetValue = () => {
7194
+ return +slider.value;
7195
+ };
7196
+
7197
+ widget.onSetValue = ( newValue, skipCallback ) => {
7198
+ slider.value = newValue;
7199
+ Panel._dispatch_event( slider, "input", skipCallback );
7200
+ };
7201
+
7202
+ let element = widget.domEl;
7203
+
7204
+ // add reset functionality
7205
+ if( widget.name )
7206
+ {
7207
+ Panel._add_reset_property( element.domName, function() {
7208
+ this.style.display = "none";
7209
+ slider.value = slider.iValue;
7210
+ Panel._dispatch_event( slider, "input" );
7211
+ });
7212
+ }
7213
+
7214
+ // add widget value
7215
+
7216
+ var container = document.createElement( 'div' );
7217
+ container.className = "lexrange";
7218
+ container.style.width = options.inputWidth || "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
7219
+
7220
+ let slider = document.createElement( 'input' );
7221
+ slider.className = "lexrangeslider " + ( options.className ?? "" );
7222
+ slider.value = slider.iValue = value;
7223
+ slider.min = options.min;
7224
+ slider.max = options.max;
7225
+ slider.step = options.step ?? 1;
7226
+ slider.type = "range";
7227
+ slider.disabled = options.disabled ?? false;
7228
+
7229
+ if( options.left ?? false )
7230
+ {
7231
+ slider.classList.add( "left" );
7232
+ }
7233
+
7234
+ if( !( options.fill ?? true ) )
7235
+ {
7236
+ slider.classList.add( "no-fill" );
7237
+ }
7238
+
7239
+ slider.addEventListener( "input", e => {
7240
+
7241
+ if( isNaN( e.target.valueAsNumber ) )
7242
+ {
7243
+ return;
7244
+ }
7245
+
7246
+ const skipCallback = e.detail;
7247
+
7248
+ let val = e.target.value = clamp( +e.target.valueAsNumber, +slider.min, +slider.max );
7249
+ slider.value = val;
7250
+
7251
+ // Reset button (default value)
7252
+ if( !skipCallback )
7253
+ {
7254
+ let btn = element.querySelector( ".lexwidgetname .lexicon" );
7255
+ if( btn ) btn.style.display = val != slider.iValue ? "block": "none";
7256
+ }
7257
+
7258
+ if( options.left )
7259
+ {
7260
+ val = ( +slider.max ) - val + ( +slider.min );
7261
+ }
7262
+
7263
+ if( !skipCallback ) this._trigger( new IEvent( name, val, e ), callback );
7264
+ }, { passive: false });
7265
+
7266
+ slider.addEventListener( "mousedown", function( e ) {
7267
+ if( options.onPress )
7268
+ {
7269
+ options.onPress.bind( slider )( e, slider );
7270
+ }
7271
+ }, false );
7272
+
7273
+ slider.addEventListener( "mouseup", function( e ) {
7274
+ if( options.onRelease )
7275
+ {
7276
+ options.onRelease.bind( slider )( e, slider );
7277
+ }
7278
+ }, false );
7279
+
7280
+ // Method to change min, max, step parameters
7281
+ widget.setLimits = ( newMin, newMax, newStep ) => {
7282
+ slider.min = newMin ?? slider.min;
7283
+ slider.max = newMax ?? slider.max;
7284
+ slider.step = newStep ?? slider.step;
7285
+ Panel._dispatch_event( slider, "input", true );
7286
+ };
7287
+
7288
+ if( value.constructor == Number )
7289
+ {
7290
+ value = clamp( value, +slider.min, +slider.max );
7291
+ }
7292
+
7293
+ container.appendChild( slider );
7294
+ element.appendChild( container );
7295
+
7296
+ // Remove branch padding and margins
7297
+ if( !widget.name )
7298
+ {
7299
+ element.className += " noname";
7300
+ container.style.width = "100%";
7301
+ }
7302
+
7303
+ return widget;
7304
+ }
7305
+
6216
7306
  /**
6217
7307
  * @method addNumber
6218
7308
  * @param {String} name Widget name
@@ -6245,7 +7335,8 @@ class Panel {
6245
7335
  let element = widget.domEl;
6246
7336
 
6247
7337
  // add reset functionality
6248
- if( widget.name ) {
7338
+ if( widget.name )
7339
+ {
6249
7340
  Panel._add_reset_property( element.domName, function() {
6250
7341
  this.style.display = "none";
6251
7342
  vecinput.value = vecinput.iValue;
@@ -6513,7 +7604,8 @@ class Panel {
6513
7604
  return;
6514
7605
  }
6515
7606
 
6516
- for( let i = 0; i < inputs.length; ++i ) {
7607
+ for( let i = 0; i < inputs.length; ++i )
7608
+ {
6517
7609
  let value = newValue[ i ];
6518
7610
  inputs[ i ].value = round( value, options.precision ) ?? 0;
6519
7611
  Panel._dispatch_event( inputs[ i ], "change", skipCallback );
@@ -6525,7 +7617,8 @@ class Panel {
6525
7617
  // Add reset functionality
6526
7618
  Panel._add_reset_property( element.domName, function() {
6527
7619
  this.style.display = "none";
6528
- for( let v of element.querySelectorAll( ".vecinput" ) ) {
7620
+ for( let v of element.querySelectorAll( ".vecinput" ) )
7621
+ {
6529
7622
  v.value = v.iValue;
6530
7623
  Panel._dispatch_event( v, "change" );
6531
7624
  }
@@ -6537,8 +7630,8 @@ class Panel {
6537
7630
  container.className = "lexvector";
6538
7631
  container.style.width = "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
6539
7632
 
6540
- for( let i = 0; i < num_components; ++i ) {
6541
-
7633
+ for( let i = 0; i < num_components; ++i )
7634
+ {
6542
7635
  let box = document.createElement( 'div' );
6543
7636
  box.className = "vecbox";
6544
7637
  box.innerHTML = "<span class='" + Panel.VECTOR_COMPONENTS[ i ] + "'></span>";
@@ -6614,7 +7707,8 @@ class Panel {
6614
7707
 
6615
7708
  if( locker.locked )
6616
7709
  {
6617
- for( let v of element.querySelectorAll( ".vecinput" ) ) {
7710
+ for( let v of element.querySelectorAll( ".vecinput" ) )
7711
+ {
6618
7712
  v.value = val;
6619
7713
  value[ v.idx ] = val;
6620
7714
  }
@@ -6672,7 +7766,8 @@ class Panel {
6672
7766
 
6673
7767
  if( locker.locked )
6674
7768
  {
6675
- for( let v of element.querySelectorAll( ".vecinput" ) ) {
7769
+ for( let v of element.querySelectorAll( ".vecinput" ) )
7770
+ {
6676
7771
  v.value = round( +v.valueAsNumber + mult * dt, options.precision );
6677
7772
  Panel._dispatch_event( v, "change" );
6678
7773
  }
@@ -6798,7 +7893,7 @@ class Panel {
6798
7893
  const value = [];
6799
7894
  for( let i = 0; i < element.dimensions.length; ++i )
6800
7895
  {
6801
- value.push( element.dimensions[ i ].onGetValue() );
7896
+ value.push( element.dimensions[ i ].value() );
6802
7897
  }
6803
7898
  return value;
6804
7899
  };
@@ -6806,7 +7901,7 @@ class Panel {
6806
7901
  widget.onSetValue = ( newValue, skipCallback ) => {
6807
7902
  for( let i = 0; i < element.dimensions.length; ++i )
6808
7903
  {
6809
- element.dimensions[ i ].onSetValue( newValue[ i ], skipCallback );
7904
+ element.dimensions[ i ].set( newValue[ i ], skipCallback );
6810
7905
  }
6811
7906
  };
6812
7907
 
@@ -6821,14 +7916,14 @@ class Panel {
6821
7916
  {
6822
7917
  element.dimensions[ i ] = this.addNumber( null, value[ i ], ( v ) => {
6823
7918
 
6824
- const value = widget.onGetValue();
7919
+ const value = widget.value();
6825
7920
 
6826
7921
  if( element.locked )
6827
7922
  {
6828
7923
  const ar = ( i == 0 ? 1.0 / element.aspectRatio : element.aspectRatio );
6829
7924
  const index = ( 1 + i ) % 2;
6830
7925
  value[ index ] = v * ar;
6831
- element.dimensions[ index ].onSetValue( value[ index ], true );
7926
+ element.dimensions[ index ].set( value[ index ], true );
6832
7927
  }
6833
7928
 
6834
7929
  if( callback )
@@ -6871,7 +7966,7 @@ class Panel {
6871
7966
  this.classList.remove( "fa-lock-open" );
6872
7967
 
6873
7968
  // Recompute ratio
6874
- const value = widget.onGetValue();
7969
+ const value = widget.value();
6875
7970
  element.aspectRatio = value[ 0 ] / value[ 1 ];
6876
7971
  }
6877
7972
  else
@@ -7268,7 +8363,8 @@ class Panel {
7268
8363
  let container = document.createElement('div');
7269
8364
  container.className = "lextree";
7270
8365
 
7271
- if(name) {
8366
+ if( name )
8367
+ {
7272
8368
  let title = document.createElement('span');
7273
8369
  title.innerHTML = name;
7274
8370
  container.appendChild(title);
@@ -7280,8 +8376,8 @@ class Panel {
7280
8376
  toolsDiv.className += " notitle";
7281
8377
 
7282
8378
  // Tree icons
7283
- if(options.icons) {
7284
-
8379
+ if( options.icons )
8380
+ {
7285
8381
  for( let data of options.icons )
7286
8382
  {
7287
8383
  let iconEl = document.createElement('a');
@@ -7341,11 +8437,15 @@ class Panel {
7341
8437
  let widget = new Widget( null, Widget.SEPARATOR );
7342
8438
  widget.domEl = element;
7343
8439
 
7344
- if(this.current_branch) {
8440
+ if( this.current_branch )
8441
+ {
7345
8442
  this.current_branch.content.appendChild( element );
7346
8443
  this.current_branch.widgets.push( widget );
7347
- } else
7348
- this.root.appendChild(element);
8444
+ }
8445
+ else
8446
+ {
8447
+ this.root.appendChild( element );
8448
+ }
7349
8449
  }
7350
8450
 
7351
8451
  /**
@@ -7670,6 +8770,12 @@ class Panel {
7670
8770
 
7671
8771
  input.addEventListener( 'change', function() {
7672
8772
  data.checkMap[ rowId ] = this.checked;
8773
+
8774
+ if( !this.checked )
8775
+ {
8776
+ const input = table.querySelector( "thead input[type='checkbox']" );
8777
+ input.checked = data.checkMap[ ":root" ] = false;
8778
+ }
7673
8779
  });
7674
8780
 
7675
8781
  row.appendChild( td );
@@ -7795,7 +8901,7 @@ class Branch {
7795
8901
  root.appendChild( title );
7796
8902
 
7797
8903
  var branchContent = document.createElement( 'div' );
7798
- branchContent.id = name.replace(/\s/g, '');
8904
+ branchContent.id = name.replace( /\s/g, '' );
7799
8905
  branchContent.className = "lexbranchcontent";
7800
8906
  root.appendChild(branchContent);
7801
8907
  this.content = branchContent;
@@ -7851,7 +8957,8 @@ class Branch {
7851
8957
 
7852
8958
  const dialog = new Dialog(this.name, p => {
7853
8959
  // add widgets
7854
- for( let w of this.widgets ) {
8960
+ for( let w of this.widgets )
8961
+ {
7855
8962
  p.root.appendChild( w.domEl );
7856
8963
  }
7857
8964
  });
@@ -7944,8 +9051,8 @@ class Branch {
7944
9051
  var size = this.grabber.style.marginLeft;
7945
9052
 
7946
9053
  // Update sizes of widgets inside
7947
- for(var i = 0; i < this.widgets.length; i++) {
7948
-
9054
+ for( var i = 0; i < this.widgets.length; i++ )
9055
+ {
7949
9056
  let widget = this.widgets[ i ];
7950
9057
  let element = widget.domEl;
7951
9058
 
@@ -7964,9 +9071,6 @@ class Branch {
7964
9071
  case Widget.FILE:
7965
9072
  padding = "10%";
7966
9073
  break;
7967
- case Widget.TEXT:
7968
- padding = "8px";
7969
- break;
7970
9074
  };
7971
9075
 
7972
9076
  value.style.width = "-moz-calc( 100% - " + size + " - " + padding + " )";
@@ -8105,7 +9209,7 @@ class Dialog {
8105
9209
  modal = options.modal ?? false;
8106
9210
 
8107
9211
  var root = document.createElement('dialog');
8108
- root.className = "lexdialog " + (options.class ?? "");
9212
+ root.className = "lexdialog " + (options.className ?? "");
8109
9213
  root.id = options.id ?? "dialog" + Dialog._last_id++;
8110
9214
  LX.root.appendChild( root );
8111
9215
 
@@ -8263,15 +9367,15 @@ class Dialog {
8263
9367
 
8264
9368
  root.style.width = size[ 0 ] ? (size[ 0 ]) : "25%";
8265
9369
  root.style.height = size[ 1 ] ? (size[ 1 ]) : "auto";
9370
+ root.style.translate = options.position ? "unset" : "-50% -50%";
8266
9371
 
8267
9372
  if( options.size )
8268
9373
  {
8269
9374
  this.size = size;
8270
9375
  }
8271
9376
 
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 )";
9377
+ root.style.left = position[ 0 ] ?? "50%";
9378
+ root.style.top = position[ 1 ] ?? "50%";
8275
9379
 
8276
9380
  panel.root.style.width = "calc( 100% - 30px )";
8277
9381
  panel.root.style.height = title ? "calc( 100% - " + ( titleDiv.offsetHeight + 30 ) + "px )" : "calc( 100% - 51px )";
@@ -8288,7 +9392,7 @@ class Dialog {
8288
9392
  this._oncreate.call(this, this.panel);
8289
9393
  }
8290
9394
 
8291
- setPosition(x, y) {
9395
+ setPosition( x, y ) {
8292
9396
 
8293
9397
  this.root.style.left = x + "px";
8294
9398
  this.root.style.top = y + "px";
@@ -8334,6 +9438,9 @@ class PocketDialog extends Dialog {
8334
9438
 
8335
9439
  // Custom
8336
9440
  this.root.classList.add( "pocket" );
9441
+
9442
+ this.root.style.translate = "none";
9443
+ this.root.style.top = "0";
8337
9444
  this.root.style.left = "unset";
8338
9445
 
8339
9446
  if( !options.position )
@@ -8349,6 +9456,11 @@ class PocketDialog extends Dialog {
8349
9456
  this.minimized = false;
8350
9457
  this.title.tabIndex = -1;
8351
9458
  this.title.addEventListener("click", e => {
9459
+ if( this.title._eventCatched )
9460
+ {
9461
+ this.title._eventCatched = false;
9462
+ return;
9463
+ }
8352
9464
 
8353
9465
  // Sized dialogs have to keep their size
8354
9466
  if( this.size )
@@ -8431,12 +9543,12 @@ class ContextMenu {
8431
9543
  constructor( event, title, options = {} ) {
8432
9544
 
8433
9545
  // remove all context menus
8434
- document.body.querySelectorAll(".lexcontextmenubox").forEach(e => e.remove());
9546
+ document.body.querySelectorAll( ".lexcontextmenu" ).forEach( e => e.remove() );
8435
9547
 
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";
9548
+ this.root = document.createElement( "div" );
9549
+ this.root.className = "lexcontextmenu";
9550
+ this.root.style.left = ( event.x - 48 + document.scrollingElement.scrollLeft ) + "px";
9551
+ this.root.style.top = ( event.y - 8 + document.scrollingElement.scrollTop ) + "px";
8440
9552
 
8441
9553
  this.root.addEventListener("mouseleave", function() {
8442
9554
  this.remove();
@@ -8449,8 +9561,8 @@ class ContextMenu {
8449
9561
  {
8450
9562
  const item = {};
8451
9563
  item[ title ] = [];
8452
- item[ 'className' ] = "cmtitle";
8453
- item[ 'icon' ] = options.icon;
9564
+ item[ "className" ] = "cmtitle";
9565
+ item[ "icon" ] = options.icon;
8454
9566
  this.items.push( item );
8455
9567
  }
8456
9568
  }
@@ -8498,16 +9610,16 @@ class ContextMenu {
8498
9610
 
8499
9611
  _create_submenu( o, k, c, d ) {
8500
9612
 
8501
- this.root.querySelectorAll(".lexcontextmenubox").forEach( cm => cm.remove() );
9613
+ this.root.querySelectorAll( ".lexcontextmenu" ).forEach( cm => cm.remove() );
8502
9614
 
8503
9615
  let contextmenu = document.createElement('div');
8504
- contextmenu.className = "lexcontextmenubox";
9616
+ contextmenu.className = "lexcontextmenu";
8505
9617
  c.appendChild( contextmenu );
8506
9618
 
8507
9619
  for( var i = 0; i < o[k].length; ++i )
8508
9620
  {
8509
- const subitem = o[k][i];
8510
- const subkey = Object.keys(subitem)[0];
9621
+ const subitem = o[ k ][ i ];
9622
+ const subkey = Object.keys( subitem )[ 0 ];
8511
9623
  this._create_entry(subitem, subkey, contextmenu, d);
8512
9624
  }
8513
9625
 
@@ -8523,22 +9635,25 @@ class ContextMenu {
8523
9635
 
8524
9636
  const hasSubmenu = o[ k ].length;
8525
9637
  let entry = document.createElement('div');
8526
- entry.className = "lexcontextmenuentry" + (o[ 'className' ] ? " " + o[ 'className' ] : "" );
9638
+ entry.className = "lexmenuboxentry" + (o[ 'className' ] ? " " + o[ 'className' ] : "" );
8527
9639
  entry.id = o.id ?? ("eId" + getSupportedDOMName( k ));
8528
9640
  entry.innerHTML = "";
8529
9641
  const icon = o[ 'icon' ];
8530
- if(icon) {
9642
+ if( icon )
9643
+ {
8531
9644
  entry.innerHTML += "<a class='" + icon + " fa-sm'></a>";
8532
9645
  }
8533
9646
  const disabled = o['disabled'];
8534
9647
  entry.innerHTML += "<div class='lexentryname" + (disabled ? " disabled" : "") + "'>" + k + "</div>";
8535
9648
  c.appendChild( entry );
8536
9649
 
8537
- if( this.colors[ k ] ) {
9650
+ if( this.colors[ k ] )
9651
+ {
8538
9652
  entry.style.borderColor = this.colors[ k ];
8539
9653
  }
8540
9654
 
8541
- if( k == "" ) {
9655
+ if( k == "" )
9656
+ {
8542
9657
  entry.className += " cmseparator";
8543
9658
  return;
8544
9659
  }
@@ -8551,7 +9666,8 @@ class ContextMenu {
8551
9666
  if(disabled) return;
8552
9667
 
8553
9668
  const f = o[ 'callback' ];
8554
- if(f) {
9669
+ if( f )
9670
+ {
8555
9671
  f.call( this, k, entry );
8556
9672
  this.root.remove();
8557
9673
  }
@@ -8583,8 +9699,7 @@ class ContextMenu {
8583
9699
 
8584
9700
  entry.addEventListener("mouseleave", () => {
8585
9701
  d = -1; // Reset depth
8586
- // delete entry.built;
8587
- c.querySelectorAll(".lexcontextmenubox").forEach(e => e.remove());
9702
+ c.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
8588
9703
  });
8589
9704
  }
8590
9705
 
@@ -8617,22 +9732,25 @@ class ContextMenu {
8617
9732
  if(key) found = o[ key ];
8618
9733
  } );
8619
9734
 
8620
- if(found) {
9735
+ if( found )
9736
+ {
8621
9737
  insert( tokens[idx++], found );
8622
9738
  }
8623
- else {
9739
+ else
9740
+ {
8624
9741
  let item = {};
8625
9742
  item[ token ] = [];
8626
- const next_token = tokens[idx++];
9743
+ const nextToken = tokens[idx++];
8627
9744
  // Check if last token -> add callback
8628
- if(!next_token) {
9745
+ if( !nextToken )
9746
+ {
8629
9747
  item[ 'id' ] = options.id;
8630
9748
  item[ 'callback' ] = options.callback;
8631
9749
  item[ 'disabled' ] = options.disabled ?? false;
8632
9750
  }
8633
9751
 
8634
9752
  list.push( item );
8635
- insert( next_token, item[ token ] );
9753
+ insert( nextToken, item[ token ] );
8636
9754
  }
8637
9755
  };
8638
9756
 
@@ -8656,7 +9774,8 @@ class ContextMenu {
8656
9774
  _item[ key ].unshift( parent );
8657
9775
  }
8658
9776
 
8659
- for( var child of _item[ key ] ) {
9777
+ for( var child of _item[ key ] )
9778
+ {
8660
9779
  let k = Object.keys(child)[0];
8661
9780
  for( var i = 0; i < child[k].length; ++i )
8662
9781
  setParent(child);
@@ -8670,7 +9789,7 @@ class ContextMenu {
8670
9789
 
8671
9790
  for( let item of this.items )
8672
9791
  {
8673
- let key = Object.keys(item)[0];
9792
+ let key = Object.keys( item )[ 0 ];
8674
9793
  let pKey = "eId" + getSupportedDOMName( key );
8675
9794
 
8676
9795
  // Item already created
@@ -8787,7 +9906,7 @@ class Curve {
8787
9906
 
8788
9907
  var r = [];
8789
9908
  var dx = (element.xrange[1] - element.xrange[ 0 ]) / samples;
8790
- for(var i = element.xrange[0]; i <= element.xrange[1]; i += dx)
9909
+ for( var i = element.xrange[0]; i <= element.xrange[1]; i += dx )
8791
9910
  {
8792
9911
  r.push( element.getValueAt(i) );
8793
9912
  }
@@ -8796,7 +9915,8 @@ class Curve {
8796
9915
 
8797
9916
  element.addValue = function(v) {
8798
9917
 
8799
- for(var i = 0; i < element.value; i++) {
9918
+ for( var i = 0; i < element.value; i++ )
9919
+ {
8800
9920
  var value = element.value[i];
8801
9921
  if(value[0] < v[0]) continue;
8802
9922
  element.value.splice(i,0,v);
@@ -8822,7 +9942,7 @@ class Curve {
8822
9942
 
8823
9943
  var selected = -1;
8824
9944
 
8825
- element.redraw = function( o = {} ) {
9945
+ element.redraw = function( o = {} ) {
8826
9946
 
8827
9947
  if( o.value ) element.value = o.value;
8828
9948
  if( o.xrange ) element.xrange = o.xrange;
@@ -8851,13 +9971,16 @@ class Curve {
8851
9971
  ctx.moveTo( pos[ 0 ], pos[ 1 ] );
8852
9972
  let values = [pos[ 0 ], pos[ 1 ]];
8853
9973
 
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)
9974
+ for( var i in element.value )
9975
+ {
9976
+ var value = element.value[ i ];
9977
+ pos = convert( value );
9978
+ values.push( pos[ 0 ] );
9979
+ values.push( pos[ 1 ] );
9980
+ if( !element.smooth )
9981
+ {
8860
9982
  ctx.lineTo( pos[ 0 ], pos[ 1 ] );
9983
+ }
8861
9984
  }
8862
9985
 
8863
9986
  pos = convert([ element.xrange[ 1 ], element.defaulty ]);
@@ -8874,7 +9997,8 @@ class Curve {
8874
9997
  }
8875
9998
 
8876
9999
  // Draw points
8877
- for( var i = 0; i < element.value.length; i += 1 ) {
10000
+ for( var i = 0; i < element.value.length; i += 1 )
10001
+ {
8878
10002
  var value = element.value[ i ];
8879
10003
  pos = convert( value );
8880
10004
  if( selected == i )
@@ -8886,10 +10010,11 @@ class Curve {
8886
10010
  ctx.fill();
8887
10011
  }
8888
10012
 
8889
- if(element.show_samples) {
10013
+ if( element.show_samples )
10014
+ {
8890
10015
  var samples = element.resample(element.show_samples);
8891
10016
  ctx.fillStyle = "#888";
8892
- for(var i = 0; i < samples.length; i += 1)
10017
+ for( var i = 0; i < samples.length; i += 1)
8893
10018
  {
8894
10019
  var value = [ i * ((element.xrange[ 1 ] - element.xrange[ 0 ]) / element.show_samples) + element.xrange[ 0 ], samples[ i ] ];
8895
10020
  pos = convert(value);
@@ -8912,7 +10037,8 @@ class Curve {
8912
10037
 
8913
10038
  selected = computeSelected( mousex, canvas.height - mousey );
8914
10039
 
8915
- if( e.button == LX.MOUSE_LEFT_CLICK && selected == -1 && element.allow_add_values ) {
10040
+ if( e.button == LX.MOUSE_LEFT_CLICK && selected == -1 && element.allow_add_values )
10041
+ {
8916
10042
  var v = unconvert([ mousex, canvas.height - mousey ]);
8917
10043
  element.value.push( v );
8918
10044
  sortValues();
@@ -8960,7 +10086,8 @@ class Curve {
8960
10086
  var dy = element.draggable_y ? last_mouse[ 1 ] - mousey : 0;
8961
10087
  var delta = unconvert([ -dx, dy ]);
8962
10088
 
8963
- if( selected != -1 ) {
10089
+ if( selected != -1 )
10090
+ {
8964
10091
  var minx = element.xrange[ 0 ];
8965
10092
  var maxx = element.xrange[ 1 ];
8966
10093
 
@@ -9117,7 +10244,7 @@ class Dial {
9117
10244
 
9118
10245
  var r = [];
9119
10246
  var dx = (element.xrange[1] - element.xrange[ 0 ]) / samples;
9120
- for(var i = element.xrange[0]; i <= element.xrange[1]; i += dx)
10247
+ for( var i = element.xrange[0]; i <= element.xrange[1]; i += dx)
9121
10248
  {
9122
10249
  r.push( element.getValueAt(i) );
9123
10250
  }
@@ -9126,15 +10253,16 @@ class Dial {
9126
10253
 
9127
10254
  element.addValue = function(v) {
9128
10255
 
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);
10256
+ for( var i = 0; i < element.value; i++ )
10257
+ {
10258
+ var value = element.value[ i ];
10259
+ if(value[ 0 ] < v[ 0 ]) continue;
10260
+ element.value.splice( i, 0, v );
9133
10261
  redraw();
9134
10262
  return;
9135
10263
  }
9136
10264
 
9137
- element.value.push(v);
10265
+ element.value.push( v );
9138
10266
  redraw();
9139
10267
  }
9140
10268
 
@@ -9154,7 +10282,7 @@ class Dial {
9154
10282
 
9155
10283
  var selected = -1;
9156
10284
 
9157
- element.redraw = function( o = {} ) {
10285
+ element.redraw = function( o = {} ) {
9158
10286
 
9159
10287
  if( o.value ) element.value = o.value;
9160
10288
  if( o.xrange ) element.xrange = o.xrange;
@@ -9183,17 +10311,17 @@ class Dial {
9183
10311
  ctx.moveTo( pos[ 0 ], pos[ 1 ] );
9184
10312
  let values = [pos[ 0 ], pos[ 1 ]];
9185
10313
 
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
-
10314
+ for( var i in element.value)
10315
+ {
10316
+ var value = element.value[ i ];
10317
+ pos = convert( value );
10318
+ values.push( pos[ 0 ] );
10319
+ values.push( pos[ 1 ] );
9192
10320
  }
9193
10321
 
9194
10322
  pos = convert([ element.xrange[ 1 ], element.defaulty ]);
9195
- values.push(pos[ 0 ]);
9196
- values.push(pos[ 1 ]);
10323
+ values.push( pos[ 0 ] );
10324
+ values.push( pos[ 1 ] );
9197
10325
 
9198
10326
  // Draw points
9199
10327
  const center = [0,0];
@@ -9203,7 +10331,8 @@ class Dial {
9203
10331
  ctx.arc( pos[ 0 ], pos[ 1 ], 3, 0, Math.PI * 2);
9204
10332
  ctx.fill();
9205
10333
 
9206
- for( var i = 0; i < element.value.length; i += 1 ) {
10334
+ for( var i = 0; i < element.value.length; i += 1 )
10335
+ {
9207
10336
  var value = element.value[ i ];
9208
10337
  pos = convert( value );
9209
10338
  if( selected == i )
@@ -9215,10 +10344,11 @@ class Dial {
9215
10344
  ctx.fill();
9216
10345
  }
9217
10346
 
9218
- if(element.show_samples) {
10347
+ if( element.show_samples )
10348
+ {
9219
10349
  var samples = element.resample(element.show_samples);
9220
10350
  ctx.fillStyle = "#888";
9221
- for(var i = 0; i < samples.length; i += 1)
10351
+ for( var i = 0; i < samples.length; i += 1)
9222
10352
  {
9223
10353
  var value = [ i * ((element.xrange[ 1 ] - element.xrange[ 0 ]) / element.show_samples) + element.xrange[ 0 ], samples[ i ] ];
9224
10354
  pos = convert(value);
@@ -9241,7 +10371,8 @@ class Dial {
9241
10371
 
9242
10372
  selected = computeSelected( mousex, canvas.height - mousey );
9243
10373
 
9244
- if( e.button == LX.MOUSE_LEFT_CLICK && selected == -1 && element.allow_add_values ) {
10374
+ if( e.button == LX.MOUSE_LEFT_CLICK && selected == -1 && element.allow_add_values )
10375
+ {
9245
10376
  var v = unconvert([ mousex, canvas.height - mousey ]);
9246
10377
  element.value.push( v );
9247
10378
  sortValues();
@@ -9289,7 +10420,8 @@ class Dial {
9289
10420
  var dy = element.draggable_y ? last_mouse[ 1 ] - mousey : 0;
9290
10421
  var delta = unconvert([ -dx, dy ]);
9291
10422
 
9292
- if( selected != -1 ) {
10423
+ if( selected != -1 )
10424
+ {
9293
10425
  var minx = element.xrange[ 0 ];
9294
10426
  var maxx = element.xrange[ 1 ];
9295
10427
 
@@ -9397,7 +10529,8 @@ class AssetViewEvent {
9397
10529
  }
9398
10530
 
9399
10531
  string() {
9400
- switch(this.type) {
10532
+ switch(this.type)
10533
+ {
9401
10534
  case AssetViewEvent.NONE: return "assetview_event_none";
9402
10535
  case AssetViewEvent.ASSET_SELECTED: return "assetview_event_selected";
9403
10536
  case AssetViewEvent.ASSET_DELETED: return "assetview_event_deleted";
@@ -9751,8 +10884,8 @@ class AssetView {
9751
10884
  icon: "fa-solid fa-arrows-rotate",
9752
10885
  callback: domEl => { this._refreshContent(); }
9753
10886
  }
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" } });
10887
+ ], { width: "20%", minWidth: "164px", noSelection: true } );
10888
+ 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
10889
  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
10890
  this.rightPanel.endLine();
9758
10891
  }
@@ -9878,8 +11011,8 @@ class AssetView {
9878
11011
  title.innerText = item.id;
9879
11012
  itemEl.appendChild( title );
9880
11013
 
9881
- if( !that.skipPreview ) {
9882
-
11014
+ if( !that.skipPreview )
11015
+ {
9883
11016
  let preview = null;
9884
11017
  const hasImage = item.src && (['png', 'jpg'].indexOf( getExtension( item.src ) ) > -1 || item.src.includes("data:image/") ); // Support b64 image as src
9885
11018
 
@@ -9906,7 +11039,8 @@ class AssetView {
9906
11039
  var newEmSize = charsPerLine / newLength;
9907
11040
  var textBaseSize = 64;
9908
11041
 
9909
- if(newEmSize < 1) {
11042
+ if( newEmSize < 1 )
11043
+ {
9910
11044
  var newFontSize = newEmSize * textBaseSize;
9911
11045
  textEl.style.fontSize = newFontSize + "px";
9912
11046
  preview.style.paddingTop = "calc(50% - " + (textEl.offsetHeight * 0.5 + 10) + "px)"
@@ -10135,7 +11269,8 @@ class AssetView {
10135
11269
 
10136
11270
  this.currentData.push( item );
10137
11271
 
10138
- if(i == (num_files - 1)) {
11272
+ if( i == (num_files - 1) )
11273
+ {
10139
11274
  this._refreshContent();
10140
11275
  if( !this.skipBrowser )
10141
11276
  this.tree.refresh();
@@ -10187,7 +11322,7 @@ class AssetView {
10187
11322
  this.currentData.splice( idx, 1 );
10188
11323
  this._refreshContent( this.searchValue, this.filter );
10189
11324
 
10190
- if(this.onevent)
11325
+ if( this.onevent)
10191
11326
  {
10192
11327
  const event = new AssetViewEvent( AssetViewEvent.ASSET_DELETED, item );
10193
11328
  this.onevent( event );
@@ -10263,7 +11398,7 @@ Object.assign(LX, {
10263
11398
  xhr.onload = function(load)
10264
11399
  {
10265
11400
  var response = this.response;
10266
- if(this.status != 200)
11401
+ if( this.status != 200)
10267
11402
  {
10268
11403
  var err = "Error " + this.status;
10269
11404
  if(request.error)
@@ -10311,7 +11446,7 @@ Object.assign(LX, {
10311
11446
  var data = new FormData();
10312
11447
  if( request.data )
10313
11448
  {
10314
- for(var i in request.data)
11449
+ for( var i in request.data)
10315
11450
  data.append(i,request.data[i]);
10316
11451
  }
10317
11452
 
@@ -10372,7 +11507,7 @@ Object.assign(LX, {
10372
11507
  var size = total;
10373
11508
  var loaded_scripts = [];
10374
11509
 
10375
- for(var i in url)
11510
+ for( var i in url)
10376
11511
  {
10377
11512
  var script = document.createElement('script');
10378
11513
  script.num = i;
@@ -10497,6 +11632,18 @@ Element.prototype.getParentArea = function() {
10497
11632
  }
10498
11633
  }
10499
11634
 
11635
+ LX.ICONS = {
11636
+ "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>`,
11637
+ "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>`,
11638
+ "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>`,
11639
+ "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>`,
11640
+ "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>`,
11641
+ "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>`,
11642
+ "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>`,
11643
+ "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>`,
11644
+ "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>`,
11645
+ }
11646
+
10500
11647
  LX.UTILS = {
10501
11648
  getTime() { return new Date().getTime() },
10502
11649
  compareThreshold( v, p, n, t ) { return Math.abs(v - p) >= t || Math.abs(v - n) >= t },
@@ -10536,17 +11683,19 @@ LX.UTILS = {
10536
11683
  drawSpline( ctx, pts, t ) {
10537
11684
 
10538
11685
  ctx.save();
10539
- var cp=[]; // array of control points, as x0,y0,x1,y1,...
10540
- var n=pts.length;
11686
+ var cp = []; // array of control points, as x0,y0,x1,y1,...
11687
+ var n = pts.length;
10541
11688
 
10542
11689
  // 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));
11690
+ for( var i = 0; i < (n - 4); i += 2 )
11691
+ {
11692
+ 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
11693
  }
10546
11694
 
10547
- for(var i=2;i<pts.length-5;i+=2) {
11695
+ for( var i = 2; i < ( pts.length - 5 ); i += 2 )
11696
+ {
10548
11697
  ctx.beginPath();
10549
- ctx.moveTo(pts[i],pts[i+1]);
11698
+ ctx.moveTo(pts[i], pts[i+1]);
10550
11699
  ctx.bezierCurveTo(cp[2*i-2],cp[2*i-1],cp[2*i],cp[2*i+1],pts[i+2],pts[i+3]);
10551
11700
  ctx.stroke();
10552
11701
  ctx.closePath();
@@ -10554,14 +11703,14 @@ LX.UTILS = {
10554
11703
 
10555
11704
  // For open curves the first and last arcs are simple quadratics.
10556
11705
  ctx.beginPath();
10557
- ctx.moveTo(pts[0],pts[1]);
10558
- ctx.quadraticCurveTo(cp[0],cp[1],pts[2],pts[3]);
11706
+ ctx.moveTo( pts[ 0 ], pts[ 1 ] );
11707
+ ctx.quadraticCurveTo( cp[ 0 ], cp[ 1 ], pts[ 2 ], pts[ 3 ]);
10559
11708
  ctx.stroke();
10560
11709
  ctx.closePath();
10561
11710
 
10562
11711
  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]);
11712
+ ctx.moveTo( pts[ n-2 ], pts[ n-1 ] );
11713
+ ctx.quadraticCurveTo( cp[ 2*n-10 ], cp[ 2*n-9 ], pts[ n-4 ], pts[ n-3 ]);
10565
11714
  ctx.stroke();
10566
11715
  ctx.closePath();
10567
11716