lexgui 0.7.4 → 0.7.6

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.
@@ -319,6 +319,25 @@ class CodeEditor {
319
319
 
320
320
  constructor( area, options = {} ) {
321
321
 
322
+ if( options.filesAsync )
323
+ {
324
+ options.files = [ ...options.filesAsync ];
325
+
326
+ return (async () => {
327
+ await this._init( area, options );
328
+ // Constructors return `this` implicitly, but this is an IIFE, so
329
+ // return `this` explicitly (else we'd return an empty object).
330
+ return this;
331
+ })();
332
+ }
333
+ else
334
+ {
335
+ this._init( area, options );
336
+ }
337
+ }
338
+
339
+ async _init( area, options ) {
340
+
322
341
  window.editor = this;
323
342
 
324
343
  CodeEditor.__instances.push( this );
@@ -338,7 +357,7 @@ class CodeEditor {
338
357
 
339
358
  let panel = new LX.Panel();
340
359
 
341
- panel.addTitle( "EXPLORER" );
360
+ panel.addTitle( options.explorerName ?? "EXPLORER" );
342
361
 
343
362
  let sceneData = {
344
363
  'id': 'WORKSPACE',
@@ -403,11 +422,11 @@ class CodeEditor {
403
422
 
404
423
  if( !this.disableEdition )
405
424
  {
406
- this.tabs.root.addEventListener( 'dblclick', (e) => {
425
+ this.tabs.root.parentElement.addEventListener( 'dblclick', (e) => {
407
426
  if( options.allowAddScripts ?? true )
408
427
  {
409
428
  e.preventDefault();
410
- this.addTab( "unnamed.js", true );
429
+ this._onCreateNewFile();
411
430
  }
412
431
  } );
413
432
  }
@@ -1232,6 +1251,7 @@ class CodeEditor {
1232
1251
  const s = getComputedStyle( r );
1233
1252
  this.fontSize = parseInt( s.getPropertyValue( "--code-editor-font-size" ) );
1234
1253
  this.charWidth = this._measureChar( "a", true );
1254
+ this.processLines();
1235
1255
  }
1236
1256
 
1237
1257
  LX.emit( "@font-size", this.fontSize );
@@ -1257,18 +1277,22 @@ class CodeEditor {
1257
1277
 
1258
1278
  if( options.allowAddScripts ?? true )
1259
1279
  {
1260
- this.addTab("+", false, "New File");
1280
+ this.onCreateFile = options.onCreateFile;
1281
+ this.addTab( "+", false, "Create file" );
1261
1282
  }
1262
1283
 
1263
1284
  if( options.files )
1264
1285
  {
1265
1286
  console.assert( options.files.constructor === Array, "_files_ must be an Array!" );
1266
1287
  const numFiles = options.files.length;
1288
+ const loadAsync = ( options.filesAsync !== undefined );
1267
1289
  let filesLoaded = 0;
1268
-
1269
1290
  for( let url of options.files )
1270
1291
  {
1271
- this.loadFile( url, { callback: ( name, text ) => {
1292
+ const finalUrl = url.constructor === Array ? url[ 0 ] : url;
1293
+ const finalFileName = url.constructor === Array ? url[ 1 ] : url;
1294
+
1295
+ await this.loadFile( finalUrl, { filename: finalFileName, async: loadAsync, callback: ( name, text ) => {
1272
1296
  filesLoaded++;
1273
1297
  if( filesLoaded == numFiles )
1274
1298
  {
@@ -1276,7 +1300,7 @@ class CodeEditor {
1276
1300
 
1277
1301
  if( options.onFilesLoaded )
1278
1302
  {
1279
- options.onFilesLoaded( this, numFiles );
1303
+ options.onFilesLoaded( this.loadedTabs, numFiles );
1280
1304
  }
1281
1305
  }
1282
1306
  }});
@@ -1418,15 +1442,12 @@ class CodeEditor {
1418
1442
  const _innerAddTab = ( text, name, title ) => {
1419
1443
 
1420
1444
  // Remove Carriage Return in some cases and sub tabs using spaces
1421
- text = text.replaceAll( '\r', '' );
1422
- text = text.replaceAll( /\t|\\t/g, ' '.repeat( this.tabSpaces ) );
1445
+ text = text.replaceAll( '\r', '' ).replaceAll( /\t|\\t/g, ' '.repeat( this.tabSpaces ) );
1423
1446
 
1424
1447
  // Set current text and language
1425
-
1426
1448
  const lines = text.split( '\n' );
1427
1449
 
1428
1450
  // Add item in the explorer if used
1429
-
1430
1451
  if( this.useFileExplorer || this.skipTabs )
1431
1452
  {
1432
1453
  this._tabStorage[ name ] = {
@@ -1462,13 +1483,20 @@ class CodeEditor {
1462
1483
 
1463
1484
  if( file.constructor == String )
1464
1485
  {
1465
- let filename = file;
1466
-
1467
- LX.request({ url: filename, success: text => {
1468
- const name = filename.substring(filename.lastIndexOf( '/' ) + 1);
1469
- _innerAddTab( text, name, filename );
1470
- } });
1486
+ const filename = file;
1487
+ const name = options.filename ?? filename.substring(filename.lastIndexOf( '/' ) + 1);
1471
1488
 
1489
+ if( options.async ?? false )
1490
+ {
1491
+ const text = await this._requestFileAsync( filename, "text" );
1492
+ _innerAddTab( text, name, options.filename ?? filename );
1493
+ }
1494
+ else
1495
+ {
1496
+ LX.request({ url: filename, success: text => {
1497
+ _innerAddTab( text, name, options.filename ?? filename );
1498
+ } });
1499
+ }
1472
1500
  }
1473
1501
  else // File Blob
1474
1502
  {
@@ -1828,10 +1856,23 @@ class CodeEditor {
1828
1856
 
1829
1857
  this.processFocus( false );
1830
1858
 
1831
- LX.addContextMenu( null, e, m => {
1832
- m.add( "Create", this.addTab.bind( this, "unnamed.js", true, "", { language: "JavaScript" } ) );
1833
- m.add( "Load", this.loadTabFromFile.bind( this, "unnamed.js", true ) );
1834
- });
1859
+ new LX.DropdownMenu( e.target, [
1860
+ { name: "Create file", icon: "FilePlus", callback: this._onCreateNewFile.bind( this ) },
1861
+ { name: "Load file", icon: "FileUp", callback: this.loadTabFromFile.bind( this ) },
1862
+ ], { side: "bottom", align: "start" });
1863
+ }
1864
+
1865
+ _onCreateNewFile() {
1866
+
1867
+ let options = {};
1868
+
1869
+ if( this.onCreateFile )
1870
+ {
1871
+ options = this.onCreateFile( this );
1872
+ }
1873
+
1874
+ const name = options.name ?? "unnamed.js";
1875
+ this.addTab( name, true, name, { language: options.language ?? "JavaScript" } );
1835
1876
  }
1836
1877
 
1837
1878
  _onSelectTab( isNewTabButton, event, name ) {
@@ -1868,18 +1909,38 @@ class CodeEditor {
1868
1909
  {
1869
1910
  this._changeLanguageFromExtension( LX.getExtension( name ) );
1870
1911
  }
1912
+
1913
+ this.processLines();
1871
1914
  }
1872
1915
 
1873
1916
  _onContextMenuTab( isNewTabButton, event, name, ) {
1874
1917
 
1875
1918
  if( isNewTabButton )
1919
+ {
1876
1920
  return;
1921
+ }
1877
1922
 
1878
- LX.addContextMenu( null, event, m => {
1879
- m.add( "Close", () => { this.tabs.delete( name ) } );
1880
- // m.add( "" );
1881
- // m.add( "Rename", () => { console.warn( "TODO" )} );
1882
- });
1923
+ new LX.DropdownMenu( event.target, [
1924
+ { name: "Close", kbd: "MWB", callback: () => { this.tabs.delete( name ) } },
1925
+ { name: "Close Others", callback: () => {
1926
+ for( const [ key, data ] of Object.entries( this.tabs.tabs ) )
1927
+ {
1928
+ if( key === '+' || key === name ) continue;
1929
+ this.tabs.delete( key )
1930
+ }
1931
+ } },
1932
+ { name: "Close All", callback: () => {
1933
+ for( const [ key, data ] of Object.entries( this.tabs.tabs ) )
1934
+ {
1935
+ if( key === '+' ) continue;
1936
+ this.tabs.delete( key )
1937
+ }
1938
+ } },
1939
+ null,
1940
+ { name: "Copy Path", icon: "Copy", callback: () => {
1941
+ navigator.clipboard.writeText( this.openedTabs[ name ].path ?? "" );
1942
+ } }
1943
+ ], { side: "bottom", align: "start", event });
1883
1944
  }
1884
1945
 
1885
1946
  addTab( name, selected, title, options = {} ) {
@@ -1900,6 +1961,7 @@ class CodeEditor {
1900
1961
  // Create code content
1901
1962
  let code = document.createElement( 'div' );
1902
1963
  Object.assign( code, {
1964
+ path: options.path ?? "",
1903
1965
  className: 'code',
1904
1966
  lines: [ "" ],
1905
1967
  language: options.language ?? "Plain Text",
@@ -2132,7 +2194,7 @@ class CodeEditor {
2132
2194
  document.body.appendChild( input );
2133
2195
  input.click();
2134
2196
  input.addEventListener('change', e => {
2135
- if (e.target.files[ 0 ])
2197
+ if( e.target.files[ 0 ] )
2136
2198
  {
2137
2199
  this.loadFile( e.target.files[ 0 ] );
2138
2200
  }
@@ -5565,6 +5627,36 @@ s
5565
5627
  delete this._lastResult;
5566
5628
  delete this._scopeStack;
5567
5629
  }
5630
+
5631
+ async _requestFileAsync( url, dataType, nocache ) {
5632
+ return new Promise( (resolve, reject) => {
5633
+ dataType = dataType ?? "arraybuffer";
5634
+ const mimeType = dataType === "arraybuffer" ? "application/octet-stream" : undefined;
5635
+ var xhr = new XMLHttpRequest();
5636
+ xhr.open( 'GET', url, true );
5637
+ xhr.responseType = dataType;
5638
+ if( mimeType )
5639
+ xhr.overrideMimeType( mimeType );
5640
+ if( nocache )
5641
+ xhr.setRequestHeader('Cache-Control', 'no-cache');
5642
+ xhr.onload = function(load)
5643
+ {
5644
+ var response = this.response;
5645
+ if( this.status != 200)
5646
+ {
5647
+ var err = "Error " + this.status;
5648
+ reject(err);
5649
+ return;
5650
+ }
5651
+ resolve( response );
5652
+ };
5653
+ xhr.onerror = function(err) {
5654
+ reject(err);
5655
+ };
5656
+ xhr.send();
5657
+ return xhr;
5658
+ });
5659
+ }
5568
5660
  }
5569
5661
 
5570
5662
  CodeEditor.languages = {
@@ -530,7 +530,10 @@ class VideoEditor {
530
530
  this.isResizing = true;
531
531
  }
532
532
  });
533
- });
533
+ });
534
+
535
+ this.onChangeStart = null;
536
+ this.onChangeEnd = null;
534
537
  }
535
538
 
536
539
  resizeCropArea(event) {
@@ -714,6 +717,10 @@ class VideoEditor {
714
717
  if(this.onSetTime) {
715
718
  this.onSetTime(t);
716
719
  }
720
+
721
+ if(this.onChangeStart) {
722
+ this.onChangeStart(t);
723
+ }
717
724
  }
718
725
 
719
726
  _setEndValue ( x ) {
@@ -733,6 +740,10 @@ class VideoEditor {
733
740
  if(this.onSetTime) {
734
741
  this.onSetTime(t);
735
742
  }
743
+
744
+ if(this.onChangeEnd) {
745
+ this.onChangeEnd(t);
746
+ }
736
747
  }
737
748
 
738
749
  getStartTime ( ) {
package/build/lexgui.css CHANGED
@@ -4933,33 +4933,37 @@ ul.lexassetscontent {
4933
4933
  .codebasearea .lexareatabs {
4934
4934
  padding: 0px;
4935
4935
  margin: 0px;
4936
+ gap: 0;
4937
+ background-color: var(--global-color-primary);
4936
4938
  }
4937
4939
 
4938
4940
  .codebasearea .lexareatab {
4939
4941
  padding: 5px;
4940
4942
  border-radius: 0px !important;
4941
4943
  margin: 0px !important;
4942
- border: 1px solid #91909036;
4943
- border-right: none;
4944
- border-bottom: none;
4945
- background-color: var(--global-color-primary) !important;
4944
+ border: none;
4945
+ background-color: var(--global-color-secondary) !important;
4946
+ border-bottom: 1px solid transparent;
4946
4947
  transition: none;
4947
4948
  display: flex !important;
4948
4949
  }
4949
4950
 
4950
4951
  .codebasearea .lexareatab:hover {
4951
- background-color: var(--global-color-secondary) !important;
4952
+ background-color: var(--global-color-tertiary) !important;
4953
+ }
4954
+
4955
+ .codebasearea .lexareatab:first-child {
4956
+ border-top-left-radius: 8px !important;
4952
4957
  }
4953
4958
 
4954
4959
  .codebasearea .lexareatab:last-child {
4955
- border-right: 1px solid #91909036;
4960
+ border-top-right-radius: 8px !important;
4956
4961
  }
4957
4962
 
4958
4963
  .codebasearea .lexareatab.selected {
4959
- background-color: var(--global-color-secondary) !important;
4960
- color: var(--global-text-secondary) !important;
4961
- border-top: 1px solid var(--global-color-accent);
4962
- border-bottom: none;
4964
+ background-color: light-dark(var(--global-color-secondary), var(--global-medium-background)) !important;
4965
+ border-bottom: 1px solid var(--global-color-accent);
4966
+ color: var(--global-text-primary) !important;
4963
4967
  }
4964
4968
 
4965
4969
  .codebasearea .lexareatab i {
@@ -6105,6 +6109,10 @@ ul.lexassetscontent {
6105
6109
  .items-center { place-items: center }
6106
6110
  .items-end { place-items: end }
6107
6111
 
6112
+ .align-items-start { align-items: start }
6113
+ .align-items-center { align-items: center }
6114
+ .align-items-end { align-items: end }
6115
+
6108
6116
  .justify-start { justify-content: start }
6109
6117
  .justify-center { justify-content: center }
6110
6118
  .justify-end { justify-content: end }
@@ -6316,6 +6324,8 @@ ul.lexassetscontent {
6316
6324
  .font-bold { font-weight: 700 }
6317
6325
  .font-extrabold { font-weight: 800 }
6318
6326
 
6327
+ .font-code { font-family: var(--global-code-font) }
6328
+
6319
6329
  .tracking-tighter { letter-spacing: -0.05em }
6320
6330
  .tracking-tight { letter-spacing: -0.025em }
6321
6331
  .tracking-normal { letter-spacing: 0em }
@@ -6327,7 +6337,11 @@ ul.lexassetscontent {
6327
6337
 
6328
6338
  .uppercase { text-transform: uppercase }
6329
6339
  .capitalize { text-transform: capitalize }
6340
+
6330
6341
  .decoration-none { text-decoration: none }
6342
+ .text-underline { text-decoration: underline }
6343
+
6344
+ .hover\:text-underline:hover { text-decoration: underline }
6331
6345
 
6332
6346
  /* Width / Height */
6333
6347
 
@@ -6358,7 +6372,6 @@ ul.lexassetscontent {
6358
6372
  .w-16 { width: 4rem } /* 64px */
6359
6373
  .w-32 { width: 8rem } /* 128px */
6360
6374
 
6361
-
6362
6375
  .h-full { height: 100% }
6363
6376
  .h-screen { height: 100vh }
6364
6377
  .h-auto { height: auto }
package/build/lexgui.js CHANGED
@@ -14,7 +14,7 @@ console.warn( 'Script _build/lexgui.js_ is depracated and will be removed soon.
14
14
  */
15
15
 
16
16
  const LX = {
17
- version: "0.7.4",
17
+ version: "0.7.6",
18
18
  ready: false,
19
19
  extensions: [], // Store extensions used
20
20
  signals: {}, // Events and triggers
@@ -543,16 +543,30 @@ async function init( options = { } )
543
543
  this.main_area = new LX.Area( { id: options.id ?? 'mainarea' } );
544
544
  }
545
545
 
546
- if( ( options.autoTheme ?? true ) )
546
+ // Initial or automatic changes don't force color scheme
547
+ // to be stored in localStorage
548
+
549
+ this._onChangeSystemTheme = function( event ) {
550
+ const storedcolorScheme = localStorage.getItem( "lxColorScheme" );
551
+ if( storedcolorScheme ) return;
552
+ LX.setTheme( event.matches ? "dark" : "light", false );
553
+ };
554
+
555
+ this._mqlPrefersDarkScheme = window.matchMedia ? window.matchMedia("(prefers-color-scheme: dark)") : null;
556
+
557
+ const storedcolorScheme = localStorage.getItem( "lxColorScheme" );
558
+ if( storedcolorScheme )
547
559
  {
548
- if( window.matchMedia && window.matchMedia( "(prefers-color-scheme: light)" ).matches )
560
+ LX.setTheme( storedcolorScheme );
561
+ }
562
+ else if( this._mqlPrefersDarkScheme && ( options.autoTheme ?? true ) )
563
+ {
564
+ if( window.matchMedia( "(prefers-color-scheme: light)" ).matches )
549
565
  {
550
- LX.setTheme( "light" );
566
+ LX.setTheme( "light", false );
551
567
  }
552
568
 
553
- window.matchMedia( "(prefers-color-scheme: dark)" ).addEventListener( "change", event => {
554
- LX.setTheme( event.matches ? "dark" : "light" );
555
- });
569
+ this._mqlPrefersDarkScheme.addEventListener( "change", this._onChangeSystemTheme );
556
570
  }
557
571
 
558
572
  return this.main_area;
@@ -1099,6 +1113,7 @@ class DropdownMenu {
1099
1113
  this.alignOffset = options.alignOffset ?? 0;
1100
1114
  this.avoidCollisions = options.avoidCollisions ?? true;
1101
1115
  this.onBlur = options.onBlur;
1116
+ this.event = options.event;
1102
1117
  this.inPlace = false;
1103
1118
 
1104
1119
  this.root = document.createElement( "div" );
@@ -1389,11 +1404,11 @@ class DropdownMenu {
1389
1404
  _adjustPosition() {
1390
1405
 
1391
1406
  const position = [ 0, 0 ];
1407
+ const rect = this._trigger.getBoundingClientRect();
1392
1408
 
1393
1409
  // Place menu using trigger position and user options
1410
+ if( !this.event )
1394
1411
  {
1395
- const rect = this._trigger.getBoundingClientRect();
1396
-
1397
1412
  let alignWidth = true;
1398
1413
 
1399
1414
  switch( this.side )
@@ -1435,11 +1450,11 @@ class DropdownMenu {
1435
1450
  if( alignWidth ) { position[ 0 ] += this.alignOffset; }
1436
1451
  else { position[ 1 ] += this.alignOffset; }
1437
1452
  }
1438
-
1439
- if( this.avoidCollisions )
1453
+ // Offset position based on event
1454
+ else
1440
1455
  {
1441
- position[ 0 ] = LX.clamp( position[ 0 ], 0, window.innerWidth - this.root.offsetWidth - this._windowPadding );
1442
- position[ 1 ] = LX.clamp( position[ 1 ], 0, window.innerHeight - this.root.offsetHeight - this._windowPadding );
1456
+ position[ 0 ] = this.event.x;
1457
+ position[ 1 ] = this.event.y;
1443
1458
  }
1444
1459
 
1445
1460
  if( this._parent instanceof HTMLDialogElement )
@@ -1449,6 +1464,12 @@ class DropdownMenu {
1449
1464
  position[ 1 ] -= parentRect.y;
1450
1465
  }
1451
1466
 
1467
+ if( this.avoidCollisions )
1468
+ {
1469
+ position[ 0 ] = LX.clamp( position[ 0 ], 0, window.innerWidth - this.root.offsetWidth - this._windowPadding );
1470
+ position[ 1 ] = LX.clamp( position[ 1 ], 0, window.innerHeight - this.root.offsetHeight - this._windowPadding );
1471
+ }
1472
+
1452
1473
  this.root.style.left = `${ position[ 0 ] }px`;
1453
1474
  this.root.style.top = `${ position[ 1 ] }px`;
1454
1475
  this.inPlace = true;
@@ -2545,8 +2566,9 @@ class Tabs {
2545
2566
  });
2546
2567
 
2547
2568
  // Attach content
2548
- tabEl.childIndex = ( this.root.childElementCount - 1 );
2549
- this.root.appendChild( tabEl );
2569
+ const indexOffset = options.indexOffset ?? -1;
2570
+ tabEl.childIndex = ( this.root.childElementCount + indexOffset );
2571
+ this.root.insertChildAtIndex( tabEl, tabEl.childIndex + 1 );
2550
2572
  this.area.attach( contentEl );
2551
2573
  this.tabDOMs[ name ] = tabEl;
2552
2574
  this.tabs[ name ] = content;
@@ -4971,11 +4993,13 @@ LX.deepCopy = deepCopy;
4971
4993
  * @method setTheme
4972
4994
  * @description Set dark or light theme
4973
4995
  * @param {String} colorScheme Name of the scheme
4996
+ * @param {Boolean} storeLocal Store in localStorage
4974
4997
  */
4975
- function setTheme( colorScheme )
4998
+ function setTheme( colorScheme, storeLocal = true )
4976
4999
  {
4977
5000
  colorScheme = ( colorScheme == "light" ) ? "light" : "dark";
4978
5001
  document.documentElement.setAttribute( "data-theme", colorScheme );
5002
+ if( storeLocal ) localStorage.setItem( "lxColorScheme", colorScheme );
4979
5003
  LX.emit( "@on_new_color_scheme", colorScheme );
4980
5004
  }
4981
5005
 
@@ -5004,6 +5028,26 @@ function switchTheme()
5004
5028
 
5005
5029
  LX.switchTheme = switchTheme;
5006
5030
 
5031
+ /**
5032
+ * @method setSystemTheme
5033
+ * @description Sets back the system theme
5034
+ */
5035
+ function setSystemTheme()
5036
+ {
5037
+ const currentTheme = ( window.matchMedia && window.matchMedia( "(prefers-color-scheme: light)" ).matches ) ? "light" : "dark";
5038
+ setTheme( currentTheme );
5039
+ localStorage.removeItem( "lxColorScheme" );
5040
+
5041
+ // Reapply listener
5042
+ if( this._mqlPrefersDarkScheme )
5043
+ {
5044
+ this._mqlPrefersDarkScheme.removeEventListener( "change", this._onChangeSystemTheme );
5045
+ this._mqlPrefersDarkScheme.addEventListener( "change", this._onChangeSystemTheme );
5046
+ }
5047
+ }
5048
+
5049
+ LX.setSystemTheme = setSystemTheme;
5050
+
5007
5051
  /**
5008
5052
  * @method setThemeColor
5009
5053
  * @description Sets a new value for one of the main theme variables
@@ -8986,7 +9030,7 @@ class TextInput extends BaseComponent {
8986
9030
 
8987
9031
  this.valid = ( v ) => {
8988
9032
  v = v ?? this.value();
8989
- if( !v.length || wValue.pattern == "" ) return true;
9033
+ if( ( wValue.pattern ?? "" ) == "" ) return true;
8990
9034
  const regexp = new RegExp( wValue.pattern );
8991
9035
  return regexp.test( v );
8992
9036
  };
@@ -9725,19 +9769,21 @@ class Form extends BaseComponent {
9725
9769
 
9726
9770
  const primaryButton = new LX.Button( null, options.primaryActionName ?? "Submit", ( value, event ) => {
9727
9771
 
9772
+ const errors = [];
9773
+
9728
9774
  for( let entry in data )
9729
9775
  {
9730
9776
  let entryData = data[ entry ];
9731
9777
 
9732
9778
  if( !entryData.textComponent.valid() )
9733
9779
  {
9734
- return;
9780
+ errors.push( { type: "input_not_valid", entry } );
9735
9781
  }
9736
9782
  }
9737
9783
 
9738
9784
  if( callback )
9739
9785
  {
9740
- callback( container.formData, event );
9786
+ callback( container.formData, errors, event );
9741
9787
  }
9742
9788
  }, { width: "100%", minWidth: "0", buttonClass: options.primaryButtonClass ?? "contrast" } );
9743
9789
 
@@ -15942,8 +15988,7 @@ class Sidebar {
15942
15988
  return;
15943
15989
  }
15944
15990
 
15945
- const f = options.callback;
15946
- if( f ) f.call( this, key, item.value, e );
15991
+ let value = undefined;
15947
15992
 
15948
15993
  if( isCollapsable )
15949
15994
  {
@@ -15953,14 +15998,18 @@ class Sidebar {
15953
15998
  {
15954
15999
  item.value = !item.value;
15955
16000
  item.checkbox.set( item.value, true );
16001
+ value = item.value;
15956
16002
  }
15957
-
15958
- if( options.swap && !( e.target instanceof HTMLInputElement ) )
16003
+ else if( options.swap && !( e.target instanceof HTMLInputElement ) )
15959
16004
  {
15960
16005
  const swapInput = itemDom.querySelector( "input" );
15961
16006
  swapInput.checked = !swapInput.checked;
16007
+ value = swapInput.checked;
15962
16008
  }
15963
16009
 
16010
+ const f = options.callback;
16011
+ if( f ) f.call( this, key, value ?? entry, e );
16012
+
15964
16013
  // Manage selected
15965
16014
  if( this.displaySelected && !options.skipSelection )
15966
16015
  {