lexgui 0.1.41 → 0.1.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  var LX = {
11
- version: "0.1.41",
11
+ version: "0.1.43",
12
12
  ready: false,
13
13
  components: [], // specific pre-build components
14
14
  signals: {} // events and triggers
@@ -25,7 +25,7 @@ LX.CURVE_MOVEOUT_CLAMP = 0;
25
25
  LX.CURVE_MOVEOUT_DELETE = 1;
26
26
 
27
27
  function clamp( num, min, max ) { return Math.min( Math.max( num, min ), max ); }
28
- function round( number, precision ) { return +(( number ).toFixed( precision ?? 2 ).replace( /([0-9]+(\.[0-9]+[1-9])?)(\.?0+$)/, '$1' )); }
28
+ function round( number, precision ) { return precision == 0 ? Math.floor( number ) : +(( number ).toFixed( precision ?? 2 ).replace( /([0-9]+(\.[0-9]+[1-9])?)(\.?0+$)/, '$1' )); }
29
29
  function remapRange( oldValue, oldMin, oldMax, newMin, newMax ) { return ((( oldValue - oldMin ) * ( newMax - newMin )) / ( oldMax - oldMin )) + newMin; }
30
30
 
31
31
  LX.clamp = clamp;
@@ -34,7 +34,7 @@ LX.remapRange = remapRange;
34
34
 
35
35
  function getSupportedDOMName( string )
36
36
  {
37
- return string.replace(/\s/g, '').replaceAll('@', '_').replaceAll('+', '_plus_').replaceAll('.', '');
37
+ return string.replace(/\s/g, '').replaceAll('@', '_').replaceAll('+', '_plus_').replaceAll('.', '');
38
38
  }
39
39
 
40
40
  LX.getSupportedDOMName = getSupportedDOMName;
@@ -47,15 +47,15 @@ function has( component_name )
47
47
  LX.has = has;
48
48
 
49
49
  function getExtension( s )
50
- {
50
+ {
51
51
  return s.includes('.') ? s.split('.').pop() : null;
52
52
  }
53
53
 
54
54
  LX.getExtension = getExtension;
55
55
 
56
56
  function deepCopy( o )
57
- {
58
- return JSON.parse(JSON.stringify(o))
57
+ {
58
+ return JSON.parse(JSON.stringify(o))
59
59
  }
60
60
 
61
61
  LX.deepCopy = deepCopy;
@@ -275,7 +275,7 @@ function makeDraggable( domEl, options = { } ) {
275
275
  onDragStart( currentTarget, e );
276
276
  }
277
277
  }, false );
278
-
278
+
279
279
  document.addEventListener( 'mouseup', () => {
280
280
  if( currentTarget )
281
281
  {
@@ -289,77 +289,90 @@ LX.makeDraggable = makeDraggable;
289
289
 
290
290
  function create_global_searchbar( root ) {
291
291
 
292
- let global_search = document.createElement("div");
293
- global_search.id = "global_search";
294
- global_search.className = "hidden";
295
- global_search.tabIndex = -1;
296
- root.appendChild( global_search );
292
+ let globalSearch = document.createElement("div");
293
+ globalSearch.id = "global-search";
294
+ globalSearch.className = "hidden";
295
+ globalSearch.tabIndex = -1;
296
+ root.appendChild( globalSearch );
297
297
 
298
298
  let allItems = [];
299
299
  let hoverElId = null;
300
300
 
301
- global_search.addEventListener('keydown', function(e) {
301
+ globalSearch.addEventListener('keydown', function( e ) {
302
302
  e.stopPropagation();
303
303
  e.stopImmediatePropagation();
304
304
  hoverElId = hoverElId ?? -1;
305
- if( e.key == 'Escape' ) {
305
+ if( e.key == 'Escape' )
306
+ {
306
307
  this.classList.add("hidden");
307
- reset_bar(true);
308
+ _resetBar( true );
308
309
  }
309
- else if( e.key == 'Enter' ) {
310
+ else if( e.key == 'Enter' )
311
+ {
310
312
  const el = allItems[ hoverElId ];
311
- if(el) {
312
- const is_checkbox = (el.item.type && el.item.type === 'checkbox');
313
+ if( el )
314
+ {
315
+ const isCheckbox = (el.item.type && el.item.type === 'checkbox');
313
316
  this.classList.toggle('hidden');
314
- if(is_checkbox) {
317
+ if( isCheckbox )
318
+ {
315
319
  el.item.checked = !el.item.checked;
316
- el.callback.call(window, el.item.checked, el.entry_name);
320
+ el.callback.call( window, el.item.checked, el.entry_name );
317
321
  }
318
322
  else
319
- el.callback.call(window, el.entry_name);
323
+ {
324
+ el.callback.call( window, el.entry_name );
325
+ }
320
326
  }
321
327
  }
322
- else if ( e.key == 'ArrowDown' && hoverElId < (allItems.length - 1) ) {
328
+ else if ( e.key == 'ArrowDown' && hoverElId < (allItems.length - 1) )
329
+ {
323
330
  hoverElId++;
324
- global_search.querySelectorAll(".hovered").forEach(e => e.classList.remove('hovered'));
331
+ globalSearch.querySelectorAll(".hovered").forEach(e => e.classList.remove('hovered'));
325
332
  allItems[ hoverElId ].classList.add('hovered');
326
333
 
327
334
  let dt = allItems[ hoverElId ].offsetHeight * (hoverElId + 1) - itemContainer.offsetHeight;
328
- if( dt > 0) {
335
+ if( dt > 0 )
336
+ {
329
337
  itemContainer.scrollTo({
330
338
  top: dt,
331
339
  behavior: "smooth",
332
340
  });
333
341
  }
334
342
 
335
- } else if ( e.key == 'ArrowUp' && hoverElId > 0 ) {
343
+ } else if ( e.key == 'ArrowUp' && hoverElId > 0 )
344
+ {
336
345
  hoverElId--;
337
- global_search.querySelectorAll(".hovered").forEach(e => e.classList.remove('hovered'));
346
+ globalSearch.querySelectorAll(".hovered").forEach(e => e.classList.remove('hovered'));
338
347
  allItems[ hoverElId ].classList.add('hovered');
339
348
  }
340
349
  });
341
350
 
342
- global_search.addEventListener('focusout', function(e) {
343
- if(e.relatedTarget == e.currentTarget) return;
351
+ globalSearch.addEventListener('focusout', function( e ) {
352
+ if( e.relatedTarget == e.currentTarget )
353
+ {
354
+ return;
355
+ }
344
356
  e.stopPropagation();
345
357
  e.stopImmediatePropagation();
346
- this.classList.add("hidden");
347
- reset_bar(true);
358
+ this.classList.add( "hidden" );
359
+ _resetBar( true );
348
360
  });
349
361
 
350
362
  root.addEventListener('keydown', e => {
351
- if( e.key == ' ' && e.ctrlKey ) {
363
+ if( e.key == ' ' && e.ctrlKey )
364
+ {
352
365
  e.stopImmediatePropagation();
353
366
  e.stopPropagation();
354
- global_search.classList.toggle('hidden');
355
- global_search.querySelector('input').focus();
356
- add_elements( undefined );
367
+ globalSearch.classList.toggle('hidden');
368
+ globalSearch.querySelector('input').focus();
369
+ _addElements( undefined );
357
370
  }
358
371
  else
359
372
  {
360
373
  for( let c of LX.components )
361
374
  {
362
- if( !LX[c] || !LX[c].prototype.onKeyPressed )
375
+ if( !LX[ c ] || !LX[ c ].prototype.onKeyPressed )
363
376
  {
364
377
  continue;
365
378
  }
@@ -373,37 +386,68 @@ function create_global_searchbar( root ) {
373
386
  }
374
387
  });
375
388
 
376
- let icon = document.createElement("a");
389
+ const header = document.createElement( "div" );
390
+ header.className = "gs-header";
391
+
392
+ const icon = document.createElement("a");
377
393
  icon.className = "fa-solid fa-magnifying-glass";
394
+ header.appendChild( icon );
378
395
 
379
- let input = document.createElement("input");
396
+ const input = document.createElement("input");
380
397
  input.placeholder = "Search...";
381
398
  input.value = "";
399
+ header.appendChild( input );
400
+
401
+ const tabArea = new Area( {
402
+ width: "100%",
403
+ skipAppend: true,
404
+ className: "gs-tabs"
405
+ } );
406
+
407
+ const gsTabs = tabArea.addTabs();
408
+ let gsFilter = null;
409
+
410
+ // These tabs will serve as buttons by now
411
+ // Filter stuff depending of the type of search
412
+ {
413
+ const _onSelectTab = ( e, tabName ) => {
414
+ gsFilter = tabName;
415
+ }
416
+
417
+ gsTabs.add( "All", document.createElement('div'), { selected: true, onSelect: _onSelectTab } );
418
+ // gsTabs.add( "Main", document.createElement('div'), { onSelect: _onSelectTab } );
419
+ }
382
420
 
383
- let itemContainer = document.createElement("div");
421
+ const itemContainer = document.createElement("div");
384
422
  itemContainer.className = "searchitembox";
385
423
 
386
- let ref_previous;
424
+ let refPrevious = null;
387
425
 
388
- const reset_bar = (reset_input) => {
426
+ const _resetBar = (reset_input) => {
389
427
  itemContainer.innerHTML = "";
390
428
  allItems.length = 0;
391
429
  hoverElId = null;
392
430
  if(reset_input) input.value = "";
393
431
  }
394
432
 
395
- const add_element = (t, c, p, i) => {
433
+ const _addElement = ( t, c, p, i ) => {
396
434
 
397
- if(!t.length) return;
435
+ if( !t.length )
436
+ {
437
+ return;
438
+ }
398
439
 
399
- if(ref_previous) ref_previous.classList.remove('last');
440
+ if( refPrevious ) refPrevious.classList.remove('last');
400
441
 
401
442
  let searchItem = document.createElement("div");
402
443
  searchItem.className = "searchitem last";
403
- const is_checkbox = (i && i.type && i.type === 'checkbox');
404
- if(is_checkbox) {
444
+ const isCheckbox = (i && i.type && i.type === 'checkbox');
445
+ if( isCheckbox )
446
+ {
405
447
  searchItem.innerHTML = "<a class='fa fa-check'></a><span>" + p + t + "</span>"
406
- } else {
448
+ }
449
+ else
450
+ {
407
451
  searchItem.innerHTML = p + t;
408
452
  }
409
453
  searchItem.entry_name = t;
@@ -411,29 +455,32 @@ function create_global_searchbar( root ) {
411
455
  searchItem.item = i;
412
456
  searchItem.addEventListener('click', function(e) {
413
457
  this.callback.call(window, this.entry_name);
414
- global_search.classList.toggle('hidden');
415
- reset_bar(true);
458
+ globalSearch.classList.toggle('hidden');
459
+ _resetBar( true );
416
460
  });
417
461
  searchItem.addEventListener('mouseenter', function(e) {
418
- global_search.querySelectorAll(".hovered").forEach(e => e.classList.remove('hovered'));
462
+ globalSearch.querySelectorAll(".hovered").forEach(e => e.classList.remove('hovered'));
419
463
  this.classList.add('hovered');
420
- hoverElId = allItems.indexOf(this);
464
+ hoverElId = allItems.indexOf( this );
421
465
  });
422
466
  searchItem.addEventListener('mouseleave', function(e) {
423
467
  this.classList.remove('hovered');
424
468
  });
425
469
  allItems.push( searchItem );
426
- itemContainer.appendChild(searchItem);
427
- ref_previous = searchItem;
470
+ itemContainer.appendChild( searchItem );
471
+ refPrevious = searchItem;
428
472
  }
429
473
 
430
- const propagate_add = ( item, filter, path ) => {
474
+ const _propagateAdd = ( item, filter, path ) => {
431
475
 
432
476
  const key = Object.keys( item )[ 0 ];
433
477
  let name = item.name ?? path + key;
434
- if( name.toLowerCase().includes( filter ) ) {
478
+ if( name.toLowerCase().includes( filter ) )
479
+ {
435
480
  if( item.callback )
436
- add_element( item.name ?? key, item.callback, path, item );
481
+ {
482
+ _addElement( item.name ?? key, item.callback, path, item );
483
+ }
437
484
  }
438
485
 
439
486
  // is sidebar..
@@ -443,17 +490,20 @@ function create_global_searchbar( root ) {
443
490
  path += key + " > ";
444
491
 
445
492
  for( let c of item[ key ] )
446
- propagate_add( c, filter, path );
493
+ _propagateAdd( c, filter, path );
447
494
  };
448
495
 
449
- const add_elements = filter => {
450
-
451
- reset_bar();
496
+ const _addElements = filter => {
497
+
498
+ _resetBar();
452
499
 
453
500
  for( let m of LX.menubars )
454
- for( let i of m.items ) {
455
- propagate_add( i, filter, "" );
501
+ {
502
+ for( let i of m.items )
503
+ {
504
+ _propagateAdd( i, filter, "" );
456
505
  }
506
+ }
457
507
 
458
508
  if( LX.has('CodeEditor') )
459
509
  {
@@ -483,35 +533,38 @@ function create_global_searchbar( root ) {
483
533
  }
484
534
 
485
535
  input.addEventListener('input', function(e) {
486
- add_elements( this.value.toLowerCase() );
536
+ _addElements( this.value.toLowerCase() );
487
537
  });
488
-
489
- global_search.appendChild(icon);
490
- global_search.appendChild(input);
491
- global_search.appendChild(itemContainer);
492
538
 
493
- return global_search;
539
+ globalSearch.appendChild( header );
540
+ globalSearch.appendChild( tabArea.root );
541
+ globalSearch.appendChild( itemContainer );
542
+
543
+ return globalSearch;
494
544
  }
495
545
 
496
546
  /**
497
547
  * @method init
498
- * @param {Object} options
548
+ * @param {Object} options
499
549
  * container: Root location for the gui (default is the document body)
500
550
  * id: Id of the main area
501
- * skip_default_area: Skip creation of main area
551
+ * skipRoot: Skip adding LX root container
552
+ * skipDefaultArea: Skip creation of main area
502
553
  */
503
554
 
504
555
  function init( options = { } )
505
556
  {
506
557
  if( this.ready )
558
+ {
507
559
  return this.main_area;
560
+ }
508
561
 
509
562
  // LexGUI root
510
563
 
511
564
  var root = document.createElement( 'div' );
512
565
  root.id = "lexroot";
513
566
  root.tabIndex = -1;
514
-
567
+
515
568
  var modal = document.createElement( 'div' );
516
569
  modal.id = "modal";
517
570
 
@@ -527,17 +580,25 @@ function init( options = { } )
527
580
 
528
581
  if( options.container )
529
582
  this.container = document.getElementById( options.container );
530
-
531
- this.global_search = create_global_searchbar( this.container );
583
+
584
+ this.globalSearch = create_global_searchbar( this.container );
532
585
 
533
586
  this.container.appendChild( modal );
534
- this.container.appendChild( root );
587
+
588
+ if( !options.skipRoot )
589
+ {
590
+ this.container.appendChild( root );
591
+ }
535
592
 
536
593
  // Disable drag icon
537
594
  root.addEventListener( 'dragover', function( e ) {
538
595
  e.preventDefault();
539
596
  }, false );
540
597
 
598
+ document.addEventListener( 'contextmenu', function( e ) {
599
+ e.preventDefault();
600
+ }, false );
601
+
541
602
  // CSS fontawesome
542
603
  var head = document.getElementsByTagName( 'HEAD' )[ 0 ];
543
604
  var link = document.createElement( 'link' );
@@ -555,8 +616,10 @@ function init( options = { } )
555
616
  this.ready = true;
556
617
  this.menubars = [ ];
557
618
 
558
- if( !options.skip_default_area )
619
+ if( !options.skipRoot && !options.skipDefaultArea )
620
+ {
559
621
  this.main_area = new Area( { id: options.id ?? 'mainarea' } );
622
+ }
560
623
 
561
624
  return this.main_area;
562
625
  }
@@ -565,33 +628,35 @@ LX.init = init;
565
628
 
566
629
  /**
567
630
  * @method message
568
- * @param {String} text
631
+ * @param {String} text
569
632
  * @param {String} title (Optional)
570
- * @param {*} options
633
+ * @param {*} options
571
634
  * id: Id of the message dialog
572
635
  * position: Dialog position in screen [screen centered]
573
636
  * draggable: Dialog can be dragged [false]
574
637
  */
575
638
 
576
- function message(text, title, options = {})
639
+ function message( text, title, options = {} )
577
640
  {
578
- if(!text)
579
- throw("No message to show");
641
+ if( !text )
642
+ {
643
+ throw( "No message to show" );
644
+ }
580
645
 
581
646
  options.modal = true;
582
647
 
583
- return new Dialog(title, p => {
584
- p.addTextArea(null, text, null, { disabled: true, fitHeight: true });
585
- }, options);
648
+ return new Dialog( title, p => {
649
+ p.addTextArea( null, text, null, { disabled: true, fitHeight: true } );
650
+ }, options );
586
651
  }
587
652
 
588
653
  LX.message = message;
589
654
 
590
655
  /**
591
656
  * @method popup
592
- * @param {String} text
657
+ * @param {String} text
593
658
  * @param {String} title (Optional)
594
- * @param {*} options
659
+ * @param {*} options
595
660
  * id: Id of the message dialog
596
661
  * time: (Number) Delay time before close automatically (ms). Defalut: [3000]
597
662
  * position: (Array) [x,y] Dialog position in screen. Default: [screen centered]
@@ -608,17 +673,17 @@ function popup( text, title, options = {} )
608
673
  options.size = options.size ?? [ "auto", "auto" ];
609
674
  options.class = "lexpopup";
610
675
 
611
- const time = options.timeout || 3000;
676
+ const time = options.timeout || 3000;
612
677
  const dialog = new Dialog( title, p => {
613
678
  p.addTextArea( null, text, null, { disabled: true, fitHeight: true } );
614
679
  }, options );
615
-
680
+
616
681
  dialog.root.classList.add( 'fadein' );
617
682
  setTimeout(() => {
618
683
  dialog.root.classList.remove( 'fadein' );
619
684
  dialog.root.classList.add( 'fadeout' );
620
685
  }, time - 1000 );
621
-
686
+
622
687
  setTimeout( dialog.close, time );
623
688
 
624
689
  return dialog;
@@ -628,9 +693,9 @@ LX.popup = popup;
628
693
 
629
694
  /**
630
695
  * @method prompt
631
- * @param {String} text
696
+ * @param {String} text
632
697
  * @param {String} title (Optional)
633
- * @param {*} options
698
+ * @param {*} options
634
699
  * id: Id of the prompt dialog
635
700
  * position: Dialog position in screen [screen centered]
636
701
  * draggable: Dialog can be dragged [false]
@@ -662,7 +727,8 @@ function prompt( text, title, callback, options = {} )
662
727
  text += text.includes("You must fill the input text.") ? "": "\nYou must fill the input text.";
663
728
  dialog.close();
664
729
  prompt( text, title, callback, options );
665
- }else
730
+ }
731
+ else
666
732
  {
667
733
  if( callback ) callback.call( this, value );
668
734
  dialog.close();
@@ -678,7 +744,7 @@ function prompt( text, title, callback, options = {} )
678
744
  {
679
745
  dialog.root.querySelector( 'input' ).focus();
680
746
  }
681
-
747
+
682
748
  return dialog;
683
749
  }
684
750
 
@@ -718,7 +784,7 @@ class TreeEvent {
718
784
  this.multiple = false; // Multiple selection
719
785
  this.panel = null;
720
786
  }
721
-
787
+
722
788
  string() {
723
789
  switch( this.type )
724
790
  {
@@ -742,7 +808,9 @@ function emit( signalName, value, options = {} )
742
808
  const data = LX.signals[ signalName ];
743
809
 
744
810
  if( !data )
745
- return;
811
+ {
812
+ return;
813
+ }
746
814
 
747
815
  const target = options.target;
748
816
 
@@ -761,7 +829,7 @@ function emit( signalName, value, options = {} )
761
829
  if( obj.constructor === Widget )
762
830
  {
763
831
  obj.set( value, options.skipCallback ?? true );
764
-
832
+
765
833
  if( obj.options && obj.options.callback )
766
834
  {
767
835
  obj.options.callback( value, data );
@@ -781,10 +849,14 @@ function addSignal( name, obj, callback )
781
849
  obj[name] = callback;
782
850
 
783
851
  if( !LX.signals[ name ] )
852
+ {
784
853
  LX.signals[ name ] = [];
785
-
854
+ }
855
+
786
856
  if( LX.signals[ name ].indexOf( obj ) > -1 )
857
+ {
787
858
  return;
859
+ }
788
860
 
789
861
  LX.signals[ name ].push( obj );
790
862
  }
@@ -799,16 +871,20 @@ class Area {
799
871
 
800
872
  /**
801
873
  * @constructor Area
802
- * @param {*} options
874
+ * @param {*} options
803
875
  * id: Id of the element
804
876
  * className: Add class to the element
805
877
  * width: Width of the area element [fit space]
806
878
  * height: Height of the area element [fit space]
807
- * no_append: Create but not append to GUI root [false]
879
+ * skipAppend: Create but not append to GUI root [false]
880
+ * minWidth: Minimum width to be applied when resizing
881
+ * minHeight: Minimum height to be applied when resizing
882
+ * maxWidth: Maximum width to be applied when resizing
883
+ * maxHeight: Maximum height to be applied when resizing
808
884
  */
809
885
 
810
886
  constructor( options = {} ) {
811
-
887
+
812
888
  var root = document.createElement( 'div' );
813
889
  root.className = "lexarea";
814
890
  if( options.id )
@@ -822,7 +898,7 @@ class Area {
822
898
 
823
899
  var width = options.width || "calc( 100% )";
824
900
  var height = options.height || "100%";
825
-
901
+
826
902
  // This has default options..
827
903
  this.setLimitBox( options.minWidth, options.minHeight, options.maxWidth, options.maxHeight );
828
904
 
@@ -844,7 +920,7 @@ class Area {
844
920
  this.sections = [];
845
921
  this.panels = [];
846
922
 
847
- if( !options.no_append )
923
+ if( !options.skipAppend )
848
924
  {
849
925
  var lexroot = document.getElementById("lexroot");
850
926
  lexroot.appendChild( this.root );
@@ -872,101 +948,104 @@ class Area {
872
948
  {
873
949
  this.root.style.bottom = options.bottom;
874
950
  }
875
-
951
+
876
952
  const draggable = options.draggable ?? true;
877
953
  if( draggable )
878
954
  {
879
955
  makeDraggable( root, options );
880
956
  }
881
957
 
882
- if( options.resizeable ) {
958
+ if( options.resizeable )
959
+ {
883
960
  root.classList.add("resizeable");
884
961
  }
885
-
962
+
886
963
  if( options.resize )
887
- {
888
- this.split_bar = document.createElement("div");
964
+ {
965
+ this.splitBar = document.createElement("div");
889
966
  let type = (overlay == "left") || (overlay == "right") ? "horizontal" : "vertical";
890
- this.type = overlay;;
891
- this.split_bar.className = "lexsplitbar " + type;
967
+ this.type = overlay;
968
+ this.splitBar.className = "lexsplitbar " + type;
892
969
 
893
970
  if( overlay == "right" )
894
971
  {
895
- this.split_bar.style.width = LX.DEFAULT_SPLITBAR_SIZE + "px";
896
- this.split_bar.style.left = -(LX.DEFAULT_SPLITBAR_SIZE / 2.0) + "px";
897
- }
972
+ this.splitBar.style.width = LX.DEFAULT_SPLITBAR_SIZE + "px";
973
+ this.splitBar.style.left = -(LX.DEFAULT_SPLITBAR_SIZE / 2.0) + "px";
974
+ }
898
975
  else if( overlay == "left" )
899
976
  {
900
977
  let size = Math.min(document.body.clientWidth - LX.DEFAULT_SPLITBAR_SIZE, this.root.clientWidth);
901
- this.split_bar.style.width = LX.DEFAULT_SPLITBAR_SIZE + "px";
902
- this.split_bar.style.left = size + (LX.DEFAULT_SPLITBAR_SIZE / 2.0) + "px";
978
+ this.splitBar.style.width = LX.DEFAULT_SPLITBAR_SIZE + "px";
979
+ this.splitBar.style.left = size + (LX.DEFAULT_SPLITBAR_SIZE / 2.0) + "px";
903
980
  }
904
981
  else if( overlay == "top" )
905
982
  {
906
983
  let size = Math.min(document.body.clientHeight - LX.DEFAULT_SPLITBAR_SIZE, this.root.clientHeight);
907
- this.split_bar.style.height = LX.DEFAULT_SPLITBAR_SIZE + "px";
908
- this.split_bar.style.top = size + (LX.DEFAULT_SPLITBAR_SIZE / 2.0) + "px";
984
+ this.splitBar.style.height = LX.DEFAULT_SPLITBAR_SIZE + "px";
985
+ this.splitBar.style.top = size + (LX.DEFAULT_SPLITBAR_SIZE / 2.0) + "px";
909
986
  }
910
987
  else if( overlay == "bottom" )
911
988
  {
912
- this.split_bar.style.height = LX.DEFAULT_SPLITBAR_SIZE + "px";
913
- this.split_bar.style.top = -(LX.DEFAULT_SPLITBAR_SIZE / 2.0) + "px";
989
+ this.splitBar.style.height = LX.DEFAULT_SPLITBAR_SIZE + "px";
990
+ this.splitBar.style.top = -(LX.DEFAULT_SPLITBAR_SIZE / 2.0) + "px";
914
991
  }
915
992
 
916
- this.split_bar.addEventListener("mousedown", inner_mousedown);
917
- this.root.appendChild( this.split_bar );
918
-
993
+ this.splitBar.addEventListener("mousedown", inner_mousedown);
994
+ this.root.appendChild( this.splitBar );
995
+
919
996
  var that = this;
920
- var last_pos = [ 0, 0 ];
921
-
997
+ var lastMousePosition = [ 0, 0 ];
998
+
922
999
  function inner_mousedown( e )
923
1000
  {
924
1001
  var doc = that.root.ownerDocument;
925
1002
  doc.addEventListener( 'mousemove', inner_mousemove );
926
1003
  doc.addEventListener( 'mouseup', inner_mouseup );
927
- last_pos[ 0 ] = e.x;
928
- last_pos[ 1 ] = e.y;
1004
+ lastMousePosition[ 0 ] = e.x;
1005
+ lastMousePosition[ 1 ] = e.y;
929
1006
  e.stopPropagation();
930
1007
  e.preventDefault();
931
1008
  document.body.classList.add( 'nocursor' );
932
- that.split_bar.classList.add( 'nocursor' );
1009
+ that.splitBar.classList.add( 'nocursor' );
933
1010
  }
934
1011
 
935
1012
  function inner_mousemove( e )
936
1013
  {
937
1014
  switch( that.type ) {
938
1015
  case "right":
939
- var dt = ( last_pos[ 0 ] - e.x );
1016
+ var dt = ( lastMousePosition[ 0 ] - e.x );
940
1017
  var size = ( that.root.offsetWidth + dt );
941
1018
  that.root.style.width = size + "px";
942
1019
  break;
943
1020
  case "left":
944
- var dt = ( last_pos[ 0 ] - e.x );
1021
+ var dt = ( lastMousePosition[ 0 ] - e.x );
945
1022
  var size = Math.min(document.body.clientWidth - LX.DEFAULT_SPLITBAR_SIZE, (that.root.offsetWidth - dt));
946
1023
  that.root.style.width = size + "px";
947
- that.split_bar.style.left = size + LX.DEFAULT_SPLITBAR_SIZE/2 + "px";
1024
+ that.splitBar.style.left = size + LX.DEFAULT_SPLITBAR_SIZE/2 + "px";
948
1025
  break;
949
1026
  case "top":
950
- var dt = ( last_pos[ 1 ] - e.y );
1027
+ var dt = ( lastMousePosition[ 1 ] - e.y );
951
1028
  var size = Math.min(document.body.clientHeight - LX.DEFAULT_SPLITBAR_SIZE, (that.root.offsetHeight - dt));
952
1029
  that.root.style.height = size + "px";
953
- that.split_bar.style.top = size + LX.DEFAULT_SPLITBAR_SIZE/2 + "px";
1030
+ that.splitBar.style.top = size + LX.DEFAULT_SPLITBAR_SIZE/2 + "px";
954
1031
  break;
955
1032
  case "bottom":
956
- var dt = ( last_pos[ 1 ] - e.y );
1033
+ var dt = ( lastMousePosition[ 1 ] - e.y );
957
1034
  var size = ( that.root.offsetHeight + dt );
958
1035
  that.root.style.height = size + "px";
959
1036
  break;
960
1037
  }
961
-
962
- last_pos[ 0 ] = e.x;
963
- last_pos[ 1 ] = e.y;
1038
+
1039
+ lastMousePosition[ 0 ] = e.x;
1040
+ lastMousePosition[ 1 ] = e.y;
964
1041
  e.stopPropagation();
965
1042
  e.preventDefault();
966
-
967
- // Resize events
1043
+
1044
+ // Resize events
968
1045
  if( that.onresize )
1046
+ {
969
1047
  that.onresize( that.root.getBoundingClientRect() );
1048
+ }
970
1049
  }
971
1050
 
972
1051
  function inner_mouseup( e )
@@ -975,7 +1054,7 @@ class Area {
975
1054
  doc.removeEventListener( 'mousemove', inner_mousemove );
976
1055
  doc.removeEventListener( 'mouseup', inner_mouseup );
977
1056
  document.body.classList.remove( 'nocursor' );
978
- that.split_bar.classList.remove( 'nocursor' );
1057
+ that.splitBar.classList.remove( 'nocursor' );
979
1058
  }
980
1059
  }
981
1060
  }
@@ -989,13 +1068,16 @@ class Area {
989
1068
  attach( content ) {
990
1069
 
991
1070
  // Append to last split section if area has been split
992
- if(this.sections.length) {
993
- this.sections[1].attach( content );
1071
+ if( this.sections.length)
1072
+ {
1073
+ this.sections[ 1 ].attach( content );
994
1074
  return;
995
1075
  }
996
1076
 
997
- if(!content)
998
- throw("no content to attach");
1077
+ if( !content )
1078
+ {
1079
+ throw("no content to attach");
1080
+ }
999
1081
 
1000
1082
  content.parent = this;
1001
1083
 
@@ -1005,11 +1087,11 @@ class Area {
1005
1087
 
1006
1088
  /**
1007
1089
  * @method split
1008
- * @param {*} options
1090
+ * @param {*} options
1009
1091
  * type: Split mode (horizontal, vertical) ["horizontal"]
1010
1092
  * sizes: Size of each new area (Array) ["50%", "50%"]
1011
1093
  */
1012
-
1094
+
1013
1095
  split( options = {} ) {
1014
1096
 
1015
1097
  if( this.sections.length )
@@ -1022,7 +1104,6 @@ class Area {
1022
1104
 
1023
1105
  var type = options.type || "horizontal";
1024
1106
  var sizes = options.sizes || [ "50%", "50%" ];
1025
- var infer_height = false;
1026
1107
  var auto = (options.sizes === 'auto');
1027
1108
 
1028
1109
  if( !sizes[ 1 ] )
@@ -1034,20 +1115,19 @@ class Area {
1034
1115
  size += margin;
1035
1116
  size += "px";
1036
1117
  }
1037
-
1118
+
1038
1119
  sizes[ 1 ] = "calc( 100% - " + size + " )";
1039
- infer_height = true;
1040
1120
  }
1041
1121
 
1042
1122
  // Create areas
1043
- var area1 = new Area( { no_append: true, className: "split" + ( options.menubar || options.sidebar ? "" : " origin" ) } );
1044
- var area2 = new Area( { no_append: true, className: "split"} );
1123
+ var area1 = new Area( { skipAppend: true, className: "split" + ( options.menubar || options.sidebar ? "" : " origin" ) } );
1124
+ var area2 = new Area( { skipAppend: true, className: "split" } );
1045
1125
 
1046
1126
  area1.parentArea = this;
1047
1127
  area2.parentArea = this;
1048
1128
 
1049
1129
  let minimizable = options.minimizable ?? false;
1050
- let resize = (options.resize ?? true) || minimizable;
1130
+ let resize = ( options.resize ?? true ) || minimizable;
1051
1131
 
1052
1132
  var data = "0px";
1053
1133
  this.offset = 0;
@@ -1055,40 +1135,40 @@ class Area {
1055
1135
  if( resize )
1056
1136
  {
1057
1137
  this.resize = resize;
1058
- this.split_bar = document.createElement( "div" );
1059
- this.split_bar.className = "lexsplitbar " + type;
1138
+ this.splitBar = document.createElement( "div" );
1139
+ this.splitBar.className = "lexsplitbar " + type;
1060
1140
 
1061
1141
  if( type == "horizontal" )
1062
1142
  {
1063
- this.split_bar.style.width = LX.DEFAULT_SPLITBAR_SIZE + "px";
1143
+ this.splitBar.style.width = LX.DEFAULT_SPLITBAR_SIZE + "px";
1064
1144
  }
1065
1145
  else
1066
1146
  {
1067
- this.split_bar.style.height = LX.DEFAULT_SPLITBAR_SIZE + "px";
1147
+ this.splitBar.style.height = LX.DEFAULT_SPLITBAR_SIZE + "px";
1068
1148
  }
1069
1149
 
1070
- this.split_bar.addEventListener( 'mousedown', inner_mousedown );
1150
+ this.splitBar.addEventListener( 'mousedown', innerMouseDown );
1071
1151
 
1072
1152
  data = ( LX.DEFAULT_SPLITBAR_SIZE / 2 ) + "px"; // updates
1073
1153
 
1074
1154
  // Being minimizable means it's also resizeable!
1075
1155
  if( minimizable )
1076
1156
  {
1077
- this.split_extended = false;
1157
+ this.splitExtended = false;
1078
1158
 
1079
1159
  // Keep state of the animation when ends...
1080
1160
  area2.root.addEventListener('animationend', e => {
1081
- const opacity = getComputedStyle(area2.root).opacity;
1161
+ const opacity = getComputedStyle( area2.root ).opacity;
1082
1162
  area2.root.classList.remove( e.animationName + "-" + type );
1083
1163
  area2.root.style.opacity = opacity;
1084
1164
  flushCss(area2.root);
1085
1165
  });
1086
1166
 
1087
- this.split_bar.addEventListener("contextmenu", e => {
1167
+ this.splitBar.addEventListener("contextmenu", e => {
1088
1168
  e.preventDefault();
1089
1169
  addContextMenu(null, e, c => {
1090
- c.add("Extend", { disabled: this.split_extended, callback: () => { this.extend() } });
1091
- c.add("Reduce", { disabled: !this.split_extended, callback: () => { this.reduce() } });
1170
+ c.add("Extend", { disabled: this.splitExtended, callback: () => { this.extend() } });
1171
+ c.add("Reduce", { disabled: !this.splitExtended, callback: () => { this.reduce() } });
1092
1172
  });
1093
1173
  });
1094
1174
  }
@@ -1100,9 +1180,14 @@ class Area {
1100
1180
  width2 = sizes[ 1 ];
1101
1181
 
1102
1182
  if( width1.constructor == Number )
1183
+ {
1103
1184
  width1 += "px";
1185
+ }
1186
+
1104
1187
  if( width2.constructor == Number )
1188
+ {
1105
1189
  width2 += "px";
1190
+ }
1106
1191
 
1107
1192
  area1.root.style.width = "calc( " + width1 + " - " + data + " )";
1108
1193
  area1.root.style.height = "calc(100% - 0px)";
@@ -1115,7 +1200,7 @@ class Area {
1115
1200
  area1.root.style.width = "100%";
1116
1201
  area2.root.style.width = "100%";
1117
1202
 
1118
- if(auto)
1203
+ if( auto )
1119
1204
  {
1120
1205
  area1.root.style.height = "auto";
1121
1206
 
@@ -1134,10 +1219,15 @@ class Area {
1134
1219
  var height1 = sizes[ 0 ],
1135
1220
  height2 = sizes[ 1 ];
1136
1221
 
1137
- if(height1.constructor == Number)
1222
+ if( height1.constructor == Number )
1223
+ {
1138
1224
  height1 += "px";
1139
- if(height2.constructor == Number)
1225
+ }
1226
+
1227
+ if( height2.constructor == Number )
1228
+ {
1140
1229
  height2 += "px";
1230
+ }
1141
1231
 
1142
1232
  area1.root.style.width = "100%";
1143
1233
  area1.root.style.height = "calc( " + height1 + " - " + data + " )";
@@ -1149,11 +1239,11 @@ class Area {
1149
1239
 
1150
1240
  if( resize )
1151
1241
  {
1152
- this.root.appendChild(this.split_bar);
1242
+ this.root.appendChild(this.splitBar);
1153
1243
  }
1154
1244
 
1155
1245
  this.root.appendChild( area2.root );
1156
- this.sections = [area1, area2];
1246
+ this.sections = [ area1, area2 ];
1157
1247
  this.type = type;
1158
1248
 
1159
1249
  // Update sizes
@@ -1165,34 +1255,28 @@ class Area {
1165
1255
  }
1166
1256
 
1167
1257
  var that = this;
1168
- var last_pos = [ 0, 0 ];
1169
1258
 
1170
- function inner_mousedown( e )
1259
+ function innerMouseDown( e )
1171
1260
  {
1172
1261
  var doc = that.root.ownerDocument;
1173
- doc.addEventListener( 'mousemove', inner_mousemove );
1174
- doc.addEventListener( 'mouseup', inner_mouseup );
1175
- last_pos[0] = e.x;
1176
- last_pos[1] = e.y;
1262
+ doc.addEventListener( 'mousemove', innerMouseMove );
1263
+ doc.addEventListener( 'mouseup', innerMouseUp );
1177
1264
  e.stopPropagation();
1178
1265
  e.preventDefault();
1179
1266
  document.body.classList.add( 'nocursor' );
1180
- that.split_bar.classList.add( 'nocursor' );
1267
+ that.splitBar.classList.add( 'nocursor' );
1181
1268
  }
1182
1269
 
1183
- function inner_mousemove( e )
1270
+ function innerMouseMove( e )
1184
1271
  {
1185
- if(that.type == "horizontal")
1272
+ if( that.type == "horizontal" )
1186
1273
  {
1187
- that._moveSplit( last_pos[ 0 ] - e.x );
1274
+ that._moveSplit( -e.movementX );
1188
1275
  }
1189
1276
  else
1190
1277
  {
1191
- that._moveSplit( last_pos[ 1 ] - e.y );
1278
+ that._moveSplit( -e.movementY );
1192
1279
  }
1193
-
1194
- last_pos[ 0 ] = e.x;
1195
- last_pos[ 1 ] = e.y;
1196
1280
 
1197
1281
  const widgets = that.root.querySelectorAll( ".lexwidget" );
1198
1282
 
@@ -1211,13 +1295,13 @@ class Area {
1211
1295
  e.preventDefault();
1212
1296
  }
1213
1297
 
1214
- function inner_mouseup( e )
1298
+ function innerMouseUp( e )
1215
1299
  {
1216
1300
  var doc = that.root.ownerDocument;
1217
- doc.removeEventListener( 'mousemove', inner_mousemove );
1218
- doc.removeEventListener( 'mouseup', inner_mouseup );
1301
+ doc.removeEventListener( 'mousemove', innerMouseMove );
1302
+ doc.removeEventListener( 'mouseup', innerMouseUp );
1219
1303
  document.body.classList.remove( 'nocursor' );
1220
- that.split_bar.classList.remove( 'nocursor' );
1304
+ that.splitBar.classList.remove( 'nocursor' );
1221
1305
  }
1222
1306
 
1223
1307
  return this.sections;
@@ -1240,7 +1324,7 @@ class Area {
1240
1324
  * Resize element
1241
1325
  */
1242
1326
  setSize( size ) {
1243
-
1327
+
1244
1328
  let [ width, height ] = size;
1245
1329
 
1246
1330
  if( width != undefined && width.constructor == Number )
@@ -1274,22 +1358,22 @@ class Area {
1274
1358
  */
1275
1359
  extend() {
1276
1360
 
1277
- if( this.split_extended )
1361
+ if( this.splitExtended )
1278
1362
  {
1279
1363
  return;
1280
1364
  }
1281
1365
 
1282
1366
  let [area1, area2] = this.sections;
1283
- this.split_extended = true;
1367
+ this.splitExtended = true;
1284
1368
 
1285
1369
  if(this.type == "vertical")
1286
1370
  {
1287
1371
  this.offset = area2.root.offsetHeight;
1288
1372
  area2.root.classList.add("fadeout-vertical");
1289
- this._moveSplit(-Infinity, true);
1373
+ this._moveSplit(-Infinity, true);
1290
1374
 
1291
1375
  }
1292
- else
1376
+ else
1293
1377
  {
1294
1378
  this.offset = area2.root.offsetWidth - 8; // Force some height here...
1295
1379
  area2.root.classList.add("fadeout-horizontal");
@@ -1306,10 +1390,10 @@ class Area {
1306
1390
  */
1307
1391
  reduce() {
1308
1392
 
1309
- if( !this.split_extended )
1393
+ if( !this.splitExtended )
1310
1394
  return;
1311
-
1312
- this.split_extended = false;
1395
+
1396
+ this.splitExtended = false;
1313
1397
  let [area1, area2] = this.sections;
1314
1398
 
1315
1399
  if(this.type == "vertical")
@@ -1387,7 +1471,7 @@ class Area {
1387
1471
  */
1388
1472
 
1389
1473
  addMenubar( callback, options = {} ) {
1390
-
1474
+
1391
1475
  let menubar = new Menubar(options);
1392
1476
 
1393
1477
  if(callback) callback( menubar );
@@ -1431,10 +1515,11 @@ class Area {
1431
1515
  */
1432
1516
 
1433
1517
  addOverlayButtons( buttons, options = {} ) {
1434
-
1518
+
1435
1519
  // Add to last split section if area has been split
1436
- if(this.sections.length) {
1437
- this.sections[1].addOverlayButtons( buttons, options );
1520
+ if( this.sections.length )
1521
+ {
1522
+ this.sections[ 1 ].addOverlayButtons( buttons, options );
1438
1523
  return;
1439
1524
  }
1440
1525
 
@@ -1444,8 +1529,14 @@ class Area {
1444
1529
  this.root.style.position = "relative";
1445
1530
 
1446
1531
  options.className = "lexoverlaybuttons";
1447
- options.width = "calc( 100% - 24px )";
1448
- options.height = "auto";
1532
+
1533
+ let overlayPanel = this.addPanel( options );
1534
+ let overlayGroup = null;
1535
+
1536
+ const container = document.createElement("div");
1537
+ container.className = "lexoverlaybuttonscontainer";
1538
+ container.appendChild( overlayPanel.root );
1539
+ this.attach( container );
1449
1540
 
1450
1541
  const float = options.float;
1451
1542
 
@@ -1457,24 +1548,21 @@ class Area {
1457
1548
  switch( t )
1458
1549
  {
1459
1550
  case 'h': break;
1460
- case 'v': options.className += " vertical"; break;
1551
+ case 'v': container.className += " vertical"; break;
1461
1552
  case 't': break;
1462
- case 'm': options.className += " middle"; break;
1463
- case 'b': options.className += " bottom"; break;
1553
+ case 'm': container.className += " middle"; break;
1554
+ case 'b': container.className += " bottom"; break;
1464
1555
  case 'l': break;
1465
- case 'c': options.className += " center"; break;
1466
- case 'r': options.className += " right"; break;
1556
+ case 'c': container.className += " center"; break;
1557
+ case 'r': container.className += " right"; break;
1467
1558
  }
1468
1559
  }
1469
1560
  }
1470
1561
 
1471
- let overlayPanel = this.addPanel( options );
1472
- let overlaygroup;
1473
-
1474
- const add_button = function(b, group, last) {
1562
+ const _addButton = function( b, group, last ) {
1475
1563
 
1476
- const _options = {
1477
- width: "auto",
1564
+ const _options = {
1565
+ width: "auto",
1478
1566
  selectable: b.selectable,
1479
1567
  selected: b.selected,
1480
1568
  icon: b.icon,
@@ -1484,55 +1572,54 @@ class Area {
1484
1572
 
1485
1573
  if( group )
1486
1574
  {
1487
- if(!overlaygroup) {
1488
- overlaygroup = document.createElement('div');
1489
- overlaygroup.className = "lexoverlaygroup";
1490
- overlayPanel.queuedContainer = overlaygroup;
1575
+ if( !overlayGroup )
1576
+ {
1577
+ overlayGroup = document.createElement('div');
1578
+ overlayGroup.className = "lexoverlaygroup";
1579
+ overlayPanel.queuedContainer = overlayGroup;
1491
1580
  }
1492
1581
 
1493
- _options.parent = overlaygroup;
1582
+ _options.parent = overlayGroup;
1494
1583
  }
1495
1584
 
1496
1585
  let callback = b.callback;
1497
1586
 
1498
1587
  if( b.options )
1499
1588
  {
1500
- callback = function(value, event) {
1501
- LX.addContextMenu(null, event, function(c) {
1502
- for( let o of b.options )
1503
- c.add(o, () => {
1504
- if( b.name == o ) return;
1505
- b.name = o;
1506
- b.callback( o );
1507
- refresh_panel();
1508
- });
1509
- });
1510
- };
1589
+ overlayPanel.addDropdown( null, b.options, b.name, callback, _options );
1511
1590
  }
1512
-
1513
- overlayPanel.addButton( null, b.name, function(value, event) {
1514
- if(b.selectable) {
1515
- if( b.group ) {
1516
- let _prev = b.selected;
1517
- b.group.forEach( sub => sub.selected = false );
1518
- b.selected = !_prev;
1591
+ else
1592
+ {
1593
+ overlayPanel.addButton( null, b.name, function( value, event ) {
1594
+ if( b.selectable )
1595
+ {
1596
+ if( b.group )
1597
+ {
1598
+ let _prev = b.selected;
1599
+ b.group.forEach( sub => sub.selected = false );
1600
+ b.selected = !_prev;
1601
+ }
1602
+ else
1603
+ {
1604
+ b.selected = !b.selected;
1605
+ }
1519
1606
  }
1520
- else
1521
- b.selected = !b.selected;
1522
- }
1523
- callback( value, event );
1524
- }, _options );
1607
+
1608
+ callback( value, event );
1609
+
1610
+ }, _options );
1611
+ }
1525
1612
 
1526
1613
  // ends the group
1527
- if(overlaygroup && last)
1614
+ if( overlayGroup && last )
1528
1615
  {
1529
- overlayPanel.root.appendChild( overlaygroup );
1530
- overlaygroup = null;
1616
+ overlayPanel.root.appendChild( overlayGroup );
1617
+ overlayGroup = null;
1531
1618
  overlayPanel.clearQueue();
1532
1619
  }
1533
1620
  }
1534
1621
 
1535
- const refresh_panel = function() {
1622
+ const _refreshPanel = function() {
1536
1623
 
1537
1624
  overlayPanel.clear();
1538
1625
 
@@ -1542,13 +1629,14 @@ class Area {
1542
1629
  {
1543
1630
  for( let i = 0; i < b.length; ++i )
1544
1631
  {
1545
- let sub = b[i];
1632
+ let sub = b[ i ];
1546
1633
  sub.group = b;
1547
- add_button(sub, true, i == (b.length - 1));
1634
+ _addButton(sub, true, i == ( b.length - 1 ));
1548
1635
  }
1549
- }else
1636
+ }
1637
+ else
1550
1638
  {
1551
- add_button(b);
1639
+ _addButton( b );
1552
1640
  }
1553
1641
 
1554
1642
  }
@@ -1559,16 +1647,16 @@ class Area {
1559
1647
  var height = 0;
1560
1648
  overlayPanel.root.childNodes.forEach( c => { height += c.offsetHeight; } );
1561
1649
 
1562
- if( options.className.includes("middle") )
1650
+ if( container.className.includes( "middle" ) )
1563
1651
  {
1564
- overlayPanel.root.style.top = "-moz-calc( 50% - " + (height * 0.5) + "px )";
1565
- overlayPanel.root.style.top = "-webkit-calc( 50% - " + (height * 0.5) + "px )";
1566
- overlayPanel.root.style.top = "calc( 50% - " + (height * 0.5) + "px )";
1652
+ container.style.top = "-moz-calc( 50% - " + (height * 0.5) + "px )";
1653
+ container.style.top = "-webkit-calc( 50% - " + (height * 0.5) + "px )";
1654
+ container.style.top = "calc( 50% - " + (height * 0.5) + "px )";
1567
1655
  }
1568
1656
  }
1569
1657
  }
1570
1658
 
1571
- refresh_panel();
1659
+ _refreshPanel();
1572
1660
  }
1573
1661
 
1574
1662
  /**
@@ -1584,73 +1672,85 @@ class Area {
1584
1672
  {
1585
1673
  this.parentArea._disableSplitResize();
1586
1674
  // Compensate split bar...
1587
- this.root.style.paddingTop = "4px";
1675
+ this.root.style.paddingTop = "4px";
1588
1676
  }
1589
1677
 
1590
1678
  return tabs;
1591
1679
  }
1592
1680
 
1593
- _moveSplit( dt, force_animation = false, force_width = 0 ) {
1681
+ _moveSplit( dt, forceAnimation = false, forceWidth = 0 ) {
1594
1682
 
1595
1683
  if( !this.type )
1684
+ {
1596
1685
  throw( "No split area" );
1686
+ }
1597
1687
 
1598
1688
  if( dt === undefined ) // Splitbar didn't move!
1689
+ {
1599
1690
  return;
1691
+ }
1692
+
1693
+ const a1 = this.sections[ 0 ];
1694
+ var a1Root = a1.root;
1695
+
1696
+ if( !a1Root.classList.contains( "origin" ) )
1697
+ {
1698
+ a1Root = a1Root.parentElement;
1699
+ }
1600
1700
 
1601
- var a1 = this.sections[ 0 ];
1602
- var a2 = this.sections[ 1 ];
1603
- var splitinfo = " - "+ LX.DEFAULT_SPLITBAR_SIZE + "px";
1701
+ const a2 = this.sections[ 1 ];
1702
+ const a2Root = a2.root;
1703
+ const splitData = " - "+ LX.DEFAULT_SPLITBAR_SIZE + "px";
1604
1704
 
1605
1705
  let transition = null;
1606
- if( !force_animation )
1706
+ if( !forceAnimation )
1607
1707
  {
1608
1708
  // Remove transitions for this change..
1609
- transition = a1.root.style.transition;
1610
- a1.root.style.transition = a2.root.style.transition = "none";
1611
- flushCss( a1.root );
1612
- flushCss( a2.root );
1709
+ transition = a1Root.style.transition;
1710
+ a1Root.style.transition = a2Root.style.transition = "none";
1711
+ flushCss( a1Root );
1712
+ flushCss( a2Root );
1613
1713
  }
1614
1714
 
1615
1715
  if( this.type == "horizontal" )
1616
1716
  {
1617
- var size = Math.max( a2.root.offsetWidth + dt, parseInt( a2.minWidth ) );
1618
- if( force_width ) size = force_width;
1619
- a1.root.style.width = "-moz-calc( 100% - " + size + "px " + splitinfo + " )";
1620
- a1.root.style.width = "-webkit-calc( 100% - " + size + "px " + splitinfo + " )";
1621
- a1.root.style.width = "calc( 100% - " + size + "px " + splitinfo + " )";
1622
- a1.root.style.minWidth = parseInt( a1.minWidth ) + "px";
1623
- a2.root.style.width = size + "px";
1624
- if( a1.maxWidth != Infinity ) a2.root.style.minWidth = "calc( 100% - " + parseInt( a1.maxWidth ) + "px" + " )";
1717
+ var size = Math.max( a2Root.offsetWidth + dt, parseInt( a2.minWidth ) );
1718
+ if( forceWidth ) size = forceWidth;
1719
+ a1Root.style.width = "-moz-calc( 100% - " + size + "px " + splitData + " )";
1720
+ a1Root.style.width = "-webkit-calc( 100% - " + size + "px " + splitData + " )";
1721
+ a1Root.style.width = "calc( 100% - " + size + "px " + splitData + " )";
1722
+ a1Root.style.minWidth = parseInt( a1.minWidth ) + "px";
1723
+ a2Root.style.width = size + "px";
1724
+ if( a1.maxWidth != Infinity ) a2Root.style.minWidth = "calc( 100% - " + parseInt( a1.maxWidth ) + "px" + " )";
1625
1725
  }
1626
1726
  else
1627
1727
  {
1628
- var size = Math.max((a2.root.offsetHeight + dt) + a2.offset, parseInt(a2.minHeight));
1629
- if( force_width ) size = force_width;
1630
- a1.root.style.height = "-moz-calc( 100% - " + size + "px " + splitinfo + " )";
1631
- a1.root.style.height = "-webkit-calc( 100% - " + size + "px " + splitinfo + " )";
1632
- a1.root.style.height = "calc( 100% - " + size + "px " + splitinfo + " )";
1633
- a1.root.style.minHeight = a1.minHeight + "px";
1634
- a2.root.style.height = ( size - a2.offset ) + "px";
1728
+ var size = Math.max((a2Root.offsetHeight + dt) + a2.offset, parseInt(a2.minHeight));
1729
+ if( forceWidth ) size = forceWidth;
1730
+ a1Root.style.height = "-moz-calc( 100% - " + size + "px " + splitData + " )";
1731
+ a1Root.style.height = "-webkit-calc( 100% - " + size + "px " + splitData + " )";
1732
+ a1Root.style.height = "calc( 100% - " + size + "px " + splitData + " )";
1733
+ a1Root.style.minHeight = a1.minHeight + "px";
1734
+ a2Root.style.height = ( size - a2.offset ) + "px";
1635
1735
  }
1636
-
1637
- if( !force_animation )
1736
+
1737
+ if( !forceAnimation )
1638
1738
  {
1639
1739
  // Reapply transitions
1640
- a1.root.style.transition = a2.root.style.transition = transition;
1740
+ a1Root.style.transition = a2Root.style.transition = transition;
1641
1741
  }
1642
1742
 
1643
1743
  this._update();
1644
1744
 
1645
- // Resize events
1745
+ // Resize events
1646
1746
  this.propagateEvent( 'onresize' );
1647
1747
  }
1648
1748
 
1649
1749
  _disableSplitResize() {
1650
1750
 
1651
1751
  this.resize = false;
1652
- this.split_bar.remove();
1653
- delete this.split_bar;
1752
+ this.splitBar.remove();
1753
+ delete this.splitBar;
1654
1754
  }
1655
1755
 
1656
1756
  _update() {
@@ -1724,7 +1824,7 @@ class Tabs {
1724
1824
 
1725
1825
  // Show on drop
1726
1826
  el.click();
1727
-
1827
+
1728
1828
  // Store info
1729
1829
  that.tabs[ el.dataset["name"] ] = content;
1730
1830
  });
@@ -1765,11 +1865,11 @@ class Tabs {
1765
1865
  }
1766
1866
 
1767
1867
  // debug
1768
- if(folding)
1868
+ if(folding)
1769
1869
  {
1770
1870
  this.folded = true;
1771
1871
  this.folding = folding;
1772
-
1872
+
1773
1873
  if(folding == "up") area.root.insertChildAtIndex(area.sections[1].root, 0);
1774
1874
 
1775
1875
  // Listen resize event on parent area
@@ -1796,7 +1896,7 @@ class Tabs {
1796
1896
  this.root.querySelectorAll( 'span' ).forEach( s => s.classList.remove( 'selected' ) );
1797
1897
  this.area.root.querySelectorAll( '.lextabcontent' ).forEach( c => c.style.display = 'none' );
1798
1898
  }
1799
-
1899
+
1800
1900
  isSelected = !Object.keys( this.tabs ).length && !this.folding ? true : isSelected;
1801
1901
 
1802
1902
  let contentEl = content.root ? content.root : content;
@@ -1824,7 +1924,7 @@ class Tabs {
1824
1924
  tabEl.className = "lexareatab" + ( isSelected ? " selected" : "" );
1825
1925
  tabEl.innerHTML = ( options.icon ?? "" ) + name;
1826
1926
  tabEl.id = name.replace( /\s/g, '' ) + Tabs.TAB_ID++;
1827
- tabEl.title = options.title;
1927
+ tabEl.title = options.title ?? "";
1828
1928
  tabEl.selected = isSelected ?? false;
1829
1929
  tabEl.fixed = options.fixed;
1830
1930
  tabEl.instance = this;
@@ -1839,9 +1939,9 @@ class Tabs {
1839
1939
  if( this.parentElement.childNodes.length == 1 )
1840
1940
  {
1841
1941
  this.parentElement.childNodes[ 0 ].click(); // single tab!!
1842
- }
1942
+ }
1843
1943
  } );
1844
-
1944
+
1845
1945
  tabEl.addEventListener("click", e => {
1846
1946
 
1847
1947
  e.preventDefault();
@@ -1852,11 +1952,11 @@ class Tabs {
1852
1952
  // For folding tabs
1853
1953
  const lastValue = tabEl.selected;
1854
1954
  tabEl.parentElement.querySelectorAll( 'span' ).forEach( s => s.selected = false );
1855
- tabEl.selected = !lastValue;
1955
+ tabEl.selected = !lastValue;
1856
1956
  // Manage selected
1857
1957
  tabEl.parentElement.querySelectorAll( 'span' ).forEach( s => s.classList.remove( 'selected' ));
1858
1958
  tabEl.classList.toggle('selected', ( this.folding && tabEl.selected ));
1859
- // Manage visibility
1959
+ // Manage visibility
1860
1960
  tabEl.instance.area.root.querySelectorAll( '.lextabcontent' ).forEach( c => c.style.display = 'none' );
1861
1961
  contentEl.style.display = contentEl.originalDisplay;
1862
1962
  tabEl.instance.selected = tabEl.dataset.name;
@@ -1900,16 +2000,16 @@ class Tabs {
1900
2000
  this.delete( tabEl.dataset[ "name" ] );
1901
2001
  }
1902
2002
  });
1903
-
2003
+
1904
2004
  tabEl.setAttribute( 'draggable', true );
1905
2005
  tabEl.addEventListener( 'dragstart', function( e ) {
1906
2006
  if( this.parentElement.childNodes.length == 1 ){
1907
2007
  e.preventDefault();
1908
2008
  return;
1909
- }
2009
+ }
1910
2010
  e.dataTransfer.setData( 'source', e.target.id );
1911
2011
  });
1912
-
2012
+
1913
2013
  // Attach content
1914
2014
  tabEl.childIndex = ( this.root.childElementCount - 1 );
1915
2015
  this.root.appendChild( tabEl );
@@ -1988,7 +2088,7 @@ class Menubar {
1988
2088
  if(options.float)
1989
2089
  this.root.style.justifyContent = options.float;
1990
2090
  this.items = [];
1991
-
2091
+
1992
2092
  this.icons = {};
1993
2093
  this.shorts = {};
1994
2094
  this.buttons = [];
@@ -2027,7 +2127,7 @@ class Menubar {
2027
2127
  } );
2028
2128
 
2029
2129
  if(found) {
2030
- insert( tokens[idx++], found );
2130
+ insert( tokens[idx++], found );
2031
2131
  }
2032
2132
  else {
2033
2133
  let item = {};
@@ -2040,7 +2140,7 @@ class Menubar {
2040
2140
  item[ 'checked' ] = options.checked;
2041
2141
  }
2042
2142
  list.push( item );
2043
- insert( next_token, item[ token ] );
2143
+ insert( next_token, item[ token ] );
2044
2144
  }
2045
2145
  };
2046
2146
 
@@ -2055,7 +2155,7 @@ class Menubar {
2055
2155
 
2056
2156
  // Item already created
2057
2157
  if( this.root.querySelector("#" + pKey) )
2058
- continue;
2158
+ continue;
2059
2159
 
2060
2160
  let entry = document.createElement('div');
2061
2161
  entry.className = "lexmenuentry";
@@ -2096,21 +2196,21 @@ class Menubar {
2096
2196
  const subitem = o[k][i];
2097
2197
  const subkey = Object.keys(subitem)[0];
2098
2198
  const hasSubmenu = subitem[ subkey ].length;
2099
- const is_checkbox = subitem[ 'type' ] == 'checkbox';
2199
+ const isCheckbox = subitem[ 'type' ] == 'checkbox';
2100
2200
  let subentry = document.createElement('div');
2101
2201
  subentry.className = "lexcontextmenuentry";
2102
2202
  subentry.className += (i == o[k].length - 1 ? " last" : "");
2103
2203
  if(subkey == '')
2104
2204
  subentry.className = " lexseparator";
2105
2205
  else {
2106
-
2206
+
2107
2207
  subentry.id = subkey;
2108
2208
  let subentrycont = document.createElement('div');
2109
2209
  subentrycont.innerHTML = "";
2110
2210
  subentrycont.classList = "lexcontextmenuentrycontainer";
2111
2211
  subentry.appendChild(subentrycont);
2112
2212
  const icon = that.icons[ subkey ];
2113
- if(is_checkbox){
2213
+ if(isCheckbox){
2114
2214
  subentrycont.innerHTML += "<input type='checkbox' >";
2115
2215
  }else if(icon) {
2116
2216
  subentrycont.innerHTML += "<a class='" + icon + " fa-sm'></a>";
@@ -2130,8 +2230,8 @@ class Menubar {
2130
2230
  const f = subitem[ 'callback' ];
2131
2231
  if(f) {
2132
2232
  f.call( this, subitem.checked, subkey, subentry );
2133
- that.root.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
2134
- }
2233
+ that.root.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
2234
+ }
2135
2235
  e.stopPropagation();
2136
2236
  e.stopImmediatePropagation();
2137
2237
  })
@@ -2161,8 +2261,8 @@ class Menubar {
2161
2261
  const f = subitem[ 'callback' ];
2162
2262
  if(f) {
2163
2263
  f.call( this, checkbox_input ? subitem.checked : subkey, checkbox_input ? subkey : subentry );
2164
- that.root.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
2165
- }
2264
+ that.root.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
2265
+ }
2166
2266
  e.stopPropagation();
2167
2267
  e.stopImmediatePropagation();
2168
2268
  });
@@ -2208,7 +2308,7 @@ class Menubar {
2208
2308
  if(f) {
2209
2309
  f.call( this, key, entry );
2210
2310
  return;
2211
- }
2311
+ }
2212
2312
 
2213
2313
  // Manage selected
2214
2314
  this.root.querySelectorAll(".lexmenuentry").forEach( e => e.classList.remove( 'selected' ) );
@@ -2240,7 +2340,7 @@ class Menubar {
2240
2340
  * @param {Array} tokens: split path strings
2241
2341
  */
2242
2342
  getSubitem(item, tokens) {
2243
-
2343
+
2244
2344
  let subitem = null;
2245
2345
  let path = tokens[0];
2246
2346
  for(let i = 0; i < item.length; i++) {
@@ -2254,7 +2354,7 @@ class Menubar {
2254
2354
  tokens.splice(0,1);
2255
2355
  return this.getSubitem(item[i][path], tokens);
2256
2356
  }
2257
-
2357
+
2258
2358
  }
2259
2359
  }
2260
2360
  }
@@ -2266,7 +2366,7 @@ class Menubar {
2266
2366
  getItem( path ) {
2267
2367
  // process path
2268
2368
  const tokens = path.split("/");
2269
-
2369
+
2270
2370
  return this.getSubitem(this.items, tokens)
2271
2371
  }
2272
2372
 
@@ -2378,7 +2478,7 @@ class Menubar {
2378
2478
  }
2379
2479
  else {
2380
2480
  this.root.appendChild( this.buttonContainer );
2381
- }
2481
+ }
2382
2482
  }
2383
2483
 
2384
2484
  for( let i = 0; i < buttons.length; ++i )
@@ -2471,6 +2571,18 @@ class SideBar {
2471
2571
  desc.innerHTML = key;
2472
2572
  entry.appendChild( desc );
2473
2573
 
2574
+ button.addEventListener("mouseenter", () => {
2575
+ setTimeout( () => {
2576
+ desc.style.display = "unset";
2577
+ }, 100 );
2578
+ });
2579
+
2580
+ button.addEventListener("mouseleave", () => {
2581
+ setTimeout( () => {
2582
+ desc.style.display = "none";
2583
+ }, 100 );
2584
+ });
2585
+
2474
2586
  entry.addEventListener("click", () => {
2475
2587
 
2476
2588
  const f = options.callback;
@@ -2512,7 +2624,7 @@ LX.SideBar = SideBar;
2512
2624
  */
2513
2625
 
2514
2626
  class Widget {
2515
-
2627
+
2516
2628
  static NONE = 0;
2517
2629
  static TEXT = 1;
2518
2630
  static TEXTAREA = 2;
@@ -2632,7 +2744,7 @@ class Widget {
2632
2744
  }
2633
2745
 
2634
2746
  refresh() {
2635
-
2747
+
2636
2748
  }
2637
2749
  }
2638
2750
 
@@ -2683,15 +2795,15 @@ function ADD_CUSTOM_WIDGET( custom_widget_name, options = {} )
2683
2795
  buttonName += custom_widget_name + (!instance ? " [empty]" : "");
2684
2796
  // Add alwayis icon to keep spacing right
2685
2797
  buttonName += "<a class='fa-solid " + (instance ? "fa-bars-staggered" : " ") + " menu' style='float:right; width:5%;'></a>";
2686
-
2798
+
2687
2799
  let buttonEl = this.addButton(null, buttonName, (value, event) => {
2688
2800
 
2689
2801
  if( instance ) {
2690
2802
  element.querySelector(".lexcustomitems").toggleAttribute('hidden');
2691
2803
  }
2692
2804
  else {
2693
- addContextMenu(null, event, c => {
2694
- c.add("New " + custom_widget_name, () => {
2805
+ addContextMenu(null, event, c => {
2806
+ c.add("New " + custom_widget_name, () => {
2695
2807
  instance = {};
2696
2808
  refresh_widget();
2697
2809
  element.querySelector(".lexcustomitems").toggleAttribute('hidden', false);
@@ -2700,7 +2812,7 @@ function ADD_CUSTOM_WIDGET( custom_widget_name, options = {} )
2700
2812
  }
2701
2813
 
2702
2814
  }, { buttonClass: 'custom' });
2703
-
2815
+
2704
2816
  this.clearQueue();
2705
2817
 
2706
2818
  if(instance)
@@ -2720,7 +2832,7 @@ function ADD_CUSTOM_WIDGET( custom_widget_name, options = {} )
2720
2832
  custom_widgets = document.createElement('div');
2721
2833
  custom_widgets.className = "lexcustomitems";
2722
2834
  custom_widgets.toggleAttribute('hidden', true);
2723
-
2835
+
2724
2836
  element.appendChild( container );
2725
2837
  element.appendChild( custom_widgets );
2726
2838
 
@@ -2728,7 +2840,7 @@ function ADD_CUSTOM_WIDGET( custom_widget_name, options = {} )
2728
2840
  {
2729
2841
 
2730
2842
  this.queue( custom_widgets );
2731
-
2843
+
2732
2844
  const on_instance_changed = ( key, value, event ) => {
2733
2845
  instance[ key ] = value;
2734
2846
  this._trigger( new IEvent( name, instance, event ), callback );
@@ -2737,7 +2849,7 @@ function ADD_CUSTOM_WIDGET( custom_widget_name, options = {} )
2737
2849
  for( let key in default_instance )
2738
2850
  {
2739
2851
  const value = instance[ key ] ?? default_instance[ key ];
2740
-
2852
+
2741
2853
  switch( value.constructor )
2742
2854
  {
2743
2855
  case String:
@@ -2784,7 +2896,7 @@ LX.ADD_CUSTOM_WIDGET = ADD_CUSTOM_WIDGET;
2784
2896
  */
2785
2897
 
2786
2898
  class NodeTree {
2787
-
2899
+
2788
2900
  constructor( domEl, data, options ) {
2789
2901
 
2790
2902
  this.domEl = domEl;
@@ -2821,7 +2933,7 @@ class NodeTree {
2821
2933
  node.parent = parent;
2822
2934
  let isParent = node.children.length > 0;
2823
2935
  let isSelected = this.selected.indexOf( node ) > -1 || node.selected;
2824
-
2936
+
2825
2937
  if( this.options.onlyFolders )
2826
2938
  {
2827
2939
  let has_folders = false;
@@ -2838,7 +2950,7 @@ class NodeTree {
2838
2950
  let icon = (this.options.skip_default_icon ?? true) ? "" : "fa-solid fa-square"; // Default: no childs
2839
2951
  if( isParent ) icon = node.closed ? "fa-solid fa-caret-right" : "fa-solid fa-caret-down";
2840
2952
  item.innerHTML = "<a class='" + icon + " hierarchy'></a>";
2841
-
2953
+
2842
2954
  // Add display icon
2843
2955
  icon = node.icon;
2844
2956
 
@@ -2873,7 +2985,7 @@ class NodeTree {
2873
2985
  list.querySelectorAll( "li" ).forEach( e => { e.classList.remove( 'selected' ); } );
2874
2986
  this.selected.length = 0;
2875
2987
  }
2876
-
2988
+
2877
2989
  // Add or remove
2878
2990
  const idx = this.selected.indexOf( node );
2879
2991
  if( idx > -1 ) {
@@ -3040,7 +3152,7 @@ class NodeTree {
3040
3152
  let name_input = document.createElement('input');
3041
3153
  name_input.toggleAttribute('hidden', !node.rename);
3042
3154
  name_input.value = node.id;
3043
- item.appendChild(name_input);
3155
+ item.appendChild(name_input);
3044
3156
 
3045
3157
  if(node.rename) {
3046
3158
  item.classList.add('selected');
@@ -3133,13 +3245,13 @@ class NodeTree {
3133
3245
  delete window.__tree_node_dragged;
3134
3246
  });
3135
3247
  }
3136
-
3248
+
3137
3249
  let handled = false;
3138
3250
 
3139
3251
  // Show/hide children
3140
3252
  if(isParent) {
3141
3253
  item.querySelector('a.hierarchy').addEventListener("click", function(e) {
3142
-
3254
+
3143
3255
  handled = true;
3144
3256
  e.stopImmediatePropagation();
3145
3257
  e.stopPropagation();
@@ -3173,8 +3285,8 @@ class NodeTree {
3173
3285
 
3174
3286
  item.appendChild(visibility);
3175
3287
  }
3176
-
3177
- if(node.actions)
3288
+
3289
+ if(node.actions)
3178
3290
  {
3179
3291
  for(var i = 0; i < node.actions.length; ++i) {
3180
3292
  let a = node.actions[i];
@@ -3233,11 +3345,12 @@ class NodeTree {
3233
3345
  class Panel {
3234
3346
 
3235
3347
  /**
3236
- * @param {*} options
3348
+ * @param {*} options
3237
3349
  * id: Id of the element
3238
3350
  * className: Add class to the element
3239
3351
  * width: Width of the panel element [fit space]
3240
3352
  * height: Height of the panel element [fit space]
3353
+ * style: CSS Style object to be applied to the panel
3241
3354
  */
3242
3355
 
3243
3356
  constructor( options = {} ) {
@@ -3384,7 +3497,7 @@ class Panel {
3384
3497
  this._inlineContainer.style.justifyContent = justifyContent;
3385
3498
  }
3386
3499
  }
3387
-
3500
+
3388
3501
  // Push all elements single element or Array[element, container]
3389
3502
  for( let item of this._inlineWidgets )
3390
3503
  {
@@ -3392,17 +3505,17 @@ class Panel {
3392
3505
 
3393
3506
  if(is_pair)
3394
3507
  {
3395
- // eg. an array, inline items appended later to
3508
+ // eg. an array, inline items appended later to
3396
3509
  if(this._inline_queued_container)
3397
3510
  this._inlineContainer.appendChild( item[0] );
3398
3511
  // eg. a dropdown, item is appended to parent, not to inline cont.
3399
3512
  else
3400
3513
  item[1].appendChild(item[0]);
3401
- }
3514
+ }
3402
3515
  else
3403
3516
  this._inlineContainer.appendChild( item );
3404
3517
  }
3405
-
3518
+
3406
3519
  if(!this._inline_queued_container)
3407
3520
  {
3408
3521
  if(this.current_branch)
@@ -3422,7 +3535,7 @@ class Panel {
3422
3535
  /**
3423
3536
  * @method branch
3424
3537
  * @param {String} name Name of the branch/section
3425
- * @param {*} options
3538
+ * @param {*} options
3426
3539
  * id: Id of the branch
3427
3540
  * className: Add class to the branch
3428
3541
  * closed: Set branch collapsed/opened [false]
@@ -3549,7 +3662,7 @@ class Panel {
3549
3662
  widget.oncontextmenu( e );
3550
3663
  });
3551
3664
  }
3552
-
3665
+
3553
3666
  this.widgets[ name ] = widget;
3554
3667
  }
3555
3668
 
@@ -3578,7 +3691,7 @@ class Panel {
3578
3691
 
3579
3692
  if(this.current_branch)
3580
3693
  {
3581
- if(!options.skipWidget)
3694
+ if(!options.skipWidget)
3582
3695
  this.current_branch.widgets.push( widget );
3583
3696
  this.current_branch.content.appendChild( el );
3584
3697
  }
@@ -3587,7 +3700,7 @@ class Panel {
3587
3700
  el.classList.add("nobranch");
3588
3701
  this.root.appendChild( el );
3589
3702
  }
3590
- }
3703
+ }
3591
3704
  // Append content to queued tab container
3592
3705
  else {
3593
3706
  this.queuedContainer.appendChild( el );
@@ -3598,7 +3711,7 @@ class Panel {
3598
3711
 
3599
3712
  if(!this.queuedContainer) {
3600
3713
  this._inlineWidgets.push( el );
3601
- }
3714
+ }
3602
3715
  // Append content to queued tab container
3603
3716
  else {
3604
3717
  this._inlineWidgets.push( [el, this.queuedContainer] );
@@ -3637,7 +3750,7 @@ class Panel {
3637
3750
  let widget = this.create_widget(null, Widget.TEXT, options);
3638
3751
  let element = widget.domEl;
3639
3752
  element.className += " lexfilter noname";
3640
-
3753
+
3641
3754
  let input = document.createElement('input');
3642
3755
  input.className = 'lexinput-filter';
3643
3756
  input.setAttribute("placeholder", options.placeholder);
@@ -3649,9 +3762,9 @@ class Panel {
3649
3762
  element.appendChild(input);
3650
3763
  element.appendChild(searchIcon);
3651
3764
 
3652
- input.addEventListener("input", (e) => {
3765
+ input.addEventListener("input", (e) => {
3653
3766
  if(options.callback)
3654
- options.callback(input.value, e);
3767
+ options.callback(input.value, e);
3655
3768
  });
3656
3769
 
3657
3770
  return element;
@@ -3663,7 +3776,7 @@ class Panel {
3663
3776
 
3664
3777
  if(b.name !== branchName)
3665
3778
  continue;
3666
-
3779
+
3667
3780
  // remove all widgets
3668
3781
  for( let w of b.widgets ) {
3669
3782
  if(w.domEl.classList.contains('lexfilter'))
@@ -3875,7 +3988,7 @@ class Panel {
3875
3988
  Panel._dispatch_event( wValue, "focusout" );
3876
3989
  } );
3877
3990
  }
3878
-
3991
+
3879
3992
  // Add widget value
3880
3993
 
3881
3994
  let container = document.createElement( 'div' );
@@ -3953,7 +4066,7 @@ class Panel {
3953
4066
 
3954
4067
  container.appendChild( wValue );
3955
4068
  element.appendChild( container );
3956
-
4069
+
3957
4070
  // Remove branch padding and margins
3958
4071
  if( !widget.name ) {
3959
4072
  element.className += " noname";
@@ -4000,7 +4113,7 @@ class Panel {
4000
4113
  Panel._dispatch_event( wValue, "focusout" );
4001
4114
  });
4002
4115
  }
4003
-
4116
+
4004
4117
  // Add widget value
4005
4118
 
4006
4119
  let container = document.createElement( 'div' );
@@ -4053,7 +4166,7 @@ class Panel {
4053
4166
 
4054
4167
  container.appendChild(wValue);
4055
4168
  element.appendChild(container);
4056
-
4169
+
4057
4170
  // Remove branch padding and margins
4058
4171
  if(!widget.name) {
4059
4172
  element.className += " noname";
@@ -4082,7 +4195,7 @@ class Panel {
4082
4195
  options.disabled = true;
4083
4196
  return this.addText( null, value, null, options );
4084
4197
  }
4085
-
4198
+
4086
4199
  /**
4087
4200
  * @method addButton
4088
4201
  * @param {String} name Widget name
@@ -4092,52 +4205,69 @@ class Panel {
4092
4205
  * disabled: Make the widget disabled [false]
4093
4206
  * icon: Icon class to show as button value
4094
4207
  * img: Path to image to show as button value
4208
+ * title: Text to show in native Element title
4095
4209
  */
4096
4210
 
4097
4211
  addButton( name, value, callback, options = {} ) {
4098
4212
 
4099
- let widget = this.create_widget(name, Widget.BUTTON, options);
4213
+ let widget = this.create_widget( name, Widget.BUTTON, options );
4214
+
4100
4215
  widget.onGetValue = () => {
4101
4216
  return wValue.innerText;
4102
4217
  };
4218
+
4103
4219
  widget.onSetValue = ( newValue, skipCallback ) => {
4104
- wValue.innerHTML =
4105
- (options.icon ? "<a class='" + options.icon + "'></a>" :
4220
+ wValue.innerHTML =
4221
+ (options.icon ? "<a class='" + options.icon + "'></a>" :
4106
4222
  ( options.img ? "<img src='" + options.img + "'>" : "<span>" + (newValue || "") + "</span>" ));
4107
4223
  };
4108
4224
 
4109
4225
  let element = widget.domEl;
4110
4226
 
4111
- var wValue = document.createElement('button');
4112
- if(options.icon || options.img)
4113
- wValue.title = value;
4227
+ var wValue = document.createElement( 'button' );
4228
+ wValue.title = options.title ?? "";
4114
4229
  wValue.className = "lexbutton";
4115
- if(options.selected)
4116
- wValue.classList.add("selected");
4117
- if(options.buttonClass)
4118
- wValue.classList.add(options.buttonClass);
4119
- wValue.innerHTML =
4120
- (options.icon ? "<a class='" + options.icon + "'></a>" :
4230
+
4231
+ if( options.selected )
4232
+ {
4233
+ wValue.classList.add( "selected" );
4234
+ }
4235
+
4236
+ if( options.buttonClass )
4237
+ {
4238
+ wValue.classList.add( options.buttonClass );
4239
+ }
4240
+
4241
+ wValue.innerHTML =
4242
+ (options.icon ? "<a class='" + options.icon + "'></a>" :
4121
4243
  ( options.img ? "<img src='" + options.img + "'>" : "<span>" + (value || "") + "</span>" ));
4122
4244
 
4123
4245
  wValue.style.width = "calc( 100% - " + (options.nameWidth ?? LX.DEFAULT_NAME_WIDTH) + ")";
4124
4246
 
4125
- if(options.disabled)
4126
- wValue.setAttribute("disabled", true);
4247
+ if( options.disabled )
4248
+ {
4249
+ wValue.setAttribute( "disabled", true );
4250
+ }
4127
4251
 
4128
4252
  wValue.addEventListener("click", e => {
4129
- if( options.selectable ) {
4253
+ if( options.selectable )
4254
+ {
4130
4255
  if( options.parent )
4256
+ {
4131
4257
  options.parent.querySelectorAll(".lexbutton.selected").forEach( e => { if(e == wValue) return; e.classList.remove("selected") } );
4258
+ }
4259
+
4132
4260
  wValue.classList.toggle('selected');
4133
4261
  }
4134
- this._trigger( new IEvent(name, value, e), callback );
4262
+
4263
+ this._trigger( new IEvent( name, value, e ), callback );
4135
4264
  });
4136
4265
 
4137
- element.appendChild(wValue);
4266
+ element.appendChild( wValue );
4138
4267
 
4139
4268
  // Remove branch padding and margins
4140
- if(!widget.name) {
4269
+ if( !widget.name )
4270
+ {
4141
4271
  wValue.className += " noname";
4142
4272
  wValue.style.width = "100%";
4143
4273
  }
@@ -4163,7 +4293,7 @@ class Panel {
4163
4293
  let container = document.createElement('div');
4164
4294
  container.className = "lexcombobuttons ";
4165
4295
  if( options.float ) container.className += options.float;
4166
- container.style.width = "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
4296
+ container.style.width = "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
4167
4297
 
4168
4298
  let should_select = !(options.noSelection ?? false);
4169
4299
  for( let b of values )
@@ -4176,27 +4306,27 @@ class Panel {
4176
4306
  if(options.buttonClass)
4177
4307
  buttonEl.classList.add(options.buttonClass);
4178
4308
 
4179
- if(options.selected == b.value)
4309
+ if(options.selected == b.value)
4180
4310
  buttonEl.classList.add("selected");
4181
-
4182
- if(b.id)
4311
+
4312
+ if(b.id)
4183
4313
  buttonEl.id = b.id;
4184
-
4314
+
4185
4315
  buttonEl.innerHTML = (b.icon ? "<a class='" + b.icon +"'></a>" : "") + "<span>" + (b.icon ? "" : b.value) + "</span>";
4186
-
4316
+
4187
4317
  if(options.disabled)
4188
4318
  buttonEl.setAttribute("disabled", true);
4189
-
4319
+
4190
4320
  buttonEl.addEventListener("click", function(e) {
4191
4321
  if(should_select) {
4192
4322
  container.querySelectorAll('button').forEach( s => s.classList.remove('selected'));
4193
4323
  this.classList.add('selected');
4194
4324
  }
4195
- that._trigger( new IEvent(name, b.value, e), b.callback );
4325
+ that._trigger( new IEvent(name, b.value, e), b.callback );
4196
4326
  });
4197
4327
 
4198
4328
  container.appendChild(buttonEl);
4199
-
4329
+
4200
4330
  // Remove branch padding and margins
4201
4331
  if(widget.name === undefined) {
4202
4332
  buttonEl.className += " noname";
@@ -4265,11 +4395,11 @@ class Panel {
4265
4395
  }
4266
4396
 
4267
4397
  container.appendChild(name_el);
4268
-
4398
+
4269
4399
  if( options.callback ) {
4270
4400
  container.style.cursor = "pointer";
4271
4401
  container.addEventListener("click", (e) => {
4272
- this._trigger( new IEvent(name, null, e), options.callback );
4402
+ this._trigger( new IEvent(name, null, e), options.callback );
4273
4403
  });
4274
4404
  }
4275
4405
 
@@ -4456,40 +4586,53 @@ class Panel {
4456
4586
  let container = document.createElement( 'div' );
4457
4587
  container.className = "lexdropdown";
4458
4588
  container.style.width = options.inputWidth || "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
4459
-
4589
+
4460
4590
  // Add widget value
4461
4591
  let wValue = document.createElement( 'div' );
4462
4592
  wValue.className = "lexdropdown lexoption";
4463
4593
  wValue.name = name;
4464
4594
  wValue.iValue = value;
4465
4595
 
4466
- // Add dropdown widget button
4596
+ // Add dropdown widget button
4467
4597
  let buttonName = value;
4468
4598
  buttonName += "<a class='fa-solid fa-angle-down' style='float:right; margin-right: 3px;'></a>";
4469
4599
 
4470
4600
  this.queue(container);
4471
4601
 
4472
- let selectedOption = this.addButton( null, buttonName, (value, event) => {
4473
- if( list.unfocus_event ) {
4602
+ const _getMaxListWidth = () => {
4603
+
4604
+ let maxWidth = 0;
4605
+ for( let i of values )
4606
+ {
4607
+ const iString = String( i );
4608
+ maxWidth = Math.max( iString.length, maxWidth );
4609
+ }
4610
+ return maxWidth * 9;
4611
+ };
4612
+
4613
+ let selectedOption = this.addButton( null, buttonName, ( value, event ) => {
4614
+ if( list.unfocus_event )
4615
+ {
4474
4616
  delete list.unfocus_event;
4475
4617
  return;
4476
4618
  }
4477
4619
  const topPosition = selectedOption.getBoundingClientRect().y;
4478
4620
  list.style.top = (topPosition + selectedOption.offsetHeight) + 'px';
4479
4621
  list.style.width = (event.currentTarget.clientWidth) + 'px';
4622
+ list.style.minWidth = (_getMaxListWidth()) + 'px';
4480
4623
  list.toggleAttribute('hidden');
4481
4624
  list.focus();
4482
4625
  }, { buttonClass: 'array', skipInlineCount: true });
4483
4626
 
4484
4627
  this.clearQueue();
4485
4628
 
4486
- selectedOption.style.width = "100%";
4629
+ selectedOption.style.width = "100%";
4487
4630
 
4488
4631
  selectedOption.refresh = (v) => {
4489
4632
  if(selectedOption.querySelector("span").innerText == "")
4490
4633
  selectedOption.querySelector("span").innerText = v;
4491
4634
  else
4492
- selectedOption.querySelector("span").innerHTML = selectedOption.querySelector("span").innerHTML.replaceAll(selectedOption.querySelector("span").innerText, v);
4635
+ selectedOption.querySelector("span").innerHTML = selectedOption.querySelector("span").innerHTML.replaceAll(selectedOption.querySelector("span").innerText, v);
4493
4636
  }
4494
4637
 
4495
4638
  // Add dropdown options container
@@ -4501,12 +4644,17 @@ class Panel {
4501
4644
  list.addEventListener( 'focusout', function( e ) {
4502
4645
  e.stopPropagation();
4503
4646
  e.stopImmediatePropagation();
4504
- if(e.relatedTarget === selectedOption.querySelector( 'button' )) {
4647
+ if( e.relatedTarget === selectedOption.querySelector( 'button' ) )
4648
+ {
4505
4649
  this.unfocus_event = true;
4506
4650
  setTimeout( () => delete this.unfocus_event, 200 );
4507
- } else if ( e.relatedTarget && e.relatedTarget.tagName == "INPUT" ) {
4651
+ }
4652
+ else if ( e.relatedTarget && e.relatedTarget.tagName == "INPUT" )
4653
+ {
4508
4654
  return;
4509
- }else if ( e.target.className == 'lexinput-filter' ) {
4655
+ }
4656
+ else if ( e.target.className == 'lexinput-filter' )
4657
+ {
4510
4658
  return;
4511
4659
  }
4512
4660
  this.toggleAttribute( 'hidden', true );
@@ -4515,28 +4663,33 @@ class Panel {
4515
4663
  // Add filter options
4516
4664
  let filter = null;
4517
4665
  if(options.filter ?? false)
4666
+ {
4518
4667
  filter = this._addFilter("Search option", {container: list, callback: this._search_options.bind(list, values)});
4668
+ }
4519
4669
 
4520
4670
  // Create option list to empty it easily..
4521
- const list_options = document.createElement('span');
4522
- list.appendChild(list_options);
4523
-
4524
- if( filter ) {
4525
- list.prepend(filter);
4526
- list_options.style.height = "calc(100% - 25px)";
4527
-
4528
- filter.addEventListener('focusout', function(e) {
4671
+ const listOptions = document.createElement('span');
4672
+ list.appendChild( listOptions );
4673
+
4674
+ if( filter )
4675
+ {
4676
+ list.prepend( filter );
4677
+ listOptions.style.height = "calc(100% - 25px)";
4678
+
4679
+ filter.addEventListener('focusout', function( e ) {
4529
4680
  if (e.relatedTarget && e.relatedTarget.tagName == "UL" && e.relatedTarget.classList.contains("lexoptions"))
4681
+ {
4530
4682
  return;
4531
- list.toggleAttribute('hidden', true);
4683
+ }
4684
+ list.toggleAttribute( 'hidden', true );
4532
4685
  });
4533
4686
  }
4534
4687
 
4535
4688
  // Add dropdown options list
4536
- list.refresh = (options) => {
4689
+ list.refresh = options => {
4537
4690
 
4538
4691
  // Empty list
4539
- list_options.innerHTML = "";
4692
+ listOptions.innerHTML = "";
4540
4693
 
4541
4694
  for(let i = 0; i < options.length; i++)
4542
4695
  {
@@ -4556,7 +4709,7 @@ class Panel {
4556
4709
 
4557
4710
  let btn = element.querySelector(".lexwidgetname .lexicon");
4558
4711
  if(btn) btn.style.display = (value != wValue.iValue ? "block" : "none");
4559
- that._trigger( new IEvent(name, value, null), callback );
4712
+ that._trigger( new IEvent(name, value, null), callback );
4560
4713
 
4561
4714
  // Reset filter
4562
4715
  if(filter)
@@ -4595,18 +4748,20 @@ class Panel {
4595
4748
  option.setAttribute("title", iValue.value);
4596
4749
  if(value == iValue.value)
4597
4750
  li.classList.add("selected");
4598
- }
4599
- list_options.appendChild(li);
4751
+ }
4752
+
4753
+ listOptions.appendChild( li );
4600
4754
  }
4601
4755
  }
4602
4756
 
4603
- list.refresh(values);
4757
+ list.refresh( values );
4604
4758
 
4605
- container.appendChild(list);
4606
- element.appendChild(container);
4759
+ container.appendChild( list );
4760
+ element.appendChild( container );
4607
4761
 
4608
4762
  // Remove branch padding and margins
4609
- if(!widget.name) {
4763
+ if( !widget.name )
4764
+ {
4610
4765
  element.className += " noname";
4611
4766
  container.style.width = "100%";
4612
4767
  }
@@ -4758,13 +4913,13 @@ class Panel {
4758
4913
  if( value != undefined )
4759
4914
  {
4760
4915
  const valueBit = binary[ 16 - bit - 1 ];
4761
- if(valueBit != undefined && valueBit == '1')
4762
- layer.classList.add('selected');
4916
+ if(valueBit != undefined && valueBit == '1')
4917
+ layer.classList.add('selected');
4763
4918
  }
4764
4919
  layer.innerText = bit + 1;
4765
4920
  layer.title = "Bit " + bit + ", value " + (1 << bit);
4766
4921
  container.appendChild( layer );
4767
-
4922
+
4768
4923
  layer.addEventListener("click", e => {
4769
4924
 
4770
4925
  e.stopPropagation();
@@ -4783,7 +4938,7 @@ class Panel {
4783
4938
  };
4784
4939
 
4785
4940
  setLayers();
4786
-
4941
+
4787
4942
  element.appendChild(container);
4788
4943
 
4789
4944
  return widget;
@@ -4828,7 +4983,7 @@ class Panel {
4828
4983
  var container = document.createElement('div');
4829
4984
  container.className = "lexarray";
4830
4985
  container.style.width = "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
4831
-
4986
+
4832
4987
  this.queue( container );
4833
4988
 
4834
4989
  const angle_down = `<a class='fa-solid fa-angle-down' style='float:right; margin-right: 3px;'></a>`;
@@ -4838,7 +4993,7 @@ class Panel {
4838
4993
  this.addButton(null, buttonName, () => {
4839
4994
  element.querySelector(".lexarrayitems").toggleAttribute('hidden');
4840
4995
  }, { buttonClass: 'array' });
4841
-
4996
+
4842
4997
  this.clearQueue();
4843
4998
 
4844
4999
  // Show elements
@@ -4846,7 +5001,7 @@ class Panel {
4846
5001
  let array_items = document.createElement('div');
4847
5002
  array_items.className = "lexarrayitems";
4848
5003
  array_items.toggleAttribute('hidden', true);
4849
-
5004
+
4850
5005
  element.appendChild(container);
4851
5006
  element.appendChild(array_items);
4852
5007
 
@@ -4944,7 +5099,7 @@ class Panel {
4944
5099
 
4945
5100
  values = newValues;
4946
5101
  listContainer.innerHTML = "";
4947
-
5102
+
4948
5103
  for( let i = 0; i < values.length; ++i )
4949
5104
  {
4950
5105
  let icon = null;
@@ -5066,7 +5221,7 @@ class Panel {
5066
5221
 
5067
5222
  tag_input.onkeydown = function( e ) {
5068
5223
  const val = this.value.replace(/\s/g, '');
5069
- if( e.key == ' ') {
5224
+ if( e.key == ' ') {
5070
5225
  e.preventDefault();
5071
5226
  if( !val.length || value.indexOf( val ) > -1 )
5072
5227
  return;
@@ -5126,7 +5281,7 @@ class Panel {
5126
5281
  Panel._add_reset_property( element.domName, function() {
5127
5282
  Panel._dispatch_event( toggle, "click" );
5128
5283
  });
5129
-
5284
+
5130
5285
  // Add widget value
5131
5286
 
5132
5287
  var container = document.createElement('div');
@@ -5140,7 +5295,7 @@ class Panel {
5140
5295
  flag.className = "checkbox " + (flag.value ? "on" : "");
5141
5296
  flag.id = "checkbox"+simple_guidGenerator();
5142
5297
  flag.innerHTML = "<a class='fa-solid fa-check' style='display: " + (flag.value ? "block" : "none") + "'></a>";
5143
-
5298
+
5144
5299
  if( options.disabled ) {
5145
5300
  flag.disabled = true;
5146
5301
  toggle.className += " disabled";
@@ -5251,7 +5406,7 @@ class Panel {
5251
5406
  color.id = "color" + simple_guidGenerator();
5252
5407
  color.useRGB = options.useRGB ?? false;
5253
5408
  color.value = color.iValue = value.constructor === Array ? rgbToHex( value ) : value;
5254
-
5409
+
5255
5410
  if( options.disabled ) {
5256
5411
  color.disabled = true;
5257
5412
  }
@@ -5287,7 +5442,7 @@ class Panel {
5287
5442
  widget.set( v );
5288
5443
  change_from_input = false;
5289
5444
  }, { width: "calc( 100% - 32px )"});
5290
-
5445
+
5291
5446
  text_widget.domEl.style.marginLeft = "4px";
5292
5447
 
5293
5448
  this.clearQueue();
@@ -5340,7 +5495,7 @@ class Panel {
5340
5495
  // add widget value
5341
5496
 
5342
5497
  var container = document.createElement( 'div' );
5343
- container.className = "lexnumber";
5498
+ container.className = "lexnumber";
5344
5499
  container.style.width = options.inputWidth || "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
5345
5500
 
5346
5501
  let box = document.createElement( 'div' );
@@ -5475,7 +5630,7 @@ class Panel {
5475
5630
 
5476
5631
  if( !skipCallback ) this._trigger( new IEvent( name, val, e ), callback );
5477
5632
  }, { passive: false });
5478
-
5633
+
5479
5634
  // Add drag input
5480
5635
 
5481
5636
  vecinput.addEventListener( "mousedown", inner_mousedown );
@@ -5544,7 +5699,7 @@ class Panel {
5544
5699
  options.onRelease.bind( vecinput )( e, vecinput );
5545
5700
  }
5546
5701
  }
5547
-
5702
+
5548
5703
  container.appendChild( box );
5549
5704
  element.appendChild( container );
5550
5705
 
@@ -5611,7 +5766,7 @@ class Panel {
5611
5766
  // Add widget value
5612
5767
 
5613
5768
  var container = document.createElement( 'div' );
5614
- container.className = "lexvector";
5769
+ container.className = "lexvector";
5615
5770
  container.style.width = "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
5616
5771
 
5617
5772
  for( let i = 0; i < num_components; ++i ) {
@@ -5704,7 +5859,7 @@ class Panel {
5704
5859
 
5705
5860
  if( !skipCallback ) this._trigger( new IEvent( name, value, e ), callback );
5706
5861
  }, false );
5707
-
5862
+
5708
5863
  // Add drag input
5709
5864
 
5710
5865
  vecinput.addEventListener( "mousedown", inner_mousedown );
@@ -5783,7 +5938,7 @@ class Panel {
5783
5938
  options.onRelease.bind( vecinput )( e, vecinput );
5784
5939
  }
5785
5940
  }
5786
-
5941
+
5787
5942
  box.appendChild( vecinput );
5788
5943
  container.appendChild( box );
5789
5944
  }
@@ -5807,7 +5962,8 @@ class Panel {
5807
5962
  }
5808
5963
 
5809
5964
  let locker = document.createElement( 'a' );
5810
- locker.className = "fa-solid fa-lock-open lexicon";
5965
+ locker.title = "Lock";
5966
+ locker.className = "fa-solid fa-lock-open lexicon lock";
5811
5967
  container.appendChild( locker );
5812
5968
  locker.addEventListener( "click", function( e ) {
5813
5969
  this.locked = !this.locked;
@@ -5822,7 +5978,7 @@ class Panel {
5822
5978
  this.classList.remove( "fa-lock" );
5823
5979
  }
5824
5980
  }, false );
5825
-
5981
+
5826
5982
  element.appendChild( container );
5827
5983
 
5828
5984
  return widget;
@@ -5831,7 +5987,7 @@ class Panel {
5831
5987
  /**
5832
5988
  * @method addVector N (2, 3, 4)
5833
5989
  * @param {String} name Widget name
5834
- * @param {Array} value Array of N components
5990
+ * @param {Array} value Array of N components
5835
5991
  * @param {Function} callback Callback function on change
5836
5992
  * @param {*} options:
5837
5993
  * disabled: Make the widget disabled [false]
@@ -5890,18 +6046,21 @@ class Panel {
5890
6046
 
5891
6047
  this.queue( element );
5892
6048
 
6049
+ element.aspectRatio = ( value.length == 2 ? value[ 0 ] / value[ 1 ] : null );
5893
6050
  element.dimensions = [];
5894
6051
 
5895
6052
  for( let i = 0; i < value.length; ++i )
5896
6053
  {
5897
- const size = measureRealWidth( JSON.stringify( value[ i ] ), 24 ) + 'px';
5898
6054
  element.dimensions[ i ] = this.addNumber( null, value[ i ], ( v ) => {
5899
6055
 
5900
- const value = [];
6056
+ const value = widget.onGetValue();
5901
6057
 
5902
- for( let i = 0; i < element.dimensions.length; ++i )
6058
+ if( element.locked )
5903
6059
  {
5904
- value.push( element.dimensions[ i ].onGetValue() );
6060
+ const ar = ( i == 0 ? 1.0 / element.aspectRatio : element.aspectRatio );
6061
+ const index = ( 1 + i ) % 2;
6062
+ value[ index ] = v * ar;
6063
+ element.dimensions[ index ].onSetValue( value[ index ], true );
5905
6064
  }
5906
6065
 
5907
6066
  if( callback )
@@ -5909,7 +6068,7 @@ class Panel {
5909
6068
  callback( value );
5910
6069
  }
5911
6070
 
5912
- }, { width: size, min: 0, disabled: options.disabled } );
6071
+ }, { min: 0, disabled: options.disabled, precision: options.precision } );
5913
6072
 
5914
6073
  if( ( i + 1 ) != value.length )
5915
6074
  {
@@ -5929,6 +6088,32 @@ class Panel {
5929
6088
  element.appendChild( unitSpan );
5930
6089
  }
5931
6090
 
6091
+ // Lock aspect ratio
6092
+ if( element.aspectRatio )
6093
+ {
6094
+ let locker = document.createElement( 'a' );
6095
+ locker.title = "Lock Aspect Ratio";
6096
+ locker.className = "fa-solid fa-lock-open lexicon lock";
6097
+ element.appendChild( locker );
6098
+ locker.addEventListener( "click", function( e ) {
6099
+ element.locked = !element.locked;
6100
+ if( element.locked )
6101
+ {
6102
+ this.classList.add( "fa-lock" );
6103
+ this.classList.remove( "fa-lock-open" );
6104
+
6105
+ // Recompute ratio
6106
+ const value = widget.onGetValue();
6107
+ element.aspectRatio = value[ 0 ] / value[ 1 ];
6108
+ }
6109
+ else
6110
+ {
6111
+ this.classList.add( "fa-lock-open" );
6112
+ this.classList.remove( "fa-lock" );
6113
+ }
6114
+ }, false );
6115
+ }
6116
+
5932
6117
  // Remove branch padding and margins
5933
6118
  if( !widget.name )
5934
6119
  {
@@ -5942,11 +6127,12 @@ class Panel {
5942
6127
  /**
5943
6128
  * @method addPad
5944
6129
  * @param {String} name Widget name
5945
- * @param {Number} value Pad value
6130
+ * @param {Array} value Pad value
5946
6131
  * @param {Function} callback Callback function on change
5947
6132
  * @param {*} options:
5948
6133
  * disabled: Make the widget disabled [false]
5949
6134
  * min, max: Min and Max values
6135
+ * padSize: Size of the pad (css)
5950
6136
  * onPress: Callback function on mouse down
5951
6137
  * onRelease: Callback function on mouse up
5952
6138
  */
@@ -6067,7 +6253,7 @@ class Panel {
6067
6253
  /**
6068
6254
  * @method addProgress
6069
6255
  * @param {String} name Widget name
6070
- * @param {Number} value Progress value
6256
+ * @param {Number} value Progress value
6071
6257
  * @param {*} options:
6072
6258
  * min, max: Min and Max values
6073
6259
  * low, optimum, high: Low and High boundary values, Optimum point in the range
@@ -6078,7 +6264,8 @@ class Panel {
6078
6264
 
6079
6265
  addProgress( name, value, options = {} ) {
6080
6266
 
6081
- if(!name) {
6267
+ if( !name )
6268
+ {
6082
6269
  throw("Set Widget Name!");
6083
6270
  }
6084
6271
 
@@ -6108,47 +6295,75 @@ class Panel {
6108
6295
  progress.min = options.min ?? 0;
6109
6296
  progress.max = options.max ?? 1;
6110
6297
  progress.value = value;
6111
-
6112
- if(options.low)
6298
+
6299
+ if( options.low )
6113
6300
  progress.low = options.low;
6114
- if(options.high)
6301
+ if( options.high )
6115
6302
  progress.high = options.high;
6116
- if(options.optimum)
6303
+ if( options.optimum )
6117
6304
  progress.optimum = options.optimum;
6118
6305
 
6119
- container.appendChild(progress);
6120
- element.appendChild(container);
6306
+ container.appendChild( progress );
6307
+ element.appendChild( container );
6121
6308
 
6122
- if(options.showValue) {
6123
- if(document.getElementById('progressvalue-' + name ))
6309
+ if( options.showValue )
6310
+ {
6311
+ if( document.getElementById('progressvalue-' + name ) )
6312
+ {
6124
6313
  document.getElementById('progressvalue-' + name ).remove();
6314
+ }
6315
+
6125
6316
  let span = document.createElement("span");
6126
6317
  span.id = "progressvalue-" + name;
6127
6318
  span.style.padding = "0px 5px";
6128
6319
  span.innerText = value;
6129
- container.appendChild(span);
6320
+ container.appendChild( span );
6130
6321
  }
6131
6322
 
6132
- if(options.editable) {
6133
- progress.classList.add("editable");
6134
- progress.addEventListener("mousemove", inner_mousemove.bind(this, value));
6135
- progress.addEventListener("mouseup", inner_mouseup.bind(this, progress));
6323
+ if( options.editable )
6324
+ {
6325
+ progress.classList.add( "editable" );
6326
+ progress.addEventListener( "mousedown", inner_mousedown );
6136
6327
 
6137
- function inner_mousemove(value, e) {
6138
-
6139
- if(e.which < 1)
6140
- return;
6141
- let v = this.getValue(name, value);
6142
- v+=e.movementX/100;
6143
- v = v.toFixed(2);
6144
- this.setValue(name, v);
6328
+ var that = this;
6145
6329
 
6146
- if(options.callback)
6147
- options.callback(v, e);
6330
+ function inner_mousedown( e )
6331
+ {
6332
+ var doc = that.root.ownerDocument;
6333
+ doc.addEventListener( 'mousemove', inner_mousemove );
6334
+ doc.addEventListener( 'mouseup', inner_mouseup );
6335
+ document.body.classList.add( 'noevents' );
6336
+ e.stopImmediatePropagation();
6337
+ e.stopPropagation();
6148
6338
  }
6149
6339
 
6150
- function inner_mouseup(el) {
6151
- el.removeEventListener("mousemove", inner_mousemove);
6340
+ function inner_mousemove( e )
6341
+ {
6342
+ let dt = -e.movementX;
6343
+
6344
+ if ( dt != 0 )
6345
+ {
6346
+ let v = that.getValue( name, value );
6347
+ v += e.movementX / 100;
6348
+ v = round( v );
6349
+ that.setValue( name, v );
6350
+
6351
+ if( options.callback )
6352
+ {
6353
+ options.callback( v, e );
6354
+ }
6355
+ }
6356
+
6357
+ e.stopPropagation();
6358
+ e.preventDefault();
6359
+ }
6360
+
6361
+ function inner_mouseup( e )
6362
+ {
6363
+ var doc = that.root.ownerDocument;
6364
+ doc.removeEventListener( 'mousemove', inner_mousemove );
6365
+ doc.removeEventListener( 'mouseup', inner_mouseup );
6366
+ document.body.classList.remove( 'noevents' );
6152
6367
  }
6153
6368
  }
6154
6369
 
@@ -6167,7 +6382,8 @@ class Panel {
6167
6382
 
6168
6383
  addFile( name, callback, options = { } ) {
6169
6384
 
6170
- if( !name ) {
6385
+ if( !name )
6386
+ {
6171
6387
  throw( "Set Widget Name!" );
6172
6388
  }
6173
6389
 
@@ -6176,7 +6392,7 @@ class Panel {
6176
6392
 
6177
6393
  let local = options.local ?? true;
6178
6394
  let type = options.type ?? 'text';
6179
- let read = options.read ?? true;
6395
+ let read = options.read ?? true;
6180
6396
 
6181
6397
  // Create hidden input
6182
6398
  let input = document.createElement( 'input' );
@@ -6184,7 +6400,9 @@ class Panel {
6184
6400
  input.type = 'file';
6185
6401
 
6186
6402
  if( options.placeholder )
6403
+ {
6187
6404
  input.placeholder = options.placeholder;
6405
+ }
6188
6406
 
6189
6407
  input.addEventListener( 'change', function( e ) {
6190
6408
 
@@ -6216,17 +6434,24 @@ class Panel {
6216
6434
  element.appendChild( input );
6217
6435
 
6218
6436
  this.queue( element );
6219
-
6437
+
6220
6438
  if( local )
6221
6439
  {
6440
+ let settingsDialog = null;
6441
+
6222
6442
  this.addButton(null, "<a style='margin-top: 0px;' class='fa-solid fa-gear'></a>", () => {
6223
-
6224
- new Dialog( "Load Settings", p => {
6443
+
6444
+ if( settingsDialog )
6445
+ {
6446
+ return;
6447
+ }
6448
+
6449
+ settingsDialog = new Dialog( "Load Settings", p => {
6225
6450
  p.addDropdown( "Type", [ 'text', 'buffer', 'bin', 'url' ], type, v => { type = v } );
6226
6451
  p.addButton( null, "Reload", v => { input.dispatchEvent( new Event( 'change' ) ) } );
6227
- });
6228
-
6229
- }, { className: "micro", skipInlineCount: true });
6452
+ }, { onclose: ( root ) => { root.remove(); settingsDialog = null; } } );
6453
+
6454
+ }, { className: "micro", skipInlineCount: true, title: "Settings" });
6230
6455
  }
6231
6456
 
6232
6457
  this.clearQueue();
@@ -6288,7 +6513,7 @@ class Panel {
6288
6513
  node_filter_input.addEventListener('input', function(){
6289
6514
  nodeTree.refresh();
6290
6515
  });
6291
-
6516
+
6292
6517
  let searchIcon = document.createElement('a');
6293
6518
  searchIcon.className = "lexicon fa-solid fa-magnifying-glass";
6294
6519
  toolsDiv.appendChild(node_filter_input);
@@ -6322,11 +6547,11 @@ class Panel {
6322
6547
  element.className = "lexseparator";
6323
6548
  let widget = new Widget( null, Widget.SEPARATOR );
6324
6549
  widget.domEl = element;
6325
-
6550
+
6326
6551
  if(this.current_branch) {
6327
6552
  this.current_branch.content.appendChild( element );
6328
6553
  this.current_branch.widgets.push( widget );
6329
- } else
6554
+ } else
6330
6555
  this.root.appendChild(element);
6331
6556
  }
6332
6557
 
@@ -6338,7 +6563,7 @@ class Panel {
6338
6563
  * onCreate: Func to be called at tab creation
6339
6564
  * onSelect: Func to be called on select tab (optional)
6340
6565
  * }
6341
- * @param {*} options
6566
+ * @param {*} options
6342
6567
  * vertical: Use vertical or horizontal tabs (vertical by default)
6343
6568
  * showNames: Show tab name only in horizontal tabs
6344
6569
  */
@@ -6420,7 +6645,7 @@ class Panel {
6420
6645
  this.clearQueue();
6421
6646
  }
6422
6647
  }
6423
-
6648
+
6424
6649
  this.addSeparator();
6425
6650
  }
6426
6651
  }
@@ -6432,7 +6657,7 @@ LX.Panel = Panel;
6432
6657
  */
6433
6658
 
6434
6659
  class Branch {
6435
-
6660
+
6436
6661
  constructor( name, options = {} ) {
6437
6662
 
6438
6663
  this.name = name;
@@ -6459,7 +6684,7 @@ class Branch {
6459
6684
  // create element
6460
6685
  var title = document.createElement( 'div' );
6461
6686
  title.className = "lexbranchtitle";
6462
-
6687
+
6463
6688
  title.innerHTML = "<a class='fa-solid fa-angle-up switch-branch-button'></a>";
6464
6689
  if( options.icon )
6465
6690
  {
@@ -6507,7 +6732,7 @@ class Branch {
6507
6732
  {
6508
6733
  return;
6509
6734
  }
6510
-
6735
+
6511
6736
  addContextMenu("Dock", e, p => {
6512
6737
  e.preventDefault();
6513
6738
  // p.add('<i class="fa-regular fa-window-maximize">', {id: 'dock_options0'});
@@ -6547,23 +6772,26 @@ class Branch {
6547
6772
 
6548
6773
  _addBranchSeparator() {
6549
6774
 
6550
- var element = document.createElement('div');
6775
+ const element = document.createElement('div');
6551
6776
  element.className = "lexwidgetseparator";
6552
6777
  element.style.width = "100%";
6553
6778
  element.style.background = "none";
6554
6779
 
6555
- var grabber = document.createElement('div');
6780
+ const grabber = document.createElement('div');
6556
6781
  grabber.innerHTML = "&#9662;";
6557
- grabber.style.marginLeft = LX.DEFAULT_NAME_WIDTH;
6558
6782
  element.appendChild(grabber);
6559
6783
 
6560
- var line = document.createElement('div');
6784
+ doAsync( () => {
6785
+ grabber.style.marginLeft = ((parseFloat(LX.DEFAULT_NAME_WIDTH) / 100.0) * this.content.offsetWidth) + "px";
6786
+ }, 10 )
6787
+
6788
+ const line = document.createElement('div');
6561
6789
  line.style.width = "1px";
6562
6790
  line.style.marginLeft = "6px";
6563
6791
  line.style.marginTop = "2px";
6564
6792
  line.style.height = "0px"; // get in time
6565
- grabber.appendChild(line);
6566
- grabber.addEventListener("mousedown", inner_mousedown);
6793
+ grabber.appendChild( line );
6794
+ grabber.addEventListener( "mousedown", innerMouseDown );
6567
6795
 
6568
6796
  this.grabber = grabber;
6569
6797
 
@@ -6571,45 +6799,40 @@ class Branch {
6571
6799
  return that.root.offsetHeight - that.root.children[0].offsetHeight;
6572
6800
  }
6573
6801
 
6574
- var that = this;
6575
- var lastX = 0;
6576
- var lastXLine = 0;
6577
- function inner_mousedown(e)
6802
+ let that = this;
6803
+
6804
+ function innerMouseDown( e )
6578
6805
  {
6579
6806
  var doc = that.root.ownerDocument;
6580
- doc.addEventListener("mouseup",inner_mouseup);
6581
- doc.addEventListener("mousemove",inner_mousemove);
6582
- lastX = e.pageX;
6583
- lastXLine = e.pageX;
6807
+ doc.addEventListener("mouseup", innerMouseUp);
6808
+ doc.addEventListener("mousemove", innerMouseMove);
6584
6809
  e.stopPropagation();
6585
6810
  e.preventDefault();
6586
- var h = getBranchHeight();
6811
+ const h = getBranchHeight();
6587
6812
  line.style.height = (h-3) + "px";
6588
6813
  document.body.classList.add('nocursor');
6589
6814
  }
6590
-
6591
- function inner_mousemove(e)
6815
+
6816
+ function innerMouseMove(e)
6592
6817
  {
6593
- if (lastXLine != e.pageX) {
6594
- var dt = lastXLine - e.pageX;
6595
- var margin = parseFloat( grabber.style.marginLeft );
6596
- grabber.style.marginLeft = clamp(margin - dt * 0.1, 10, 90) + "%";
6597
- }
6818
+ let dt = e.movementX;
6598
6819
 
6599
- lastXLine = e.pageX;
6820
+ if ( dt != 0 )
6821
+ {
6822
+ const margin = parseFloat( grabber.style.marginLeft );
6823
+ grabber.style.marginLeft = clamp( margin + dt, 32, that.content.offsetWidth - 32 ) + "px";
6824
+ }
6600
6825
  }
6601
6826
 
6602
- function inner_mouseup(e)
6827
+ function innerMouseUp(e)
6603
6828
  {
6604
- if (lastX != e.pageX)
6605
- that._updateWidgets();
6606
- lastX = e.pageX;
6607
- lastXLine = e.pageX;
6829
+ that._updateWidgets();
6830
+
6608
6831
  line.style.height = "0px";
6609
6832
 
6610
6833
  var doc = that.root.ownerDocument;
6611
- doc.removeEventListener("mouseup",inner_mouseup);
6612
- doc.removeEventListener("mousemove",inner_mousemove);
6834
+ doc.removeEventListener("mouseup", innerMouseUp);
6835
+ doc.removeEventListener("mousemove", innerMouseMove);
6613
6836
  document.body.classList.remove('nocursor');
6614
6837
  }
6615
6838
 
@@ -6669,7 +6892,7 @@ class Dialog {
6669
6892
  static _last_id = 0;
6670
6893
 
6671
6894
  constructor( title, callback, options = {} ) {
6672
-
6895
+
6673
6896
  if( !callback )
6674
6897
  {
6675
6898
  console.warn("Content is empty, add some widgets using 'callback' parameter!");
@@ -6712,25 +6935,30 @@ class Dialog {
6712
6935
 
6713
6936
  addContextMenu("Dock", e, p => {
6714
6937
  e.preventDefault();
6715
-
6716
- const get_next_panel = function(area) {
6717
- let p = area.panels[0];
6938
+
6939
+ const _getNextPanel = function( area ) {
6940
+ let p = area.panels[ 0 ];
6718
6941
  if( p ) return p;
6719
6942
  for(var s of area.sections){
6720
- p = get_next_panel(s);
6943
+ p = _getNextPanel( s );
6721
6944
  if( p ) return p;
6722
6945
  }
6723
6946
  }
6724
6947
 
6725
- const append_branch = function(panel) {
6948
+ const _appendBranch = function( panel ) {
6726
6949
  let branch = panel.branches.find( b => b.name === title );
6727
- if( !branch ) {
6728
- panel.branch(title);
6729
- branch = panel.branches.find( b => b.name === title );
6730
- }else
6950
+ if( !branch )
6951
+ {
6952
+ panel.branch( title );
6953
+ branch = panel.branches.find( b => b.name === title );
6954
+ }
6955
+ else
6956
+ {
6731
6957
  panel.root.appendChild( branch.root );
6958
+ }
6732
6959
 
6733
- for( let w of that.widgets ) {
6960
+ for( let w of that.widgets )
6961
+ {
6734
6962
  branch.content.appendChild( w.domEl );
6735
6963
  }
6736
6964
 
@@ -6741,16 +6969,16 @@ class Dialog {
6741
6969
  branch.root.classList.add('last');
6742
6970
  root.remove();
6743
6971
  }
6744
-
6972
+
6745
6973
  // Right
6746
- let rpanel = get_next_panel(LX.main_area.sections[1]);
6974
+ let rpanel = _getNextPanel(LX.main_area.sections[ 1 ]);
6747
6975
  p.add('<i class="fa-regular fa-window-maximize fa-window-maximize fa-rotate-90">', {disabled: !rpanel, id: 'dock_options0', callback: () => {
6748
- append_branch(rpanel);
6976
+ _appendBranch(rpanel);
6749
6977
  }});
6750
6978
  // Left
6751
- let lpanel = get_next_panel(LX.main_area.sections[0]);
6979
+ let lpanel = _getNextPanel(LX.main_area.sections[ 0 ]);
6752
6980
  p.add('<i class="fa-regular fa-window-maximize fa-window-maximize fa-rotate-270">', {disabled: !lpanel, id: 'dock_options1', callback: () => {
6753
- append_branch(lpanel);
6981
+ _appendBranch(lpanel);
6754
6982
  }});
6755
6983
  }, { icon: "fa-regular fa-window-restore" });
6756
6984
  };
@@ -6762,41 +6990,61 @@ class Dialog {
6762
6990
  {
6763
6991
  this.close = () => {
6764
6992
 
6993
+ if( options.onBeforeClose )
6994
+ {
6995
+ options.onBeforeClose( this );
6996
+ }
6997
+
6765
6998
  if( !options.onclose )
6766
6999
  {
6767
7000
  that.panel.clear();
6768
7001
  root.remove();
6769
- } else
7002
+ }
7003
+ else
6770
7004
  {
6771
7005
  options.onclose( this.root );
6772
7006
  }
6773
7007
 
6774
- if(modal)
6775
- LX.modal.toggle(true);
7008
+ if( modal )
7009
+ {
7010
+ LX.modal.toggle( true );
7011
+ }
6776
7012
  };
6777
7013
 
6778
- var closeButton = document.createElement('a');
7014
+ var closeButton = document.createElement( 'a' );
6779
7015
  closeButton.className = "lexdialogcloser fa-solid fa-xmark";
6780
7016
  closeButton.title = "Close";
6781
- closeButton.addEventListener('click', this.close);
7017
+ closeButton.addEventListener( "click", this.close );
6782
7018
 
6783
- if(title) titleDiv.appendChild(closeButton);
6784
- else {
6785
- closeButton.classList.add("notitle");
6786
- root.appendChild(closeButton);
7019
+ if( title )
7020
+ {
7021
+ titleDiv.appendChild( closeButton );
7022
+ }
7023
+ else
7024
+ {
7025
+ closeButton.classList.add( "notitle" );
7026
+ root.appendChild( closeButton );
6787
7027
  }
6788
7028
  }
6789
7029
 
6790
7030
  const panel = new Panel();
6791
- panel.root.classList.add('lexdialogcontent');
6792
- if(!title) panel.root.classList.add('notitle');
6793
- if(callback)
6794
- callback.call(this, panel);
6795
- root.appendChild(panel.root);
7031
+ panel.root.classList.add( "lexdialogcontent" );
7032
+
7033
+ if( !title )
7034
+ {
7035
+ panel.root.classList.add( "notitle" );
7036
+ }
7037
+
7038
+ if( callback )
7039
+ {
7040
+ callback.call( this, panel );
7041
+ }
7042
+
7043
+ root.appendChild( panel.root );
6796
7044
 
6797
7045
  // Make branches have a distintive to manage some cases
6798
7046
  panel.root.querySelectorAll(".lexbranch").forEach( b => b.classList.add("dialog") );
6799
-
7047
+
6800
7048
  this.panel = panel;
6801
7049
  this.root = root;
6802
7050
  this.title = titleDiv;
@@ -6807,22 +7055,30 @@ class Dialog {
6807
7055
  }
6808
7056
 
6809
7057
  // Process position and size
6810
- if(size.length && typeof(size[0]) != "string")
6811
- size[0] += "px";
6812
- if(size.length && typeof(size[1]) != "string")
6813
- size[1] += "px";
7058
+ if( size.length && typeof(size[ 0 ]) != "string" )
7059
+ {
7060
+ size[ 0 ] += "px";
7061
+ }
7062
+
7063
+ if( size.length && typeof(size[ 1 ]) != "string" )
7064
+ {
7065
+ size[ 1 ] += "px";
7066
+ }
6814
7067
 
6815
- root.style.width = size[0] ? (size[0]) : "25%";
6816
- root.style.height = size[1] ? (size[1]) : "auto";
7068
+ root.style.width = size[ 0 ] ? (size[ 0 ]) : "25%";
7069
+ root.style.height = size[ 1 ] ? (size[ 1 ]) : "auto";
7070
+
7071
+ if( options.size )
7072
+ {
7073
+ this.size = size;
7074
+ }
6817
7075
 
6818
- if(options.size) this.size = size;
6819
-
6820
7076
  let rect = root.getBoundingClientRect();
6821
- root.style.left = position[0] ? (position[0]) : "calc( 50% - " + (rect.width * 0.5) + "px )";
6822
- root.style.top = position[1] ? (position[1]) : "calc( 50% - " + (rect.height * 0.5) + "px )";
7077
+ root.style.left = position[ 0 ] ? (position[ 0 ]) : "calc( 50% - " + ( rect.width * 0.5 ) + "px )";
7078
+ root.style.top = position[ 1 ] ? (position[ 1 ]) : "calc( 50% - " + ( rect.height * 0.5 ) + "px )";
6823
7079
 
6824
7080
  panel.root.style.width = "calc( 100% - 30px )";
6825
- panel.root.style.height = title ? "calc( 100% - " + (titleDiv.offsetHeight + 30) + "px )" : "calc( 100% - 51px )";
7081
+ panel.root.style.height = title ? "calc( 100% - " + ( titleDiv.offsetHeight + 30 ) + "px )" : "calc( 100% - 51px )";
6826
7082
  }
6827
7083
 
6828
7084
  destroy() {
@@ -6837,7 +7093,7 @@ class Dialog {
6837
7093
  }
6838
7094
 
6839
7095
  setPosition(x, y) {
6840
-
7096
+
6841
7097
  this.root.style.left = x + "px";
6842
7098
  this.root.style.top = y + "px";
6843
7099
  }
@@ -6866,9 +7122,9 @@ class PocketDialog extends Dialog {
6866
7122
 
6867
7123
  options.draggable = options.draggable ?? false;
6868
7124
  options.closable = options.closable ?? false;
6869
-
7125
+
6870
7126
  super( title, callback, options );
6871
-
7127
+
6872
7128
  let that = this;
6873
7129
  // Update margins on branch title closes/opens
6874
7130
  LX.addSignal("@on_branch_closed", this.panel, closed => {
@@ -6876,7 +7132,7 @@ class PocketDialog extends Dialog {
6876
7132
  this.root.style.top = "calc(100% - " + (this.root.offsetHeight + 6) + "px)";
6877
7133
  });
6878
7134
 
6879
- // Custom
7135
+ // Custom
6880
7136
  this.root.classList.add( "pocket" );
6881
7137
  if( !options.position ) {
6882
7138
  this.root.style.left = "calc(100% - " + (this.root.offsetWidth + 6) + "px)";
@@ -6890,7 +7146,7 @@ class PocketDialog extends Dialog {
6890
7146
  this.title.tabIndex = -1;
6891
7147
  this.title.addEventListener("click", e => {
6892
7148
 
6893
- // Sized dialogs have to keep their size
7149
+ // Sized dialogs have to keep their size
6894
7150
  if( this.size )
6895
7151
  {
6896
7152
  if( !this.minimized ) this.root.style.height = "auto";
@@ -6901,7 +7157,7 @@ class PocketDialog extends Dialog {
6901
7157
  this.minimized = !this.minimized;
6902
7158
 
6903
7159
  if( this.dock_pos == PocketDialog.BOTTOM )
6904
- that.root.style.top = this.root.classList.contains("minimized") ?
7160
+ that.root.style.top = this.root.classList.contains("minimized") ?
6905
7161
  "calc(100% - " + (that.title.offsetHeight + 6) + "px)" : "calc(100% - " + (that.root.offsetHeight + 6) + "px)";
6906
7162
  });
6907
7163
 
@@ -6916,10 +7172,10 @@ class PocketDialog extends Dialog {
6916
7172
  const t = float[i];
6917
7173
  switch( t )
6918
7174
  {
6919
- case 'b':
7175
+ case 'b':
6920
7176
  this.root.style.top = "calc(100% - " + (this.root.offsetHeight + 6) + "px)";
6921
7177
  break;
6922
- case 'l':
7178
+ case 'l':
6923
7179
  this.root.style.left = options.position ? options.position[ 1 ] : "0px";
6924
7180
  break;
6925
7181
  }
@@ -6953,7 +7209,7 @@ LX.PocketDialog = PocketDialog;
6953
7209
  class ContextMenu {
6954
7210
 
6955
7211
  constructor( event, title, options = {} ) {
6956
-
7212
+
6957
7213
  // remove all context menus
6958
7214
  document.body.querySelectorAll(".lexcontextmenubox").forEach(e => e.remove());
6959
7215
 
@@ -6965,7 +7221,7 @@ class ContextMenu {
6965
7221
  this.root.addEventListener("mouseleave", function() {
6966
7222
  this.remove();
6967
7223
  });
6968
-
7224
+
6969
7225
  this.items = [];
6970
7226
  this.colors = {};
6971
7227
 
@@ -6980,11 +7236,11 @@ class ContextMenu {
6980
7236
  }
6981
7237
 
6982
7238
  _adjust_position( div, margin, useAbsolute = false ) {
6983
-
7239
+
6984
7240
  let rect = div.getBoundingClientRect();
6985
-
7241
+
6986
7242
  if( !useAbsolute )
6987
- {
7243
+ {
6988
7244
  let width = rect.width;
6989
7245
  if( rect.left < 0 )
6990
7246
  {
@@ -6994,7 +7250,7 @@ class ContextMenu {
6994
7250
  {
6995
7251
  div.style.left = (window.innerWidth - width - margin) + "px";
6996
7252
  }
6997
-
7253
+
6998
7254
  if( rect.top < 0 )
6999
7255
  {
7000
7256
  div.style.top = margin + "px";
@@ -7011,7 +7267,7 @@ class ContextMenu {
7011
7267
  {
7012
7268
  div.style.left = div.offsetLeft + (dt - margin) + "px";
7013
7269
  }
7014
-
7270
+
7015
7271
  dt = window.innerHeight - (rect.top + rect.height);
7016
7272
  if( dt < 0 )
7017
7273
  {
@@ -7071,14 +7327,14 @@ class ContextMenu {
7071
7327
  entry.addEventListener("click", e => {
7072
7328
  e.stopPropagation();
7073
7329
  e.stopImmediatePropagation();
7074
-
7330
+
7075
7331
  if(disabled) return;
7076
-
7332
+
7077
7333
  const f = o[ 'callback' ];
7078
7334
  if(f) {
7079
7335
  f.call( this, k, entry );
7080
7336
  this.root.remove();
7081
- }
7337
+ }
7082
7338
 
7083
7339
  if( !hasSubmenu )
7084
7340
  return;
@@ -7142,7 +7398,7 @@ class ContextMenu {
7142
7398
  } );
7143
7399
 
7144
7400
  if(found) {
7145
- insert( tokens[idx++], found );
7401
+ insert( tokens[idx++], found );
7146
7402
  }
7147
7403
  else {
7148
7404
  let item = {};
@@ -7153,10 +7409,10 @@ class ContextMenu {
7153
7409
  item[ 'id' ] = options.id;
7154
7410
  item[ 'callback' ] = options.callback;
7155
7411
  item[ 'disabled' ] = options.disabled ?? false;
7156
- }
7412
+ }
7157
7413
 
7158
7414
  list.push( item );
7159
- insert( next_token, item[ token ] );
7415
+ insert( next_token, item[ token ] );
7160
7416
  }
7161
7417
  };
7162
7418
 
@@ -7341,7 +7597,7 @@ class Curve {
7341
7597
  var selected = -1;
7342
7598
 
7343
7599
  element.redraw = function( o = {} ) {
7344
-
7600
+
7345
7601
  if( o.value ) element.value = o.value;
7346
7602
  if( o.xrange ) element.xrange = o.xrange;
7347
7603
  if( o.yrange ) element.yrange = o.yrange;
@@ -7516,7 +7772,7 @@ class Curve {
7516
7772
  e.preventDefault();
7517
7773
  e.stopPropagation();
7518
7774
  }
7519
-
7775
+
7520
7776
  function onchange( e ) {
7521
7777
  if( options.callback )
7522
7778
  options.callback.call( element, element.value, e );
@@ -7555,7 +7811,7 @@ class Curve {
7555
7811
  selected = element.value.indexOf( v );
7556
7812
  }
7557
7813
  }
7558
-
7814
+
7559
7815
  element.redraw();
7560
7816
  return this;
7561
7817
  }
@@ -7584,7 +7840,7 @@ class AssetViewEvent {
7584
7840
  this.value = value;
7585
7841
  this.multiple = false; // Multiple selection
7586
7842
  }
7587
-
7843
+
7588
7844
  string() {
7589
7845
  switch(this.type) {
7590
7846
  case AssetViewEvent.NONE: return "assetview_event_none";
@@ -7640,9 +7896,11 @@ class AssetView {
7640
7896
 
7641
7897
  this.skipBrowser = options.skipBrowser ?? false;
7642
7898
  this.skipPreview = options.skipPreview ?? false;
7899
+ this.useNativeTitle = options.useNativeTitle ?? false;
7643
7900
  this.onlyFolders = options.onlyFolders ?? true;
7644
7901
  this.previewActions = options.previewActions ?? [];
7645
7902
  this.contextMenu = options.contextMenu ?? [];
7903
+ this.onRefreshContent = options.onRefreshContent;
7646
7904
 
7647
7905
  if( !this.skipBrowser )
7648
7906
  {
@@ -7657,7 +7915,7 @@ class AssetView {
7657
7915
  {
7658
7916
  [ contentArea, right ] = contentArea.split({ type: "horizontal", sizes: ["80%", "20%"]});
7659
7917
  }
7660
-
7918
+
7661
7919
  this.allowedTypes = options.allowedTypes || ["None", "Image", "Mesh", "Script", "JSON", "Clip"];
7662
7920
 
7663
7921
  this.prevData = [];
@@ -7675,7 +7933,7 @@ class AssetView {
7675
7933
  }
7676
7934
 
7677
7935
  this._createContentPanel( contentArea );
7678
-
7936
+
7679
7937
  // Create resource preview panel
7680
7938
  if( !this.skipPreview )
7681
7939
  {
@@ -7691,7 +7949,7 @@ class AssetView {
7691
7949
 
7692
7950
  this.prevData.length = 0;
7693
7951
  this.nextData.length = 0;
7694
-
7952
+
7695
7953
  this.data = data;
7696
7954
 
7697
7955
  this._processData( this.data, null );
@@ -7712,14 +7970,17 @@ class AssetView {
7712
7970
  * @method clear
7713
7971
  */
7714
7972
  clear() {
7973
+
7715
7974
  if( this.previewPanel )
7716
7975
  {
7717
7976
  this.previewPanel.clear();
7718
7977
  }
7978
+
7719
7979
  if( this.leftPanel )
7720
7980
  {
7721
7981
  this.leftPanel.clear();
7722
7982
  }
7983
+
7723
7984
  if( this.rightPanel )
7724
7985
  {
7725
7986
  this.rightPanel.clear()
@@ -7751,7 +8012,7 @@ class AssetView {
7751
8012
  */
7752
8013
 
7753
8014
  _updatePath( data ) {
7754
-
8015
+
7755
8016
  this.path.length = 0;
7756
8017
 
7757
8018
  const push_parents_id = i => {
@@ -7790,8 +8051,8 @@ class AssetView {
7790
8051
  children: this.data
7791
8052
  }
7792
8053
 
7793
- this.tree = this.leftPanel.addTree("Content Browser", tree_data, {
7794
- // icons: tree_icons,
8054
+ this.tree = this.leftPanel.addTree( "Content Browser", tree_data, {
8055
+ // icons: tree_icons,
7795
8056
  filter: false,
7796
8057
  onlyFolders: this.onlyFolders,
7797
8058
  onevent: event => {
@@ -7801,7 +8062,7 @@ class AssetView {
7801
8062
 
7802
8063
  switch( event.type )
7803
8064
  {
7804
- case LX.TreeEvent.NODE_SELECTED:
8065
+ case LX.TreeEvent.NODE_SELECTED:
7805
8066
  if( !event.multiple )
7806
8067
  {
7807
8068
  this._enterFolder( node );
@@ -7816,13 +8077,13 @@ class AssetView {
7816
8077
  LX.emit("@on_folder_change", this.path.join('/'));
7817
8078
  }
7818
8079
  break;
7819
- case LX.TreeEvent.NODE_DRAGGED:
8080
+ case LX.TreeEvent.NODE_DRAGGED:
7820
8081
  node.folder = value;
7821
8082
  this._refreshContent();
7822
8083
  break;
7823
8084
  }
7824
8085
  },
7825
- });
8086
+ });
7826
8087
  }
7827
8088
 
7828
8089
  /**
@@ -7851,7 +8112,7 @@ class AssetView {
7851
8112
  this.rightPanel = area.addPanel({ className: 'lexassetcontentpanel' });
7852
8113
  }
7853
8114
 
7854
- const on_sort = (value, event) => {
8115
+ const on_sort = ( value, event ) => {
7855
8116
  const cmenu = addContextMenu( "Sort by", event, c => {
7856
8117
  c.add("Name", () => this._sortData('id') );
7857
8118
  c.add("Type", () => this._sortData('type') );
@@ -7861,10 +8122,12 @@ class AssetView {
7861
8122
  } );
7862
8123
  const parent = this.parent.root.parentElement;
7863
8124
  if( parent.classList.contains('lexdialog') )
8125
+ {
7864
8126
  cmenu.root.style.zIndex = (+getComputedStyle( parent ).zIndex) + 1;
8127
+ }
7865
8128
  }
7866
8129
 
7867
- const on_change_view = (value, event) => {
8130
+ const on_change_view = ( value, event ) => {
7868
8131
  const cmenu = addContextMenu( "Layout", event, c => {
7869
8132
  c.add("Content", () => this._setContentLayout( AssetView.LAYOUT_CONTENT ) );
7870
8133
  c.add("");
@@ -7875,7 +8138,7 @@ class AssetView {
7875
8138
  cmenu.root.style.zIndex = (+getComputedStyle( parent ).zIndex) + 1;
7876
8139
  }
7877
8140
 
7878
- const on_change_page = (value, event) => {
8141
+ const on_change_page = ( value, event ) => {
7879
8142
  if( !this.allowNextPage )
7880
8143
  {
7881
8144
  return;
@@ -7892,13 +8155,13 @@ class AssetView {
7892
8155
  }
7893
8156
 
7894
8157
  this.rightPanel.sameLine();
7895
- this.rightPanel.addDropdown("Filter", this.allowedTypes, this.allowedTypes[0], (v) => this._refreshContent.call(this, null, v), { width: "20%", minWidth: "128px" });
7896
- this.rightPanel.addText(null, this.searchValue ?? "", (v) => this._refreshContent.call(this, v, null), { placeholder: "Search assets.." });
7897
- this.rightPanel.addButton(null, "<a class='fa fa-arrow-up-short-wide'></a>", on_sort.bind(this), { className: "micro", title: "Sort" });
7898
- this.rightPanel.addButton(null, "<a class='fa-solid fa-grip'></a>", on_change_view.bind(this), { className: "micro", title: "View" });
8158
+ this.rightPanel.addDropdown( "Filter", this.allowedTypes, this.allowedTypes[ 0 ], v => this._refreshContent.call(this, null, v), { width: "20%", minWidth: "128px" } );
8159
+ this.rightPanel.addText( null, this.searchValue ?? "", v => this._refreshContent.call(this, v, null), { placeholder: "Search assets.." } );
8160
+ this.rightPanel.addButton( null, "<a class='fa fa-arrow-up-short-wide'></a>", on_sort.bind(this), { className: "micro", title: "Sort" } );
8161
+ this.rightPanel.addButton( null, "<a class='fa-solid fa-grip'></a>", on_change_view.bind(this), { className: "micro", title: "View" } );
7899
8162
  // Content Pages
7900
- this.rightPanel.addButton(null, "<a class='fa-solid fa-angles-left'></a>", on_change_page.bind(this, -1), { className: "micro", title: "Previous Page" });
7901
- this.rightPanel.addButton(null, "<a class='fa-solid fa-angles-right'></a>", on_change_page.bind(this, 1), { className: "micro", title: "Next Page" });
8163
+ this.rightPanel.addButton( null, "<a class='fa-solid fa-angles-left'></a>", on_change_page.bind(this, -1), { className: "micro", title: "Previous Page" } );
8164
+ this.rightPanel.addButton( null, "<a class='fa-solid fa-angles-right'></a>", on_change_page.bind(this, 1), { className: "micro", title: "Next Page" } );
7902
8165
  this.rightPanel.endLine();
7903
8166
 
7904
8167
  if( !this.skipBrowser )
@@ -7908,7 +8171,7 @@ class AssetView {
7908
8171
  {
7909
8172
  value: "Left",
7910
8173
  icon: "fa-solid fa-left-long",
7911
- callback: (domEl) => {
8174
+ callback: domEl => {
7912
8175
  if(!this.prevData.length) return;
7913
8176
  this.nextData.push( this.currentData );
7914
8177
  this.currentData = this.prevData.pop();
@@ -7919,7 +8182,7 @@ class AssetView {
7919
8182
  {
7920
8183
  value: "Right",
7921
8184
  icon: "fa-solid fa-right-long",
7922
- callback: (domEl) => {
8185
+ callback: domEl => {
7923
8186
  if(!this.nextData.length) return;
7924
8187
  this.prevData.push( this.currentData );
7925
8188
  this.currentData = this.nextData.pop();
@@ -7930,7 +8193,7 @@ class AssetView {
7930
8193
  {
7931
8194
  value: "Refresh",
7932
8195
  icon: "fa-solid fa-arrows-rotate",
7933
- callback: (domEl) => { this._refreshContent(); }
8196
+ callback: domEl => { this._refreshContent(); }
7934
8197
  }
7935
8198
  ], { width: "auto", noSelection: true } );
7936
8199
  this.rightPanel.addText(null, this.path.join('/'), null, { disabled: true, signal: "@on_folder_change", style: { fontWeight: "bolder", fontSize: "16px", color: "#aaa" } });
@@ -7942,17 +8205,17 @@ class AssetView {
7942
8205
  this.content.className = "lexassetscontent";
7943
8206
  this.rightPanel.root.appendChild(this.content);
7944
8207
 
7945
- this.content.addEventListener('dragenter', function(e) {
8208
+ this.content.addEventListener('dragenter', function( e ) {
7946
8209
  e.preventDefault();
7947
8210
  this.classList.add('dragging');
7948
8211
  });
7949
- this.content.addEventListener('dragleave', function(e) {
8212
+ this.content.addEventListener('dragleave', function( e ) {
7950
8213
  e.preventDefault();
7951
8214
  this.classList.remove('dragging');
7952
8215
  });
7953
- this.content.addEventListener('drop', (e) => {
8216
+ this.content.addEventListener('drop', ( e ) => {
7954
8217
  e.preventDefault();
7955
- this._processDrop(e);
8218
+ this._processDrop( e );
7956
8219
  });
7957
8220
  this.content.addEventListener('click', function() {
7958
8221
  this.querySelectorAll('.lexassetitem').forEach( i => i.classList.remove('selected') );
@@ -7963,14 +8226,14 @@ class AssetView {
7963
8226
 
7964
8227
  _refreshContent( searchValue, filter ) {
7965
8228
 
7966
- const isContentLayout = (this.layout == AssetView.LAYOUT_CONTENT); // default
8229
+ const isContentLayout = ( this.layout == AssetView.LAYOUT_CONTENT ); // default
7967
8230
 
7968
- this.filter = filter ?? (this.filter ?? "None");
8231
+ this.filter = filter ?? ( this.filter ?? "None" );
7969
8232
  this.searchValue = searchValue ?? (this.searchValue ?? "");
7970
8233
  this.content.innerHTML = "";
7971
8234
  this.content.className = (isContentLayout ? "lexassetscontent" : "lexassetscontent list");
7972
8235
  let that = this;
7973
-
8236
+
7974
8237
  const add_item = function(item) {
7975
8238
 
7976
8239
  const type = item.type.charAt( 0 ).toUpperCase() + item.type.slice( 1 );
@@ -7979,13 +8242,64 @@ class AssetView {
7979
8242
 
7980
8243
  let itemEl = document.createElement('li');
7981
8244
  itemEl.className = "lexassetitem " + item.type.toLowerCase();
7982
- itemEl.title = type + ": " + item.id;
7983
8245
  itemEl.tabIndex = -1;
7984
8246
  that.content.appendChild( itemEl );
7985
8247
 
7986
- if(item.selected != undefined) {
8248
+ if( !that.useNativeTitle )
8249
+ {
8250
+ let desc = document.createElement( 'span' );
8251
+ desc.className = 'lexitemdesc';
8252
+ desc.innerHTML = "File: " + item.id + "<br>Type: " + type;
8253
+ that.content.appendChild( desc );
8254
+
8255
+ itemEl.addEventListener("mousemove", e => {
8256
+
8257
+ if( !isContentLayout )
8258
+ {
8259
+ return;
8260
+ }
8261
+
8262
+ const rect = itemEl.getBoundingClientRect();
8263
+ const targetRect = e.target.getBoundingClientRect();
8264
+ const parentRect = desc.parentElement.getBoundingClientRect();
8265
+
8266
+ let localOffsetX = targetRect.x - parentRect.x - ( targetRect.x - rect.x );
8267
+ let localOffsetY = targetRect.y - parentRect.y - ( targetRect.y - rect.y );
8268
+
8269
+ if( e.target.classList.contains( "lexassettitle" ) )
8270
+ {
8271
+ localOffsetY += ( targetRect.y - rect.y );
8272
+ }
8273
+
8274
+ desc.style.left = (localOffsetX + e.offsetX + 12) + "px";
8275
+ desc.style.top = (localOffsetY + e.offsetY) + "px";
8276
+ });
8277
+
8278
+ itemEl.addEventListener("mouseenter", () => {
8279
+ if( isContentLayout )
8280
+ {
8281
+ desc.style.display = "unset";
8282
+ }
8283
+ });
8284
+
8285
+ itemEl.addEventListener("mouseleave", () => {
8286
+ if( isContentLayout )
8287
+ {
8288
+ setTimeout( () => {
8289
+ desc.style.display = "none";
8290
+ }, 100 );
8291
+ }
8292
+ });
8293
+ }
8294
+ else
8295
+ {
8296
+ itemEl.title = type + ": " + item.id;
8297
+ }
8298
+
8299
+ if( item.selected != undefined )
8300
+ {
7987
8301
  let span = document.createElement('span');
7988
- span.className = "lexcheckbox";
8302
+ span.className = "lexcheckbox";
7989
8303
  let checkbox_input = document.createElement('input');
7990
8304
  checkbox_input.type = "checkbox";
7991
8305
  checkbox_input.className = "checkbox";
@@ -8003,8 +8317,9 @@ class AssetView {
8003
8317
  })
8004
8318
  span.appendChild(checkbox_input);
8005
8319
  itemEl.appendChild(span);
8006
-
8320
+
8007
8321
  }
8322
+
8008
8323
  let title = document.createElement('span');
8009
8324
  title.className = "lexassettitle";
8010
8325
  title.innerText = item.id;
@@ -8027,7 +8342,7 @@ class AssetView {
8027
8342
  preview = document.createElement('svg');
8028
8343
  preview.className = "asset-file-preview";
8029
8344
  itemEl.appendChild(preview);
8030
-
8345
+
8031
8346
  let textEl = document.createElement('text');
8032
8347
  preview.appendChild(textEl);
8033
8348
  // If no extension, e.g. Clip, use the type...
@@ -8073,7 +8388,7 @@ class AssetView {
8073
8388
  {
8074
8389
  that._previewAsset( item );
8075
8390
  }
8076
- }
8391
+ }
8077
8392
  else if( isFolder )
8078
8393
  {
8079
8394
  that._enterFolder( item );
@@ -8095,16 +8410,22 @@ class AssetView {
8095
8410
 
8096
8411
  const multiple = that.content.querySelectorAll('.selected').length;
8097
8412
 
8098
- LX.addContextMenu( multiple > 1 ? (multiple + " selected") :
8413
+ LX.addContextMenu( multiple > 1 ? (multiple + " selected") :
8099
8414
  isFolder ? item.id : item.type, e, m => {
8100
- if(multiple <= 1)
8415
+ if( multiple <= 1 )
8416
+ {
8101
8417
  m.add("Rename");
8418
+ }
8102
8419
  if( !isFolder )
8103
- m.add("Clone", that._clone_item.bind(that, item));
8104
- if(multiple <= 1)
8420
+ {
8421
+ m.add("Clone", that._cloneItem.bind( that, item ));
8422
+ }
8423
+ if( multiple <= 1 )
8424
+ {
8105
8425
  m.add("Properties");
8426
+ }
8106
8427
  m.add("");
8107
- m.add("Delete", that._delete_item.bind(that, item));
8428
+ m.add("Delete", that._deleteItem.bind( that, item ));
8108
8429
  });
8109
8430
  });
8110
8431
  }
@@ -8141,7 +8462,7 @@ class AssetView {
8141
8462
  LX.request({ url: item.path, dataType: 'blob', success: (f) => {
8142
8463
  item.bytesize = f.size;
8143
8464
  fr.readAsDataURL( f );
8144
- fr.onload = e => {
8465
+ fr.onload = e => {
8145
8466
  item.src = e.currentTarget.result; // This is a base64 string...
8146
8467
  item._path = item.path;
8147
8468
  delete item.path;
@@ -8153,8 +8474,14 @@ class AssetView {
8153
8474
  item.domEl = add_item( item );
8154
8475
  }
8155
8476
  }
8477
+
8156
8478
  this.allowNextPage = filteredData.length - 1 > AssetView.MAX_PAGE_ELEMENTS;
8157
8479
  LX.emit("@on_page_change", "Page " + this.contentPage + " / " + ((((filteredData.length - 1) / AssetView.MAX_PAGE_ELEMENTS )|0) + 1));
8480
+
8481
+ if( this.onRefreshContent )
8482
+ {
8483
+ this.onRefreshContent( searchValue, filter );
8484
+ }
8158
8485
  }
8159
8486
 
8160
8487
  /**
@@ -8188,14 +8515,14 @@ class AssetView {
8188
8515
  if( file.type == "folder" ) this.previewPanel.addText("Files", file.children ? file.children.length.toString() : "0", null, options);
8189
8516
 
8190
8517
  this.previewPanel.addSeparator();
8191
-
8518
+
8192
8519
  const previewActions = [...this.previewActions];
8193
8520
 
8194
8521
  if( !previewActions.length )
8195
8522
  {
8196
8523
  // By default
8197
8524
  previewActions.push({
8198
- name: 'Download',
8525
+ name: 'Download',
8199
8526
  callback: () => LX.downloadURL(file.src, file.id)
8200
8527
  });
8201
8528
  }
@@ -8223,8 +8550,8 @@ class AssetView {
8223
8550
  if(result) continue;
8224
8551
 
8225
8552
  fr.readAsDataURL( file );
8226
- fr.onload = e => {
8227
-
8553
+ fr.onload = e => {
8554
+
8228
8555
  let ext = file.name.substr(file.name.lastIndexOf('.') + 1).toLowerCase();
8229
8556
 
8230
8557
  let item = {
@@ -8239,12 +8566,12 @@ class AssetView {
8239
8566
  case 'png':
8240
8567
  case 'jpg':
8241
8568
  item.type = "image"; break;
8242
- case 'js':
8243
- case 'css':
8569
+ case 'js':
8570
+ case 'css':
8244
8571
  item.type = "script"; break;
8245
- case 'json':
8572
+ case 'json':
8246
8573
  item.type = "json"; break;
8247
- case 'obj':
8574
+ case 'obj':
8248
8575
  item.type = "mesh"; break;
8249
8576
  default:
8250
8577
  item.type = ext;
@@ -8253,7 +8580,7 @@ class AssetView {
8253
8580
  }
8254
8581
 
8255
8582
  this.currentData.push( item );
8256
-
8583
+
8257
8584
  if(i == (num_files - 1)) {
8258
8585
  this._refreshContent();
8259
8586
  if( !this.skipBrowser )
@@ -8318,26 +8645,31 @@ class AssetView {
8318
8645
 
8319
8646
  _cloneItem( item ) {
8320
8647
 
8321
- const idx = this.currentData.indexOf(item);
8322
- if(idx > -1) {
8323
- delete item.domEl;
8324
- delete item.folder;
8325
- const new_item = deepCopy( item );
8326
- this.currentData.splice(idx, 0, new_item);
8327
- this._refreshContent(this.searchValue, this.filter);
8648
+ const idx = this.currentData.indexOf( item );
8649
+ if( idx < 0 )
8650
+ {
8651
+ return;
8652
+ }
8653
+
8654
+ delete item.domEl;
8655
+ delete item.folder;
8656
+ const new_item = deepCopy( item );
8657
+ this.currentData.splice( idx, 0, new_item );
8328
8658
 
8329
- if(this.onevent) {
8330
- const event = new AssetViewEvent(AssetViewEvent.ASSET_CLONED, item );
8331
- this.onevent( event );
8332
- }
8659
+ this._refreshContent( this.searchValue, this.filter );
8333
8660
 
8334
- this._processData(this.data);
8661
+ if( this.onevent )
8662
+ {
8663
+ const event = new AssetViewEvent( AssetViewEvent.ASSET_CLONED, item );
8664
+ this.onevent( event );
8335
8665
  }
8666
+
8667
+ this._processData( this.data );
8336
8668
  }
8337
8669
  }
8338
8670
 
8339
8671
  LX.AssetView = AssetView;
8340
-
8672
+
8341
8673
  /*
8342
8674
  * Requests
8343
8675
  */
@@ -8345,12 +8677,12 @@ LX.AssetView = AssetView;
8345
8677
  Object.assign(LX, {
8346
8678
 
8347
8679
  /**
8348
- * Request file from url (it could be a binary, text, etc.). If you want a simplied version use
8680
+ * Request file from url (it could be a binary, text, etc.). If you want a simplied version use
8349
8681
  * @method request
8350
8682
  * @param {Object} request object with all the parameters like data (for sending forms), dataType, success, error
8351
8683
  * @param {Function} on_complete
8352
8684
  **/
8353
- request(request) {
8685
+ request( request ) {
8354
8686
 
8355
8687
  var dataType = request.dataType || "text";
8356
8688
  if(dataType == "json") //parse it locally
@@ -8465,7 +8797,7 @@ Object.assign(LX, {
8465
8797
  requestBinary(url, on_complete, on_error ) {
8466
8798
  return this.request({ url: url, dataType:"binary", success: on_complete, error: on_error });
8467
8799
  },
8468
-
8800
+
8469
8801
  /**
8470
8802
  * Request script and inserts it in the DOM
8471
8803
  * @method requireScript
@@ -8494,7 +8826,7 @@ Object.assign(LX, {
8494
8826
  script.src = url[i] + ( version ? "?version=" + version : "" );
8495
8827
  script.original_src = url[i];
8496
8828
  script.async = false;
8497
- script.onload = function(e) {
8829
+ script.onload = function(e) {
8498
8830
  total--;
8499
8831
  loaded_scripts.push(this);
8500
8832
  if(total)
@@ -8506,7 +8838,7 @@ Object.assign(LX, {
8506
8838
  on_complete( loaded_scripts );
8507
8839
  };
8508
8840
  if(on_error)
8509
- script.onerror = function(err) {
8841
+ script.onerror = function(err) {
8510
8842
  on_error(err, this.original_src, this.num );
8511
8843
  }
8512
8844
  document.getElementsByTagName('head')[0].appendChild(script);
@@ -8530,7 +8862,7 @@ Object.assign(LX, {
8530
8862
  {
8531
8863
  LX.request({ url: url, dataType: 'blob', success: (f) => {
8532
8864
  fr.readAsDataURL( f );
8533
- fr.onload = e => {
8865
+ fr.onload = e => {
8534
8866
  _download(e.currentTarget.result);
8535
8867
  };
8536
8868
  } });
@@ -8616,31 +8948,31 @@ LX.UTILS = {
8616
8948
  element.offsetHeight;
8617
8949
  },
8618
8950
  getControlPoints( x0, y0, x1, y1, x2, y2, t ) {
8619
-
8951
+
8620
8952
  // x0,y0,x1,y1 are the coordinates of the end (knot) pts of this segment
8621
8953
  // x2,y2 is the next knot -- not connected here but needed to calculate p2
8622
8954
  // p1 is the control point calculated here, from x1 back toward x0.
8623
- // p2 is the next control point, calculated here and returned to become the
8955
+ // p2 is the next control point, calculated here and returned to become the
8624
8956
  // next segment's p1.
8625
8957
  // t is the 'tension' which controls how far the control points spread.
8626
-
8958
+
8627
8959
  // Scaling factors: distances from this knot to the previous and following knots.
8628
8960
  var d01=Math.sqrt(Math.pow(x1-x0,2)+Math.pow(y1-y0,2));
8629
8961
  var d12=Math.sqrt(Math.pow(x2-x1,2)+Math.pow(y2-y1,2));
8630
-
8962
+
8631
8963
  var fa=t*d01/(d01+d12);
8632
8964
  var fb=t-fa;
8633
-
8965
+
8634
8966
  var p1x=x1+fa*(x0-x2);
8635
8967
  var p1y=y1+fa*(y0-y2);
8636
-
8968
+
8637
8969
  var p2x=x1-fb*(x0-x2);
8638
- var p2y=y1-fb*(y0-y2);
8639
-
8970
+ var p2y=y1-fb*(y0-y2);
8971
+
8640
8972
  return [p1x,p1y,p2x,p2y]
8641
8973
  },
8642
8974
  drawSpline( ctx, pts, t ) {
8643
-
8975
+
8644
8976
  ctx.save();
8645
8977
  var cp=[]; // array of control points, as x0,y0,x1,y1,...
8646
8978
  var n=pts.length;
@@ -8648,7 +8980,7 @@ LX.UTILS = {
8648
8980
  // Draw an open curve, not connected at the ends
8649
8981
  for(var i=0;i<n-4;i+=2) {
8650
8982
  cp=cp.concat(LX.UTILS.getControlPoints(pts[i],pts[i+1],pts[i+2],pts[i+3],pts[i+4],pts[i+5],t));
8651
- }
8983
+ }
8652
8984
 
8653
8985
  for(var i=2;i<pts.length-5;i+=2) {
8654
8986
  ctx.beginPath();
@@ -8664,13 +8996,13 @@ LX.UTILS = {
8664
8996
  ctx.quadraticCurveTo(cp[0],cp[1],pts[2],pts[3]);
8665
8997
  ctx.stroke();
8666
8998
  ctx.closePath();
8667
-
8999
+
8668
9000
  ctx.beginPath();
8669
9001
  ctx.moveTo(pts[n-2],pts[n-1]);
8670
9002
  ctx.quadraticCurveTo(cp[2*n-10],cp[2*n-9],pts[n-4],pts[n-3]);
8671
9003
  ctx.stroke();
8672
9004
  ctx.closePath();
8673
-
9005
+
8674
9006
  ctx.restore();
8675
9007
  }
8676
9008
  };