lexgui 0.1.42 → 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.42",
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
+ }
382
416
 
383
- let itemContainer = document.createElement("div");
417
+ gsTabs.add( "All", document.createElement('div'), { selected: true, onSelect: _onSelectTab } );
418
+ // gsTabs.add( "Main", document.createElement('div'), { onSelect: _onSelectTab } );
419
+ }
420
+
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
551
+ * skipRoot: Skip adding LX root container
501
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,7 +616,7 @@ function init( options = { } )
555
616
  this.ready = true;
556
617
  this.menubars = [ ];
557
618
 
558
- if( !options.skipDefaultArea )
619
+ if( !options.skipRoot && !options.skipDefaultArea )
559
620
  {
560
621
  this.main_area = new Area( { id: options.id ?? 'mainarea' } );
561
622
  }
@@ -567,9 +628,9 @@ LX.init = init;
567
628
 
568
629
  /**
569
630
  * @method message
570
- * @param {String} text
631
+ * @param {String} text
571
632
  * @param {String} title (Optional)
572
- * @param {*} options
633
+ * @param {*} options
573
634
  * id: Id of the message dialog
574
635
  * position: Dialog position in screen [screen centered]
575
636
  * draggable: Dialog can be dragged [false]
@@ -593,9 +654,9 @@ LX.message = message;
593
654
 
594
655
  /**
595
656
  * @method popup
596
- * @param {String} text
657
+ * @param {String} text
597
658
  * @param {String} title (Optional)
598
- * @param {*} options
659
+ * @param {*} options
599
660
  * id: Id of the message dialog
600
661
  * time: (Number) Delay time before close automatically (ms). Defalut: [3000]
601
662
  * position: (Array) [x,y] Dialog position in screen. Default: [screen centered]
@@ -612,17 +673,17 @@ function popup( text, title, options = {} )
612
673
  options.size = options.size ?? [ "auto", "auto" ];
613
674
  options.class = "lexpopup";
614
675
 
615
- const time = options.timeout || 3000;
676
+ const time = options.timeout || 3000;
616
677
  const dialog = new Dialog( title, p => {
617
678
  p.addTextArea( null, text, null, { disabled: true, fitHeight: true } );
618
679
  }, options );
619
-
680
+
620
681
  dialog.root.classList.add( 'fadein' );
621
682
  setTimeout(() => {
622
683
  dialog.root.classList.remove( 'fadein' );
623
684
  dialog.root.classList.add( 'fadeout' );
624
685
  }, time - 1000 );
625
-
686
+
626
687
  setTimeout( dialog.close, time );
627
688
 
628
689
  return dialog;
@@ -632,9 +693,9 @@ LX.popup = popup;
632
693
 
633
694
  /**
634
695
  * @method prompt
635
- * @param {String} text
696
+ * @param {String} text
636
697
  * @param {String} title (Optional)
637
- * @param {*} options
698
+ * @param {*} options
638
699
  * id: Id of the prompt dialog
639
700
  * position: Dialog position in screen [screen centered]
640
701
  * draggable: Dialog can be dragged [false]
@@ -683,7 +744,7 @@ function prompt( text, title, callback, options = {} )
683
744
  {
684
745
  dialog.root.querySelector( 'input' ).focus();
685
746
  }
686
-
747
+
687
748
  return dialog;
688
749
  }
689
750
 
@@ -723,7 +784,7 @@ class TreeEvent {
723
784
  this.multiple = false; // Multiple selection
724
785
  this.panel = null;
725
786
  }
726
-
787
+
727
788
  string() {
728
789
  switch( this.type )
729
790
  {
@@ -768,7 +829,7 @@ function emit( signalName, value, options = {} )
768
829
  if( obj.constructor === Widget )
769
830
  {
770
831
  obj.set( value, options.skipCallback ?? true );
771
-
832
+
772
833
  if( obj.options && obj.options.callback )
773
834
  {
774
835
  obj.options.callback( value, data );
@@ -791,7 +852,7 @@ function addSignal( name, obj, callback )
791
852
  {
792
853
  LX.signals[ name ] = [];
793
854
  }
794
-
855
+
795
856
  if( LX.signals[ name ].indexOf( obj ) > -1 )
796
857
  {
797
858
  return;
@@ -810,16 +871,20 @@ class Area {
810
871
 
811
872
  /**
812
873
  * @constructor Area
813
- * @param {*} options
874
+ * @param {*} options
814
875
  * id: Id of the element
815
876
  * className: Add class to the element
816
877
  * width: Width of the area element [fit space]
817
878
  * height: Height of the area element [fit space]
818
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
819
884
  */
820
885
 
821
886
  constructor( options = {} ) {
822
-
887
+
823
888
  var root = document.createElement( 'div' );
824
889
  root.className = "lexarea";
825
890
  if( options.id )
@@ -833,7 +898,7 @@ class Area {
833
898
 
834
899
  var width = options.width || "calc( 100% )";
835
900
  var height = options.height || "100%";
836
-
901
+
837
902
  // This has default options..
838
903
  this.setLimitBox( options.minWidth, options.minHeight, options.maxWidth, options.maxHeight );
839
904
 
@@ -883,7 +948,7 @@ class Area {
883
948
  {
884
949
  this.root.style.bottom = options.bottom;
885
950
  }
886
-
951
+
887
952
  const draggable = options.draggable ?? true;
888
953
  if( draggable )
889
954
  {
@@ -894,9 +959,9 @@ class Area {
894
959
  {
895
960
  root.classList.add("resizeable");
896
961
  }
897
-
962
+
898
963
  if( options.resize )
899
- {
964
+ {
900
965
  this.splitBar = document.createElement("div");
901
966
  let type = (overlay == "left") || (overlay == "right") ? "horizontal" : "vertical";
902
967
  this.type = overlay;
@@ -906,7 +971,7 @@ class Area {
906
971
  {
907
972
  this.splitBar.style.width = LX.DEFAULT_SPLITBAR_SIZE + "px";
908
973
  this.splitBar.style.left = -(LX.DEFAULT_SPLITBAR_SIZE / 2.0) + "px";
909
- }
974
+ }
910
975
  else if( overlay == "left" )
911
976
  {
912
977
  let size = Math.min(document.body.clientWidth - LX.DEFAULT_SPLITBAR_SIZE, this.root.clientWidth);
@@ -927,10 +992,10 @@ class Area {
927
992
 
928
993
  this.splitBar.addEventListener("mousedown", inner_mousedown);
929
994
  this.root.appendChild( this.splitBar );
930
-
995
+
931
996
  var that = this;
932
997
  var lastMousePosition = [ 0, 0 ];
933
-
998
+
934
999
  function inner_mousedown( e )
935
1000
  {
936
1001
  var doc = that.root.ownerDocument;
@@ -970,13 +1035,13 @@ class Area {
970
1035
  that.root.style.height = size + "px";
971
1036
  break;
972
1037
  }
973
-
1038
+
974
1039
  lastMousePosition[ 0 ] = e.x;
975
1040
  lastMousePosition[ 1 ] = e.y;
976
1041
  e.stopPropagation();
977
1042
  e.preventDefault();
978
-
979
- // Resize events
1043
+
1044
+ // Resize events
980
1045
  if( that.onresize )
981
1046
  {
982
1047
  that.onresize( that.root.getBoundingClientRect() );
@@ -1022,11 +1087,11 @@ class Area {
1022
1087
 
1023
1088
  /**
1024
1089
  * @method split
1025
- * @param {*} options
1090
+ * @param {*} options
1026
1091
  * type: Split mode (horizontal, vertical) ["horizontal"]
1027
1092
  * sizes: Size of each new area (Array) ["50%", "50%"]
1028
1093
  */
1029
-
1094
+
1030
1095
  split( options = {} ) {
1031
1096
 
1032
1097
  if( this.sections.length )
@@ -1039,7 +1104,6 @@ class Area {
1039
1104
 
1040
1105
  var type = options.type || "horizontal";
1041
1106
  var sizes = options.sizes || [ "50%", "50%" ];
1042
- var infer_height = false;
1043
1107
  var auto = (options.sizes === 'auto');
1044
1108
 
1045
1109
  if( !sizes[ 1 ] )
@@ -1051,9 +1115,8 @@ class Area {
1051
1115
  size += margin;
1052
1116
  size += "px";
1053
1117
  }
1054
-
1118
+
1055
1119
  sizes[ 1 ] = "calc( 100% - " + size + " )";
1056
- infer_height = true;
1057
1120
  }
1058
1121
 
1059
1122
  // Create areas
@@ -1084,18 +1147,18 @@ class Area {
1084
1147
  this.splitBar.style.height = LX.DEFAULT_SPLITBAR_SIZE + "px";
1085
1148
  }
1086
1149
 
1087
- this.splitBar.addEventListener( 'mousedown', inner_mousedown );
1150
+ this.splitBar.addEventListener( 'mousedown', innerMouseDown );
1088
1151
 
1089
1152
  data = ( LX.DEFAULT_SPLITBAR_SIZE / 2 ) + "px"; // updates
1090
1153
 
1091
1154
  // Being minimizable means it's also resizeable!
1092
1155
  if( minimizable )
1093
1156
  {
1094
- this.split_extended = false;
1157
+ this.splitExtended = false;
1095
1158
 
1096
1159
  // Keep state of the animation when ends...
1097
1160
  area2.root.addEventListener('animationend', e => {
1098
- const opacity = getComputedStyle(area2.root).opacity;
1161
+ const opacity = getComputedStyle( area2.root ).opacity;
1099
1162
  area2.root.classList.remove( e.animationName + "-" + type );
1100
1163
  area2.root.style.opacity = opacity;
1101
1164
  flushCss(area2.root);
@@ -1104,8 +1167,8 @@ class Area {
1104
1167
  this.splitBar.addEventListener("contextmenu", e => {
1105
1168
  e.preventDefault();
1106
1169
  addContextMenu(null, e, c => {
1107
- c.add("Extend", { disabled: this.split_extended, callback: () => { this.extend() } });
1108
- 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() } });
1109
1172
  });
1110
1173
  });
1111
1174
  }
@@ -1117,9 +1180,14 @@ class Area {
1117
1180
  width2 = sizes[ 1 ];
1118
1181
 
1119
1182
  if( width1.constructor == Number )
1183
+ {
1120
1184
  width1 += "px";
1185
+ }
1186
+
1121
1187
  if( width2.constructor == Number )
1188
+ {
1122
1189
  width2 += "px";
1190
+ }
1123
1191
 
1124
1192
  area1.root.style.width = "calc( " + width1 + " - " + data + " )";
1125
1193
  area1.root.style.height = "calc(100% - 0px)";
@@ -1151,10 +1219,15 @@ class Area {
1151
1219
  var height1 = sizes[ 0 ],
1152
1220
  height2 = sizes[ 1 ];
1153
1221
 
1154
- if(height1.constructor == Number)
1222
+ if( height1.constructor == Number )
1223
+ {
1155
1224
  height1 += "px";
1156
- if(height2.constructor == Number)
1225
+ }
1226
+
1227
+ if( height2.constructor == Number )
1228
+ {
1157
1229
  height2 += "px";
1230
+ }
1158
1231
 
1159
1232
  area1.root.style.width = "100%";
1160
1233
  area1.root.style.height = "calc( " + height1 + " - " + data + " )";
@@ -1182,34 +1255,28 @@ class Area {
1182
1255
  }
1183
1256
 
1184
1257
  var that = this;
1185
- var lastMousePosition = [ 0, 0 ];
1186
1258
 
1187
- function inner_mousedown( e )
1259
+ function innerMouseDown( e )
1188
1260
  {
1189
1261
  var doc = that.root.ownerDocument;
1190
- doc.addEventListener( 'mousemove', inner_mousemove );
1191
- doc.addEventListener( 'mouseup', inner_mouseup );
1192
- lastMousePosition[0] = e.x;
1193
- lastMousePosition[1] = e.y;
1262
+ doc.addEventListener( 'mousemove', innerMouseMove );
1263
+ doc.addEventListener( 'mouseup', innerMouseUp );
1194
1264
  e.stopPropagation();
1195
1265
  e.preventDefault();
1196
1266
  document.body.classList.add( 'nocursor' );
1197
1267
  that.splitBar.classList.add( 'nocursor' );
1198
1268
  }
1199
1269
 
1200
- function inner_mousemove( e )
1270
+ function innerMouseMove( e )
1201
1271
  {
1202
- if(that.type == "horizontal")
1272
+ if( that.type == "horizontal" )
1203
1273
  {
1204
- that._moveSplit( lastMousePosition[ 0 ] - e.x );
1274
+ that._moveSplit( -e.movementX );
1205
1275
  }
1206
1276
  else
1207
1277
  {
1208
- that._moveSplit( lastMousePosition[ 1 ] - e.y );
1278
+ that._moveSplit( -e.movementY );
1209
1279
  }
1210
-
1211
- lastMousePosition[ 0 ] = e.x;
1212
- lastMousePosition[ 1 ] = e.y;
1213
1280
 
1214
1281
  const widgets = that.root.querySelectorAll( ".lexwidget" );
1215
1282
 
@@ -1228,11 +1295,11 @@ class Area {
1228
1295
  e.preventDefault();
1229
1296
  }
1230
1297
 
1231
- function inner_mouseup( e )
1298
+ function innerMouseUp( e )
1232
1299
  {
1233
1300
  var doc = that.root.ownerDocument;
1234
- doc.removeEventListener( 'mousemove', inner_mousemove );
1235
- doc.removeEventListener( 'mouseup', inner_mouseup );
1301
+ doc.removeEventListener( 'mousemove', innerMouseMove );
1302
+ doc.removeEventListener( 'mouseup', innerMouseUp );
1236
1303
  document.body.classList.remove( 'nocursor' );
1237
1304
  that.splitBar.classList.remove( 'nocursor' );
1238
1305
  }
@@ -1257,7 +1324,7 @@ class Area {
1257
1324
  * Resize element
1258
1325
  */
1259
1326
  setSize( size ) {
1260
-
1327
+
1261
1328
  let [ width, height ] = size;
1262
1329
 
1263
1330
  if( width != undefined && width.constructor == Number )
@@ -1291,22 +1358,22 @@ class Area {
1291
1358
  */
1292
1359
  extend() {
1293
1360
 
1294
- if( this.split_extended )
1361
+ if( this.splitExtended )
1295
1362
  {
1296
1363
  return;
1297
1364
  }
1298
1365
 
1299
1366
  let [area1, area2] = this.sections;
1300
- this.split_extended = true;
1367
+ this.splitExtended = true;
1301
1368
 
1302
1369
  if(this.type == "vertical")
1303
1370
  {
1304
1371
  this.offset = area2.root.offsetHeight;
1305
1372
  area2.root.classList.add("fadeout-vertical");
1306
- this._moveSplit(-Infinity, true);
1373
+ this._moveSplit(-Infinity, true);
1307
1374
 
1308
1375
  }
1309
- else
1376
+ else
1310
1377
  {
1311
1378
  this.offset = area2.root.offsetWidth - 8; // Force some height here...
1312
1379
  area2.root.classList.add("fadeout-horizontal");
@@ -1323,10 +1390,10 @@ class Area {
1323
1390
  */
1324
1391
  reduce() {
1325
1392
 
1326
- if( !this.split_extended )
1393
+ if( !this.splitExtended )
1327
1394
  return;
1328
-
1329
- this.split_extended = false;
1395
+
1396
+ this.splitExtended = false;
1330
1397
  let [area1, area2] = this.sections;
1331
1398
 
1332
1399
  if(this.type == "vertical")
@@ -1404,7 +1471,7 @@ class Area {
1404
1471
  */
1405
1472
 
1406
1473
  addMenubar( callback, options = {} ) {
1407
-
1474
+
1408
1475
  let menubar = new Menubar(options);
1409
1476
 
1410
1477
  if(callback) callback( menubar );
@@ -1448,10 +1515,11 @@ class Area {
1448
1515
  */
1449
1516
 
1450
1517
  addOverlayButtons( buttons, options = {} ) {
1451
-
1518
+
1452
1519
  // Add to last split section if area has been split
1453
- if(this.sections.length) {
1454
- this.sections[1].addOverlayButtons( buttons, options );
1520
+ if( this.sections.length )
1521
+ {
1522
+ this.sections[ 1 ].addOverlayButtons( buttons, options );
1455
1523
  return;
1456
1524
  }
1457
1525
 
@@ -1461,8 +1529,14 @@ class Area {
1461
1529
  this.root.style.position = "relative";
1462
1530
 
1463
1531
  options.className = "lexoverlaybuttons";
1464
- options.width = "calc( 100% - 24px )";
1465
- 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 );
1466
1540
 
1467
1541
  const float = options.float;
1468
1542
 
@@ -1474,24 +1548,21 @@ class Area {
1474
1548
  switch( t )
1475
1549
  {
1476
1550
  case 'h': break;
1477
- case 'v': options.className += " vertical"; break;
1551
+ case 'v': container.className += " vertical"; break;
1478
1552
  case 't': break;
1479
- case 'm': options.className += " middle"; break;
1480
- case 'b': options.className += " bottom"; break;
1553
+ case 'm': container.className += " middle"; break;
1554
+ case 'b': container.className += " bottom"; break;
1481
1555
  case 'l': break;
1482
- case 'c': options.className += " center"; break;
1483
- case 'r': options.className += " right"; break;
1556
+ case 'c': container.className += " center"; break;
1557
+ case 'r': container.className += " right"; break;
1484
1558
  }
1485
1559
  }
1486
1560
  }
1487
1561
 
1488
- let overlayPanel = this.addPanel( options );
1489
- let overlaygroup;
1490
-
1491
- const add_button = function(b, group, last) {
1562
+ const _addButton = function( b, group, last ) {
1492
1563
 
1493
- const _options = {
1494
- width: "auto",
1564
+ const _options = {
1565
+ width: "auto",
1495
1566
  selectable: b.selectable,
1496
1567
  selected: b.selected,
1497
1568
  icon: b.icon,
@@ -1501,55 +1572,54 @@ class Area {
1501
1572
 
1502
1573
  if( group )
1503
1574
  {
1504
- if(!overlaygroup) {
1505
- overlaygroup = document.createElement('div');
1506
- overlaygroup.className = "lexoverlaygroup";
1507
- overlayPanel.queuedContainer = overlaygroup;
1575
+ if( !overlayGroup )
1576
+ {
1577
+ overlayGroup = document.createElement('div');
1578
+ overlayGroup.className = "lexoverlaygroup";
1579
+ overlayPanel.queuedContainer = overlayGroup;
1508
1580
  }
1509
1581
 
1510
- _options.parent = overlaygroup;
1582
+ _options.parent = overlayGroup;
1511
1583
  }
1512
1584
 
1513
1585
  let callback = b.callback;
1514
1586
 
1515
1587
  if( b.options )
1516
1588
  {
1517
- callback = function(value, event) {
1518
- LX.addContextMenu(null, event, function(c) {
1519
- for( let o of b.options )
1520
- c.add(o, () => {
1521
- if( b.name == o ) return;
1522
- b.name = o;
1523
- b.callback( o );
1524
- refresh_panel();
1525
- });
1526
- });
1527
- };
1589
+ overlayPanel.addDropdown( null, b.options, b.name, callback, _options );
1528
1590
  }
1529
-
1530
- overlayPanel.addButton( null, b.name, function(value, event) {
1531
- if(b.selectable) {
1532
- if( b.group ) {
1533
- let _prev = b.selected;
1534
- b.group.forEach( sub => sub.selected = false );
1535
- 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
+ }
1536
1606
  }
1537
- else
1538
- b.selected = !b.selected;
1539
- }
1540
- callback( value, event );
1541
- }, _options );
1607
+
1608
+ callback( value, event );
1609
+
1610
+ }, _options );
1611
+ }
1542
1612
 
1543
1613
  // ends the group
1544
- if(overlaygroup && last)
1614
+ if( overlayGroup && last )
1545
1615
  {
1546
- overlayPanel.root.appendChild( overlaygroup );
1547
- overlaygroup = null;
1616
+ overlayPanel.root.appendChild( overlayGroup );
1617
+ overlayGroup = null;
1548
1618
  overlayPanel.clearQueue();
1549
1619
  }
1550
1620
  }
1551
1621
 
1552
- const refresh_panel = function() {
1622
+ const _refreshPanel = function() {
1553
1623
 
1554
1624
  overlayPanel.clear();
1555
1625
 
@@ -1559,13 +1629,14 @@ class Area {
1559
1629
  {
1560
1630
  for( let i = 0; i < b.length; ++i )
1561
1631
  {
1562
- let sub = b[i];
1632
+ let sub = b[ i ];
1563
1633
  sub.group = b;
1564
- add_button(sub, true, i == (b.length - 1));
1634
+ _addButton(sub, true, i == ( b.length - 1 ));
1565
1635
  }
1566
- }else
1636
+ }
1637
+ else
1567
1638
  {
1568
- add_button(b);
1639
+ _addButton( b );
1569
1640
  }
1570
1641
 
1571
1642
  }
@@ -1576,16 +1647,16 @@ class Area {
1576
1647
  var height = 0;
1577
1648
  overlayPanel.root.childNodes.forEach( c => { height += c.offsetHeight; } );
1578
1649
 
1579
- if( options.className.includes("middle") )
1650
+ if( container.className.includes( "middle" ) )
1580
1651
  {
1581
- overlayPanel.root.style.top = "-moz-calc( 50% - " + (height * 0.5) + "px )";
1582
- overlayPanel.root.style.top = "-webkit-calc( 50% - " + (height * 0.5) + "px )";
1583
- 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 )";
1584
1655
  }
1585
1656
  }
1586
1657
  }
1587
1658
 
1588
- refresh_panel();
1659
+ _refreshPanel();
1589
1660
  }
1590
1661
 
1591
1662
  /**
@@ -1601,65 +1672,77 @@ class Area {
1601
1672
  {
1602
1673
  this.parentArea._disableSplitResize();
1603
1674
  // Compensate split bar...
1604
- this.root.style.paddingTop = "4px";
1675
+ this.root.style.paddingTop = "4px";
1605
1676
  }
1606
1677
 
1607
1678
  return tabs;
1608
1679
  }
1609
1680
 
1610
- _moveSplit( dt, force_animation = false, force_width = 0 ) {
1681
+ _moveSplit( dt, forceAnimation = false, forceWidth = 0 ) {
1611
1682
 
1612
1683
  if( !this.type )
1684
+ {
1613
1685
  throw( "No split area" );
1686
+ }
1614
1687
 
1615
1688
  if( dt === undefined ) // Splitbar didn't move!
1689
+ {
1616
1690
  return;
1691
+ }
1617
1692
 
1618
- var a1 = this.sections[ 0 ];
1619
- var a2 = this.sections[ 1 ];
1620
- var splitinfo = " - "+ LX.DEFAULT_SPLITBAR_SIZE + "px";
1693
+ const a1 = this.sections[ 0 ];
1694
+ var a1Root = a1.root;
1695
+
1696
+ if( !a1Root.classList.contains( "origin" ) )
1697
+ {
1698
+ a1Root = a1Root.parentElement;
1699
+ }
1700
+
1701
+ const a2 = this.sections[ 1 ];
1702
+ const a2Root = a2.root;
1703
+ const splitData = " - "+ LX.DEFAULT_SPLITBAR_SIZE + "px";
1621
1704
 
1622
1705
  let transition = null;
1623
- if( !force_animation )
1706
+ if( !forceAnimation )
1624
1707
  {
1625
1708
  // Remove transitions for this change..
1626
- transition = a1.root.style.transition;
1627
- a1.root.style.transition = a2.root.style.transition = "none";
1628
- flushCss( a1.root );
1629
- flushCss( a2.root );
1709
+ transition = a1Root.style.transition;
1710
+ a1Root.style.transition = a2Root.style.transition = "none";
1711
+ flushCss( a1Root );
1712
+ flushCss( a2Root );
1630
1713
  }
1631
1714
 
1632
1715
  if( this.type == "horizontal" )
1633
1716
  {
1634
- var size = Math.max( a2.root.offsetWidth + dt, parseInt( a2.minWidth ) );
1635
- if( force_width ) size = force_width;
1636
- a1.root.style.width = "-moz-calc( 100% - " + size + "px " + splitinfo + " )";
1637
- a1.root.style.width = "-webkit-calc( 100% - " + size + "px " + splitinfo + " )";
1638
- a1.root.style.width = "calc( 100% - " + size + "px " + splitinfo + " )";
1639
- a1.root.style.minWidth = parseInt( a1.minWidth ) + "px";
1640
- a2.root.style.width = size + "px";
1641
- 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" + " )";
1642
1725
  }
1643
1726
  else
1644
1727
  {
1645
- var size = Math.max((a2.root.offsetHeight + dt) + a2.offset, parseInt(a2.minHeight));
1646
- if( force_width ) size = force_width;
1647
- a1.root.style.height = "-moz-calc( 100% - " + size + "px " + splitinfo + " )";
1648
- a1.root.style.height = "-webkit-calc( 100% - " + size + "px " + splitinfo + " )";
1649
- a1.root.style.height = "calc( 100% - " + size + "px " + splitinfo + " )";
1650
- a1.root.style.minHeight = a1.minHeight + "px";
1651
- 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";
1652
1735
  }
1653
-
1654
- if( !force_animation )
1736
+
1737
+ if( !forceAnimation )
1655
1738
  {
1656
1739
  // Reapply transitions
1657
- a1.root.style.transition = a2.root.style.transition = transition;
1740
+ a1Root.style.transition = a2Root.style.transition = transition;
1658
1741
  }
1659
1742
 
1660
1743
  this._update();
1661
1744
 
1662
- // Resize events
1745
+ // Resize events
1663
1746
  this.propagateEvent( 'onresize' );
1664
1747
  }
1665
1748
 
@@ -1741,7 +1824,7 @@ class Tabs {
1741
1824
 
1742
1825
  // Show on drop
1743
1826
  el.click();
1744
-
1827
+
1745
1828
  // Store info
1746
1829
  that.tabs[ el.dataset["name"] ] = content;
1747
1830
  });
@@ -1782,11 +1865,11 @@ class Tabs {
1782
1865
  }
1783
1866
 
1784
1867
  // debug
1785
- if(folding)
1868
+ if(folding)
1786
1869
  {
1787
1870
  this.folded = true;
1788
1871
  this.folding = folding;
1789
-
1872
+
1790
1873
  if(folding == "up") area.root.insertChildAtIndex(area.sections[1].root, 0);
1791
1874
 
1792
1875
  // Listen resize event on parent area
@@ -1813,7 +1896,7 @@ class Tabs {
1813
1896
  this.root.querySelectorAll( 'span' ).forEach( s => s.classList.remove( 'selected' ) );
1814
1897
  this.area.root.querySelectorAll( '.lextabcontent' ).forEach( c => c.style.display = 'none' );
1815
1898
  }
1816
-
1899
+
1817
1900
  isSelected = !Object.keys( this.tabs ).length && !this.folding ? true : isSelected;
1818
1901
 
1819
1902
  let contentEl = content.root ? content.root : content;
@@ -1841,7 +1924,7 @@ class Tabs {
1841
1924
  tabEl.className = "lexareatab" + ( isSelected ? " selected" : "" );
1842
1925
  tabEl.innerHTML = ( options.icon ?? "" ) + name;
1843
1926
  tabEl.id = name.replace( /\s/g, '' ) + Tabs.TAB_ID++;
1844
- tabEl.title = options.title;
1927
+ tabEl.title = options.title ?? "";
1845
1928
  tabEl.selected = isSelected ?? false;
1846
1929
  tabEl.fixed = options.fixed;
1847
1930
  tabEl.instance = this;
@@ -1856,9 +1939,9 @@ class Tabs {
1856
1939
  if( this.parentElement.childNodes.length == 1 )
1857
1940
  {
1858
1941
  this.parentElement.childNodes[ 0 ].click(); // single tab!!
1859
- }
1942
+ }
1860
1943
  } );
1861
-
1944
+
1862
1945
  tabEl.addEventListener("click", e => {
1863
1946
 
1864
1947
  e.preventDefault();
@@ -1869,11 +1952,11 @@ class Tabs {
1869
1952
  // For folding tabs
1870
1953
  const lastValue = tabEl.selected;
1871
1954
  tabEl.parentElement.querySelectorAll( 'span' ).forEach( s => s.selected = false );
1872
- tabEl.selected = !lastValue;
1955
+ tabEl.selected = !lastValue;
1873
1956
  // Manage selected
1874
1957
  tabEl.parentElement.querySelectorAll( 'span' ).forEach( s => s.classList.remove( 'selected' ));
1875
1958
  tabEl.classList.toggle('selected', ( this.folding && tabEl.selected ));
1876
- // Manage visibility
1959
+ // Manage visibility
1877
1960
  tabEl.instance.area.root.querySelectorAll( '.lextabcontent' ).forEach( c => c.style.display = 'none' );
1878
1961
  contentEl.style.display = contentEl.originalDisplay;
1879
1962
  tabEl.instance.selected = tabEl.dataset.name;
@@ -1917,16 +2000,16 @@ class Tabs {
1917
2000
  this.delete( tabEl.dataset[ "name" ] );
1918
2001
  }
1919
2002
  });
1920
-
2003
+
1921
2004
  tabEl.setAttribute( 'draggable', true );
1922
2005
  tabEl.addEventListener( 'dragstart', function( e ) {
1923
2006
  if( this.parentElement.childNodes.length == 1 ){
1924
2007
  e.preventDefault();
1925
2008
  return;
1926
- }
2009
+ }
1927
2010
  e.dataTransfer.setData( 'source', e.target.id );
1928
2011
  });
1929
-
2012
+
1930
2013
  // Attach content
1931
2014
  tabEl.childIndex = ( this.root.childElementCount - 1 );
1932
2015
  this.root.appendChild( tabEl );
@@ -2005,7 +2088,7 @@ class Menubar {
2005
2088
  if(options.float)
2006
2089
  this.root.style.justifyContent = options.float;
2007
2090
  this.items = [];
2008
-
2091
+
2009
2092
  this.icons = {};
2010
2093
  this.shorts = {};
2011
2094
  this.buttons = [];
@@ -2044,7 +2127,7 @@ class Menubar {
2044
2127
  } );
2045
2128
 
2046
2129
  if(found) {
2047
- insert( tokens[idx++], found );
2130
+ insert( tokens[idx++], found );
2048
2131
  }
2049
2132
  else {
2050
2133
  let item = {};
@@ -2057,7 +2140,7 @@ class Menubar {
2057
2140
  item[ 'checked' ] = options.checked;
2058
2141
  }
2059
2142
  list.push( item );
2060
- insert( next_token, item[ token ] );
2143
+ insert( next_token, item[ token ] );
2061
2144
  }
2062
2145
  };
2063
2146
 
@@ -2072,7 +2155,7 @@ class Menubar {
2072
2155
 
2073
2156
  // Item already created
2074
2157
  if( this.root.querySelector("#" + pKey) )
2075
- continue;
2158
+ continue;
2076
2159
 
2077
2160
  let entry = document.createElement('div');
2078
2161
  entry.className = "lexmenuentry";
@@ -2113,21 +2196,21 @@ class Menubar {
2113
2196
  const subitem = o[k][i];
2114
2197
  const subkey = Object.keys(subitem)[0];
2115
2198
  const hasSubmenu = subitem[ subkey ].length;
2116
- const is_checkbox = subitem[ 'type' ] == 'checkbox';
2199
+ const isCheckbox = subitem[ 'type' ] == 'checkbox';
2117
2200
  let subentry = document.createElement('div');
2118
2201
  subentry.className = "lexcontextmenuentry";
2119
2202
  subentry.className += (i == o[k].length - 1 ? " last" : "");
2120
2203
  if(subkey == '')
2121
2204
  subentry.className = " lexseparator";
2122
2205
  else {
2123
-
2206
+
2124
2207
  subentry.id = subkey;
2125
2208
  let subentrycont = document.createElement('div');
2126
2209
  subentrycont.innerHTML = "";
2127
2210
  subentrycont.classList = "lexcontextmenuentrycontainer";
2128
2211
  subentry.appendChild(subentrycont);
2129
2212
  const icon = that.icons[ subkey ];
2130
- if(is_checkbox){
2213
+ if(isCheckbox){
2131
2214
  subentrycont.innerHTML += "<input type='checkbox' >";
2132
2215
  }else if(icon) {
2133
2216
  subentrycont.innerHTML += "<a class='" + icon + " fa-sm'></a>";
@@ -2147,8 +2230,8 @@ class Menubar {
2147
2230
  const f = subitem[ 'callback' ];
2148
2231
  if(f) {
2149
2232
  f.call( this, subitem.checked, subkey, subentry );
2150
- that.root.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
2151
- }
2233
+ that.root.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
2234
+ }
2152
2235
  e.stopPropagation();
2153
2236
  e.stopImmediatePropagation();
2154
2237
  })
@@ -2178,8 +2261,8 @@ class Menubar {
2178
2261
  const f = subitem[ 'callback' ];
2179
2262
  if(f) {
2180
2263
  f.call( this, checkbox_input ? subitem.checked : subkey, checkbox_input ? subkey : subentry );
2181
- that.root.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
2182
- }
2264
+ that.root.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
2265
+ }
2183
2266
  e.stopPropagation();
2184
2267
  e.stopImmediatePropagation();
2185
2268
  });
@@ -2225,7 +2308,7 @@ class Menubar {
2225
2308
  if(f) {
2226
2309
  f.call( this, key, entry );
2227
2310
  return;
2228
- }
2311
+ }
2229
2312
 
2230
2313
  // Manage selected
2231
2314
  this.root.querySelectorAll(".lexmenuentry").forEach( e => e.classList.remove( 'selected' ) );
@@ -2257,7 +2340,7 @@ class Menubar {
2257
2340
  * @param {Array} tokens: split path strings
2258
2341
  */
2259
2342
  getSubitem(item, tokens) {
2260
-
2343
+
2261
2344
  let subitem = null;
2262
2345
  let path = tokens[0];
2263
2346
  for(let i = 0; i < item.length; i++) {
@@ -2271,7 +2354,7 @@ class Menubar {
2271
2354
  tokens.splice(0,1);
2272
2355
  return this.getSubitem(item[i][path], tokens);
2273
2356
  }
2274
-
2357
+
2275
2358
  }
2276
2359
  }
2277
2360
  }
@@ -2283,7 +2366,7 @@ class Menubar {
2283
2366
  getItem( path ) {
2284
2367
  // process path
2285
2368
  const tokens = path.split("/");
2286
-
2369
+
2287
2370
  return this.getSubitem(this.items, tokens)
2288
2371
  }
2289
2372
 
@@ -2395,7 +2478,7 @@ class Menubar {
2395
2478
  }
2396
2479
  else {
2397
2480
  this.root.appendChild( this.buttonContainer );
2398
- }
2481
+ }
2399
2482
  }
2400
2483
 
2401
2484
  for( let i = 0; i < buttons.length; ++i )
@@ -2541,7 +2624,7 @@ LX.SideBar = SideBar;
2541
2624
  */
2542
2625
 
2543
2626
  class Widget {
2544
-
2627
+
2545
2628
  static NONE = 0;
2546
2629
  static TEXT = 1;
2547
2630
  static TEXTAREA = 2;
@@ -2661,7 +2744,7 @@ class Widget {
2661
2744
  }
2662
2745
 
2663
2746
  refresh() {
2664
-
2747
+
2665
2748
  }
2666
2749
  }
2667
2750
 
@@ -2712,15 +2795,15 @@ function ADD_CUSTOM_WIDGET( custom_widget_name, options = {} )
2712
2795
  buttonName += custom_widget_name + (!instance ? " [empty]" : "");
2713
2796
  // Add alwayis icon to keep spacing right
2714
2797
  buttonName += "<a class='fa-solid " + (instance ? "fa-bars-staggered" : " ") + " menu' style='float:right; width:5%;'></a>";
2715
-
2798
+
2716
2799
  let buttonEl = this.addButton(null, buttonName, (value, event) => {
2717
2800
 
2718
2801
  if( instance ) {
2719
2802
  element.querySelector(".lexcustomitems").toggleAttribute('hidden');
2720
2803
  }
2721
2804
  else {
2722
- addContextMenu(null, event, c => {
2723
- c.add("New " + custom_widget_name, () => {
2805
+ addContextMenu(null, event, c => {
2806
+ c.add("New " + custom_widget_name, () => {
2724
2807
  instance = {};
2725
2808
  refresh_widget();
2726
2809
  element.querySelector(".lexcustomitems").toggleAttribute('hidden', false);
@@ -2729,7 +2812,7 @@ function ADD_CUSTOM_WIDGET( custom_widget_name, options = {} )
2729
2812
  }
2730
2813
 
2731
2814
  }, { buttonClass: 'custom' });
2732
-
2815
+
2733
2816
  this.clearQueue();
2734
2817
 
2735
2818
  if(instance)
@@ -2749,7 +2832,7 @@ function ADD_CUSTOM_WIDGET( custom_widget_name, options = {} )
2749
2832
  custom_widgets = document.createElement('div');
2750
2833
  custom_widgets.className = "lexcustomitems";
2751
2834
  custom_widgets.toggleAttribute('hidden', true);
2752
-
2835
+
2753
2836
  element.appendChild( container );
2754
2837
  element.appendChild( custom_widgets );
2755
2838
 
@@ -2757,7 +2840,7 @@ function ADD_CUSTOM_WIDGET( custom_widget_name, options = {} )
2757
2840
  {
2758
2841
 
2759
2842
  this.queue( custom_widgets );
2760
-
2843
+
2761
2844
  const on_instance_changed = ( key, value, event ) => {
2762
2845
  instance[ key ] = value;
2763
2846
  this._trigger( new IEvent( name, instance, event ), callback );
@@ -2766,7 +2849,7 @@ function ADD_CUSTOM_WIDGET( custom_widget_name, options = {} )
2766
2849
  for( let key in default_instance )
2767
2850
  {
2768
2851
  const value = instance[ key ] ?? default_instance[ key ];
2769
-
2852
+
2770
2853
  switch( value.constructor )
2771
2854
  {
2772
2855
  case String:
@@ -2813,7 +2896,7 @@ LX.ADD_CUSTOM_WIDGET = ADD_CUSTOM_WIDGET;
2813
2896
  */
2814
2897
 
2815
2898
  class NodeTree {
2816
-
2899
+
2817
2900
  constructor( domEl, data, options ) {
2818
2901
 
2819
2902
  this.domEl = domEl;
@@ -2850,7 +2933,7 @@ class NodeTree {
2850
2933
  node.parent = parent;
2851
2934
  let isParent = node.children.length > 0;
2852
2935
  let isSelected = this.selected.indexOf( node ) > -1 || node.selected;
2853
-
2936
+
2854
2937
  if( this.options.onlyFolders )
2855
2938
  {
2856
2939
  let has_folders = false;
@@ -2867,7 +2950,7 @@ class NodeTree {
2867
2950
  let icon = (this.options.skip_default_icon ?? true) ? "" : "fa-solid fa-square"; // Default: no childs
2868
2951
  if( isParent ) icon = node.closed ? "fa-solid fa-caret-right" : "fa-solid fa-caret-down";
2869
2952
  item.innerHTML = "<a class='" + icon + " hierarchy'></a>";
2870
-
2953
+
2871
2954
  // Add display icon
2872
2955
  icon = node.icon;
2873
2956
 
@@ -2902,7 +2985,7 @@ class NodeTree {
2902
2985
  list.querySelectorAll( "li" ).forEach( e => { e.classList.remove( 'selected' ); } );
2903
2986
  this.selected.length = 0;
2904
2987
  }
2905
-
2988
+
2906
2989
  // Add or remove
2907
2990
  const idx = this.selected.indexOf( node );
2908
2991
  if( idx > -1 ) {
@@ -3069,7 +3152,7 @@ class NodeTree {
3069
3152
  let name_input = document.createElement('input');
3070
3153
  name_input.toggleAttribute('hidden', !node.rename);
3071
3154
  name_input.value = node.id;
3072
- item.appendChild(name_input);
3155
+ item.appendChild(name_input);
3073
3156
 
3074
3157
  if(node.rename) {
3075
3158
  item.classList.add('selected');
@@ -3162,13 +3245,13 @@ class NodeTree {
3162
3245
  delete window.__tree_node_dragged;
3163
3246
  });
3164
3247
  }
3165
-
3248
+
3166
3249
  let handled = false;
3167
3250
 
3168
3251
  // Show/hide children
3169
3252
  if(isParent) {
3170
3253
  item.querySelector('a.hierarchy').addEventListener("click", function(e) {
3171
-
3254
+
3172
3255
  handled = true;
3173
3256
  e.stopImmediatePropagation();
3174
3257
  e.stopPropagation();
@@ -3202,8 +3285,8 @@ class NodeTree {
3202
3285
 
3203
3286
  item.appendChild(visibility);
3204
3287
  }
3205
-
3206
- if(node.actions)
3288
+
3289
+ if(node.actions)
3207
3290
  {
3208
3291
  for(var i = 0; i < node.actions.length; ++i) {
3209
3292
  let a = node.actions[i];
@@ -3262,11 +3345,12 @@ class NodeTree {
3262
3345
  class Panel {
3263
3346
 
3264
3347
  /**
3265
- * @param {*} options
3348
+ * @param {*} options
3266
3349
  * id: Id of the element
3267
3350
  * className: Add class to the element
3268
3351
  * width: Width of the panel element [fit space]
3269
3352
  * height: Height of the panel element [fit space]
3353
+ * style: CSS Style object to be applied to the panel
3270
3354
  */
3271
3355
 
3272
3356
  constructor( options = {} ) {
@@ -3413,7 +3497,7 @@ class Panel {
3413
3497
  this._inlineContainer.style.justifyContent = justifyContent;
3414
3498
  }
3415
3499
  }
3416
-
3500
+
3417
3501
  // Push all elements single element or Array[element, container]
3418
3502
  for( let item of this._inlineWidgets )
3419
3503
  {
@@ -3421,17 +3505,17 @@ class Panel {
3421
3505
 
3422
3506
  if(is_pair)
3423
3507
  {
3424
- // eg. an array, inline items appended later to
3508
+ // eg. an array, inline items appended later to
3425
3509
  if(this._inline_queued_container)
3426
3510
  this._inlineContainer.appendChild( item[0] );
3427
3511
  // eg. a dropdown, item is appended to parent, not to inline cont.
3428
3512
  else
3429
3513
  item[1].appendChild(item[0]);
3430
- }
3514
+ }
3431
3515
  else
3432
3516
  this._inlineContainer.appendChild( item );
3433
3517
  }
3434
-
3518
+
3435
3519
  if(!this._inline_queued_container)
3436
3520
  {
3437
3521
  if(this.current_branch)
@@ -3451,7 +3535,7 @@ class Panel {
3451
3535
  /**
3452
3536
  * @method branch
3453
3537
  * @param {String} name Name of the branch/section
3454
- * @param {*} options
3538
+ * @param {*} options
3455
3539
  * id: Id of the branch
3456
3540
  * className: Add class to the branch
3457
3541
  * closed: Set branch collapsed/opened [false]
@@ -3578,7 +3662,7 @@ class Panel {
3578
3662
  widget.oncontextmenu( e );
3579
3663
  });
3580
3664
  }
3581
-
3665
+
3582
3666
  this.widgets[ name ] = widget;
3583
3667
  }
3584
3668
 
@@ -3607,7 +3691,7 @@ class Panel {
3607
3691
 
3608
3692
  if(this.current_branch)
3609
3693
  {
3610
- if(!options.skipWidget)
3694
+ if(!options.skipWidget)
3611
3695
  this.current_branch.widgets.push( widget );
3612
3696
  this.current_branch.content.appendChild( el );
3613
3697
  }
@@ -3616,7 +3700,7 @@ class Panel {
3616
3700
  el.classList.add("nobranch");
3617
3701
  this.root.appendChild( el );
3618
3702
  }
3619
- }
3703
+ }
3620
3704
  // Append content to queued tab container
3621
3705
  else {
3622
3706
  this.queuedContainer.appendChild( el );
@@ -3627,7 +3711,7 @@ class Panel {
3627
3711
 
3628
3712
  if(!this.queuedContainer) {
3629
3713
  this._inlineWidgets.push( el );
3630
- }
3714
+ }
3631
3715
  // Append content to queued tab container
3632
3716
  else {
3633
3717
  this._inlineWidgets.push( [el, this.queuedContainer] );
@@ -3666,7 +3750,7 @@ class Panel {
3666
3750
  let widget = this.create_widget(null, Widget.TEXT, options);
3667
3751
  let element = widget.domEl;
3668
3752
  element.className += " lexfilter noname";
3669
-
3753
+
3670
3754
  let input = document.createElement('input');
3671
3755
  input.className = 'lexinput-filter';
3672
3756
  input.setAttribute("placeholder", options.placeholder);
@@ -3678,9 +3762,9 @@ class Panel {
3678
3762
  element.appendChild(input);
3679
3763
  element.appendChild(searchIcon);
3680
3764
 
3681
- input.addEventListener("input", (e) => {
3765
+ input.addEventListener("input", (e) => {
3682
3766
  if(options.callback)
3683
- options.callback(input.value, e);
3767
+ options.callback(input.value, e);
3684
3768
  });
3685
3769
 
3686
3770
  return element;
@@ -3692,7 +3776,7 @@ class Panel {
3692
3776
 
3693
3777
  if(b.name !== branchName)
3694
3778
  continue;
3695
-
3779
+
3696
3780
  // remove all widgets
3697
3781
  for( let w of b.widgets ) {
3698
3782
  if(w.domEl.classList.contains('lexfilter'))
@@ -3904,7 +3988,7 @@ class Panel {
3904
3988
  Panel._dispatch_event( wValue, "focusout" );
3905
3989
  } );
3906
3990
  }
3907
-
3991
+
3908
3992
  // Add widget value
3909
3993
 
3910
3994
  let container = document.createElement( 'div' );
@@ -3982,7 +4066,7 @@ class Panel {
3982
4066
 
3983
4067
  container.appendChild( wValue );
3984
4068
  element.appendChild( container );
3985
-
4069
+
3986
4070
  // Remove branch padding and margins
3987
4071
  if( !widget.name ) {
3988
4072
  element.className += " noname";
@@ -4029,7 +4113,7 @@ class Panel {
4029
4113
  Panel._dispatch_event( wValue, "focusout" );
4030
4114
  });
4031
4115
  }
4032
-
4116
+
4033
4117
  // Add widget value
4034
4118
 
4035
4119
  let container = document.createElement( 'div' );
@@ -4082,7 +4166,7 @@ class Panel {
4082
4166
 
4083
4167
  container.appendChild(wValue);
4084
4168
  element.appendChild(container);
4085
-
4169
+
4086
4170
  // Remove branch padding and margins
4087
4171
  if(!widget.name) {
4088
4172
  element.className += " noname";
@@ -4111,7 +4195,7 @@ class Panel {
4111
4195
  options.disabled = true;
4112
4196
  return this.addText( null, value, null, options );
4113
4197
  }
4114
-
4198
+
4115
4199
  /**
4116
4200
  * @method addButton
4117
4201
  * @param {String} name Widget name
@@ -4133,8 +4217,8 @@ class Panel {
4133
4217
  };
4134
4218
 
4135
4219
  widget.onSetValue = ( newValue, skipCallback ) => {
4136
- wValue.innerHTML =
4137
- (options.icon ? "<a class='" + options.icon + "'></a>" :
4220
+ wValue.innerHTML =
4221
+ (options.icon ? "<a class='" + options.icon + "'></a>" :
4138
4222
  ( options.img ? "<img src='" + options.img + "'>" : "<span>" + (newValue || "") + "</span>" ));
4139
4223
  };
4140
4224
 
@@ -4154,8 +4238,8 @@ class Panel {
4154
4238
  wValue.classList.add( options.buttonClass );
4155
4239
  }
4156
4240
 
4157
- wValue.innerHTML =
4158
- (options.icon ? "<a class='" + options.icon + "'></a>" :
4241
+ wValue.innerHTML =
4242
+ (options.icon ? "<a class='" + options.icon + "'></a>" :
4159
4243
  ( options.img ? "<img src='" + options.img + "'>" : "<span>" + (value || "") + "</span>" ));
4160
4244
 
4161
4245
  wValue.style.width = "calc( 100% - " + (options.nameWidth ?? LX.DEFAULT_NAME_WIDTH) + ")";
@@ -4209,7 +4293,7 @@ class Panel {
4209
4293
  let container = document.createElement('div');
4210
4294
  container.className = "lexcombobuttons ";
4211
4295
  if( options.float ) container.className += options.float;
4212
- container.style.width = "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
4296
+ container.style.width = "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
4213
4297
 
4214
4298
  let should_select = !(options.noSelection ?? false);
4215
4299
  for( let b of values )
@@ -4222,27 +4306,27 @@ class Panel {
4222
4306
  if(options.buttonClass)
4223
4307
  buttonEl.classList.add(options.buttonClass);
4224
4308
 
4225
- if(options.selected == b.value)
4309
+ if(options.selected == b.value)
4226
4310
  buttonEl.classList.add("selected");
4227
-
4228
- if(b.id)
4311
+
4312
+ if(b.id)
4229
4313
  buttonEl.id = b.id;
4230
-
4314
+
4231
4315
  buttonEl.innerHTML = (b.icon ? "<a class='" + b.icon +"'></a>" : "") + "<span>" + (b.icon ? "" : b.value) + "</span>";
4232
-
4316
+
4233
4317
  if(options.disabled)
4234
4318
  buttonEl.setAttribute("disabled", true);
4235
-
4319
+
4236
4320
  buttonEl.addEventListener("click", function(e) {
4237
4321
  if(should_select) {
4238
4322
  container.querySelectorAll('button').forEach( s => s.classList.remove('selected'));
4239
4323
  this.classList.add('selected');
4240
4324
  }
4241
- that._trigger( new IEvent(name, b.value, e), b.callback );
4325
+ that._trigger( new IEvent(name, b.value, e), b.callback );
4242
4326
  });
4243
4327
 
4244
4328
  container.appendChild(buttonEl);
4245
-
4329
+
4246
4330
  // Remove branch padding and margins
4247
4331
  if(widget.name === undefined) {
4248
4332
  buttonEl.className += " noname";
@@ -4311,11 +4395,11 @@ class Panel {
4311
4395
  }
4312
4396
 
4313
4397
  container.appendChild(name_el);
4314
-
4398
+
4315
4399
  if( options.callback ) {
4316
4400
  container.style.cursor = "pointer";
4317
4401
  container.addEventListener("click", (e) => {
4318
- this._trigger( new IEvent(name, null, e), options.callback );
4402
+ this._trigger( new IEvent(name, null, e), options.callback );
4319
4403
  });
4320
4404
  }
4321
4405
 
@@ -4502,40 +4586,53 @@ class Panel {
4502
4586
  let container = document.createElement( 'div' );
4503
4587
  container.className = "lexdropdown";
4504
4588
  container.style.width = options.inputWidth || "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
4505
-
4589
+
4506
4590
  // Add widget value
4507
4591
  let wValue = document.createElement( 'div' );
4508
4592
  wValue.className = "lexdropdown lexoption";
4509
4593
  wValue.name = name;
4510
4594
  wValue.iValue = value;
4511
4595
 
4512
- // Add dropdown widget button
4596
+ // Add dropdown widget button
4513
4597
  let buttonName = value;
4514
4598
  buttonName += "<a class='fa-solid fa-angle-down' style='float:right; margin-right: 3px;'></a>";
4515
4599
 
4516
4600
  this.queue(container);
4517
4601
 
4518
- let selectedOption = this.addButton( null, buttonName, (value, event) => {
4519
- 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
+ {
4520
4616
  delete list.unfocus_event;
4521
4617
  return;
4522
4618
  }
4523
4619
  const topPosition = selectedOption.getBoundingClientRect().y;
4524
4620
  list.style.top = (topPosition + selectedOption.offsetHeight) + 'px';
4525
4621
  list.style.width = (event.currentTarget.clientWidth) + 'px';
4622
+ list.style.minWidth = (_getMaxListWidth()) + 'px';
4526
4623
  list.toggleAttribute('hidden');
4527
4624
  list.focus();
4528
4625
  }, { buttonClass: 'array', skipInlineCount: true });
4529
4626
 
4530
4627
  this.clearQueue();
4531
4628
 
4532
- selectedOption.style.width = "100%";
4629
+ selectedOption.style.width = "100%";
4533
4630
 
4534
4631
  selectedOption.refresh = (v) => {
4535
4632
  if(selectedOption.querySelector("span").innerText == "")
4536
4633
  selectedOption.querySelector("span").innerText = v;
4537
4634
  else
4538
- 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);
4539
4636
  }
4540
4637
 
4541
4638
  // Add dropdown options container
@@ -4547,12 +4644,17 @@ class Panel {
4547
4644
  list.addEventListener( 'focusout', function( e ) {
4548
4645
  e.stopPropagation();
4549
4646
  e.stopImmediatePropagation();
4550
- if(e.relatedTarget === selectedOption.querySelector( 'button' )) {
4647
+ if( e.relatedTarget === selectedOption.querySelector( 'button' ) )
4648
+ {
4551
4649
  this.unfocus_event = true;
4552
4650
  setTimeout( () => delete this.unfocus_event, 200 );
4553
- } else if ( e.relatedTarget && e.relatedTarget.tagName == "INPUT" ) {
4651
+ }
4652
+ else if ( e.relatedTarget && e.relatedTarget.tagName == "INPUT" )
4653
+ {
4554
4654
  return;
4555
- }else if ( e.target.className == 'lexinput-filter' ) {
4655
+ }
4656
+ else if ( e.target.className == 'lexinput-filter' )
4657
+ {
4556
4658
  return;
4557
4659
  }
4558
4660
  this.toggleAttribute( 'hidden', true );
@@ -4561,28 +4663,33 @@ class Panel {
4561
4663
  // Add filter options
4562
4664
  let filter = null;
4563
4665
  if(options.filter ?? false)
4666
+ {
4564
4667
  filter = this._addFilter("Search option", {container: list, callback: this._search_options.bind(list, values)});
4668
+ }
4565
4669
 
4566
4670
  // Create option list to empty it easily..
4567
- const list_options = document.createElement('span');
4568
- list.appendChild(list_options);
4569
-
4570
- if( filter ) {
4571
- list.prepend(filter);
4572
- list_options.style.height = "calc(100% - 25px)";
4573
-
4574
- 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 ) {
4575
4680
  if (e.relatedTarget && e.relatedTarget.tagName == "UL" && e.relatedTarget.classList.contains("lexoptions"))
4681
+ {
4576
4682
  return;
4577
- list.toggleAttribute('hidden', true);
4683
+ }
4684
+ list.toggleAttribute( 'hidden', true );
4578
4685
  });
4579
4686
  }
4580
4687
 
4581
4688
  // Add dropdown options list
4582
- list.refresh = (options) => {
4689
+ list.refresh = options => {
4583
4690
 
4584
4691
  // Empty list
4585
- list_options.innerHTML = "";
4692
+ listOptions.innerHTML = "";
4586
4693
 
4587
4694
  for(let i = 0; i < options.length; i++)
4588
4695
  {
@@ -4602,7 +4709,7 @@ class Panel {
4602
4709
 
4603
4710
  let btn = element.querySelector(".lexwidgetname .lexicon");
4604
4711
  if(btn) btn.style.display = (value != wValue.iValue ? "block" : "none");
4605
- that._trigger( new IEvent(name, value, null), callback );
4712
+ that._trigger( new IEvent(name, value, null), callback );
4606
4713
 
4607
4714
  // Reset filter
4608
4715
  if(filter)
@@ -4641,18 +4748,20 @@ class Panel {
4641
4748
  option.setAttribute("title", iValue.value);
4642
4749
  if(value == iValue.value)
4643
4750
  li.classList.add("selected");
4644
- }
4645
- list_options.appendChild(li);
4751
+ }
4752
+
4753
+ listOptions.appendChild( li );
4646
4754
  }
4647
4755
  }
4648
4756
 
4649
- list.refresh(values);
4757
+ list.refresh( values );
4650
4758
 
4651
- container.appendChild(list);
4652
- element.appendChild(container);
4759
+ container.appendChild( list );
4760
+ element.appendChild( container );
4653
4761
 
4654
4762
  // Remove branch padding and margins
4655
- if(!widget.name) {
4763
+ if( !widget.name )
4764
+ {
4656
4765
  element.className += " noname";
4657
4766
  container.style.width = "100%";
4658
4767
  }
@@ -4804,13 +4913,13 @@ class Panel {
4804
4913
  if( value != undefined )
4805
4914
  {
4806
4915
  const valueBit = binary[ 16 - bit - 1 ];
4807
- if(valueBit != undefined && valueBit == '1')
4808
- layer.classList.add('selected');
4916
+ if(valueBit != undefined && valueBit == '1')
4917
+ layer.classList.add('selected');
4809
4918
  }
4810
4919
  layer.innerText = bit + 1;
4811
4920
  layer.title = "Bit " + bit + ", value " + (1 << bit);
4812
4921
  container.appendChild( layer );
4813
-
4922
+
4814
4923
  layer.addEventListener("click", e => {
4815
4924
 
4816
4925
  e.stopPropagation();
@@ -4829,7 +4938,7 @@ class Panel {
4829
4938
  };
4830
4939
 
4831
4940
  setLayers();
4832
-
4941
+
4833
4942
  element.appendChild(container);
4834
4943
 
4835
4944
  return widget;
@@ -4874,7 +4983,7 @@ class Panel {
4874
4983
  var container = document.createElement('div');
4875
4984
  container.className = "lexarray";
4876
4985
  container.style.width = "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
4877
-
4986
+
4878
4987
  this.queue( container );
4879
4988
 
4880
4989
  const angle_down = `<a class='fa-solid fa-angle-down' style='float:right; margin-right: 3px;'></a>`;
@@ -4884,7 +4993,7 @@ class Panel {
4884
4993
  this.addButton(null, buttonName, () => {
4885
4994
  element.querySelector(".lexarrayitems").toggleAttribute('hidden');
4886
4995
  }, { buttonClass: 'array' });
4887
-
4996
+
4888
4997
  this.clearQueue();
4889
4998
 
4890
4999
  // Show elements
@@ -4892,7 +5001,7 @@ class Panel {
4892
5001
  let array_items = document.createElement('div');
4893
5002
  array_items.className = "lexarrayitems";
4894
5003
  array_items.toggleAttribute('hidden', true);
4895
-
5004
+
4896
5005
  element.appendChild(container);
4897
5006
  element.appendChild(array_items);
4898
5007
 
@@ -4990,7 +5099,7 @@ class Panel {
4990
5099
 
4991
5100
  values = newValues;
4992
5101
  listContainer.innerHTML = "";
4993
-
5102
+
4994
5103
  for( let i = 0; i < values.length; ++i )
4995
5104
  {
4996
5105
  let icon = null;
@@ -5112,7 +5221,7 @@ class Panel {
5112
5221
 
5113
5222
  tag_input.onkeydown = function( e ) {
5114
5223
  const val = this.value.replace(/\s/g, '');
5115
- if( e.key == ' ') {
5224
+ if( e.key == ' ') {
5116
5225
  e.preventDefault();
5117
5226
  if( !val.length || value.indexOf( val ) > -1 )
5118
5227
  return;
@@ -5172,7 +5281,7 @@ class Panel {
5172
5281
  Panel._add_reset_property( element.domName, function() {
5173
5282
  Panel._dispatch_event( toggle, "click" );
5174
5283
  });
5175
-
5284
+
5176
5285
  // Add widget value
5177
5286
 
5178
5287
  var container = document.createElement('div');
@@ -5186,7 +5295,7 @@ class Panel {
5186
5295
  flag.className = "checkbox " + (flag.value ? "on" : "");
5187
5296
  flag.id = "checkbox"+simple_guidGenerator();
5188
5297
  flag.innerHTML = "<a class='fa-solid fa-check' style='display: " + (flag.value ? "block" : "none") + "'></a>";
5189
-
5298
+
5190
5299
  if( options.disabled ) {
5191
5300
  flag.disabled = true;
5192
5301
  toggle.className += " disabled";
@@ -5297,7 +5406,7 @@ class Panel {
5297
5406
  color.id = "color" + simple_guidGenerator();
5298
5407
  color.useRGB = options.useRGB ?? false;
5299
5408
  color.value = color.iValue = value.constructor === Array ? rgbToHex( value ) : value;
5300
-
5409
+
5301
5410
  if( options.disabled ) {
5302
5411
  color.disabled = true;
5303
5412
  }
@@ -5333,7 +5442,7 @@ class Panel {
5333
5442
  widget.set( v );
5334
5443
  change_from_input = false;
5335
5444
  }, { width: "calc( 100% - 32px )"});
5336
-
5445
+
5337
5446
  text_widget.domEl.style.marginLeft = "4px";
5338
5447
 
5339
5448
  this.clearQueue();
@@ -5386,7 +5495,7 @@ class Panel {
5386
5495
  // add widget value
5387
5496
 
5388
5497
  var container = document.createElement( 'div' );
5389
- container.className = "lexnumber";
5498
+ container.className = "lexnumber";
5390
5499
  container.style.width = options.inputWidth || "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
5391
5500
 
5392
5501
  let box = document.createElement( 'div' );
@@ -5521,7 +5630,7 @@ class Panel {
5521
5630
 
5522
5631
  if( !skipCallback ) this._trigger( new IEvent( name, val, e ), callback );
5523
5632
  }, { passive: false });
5524
-
5633
+
5525
5634
  // Add drag input
5526
5635
 
5527
5636
  vecinput.addEventListener( "mousedown", inner_mousedown );
@@ -5590,7 +5699,7 @@ class Panel {
5590
5699
  options.onRelease.bind( vecinput )( e, vecinput );
5591
5700
  }
5592
5701
  }
5593
-
5702
+
5594
5703
  container.appendChild( box );
5595
5704
  element.appendChild( container );
5596
5705
 
@@ -5657,7 +5766,7 @@ class Panel {
5657
5766
  // Add widget value
5658
5767
 
5659
5768
  var container = document.createElement( 'div' );
5660
- container.className = "lexvector";
5769
+ container.className = "lexvector";
5661
5770
  container.style.width = "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
5662
5771
 
5663
5772
  for( let i = 0; i < num_components; ++i ) {
@@ -5750,7 +5859,7 @@ class Panel {
5750
5859
 
5751
5860
  if( !skipCallback ) this._trigger( new IEvent( name, value, e ), callback );
5752
5861
  }, false );
5753
-
5862
+
5754
5863
  // Add drag input
5755
5864
 
5756
5865
  vecinput.addEventListener( "mousedown", inner_mousedown );
@@ -5829,7 +5938,7 @@ class Panel {
5829
5938
  options.onRelease.bind( vecinput )( e, vecinput );
5830
5939
  }
5831
5940
  }
5832
-
5941
+
5833
5942
  box.appendChild( vecinput );
5834
5943
  container.appendChild( box );
5835
5944
  }
@@ -5853,7 +5962,8 @@ class Panel {
5853
5962
  }
5854
5963
 
5855
5964
  let locker = document.createElement( 'a' );
5856
- locker.className = "fa-solid fa-lock-open lexicon";
5965
+ locker.title = "Lock";
5966
+ locker.className = "fa-solid fa-lock-open lexicon lock";
5857
5967
  container.appendChild( locker );
5858
5968
  locker.addEventListener( "click", function( e ) {
5859
5969
  this.locked = !this.locked;
@@ -5868,7 +5978,7 @@ class Panel {
5868
5978
  this.classList.remove( "fa-lock" );
5869
5979
  }
5870
5980
  }, false );
5871
-
5981
+
5872
5982
  element.appendChild( container );
5873
5983
 
5874
5984
  return widget;
@@ -5877,7 +5987,7 @@ class Panel {
5877
5987
  /**
5878
5988
  * @method addVector N (2, 3, 4)
5879
5989
  * @param {String} name Widget name
5880
- * @param {Array} value Array of N components
5990
+ * @param {Array} value Array of N components
5881
5991
  * @param {Function} callback Callback function on change
5882
5992
  * @param {*} options:
5883
5993
  * disabled: Make the widget disabled [false]
@@ -5936,18 +6046,21 @@ class Panel {
5936
6046
 
5937
6047
  this.queue( element );
5938
6048
 
6049
+ element.aspectRatio = ( value.length == 2 ? value[ 0 ] / value[ 1 ] : null );
5939
6050
  element.dimensions = [];
5940
6051
 
5941
6052
  for( let i = 0; i < value.length; ++i )
5942
6053
  {
5943
- const size = measureRealWidth( JSON.stringify( value[ i ] ), 24 ) + 'px';
5944
6054
  element.dimensions[ i ] = this.addNumber( null, value[ i ], ( v ) => {
5945
6055
 
5946
- const value = [];
6056
+ const value = widget.onGetValue();
5947
6057
 
5948
- for( let i = 0; i < element.dimensions.length; ++i )
6058
+ if( element.locked )
5949
6059
  {
5950
- 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 );
5951
6064
  }
5952
6065
 
5953
6066
  if( callback )
@@ -5955,7 +6068,7 @@ class Panel {
5955
6068
  callback( value );
5956
6069
  }
5957
6070
 
5958
- }, { width: size, min: 0, disabled: options.disabled } );
6071
+ }, { min: 0, disabled: options.disabled, precision: options.precision } );
5959
6072
 
5960
6073
  if( ( i + 1 ) != value.length )
5961
6074
  {
@@ -5975,6 +6088,32 @@ class Panel {
5975
6088
  element.appendChild( unitSpan );
5976
6089
  }
5977
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
+
5978
6117
  // Remove branch padding and margins
5979
6118
  if( !widget.name )
5980
6119
  {
@@ -5988,11 +6127,12 @@ class Panel {
5988
6127
  /**
5989
6128
  * @method addPad
5990
6129
  * @param {String} name Widget name
5991
- * @param {Number} value Pad value
6130
+ * @param {Array} value Pad value
5992
6131
  * @param {Function} callback Callback function on change
5993
6132
  * @param {*} options:
5994
6133
  * disabled: Make the widget disabled [false]
5995
6134
  * min, max: Min and Max values
6135
+ * padSize: Size of the pad (css)
5996
6136
  * onPress: Callback function on mouse down
5997
6137
  * onRelease: Callback function on mouse up
5998
6138
  */
@@ -6113,7 +6253,7 @@ class Panel {
6113
6253
  /**
6114
6254
  * @method addProgress
6115
6255
  * @param {String} name Widget name
6116
- * @param {Number} value Progress value
6256
+ * @param {Number} value Progress value
6117
6257
  * @param {*} options:
6118
6258
  * min, max: Min and Max values
6119
6259
  * low, optimum, high: Low and High boundary values, Optimum point in the range
@@ -6155,7 +6295,7 @@ class Panel {
6155
6295
  progress.min = options.min ?? 0;
6156
6296
  progress.max = options.max ?? 1;
6157
6297
  progress.value = value;
6158
-
6298
+
6159
6299
  if( options.low )
6160
6300
  progress.low = options.low;
6161
6301
  if( options.high )
@@ -6242,7 +6382,8 @@ class Panel {
6242
6382
 
6243
6383
  addFile( name, callback, options = { } ) {
6244
6384
 
6245
- if( !name ) {
6385
+ if( !name )
6386
+ {
6246
6387
  throw( "Set Widget Name!" );
6247
6388
  }
6248
6389
 
@@ -6251,7 +6392,7 @@ class Panel {
6251
6392
 
6252
6393
  let local = options.local ?? true;
6253
6394
  let type = options.type ?? 'text';
6254
- let read = options.read ?? true;
6395
+ let read = options.read ?? true;
6255
6396
 
6256
6397
  // Create hidden input
6257
6398
  let input = document.createElement( 'input' );
@@ -6259,7 +6400,9 @@ class Panel {
6259
6400
  input.type = 'file';
6260
6401
 
6261
6402
  if( options.placeholder )
6403
+ {
6262
6404
  input.placeholder = options.placeholder;
6405
+ }
6263
6406
 
6264
6407
  input.addEventListener( 'change', function( e ) {
6265
6408
 
@@ -6291,17 +6434,24 @@ class Panel {
6291
6434
  element.appendChild( input );
6292
6435
 
6293
6436
  this.queue( element );
6294
-
6437
+
6295
6438
  if( local )
6296
6439
  {
6440
+ let settingsDialog = null;
6441
+
6297
6442
  this.addButton(null, "<a style='margin-top: 0px;' class='fa-solid fa-gear'></a>", () => {
6298
-
6299
- new Dialog( "Load Settings", p => {
6443
+
6444
+ if( settingsDialog )
6445
+ {
6446
+ return;
6447
+ }
6448
+
6449
+ settingsDialog = new Dialog( "Load Settings", p => {
6300
6450
  p.addDropdown( "Type", [ 'text', 'buffer', 'bin', 'url' ], type, v => { type = v } );
6301
6451
  p.addButton( null, "Reload", v => { input.dispatchEvent( new Event( 'change' ) ) } );
6302
- });
6303
-
6304
- }, { className: "micro", skipInlineCount: true });
6452
+ }, { onclose: ( root ) => { root.remove(); settingsDialog = null; } } );
6453
+
6454
+ }, { className: "micro", skipInlineCount: true, title: "Settings" });
6305
6455
  }
6306
6456
 
6307
6457
  this.clearQueue();
@@ -6363,7 +6513,7 @@ class Panel {
6363
6513
  node_filter_input.addEventListener('input', function(){
6364
6514
  nodeTree.refresh();
6365
6515
  });
6366
-
6516
+
6367
6517
  let searchIcon = document.createElement('a');
6368
6518
  searchIcon.className = "lexicon fa-solid fa-magnifying-glass";
6369
6519
  toolsDiv.appendChild(node_filter_input);
@@ -6397,11 +6547,11 @@ class Panel {
6397
6547
  element.className = "lexseparator";
6398
6548
  let widget = new Widget( null, Widget.SEPARATOR );
6399
6549
  widget.domEl = element;
6400
-
6550
+
6401
6551
  if(this.current_branch) {
6402
6552
  this.current_branch.content.appendChild( element );
6403
6553
  this.current_branch.widgets.push( widget );
6404
- } else
6554
+ } else
6405
6555
  this.root.appendChild(element);
6406
6556
  }
6407
6557
 
@@ -6413,7 +6563,7 @@ class Panel {
6413
6563
  * onCreate: Func to be called at tab creation
6414
6564
  * onSelect: Func to be called on select tab (optional)
6415
6565
  * }
6416
- * @param {*} options
6566
+ * @param {*} options
6417
6567
  * vertical: Use vertical or horizontal tabs (vertical by default)
6418
6568
  * showNames: Show tab name only in horizontal tabs
6419
6569
  */
@@ -6495,7 +6645,7 @@ class Panel {
6495
6645
  this.clearQueue();
6496
6646
  }
6497
6647
  }
6498
-
6648
+
6499
6649
  this.addSeparator();
6500
6650
  }
6501
6651
  }
@@ -6507,7 +6657,7 @@ LX.Panel = Panel;
6507
6657
  */
6508
6658
 
6509
6659
  class Branch {
6510
-
6660
+
6511
6661
  constructor( name, options = {} ) {
6512
6662
 
6513
6663
  this.name = name;
@@ -6534,7 +6684,7 @@ class Branch {
6534
6684
  // create element
6535
6685
  var title = document.createElement( 'div' );
6536
6686
  title.className = "lexbranchtitle";
6537
-
6687
+
6538
6688
  title.innerHTML = "<a class='fa-solid fa-angle-up switch-branch-button'></a>";
6539
6689
  if( options.icon )
6540
6690
  {
@@ -6582,7 +6732,7 @@ class Branch {
6582
6732
  {
6583
6733
  return;
6584
6734
  }
6585
-
6735
+
6586
6736
  addContextMenu("Dock", e, p => {
6587
6737
  e.preventDefault();
6588
6738
  // p.add('<i class="fa-regular fa-window-maximize">', {id: 'dock_options0'});
@@ -6622,23 +6772,26 @@ class Branch {
6622
6772
 
6623
6773
  _addBranchSeparator() {
6624
6774
 
6625
- var element = document.createElement('div');
6775
+ const element = document.createElement('div');
6626
6776
  element.className = "lexwidgetseparator";
6627
6777
  element.style.width = "100%";
6628
6778
  element.style.background = "none";
6629
6779
 
6630
- var grabber = document.createElement('div');
6780
+ const grabber = document.createElement('div');
6631
6781
  grabber.innerHTML = "&#9662;";
6632
- grabber.style.marginLeft = LX.DEFAULT_NAME_WIDTH;
6633
6782
  element.appendChild(grabber);
6634
6783
 
6635
- 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');
6636
6789
  line.style.width = "1px";
6637
6790
  line.style.marginLeft = "6px";
6638
6791
  line.style.marginTop = "2px";
6639
6792
  line.style.height = "0px"; // get in time
6640
- grabber.appendChild(line);
6641
- grabber.addEventListener("mousedown", inner_mousedown);
6793
+ grabber.appendChild( line );
6794
+ grabber.addEventListener( "mousedown", innerMouseDown );
6642
6795
 
6643
6796
  this.grabber = grabber;
6644
6797
 
@@ -6646,45 +6799,40 @@ class Branch {
6646
6799
  return that.root.offsetHeight - that.root.children[0].offsetHeight;
6647
6800
  }
6648
6801
 
6649
- var that = this;
6650
- var lastX = 0;
6651
- var lastXLine = 0;
6652
- function inner_mousedown(e)
6802
+ let that = this;
6803
+
6804
+ function innerMouseDown( e )
6653
6805
  {
6654
6806
  var doc = that.root.ownerDocument;
6655
- doc.addEventListener("mouseup",inner_mouseup);
6656
- doc.addEventListener("mousemove",inner_mousemove);
6657
- lastX = e.pageX;
6658
- lastXLine = e.pageX;
6807
+ doc.addEventListener("mouseup", innerMouseUp);
6808
+ doc.addEventListener("mousemove", innerMouseMove);
6659
6809
  e.stopPropagation();
6660
6810
  e.preventDefault();
6661
- var h = getBranchHeight();
6811
+ const h = getBranchHeight();
6662
6812
  line.style.height = (h-3) + "px";
6663
6813
  document.body.classList.add('nocursor');
6664
6814
  }
6665
-
6666
- function inner_mousemove(e)
6815
+
6816
+ function innerMouseMove(e)
6667
6817
  {
6668
- if (lastXLine != e.pageX) {
6669
- var dt = lastXLine - e.pageX;
6670
- var margin = parseFloat( grabber.style.marginLeft );
6671
- grabber.style.marginLeft = clamp(margin - dt * 0.1, 10, 90) + "%";
6672
- }
6818
+ let dt = e.movementX;
6673
6819
 
6674
- 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
+ }
6675
6825
  }
6676
6826
 
6677
- function inner_mouseup(e)
6827
+ function innerMouseUp(e)
6678
6828
  {
6679
- if (lastX != e.pageX)
6680
- that._updateWidgets();
6681
- lastX = e.pageX;
6682
- lastXLine = e.pageX;
6829
+ that._updateWidgets();
6830
+
6683
6831
  line.style.height = "0px";
6684
6832
 
6685
6833
  var doc = that.root.ownerDocument;
6686
- doc.removeEventListener("mouseup",inner_mouseup);
6687
- doc.removeEventListener("mousemove",inner_mousemove);
6834
+ doc.removeEventListener("mouseup", innerMouseUp);
6835
+ doc.removeEventListener("mousemove", innerMouseMove);
6688
6836
  document.body.classList.remove('nocursor');
6689
6837
  }
6690
6838
 
@@ -6744,7 +6892,7 @@ class Dialog {
6744
6892
  static _last_id = 0;
6745
6893
 
6746
6894
  constructor( title, callback, options = {} ) {
6747
-
6895
+
6748
6896
  if( !callback )
6749
6897
  {
6750
6898
  console.warn("Content is empty, add some widgets using 'callback' parameter!");
@@ -6787,25 +6935,30 @@ class Dialog {
6787
6935
 
6788
6936
  addContextMenu("Dock", e, p => {
6789
6937
  e.preventDefault();
6790
-
6791
- const get_next_panel = function(area) {
6792
- let p = area.panels[0];
6938
+
6939
+ const _getNextPanel = function( area ) {
6940
+ let p = area.panels[ 0 ];
6793
6941
  if( p ) return p;
6794
6942
  for(var s of area.sections){
6795
- p = get_next_panel(s);
6943
+ p = _getNextPanel( s );
6796
6944
  if( p ) return p;
6797
6945
  }
6798
6946
  }
6799
6947
 
6800
- const append_branch = function(panel) {
6948
+ const _appendBranch = function( panel ) {
6801
6949
  let branch = panel.branches.find( b => b.name === title );
6802
- if( !branch ) {
6803
- panel.branch(title);
6804
- branch = panel.branches.find( b => b.name === title );
6805
- }else
6950
+ if( !branch )
6951
+ {
6952
+ panel.branch( title );
6953
+ branch = panel.branches.find( b => b.name === title );
6954
+ }
6955
+ else
6956
+ {
6806
6957
  panel.root.appendChild( branch.root );
6958
+ }
6807
6959
 
6808
- for( let w of that.widgets ) {
6960
+ for( let w of that.widgets )
6961
+ {
6809
6962
  branch.content.appendChild( w.domEl );
6810
6963
  }
6811
6964
 
@@ -6816,16 +6969,16 @@ class Dialog {
6816
6969
  branch.root.classList.add('last');
6817
6970
  root.remove();
6818
6971
  }
6819
-
6972
+
6820
6973
  // Right
6821
- let rpanel = get_next_panel(LX.main_area.sections[1]);
6974
+ let rpanel = _getNextPanel(LX.main_area.sections[ 1 ]);
6822
6975
  p.add('<i class="fa-regular fa-window-maximize fa-window-maximize fa-rotate-90">', {disabled: !rpanel, id: 'dock_options0', callback: () => {
6823
- append_branch(rpanel);
6976
+ _appendBranch(rpanel);
6824
6977
  }});
6825
6978
  // Left
6826
- let lpanel = get_next_panel(LX.main_area.sections[0]);
6979
+ let lpanel = _getNextPanel(LX.main_area.sections[ 0 ]);
6827
6980
  p.add('<i class="fa-regular fa-window-maximize fa-window-maximize fa-rotate-270">', {disabled: !lpanel, id: 'dock_options1', callback: () => {
6828
- append_branch(lpanel);
6981
+ _appendBranch(lpanel);
6829
6982
  }});
6830
6983
  }, { icon: "fa-regular fa-window-restore" });
6831
6984
  };
@@ -6837,41 +6990,61 @@ class Dialog {
6837
6990
  {
6838
6991
  this.close = () => {
6839
6992
 
6993
+ if( options.onBeforeClose )
6994
+ {
6995
+ options.onBeforeClose( this );
6996
+ }
6997
+
6840
6998
  if( !options.onclose )
6841
6999
  {
6842
7000
  that.panel.clear();
6843
7001
  root.remove();
6844
- } else
7002
+ }
7003
+ else
6845
7004
  {
6846
7005
  options.onclose( this.root );
6847
7006
  }
6848
7007
 
6849
- if(modal)
6850
- LX.modal.toggle(true);
7008
+ if( modal )
7009
+ {
7010
+ LX.modal.toggle( true );
7011
+ }
6851
7012
  };
6852
7013
 
6853
- var closeButton = document.createElement('a');
7014
+ var closeButton = document.createElement( 'a' );
6854
7015
  closeButton.className = "lexdialogcloser fa-solid fa-xmark";
6855
7016
  closeButton.title = "Close";
6856
- closeButton.addEventListener('click', this.close);
7017
+ closeButton.addEventListener( "click", this.close );
6857
7018
 
6858
- if(title) titleDiv.appendChild(closeButton);
6859
- else {
6860
- closeButton.classList.add("notitle");
6861
- root.appendChild(closeButton);
7019
+ if( title )
7020
+ {
7021
+ titleDiv.appendChild( closeButton );
7022
+ }
7023
+ else
7024
+ {
7025
+ closeButton.classList.add( "notitle" );
7026
+ root.appendChild( closeButton );
6862
7027
  }
6863
7028
  }
6864
7029
 
6865
7030
  const panel = new Panel();
6866
- panel.root.classList.add('lexdialogcontent');
6867
- if(!title) panel.root.classList.add('notitle');
6868
- if(callback)
6869
- callback.call(this, panel);
6870
- 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 );
6871
7044
 
6872
7045
  // Make branches have a distintive to manage some cases
6873
7046
  panel.root.querySelectorAll(".lexbranch").forEach( b => b.classList.add("dialog") );
6874
-
7047
+
6875
7048
  this.panel = panel;
6876
7049
  this.root = root;
6877
7050
  this.title = titleDiv;
@@ -6882,22 +7055,30 @@ class Dialog {
6882
7055
  }
6883
7056
 
6884
7057
  // Process position and size
6885
- if(size.length && typeof(size[0]) != "string")
6886
- size[0] += "px";
6887
- if(size.length && typeof(size[1]) != "string")
6888
- 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
+ }
7067
+
7068
+ root.style.width = size[ 0 ] ? (size[ 0 ]) : "25%";
7069
+ root.style.height = size[ 1 ] ? (size[ 1 ]) : "auto";
6889
7070
 
6890
- root.style.width = size[0] ? (size[0]) : "25%";
6891
- root.style.height = size[1] ? (size[1]) : "auto";
7071
+ if( options.size )
7072
+ {
7073
+ this.size = size;
7074
+ }
6892
7075
 
6893
- if(options.size) this.size = size;
6894
-
6895
7076
  let rect = root.getBoundingClientRect();
6896
- root.style.left = position[0] ? (position[0]) : "calc( 50% - " + (rect.width * 0.5) + "px )";
6897
- 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 )";
6898
7079
 
6899
7080
  panel.root.style.width = "calc( 100% - 30px )";
6900
- 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 )";
6901
7082
  }
6902
7083
 
6903
7084
  destroy() {
@@ -6912,7 +7093,7 @@ class Dialog {
6912
7093
  }
6913
7094
 
6914
7095
  setPosition(x, y) {
6915
-
7096
+
6916
7097
  this.root.style.left = x + "px";
6917
7098
  this.root.style.top = y + "px";
6918
7099
  }
@@ -6941,9 +7122,9 @@ class PocketDialog extends Dialog {
6941
7122
 
6942
7123
  options.draggable = options.draggable ?? false;
6943
7124
  options.closable = options.closable ?? false;
6944
-
7125
+
6945
7126
  super( title, callback, options );
6946
-
7127
+
6947
7128
  let that = this;
6948
7129
  // Update margins on branch title closes/opens
6949
7130
  LX.addSignal("@on_branch_closed", this.panel, closed => {
@@ -6951,7 +7132,7 @@ class PocketDialog extends Dialog {
6951
7132
  this.root.style.top = "calc(100% - " + (this.root.offsetHeight + 6) + "px)";
6952
7133
  });
6953
7134
 
6954
- // Custom
7135
+ // Custom
6955
7136
  this.root.classList.add( "pocket" );
6956
7137
  if( !options.position ) {
6957
7138
  this.root.style.left = "calc(100% - " + (this.root.offsetWidth + 6) + "px)";
@@ -6965,7 +7146,7 @@ class PocketDialog extends Dialog {
6965
7146
  this.title.tabIndex = -1;
6966
7147
  this.title.addEventListener("click", e => {
6967
7148
 
6968
- // Sized dialogs have to keep their size
7149
+ // Sized dialogs have to keep their size
6969
7150
  if( this.size )
6970
7151
  {
6971
7152
  if( !this.minimized ) this.root.style.height = "auto";
@@ -6976,7 +7157,7 @@ class PocketDialog extends Dialog {
6976
7157
  this.minimized = !this.minimized;
6977
7158
 
6978
7159
  if( this.dock_pos == PocketDialog.BOTTOM )
6979
- that.root.style.top = this.root.classList.contains("minimized") ?
7160
+ that.root.style.top = this.root.classList.contains("minimized") ?
6980
7161
  "calc(100% - " + (that.title.offsetHeight + 6) + "px)" : "calc(100% - " + (that.root.offsetHeight + 6) + "px)";
6981
7162
  });
6982
7163
 
@@ -6991,10 +7172,10 @@ class PocketDialog extends Dialog {
6991
7172
  const t = float[i];
6992
7173
  switch( t )
6993
7174
  {
6994
- case 'b':
7175
+ case 'b':
6995
7176
  this.root.style.top = "calc(100% - " + (this.root.offsetHeight + 6) + "px)";
6996
7177
  break;
6997
- case 'l':
7178
+ case 'l':
6998
7179
  this.root.style.left = options.position ? options.position[ 1 ] : "0px";
6999
7180
  break;
7000
7181
  }
@@ -7028,7 +7209,7 @@ LX.PocketDialog = PocketDialog;
7028
7209
  class ContextMenu {
7029
7210
 
7030
7211
  constructor( event, title, options = {} ) {
7031
-
7212
+
7032
7213
  // remove all context menus
7033
7214
  document.body.querySelectorAll(".lexcontextmenubox").forEach(e => e.remove());
7034
7215
 
@@ -7040,7 +7221,7 @@ class ContextMenu {
7040
7221
  this.root.addEventListener("mouseleave", function() {
7041
7222
  this.remove();
7042
7223
  });
7043
-
7224
+
7044
7225
  this.items = [];
7045
7226
  this.colors = {};
7046
7227
 
@@ -7055,11 +7236,11 @@ class ContextMenu {
7055
7236
  }
7056
7237
 
7057
7238
  _adjust_position( div, margin, useAbsolute = false ) {
7058
-
7239
+
7059
7240
  let rect = div.getBoundingClientRect();
7060
-
7241
+
7061
7242
  if( !useAbsolute )
7062
- {
7243
+ {
7063
7244
  let width = rect.width;
7064
7245
  if( rect.left < 0 )
7065
7246
  {
@@ -7069,7 +7250,7 @@ class ContextMenu {
7069
7250
  {
7070
7251
  div.style.left = (window.innerWidth - width - margin) + "px";
7071
7252
  }
7072
-
7253
+
7073
7254
  if( rect.top < 0 )
7074
7255
  {
7075
7256
  div.style.top = margin + "px";
@@ -7086,7 +7267,7 @@ class ContextMenu {
7086
7267
  {
7087
7268
  div.style.left = div.offsetLeft + (dt - margin) + "px";
7088
7269
  }
7089
-
7270
+
7090
7271
  dt = window.innerHeight - (rect.top + rect.height);
7091
7272
  if( dt < 0 )
7092
7273
  {
@@ -7146,14 +7327,14 @@ class ContextMenu {
7146
7327
  entry.addEventListener("click", e => {
7147
7328
  e.stopPropagation();
7148
7329
  e.stopImmediatePropagation();
7149
-
7330
+
7150
7331
  if(disabled) return;
7151
-
7332
+
7152
7333
  const f = o[ 'callback' ];
7153
7334
  if(f) {
7154
7335
  f.call( this, k, entry );
7155
7336
  this.root.remove();
7156
- }
7337
+ }
7157
7338
 
7158
7339
  if( !hasSubmenu )
7159
7340
  return;
@@ -7217,7 +7398,7 @@ class ContextMenu {
7217
7398
  } );
7218
7399
 
7219
7400
  if(found) {
7220
- insert( tokens[idx++], found );
7401
+ insert( tokens[idx++], found );
7221
7402
  }
7222
7403
  else {
7223
7404
  let item = {};
@@ -7228,10 +7409,10 @@ class ContextMenu {
7228
7409
  item[ 'id' ] = options.id;
7229
7410
  item[ 'callback' ] = options.callback;
7230
7411
  item[ 'disabled' ] = options.disabled ?? false;
7231
- }
7412
+ }
7232
7413
 
7233
7414
  list.push( item );
7234
- insert( next_token, item[ token ] );
7415
+ insert( next_token, item[ token ] );
7235
7416
  }
7236
7417
  };
7237
7418
 
@@ -7416,7 +7597,7 @@ class Curve {
7416
7597
  var selected = -1;
7417
7598
 
7418
7599
  element.redraw = function( o = {} ) {
7419
-
7600
+
7420
7601
  if( o.value ) element.value = o.value;
7421
7602
  if( o.xrange ) element.xrange = o.xrange;
7422
7603
  if( o.yrange ) element.yrange = o.yrange;
@@ -7591,7 +7772,7 @@ class Curve {
7591
7772
  e.preventDefault();
7592
7773
  e.stopPropagation();
7593
7774
  }
7594
-
7775
+
7595
7776
  function onchange( e ) {
7596
7777
  if( options.callback )
7597
7778
  options.callback.call( element, element.value, e );
@@ -7630,7 +7811,7 @@ class Curve {
7630
7811
  selected = element.value.indexOf( v );
7631
7812
  }
7632
7813
  }
7633
-
7814
+
7634
7815
  element.redraw();
7635
7816
  return this;
7636
7817
  }
@@ -7659,7 +7840,7 @@ class AssetViewEvent {
7659
7840
  this.value = value;
7660
7841
  this.multiple = false; // Multiple selection
7661
7842
  }
7662
-
7843
+
7663
7844
  string() {
7664
7845
  switch(this.type) {
7665
7846
  case AssetViewEvent.NONE: return "assetview_event_none";
@@ -7734,7 +7915,7 @@ class AssetView {
7734
7915
  {
7735
7916
  [ contentArea, right ] = contentArea.split({ type: "horizontal", sizes: ["80%", "20%"]});
7736
7917
  }
7737
-
7918
+
7738
7919
  this.allowedTypes = options.allowedTypes || ["None", "Image", "Mesh", "Script", "JSON", "Clip"];
7739
7920
 
7740
7921
  this.prevData = [];
@@ -7752,7 +7933,7 @@ class AssetView {
7752
7933
  }
7753
7934
 
7754
7935
  this._createContentPanel( contentArea );
7755
-
7936
+
7756
7937
  // Create resource preview panel
7757
7938
  if( !this.skipPreview )
7758
7939
  {
@@ -7768,7 +7949,7 @@ class AssetView {
7768
7949
 
7769
7950
  this.prevData.length = 0;
7770
7951
  this.nextData.length = 0;
7771
-
7952
+
7772
7953
  this.data = data;
7773
7954
 
7774
7955
  this._processData( this.data, null );
@@ -7831,7 +8012,7 @@ class AssetView {
7831
8012
  */
7832
8013
 
7833
8014
  _updatePath( data ) {
7834
-
8015
+
7835
8016
  this.path.length = 0;
7836
8017
 
7837
8018
  const push_parents_id = i => {
@@ -7871,7 +8052,7 @@ class AssetView {
7871
8052
  }
7872
8053
 
7873
8054
  this.tree = this.leftPanel.addTree( "Content Browser", tree_data, {
7874
- // icons: tree_icons,
8055
+ // icons: tree_icons,
7875
8056
  filter: false,
7876
8057
  onlyFolders: this.onlyFolders,
7877
8058
  onevent: event => {
@@ -7881,7 +8062,7 @@ class AssetView {
7881
8062
 
7882
8063
  switch( event.type )
7883
8064
  {
7884
- case LX.TreeEvent.NODE_SELECTED:
8065
+ case LX.TreeEvent.NODE_SELECTED:
7885
8066
  if( !event.multiple )
7886
8067
  {
7887
8068
  this._enterFolder( node );
@@ -7896,13 +8077,13 @@ class AssetView {
7896
8077
  LX.emit("@on_folder_change", this.path.join('/'));
7897
8078
  }
7898
8079
  break;
7899
- case LX.TreeEvent.NODE_DRAGGED:
8080
+ case LX.TreeEvent.NODE_DRAGGED:
7900
8081
  node.folder = value;
7901
8082
  this._refreshContent();
7902
8083
  break;
7903
8084
  }
7904
8085
  },
7905
- });
8086
+ });
7906
8087
  }
7907
8088
 
7908
8089
  /**
@@ -8052,7 +8233,7 @@ class AssetView {
8052
8233
  this.content.innerHTML = "";
8053
8234
  this.content.className = (isContentLayout ? "lexassetscontent" : "lexassetscontent list");
8054
8235
  let that = this;
8055
-
8236
+
8056
8237
  const add_item = function(item) {
8057
8238
 
8058
8239
  const type = item.type.charAt( 0 ).toUpperCase() + item.type.slice( 1 );
@@ -8118,7 +8299,7 @@ class AssetView {
8118
8299
  if( item.selected != undefined )
8119
8300
  {
8120
8301
  let span = document.createElement('span');
8121
- span.className = "lexcheckbox";
8302
+ span.className = "lexcheckbox";
8122
8303
  let checkbox_input = document.createElement('input');
8123
8304
  checkbox_input.type = "checkbox";
8124
8305
  checkbox_input.className = "checkbox";
@@ -8136,7 +8317,7 @@ class AssetView {
8136
8317
  })
8137
8318
  span.appendChild(checkbox_input);
8138
8319
  itemEl.appendChild(span);
8139
-
8320
+
8140
8321
  }
8141
8322
 
8142
8323
  let title = document.createElement('span');
@@ -8161,7 +8342,7 @@ class AssetView {
8161
8342
  preview = document.createElement('svg');
8162
8343
  preview.className = "asset-file-preview";
8163
8344
  itemEl.appendChild(preview);
8164
-
8345
+
8165
8346
  let textEl = document.createElement('text');
8166
8347
  preview.appendChild(textEl);
8167
8348
  // If no extension, e.g. Clip, use the type...
@@ -8207,7 +8388,7 @@ class AssetView {
8207
8388
  {
8208
8389
  that._previewAsset( item );
8209
8390
  }
8210
- }
8391
+ }
8211
8392
  else if( isFolder )
8212
8393
  {
8213
8394
  that._enterFolder( item );
@@ -8229,7 +8410,7 @@ class AssetView {
8229
8410
 
8230
8411
  const multiple = that.content.querySelectorAll('.selected').length;
8231
8412
 
8232
- LX.addContextMenu( multiple > 1 ? (multiple + " selected") :
8413
+ LX.addContextMenu( multiple > 1 ? (multiple + " selected") :
8233
8414
  isFolder ? item.id : item.type, e, m => {
8234
8415
  if( multiple <= 1 )
8235
8416
  {
@@ -8281,7 +8462,7 @@ class AssetView {
8281
8462
  LX.request({ url: item.path, dataType: 'blob', success: (f) => {
8282
8463
  item.bytesize = f.size;
8283
8464
  fr.readAsDataURL( f );
8284
- fr.onload = e => {
8465
+ fr.onload = e => {
8285
8466
  item.src = e.currentTarget.result; // This is a base64 string...
8286
8467
  item._path = item.path;
8287
8468
  delete item.path;
@@ -8334,14 +8515,14 @@ class AssetView {
8334
8515
  if( file.type == "folder" ) this.previewPanel.addText("Files", file.children ? file.children.length.toString() : "0", null, options);
8335
8516
 
8336
8517
  this.previewPanel.addSeparator();
8337
-
8518
+
8338
8519
  const previewActions = [...this.previewActions];
8339
8520
 
8340
8521
  if( !previewActions.length )
8341
8522
  {
8342
8523
  // By default
8343
8524
  previewActions.push({
8344
- name: 'Download',
8525
+ name: 'Download',
8345
8526
  callback: () => LX.downloadURL(file.src, file.id)
8346
8527
  });
8347
8528
  }
@@ -8369,8 +8550,8 @@ class AssetView {
8369
8550
  if(result) continue;
8370
8551
 
8371
8552
  fr.readAsDataURL( file );
8372
- fr.onload = e => {
8373
-
8553
+ fr.onload = e => {
8554
+
8374
8555
  let ext = file.name.substr(file.name.lastIndexOf('.') + 1).toLowerCase();
8375
8556
 
8376
8557
  let item = {
@@ -8385,12 +8566,12 @@ class AssetView {
8385
8566
  case 'png':
8386
8567
  case 'jpg':
8387
8568
  item.type = "image"; break;
8388
- case 'js':
8389
- case 'css':
8569
+ case 'js':
8570
+ case 'css':
8390
8571
  item.type = "script"; break;
8391
- case 'json':
8572
+ case 'json':
8392
8573
  item.type = "json"; break;
8393
- case 'obj':
8574
+ case 'obj':
8394
8575
  item.type = "mesh"; break;
8395
8576
  default:
8396
8577
  item.type = ext;
@@ -8399,7 +8580,7 @@ class AssetView {
8399
8580
  }
8400
8581
 
8401
8582
  this.currentData.push( item );
8402
-
8583
+
8403
8584
  if(i == (num_files - 1)) {
8404
8585
  this._refreshContent();
8405
8586
  if( !this.skipBrowser )
@@ -8488,7 +8669,7 @@ class AssetView {
8488
8669
  }
8489
8670
 
8490
8671
  LX.AssetView = AssetView;
8491
-
8672
+
8492
8673
  /*
8493
8674
  * Requests
8494
8675
  */
@@ -8496,7 +8677,7 @@ LX.AssetView = AssetView;
8496
8677
  Object.assign(LX, {
8497
8678
 
8498
8679
  /**
8499
- * 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
8500
8681
  * @method request
8501
8682
  * @param {Object} request object with all the parameters like data (for sending forms), dataType, success, error
8502
8683
  * @param {Function} on_complete
@@ -8616,7 +8797,7 @@ Object.assign(LX, {
8616
8797
  requestBinary(url, on_complete, on_error ) {
8617
8798
  return this.request({ url: url, dataType:"binary", success: on_complete, error: on_error });
8618
8799
  },
8619
-
8800
+
8620
8801
  /**
8621
8802
  * Request script and inserts it in the DOM
8622
8803
  * @method requireScript
@@ -8645,7 +8826,7 @@ Object.assign(LX, {
8645
8826
  script.src = url[i] + ( version ? "?version=" + version : "" );
8646
8827
  script.original_src = url[i];
8647
8828
  script.async = false;
8648
- script.onload = function(e) {
8829
+ script.onload = function(e) {
8649
8830
  total--;
8650
8831
  loaded_scripts.push(this);
8651
8832
  if(total)
@@ -8657,7 +8838,7 @@ Object.assign(LX, {
8657
8838
  on_complete( loaded_scripts );
8658
8839
  };
8659
8840
  if(on_error)
8660
- script.onerror = function(err) {
8841
+ script.onerror = function(err) {
8661
8842
  on_error(err, this.original_src, this.num );
8662
8843
  }
8663
8844
  document.getElementsByTagName('head')[0].appendChild(script);
@@ -8681,7 +8862,7 @@ Object.assign(LX, {
8681
8862
  {
8682
8863
  LX.request({ url: url, dataType: 'blob', success: (f) => {
8683
8864
  fr.readAsDataURL( f );
8684
- fr.onload = e => {
8865
+ fr.onload = e => {
8685
8866
  _download(e.currentTarget.result);
8686
8867
  };
8687
8868
  } });
@@ -8767,31 +8948,31 @@ LX.UTILS = {
8767
8948
  element.offsetHeight;
8768
8949
  },
8769
8950
  getControlPoints( x0, y0, x1, y1, x2, y2, t ) {
8770
-
8951
+
8771
8952
  // x0,y0,x1,y1 are the coordinates of the end (knot) pts of this segment
8772
8953
  // x2,y2 is the next knot -- not connected here but needed to calculate p2
8773
8954
  // p1 is the control point calculated here, from x1 back toward x0.
8774
- // 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
8775
8956
  // next segment's p1.
8776
8957
  // t is the 'tension' which controls how far the control points spread.
8777
-
8958
+
8778
8959
  // Scaling factors: distances from this knot to the previous and following knots.
8779
8960
  var d01=Math.sqrt(Math.pow(x1-x0,2)+Math.pow(y1-y0,2));
8780
8961
  var d12=Math.sqrt(Math.pow(x2-x1,2)+Math.pow(y2-y1,2));
8781
-
8962
+
8782
8963
  var fa=t*d01/(d01+d12);
8783
8964
  var fb=t-fa;
8784
-
8965
+
8785
8966
  var p1x=x1+fa*(x0-x2);
8786
8967
  var p1y=y1+fa*(y0-y2);
8787
-
8968
+
8788
8969
  var p2x=x1-fb*(x0-x2);
8789
- var p2y=y1-fb*(y0-y2);
8790
-
8970
+ var p2y=y1-fb*(y0-y2);
8971
+
8791
8972
  return [p1x,p1y,p2x,p2y]
8792
8973
  },
8793
8974
  drawSpline( ctx, pts, t ) {
8794
-
8975
+
8795
8976
  ctx.save();
8796
8977
  var cp=[]; // array of control points, as x0,y0,x1,y1,...
8797
8978
  var n=pts.length;
@@ -8799,7 +8980,7 @@ LX.UTILS = {
8799
8980
  // Draw an open curve, not connected at the ends
8800
8981
  for(var i=0;i<n-4;i+=2) {
8801
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));
8802
- }
8983
+ }
8803
8984
 
8804
8985
  for(var i=2;i<pts.length-5;i+=2) {
8805
8986
  ctx.beginPath();
@@ -8815,13 +8996,13 @@ LX.UTILS = {
8815
8996
  ctx.quadraticCurveTo(cp[0],cp[1],pts[2],pts[3]);
8816
8997
  ctx.stroke();
8817
8998
  ctx.closePath();
8818
-
8999
+
8819
9000
  ctx.beginPath();
8820
9001
  ctx.moveTo(pts[n-2],pts[n-1]);
8821
9002
  ctx.quadraticCurveTo(cp[2*n-10],cp[2*n-9],pts[n-4],pts[n-3]);
8822
9003
  ctx.stroke();
8823
9004
  ctx.closePath();
8824
-
9005
+
8825
9006
  ctx.restore();
8826
9007
  }
8827
9008
  };