lexgui 0.6.12 → 0.7.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.
Files changed (46) hide show
  1. package/README.md +14 -9
  2. package/build/{components → extensions}/audio.js +11 -11
  3. package/build/{components → extensions}/codeeditor.js +95 -63
  4. package/build/{components → extensions}/docmaker.js +10 -3
  5. package/build/{components → extensions}/imui.js +19 -12
  6. package/build/{components → extensions}/nodegraph.js +1 -1
  7. package/build/{components → extensions}/timeline.js +23 -23
  8. package/build/{components → extensions}/videoeditor.js +1 -1
  9. package/build/lexgui-docs.css +9 -9
  10. package/build/lexgui.css +484 -224
  11. package/build/lexgui.js +1399 -735
  12. package/build/lexgui.min.css +2 -2
  13. package/build/lexgui.min.js +1 -1
  14. package/build/lexgui.module.js +1400 -736
  15. package/build/lexgui.module.min.js +1 -1
  16. package/changelog.md +72 -21
  17. package/demo.js +43 -30
  18. package/examples/{all_widgets.html → all-components.html} +21 -4
  19. package/examples/{area_tabs.html → area-tabs.html} +3 -3
  20. package/examples/{asset_view.html → asset-view.html} +3 -3
  21. package/examples/{code_editor.html → code-editor.html} +4 -4
  22. package/examples/dialogs.html +3 -3
  23. package/examples/editor.html +27 -18
  24. package/examples/{immediate_ui.html → immediate-ui.html} +3 -3
  25. package/examples/index.html +8 -8
  26. package/examples/{node_graph.html → node-graph.html} +3 -3
  27. package/examples/previews/all-components.png +0 -0
  28. package/examples/previews/area-tabs.png +0 -0
  29. package/examples/previews/asset-view.png +0 -0
  30. package/examples/previews/code-editor.png +0 -0
  31. package/examples/previews/dialogs.png +0 -0
  32. package/examples/previews/editor.png +0 -0
  33. package/examples/previews/node-graph.png +0 -0
  34. package/examples/previews/side-bar.png +0 -0
  35. package/examples/previews/timeline.png +0 -0
  36. package/examples/{side_bar.html → side-bar.html} +3 -3
  37. package/examples/timeline.html +4 -4
  38. package/examples/{video_editor.html → video-editor.html} +3 -3
  39. package/examples/{video_editor2.html → video-editor2.html} +3 -3
  40. package/package.json +2 -2
  41. package/examples/previews/all_widgets.png +0 -0
  42. package/examples/previews/area_tabs.png +0 -0
  43. package/examples/previews/asset_view.png +0 -0
  44. package/examples/previews/code_editor.png +0 -0
  45. package/examples/previews/node_graph.png +0 -0
  46. package/examples/previews/side_bar.png +0 -0
package/build/lexgui.js CHANGED
@@ -14,9 +14,9 @@ console.warn( 'Script _build/lexgui.js_ is depracated and will be removed soon.
14
14
  */
15
15
 
16
16
  const LX = {
17
- version: "0.6.12",
17
+ version: "0.7.0",
18
18
  ready: false,
19
- components: [], // Specific pre-build components
19
+ extensions: [], // Store extensions used
20
20
  signals: {}, // Events and triggers
21
21
  extraCommandbarEntries: [], // User specific entries for command bar
22
22
  activeDraggable: null // Watch for the current active draggable
@@ -210,7 +210,7 @@ function _createCommandbar( root )
210
210
  }
211
211
  else
212
212
  {
213
- for( let c of LX.components )
213
+ for( let c of LX.extensions )
214
214
  {
215
215
  if( !LX[ c ] || !LX[ c ].prototype.onKeyPressed )
216
216
  {
@@ -423,6 +423,7 @@ function _createCommandbar( root )
423
423
  * skipRoot: Skip adding LX root container
424
424
  * skipDefaultArea: Skip creation of main area
425
425
  * layoutMode: Sets page layout mode (document | app)
426
+ * spacingMode: Sets page layout spacing mode (default | compact)
426
427
  */
427
428
 
428
429
  async function init( options = { } )
@@ -474,6 +475,9 @@ async function init( options = { } )
474
475
  } );
475
476
  }
476
477
 
478
+ this.spacingMode = options.spacingMode ?? "default";
479
+ document.documentElement.setAttribute( "data-spacing", this.spacingMode );
480
+
477
481
  this.container.appendChild( this.modal );
478
482
 
479
483
  if( !options.skipRoot )
@@ -518,13 +522,13 @@ async function init( options = { } )
518
522
  this.DEFAULT_SPLITBAR_SIZE = 4;
519
523
  this.OPEN_CONTEXTMENU_ENTRY = 'click';
520
524
 
521
- this.widgetResizeObserver = new ResizeObserver( entries => {
525
+ this.componentResizeObserver = new ResizeObserver( entries => {
522
526
  for ( const entry of entries )
523
527
  {
524
- const widget = entry.target?.jsInstance;
525
- if( widget && widget.onResize )
528
+ const c = entry.target?.jsInstance;
529
+ if( c && c.onResize )
526
530
  {
527
- widget.onResize( entry.contentRect );
531
+ c.onResize( entry.contentRect );
528
532
  }
529
533
  }
530
534
  });
@@ -569,6 +573,19 @@ function setLayoutMode( mode )
569
573
 
570
574
  LX.setLayoutMode = setLayoutMode;
571
575
 
576
+ /**
577
+ * @method setSpacingMode
578
+ * @param {String} mode: default | compact
579
+ */
580
+
581
+ function setSpacingMode( mode )
582
+ {
583
+ this.spacingMode = mode;
584
+ document.documentElement.setAttribute( "data-spacing", this.spacingMode );
585
+ }
586
+
587
+ LX.setSpacingMode = setSpacingMode;
588
+
572
589
  /**
573
590
  * @method setCommandbarState
574
591
  * @param {Boolean} value
@@ -671,7 +688,7 @@ function emit( signalName, value, options = {} )
671
688
 
672
689
  for( let obj of data )
673
690
  {
674
- if( obj instanceof LX.Widget )
691
+ if( obj instanceof LX.BaseComponent )
675
692
  {
676
693
  obj.set( value, options.skipCallback ?? true );
677
694
  }
@@ -751,7 +768,18 @@ class Popover {
751
768
  this.root.dataset["side"] = this.side;
752
769
  this.root.tabIndex = "1";
753
770
  this.root.className = "lexpopover";
754
- LX.root.appendChild( this.root );
771
+
772
+ const nestedDialog = trigger.closest( "dialog" );
773
+ if( nestedDialog && nestedDialog.dataset[ "modal" ] == 'true' )
774
+ {
775
+ this._parent = nestedDialog;
776
+ }
777
+ else
778
+ {
779
+ this._parent = LX.root;
780
+ }
781
+
782
+ this._parent.appendChild( this.root );
755
783
 
756
784
  this.root.addEventListener( "keydown", (e) => {
757
785
  if( e.key == "Escape" )
@@ -873,6 +901,13 @@ class Popover {
873
901
  position[ 1 ] = LX.clamp( position[ 1 ], 0, window.innerHeight - this.root.offsetHeight - this._windowPadding );
874
902
  }
875
903
 
904
+ if( this._parent instanceof HTMLDialogElement )
905
+ {
906
+ let parentRect = this._parent.getBoundingClientRect();
907
+ position[ 0 ] -= parentRect.x;
908
+ position[ 1 ] -= parentRect.y;
909
+ }
910
+
876
911
  this.root.style.left = `${ position[ 0 ] }px`;
877
912
  this.root.style.top = `${ position[ 1 ] }px`;
878
913
  }
@@ -946,7 +981,7 @@ class Sheet {
946
981
  this.root.tabIndex = "1";
947
982
  this.root.role = "dialog";
948
983
  this.root.className = "lexsheet fixed z-1000 bg-primary";
949
- LX.root.appendChild( this.root );
984
+ document.body.appendChild( this.root );
950
985
 
951
986
  this.root.addEventListener( "keydown", (e) => {
952
987
  if( e.key == "Escape" )
@@ -987,17 +1022,21 @@ class Sheet {
987
1022
  this.root.style.height = "100%";
988
1023
  break;
989
1024
  case "top":
1025
+ this.root.style.left = 0;
990
1026
  this.root.style.top = 0;
991
1027
  this.root.style.width = "100%";
992
1028
  this.root.style.height = size;
993
1029
  break;
994
1030
  case "bottom":
1031
+ this.root.style.left = 0;
995
1032
  this.root.style.bottom = 0;
996
1033
  this.root.style.width = "100%";
997
1034
  this.root.style.height = size;
998
1035
  break;
999
1036
  }
1000
1037
 
1038
+ document.documentElement.setAttribute( "data-scale", `sheet-${ this.side }` );
1039
+
1001
1040
  this.root.focus();
1002
1041
 
1003
1042
  this._onClick = e => {
@@ -1015,6 +1054,8 @@ class Sheet {
1015
1054
 
1016
1055
  destroy() {
1017
1056
 
1057
+ document.documentElement.setAttribute( "data-scale", "" );
1058
+
1018
1059
  document.body.removeEventListener( "mousedown", this._onClick, true );
1019
1060
  document.body.removeEventListener( "focusin", this._onClick, true );
1020
1061
 
@@ -1064,7 +1105,18 @@ class DropdownMenu {
1064
1105
  this.root.dataset["side"] = this.side;
1065
1106
  this.root.tabIndex = "1";
1066
1107
  this.root.className = "lexdropdownmenu";
1067
- LX.root.appendChild( this.root );
1108
+
1109
+ const nestedDialog = trigger.closest( "dialog" );
1110
+ if( nestedDialog && nestedDialog.dataset[ "modal" ] == 'true' )
1111
+ {
1112
+ this._parent = nestedDialog;
1113
+ }
1114
+ else
1115
+ {
1116
+ this._parent = LX.root;
1117
+ }
1118
+
1119
+ this._parent.appendChild( this.root );
1068
1120
 
1069
1121
  this._create( this._items );
1070
1122
 
@@ -1100,7 +1152,7 @@ class DropdownMenu {
1100
1152
  document.body.removeEventListener( "mousedown", this._onClick, true );
1101
1153
  document.body.removeEventListener( "focusin", this._onClick, true );
1102
1154
 
1103
- LX.root.querySelectorAll( ".lexdropdownmenu" ).forEach( m => { m.remove(); } );
1155
+ this._parent.querySelectorAll( ".lexdropdownmenu" ).forEach( m => { m.remove(); } );
1104
1156
 
1105
1157
  DropdownMenu.currentMenu = null;
1106
1158
 
@@ -1125,14 +1177,22 @@ class DropdownMenu {
1125
1177
  newParent.className = "lexdropdownmenu";
1126
1178
  newParent.dataset["id"] = parentDom.dataset["id"];
1127
1179
  newParent.dataset["side"] = "right"; // submenus always come from the right
1128
- LX.root.appendChild( newParent );
1180
+ this._parent.appendChild( newParent );
1129
1181
 
1130
1182
  newParent.currentParent = parentDom;
1131
1183
  parentDom = newParent;
1132
1184
 
1133
1185
  LX.doAsync( () => {
1186
+
1134
1187
  const position = [ parentRect.x + parentRect.width, parentRect.y ];
1135
1188
 
1189
+ if( this._parent instanceof HTMLDialogElement )
1190
+ {
1191
+ let rootParentRect = this._parent.getBoundingClientRect();
1192
+ position[ 0 ] -= rootParentRect.x;
1193
+ position[ 1 ] -= rootParentRect.y;
1194
+ }
1195
+
1136
1196
  if( this.avoidCollisions )
1137
1197
  {
1138
1198
  position[ 0 ] = LX.clamp( position[ 0 ], 0, window.innerWidth - newParent.offsetWidth - this._windowPadding );
@@ -1148,139 +1208,180 @@ class DropdownMenu {
1148
1208
 
1149
1209
  for( let item of items )
1150
1210
  {
1151
- if( !item )
1152
- {
1153
- this._addSeparator( parentDom );
1154
- continue;
1155
- }
1211
+ this._createItem( item, parentDom, applyIconPadding );
1212
+ }
1213
+ }
1156
1214
 
1157
- const key = item.name ?? item;
1158
- const pKey = LX.getSupportedDOMName( key );
1215
+ _createItem( item, parentDom, applyIconPadding ) {
1159
1216
 
1160
- // Item already created
1161
- if( parentDom.querySelector( "#" + pKey ) )
1162
- {
1163
- continue;
1164
- }
1217
+ if( !item )
1218
+ {
1219
+ this._addSeparator( parentDom );
1220
+ return;
1221
+ }
1165
1222
 
1166
- const menuItem = document.createElement('div');
1167
- menuItem.className = "lexdropdownmenuitem" + ( item.name ? "" : " label" ) + ( item.disabled ?? false ? " disabled" : "" ) + ( ` ${ item.className ?? "" }` );
1168
- menuItem.dataset["id"] = pKey;
1169
- menuItem.innerHTML = `<span>${ key }</span>`;
1170
- menuItem.tabIndex = "1";
1171
- parentDom.appendChild( menuItem );
1223
+ const key = item.name ?? item;
1224
+ const pKey = LX.getSupportedDOMName( key );
1172
1225
 
1173
- if( item.constructor === String ) // Label case
1174
- {
1175
- continue;
1176
- }
1226
+ // Item already created
1227
+ if( parentDom.querySelector( "#" + pKey ) )
1228
+ {
1229
+ return;
1230
+ }
1177
1231
 
1178
- if( item.submenu )
1179
- {
1180
- const submenuIcon = LX.makeIcon( "Right", { svgClass: "sm" } );
1181
- menuItem.appendChild( submenuIcon );
1182
- }
1183
- else if( item.kbd )
1184
- {
1185
- item.kbd = [].concat( item.kbd );
1232
+ const menuItem = document.createElement('div');
1233
+ menuItem.className = "lexdropdownmenuitem" + ( item.name ? "" : " label" ) + ( item.disabled ?? false ? " disabled" : "" ) + ( ` ${ item.className ?? "" }` );
1234
+ menuItem.dataset["id"] = pKey;
1235
+ menuItem.innerHTML = `<span>${ key }</span>`;
1236
+ menuItem.tabIndex = "1";
1237
+ parentDom.appendChild( menuItem );
1186
1238
 
1187
- const kbd = LX.makeKbd( item.kbd );
1188
- menuItem.appendChild( kbd );
1239
+ if( item.constructor === String ) // Label case
1240
+ {
1241
+ return;
1242
+ }
1189
1243
 
1190
- document.addEventListener( "keydown", e => {
1191
- if( !this._trigger.ddm ) return;
1192
- e.preventDefault();
1193
- // Check if it's a letter or other key
1194
- let kdbKey = item.kbd.join("");
1195
- kdbKey = kdbKey.length == 1 ? kdbKey.toLowerCase() : kdbKey;
1196
- if( kdbKey == e.key )
1197
- {
1198
- menuItem.click();
1199
- }
1200
- } );
1201
- }
1244
+ if( item.submenu )
1245
+ {
1246
+ const submenuIcon = LX.makeIcon( "Right", { svgClass: "sm" } );
1247
+ menuItem.appendChild( submenuIcon );
1248
+ }
1249
+ else if( item.kbd )
1250
+ {
1251
+ item.kbd = [].concat( item.kbd );
1202
1252
 
1203
- const disabled = item.disabled ?? false;
1253
+ const kbd = LX.makeKbd( item.kbd );
1254
+ menuItem.appendChild( kbd );
1204
1255
 
1205
- if( item.icon )
1206
- {
1207
- const icon = LX.makeIcon( item.icon, { svgClass: disabled ? "fg-tertiary" : item.className } );
1208
- menuItem.prepend( icon );
1209
- }
1210
- else if( item.checked == undefined && applyIconPadding ) // no checkbox, no icon, apply padding if there's checkbox or icon in other items
1211
- {
1212
- menuItem.classList.add( "pl-8" );
1213
- }
1256
+ document.addEventListener( "keydown", e => {
1257
+ if( !this._trigger.ddm ) return;
1258
+ e.preventDefault();
1259
+ // Check if it's a letter or other key
1260
+ let kdbKey = item.kbd.join("");
1261
+ kdbKey = kdbKey.length == 1 ? kdbKey.toLowerCase() : kdbKey;
1262
+ if( kdbKey == e.key )
1263
+ {
1264
+ menuItem.click();
1265
+ }
1266
+ } );
1267
+ }
1214
1268
 
1215
- if( disabled )
1216
- {
1217
- continue;
1218
- }
1269
+ const disabled = item.disabled ?? false;
1219
1270
 
1220
- if( item.checked != undefined )
1271
+ if( this._radioGroup !== undefined )
1272
+ {
1273
+ if( item.name === this._radioGroup.selected )
1221
1274
  {
1222
- const checkbox = new LX.Checkbox( pKey + "_entryChecked", item.checked, (v) => {
1223
- const f = item[ 'callback' ];
1224
- item.checked = v;
1225
- if( f )
1226
- {
1227
- f.call( this, key, v, menuItem );
1228
- }
1229
- }, { className: "accent" });
1230
- const input = checkbox.root.querySelector( "input" );
1231
- input.classList.add( "ml-auto" );
1232
- menuItem.appendChild( input );
1233
-
1234
- menuItem.addEventListener( "click", (e) => {
1235
- if( e.target.type == "checkbox" ) return;
1236
- input.checked = !input.checked;
1237
- checkbox.set( input.checked );
1238
- } );
1275
+ const icon = LX.makeIcon( "Circle", { svgClass: "xxs fill-current" } );
1276
+ menuItem.prepend( icon );
1239
1277
  }
1240
- else
1241
- {
1242
- menuItem.addEventListener( "click", () => {
1243
- const f = item[ 'callback' ];
1244
- if( f )
1245
- {
1246
- f.call( this, key, menuItem );
1247
- }
1248
1278
 
1249
- this.destroy( true );
1250
- } );
1251
- }
1279
+ menuItem.setAttribute( "data-radioname", this._radioGroup.name );
1280
+ }
1281
+ else if( item.icon )
1282
+ {
1283
+ const icon = LX.makeIcon( item.icon, { svgClass: disabled ? "fg-tertiary" : item.className } );
1284
+ menuItem.prepend( icon );
1285
+ }
1286
+ else if( item.checked == undefined && applyIconPadding ) // no checkbox, no icon, apply padding if there's checkbox or icon in other items
1287
+ {
1288
+ menuItem.classList.add( "pl-8" );
1289
+ }
1252
1290
 
1253
- menuItem.addEventListener("mouseover", e => {
1291
+ if( disabled )
1292
+ {
1293
+ return;
1294
+ }
1254
1295
 
1255
- let path = menuItem.dataset["id"];
1256
- let p = parentDom;
1296
+ if( item.checked != undefined )
1297
+ {
1298
+ const checkbox = new LX.Checkbox( pKey + "_entryChecked", item.checked, (v) => {
1299
+ const f = item[ 'callback' ];
1300
+ item.checked = v;
1301
+ if( f )
1302
+ {
1303
+ f.call( this, key, v, menuItem );
1304
+ }
1305
+ }, { className: "accent" });
1306
+ const input = checkbox.root.querySelector( "input" );
1307
+ input.classList.add( "ml-auto" );
1308
+ menuItem.appendChild( input );
1309
+
1310
+ menuItem.addEventListener( "click", (e) => {
1311
+ if( e.target.type == "checkbox" ) return;
1312
+ input.checked = !input.checked;
1313
+ checkbox.set( input.checked );
1314
+ } );
1315
+ }
1316
+ else
1317
+ {
1318
+ menuItem.addEventListener( "click", () => {
1319
+ const f = item[ 'callback' ];
1320
+ if( f )
1321
+ {
1322
+ f.call( this, key, menuItem );
1323
+ }
1257
1324
 
1258
- while( p )
1325
+ const radioName = menuItem.getAttribute( "data-radioname" );
1326
+ if( radioName )
1259
1327
  {
1260
- path += "/" + p.dataset["id"];
1261
- p = p.currentParent?.parentElement;
1328
+ this._trigger[ radioName ] = key;
1262
1329
  }
1263
1330
 
1264
- LX.root.querySelectorAll( ".lexdropdownmenu" ).forEach( m => {
1265
- if( !path.includes( m.dataset["id"] ) )
1266
- {
1267
- m.currentParent.built = false;
1268
- m.remove();
1269
- }
1270
- } );
1331
+ this.destroy( true );
1332
+ } );
1333
+ }
1271
1334
 
1272
- if( item.submenu && this.inPlace )
1335
+ menuItem.addEventListener("mouseover", e => {
1336
+
1337
+ let path = menuItem.dataset["id"];
1338
+ let p = parentDom;
1339
+
1340
+ while( p )
1341
+ {
1342
+ path += "/" + p.dataset["id"];
1343
+ p = p.currentParent?.parentElement;
1344
+ }
1345
+
1346
+ this._parent.querySelectorAll( ".lexdropdownmenu" ).forEach( m => {
1347
+ if( !path.includes( m.dataset["id"] ) )
1273
1348
  {
1274
- if( menuItem.built )
1275
- {
1276
- return;
1277
- }
1278
- menuItem.built = true;
1279
- this._create( item.submenu, menuItem );
1349
+ m.currentParent.built = false;
1350
+ m.remove();
1280
1351
  }
1352
+ } );
1281
1353
 
1282
- e.stopPropagation();
1283
- });
1354
+ if( item.submenu && this.inPlace )
1355
+ {
1356
+ if( menuItem.built )
1357
+ {
1358
+ return;
1359
+ }
1360
+ menuItem.built = true;
1361
+ this._create( item.submenu, menuItem );
1362
+ }
1363
+
1364
+ e.stopPropagation();
1365
+ });
1366
+
1367
+ if( item.options )
1368
+ {
1369
+ this._addSeparator();
1370
+
1371
+ console.assert( this._trigger[ item.name ] && "An item of the radio group must be selected!" );
1372
+ this._radioGroup = {
1373
+ name: item.name,
1374
+ selected: this._trigger[ item.name ]
1375
+ };
1376
+
1377
+ for( let o of item.options )
1378
+ {
1379
+ this._createItem( o, parentDom, applyIconPadding );
1380
+ }
1381
+
1382
+ delete this._radioGroup;
1383
+
1384
+ this._addSeparator();
1284
1385
  }
1285
1386
  }
1286
1387
 
@@ -1340,6 +1441,13 @@ class DropdownMenu {
1340
1441
  position[ 1 ] = LX.clamp( position[ 1 ], 0, window.innerHeight - this.root.offsetHeight - this._windowPadding );
1341
1442
  }
1342
1443
 
1444
+ if( this._parent instanceof HTMLDialogElement )
1445
+ {
1446
+ let parentRect = this._parent.getBoundingClientRect();
1447
+ position[ 0 ] -= parentRect.x;
1448
+ position[ 1 ] -= parentRect.y;
1449
+ }
1450
+
1343
1451
  this.root.style.left = `${ position[ 0 ] }px`;
1344
1452
  this.root.style.top = `${ position[ 1 ] }px`;
1345
1453
  this.inPlace = true;
@@ -1600,25 +1708,25 @@ class ColorPicker {
1600
1708
  this._updateColorValue( null, true );
1601
1709
  } ).root );
1602
1710
 
1603
- this.labelWidget = new LX.TextInput( null, "", null, { inputClass: "bg-none", fit: true, disabled: true } );
1604
- colorLabel.appendChild( this.labelWidget.root );
1711
+ this.labelComponent = new LX.TextInput( null, "", null, { inputClass: "bg-none", fit: true, disabled: true } );
1712
+ colorLabel.appendChild( this.labelComponent.root );
1605
1713
 
1606
1714
  // Copy button
1607
1715
  {
1608
- const copyButtonWidget = new LX.Button(null, "copy", async () => {
1609
- navigator.clipboard.writeText( this.labelWidget.value() );
1610
- copyButtonWidget.root.querySelector( "input[type='checkbox']" ).style.pointerEvents = "none";
1716
+ const copyButtonComponent = new LX.Button(null, "copy", async () => {
1717
+ navigator.clipboard.writeText( this.labelComponent.value() );
1718
+ copyButtonComponent.root.querySelector( "input[type='checkbox']" ).style.pointerEvents = "none";
1611
1719
 
1612
1720
  LX.doAsync( () => {
1613
- copyButtonWidget.swap( true );
1614
- copyButtonWidget.root.querySelector( "input[type='checkbox']" ).style.pointerEvents = "auto";
1721
+ copyButtonComponent.swap( true );
1722
+ copyButtonComponent.root.querySelector( "input[type='checkbox']" ).style.pointerEvents = "auto";
1615
1723
  }, 3000 );
1616
1724
 
1617
1725
  }, { swap: "Check", icon: "Copy", buttonClass: "bg-none", className: "ml-auto", title: "Copy" } );
1618
1726
 
1619
- copyButtonWidget.root.querySelector( ".swap-on svg" ).addClass( "fg-success" );
1727
+ copyButtonComponent.root.querySelector( ".swap-on svg" ).addClass( "fg-success" );
1620
1728
 
1621
- colorLabel.appendChild( copyButtonWidget.root );
1729
+ colorLabel.appendChild( copyButtonComponent.root );
1622
1730
  }
1623
1731
 
1624
1732
  this._updateColorValue( hexValue, true );
@@ -1673,25 +1781,25 @@ class ColorPicker {
1673
1781
  if( this.colorModel == "CSS" )
1674
1782
  {
1675
1783
  const { r, g, b, a } = this.currentColor.css;
1676
- this.labelWidget.set( `rgb${ this.useAlpha ? 'a' : '' }(${ r },${ g },${ b }${ this.useAlpha ? ',' + toFixed( a ) : '' })` );
1784
+ this.labelComponent.set( `rgb${ this.useAlpha ? 'a' : '' }(${ r },${ g },${ b }${ this.useAlpha ? ',' + toFixed( a ) : '' })` );
1677
1785
  }
1678
1786
  else if( this.colorModel == "Hex" )
1679
1787
  {
1680
- this.labelWidget.set( ( this.useAlpha ? this.currentColor.hex : this.currentColor.hex.substr( 0, 7 ) ).toUpperCase() );
1788
+ this.labelComponent.set( ( this.useAlpha ? this.currentColor.hex : this.currentColor.hex.substr( 0, 7 ) ).toUpperCase() );
1681
1789
  }
1682
1790
  else if( this.colorModel == "HSV" )
1683
1791
  {
1684
1792
  const { h, s, v, a } = this.currentColor.hsv;
1685
1793
  const components = [ Math.floor( h ) + 'º', Math.floor( s * 100 ) + '%', Math.floor( v * 100 ) + '%' ];
1686
1794
  if( this.useAlpha ) components.push( toFixed( a ) );
1687
- this.labelWidget.set( components.join( ' ' ) );
1795
+ this.labelComponent.set( components.join( ' ' ) );
1688
1796
  }
1689
1797
  else // RGB
1690
1798
  {
1691
1799
  const { r, g, b, a } = this.currentColor.rgb;
1692
1800
  const components = [ toFixed( r ), toFixed( g ), toFixed( b ) ];
1693
1801
  if( this.useAlpha ) components.push( toFixed( a ) );
1694
- this.labelWidget.set( components.join( ' ' ) );
1802
+ this.labelComponent.set( components.join( ' ' ) );
1695
1803
  }
1696
1804
  }
1697
1805
 
@@ -1855,8 +1963,6 @@ class Calendar {
1855
1963
  let fromRangeDate = this.range ? LX.dateFromDateString( this.range[ 0 ] ) : null;
1856
1964
  let toRangeDate = this.range ? LX.dateFromDateString( this.range[ 1 ] ) : null;
1857
1965
 
1858
- this.currentDate ? new Date( `${ this.currentDate.month }/${ this.currentDate.day }/${ this.currentDate.year }` ) : null;
1859
-
1860
1966
  for( let week = 0; week < 6; week++ )
1861
1967
  {
1862
1968
  const hrow = document.createElement( 'tr' );
@@ -2007,6 +2113,13 @@ class Calendar {
2007
2113
  this.refresh();
2008
2114
  }
2009
2115
 
2116
+ setMonth( month ) {
2117
+
2118
+ this.month = month;
2119
+
2120
+ this.fromMonthYear( this.month, this.year );
2121
+ }
2122
+
2010
2123
  _getOrdinalSuffix( day ) {
2011
2124
  if ( day > 3 && day < 21 ) return "th";
2012
2125
  switch ( day % 10 )
@@ -2035,7 +2148,8 @@ class CalendarRange {
2035
2148
 
2036
2149
  console.assert( range && range.constructor === Array, "Range cannot be empty and has to be an Array!" );
2037
2150
 
2038
- let mustAdvanceMonth = false;
2151
+ let mustSetMonth = null;
2152
+ let dateReversed = false;
2039
2153
 
2040
2154
  // Fix any issues with date range picking
2041
2155
  {
@@ -2047,9 +2161,10 @@ class CalendarRange {
2047
2161
  const tmp = range[ 0 ];
2048
2162
  range[ 0 ] = range[ 1 ];
2049
2163
  range[ 1 ] = tmp;
2164
+ dateReversed = true;
2050
2165
  }
2051
2166
 
2052
- mustAdvanceMonth = ( t0.getMonth() == t1.getMonth() ) && ( t0.getFullYear() == t1.getFullYear() );
2167
+ mustSetMonth = (dateReversed ? t1.getMonth() : t0.getMonth() ) + 2; // +1 to convert range, +1 to use next month
2053
2168
  }
2054
2169
 
2055
2170
  this.from = range[ 0 ];
@@ -2102,10 +2217,8 @@ class CalendarRange {
2102
2217
  range
2103
2218
  });
2104
2219
 
2105
- if( mustAdvanceMonth )
2106
- {
2107
- this.toCalendar._nextMonth( true );
2108
- }
2220
+ console.assert( mustSetMonth && "New Month must be valid" );
2221
+ this.toCalendar.setMonth( mustSetMonth );
2109
2222
 
2110
2223
  this.root.appendChild( this.fromCalendar.root );
2111
2224
  this.root.appendChild( this.toCalendar.root );
@@ -2329,6 +2442,19 @@ class Tabs {
2329
2442
  tabEl.instance = this;
2330
2443
  contentEl.id = tabEl.id + "_content";
2331
2444
 
2445
+ if( options.badge )
2446
+ {
2447
+ const asChild = options.badge.asChild ?? false;
2448
+ const badgeOptions = { };
2449
+
2450
+ if( asChild )
2451
+ {
2452
+ badgeOptions.parent = tabEl;
2453
+ }
2454
+
2455
+ tabEl.innerHTML += LX.badge( options.badge.content ?? "", options.badge.className, badgeOptions );
2456
+ }
2457
+
2332
2458
  if( tabEl.selected )
2333
2459
  {
2334
2460
  this.selected = name;
@@ -2601,7 +2727,7 @@ class Dialog {
2601
2727
 
2602
2728
  if( !callback )
2603
2729
  {
2604
- console.warn("Content is empty, add some widgets using 'callback' parameter!");
2730
+ console.warn("Content is empty, add some components using 'callback' parameter!");
2605
2731
  }
2606
2732
 
2607
2733
  this._oncreate = callback;
@@ -2616,6 +2742,7 @@ class Dialog {
2616
2742
  let root = document.createElement('dialog');
2617
2743
  root.className = "lexdialog " + (options.className ?? "");
2618
2744
  root.id = options.id ?? "dialog" + Dialog._last_id++;
2745
+ root.dataset["modal"] = modal;
2619
2746
  LX.root.appendChild( root );
2620
2747
 
2621
2748
  LX.doAsync( () => {
@@ -2669,9 +2796,9 @@ class Dialog {
2669
2796
  const panelChildCount = panel.root.childElementCount;
2670
2797
 
2671
2798
  const branch = panel.branch( data.name, { closed: data.closed } );
2672
- branch.widgets = data.widgets;
2799
+ branch.components = data.components;
2673
2800
 
2674
- for( let w of branch.widgets )
2801
+ for( let w of branch.components )
2675
2802
  {
2676
2803
  branch.content.appendChild( w.root );
2677
2804
  }
@@ -2915,9 +3042,6 @@ class ContextMenu {
2915
3042
 
2916
3043
  this.root = document.createElement( "div" );
2917
3044
  this.root.className = "lexcontextmenu";
2918
- this.root.style.left = ( event.x - 48 ) + "px";
2919
- this.root.style.top = ( event.y - 8 ) + "px";
2920
-
2921
3045
  this.root.addEventListener("mouseleave", function() {
2922
3046
  this.remove();
2923
3047
  });
@@ -2933,31 +3057,57 @@ class ContextMenu {
2933
3057
  item[ "icon" ] = options.icon;
2934
3058
  this.items.push( item );
2935
3059
  }
3060
+
3061
+ const nestedDialog = event.target.closest( "dialog" );
3062
+ if( nestedDialog && nestedDialog.dataset[ "modal" ] == 'true' )
3063
+ {
3064
+ this._parent = nestedDialog;
3065
+ }
3066
+ else
3067
+ {
3068
+ this._parent = LX.root;
3069
+ }
3070
+
3071
+ this._parent.appendChild( this.root );
3072
+
3073
+ // Set position based on parent
3074
+ const position = [ event.x - 48, event.y - 8 ];
3075
+ if( this._parent instanceof HTMLDialogElement )
3076
+ {
3077
+ let parentRect = this._parent.getBoundingClientRect();
3078
+ position[ 0 ] -= parentRect.x;
3079
+ position[ 1 ] -= parentRect.y;
3080
+ }
3081
+
3082
+ this.root.style.left = `${ position[ 0 ] }px`;
3083
+ this.root.style.top = `${ position[ 1 ] }px`;
2936
3084
  }
2937
3085
 
2938
3086
  _adjustPosition( div, margin, useAbsolute = false ) {
2939
3087
 
2940
3088
  let rect = div.getBoundingClientRect();
3089
+ let left = parseInt( div.style.left );
3090
+ let top = parseInt( div.style.top );
2941
3091
 
2942
3092
  if( !useAbsolute )
2943
3093
  {
2944
3094
  let width = rect.width;
2945
3095
  if( rect.left < 0 )
2946
3096
  {
2947
- div.style.left = margin + "px";
3097
+ left = margin;
2948
3098
  }
2949
3099
  else if( window.innerWidth - rect.right < 0 )
2950
3100
  {
2951
- div.style.left = (window.innerWidth - width - margin) + "px";
3101
+ left = (window.innerWidth - width - margin);
2952
3102
  }
2953
3103
 
2954
3104
  if( rect.top < 0 )
2955
3105
  {
2956
- div.style.top = margin + "px";
3106
+ top = margin;
2957
3107
  }
2958
3108
  else if( (rect.top + rect.height) > window.innerHeight )
2959
3109
  {
2960
- div.style.top = (window.innerHeight - rect.height - margin) + "px";
3110
+ top = (window.innerHeight - rect.height - margin);
2961
3111
  }
2962
3112
  }
2963
3113
  else
@@ -2965,15 +3115,18 @@ class ContextMenu {
2965
3115
  let dt = window.innerWidth - rect.right;
2966
3116
  if( dt < 0 )
2967
3117
  {
2968
- div.style.left = div.offsetLeft + (dt - margin) + "px";
3118
+ left = div.offsetLeft + (dt - margin);
2969
3119
  }
2970
3120
 
2971
3121
  dt = window.innerHeight - (rect.top + rect.height);
2972
3122
  if( dt < 0 )
2973
3123
  {
2974
- div.style.top = div.offsetTop + (dt - margin + 20 ) + "px";
3124
+ top = div.offsetTop + (dt - margin + 20 );
2975
3125
  }
2976
3126
  }
3127
+
3128
+ div.style.left = `${ left }px`;
3129
+ div.style.top = `${ top }px`;
2977
3130
  }
2978
3131
 
2979
3132
  _createSubmenu( o, k, c, d ) {
@@ -3195,7 +3348,6 @@ LX.ContextMenu = ContextMenu;
3195
3348
  function addContextMenu( title, event, callback, options )
3196
3349
  {
3197
3350
  const menu = new ContextMenu( event, title, options );
3198
- LX.root.appendChild( menu.root );
3199
3351
 
3200
3352
  if( callback )
3201
3353
  {
@@ -4311,6 +4463,43 @@ class CanvasMap2D {
4311
4463
 
4312
4464
  LX.CanvasMap2D = CanvasMap2D;
4313
4465
 
4466
+ class Skeleton {
4467
+
4468
+ constructor( elements ) {
4469
+
4470
+ this.root = LX.makeContainer( [ "auto", "auto" ], "flex flex-row lexskeleton" );
4471
+
4472
+ if( elements.constructor === String )
4473
+ {
4474
+ this.root.innerHTML = elements;
4475
+ }
4476
+ else
4477
+ {
4478
+ // Force array
4479
+ elements = [].concat( elements );
4480
+
4481
+ for( let e of elements )
4482
+ {
4483
+ this.root.appendChild( e );
4484
+ }
4485
+ }
4486
+ }
4487
+
4488
+ destroy() {
4489
+
4490
+ this.root.dataset[ "closed" ] = true;
4491
+
4492
+ LX.doAsync( () => {
4493
+ this.root.remove();
4494
+ this.root = null;
4495
+ }, 200 );
4496
+ }
4497
+ }
4498
+
4499
+ LX.Skeleton = Skeleton;
4500
+
4501
+ // Js native overrides
4502
+
4314
4503
  Object.defineProperty(String.prototype, 'lastChar', {
4315
4504
  get: function() { return this[ this.length - 1 ]; },
4316
4505
  enumerable: true,
@@ -4611,6 +4800,28 @@ function deleteElement( element )
4611
4800
 
4612
4801
  LX.deleteElement = deleteElement;
4613
4802
 
4803
+ /**
4804
+ * @method toCamelCase
4805
+ * @param {String} str
4806
+ */
4807
+ function toCamelCase( str )
4808
+ {
4809
+ return str.toLowerCase().replace(/[^a-zA-Z0-9]+(.)/g, (_, chr) => chr.toUpperCase());
4810
+ }
4811
+
4812
+ LX.toCamelCase = toCamelCase;
4813
+
4814
+ /**
4815
+ * @method toTitleCase
4816
+ * @param {String} str
4817
+ */
4818
+ function toTitleCase( str )
4819
+ {
4820
+ return str.replace(/-/g, " ").toLowerCase().replace(/\b\w/g, char => char.toUpperCase());
4821
+ }
4822
+
4823
+ LX.toTitleCase = toTitleCase;
4824
+
4614
4825
  /**
4615
4826
  * @method getSupportedDOMName
4616
4827
  * @description Convert a text string to a valid DOM name
@@ -4639,12 +4850,12 @@ LX.getSupportedDOMName = getSupportedDOMName;
4639
4850
 
4640
4851
  /**
4641
4852
  * @method has
4642
- * @description Ask if LexGUI is using a specific component
4643
- * @param {String} componentName Name of the LexGUI component
4853
+ * @description Ask if LexGUI is using a specific extension
4854
+ * @param {String} extensionName Name of the LexGUI extension
4644
4855
  */
4645
- function has( componentName )
4856
+ function has( extensionName )
4646
4857
  {
4647
- return ( LX.components.indexOf( componentName ) > -1 );
4858
+ return ( LX.extensions.indexOf( extensionName ) > -1 );
4648
4859
  }
4649
4860
 
4650
4861
  LX.has = has;
@@ -4821,6 +5032,19 @@ function getThemeColor( colorName )
4821
5032
 
4822
5033
  LX.getThemeColor = getThemeColor;
4823
5034
 
5035
+ /**
5036
+ * @method switchSpacing
5037
+ * @description Toggles between "default" and "compact" spacing layouts
5038
+ */
5039
+ function switchSpacing()
5040
+ {
5041
+ const currentSpacing = document.documentElement.getAttribute( "data-spacing" ) ?? "default";
5042
+ document.documentElement.setAttribute( "data-spacing", ( currentSpacing == "default" ) ? "compact" : "default" );
5043
+ LX.emit( "@on_new_spacing_layout", currentSpacing );
5044
+ }
5045
+
5046
+ LX.switchSpacing = switchSpacing;
5047
+
4824
5048
  /**
4825
5049
  * @method getBase64Image
4826
5050
  * @description Convert an image to a base64 string
@@ -5362,6 +5586,80 @@ function makeKbd( keys, useSpecialKeys = true, extraClass = "" )
5362
5586
 
5363
5587
  LX.makeKbd = makeKbd;
5364
5588
 
5589
+ /**
5590
+ * @method makeBreadcrumb
5591
+ * @description Displays the path to the current resource using a hierarchy
5592
+ * @param {Array} items
5593
+ * @param {Object} options
5594
+ * maxItems: Max items until ellipsis is used to overflow
5595
+ * separatorIcon: Customize separator icon
5596
+ */
5597
+ function makeBreadcrumb( items, options = {} )
5598
+ {
5599
+ const breadcrumb = LX.makeContainer( ["auto", "auto"], "flex flex-row gap-1" );
5600
+
5601
+ const separatorIcon = options.separatorIcon ?? "ChevronRight";
5602
+ const maxItems = options.maxItems ?? 4;
5603
+ const eraseNum = items.length - maxItems;
5604
+ if( eraseNum > 0 )
5605
+ {
5606
+ const erased = items.splice( 1, eraseNum + 1 );
5607
+ const ellipsisItem = { title: "...", ellipsis: erased.map( v => v.title ).join( "/" ) };
5608
+ items.splice( 1, 0, ellipsisItem );
5609
+ }
5610
+
5611
+ for( let i = 0; i < items.length; ++i )
5612
+ {
5613
+ const item = items[ i ];
5614
+ console.assert( item.title, "Breadcrumb item must have a title!" );
5615
+
5616
+ if( i != 0 )
5617
+ {
5618
+ const icon = LX.makeIcon( separatorIcon, { svgClass: "sm fg-secondary separator" } );
5619
+ breadcrumb.appendChild( icon );
5620
+ }
5621
+
5622
+ const lastElement = ( i == items.length - 1 );
5623
+ const breadcrumbItem = LX.makeContainer( ["auto", "auto"], `p-1 flex flex-row gap-1 items-center ${ lastElement ? "" : "fg-secondary" }` );
5624
+ breadcrumb.appendChild( breadcrumbItem );
5625
+
5626
+ let itemTitle = LX.makeElement( "p", "", item.title );
5627
+ if( item.icon )
5628
+ {
5629
+ breadcrumbItem.appendChild( LX.makeIcon( item.icon, { svgClass: "sm" } ) );
5630
+ }
5631
+
5632
+ if( item.items !== undefined )
5633
+ {
5634
+ const bDropdownTrigger = LX.makeContainer( ["auto", "auto"], `${ lastElement ? "" : "fg-secondary" }` );
5635
+ bDropdownTrigger.listen( "click", (e) => {
5636
+ new LX.DropdownMenu( e.target, item.items, { side: "bottom", align: "start" });
5637
+ } );
5638
+ bDropdownTrigger.append( itemTitle );
5639
+ breadcrumbItem.appendChild( bDropdownTrigger );
5640
+ }
5641
+ else if( item.url !== undefined )
5642
+ {
5643
+ let itemUrl = LX.makeElement( "a", `decoration-none fg-${ lastElement ? "primary" : "secondary" }`, "", breadcrumbItem );
5644
+ itemUrl.href = item.url;
5645
+ itemUrl.appendChild( itemTitle );
5646
+ }
5647
+ else
5648
+ {
5649
+ breadcrumbItem.appendChild( itemTitle );
5650
+ }
5651
+
5652
+ if( item.ellipsis )
5653
+ {
5654
+ LX.asTooltip( breadcrumbItem, item.ellipsis, { side: "bottom", offset: 4 } );
5655
+ }
5656
+ }
5657
+
5658
+ return breadcrumb;
5659
+ }
5660
+
5661
+ LX.makeBreadcrumb = makeBreadcrumb;
5662
+
5365
5663
  /**
5366
5664
  * @method makeIcon
5367
5665
  * @description Gets an SVG element using one of LX.ICONS
@@ -5678,6 +5976,7 @@ LX.prompt = prompt;
5678
5976
  * @param {String} title
5679
5977
  * @param {String} description (Optional)
5680
5978
  * @param {Object} options
5979
+ * position: Set new position for the toasts ("top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right")
5681
5980
  * action: Data of the custom action { name, callback }
5682
5981
  * closable: Allow closing the toast
5683
5982
  * timeout: Time in which the toast closed automatically, in ms. -1 means persistent. [3000]
@@ -5694,9 +5993,48 @@ function toast( title, description, options = {} )
5694
5993
 
5695
5994
  const toast = document.createElement( "li" );
5696
5995
  toast.className = "lextoast";
5697
- toast.style.translate = "0 calc(100% + 30px)";
5698
5996
  this.notifications.prepend( toast );
5699
5997
 
5998
+ const [ positionVertical, positionHorizontal ] = options.position ? options.position.split( "-" ) : [ "bottom", "right" ];
5999
+
6000
+ // Reset style
6001
+ this.notifications.style.right = "unset";
6002
+ this.notifications.style.left = "unset";
6003
+ this.notifications.style.top = "unset";
6004
+ this.notifications.style.bottom = "unset";
6005
+ this.notifications.style.placeSelf = "unset";
6006
+
6007
+ switch( positionVertical )
6008
+ {
6009
+ case "top":
6010
+ toast.style.translate = "0 -30px";
6011
+ this.notifications.style.top = "1rem";
6012
+ this.notifications.style.flexDirection = "column";
6013
+ break;
6014
+ case "bottom":
6015
+ toast.style.translate = "0 calc(100% + 30px)";
6016
+ this.notifications.style.top = "auto";
6017
+ this.notifications.style.bottom = "1rem";
6018
+ this.notifications.style.flexDirection = "column-reverse";
6019
+ break;
6020
+ }
6021
+
6022
+ switch( positionHorizontal )
6023
+ {
6024
+ case "left":
6025
+ this.notifications.style.left = "1rem";
6026
+ break;
6027
+ case "center":
6028
+ this.notifications.style.placeSelf = "center";
6029
+ break;
6030
+ case "right":
6031
+ this.notifications.style.right = "1rem";
6032
+ break;
6033
+ }
6034
+
6035
+ toast.classList.add( positionVertical );
6036
+ toast.classList.add( positionHorizontal );
6037
+
5700
6038
  LX.doAsync( () => {
5701
6039
 
5702
6040
  if( this.notifications.offsetWidth > this.notifications.iWidth )
@@ -5735,7 +6073,7 @@ function toast( title, description, options = {} )
5735
6073
  const that = this;
5736
6074
 
5737
6075
  toast.close = function() {
5738
- this.dataset[ "closed" ] = true;
6076
+ this.dataset[ "open" ] = false;
5739
6077
  LX.doAsync( () => {
5740
6078
  this.remove();
5741
6079
  if( !that.notifications.childElementCount )
@@ -5781,7 +6119,32 @@ function badge( text, className, options = {} )
5781
6119
  const container = document.createElement( "div" );
5782
6120
  container.innerHTML = text;
5783
6121
  container.className = "lexbadge " + ( className ?? "" );
6122
+
6123
+ if( options.chip )
6124
+ {
6125
+ container.classList.add( "chip" );
6126
+ }
6127
+
5784
6128
  Object.assign( container.style, options.style ?? {} );
6129
+
6130
+ if( options.callback )
6131
+ {
6132
+ const arrowIcon = LX.makeIcon( "ArrowUpRight", { svgClass: "xs fg-contrast" } );
6133
+ arrowIcon.querySelector("svg").style.marginLeft = "-0.25rem";
6134
+ container.innerHTML += arrowIcon.innerHTML;
6135
+ container.addEventListener( "click", e => {
6136
+ e.preventDefault();
6137
+ e.stopPropagation();
6138
+ options.callback();
6139
+ } );
6140
+ }
6141
+
6142
+ if( options.parent )
6143
+ {
6144
+ options.parent.classList.add( "lexbadge-parent" );
6145
+ options.parent.appendChild( container );
6146
+ }
6147
+
5785
6148
  return ( options.asElement ?? false ) ? container : container.outerHTML;
5786
6149
  }
5787
6150
 
@@ -5856,6 +6219,7 @@ function asTooltip( trigger, content, options = {} )
5856
6219
  trigger.dataset[ "disableTooltip" ] = !( options.active ?? true );
5857
6220
 
5858
6221
  let tooltipDom = null;
6222
+ let tooltipParent = LX.root;
5859
6223
 
5860
6224
  trigger.addEventListener( "mouseenter", function(e) {
5861
6225
 
@@ -5864,12 +6228,22 @@ function asTooltip( trigger, content, options = {} )
5864
6228
  return;
5865
6229
  }
5866
6230
 
5867
- LX.root.querySelectorAll( ".lextooltip" ).forEach( e => e.remove() );
5868
-
5869
6231
  tooltipDom = document.createElement( "div" );
5870
6232
  tooltipDom.className = "lextooltip";
5871
6233
  tooltipDom.innerHTML = content;
5872
6234
 
6235
+ const nestedDialog = trigger.closest( "dialog" );
6236
+ if( nestedDialog && nestedDialog.dataset[ "modal" ] == 'true' )
6237
+ {
6238
+ tooltipParent = nestedDialog;
6239
+ }
6240
+
6241
+ // Remove other first
6242
+ LX.root.querySelectorAll( ".lextooltip" ).forEach( e => e.remove() );
6243
+
6244
+ // Append new tooltip
6245
+ tooltipParent.appendChild( tooltipDom );
6246
+
5873
6247
  LX.doAsync( () => {
5874
6248
 
5875
6249
  const position = [ 0, 0 ];
@@ -5904,11 +6278,16 @@ function asTooltip( trigger, content, options = {} )
5904
6278
  position[ 0 ] = LX.clamp( position[ 0 ], 0, window.innerWidth - tooltipDom.offsetWidth - 4 );
5905
6279
  position[ 1 ] = LX.clamp( position[ 1 ], 0, window.innerHeight - tooltipDom.offsetHeight - 4 );
5906
6280
 
6281
+ if( tooltipParent instanceof HTMLDialogElement )
6282
+ {
6283
+ let parentRect = tooltipParent.getBoundingClientRect();
6284
+ position[ 0 ] -= parentRect.x;
6285
+ position[ 1 ] -= parentRect.y;
6286
+ }
6287
+
5907
6288
  tooltipDom.style.left = `${ position[ 0 ] }px`;
5908
6289
  tooltipDom.style.top = `${ position[ 1 ] }px`;
5909
6290
  } );
5910
-
5911
- LX.root.appendChild( tooltipDom );
5912
6291
  } );
5913
6292
 
5914
6293
  trigger.addEventListener( "mouseleave", function(e) {
@@ -6354,15 +6733,15 @@ class AreaOverlayButtons {
6354
6733
  }
6355
6734
 
6356
6735
  let callback = b.callback;
6357
- let widget = null;
6736
+ let component = null;
6358
6737
 
6359
6738
  if( b.options )
6360
6739
  {
6361
- widget = overlayPanel.addSelect( null, b.options, b.value ?? b.name, callback, _options );
6740
+ component = overlayPanel.addSelect( null, b.options, b.value ?? b.name, callback, _options );
6362
6741
  }
6363
6742
  else
6364
6743
  {
6365
- widget = overlayPanel.addButton( null, b.name, function( value, event ) {
6744
+ component = overlayPanel.addButton( null, b.name, function( value, event ) {
6366
6745
  if( b.selectable )
6367
6746
  {
6368
6747
  if( b.group )
@@ -6379,13 +6758,13 @@ class AreaOverlayButtons {
6379
6758
 
6380
6759
  if( callback )
6381
6760
  {
6382
- callback( value, event, widget.root );
6761
+ callback( value, event, component.root );
6383
6762
  }
6384
6763
 
6385
6764
  }, _options );
6386
6765
  }
6387
6766
 
6388
- this.buttons[ b.name ] = widget;
6767
+ this.buttons[ b.name ] = component;
6389
6768
 
6390
6769
  // ends the group
6391
6770
  if( overlayGroup && last )
@@ -6459,6 +6838,7 @@ class Area {
6459
6838
  * minHeight: Minimum height to be applied when resizing
6460
6839
  * maxWidth: Maximum width to be applied when resizing
6461
6840
  * maxHeight: Maximum height to be applied when resizing
6841
+ * layout: Layout to automatically split the area
6462
6842
  */
6463
6843
 
6464
6844
  constructor( options = {} ) {
@@ -6504,6 +6884,11 @@ class Area {
6504
6884
  lexroot.appendChild( this.root );
6505
6885
  }
6506
6886
 
6887
+ if( options.layout )
6888
+ {
6889
+ this.setLayout( options.layout );
6890
+ }
6891
+
6507
6892
  let overlay = options.overlay;
6508
6893
 
6509
6894
  if( overlay )
@@ -6664,6 +7049,47 @@ class Area {
6664
7049
  this.root.appendChild( element );
6665
7050
  }
6666
7051
 
7052
+ /**
7053
+ * @method setLayout
7054
+ * @description Automatically split the area to generate the desired layout
7055
+ * @param {Array} layout
7056
+ */
7057
+
7058
+ setLayout( layout ) {
7059
+
7060
+ this.layout = LX.deepCopy( layout );
7061
+
7062
+ if( !layout.splits )
7063
+ {
7064
+ console.warn( "Area layout has no splits!" );
7065
+ return;
7066
+ }
7067
+
7068
+ const _splitArea = ( area, layout ) => {
7069
+
7070
+ if( layout.className )
7071
+ {
7072
+ area.root.className += ` ${ layout.className }`;
7073
+ }
7074
+
7075
+ if( !layout.splits )
7076
+ {
7077
+ return;
7078
+ }
7079
+
7080
+ const type = layout.type ?? "horizontal";
7081
+ const resize = layout.resize ?? true;
7082
+ const minimizable = layout.minimizable ?? false;
7083
+ const [ splitA, splitB ] = area.split( { type, resize, minimizable, sizes: [ layout.splits[ 0 ].size, layout.splits[ 1 ].size ] } );
7084
+
7085
+ _splitArea( splitA, layout.splits[ 0 ] );
7086
+ _splitArea( splitB, layout.splits[ 1 ] );
7087
+
7088
+ };
7089
+
7090
+ _splitArea( this, layout );
7091
+ }
7092
+
6667
7093
  /**
6668
7094
  * @method split
6669
7095
  * @param {Object} options
@@ -6922,12 +7348,17 @@ class Area {
6922
7348
  doc.addEventListener( 'mouseup', innerMouseUp );
6923
7349
  e.stopPropagation();
6924
7350
  e.preventDefault();
6925
- document.body.classList.add( 'nocursor' );
6926
- that.splitBar.classList.add( 'nocursor' );
6927
7351
  }
6928
7352
 
6929
7353
  function innerMouseMove( e )
6930
7354
  {
7355
+ const rect = that.root.getBoundingClientRect();
7356
+ if( ( ( e.x ) < rect.x || ( e.x ) > ( rect.x + rect.width ) ) ||
7357
+ ( ( e.y ) < rect.y || ( e.y ) > ( rect.y + rect.height ) ) )
7358
+ {
7359
+ return;
7360
+ }
7361
+
6931
7362
  if( that.type == "horizontal" )
6932
7363
  {
6933
7364
  that._moveSplit( -e.movementX );
@@ -6946,8 +7377,6 @@ class Area {
6946
7377
  const doc = that.root.ownerDocument;
6947
7378
  doc.removeEventListener( 'mousemove', innerMouseMove );
6948
7379
  doc.removeEventListener( 'mouseup', innerMouseUp );
6949
- document.body.classList.remove( 'nocursor' );
6950
- that.splitBar.classList.remove( 'nocursor' );
6951
7380
  }
6952
7381
 
6953
7382
  return this.sections;
@@ -7306,10 +7735,11 @@ class Area {
7306
7735
  }
7307
7736
  else
7308
7737
  {
7738
+ const parentHeight = this.size[ 1 ];
7309
7739
  var size = Math.max( ( a2Root.offsetHeight + dt ) + a2.offset, parseInt( a2.minHeight ) );
7740
+ size = Math.min( parentHeight - LX.DEFAULT_SPLITBAR_SIZE, size );
7310
7741
  if( forceWidth ) size = forceWidth;
7311
7742
 
7312
- const parentHeight = this.size[ 1 ];
7313
7743
  const bottomPercent = ( size / parentHeight ) * 100;
7314
7744
  const topPercent = Math.max( 0, 100 - bottomPercent );
7315
7745
 
@@ -7368,13 +7798,13 @@ class Area {
7368
7798
  }
7369
7799
  LX.Area = Area;
7370
7800
 
7371
- // widget.js @jxarco
7801
+ // base_component.js @jxarco
7372
7802
 
7373
7803
  /**
7374
- * @class Widget
7804
+ * @class BaseComponent
7375
7805
  */
7376
7806
 
7377
- class Widget {
7807
+ class BaseComponent {
7378
7808
 
7379
7809
  static NONE = 0;
7380
7810
  static TEXT = 1;
@@ -7414,14 +7844,15 @@ class Widget {
7414
7844
  static TABS = 35;
7415
7845
  static DATE = 36;
7416
7846
  static MAP2D = 37;
7417
- static LABEL = 38;
7418
- static BLANK = 39;
7847
+ static LABEL = 39;
7848
+ static BLANK = 40;
7849
+ static RATE = 41;
7419
7850
 
7420
7851
  static NO_CONTEXT_TYPES = [
7421
- Widget.BUTTON,
7422
- Widget.LIST,
7423
- Widget.FILE,
7424
- Widget.PROGRESS
7852
+ BaseComponent.BUTTON,
7853
+ BaseComponent.LIST,
7854
+ BaseComponent.FILE,
7855
+ BaseComponent.PROGRESS
7425
7856
  ];
7426
7857
 
7427
7858
  constructor( type, name, value, options = {} ) {
@@ -7432,7 +7863,7 @@ class Widget {
7432
7863
  this._initialValue = value;
7433
7864
 
7434
7865
  const root = document.createElement( 'div' );
7435
- root.className = "lexwidget";
7866
+ root.className = "lexcomponent";
7436
7867
 
7437
7868
  if( options.id )
7438
7869
  {
@@ -7449,7 +7880,7 @@ class Widget {
7449
7880
  root.className += " " + options.className;
7450
7881
  }
7451
7882
 
7452
- if( type != Widget.TITLE )
7883
+ if( type != BaseComponent.TITLE )
7453
7884
  {
7454
7885
  if( options.width )
7455
7886
  {
@@ -7468,7 +7899,7 @@ class Widget {
7468
7899
  root.style.height = root.style.minHeight = options.height;
7469
7900
  }
7470
7901
 
7471
- LX.widgetResizeObserver.observe( root );
7902
+ LX.componentResizeObserver.observe( root );
7472
7903
  }
7473
7904
 
7474
7905
  if( name != undefined )
@@ -7476,7 +7907,7 @@ class Widget {
7476
7907
  if( !( options.hideName ?? false ) )
7477
7908
  {
7478
7909
  let domName = document.createElement( 'div' );
7479
- domName.className = "lexwidgetname";
7910
+ domName.className = "lexcomponentname";
7480
7911
 
7481
7912
  if( options.justifyName )
7482
7913
  {
@@ -7538,7 +7969,7 @@ class Widget {
7538
7969
  }
7539
7970
 
7540
7971
  _canPaste() {
7541
- let pasteAllowed = this.type === Widget.CUSTOM ?
7972
+ let pasteAllowed = this.type === BaseComponent.CUSTOM ?
7542
7973
  ( navigator.clipboard.customIdx !== undefined && this.customIdx == navigator.clipboard.customIdx ) : navigator.clipboard.type === this.type;
7543
7974
 
7544
7975
  pasteAllowed &= ( this.disabled !== true );
@@ -7575,7 +8006,7 @@ class Widget {
7575
8006
 
7576
8007
  if( this.onSetValue )
7577
8008
  {
7578
- let resetButton = this.root.querySelector( ".lexwidgetname .lexicon" );
8009
+ let resetButton = this.root.querySelector( ".lexcomponentname .lexicon" );
7579
8010
  if( resetButton )
7580
8011
  {
7581
8012
  resetButton.style.display = ( value != this.value() ? "block" : "none" );
@@ -7601,7 +8032,7 @@ class Widget {
7601
8032
 
7602
8033
  oncontextmenu( e ) {
7603
8034
 
7604
- if( Widget.NO_CONTEXT_TYPES.includes( this.type ) )
8035
+ if( BaseComponent.NO_CONTEXT_TYPES.includes( this.type ) )
7605
8036
  {
7606
8037
  return;
7607
8038
  }
@@ -7632,41 +8063,42 @@ class Widget {
7632
8063
 
7633
8064
  switch( this.type )
7634
8065
  {
7635
- case Widget.TEXT: return "Text";
7636
- case Widget.TEXTAREA: return "TextArea";
7637
- case Widget.BUTTON: return "Button";
7638
- case Widget.SELECT: return "Select";
7639
- case Widget.CHECKBOX: return "Checkbox";
7640
- case Widget.TOGGLE: return "Toggle";
7641
- case Widget.RADIO: return "Radio";
7642
- case Widget.COLOR: return "Color";
7643
- case Widget.RANGE: return "Range";
7644
- case Widget.NUMBER: return "Number";
7645
- case Widget.VECTOR: return "Vector";
7646
- case Widget.TREE: return "Tree";
7647
- case Widget.PROGRESS: return "Progress";
7648
- case Widget.FILE: return "File";
7649
- case Widget.LAYERS: return "Layers";
7650
- case Widget.ARRAY: return "Array";
7651
- case Widget.LIST: return "List";
7652
- case Widget.TAGS: return "Tags";
7653
- case Widget.CURVE: return "Curve";
7654
- case Widget.KNOB: return "Knob";
7655
- case Widget.SIZE: return "Size";
7656
- case Widget.PAD: return "Pad";
7657
- case Widget.FORM: return "Form";
7658
- case Widget.DIAL: return "Dial";
7659
- case Widget.COUNTER: return "Counter";
7660
- case Widget.TABLE: return "Table";
7661
- case Widget.TABS: return "Tabs";
7662
- case Widget.DATE: return "Date";
7663
- case Widget.MAP2D: return "Map2D";
7664
- case Widget.LABEL: return "Label";
7665
- case Widget.BLANK: return "Blank";
7666
- case Widget.CUSTOM: return this.customName;
7667
- }
7668
-
7669
- console.error( `Unknown Widget type: ${ this.type }` );
8066
+ case BaseComponent.TEXT: return "Text";
8067
+ case BaseComponent.TEXTAREA: return "TextArea";
8068
+ case BaseComponent.BUTTON: return "Button";
8069
+ case BaseComponent.SELECT: return "Select";
8070
+ case BaseComponent.CHECKBOX: return "Checkbox";
8071
+ case BaseComponent.TOGGLE: return "Toggle";
8072
+ case BaseComponent.RADIO: return "Radio";
8073
+ case BaseComponent.COLOR: return "Color";
8074
+ case BaseComponent.RANGE: return "Range";
8075
+ case BaseComponent.NUMBER: return "Number";
8076
+ case BaseComponent.VECTOR: return "Vector";
8077
+ case BaseComponent.TREE: return "Tree";
8078
+ case BaseComponent.PROGRESS: return "Progress";
8079
+ case BaseComponent.FILE: return "File";
8080
+ case BaseComponent.LAYERS: return "Layers";
8081
+ case BaseComponent.ARRAY: return "Array";
8082
+ case BaseComponent.LIST: return "List";
8083
+ case BaseComponent.TAGS: return "Tags";
8084
+ case BaseComponent.CURVE: return "Curve";
8085
+ case BaseComponent.KNOB: return "Knob";
8086
+ case BaseComponent.SIZE: return "Size";
8087
+ case BaseComponent.PAD: return "Pad";
8088
+ case BaseComponent.FORM: return "Form";
8089
+ case BaseComponent.DIAL: return "Dial";
8090
+ case BaseComponent.COUNTER: return "Counter";
8091
+ case BaseComponent.TABLE: return "Table";
8092
+ case BaseComponent.TABS: return "Tabs";
8093
+ case BaseComponent.DATE: return "Date";
8094
+ case BaseComponent.MAP2D: return "Map2D";
8095
+ case BaseComponent.RATE: return "Rate";
8096
+ case BaseComponent.LABEL: return "Label";
8097
+ case BaseComponent.BLANK: return "Blank";
8098
+ case BaseComponent.CUSTOM: return this.customName;
8099
+ }
8100
+
8101
+ console.error( `Unknown Component type: ${ this.type }` );
7670
8102
  }
7671
8103
 
7672
8104
  refresh() {
@@ -7674,52 +8106,52 @@ class Widget {
7674
8106
  }
7675
8107
  }
7676
8108
 
7677
- LX.Widget = Widget;
8109
+ LX.BaseComponent = BaseComponent;
7678
8110
 
7679
- function ADD_CUSTOM_WIDGET( customWidgetName, options = {} )
8111
+ function ADD_CUSTOM_COMPONENT( customComponentName, options = {} )
7680
8112
  {
7681
8113
  let customIdx = LX.guidGenerator();
7682
8114
 
7683
- LX.Panel.prototype[ 'add' + customWidgetName ] = function( name, instance, callback ) {
8115
+ LX.Panel.prototype[ 'add' + customComponentName ] = function( name, instance, callback ) {
7684
8116
 
7685
8117
  const userParams = Array.from( arguments ).slice( 3 );
7686
8118
 
7687
- let widget = new Widget( Widget.CUSTOM, name, null, options );
7688
- this._attachWidget( widget );
8119
+ let component = new BaseComponent( BaseComponent.CUSTOM, name, null, options );
8120
+ this._attachComponent( component );
7689
8121
 
7690
- widget.customName = customWidgetName;
7691
- widget.customIdx = customIdx;
8122
+ component.customName = customComponentName;
8123
+ component.customIdx = customIdx;
7692
8124
 
7693
- widget.onGetValue = () => {
8125
+ component.onGetValue = () => {
7694
8126
  return instance;
7695
8127
  };
7696
8128
 
7697
- widget.onSetValue = ( newValue, skipCallback, event ) => {
8129
+ component.onSetValue = ( newValue, skipCallback, event ) => {
7698
8130
  instance = newValue;
7699
- refresh_widget();
8131
+ _refreshComponent();
7700
8132
  element.querySelector( ".lexcustomitems" ).toggleAttribute( 'hidden', false );
7701
8133
  if( !skipCallback )
7702
8134
  {
7703
- widget._trigger( new LX.IEvent( name, instance, event ), callback );
8135
+ component._trigger( new LX.IEvent( name, instance, event ), callback );
7704
8136
  }
7705
8137
  };
7706
8138
 
7707
- widget.onResize = ( rect ) => {
7708
- const realNameWidth = ( widget.root.domName?.style.width ?? "0px" );
8139
+ component.onResize = ( rect ) => {
8140
+ const realNameWidth = ( component.root.domName?.style.width ?? "0px" );
7709
8141
  container.style.width = `calc( 100% - ${ realNameWidth })`;
7710
8142
  };
7711
8143
 
7712
- const element = widget.root;
8144
+ const element = component.root;
7713
8145
 
7714
- let container, customWidgetsDom;
8146
+ let container, customComponentsDom;
7715
8147
  let defaultInstance = options.default ?? {};
7716
8148
 
7717
8149
  // Add instance button
7718
8150
 
7719
- const refresh_widget = () => {
8151
+ const _refreshComponent = () => {
7720
8152
 
7721
8153
  if( container ) container.remove();
7722
- if( customWidgetsDom ) customWidgetsDom.remove();
8154
+ if( customComponentsDom ) customComponentsDom.remove();
7723
8155
 
7724
8156
  container = document.createElement('div');
7725
8157
  container.className = "lexcustomcontainer w-full";
@@ -7729,7 +8161,7 @@ function ADD_CUSTOM_WIDGET( customWidgetName, options = {} )
7729
8161
  const customIcon = LX.makeIcon( options.icon ?? "Box" );
7730
8162
  const menuIcon = LX.makeIcon( "Menu" );
7731
8163
 
7732
- let buttonName = customWidgetName + (!instance ? " [empty]" : "");
8164
+ let buttonName = customComponentName + (!instance ? " [empty]" : "");
7733
8165
  let buttonEl = this.addButton(null, buttonName, (value, event) => {
7734
8166
  if( instance )
7735
8167
  {
@@ -7739,9 +8171,9 @@ function ADD_CUSTOM_WIDGET( customWidgetName, options = {} )
7739
8171
  else
7740
8172
  {
7741
8173
  LX.addContextMenu(null, event, c => {
7742
- c.add("New " + customWidgetName, () => {
8174
+ c.add("New " + customComponentName, () => {
7743
8175
  instance = {};
7744
- refresh_widget();
8176
+ _refreshComponent();
7745
8177
  element.querySelector(".lexcustomitems").toggleAttribute('hidden', false);
7746
8178
  element.dataset["opened"] = !element.querySelector(".lexcustomitems").hasAttribute("hidden");
7747
8179
  });
@@ -7763,7 +8195,7 @@ function ADD_CUSTOM_WIDGET( customWidgetName, options = {} )
7763
8195
  LX.addContextMenu(null, e, c => {
7764
8196
  c.add("Clear", () => {
7765
8197
  instance = null;
7766
- refresh_widget();
8198
+ _refreshComponent();
7767
8199
  });
7768
8200
  });
7769
8201
  });
@@ -7771,14 +8203,14 @@ function ADD_CUSTOM_WIDGET( customWidgetName, options = {} )
7771
8203
 
7772
8204
  // Show elements
7773
8205
 
7774
- customWidgetsDom = document.createElement('div');
7775
- customWidgetsDom.className = "lexcustomitems";
7776
- customWidgetsDom.toggleAttribute('hidden', true);
7777
- element.appendChild( customWidgetsDom );
8206
+ customComponentsDom = document.createElement('div');
8207
+ customComponentsDom.className = "lexcustomitems";
8208
+ customComponentsDom.toggleAttribute('hidden', true);
8209
+ element.appendChild( customComponentsDom );
7778
8210
 
7779
8211
  if( instance )
7780
8212
  {
7781
- this.queue( customWidgetsDom );
8213
+ this.queue( customComponentsDom );
7782
8214
 
7783
8215
  const on_instance_changed = ( key, value, event ) => {
7784
8216
  const setter = options[ `_set_${ key }` ];
@@ -7790,7 +8222,7 @@ function ADD_CUSTOM_WIDGET( customWidgetName, options = {} )
7790
8222
  {
7791
8223
  instance[ key ] = value;
7792
8224
  }
7793
- widget._trigger( new LX.IEvent( name, instance, event ), callback );
8225
+ component._trigger( new LX.IEvent( name, instance, event ), callback );
7794
8226
  };
7795
8227
 
7796
8228
  for( let key in defaultInstance )
@@ -7855,11 +8287,11 @@ function ADD_CUSTOM_WIDGET( customWidgetName, options = {} )
7855
8287
  }
7856
8288
  };
7857
8289
 
7858
- refresh_widget();
8290
+ _refreshComponent();
7859
8291
  };
7860
8292
  }
7861
8293
 
7862
- LX.ADD_CUSTOM_WIDGET = ADD_CUSTOM_WIDGET;
8294
+ LX.ADD_CUSTOM_COMPONENT = ADD_CUSTOM_COMPONENT;
7863
8295
 
7864
8296
  /**
7865
8297
  * @class NodeTree
@@ -8431,14 +8863,14 @@ LX.NodeTree = NodeTree;
8431
8863
 
8432
8864
  /**
8433
8865
  * @class Blank
8434
- * @description Blank Widget
8866
+ * @description Blank Component
8435
8867
  */
8436
8868
 
8437
- class Blank extends Widget {
8869
+ class Blank extends BaseComponent {
8438
8870
 
8439
8871
  constructor( width, height ) {
8440
8872
 
8441
- super( Widget.BLANK );
8873
+ super( BaseComponent.BLANK );
8442
8874
 
8443
8875
  this.root.style.width = width ?? "auto";
8444
8876
  this.root.style.height = height ?? "8px";
@@ -8449,17 +8881,17 @@ LX.Blank = Blank;
8449
8881
 
8450
8882
  /**
8451
8883
  * @class Title
8452
- * @description Title Widget
8884
+ * @description Title Component
8453
8885
  */
8454
8886
 
8455
- class Title extends Widget {
8887
+ class Title extends BaseComponent {
8456
8888
 
8457
8889
  constructor( name, options = {} ) {
8458
8890
 
8459
- console.assert( name, "Can't create Title Widget without text!" );
8891
+ console.assert( name, "Can't create Title Component without text!" );
8460
8892
 
8461
- // Note: Titles are not registered in Panel.widgets by now
8462
- super( Widget.TITLE, null, null, options );
8893
+ // Note: Titles are not registered in Panel.components by now
8894
+ super( BaseComponent.TITLE, null, null, options );
8463
8895
 
8464
8896
  this.root.className = `lextitle ${ this.root.className }`;
8465
8897
 
@@ -8493,14 +8925,14 @@ LX.Title = Title;
8493
8925
 
8494
8926
  /**
8495
8927
  * @class TextInput
8496
- * @description TextInput Widget
8928
+ * @description TextInput Component
8497
8929
  */
8498
8930
 
8499
- class TextInput extends Widget {
8931
+ class TextInput extends BaseComponent {
8500
8932
 
8501
8933
  constructor( name, value, callback, options = {} ) {
8502
8934
 
8503
- super( Widget.TEXT, name, String( value ), options );
8935
+ super( BaseComponent.TEXT, name, String( value ), options );
8504
8936
 
8505
8937
  this.onGetValue = () => {
8506
8938
  return value;
@@ -8633,14 +9065,14 @@ LX.TextInput = TextInput;
8633
9065
 
8634
9066
  /**
8635
9067
  * @class TextArea
8636
- * @description TextArea Widget
9068
+ * @description TextArea Component
8637
9069
  */
8638
9070
 
8639
- class TextArea extends Widget {
9071
+ class TextArea extends BaseComponent {
8640
9072
 
8641
9073
  constructor( name, value, callback, options = {} ) {
8642
9074
 
8643
- super( Widget.TEXTAREA, name, value, options );
9075
+ super( BaseComponent.TEXTAREA, name, value, options );
8644
9076
 
8645
9077
  this.onGetValue = () => {
8646
9078
  return value;
@@ -8734,14 +9166,14 @@ LX.TextArea = TextArea;
8734
9166
 
8735
9167
  /**
8736
9168
  * @class Button
8737
- * @description Button Widget
9169
+ * @description Button Component
8738
9170
  */
8739
9171
 
8740
- class Button extends Widget {
9172
+ class Button extends BaseComponent {
8741
9173
 
8742
9174
  constructor( name, value, callback, options = {} ) {
8743
9175
 
8744
- super( Widget.BUTTON, name, null, options );
9176
+ super( BaseComponent.BUTTON, name, null, options );
8745
9177
 
8746
9178
  this.onGetValue = () => {
8747
9179
  const swapInput = wValue.querySelector( "input" );
@@ -8956,10 +9388,10 @@ LX.Button = Button;
8956
9388
 
8957
9389
  /**
8958
9390
  * @class ComboButtons
8959
- * @description ComboButtons Widget
9391
+ * @description ComboButtons Component
8960
9392
  */
8961
9393
 
8962
- class ComboButtons extends Widget {
9394
+ class ComboButtons extends BaseComponent {
8963
9395
 
8964
9396
  constructor( name, values, options = {} ) {
8965
9397
 
@@ -9074,7 +9506,7 @@ class ComboButtons extends Widget {
9074
9506
  currentValue = currentValue[ 0 ];
9075
9507
  }
9076
9508
 
9077
- super( Widget.BUTTONS, name, null, options );
9509
+ super( BaseComponent.BUTTONS, name, null, options );
9078
9510
 
9079
9511
  this.onGetValue = () => {
9080
9512
  return currentValue;
@@ -9117,16 +9549,16 @@ LX.ComboButtons = ComboButtons;
9117
9549
 
9118
9550
  /**
9119
9551
  * @class Card
9120
- * @description Card Widget
9552
+ * @description Card Component
9121
9553
  */
9122
9554
 
9123
- class Card extends Widget {
9555
+ class Card extends BaseComponent {
9124
9556
 
9125
9557
  constructor( name, options = {} ) {
9126
9558
 
9127
9559
  options.hideName = true;
9128
9560
 
9129
- super( Widget.CARD, name, null, options );
9561
+ super( BaseComponent.CARD, name, null, options );
9130
9562
 
9131
9563
  let container = document.createElement('div');
9132
9564
  container.className = "lexcard";
@@ -9180,10 +9612,10 @@ LX.Card = Card;
9180
9612
 
9181
9613
  /**
9182
9614
  * @class Form
9183
- * @description Form Widget
9615
+ * @description Form Component
9184
9616
  */
9185
9617
 
9186
- class Form extends Widget {
9618
+ class Form extends BaseComponent {
9187
9619
 
9188
9620
  constructor( name, data, callback, options = {} ) {
9189
9621
 
@@ -9196,7 +9628,7 @@ class Form extends Widget {
9196
9628
  // Always hide name for this one
9197
9629
  options.hideName = true;
9198
9630
 
9199
- super( Widget.FORM, name, null, options );
9631
+ super( BaseComponent.FORM, name, null, options );
9200
9632
 
9201
9633
  this.onGetValue = () => {
9202
9634
  return container.formData;
@@ -9204,18 +9636,18 @@ class Form extends Widget {
9204
9636
 
9205
9637
  this.onSetValue = ( newValue, skipCallback, event ) => {
9206
9638
  container.formData = newValue;
9207
- const entries = container.querySelectorAll( ".lexwidget" );
9639
+ const entries = container.querySelectorAll( ".lexcomponent" );
9208
9640
  for( let i = 0; i < entries.length; ++i )
9209
9641
  {
9210
9642
  const entry = entries[ i ];
9211
- if( entry.jsInstance.type != LX.Widget.TEXT )
9643
+ if( entry.jsInstance.type != BaseComponent.TEXT )
9212
9644
  {
9213
9645
  continue;
9214
9646
  }
9215
- let entryName = entries[ i ].querySelector( ".lexwidgetname" ).innerText;
9647
+ let entryName = entries[ i ].querySelector( ".lexcomponentname" ).innerText;
9216
9648
  let entryInput = entries[ i ].querySelector( ".lextext input" );
9217
9649
  entryInput.value = newValue[ entryName ] ?? "";
9218
- Widget._dispatchEvent( entryInput, "focusout", skipCallback );
9650
+ BaseComponent._dispatchEvent( entryInput, "focusout", skipCallback );
9219
9651
  }
9220
9652
  };
9221
9653
 
@@ -9245,10 +9677,10 @@ class Form extends Widget {
9245
9677
  container.appendChild( label.root );
9246
9678
  }
9247
9679
 
9248
- entryData.textWidget = new LX.TextInput( null, entryData.constructor == Object ? entryData.value : entryData, ( value ) => {
9680
+ entryData.textComponent = new LX.TextInput( null, entryData.constructor == Object ? entryData.value : entryData, ( value ) => {
9249
9681
  container.formData[ entry ] = value;
9250
9682
  }, entryData );
9251
- container.appendChild( entryData.textWidget.root );
9683
+ container.appendChild( entryData.textComponent.root );
9252
9684
 
9253
9685
  container.formData[ entry ] = entryData.constructor == Object ? entryData.value : entryData;
9254
9686
  }
@@ -9273,7 +9705,7 @@ class Form extends Widget {
9273
9705
  {
9274
9706
  let entryData = data[ entry ];
9275
9707
 
9276
- if( !entryData.textWidget.valid() )
9708
+ if( !entryData.textComponent.valid() )
9277
9709
  {
9278
9710
  return;
9279
9711
  }
@@ -9293,14 +9725,14 @@ LX.Form = Form;
9293
9725
 
9294
9726
  /**
9295
9727
  * @class Select
9296
- * @description Select Widget
9728
+ * @description Select Component
9297
9729
  */
9298
9730
 
9299
- class Select extends Widget {
9731
+ class Select extends BaseComponent {
9300
9732
 
9301
9733
  constructor( name, values, value, callback, options = {} ) {
9302
9734
 
9303
- super( Widget.SELECT, name, value, options );
9735
+ super( BaseComponent.SELECT, name, value, options );
9304
9736
 
9305
9737
  this.onGetValue = () => {
9306
9738
  return value;
@@ -9373,7 +9805,7 @@ class Select extends Widget {
9373
9805
  options.overflowContainerX = options.overflowContainerY = options.overflowContainer;
9374
9806
  }
9375
9807
 
9376
- const _placeOptions = ( parent ) => {
9808
+ const _placeOptions = ( parent, forceLastPlacement ) => {
9377
9809
 
9378
9810
  const selectRoot = selectedOption.root;
9379
9811
  const rect = selectRoot.getBoundingClientRect();
@@ -9403,8 +9835,8 @@ class Select extends Widget {
9403
9835
  parent.style.top = ( topPosition + selectRoot.offsetHeight ) + 'px';
9404
9836
  list.style.height = ""; // set auto height by default
9405
9837
 
9406
- const failBelow = ( topPosition + listHeight ) > maxY;
9407
- const failAbove = ( topPosition - listHeight ) < 0;
9838
+ const failAbove = forceLastPlacement ? this._lastPlacement[ 0 ] : ( topPosition - listHeight ) < 0;
9839
+ const failBelow = forceLastPlacement ? this._lastPlacement[ 1 ] : ( topPosition + listHeight ) > maxY;
9408
9840
  if( failBelow && !failAbove )
9409
9841
  {
9410
9842
  parent.style.top = ( topPosition - listHeight ) + 'px';
@@ -9415,6 +9847,8 @@ class Select extends Widget {
9415
9847
  {
9416
9848
  list.style.height = `${ maxY - topPosition - 32 }px`; // 32px margin
9417
9849
  }
9850
+
9851
+ this._lastPlacement = [ failAbove, failBelow ];
9418
9852
  }
9419
9853
 
9420
9854
  // Manage horizontal aspect
@@ -9528,7 +9962,7 @@ class Select extends Widget {
9528
9962
  {
9529
9963
  const filterOptions = LX.deepCopy( options );
9530
9964
  filterOptions.placeholder = filterOptions.placeholder ?? "Search...";
9531
- filterOptions.skipWidget = filterOptions.skipWidget ?? true;
9965
+ filterOptions.skipComponent = filterOptions.skipComponent ?? true;
9532
9966
  filterOptions.trigger = "input";
9533
9967
  filterOptions.icon = "Search";
9534
9968
  filterOptions.className = "lexfilter";
@@ -9537,6 +9971,7 @@ class Select extends Widget {
9537
9971
  filter = new LX.TextInput(null, options.filterValue ?? "", ( v ) => {
9538
9972
  const filteredOptions = this._filterOptions( values, v );
9539
9973
  list.refresh( filteredOptions );
9974
+ _placeOptions( listDialog, true );
9540
9975
  }, filterOptions );
9541
9976
  filter.root.querySelector( ".lextext" ).style.border = "1px solid transparent";
9542
9977
 
@@ -9570,7 +10005,7 @@ class Select extends Widget {
9570
10005
 
9571
10006
  let option = document.createElement( "div" );
9572
10007
  option.className = "option";
9573
- option.innerHTML = iValue;
10008
+ option.innerHTML = ( LX.makeIcon( "Inbox", { svgClass: "mr-2" } ).innerHTML + iValue );
9574
10009
 
9575
10010
  let li = document.createElement( "li" );
9576
10011
  li.className = "lexselectitem empty";
@@ -9679,7 +10114,7 @@ class Select extends Widget {
9679
10114
  const emptyFilter = !value.length;
9680
10115
  let filteredOptions = [];
9681
10116
 
9682
- // Add widgets
10117
+ // Add components
9683
10118
  for( let i = 0; i < options.length; i++ )
9684
10119
  {
9685
10120
  let o = options[ i ];
@@ -9702,16 +10137,16 @@ LX.Select = Select;
9702
10137
 
9703
10138
  /**
9704
10139
  * @class Curve
9705
- * @description Curve Widget
10140
+ * @description Curve Component
9706
10141
  */
9707
10142
 
9708
- class Curve extends Widget {
10143
+ class Curve extends BaseComponent {
9709
10144
 
9710
10145
  constructor( name, values, callback, options = {} ) {
9711
10146
 
9712
10147
  let defaultValues = JSON.parse( JSON.stringify( values ) );
9713
10148
 
9714
- super( Widget.CURVE, name, defaultValues, options );
10149
+ super( BaseComponent.CURVE, name, defaultValues, options );
9715
10150
 
9716
10151
  this.onGetValue = () => {
9717
10152
  return JSON.parse(JSON.stringify( curveInstance.element.value ));
@@ -9763,16 +10198,16 @@ LX.Curve = Curve;
9763
10198
 
9764
10199
  /**
9765
10200
  * @class Dial
9766
- * @description Dial Widget
10201
+ * @description Dial Component
9767
10202
  */
9768
10203
 
9769
- class Dial extends Widget {
10204
+ class Dial extends BaseComponent {
9770
10205
 
9771
10206
  constructor( name, values, callback, options = {} ) {
9772
10207
 
9773
10208
  let defaultValues = JSON.parse( JSON.stringify( values ) );
9774
10209
 
9775
- super( Widget.DIAL, name, defaultValues, options );
10210
+ super( BaseComponent.DIAL, name, defaultValues, options );
9776
10211
 
9777
10212
  this.onGetValue = () => {
9778
10213
  return JSON.parse( JSON.stringify( dialInstance.element.value ) );
@@ -9820,14 +10255,14 @@ LX.Dial = Dial;
9820
10255
 
9821
10256
  /**
9822
10257
  * @class Layers
9823
- * @description Layers Widget
10258
+ * @description Layers Component
9824
10259
  */
9825
10260
 
9826
- class Layers extends Widget {
10261
+ class Layers extends BaseComponent {
9827
10262
 
9828
10263
  constructor( name, value, callback, options = {} ) {
9829
10264
 
9830
- super( Widget.LAYERS, name, value, options );
10265
+ super( BaseComponent.LAYERS, name, value, options );
9831
10266
 
9832
10267
  this.onGetValue = () => {
9833
10268
  return value;
@@ -9904,16 +10339,16 @@ LX.Layers = Layers;
9904
10339
 
9905
10340
  /**
9906
10341
  * @class ItemArray
9907
- * @description ItemArray Widget
10342
+ * @description ItemArray Component
9908
10343
  */
9909
10344
 
9910
- class ItemArray extends Widget {
10345
+ class ItemArray extends BaseComponent {
9911
10346
 
9912
10347
  constructor( name, values = [], callback, options = {} ) {
9913
10348
 
9914
10349
  options.nameWidth = "100%";
9915
10350
 
9916
- super( Widget.ARRAY, name, null, options );
10351
+ super( BaseComponent.ARRAY, name, null, options );
9917
10352
 
9918
10353
  this.onGetValue = () => {
9919
10354
  return values;
@@ -9968,41 +10403,41 @@ class ItemArray extends Widget {
9968
10403
  {
9969
10404
  const value = values[ i ];
9970
10405
  let baseclass = options.innerValues ? 'select' : value.constructor;
9971
- let widget = null;
10406
+ let component = null;
9972
10407
 
9973
10408
  switch( baseclass )
9974
10409
  {
9975
10410
  case String:
9976
- widget = new LX.TextInput(i + "", value, function(value, event) {
10411
+ component = new LX.TextInput(i + "", value, function(value, event) {
9977
10412
  values[ i ] = value;
9978
10413
  callback( values );
9979
10414
  }, { nameWidth: "12px", className: "p-0", skipReset: true });
9980
10415
  break;
9981
10416
  case Number:
9982
- widget = new NumberInput(i + "", value, function(value, event) {
10417
+ component = new NumberInput(i + "", value, function(value, event) {
9983
10418
  values[ i ] = value;
9984
10419
  callback( values );
9985
10420
  }, { nameWidth: "12px", className: "p-0", skipReset: true });
9986
10421
  break;
9987
10422
  case 'select':
9988
- widget = new Select(i + "", options.innerValues, value, function(value, event) {
10423
+ component = new Select(i + "", options.innerValues, value, function(value, event) {
9989
10424
  values[ i ] = value;
9990
10425
  callback( values );
9991
10426
  }, { nameWidth: "12px", className: "p-0", skipReset: true });
9992
10427
  break;
9993
10428
  }
9994
10429
 
9995
- console.assert( widget, `Value of type ${ baseclass } cannot be modified in ItemArray` );
10430
+ console.assert( component, `Value of type ${ baseclass } cannot be modified in ItemArray` );
9996
10431
 
9997
- arrayItems.appendChild( widget.root );
10432
+ arrayItems.appendChild( component.root );
9998
10433
 
9999
- const removeWidget = new LX.Button( null, "", ( v, event) => {
10434
+ const removeComponent = new LX.Button( null, "", ( v, event) => {
10000
10435
  values.splice( values.indexOf( value ), 1 );
10001
10436
  this._updateItems();
10002
10437
  this._trigger( new LX.IEvent(name, values, event), callback );
10003
10438
  }, { title: "Remove item", icon: "Trash3"} );
10004
10439
 
10005
- widget.root.appendChild( removeWidget.root );
10440
+ component.root.appendChild( removeComponent.root );
10006
10441
  }
10007
10442
 
10008
10443
  const addButton = new LX.Button(null, LX.makeIcon( "Plus", { svgClass: "sm" } ).innerHTML + "Add item", (v, event) => {
@@ -10022,14 +10457,14 @@ LX.ItemArray = ItemArray;
10022
10457
 
10023
10458
  /**
10024
10459
  * @class List
10025
- * @description List Widget
10460
+ * @description List Component
10026
10461
  */
10027
10462
 
10028
- class List extends Widget {
10463
+ class List extends BaseComponent {
10029
10464
 
10030
10465
  constructor( name, values, value, callback, options = {} ) {
10031
10466
 
10032
- super( Widget.LIST, name, value, options );
10467
+ super( BaseComponent.LIST, name, value, options );
10033
10468
 
10034
10469
  this.onGetValue = () => {
10035
10470
  return value;
@@ -10122,17 +10557,17 @@ LX.List = List;
10122
10557
 
10123
10558
  /**
10124
10559
  * @class Tags
10125
- * @description Tags Widget
10560
+ * @description Tags Component
10126
10561
  */
10127
10562
 
10128
- class Tags extends Widget {
10563
+ class Tags extends BaseComponent {
10129
10564
 
10130
10565
  constructor( name, value, callback, options = {} ) {
10131
10566
 
10132
10567
  value = value.replace( /\s/g, '' ).split( ',' );
10133
10568
 
10134
10569
  let defaultValue = [].concat( value );
10135
- super( Widget.TAGS, name, defaultValue, options );
10570
+ super( BaseComponent.TAGS, name, defaultValue, options );
10136
10571
 
10137
10572
  this.onGetValue = () => {
10138
10573
  return [].concat( value );
@@ -10211,19 +10646,19 @@ LX.Tags = Tags;
10211
10646
 
10212
10647
  /**
10213
10648
  * @class Checkbox
10214
- * @description Checkbox Widget
10649
+ * @description Checkbox Component
10215
10650
  */
10216
10651
 
10217
- class Checkbox extends Widget {
10652
+ class Checkbox extends BaseComponent {
10218
10653
 
10219
10654
  constructor( name, value, callback, options = {} ) {
10220
10655
 
10221
10656
  if( !name && !options.label )
10222
10657
  {
10223
- throw( "Set Widget Name or at least a label!" );
10658
+ throw( "Set Component Name or at least a label!" );
10224
10659
  }
10225
10660
 
10226
- super( Widget.CHECKBOX, name, value, options );
10661
+ super( BaseComponent.CHECKBOX, name, value, options );
10227
10662
 
10228
10663
  this.onGetValue = () => {
10229
10664
  return value;
@@ -10294,19 +10729,19 @@ LX.Checkbox = Checkbox;
10294
10729
 
10295
10730
  /**
10296
10731
  * @class Toggle
10297
- * @description Toggle Widget
10732
+ * @description Toggle Component
10298
10733
  */
10299
10734
 
10300
- class Toggle extends Widget {
10735
+ class Toggle extends BaseComponent {
10301
10736
 
10302
10737
  constructor( name, value, callback, options = {} ) {
10303
10738
 
10304
10739
  if( !name && !options.label )
10305
10740
  {
10306
- throw( "Set Widget Name or at least a label!" );
10741
+ throw( "Set Component Name or at least a label!" );
10307
10742
  }
10308
10743
 
10309
- super( Widget.TOGGLE, name, value, options );
10744
+ super( BaseComponent.TOGGLE, name, value, options );
10310
10745
 
10311
10746
  this.onGetValue = () => {
10312
10747
  return toggle.checked;
@@ -10378,14 +10813,14 @@ LX.Toggle = Toggle;
10378
10813
 
10379
10814
  /**
10380
10815
  * @class RadioGroup
10381
- * @description RadioGroup Widget
10816
+ * @description RadioGroup Component
10382
10817
  */
10383
10818
 
10384
- class RadioGroup extends Widget {
10819
+ class RadioGroup extends BaseComponent {
10385
10820
 
10386
10821
  constructor( name, label, values, callback, options = {} ) {
10387
10822
 
10388
- super( Widget.RADIO, name, null, options );
10823
+ super( BaseComponent.RADIO, name, null, options );
10389
10824
 
10390
10825
  let currentIndex = null;
10391
10826
 
@@ -10457,10 +10892,10 @@ LX.RadioGroup = RadioGroup;
10457
10892
 
10458
10893
  /**
10459
10894
  * @class ColorInput
10460
- * @description ColorInput Widget
10895
+ * @description ColorInput Component
10461
10896
  */
10462
10897
 
10463
- class ColorInput extends Widget {
10898
+ class ColorInput extends BaseComponent {
10464
10899
 
10465
10900
  constructor( name, value, callback, options = {} ) {
10466
10901
 
@@ -10469,12 +10904,12 @@ class ColorInput extends Widget {
10469
10904
  const useAlpha = options.useAlpha ??
10470
10905
  ( ( value.constructor === Object && 'a' in value ) || ( value.constructor === String && [ 5, 9 ].includes( value.length ) ) );
10471
10906
 
10472
- const widgetColor = new LX.Color( value );
10907
+ const componentColor = new LX.Color( value );
10473
10908
 
10474
10909
  // Force always hex internally
10475
- value = useAlpha ? widgetColor.hex : widgetColor.hex.substr( 0, 7 );
10910
+ value = useAlpha ? componentColor.hex : componentColor.hex.substr( 0, 7 );
10476
10911
 
10477
- super( Widget.COLOR, name, value, options );
10912
+ super( BaseComponent.COLOR, name, value, options );
10478
10913
 
10479
10914
  this.onGetValue = () => {
10480
10915
  const currentColor = new LX.Color( value );
@@ -10494,7 +10929,7 @@ class ColorInput extends Widget {
10494
10929
 
10495
10930
  if( !this._skipTextUpdate )
10496
10931
  {
10497
- textWidget.set( value, true, event );
10932
+ textComponent.set( value, true, event );
10498
10933
  }
10499
10934
 
10500
10935
  if( !skipCallback )
@@ -10562,15 +10997,15 @@ class ColorInput extends Widget {
10562
10997
  colorSampleRGB.style.width = "18px";
10563
10998
  }
10564
10999
 
10565
- const textWidget = new LX.TextInput( null, value, v => {
11000
+ const textComponent = new LX.TextInput( null, value, v => {
10566
11001
  this._skipTextUpdate = true;
10567
11002
  this.set( v );
10568
11003
  delete this._skipTextUpdate;
10569
11004
  this.picker.fromHexColor( v );
10570
11005
  }, { width: "calc( 100% - 24px )", disabled: options.disabled });
10571
11006
 
10572
- textWidget.root.style.marginLeft = "6px";
10573
- container.appendChild( textWidget.root );
11007
+ textComponent.root.style.marginLeft = "6px";
11008
+ container.appendChild( textComponent.root );
10574
11009
 
10575
11010
  LX.doAsync( this.onResize.bind( this ) );
10576
11011
  }
@@ -10580,14 +11015,14 @@ LX.ColorInput = ColorInput;
10580
11015
 
10581
11016
  /**
10582
11017
  * @class RangeInput
10583
- * @description RangeInput Widget
11018
+ * @description RangeInput Component
10584
11019
  */
10585
11020
 
10586
- class RangeInput extends Widget {
11021
+ class RangeInput extends BaseComponent {
10587
11022
 
10588
11023
  constructor( name, value, callback, options = {} ) {
10589
11024
 
10590
- super( Widget.RANGE, name, value, options );
11025
+ super( BaseComponent.RANGE, name, value, options );
10591
11026
 
10592
11027
  this.onGetValue = () => {
10593
11028
  return value;
@@ -10671,7 +11106,7 @@ class RangeInput extends Widget {
10671
11106
  slider.min = newMin ?? slider.min;
10672
11107
  slider.max = newMax ?? slider.max;
10673
11108
  slider.step = newStep ?? slider.step;
10674
- Widget._dispatchEvent( slider, "input", true );
11109
+ BaseComponent._dispatchEvent( slider, "input", true );
10675
11110
  };
10676
11111
 
10677
11112
  LX.doAsync( this.onResize.bind( this ) );
@@ -10682,14 +11117,14 @@ LX.RangeInput = RangeInput;
10682
11117
 
10683
11118
  /**
10684
11119
  * @class NumberInput
10685
- * @description NumberInput Widget
11120
+ * @description NumberInput Component
10686
11121
  */
10687
11122
 
10688
- class NumberInput extends Widget {
11123
+ class NumberInput extends BaseComponent {
10689
11124
 
10690
11125
  constructor( name, value, callback, options = {} ) {
10691
11126
 
10692
- super( Widget.NUMBER, name, value, options );
11127
+ super( BaseComponent.NUMBER, name, value, options );
10693
11128
 
10694
11129
  this.onGetValue = () => {
10695
11130
  return value;
@@ -10766,7 +11201,7 @@ class NumberInput extends Widget {
10766
11201
  // Add slider below
10767
11202
  if( !options.skipSlider && options.min !== undefined && options.max !== undefined )
10768
11203
  {
10769
- let sliderBox = LX.makeContainer( [ "100%", "auto" ], "", "", box );
11204
+ let sliderBox = LX.makeContainer( [ "100%", "auto" ], "z-1 input-box", "", box );
10770
11205
  let slider = document.createElement( 'input' );
10771
11206
  slider.className = "lexinputslider";
10772
11207
  slider.min = options.min;
@@ -10904,17 +11339,17 @@ LX.NumberInput = NumberInput;
10904
11339
 
10905
11340
  /**
10906
11341
  * @class Vector
10907
- * @description Vector Widget
11342
+ * @description Vector Component
10908
11343
  */
10909
11344
 
10910
- class Vector extends Widget {
11345
+ class Vector extends BaseComponent {
10911
11346
 
10912
11347
  constructor( numComponents, name, value, callback, options = {} ) {
10913
11348
 
10914
11349
  numComponents = LX.clamp( numComponents, 2, 4 );
10915
11350
  value = value ?? new Array( numComponents ).fill( 0 );
10916
11351
 
10917
- super( Widget.VECTOR, name, [].concat( value ), options );
11352
+ super( BaseComponent.VECTOR, name, [].concat( value ), options );
10918
11353
 
10919
11354
  this.onGetValue = () => {
10920
11355
  let inputs = this.root.querySelectorAll( "input[type='number']" );
@@ -11009,13 +11444,13 @@ class Vector extends Widget {
11009
11444
  for( let v of that.querySelectorAll(".vecinput") )
11010
11445
  {
11011
11446
  v.value = LX.round( +v.valueAsNumber - mult * ( e.deltaY > 0 ? 1 : -1 ), options.precision );
11012
- Widget._dispatchEvent( v, "change" );
11447
+ BaseComponent._dispatchEvent( v, "change" );
11013
11448
  }
11014
11449
  }
11015
11450
  else
11016
11451
  {
11017
11452
  this.value = LX.round( +this.valueAsNumber - mult * ( e.deltaY > 0 ? 1 : -1 ), options.precision );
11018
- Widget._dispatchEvent( vecinput, "change" );
11453
+ BaseComponent._dispatchEvent( vecinput, "change" );
11019
11454
  }
11020
11455
  }, { passive: false } );
11021
11456
 
@@ -11089,13 +11524,13 @@ class Vector extends Widget {
11089
11524
  for( let v of that.root.querySelectorAll( ".vecinput" ) )
11090
11525
  {
11091
11526
  v.value = LX.round( +v.valueAsNumber + mult * dt, options.precision );
11092
- Widget._dispatchEvent( v, "change" );
11527
+ BaseComponent._dispatchEvent( v, "change" );
11093
11528
  }
11094
11529
  }
11095
11530
  else
11096
11531
  {
11097
11532
  vecinput.value = LX.round( +vecinput.valueAsNumber + mult * dt, options.precision );
11098
- Widget._dispatchEvent( vecinput, "change" );
11533
+ BaseComponent._dispatchEvent( vecinput, "change" );
11099
11534
  }
11100
11535
  }
11101
11536
 
@@ -11154,14 +11589,14 @@ LX.Vector = Vector;
11154
11589
 
11155
11590
  /**
11156
11591
  * @class SizeInput
11157
- * @description SizeInput Widget
11592
+ * @description SizeInput Component
11158
11593
  */
11159
11594
 
11160
- class SizeInput extends Widget {
11595
+ class SizeInput extends BaseComponent {
11161
11596
 
11162
11597
  constructor( name, value, callback, options = {} ) {
11163
11598
 
11164
- super( Widget.SIZE, name, value, options );
11599
+ super( BaseComponent.SIZE, name, value, options );
11165
11600
 
11166
11601
  this.onGetValue = () => {
11167
11602
  const value = [];
@@ -11242,10 +11677,10 @@ LX.SizeInput = SizeInput;
11242
11677
 
11243
11678
  /**
11244
11679
  * @class OTPInput
11245
- * @description OTPInput Widget
11680
+ * @description OTPInput Component
11246
11681
  */
11247
11682
 
11248
- class OTPInput extends Widget {
11683
+ class OTPInput extends BaseComponent {
11249
11684
 
11250
11685
  constructor( name, value, callback, options = {} ) {
11251
11686
 
@@ -11258,7 +11693,7 @@ class OTPInput extends Widget {
11258
11693
  value = "x".repeat( patternSize );
11259
11694
  }
11260
11695
 
11261
- super( Widget.OTP, name, value, options );
11696
+ super( BaseComponent.OTP, name, value, options );
11262
11697
 
11263
11698
  this.onGetValue = () => {
11264
11699
  return +value;
@@ -11400,14 +11835,14 @@ LX.OTPInput = OTPInput;
11400
11835
 
11401
11836
  /**
11402
11837
  * @class Pad
11403
- * @description Pad Widget
11838
+ * @description Pad Component
11404
11839
  */
11405
11840
 
11406
- class Pad extends Widget {
11841
+ class Pad extends BaseComponent {
11407
11842
 
11408
11843
  constructor( name, value, callback, options = {} ) {
11409
11844
 
11410
- super( Widget.PAD, name, null, options );
11845
+ super( BaseComponent.PAD, name, null, options );
11411
11846
 
11412
11847
  this.onGetValue = () => {
11413
11848
  return thumb.value.xy;
@@ -11520,14 +11955,14 @@ LX.Pad = Pad;
11520
11955
 
11521
11956
  /**
11522
11957
  * @class Progress
11523
- * @description Progress Widget
11958
+ * @description Progress Component
11524
11959
  */
11525
11960
 
11526
- class Progress extends Widget {
11961
+ class Progress extends BaseComponent {
11527
11962
 
11528
11963
  constructor( name, value, options = {} ) {
11529
11964
 
11530
- super( Widget.PROGRESS, name, value, options );
11965
+ super( BaseComponent.PROGRESS, name, value, options );
11531
11966
 
11532
11967
  this.onGetValue = () => {
11533
11968
  return progress.value;
@@ -11657,14 +12092,14 @@ LX.Progress = Progress;
11657
12092
 
11658
12093
  /**
11659
12094
  * @class FileInput
11660
- * @description FileInput Widget
12095
+ * @description FileInput Component
11661
12096
  */
11662
12097
 
11663
- class FileInput extends Widget {
12098
+ class FileInput extends BaseComponent {
11664
12099
 
11665
12100
  constructor( name, callback, options = { } ) {
11666
12101
 
11667
- super( Widget.FILE, name, null, options );
12102
+ super( BaseComponent.FILE, name, null, options );
11668
12103
 
11669
12104
  let local = options.local ?? true;
11670
12105
  let type = options.type ?? 'text';
@@ -11742,16 +12177,16 @@ LX.FileInput = FileInput;
11742
12177
 
11743
12178
  /**
11744
12179
  * @class Tree
11745
- * @description Tree Widget
12180
+ * @description Tree Component
11746
12181
  */
11747
12182
 
11748
- class Tree extends Widget {
12183
+ class Tree extends BaseComponent {
11749
12184
 
11750
12185
  constructor( name, data, options = {} ) {
11751
12186
 
11752
12187
  options.hideName = true;
11753
12188
 
11754
- super( Widget.TREE, name, null, options );
12189
+ super( BaseComponent.TREE, name, null, options );
11755
12190
 
11756
12191
  let container = document.createElement('div');
11757
12192
  container.className = "lextree";
@@ -11824,16 +12259,16 @@ LX.Tree = Tree;
11824
12259
 
11825
12260
  /**
11826
12261
  * @class TabSections
11827
- * @description TabSections Widget
12262
+ * @description TabSections Component
11828
12263
  */
11829
12264
 
11830
- class TabSections extends Widget {
12265
+ class TabSections extends BaseComponent {
11831
12266
 
11832
12267
  constructor( name, tabs, options = {} ) {
11833
12268
 
11834
12269
  options.hideName = true;
11835
12270
 
11836
- super( Widget.TABS, name, null, options );
12271
+ super( BaseComponent.TABS, name, null, options );
11837
12272
 
11838
12273
  if( tabs.constructor != Array )
11839
12274
  {
@@ -11879,7 +12314,7 @@ class TabSections extends Widget {
11879
12314
 
11880
12315
  let infoContainer = document.createElement( "div" );
11881
12316
  infoContainer.id = tab.name.replace( /\s/g, '' );
11882
- infoContainer.className = "widgets";
12317
+ infoContainer.className = "components";
11883
12318
  infoContainer.toggleAttribute( "hidden", !( tab.selected ?? false ) );
11884
12319
  container.appendChild( infoContainer );
11885
12320
 
@@ -11888,7 +12323,7 @@ class TabSections extends Widget {
11888
12323
  tabContainer.querySelectorAll( ".lextab" ).forEach( e => { e.classList.remove( "selected" ); } );
11889
12324
  tabEl.classList.add( "selected" );
11890
12325
  // Hide all tabs content
11891
- container.querySelectorAll(".widgets").forEach( e => { e.toggleAttribute( "hidden", true ); } );
12326
+ container.querySelectorAll(".components").forEach( e => { e.toggleAttribute( "hidden", true ); } );
11892
12327
  // Show tab content
11893
12328
  const el = container.querySelector( '#' + infoContainer.id );
11894
12329
  el.toggleAttribute( "hidden" );
@@ -11931,14 +12366,14 @@ LX.TabSections = TabSections;
11931
12366
 
11932
12367
  /**
11933
12368
  * @class Counter
11934
- * @description Counter Widget
12369
+ * @description Counter Component
11935
12370
  */
11936
12371
 
11937
- class Counter extends Widget {
12372
+ class Counter extends BaseComponent {
11938
12373
 
11939
12374
  constructor( name, value, callback, options = { } ) {
11940
12375
 
11941
- super( Widget.COUNTER, name, value, options );
12376
+ super( BaseComponent.COUNTER, name, value, options );
11942
12377
 
11943
12378
  this.onGetValue = () => {
11944
12379
  return counterText.count;
@@ -12001,10 +12436,10 @@ LX.Counter = Counter;
12001
12436
 
12002
12437
  /**
12003
12438
  * @class Table
12004
- * @description Table Widget
12439
+ * @description Table Component
12005
12440
  */
12006
12441
 
12007
- class Table extends Widget {
12442
+ class Table extends BaseComponent {
12008
12443
 
12009
12444
  constructor( name, data, options = { } ) {
12010
12445
 
@@ -12013,7 +12448,7 @@ class Table extends Widget {
12013
12448
  throw( "Data is needed to create a table!" );
12014
12449
  }
12015
12450
 
12016
- super( Widget.TABLE, name, null, options );
12451
+ super( BaseComponent.TABLE, name, null, options );
12017
12452
 
12018
12453
  this.onResize = ( rect ) => {
12019
12454
  const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
@@ -12063,7 +12498,7 @@ class Table extends Widget {
12063
12498
  {
12064
12499
  const filterOptions = LX.deepCopy( options );
12065
12500
  filterOptions.placeholder = `Filter ${ this.filter }...`;
12066
- filterOptions.skipWidget = true;
12501
+ filterOptions.skipComponent = true;
12067
12502
  filterOptions.trigger = "input";
12068
12503
  filterOptions.inputClass = "outline";
12069
12504
 
@@ -12082,9 +12517,9 @@ class Table extends Widget {
12082
12517
 
12083
12518
  for( let f of this.customFilters )
12084
12519
  {
12085
- f.widget = new LX.Button(null, icon.innerHTML + f.name, ( v ) => {
12520
+ f.component = new LX.Button(null, icon.innerHTML + f.name, ( v ) => {
12086
12521
 
12087
- const spanName = f.widget.root.querySelector( "span" );
12522
+ const spanName = f.component.root.querySelector( "span" );
12088
12523
 
12089
12524
  if( f.options )
12090
12525
  {
@@ -12105,7 +12540,7 @@ class Table extends Widget {
12105
12540
  };
12106
12541
  return item;
12107
12542
  } );
12108
- new LX.DropdownMenu( f.widget.root, menuOptions, { side: "bottom", align: "start" });
12543
+ new LX.DropdownMenu( f.component.root, menuOptions, { side: "bottom", align: "start" });
12109
12544
  }
12110
12545
  else if( f.type == "range" )
12111
12546
  {
@@ -12124,12 +12559,14 @@ class Table extends Widget {
12124
12559
  f.start = v;
12125
12560
  const inUse = ( f.start != f.min || f.end != f.max );
12126
12561
  spanName.innerHTML = icon.innerHTML + f.name + ( inUse ? separatorHtml + LX.badge( `${ f.start } - ${ f.end } ${ f.units ?? "" }`, "bg-tertiary fg-secondary text-sm border-0" ) : "" );
12562
+ if( inUse ) this._resetCustomFiltersBtn.root.classList.remove( "hidden" );
12127
12563
  this.refresh();
12128
12564
  }, { skipSlider: true, min: f.min, max: f.max, step: f.step, units: f.units } );
12129
12565
  panel.addNumber( null, f.end, (v) => {
12130
12566
  f.end = v;
12131
12567
  const inUse = ( f.start != f.min || f.end != f.max );
12132
12568
  spanName.innerHTML = icon.innerHTML + f.name + ( inUse ? separatorHtml + LX.badge( `${ f.start } - ${ f.end } ${ f.units ?? "" }`, "bg-tertiary fg-secondary text-sm border-0" ) : "" );
12569
+ if( inUse ) this._resetCustomFiltersBtn.root.classList.remove( "hidden" );
12133
12570
  this.refresh();
12134
12571
  }, { skipSlider: true, min: f.min, max: f.max, step: f.step, units: f.units } );
12135
12572
  panel.addButton( null, "Reset", () => {
@@ -12142,11 +12579,43 @@ class Table extends Widget {
12142
12579
  };
12143
12580
  panel.refresh();
12144
12581
  container.appendChild( panel.root );
12145
- new LX.Popover( f.widget.root, [ container ], { side: "bottom" } );
12582
+ new LX.Popover( f.component.root, [ container ], { side: "bottom" } );
12583
+ }
12584
+ else if( f.type == "date" )
12585
+ {
12586
+ const container = LX.makeContainer( ["auto", "auto"], "text-md" );
12587
+ const panel = new LX.Panel();
12588
+ LX.makeContainer( ["100%", "auto"], "px-3 p-2 pb-0 text-md font-medium", f.name, container );
12589
+
12590
+ panel.refresh = () => {
12591
+ panel.clear();
12592
+
12593
+ // Generate default value once the filter is used
12594
+ if( !f.default )
12595
+ {
12596
+ const date = new Date();
12597
+ const todayStringDate = `${ date.getDate() }/${ date.getMonth() + 1 }/${ date.getFullYear() }`;
12598
+ f.default = [ todayStringDate, todayStringDate ];
12599
+ }
12600
+
12601
+ const calendar = new LX.CalendarRange( f.value, {
12602
+ onChange: ( dateRange ) => {
12603
+ f.value = dateRange;
12604
+ spanName.innerHTML = icon.innerHTML + f.name + ( separatorHtml + LX.badge( `${ calendar.getFullDate() }`, "bg-tertiary fg-secondary text-sm border-0" ) );
12605
+ this._resetCustomFiltersBtn.root.classList.remove( "hidden" );
12606
+ this.refresh();
12607
+ }
12608
+ });
12609
+
12610
+ panel.attach( calendar );
12611
+ };
12612
+ panel.refresh();
12613
+ container.appendChild( panel.root );
12614
+ new LX.Popover( f.component.root, [ container ], { side: "bottom" } );
12146
12615
  }
12147
12616
 
12148
12617
  }, { buttonClass: "px-2 primary dashed" } );
12149
- headerContainer.appendChild( f.widget.root );
12618
+ headerContainer.appendChild( f.component.root );
12150
12619
  }
12151
12620
 
12152
12621
  this._resetCustomFiltersBtn = new LX.Button(null, "resetButton", ( v ) => {
@@ -12154,12 +12623,16 @@ class Table extends Widget {
12154
12623
  this._resetCustomFiltersBtn.root.classList.add( "hidden" );
12155
12624
  for( let f of this.customFilters )
12156
12625
  {
12157
- f.widget.root.querySelector( "span" ).innerHTML = ( icon.innerHTML + f.name );
12626
+ f.component.root.querySelector( "span" ).innerHTML = ( icon.innerHTML + f.name );
12158
12627
  if( f.type == "range" )
12159
12628
  {
12160
12629
  f.start = f.min;
12161
12630
  f.end = f.max;
12162
12631
  }
12632
+ else if( f.type == "date" )
12633
+ {
12634
+ delete f.default;
12635
+ }
12163
12636
  }
12164
12637
  this.refresh();
12165
12638
  }, { title: "Reset filters", tooltip: true, icon: "X" } );
@@ -12441,7 +12914,30 @@ class Table extends Widget {
12441
12914
  }
12442
12915
  }
12443
12916
  else if( f.type == "date" )
12444
- ;
12917
+ {
12918
+ acfMap[ acfName ] = acfMap[ acfName ] ?? false;
12919
+
12920
+ const filterColIndex = data.head.indexOf( acfName );
12921
+ if( filterColIndex > -1 )
12922
+ {
12923
+ if( !f.default )
12924
+ {
12925
+ const date = new Date();
12926
+ const todayStringDate = `${ date.getDate() }/${ date.getMonth() + 1 }/${ date.getFullYear() }`;
12927
+ f.value = [ todayStringDate, todayStringDate ];
12928
+ acfMap[ acfName ] |= true;
12929
+ continue;
12930
+ }
12931
+
12932
+ f.value = f.value ?? f.default;
12933
+
12934
+ const dateString = bodyData[ filterColIndex ];
12935
+ const date = LX.dateFromDateString( dateString );
12936
+ const minDate = LX.dateFromDateString( f.value[ 0 ] );
12937
+ const maxDate = LX.dateFromDateString( f.value[ 1 ] );
12938
+ acfMap[ acfName ] |= ( date >= minDate ) && ( date <= maxDate );
12939
+ }
12940
+ }
12445
12941
  }
12446
12942
 
12447
12943
  const show = Object.values( acfMap ).reduce( ( e, acc ) => acc *= e, true );
@@ -12686,14 +13182,14 @@ LX.Table = Table;
12686
13182
 
12687
13183
  /**
12688
13184
  * @class DatePicker
12689
- * @description DatePicker Widget
13185
+ * @description DatePicker Component
12690
13186
  */
12691
13187
 
12692
- class DatePicker extends Widget {
13188
+ class DatePicker extends BaseComponent {
12693
13189
 
12694
13190
  constructor( name, dateValue, callback, options = { } ) {
12695
13191
 
12696
- super( Widget.DATE, name, null, options );
13192
+ super( BaseComponent.DATE, name, null, options );
12697
13193
 
12698
13194
  const dateAsRange = ( dateValue?.constructor === Array );
12699
13195
 
@@ -12766,10 +13262,6 @@ class DatePicker extends Widget {
12766
13262
  const calendarIcon = LX.makeIcon( "Calendar" );
12767
13263
  const calendarButton = new LX.Button( null, d0, () => {
12768
13264
  this._popover = new LX.Popover( calendarButton.root, [ this.calendar ] );
12769
- if( dateAsRange )
12770
- {
12771
- Object.assign( this._popover.root.style, { display: "flex", width: "auto" } );
12772
- }
12773
13265
  }, { buttonClass: `flex flex-row px-3 ${ emptyDate ? "" : "fg-tertiary" } justify-between` } );
12774
13266
  calendarButton.root.querySelector( "button" ).appendChild( calendarIcon );
12775
13267
  calendarButton.root.style.width = "100%";
@@ -12784,10 +13276,6 @@ class DatePicker extends Widget {
12784
13276
  const calendarIcon = LX.makeIcon( "Calendar" );
12785
13277
  const calendarButton = new LX.Button( null, d1, () => {
12786
13278
  this._popover = new LX.Popover( calendarButton.root, [ this.calendar ] );
12787
- if( dateAsRange )
12788
- {
12789
- Object.assign( this._popover.root.style, { display: "flex", width: "auto" } );
12790
- }
12791
13279
  }, { buttonClass: `flex flex-row px-3 ${ emptyDate ? "" : "fg-tertiary" } justify-between` } );
12792
13280
  calendarButton.root.querySelector( "button" ).appendChild( calendarIcon );
12793
13281
  calendarButton.root.style.width = "100%";
@@ -12812,14 +13300,14 @@ LX.DatePicker = DatePicker;
12812
13300
 
12813
13301
  /**
12814
13302
  * @class Map2D
12815
- * @description Map2D Widget
13303
+ * @description Map2D Component
12816
13304
  */
12817
13305
 
12818
- class Map2D extends Widget {
13306
+ class Map2D extends BaseComponent {
12819
13307
 
12820
13308
  constructor( name, points, callback, options = {} ) {
12821
13309
 
12822
- super( Widget.MAP2D, name, null, options );
13310
+ super( BaseComponent.MAP2D, name, null, options );
12823
13311
 
12824
13312
  this.onGetValue = () => {
12825
13313
  return this.map2d.weightsObj;
@@ -12857,6 +13345,127 @@ class Map2D extends Widget {
12857
13345
 
12858
13346
  LX.Map2D = Map2D;
12859
13347
 
13348
+ /**
13349
+ * @class Rate
13350
+ * @description Rate Component
13351
+ */
13352
+
13353
+ class Rate extends BaseComponent {
13354
+
13355
+ constructor( name, value, callback, options = {} ) {
13356
+
13357
+ const allowHalf = options.allowHalf ?? false;
13358
+
13359
+ if( !allowHalf)
13360
+ {
13361
+ value = Math.floor( value );
13362
+ }
13363
+
13364
+ super( BaseComponent.RATE, name, value, options );
13365
+
13366
+ this.onGetValue = () => {
13367
+ return value;
13368
+ };
13369
+
13370
+ this.onSetValue = ( newValue, skipCallback, event ) => {
13371
+
13372
+ value = newValue;
13373
+
13374
+ _updateStars( value );
13375
+
13376
+ if( !skipCallback )
13377
+ {
13378
+ this._trigger( new LX.IEvent( name, newValue, event ), callback );
13379
+ }
13380
+ };
13381
+
13382
+ this.onResize = ( rect ) => {
13383
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
13384
+ container.style.width = `calc( 100% - ${ realNameWidth })`;
13385
+ };
13386
+
13387
+ const container = document.createElement('div');
13388
+ container.className = "lexrate relative";
13389
+ this.root.appendChild( container );
13390
+
13391
+ const starsContainer = LX.makeContainer( ["fit-content", "auto"], "flex flex-row gap-1", "", container );
13392
+ const filledStarsContainer = LX.makeContainer( ["fit-content", "auto"], "absolute top-0 flex flex-row gap-1 pointer-events-none", "", container );
13393
+ const halfStarsContainer = LX.makeContainer( ["fit-content", "auto"], "absolute top-0 flex flex-row gap-1 pointer-events-none", "", container );
13394
+
13395
+ starsContainer.addEventListener("mousemove", e => {
13396
+ const star = e.target;
13397
+ const idx = star.dataset["idx"];
13398
+
13399
+ if( idx !== undefined )
13400
+ {
13401
+ const rect = star.getBoundingClientRect();
13402
+ const half = allowHalf && e.offsetX < ( rect.width * 0.5 );
13403
+ _updateStars( idx - ( half ? 0.5 : 0.0 ) );
13404
+ }
13405
+ }, false );
13406
+
13407
+ starsContainer.addEventListener("mouseleave", e => {
13408
+ _updateStars( value );
13409
+ }, false );
13410
+
13411
+ // Create all layers of stars
13412
+
13413
+ for( let i = 0; i < 5; ++i )
13414
+ {
13415
+ const starIcon = LX.makeIcon( "Star", { svgClass: `lg fill-current fg-secondary` } );
13416
+ starIcon.dataset["idx"] = ( i + 1 );
13417
+ starsContainer.appendChild( starIcon );
13418
+
13419
+ starIcon.addEventListener("click", e => {
13420
+ const rect = e.target.getBoundingClientRect();
13421
+ const half = allowHalf && e.offsetX < ( rect.width * 0.5 );
13422
+ this.set( parseFloat( e.target.dataset["idx"] ) - ( half ? 0.5 : 0.0 ) );
13423
+ }, false );
13424
+
13425
+ const filledStarIcon = LX.makeIcon( "Star", { svgClass: `lg fill-current metallicyellow` } );
13426
+ filledStarsContainer.appendChild( filledStarIcon );
13427
+
13428
+ const halfStarIcon = LX.makeIcon( "StarHalf", { svgClass: `lg fill-current metallicyellow` } );
13429
+ halfStarsContainer.appendChild( halfStarIcon );
13430
+ }
13431
+
13432
+ const _updateStars = ( v ) => {
13433
+
13434
+ for( let i = 0; i < 5; ++i )
13435
+ {
13436
+ const filled = ( v > ( i + 0.5 ) );
13437
+ const starIcon = filledStarsContainer.childNodes[ i ];
13438
+ const halfStarIcon = halfStarsContainer.childNodes[ i ];
13439
+ if( filled )
13440
+ {
13441
+ starIcon.style.opacity = 1;
13442
+ }
13443
+ else
13444
+ {
13445
+ starIcon.style.opacity = 0;
13446
+
13447
+ const halfFilled = allowHalf && ( v > i );
13448
+ if( halfFilled )
13449
+ {
13450
+ halfStarIcon.style.opacity = 1;
13451
+ }
13452
+ else
13453
+ {
13454
+ halfStarIcon.style.opacity = 0;
13455
+ }
13456
+
13457
+ }
13458
+ }
13459
+ };
13460
+
13461
+ _updateStars( value );
13462
+
13463
+ LX.doAsync( this.onResize.bind( this ) );
13464
+ }
13465
+ }
13466
+
13467
+ LX.Rate = Rate;
13468
+
12860
13469
  // panel.js @jxarco
12861
13470
 
12862
13471
  /**
@@ -12895,42 +13504,42 @@ class Panel {
12895
13504
 
12896
13505
  this.root = root;
12897
13506
  this.branches = [];
12898
- this.widgets = {};
13507
+ this.components = {};
12899
13508
 
12900
13509
  this._branchOpen = false;
12901
13510
  this._currentBranch = null;
12902
- this._queue = []; // Append widgets in other locations
12903
- this._inlineWidgetsLeft = -1;
12904
- this._inline_queued_container = null;
13511
+ this._queue = []; // Append components in other locations
13512
+ this._inlineComponentsLeft = -1;
13513
+ this._inlineQueuedContainer = null;
12905
13514
  }
12906
13515
 
12907
13516
  get( name ) {
12908
13517
 
12909
- return this.widgets[ name ];
13518
+ return this.components[ name ];
12910
13519
  }
12911
13520
 
12912
13521
  getValue( name ) {
12913
13522
 
12914
- let widget = this.widgets[ name ];
13523
+ let component = this.components[ name ];
12915
13524
 
12916
- if( !widget )
13525
+ if( !component )
12917
13526
  {
12918
- throw( "No widget called " + name );
13527
+ throw( "No component called " + name );
12919
13528
  }
12920
13529
 
12921
- return widget.value();
13530
+ return component.value();
12922
13531
  }
12923
13532
 
12924
13533
  setValue( name, value, skipCallback ) {
12925
13534
 
12926
- let widget = this.widgets[ name ];
13535
+ let component = this.components[ name ];
12927
13536
 
12928
- if( !widget )
13537
+ if( !component )
12929
13538
  {
12930
- throw( "No widget called " + name );
13539
+ throw( "No component called " + name );
12931
13540
  }
12932
13541
 
12933
- return widget.set( value, skipCallback );
13542
+ return component.set( value, skipCallback );
12934
13543
  }
12935
13544
 
12936
13545
  /**
@@ -12951,18 +13560,18 @@ class Panel {
12951
13560
 
12952
13561
  clear() {
12953
13562
 
12954
- this._branchOpen = false;
12955
13563
  this.branches = [];
13564
+ this._branchOpen = false;
12956
13565
  this._currentBranch = null;
12957
13566
 
12958
- for( let w in this.widgets )
13567
+ for( let w in this.components )
12959
13568
  {
12960
- if( this.widgets[ w ].options && this.widgets[ w ].options.signal )
13569
+ if( this.components[ w ].options && this.components[ w ].options.signal )
12961
13570
  {
12962
- const signal = this.widgets[ w ].options.signal;
13571
+ const signal = this.components[ w ].options.signal;
12963
13572
  for( let i = 0; i < LX.signals[signal].length; i++ )
12964
13573
  {
12965
- if( LX.signals[signal][ i ] == this.widgets[ w ] )
13574
+ if( LX.signals[signal][ i ] == this.components[ w ] )
12966
13575
  {
12967
13576
  LX.signals[signal] = [...LX.signals[signal].slice(0, i), ...LX.signals[signal].slice(i+1)];
12968
13577
  }
@@ -12974,11 +13583,11 @@ class Panel {
12974
13583
  {
12975
13584
  for( let w = 0; w < this.signals.length; w++ )
12976
13585
  {
12977
- let widget = Object.values(this.signals[ w ])[ 0 ];
12978
- let signal = widget.options.signal;
13586
+ let c = Object.values(this.signals[ w ])[ 0 ];
13587
+ let signal = c.options.signal;
12979
13588
  for( let i = 0; i < LX.signals[signal].length; i++ )
12980
13589
  {
12981
- if( LX.signals[signal][ i ] == widget )
13590
+ if( LX.signals[signal][ i ] == c )
12982
13591
  {
12983
13592
  LX.signals[signal] = [...LX.signals[signal].slice(0, i), ...LX.signals[signal].slice(i+1)];
12984
13593
  }
@@ -12986,46 +13595,46 @@ class Panel {
12986
13595
  }
12987
13596
  }
12988
13597
 
12989
- this.widgets = {};
13598
+ this.components = {};
12990
13599
  this.root.innerHTML = "";
12991
13600
  }
12992
13601
 
12993
13602
  /**
12994
13603
  * @method sameLine
12995
- * @param {Number} number Of widgets that will be placed in the same line
12996
- * @param {String} className Extra class to customize inline widgets parent container
12997
- * @description Next N widgets will be in the same line. If no number, it will inline all until calling nextLine()
13604
+ * @param {Number} numberOfComponents Of components that will be placed in the same line
13605
+ * @param {String} className Extra class to customize inline components parent container
13606
+ * @description Next N components will be in the same line. If no number, it will inline all until calling nextLine()
12998
13607
  */
12999
13608
 
13000
- sameLine( number, className ) {
13609
+ sameLine( numberOfComponents, className ) {
13001
13610
 
13002
- this._inline_queued_container = this.queuedContainer;
13003
- this._inlineWidgetsLeft = ( number ?? Infinity );
13611
+ this._inlineQueuedContainer = this.queuedContainer;
13612
+ this._inlineComponentsLeft = ( numberOfComponents ?? Infinity );
13004
13613
  this._inlineExtraClass = className ?? null;
13005
13614
  }
13006
13615
 
13007
13616
  /**
13008
13617
  * @method endLine
13009
- * @param {String} className Extra class to customize inline widgets parent container
13010
- * @description Stop inlining widgets. Use it only if the number of widgets to be inlined is NOT specified.
13618
+ * @param {String} className Extra class to customize inline components parent container
13619
+ * @description Stop inlining components. Use it only if the number of components to be inlined is NOT specified.
13011
13620
  */
13012
13621
 
13013
13622
  endLine( className ) {
13014
13623
 
13015
13624
  className = className ?? this._inlineExtraClass;
13016
13625
 
13017
- if( this._inlineWidgetsLeft == -1 )
13626
+ if( this._inlineComponentsLeft == -1 )
13018
13627
  {
13019
- console.warn("No pending widgets to be inlined!");
13628
+ console.warn("No pending components to be inlined!");
13020
13629
  return;
13021
13630
  }
13022
13631
 
13023
- this._inlineWidgetsLeft = -1;
13632
+ this._inlineComponentsLeft = -1;
13024
13633
 
13025
13634
  if( !this._inlineContainer )
13026
13635
  {
13027
13636
  this._inlineContainer = document.createElement('div');
13028
- this._inlineContainer.className = "lexinlinewidgets";
13637
+ this._inlineContainer.className = "lexinlinecomponents";
13029
13638
 
13030
13639
  if( className )
13031
13640
  {
@@ -13034,14 +13643,14 @@ class Panel {
13034
13643
  }
13035
13644
 
13036
13645
  // Push all elements single element or Array[element, container]
13037
- for( let item of this._inlineWidgets )
13646
+ for( let item of this._inlineComponents )
13038
13647
  {
13039
13648
  const isPair = ( item.constructor == Array );
13040
13649
 
13041
13650
  if( isPair )
13042
13651
  {
13043
13652
  // eg. an array, inline items appended later to
13044
- if( this._inline_queued_container )
13653
+ if( this._inlineQueuedContainer )
13045
13654
  {
13046
13655
  this._inlineContainer.appendChild( item[ 0 ] );
13047
13656
  }
@@ -13057,7 +13666,7 @@ class Panel {
13057
13666
  }
13058
13667
  }
13059
13668
 
13060
- if( !this._inline_queued_container )
13669
+ if( !this._inlineQueuedContainer )
13061
13670
  {
13062
13671
  if( this._currentBranch )
13063
13672
  {
@@ -13070,10 +13679,10 @@ class Panel {
13070
13679
  }
13071
13680
  else
13072
13681
  {
13073
- this._inline_queued_container.appendChild( this._inlineContainer );
13682
+ this._inlineQueuedContainer.appendChild( this._inlineContainer );
13074
13683
  }
13075
13684
 
13076
- delete this._inlineWidgets;
13685
+ delete this._inlineComponents;
13077
13686
  delete this._inlineContainer;
13078
13687
  delete this._inlineExtraClass;
13079
13688
  }
@@ -13086,7 +13695,7 @@ class Panel {
13086
13695
  * className: Add class to the branch
13087
13696
  * closed: Set branch collapsed/opened [false]
13088
13697
  * icon: Set branch icon (LX.ICONS)
13089
- * filter: Allow filter widgets in branch by name [false]
13698
+ * filter: Allow filter components in branch by name [false]
13090
13699
  */
13091
13700
 
13092
13701
  branch( name, options = {} ) {
@@ -13107,10 +13716,10 @@ class Panel {
13107
13716
  this.branches.push( branch );
13108
13717
  this.root.appendChild( branch.root );
13109
13718
 
13110
- // Add widget filter
13719
+ // Add component filter
13111
13720
  if( options.filter )
13112
13721
  {
13113
- this._addFilter( options.filter, { callback: this._searchWidgets.bind( this, branch.name ) } );
13722
+ this._addFilter( options.filter, { callback: this._searchComponents.bind( this, branch.name ) } );
13114
13723
  }
13115
13724
 
13116
13725
  return branch;
@@ -13126,27 +13735,27 @@ class Panel {
13126
13735
  }
13127
13736
 
13128
13737
  /*
13129
- Panel Widgets
13738
+ Panel Components
13130
13739
  */
13131
13740
 
13132
- _attachWidget( widget, options = {} ) {
13741
+ _attachComponent( component, options = {} ) {
13133
13742
 
13134
- if( widget.name != undefined )
13743
+ if( component.name != undefined )
13135
13744
  {
13136
- this.widgets[ widget.name ] = widget;
13745
+ this.components[ component.name ] = component;
13137
13746
  }
13138
13747
 
13139
- if( widget.options.signal && !widget.name )
13748
+ if( component.options.signal && !component.name )
13140
13749
  {
13141
13750
  if( !this.signals )
13142
13751
  {
13143
13752
  this.signals = [];
13144
13753
  }
13145
13754
 
13146
- this.signals.push( { [ widget.options.signal ]: widget } );
13755
+ this.signals.push( { [ component.options.signal ]: component } );
13147
13756
  }
13148
13757
 
13149
- const _insertWidget = el => {
13758
+ const _insertComponent = el => {
13150
13759
  if( options.container )
13151
13760
  {
13152
13761
  options.container.appendChild( el );
@@ -13155,9 +13764,9 @@ class Panel {
13155
13764
  {
13156
13765
  if( this._currentBranch )
13157
13766
  {
13158
- if( !options.skipWidget )
13767
+ if( !options.skipComponent )
13159
13768
  {
13160
- this._currentBranch.widgets.push( widget );
13769
+ this._currentBranch.components.push( component );
13161
13770
  }
13162
13771
  this._currentBranch.content.appendChild( el );
13163
13772
  }
@@ -13174,54 +13783,54 @@ class Panel {
13174
13783
  }
13175
13784
  };
13176
13785
 
13177
- const _storeWidget = el => {
13786
+ const _storeComponent = el => {
13178
13787
 
13179
13788
  if( !this.queuedContainer )
13180
13789
  {
13181
- this._inlineWidgets.push( el );
13790
+ this._inlineComponents.push( el );
13182
13791
  }
13183
13792
  // Append content to queued tab container
13184
13793
  else
13185
13794
  {
13186
- this._inlineWidgets.push( [ el, this.queuedContainer ] );
13795
+ this._inlineComponents.push( [ el, this.queuedContainer ] );
13187
13796
  }
13188
13797
  };
13189
13798
 
13190
- // Process inline widgets
13191
- if( this._inlineWidgetsLeft > 0 && !options.skipInlineCount )
13799
+ // Process inline components
13800
+ if( this._inlineComponentsLeft > 0 && !options.skipInlineCount )
13192
13801
  {
13193
- if( !this._inlineWidgets )
13802
+ if( !this._inlineComponents )
13194
13803
  {
13195
- this._inlineWidgets = [];
13804
+ this._inlineComponents = [];
13196
13805
  }
13197
13806
 
13198
- // Store widget and its container
13199
- _storeWidget( widget.root );
13807
+ // Store component and its container
13808
+ _storeComponent( component.root );
13200
13809
 
13201
- this._inlineWidgetsLeft--;
13810
+ this._inlineComponentsLeft--;
13202
13811
 
13203
- // Last widget
13204
- if( !this._inlineWidgetsLeft )
13812
+ // Last component
13813
+ if( !this._inlineComponentsLeft )
13205
13814
  {
13206
13815
  this.endLine();
13207
13816
  }
13208
13817
  }
13209
13818
  else
13210
13819
  {
13211
- _insertWidget( widget.root );
13820
+ _insertComponent( component.root );
13212
13821
  }
13213
13822
 
13214
- return widget;
13823
+ return component;
13215
13824
  }
13216
13825
 
13217
13826
  _addFilter( placeholder, options = {} ) {
13218
13827
 
13219
13828
  options.placeholder = placeholder.constructor == String ? placeholder : "Filter properties..";
13220
- options.skipWidget = options.skipWidget ?? true;
13829
+ options.skipComponent = options.skipComponent ?? true;
13221
13830
  options.skipInlineCount = true;
13222
13831
 
13223
- let widget = new LX.TextInput( null, null, null, options );
13224
- const element = widget.root;
13832
+ let component = new LX.TextInput( null, null, null, options );
13833
+ const element = component.root;
13225
13834
  element.className += " lexfilter";
13226
13835
 
13227
13836
  let input = document.createElement('input');
@@ -13244,7 +13853,7 @@ class Panel {
13244
13853
  return element;
13245
13854
  }
13246
13855
 
13247
- _searchWidgets( branchName, value ) {
13856
+ _searchComponents( branchName, value ) {
13248
13857
 
13249
13858
  for( let b of this.branches )
13250
13859
  {
@@ -13253,8 +13862,8 @@ class Panel {
13253
13862
  continue;
13254
13863
  }
13255
13864
 
13256
- // remove all widgets
13257
- for( let w of b.widgets )
13865
+ // remove all components
13866
+ for( let w of b.components )
13258
13867
  {
13259
13868
  if( w.domEl.classList.contains('lexfilter') )
13260
13869
  {
@@ -13268,8 +13877,8 @@ class Panel {
13268
13877
 
13269
13878
  const emptyFilter = !value.length;
13270
13879
 
13271
- // add widgets
13272
- for( let w of b.widgets )
13880
+ // add components
13881
+ for( let w of b.components )
13273
13882
  {
13274
13883
  if( !emptyFilter )
13275
13884
  {
@@ -13279,7 +13888,7 @@ class Panel {
13279
13888
  if(!name.includes(value)) continue;
13280
13889
  }
13281
13890
 
13282
- // insert filtered widget
13891
+ // insert filtered component
13283
13892
  this.queuedContainer.appendChild( w.domEl );
13284
13893
  }
13285
13894
 
@@ -13350,13 +13959,13 @@ class Panel {
13350
13959
  var element = document.createElement('div');
13351
13960
  element.className = "lexseparator";
13352
13961
 
13353
- let widget = new LX.Widget( LX.Widget.SEPARATOR );
13354
- widget.root = element;
13962
+ let component = new LX.BaseComponent( LX.BaseComponent.SEPARATOR );
13963
+ component.root = element;
13355
13964
 
13356
13965
  if( this._currentBranch )
13357
13966
  {
13358
13967
  this._currentBranch.content.appendChild( element );
13359
- this._currentBranch.widgets.push( widget );
13968
+ this._currentBranch.components.push( component );
13360
13969
  }
13361
13970
  else
13362
13971
  {
@@ -13371,8 +13980,8 @@ class Panel {
13371
13980
  */
13372
13981
 
13373
13982
  addBlank( width, height ) {
13374
- const widget = new LX.Blank( width, height );
13375
- return this._attachWidget( widget );
13983
+ const component = new LX.Blank( width, height );
13984
+ return this._attachComponent( component );
13376
13985
  }
13377
13986
 
13378
13987
  /**
@@ -13387,18 +13996,18 @@ class Panel {
13387
13996
  */
13388
13997
 
13389
13998
  addTitle( name, options = {} ) {
13390
- const widget = new LX.Title( name, options );
13391
- return this._attachWidget( widget );
13999
+ const component = new LX.Title( name, options );
14000
+ return this._attachComponent( component );
13392
14001
  }
13393
14002
 
13394
14003
  /**
13395
14004
  * @method addText
13396
- * @param {String} name Widget name
14005
+ * @param {String} name Component name
13397
14006
  * @param {String} value Text value
13398
14007
  * @param {Function} callback Callback function on change
13399
14008
  * @param {Object} options:
13400
14009
  * hideName: Don't use name as label [false]
13401
- * disabled: Make the widget disabled [false]
14010
+ * disabled: Make the component disabled [false]
13402
14011
  * required: Make the input required
13403
14012
  * placeholder: Add input placeholder
13404
14013
  * icon: Icon (if any) to append at the input start
@@ -13413,18 +14022,18 @@ class Panel {
13413
14022
  */
13414
14023
 
13415
14024
  addText( name, value, callback, options = {} ) {
13416
- const widget = new LX.TextInput( name, value, callback, options );
13417
- return this._attachWidget( widget );
14025
+ const component = new LX.TextInput( name, value, callback, options );
14026
+ return this._attachComponent( component );
13418
14027
  }
13419
14028
 
13420
14029
  /**
13421
14030
  * @method addTextArea
13422
- * @param {String} name Widget name
14031
+ * @param {String} name Component name
13423
14032
  * @param {String} value Text Area value
13424
14033
  * @param {Function} callback Callback function on change
13425
14034
  * @param {Object} options:
13426
14035
  * hideName: Don't use name as label [false]
13427
- * disabled: Make the widget disabled [false]
14036
+ * disabled: Make the component disabled [false]
13428
14037
  * placeholder: Add input placeholder
13429
14038
  * resize: Allow resize [true]
13430
14039
  * trigger: Choose onchange trigger (default, input) [default]
@@ -13435,8 +14044,8 @@ class Panel {
13435
14044
  */
13436
14045
 
13437
14046
  addTextArea( name, value, callback, options = {} ) {
13438
- const widget = new LX.TextArea( name, value, callback, options );
13439
- return this._attachWidget( widget );
14047
+ const component = new LX.TextArea( name, value, callback, options );
14048
+ return this._attachComponent( component );
13440
14049
  }
13441
14050
 
13442
14051
  /**
@@ -13448,19 +14057,19 @@ class Panel {
13448
14057
  addLabel( value, options = {} ) {
13449
14058
  options.disabled = true;
13450
14059
  options.inputClass = ( options.inputClass ?? "" ) + " nobg";
13451
- const widget = this.addText( null, value, null, options );
13452
- widget.type = LX.Widget.LABEL;
13453
- return widget;
14060
+ const component = this.addText( null, value, null, options );
14061
+ component.type = LX.BaseComponent.LABEL;
14062
+ return component;
13454
14063
  }
13455
14064
 
13456
14065
  /**
13457
14066
  * @method addButton
13458
- * @param {String} name Widget name
14067
+ * @param {String} name Component name
13459
14068
  * @param {String} value Button name
13460
14069
  * @param {Function} callback Callback function on click
13461
14070
  * @param {Object} options:
13462
14071
  * hideName: Don't use name as label [false]
13463
- * disabled: Make the widget disabled [false]
14072
+ * disabled: Make the component disabled [false]
13464
14073
  * icon: Icon class to show as button value
13465
14074
  * iconPosition: Icon position (cover|start|end)
13466
14075
  * fileInput: Button click requests a file
@@ -13472,13 +14081,13 @@ class Panel {
13472
14081
  */
13473
14082
 
13474
14083
  addButton( name, value, callback, options = {} ) {
13475
- const widget = new LX.Button( name, value, callback, options );
13476
- return this._attachWidget( widget );
14084
+ const component = new LX.Button( name, value, callback, options );
14085
+ return this._attachComponent( component );
13477
14086
  }
13478
14087
 
13479
14088
  /**
13480
14089
  * @method addComboButtons
13481
- * @param {String} name Widget name
14090
+ * @param {String} name Component name
13482
14091
  * @param {Array} values Each of the {value, callback, selected, disabled} items
13483
14092
  * @param {Object} options:
13484
14093
  * hideName: Don't use name as label [false]
@@ -13489,8 +14098,8 @@ class Panel {
13489
14098
  */
13490
14099
 
13491
14100
  addComboButtons( name, values, options = {} ) {
13492
- const widget = new LX.ComboButtons( name, values, options );
13493
- return this._attachWidget( widget );
14101
+ const component = new LX.ComboButtons( name, values, options );
14102
+ return this._attachComponent( component );
13494
14103
  }
13495
14104
 
13496
14105
  /**
@@ -13505,13 +14114,13 @@ class Panel {
13505
14114
  */
13506
14115
 
13507
14116
  addCard( name, options = {} ) {
13508
- const widget = new LX.Card( name, options );
13509
- return this._attachWidget( widget );
14117
+ const component = new LX.Card( name, options );
14118
+ return this._attachComponent( component );
13510
14119
  }
13511
14120
 
13512
14121
  /**
13513
14122
  * @method addForm
13514
- * @param {String} name Widget name
14123
+ * @param {String} name Component name
13515
14124
  * @param {Object} data Form data
13516
14125
  * @param {Function} callback Callback function on submit form
13517
14126
  * @param {Object} options:
@@ -13524,13 +14133,13 @@ class Panel {
13524
14133
  */
13525
14134
 
13526
14135
  addForm( name, data, callback, options = {} ) {
13527
- const widget = new LX.Form( name, data, callback, options );
13528
- return this._attachWidget( widget );
14136
+ const component = new LX.Form( name, data, callback, options );
14137
+ return this._attachComponent( component );
13529
14138
  }
13530
14139
 
13531
14140
  /**
13532
14141
  * @method addContent
13533
- * @param {String} name Widget name
14142
+ * @param {String} name Component name
13534
14143
  * @param {HTMLElement/String} element
13535
14144
  * @param {Object} options
13536
14145
  */
@@ -13556,15 +14165,15 @@ class Panel {
13556
14165
 
13557
14166
  options.hideName = true;
13558
14167
 
13559
- let widget = new LX.Widget( LX.Widget.CONTENT, name, null, options );
13560
- widget.root.appendChild( element );
14168
+ let component = new LX.BaseComponent( LX.BaseComponent.CONTENT, name, null, options );
14169
+ component.root.appendChild( element );
13561
14170
 
13562
- return this._attachWidget( widget );
14171
+ return this._attachComponent( component );
13563
14172
  }
13564
14173
 
13565
14174
  /**
13566
14175
  * @method addImage
13567
- * @param {String} name Widget name
14176
+ * @param {String} name Component name
13568
14177
  * @param {String} url Image Url
13569
14178
  * @param {Object} options
13570
14179
  * hideName: Don't use name as label [false]
@@ -13583,43 +14192,43 @@ class Panel {
13583
14192
  Object.assign( img.style, options.style ?? {} );
13584
14193
  container.appendChild( img );
13585
14194
 
13586
- let widget = new LX.Widget( LX.Widget.IMAGE, name, null, options );
13587
- widget.root.appendChild( container );
14195
+ let component = new LX.BaseComponent( LX.BaseComponent.IMAGE, name, null, options );
14196
+ component.root.appendChild( container );
13588
14197
 
13589
14198
  // await img.decode();
13590
14199
  img.decode();
13591
14200
 
13592
- return this._attachWidget( widget );
14201
+ return this._attachComponent( component );
13593
14202
  }
13594
14203
 
13595
14204
  /**
13596
14205
  * @method addSelect
13597
- * @param {String} name Widget name
13598
- * @param {Array} values Posible options of the select widget -> String (for default select) or Object = {value, url} (for images, gifs..)
14206
+ * @param {String} name Component name
14207
+ * @param {Array} values Posible options of the select component -> String (for default select) or Object = {value, url} (for images, gifs..)
13599
14208
  * @param {String} value Select by default option
13600
14209
  * @param {Function} callback Callback function on change
13601
14210
  * @param {Object} options:
13602
14211
  * hideName: Don't use name as label [false]
13603
- * filter: Add a search bar to the widget [false]
13604
- * disabled: Make the widget disabled [false]
14212
+ * filter: Add a search bar to the component [false]
14213
+ * disabled: Make the component disabled [false]
13605
14214
  * skipReset: Don't add the reset value button when value changes
13606
14215
  * placeholder: Placeholder for the filter input
13607
14216
  * emptyMsg: Custom message to show when no filtered results
13608
14217
  */
13609
14218
 
13610
14219
  addSelect( name, values, value, callback, options = {} ) {
13611
- const widget = new LX.Select( name, values, value, callback, options );
13612
- return this._attachWidget( widget );
14220
+ const component = new LX.Select( name, values, value, callback, options );
14221
+ return this._attachComponent( component );
13613
14222
  }
13614
14223
 
13615
14224
  /**
13616
14225
  * @method addCurve
13617
- * @param {String} name Widget name
14226
+ * @param {String} name Component name
13618
14227
  * @param {Array of Array} values Array of 2N Arrays of each value of the curve
13619
14228
  * @param {Function} callback Callback function on change
13620
14229
  * @param {Object} options:
13621
14230
  * skipReset: Don't add the reset value button when value changes
13622
- * bgColor: Widget background color
14231
+ * bgColor: Component background color
13623
14232
  * pointsColor: Curve points color
13624
14233
  * lineColor: Curve line color
13625
14234
  * noOverlap: Points do not overlap, replacing themselves if necessary
@@ -13629,18 +14238,18 @@ class Panel {
13629
14238
  */
13630
14239
 
13631
14240
  addCurve( name, values, callback, options = {} ) {
13632
- const widget = new LX.Curve( name, values, callback, options );
13633
- return this._attachWidget( widget );
14241
+ const component = new LX.Curve( name, values, callback, options );
14242
+ return this._attachComponent( component );
13634
14243
  }
13635
14244
 
13636
14245
  /**
13637
14246
  * @method addDial
13638
- * @param {String} name Widget name
14247
+ * @param {String} name Component name
13639
14248
  * @param {Array of Array} values Array of 2N Arrays of each value of the dial
13640
14249
  * @param {Function} callback Callback function on change
13641
14250
  * @param {Object} options:
13642
14251
  * skipReset: Don't add the reset value button when value changes
13643
- * bgColor: Widget background color
14252
+ * bgColor: Component background color
13644
14253
  * pointsColor: Curve points color
13645
14254
  * lineColor: Curve line color
13646
14255
  * noOverlap: Points do not overlap, replacing themselves if necessary
@@ -13650,26 +14259,26 @@ class Panel {
13650
14259
  */
13651
14260
 
13652
14261
  addDial( name, values, callback, options = {} ) {
13653
- const widget = new LX.Dial( name, values, callback, options );
13654
- return this._attachWidget( widget );
14262
+ const component = new LX.Dial( name, values, callback, options );
14263
+ return this._attachComponent( component );
13655
14264
  }
13656
14265
 
13657
14266
  /**
13658
14267
  * @method addLayers
13659
- * @param {String} name Widget name
14268
+ * @param {String} name Component name
13660
14269
  * @param {Number} value Flag value by default option
13661
14270
  * @param {Function} callback Callback function on change
13662
14271
  * @param {Object} options:
13663
14272
  */
13664
14273
 
13665
14274
  addLayers( name, value, callback, options = {} ) {
13666
- const widget = new LX.Layers( name, value, callback, options );
13667
- return this._attachWidget( widget );
14275
+ const component = new LX.Layers( name, value, callback, options );
14276
+ return this._attachComponent( component );
13668
14277
  }
13669
14278
 
13670
14279
  /**
13671
14280
  * @method addArray
13672
- * @param {String} name Widget name
14281
+ * @param {String} name Component name
13673
14282
  * @param {Array} values By default values in the array
13674
14283
  * @param {Function} callback Callback function on change
13675
14284
  * @param {Object} options:
@@ -13677,13 +14286,13 @@ class Panel {
13677
14286
  */
13678
14287
 
13679
14288
  addArray( name, values = [], callback, options = {} ) {
13680
- const widget = new LX.ItemArray( name, values, callback, options );
13681
- return this._attachWidget( widget );
14289
+ const component = new LX.ItemArray( name, values, callback, options );
14290
+ return this._attachComponent( component );
13682
14291
  }
13683
14292
 
13684
14293
  /**
13685
14294
  * @method addList
13686
- * @param {String} name Widget name
14295
+ * @param {String} name Component name
13687
14296
  * @param {Array} values List values
13688
14297
  * @param {String} value Selected list value
13689
14298
  * @param {Function} callback Callback function on change
@@ -13692,13 +14301,13 @@ class Panel {
13692
14301
  */
13693
14302
 
13694
14303
  addList( name, values, value, callback, options = {} ) {
13695
- const widget = new LX.List( name, values, value, callback, options );
13696
- return this._attachWidget( widget );
14304
+ const component = new LX.List( name, values, value, callback, options );
14305
+ return this._attachComponent( component );
13697
14306
  }
13698
14307
 
13699
14308
  /**
13700
14309
  * @method addTags
13701
- * @param {String} name Widget name
14310
+ * @param {String} name Component name
13702
14311
  * @param {String} value Comma separated tags
13703
14312
  * @param {Function} callback Callback function on change
13704
14313
  * @param {Object} options:
@@ -13706,85 +14315,85 @@ class Panel {
13706
14315
  */
13707
14316
 
13708
14317
  addTags( name, value, callback, options = {} ) {
13709
- const widget = new LX.Tags( name, value, callback, options );
13710
- return this._attachWidget( widget );
14318
+ const component = new LX.Tags( name, value, callback, options );
14319
+ return this._attachComponent( component );
13711
14320
  }
13712
14321
 
13713
14322
  /**
13714
14323
  * @method addCheckbox
13715
- * @param {String} name Widget name
14324
+ * @param {String} name Component name
13716
14325
  * @param {Boolean} value Value of the checkbox
13717
14326
  * @param {Function} callback Callback function on change
13718
14327
  * @param {Object} options:
13719
- * disabled: Make the widget disabled [false]
14328
+ * disabled: Make the component disabled [false]
13720
14329
  * label: Checkbox label
13721
- * suboptions: Callback to add widgets in case of TRUE value
14330
+ * suboptions: Callback to add components in case of TRUE value
13722
14331
  * className: Extra classes to customize style
13723
14332
  */
13724
14333
 
13725
14334
  addCheckbox( name, value, callback, options = {} ) {
13726
- const widget = new LX.Checkbox( name, value, callback, options );
13727
- return this._attachWidget( widget );
14335
+ const component = new LX.Checkbox( name, value, callback, options );
14336
+ return this._attachComponent( component );
13728
14337
  }
13729
14338
 
13730
14339
  /**
13731
14340
  * @method addToggle
13732
- * @param {String} name Widget name
14341
+ * @param {String} name Component name
13733
14342
  * @param {Boolean} value Value of the checkbox
13734
14343
  * @param {Function} callback Callback function on change
13735
14344
  * @param {Object} options:
13736
- * disabled: Make the widget disabled [false]
14345
+ * disabled: Make the component disabled [false]
13737
14346
  * label: Toggle label
13738
- * suboptions: Callback to add widgets in case of TRUE value
14347
+ * suboptions: Callback to add components in case of TRUE value
13739
14348
  * className: Customize colors
13740
14349
  */
13741
14350
 
13742
14351
  addToggle( name, value, callback, options = {} ) {
13743
- const widget = new LX.Toggle( name, value, callback, options );
13744
- return this._attachWidget( widget );
14352
+ const component = new LX.Toggle( name, value, callback, options );
14353
+ return this._attachComponent( component );
13745
14354
  }
13746
14355
 
13747
14356
  /**
13748
14357
  * @method addRadioGroup
13749
- * @param {String} name Widget name
14358
+ * @param {String} name Component name
13750
14359
  * @param {String} label Radio label
13751
14360
  * @param {Array} values Radio options
13752
14361
  * @param {Function} callback Callback function on change
13753
14362
  * @param {Object} options:
13754
- * disabled: Make the widget disabled [false]
14363
+ * disabled: Make the component disabled [false]
13755
14364
  * className: Customize colors
13756
14365
  * selected: Index of the default selected option
13757
14366
  */
13758
14367
 
13759
14368
  addRadioGroup( name, label, values, callback, options = {} ) {
13760
- const widget = new LX.RadioGroup( name, label, values, callback, options );
13761
- return this._attachWidget( widget );
14369
+ const component = new LX.RadioGroup( name, label, values, callback, options );
14370
+ return this._attachComponent( component );
13762
14371
  }
13763
14372
 
13764
14373
  /**
13765
14374
  * @method addColor
13766
- * @param {String} name Widget name
14375
+ * @param {String} name Component name
13767
14376
  * @param {String} value Default color (hex)
13768
14377
  * @param {Function} callback Callback function on change
13769
14378
  * @param {Object} options:
13770
- * disabled: Make the widget disabled [false]
14379
+ * disabled: Make the component disabled [false]
13771
14380
  * useRGB: The callback returns color as Array (r, g, b) and not hex [false]
13772
14381
  */
13773
14382
 
13774
14383
  addColor( name, value, callback, options = {} ) {
13775
- const widget = new LX.ColorInput( name, value, callback, options );
13776
- return this._attachWidget( widget );
14384
+ const component = new LX.ColorInput( name, value, callback, options );
14385
+ return this._attachComponent( component );
13777
14386
  }
13778
14387
 
13779
14388
  /**
13780
14389
  * @method addRange
13781
- * @param {String} name Widget name
14390
+ * @param {String} name Component name
13782
14391
  * @param {Number} value Default number value
13783
14392
  * @param {Function} callback Callback function on change
13784
14393
  * @param {Object} options:
13785
14394
  * hideName: Don't use name as label [false]
13786
14395
  * className: Extra classes to customize style
13787
- * disabled: Make the widget disabled [false]
14396
+ * disabled: Make the component disabled [false]
13788
14397
  * left: The slider goes to the left instead of the right
13789
14398
  * fill: Fill slider progress [true]
13790
14399
  * step: Step of the input
@@ -13792,18 +14401,18 @@ class Panel {
13792
14401
  */
13793
14402
 
13794
14403
  addRange( name, value, callback, options = {} ) {
13795
- const widget = new LX.RangeInput( name, value, callback, options );
13796
- return this._attachWidget( widget );
14404
+ const component = new LX.RangeInput( name, value, callback, options );
14405
+ return this._attachComponent( component );
13797
14406
  }
13798
14407
 
13799
14408
  /**
13800
14409
  * @method addNumber
13801
- * @param {String} name Widget name
14410
+ * @param {String} name Component name
13802
14411
  * @param {Number} value Default number value
13803
14412
  * @param {Function} callback Callback function on change
13804
14413
  * @param {Object} options:
13805
14414
  * hideName: Don't use name as label [false]
13806
- * disabled: Make the widget disabled [false]
14415
+ * disabled: Make the component disabled [false]
13807
14416
  * step: Step of the input
13808
14417
  * precision: The number of digits to appear after the decimal point
13809
14418
  * min, max: Min and Max values for the input
@@ -13814,24 +14423,24 @@ class Panel {
13814
14423
  */
13815
14424
 
13816
14425
  addNumber( name, value, callback, options = {} ) {
13817
- const widget = new LX.NumberInput( name, value, callback, options );
13818
- return this._attachWidget( widget );
14426
+ const component = new LX.NumberInput( name, value, callback, options );
14427
+ return this._attachComponent( component );
13819
14428
  }
13820
14429
 
13821
14430
  static VECTOR_COMPONENTS = { 0: 'x', 1: 'y', 2: 'z', 3: 'w' };
13822
14431
 
13823
14432
  _addVector( numComponents, name, value, callback, options = {} ) {
13824
- const widget = new LX.Vector( numComponents, name, value, callback, options );
13825
- return this._attachWidget( widget );
14433
+ const component = new LX.Vector( numComponents, name, value, callback, options );
14434
+ return this._attachComponent( component );
13826
14435
  }
13827
14436
 
13828
14437
  /**
13829
14438
  * @method addVector N (2, 3, 4)
13830
- * @param {String} name Widget name
14439
+ * @param {String} name Component name
13831
14440
  * @param {Array} value Array of N components
13832
14441
  * @param {Function} callback Callback function on change
13833
14442
  * @param {Object} options:
13834
- * disabled: Make the widget disabled [false]
14443
+ * disabled: Make the component disabled [false]
13835
14444
  * step: Step of the inputs
13836
14445
  * min, max: Min and Max values for the inputs
13837
14446
  * onPress: Callback function on mouse down
@@ -13852,43 +14461,43 @@ class Panel {
13852
14461
 
13853
14462
  /**
13854
14463
  * @method addSize
13855
- * @param {String} name Widget name
14464
+ * @param {String} name Component name
13856
14465
  * @param {Number} value Default number value
13857
14466
  * @param {Function} callback Callback function on change
13858
14467
  * @param {Object} options:
13859
14468
  * hideName: Don't use name as label [false]
13860
- * disabled: Make the widget disabled [false]
14469
+ * disabled: Make the component disabled [false]
13861
14470
  * units: Unit as string added to the end of the value
13862
14471
  */
13863
14472
 
13864
14473
  addSize( name, value, callback, options = {} ) {
13865
- const widget = new LX.SizeInput( name, value, callback, options );
13866
- return this._attachWidget( widget );
14474
+ const component = new LX.SizeInput( name, value, callback, options );
14475
+ return this._attachComponent( component );
13867
14476
  }
13868
14477
 
13869
14478
  /**
13870
14479
  * @method addOTP
13871
- * @param {String} name Widget name
14480
+ * @param {String} name Component name
13872
14481
  * @param {String} value Default numeric value in string format
13873
14482
  * @param {Function} callback Callback function on change
13874
14483
  * @param {Object} options:
13875
14484
  * hideName: Don't use name as label [false]
13876
- * disabled: Make the widget disabled [false]
14485
+ * disabled: Make the component disabled [false]
13877
14486
  * pattern: OTP numeric pattern
13878
14487
  */
13879
14488
 
13880
14489
  addOTP( name, value, callback, options = {} ) {
13881
- const widget = new LX.OTPInput( name, value, callback, options );
13882
- return this._attachWidget( widget );
14490
+ const component = new LX.OTPInput( name, value, callback, options );
14491
+ return this._attachComponent( component );
13883
14492
  }
13884
14493
 
13885
14494
  /**
13886
14495
  * @method addPad
13887
- * @param {String} name Widget name
14496
+ * @param {String} name Component name
13888
14497
  * @param {Array} value Pad value
13889
14498
  * @param {Function} callback Callback function on change
13890
14499
  * @param {Object} options:
13891
- * disabled: Make the widget disabled [false]
14500
+ * disabled: Make the component disabled [false]
13892
14501
  * min, max: Min and Max values
13893
14502
  * padSize: Size of the pad (css)
13894
14503
  * onPress: Callback function on mouse down
@@ -13896,13 +14505,13 @@ class Panel {
13896
14505
  */
13897
14506
 
13898
14507
  addPad( name, value, callback, options = {} ) {
13899
- const widget = new LX.Pad( name, value, callback, options );
13900
- return this._attachWidget( widget );
14508
+ const component = new LX.Pad( name, value, callback, options );
14509
+ return this._attachComponent( component );
13901
14510
  }
13902
14511
 
13903
14512
  /**
13904
14513
  * @method addProgress
13905
- * @param {String} name Widget name
14514
+ * @param {String} name Component name
13906
14515
  * @param {Number} value Progress value
13907
14516
  * @param {Object} options:
13908
14517
  * min, max: Min and Max values
@@ -13913,29 +14522,29 @@ class Panel {
13913
14522
  */
13914
14523
 
13915
14524
  addProgress( name, value, options = {} ) {
13916
- const widget = new LX.Progress( name, value, options );
13917
- return this._attachWidget( widget );
14525
+ const component = new LX.Progress( name, value, options );
14526
+ return this._attachComponent( component );
13918
14527
  }
13919
14528
 
13920
14529
  /**
13921
14530
  * @method addFile
13922
- * @param {String} name Widget name
14531
+ * @param {String} name Component name
13923
14532
  * @param {Function} callback Callback function on change
13924
14533
  * @param {Object} options:
13925
14534
  * local: Ask for local file
13926
- * disabled: Make the widget disabled [false]
14535
+ * disabled: Make the component disabled [false]
13927
14536
  * read: Return the file itself (False) or the contents (True)
13928
14537
  * type: type to read as [text (Default), buffer, bin, url]
13929
14538
  */
13930
14539
 
13931
14540
  addFile( name, callback, options = { } ) {
13932
- const widget = new LX.FileInput( name, callback, options );
13933
- return this._attachWidget( widget );
14541
+ const component = new LX.FileInput( name, callback, options );
14542
+ return this._attachComponent( component );
13934
14543
  }
13935
14544
 
13936
14545
  /**
13937
14546
  * @method addTree
13938
- * @param {String} name Widget name
14547
+ * @param {String} name Component name
13939
14548
  * @param {Object} data Data of the tree
13940
14549
  * @param {Object} options:
13941
14550
  * icons: Array of objects with icon button information {name, icon, callback}
@@ -13945,13 +14554,13 @@ class Panel {
13945
14554
  */
13946
14555
 
13947
14556
  addTree( name, data, options = {} ) {
13948
- const widget = new LX.Tree( name, data, options );
13949
- return this._attachWidget( widget );
14557
+ const component = new LX.Tree( name, data, options );
14558
+ return this._attachComponent( component );
13950
14559
  }
13951
14560
 
13952
14561
  /**
13953
14562
  * @method addTabSections
13954
- * @param {String} name Widget name
14563
+ * @param {String} name Component name
13955
14564
  * @param {Array} tabs Contains objects with {
13956
14565
  * name: Name of the tab (if icon, use as title)
13957
14566
  * icon: Icon to be used as the tab icon (optional)
@@ -13966,30 +14575,30 @@ class Panel {
13966
14575
  */
13967
14576
 
13968
14577
  addTabSections( name, tabs, options = {} ) {
13969
- const widget = new LX.TabSections( name, tabs, options );
13970
- return this._attachWidget( widget );
14578
+ const component = new LX.TabSections( name, tabs, options );
14579
+ return this._attachComponent( component );
13971
14580
  }
13972
14581
 
13973
14582
  /**
13974
14583
  * @method addCounter
13975
- * @param {String} name Widget name
14584
+ * @param {String} name Component name
13976
14585
  * @param {Number} value Counter value
13977
14586
  * @param {Function} callback Callback function on change
13978
14587
  * @param {Object} options:
13979
- * disabled: Make the widget disabled [false]
14588
+ * disabled: Make the component disabled [false]
13980
14589
  * min, max: Min and Max values
13981
14590
  * step: Step for adding/substracting
13982
14591
  * label: Text to show below the counter
13983
14592
  */
13984
14593
 
13985
14594
  addCounter( name, value, callback, options = { } ) {
13986
- const widget = new LX.Counter( name, value, callback, options );
13987
- return this._attachWidget( widget );
14595
+ const component = new LX.Counter( name, value, callback, options );
14596
+ return this._attachComponent( component );
13988
14597
  }
13989
14598
 
13990
14599
  /**
13991
14600
  * @method addTable
13992
- * @param {String} name Widget name
14601
+ * @param {String} name Component name
13993
14602
  * @param {Number} data Table data
13994
14603
  * @param {Object} options:
13995
14604
  * hideName: Don't use name as label [false]
@@ -14007,13 +14616,13 @@ class Panel {
14007
14616
  */
14008
14617
 
14009
14618
  addTable( name, data, options = { } ) {
14010
- const widget = new LX.Table( name, data, options );
14011
- return this._attachWidget( widget );
14619
+ const component = new LX.Table( name, data, options );
14620
+ return this._attachComponent( component );
14012
14621
  }
14013
14622
 
14014
14623
  /**
14015
14624
  * @method addDate
14016
- * @param {String} name Widget name
14625
+ * @param {String} name Component name
14017
14626
  * @param {String} dateValue
14018
14627
  * @param {Function} callback
14019
14628
  * @param {Object} options:
@@ -14024,21 +14633,34 @@ class Panel {
14024
14633
  */
14025
14634
 
14026
14635
  addDate( name, dateValue, callback, options = { } ) {
14027
- const widget = new LX.DatePicker( name, dateValue, callback, options );
14028
- return this._attachWidget( widget );
14636
+ const component = new LX.DatePicker( name, dateValue, callback, options );
14637
+ return this._attachComponent( component );
14029
14638
  }
14030
14639
 
14031
14640
  /**
14032
14641
  * @method addMap2D
14033
- * @param {String} name Widget name
14642
+ * @param {String} name Component name
14034
14643
  * @param {Array} points
14035
14644
  * @param {Function} callback
14036
14645
  * @param {Object} options:
14037
14646
  */
14038
14647
 
14039
14648
  addMap2D( name, points, callback, options = { } ) {
14040
- const widget = new LX.Map2D( name, points, callback, options );
14041
- return this._attachWidget( widget );
14649
+ const component = new LX.Map2D( name, points, callback, options );
14650
+ return this._attachComponent( component );
14651
+ }
14652
+
14653
+ /**
14654
+ * @method addRate
14655
+ * @param {String} name Component name
14656
+ * @param {Number} value
14657
+ * @param {Function} callback
14658
+ * @param {Object} options:
14659
+ */
14660
+
14661
+ addRate( name, value, callback, options = { } ) {
14662
+ const component = new LX.Rate( name, value, callback, options );
14663
+ return this._attachComponent( component );
14042
14664
  }
14043
14665
  }
14044
14666
 
@@ -14073,7 +14695,7 @@ class Branch {
14073
14695
 
14074
14696
  this.closed = options.closed ?? false;
14075
14697
  this.root = root;
14076
- this.widgets = [];
14698
+ this.components = [];
14077
14699
 
14078
14700
  // Create element
14079
14701
  var title = document.createElement( 'div' );
@@ -14144,8 +14766,8 @@ class Branch {
14144
14766
  _onMakeFloating() {
14145
14767
 
14146
14768
  const dialog = new LX.Dialog( this.name, p => {
14147
- // add widgets
14148
- for( let w of this.widgets )
14769
+ // Add components
14770
+ for( let w of this.components )
14149
14771
  {
14150
14772
  p.root.appendChild( w.root );
14151
14773
  }
@@ -14156,7 +14778,7 @@ class Branch {
14156
14778
 
14157
14779
  dialog.branchData = {
14158
14780
  name: this.name,
14159
- widgets: this.widgets,
14781
+ components: this.components,
14160
14782
  closed: this.closed,
14161
14783
  panel: this.panel,
14162
14784
  childIndex
@@ -14168,7 +14790,7 @@ class Branch {
14168
14790
  _addBranchSeparator() {
14169
14791
 
14170
14792
  const element = document.createElement('div');
14171
- element.className = "lexwidgetseparator";
14793
+ element.className = "lexcomponentseparator";
14172
14794
  element.style.width = "100%";
14173
14795
  element.style.background = "none";
14174
14796
 
@@ -14221,7 +14843,7 @@ class Branch {
14221
14843
 
14222
14844
  function innerMouseUp(e)
14223
14845
  {
14224
- that._updateWidgets();
14846
+ that._updateComponents();
14225
14847
 
14226
14848
  line.style.height = "0px";
14227
14849
 
@@ -14234,15 +14856,15 @@ class Branch {
14234
14856
  this.content.appendChild( element );
14235
14857
  }
14236
14858
 
14237
- _updateWidgets() {
14859
+ _updateComponents() {
14238
14860
 
14239
14861
  var size = this.grabber.style.marginLeft;
14240
14862
 
14241
- // Update sizes of widgets inside
14242
- for( let i = 0; i < this.widgets.length; i++ )
14863
+ // Update sizes of components inside
14864
+ for( let i = 0; i < this.components.length; i++ )
14243
14865
  {
14244
- let widget = this.widgets[ i ];
14245
- const element = widget.root;
14866
+ let component = this.components[ i ];
14867
+ const element = component.root;
14246
14868
 
14247
14869
  if( element.children.length < 2 )
14248
14870
  {
@@ -14255,10 +14877,10 @@ class Branch {
14255
14877
  name.style.width = size;
14256
14878
  name.style.minWidth = size;
14257
14879
 
14258
- switch( widget.type )
14880
+ switch( component.type )
14259
14881
  {
14260
- case LX.Widget.CUSTOM:
14261
- case LX.Widget.ARRAY:
14882
+ case LX.BaseComponent.CUSTOM:
14883
+ case LX.BaseComponent.ARRAY:
14262
14884
  continue;
14263
14885
  }
14264
14886
  value.style.width = "-moz-calc( 100% - " + size + " )";
@@ -14572,7 +15194,15 @@ class Menubar {
14572
15194
  {
14573
15195
  const data = buttons[ i ];
14574
15196
  const title = data.title;
14575
- const button = new LX.Button( title, data.label, data.callback, { title, buttonClass: "bg-none", disabled: data.disabled, icon: data.icon, hideName: true, swap: data.swap, iconPosition: "start" } );
15197
+ const button = new LX.Button( title, data.label, data.callback, {
15198
+ title,
15199
+ buttonClass: "bg-none",
15200
+ disabled: data.disabled,
15201
+ icon: data.icon,
15202
+ hideName: true,
15203
+ swap: data.swap,
15204
+ iconPosition: "start"
15205
+ } );
14576
15206
  this.buttonContainer.appendChild( button.root );
14577
15207
 
14578
15208
  if( title )
@@ -15146,9 +15776,21 @@ class Sidebar {
15146
15776
  LX.asTooltip( itemDom, key, { side: "right", offset: 16, active: false } );
15147
15777
  }
15148
15778
 
15149
- let itemName = document.createElement( 'a' );
15150
- itemName.innerHTML = key;
15151
- itemDom.appendChild( itemName );
15779
+ LX.makeElement( 'a', "grid-column-start-2", key, itemDom );
15780
+
15781
+ if( options.swap )
15782
+ {
15783
+ itemDom.classList.add( "swap", "inline-grid" );
15784
+ itemDom.querySelector( "a" ).classList.add( "swap-off" );
15785
+
15786
+ const input = document.createElement( "input" );
15787
+ input.className = "p-0 border-0";
15788
+ input.type = "checkbox";
15789
+ itemDom.prepend( input );
15790
+
15791
+ const swapIcon = LX.makeIcon( options.swap, { iconClass: "lexsidebarentryicon swap-on" } );
15792
+ itemDom.appendChild( swapIcon );
15793
+ }
15152
15794
 
15153
15795
  if( options.content )
15154
15796
  {
@@ -15177,8 +15819,14 @@ class Sidebar {
15177
15819
  item.checkbox.set( item.value, true );
15178
15820
  }
15179
15821
 
15822
+ if( options.swap && !( e.target instanceof HTMLInputElement ) )
15823
+ {
15824
+ const swapInput = itemDom.querySelector( "input" );
15825
+ swapInput.checked = !swapInput.checked;
15826
+ }
15827
+
15180
15828
  // Manage selected
15181
- if( this.displaySelected )
15829
+ if( this.displaySelected && !options.skipSelection )
15182
15830
  {
15183
15831
  this.root.querySelectorAll(".lexsidebarentry").forEach( e => e.classList.remove( 'selected' ) );
15184
15832
  entry.classList.add( "selected" );
@@ -15260,6 +15908,14 @@ class Sidebar {
15260
15908
 
15261
15909
  subentry.className = "lexsidebarentry";
15262
15910
  subentry.id = subkey;
15911
+
15912
+ if( suboptions.content )
15913
+ {
15914
+ const parentContainer = LX.makeElement( "div" );
15915
+ parentContainer.appendChild( suboptions.content );
15916
+ subentry.appendChild( parentContainer );
15917
+ }
15918
+
15263
15919
  subentryContainer.appendChild( subentry );
15264
15920
 
15265
15921
  subentry.addEventListener("click", (e) => {
@@ -15268,10 +15924,10 @@ class Sidebar {
15268
15924
  if( f ) f.call( this, subkey, subentry, e );
15269
15925
 
15270
15926
  // Manage selected
15271
- if( this.displaySelected )
15927
+ if( this.displaySelected && !suboptions.skipSelection )
15272
15928
  {
15273
15929
  this.root.querySelectorAll(".lexsidebarentry").forEach( e => e.classList.remove( 'selected' ) );
15274
- entry.classList.add( "selected" );
15930
+ subentry.classList.add( "selected" );
15275
15931
  }
15276
15932
  });
15277
15933
  }
@@ -15290,8 +15946,8 @@ class AssetViewEvent {
15290
15946
  static ASSET_RENAMED = 3;
15291
15947
  static ASSET_CLONED = 4;
15292
15948
  static ASSET_DBLCLICKED = 5;
15293
- static ENTER_FOLDER = 6;
15294
- static ASSET_CHECKED = 7;
15949
+ static ASSET_CHECKED = 6;
15950
+ static ENTER_FOLDER = 7;
15295
15951
 
15296
15952
  constructor( type, item, value ) {
15297
15953
  this.type = type || LX.TreeEvent.NONE;
@@ -15309,8 +15965,8 @@ class AssetViewEvent {
15309
15965
  case AssetViewEvent.ASSET_RENAMED: return "assetview_event_renamed";
15310
15966
  case AssetViewEvent.ASSET_CLONED: return "assetview_event_cloned";
15311
15967
  case AssetViewEvent.ASSET_DBLCLICKED: return "assetview_event_dblclicked";
15312
- case AssetViewEvent.ENTER_FOLDER: return "assetview_event_enter_folder";
15313
15968
  case AssetViewEvent.ASSET_CHECKED: return "assetview_event_checked";
15969
+ case AssetViewEvent.ENTER_FOLDER: return "assetview_event_enter_folder";
15314
15970
  }
15315
15971
  }
15316
15972
  }
@@ -15323,8 +15979,12 @@ LX.AssetViewEvent = AssetViewEvent;
15323
15979
 
15324
15980
  class AssetView {
15325
15981
 
15326
- static LAYOUT_CONTENT = 0;
15982
+ static LAYOUT_GRID = 0;
15327
15983
  static LAYOUT_LIST = 1;
15984
+
15985
+ static CONTENT_SORT_ASC = 0;
15986
+ static CONTENT_SORT_DESC = 1;
15987
+
15328
15988
  static MAX_PAGE_ELEMENTS = 50;
15329
15989
 
15330
15990
  /**
@@ -15333,7 +15993,8 @@ class AssetView {
15333
15993
  constructor( options = {} ) {
15334
15994
 
15335
15995
  this.rootPath = "https://raw.githubusercontent.com/jxarco/lexgui.js/master/";
15336
- this.layout = options.layout ?? AssetView.LAYOUT_CONTENT;
15996
+ this.layout = options.layout ?? AssetView.LAYOUT_GRID;
15997
+ this.sortMode = options.sortMode ?? AssetView.CONTENT_SORT_ASC;
15337
15998
  this.contentPage = 1;
15338
15999
 
15339
16000
  if( options.rootPath )
@@ -15451,9 +16112,9 @@ class AssetView {
15451
16112
  this.leftPanel.clear();
15452
16113
  }
15453
16114
 
15454
- if( this.rightPanel )
16115
+ if( this.toolsPanel )
15455
16116
  {
15456
- this.rightPanel.clear();
16117
+ this.toolsPanel.clear();
15457
16118
  }
15458
16119
  }
15459
16120
 
@@ -15565,7 +16226,7 @@ class AssetView {
15565
16226
  _setContentLayout( layoutMode ) {
15566
16227
 
15567
16228
  this.layout = layoutMode;
15568
-
16229
+ this.toolsPanel.refresh();
15569
16230
  this._refreshContent();
15570
16231
  }
15571
16232
 
@@ -15575,42 +16236,34 @@ class AssetView {
15575
16236
 
15576
16237
  _createContentPanel( area ) {
15577
16238
 
15578
- if( this.rightPanel )
16239
+ if( this.toolsPanel )
15579
16240
  {
15580
- this.rightPanel.clear();
16241
+ this.contentPanel.clear();
15581
16242
  }
15582
16243
  else
15583
16244
  {
15584
- this.rightPanel = area.addPanel({ className: 'lexassetcontentpanel flex flex-col overflow-hidden' });
16245
+ this.toolsPanel = area.addPanel({ className: 'flex flex-col overflow-hidden' });
16246
+ this.contentPanel = area.addPanel({ className: 'lexassetcontentpanel flex flex-col overflow-hidden' });
15585
16247
  }
15586
16248
 
15587
- const on_sort = ( value, event ) => {
15588
- const cmenu = LX.addContextMenu( "Sort by", event, c => {
15589
- c.add("Name", () => this._sortData('id') );
15590
- c.add("Type", () => this._sortData('type') );
15591
- c.add("");
15592
- c.add("Ascending", () => this._sortData() );
15593
- c.add("Descending", () => this._sortData(null, true) );
15594
- } );
15595
- const parent = this.parent.root.parentElement;
15596
- if( parent.classList.contains('lexdialog') )
15597
- {
15598
- cmenu.root.style.zIndex = (+getComputedStyle( parent ).zIndex) + 1;
15599
- }
16249
+ const _onSort = ( value, event ) => {
16250
+ new LX.DropdownMenu( event.target, [
16251
+ { name: "Name", icon: "ALargeSmall", callback: () => this._sortData( "id" ) },
16252
+ { name: "Type", icon: "Type", callback: () => this._sortData( "type" ) },
16253
+ null,
16254
+ { name: "Ascending", icon: "SortAsc", callback: () => this._sortData( null, AssetView.CONTENT_SORT_ASC ) },
16255
+ { name: "Descending", icon: "SortDesc", callback: () => this._sortData( null, AssetView.CONTENT_SORT_DESC ) }
16256
+ ], { side: "right", align: "start" });
15600
16257
  };
15601
16258
 
15602
- const on_change_view = ( value, event ) => {
15603
- const cmenu = LX.addContextMenu( "Layout", event, c => {
15604
- c.add("Content", () => this._setContentLayout( AssetView.LAYOUT_CONTENT ) );
15605
- c.add("");
15606
- c.add("List", () => this._setContentLayout( AssetView.LAYOUT_LIST ) );
15607
- } );
15608
- const parent = this.parent.root.parentElement;
15609
- if( parent.classList.contains('lexdialog') )
15610
- cmenu.root.style.zIndex = (+getComputedStyle( parent ).zIndex) + 1;
16259
+ const _onChangeView = ( value, event ) => {
16260
+ new LX.DropdownMenu( event.target, [
16261
+ { name: "Grid", icon: "LayoutGrid", callback: () => this._setContentLayout( AssetView.LAYOUT_GRID ) },
16262
+ { name: "List", icon: "LayoutList", callback: () => this._setContentLayout( AssetView.LAYOUT_LIST ) }
16263
+ ], { side: "right", align: "start" });
15611
16264
  };
15612
16265
 
15613
- const on_change_page = ( value, event ) => {
16266
+ const _onChangePage = ( value, event ) => {
15614
16267
  if( !this.allowNextPage )
15615
16268
  {
15616
16269
  return;
@@ -15626,64 +16279,71 @@ class AssetView {
15626
16279
  }
15627
16280
  };
15628
16281
 
15629
- this.rightPanel.sameLine();
15630
- this.rightPanel.addSelect( "Filter", this.allowedTypes, this.allowedTypes[ 0 ], v => this._refreshContent.call(this, null, v), { width: "30%", minWidth: "128px" } );
15631
- this.rightPanel.addText( null, this.searchValue ?? "", v => this._refreshContent.call(this, v, null), { placeholder: "Search assets.." } );
15632
- this.rightPanel.addButton( null, "", on_sort.bind(this), { title: "Sort", icon: "ArrowUpNarrowWide" } );
15633
- this.rightPanel.addButton( null, "", on_change_view.bind(this), { title: "View", icon: "GripHorizontal" } );
15634
- // Content Pages
15635
- this.rightPanel.addButton( null, "", on_change_page.bind(this, -1), { title: "Previous Page", icon: "ChevronsLeft", className: "ml-auto" } );
15636
- this.rightPanel.addButton( null, "", on_change_page.bind(this, 1), { title: "Next Page", icon: "ChevronsRight" } );
15637
- const textString = "Page " + this.contentPage + " / " + ((((this.currentData.length - 1) / AssetView.MAX_PAGE_ELEMENTS )|0) + 1);
15638
- this.rightPanel.addText(null, textString, null, {
15639
- inputClass: "nobg", disabled: true, signal: "@on_page_change", maxWidth: "16ch" }
15640
- );
15641
- this.rightPanel.endLine();
16282
+ this.toolsPanel.refresh = () => {
16283
+ this.toolsPanel.clear();
16284
+ this.toolsPanel.sameLine();
16285
+ this.toolsPanel.addSelect( "Filter", this.allowedTypes, this.filter ?? this.allowedTypes[ 0 ], v => {
16286
+ this._refreshContent( null, v );
16287
+ }, { width: "30%", minWidth: "128px" } );
16288
+ this.toolsPanel.addText( null, this.searchValue ?? "", v => this._refreshContent.call(this, v, null), { placeholder: "Search assets.." } );
16289
+ this.toolsPanel.addButton( null, "", _onSort.bind(this), { title: "Sort", tooltip: true, icon: ( this.sortMode === AssetView.CONTENT_SORT_ASC ) ? "SortAsc" : "SortDesc" } );
16290
+ this.toolsPanel.addButton( null, "", _onChangeView.bind(this), { title: "View", tooltip: true, icon: ( this.layout === AssetView.LAYOUT_GRID ) ? "LayoutGrid" : "LayoutList" } );
16291
+ // Content Pages
16292
+ this.toolsPanel.addButton( null, "", _onChangePage.bind(this, -1), { title: "Previous Page", icon: "ChevronsLeft", className: "ml-auto" } );
16293
+ this.toolsPanel.addButton( null, "", _onChangePage.bind(this, 1), { title: "Next Page", icon: "ChevronsRight" } );
16294
+ const textString = "Page " + this.contentPage + " / " + ((((this.currentData.length - 1) / AssetView.MAX_PAGE_ELEMENTS )|0) + 1);
16295
+ this.toolsPanel.addText(null, textString, null, {
16296
+ inputClass: "nobg", disabled: true, signal: "@on_page_change", maxWidth: "16ch" }
16297
+ );
15642
16298
 
15643
- if( !this.skipBrowser )
15644
- {
15645
- this.rightPanel.sameLine();
15646
- this.rightPanel.addComboButtons( null, [
15647
- {
15648
- value: "Left",
15649
- icon: "ArrowLeft",
15650
- callback: domEl => {
15651
- if(!this.prevData.length) return;
15652
- this.nextData.push( this.currentData );
15653
- this.currentData = this.prevData.pop();
15654
- this._refreshContent();
15655
- this._updatePath( this.currentData );
15656
- }
15657
- },
15658
- {
15659
- value: "Right",
15660
- icon: "ArrowRight",
15661
- callback: domEl => {
15662
- if(!this.nextData.length) return;
15663
- this.prevData.push( this.currentData );
15664
- this.currentData = this.nextData.pop();
15665
- this._refreshContent();
15666
- this._updatePath( this.currentData );
16299
+ if( !this.skipBrowser )
16300
+ {
16301
+ this.toolsPanel.addComboButtons( null, [
16302
+ {
16303
+ value: "Left",
16304
+ icon: "ArrowLeft",
16305
+ callback: domEl => {
16306
+ if(!this.prevData.length) return;
16307
+ this.nextData.push( this.currentData );
16308
+ this.currentData = this.prevData.pop();
16309
+ this._refreshContent();
16310
+ this._updatePath( this.currentData );
16311
+ }
16312
+ },
16313
+ {
16314
+ value: "Right",
16315
+ icon: "ArrowRight",
16316
+ callback: domEl => {
16317
+ if(!this.nextData.length) return;
16318
+ this.prevData.push( this.currentData );
16319
+ this.currentData = this.nextData.pop();
16320
+ this._refreshContent();
16321
+ this._updatePath( this.currentData );
16322
+ }
16323
+ },
16324
+ {
16325
+ value: "Refresh",
16326
+ icon: "Refresh",
16327
+ callback: domEl => { this._refreshContent(); }
15667
16328
  }
15668
- },
15669
- {
15670
- value: "Refresh",
15671
- icon: "Refresh",
15672
- callback: domEl => { this._refreshContent(); }
15673
- }
15674
- ], { noSelection: true } );
16329
+ ], { noSelection: true } );
15675
16330
 
15676
- this.rightPanel.addText(null, this.path.join('/'), null, {
15677
- inputClass: "nobg", disabled: true, signal: "@on_folder_change",
15678
- style: { fontWeight: "600", fontSize: "15px" }
15679
- });
16331
+ this.toolsPanel.addText(null, this.path.join('/'), null, {
16332
+ inputClass: "nobg", disabled: true, signal: "@on_folder_change",
16333
+ style: { fontWeight: "600", fontSize: "15px" }
16334
+ });
16335
+ }
15680
16336
 
15681
- this.rightPanel.endLine();
15682
- }
16337
+ this.toolsPanel.endLine();
16338
+ };
16339
+
16340
+ this.toolsPanel.refresh();
16341
+
16342
+ // Start content panel
15683
16343
 
15684
16344
  this.content = document.createElement('ul');
15685
16345
  this.content.className = "lexassetscontent";
15686
- this.rightPanel.root.appendChild(this.content);
16346
+ this.contentPanel.attach( this.content );
15687
16347
 
15688
16348
  this.content.addEventListener('dragenter', function( e ) {
15689
16349
  e.preventDefault();
@@ -15706,12 +16366,12 @@ class AssetView {
15706
16366
 
15707
16367
  _refreshContent( searchValue, filter ) {
15708
16368
 
15709
- const isContentLayout = ( this.layout == AssetView.LAYOUT_CONTENT ); // default
16369
+ const isGridLayout = ( this.layout == AssetView.LAYOUT_GRID ); // default
15710
16370
 
15711
16371
  this.filter = filter ?? ( this.filter ?? "None" );
15712
16372
  this.searchValue = searchValue ?? (this.searchValue ?? "");
15713
16373
  this.content.innerHTML = "";
15714
- this.content.className = (isContentLayout ? "lexassetscontent" : "lexassetscontent list");
16374
+ this.content.className = (isGridLayout ? "lexassetscontent" : "lexassetscontent list");
15715
16375
  let that = this;
15716
16376
 
15717
16377
  const _addItem = function(item) {
@@ -15734,7 +16394,7 @@ class AssetView {
15734
16394
 
15735
16395
  itemEl.addEventListener("mousemove", e => {
15736
16396
 
15737
- if( !isContentLayout )
16397
+ if( !isGridLayout )
15738
16398
  {
15739
16399
  return;
15740
16400
  }
@@ -15763,14 +16423,14 @@ class AssetView {
15763
16423
  });
15764
16424
 
15765
16425
  itemEl.addEventListener("mouseenter", () => {
15766
- if( isContentLayout )
16426
+ if( isGridLayout )
15767
16427
  {
15768
16428
  desc.style.display = "unset";
15769
16429
  }
15770
16430
  });
15771
16431
 
15772
16432
  itemEl.addEventListener("mouseleave", () => {
15773
- if( isContentLayout )
16433
+ if( isGridLayout )
15774
16434
  {
15775
16435
  setTimeout( () => {
15776
16436
  desc.style.display = "none";
@@ -15814,11 +16474,11 @@ class AssetView {
15814
16474
  let preview = null;
15815
16475
  const hasImage = item.src && (['png', 'jpg'].indexOf( LX.getExtension( item.src ) ) > -1 || item.src.includes("data:image/") ); // Support b64 image as src
15816
16476
 
15817
- if( hasImage || isFolder || !isContentLayout)
16477
+ if( hasImage || isFolder || !isGridLayout)
15818
16478
  {
15819
16479
  preview = document.createElement('img');
15820
16480
  let real_src = item.unknown_extension ? that.rootPath + "images/file.png" : (isFolder ? that.rootPath + "images/folder.png" : item.src);
15821
- preview.src = (isContentLayout || isFolder ? real_src : that.rootPath + "images/file.png");
16481
+ preview.src = (isGridLayout || isFolder ? real_src : that.rootPath + "images/file.png");
15822
16482
  itemEl.appendChild( preview );
15823
16483
  }
15824
16484
  else
@@ -16079,16 +16739,20 @@ class AssetView {
16079
16739
  }
16080
16740
  }
16081
16741
 
16082
- _sortData( sort_by, sort_descending = false ) {
16742
+ _sortData( sortBy, sortMode ) {
16083
16743
 
16084
- sort_by = sort_by ?? (this._lastSortBy ?? 'id');
16085
- this.currentData = this.currentData.sort( (a, b) => {
16086
- var r = sort_descending ? b[sort_by].localeCompare(a[sort_by]) : a[sort_by].localeCompare(b[sort_by]);
16087
- if(r == 0) r = sort_descending ? b['id'].localeCompare(a['id']) : a['id'].localeCompare(b['id']);
16744
+ sortBy = sortBy ?? ( this._lastSortBy ?? 'id' );
16745
+ sortMode = sortMode ?? this.sortMode;
16746
+ const sortDesc = ( sortMode === AssetView.CONTENT_SORT_DESC );
16747
+ this.currentData = this.currentData.sort( ( a, b ) => {
16748
+ var r = sortDesc ? b[ sortBy ].localeCompare( a[ sortBy ] ) : a[ sortBy ].localeCompare( b[ sortBy ] );
16749
+ if( r == 0 ) r = sortDesc ? b['id'].localeCompare( a['id'] ) : a[ 'id' ].localeCompare( b[ 'id' ] );
16088
16750
  return r;
16089
16751
  } );
16090
16752
 
16091
- this._lastSortBy = sort_by;
16753
+ this._lastSortBy = sortBy;
16754
+ this.sortMode = sortMode;
16755
+ this.toolsPanel.refresh();
16092
16756
  this._refreshContent();
16093
16757
  }
16094
16758