lexgui 0.5.7 → 0.5.9

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.
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  var LX = {
9
- version: "0.5.7",
9
+ version: "0.5.9",
10
10
  ready: false,
11
11
  components: [], // Specific pre-build components
12
12
  signals: {}, // Events and triggers
@@ -128,6 +128,56 @@ function stripHTML( html )
128
128
 
129
129
  LX.stripHTML = stripHTML;
130
130
 
131
+ /**
132
+ * @method parsePixelSize
133
+ * @description Parses any css size and returns a number of pixels
134
+ * @param {Number|String} size
135
+ * @param {Number} total
136
+ */
137
+ const parsePixelSize = ( size, total ) => {
138
+
139
+ if( size.constructor === Number ) { return size; } // Assuming pixels..
140
+
141
+ if( size.constructor === String )
142
+ {
143
+ const value = parseFloat( size );
144
+
145
+ if( size.endsWith( "px" ) ) { return value; } // String pixels
146
+ if( size.endsWith( '%' ) ) { return ( value / 100 ) * total; } // Percentage
147
+ if( size.endsWith( "rem" ) || size.endsWith( "em" ) ) { const rootFontSize = 16; /*parseFloat(getComputedStyle(document.documentElement).fontSize);*/ return value * rootFontSize; } // rem unit: assume 16px = 1rem
148
+ if( size.endsWith( "vw" ) ) { return ( value / 100 ) * window.innerWidth; } // wViewport units
149
+ if( size.endsWith( "vh" ) ) { return ( value / 100 ) * window.innerHeight; } // hViewport units
150
+
151
+ // Any CSS calc expression (e.g., "calc(30% - 4px)")
152
+ if( size.startsWith( "calc(" ) )
153
+ {
154
+ const expr = size.slice( 5, -1 );
155
+ const parts = expr.split( /([+\-])/ ); // ["30% ", "-", "4px"]
156
+ let result = 0;
157
+ let op = "+";
158
+ for( let part of parts )
159
+ {
160
+ part = part.trim();
161
+ if( part === "+" || part === "-" )
162
+ {
163
+ op = part;
164
+ }
165
+ else
166
+ {
167
+ let value = parsePixelSize( part, total );
168
+ result = ( op === "+" ) ? result + value : result - value;
169
+ }
170
+ }
171
+
172
+ return result;
173
+ }
174
+ }
175
+
176
+ throw( "Bad size format!" );
177
+ }
178
+
179
+ LX.parsePixelSize = parsePixelSize;
180
+
131
181
  /**
132
182
  * @method deepCopy
133
183
  * @description Create a deep copy with no references from an object
@@ -723,7 +773,7 @@ LX.makeKbd = makeKbd;
723
773
  * @description Gets an SVG element using one of LX.ICONS
724
774
  * @param {String} iconName
725
775
  * @param {Object} options
726
- * iconTitle
776
+ * title
727
777
  * extraClass
728
778
  * svgClass
729
779
  */
@@ -732,7 +782,7 @@ function makeIcon( iconName, options = { } )
732
782
  let data = LX.ICONS[ iconName ];
733
783
  console.assert( data, `No icon named _${ iconName }_` );
734
784
 
735
- const iconTitle = options.iconTitle;
785
+ const iconTitle = options.title;
736
786
  const iconClass = options.iconClass;
737
787
  const svgClass = options.svgClass;
738
788
 
@@ -1591,7 +1641,7 @@ function toast( title, description, options = {} )
1591
1641
  if( options.action )
1592
1642
  {
1593
1643
  const panel = new Panel();
1594
- panel.addButton(null, options.action.name ?? "Accept", options.action.callback.bind( this, toast ), { width: "auto", maxWidth: "150px", className: "right", buttonClass: "outline" });
1644
+ panel.addButton(null, options.action.name ?? "Accept", options.action.callback.bind( this, toast ), { width: "auto", maxWidth: "150px", className: "right", buttonClass: "border" });
1595
1645
  toast.appendChild( panel.root.childNodes[ 0 ] );
1596
1646
  }
1597
1647
 
@@ -1891,6 +1941,158 @@ LX.addSignal = addSignal;
1891
1941
  * DOM Elements
1892
1942
  */
1893
1943
 
1944
+ /**
1945
+ * @class Popover
1946
+ */
1947
+
1948
+ class Popover {
1949
+
1950
+ static activeElement = false;
1951
+
1952
+ constructor( trigger, content, options = {} ) {
1953
+
1954
+ console.assert( trigger, "Popover needs a DOM element as trigger!" );
1955
+
1956
+ if( Popover.activeElement )
1957
+ {
1958
+ Popover.activeElement.destroy();
1959
+ return;
1960
+ }
1961
+
1962
+ this._trigger = trigger;
1963
+ trigger.classList.add( "triggered" );
1964
+ trigger.active = this;
1965
+
1966
+ this._windowPadding = 4;
1967
+ this.side = options.side ?? "bottom";
1968
+ this.align = options.align ?? "center";
1969
+ this.avoidCollisions = options.avoidCollisions ?? true;
1970
+
1971
+ this.root = document.createElement( "div" );
1972
+ this.root.dataset["side"] = this.side;
1973
+ this.root.tabIndex = "1";
1974
+ this.root.className = "lexpopover";
1975
+ LX.root.appendChild( this.root );
1976
+
1977
+ this.root.addEventListener( "keydown", (e) => {
1978
+ if( e.key == "Escape" )
1979
+ {
1980
+ e.preventDefault();
1981
+ e.stopPropagation();
1982
+ this.destroy();
1983
+ }
1984
+ } )
1985
+
1986
+ if( content )
1987
+ {
1988
+ content = [].concat( content );
1989
+ content.forEach( e => {
1990
+ const domNode = e.root ?? e;
1991
+ this.root.appendChild( domNode );
1992
+ if( e.onPopover )
1993
+ {
1994
+ e.onPopover();
1995
+ }
1996
+ } );
1997
+ }
1998
+
1999
+ Popover.activeElement = this;
2000
+
2001
+ doAsync( () => {
2002
+ this._adjustPosition();
2003
+
2004
+ this.root.focus();
2005
+
2006
+ this._onClick = e => {
2007
+ if( e.target && ( this.root.contains( e.target ) || e.target == this._trigger ) )
2008
+ {
2009
+ return;
2010
+ }
2011
+ this.destroy();
2012
+ };
2013
+
2014
+ document.body.addEventListener( "mousedown", this._onClick, true );
2015
+ document.body.addEventListener( "focusin", this._onClick, true );
2016
+ }, 10 );
2017
+ }
2018
+
2019
+ destroy() {
2020
+
2021
+ this._trigger.classList.remove( "triggered" );
2022
+
2023
+ delete this._trigger.active;
2024
+
2025
+ document.body.removeEventListener( "click", this._onClick );
2026
+
2027
+ this.root.remove();
2028
+
2029
+ Popover.activeElement = null;
2030
+ }
2031
+
2032
+ _adjustPosition() {
2033
+
2034
+ const position = [ 0, 0 ];
2035
+
2036
+ // Place menu using trigger position and user options
2037
+ {
2038
+ const rect = this._trigger.getBoundingClientRect();
2039
+
2040
+ let alignWidth = true;
2041
+
2042
+ switch( this.side )
2043
+ {
2044
+ case "left":
2045
+ position[ 0 ] += ( rect.x - this.root.offsetWidth );
2046
+ alignWidth = false;
2047
+ break;
2048
+ case "right":
2049
+ position[ 0 ] += ( rect.x + rect.width );
2050
+ alignWidth = false;
2051
+ break;
2052
+ case "top":
2053
+ position[ 1 ] += ( rect.y - this.root.offsetHeight );
2054
+ alignWidth = true;
2055
+ break;
2056
+ case "bottom":
2057
+ position[ 1 ] += ( rect.y + rect.height );
2058
+ alignWidth = true;
2059
+ break;
2060
+ default:
2061
+ break;
2062
+ }
2063
+
2064
+ switch( this.align )
2065
+ {
2066
+ case "start":
2067
+ if( alignWidth ) { position[ 0 ] += rect.x; }
2068
+ else { position[ 1 ] += rect.y; }
2069
+ break;
2070
+ case "center":
2071
+ if( alignWidth ) { position[ 0 ] += ( rect.x + rect.width * 0.5 ) - this.root.offsetWidth * 0.5; }
2072
+ else { position[ 1 ] += ( rect.y + rect.height * 0.5 ) - this.root.offsetHeight * 0.5; }
2073
+ break;
2074
+ case "end":
2075
+ if( alignWidth ) { position[ 0 ] += rect.x - this.root.offsetWidth + rect.width; }
2076
+ else { position[ 1 ] += rect.y - this.root.offsetHeight + rect.height; }
2077
+ break;
2078
+ default:
2079
+ break;
2080
+ }
2081
+ }
2082
+
2083
+ if( this.avoidCollisions )
2084
+ {
2085
+ position[ 0 ] = LX.clamp( position[ 0 ], 0, window.innerWidth - this.root.offsetWidth - this._windowPadding );
2086
+ position[ 1 ] = LX.clamp( position[ 1 ], 0, window.innerHeight - this.root.offsetHeight - this._windowPadding );
2087
+ }
2088
+
2089
+ this.root.style.left = `${ position[ 0 ] }px`;
2090
+ this.root.style.top = `${ position[ 1 ] }px`;
2091
+ }
2092
+ };
2093
+
2094
+ LX.Popover = Popover;
2095
+
1894
2096
  /**
1895
2097
  * @class DropdownMenu
1896
2098
  */
@@ -2188,14 +2390,8 @@ class ColorPicker {
2188
2390
 
2189
2391
  static currentPicker = false;
2190
2392
 
2191
- constructor( hexValue, trigger, options = {} ) {
2192
-
2193
- console.assert( trigger, "ColorPicker needs a DOM element as trigger!" );
2393
+ constructor( hexValue, options = {} ) {
2194
2394
 
2195
- this._windowPadding = 4;
2196
- this.side = options.side ?? "bottom";
2197
- this.align = options.align ?? "center";
2198
- this.avoidCollisions = options.avoidCollisions ?? true;
2199
2395
  this.colorModel = options.colorModel ?? "Hex";
2200
2396
  this.useAlpha = options.useAlpha ?? false;
2201
2397
  this.callback = options.onChange;
@@ -2205,32 +2401,8 @@ class ColorPicker {
2205
2401
  console.warn( "Define a callback in _options.onChange_ to allow getting new Color values!" );
2206
2402
  }
2207
2403
 
2208
- if( ColorPicker.currentPicker )
2209
- {
2210
- ColorPicker.currentPicker.destroy();
2211
- return;
2212
- }
2213
-
2214
- this._trigger = trigger;
2215
- trigger.classList.add( "triggered" );
2216
- trigger.picker = this;
2217
-
2218
2404
  this.root = document.createElement( "div" );
2219
- this.root.tabIndex = "1";
2220
2405
  this.root.className = "lexcolorpicker";
2221
- this.root.dataset["side"] = this.side;
2222
- LX.root.appendChild( this.root );
2223
-
2224
- this.root.addEventListener( "keydown", (e) => {
2225
- if( e.key == "Escape" )
2226
- {
2227
- e.preventDefault();
2228
- e.stopPropagation();
2229
- this.destroy();
2230
- }
2231
- } )
2232
-
2233
- ColorPicker.currentPicker = this;
2234
2406
 
2235
2407
  this.markerHalfSize = 8;
2236
2408
  this.markerSize = this.markerHalfSize * 2;
@@ -2249,7 +2421,7 @@ class ColorPicker {
2249
2421
  this.intSatMarker.style.backgroundColor = this.currentColor.hex;
2250
2422
  this.colorPickerBackground.appendChild( this.intSatMarker );
2251
2423
 
2252
- doAsync( this._svToPosition.bind( this, this.currentColor.hsv.s, this.currentColor.hsv.v ) );
2424
+ let pickerRect = null;
2253
2425
 
2254
2426
  let innerMouseDown = e => {
2255
2427
  var doc = this.root.ownerDocument;
@@ -2265,15 +2437,15 @@ class ColorPicker {
2265
2437
  this.intSatMarker.style.top = currentTop + "px";
2266
2438
  this._positionToSv( currentLeft, currentTop );
2267
2439
  this._updateColorValue();
2440
+
2441
+ pickerRect = this.colorPickerBackground.getBoundingClientRect();
2268
2442
  }
2269
2443
 
2270
2444
  let innerMouseMove = e => {
2271
2445
  const dX = e.movementX;
2272
2446
  const dY = e.movementY;
2273
-
2274
- const rect = this.colorPickerBackground.getBoundingClientRect();
2275
- const mouseX = e.offsetX - rect.x;
2276
- const mouseY = e.offsetY - rect.y;
2447
+ const mouseX = e.x - pickerRect.x;
2448
+ const mouseY = e.y - pickerRect.y;
2277
2449
 
2278
2450
  if ( dX != 0 && ( mouseX >= 0 || dX < 0 ) && ( mouseX < this.colorPickerBackground.offsetWidth || dX > 0 ) )
2279
2451
  {
@@ -2328,11 +2500,6 @@ class ColorPicker {
2328
2500
  this.hueMarker.style.backgroundColor = `rgb(${ hueColor.css.r }, ${ hueColor.css.g }, ${ hueColor.css.b })`;
2329
2501
  this.colorPickerTracker.appendChild( this.hueMarker );
2330
2502
 
2331
- doAsync( () => {
2332
- const hueLeft = LX.remapRange( this.currentColor.hsv.h, 0, 360, 0, this.colorPickerTracker.offsetWidth - this.markerSize );
2333
- this.hueMarker.style.left = hueLeft + "px";
2334
- } );
2335
-
2336
2503
  const _fromHueX = ( hueX ) => {
2337
2504
  this.hueMarker.style.left = hueX + "px";
2338
2505
  this.currentColor.hsv.h = LX.remapRange( hueX, 0, this.colorPickerTracker.offsetWidth - this.markerSize, 0, 360 );
@@ -2343,6 +2510,8 @@ class ColorPicker {
2343
2510
  this._updateColorValue();
2344
2511
  };
2345
2512
 
2513
+ let hueTrackerRect = null;
2514
+
2346
2515
  let innerMouseDownHue = e => {
2347
2516
  const doc = this.root.ownerDocument;
2348
2517
  doc.addEventListener( 'mousemove', innerMouseMoveHue );
@@ -2353,15 +2522,15 @@ class ColorPicker {
2353
2522
 
2354
2523
  const hueX = clamp( e.offsetX - this.markerHalfSize, 0, this.colorPickerTracker.offsetWidth - this.markerSize );
2355
2524
  _fromHueX( hueX );
2525
+
2526
+ hueTrackerRect = this.colorPickerTracker.getBoundingClientRect();
2356
2527
  }
2357
2528
 
2358
2529
  let innerMouseMoveHue = e => {
2359
- let dX = e.movementX;
2360
-
2361
- const rect = this.colorPickerTracker.getBoundingClientRect();
2362
- const mouseX = e.offsetX - rect.x;
2530
+ const dX = e.movementX;
2531
+ const mouseX = e.x - hueTrackerRect.x;
2363
2532
 
2364
- if ( dX != 0 && ( mouseX >= 0 || dX < 0 ) && ( mouseX < this.colorPickerTracker.offsetWidth || dX > 0 ) )
2533
+ if ( dX != 0 && ( mouseX >= this.markerHalfSize || dX < 0 ) && ( mouseX < ( this.colorPickerTracker.offsetWidth - this.markerHalfSize ) || dX > 0 ) )
2365
2534
  {
2366
2535
  const hueX = LX.clamp( parseInt( this.hueMarker.style.left ) + dX, 0, this.colorPickerTracker.offsetWidth - this.markerSize );
2367
2536
  _fromHueX( hueX )
@@ -2393,11 +2562,6 @@ class ColorPicker {
2393
2562
  this.alphaMarker.style.backgroundColor = `rgb(${ this.currentColor.css.r }, ${ this.currentColor.css.g }, ${ this.currentColor.css.b },${ this.currentColor.css.a })`;
2394
2563
  this.alphaTracker.appendChild( this.alphaMarker );
2395
2564
 
2396
- doAsync( () => {
2397
- const alphaLeft = LX.remapRange( this.currentColor.hsv.a, 0, 1, 0, this.alphaTracker.offsetWidth - this.markerSize );
2398
- this.alphaMarker.style.left = alphaLeft + "px";
2399
- } );
2400
-
2401
2565
  const _fromAlphaX = ( alphaX ) => {
2402
2566
  this.alphaMarker.style.left = alphaX + "px";
2403
2567
  this.currentColor.hsv.a = LX.remapRange( alphaX, 0, this.alphaTracker.offsetWidth - this.markerSize, 0, 1 );
@@ -2406,6 +2570,8 @@ class ColorPicker {
2406
2570
  this.alphaMarker.style.backgroundColor = `rgb(${ this.currentColor.css.r }, ${ this.currentColor.css.g }, ${ this.currentColor.css.b },${ this.currentColor.css.a })`;
2407
2571
  };
2408
2572
 
2573
+ let alphaTrackerRect = null;
2574
+
2409
2575
  let innerMouseDownAlpha = e => {
2410
2576
  const doc = this.root.ownerDocument;
2411
2577
  doc.addEventListener( 'mousemove', innerMouseMoveAlpha );
@@ -2415,15 +2581,14 @@ class ColorPicker {
2415
2581
  e.stopPropagation();
2416
2582
  const alphaX = clamp( e.offsetX - this.markerHalfSize, 0, this.alphaTracker.offsetWidth - this.markerSize );
2417
2583
  _fromAlphaX( alphaX );
2584
+ alphaTrackerRect = this.alphaTracker.getBoundingClientRect();
2418
2585
  }
2419
2586
 
2420
2587
  let innerMouseMoveAlpha = e => {
2421
- let dX = e.movementX;
2422
-
2423
- const rect = this.alphaTracker.getBoundingClientRect();
2424
- const mouseX = e.offsetX - rect.x;
2588
+ const dX = e.movementX;
2589
+ const mouseX = e.x - alphaTrackerRect.x;
2425
2590
 
2426
- if ( dX != 0 && ( mouseX >= 0 || dX < 0 ) && ( mouseX < this.alphaTracker.offsetWidth || dX > 0 ) )
2591
+ if ( dX != 0 && ( mouseX >= this.markerHalfSize || dX < 0 ) && ( mouseX < ( this.alphaTracker.offsetWidth - this.markerHalfSize ) || dX > 0 ) )
2427
2592
  {
2428
2593
  const alphaX = LX.clamp( parseInt( this.alphaMarker.style.left ) + dX, 0, this.alphaTracker.offsetWidth - this.markerSize );
2429
2594
  _fromAlphaX( alphaX );
@@ -2474,22 +2639,76 @@ class ColorPicker {
2474
2639
 
2475
2640
  this._updateColorValue( hexValue, true );
2476
2641
 
2477
- doAsync( () => {
2478
- this._adjustPosition();
2642
+ doAsync( this._placeMarkers.bind( this ) );
2479
2643
 
2480
- this.root.focus();
2644
+ this.onPopover = this._placeMarkers.bind( this );
2645
+ }
2481
2646
 
2482
- this._onClick = e => {
2483
- if( e.target && ( this.root.contains( e.target ) || e.target == this._trigger ) )
2484
- {
2485
- return;
2486
- }
2487
- this.destroy();
2488
- };
2647
+ _placeMarkers() {
2489
2648
 
2490
- document.body.addEventListener( "mousedown", this._onClick, true );
2491
- document.body.addEventListener( "focusin", this._onClick, true );
2492
- }, 10 );
2649
+ this._svToPosition( this.currentColor.hsv.s, this.currentColor.hsv.v );
2650
+
2651
+ const hueLeft = LX.remapRange( this.currentColor.hsv.h, 0, 360, 0, this.colorPickerTracker.offsetWidth - this.markerSize );
2652
+ this.hueMarker.style.left = hueLeft + "px";
2653
+
2654
+ if( this.useAlpha )
2655
+ {
2656
+ const alphaLeft = LX.remapRange( this.currentColor.hsv.a, 0, 1, 0, this.alphaTracker.offsetWidth - this.markerSize );
2657
+ this.alphaMarker.style.left = alphaLeft + "px";
2658
+ }
2659
+ }
2660
+
2661
+ _svToPosition( s, v ) {
2662
+ this.intSatMarker.style.left = `${ LX.remapRange( s, 0, 1, -this.markerHalfSize, this.colorPickerBackground.offsetWidth - this.markerHalfSize ) }px`;
2663
+ this.intSatMarker.style.top = `${ LX.remapRange( 1 - v, 0, 1, -this.markerHalfSize, this.colorPickerBackground.offsetHeight - this.markerHalfSize ) }px`
2664
+ }
2665
+
2666
+ _positionToSv( left, top ) {
2667
+ this.currentColor.hsv.s = LX.remapRange( left, -this.markerHalfSize, this.colorPickerBackground.offsetWidth - this.markerHalfSize, 0, 1 );
2668
+ this.currentColor.hsv.v = 1 - LX.remapRange( top, -this.markerHalfSize, this.colorPickerBackground.offsetHeight - this.markerHalfSize, 0, 1 );
2669
+ }
2670
+
2671
+ _updateColorValue( newHexValue, skipCallback = false ) {
2672
+
2673
+ this.currentColor.set( newHexValue ?? this.currentColor.hsv );
2674
+
2675
+ if( this.callback && !skipCallback )
2676
+ {
2677
+ this.callback( this.currentColor );
2678
+ }
2679
+
2680
+ this.intSatMarker.style.backgroundColor = this.currentColor.hex;
2681
+
2682
+ if( this.useAlpha )
2683
+ {
2684
+ this.alphaTracker.style.color = `rgb(${ this.currentColor.css.r }, ${ this.currentColor.css.g }, ${ this.currentColor.css.b })`;
2685
+ }
2686
+
2687
+ const toFixed = ( s, n = 2) => { return s.toFixed( n ).replace( /([0-9]+(\.[0-9]+[1-9])?)(\.?0+$)/, '$1' ) };
2688
+
2689
+ if( this.colorModel == "CSS" )
2690
+ {
2691
+ const { r, g, b, a } = this.currentColor.css;
2692
+ this.labelWidget.set( `rgb${ this.useAlpha ? 'a' : '' }(${ r },${ g },${ b }${ this.useAlpha ? ',' + toFixed( a ) : '' })` );
2693
+ }
2694
+ else if( this.colorModel == "Hex" )
2695
+ {
2696
+ this.labelWidget.set( ( this.useAlpha ? this.currentColor.hex : this.currentColor.hex.substr( 0, 7 ) ).toUpperCase() );
2697
+ }
2698
+ else if( this.colorModel == "HSV" )
2699
+ {
2700
+ const { h, s, v, a } = this.currentColor.hsv;
2701
+ const components = [ Math.floor( h ) + 'º', Math.floor( s * 100 ) + '%', Math.floor( v * 100 ) + '%' ];
2702
+ if( this.useAlpha ) components.push( toFixed( a ) );
2703
+ this.labelWidget.set( components.join( ' ' ) );
2704
+ }
2705
+ else // RGB
2706
+ {
2707
+ const { r, g, b, a } = this.currentColor.rgb;
2708
+ const components = [ toFixed( r ), toFixed( g ), toFixed( b ) ];
2709
+ if( this.useAlpha ) components.push( toFixed( a ) );
2710
+ this.labelWidget.set( components.join( ' ' ) );
2711
+ }
2493
2712
  }
2494
2713
 
2495
2714
  fromHexColor( hexColor ) {
@@ -2504,139 +2723,260 @@ class ColorPicker {
2504
2723
  this.hueMarker.style.backgroundColor = this.colorPickerBackground.style.backgroundColor = `rgb(${ hueColor.css.r }, ${ hueColor.css.g }, ${ hueColor.css.b })`;
2505
2724
  this.hueMarker.style.left = LX.remapRange( h, 0, 360, -this.markerHalfSize, this.colorPickerTracker.offsetWidth - this.markerHalfSize ) + "px";
2506
2725
 
2507
- this._updateColorValue( hexColor );
2726
+ this._updateColorValue( hexColor );
2727
+ }
2728
+ };
2729
+
2730
+ LX.ColorPicker = ColorPicker;
2731
+
2732
+ class Calendar {
2733
+
2734
+ /**
2735
+ * @constructor Calendar
2736
+ * @param {String} dateString D/M/Y
2737
+ * @param {Object} options
2738
+ * onChange: Function to call on date changes
2739
+ */
2740
+
2741
+ constructor( dateString, options = {} ) {
2742
+
2743
+ this.root = LX.makeContainer( ["256px", "auto"], "border p-3 bg-primary rounded-lg text-md" );
2744
+
2745
+ this.onChange = options.onChange;
2746
+ this.untilToday = options.untilToday;
2747
+ this.fromToday = options.fromToday;
2748
+
2749
+ if( dateString )
2750
+ {
2751
+ this.fromDateString( dateString );
2752
+ }
2753
+ else
2754
+ {
2755
+ const date = new Date();
2756
+ this.month = date.getMonth() + 1;
2757
+ this.year = date.getFullYear();
2758
+ this.fromMonthYear( this.month, this.year );
2759
+ }
2760
+ }
2761
+
2762
+ _getCurrentDate() {
2763
+ return {
2764
+ day: this.day,
2765
+ month: this.month,
2766
+ year: this.year,
2767
+ fullDate: this.getFullDate()
2768
+ }
2769
+ }
2770
+
2771
+ _previousMonth() {
2772
+
2773
+ this.month = Math.max( 0, this.month - 1 );
2774
+
2775
+ if( this.month == 0 )
2776
+ {
2777
+ this.month = 12;
2778
+ this.year--;
2779
+ }
2780
+
2781
+ this.fromMonthYear( this.month, this.year );
2782
+ }
2783
+
2784
+ _nextMonth() {
2785
+
2786
+ this.month = Math.min( this.month + 1, 12 );
2787
+
2788
+ if( this.month == 12 )
2789
+ {
2790
+ this.month = 0;
2791
+ this.year++;
2792
+ }
2793
+
2794
+ this.fromMonthYear( this.month, this.year );
2795
+ }
2796
+
2797
+ refresh() {
2798
+
2799
+ this.root.innerHTML = "";
2800
+
2801
+ // Header
2802
+ {
2803
+ const header = LX.makeContainer( ["100%", "auto"], "flex flex-row p-1", "", this.root );
2804
+
2805
+ const prevMonthIcon = LX.makeIcon( "left", { title: "Previous Month", iconClass: "border p-1 rounded hover:bg-secondary", svgClass: "sm" } );
2806
+ header.appendChild( prevMonthIcon );
2807
+ prevMonthIcon.addEventListener( "click", () => {
2808
+ this._previousMonth();
2809
+ } );
2810
+
2811
+ const monthYearLabel = LX.makeContainer( ["100%", "auto"], "text-center font-medium select-none", `${ this.monthName } ${ this.year }`, header );
2812
+
2813
+ const nextMonthIcon = LX.makeIcon( "right", { title: "Next Month", iconClass: "border p-1 rounded hover:bg-secondary", svgClass: "sm" } );
2814
+ header.appendChild( nextMonthIcon );
2815
+ nextMonthIcon.addEventListener( "click", () => {
2816
+ this._nextMonth();
2817
+ } );
2818
+ }
2819
+
2820
+ // Body
2821
+ {
2822
+ const daysTable = document.createElement( "table" );
2823
+ daysTable.className = "w-full";
2824
+ this.root.appendChild( daysTable );
2825
+
2826
+ // Table Head
2827
+ {
2828
+ const head = document.createElement( 'thead' );
2829
+ daysTable.appendChild( head );
2830
+
2831
+ const hrow = document.createElement( 'tr' );
2832
+
2833
+ for( const headData of [ "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su" ] )
2834
+ {
2835
+ const th = document.createElement( 'th' );
2836
+ th.className = "fg-tertiary text-sm font-normal select-none";
2837
+ th.innerHTML = `<span>${ headData }</span>`;
2838
+ hrow.appendChild( th );
2839
+ }
2840
+
2841
+ head.appendChild( hrow );
2842
+ }
2843
+
2844
+ // Table Body
2845
+ {
2846
+ const body = document.createElement( 'tbody' );
2847
+ daysTable.appendChild( body );
2848
+
2849
+ for( let week = 0; week < 6; week++ )
2850
+ {
2851
+ const hrow = document.createElement( 'tr' );
2852
+ const weekDays = this.calendarDays.slice( week * 7, week * 7 + 7 );
2853
+
2854
+ for( const dayData of weekDays )
2855
+ {
2856
+ const th = document.createElement( 'th' );
2857
+ th.className = "leading-loose font-normal rounded select-none cursor-pointer";
2858
+
2859
+ const dayDate = new Date( `${ this.month }/${ dayData.day }/${ this.year }` );
2860
+ const date = new Date();
2861
+ const beforeToday = this.untilToday ? ( dayDate.getTime() < date.getTime() ) : true;
2862
+ const afterToday = this.fromToday ? ( dayDate.getTime() > date.getTime() ) : true;
2863
+ const selectable = dayData.currentMonth && beforeToday && afterToday;
2864
+
2865
+ if( this.currentDate && ( dayData.day == this.currentDate.day ) && ( this.month == this.currentDate.month )
2866
+ && ( this.year == this.currentDate.year ) && dayData.currentMonth )
2867
+ {
2868
+ th.className += ` bg-contrast fg-contrast`;
2869
+ }
2870
+ else
2871
+ {
2872
+ th.className += ` ${ selectable ? "fg-primary" : "fg-tertiary" } hover:bg-secondary`;
2873
+ }
2874
+
2875
+ th.innerHTML = `<span>${ dayData.day }</span>`;
2876
+ hrow.appendChild( th );
2877
+
2878
+ if( selectable )
2879
+ {
2880
+ th.addEventListener( "click", () => {
2881
+ this.day = dayData.day;
2882
+ this.currentDate = this._getCurrentDate();
2883
+ if( this.onChange )
2884
+ {
2885
+ this.onChange( this.currentDate );
2886
+ }
2887
+ } );
2888
+ }
2889
+ }
2890
+
2891
+ body.appendChild( hrow );
2892
+ }
2893
+ }
2894
+ }
2508
2895
  }
2509
2896
 
2510
- destroy() {
2511
-
2512
- this._trigger.classList.remove( "triggered" );
2897
+ fromDateString( dateString ) {
2513
2898
 
2514
- delete this._trigger.picker;
2899
+ const tokens = dateString.split( '/' );
2515
2900
 
2516
- document.body.removeEventListener( "mousedown", this._onClick, true );
2517
- document.body.removeEventListener( "focusin", this._onClick, true );
2901
+ this.day = parseInt( tokens[ 0 ] );
2902
+ this.month = parseInt( tokens[ 1 ] );
2903
+ this.year = parseInt( tokens[ 2 ] );
2518
2904
 
2519
- LX.root.querySelectorAll( ".lexcolorpicker" ).forEach( m => { m.remove(); } );
2905
+ this.currentDate = this._getCurrentDate();
2520
2906
 
2521
- ColorPicker.currentPicker = null;
2907
+ this.fromMonthYear( this.month, this.year );
2522
2908
  }
2523
2909
 
2524
- _svToPosition( s, v ) {
2525
- this.intSatMarker.style.left = `${ LX.remapRange( s, 0, 1, -this.markerHalfSize, this.colorPickerBackground.offsetWidth - this.markerHalfSize ) }px`;
2526
- this.intSatMarker.style.top = `${ LX.remapRange( 1 - v, 0, 1, -this.markerHalfSize, this.colorPickerBackground.offsetHeight - this.markerHalfSize ) }px`
2527
- };
2910
+ fromMonthYear( month, year ) {
2528
2911
 
2529
- _positionToSv( left, top ) {
2530
- this.currentColor.hsv.s = LX.remapRange( left, -this.markerHalfSize, this.colorPickerBackground.offsetWidth - this.markerHalfSize, 0, 1 );
2531
- this.currentColor.hsv.v = 1 - LX.remapRange( top, -this.markerHalfSize, this.colorPickerBackground.offsetHeight - this.markerHalfSize, 0, 1 );
2532
- };
2912
+ // Month is 0-based (0 = January, ... 11 = December)
2913
+ month--;
2533
2914
 
2534
- _updateColorValue( newHexValue, skipCallback = false ) {
2915
+ year = year ?? new Date().getFullYear();
2535
2916
 
2536
- this.currentColor.set( newHexValue ?? this.currentColor.hsv );
2917
+ const weekDay = new Date( year, month, 1 ).getDay();
2918
+ const firstDay = weekDay === 0 ? 6 : weekDay - 1; // 0 = Monday, 1 = Tuesday...
2919
+ const daysInMonth = new Date( year, month + 1, 0 ).getDate();
2537
2920
 
2538
- if( this.callback && !skipCallback )
2539
- {
2540
- this.callback( this.currentColor );
2541
- }
2921
+ // Previous month
2922
+ const prevMonth = month === 0 ? 11 : month - 1;
2923
+ const prevYear = month === 0 ? year - 1 : year;
2924
+ const daysInPrevMonth = new Date( prevYear, prevMonth + 1, 0 ).getDate();
2542
2925
 
2543
- this.intSatMarker.style.backgroundColor = this.currentColor.hex;
2926
+ // Prepare full grid (up to 6 weeks = 42 days)
2927
+ const calendarDays = [];
2544
2928
 
2545
- if( this.useAlpha )
2929
+ // Fill in days from previous month
2930
+ for( let i = firstDay - 1; i >= 0; i--)
2546
2931
  {
2547
- this.alphaTracker.style.color = `rgb(${ this.currentColor.css.r }, ${ this.currentColor.css.g }, ${ this.currentColor.css.b })`;
2932
+ calendarDays.push( { day: daysInPrevMonth - i, currentMonth: false } );
2548
2933
  }
2549
2934
 
2550
- const toFixed = ( s, n = 2) => { return s.toFixed( n ).replace( /([0-9]+(\.[0-9]+[1-9])?)(\.?0+$)/, '$1' ) };
2551
-
2552
- if( this.colorModel == "CSS" )
2935
+ // Fill in current month days
2936
+ for ( let i = 1; i <= daysInMonth; i++ )
2553
2937
  {
2554
- const { r, g, b, a } = this.currentColor.css;
2555
- this.labelWidget.set( `rgb${ this.useAlpha ? 'a' : '' }(${ r },${ g },${ b }${ this.useAlpha ? ',' + toFixed( a ) : '' })` );
2556
- }
2557
- else if( this.colorModel == "Hex" )
2558
- {
2559
- this.labelWidget.set( ( this.useAlpha ? this.currentColor.hex : this.currentColor.hex.substr( 0, 7 ) ).toUpperCase() );
2560
- }
2561
- else if( this.colorModel == "HSV" )
2562
- {
2563
- const { h, s, v, a } = this.currentColor.hsv;
2564
- const components = [ Math.floor( h ) + 'º', Math.floor( s * 100 ) + '%', Math.floor( v * 100 ) + '%' ];
2565
- if( this.useAlpha ) components.push( toFixed( a ) );
2566
- this.labelWidget.set( components.join( ' ' ) );
2938
+ calendarDays.push( { day: i, currentMonth: true } );
2567
2939
  }
2568
- else // RGB
2940
+
2941
+ // Fill in next month days to complete the grid (if needed)
2942
+ const remaining = 42 - calendarDays.length;
2943
+ for( let i = 1; i <= remaining; i++ )
2569
2944
  {
2570
- const { r, g, b, a } = this.currentColor.rgb;
2571
- const components = [ toFixed( r ), toFixed( g ), toFixed( b ) ];
2572
- if( this.useAlpha ) components.push( toFixed( a ) );
2573
- this.labelWidget.set( components.join( ' ' ) );
2945
+ calendarDays.push( { day: i, currentMonth: false } );
2574
2946
  }
2575
- };
2576
-
2577
- _adjustPosition() {
2578
-
2579
- const position = [ 0, 0 ];
2580
2947
 
2581
- // Place menu using trigger position and user options
2582
- {
2583
- const rect = this._trigger.getBoundingClientRect();
2948
+ this.monthName = this.getMonthName( month );
2949
+ this.firstDay = firstDay;
2950
+ this.daysInMonth = daysInMonth;
2951
+ this.calendarDays = calendarDays;
2584
2952
 
2585
- let alignWidth = true;
2953
+ this.refresh();
2954
+ }
2586
2955
 
2587
- switch( this.side )
2588
- {
2589
- case "left":
2590
- position[ 0 ] += ( rect.x - this.root.offsetWidth );
2591
- alignWidth = false;
2592
- break;
2593
- case "right":
2594
- position[ 0 ] += ( rect.x + rect.width );
2595
- alignWidth = false;
2596
- break;
2597
- case "top":
2598
- position[ 1 ] += ( rect.y - this.root.offsetHeight );
2599
- alignWidth = true;
2600
- break;
2601
- case "bottom":
2602
- position[ 1 ] += ( rect.y + rect.height );
2603
- alignWidth = true;
2604
- break;
2605
- default:
2606
- break;
2607
- }
2956
+ getMonthName( monthIndex, locale = "en-US" ) {
2957
+ const formatter = new Intl.DateTimeFormat( locale, { month: "long" } );
2958
+ return formatter.format( new Date( 2000, monthIndex, 1 ) );
2959
+ }
2608
2960
 
2609
- switch( this.align )
2610
- {
2611
- case "start":
2612
- if( alignWidth ) { position[ 0 ] += rect.x; }
2613
- else { position[ 1 ] += rect.y; }
2614
- break;
2615
- case "center":
2616
- if( alignWidth ) { position[ 0 ] += ( rect.x + rect.width * 0.5 ) - this.root.offsetWidth * 0.5; }
2617
- else { position[ 1 ] += ( rect.y + rect.height * 0.5 ) - this.root.offsetHeight * 0.5; }
2618
- break;
2619
- case "end":
2620
- if( alignWidth ) { position[ 0 ] += rect.x - this.root.offsetWidth + rect.width; }
2621
- else { position[ 1 ] += rect.y - this.root.offsetHeight + rect.height; }
2622
- break;
2623
- default:
2624
- break;
2625
- }
2626
- }
2961
+ getFullDate() {
2962
+ return `${ this.monthName } ${ this.day }${ this._getOrdinalSuffix( this.day ) }, ${ this.year }`;
2963
+ }
2627
2964
 
2628
- if( this.avoidCollisions )
2965
+ _getOrdinalSuffix( day ) {
2966
+ if ( day > 3 && day < 21 ) return "th";
2967
+ switch ( day % 10 )
2629
2968
  {
2630
- position[ 0 ] = LX.clamp( position[ 0 ], 0, window.innerWidth - this.root.offsetWidth - this._windowPadding );
2631
- position[ 1 ] = LX.clamp( position[ 1 ], 0, window.innerHeight - this.root.offsetHeight - this._windowPadding );
2969
+ case 1: return "st";
2970
+ case 2: return "nd";
2971
+ case 3: return "rd";
2972
+ default: return "th";
2632
2973
  }
2633
-
2634
- this.root.style.left = `${ position[ 0 ] }px`;
2635
- this.root.style.top = `${ position[ 1 ] }px`;
2636
2974
  }
2637
- };
2975
+ }
2638
2976
 
2639
- LX.ColorPicker = ColorPicker;
2977
+ LX.Calendar = Calendar;
2978
+
2979
+ /* Layout Classes */
2640
2980
 
2641
2981
  class Area {
2642
2982
 
@@ -2861,7 +3201,7 @@ class Area {
2861
3201
  * @method split
2862
3202
  * @param {Object} options
2863
3203
  * type: Split mode (horizontal, vertical) ["horizontal"]
2864
- * sizes: Size of each new area (Array) ["50%", "50%"]
3204
+ * sizes: CSS Size of each new area (Array) ["50%", "50%"]
2865
3205
  * resize: Allow area manual resizing [true]
2866
3206
  * sizes: "Allow the area to be minimized [false]
2867
3207
  */
@@ -2876,11 +3216,12 @@ class Area {
2876
3216
  this.root = this.sections[ 1 ].root;
2877
3217
  }
2878
3218
 
2879
- const type = options.type || "horizontal";
3219
+ const type = options.type ?? "horizontal";
2880
3220
  const sizes = options.sizes || [ "50%", "50%" ];
2881
3221
  const auto = (options.sizes === 'auto') || ( options.sizes && options.sizes[ 0 ] == "auto" && options.sizes[ 1 ] == "auto" );
2882
3222
 
2883
- if( !sizes[ 1 ] )
3223
+ // Secondary area fills space
3224
+ if( !sizes[ 1 ] || ( sizes[ 0 ] != "auto" && sizes[ 1 ] == "auto" ) )
2884
3225
  {
2885
3226
  let size = sizes[ 0 ];
2886
3227
  let margin = options.top ? options.top : 0;
@@ -2893,17 +3234,13 @@ class Area {
2893
3234
  sizes[ 1 ] = "calc( 100% - " + size + " )";
2894
3235
  }
2895
3236
 
2896
- // Create areas
2897
- let area1 = new Area( { skipAppend: true, className: "split" + ( options.menubar || options.sidebar ? "" : " origin" ) } );
2898
- let area2 = new Area( { skipAppend: true, className: "split" } );
2899
-
2900
- area1.parentArea = this;
2901
- area2.parentArea = this;
2902
-
2903
3237
  let minimizable = options.minimizable ?? false;
2904
3238
  let resize = ( options.resize ?? true ) || minimizable;
3239
+ let fixedSize = options.fixedSize ?? !resize;
3240
+ let splitbarOffset = 0;
3241
+ let primarySize = [];
3242
+ let secondarySize = [];
2905
3243
 
2906
- let data = "0px";
2907
3244
  this.offset = 0;
2908
3245
 
2909
3246
  if( resize )
@@ -2923,93 +3260,104 @@ class Area {
2923
3260
 
2924
3261
  this.splitBar.addEventListener( 'mousedown', innerMouseDown );
2925
3262
 
2926
- data = ( LX.DEFAULT_SPLITBAR_SIZE / 2 ) + "px"; // updates
2927
-
2928
- // Being minimizable means it's also resizeable!
2929
- if( minimizable )
2930
- {
2931
- this.splitExtended = false;
2932
-
2933
- // Keep state of the animation when ends...
2934
- area2.root.addEventListener('animationend', e => {
2935
- const opacity = getComputedStyle( area2.root ).opacity;
2936
- area2.root.classList.remove( e.animationName + "-" + type );
2937
- area2.root.style.opacity = opacity;
2938
- flushCss(area2.root);
2939
- });
2940
-
2941
- this.splitBar.addEventListener("contextmenu", e => {
2942
- e.preventDefault();
2943
- addContextMenu(null, e, c => {
2944
- c.add("Extend", { disabled: this.splitExtended, callback: () => { this.extend() } });
2945
- c.add("Reduce", { disabled: !this.splitExtended, callback: () => { this.reduce() } });
2946
- });
2947
- });
2948
- }
3263
+ splitbarOffset = ( LX.DEFAULT_SPLITBAR_SIZE / 2 ); // updates
2949
3264
  }
2950
3265
 
2951
3266
  if( type == "horizontal" )
2952
3267
  {
2953
- let width1 = sizes[ 0 ],
2954
- width2 = sizes[ 1 ];
3268
+ this.root.style.display = "flex";
2955
3269
 
2956
- if( width1.constructor == Number )
3270
+ if( !fixedSize )
2957
3271
  {
2958
- width1 += "px";
2959
- }
3272
+ const parentWidth = this.root.offsetWidth;
3273
+ const leftPx = parsePixelSize( sizes[ 0 ], parentWidth );
3274
+ const rightPx = parsePixelSize( sizes[ 1 ], parentWidth );
3275
+ const leftPercent = ( leftPx / parentWidth ) * 100;
3276
+ const rightPercent = ( rightPx / parentWidth ) * 100;
2960
3277
 
2961
- if( width2.constructor == Number )
3278
+ // Style using percentages
3279
+ primarySize[ 0 ] = `calc(${ leftPercent }% - ${ splitbarOffset }px)`;
3280
+ secondarySize[ 0 ] = `calc(${ rightPercent }% - ${ splitbarOffset }px)`;
3281
+ }
3282
+ else
2962
3283
  {
2963
- width2 += "px";
3284
+ primarySize[ 0 ] = `calc(${ sizes[ 0 ] } - ${ splitbarOffset }px)`;
3285
+ secondarySize[ 0 ] = `calc(${ sizes[ 1 ] } - ${ splitbarOffset }px)`;
2964
3286
  }
2965
3287
 
2966
- area1.root.style.width = "calc( " + width1 + " - " + data + " )";
2967
- area1.root.style.height = "calc(100% - 0px)";
2968
- area2.root.style.width = "calc( " + width2 + " - " + data + " )";
2969
- area2.root.style.height = "calc(100% - 0px)";
2970
- this.root.style.display = "flex";
3288
+ primarySize[ 1 ] = "100%";
3289
+ secondarySize[ 1 ] = "100%";
2971
3290
  }
2972
3291
  else // vertical
2973
3292
  {
2974
- area1.root.style.width = "100%";
2975
- area2.root.style.width = "100%";
2976
-
2977
3293
  if( auto )
2978
3294
  {
2979
- area1.root.style.height = "auto";
2980
-
2981
- // Listen resize event on first area
2982
- const resizeObserver = new ResizeObserver( entries => {
2983
- for ( const entry of entries )
2984
- {
2985
- const size = entry.target.getComputedSize();
2986
- area2.root.style.height = "calc(100% - " + ( size.height ) + "px )";
2987
- }
2988
- });
3295
+ primarySize[ 1 ] = "auto";
3296
+ }
3297
+ else if( !fixedSize )
3298
+ {
3299
+ const parentHeight = this.root.offsetHeight;
3300
+ const topPx = parsePixelSize( sizes[ 0 ], parentHeight );
3301
+ const bottomPx = parsePixelSize( sizes[ 1 ], parentHeight );
3302
+ const topPercent = ( topPx / parentHeight ) * 100;
3303
+ const bottomPercent = ( bottomPx / parentHeight ) * 100;
2989
3304
 
2990
- resizeObserver.observe( area1.root );
3305
+ primarySize[ 1 ] = ( sizes[ 0 ] == "auto" ? "auto" : `calc(${ topPercent }% - ${ splitbarOffset }px)`);
3306
+ secondarySize[ 1 ] = ( sizes[ 1 ] == "auto" ? "auto" : `calc(${ bottomPercent }% - ${ splitbarOffset }px)`);
2991
3307
  }
2992
3308
  else
2993
3309
  {
2994
- let height1 = sizes[ 0 ],
2995
- height2 = sizes[ 1 ];
3310
+ primarySize[ 1 ] = ( sizes[ 0 ] == "auto" ? "auto" : `calc(${ sizes[ 0 ] } - ${ splitbarOffset }px)`);
3311
+ secondarySize[ 1 ] = ( sizes[ 1 ] == "auto" ? "auto" : `calc(${ sizes[ 1 ] } - ${ splitbarOffset }px)`);
3312
+ }
2996
3313
 
2997
- if( height1.constructor == Number )
2998
- {
2999
- height1 += "px";
3000
- }
3314
+ primarySize[ 0 ] = "100%";
3315
+ secondarySize[ 0 ] = "100%";
3316
+ }
3001
3317
 
3002
- if( height2.constructor == Number )
3318
+ // Create areas
3319
+ let area1 = new Area( { width: primarySize[ 0 ], height: primarySize[ 1 ], skipAppend: true, className: "split" + ( options.menubar || options.sidebar ? "" : " origin" ) } );
3320
+ let area2 = new Area( { width: secondarySize[ 0 ], height: secondarySize[ 1 ], skipAppend: true, className: "split" } );
3321
+
3322
+ if( auto && type == "vertical" )
3323
+ {
3324
+ // Listen resize event on first area
3325
+ const resizeObserver = new ResizeObserver( entries => {
3326
+ for ( const entry of entries )
3003
3327
  {
3004
- height2 += "px";
3328
+ const size = entry.target.getComputedSize();
3329
+ area2.root.style.height = "calc(100% - " + ( size.height ) + "px )";
3005
3330
  }
3331
+ });
3006
3332
 
3007
- area1.root.style.width = "100%";
3008
- area1.root.style.height = ( height1 == "auto" ? height1 : "calc( " + height1 + " - " + data + " )");
3009
- area2.root.style.height = ( height2 == "auto" ? height2 : "calc( " + height2 + " - " + data + " )");
3010
- }
3333
+ resizeObserver.observe( area1.root );
3334
+ }
3335
+
3336
+ // Being minimizable means it's also resizeable!
3337
+ if( resize && minimizable )
3338
+ {
3339
+ this.splitExtended = false;
3340
+
3341
+ // Keep state of the animation when ends...
3342
+ area2.root.addEventListener('animationend', e => {
3343
+ const opacity = getComputedStyle( area2.root ).opacity;
3344
+ area2.root.classList.remove( e.animationName + "-" + type );
3345
+ area2.root.style.opacity = opacity;
3346
+ flushCss( area2.root );
3347
+ });
3348
+
3349
+ this.splitBar.addEventListener("contextmenu", e => {
3350
+ e.preventDefault();
3351
+ addContextMenu(null, e, c => {
3352
+ c.add("Extend", { disabled: this.splitExtended, callback: () => { this.extend() } });
3353
+ c.add("Reduce", { disabled: !this.splitExtended, callback: () => { this.reduce() } });
3354
+ });
3355
+ });
3011
3356
  }
3012
3357
 
3358
+ area1.parentArea = this;
3359
+ area2.parentArea = this;
3360
+
3013
3361
  this.root.appendChild( area1.root );
3014
3362
 
3015
3363
  if( resize )
@@ -3018,6 +3366,7 @@ class Area {
3018
3366
  }
3019
3367
 
3020
3368
  this.root.appendChild( area2.root );
3369
+
3021
3370
  this.sections = [ area1, area2 ];
3022
3371
  this.type = type;
3023
3372
 
@@ -3131,10 +3480,10 @@ class Area {
3131
3480
  return;
3132
3481
  }
3133
3482
 
3134
- let [area1, area2] = this.sections;
3483
+ let [ area1, area2 ] = this.sections;
3135
3484
  this.splitExtended = true;
3136
3485
 
3137
- if( this.type == "vertical")
3486
+ if( this.type == "vertical" )
3138
3487
  {
3139
3488
  this.offset = area2.root.offsetHeight;
3140
3489
  area2.root.classList.add("fadeout-vertical");
@@ -3145,7 +3494,7 @@ class Area {
3145
3494
  {
3146
3495
  this.offset = area2.root.offsetWidth - 8; // Force some height here...
3147
3496
  area2.root.classList.add("fadeout-horizontal");
3148
- this._moveSplit(-Infinity, true, 8);
3497
+ this._moveSplit( -Infinity, true, 8 );
3149
3498
  }
3150
3499
 
3151
3500
  doAsync( () => this.propagateEvent('onresize'), 150 );
@@ -3252,8 +3601,7 @@ class Area {
3252
3601
 
3253
3602
  LX.menubars.push( menubar );
3254
3603
 
3255
- const height = 48; // pixels
3256
- const [ bar, content ] = this.split({ type: 'vertical', sizes: [height, null], resize: false, menubar: true });
3604
+ const [ bar, content ] = this.split({ type: 'vertical', sizes: ["48px", null], resize: false, menubar: true });
3257
3605
  menubar.siblingArea = content;
3258
3606
 
3259
3607
  bar.attach( menubar );
@@ -3370,7 +3718,8 @@ class Area {
3370
3718
  img: b.img,
3371
3719
  className: b.class ?? "",
3372
3720
  title: b.name,
3373
- overflowContainerX: overlayPanel.root
3721
+ overflowContainerX: overlayPanel.root,
3722
+ swap: b.swap
3374
3723
  };
3375
3724
 
3376
3725
  if( group )
@@ -3507,7 +3856,7 @@ class Area {
3507
3856
 
3508
3857
  const a2 = this.sections[ 1 ];
3509
3858
  const a2Root = a2.root;
3510
- const splitData = " - "+ LX.DEFAULT_SPLITBAR_SIZE + "px";
3859
+ const splitData = "- "+ LX.DEFAULT_SPLITBAR_SIZE + "px";
3511
3860
 
3512
3861
  let transition = null;
3513
3862
  if( !forceAnimation )
@@ -3515,30 +3864,47 @@ class Area {
3515
3864
  // Remove transitions for this change..
3516
3865
  transition = a1Root.style.transition;
3517
3866
  a1Root.style.transition = a2Root.style.transition = "none";
3518
- flushCss( a1Root );
3519
- flushCss( a2Root );
3867
+ // flushCss( a1Root );
3520
3868
  }
3521
3869
 
3522
3870
  if( this.type == "horizontal" )
3523
3871
  {
3524
3872
  var size = Math.max( a2Root.offsetWidth + dt, parseInt( a2.minWidth ) );
3525
3873
  if( forceWidth ) size = forceWidth;
3526
- a1Root.style.width = "-moz-calc( 100% - " + size + "px " + splitData + " )";
3527
- a1Root.style.width = "-webkit-calc( 100% - " + size + "px " + splitData + " )";
3528
- a1Root.style.width = "calc( 100% - " + size + "px " + splitData + " )";
3529
- a1Root.style.minWidth = parseInt( a1.minWidth ) + "px";
3530
- a2Root.style.width = size + "px";
3531
- if( a1.maxWidth != Infinity ) a2Root.style.minWidth = "calc( 100% - " + parseInt( a1.maxWidth ) + "px" + " )";
3874
+
3875
+ const parentWidth = this.size[ 0 ];
3876
+ const rightPercent = ( size / parentWidth ) * 100;
3877
+ const leftPercent = Math.max( 0, 100 - rightPercent );
3878
+
3879
+ a1Root.style.width = `-moz-calc(${ leftPercent }% ${ splitData })`;
3880
+ a1Root.style.width = `-webkit-calc( ${ leftPercent }% ${ splitData })`;
3881
+ a1Root.style.width = `calc( ${ leftPercent }% ${ splitData })`;
3882
+ a2Root.style.width = `${ rightPercent }%`;
3883
+ a2Root.style.width = `${ rightPercent }%`;
3884
+ a2Root.style.width = `${ rightPercent }%`;
3885
+
3886
+ if( a1.maxWidth != Infinity )
3887
+ {
3888
+ a2Root.style.minWidth = "calc( 100% - " + parseInt( a1.maxWidth ) + "px" + " )";
3889
+ }
3532
3890
  }
3533
3891
  else
3534
3892
  {
3535
- var size = Math.max((a2Root.offsetHeight + dt) + a2.offset, parseInt(a2.minHeight));
3893
+ var size = Math.max( ( a2Root.offsetHeight + dt ) + a2.offset, parseInt(a2.minHeight) );
3536
3894
  if( forceWidth ) size = forceWidth;
3537
- a1Root.style.height = "-moz-calc( 100% - " + size + "px " + splitData + " )";
3538
- a1Root.style.height = "-webkit-calc( 100% - " + size + "px " + splitData + " )";
3539
- a1Root.style.height = "calc( 100% - " + size + "px " + splitData + " )";
3895
+
3896
+ const parentHeight = this.size[ 1 ];
3897
+ const bottomPercent = ( size / parentHeight ) * 100;
3898
+ const topPercent = Math.max( 0, 100 - bottomPercent );
3899
+
3900
+ a1Root.style.height = `-moz-calc(${ topPercent }% ${ splitData })`;
3901
+ a1Root.style.height = `-webkit-calc( ${ topPercent }% ${ splitData })`;
3902
+ a1Root.style.height = `calc( ${ topPercent }% ${ splitData })`;
3903
+ a2Root.style.height = `${ bottomPercent }%`;
3904
+ a2Root.style.height = `${ bottomPercent }%`;
3905
+ a2Root.style.height = `${ bottomPercent }%`;
3906
+
3540
3907
  a1Root.style.minHeight = a1.minHeight + "px";
3541
- a2Root.style.height = ( size - a2.offset ) + "px";
3542
3908
  }
3543
3909
 
3544
3910
  if( !forceAnimation )
@@ -3547,10 +3913,10 @@ class Area {
3547
3913
  a1Root.style.transition = a2Root.style.transition = transition;
3548
3914
  }
3549
3915
 
3550
- this._update();
3551
-
3552
- // Resize events
3553
- this.propagateEvent( 'onresize' );
3916
+ doAsync( () => {
3917
+ this._update();
3918
+ this.propagateEvent( 'onresize' );
3919
+ }, 10 );
3554
3920
  }
3555
3921
 
3556
3922
  _disableSplitResize() {
@@ -5216,8 +5582,9 @@ class Widget {
5216
5582
  static COUNTER = 33;
5217
5583
  static TABLE = 34;
5218
5584
  static TABS = 35;
5219
- static LABEL = 36;
5220
- static BLANK = 37;
5585
+ static DATE = 36;
5586
+ static LABEL = 37;
5587
+ static BLANK = 38;
5221
5588
 
5222
5589
  static NO_CONTEXT_TYPES = [
5223
5590
  Widget.BUTTON,
@@ -5455,6 +5822,7 @@ class Widget {
5455
5822
  case Widget.COUNTER: return "Counter";
5456
5823
  case Widget.TABLE: return "Table";
5457
5824
  case Widget.TABS: return "Tabs";
5825
+ case Widget.DATE: return "Date";
5458
5826
  case Widget.LABEL: return "Label";
5459
5827
  case Widget.BLANK: return "Blank";
5460
5828
  case Widget.CUSTOM: return this.customName;
@@ -6055,8 +6423,11 @@ class NodeTree {
6055
6423
  actionEl.className = "lexicon " + a.icon;
6056
6424
  actionEl.title = a.name;
6057
6425
  actionEl.addEventListener("click", function( e ) {
6058
- a.callback( node, actionEl );
6059
- e.stopPropagation();
6426
+ if( a.callback )
6427
+ {
6428
+ a.callback( node, actionEl );
6429
+ e.stopPropagation();
6430
+ }
6060
6431
  });
6061
6432
 
6062
6433
  inputContainer.appendChild( actionEl );
@@ -6116,7 +6487,17 @@ class NodeTree {
6116
6487
 
6117
6488
  this.data = newData ?? this.data;
6118
6489
  this.domEl.querySelector( "ul" ).innerHTML = "";
6119
- this._createItem( null, this.data, 0, selectedId );
6490
+ if( this.data.constructor === Object )
6491
+ {
6492
+ this._createItem( null, this.data, 0, selectedId );
6493
+ }
6494
+ else
6495
+ {
6496
+ for( let d of this.data )
6497
+ {
6498
+ this._createItem( null, d, 0, selectedId );
6499
+ }
6500
+ }
6120
6501
  }
6121
6502
 
6122
6503
  /* Refreshes the tree and focuses current element */
@@ -6147,6 +6528,8 @@ class NodeTree {
6147
6528
  }
6148
6529
  }
6149
6530
 
6531
+ LX.NodeTree = NodeTree;
6532
+
6150
6533
  /**
6151
6534
  * @class Blank
6152
6535
  * @description Blank Widget
@@ -6243,8 +6626,8 @@ class TextInput extends Widget {
6243
6626
  };
6244
6627
 
6245
6628
  this.onResize = ( rect ) => {
6246
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
6247
- container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth }px)`;
6629
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
6630
+ container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth })`;
6248
6631
  };
6249
6632
 
6250
6633
  this.valid = ( v ) => {
@@ -6382,8 +6765,8 @@ class TextArea extends Widget {
6382
6765
  };
6383
6766
 
6384
6767
  this.onResize = ( rect ) => {
6385
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
6386
- container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth }px)`;
6768
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
6769
+ container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth })`;
6387
6770
  };
6388
6771
 
6389
6772
  let container = document.createElement( "div" );
@@ -6476,8 +6859,8 @@ class Button extends Widget {
6476
6859
  };
6477
6860
 
6478
6861
  this.onResize = ( rect ) => {
6479
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
6480
- wValue.style.width = `calc( 100% - ${ realNameWidth }px)`;
6862
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
6863
+ wValue.style.width = `calc( 100% - ${ realNameWidth })`;
6481
6864
  };
6482
6865
 
6483
6866
  var wValue = document.createElement( 'button' );
@@ -6537,6 +6920,7 @@ class Button extends Widget {
6537
6920
  wValue.querySelector( "a" ).classList.add( "swap-off" );
6538
6921
 
6539
6922
  const input = document.createElement( "input" );
6923
+ input.className = "p-0 border-0";
6540
6924
  input.type = "checkbox";
6541
6925
  wValue.prepend( input );
6542
6926
 
@@ -6739,8 +7123,8 @@ class ComboButtons extends Widget {
6739
7123
  };
6740
7124
 
6741
7125
  this.onResize = ( rect ) => {
6742
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
6743
- container.style.width = `calc( 100% - ${ realNameWidth }px)`;
7126
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
7127
+ container.style.width = `calc( 100% - ${ realNameWidth })`;
6744
7128
  };
6745
7129
 
6746
7130
  this.root.appendChild( container );
@@ -6956,8 +7340,8 @@ class Select extends Widget {
6956
7340
  };
6957
7341
 
6958
7342
  this.onResize = ( rect ) => {
6959
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
6960
- container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth }px)`;
7343
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
7344
+ container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth })`;
6961
7345
  };
6962
7346
 
6963
7347
  let container = document.createElement( "div" );
@@ -7304,11 +7688,8 @@ class Curve extends Widget {
7304
7688
  };
7305
7689
 
7306
7690
  this.onResize = ( rect ) => {
7307
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
7308
- container.style.width = `calc( 100% - ${ realNameWidth }px)`;
7309
- flushCss( container );
7310
- curveInstance.canvas.width = container.offsetWidth;
7311
- curveInstance.redraw();
7691
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
7692
+ container.style.width = `calc( 100% - ${ realNameWidth })`;
7312
7693
  };
7313
7694
 
7314
7695
  var container = document.createElement( "div" );
@@ -7325,6 +7706,16 @@ class Curve extends Widget {
7325
7706
  container.appendChild( curveInstance.element );
7326
7707
  this.curveInstance = curveInstance;
7327
7708
 
7709
+ const observer = new ResizeObserver( entries => {
7710
+ for ( const entry of entries )
7711
+ {
7712
+ curveInstance.canvas.width = entry.contentRect.width;
7713
+ curveInstance.redraw();
7714
+ }
7715
+ });
7716
+
7717
+ observer.observe( container );
7718
+
7328
7719
  doAsync( this.onResize.bind( this ) );
7329
7720
  }
7330
7721
  }
@@ -7358,8 +7749,8 @@ class Dial extends Widget {
7358
7749
  };
7359
7750
 
7360
7751
  this.onResize = ( rect ) => {
7361
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
7362
- container.style.width = `calc( 100% - ${ realNameWidth }px)`;
7752
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
7753
+ container.style.width = `calc( 100% - ${ realNameWidth })`;
7363
7754
  flushCss( container );
7364
7755
  dialInstance.element.style.height = dialInstance.element.offsetWidth + "px";
7365
7756
  dialInstance.canvas.width = dialInstance.element.offsetWidth;
@@ -7413,8 +7804,8 @@ class Layers extends Widget {
7413
7804
  };
7414
7805
 
7415
7806
  this.onResize = ( rect ) => {
7416
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
7417
- container.style.width = `calc( 100% - ${ realNameWidth }px)`;
7807
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
7808
+ container.style.width = `calc( 100% - ${ realNameWidth })`;
7418
7809
  };
7419
7810
 
7420
7811
  var container = document.createElement( "div" );
@@ -7638,8 +8029,8 @@ class List extends Widget {
7638
8029
  };
7639
8030
 
7640
8031
  this.onResize = ( rect ) => {
7641
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
7642
- listContainer.style.width = `calc( 100% - ${ realNameWidth }px)`;
8032
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
8033
+ listContainer.style.width = `calc( 100% - ${ realNameWidth })`;
7643
8034
  };
7644
8035
 
7645
8036
  this._updateValues = ( newValues ) => {
@@ -7715,8 +8106,8 @@ class Tags extends Widget {
7715
8106
  };
7716
8107
 
7717
8108
  this.onResize = ( rect ) => {
7718
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
7719
- tagsContainer.style.width = `calc( 100% - ${ realNameWidth }px)`;
8109
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
8110
+ tagsContainer.style.width = `calc( 100% - ${ realNameWidth })`;
7720
8111
  };
7721
8112
 
7722
8113
  // Show tags
@@ -7816,8 +8207,8 @@ class Checkbox extends Widget {
7816
8207
  };
7817
8208
 
7818
8209
  this.onResize = ( rect ) => {
7819
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
7820
- container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth }px)`;
8210
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
8211
+ container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth })`;
7821
8212
  };
7822
8213
 
7823
8214
  var container = document.createElement( "div" );
@@ -7899,8 +8290,8 @@ class Toggle extends Widget {
7899
8290
  };
7900
8291
 
7901
8292
  this.onResize = ( rect ) => {
7902
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
7903
- container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth }px)`;
8293
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
8294
+ container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth })`;
7904
8295
  };
7905
8296
 
7906
8297
  var container = document.createElement('div');
@@ -8082,14 +8473,24 @@ class ColorInput extends Widget {
8082
8473
  };
8083
8474
 
8084
8475
  this.onResize = ( rect ) => {
8085
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
8086
- container.style.width = `calc( 100% - ${ realNameWidth }px)`;
8476
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
8477
+ container.style.width = `calc( 100% - ${ realNameWidth })`;
8087
8478
  };
8088
8479
 
8089
8480
  var container = document.createElement( 'span' );
8090
8481
  container.className = "lexcolor";
8091
8482
  this.root.appendChild( container );
8092
8483
 
8484
+ this.picker = new ColorPicker( value, {
8485
+ colorModel: options.useRGB ? "RGB" : "Hex",
8486
+ useAlpha,
8487
+ onChange: ( color ) => {
8488
+ this._fromColorPicker = true;
8489
+ this.set( color.hex );
8490
+ delete this._fromColorPicker;
8491
+ }
8492
+ } );
8493
+
8093
8494
  let sampleContainer = LX.makeContainer( ["18px", "18px"], "flex flex-row bg-contrast rounded overflow-hidden", "", container );
8094
8495
  sampleContainer.tabIndex = "1";
8095
8496
  sampleContainer.addEventListener( "click", e => {
@@ -8097,15 +8498,8 @@ class ColorInput extends Widget {
8097
8498
  {
8098
8499
  return;
8099
8500
  }
8100
- new ColorPicker( value, sampleContainer, {
8101
- colorModel: options.useRGB ? "RGB" : "Hex",
8102
- useAlpha,
8103
- onChange: ( color ) => {
8104
- this._fromColorPicker = true;
8105
- this.set( color.hex );
8106
- delete this._fromColorPicker;
8107
- }
8108
- } );
8501
+
8502
+ this._popover = new Popover( sampleContainer, [ this.picker ] );
8109
8503
  } );
8110
8504
 
8111
8505
  let colorSampleRGB = document.createElement( 'div' );
@@ -8173,8 +8567,8 @@ class RangeInput extends Widget {
8173
8567
  };
8174
8568
 
8175
8569
  this.onResize = ( rect ) => {
8176
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
8177
- container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth }px)`;
8570
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
8571
+ container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth })`;
8178
8572
  };
8179
8573
 
8180
8574
  const container = document.createElement( 'div' );
@@ -8287,8 +8681,8 @@ class NumberInput extends Widget {
8287
8681
  };
8288
8682
 
8289
8683
  this.onResize = ( rect ) => {
8290
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
8291
- container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth }px)`;
8684
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
8685
+ container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth })`;
8292
8686
  };
8293
8687
 
8294
8688
  var container = document.createElement( 'div' );
@@ -8523,8 +8917,8 @@ class Vector extends Widget {
8523
8917
  };
8524
8918
 
8525
8919
  this.onResize = ( rect ) => {
8526
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
8527
- container.style.width = `calc( 100% - ${ realNameWidth }px)`;
8920
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
8921
+ container.style.width = `calc( 100% - ${ realNameWidth })`;
8528
8922
  };
8529
8923
 
8530
8924
  const vectorInputs = [];
@@ -8877,8 +9271,8 @@ class OTPInput extends Widget {
8877
9271
  };
8878
9272
 
8879
9273
  this.onResize = ( rect ) => {
8880
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
8881
- container.style.width = `calc( 100% - ${ realNameWidth }px)`;
9274
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
9275
+ container.style.width = `calc( 100% - ${ realNameWidth })`;
8882
9276
  };
8883
9277
 
8884
9278
  this.disabled = options.disabled ?? false;
@@ -9023,8 +9417,8 @@ class Pad extends Widget {
9023
9417
  };
9024
9418
 
9025
9419
  this.onResize = ( rect ) => {
9026
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
9027
- container.style.width = `calc( 100% - ${ realNameWidth }px)`;
9420
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
9421
+ container.style.width = `calc( 100% - ${ realNameWidth })`;
9028
9422
  };
9029
9423
 
9030
9424
  var container = document.createElement( 'div' );
@@ -9143,8 +9537,8 @@ class Progress extends Widget {
9143
9537
  };
9144
9538
 
9145
9539
  this.onResize = ( rect ) => {
9146
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
9147
- container.style.width = `calc( 100% - ${ realNameWidth }px)`;
9540
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
9541
+ container.style.width = `calc( 100% - ${ realNameWidth })`;
9148
9542
  };
9149
9543
 
9150
9544
  const container = document.createElement('div');
@@ -9270,8 +9664,8 @@ class FileInput extends Widget {
9270
9664
  let read = options.read ?? true;
9271
9665
 
9272
9666
  this.onResize = ( rect ) => {
9273
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
9274
- input.style.width = `calc( 100% - ${ realNameWidth }px)`;
9667
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
9668
+ input.style.width = `calc( 100% - ${ realNameWidth })`;
9275
9669
  };
9276
9670
 
9277
9671
  // Create hidden input
@@ -9597,8 +9991,8 @@ class Table extends Widget {
9597
9991
  super( Widget.TABLE, name, null, options );
9598
9992
 
9599
9993
  this.onResize = ( rect ) => {
9600
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
9601
- container.style.width = `calc( 100% - ${ realNameWidth }px)`;
9994
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
9995
+ container.style.width = `calc( 100% - ${ realNameWidth })`;
9602
9996
  };
9603
9997
 
9604
9998
  const container = document.createElement('div');
@@ -10098,6 +10492,75 @@ class Table extends Widget {
10098
10492
 
10099
10493
  LX.Table = Table;
10100
10494
 
10495
+ /**
10496
+ * @class DatePicker
10497
+ * @description DatePicker Widget
10498
+ */
10499
+
10500
+ class DatePicker extends Widget {
10501
+
10502
+ constructor( name, dateString, callback, options = { } ) {
10503
+
10504
+ super( Widget.DATE, name, null, options );
10505
+
10506
+ if( options.today )
10507
+ {
10508
+ const date = new Date();
10509
+ dateString = `${ date.getDate() }/${ date.getMonth() + 1 }/${ date.getFullYear() }`;
10510
+ }
10511
+
10512
+ this.onGetValue = () => {
10513
+ return dateString;
10514
+ }
10515
+
10516
+ this.onSetValue = ( newValue, skipCallback, event ) => {
10517
+
10518
+ dateString = newValue;
10519
+
10520
+ this.calendar.fromDateString( newValue );
10521
+
10522
+ refresh( this.calendar.getFullDate() );
10523
+
10524
+ if( !skipCallback )
10525
+ {
10526
+ this._trigger( new IEvent( name, newValue, event ), callback );
10527
+ }
10528
+ }
10529
+
10530
+ this.onResize = ( rect ) => {
10531
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
10532
+ container.style.width = `calc( 100% - ${ realNameWidth })`;
10533
+ };
10534
+
10535
+ const container = document.createElement('div');
10536
+ container.className = "lexdate";
10537
+ this.root.appendChild( container );
10538
+
10539
+ this.calendar = new Calendar( dateString, { onChange: ( date ) => {
10540
+ this.set( `${ date.day }/${ date.month }/${ date.year }` )
10541
+ }, ...options });
10542
+
10543
+ const refresh = ( currentDate ) => {
10544
+ container.innerHTML = "";
10545
+ const calendarIcon = LX.makeIcon( "calendar" );
10546
+ const calendarButton = new Button( null, currentDate ?? "Pick a date", () => {
10547
+ this._popover = new Popover( calendarButton.root, ( popoverRoot ) => {
10548
+ popoverRoot.appendChild( this.calendar.root );
10549
+ } );
10550
+ }, { buttonClass: `flex flex-row px-3 ${ currentDate ? "" : "fg-tertiary" } justify-between` } );
10551
+
10552
+ calendarButton.root.querySelector( "button" ).appendChild( calendarIcon );
10553
+ container.appendChild( calendarButton.root );
10554
+ };
10555
+
10556
+ refresh( dateString ? this.calendar.getFullDate(): null );
10557
+
10558
+ doAsync( this.onResize.bind( this ) );
10559
+ }
10560
+ }
10561
+
10562
+ LX.DatePicker = DatePicker;
10563
+
10101
10564
  /**
10102
10565
  * @class Panel
10103
10566
  */
@@ -11244,6 +11707,23 @@ class Panel {
11244
11707
  const widget = new Table( name, data, options );
11245
11708
  return this._attachWidget( widget );
11246
11709
  }
11710
+
11711
+ /**
11712
+ * @method addDate
11713
+ * @param {String} name Widget name
11714
+ * @param {String} dateString
11715
+ * @param {Function} callback
11716
+ * @param {Object} options:
11717
+ * hideName: Don't use name as label [false]
11718
+ * today: Set current day as selected by default
11719
+ * untilToday: Allow dates only until current day
11720
+ * fromToday: Allow dates only from current day
11721
+ */
11722
+
11723
+ addDate( name, dateString, callback, options = { } ) {
11724
+ const widget = new DatePicker( name, dateString, callback, options );
11725
+ return this._attachWidget( widget );
11726
+ }
11247
11727
  }
11248
11728
 
11249
11729
  LX.Panel = Panel;
@@ -12349,12 +12829,6 @@ class CanvasCurve {
12349
12829
  if( o.xrange ) element.xrange = o.xrange;
12350
12830
  if( o.yrange ) element.yrange = o.yrange;
12351
12831
  if( o.smooth ) element.smooth = o.smooth;
12352
- var rect = canvas.parentElement.getBoundingClientRect();
12353
- if( canvas.parentElement.parentElement ) rect = canvas.parentElement.parentElement.getBoundingClientRect();
12354
- if( rect && canvas.width != rect.width && rect.width && rect.width < 1000 )
12355
- {
12356
- canvas.width = rect.width;
12357
- }
12358
12832
 
12359
12833
  var ctx = canvas.getContext( "2d" );
12360
12834
  ctx.setTransform( 1, 0, 0, 1, 0, 0 );
@@ -12689,12 +13163,6 @@ class CanvasDial {
12689
13163
  if( o.xrange ) element.xrange = o.xrange;
12690
13164
  if( o.yrange ) element.yrange = o.yrange;
12691
13165
  if( o.smooth ) element.smooth = o.smooth;
12692
- var rect = canvas.parentElement.getBoundingClientRect();
12693
- if( canvas.parentElement.parentElement ) rect = canvas.parentElement.parentElement.getBoundingClientRect();
12694
- if( rect && canvas.width != rect.width && rect.width && rect.width < 1000 )
12695
- {
12696
- canvas.width = rect.width;
12697
- }
12698
13166
 
12699
13167
  var ctx = canvas.getContext( "2d" );
12700
13168
  ctx.setTransform( 1, 0, 0, 1, 0, 0 );
@@ -14288,6 +14756,7 @@ LX.ICONS = {
14288
14756
  "sidebar": [512, 512, [], "regular", "M64 64h384a32 32 0 0 1 32 32v320a32 32 0 0 1-32 32H64a32 32 0 0 1-32-32V96a32 32 0 0 1 32-32zm128 0v384", null, "fill=none stroke-width=50 stroke-linejoin=round stroke-linecap=round"],
14289
14757
  "table-cells": [512, 512, [], "solid", "M64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l384 0c35.3 0 64-28.7 64-64l0-320c0-35.3-28.7-64-64-64L64 32zm88 64l0 64-88 0 0-64 88 0zm56 0l88 0 0 64-88 0 0-64zm240 0l0 64-88 0 0-64 88 0zM64 224l88 0 0 64-88 0 0-64zm232 0l0 64-88 0 0-64 88 0zm64 0l88 0 0 64-88 0 0-64zM152 352l0 64-88 0 0-64 88 0zm56 0l88 0 0 64-88 0 0-64zm240 0l0 64-88 0 0-64 88 0z"],
14290
14758
  "table-cells-large": [512, 512, [], "solid", "M448 96l0 128-160 0 0-128 160 0zm0 192l0 128-160 0 0-128 160 0zM224 224L64 224 64 96l160 0 0 128zM64 288l160 0 0 128L64 416l0-128zM64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l384 0c35.3 0 64-28.7 64-64l0-320c0-35.3-28.7-64-64-64L64 32z"],
14759
+ "frame": [24, 24, [], "solid", "M2 6h20M2 18h20M6 2v20M18 2v20", null, "fill=none stroke-width=2 stroke-linejoin=round stroke-linecap=round"],
14291
14760
  "lightbulb": [384, 512, [], "regular", "M297.2 248.9C311.6 228.3 320 203.2 320 176c0-70.7-57.3-128-128-128S64 105.3 64 176c0 27.2 8.4 52.3 22.8 72.9c3.7 5.3 8.1 11.3 12.8 17.7c0 0 0 0 0 0c12.9 17.7 28.3 38.9 39.8 59.8c10.4 19 15.7 38.8 18.3 57.5L109 384c-2.2-12-5.9-23.7-11.8-34.5c-9.9-18-22.2-34.9-34.5-51.8c0 0 0 0 0 0s0 0 0 0c-5.2-7.1-10.4-14.2-15.4-21.4C27.6 247.9 16 213.3 16 176C16 78.8 94.8 0 192 0s176 78.8 176 176c0 37.3-11.6 71.9-31.4 100.3c-5 7.2-10.2 14.3-15.4 21.4c0 0 0 0 0 0s0 0 0 0c-12.3 16.8-24.6 33.7-34.5 51.8c-5.9 10.8-9.6 22.5-11.8 34.5l-48.6 0c2.6-18.7 7.9-38.6 18.3-57.5c11.5-20.9 26.9-42.1 39.8-59.8c0 0 0 0 0 0s0 0 0 0s0 0 0 0c4.7-6.4 9-12.4 12.7-17.7zM192 128c-26.5 0-48 21.5-48 48c0 8.8-7.2 16-16 16s-16-7.2-16-16c0-44.2 35.8-80 80-80c8.8 0 16 7.2 16 16s-7.2 16-16 16zm0 384c-44.2 0-80-35.8-80-80l0-16 160 0 0 16c0 44.2-35.8 80-80 80z"],
14292
14761
  "flag": [448, 512, [], "regular", "M48 24C48 10.7 37.3 0 24 0S0 10.7 0 24L0 64 0 350.5 0 400l0 88c0 13.3 10.7 24 24 24s24-10.7 24-24l0-100 80.3-20.1c41.1-10.3 84.6-5.5 122.5 13.4c44.2 22.1 95.5 24.8 141.7 7.4l34.7-13c12.5-4.7 20.8-16.6 20.8-30l0-279.7c0-23-24.2-38-44.8-27.7l-9.6 4.8c-46.3 23.2-100.8 23.2-147.1 0c-35.1-17.6-75.4-22-113.5-12.5L48 52l0-28zm0 77.5l96.6-24.2c27-6.7 55.5-3.6 80.4 8.8c54.9 27.4 118.7 29.7 175 6.8l0 241.8-24.4 9.1c-33.7 12.6-71.2 10.7-103.4-5.4c-48.2-24.1-103.3-30.1-155.6-17.1L48 338.5l0-237z"],
14293
14762
  "newspaper": [512, 512, [], "regular", "M168 80c-13.3 0-24 10.7-24 24l0 304c0 8.4-1.4 16.5-4.1 24L440 432c13.3 0 24-10.7 24-24l0-304c0-13.3-10.7-24-24-24L168 80zM72 480c-39.8 0-72-32.2-72-72L0 112C0 98.7 10.7 88 24 88s24 10.7 24 24l0 296c0 13.3 10.7 24 24 24s24-10.7 24-24l0-304c0-39.8 32.2-72 72-72l272 0c39.8 0 72 32.2 72 72l0 304c0 39.8-32.2 72-72 72L72 480zM176 136c0-13.3 10.7-24 24-24l96 0c13.3 0 24 10.7 24 24l0 80c0 13.3-10.7 24-24 24l-96 0c-13.3 0-24-10.7-24-24l0-80zm200-24l32 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-32 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm0 80l32 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-32 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zM200 272l208 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-208 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm0 80l208 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-208 0c-13.3 0-24-10.7-24-24s10.7-24 24-24z"],