lexgui 0.1.44 → 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.44",
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' );
@@ -75,7 +84,9 @@ function getThemeColor( colorName )
75
84
 
76
85
  if( value.includes( "light-dark" ) && window.matchMedia )
77
86
  {
78
- if( window.matchMedia( "(prefers-color-scheme: light)" ).matches )
87
+ const currentScheme = r.getPropertyValue( "color-scheme" );
88
+
89
+ if( ( window.matchMedia( "(prefers-color-scheme: light)" ).matches ) || ( currentScheme == "light" ) )
79
90
  {
80
91
  return value.substring( value.indexOf( '(' ) + 1, value.indexOf( ',' ) ).replace( /\s/g, '' );
81
92
  }
@@ -142,6 +153,23 @@ function simple_guidGenerator() {
142
153
 
143
154
  LX.guidGenerator = simple_guidGenerator;
144
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
+
145
173
  // Timer that works everywhere (from litegraph.js)
146
174
  if (typeof performance != "undefined") {
147
175
  LX.getTime = performance.now.bind(performance);
@@ -756,7 +784,7 @@ function prompt( text, title, callback, options = {} )
756
784
  if( callback ) callback.call( this, value );
757
785
  dialog.close();
758
786
  }
759
- }, { buttonClass: "accept" });
787
+ }, { buttonClass: "primary" });
760
788
 
761
789
  p.addButton(null, "Cancel", () => {if(options.on_cancel) options.on_cancel(); dialog.close();} );
762
790
 
@@ -2205,18 +2233,18 @@ class Menubar {
2205
2233
  entry.className = "lexmenuentry";
2206
2234
  entry.id = pKey;
2207
2235
  entry.innerHTML = "<span>" + key + "</span>";
2208
- if(options.position == "left") {
2209
- this.root.prepend( entry );
2210
- }
2211
- else {
2212
- if(options.position == "right")
2213
- entry.right = true;
2214
- if(this.root.lastChild && this.root.lastChild.right) {
2215
- this.root.lastChild.before( entry );
2216
- }
2217
- else {
2218
- this.root.appendChild( entry );
2219
- }
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
+ }
2220
2248
  }
2221
2249
 
2222
2250
  const create_submenu = function( o, k, c, d ) {
@@ -2437,16 +2465,16 @@ class Menubar {
2437
2465
  button.style.maxHeight = "calc(100% - 10px)";
2438
2466
  button.style.alignItems = "center";
2439
2467
 
2440
- if(options.float == "right")
2441
- button.right = true;
2442
- if(this.root.lastChild && this.root.lastChild.right) {
2443
- 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 );
2444
2472
  }
2445
2473
  else if(options.float == "left") {
2446
2474
  this.root.prepend(button);
2447
- }
2448
- else {
2449
- this.root.appendChild( button );
2475
+ }
2476
+ else {
2477
+ this.root.appendChild( button );
2450
2478
  }
2451
2479
 
2452
2480
  const _b = button.querySelector('a');
@@ -2478,16 +2506,16 @@ class Menubar {
2478
2506
  button.style.padding = "5px";
2479
2507
  button.style.alignItems = "center";
2480
2508
 
2481
- if(options.float == "right")
2482
- button.right = true;
2483
- if(this.root.lastChild && this.root.lastChild.right) {
2484
- this.root.lastChild.before( button );
2485
- }
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
+ }
2486
2514
  else if(options.float == "left") {
2487
2515
  this.root.prepend(button);
2488
2516
  }
2489
- else {
2490
- this.root.appendChild( button );
2517
+ else {
2518
+ this.root.appendChild( button );
2491
2519
  }
2492
2520
 
2493
2521
  const _b = button.querySelector('a');
@@ -2507,44 +2535,75 @@ class Menubar {
2507
2535
 
2508
2536
  addButtons( buttons, options = {} ) {
2509
2537
 
2510
- if(!buttons)
2511
- throw("No buttons to add!");
2538
+ if( !buttons )
2539
+ {
2540
+ throw( "No buttons to add!" );
2541
+ }
2512
2542
 
2513
- if(!this.buttonContainer)
2543
+ if( !this.buttonContainer )
2514
2544
  {
2515
- this.buttonContainer = document.createElement('div');
2545
+ this.buttonContainer = document.createElement( "div" );
2516
2546
  this.buttonContainer.className = "lexmenubuttons";
2517
- this.buttonContainer.classList.add(options.float ?? 'center');
2518
- if(options.position == "right")
2519
- this.buttonContainer.right = true;
2520
- if(this.root.lastChild && this.root.lastChild.right) {
2521
- this.root.lastChild.before( this.buttonContainer );
2522
- }
2523
- else {
2524
- 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 );
2525
2561
  }
2526
2562
  }
2527
2563
 
2528
2564
  for( let i = 0; i < buttons.length; ++i )
2529
2565
  {
2530
- let data = buttons[i];
2531
- let button = document.createElement('div');
2566
+ let data = buttons[ i ];
2567
+ let button = document.createElement( "label" );
2532
2568
  const title = data.title;
2533
2569
  let disabled = data.disabled ?? false;
2534
2570
  button.className = "lexmenubutton" + (disabled ? " disabled" : "");
2535
2571
  button.title = title ?? "";
2536
- button.innerHTML = "<a class='" + data.icon + " lexicon'></a>";
2537
2572
  this.buttonContainer.appendChild( button );
2538
2573
 
2539
- const _b = button.querySelector('a');
2540
- _b.addEventListener("click", (e) => {
2541
- disabled = e.target.parentElement.classList.contains("disabled");
2542
- if(data.callback && !disabled)
2543
- 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
+ }
2544
2601
  });
2545
2602
 
2546
- if(title)
2603
+ if( title )
2604
+ {
2547
2605
  this.buttons[ title ] = button;
2606
+ }
2548
2607
  }
2549
2608
  }
2550
2609
  };
@@ -2698,6 +2757,7 @@ class Widget {
2698
2757
  static PAD = 26;
2699
2758
  static FORM = 27;
2700
2759
  static DIAL = 28;
2760
+ static COUNTER = 29;
2701
2761
 
2702
2762
  static NO_CONTEXT_TYPES = [
2703
2763
  Widget.BUTTON,
@@ -2787,6 +2847,7 @@ class Widget {
2787
2847
  case Widget.PAD: return "Pad";
2788
2848
  case Widget.FORM: return "Form";
2789
2849
  case Widget.DIAL: return "Dial";
2850
+ case Widget.COUNTER: return "Counter";
2790
2851
  case Widget.CUSTOM: return this.customName;
2791
2852
  }
2792
2853
 
@@ -4017,7 +4078,9 @@ class Panel {
4017
4078
  * @param {Function} callback Callback function on change
4018
4079
  * @param {*} options:
4019
4080
  * disabled: Make the widget disabled [false]
4081
+ * required: Make the input required
4020
4082
  * placeholder: Add input placeholder
4083
+ * pattern: Regular expression that value must match
4021
4084
  * trigger: Choose onchange trigger (default, input) [default]
4022
4085
  * inputWidth: Width of the text input
4023
4086
  * skipReset: Don't add the reset value button when value changes
@@ -4032,11 +4095,18 @@ class Panel {
4032
4095
  widget.onGetValue = () => {
4033
4096
  return wValue.value;
4034
4097
  };
4098
+
4035
4099
  widget.onSetValue = ( newValue, skipCallback ) => {
4036
4100
  this.disabled ? wValue.innerText = newValue : wValue.value = newValue;
4037
4101
  Panel._dispatch_event( wValue, "focusout", skipCallback );
4038
4102
  };
4039
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
+
4040
4110
  let element = widget.domEl;
4041
4111
 
4042
4112
  // Add reset functionality
@@ -4071,14 +4141,33 @@ class Panel {
4071
4141
  wValue.style.width = "100%";
4072
4142
  wValue.style.textAlign = options.float ?? "";
4073
4143
 
4074
- if( options.placeholder )
4075
- 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
+ }
4076
4155
 
4077
4156
  var resolve = ( function( val, event ) {
4157
+
4158
+ if( !widget.valid() )
4159
+ {
4160
+ return;
4161
+ }
4162
+
4078
4163
  const skipCallback = event.detail;
4079
4164
  let btn = element.querySelector( ".lexwidgetname .lexicon" );
4080
4165
  if( btn ) btn.style.display = ( val != wValue.iValue ? "block" : "none" );
4081
- 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
+
4082
4171
  }).bind( this );
4083
4172
 
4084
4173
  const trigger = options.trigger ?? 'default';
@@ -4290,18 +4379,13 @@ class Panel {
4290
4379
 
4291
4380
  var wValue = document.createElement( 'button' );
4292
4381
  wValue.title = options.title ?? "";
4293
- wValue.className = "lexbutton";
4382
+ wValue.className = "lexbutton " + ( options.buttonClass ?? "" );
4294
4383
 
4295
4384
  if( options.selected )
4296
4385
  {
4297
4386
  wValue.classList.add( "selected" );
4298
4387
  }
4299
4388
 
4300
- if( options.buttonClass )
4301
- {
4302
- wValue.classList.add( options.buttonClass );
4303
- }
4304
-
4305
4389
  wValue.innerHTML =
4306
4390
  (options.icon ? "<a class='" + options.icon + "'></a>" :
4307
4391
  ( options.img ? "<img src='" + options.img + "'>" : "<span>" + (value || "") + "</span>" ));
@@ -4540,7 +4624,7 @@ class Panel {
4540
4624
 
4541
4625
  this.addLabel( entry, { textClass: "formlabel" } );
4542
4626
 
4543
- this.addText( null, entryData.constructor == Object ? entryData.value : entryData, ( value ) => {
4627
+ entryData.textWidget = this.addText( null, entryData.constructor == Object ? entryData.value : entryData, ( value ) => {
4544
4628
  container.formData[ entry ] = value;
4545
4629
  }, entryData );
4546
4630
 
@@ -4550,11 +4634,22 @@ class Panel {
4550
4634
  this.addBlank( );
4551
4635
 
4552
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
+
4553
4648
  if( callback )
4554
4649
  {
4555
4650
  callback( container.formData, event );
4556
4651
  }
4557
- }, { buttonClass: "accept", width: "calc(100% - 10px)" } );
4652
+ }, { buttonClass: "primary", width: "calc(100% - 10px)" } );
4558
4653
 
4559
4654
  this.clearQueue();
4560
4655
 
@@ -4709,13 +4804,35 @@ class Panel {
4709
4804
  delete list.unfocus_event;
4710
4805
  return;
4711
4806
  }
4712
- const topPosition = selectedOption.getBoundingClientRect().y;
4713
- 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
+
4714
4832
  list.style.width = (event.currentTarget.clientWidth) + 'px';
4715
4833
  list.style.minWidth = (_getMaxListWidth()) + 'px';
4716
- list.toggleAttribute('hidden');
4717
4834
  list.focus();
4718
- }, { buttonClass: 'array', skipInlineCount: true });
4835
+ }, { buttonClass: "array", skipInlineCount: true });
4719
4836
 
4720
4837
  this.clearQueue();
4721
4838
 
@@ -5361,40 +5478,45 @@ class Panel {
5361
5478
 
5362
5479
  // Show tags
5363
5480
 
5364
- let tags_container = document.createElement('div');
5365
- tags_container.className = "lextags";
5366
- 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 + ")";
5367
5484
 
5368
5485
  const create_tags = () => {
5369
5486
 
5370
- tags_container.innerHTML = "";
5487
+ tagsContainer.innerHTML = "";
5371
5488
 
5372
5489
  for( let i = 0; i < value.length; ++i )
5373
5490
  {
5374
- let tag_name = value[i];
5375
- let tag = document.createElement('span');
5491
+ const tagName = value[i];
5492
+ const tag = document.createElement('span');
5376
5493
  tag.className = "lextag";
5377
- tag.innerHTML = tag_name;
5494
+ tag.innerHTML = tagName;
5378
5495
 
5379
- tag.addEventListener('click', function( e ) {
5380
- this.remove();
5381
- 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 );
5382
5503
  let btn = element.querySelector( ".lexwidgetname .lexicon" );
5383
5504
  if( btn ) btn.style.display = ( value != defaultValue ? "block" : "none" );
5384
5505
  that._trigger( new IEvent( name, value, e ), callback );
5385
- });
5506
+ } );
5386
5507
 
5387
- tags_container.appendChild( tag );
5508
+ tagsContainer.appendChild( tag );
5388
5509
  }
5389
5510
 
5390
- let tag_input = document.createElement( 'input' );
5391
- tag_input.value = "";
5392
- tag_input.placeholder = "Tag...";
5393
- tags_container.insertChildAtIndex( tag_input, 0 );
5511
+ let tagInput = document.createElement( 'input' );
5512
+ tagInput.value = "";
5513
+ tagInput.placeholder = "Add tag...";
5514
+ tagsContainer.appendChild( tagInput );
5394
5515
 
5395
- tag_input.onkeydown = function( e ) {
5516
+ tagInput.onkeydown = function( e ) {
5396
5517
  const val = this.value.replace(/\s/g, '');
5397
- if( e.key == ' ') {
5518
+ if( e.key == ' ' || e.key == 'Enter' )
5519
+ {
5398
5520
  e.preventDefault();
5399
5521
  if( !val.length || value.indexOf( val ) > -1 )
5400
5522
  return;
@@ -5406,18 +5528,19 @@ class Panel {
5406
5528
  }
5407
5529
  };
5408
5530
 
5409
- tag_input.focus();
5531
+ tagInput.focus();
5410
5532
  }
5411
5533
 
5412
5534
  create_tags();
5413
5535
 
5414
5536
  // Remove branch padding and margins
5415
- if(!widget.name) {
5537
+ if( !widget.name )
5538
+ {
5416
5539
  element.className += " noname";
5417
- tags_container.style.width = "100%";
5540
+ tagsContainer.style.width = "100%";
5418
5541
  }
5419
5542
 
5420
- element.appendChild(tags_container);
5543
+ element.appendChild( tagsContainer );
5421
5544
 
5422
5545
  return widget;
5423
5546
  }
@@ -6604,7 +6727,7 @@ class Panel {
6604
6727
  progress.classList.add( "editable" );
6605
6728
  progress.addEventListener( "mousedown", inner_mousedown );
6606
6729
 
6607
- var that = this;
6730
+ const that = this;
6608
6731
 
6609
6732
  function inner_mousedown( e )
6610
6733
  {
@@ -6612,24 +6735,28 @@ class Panel {
6612
6735
  doc.addEventListener( 'mousemove', inner_mousemove );
6613
6736
  doc.addEventListener( 'mouseup', inner_mouseup );
6614
6737
  document.body.classList.add( 'noevents' );
6738
+ progress.classList.add( "grabbing" );
6615
6739
  e.stopImmediatePropagation();
6616
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 );
6617
6745
  }
6618
6746
 
6619
6747
  function inner_mousemove( e )
6620
6748
  {
6621
- let dt = -e.movementX;
6749
+ let dt = e.movementX;
6622
6750
 
6623
6751
  if ( dt != 0 )
6624
6752
  {
6625
- let v = that.getValue( name, value );
6626
- v += e.movementX / 100;
6627
- v = round( v );
6628
- 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 );
6629
6756
 
6630
6757
  if( options.callback )
6631
6758
  {
6632
- options.callback( v, e );
6759
+ options.callback( newValue, e );
6633
6760
  }
6634
6761
  }
6635
6762
 
@@ -6643,6 +6770,7 @@ class Panel {
6643
6770
  doc.removeEventListener( 'mousemove', inner_mousemove );
6644
6771
  doc.removeEventListener( 'mouseup', inner_mouseup );
6645
6772
  document.body.classList.remove( 'noevents' );
6773
+ progress.classList.remove( "grabbing" );
6646
6774
  }
6647
6775
  }
6648
6776
 
@@ -6932,6 +7060,91 @@ class Panel {
6932
7060
 
6933
7061
  this.addSeparator();
6934
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
+ }
6935
7148
  }
6936
7149
 
6937
7150
  LX.Panel = Panel;
@@ -7167,6 +7380,104 @@ class Branch {
7167
7380
 
7168
7381
  LX.Branch = Branch;
7169
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
+
7170
7481
  /**
7171
7482
  * @class Dialog
7172
7483
  */
@@ -9312,7 +9623,7 @@ Object.assign(LX, {
9312
9623
  //request.mimeType = "text/plain; charset=x-user-defined";
9313
9624
  dataType = "arraybuffer";
9314
9625
  request.mimeType = "application/octet-stream";
9315
- }
9626
+ }
9316
9627
 
9317
9628
  //regular case, use AJAX call
9318
9629
  var xhr = new XMLHttpRequest();