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.
package/build/lexgui.js CHANGED
@@ -12,7 +12,7 @@ console.warn( 'Script _build/lexgui.js_ is depracated and will be removed soon.
12
12
  */
13
13
 
14
14
  var LX = {
15
- version: "0.5.7",
15
+ version: "0.5.9",
16
16
  ready: false,
17
17
  components: [], // Specific pre-build components
18
18
  signals: {}, // Events and triggers
@@ -134,6 +134,56 @@ function stripHTML( html )
134
134
 
135
135
  LX.stripHTML = stripHTML;
136
136
 
137
+ /**
138
+ * @method parsePixelSize
139
+ * @description Parses any css size and returns a number of pixels
140
+ * @param {Number|String} size
141
+ * @param {Number} total
142
+ */
143
+ const parsePixelSize = ( size, total ) => {
144
+
145
+ if( size.constructor === Number ) { return size; } // Assuming pixels..
146
+
147
+ if( size.constructor === String )
148
+ {
149
+ const value = parseFloat( size );
150
+
151
+ if( size.endsWith( "px" ) ) { return value; } // String pixels
152
+ if( size.endsWith( '%' ) ) { return ( value / 100 ) * total; } // Percentage
153
+ if( size.endsWith( "rem" ) || size.endsWith( "em" ) ) { const rootFontSize = 16; /*parseFloat(getComputedStyle(document.documentElement).fontSize);*/ return value * rootFontSize; } // rem unit: assume 16px = 1rem
154
+ if( size.endsWith( "vw" ) ) { return ( value / 100 ) * window.innerWidth; } // wViewport units
155
+ if( size.endsWith( "vh" ) ) { return ( value / 100 ) * window.innerHeight; } // hViewport units
156
+
157
+ // Any CSS calc expression (e.g., "calc(30% - 4px)")
158
+ if( size.startsWith( "calc(" ) )
159
+ {
160
+ const expr = size.slice( 5, -1 );
161
+ const parts = expr.split( /([+\-])/ ); // ["30% ", "-", "4px"]
162
+ let result = 0;
163
+ let op = "+";
164
+ for( let part of parts )
165
+ {
166
+ part = part.trim();
167
+ if( part === "+" || part === "-" )
168
+ {
169
+ op = part;
170
+ }
171
+ else
172
+ {
173
+ let value = parsePixelSize( part, total );
174
+ result = ( op === "+" ) ? result + value : result - value;
175
+ }
176
+ }
177
+
178
+ return result;
179
+ }
180
+ }
181
+
182
+ throw( "Bad size format!" );
183
+ }
184
+
185
+ LX.parsePixelSize = parsePixelSize;
186
+
137
187
  /**
138
188
  * @method deepCopy
139
189
  * @description Create a deep copy with no references from an object
@@ -729,7 +779,7 @@ LX.makeKbd = makeKbd;
729
779
  * @description Gets an SVG element using one of LX.ICONS
730
780
  * @param {String} iconName
731
781
  * @param {Object} options
732
- * iconTitle
782
+ * title
733
783
  * extraClass
734
784
  * svgClass
735
785
  */
@@ -738,7 +788,7 @@ function makeIcon( iconName, options = { } )
738
788
  let data = LX.ICONS[ iconName ];
739
789
  console.assert( data, `No icon named _${ iconName }_` );
740
790
 
741
- const iconTitle = options.iconTitle;
791
+ const iconTitle = options.title;
742
792
  const iconClass = options.iconClass;
743
793
  const svgClass = options.svgClass;
744
794
 
@@ -1597,7 +1647,7 @@ function toast( title, description, options = {} )
1597
1647
  if( options.action )
1598
1648
  {
1599
1649
  const panel = new Panel();
1600
- panel.addButton(null, options.action.name ?? "Accept", options.action.callback.bind( this, toast ), { width: "auto", maxWidth: "150px", className: "right", buttonClass: "outline" });
1650
+ panel.addButton(null, options.action.name ?? "Accept", options.action.callback.bind( this, toast ), { width: "auto", maxWidth: "150px", className: "right", buttonClass: "border" });
1601
1651
  toast.appendChild( panel.root.childNodes[ 0 ] );
1602
1652
  }
1603
1653
 
@@ -1897,6 +1947,158 @@ LX.addSignal = addSignal;
1897
1947
  * DOM Elements
1898
1948
  */
1899
1949
 
1950
+ /**
1951
+ * @class Popover
1952
+ */
1953
+
1954
+ class Popover {
1955
+
1956
+ static activeElement = false;
1957
+
1958
+ constructor( trigger, content, options = {} ) {
1959
+
1960
+ console.assert( trigger, "Popover needs a DOM element as trigger!" );
1961
+
1962
+ if( Popover.activeElement )
1963
+ {
1964
+ Popover.activeElement.destroy();
1965
+ return;
1966
+ }
1967
+
1968
+ this._trigger = trigger;
1969
+ trigger.classList.add( "triggered" );
1970
+ trigger.active = this;
1971
+
1972
+ this._windowPadding = 4;
1973
+ this.side = options.side ?? "bottom";
1974
+ this.align = options.align ?? "center";
1975
+ this.avoidCollisions = options.avoidCollisions ?? true;
1976
+
1977
+ this.root = document.createElement( "div" );
1978
+ this.root.dataset["side"] = this.side;
1979
+ this.root.tabIndex = "1";
1980
+ this.root.className = "lexpopover";
1981
+ LX.root.appendChild( this.root );
1982
+
1983
+ this.root.addEventListener( "keydown", (e) => {
1984
+ if( e.key == "Escape" )
1985
+ {
1986
+ e.preventDefault();
1987
+ e.stopPropagation();
1988
+ this.destroy();
1989
+ }
1990
+ } )
1991
+
1992
+ if( content )
1993
+ {
1994
+ content = [].concat( content );
1995
+ content.forEach( e => {
1996
+ const domNode = e.root ?? e;
1997
+ this.root.appendChild( domNode );
1998
+ if( e.onPopover )
1999
+ {
2000
+ e.onPopover();
2001
+ }
2002
+ } );
2003
+ }
2004
+
2005
+ Popover.activeElement = this;
2006
+
2007
+ doAsync( () => {
2008
+ this._adjustPosition();
2009
+
2010
+ this.root.focus();
2011
+
2012
+ this._onClick = e => {
2013
+ if( e.target && ( this.root.contains( e.target ) || e.target == this._trigger ) )
2014
+ {
2015
+ return;
2016
+ }
2017
+ this.destroy();
2018
+ };
2019
+
2020
+ document.body.addEventListener( "mousedown", this._onClick, true );
2021
+ document.body.addEventListener( "focusin", this._onClick, true );
2022
+ }, 10 );
2023
+ }
2024
+
2025
+ destroy() {
2026
+
2027
+ this._trigger.classList.remove( "triggered" );
2028
+
2029
+ delete this._trigger.active;
2030
+
2031
+ document.body.removeEventListener( "click", this._onClick );
2032
+
2033
+ this.root.remove();
2034
+
2035
+ Popover.activeElement = null;
2036
+ }
2037
+
2038
+ _adjustPosition() {
2039
+
2040
+ const position = [ 0, 0 ];
2041
+
2042
+ // Place menu using trigger position and user options
2043
+ {
2044
+ const rect = this._trigger.getBoundingClientRect();
2045
+
2046
+ let alignWidth = true;
2047
+
2048
+ switch( this.side )
2049
+ {
2050
+ case "left":
2051
+ position[ 0 ] += ( rect.x - this.root.offsetWidth );
2052
+ alignWidth = false;
2053
+ break;
2054
+ case "right":
2055
+ position[ 0 ] += ( rect.x + rect.width );
2056
+ alignWidth = false;
2057
+ break;
2058
+ case "top":
2059
+ position[ 1 ] += ( rect.y - this.root.offsetHeight );
2060
+ alignWidth = true;
2061
+ break;
2062
+ case "bottom":
2063
+ position[ 1 ] += ( rect.y + rect.height );
2064
+ alignWidth = true;
2065
+ break;
2066
+ default:
2067
+ break;
2068
+ }
2069
+
2070
+ switch( this.align )
2071
+ {
2072
+ case "start":
2073
+ if( alignWidth ) { position[ 0 ] += rect.x; }
2074
+ else { position[ 1 ] += rect.y; }
2075
+ break;
2076
+ case "center":
2077
+ if( alignWidth ) { position[ 0 ] += ( rect.x + rect.width * 0.5 ) - this.root.offsetWidth * 0.5; }
2078
+ else { position[ 1 ] += ( rect.y + rect.height * 0.5 ) - this.root.offsetHeight * 0.5; }
2079
+ break;
2080
+ case "end":
2081
+ if( alignWidth ) { position[ 0 ] += rect.x - this.root.offsetWidth + rect.width; }
2082
+ else { position[ 1 ] += rect.y - this.root.offsetHeight + rect.height; }
2083
+ break;
2084
+ default:
2085
+ break;
2086
+ }
2087
+ }
2088
+
2089
+ if( this.avoidCollisions )
2090
+ {
2091
+ position[ 0 ] = LX.clamp( position[ 0 ], 0, window.innerWidth - this.root.offsetWidth - this._windowPadding );
2092
+ position[ 1 ] = LX.clamp( position[ 1 ], 0, window.innerHeight - this.root.offsetHeight - this._windowPadding );
2093
+ }
2094
+
2095
+ this.root.style.left = `${ position[ 0 ] }px`;
2096
+ this.root.style.top = `${ position[ 1 ] }px`;
2097
+ }
2098
+ };
2099
+
2100
+ LX.Popover = Popover;
2101
+
1900
2102
  /**
1901
2103
  * @class DropdownMenu
1902
2104
  */
@@ -2194,14 +2396,8 @@ class ColorPicker {
2194
2396
 
2195
2397
  static currentPicker = false;
2196
2398
 
2197
- constructor( hexValue, trigger, options = {} ) {
2198
-
2199
- console.assert( trigger, "ColorPicker needs a DOM element as trigger!" );
2399
+ constructor( hexValue, options = {} ) {
2200
2400
 
2201
- this._windowPadding = 4;
2202
- this.side = options.side ?? "bottom";
2203
- this.align = options.align ?? "center";
2204
- this.avoidCollisions = options.avoidCollisions ?? true;
2205
2401
  this.colorModel = options.colorModel ?? "Hex";
2206
2402
  this.useAlpha = options.useAlpha ?? false;
2207
2403
  this.callback = options.onChange;
@@ -2211,32 +2407,8 @@ class ColorPicker {
2211
2407
  console.warn( "Define a callback in _options.onChange_ to allow getting new Color values!" );
2212
2408
  }
2213
2409
 
2214
- if( ColorPicker.currentPicker )
2215
- {
2216
- ColorPicker.currentPicker.destroy();
2217
- return;
2218
- }
2219
-
2220
- this._trigger = trigger;
2221
- trigger.classList.add( "triggered" );
2222
- trigger.picker = this;
2223
-
2224
2410
  this.root = document.createElement( "div" );
2225
- this.root.tabIndex = "1";
2226
2411
  this.root.className = "lexcolorpicker";
2227
- this.root.dataset["side"] = this.side;
2228
- LX.root.appendChild( this.root );
2229
-
2230
- this.root.addEventListener( "keydown", (e) => {
2231
- if( e.key == "Escape" )
2232
- {
2233
- e.preventDefault();
2234
- e.stopPropagation();
2235
- this.destroy();
2236
- }
2237
- } )
2238
-
2239
- ColorPicker.currentPicker = this;
2240
2412
 
2241
2413
  this.markerHalfSize = 8;
2242
2414
  this.markerSize = this.markerHalfSize * 2;
@@ -2255,7 +2427,7 @@ class ColorPicker {
2255
2427
  this.intSatMarker.style.backgroundColor = this.currentColor.hex;
2256
2428
  this.colorPickerBackground.appendChild( this.intSatMarker );
2257
2429
 
2258
- doAsync( this._svToPosition.bind( this, this.currentColor.hsv.s, this.currentColor.hsv.v ) );
2430
+ let pickerRect = null;
2259
2431
 
2260
2432
  let innerMouseDown = e => {
2261
2433
  var doc = this.root.ownerDocument;
@@ -2271,15 +2443,15 @@ class ColorPicker {
2271
2443
  this.intSatMarker.style.top = currentTop + "px";
2272
2444
  this._positionToSv( currentLeft, currentTop );
2273
2445
  this._updateColorValue();
2446
+
2447
+ pickerRect = this.colorPickerBackground.getBoundingClientRect();
2274
2448
  }
2275
2449
 
2276
2450
  let innerMouseMove = e => {
2277
2451
  const dX = e.movementX;
2278
2452
  const dY = e.movementY;
2279
-
2280
- const rect = this.colorPickerBackground.getBoundingClientRect();
2281
- const mouseX = e.offsetX - rect.x;
2282
- const mouseY = e.offsetY - rect.y;
2453
+ const mouseX = e.x - pickerRect.x;
2454
+ const mouseY = e.y - pickerRect.y;
2283
2455
 
2284
2456
  if ( dX != 0 && ( mouseX >= 0 || dX < 0 ) && ( mouseX < this.colorPickerBackground.offsetWidth || dX > 0 ) )
2285
2457
  {
@@ -2334,11 +2506,6 @@ class ColorPicker {
2334
2506
  this.hueMarker.style.backgroundColor = `rgb(${ hueColor.css.r }, ${ hueColor.css.g }, ${ hueColor.css.b })`;
2335
2507
  this.colorPickerTracker.appendChild( this.hueMarker );
2336
2508
 
2337
- doAsync( () => {
2338
- const hueLeft = LX.remapRange( this.currentColor.hsv.h, 0, 360, 0, this.colorPickerTracker.offsetWidth - this.markerSize );
2339
- this.hueMarker.style.left = hueLeft + "px";
2340
- } );
2341
-
2342
2509
  const _fromHueX = ( hueX ) => {
2343
2510
  this.hueMarker.style.left = hueX + "px";
2344
2511
  this.currentColor.hsv.h = LX.remapRange( hueX, 0, this.colorPickerTracker.offsetWidth - this.markerSize, 0, 360 );
@@ -2349,6 +2516,8 @@ class ColorPicker {
2349
2516
  this._updateColorValue();
2350
2517
  };
2351
2518
 
2519
+ let hueTrackerRect = null;
2520
+
2352
2521
  let innerMouseDownHue = e => {
2353
2522
  const doc = this.root.ownerDocument;
2354
2523
  doc.addEventListener( 'mousemove', innerMouseMoveHue );
@@ -2359,15 +2528,15 @@ class ColorPicker {
2359
2528
 
2360
2529
  const hueX = clamp( e.offsetX - this.markerHalfSize, 0, this.colorPickerTracker.offsetWidth - this.markerSize );
2361
2530
  _fromHueX( hueX );
2531
+
2532
+ hueTrackerRect = this.colorPickerTracker.getBoundingClientRect();
2362
2533
  }
2363
2534
 
2364
2535
  let innerMouseMoveHue = e => {
2365
- let dX = e.movementX;
2366
-
2367
- const rect = this.colorPickerTracker.getBoundingClientRect();
2368
- const mouseX = e.offsetX - rect.x;
2536
+ const dX = e.movementX;
2537
+ const mouseX = e.x - hueTrackerRect.x;
2369
2538
 
2370
- if ( dX != 0 && ( mouseX >= 0 || dX < 0 ) && ( mouseX < this.colorPickerTracker.offsetWidth || dX > 0 ) )
2539
+ if ( dX != 0 && ( mouseX >= this.markerHalfSize || dX < 0 ) && ( mouseX < ( this.colorPickerTracker.offsetWidth - this.markerHalfSize ) || dX > 0 ) )
2371
2540
  {
2372
2541
  const hueX = LX.clamp( parseInt( this.hueMarker.style.left ) + dX, 0, this.colorPickerTracker.offsetWidth - this.markerSize );
2373
2542
  _fromHueX( hueX )
@@ -2399,11 +2568,6 @@ class ColorPicker {
2399
2568
  this.alphaMarker.style.backgroundColor = `rgb(${ this.currentColor.css.r }, ${ this.currentColor.css.g }, ${ this.currentColor.css.b },${ this.currentColor.css.a })`;
2400
2569
  this.alphaTracker.appendChild( this.alphaMarker );
2401
2570
 
2402
- doAsync( () => {
2403
- const alphaLeft = LX.remapRange( this.currentColor.hsv.a, 0, 1, 0, this.alphaTracker.offsetWidth - this.markerSize );
2404
- this.alphaMarker.style.left = alphaLeft + "px";
2405
- } );
2406
-
2407
2571
  const _fromAlphaX = ( alphaX ) => {
2408
2572
  this.alphaMarker.style.left = alphaX + "px";
2409
2573
  this.currentColor.hsv.a = LX.remapRange( alphaX, 0, this.alphaTracker.offsetWidth - this.markerSize, 0, 1 );
@@ -2412,6 +2576,8 @@ class ColorPicker {
2412
2576
  this.alphaMarker.style.backgroundColor = `rgb(${ this.currentColor.css.r }, ${ this.currentColor.css.g }, ${ this.currentColor.css.b },${ this.currentColor.css.a })`;
2413
2577
  };
2414
2578
 
2579
+ let alphaTrackerRect = null;
2580
+
2415
2581
  let innerMouseDownAlpha = e => {
2416
2582
  const doc = this.root.ownerDocument;
2417
2583
  doc.addEventListener( 'mousemove', innerMouseMoveAlpha );
@@ -2421,15 +2587,14 @@ class ColorPicker {
2421
2587
  e.stopPropagation();
2422
2588
  const alphaX = clamp( e.offsetX - this.markerHalfSize, 0, this.alphaTracker.offsetWidth - this.markerSize );
2423
2589
  _fromAlphaX( alphaX );
2590
+ alphaTrackerRect = this.alphaTracker.getBoundingClientRect();
2424
2591
  }
2425
2592
 
2426
2593
  let innerMouseMoveAlpha = e => {
2427
- let dX = e.movementX;
2428
-
2429
- const rect = this.alphaTracker.getBoundingClientRect();
2430
- const mouseX = e.offsetX - rect.x;
2594
+ const dX = e.movementX;
2595
+ const mouseX = e.x - alphaTrackerRect.x;
2431
2596
 
2432
- if ( dX != 0 && ( mouseX >= 0 || dX < 0 ) && ( mouseX < this.alphaTracker.offsetWidth || dX > 0 ) )
2597
+ if ( dX != 0 && ( mouseX >= this.markerHalfSize || dX < 0 ) && ( mouseX < ( this.alphaTracker.offsetWidth - this.markerHalfSize ) || dX > 0 ) )
2433
2598
  {
2434
2599
  const alphaX = LX.clamp( parseInt( this.alphaMarker.style.left ) + dX, 0, this.alphaTracker.offsetWidth - this.markerSize );
2435
2600
  _fromAlphaX( alphaX );
@@ -2480,22 +2645,76 @@ class ColorPicker {
2480
2645
 
2481
2646
  this._updateColorValue( hexValue, true );
2482
2647
 
2483
- doAsync( () => {
2484
- this._adjustPosition();
2648
+ doAsync( this._placeMarkers.bind( this ) );
2485
2649
 
2486
- this.root.focus();
2650
+ this.onPopover = this._placeMarkers.bind( this );
2651
+ }
2487
2652
 
2488
- this._onClick = e => {
2489
- if( e.target && ( this.root.contains( e.target ) || e.target == this._trigger ) )
2490
- {
2491
- return;
2492
- }
2493
- this.destroy();
2494
- };
2653
+ _placeMarkers() {
2495
2654
 
2496
- document.body.addEventListener( "mousedown", this._onClick, true );
2497
- document.body.addEventListener( "focusin", this._onClick, true );
2498
- }, 10 );
2655
+ this._svToPosition( this.currentColor.hsv.s, this.currentColor.hsv.v );
2656
+
2657
+ const hueLeft = LX.remapRange( this.currentColor.hsv.h, 0, 360, 0, this.colorPickerTracker.offsetWidth - this.markerSize );
2658
+ this.hueMarker.style.left = hueLeft + "px";
2659
+
2660
+ if( this.useAlpha )
2661
+ {
2662
+ const alphaLeft = LX.remapRange( this.currentColor.hsv.a, 0, 1, 0, this.alphaTracker.offsetWidth - this.markerSize );
2663
+ this.alphaMarker.style.left = alphaLeft + "px";
2664
+ }
2665
+ }
2666
+
2667
+ _svToPosition( s, v ) {
2668
+ this.intSatMarker.style.left = `${ LX.remapRange( s, 0, 1, -this.markerHalfSize, this.colorPickerBackground.offsetWidth - this.markerHalfSize ) }px`;
2669
+ this.intSatMarker.style.top = `${ LX.remapRange( 1 - v, 0, 1, -this.markerHalfSize, this.colorPickerBackground.offsetHeight - this.markerHalfSize ) }px`
2670
+ }
2671
+
2672
+ _positionToSv( left, top ) {
2673
+ this.currentColor.hsv.s = LX.remapRange( left, -this.markerHalfSize, this.colorPickerBackground.offsetWidth - this.markerHalfSize, 0, 1 );
2674
+ this.currentColor.hsv.v = 1 - LX.remapRange( top, -this.markerHalfSize, this.colorPickerBackground.offsetHeight - this.markerHalfSize, 0, 1 );
2675
+ }
2676
+
2677
+ _updateColorValue( newHexValue, skipCallback = false ) {
2678
+
2679
+ this.currentColor.set( newHexValue ?? this.currentColor.hsv );
2680
+
2681
+ if( this.callback && !skipCallback )
2682
+ {
2683
+ this.callback( this.currentColor );
2684
+ }
2685
+
2686
+ this.intSatMarker.style.backgroundColor = this.currentColor.hex;
2687
+
2688
+ if( this.useAlpha )
2689
+ {
2690
+ this.alphaTracker.style.color = `rgb(${ this.currentColor.css.r }, ${ this.currentColor.css.g }, ${ this.currentColor.css.b })`;
2691
+ }
2692
+
2693
+ const toFixed = ( s, n = 2) => { return s.toFixed( n ).replace( /([0-9]+(\.[0-9]+[1-9])?)(\.?0+$)/, '$1' ) };
2694
+
2695
+ if( this.colorModel == "CSS" )
2696
+ {
2697
+ const { r, g, b, a } = this.currentColor.css;
2698
+ this.labelWidget.set( `rgb${ this.useAlpha ? 'a' : '' }(${ r },${ g },${ b }${ this.useAlpha ? ',' + toFixed( a ) : '' })` );
2699
+ }
2700
+ else if( this.colorModel == "Hex" )
2701
+ {
2702
+ this.labelWidget.set( ( this.useAlpha ? this.currentColor.hex : this.currentColor.hex.substr( 0, 7 ) ).toUpperCase() );
2703
+ }
2704
+ else if( this.colorModel == "HSV" )
2705
+ {
2706
+ const { h, s, v, a } = this.currentColor.hsv;
2707
+ const components = [ Math.floor( h ) + 'º', Math.floor( s * 100 ) + '%', Math.floor( v * 100 ) + '%' ];
2708
+ if( this.useAlpha ) components.push( toFixed( a ) );
2709
+ this.labelWidget.set( components.join( ' ' ) );
2710
+ }
2711
+ else // RGB
2712
+ {
2713
+ const { r, g, b, a } = this.currentColor.rgb;
2714
+ const components = [ toFixed( r ), toFixed( g ), toFixed( b ) ];
2715
+ if( this.useAlpha ) components.push( toFixed( a ) );
2716
+ this.labelWidget.set( components.join( ' ' ) );
2717
+ }
2499
2718
  }
2500
2719
 
2501
2720
  fromHexColor( hexColor ) {
@@ -2510,139 +2729,260 @@ class ColorPicker {
2510
2729
  this.hueMarker.style.backgroundColor = this.colorPickerBackground.style.backgroundColor = `rgb(${ hueColor.css.r }, ${ hueColor.css.g }, ${ hueColor.css.b })`;
2511
2730
  this.hueMarker.style.left = LX.remapRange( h, 0, 360, -this.markerHalfSize, this.colorPickerTracker.offsetWidth - this.markerHalfSize ) + "px";
2512
2731
 
2513
- this._updateColorValue( hexColor );
2732
+ this._updateColorValue( hexColor );
2733
+ }
2734
+ };
2735
+
2736
+ LX.ColorPicker = ColorPicker;
2737
+
2738
+ class Calendar {
2739
+
2740
+ /**
2741
+ * @constructor Calendar
2742
+ * @param {String} dateString D/M/Y
2743
+ * @param {Object} options
2744
+ * onChange: Function to call on date changes
2745
+ */
2746
+
2747
+ constructor( dateString, options = {} ) {
2748
+
2749
+ this.root = LX.makeContainer( ["256px", "auto"], "border p-3 bg-primary rounded-lg text-md" );
2750
+
2751
+ this.onChange = options.onChange;
2752
+ this.untilToday = options.untilToday;
2753
+ this.fromToday = options.fromToday;
2754
+
2755
+ if( dateString )
2756
+ {
2757
+ this.fromDateString( dateString );
2758
+ }
2759
+ else
2760
+ {
2761
+ const date = new Date();
2762
+ this.month = date.getMonth() + 1;
2763
+ this.year = date.getFullYear();
2764
+ this.fromMonthYear( this.month, this.year );
2765
+ }
2766
+ }
2767
+
2768
+ _getCurrentDate() {
2769
+ return {
2770
+ day: this.day,
2771
+ month: this.month,
2772
+ year: this.year,
2773
+ fullDate: this.getFullDate()
2774
+ }
2775
+ }
2776
+
2777
+ _previousMonth() {
2778
+
2779
+ this.month = Math.max( 0, this.month - 1 );
2780
+
2781
+ if( this.month == 0 )
2782
+ {
2783
+ this.month = 12;
2784
+ this.year--;
2785
+ }
2786
+
2787
+ this.fromMonthYear( this.month, this.year );
2788
+ }
2789
+
2790
+ _nextMonth() {
2791
+
2792
+ this.month = Math.min( this.month + 1, 12 );
2793
+
2794
+ if( this.month == 12 )
2795
+ {
2796
+ this.month = 0;
2797
+ this.year++;
2798
+ }
2799
+
2800
+ this.fromMonthYear( this.month, this.year );
2801
+ }
2802
+
2803
+ refresh() {
2804
+
2805
+ this.root.innerHTML = "";
2806
+
2807
+ // Header
2808
+ {
2809
+ const header = LX.makeContainer( ["100%", "auto"], "flex flex-row p-1", "", this.root );
2810
+
2811
+ const prevMonthIcon = LX.makeIcon( "left", { title: "Previous Month", iconClass: "border p-1 rounded hover:bg-secondary", svgClass: "sm" } );
2812
+ header.appendChild( prevMonthIcon );
2813
+ prevMonthIcon.addEventListener( "click", () => {
2814
+ this._previousMonth();
2815
+ } );
2816
+
2817
+ const monthYearLabel = LX.makeContainer( ["100%", "auto"], "text-center font-medium select-none", `${ this.monthName } ${ this.year }`, header );
2818
+
2819
+ const nextMonthIcon = LX.makeIcon( "right", { title: "Next Month", iconClass: "border p-1 rounded hover:bg-secondary", svgClass: "sm" } );
2820
+ header.appendChild( nextMonthIcon );
2821
+ nextMonthIcon.addEventListener( "click", () => {
2822
+ this._nextMonth();
2823
+ } );
2824
+ }
2825
+
2826
+ // Body
2827
+ {
2828
+ const daysTable = document.createElement( "table" );
2829
+ daysTable.className = "w-full";
2830
+ this.root.appendChild( daysTable );
2831
+
2832
+ // Table Head
2833
+ {
2834
+ const head = document.createElement( 'thead' );
2835
+ daysTable.appendChild( head );
2836
+
2837
+ const hrow = document.createElement( 'tr' );
2838
+
2839
+ for( const headData of [ "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su" ] )
2840
+ {
2841
+ const th = document.createElement( 'th' );
2842
+ th.className = "fg-tertiary text-sm font-normal select-none";
2843
+ th.innerHTML = `<span>${ headData }</span>`;
2844
+ hrow.appendChild( th );
2845
+ }
2846
+
2847
+ head.appendChild( hrow );
2848
+ }
2849
+
2850
+ // Table Body
2851
+ {
2852
+ const body = document.createElement( 'tbody' );
2853
+ daysTable.appendChild( body );
2854
+
2855
+ for( let week = 0; week < 6; week++ )
2856
+ {
2857
+ const hrow = document.createElement( 'tr' );
2858
+ const weekDays = this.calendarDays.slice( week * 7, week * 7 + 7 );
2859
+
2860
+ for( const dayData of weekDays )
2861
+ {
2862
+ const th = document.createElement( 'th' );
2863
+ th.className = "leading-loose font-normal rounded select-none cursor-pointer";
2864
+
2865
+ const dayDate = new Date( `${ this.month }/${ dayData.day }/${ this.year }` );
2866
+ const date = new Date();
2867
+ const beforeToday = this.untilToday ? ( dayDate.getTime() < date.getTime() ) : true;
2868
+ const afterToday = this.fromToday ? ( dayDate.getTime() > date.getTime() ) : true;
2869
+ const selectable = dayData.currentMonth && beforeToday && afterToday;
2870
+
2871
+ if( this.currentDate && ( dayData.day == this.currentDate.day ) && ( this.month == this.currentDate.month )
2872
+ && ( this.year == this.currentDate.year ) && dayData.currentMonth )
2873
+ {
2874
+ th.className += ` bg-contrast fg-contrast`;
2875
+ }
2876
+ else
2877
+ {
2878
+ th.className += ` ${ selectable ? "fg-primary" : "fg-tertiary" } hover:bg-secondary`;
2879
+ }
2880
+
2881
+ th.innerHTML = `<span>${ dayData.day }</span>`;
2882
+ hrow.appendChild( th );
2883
+
2884
+ if( selectable )
2885
+ {
2886
+ th.addEventListener( "click", () => {
2887
+ this.day = dayData.day;
2888
+ this.currentDate = this._getCurrentDate();
2889
+ if( this.onChange )
2890
+ {
2891
+ this.onChange( this.currentDate );
2892
+ }
2893
+ } );
2894
+ }
2895
+ }
2896
+
2897
+ body.appendChild( hrow );
2898
+ }
2899
+ }
2900
+ }
2514
2901
  }
2515
2902
 
2516
- destroy() {
2517
-
2518
- this._trigger.classList.remove( "triggered" );
2903
+ fromDateString( dateString ) {
2519
2904
 
2520
- delete this._trigger.picker;
2905
+ const tokens = dateString.split( '/' );
2521
2906
 
2522
- document.body.removeEventListener( "mousedown", this._onClick, true );
2523
- document.body.removeEventListener( "focusin", this._onClick, true );
2907
+ this.day = parseInt( tokens[ 0 ] );
2908
+ this.month = parseInt( tokens[ 1 ] );
2909
+ this.year = parseInt( tokens[ 2 ] );
2524
2910
 
2525
- LX.root.querySelectorAll( ".lexcolorpicker" ).forEach( m => { m.remove(); } );
2911
+ this.currentDate = this._getCurrentDate();
2526
2912
 
2527
- ColorPicker.currentPicker = null;
2913
+ this.fromMonthYear( this.month, this.year );
2528
2914
  }
2529
2915
 
2530
- _svToPosition( s, v ) {
2531
- this.intSatMarker.style.left = `${ LX.remapRange( s, 0, 1, -this.markerHalfSize, this.colorPickerBackground.offsetWidth - this.markerHalfSize ) }px`;
2532
- this.intSatMarker.style.top = `${ LX.remapRange( 1 - v, 0, 1, -this.markerHalfSize, this.colorPickerBackground.offsetHeight - this.markerHalfSize ) }px`
2533
- };
2916
+ fromMonthYear( month, year ) {
2534
2917
 
2535
- _positionToSv( left, top ) {
2536
- this.currentColor.hsv.s = LX.remapRange( left, -this.markerHalfSize, this.colorPickerBackground.offsetWidth - this.markerHalfSize, 0, 1 );
2537
- this.currentColor.hsv.v = 1 - LX.remapRange( top, -this.markerHalfSize, this.colorPickerBackground.offsetHeight - this.markerHalfSize, 0, 1 );
2538
- };
2918
+ // Month is 0-based (0 = January, ... 11 = December)
2919
+ month--;
2539
2920
 
2540
- _updateColorValue( newHexValue, skipCallback = false ) {
2921
+ year = year ?? new Date().getFullYear();
2541
2922
 
2542
- this.currentColor.set( newHexValue ?? this.currentColor.hsv );
2923
+ const weekDay = new Date( year, month, 1 ).getDay();
2924
+ const firstDay = weekDay === 0 ? 6 : weekDay - 1; // 0 = Monday, 1 = Tuesday...
2925
+ const daysInMonth = new Date( year, month + 1, 0 ).getDate();
2543
2926
 
2544
- if( this.callback && !skipCallback )
2545
- {
2546
- this.callback( this.currentColor );
2547
- }
2927
+ // Previous month
2928
+ const prevMonth = month === 0 ? 11 : month - 1;
2929
+ const prevYear = month === 0 ? year - 1 : year;
2930
+ const daysInPrevMonth = new Date( prevYear, prevMonth + 1, 0 ).getDate();
2548
2931
 
2549
- this.intSatMarker.style.backgroundColor = this.currentColor.hex;
2932
+ // Prepare full grid (up to 6 weeks = 42 days)
2933
+ const calendarDays = [];
2550
2934
 
2551
- if( this.useAlpha )
2935
+ // Fill in days from previous month
2936
+ for( let i = firstDay - 1; i >= 0; i--)
2552
2937
  {
2553
- this.alphaTracker.style.color = `rgb(${ this.currentColor.css.r }, ${ this.currentColor.css.g }, ${ this.currentColor.css.b })`;
2938
+ calendarDays.push( { day: daysInPrevMonth - i, currentMonth: false } );
2554
2939
  }
2555
2940
 
2556
- const toFixed = ( s, n = 2) => { return s.toFixed( n ).replace( /([0-9]+(\.[0-9]+[1-9])?)(\.?0+$)/, '$1' ) };
2557
-
2558
- if( this.colorModel == "CSS" )
2941
+ // Fill in current month days
2942
+ for ( let i = 1; i <= daysInMonth; i++ )
2559
2943
  {
2560
- const { r, g, b, a } = this.currentColor.css;
2561
- this.labelWidget.set( `rgb${ this.useAlpha ? 'a' : '' }(${ r },${ g },${ b }${ this.useAlpha ? ',' + toFixed( a ) : '' })` );
2562
- }
2563
- else if( this.colorModel == "Hex" )
2564
- {
2565
- this.labelWidget.set( ( this.useAlpha ? this.currentColor.hex : this.currentColor.hex.substr( 0, 7 ) ).toUpperCase() );
2566
- }
2567
- else if( this.colorModel == "HSV" )
2568
- {
2569
- const { h, s, v, a } = this.currentColor.hsv;
2570
- const components = [ Math.floor( h ) + 'º', Math.floor( s * 100 ) + '%', Math.floor( v * 100 ) + '%' ];
2571
- if( this.useAlpha ) components.push( toFixed( a ) );
2572
- this.labelWidget.set( components.join( ' ' ) );
2944
+ calendarDays.push( { day: i, currentMonth: true } );
2573
2945
  }
2574
- else // RGB
2946
+
2947
+ // Fill in next month days to complete the grid (if needed)
2948
+ const remaining = 42 - calendarDays.length;
2949
+ for( let i = 1; i <= remaining; i++ )
2575
2950
  {
2576
- const { r, g, b, a } = this.currentColor.rgb;
2577
- const components = [ toFixed( r ), toFixed( g ), toFixed( b ) ];
2578
- if( this.useAlpha ) components.push( toFixed( a ) );
2579
- this.labelWidget.set( components.join( ' ' ) );
2951
+ calendarDays.push( { day: i, currentMonth: false } );
2580
2952
  }
2581
- };
2582
-
2583
- _adjustPosition() {
2584
-
2585
- const position = [ 0, 0 ];
2586
2953
 
2587
- // Place menu using trigger position and user options
2588
- {
2589
- const rect = this._trigger.getBoundingClientRect();
2954
+ this.monthName = this.getMonthName( month );
2955
+ this.firstDay = firstDay;
2956
+ this.daysInMonth = daysInMonth;
2957
+ this.calendarDays = calendarDays;
2590
2958
 
2591
- let alignWidth = true;
2959
+ this.refresh();
2960
+ }
2592
2961
 
2593
- switch( this.side )
2594
- {
2595
- case "left":
2596
- position[ 0 ] += ( rect.x - this.root.offsetWidth );
2597
- alignWidth = false;
2598
- break;
2599
- case "right":
2600
- position[ 0 ] += ( rect.x + rect.width );
2601
- alignWidth = false;
2602
- break;
2603
- case "top":
2604
- position[ 1 ] += ( rect.y - this.root.offsetHeight );
2605
- alignWidth = true;
2606
- break;
2607
- case "bottom":
2608
- position[ 1 ] += ( rect.y + rect.height );
2609
- alignWidth = true;
2610
- break;
2611
- default:
2612
- break;
2613
- }
2962
+ getMonthName( monthIndex, locale = "en-US" ) {
2963
+ const formatter = new Intl.DateTimeFormat( locale, { month: "long" } );
2964
+ return formatter.format( new Date( 2000, monthIndex, 1 ) );
2965
+ }
2614
2966
 
2615
- switch( this.align )
2616
- {
2617
- case "start":
2618
- if( alignWidth ) { position[ 0 ] += rect.x; }
2619
- else { position[ 1 ] += rect.y; }
2620
- break;
2621
- case "center":
2622
- if( alignWidth ) { position[ 0 ] += ( rect.x + rect.width * 0.5 ) - this.root.offsetWidth * 0.5; }
2623
- else { position[ 1 ] += ( rect.y + rect.height * 0.5 ) - this.root.offsetHeight * 0.5; }
2624
- break;
2625
- case "end":
2626
- if( alignWidth ) { position[ 0 ] += rect.x - this.root.offsetWidth + rect.width; }
2627
- else { position[ 1 ] += rect.y - this.root.offsetHeight + rect.height; }
2628
- break;
2629
- default:
2630
- break;
2631
- }
2632
- }
2967
+ getFullDate() {
2968
+ return `${ this.monthName } ${ this.day }${ this._getOrdinalSuffix( this.day ) }, ${ this.year }`;
2969
+ }
2633
2970
 
2634
- if( this.avoidCollisions )
2971
+ _getOrdinalSuffix( day ) {
2972
+ if ( day > 3 && day < 21 ) return "th";
2973
+ switch ( day % 10 )
2635
2974
  {
2636
- position[ 0 ] = LX.clamp( position[ 0 ], 0, window.innerWidth - this.root.offsetWidth - this._windowPadding );
2637
- position[ 1 ] = LX.clamp( position[ 1 ], 0, window.innerHeight - this.root.offsetHeight - this._windowPadding );
2975
+ case 1: return "st";
2976
+ case 2: return "nd";
2977
+ case 3: return "rd";
2978
+ default: return "th";
2638
2979
  }
2639
-
2640
- this.root.style.left = `${ position[ 0 ] }px`;
2641
- this.root.style.top = `${ position[ 1 ] }px`;
2642
2980
  }
2643
- };
2981
+ }
2644
2982
 
2645
- LX.ColorPicker = ColorPicker;
2983
+ LX.Calendar = Calendar;
2984
+
2985
+ /* Layout Classes */
2646
2986
 
2647
2987
  class Area {
2648
2988
 
@@ -2867,7 +3207,7 @@ class Area {
2867
3207
  * @method split
2868
3208
  * @param {Object} options
2869
3209
  * type: Split mode (horizontal, vertical) ["horizontal"]
2870
- * sizes: Size of each new area (Array) ["50%", "50%"]
3210
+ * sizes: CSS Size of each new area (Array) ["50%", "50%"]
2871
3211
  * resize: Allow area manual resizing [true]
2872
3212
  * sizes: "Allow the area to be minimized [false]
2873
3213
  */
@@ -2882,11 +3222,12 @@ class Area {
2882
3222
  this.root = this.sections[ 1 ].root;
2883
3223
  }
2884
3224
 
2885
- const type = options.type || "horizontal";
3225
+ const type = options.type ?? "horizontal";
2886
3226
  const sizes = options.sizes || [ "50%", "50%" ];
2887
3227
  const auto = (options.sizes === 'auto') || ( options.sizes && options.sizes[ 0 ] == "auto" && options.sizes[ 1 ] == "auto" );
2888
3228
 
2889
- if( !sizes[ 1 ] )
3229
+ // Secondary area fills space
3230
+ if( !sizes[ 1 ] || ( sizes[ 0 ] != "auto" && sizes[ 1 ] == "auto" ) )
2890
3231
  {
2891
3232
  let size = sizes[ 0 ];
2892
3233
  let margin = options.top ? options.top : 0;
@@ -2899,17 +3240,13 @@ class Area {
2899
3240
  sizes[ 1 ] = "calc( 100% - " + size + " )";
2900
3241
  }
2901
3242
 
2902
- // Create areas
2903
- let area1 = new Area( { skipAppend: true, className: "split" + ( options.menubar || options.sidebar ? "" : " origin" ) } );
2904
- let area2 = new Area( { skipAppend: true, className: "split" } );
2905
-
2906
- area1.parentArea = this;
2907
- area2.parentArea = this;
2908
-
2909
3243
  let minimizable = options.minimizable ?? false;
2910
3244
  let resize = ( options.resize ?? true ) || minimizable;
3245
+ let fixedSize = options.fixedSize ?? !resize;
3246
+ let splitbarOffset = 0;
3247
+ let primarySize = [];
3248
+ let secondarySize = [];
2911
3249
 
2912
- let data = "0px";
2913
3250
  this.offset = 0;
2914
3251
 
2915
3252
  if( resize )
@@ -2929,93 +3266,104 @@ class Area {
2929
3266
 
2930
3267
  this.splitBar.addEventListener( 'mousedown', innerMouseDown );
2931
3268
 
2932
- data = ( LX.DEFAULT_SPLITBAR_SIZE / 2 ) + "px"; // updates
2933
-
2934
- // Being minimizable means it's also resizeable!
2935
- if( minimizable )
2936
- {
2937
- this.splitExtended = false;
2938
-
2939
- // Keep state of the animation when ends...
2940
- area2.root.addEventListener('animationend', e => {
2941
- const opacity = getComputedStyle( area2.root ).opacity;
2942
- area2.root.classList.remove( e.animationName + "-" + type );
2943
- area2.root.style.opacity = opacity;
2944
- flushCss(area2.root);
2945
- });
2946
-
2947
- this.splitBar.addEventListener("contextmenu", e => {
2948
- e.preventDefault();
2949
- addContextMenu(null, e, c => {
2950
- c.add("Extend", { disabled: this.splitExtended, callback: () => { this.extend() } });
2951
- c.add("Reduce", { disabled: !this.splitExtended, callback: () => { this.reduce() } });
2952
- });
2953
- });
2954
- }
3269
+ splitbarOffset = ( LX.DEFAULT_SPLITBAR_SIZE / 2 ); // updates
2955
3270
  }
2956
3271
 
2957
3272
  if( type == "horizontal" )
2958
3273
  {
2959
- let width1 = sizes[ 0 ],
2960
- width2 = sizes[ 1 ];
3274
+ this.root.style.display = "flex";
2961
3275
 
2962
- if( width1.constructor == Number )
3276
+ if( !fixedSize )
2963
3277
  {
2964
- width1 += "px";
2965
- }
3278
+ const parentWidth = this.root.offsetWidth;
3279
+ const leftPx = parsePixelSize( sizes[ 0 ], parentWidth );
3280
+ const rightPx = parsePixelSize( sizes[ 1 ], parentWidth );
3281
+ const leftPercent = ( leftPx / parentWidth ) * 100;
3282
+ const rightPercent = ( rightPx / parentWidth ) * 100;
2966
3283
 
2967
- if( width2.constructor == Number )
3284
+ // Style using percentages
3285
+ primarySize[ 0 ] = `calc(${ leftPercent }% - ${ splitbarOffset }px)`;
3286
+ secondarySize[ 0 ] = `calc(${ rightPercent }% - ${ splitbarOffset }px)`;
3287
+ }
3288
+ else
2968
3289
  {
2969
- width2 += "px";
3290
+ primarySize[ 0 ] = `calc(${ sizes[ 0 ] } - ${ splitbarOffset }px)`;
3291
+ secondarySize[ 0 ] = `calc(${ sizes[ 1 ] } - ${ splitbarOffset }px)`;
2970
3292
  }
2971
3293
 
2972
- area1.root.style.width = "calc( " + width1 + " - " + data + " )";
2973
- area1.root.style.height = "calc(100% - 0px)";
2974
- area2.root.style.width = "calc( " + width2 + " - " + data + " )";
2975
- area2.root.style.height = "calc(100% - 0px)";
2976
- this.root.style.display = "flex";
3294
+ primarySize[ 1 ] = "100%";
3295
+ secondarySize[ 1 ] = "100%";
2977
3296
  }
2978
3297
  else // vertical
2979
3298
  {
2980
- area1.root.style.width = "100%";
2981
- area2.root.style.width = "100%";
2982
-
2983
3299
  if( auto )
2984
3300
  {
2985
- area1.root.style.height = "auto";
2986
-
2987
- // Listen resize event on first area
2988
- const resizeObserver = new ResizeObserver( entries => {
2989
- for ( const entry of entries )
2990
- {
2991
- const size = entry.target.getComputedSize();
2992
- area2.root.style.height = "calc(100% - " + ( size.height ) + "px )";
2993
- }
2994
- });
3301
+ primarySize[ 1 ] = "auto";
3302
+ }
3303
+ else if( !fixedSize )
3304
+ {
3305
+ const parentHeight = this.root.offsetHeight;
3306
+ const topPx = parsePixelSize( sizes[ 0 ], parentHeight );
3307
+ const bottomPx = parsePixelSize( sizes[ 1 ], parentHeight );
3308
+ const topPercent = ( topPx / parentHeight ) * 100;
3309
+ const bottomPercent = ( bottomPx / parentHeight ) * 100;
2995
3310
 
2996
- resizeObserver.observe( area1.root );
3311
+ primarySize[ 1 ] = ( sizes[ 0 ] == "auto" ? "auto" : `calc(${ topPercent }% - ${ splitbarOffset }px)`);
3312
+ secondarySize[ 1 ] = ( sizes[ 1 ] == "auto" ? "auto" : `calc(${ bottomPercent }% - ${ splitbarOffset }px)`);
2997
3313
  }
2998
3314
  else
2999
3315
  {
3000
- let height1 = sizes[ 0 ],
3001
- height2 = sizes[ 1 ];
3316
+ primarySize[ 1 ] = ( sizes[ 0 ] == "auto" ? "auto" : `calc(${ sizes[ 0 ] } - ${ splitbarOffset }px)`);
3317
+ secondarySize[ 1 ] = ( sizes[ 1 ] == "auto" ? "auto" : `calc(${ sizes[ 1 ] } - ${ splitbarOffset }px)`);
3318
+ }
3002
3319
 
3003
- if( height1.constructor == Number )
3004
- {
3005
- height1 += "px";
3006
- }
3320
+ primarySize[ 0 ] = "100%";
3321
+ secondarySize[ 0 ] = "100%";
3322
+ }
3007
3323
 
3008
- if( height2.constructor == Number )
3324
+ // Create areas
3325
+ let area1 = new Area( { width: primarySize[ 0 ], height: primarySize[ 1 ], skipAppend: true, className: "split" + ( options.menubar || options.sidebar ? "" : " origin" ) } );
3326
+ let area2 = new Area( { width: secondarySize[ 0 ], height: secondarySize[ 1 ], skipAppend: true, className: "split" } );
3327
+
3328
+ if( auto && type == "vertical" )
3329
+ {
3330
+ // Listen resize event on first area
3331
+ const resizeObserver = new ResizeObserver( entries => {
3332
+ for ( const entry of entries )
3009
3333
  {
3010
- height2 += "px";
3334
+ const size = entry.target.getComputedSize();
3335
+ area2.root.style.height = "calc(100% - " + ( size.height ) + "px )";
3011
3336
  }
3337
+ });
3012
3338
 
3013
- area1.root.style.width = "100%";
3014
- area1.root.style.height = ( height1 == "auto" ? height1 : "calc( " + height1 + " - " + data + " )");
3015
- area2.root.style.height = ( height2 == "auto" ? height2 : "calc( " + height2 + " - " + data + " )");
3016
- }
3339
+ resizeObserver.observe( area1.root );
3340
+ }
3341
+
3342
+ // Being minimizable means it's also resizeable!
3343
+ if( resize && minimizable )
3344
+ {
3345
+ this.splitExtended = false;
3346
+
3347
+ // Keep state of the animation when ends...
3348
+ area2.root.addEventListener('animationend', e => {
3349
+ const opacity = getComputedStyle( area2.root ).opacity;
3350
+ area2.root.classList.remove( e.animationName + "-" + type );
3351
+ area2.root.style.opacity = opacity;
3352
+ flushCss( area2.root );
3353
+ });
3354
+
3355
+ this.splitBar.addEventListener("contextmenu", e => {
3356
+ e.preventDefault();
3357
+ addContextMenu(null, e, c => {
3358
+ c.add("Extend", { disabled: this.splitExtended, callback: () => { this.extend() } });
3359
+ c.add("Reduce", { disabled: !this.splitExtended, callback: () => { this.reduce() } });
3360
+ });
3361
+ });
3017
3362
  }
3018
3363
 
3364
+ area1.parentArea = this;
3365
+ area2.parentArea = this;
3366
+
3019
3367
  this.root.appendChild( area1.root );
3020
3368
 
3021
3369
  if( resize )
@@ -3024,6 +3372,7 @@ class Area {
3024
3372
  }
3025
3373
 
3026
3374
  this.root.appendChild( area2.root );
3375
+
3027
3376
  this.sections = [ area1, area2 ];
3028
3377
  this.type = type;
3029
3378
 
@@ -3137,10 +3486,10 @@ class Area {
3137
3486
  return;
3138
3487
  }
3139
3488
 
3140
- let [area1, area2] = this.sections;
3489
+ let [ area1, area2 ] = this.sections;
3141
3490
  this.splitExtended = true;
3142
3491
 
3143
- if( this.type == "vertical")
3492
+ if( this.type == "vertical" )
3144
3493
  {
3145
3494
  this.offset = area2.root.offsetHeight;
3146
3495
  area2.root.classList.add("fadeout-vertical");
@@ -3151,7 +3500,7 @@ class Area {
3151
3500
  {
3152
3501
  this.offset = area2.root.offsetWidth - 8; // Force some height here...
3153
3502
  area2.root.classList.add("fadeout-horizontal");
3154
- this._moveSplit(-Infinity, true, 8);
3503
+ this._moveSplit( -Infinity, true, 8 );
3155
3504
  }
3156
3505
 
3157
3506
  doAsync( () => this.propagateEvent('onresize'), 150 );
@@ -3258,8 +3607,7 @@ class Area {
3258
3607
 
3259
3608
  LX.menubars.push( menubar );
3260
3609
 
3261
- const height = 48; // pixels
3262
- const [ bar, content ] = this.split({ type: 'vertical', sizes: [height, null], resize: false, menubar: true });
3610
+ const [ bar, content ] = this.split({ type: 'vertical', sizes: ["48px", null], resize: false, menubar: true });
3263
3611
  menubar.siblingArea = content;
3264
3612
 
3265
3613
  bar.attach( menubar );
@@ -3376,7 +3724,8 @@ class Area {
3376
3724
  img: b.img,
3377
3725
  className: b.class ?? "",
3378
3726
  title: b.name,
3379
- overflowContainerX: overlayPanel.root
3727
+ overflowContainerX: overlayPanel.root,
3728
+ swap: b.swap
3380
3729
  };
3381
3730
 
3382
3731
  if( group )
@@ -3513,7 +3862,7 @@ class Area {
3513
3862
 
3514
3863
  const a2 = this.sections[ 1 ];
3515
3864
  const a2Root = a2.root;
3516
- const splitData = " - "+ LX.DEFAULT_SPLITBAR_SIZE + "px";
3865
+ const splitData = "- "+ LX.DEFAULT_SPLITBAR_SIZE + "px";
3517
3866
 
3518
3867
  let transition = null;
3519
3868
  if( !forceAnimation )
@@ -3521,30 +3870,47 @@ class Area {
3521
3870
  // Remove transitions for this change..
3522
3871
  transition = a1Root.style.transition;
3523
3872
  a1Root.style.transition = a2Root.style.transition = "none";
3524
- flushCss( a1Root );
3525
- flushCss( a2Root );
3873
+ // flushCss( a1Root );
3526
3874
  }
3527
3875
 
3528
3876
  if( this.type == "horizontal" )
3529
3877
  {
3530
3878
  var size = Math.max( a2Root.offsetWidth + dt, parseInt( a2.minWidth ) );
3531
3879
  if( forceWidth ) size = forceWidth;
3532
- a1Root.style.width = "-moz-calc( 100% - " + size + "px " + splitData + " )";
3533
- a1Root.style.width = "-webkit-calc( 100% - " + size + "px " + splitData + " )";
3534
- a1Root.style.width = "calc( 100% - " + size + "px " + splitData + " )";
3535
- a1Root.style.minWidth = parseInt( a1.minWidth ) + "px";
3536
- a2Root.style.width = size + "px";
3537
- if( a1.maxWidth != Infinity ) a2Root.style.minWidth = "calc( 100% - " + parseInt( a1.maxWidth ) + "px" + " )";
3880
+
3881
+ const parentWidth = this.size[ 0 ];
3882
+ const rightPercent = ( size / parentWidth ) * 100;
3883
+ const leftPercent = Math.max( 0, 100 - rightPercent );
3884
+
3885
+ a1Root.style.width = `-moz-calc(${ leftPercent }% ${ splitData })`;
3886
+ a1Root.style.width = `-webkit-calc( ${ leftPercent }% ${ splitData })`;
3887
+ a1Root.style.width = `calc( ${ leftPercent }% ${ splitData })`;
3888
+ a2Root.style.width = `${ rightPercent }%`;
3889
+ a2Root.style.width = `${ rightPercent }%`;
3890
+ a2Root.style.width = `${ rightPercent }%`;
3891
+
3892
+ if( a1.maxWidth != Infinity )
3893
+ {
3894
+ a2Root.style.minWidth = "calc( 100% - " + parseInt( a1.maxWidth ) + "px" + " )";
3895
+ }
3538
3896
  }
3539
3897
  else
3540
3898
  {
3541
- var size = Math.max((a2Root.offsetHeight + dt) + a2.offset, parseInt(a2.minHeight));
3899
+ var size = Math.max( ( a2Root.offsetHeight + dt ) + a2.offset, parseInt(a2.minHeight) );
3542
3900
  if( forceWidth ) size = forceWidth;
3543
- a1Root.style.height = "-moz-calc( 100% - " + size + "px " + splitData + " )";
3544
- a1Root.style.height = "-webkit-calc( 100% - " + size + "px " + splitData + " )";
3545
- a1Root.style.height = "calc( 100% - " + size + "px " + splitData + " )";
3901
+
3902
+ const parentHeight = this.size[ 1 ];
3903
+ const bottomPercent = ( size / parentHeight ) * 100;
3904
+ const topPercent = Math.max( 0, 100 - bottomPercent );
3905
+
3906
+ a1Root.style.height = `-moz-calc(${ topPercent }% ${ splitData })`;
3907
+ a1Root.style.height = `-webkit-calc( ${ topPercent }% ${ splitData })`;
3908
+ a1Root.style.height = `calc( ${ topPercent }% ${ splitData })`;
3909
+ a2Root.style.height = `${ bottomPercent }%`;
3910
+ a2Root.style.height = `${ bottomPercent }%`;
3911
+ a2Root.style.height = `${ bottomPercent }%`;
3912
+
3546
3913
  a1Root.style.minHeight = a1.minHeight + "px";
3547
- a2Root.style.height = ( size - a2.offset ) + "px";
3548
3914
  }
3549
3915
 
3550
3916
  if( !forceAnimation )
@@ -3553,10 +3919,10 @@ class Area {
3553
3919
  a1Root.style.transition = a2Root.style.transition = transition;
3554
3920
  }
3555
3921
 
3556
- this._update();
3557
-
3558
- // Resize events
3559
- this.propagateEvent( 'onresize' );
3922
+ doAsync( () => {
3923
+ this._update();
3924
+ this.propagateEvent( 'onresize' );
3925
+ }, 10 );
3560
3926
  }
3561
3927
 
3562
3928
  _disableSplitResize() {
@@ -5222,8 +5588,9 @@ class Widget {
5222
5588
  static COUNTER = 33;
5223
5589
  static TABLE = 34;
5224
5590
  static TABS = 35;
5225
- static LABEL = 36;
5226
- static BLANK = 37;
5591
+ static DATE = 36;
5592
+ static LABEL = 37;
5593
+ static BLANK = 38;
5227
5594
 
5228
5595
  static NO_CONTEXT_TYPES = [
5229
5596
  Widget.BUTTON,
@@ -5461,6 +5828,7 @@ class Widget {
5461
5828
  case Widget.COUNTER: return "Counter";
5462
5829
  case Widget.TABLE: return "Table";
5463
5830
  case Widget.TABS: return "Tabs";
5831
+ case Widget.DATE: return "Date";
5464
5832
  case Widget.LABEL: return "Label";
5465
5833
  case Widget.BLANK: return "Blank";
5466
5834
  case Widget.CUSTOM: return this.customName;
@@ -6061,8 +6429,11 @@ class NodeTree {
6061
6429
  actionEl.className = "lexicon " + a.icon;
6062
6430
  actionEl.title = a.name;
6063
6431
  actionEl.addEventListener("click", function( e ) {
6064
- a.callback( node, actionEl );
6065
- e.stopPropagation();
6432
+ if( a.callback )
6433
+ {
6434
+ a.callback( node, actionEl );
6435
+ e.stopPropagation();
6436
+ }
6066
6437
  });
6067
6438
 
6068
6439
  inputContainer.appendChild( actionEl );
@@ -6122,7 +6493,17 @@ class NodeTree {
6122
6493
 
6123
6494
  this.data = newData ?? this.data;
6124
6495
  this.domEl.querySelector( "ul" ).innerHTML = "";
6125
- this._createItem( null, this.data, 0, selectedId );
6496
+ if( this.data.constructor === Object )
6497
+ {
6498
+ this._createItem( null, this.data, 0, selectedId );
6499
+ }
6500
+ else
6501
+ {
6502
+ for( let d of this.data )
6503
+ {
6504
+ this._createItem( null, d, 0, selectedId );
6505
+ }
6506
+ }
6126
6507
  }
6127
6508
 
6128
6509
  /* Refreshes the tree and focuses current element */
@@ -6153,6 +6534,8 @@ class NodeTree {
6153
6534
  }
6154
6535
  }
6155
6536
 
6537
+ LX.NodeTree = NodeTree;
6538
+
6156
6539
  /**
6157
6540
  * @class Blank
6158
6541
  * @description Blank Widget
@@ -6249,8 +6632,8 @@ class TextInput extends Widget {
6249
6632
  };
6250
6633
 
6251
6634
  this.onResize = ( rect ) => {
6252
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
6253
- container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth }px)`;
6635
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
6636
+ container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth })`;
6254
6637
  };
6255
6638
 
6256
6639
  this.valid = ( v ) => {
@@ -6388,8 +6771,8 @@ class TextArea extends Widget {
6388
6771
  };
6389
6772
 
6390
6773
  this.onResize = ( rect ) => {
6391
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
6392
- container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth }px)`;
6774
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
6775
+ container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth })`;
6393
6776
  };
6394
6777
 
6395
6778
  let container = document.createElement( "div" );
@@ -6482,8 +6865,8 @@ class Button extends Widget {
6482
6865
  };
6483
6866
 
6484
6867
  this.onResize = ( rect ) => {
6485
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
6486
- wValue.style.width = `calc( 100% - ${ realNameWidth }px)`;
6868
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
6869
+ wValue.style.width = `calc( 100% - ${ realNameWidth })`;
6487
6870
  };
6488
6871
 
6489
6872
  var wValue = document.createElement( 'button' );
@@ -6543,6 +6926,7 @@ class Button extends Widget {
6543
6926
  wValue.querySelector( "a" ).classList.add( "swap-off" );
6544
6927
 
6545
6928
  const input = document.createElement( "input" );
6929
+ input.className = "p-0 border-0";
6546
6930
  input.type = "checkbox";
6547
6931
  wValue.prepend( input );
6548
6932
 
@@ -6745,8 +7129,8 @@ class ComboButtons extends Widget {
6745
7129
  };
6746
7130
 
6747
7131
  this.onResize = ( rect ) => {
6748
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
6749
- container.style.width = `calc( 100% - ${ realNameWidth }px)`;
7132
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
7133
+ container.style.width = `calc( 100% - ${ realNameWidth })`;
6750
7134
  };
6751
7135
 
6752
7136
  this.root.appendChild( container );
@@ -6962,8 +7346,8 @@ class Select extends Widget {
6962
7346
  };
6963
7347
 
6964
7348
  this.onResize = ( rect ) => {
6965
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
6966
- container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth }px)`;
7349
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
7350
+ container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth })`;
6967
7351
  };
6968
7352
 
6969
7353
  let container = document.createElement( "div" );
@@ -7310,11 +7694,8 @@ class Curve extends Widget {
7310
7694
  };
7311
7695
 
7312
7696
  this.onResize = ( rect ) => {
7313
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
7314
- container.style.width = `calc( 100% - ${ realNameWidth }px)`;
7315
- flushCss( container );
7316
- curveInstance.canvas.width = container.offsetWidth;
7317
- curveInstance.redraw();
7697
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
7698
+ container.style.width = `calc( 100% - ${ realNameWidth })`;
7318
7699
  };
7319
7700
 
7320
7701
  var container = document.createElement( "div" );
@@ -7331,6 +7712,16 @@ class Curve extends Widget {
7331
7712
  container.appendChild( curveInstance.element );
7332
7713
  this.curveInstance = curveInstance;
7333
7714
 
7715
+ const observer = new ResizeObserver( entries => {
7716
+ for ( const entry of entries )
7717
+ {
7718
+ curveInstance.canvas.width = entry.contentRect.width;
7719
+ curveInstance.redraw();
7720
+ }
7721
+ });
7722
+
7723
+ observer.observe( container );
7724
+
7334
7725
  doAsync( this.onResize.bind( this ) );
7335
7726
  }
7336
7727
  }
@@ -7364,8 +7755,8 @@ class Dial extends Widget {
7364
7755
  };
7365
7756
 
7366
7757
  this.onResize = ( rect ) => {
7367
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
7368
- container.style.width = `calc( 100% - ${ realNameWidth }px)`;
7758
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
7759
+ container.style.width = `calc( 100% - ${ realNameWidth })`;
7369
7760
  flushCss( container );
7370
7761
  dialInstance.element.style.height = dialInstance.element.offsetWidth + "px";
7371
7762
  dialInstance.canvas.width = dialInstance.element.offsetWidth;
@@ -7419,8 +7810,8 @@ class Layers extends Widget {
7419
7810
  };
7420
7811
 
7421
7812
  this.onResize = ( rect ) => {
7422
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
7423
- container.style.width = `calc( 100% - ${ realNameWidth }px)`;
7813
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
7814
+ container.style.width = `calc( 100% - ${ realNameWidth })`;
7424
7815
  };
7425
7816
 
7426
7817
  var container = document.createElement( "div" );
@@ -7644,8 +8035,8 @@ class List extends Widget {
7644
8035
  };
7645
8036
 
7646
8037
  this.onResize = ( rect ) => {
7647
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
7648
- listContainer.style.width = `calc( 100% - ${ realNameWidth }px)`;
8038
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
8039
+ listContainer.style.width = `calc( 100% - ${ realNameWidth })`;
7649
8040
  };
7650
8041
 
7651
8042
  this._updateValues = ( newValues ) => {
@@ -7721,8 +8112,8 @@ class Tags extends Widget {
7721
8112
  };
7722
8113
 
7723
8114
  this.onResize = ( rect ) => {
7724
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
7725
- tagsContainer.style.width = `calc( 100% - ${ realNameWidth }px)`;
8115
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
8116
+ tagsContainer.style.width = `calc( 100% - ${ realNameWidth })`;
7726
8117
  };
7727
8118
 
7728
8119
  // Show tags
@@ -7822,8 +8213,8 @@ class Checkbox extends Widget {
7822
8213
  };
7823
8214
 
7824
8215
  this.onResize = ( rect ) => {
7825
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
7826
- container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth }px)`;
8216
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
8217
+ container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth })`;
7827
8218
  };
7828
8219
 
7829
8220
  var container = document.createElement( "div" );
@@ -7905,8 +8296,8 @@ class Toggle extends Widget {
7905
8296
  };
7906
8297
 
7907
8298
  this.onResize = ( rect ) => {
7908
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
7909
- container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth }px)`;
8299
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
8300
+ container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth })`;
7910
8301
  };
7911
8302
 
7912
8303
  var container = document.createElement('div');
@@ -8088,14 +8479,24 @@ class ColorInput extends Widget {
8088
8479
  };
8089
8480
 
8090
8481
  this.onResize = ( rect ) => {
8091
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
8092
- container.style.width = `calc( 100% - ${ realNameWidth }px)`;
8482
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
8483
+ container.style.width = `calc( 100% - ${ realNameWidth })`;
8093
8484
  };
8094
8485
 
8095
8486
  var container = document.createElement( 'span' );
8096
8487
  container.className = "lexcolor";
8097
8488
  this.root.appendChild( container );
8098
8489
 
8490
+ this.picker = new ColorPicker( value, {
8491
+ colorModel: options.useRGB ? "RGB" : "Hex",
8492
+ useAlpha,
8493
+ onChange: ( color ) => {
8494
+ this._fromColorPicker = true;
8495
+ this.set( color.hex );
8496
+ delete this._fromColorPicker;
8497
+ }
8498
+ } );
8499
+
8099
8500
  let sampleContainer = LX.makeContainer( ["18px", "18px"], "flex flex-row bg-contrast rounded overflow-hidden", "", container );
8100
8501
  sampleContainer.tabIndex = "1";
8101
8502
  sampleContainer.addEventListener( "click", e => {
@@ -8103,15 +8504,8 @@ class ColorInput extends Widget {
8103
8504
  {
8104
8505
  return;
8105
8506
  }
8106
- new ColorPicker( value, sampleContainer, {
8107
- colorModel: options.useRGB ? "RGB" : "Hex",
8108
- useAlpha,
8109
- onChange: ( color ) => {
8110
- this._fromColorPicker = true;
8111
- this.set( color.hex );
8112
- delete this._fromColorPicker;
8113
- }
8114
- } );
8507
+
8508
+ this._popover = new Popover( sampleContainer, [ this.picker ] );
8115
8509
  } );
8116
8510
 
8117
8511
  let colorSampleRGB = document.createElement( 'div' );
@@ -8179,8 +8573,8 @@ class RangeInput extends Widget {
8179
8573
  };
8180
8574
 
8181
8575
  this.onResize = ( rect ) => {
8182
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
8183
- container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth }px)`;
8576
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
8577
+ container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth })`;
8184
8578
  };
8185
8579
 
8186
8580
  const container = document.createElement( 'div' );
@@ -8293,8 +8687,8 @@ class NumberInput extends Widget {
8293
8687
  };
8294
8688
 
8295
8689
  this.onResize = ( rect ) => {
8296
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
8297
- container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth }px)`;
8690
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
8691
+ container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth })`;
8298
8692
  };
8299
8693
 
8300
8694
  var container = document.createElement( 'div' );
@@ -8529,8 +8923,8 @@ class Vector extends Widget {
8529
8923
  };
8530
8924
 
8531
8925
  this.onResize = ( rect ) => {
8532
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
8533
- container.style.width = `calc( 100% - ${ realNameWidth }px)`;
8926
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
8927
+ container.style.width = `calc( 100% - ${ realNameWidth })`;
8534
8928
  };
8535
8929
 
8536
8930
  const vectorInputs = [];
@@ -8883,8 +9277,8 @@ class OTPInput extends Widget {
8883
9277
  };
8884
9278
 
8885
9279
  this.onResize = ( rect ) => {
8886
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
8887
- container.style.width = `calc( 100% - ${ realNameWidth }px)`;
9280
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
9281
+ container.style.width = `calc( 100% - ${ realNameWidth })`;
8888
9282
  };
8889
9283
 
8890
9284
  this.disabled = options.disabled ?? false;
@@ -9029,8 +9423,8 @@ class Pad extends Widget {
9029
9423
  };
9030
9424
 
9031
9425
  this.onResize = ( rect ) => {
9032
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
9033
- container.style.width = `calc( 100% - ${ realNameWidth }px)`;
9426
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
9427
+ container.style.width = `calc( 100% - ${ realNameWidth })`;
9034
9428
  };
9035
9429
 
9036
9430
  var container = document.createElement( 'div' );
@@ -9149,8 +9543,8 @@ class Progress extends Widget {
9149
9543
  };
9150
9544
 
9151
9545
  this.onResize = ( rect ) => {
9152
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
9153
- container.style.width = `calc( 100% - ${ realNameWidth }px)`;
9546
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
9547
+ container.style.width = `calc( 100% - ${ realNameWidth })`;
9154
9548
  };
9155
9549
 
9156
9550
  const container = document.createElement('div');
@@ -9276,8 +9670,8 @@ class FileInput extends Widget {
9276
9670
  let read = options.read ?? true;
9277
9671
 
9278
9672
  this.onResize = ( rect ) => {
9279
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
9280
- input.style.width = `calc( 100% - ${ realNameWidth }px)`;
9673
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
9674
+ input.style.width = `calc( 100% - ${ realNameWidth })`;
9281
9675
  };
9282
9676
 
9283
9677
  // Create hidden input
@@ -9603,8 +9997,8 @@ class Table extends Widget {
9603
9997
  super( Widget.TABLE, name, null, options );
9604
9998
 
9605
9999
  this.onResize = ( rect ) => {
9606
- const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
9607
- container.style.width = `calc( 100% - ${ realNameWidth }px)`;
10000
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
10001
+ container.style.width = `calc( 100% - ${ realNameWidth })`;
9608
10002
  };
9609
10003
 
9610
10004
  const container = document.createElement('div');
@@ -10104,6 +10498,75 @@ class Table extends Widget {
10104
10498
 
10105
10499
  LX.Table = Table;
10106
10500
 
10501
+ /**
10502
+ * @class DatePicker
10503
+ * @description DatePicker Widget
10504
+ */
10505
+
10506
+ class DatePicker extends Widget {
10507
+
10508
+ constructor( name, dateString, callback, options = { } ) {
10509
+
10510
+ super( Widget.DATE, name, null, options );
10511
+
10512
+ if( options.today )
10513
+ {
10514
+ const date = new Date();
10515
+ dateString = `${ date.getDate() }/${ date.getMonth() + 1 }/${ date.getFullYear() }`;
10516
+ }
10517
+
10518
+ this.onGetValue = () => {
10519
+ return dateString;
10520
+ }
10521
+
10522
+ this.onSetValue = ( newValue, skipCallback, event ) => {
10523
+
10524
+ dateString = newValue;
10525
+
10526
+ this.calendar.fromDateString( newValue );
10527
+
10528
+ refresh( this.calendar.getFullDate() );
10529
+
10530
+ if( !skipCallback )
10531
+ {
10532
+ this._trigger( new IEvent( name, newValue, event ), callback );
10533
+ }
10534
+ }
10535
+
10536
+ this.onResize = ( rect ) => {
10537
+ const realNameWidth = ( this.root.domName?.style.width ?? "0px" );
10538
+ container.style.width = `calc( 100% - ${ realNameWidth })`;
10539
+ };
10540
+
10541
+ const container = document.createElement('div');
10542
+ container.className = "lexdate";
10543
+ this.root.appendChild( container );
10544
+
10545
+ this.calendar = new Calendar( dateString, { onChange: ( date ) => {
10546
+ this.set( `${ date.day }/${ date.month }/${ date.year }` )
10547
+ }, ...options });
10548
+
10549
+ const refresh = ( currentDate ) => {
10550
+ container.innerHTML = "";
10551
+ const calendarIcon = LX.makeIcon( "calendar" );
10552
+ const calendarButton = new Button( null, currentDate ?? "Pick a date", () => {
10553
+ this._popover = new Popover( calendarButton.root, ( popoverRoot ) => {
10554
+ popoverRoot.appendChild( this.calendar.root );
10555
+ } );
10556
+ }, { buttonClass: `flex flex-row px-3 ${ currentDate ? "" : "fg-tertiary" } justify-between` } );
10557
+
10558
+ calendarButton.root.querySelector( "button" ).appendChild( calendarIcon );
10559
+ container.appendChild( calendarButton.root );
10560
+ };
10561
+
10562
+ refresh( dateString ? this.calendar.getFullDate(): null );
10563
+
10564
+ doAsync( this.onResize.bind( this ) );
10565
+ }
10566
+ }
10567
+
10568
+ LX.DatePicker = DatePicker;
10569
+
10107
10570
  /**
10108
10571
  * @class Panel
10109
10572
  */
@@ -11250,6 +11713,23 @@ class Panel {
11250
11713
  const widget = new Table( name, data, options );
11251
11714
  return this._attachWidget( widget );
11252
11715
  }
11716
+
11717
+ /**
11718
+ * @method addDate
11719
+ * @param {String} name Widget name
11720
+ * @param {String} dateString
11721
+ * @param {Function} callback
11722
+ * @param {Object} options:
11723
+ * hideName: Don't use name as label [false]
11724
+ * today: Set current day as selected by default
11725
+ * untilToday: Allow dates only until current day
11726
+ * fromToday: Allow dates only from current day
11727
+ */
11728
+
11729
+ addDate( name, dateString, callback, options = { } ) {
11730
+ const widget = new DatePicker( name, dateString, callback, options );
11731
+ return this._attachWidget( widget );
11732
+ }
11253
11733
  }
11254
11734
 
11255
11735
  LX.Panel = Panel;
@@ -12355,12 +12835,6 @@ class CanvasCurve {
12355
12835
  if( o.xrange ) element.xrange = o.xrange;
12356
12836
  if( o.yrange ) element.yrange = o.yrange;
12357
12837
  if( o.smooth ) element.smooth = o.smooth;
12358
- var rect = canvas.parentElement.getBoundingClientRect();
12359
- if( canvas.parentElement.parentElement ) rect = canvas.parentElement.parentElement.getBoundingClientRect();
12360
- if( rect && canvas.width != rect.width && rect.width && rect.width < 1000 )
12361
- {
12362
- canvas.width = rect.width;
12363
- }
12364
12838
 
12365
12839
  var ctx = canvas.getContext( "2d" );
12366
12840
  ctx.setTransform( 1, 0, 0, 1, 0, 0 );
@@ -12695,12 +13169,6 @@ class CanvasDial {
12695
13169
  if( o.xrange ) element.xrange = o.xrange;
12696
13170
  if( o.yrange ) element.yrange = o.yrange;
12697
13171
  if( o.smooth ) element.smooth = o.smooth;
12698
- var rect = canvas.parentElement.getBoundingClientRect();
12699
- if( canvas.parentElement.parentElement ) rect = canvas.parentElement.parentElement.getBoundingClientRect();
12700
- if( rect && canvas.width != rect.width && rect.width && rect.width < 1000 )
12701
- {
12702
- canvas.width = rect.width;
12703
- }
12704
13172
 
12705
13173
  var ctx = canvas.getContext( "2d" );
12706
13174
  ctx.setTransform( 1, 0, 0, 1, 0, 0 );
@@ -14294,6 +14762,7 @@ LX.ICONS = {
14294
14762
  "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"],
14295
14763
  "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"],
14296
14764
  "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"],
14765
+ "frame": [24, 24, [], "solid", "M2 6h20M2 18h20M6 2v20M18 2v20", null, "fill=none stroke-width=2 stroke-linejoin=round stroke-linecap=round"],
14297
14766
  "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"],
14298
14767
  "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"],
14299
14768
  "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"],