lexgui 0.1.43 → 0.1.45

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.43",
11
+ version: "0.1.45",
12
12
  ready: false,
13
13
  components: [], // specific pre-build components
14
14
  signals: {} // events and triggers
@@ -60,6 +60,15 @@ function deepCopy( o )
60
60
 
61
61
  LX.deepCopy = deepCopy;
62
62
 
63
+ function setTheme( colorScheme )
64
+ {
65
+ colorScheme = ( colorScheme == "light" ) ? "light" : "dark";
66
+ document.documentElement.setAttribute( "data-theme", colorScheme );
67
+ LX.emit( "@on_new_color_scheme", colorScheme );
68
+ }
69
+
70
+ LX.setTheme = setTheme;
71
+
63
72
  function setThemeColor( colorName, color )
64
73
  {
65
74
  var r = document.querySelector( ':root' );
@@ -70,8 +79,24 @@ LX.setThemeColor = setThemeColor;
70
79
 
71
80
  function getThemeColor( colorName )
72
81
  {
73
- var r = getComputedStyle( document.querySelector( ':root' ) );
74
- return r.getPropertyValue( '--' + colorName );
82
+ const r = getComputedStyle( document.querySelector( ':root' ) );
83
+ const value = r.getPropertyValue( '--' + colorName );
84
+
85
+ if( value.includes( "light-dark" ) && window.matchMedia )
86
+ {
87
+ const currentScheme = r.getPropertyValue( "color-scheme" );
88
+
89
+ if( ( window.matchMedia( "(prefers-color-scheme: light)" ).matches ) || ( currentScheme == "light" ) )
90
+ {
91
+ return value.substring( value.indexOf( '(' ) + 1, value.indexOf( ',' ) ).replace( /\s/g, '' );
92
+ }
93
+ else
94
+ {
95
+ return value.substring( value.indexOf( ',' ) + 1, value.indexOf( ')' ) ).replace( /\s/g, '' );
96
+ }
97
+ }
98
+
99
+ return value;
75
100
  }
76
101
 
77
102
  LX.getThemeColor = getThemeColor;
@@ -128,6 +153,23 @@ function simple_guidGenerator() {
128
153
 
129
154
  LX.guidGenerator = simple_guidGenerator;
130
155
 
156
+ function buildTextPattern( options = {} ) {
157
+ let patterns = [];
158
+ if ( options.lowercase ) patterns.push("(?=.*[a-z])");
159
+ if ( options.uppercase ) patterns.push("(?=.*[A-Z])");
160
+ if ( options.digit ) patterns.push("(?=.*\\d)");
161
+ if ( options.specialChar ) patterns.push("(?=.*[@#$%^&+=!])");
162
+ if ( options.noSpaces ) patterns.push("(?!.*\\s)");
163
+
164
+ let minLength = options.minLength || 0;
165
+ let maxLength = options.maxLength || ""; // Empty means no max length restriction
166
+
167
+ let pattern = `^${ patterns.join("") }.{${ minLength },${ maxLength }}$`;
168
+ return options.asRegExp ? new RegExp( pattern ) : pattern;
169
+ }
170
+
171
+ LX.buildTextPattern = buildTextPattern;
172
+
131
173
  // Timer that works everywhere (from litegraph.js)
132
174
  if (typeof performance != "undefined") {
133
175
  LX.getTime = performance.now.bind(performance);
@@ -589,6 +631,10 @@ function init( options = { } )
589
631
  {
590
632
  this.container.appendChild( root );
591
633
  }
634
+ else
635
+ {
636
+ this.root = document.body;
637
+ }
592
638
 
593
639
  // Disable drag icon
594
640
  root.addEventListener( 'dragover', function( e ) {
@@ -621,6 +667,11 @@ function init( options = { } )
621
667
  this.main_area = new Area( { id: options.id ?? 'mainarea' } );
622
668
  }
623
669
 
670
+ window.matchMedia( "(prefers-color-scheme: dark)" ).addEventListener( "change", event => {
671
+ const newColorScheme = event.matches ? "dark" : "light";
672
+ LX.emit( "@on_new_color_scheme", newColorScheme );
673
+ });
674
+
624
675
  return this.main_area;
625
676
  }
626
677
 
@@ -733,7 +784,7 @@ function prompt( text, title, callback, options = {} )
733
784
  if( callback ) callback.call( this, value );
734
785
  dialog.close();
735
786
  }
736
- }, { buttonClass: "accept" });
787
+ }, { buttonClass: "primary" });
737
788
 
738
789
  p.addButton(null, "Cancel", () => {if(options.on_cancel) options.on_cancel(); dialog.close();} );
739
790
 
@@ -750,6 +801,25 @@ function prompt( text, title, callback, options = {} )
750
801
 
751
802
  LX.prompt = prompt;
752
803
 
804
+ /**
805
+ * @method badge
806
+ * @param {String} text
807
+ * @param {String} className
808
+ * @param {*} options
809
+ * style: Style attributes to override
810
+ */
811
+
812
+ function badge( text, className, options = {} )
813
+ {
814
+ const container = document.createElement( "div" );
815
+ container.innerHTML = text;
816
+ container.className = "lexbadge " + ( className ?? "" );
817
+ Object.assign( container.style, options.style ?? {} );
818
+ return container.outerHTML;
819
+ }
820
+
821
+ LX.badge = badge;
822
+
753
823
  /*
754
824
  * Events and Signals
755
825
  */
@@ -837,7 +907,9 @@ function emit( signalName, value, options = {} )
837
907
  }
838
908
  else
839
909
  {
840
- obj[ signalName ].call( obj, value );
910
+ // This is a function callback!
911
+ const fn = obj;
912
+ fn( null, value );
841
913
  }
842
914
  }
843
915
  }
@@ -2161,18 +2233,18 @@ class Menubar {
2161
2233
  entry.className = "lexmenuentry";
2162
2234
  entry.id = pKey;
2163
2235
  entry.innerHTML = "<span>" + key + "</span>";
2164
- if(options.position == "left") {
2165
- this.root.prepend( entry );
2166
- }
2167
- else {
2168
- if(options.position == "right")
2169
- entry.right = true;
2170
- if(this.root.lastChild && this.root.lastChild.right) {
2171
- this.root.lastChild.before( entry );
2172
- }
2173
- else {
2174
- this.root.appendChild( entry );
2175
- }
2236
+ if(options.position == "left") {
2237
+ this.root.prepend( entry );
2238
+ }
2239
+ else {
2240
+ if(options.position == "right")
2241
+ entry.right = true;
2242
+ if(this.root.lastChild && this.root.lastChild.right) {
2243
+ this.root.lastChild.before( entry );
2244
+ }
2245
+ else {
2246
+ this.root.appendChild( entry );
2247
+ }
2176
2248
  }
2177
2249
 
2178
2250
  const create_submenu = function( o, k, c, d ) {
@@ -2393,16 +2465,16 @@ class Menubar {
2393
2465
  button.style.maxHeight = "calc(100% - 10px)";
2394
2466
  button.style.alignItems = "center";
2395
2467
 
2396
- if(options.float == "right")
2397
- button.right = true;
2398
- if(this.root.lastChild && this.root.lastChild.right) {
2399
- this.root.lastChild.before( button );
2468
+ if(options.float == "right")
2469
+ button.right = true;
2470
+ if(this.root.lastChild && this.root.lastChild.right) {
2471
+ this.root.lastChild.before( button );
2400
2472
  }
2401
2473
  else if(options.float == "left") {
2402
2474
  this.root.prepend(button);
2403
- }
2404
- else {
2405
- this.root.appendChild( button );
2475
+ }
2476
+ else {
2477
+ this.root.appendChild( button );
2406
2478
  }
2407
2479
 
2408
2480
  const _b = button.querySelector('a');
@@ -2434,16 +2506,16 @@ class Menubar {
2434
2506
  button.style.padding = "5px";
2435
2507
  button.style.alignItems = "center";
2436
2508
 
2437
- if(options.float == "right")
2438
- button.right = true;
2439
- if(this.root.lastChild && this.root.lastChild.right) {
2440
- this.root.lastChild.before( button );
2441
- }
2509
+ if(options.float == "right")
2510
+ button.right = true;
2511
+ if(this.root.lastChild && this.root.lastChild.right) {
2512
+ this.root.lastChild.before( button );
2513
+ }
2442
2514
  else if(options.float == "left") {
2443
2515
  this.root.prepend(button);
2444
2516
  }
2445
- else {
2446
- this.root.appendChild( button );
2517
+ else {
2518
+ this.root.appendChild( button );
2447
2519
  }
2448
2520
 
2449
2521
  const _b = button.querySelector('a');
@@ -2463,44 +2535,75 @@ class Menubar {
2463
2535
 
2464
2536
  addButtons( buttons, options = {} ) {
2465
2537
 
2466
- if(!buttons)
2467
- throw("No buttons to add!");
2538
+ if( !buttons )
2539
+ {
2540
+ throw( "No buttons to add!" );
2541
+ }
2468
2542
 
2469
- if(!this.buttonContainer)
2543
+ if( !this.buttonContainer )
2470
2544
  {
2471
- this.buttonContainer = document.createElement('div');
2545
+ this.buttonContainer = document.createElement( "div" );
2472
2546
  this.buttonContainer.className = "lexmenubuttons";
2473
- this.buttonContainer.classList.add(options.float ?? 'center');
2474
- if(options.position == "right")
2475
- this.buttonContainer.right = true;
2476
- if(this.root.lastChild && this.root.lastChild.right) {
2477
- this.root.lastChild.before( this.buttonContainer );
2478
- }
2479
- else {
2480
- this.root.appendChild( this.buttonContainer );
2547
+ this.buttonContainer.classList.add( options.float ?? "center" );
2548
+
2549
+ if( options.position == "right" )
2550
+ {
2551
+ this.buttonContainer.right = true;
2552
+ }
2553
+
2554
+ if( this.root.lastChild && this.root.lastChild.right )
2555
+ {
2556
+ this.root.lastChild.before( this.buttonContainer );
2557
+ }
2558
+ else
2559
+ {
2560
+ this.root.appendChild( this.buttonContainer );
2481
2561
  }
2482
2562
  }
2483
2563
 
2484
2564
  for( let i = 0; i < buttons.length; ++i )
2485
2565
  {
2486
- let data = buttons[i];
2487
- let button = document.createElement('div');
2566
+ let data = buttons[ i ];
2567
+ let button = document.createElement( "label" );
2488
2568
  const title = data.title;
2489
2569
  let disabled = data.disabled ?? false;
2490
2570
  button.className = "lexmenubutton" + (disabled ? " disabled" : "");
2491
2571
  button.title = title ?? "";
2492
- button.innerHTML = "<a class='" + data.icon + " lexicon'></a>";
2493
2572
  this.buttonContainer.appendChild( button );
2494
2573
 
2495
- const _b = button.querySelector('a');
2496
- _b.addEventListener("click", (e) => {
2497
- disabled = e.target.parentElement.classList.contains("disabled");
2498
- if(data.callback && !disabled)
2499
- data.callback.call( this, _b, e );
2574
+ const icon = document.createElement( "a" );
2575
+ icon.className = data.icon + " lexicon";
2576
+ button.appendChild( icon );
2577
+
2578
+ let trigger = icon;
2579
+
2580
+ if( data.swap )
2581
+ {
2582
+ button.classList.add( "swap" );
2583
+ icon.classList.add( "swap-off" );
2584
+
2585
+ const input = document.createElement( "input" );
2586
+ input.type = "checkbox";
2587
+ button.prepend( input );
2588
+ trigger = input;
2589
+
2590
+ const swapIcon = document.createElement( "a" );
2591
+ swapIcon.className = data.swap + " swap-on lexicon";
2592
+ button.appendChild( swapIcon );
2593
+ }
2594
+
2595
+ trigger.addEventListener("click", e => {
2596
+ if( data.callback && !disabled )
2597
+ {
2598
+ const swapInput = button.querySelector( "input" );
2599
+ data.callback.call( this, e, swapInput?.checked );
2600
+ }
2500
2601
  });
2501
2602
 
2502
- if(title)
2603
+ if( title )
2604
+ {
2503
2605
  this.buttons[ title ] = button;
2606
+ }
2504
2607
  }
2505
2608
  }
2506
2609
  };
@@ -2631,27 +2734,30 @@ class Widget {
2631
2734
  static BUTTON = 3;
2632
2735
  static DROPDOWN = 4;
2633
2736
  static CHECKBOX = 5;
2634
- static COLOR = 6;
2635
- static NUMBER = 7;
2636
- static TITLE = 8;
2637
- static VECTOR = 9;
2638
- static TREE = 10;
2639
- static PROGRESS = 11;
2640
- static FILE = 12;
2641
- static LAYERS = 13;
2642
- static ARRAY = 14;
2643
- static LIST = 15;
2644
- static TAGS = 16;
2645
- static CURVE = 17;
2646
- static CARD = 18;
2647
- static IMAGE = 19;
2648
- static CONTENT = 20;
2649
- static CUSTOM = 21;
2650
- static SEPARATOR = 22;
2651
- static KNOB = 23;
2652
- static SIZE = 24;
2653
- static PAD = 25;
2654
- static FORM = 26;
2737
+ static TOGGLE = 6;
2738
+ static COLOR = 7;
2739
+ static NUMBER = 8;
2740
+ static TITLE = 9;
2741
+ static VECTOR = 10;
2742
+ static TREE = 11;
2743
+ static PROGRESS = 12;
2744
+ static FILE = 13;
2745
+ static LAYERS = 14;
2746
+ static ARRAY = 15;
2747
+ static LIST = 16;
2748
+ static TAGS = 17;
2749
+ static CURVE = 18;
2750
+ static CARD = 19;
2751
+ static IMAGE = 20;
2752
+ static CONTENT = 21;
2753
+ static CUSTOM = 22;
2754
+ static SEPARATOR = 23;
2755
+ static KNOB = 24;
2756
+ static SIZE = 25;
2757
+ static PAD = 26;
2758
+ static FORM = 27;
2759
+ static DIAL = 28;
2760
+ static COUNTER = 29;
2655
2761
 
2656
2762
  static NO_CONTEXT_TYPES = [
2657
2763
  Widget.BUTTON,
@@ -2724,6 +2830,7 @@ class Widget {
2724
2830
  case Widget.BUTTON: return "Button";
2725
2831
  case Widget.DROPDOWN: return "Dropdown";
2726
2832
  case Widget.CHECKBOX: return "Checkbox";
2833
+ case Widget.TOGGLE: return "Toggle";
2727
2834
  case Widget.COLOR: return "Color";
2728
2835
  case Widget.NUMBER: return "Number";
2729
2836
  case Widget.VECTOR: return "Vector";
@@ -2739,8 +2846,12 @@ class Widget {
2739
2846
  case Widget.SIZE: return "Size";
2740
2847
  case Widget.PAD: return "Pad";
2741
2848
  case Widget.FORM: return "Form";
2849
+ case Widget.DIAL: return "Dial";
2850
+ case Widget.COUNTER: return "Counter";
2742
2851
  case Widget.CUSTOM: return this.customName;
2743
2852
  }
2853
+
2854
+ console.error( `Unknown Widget type: ${ this.type }` );
2744
2855
  }
2745
2856
 
2746
2857
  refresh() {
@@ -3913,41 +4024,50 @@ class Panel {
3913
4024
  /**
3914
4025
  * @method addTitle
3915
4026
  * @param {String} name Title name
4027
+ * @param {*} options:
4028
+ * link: Href in case title is an hyperlink
4029
+ * target: Target name of the iframe (if any)
4030
+ * icon: FA class of the icon (if any)
4031
+ * iconColor: Color of title icon (if any)
4032
+ * style: CSS to override
3916
4033
  */
3917
4034
 
3918
4035
  addTitle( name, options = {} ) {
3919
4036
 
3920
- if(!name) {
3921
- throw("Set Widget Name!");
4037
+ if( !name )
4038
+ {
4039
+ throw( "Can't create Title without text!" );
3922
4040
  }
3923
4041
 
3924
- let widget = this.create_widget(null, Widget.TITLE, options);
4042
+ let widget = this.create_widget( null, Widget.TITLE, options );
3925
4043
  let element = widget.domEl;
3926
4044
  element.className = "lextitle";
3927
4045
 
3928
- if(options.icon) {
3929
- let icon = document.createElement('a');
4046
+ if( options.icon )
4047
+ {
4048
+ let icon = document.createElement( 'a' );
3930
4049
  icon.className = options.icon;
3931
- icon.style.color = options.icon_color || "";
3932
- element.appendChild(icon);
4050
+ icon.style.color = options.iconColor || "";
4051
+ element.appendChild( icon );
3933
4052
  }
3934
4053
 
3935
- let text = document.createElement('span');
4054
+ let text = document.createElement( "span");
3936
4055
  text.innerText = name;
3937
- element.appendChild(text);
4056
+ element.appendChild( text );
3938
4057
 
3939
- Object.assign(element.style, options.style ?? {});
4058
+ Object.assign( element.style, options.style ?? {} );
3940
4059
 
3941
- if(options.link != undefined)
4060
+ if( options.link != undefined )
3942
4061
  {
3943
- let link_el = document.createElement('a');
3944
- link_el.innerText = name;
3945
- link_el.href = options.link;
3946
- link_el.target = options.target ?? "";
3947
- link_el.className = "lextitle link";
3948
- Object.assign(link_el.style, options.style ?? {});
3949
- element.replaceWith(link_el);
4062
+ let linkDom = document.createElement('a');
4063
+ linkDom.innerText = name;
4064
+ linkDom.href = options.link;
4065
+ linkDom.target = options.target ?? "";
4066
+ linkDom.className = "lextitle link";
4067
+ Object.assign( linkDom.style, options.style ?? {} );
4068
+ element.replaceWith( linkDom );
3950
4069
  }
4070
+
3951
4071
  return element;
3952
4072
  }
3953
4073
 
@@ -3958,7 +4078,9 @@ class Panel {
3958
4078
  * @param {Function} callback Callback function on change
3959
4079
  * @param {*} options:
3960
4080
  * disabled: Make the widget disabled [false]
4081
+ * required: Make the input required
3961
4082
  * placeholder: Add input placeholder
4083
+ * pattern: Regular expression that value must match
3962
4084
  * trigger: Choose onchange trigger (default, input) [default]
3963
4085
  * inputWidth: Width of the text input
3964
4086
  * skipReset: Don't add the reset value button when value changes
@@ -3973,11 +4095,18 @@ class Panel {
3973
4095
  widget.onGetValue = () => {
3974
4096
  return wValue.value;
3975
4097
  };
4098
+
3976
4099
  widget.onSetValue = ( newValue, skipCallback ) => {
3977
4100
  this.disabled ? wValue.innerText = newValue : wValue.value = newValue;
3978
4101
  Panel._dispatch_event( wValue, "focusout", skipCallback );
3979
4102
  };
3980
4103
 
4104
+ widget.valid = () => {
4105
+ if( wValue.pattern == "" ) { return true; }
4106
+ const regexp = new RegExp( wValue.pattern );
4107
+ return regexp.test( wValue.value );
4108
+ };
4109
+
3981
4110
  let element = widget.domEl;
3982
4111
 
3983
4112
  // Add reset functionality
@@ -3996,6 +4125,11 @@ class Panel {
3996
4125
  container.style.width = options.inputWidth || "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + " )";
3997
4126
  container.style.display = "flex";
3998
4127
 
4128
+ if( options.textClass )
4129
+ {
4130
+ container.classList.add( options.textClass );
4131
+ }
4132
+
3999
4133
  this.disabled = ( options.disabled || options.warning ) ?? ( options.url ? true : false );
4000
4134
  let wValue = null;
4001
4135
 
@@ -4007,14 +4141,33 @@ class Panel {
4007
4141
  wValue.style.width = "100%";
4008
4142
  wValue.style.textAlign = options.float ?? "";
4009
4143
 
4010
- if( options.placeholder )
4011
- wValue.setAttribute( "placeholder", options.placeholder );
4144
+ wValue.setAttribute( "placeholder", options.placeholder ?? "" );
4145
+
4146
+ if( options.required )
4147
+ {
4148
+ wValue.setAttribute( "required", options.required );
4149
+ }
4150
+
4151
+ if( options.pattern )
4152
+ {
4153
+ wValue.setAttribute( "pattern", options.pattern );
4154
+ }
4012
4155
 
4013
4156
  var resolve = ( function( val, event ) {
4157
+
4158
+ if( !widget.valid() )
4159
+ {
4160
+ return;
4161
+ }
4162
+
4014
4163
  const skipCallback = event.detail;
4015
4164
  let btn = element.querySelector( ".lexwidgetname .lexicon" );
4016
4165
  if( btn ) btn.style.display = ( val != wValue.iValue ? "block" : "none" );
4017
- if( !skipCallback ) this._trigger( new IEvent( name, val, event ), callback );
4166
+ if( !skipCallback )
4167
+ {
4168
+ this._trigger( new IEvent( name, val, event ), callback );
4169
+ }
4170
+
4018
4171
  }).bind( this );
4019
4172
 
4020
4173
  const trigger = options.trigger ?? 'default';
@@ -4226,18 +4379,13 @@ class Panel {
4226
4379
 
4227
4380
  var wValue = document.createElement( 'button' );
4228
4381
  wValue.title = options.title ?? "";
4229
- wValue.className = "lexbutton";
4382
+ wValue.className = "lexbutton " + ( options.buttonClass ?? "" );
4230
4383
 
4231
4384
  if( options.selected )
4232
4385
  {
4233
4386
  wValue.classList.add( "selected" );
4234
4387
  }
4235
4388
 
4236
- if( options.buttonClass )
4237
- {
4238
- wValue.classList.add( options.buttonClass );
4239
- }
4240
-
4241
4389
  wValue.innerHTML =
4242
4390
  (options.icon ? "<a class='" + options.icon + "'></a>" :
4243
4391
  ( options.img ? "<img src='" + options.img + "'>" : "<span>" + (value || "") + "</span>" ));
@@ -4464,20 +4612,44 @@ class Panel {
4464
4612
 
4465
4613
  for( let entry in data )
4466
4614
  {
4467
- const entryData = data[ entry ];
4468
- this.addText( entry, entryData.constructor == Object ? entryData.value : entryData, ( value ) => {
4615
+ let entryData = data[ entry ];
4616
+
4617
+ if( entryData.constructor != Object )
4618
+ {
4619
+ entryData = { };
4620
+ }
4621
+
4622
+ entryData.placeholder = entryData.placeholder ?? entry;
4623
+ entryData.width = "calc(100% - 10px)";
4624
+
4625
+ this.addLabel( entry, { textClass: "formlabel" } );
4626
+
4627
+ entryData.textWidget = this.addText( null, entryData.constructor == Object ? entryData.value : entryData, ( value ) => {
4469
4628
  container.formData[ entry ] = value;
4470
4629
  }, entryData );
4471
4630
 
4472
4631
  container.formData[ entry ] = entryData.constructor == Object ? entryData.value : entryData;
4473
4632
  }
4474
4633
 
4634
+ this.addBlank( );
4635
+
4475
4636
  this.addButton( null, options.actionName ?? "Submit", ( value, event ) => {
4637
+
4638
+ for( let entry in data )
4639
+ {
4640
+ let entryData = data[ entry ];
4641
+
4642
+ if( !entryData.textWidget.valid() )
4643
+ {
4644
+ return;
4645
+ }
4646
+ }
4647
+
4476
4648
  if( callback )
4477
4649
  {
4478
4650
  callback( container.formData, event );
4479
4651
  }
4480
- } );
4652
+ }, { buttonClass: "primary", width: "calc(100% - 10px)" } );
4481
4653
 
4482
4654
  this.clearQueue();
4483
4655
 
@@ -4493,16 +4665,32 @@ class Panel {
4493
4665
 
4494
4666
  /**
4495
4667
  * @method addContent
4496
- * @param {HTMLElement} element
4668
+ * @param {HTMLElement/String} element
4497
4669
  */
4498
4670
 
4499
4671
  addContent( element, options = {} ) {
4500
4672
 
4501
4673
  if( !element )
4502
- return;
4674
+ {
4675
+ return;
4676
+ }
4677
+
4678
+ if( element.constructor == String )
4679
+ {
4680
+ const tmp = document.createElement( "div" );
4681
+ tmp.innerHTML = element;
4682
+ if( tmp.childElementCount > 1 )
4683
+ {
4684
+ element = tmp;
4685
+ }
4686
+ else
4687
+ {
4688
+ element = tmp.firstElementChild;
4689
+ }
4690
+ }
4503
4691
 
4504
- let widget = this.create_widget(null, Widget.CONTENT, options);
4505
- widget.domEl.appendChild(element);
4692
+ let widget = this.create_widget( null, Widget.CONTENT, options );
4693
+ widget.domEl.appendChild( element );
4506
4694
  return widget;
4507
4695
  }
4508
4696
 
@@ -4607,7 +4795,7 @@ class Panel {
4607
4795
  const iString = String( i );
4608
4796
  maxWidth = Math.max( iString.length, maxWidth );
4609
4797
  }
4610
- return maxWidth * 9;
4798
+ return Math.max( maxWidth * 10, 80 );
4611
4799
  };
4612
4800
 
4613
4801
  let selectedOption = this.addButton( null, buttonName, ( value, event ) => {
@@ -4616,13 +4804,35 @@ class Panel {
4616
4804
  delete list.unfocus_event;
4617
4805
  return;
4618
4806
  }
4619
- const topPosition = selectedOption.getBoundingClientRect().y;
4620
- list.style.top = (topPosition + selectedOption.offsetHeight) + 'px';
4807
+
4808
+ list.toggleAttribute( "hidden" );
4809
+ list.classList.remove( "place-above" );
4810
+
4811
+ const listHeight = 26 * values.length;
4812
+ const rect = selectedOption.getBoundingClientRect();
4813
+ const topPosition = rect.y;
4814
+
4815
+ let maxY = window.innerHeight;
4816
+
4817
+ if( this.mainContainer )
4818
+ {
4819
+ const parentRect = this.mainContainer.getBoundingClientRect();
4820
+ maxY = parentRect.y + parentRect.height;
4821
+ }
4822
+
4823
+ list.style.top = ( topPosition + selectedOption.offsetHeight ) + 'px';
4824
+
4825
+ const showAbove = ( topPosition + listHeight ) > maxY;
4826
+ if( showAbove )
4827
+ {
4828
+ list.style.top = ( topPosition - listHeight ) + 'px';
4829
+ list.classList.add( "place-above" );
4830
+ }
4831
+
4621
4832
  list.style.width = (event.currentTarget.clientWidth) + 'px';
4622
4833
  list.style.minWidth = (_getMaxListWidth()) + 'px';
4623
- list.toggleAttribute('hidden');
4624
4834
  list.focus();
4625
- }, { buttonClass: 'array', skipInlineCount: true });
4835
+ }, { buttonClass: "array", skipInlineCount: true });
4626
4836
 
4627
4837
  this.clearQueue();
4628
4838
 
@@ -4850,6 +5060,86 @@ class Panel {
4850
5060
  return widget;
4851
5061
  }
4852
5062
 
5063
+ /**
5064
+ * @method addDial
5065
+ * @param {String} name Widget name
5066
+ * @param {Array of Array} values Array of 2N Arrays of each value of the dial
5067
+ * @param {Function} callback Callback function on change
5068
+ * @param {*} options:
5069
+ * skipReset: Don't add the reset value button when value changes
5070
+ * bgColor: Widget background color
5071
+ * pointsColor: Curve points color
5072
+ * lineColor: Curve line color
5073
+ * noOverlap: Points do not overlap, replacing themselves if necessary
5074
+ * allowAddValues: Support adding values on click
5075
+ * smooth: Curve smoothness
5076
+ * moveOutAction: Clamp or delete points moved out of the curve (LX.CURVE_MOVEOUT_CLAMP, LX.CURVE_MOVEOUT_DELETE)
5077
+ */
5078
+
5079
+ addDial( name, values, callback, options = {} ) {
5080
+
5081
+ let that = this;
5082
+ let widget = this.create_widget(name, Widget.DIAL, options);
5083
+
5084
+ widget.onGetValue = () => {
5085
+ return JSON.parse(JSON.stringify(curveInstance.element.value));
5086
+ };
5087
+
5088
+ widget.onSetValue = ( newValue, skipCallback ) => {
5089
+ let btn = element.querySelector( ".lexwidgetname .lexicon" );
5090
+ if( btn ) btn.style.display = ( newValue != curveInstance.element.value ? "block" : "none" );
5091
+ curveInstance.element.value = JSON.parse( JSON.stringify( newValue ) );
5092
+ curveInstance.redraw();
5093
+ if( !skipCallback ) that._trigger( new IEvent( name, curveInstance.element.value, null ), callback );
5094
+ };
5095
+
5096
+ let element = widget.domEl;
5097
+ let defaultValues = JSON.parse( JSON.stringify( values ) );
5098
+
5099
+ // Add reset functionality
5100
+ if( widget.name && !(options.skipReset ?? false) )
5101
+ {
5102
+ Panel._add_reset_property(element.domName, function(e) {
5103
+ this.style.display = "none";
5104
+ curveInstance.element.value = JSON.parse( JSON.stringify( defaultValues ) );
5105
+ curveInstance.redraw();
5106
+ that._trigger( new IEvent( name, curveInstance.element.value, e ), callback );
5107
+ });
5108
+ }
5109
+
5110
+ // Add widget value
5111
+
5112
+ var container = document.createElement( 'div' );
5113
+ container.className = "lexcurve";
5114
+ container.style.width = widget.name ? "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")" : '100%';
5115
+
5116
+ options.callback = (v, e) => {
5117
+ let btn = element.querySelector(".lexwidgetname .lexicon");
5118
+ if(btn) btn.style.display = (v != defaultValues ? "block" : "none");
5119
+ that._trigger( new IEvent(name, v, e), callback );
5120
+ };
5121
+
5122
+ options.name = name;
5123
+
5124
+ let curveInstance = new Dial( this, values, options );
5125
+ container.appendChild( curveInstance.element );
5126
+ element.appendChild( container );
5127
+
5128
+ // Resize
5129
+ widget.onresize = curveInstance.redraw.bind( curveInstance );
5130
+ widget.curveInstance = curveInstance;
5131
+
5132
+ doAsync(() => {
5133
+ curveInstance.element.style.height = curveInstance.element.offsetWidth + "px";
5134
+ curveInstance.canvas.width = curveInstance.element.offsetWidth;
5135
+ container.style.width = curveInstance.element.offsetWidth + "px";
5136
+ curveInstance.canvas.height = curveInstance.canvas.width;
5137
+ curveInstance.redraw();
5138
+ });
5139
+
5140
+ return widget;
5141
+ }
5142
+
4853
5143
  /**
4854
5144
  * @method addLayers
4855
5145
  * @param {String} name Widget name
@@ -5188,40 +5478,45 @@ class Panel {
5188
5478
 
5189
5479
  // Show tags
5190
5480
 
5191
- let tags_container = document.createElement('div');
5192
- tags_container.className = "lextags";
5193
- tags_container.style.width = "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
5481
+ const tagsContainer = document.createElement('div');
5482
+ tagsContainer.className = "lextags";
5483
+ tagsContainer.style.width = "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
5194
5484
 
5195
5485
  const create_tags = () => {
5196
5486
 
5197
- tags_container.innerHTML = "";
5487
+ tagsContainer.innerHTML = "";
5198
5488
 
5199
5489
  for( let i = 0; i < value.length; ++i )
5200
5490
  {
5201
- let tag_name = value[i];
5202
- let tag = document.createElement('span');
5491
+ const tagName = value[i];
5492
+ const tag = document.createElement('span');
5203
5493
  tag.className = "lextag";
5204
- tag.innerHTML = tag_name;
5494
+ tag.innerHTML = tagName;
5205
5495
 
5206
- tag.addEventListener('click', function( e ) {
5207
- this.remove();
5208
- value.splice( value.indexOf( tag_name ), 1 );
5496
+ const removeButton = document.createElement('a');
5497
+ removeButton.className = "lextagrmb fa-solid fa-xmark lexicon";
5498
+ tag.appendChild( removeButton );
5499
+
5500
+ removeButton.addEventListener( 'click', e => {
5501
+ tag.remove();
5502
+ value.splice( value.indexOf( tagName ), 1 );
5209
5503
  let btn = element.querySelector( ".lexwidgetname .lexicon" );
5210
5504
  if( btn ) btn.style.display = ( value != defaultValue ? "block" : "none" );
5211
5505
  that._trigger( new IEvent( name, value, e ), callback );
5212
- });
5506
+ } );
5213
5507
 
5214
- tags_container.appendChild( tag );
5508
+ tagsContainer.appendChild( tag );
5215
5509
  }
5216
5510
 
5217
- let tag_input = document.createElement( 'input' );
5218
- tag_input.value = "";
5219
- tag_input.placeholder = "Tag...";
5220
- tags_container.insertChildAtIndex( tag_input, 0 );
5511
+ let tagInput = document.createElement( 'input' );
5512
+ tagInput.value = "";
5513
+ tagInput.placeholder = "Add tag...";
5514
+ tagsContainer.appendChild( tagInput );
5221
5515
 
5222
- tag_input.onkeydown = function( e ) {
5516
+ tagInput.onkeydown = function( e ) {
5223
5517
  const val = this.value.replace(/\s/g, '');
5224
- if( e.key == ' ') {
5518
+ if( e.key == ' ' || e.key == 'Enter' )
5519
+ {
5225
5520
  e.preventDefault();
5226
5521
  if( !val.length || value.indexOf( val ) > -1 )
5227
5522
  return;
@@ -5233,18 +5528,19 @@ class Panel {
5233
5528
  }
5234
5529
  };
5235
5530
 
5236
- tag_input.focus();
5531
+ tagInput.focus();
5237
5532
  }
5238
5533
 
5239
5534
  create_tags();
5240
5535
 
5241
5536
  // Remove branch padding and margins
5242
- if(!widget.name) {
5537
+ if( !widget.name )
5538
+ {
5243
5539
  element.className += " noname";
5244
- tags_container.style.width = "100%";
5540
+ tagsContainer.style.width = "100%";
5245
5541
  }
5246
5542
 
5247
- element.appendChild(tags_container);
5543
+ element.appendChild( tagsContainer );
5248
5544
 
5249
5545
  return widget;
5250
5546
  }
@@ -5257,29 +5553,36 @@ class Panel {
5257
5553
  * @param {*} options:
5258
5554
  * disabled: Make the widget disabled [false]
5259
5555
  * suboptions: Callback to add widgets in case of TRUE value
5556
+ * className: Customize colors
5260
5557
  */
5261
5558
 
5262
5559
  addCheckbox( name, value, callback, options = {} ) {
5263
5560
 
5264
- if( !name ) {
5561
+ if( !name )
5562
+ {
5265
5563
  throw( "Set Widget Name!" );
5266
5564
  }
5267
5565
 
5268
5566
  let widget = this.create_widget( name, Widget.CHECKBOX, options );
5269
5567
 
5270
5568
  widget.onGetValue = () => {
5271
- return flag.value;
5569
+ return checkbox.checked;
5272
5570
  };
5571
+
5273
5572
  widget.onSetValue = ( newValue, skipCallback ) => {
5274
- if( flag.value !== newValue )
5275
- Panel._dispatch_event( toggle, "click", skipCallback );
5573
+ if( checkbox.checked !== newValue )
5574
+ {
5575
+ checkbox.checked = newValue;
5576
+ Panel._dispatch_event( checkbox, "change", skipCallback );
5577
+ }
5276
5578
  };
5277
5579
 
5278
5580
  let element = widget.domEl;
5279
5581
 
5280
5582
  // Add reset functionality
5281
5583
  Panel._add_reset_property( element.domName, function() {
5282
- Panel._dispatch_event( toggle, "click" );
5584
+ checkbox.checked = !checkbox.checked;
5585
+ Panel._dispatch_event( checkbox, "change" );
5283
5586
  });
5284
5587
 
5285
5588
  // Add widget value
@@ -5287,55 +5590,36 @@ class Panel {
5287
5590
  var container = document.createElement('div');
5288
5591
  container.className = "lexcheckboxcont";
5289
5592
 
5290
- let toggle = document.createElement('span');
5291
- toggle.className = "lexcheckbox";
5292
-
5293
- let flag = document.createElement('span');
5294
- flag.value = flag.iValue = value || false;
5295
- flag.className = "checkbox " + (flag.value ? "on" : "");
5296
- flag.id = "checkbox"+simple_guidGenerator();
5297
- flag.innerHTML = "<a class='fa-solid fa-check' style='display: " + (flag.value ? "block" : "none") + "'></a>";
5298
-
5299
- if( options.disabled ) {
5300
- flag.disabled = true;
5301
- toggle.className += " disabled";
5302
- }
5303
-
5304
- toggle.appendChild( flag );
5593
+ let checkbox = document.createElement('input');
5594
+ checkbox.type = "checkbox";
5595
+ checkbox.className = "lexcheckbox " + ( options.className ?? "" );
5596
+ checkbox.checked = value;
5597
+ checkbox.iValue = value;
5598
+ checkbox.disabled = options.disabled ?? false;
5305
5599
 
5306
- let value_name = document.createElement( 'span' );
5307
- value_name.id = "checkboxtext";
5308
- value_name.innerHTML = "On";
5600
+ let valueName = document.createElement( 'span' );
5601
+ valueName.className = "checkboxtext";
5602
+ valueName.innerHTML = "On";
5309
5603
 
5310
- container.appendChild( toggle );
5311
- container.appendChild( value_name );
5312
-
5313
- toggle.addEventListener( "click" , e => {
5314
-
5315
- let flag = toggle.querySelector( ".checkbox" );
5316
- if( flag.disabled )
5317
- return;
5604
+ container.appendChild( checkbox );
5605
+ container.appendChild( valueName );
5318
5606
 
5319
- const skipCallback = ( e.detail.constructor == Number ? null : e.detail );
5607
+ checkbox.addEventListener( "change" , e => {
5320
5608
 
5321
- let check = toggle.querySelector( ".checkbox a" );
5322
-
5323
- flag.value = !flag.value;
5324
- flag.className = "checkbox " + ( flag.value ? "on" : "" );
5325
- check.style.display = flag.value ? "block" : "none";
5609
+ const skipCallback = ( e.detail?.constructor == Number ? null : e.detail );
5326
5610
 
5327
5611
  // Reset button (default value)
5328
5612
  if( !skipCallback )
5329
5613
  {
5330
5614
  let btn = element.querySelector( ".lexwidgetname .lexicon" );
5331
- if( btn ) btn.style.display = flag.value != flag.iValue ? "block": "none";
5615
+ if( btn ) btn.style.display = checkbox.checked != checkbox.iValue ? "block": "none";
5332
5616
  }
5333
5617
 
5334
5618
  // Open suboptions
5335
5619
  let submenu = element.querySelector( ".lexcheckboxsubmenu" );
5336
- if( submenu ) submenu.toggleAttribute( 'hidden', !flag.value );
5620
+ if( submenu ) submenu.toggleAttribute( 'hidden', !checkbox.checked );
5337
5621
 
5338
- if( !skipCallback ) this._trigger( new IEvent( name, flag.value, e ), callback );
5622
+ if( !skipCallback ) this._trigger( new IEvent( name, checkbox.checked, e ), callback );
5339
5623
  });
5340
5624
 
5341
5625
  element.appendChild( container );
@@ -5345,51 +5629,147 @@ class Panel {
5345
5629
  element.style.flexWrap = "wrap";
5346
5630
  let suboptions = document.createElement('div');
5347
5631
  suboptions.className = "lexcheckboxsubmenu";
5348
- suboptions.toggleAttribute('hidden', !flag.value);
5632
+ suboptions.toggleAttribute( 'hidden', !checkbox.checked );
5349
5633
 
5350
5634
  this.queue( suboptions );
5351
5635
  options.suboptions.call(this, this);
5352
5636
  this.clearQueue();
5353
5637
 
5354
- element.appendChild(suboptions);
5638
+ element.appendChild( suboptions );
5355
5639
  }
5356
5640
 
5357
5641
  return widget;
5358
5642
  }
5359
5643
 
5360
5644
  /**
5361
- * @method addColor
5645
+ * @method addToggle
5362
5646
  * @param {String} name Widget name
5363
- * @param {String} value Default color (hex)
5647
+ * @param {Boolean} value Value of the checkbox
5364
5648
  * @param {Function} callback Callback function on change
5365
5649
  * @param {*} options:
5366
5650
  * disabled: Make the widget disabled [false]
5367
- * useRGB: The callback returns color as Array (r, g, b) and not hex [false]
5651
+ * suboptions: Callback to add widgets in case of TRUE value
5652
+ * className: Customize colors
5368
5653
  */
5369
5654
 
5370
- addColor( name, value, callback, options = {} ) {
5655
+ addToggle( name, value, callback, options = {} ) {
5371
5656
 
5372
- if( !name ) {
5657
+ if( !name )
5658
+ {
5373
5659
  throw( "Set Widget Name!" );
5374
5660
  }
5375
5661
 
5376
- let widget = this.create_widget( name, Widget.COLOR, options );
5662
+ let widget = this.create_widget( name, Widget.TOGGLE, options );
5377
5663
 
5378
5664
  widget.onGetValue = () => {
5379
- return color.value;
5665
+ return toggle.checked;
5380
5666
  };
5667
+
5381
5668
  widget.onSetValue = ( newValue, skipCallback ) => {
5382
- color.value = newValue;
5383
- Panel._dispatch_event( color, "input", skipCallback );
5669
+ if( toggle.checked !== newValue )
5670
+ {
5671
+ toggle.checked = newValue;
5672
+ Panel._dispatch_event( toggle, "change", skipCallback );
5673
+ }
5384
5674
  };
5385
5675
 
5386
5676
  let element = widget.domEl;
5387
- let change_from_input = false;
5388
5677
 
5389
5678
  // Add reset functionality
5390
5679
  Panel._add_reset_property( element.domName, function() {
5391
- this.style.display = "none";
5392
- color.value = color.iValue;
5680
+ toggle.checked = !toggle.checked;
5681
+ Panel._dispatch_event( toggle, "change" );
5682
+ });
5683
+
5684
+ // Add widget value
5685
+
5686
+ var container = document.createElement('div');
5687
+ container.className = "lextogglecont";
5688
+
5689
+ let toggle = document.createElement('input');
5690
+ toggle.type = "checkbox";
5691
+ toggle.className = "lextoggle " + ( options.className ?? "" );
5692
+ toggle.checked = value;
5693
+ toggle.iValue = value;
5694
+ toggle.disabled = options.disabled ?? false;
5695
+
5696
+ let valueName = document.createElement( 'span' );
5697
+ valueName.className = "toggletext";
5698
+ valueName.innerHTML = "On";
5699
+
5700
+ container.appendChild( toggle );
5701
+ container.appendChild( valueName );
5702
+
5703
+ toggle.addEventListener( "change" , e => {
5704
+
5705
+ const skipCallback = ( e.detail?.constructor == Number ? null : e.detail );
5706
+
5707
+ // Reset button (default value)
5708
+ if( !skipCallback )
5709
+ {
5710
+ let btn = element.querySelector( ".lexwidgetname .lexicon" );
5711
+ if( btn ) btn.style.display = toggle.checked != toggle.iValue ? "block": "none";
5712
+ }
5713
+
5714
+ // Open suboptions
5715
+ let submenu = element.querySelector( ".lextogglesubmenu" );
5716
+ if( submenu ) submenu.toggleAttribute( 'hidden', !toggle.checked );
5717
+
5718
+ if( !skipCallback ) this._trigger( new IEvent( name, toggle.checked, e ), callback );
5719
+ });
5720
+
5721
+ element.appendChild( container );
5722
+
5723
+ if( options.suboptions )
5724
+ {
5725
+ element.style.flexWrap = "wrap";
5726
+ let suboptions = document.createElement('div');
5727
+ suboptions.className = "lextogglesubmenu";
5728
+ suboptions.toggleAttribute( 'hidden', !toggle.checked );
5729
+
5730
+ this.queue( suboptions );
5731
+ options.suboptions.call(this, this);
5732
+ this.clearQueue();
5733
+
5734
+ element.appendChild( suboptions );
5735
+ }
5736
+
5737
+ return widget;
5738
+ }
5739
+
5740
+ /**
5741
+ * @method addColor
5742
+ * @param {String} name Widget name
5743
+ * @param {String} value Default color (hex)
5744
+ * @param {Function} callback Callback function on change
5745
+ * @param {*} options:
5746
+ * disabled: Make the widget disabled [false]
5747
+ * useRGB: The callback returns color as Array (r, g, b) and not hex [false]
5748
+ */
5749
+
5750
+ addColor( name, value, callback, options = {} ) {
5751
+
5752
+ if( !name ) {
5753
+ throw( "Set Widget Name!" );
5754
+ }
5755
+
5756
+ let widget = this.create_widget( name, Widget.COLOR, options );
5757
+
5758
+ widget.onGetValue = () => {
5759
+ return color.value;
5760
+ };
5761
+ widget.onSetValue = ( newValue, skipCallback ) => {
5762
+ color.value = newValue;
5763
+ Panel._dispatch_event( color, "input", skipCallback );
5764
+ };
5765
+
5766
+ let element = widget.domEl;
5767
+ let change_from_input = false;
5768
+
5769
+ // Add reset functionality
5770
+ Panel._add_reset_property( element.domName, function() {
5771
+ this.style.display = "none";
5772
+ color.value = color.iValue;
5393
5773
  Panel._dispatch_event( color, "input" );
5394
5774
  });
5395
5775
 
@@ -5580,7 +5960,14 @@ class Panel {
5580
5960
  };
5581
5961
  }
5582
5962
 
5583
- // Add wheel input
5963
+ vecinput.addEventListener( "input", function( e ) {
5964
+ let new_value = +this.valueAsNumber;
5965
+ vecinput.value = round( new_value, options.precision );
5966
+ if( options.units )
5967
+ {
5968
+ vecinput.unitSpan.style.left = measureRealWidth( vecinput.value ) + "px";
5969
+ }
5970
+ }, false );
5584
5971
 
5585
5972
  vecinput.addEventListener( "wheel", function( e ) {
5586
5973
  e.preventDefault();
@@ -6276,8 +6663,11 @@ class Panel {
6276
6663
  };
6277
6664
  widget.onSetValue = ( newValue, skipCallback ) => {
6278
6665
  element.querySelector("meter").value = newValue;
6666
+ _updateColor();
6279
6667
  if( element.querySelector("span") )
6668
+ {
6280
6669
  element.querySelector("span").innerText = newValue;
6670
+ }
6281
6671
  };
6282
6672
 
6283
6673
  let element = widget.domEl;
@@ -6294,14 +6684,26 @@ class Panel {
6294
6684
  progress.step = "any";
6295
6685
  progress.min = options.min ?? 0;
6296
6686
  progress.max = options.max ?? 1;
6687
+ progress.low = options.low ?? progress.low;
6688
+ progress.high = options.high ?? progress.high;
6689
+ progress.optimum = options.optimum ?? progress.optimum;
6297
6690
  progress.value = value;
6298
6691
 
6299
- if( options.low )
6300
- progress.low = options.low;
6301
- if( options.high )
6302
- progress.high = options.high;
6303
- if( options.optimum )
6304
- progress.optimum = options.optimum;
6692
+ const _updateColor = () => {
6693
+
6694
+ let backgroundColor = LX.getThemeColor( "global-selected" );
6695
+
6696
+ if( progress.low != undefined && progress.value < progress.low )
6697
+ {
6698
+ backgroundColor = LX.getThemeColor( "global-color-error" );
6699
+ }
6700
+ else if( progress.high != undefined && progress.value < progress.high )
6701
+ {
6702
+ backgroundColor = LX.getThemeColor( "global-color-warning" );
6703
+ }
6704
+
6705
+ progress.style.background = `color-mix(in srgb, ${backgroundColor} 20%, transparent)`;
6706
+ };
6305
6707
 
6306
6708
  container.appendChild( progress );
6307
6709
  element.appendChild( container );
@@ -6325,7 +6727,7 @@ class Panel {
6325
6727
  progress.classList.add( "editable" );
6326
6728
  progress.addEventListener( "mousedown", inner_mousedown );
6327
6729
 
6328
- var that = this;
6730
+ const that = this;
6329
6731
 
6330
6732
  function inner_mousedown( e )
6331
6733
  {
@@ -6333,24 +6735,28 @@ class Panel {
6333
6735
  doc.addEventListener( 'mousemove', inner_mousemove );
6334
6736
  doc.addEventListener( 'mouseup', inner_mouseup );
6335
6737
  document.body.classList.add( 'noevents' );
6738
+ progress.classList.add( "grabbing" );
6336
6739
  e.stopImmediatePropagation();
6337
6740
  e.stopPropagation();
6741
+
6742
+ const rect = progress.getBoundingClientRect();
6743
+ const newValue = round( remapRange( e.offsetX, 0, rect.width, progress.min, progress.max ) );
6744
+ that.setValue( name, newValue );
6338
6745
  }
6339
6746
 
6340
6747
  function inner_mousemove( e )
6341
6748
  {
6342
- let dt = -e.movementX;
6749
+ let dt = e.movementX;
6343
6750
 
6344
6751
  if ( dt != 0 )
6345
6752
  {
6346
- let v = that.getValue( name, value );
6347
- v += e.movementX / 100;
6348
- v = round( v );
6349
- that.setValue( name, v );
6753
+ const rect = progress.getBoundingClientRect();
6754
+ const newValue = round( remapRange( e.offsetX - rect.x, 0, rect.width, progress.min, progress.max ) );
6755
+ that.setValue( name, newValue );
6350
6756
 
6351
6757
  if( options.callback )
6352
6758
  {
6353
- options.callback( v, e );
6759
+ options.callback( newValue, e );
6354
6760
  }
6355
6761
  }
6356
6762
 
@@ -6364,9 +6770,12 @@ class Panel {
6364
6770
  doc.removeEventListener( 'mousemove', inner_mousemove );
6365
6771
  doc.removeEventListener( 'mouseup', inner_mouseup );
6366
6772
  document.body.classList.remove( 'noevents' );
6773
+ progress.classList.remove( "grabbing" );
6367
6774
  }
6368
6775
  }
6369
6776
 
6777
+ _updateColor();
6778
+
6370
6779
  return widget;
6371
6780
  }
6372
6781
 
@@ -6376,6 +6785,7 @@ class Panel {
6376
6785
  * @param {Function} callback Callback function on change
6377
6786
  * @param {*} options:
6378
6787
  * local: Ask for local file
6788
+ * disabled: Make the widget disabled [false]
6379
6789
  * read: Return the file itself (False) or the contents (True)
6380
6790
  * type: type to read as [text (Default), buffer, bin, url]
6381
6791
  */
@@ -6396,8 +6806,10 @@ class Panel {
6396
6806
 
6397
6807
  // Create hidden input
6398
6808
  let input = document.createElement( 'input' );
6809
+ input.className = "lexfileinput";
6399
6810
  input.style.width = "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + " - 10%)";
6400
6811
  input.type = 'file';
6812
+ input.disabled = options.disabled ?? false;
6401
6813
 
6402
6814
  if( options.placeholder )
6403
6815
  {
@@ -6648,6 +7060,91 @@ class Panel {
6648
7060
 
6649
7061
  this.addSeparator();
6650
7062
  }
7063
+
7064
+ /**
7065
+ * @method addCounter
7066
+ * @param {String} name Widget name
7067
+ * @param {Number} value Counter value
7068
+ * @param {Function} callback Callback function on change
7069
+ * @param {*} options:
7070
+ * disabled: Make the widget disabled [false]
7071
+ * min, max: Min and Max values
7072
+ * step: Step for adding/substracting
7073
+ * label: Text to show below the counter
7074
+ */
7075
+
7076
+ addCounter( name, value, callback, options = { } ) {
7077
+
7078
+ let widget = this.create_widget( name, Widget.COUNTER, options );
7079
+
7080
+ widget.onGetValue = () => {
7081
+ return counterText.count;
7082
+ };
7083
+
7084
+ widget.onSetValue = ( newValue, skipCallback ) => {
7085
+ _onChange( newValue, skipCallback );
7086
+ };
7087
+
7088
+ let element = widget.domEl;
7089
+
7090
+ const min = options.min ?? 0;
7091
+ const max = options.max ?? 100;
7092
+ const step = options.step ?? 1;
7093
+
7094
+ const _onChange = ( value, skipCallback, event ) => {
7095
+ value = clamp( value, min, max );
7096
+ counterText.count = value;
7097
+ counterText.innerHTML = value;
7098
+ if( !skipCallback )
7099
+ {
7100
+ this._trigger( new IEvent( name, value, event ), callback );
7101
+ }
7102
+ }
7103
+
7104
+ const container = document.createElement( 'div' );
7105
+ container.className = "lexcounter";
7106
+ element.appendChild( container );
7107
+
7108
+ this.queue( container );
7109
+
7110
+ this.addButton(null, "<a style='margin-top: 0px;' class='fa-solid fa-minus'></a>", (value, e) => {
7111
+ let mult = step ?? 1;
7112
+ if( e.shiftKey ) mult *= 10;
7113
+ _onChange( counterText.count - mult, false, e );
7114
+ }, { className: "micro", skipInlineCount: true, title: "Minus" });
7115
+
7116
+ this.clearQueue();
7117
+
7118
+ const containerBox = document.createElement( 'div' );
7119
+ containerBox.className = "lexcounterbox";
7120
+ container.appendChild( containerBox );
7121
+
7122
+ const counterText = document.createElement( 'span' );
7123
+ counterText.className = "lexcountervalue";
7124
+ counterText.innerHTML = value;
7125
+ counterText.count = value;
7126
+ containerBox.appendChild( counterText );
7127
+
7128
+ if( options.label )
7129
+ {
7130
+ const counterLabel = document.createElement( 'span' );
7131
+ counterLabel.className = "lexcounterlabel";
7132
+ counterLabel.innerHTML = options.label;
7133
+ containerBox.appendChild( counterLabel );
7134
+ }
7135
+
7136
+ this.queue( container );
7137
+
7138
+ this.addButton(null, "<a style='margin-top: 0px;' class='fa-solid fa-plus'></a>", (value, e) => {
7139
+ let mult = step ?? 1;
7140
+ if( e.shiftKey ) mult *= 10;
7141
+ _onChange( counterText.count + mult, false, e );
7142
+ }, { className: "micro", skipInlineCount: true, title: "Plus" });
7143
+
7144
+ this.clearQueue();
7145
+
7146
+ return widget;
7147
+ }
6651
7148
  }
6652
7149
 
6653
7150
  LX.Panel = Panel;
@@ -6883,6 +7380,104 @@ class Branch {
6883
7380
 
6884
7381
  LX.Branch = Branch;
6885
7382
 
7383
+ /**
7384
+ * @class Footer
7385
+ */
7386
+
7387
+ class Footer {
7388
+ /**
7389
+ * @param {*} options:
7390
+ * columns: Array with data per column { title, items: [ { title, link } ] }
7391
+ * credits: html string
7392
+ * socials: Array with data per item { title, link, iconHtml }
7393
+ */
7394
+ constructor( options = {} ) {
7395
+
7396
+ const root = document.createElement( "footer" );
7397
+ root.className = "lexfooter";
7398
+
7399
+ const wrapper = document.createElement( "div" );
7400
+ wrapper.className = "wrapper";
7401
+ root.appendChild( wrapper );
7402
+
7403
+ if( options.columns && options.columns.constructor == Array )
7404
+ {
7405
+ const cols = document.createElement( "div" );
7406
+ cols.className = "columns";
7407
+ cols.style.gridTemplateColumns = "1fr ".repeat( options.columns.length );
7408
+ wrapper.appendChild( cols );
7409
+
7410
+ for( let col of options.columns )
7411
+ {
7412
+ const colDom = document.createElement( "div" );
7413
+ colDom.className = "col";
7414
+ cols.appendChild( colDom );
7415
+
7416
+ const colTitle = document.createElement( "h2" );
7417
+ colTitle.innerHTML = col.title;
7418
+ colDom.appendChild( colTitle );
7419
+
7420
+ if( !col.items || !col.items.length )
7421
+ {
7422
+ continue;
7423
+ }
7424
+
7425
+ const itemListDom = document.createElement( "ul" );
7426
+ colDom.appendChild( itemListDom );
7427
+
7428
+ for( let item of col.items )
7429
+ {
7430
+ const itemDom = document.createElement( "li" );
7431
+ itemDom.innerHTML = `<a class="" href="${ item.link }">${ item.title }</a>`;
7432
+ itemListDom.appendChild( itemDom );
7433
+ }
7434
+ }
7435
+ }
7436
+
7437
+ if( options.credits || options.socials )
7438
+ {
7439
+ const hr = document.createElement( "hr" );
7440
+ wrapper.appendChild( hr );
7441
+
7442
+ const creditsSocials = document.createElement( "div" );
7443
+ creditsSocials.className = "credits-and-socials";
7444
+ wrapper.appendChild( creditsSocials );
7445
+
7446
+ if( options.credits )
7447
+ {
7448
+ const credits = document.createElement( "p" );
7449
+ credits.innerHTML = options.credits;
7450
+ creditsSocials.appendChild( credits );
7451
+ }
7452
+
7453
+ if( options.socials )
7454
+ {
7455
+ const socials = document.createElement( "div" );
7456
+ socials.className = "social";
7457
+
7458
+ for( let social of options.socials )
7459
+ {
7460
+ const itemDom = document.createElement( "a" );
7461
+ itemDom.title = social.title;
7462
+ itemDom.innerHTML = social.icon;
7463
+ itemDom.href = social.link;
7464
+ itemDom.target = "_blank";
7465
+ socials.appendChild( itemDom );
7466
+ }
7467
+
7468
+ creditsSocials.appendChild( socials );
7469
+ }
7470
+ }
7471
+
7472
+ // Append directly to body
7473
+ const parent = options.parent ?? document.body;
7474
+ parent.appendChild( root );
7475
+ }
7476
+
7477
+ }
7478
+
7479
+ LX.Footer = Footer;
7480
+
6886
7481
  /**
6887
7482
  * @class Dialog
6888
7483
  */
@@ -7215,8 +7810,8 @@ class ContextMenu {
7215
7810
 
7216
7811
  this.root = document.createElement('div');
7217
7812
  this.root.className = "lexcontextmenubox";
7218
- this.root.style.left = (event.x - 48) + "px";
7219
- this.root.style.top = (event.y - 8) + "px";
7813
+ this.root.style.left = (event.x - 48 + document.scrollingElement.scrollLeft) + "px";
7814
+ this.root.style.top = (event.y - 8 + document.scrollingElement.scrollTop) + "px";
7220
7815
 
7221
7816
  this.root.addEventListener("mouseleave", function() {
7222
7817
  this.remove();
@@ -7505,7 +8100,7 @@ class Curve {
7505
8100
  element.style.minWidth = "50px";
7506
8101
  element.style.minHeight = "20px";
7507
8102
 
7508
- element.bgcolor = options.bgColor || LX.getThemeColor( "global-dark-background" );
8103
+ element.bgcolor = options.bgColor || LX.getThemeColor( "global-intense-background" );
7509
8104
  element.pointscolor = options.pointsColor || LX.getThemeColor( "global-selected-light" );
7510
8105
  element.linecolor = options.lineColor || "#555";
7511
8106
  element.value = value || [];
@@ -7520,6 +8115,12 @@ class Curve {
7520
8115
  element.smooth = (options.smooth && typeof( options.smooth ) == 'number' ? options.smooth : 0.3) || false;
7521
8116
  element.move_out = options.moveOutAction ?? LX.CURVE_MOVEOUT_DELETE;
7522
8117
 
8118
+ LX.addSignal( "@on_new_color_scheme", (el, value) => {
8119
+ element.bgcolor = options.bgColor || LX.getThemeColor( "global-intense-background" );
8120
+ element.pointscolor = options.pointsColor || LX.getThemeColor( "global-selected-light" );
8121
+ this.redraw();
8122
+ } );
8123
+
7523
8124
  this.element = element;
7524
8125
 
7525
8126
  let canvas = document.createElement( "canvas" );
@@ -7823,6 +8424,335 @@ class Curve {
7823
8424
 
7824
8425
  LX.Curve = Curve;
7825
8426
 
8427
+ /**
8428
+ * @class Dial
8429
+ */
8430
+
8431
+ class Dial {
8432
+
8433
+ constructor( panel, value, options = {} ) {
8434
+
8435
+ let element = document.createElement( "div" );
8436
+ element.className = "dial " + ( options.className ? options.className : "" );
8437
+ element.style.width = element.style.height = options.size || "100%";
8438
+ element.style.minWidth = element.style.minHeight = "50px";
8439
+
8440
+ element.bgcolor = options.bgColor || LX.getThemeColor( "global-dark-background" );
8441
+ element.pointscolor = options.pointsColor || LX.getThemeColor( "global-selected-light" );
8442
+ element.linecolor = options.lineColor || "#555";
8443
+ element.value = value || [];
8444
+ element.xrange = options.xrange || [ 0, 1 ]; // min, max
8445
+ element.yrange = options.yrange || [ 0, 1 ]; // min, max
8446
+ element.defaulty = options.defaulty != null ? options.defaulty : 0.0;
8447
+ element.no_overlap = options.noOverlap || false;
8448
+ element.show_samples = options.showSamples || 0;
8449
+ element.allow_add_values = options.allowAddValues ?? true;
8450
+ element.draggable_x = options.draggableX ?? true;
8451
+ element.draggable_y = options.draggableY ?? true;
8452
+ element.smooth = (options.smooth && typeof( options.smooth ) == 'number' ? options.smooth : 0.3) || false;
8453
+ element.move_out = options.moveOutAction ?? LX.CURVE_MOVEOUT_DELETE;
8454
+
8455
+ this.element = element;
8456
+
8457
+ let canvas = document.createElement( "canvas" );
8458
+ canvas.width = canvas.height = options.size || 200;
8459
+ element.appendChild( canvas );
8460
+ this.canvas = canvas;
8461
+
8462
+ element.addEventListener( "mousedown", onmousedown );
8463
+
8464
+ element.getValueAt = function( x ) {
8465
+
8466
+ if( x < element.xrange[ 0 ] || x > element.xrange[ 1 ] )
8467
+ {
8468
+ return element.defaulty;
8469
+ }
8470
+
8471
+ var last = [ element.xrange[ 0 ], element.defaulty ];
8472
+ var f = 0;
8473
+ for( var i = 0; i < element.value.length; i += 1 )
8474
+ {
8475
+ var v = element.value[ i ];
8476
+ if( x == v[ 0 ] ) return v[ 1 ];
8477
+ if( x < v[ 0 ] )
8478
+ {
8479
+ f = ( x - last[ 0 ] ) / (v[ 0 ] - last[ 0 ]);
8480
+ return last[ 1 ] * ( 1 - f ) + v[ 1 ] * f;
8481
+ }
8482
+
8483
+ last = v;
8484
+ }
8485
+
8486
+ v = [ element.xrange[ 1 ], element.defaulty ];
8487
+ f = (x - last[ 0 ]) / (v[ 0 ] - last[ 0 ]);
8488
+ return last[ 1 ] * ( 1 - f ) + v[ 1 ] * f;
8489
+ }
8490
+
8491
+ element.resample = function( samples ) {
8492
+
8493
+ var r = [];
8494
+ var dx = (element.xrange[1] - element.xrange[ 0 ]) / samples;
8495
+ for(var i = element.xrange[0]; i <= element.xrange[1]; i += dx)
8496
+ {
8497
+ r.push( element.getValueAt(i) );
8498
+ }
8499
+ return r;
8500
+ }
8501
+
8502
+ element.addValue = function(v) {
8503
+
8504
+ for(var i = 0; i < element.value; i++) {
8505
+ var value = element.value[i];
8506
+ if(value[0] < v[0]) continue;
8507
+ element.value.splice(i,0,v);
8508
+ redraw();
8509
+ return;
8510
+ }
8511
+
8512
+ element.value.push(v);
8513
+ redraw();
8514
+ }
8515
+
8516
+ //value to canvas
8517
+ function convert(v, r) {
8518
+
8519
+ Math.pow(v[0],2)
8520
+ return [ canvas.width * ( v[0] - element.xrange[0])/ (element.xrange[1]),
8521
+ canvas.height * (v[1] - element.yrange[0])/ (element.yrange[1])];
8522
+ }
8523
+
8524
+ //canvas to value
8525
+ function unconvert(v) {
8526
+ return [(v[0] * element.xrange[1] / canvas.width + element.xrange[0]),
8527
+ (v[1] * element.yrange[1] / canvas.height + element.yrange[0])];
8528
+ }
8529
+
8530
+ var selected = -1;
8531
+
8532
+ element.redraw = function( o = {} ) {
8533
+
8534
+ if( o.value ) element.value = o.value;
8535
+ if( o.xrange ) element.xrange = o.xrange;
8536
+ if( o.yrange ) element.yrange = o.yrange;
8537
+ if( o.smooth ) element.smooth = o.smooth;
8538
+ var rect = canvas.parentElement.getBoundingClientRect();
8539
+ if( canvas.parentElement.parentElement ) rect = canvas.parentElement.parentElement.getBoundingClientRect();
8540
+ if( rect && canvas.width != rect.width && rect.width && rect.width < 1000 )
8541
+ {
8542
+ canvas.width = rect.width;
8543
+ }
8544
+
8545
+ var ctx = canvas.getContext( "2d" );
8546
+ ctx.setTransform( 1, 0, 0, 1, 0, 0 );
8547
+ ctx.translate( 0, canvas.height );
8548
+ ctx.scale( 1, -1 );
8549
+
8550
+ ctx.fillStyle = element.bgcolor;
8551
+ ctx.fillRect(0,0,canvas.width,canvas.height);
8552
+
8553
+ ctx.strokeStyle = element.linecolor;
8554
+ ctx.beginPath();
8555
+
8556
+ //draw line
8557
+ var pos = convert([ element.xrange[ 0 ],element.defaulty ]);
8558
+ ctx.moveTo( pos[ 0 ], pos[ 1 ] );
8559
+ let values = [pos[ 0 ], pos[ 1 ]];
8560
+
8561
+ for(var i in element.value) {
8562
+ var value = element.value[i];
8563
+ pos = convert(value);
8564
+ values.push(pos[ 0 ]);
8565
+ values.push(pos[ 1 ]);
8566
+
8567
+ }
8568
+
8569
+ pos = convert([ element.xrange[ 1 ], element.defaulty ]);
8570
+ values.push(pos[ 0 ]);
8571
+ values.push(pos[ 1 ]);
8572
+
8573
+ // Draw points
8574
+ const center = [0,0];
8575
+ pos = convert(center)
8576
+ ctx.fillStyle = "gray";
8577
+ ctx.beginPath();
8578
+ ctx.arc( pos[ 0 ], pos[ 1 ], 3, 0, Math.PI * 2);
8579
+ ctx.fill();
8580
+
8581
+ for( var i = 0; i < element.value.length; i += 1 ) {
8582
+ var value = element.value[ i ];
8583
+ pos = convert( value );
8584
+ if( selected == i )
8585
+ ctx.fillStyle = "white";
8586
+ else
8587
+ ctx.fillStyle = element.pointscolor;
8588
+ ctx.beginPath();
8589
+ ctx.arc( pos[ 0 ], pos[ 1 ], selected == i ? 4 : 3, 0, Math.PI * 2);
8590
+ ctx.fill();
8591
+ }
8592
+
8593
+ if(element.show_samples) {
8594
+ var samples = element.resample(element.show_samples);
8595
+ ctx.fillStyle = "#888";
8596
+ for(var i = 0; i < samples.length; i += 1)
8597
+ {
8598
+ var value = [ i * ((element.xrange[ 1 ] - element.xrange[ 0 ]) / element.show_samples) + element.xrange[ 0 ], samples[ i ] ];
8599
+ pos = convert(value);
8600
+ ctx.beginPath();
8601
+ ctx.arc( pos[ 0 ], pos[ 1 ], 2, 0, Math.PI * 2);
8602
+ ctx.fill();
8603
+ }
8604
+ }
8605
+ }
8606
+
8607
+ var last_mouse = [ 0, 0 ];
8608
+
8609
+ function onmousedown( e ) {
8610
+ document.addEventListener( "mousemove", onmousemove );
8611
+ document.addEventListener( "mouseup", onmouseup );
8612
+
8613
+ var rect = canvas.getBoundingClientRect();
8614
+ var mousex = e.clientX - rect.left;
8615
+ var mousey = e.clientY - rect.top;
8616
+
8617
+ selected = computeSelected( mousex, canvas.height - mousey );
8618
+
8619
+ if( e.button == LX.MOUSE_LEFT_CLICK && selected == -1 && element.allow_add_values ) {
8620
+ var v = unconvert([ mousex, canvas.height - mousey ]);
8621
+ element.value.push( v );
8622
+ sortValues();
8623
+ selected = element.value.indexOf( v );
8624
+ }
8625
+
8626
+ last_mouse = [ mousex, mousey ];
8627
+ element.redraw();
8628
+ e.preventDefault();
8629
+ e.stopPropagation();
8630
+ }
8631
+
8632
+ function onmousemove( e ) {
8633
+
8634
+ var rect = canvas.getBoundingClientRect();
8635
+ var mousex = e.clientX - rect.left;
8636
+ var mousey = e.clientY - rect.top;
8637
+
8638
+ if( mousex < 0 ) mousex = 0;
8639
+ else if( mousex > canvas.width ) mousex = canvas.width;
8640
+ if( mousey < 0 ) mousey = 0;
8641
+ else if( mousey > canvas.height ) mousey = canvas.height;
8642
+
8643
+ // Dragging to remove
8644
+ const currentMouseDiff = [ e.clientX - rect.left, e.clientY - rect.top ];
8645
+ if( selected != -1 && distance( currentMouseDiff, [ mousex, mousey ] ) > canvas.height * 0.5 )
8646
+ {
8647
+ if( element.move_out == LX.CURVE_MOVEOUT_DELETE)
8648
+ {
8649
+ element.value.splice( selected, 1 );
8650
+ }
8651
+ else
8652
+ {
8653
+ const d = [ currentMouseDiff[ 0 ] - mousex, currentMouseDiff[ 1 ] - mousey ];
8654
+ let value = element.value[ selected ];
8655
+ value[ 0 ] = ( d[ 0 ] == 0.0 ) ? value[ 0 ] : ( d[ 0 ] < 0.0 ? element.xrange[ 0 ] : element.xrange[ 1 ] );
8656
+ value[ 1 ] = ( d[ 1 ] == 0.0 ) ? value[ 1 ] : ( d[ 1 ] < 0.0 ? element.yrange[ 1 ] : element.yrange[ 0 ] );
8657
+ }
8658
+
8659
+ onmouseup( e );
8660
+ return;
8661
+ }
8662
+
8663
+ var dx = element.draggable_x ? last_mouse[ 0 ] - mousex : 0;
8664
+ var dy = element.draggable_y ? last_mouse[ 1 ] - mousey : 0;
8665
+ var delta = unconvert([ -dx, dy ]);
8666
+
8667
+ if( selected != -1 ) {
8668
+ var minx = element.xrange[ 0 ];
8669
+ var maxx = element.xrange[ 1 ];
8670
+
8671
+ if( element.no_overlap )
8672
+ {
8673
+ if( selected > 0) minx = element.value[ selected - 1 ][ 0 ];
8674
+ if( selected < ( element.value.length - 1 ) ) maxx = element.value[ selected + 1 ][ 0 ];
8675
+ }
8676
+
8677
+ var v = element.value[selected];
8678
+ v[ 0 ] += delta[ 0 ];
8679
+ v[ 1 ] += delta[ 1 ];
8680
+ if(v[ 0 ] < minx) v[ 0 ] = minx;
8681
+ else if(v[ 0 ] > maxx) v[ 0 ] = maxx;
8682
+ if(v[ 1 ] < element.yrange[ 0 ]) v[ 1 ] = element.yrange[ 0 ];
8683
+ else if(v[ 1 ] > element.yrange[ 1 ]) v[ 1 ] = element.yrange[ 1 ];
8684
+ }
8685
+
8686
+ sortValues();
8687
+ element.redraw();
8688
+ last_mouse[ 0 ] = mousex;
8689
+ last_mouse[ 1 ] = mousey;
8690
+ onchange( e );
8691
+
8692
+ e.preventDefault();
8693
+ e.stopPropagation();
8694
+ }
8695
+
8696
+ function onmouseup( e ) {
8697
+ selected = -1;
8698
+ element.redraw();
8699
+ document.removeEventListener("mousemove", onmousemove);
8700
+ document.removeEventListener("mouseup", onmouseup);
8701
+ onchange(e);
8702
+ e.preventDefault();
8703
+ e.stopPropagation();
8704
+ }
8705
+
8706
+ function onchange( e ) {
8707
+ if( options.callback )
8708
+ options.callback.call( element, element.value, e );
8709
+ }
8710
+
8711
+ function distance(a,b) { return Math.sqrt( Math.pow(b[0]-a[0],2) + Math.pow(b[1]-a[1],2) ); };
8712
+
8713
+ function computeSelected( x, y ) {
8714
+
8715
+ var minDistance = 100000;
8716
+ var maxDistance = 8; //pixels
8717
+ var selected = -1;
8718
+ for( var i = 0; i < element.value.length; i++ )
8719
+ {
8720
+ var value = element.value[ i ];
8721
+ var pos = convert( value );
8722
+ var dist = distance( [ x,y ], pos );
8723
+ if( dist < minDistance && dist < maxDistance )
8724
+ {
8725
+ minDistance = dist;
8726
+ selected = i;
8727
+ }
8728
+ }
8729
+ return selected;
8730
+ }
8731
+
8732
+ function sortValues() {
8733
+ var v = null;
8734
+ if( selected != -1 )
8735
+ {
8736
+ v = element.value[ selected ];
8737
+ }
8738
+ element.value.sort(function( a,b ) { return a[ 0 ] - b[ 0 ]; });
8739
+ if( v )
8740
+ {
8741
+ selected = element.value.indexOf( v );
8742
+ }
8743
+ }
8744
+
8745
+ element.redraw();
8746
+ return this;
8747
+ }
8748
+
8749
+ redraw( options = {} ) {
8750
+ this.element.redraw( options );
8751
+ }
8752
+ }
8753
+
8754
+ LX.Dial = Dial;
8755
+
7826
8756
  class AssetViewEvent {
7827
8757
 
7828
8758
  static NONE = 0;
@@ -7898,6 +8828,7 @@ class AssetView {
7898
8828
  this.skipPreview = options.skipPreview ?? false;
7899
8829
  this.useNativeTitle = options.useNativeTitle ?? false;
7900
8830
  this.onlyFolders = options.onlyFolders ?? true;
8831
+ this.allowMultipleSelection = options.allowMultipleSelection ?? false;
7901
8832
  this.previewActions = options.previewActions ?? [];
7902
8833
  this.contextMenu = options.contextMenu ?? [];
7903
8834
  this.onRefreshContent = options.onRefreshContent;
@@ -8296,15 +9227,13 @@ class AssetView {
8296
9227
  itemEl.title = type + ": " + item.id;
8297
9228
  }
8298
9229
 
8299
- if( item.selected != undefined )
9230
+ if( that.allowMultipleSelection )
8300
9231
  {
8301
- let span = document.createElement('span');
8302
- span.className = "lexcheckbox";
8303
- let checkbox_input = document.createElement('input');
8304
- checkbox_input.type = "checkbox";
8305
- checkbox_input.className = "checkbox";
8306
- checkbox_input.checked = item.selected;
8307
- checkbox_input.addEventListener('change', ( e, v ) => {
9232
+ let checkbox = document.createElement( 'input' );
9233
+ checkbox.type = "checkbox";
9234
+ checkbox.className = "lexcheckbox";
9235
+ checkbox.checked = item.selected;
9236
+ checkbox.addEventListener('change', ( e, v ) => {
8308
9237
  item.selected = !item.selected;
8309
9238
  if( that.onevent )
8310
9239
  {
@@ -8314,10 +9243,9 @@ class AssetView {
8314
9243
  }
8315
9244
  e.stopPropagation();
8316
9245
  e.stopImmediatePropagation();
8317
- })
8318
- span.appendChild(checkbox_input);
8319
- itemEl.appendChild(span);
9246
+ });
8320
9247
 
9248
+ itemEl.appendChild( checkbox );
8321
9249
  }
8322
9250
 
8323
9251
  let title = document.createElement('span');
@@ -8383,6 +9311,7 @@ class AssetView {
8383
9311
  }
8384
9312
 
8385
9313
  this.classList.add('selected');
9314
+ that.selectedItem = item;
8386
9315
 
8387
9316
  if( !that.skipPreview )
8388
9317
  {
@@ -8694,7 +9623,7 @@ Object.assign(LX, {
8694
9623
  //request.mimeType = "text/plain; charset=x-user-defined";
8695
9624
  dataType = "arraybuffer";
8696
9625
  request.mimeType = "application/octet-stream";
8697
- }
9626
+ }
8698
9627
 
8699
9628
  //regular case, use AJAX call
8700
9629
  var xhr = new XMLHttpRequest();