lexgui 0.5.5 → 0.5.7

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.5",
15
+ version: "0.5.7",
16
16
  ready: false,
17
17
  components: [], // Specific pre-build components
18
18
  signals: {}, // Events and triggers
@@ -222,37 +222,141 @@ LX.getBase64Image = getBase64Image;
222
222
 
223
223
  /**
224
224
  * @method hexToRgb
225
- * @description Convert a hexadecimal string to a valid RGB color array
226
- * @param {String} hexStr Hexadecimal color
225
+ * @description Convert a hexadecimal string to a valid RGB color
226
+ * @param {String} hex Hexadecimal color
227
227
  */
228
- function hexToRgb( hexStr )
228
+ function hexToRgb( hex )
229
229
  {
230
- const red = parseInt( hexStr.substring( 1, 3 ), 16 ) / 255;
231
- const green = parseInt( hexStr.substring( 3, 5 ), 16 ) / 255;
232
- const blue = parseInt( hexStr.substring( 5, 7 ), 16 ) / 255;
233
- return [ red, green, blue ];
230
+ const hexPattern = /^#(?:[A-Fa-f0-9]{3,4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$/;
231
+ if( !hexPattern.test( hex ) )
232
+ {
233
+ throw( `Invalid Hex Color: ${ hex }` );
234
+ }
235
+
236
+ hex = hex.replace( /^#/, '' );
237
+
238
+ // Expand shorthand form (#RGB or #RGBA)
239
+ if( hex.length === 3 || hex.length === 4 )
240
+ {
241
+ hex = hex.split( '' ).map( c => c + c ).join( '' );
242
+ }
243
+
244
+ const bigint = parseInt( hex, 16 );
245
+
246
+ const r = ( ( bigint >> ( hex.length === 8 ? 24 : 16 ) ) & 255 ) / 255;
247
+ const g = ( ( bigint >> ( hex.length === 8 ? 16 : 8 ) ) & 255 ) / 255;
248
+ const b = ( ( bigint >> ( hex.length === 8 ? 8 : 0 ) ) & 255 ) / 255;
249
+ const a = ( hex.length === 8 ? ( bigint & 255 ) : ( hex.length === 4 ? parseInt( hex.slice( -2 ), 16 ) : 255 ) ) / 255;
250
+
251
+ return { r, g, b, a };
234
252
  }
235
253
 
236
254
  LX.hexToRgb = hexToRgb;
237
255
 
256
+ /**
257
+ * @method hexToHsv
258
+ * @description Convert a hexadecimal string to HSV (0..360|0..1|0..1)
259
+ * @param {String} hexStr Hexadecimal color
260
+ */
261
+ function hexToHsv( hexStr )
262
+ {
263
+ const rgb = hexToRgb( hexStr );
264
+ return rgbToHsv( rgb );
265
+ }
266
+
267
+ LX.hexToHsv = hexToHsv;
268
+
238
269
  /**
239
270
  * @method rgbToHex
240
- * @description Convert a RGB color array to a hexadecimal string
241
- * @param {Array} rgb Array containing R, G, B, A*
271
+ * @description Convert a RGB color to a hexadecimal string
272
+ * @param {Object} rgb Object containing RGB color
273
+ * @param {Number} scale Use 255 for 0..255 range or 1 for 0..1 range
242
274
  */
243
- function rgbToHex( rgb )
275
+ function rgbToHex( rgb, scale = 255 )
244
276
  {
245
- let hex = "#";
246
- for( let c of rgb )
247
- {
248
- c = Math.floor( c * 255 );
249
- hex += c.toString( 16 );
250
- }
251
- return hex;
277
+ const rgbArray = [ rgb.r, rgb.g, rgb.b ];
278
+ if( rgb.a != undefined ) rgbArray.push( rgb.a );
279
+
280
+ return (
281
+ "#" +
282
+ rgbArray.map( c => {
283
+ c = Math.floor( c * scale );
284
+ const hex = c.toString(16);
285
+ return hex.length === 1 ? ( '0' + hex ) : hex;
286
+ }).join("")
287
+ );
252
288
  }
253
289
 
254
290
  LX.rgbToHex = rgbToHex;
255
291
 
292
+ /**
293
+ * @method rgbToCss
294
+ * @description Convert a RGB color (0..1) to a CSS color format
295
+ * @param {Object} rgb Object containing RGB color
296
+ */
297
+ function rgbToCss( rgb )
298
+ {
299
+ return { r: Math.floor( rgb.r * 255 ), g: Math.floor( rgb.g * 255 ), b: Math.floor( rgb.b * 255 ), a: rgb.a };
300
+ }
301
+
302
+ LX.rgbToCss = rgbToCss;
303
+
304
+ /**
305
+ * @method rgbToHsv
306
+ * @description Convert a RGB color (0..1) array to HSV (0..360|0..1|0..1)
307
+ * @param {Object} rgb Array containing R, G, B
308
+ */
309
+ function rgbToHsv( rgb )
310
+ {
311
+ let { r, g, b, a } = rgb;
312
+ a = a ?? 1;
313
+
314
+ const max = Math.max(r, g, b);
315
+ const min = Math.min(r, g, b);
316
+ const d = max - min;
317
+ let h = 0;
318
+
319
+ if (d !== 0) {
320
+ if (max === r) { h = ((g - b) / d) % 6 }
321
+ else if (max === g) { h = (b - r) / d + 2 }
322
+ else { h = (r - g) / d + 4 }
323
+ h *= 60
324
+ if (h < 0) { h += 360 }
325
+ }
326
+
327
+ const s = max === 0 ? 0 : (d / max);
328
+ const v = max;
329
+
330
+ return { h, s, v, a };
331
+ }
332
+
333
+ LX.rgbToHsv = rgbToHsv;
334
+
335
+ /**
336
+ * @method hsvToRgb
337
+ * @description Convert an HSV color (0..360|0..1|0..1) array to RGB (0..1|0..255)
338
+ * @param {Array} hsv Array containing H, S, V
339
+ */
340
+ function hsvToRgb( hsv )
341
+ {
342
+ const { h, s, v, a } = hsv;
343
+ const c = v * s;
344
+ const x = c * (1 - Math.abs( ( (h / 60) % 2 ) - 1) )
345
+ const m = v - c;
346
+ let r = 0, g = 0, b = 0;
347
+
348
+ if( h < 60 ) { r = c; g = x; b = 0; }
349
+ else if ( h < 120 ) { r = x; g = c; b = 0; }
350
+ else if ( h < 180 ) { r = 0; g = c; b = x; }
351
+ else if ( h < 240 ) { r = 0; g = x; b = c; }
352
+ else if ( h < 300 ) { r = x; g = 0; b = c; }
353
+ else { r = c; g = 0; b = x; }
354
+
355
+ return { r: ( r + m ), g: ( g + m ), b: ( b + m ), a };
356
+ }
357
+
358
+ LX.hsvToRgb = hsvToRgb;
359
+
256
360
  /**
257
361
  * @method measureRealWidth
258
362
  * @description Measure the pixel width of a text
@@ -584,18 +688,60 @@ function makeCodeSnippet( code, size, options = { } )
584
688
 
585
689
  LX.makeCodeSnippet = makeCodeSnippet;
586
690
 
691
+ /**
692
+ * @method makeKbd
693
+ * @description Kbd element to display a keyboard key.
694
+ * @param {Array} keys
695
+ * @param {String} extraClass
696
+ */
697
+ function makeKbd( keys, extraClass = "" )
698
+ {
699
+ const specialKeys = {
700
+ "Ctrl": '⌃',
701
+ "Enter": '↩',
702
+ "Shift": '⇧',
703
+ "CapsLock": '⇪',
704
+ "Meta": '⌘',
705
+ "Option": '⌥',
706
+ "Alt": '⌥',
707
+ "Tab": '⇥',
708
+ "ArrowUp": '↑',
709
+ "ArrowDown": '↓',
710
+ "ArrowLeft": '←',
711
+ "ArrowRight": '→',
712
+ "Space": '␣'
713
+ };
714
+
715
+ const kbd = LX.makeContainer( ["auto", "auto"], "flex flex-row ml-auto" );
716
+
717
+ for( const k of keys )
718
+ {
719
+ LX.makeContainer( ["auto", "auto"], "self-center text-xs fg-secondary select-none", specialKeys[ k ] ?? k, kbd );
720
+ }
721
+
722
+ return kbd;
723
+ }
724
+
725
+ LX.makeKbd = makeKbd;
726
+
587
727
  /**
588
728
  * @method makeIcon
589
729
  * @description Gets an SVG element using one of LX.ICONS
590
730
  * @param {String} iconName
591
- * @param {String} iconTitle
592
- * @param {String} extraClass
731
+ * @param {Object} options
732
+ * iconTitle
733
+ * extraClass
734
+ * svgClass
593
735
  */
594
- function makeIcon( iconName, iconTitle, extraClass = "" )
736
+ function makeIcon( iconName, options = { } )
595
737
  {
596
738
  let data = LX.ICONS[ iconName ];
597
739
  console.assert( data, `No icon named _${ iconName }_` );
598
740
 
741
+ const iconTitle = options.iconTitle;
742
+ const iconClass = options.iconClass;
743
+ const svgClass = options.svgClass;
744
+
599
745
  // Just another name for the same icon..
600
746
  if( data.constructor == String )
601
747
  {
@@ -605,9 +751,9 @@ function makeIcon( iconName, iconTitle, extraClass = "" )
605
751
  const svg = document.createElementNS( "http://www.w3.org/2000/svg", "svg" );
606
752
  svg.setAttribute( "viewBox", `0 0 ${ data[ 0 ] } ${ data[ 1 ] }` );
607
753
 
608
- if( extraClass )
754
+ if( svgClass )
609
755
  {
610
- svg.classList.add( extraClass );
756
+ svg.classList.add( svgClass );
611
757
  }
612
758
 
613
759
  if( data[ 5 ] )
@@ -635,7 +781,7 @@ function makeIcon( iconName, iconTitle, extraClass = "" )
635
781
 
636
782
  const icon = document.createElement( "a" );
637
783
  icon.title = iconTitle ?? "";
638
- icon.className = "lexicon " + extraClass;
784
+ icon.className = "lexicon " + ( iconClass ?? "" );
639
785
  icon.appendChild( svg );
640
786
 
641
787
  return icon;
@@ -722,7 +868,7 @@ function registerCommandbarEntry( name, callback )
722
868
 
723
869
  LX.registerCommandbarEntry = registerCommandbarEntry;
724
870
 
725
- // Math classes
871
+ // Utils classes
726
872
 
727
873
  class vec2 {
728
874
 
@@ -750,6 +896,77 @@ class vec2 {
750
896
 
751
897
  LX.vec2 = vec2;
752
898
 
899
+ class Color {
900
+
901
+ constructor( value ) {
902
+
903
+ Object.defineProperty( Color.prototype, "rgb", {
904
+ get: function() { return this._rgb; },
905
+ set: function( v ) { this._fromRGB( v ) }, enumerable: true, configurable: true
906
+ });
907
+
908
+ Object.defineProperty( Color.prototype, "hex", {
909
+ get: function() { return this._hex; },
910
+ set: function( v ) { this._fromHex( v ) }, enumerable: true, configurable: true
911
+ });
912
+
913
+ Object.defineProperty( Color.prototype, "hsv", {
914
+ get: function() { return this._hsv; },
915
+ set: function( v ) { this._fromHSV( v ) }, enumerable: true, configurable: true
916
+ });
917
+
918
+ this.set( value );
919
+ }
920
+
921
+ set( value ) {
922
+
923
+ if ( typeof value === 'string' && value.startsWith( '#' ) )
924
+ {
925
+ this._fromHex( value );
926
+ }
927
+ else if( 'r' in value && 'g' in value && 'b' in value)
928
+ {
929
+ value.a = value.a ?? 1.0;
930
+ this._fromRGB( value );
931
+ }
932
+ else if( 'h' in value && 's' in value && 'v' in value )
933
+ {
934
+ value.a = value.a ?? 1.0;
935
+ this._fromHSV( value );
936
+ }
937
+ else
938
+ {
939
+ throw( "Bad color model!", value );
940
+ }
941
+ }
942
+
943
+ setHSV( hsv ) { this._fromHSV( hsv ); }
944
+ setRGB( rgb ) { this._fromRGB( rgb ); }
945
+ setHex( hex ) { this._fromHex( hex ); }
946
+
947
+ _fromHex( hex ) {
948
+ this._fromRGB( hexToRgb( hex ) );
949
+ }
950
+
951
+ _fromRGB( rgb ) {
952
+ this._rgb = rgb;
953
+ this._hsv = rgbToHsv( rgb );
954
+ this._hex = rgbToHex( rgb );
955
+ this.css = rgbToCss( this._rgb );
956
+ }
957
+
958
+ _fromHSV( hsv ) {
959
+ this._hsv = hsv;
960
+ this._rgb = hsvToRgb( hsv );
961
+ this._hex = rgbToHex( this._rgb );
962
+ this.css = rgbToCss( this._rgb );
963
+ }
964
+ }
965
+
966
+ LX.Color = Color;
967
+
968
+ // Command bar creation
969
+
753
970
  function _createCommandbar( root )
754
971
  {
755
972
  let commandbar = document.createElement( "dialog" );
@@ -1163,6 +1380,19 @@ function init( options = { } )
1163
1380
 
1164
1381
  LX.init = init;
1165
1382
 
1383
+ /**
1384
+ * @method setStrictViewport
1385
+ * @param {Boolean} value
1386
+ */
1387
+
1388
+ function setStrictViewport( value )
1389
+ {
1390
+ this.usingStrictViewport = value ?? true;
1391
+ document.documentElement.setAttribute( "data-strictVP", ( this.usingStrictViewport ) ? "true" : "false" );
1392
+ }
1393
+
1394
+ LX.setStrictViewport = setStrictViewport;
1395
+
1166
1396
  /**
1167
1397
  * @method setCommandbarState
1168
1398
  * @param {Boolean} value
@@ -1807,6 +2037,13 @@ class DropdownMenu {
1807
2037
  submenuIcon.className = "fa-solid fa-angle-right fa-xs";
1808
2038
  menuItem.appendChild( submenuIcon );
1809
2039
  }
2040
+ else if( item.kbd )
2041
+ {
2042
+ item.kbd = [].concat( item.kbd );
2043
+
2044
+ const kbd = LX.makeKbd( item.kbd );
2045
+ menuItem.appendChild( kbd );
2046
+ }
1810
2047
 
1811
2048
  if( item.icon )
1812
2049
  {
@@ -1844,39 +2081,504 @@ class DropdownMenu {
1844
2081
  } );
1845
2082
  }
1846
2083
 
1847
- menuItem.addEventListener("mouseover", e => {
2084
+ menuItem.addEventListener("mouseover", e => {
2085
+
2086
+ let path = menuItem.id;
2087
+ let p = parentDom;
2088
+
2089
+ while( p )
2090
+ {
2091
+ path += "/" + p.id;
2092
+ p = p.currentParent?.parentElement;
2093
+ }
2094
+
2095
+ LX.root.querySelectorAll( ".lexdropdownmenu" ).forEach( m => {
2096
+ if( !path.includes( m.id ) )
2097
+ {
2098
+ m.currentParent.built = false;
2099
+ m.remove();
2100
+ }
2101
+ } );
2102
+
2103
+ if( item.submenu )
2104
+ {
2105
+ if( menuItem.built )
2106
+ {
2107
+ return;
2108
+ }
2109
+ menuItem.built = true;
2110
+ this._create( item.submenu, menuItem );
2111
+ }
2112
+
2113
+ e.stopPropagation();
2114
+ });
2115
+ }
2116
+ }
2117
+
2118
+ _adjustPosition() {
2119
+
2120
+ const position = [ 0, 0 ];
2121
+
2122
+ // Place menu using trigger position and user options
2123
+ {
2124
+ const rect = this._trigger.getBoundingClientRect();
2125
+
2126
+ let alignWidth = true;
2127
+
2128
+ switch( this.side )
2129
+ {
2130
+ case "left":
2131
+ position[ 0 ] += ( rect.x - this.root.offsetWidth );
2132
+ alignWidth = false;
2133
+ break;
2134
+ case "right":
2135
+ position[ 0 ] += ( rect.x + rect.width );
2136
+ alignWidth = false;
2137
+ break;
2138
+ case "top":
2139
+ position[ 1 ] += ( rect.y - this.root.offsetHeight );
2140
+ alignWidth = true;
2141
+ break;
2142
+ case "bottom":
2143
+ position[ 1 ] += ( rect.y + rect.height );
2144
+ alignWidth = true;
2145
+ break;
2146
+ default:
2147
+ break;
2148
+ }
2149
+
2150
+ switch( this.align )
2151
+ {
2152
+ case "start":
2153
+ if( alignWidth ) { position[ 0 ] += rect.x; }
2154
+ else { position[ 1 ] += rect.y; }
2155
+ break;
2156
+ case "center":
2157
+ if( alignWidth ) { position[ 0 ] += ( rect.x + rect.width * 0.5 ) - this.root.offsetWidth * 0.5; }
2158
+ else { position[ 1 ] += ( rect.y + rect.height * 0.5 ) - this.root.offsetHeight * 0.5; }
2159
+ break;
2160
+ case "end":
2161
+ if( alignWidth ) { position[ 0 ] += rect.x - this.root.offsetWidth + rect.width; }
2162
+ else { position[ 1 ] += rect.y - this.root.offsetHeight + rect.height; }
2163
+ break;
2164
+ default:
2165
+ break;
2166
+ }
2167
+ }
2168
+
2169
+ if( this.avoidCollisions )
2170
+ {
2171
+ position[ 0 ] = LX.clamp( position[ 0 ], 0, window.innerWidth - this.root.offsetWidth - this._windowPadding );
2172
+ position[ 1 ] = LX.clamp( position[ 1 ], 0, window.innerHeight - this.root.offsetHeight - this._windowPadding );
2173
+ }
2174
+
2175
+ this.root.style.left = `${ position[ 0 ] }px`;
2176
+ this.root.style.top = `${ position[ 1 ] }px`;
2177
+ }
2178
+
2179
+ _addSeparator( parent ) {
2180
+ const separator = document.createElement('div');
2181
+ separator.className = "separator";
2182
+ parent = parent ?? this.root;
2183
+ parent.appendChild( separator );
2184
+ }
2185
+ };
2186
+
2187
+ LX.DropdownMenu = DropdownMenu;
2188
+
2189
+ /**
2190
+ * @class ColorPicker
2191
+ */
2192
+
2193
+ class ColorPicker {
2194
+
2195
+ static currentPicker = false;
2196
+
2197
+ constructor( hexValue, trigger, options = {} ) {
2198
+
2199
+ console.assert( trigger, "ColorPicker needs a DOM element as trigger!" );
2200
+
2201
+ this._windowPadding = 4;
2202
+ this.side = options.side ?? "bottom";
2203
+ this.align = options.align ?? "center";
2204
+ this.avoidCollisions = options.avoidCollisions ?? true;
2205
+ this.colorModel = options.colorModel ?? "Hex";
2206
+ this.useAlpha = options.useAlpha ?? false;
2207
+ this.callback = options.onChange;
2208
+
2209
+ if( !this.callback )
2210
+ {
2211
+ console.warn( "Define a callback in _options.onChange_ to allow getting new Color values!" );
2212
+ }
2213
+
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
+ this.root = document.createElement( "div" );
2225
+ this.root.tabIndex = "1";
2226
+ 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
+
2241
+ this.markerHalfSize = 8;
2242
+ this.markerSize = this.markerHalfSize * 2;
2243
+ this.currentColor = new Color( hexValue );
2244
+
2245
+ const hueColor = new Color( { h: this.currentColor.hsv.h, s: 1, v: 1 } );
2246
+
2247
+ // Intensity, Sat
2248
+ this.colorPickerBackground = document.createElement( 'div' );
2249
+ this.colorPickerBackground.className = "lexcolorpickerbg";
2250
+ this.colorPickerBackground.style.backgroundColor = `rgb(${ hueColor.css.r }, ${ hueColor.css.g }, ${ hueColor.css.b })`;
2251
+ this.root.appendChild( this.colorPickerBackground );
2252
+
2253
+ this.intSatMarker = document.createElement( 'div' );
2254
+ this.intSatMarker.className = "lexcolormarker";
2255
+ this.intSatMarker.style.backgroundColor = this.currentColor.hex;
2256
+ this.colorPickerBackground.appendChild( this.intSatMarker );
2257
+
2258
+ doAsync( this._svToPosition.bind( this, this.currentColor.hsv.s, this.currentColor.hsv.v ) );
2259
+
2260
+ let innerMouseDown = e => {
2261
+ var doc = this.root.ownerDocument;
2262
+ doc.addEventListener( 'mousemove', innerMouseMove );
2263
+ doc.addEventListener( 'mouseup', innerMouseUp );
2264
+ document.body.classList.add( 'noevents' );
2265
+ e.stopImmediatePropagation();
2266
+ e.stopPropagation();
2267
+
2268
+ const currentLeft = ( e.offsetX - this.markerHalfSize );
2269
+ this.intSatMarker.style.left = currentLeft + "px";
2270
+ const currentTop = ( e.offsetY - this.markerHalfSize );
2271
+ this.intSatMarker.style.top = currentTop + "px";
2272
+ this._positionToSv( currentLeft, currentTop );
2273
+ this._updateColorValue();
2274
+ }
2275
+
2276
+ let innerMouseMove = e => {
2277
+ const dX = e.movementX;
2278
+ 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;
2283
+
2284
+ if ( dX != 0 && ( mouseX >= 0 || dX < 0 ) && ( mouseX < this.colorPickerBackground.offsetWidth || dX > 0 ) )
2285
+ {
2286
+ this.intSatMarker.style.left = LX.clamp( parseInt( this.intSatMarker.style.left ) + dX, -this.markerHalfSize, this.colorPickerBackground.offsetWidth - this.markerHalfSize ) + "px";
2287
+ }
2288
+
2289
+ if ( dY != 0 && ( mouseY >= 0 || dY < 0 ) && ( mouseY < this.colorPickerBackground.offsetHeight || dY > 0 ) )
2290
+ {
2291
+ this.intSatMarker.style.top = LX.clamp( parseInt( this.intSatMarker.style.top ) + dY, -this.markerHalfSize, this.colorPickerBackground.offsetHeight - this.markerHalfSize ) + "px";
2292
+ }
2293
+
2294
+ this._positionToSv( parseInt( this.intSatMarker.style.left ), parseInt( this.intSatMarker.style.top ) );
2295
+ this._updateColorValue();
2296
+
2297
+ e.stopPropagation();
2298
+ e.preventDefault();
2299
+ }
2300
+
2301
+ let innerMouseUp = e => {
2302
+ var doc = this.root.ownerDocument;
2303
+ doc.removeEventListener( 'mousemove', innerMouseMove );
2304
+ doc.removeEventListener( 'mouseup', innerMouseUp );
2305
+ document.body.classList.remove( 'noevents' );
2306
+ }
2307
+
2308
+ this.colorPickerBackground.addEventListener( "mousedown", innerMouseDown );
2309
+
2310
+ const hueAlphaContainer = LX.makeContainer( ["100%", "auto"], "flex flex-row gap-1 items-center", "", this.root );
2311
+
2312
+ if( window.EyeDropper )
2313
+ {
2314
+ hueAlphaContainer.appendChild( new Button(null, "eyedrop", async () => {
2315
+ const eyeDropper = new EyeDropper()
2316
+ try {
2317
+ const result = await eyeDropper.open();
2318
+ this.fromHexColor( result.sRGBHex );
2319
+ } catch ( err ) {
2320
+ // console.error("EyeDropper cancelled or failed: ", err)
2321
+ }
2322
+ }, { icon: "eye-dropper", buttonClass: "bg-none", title: "Sample Color" }).root );
2323
+ }
2324
+
2325
+ const innerHueAlpha = LX.makeContainer( ["100%", "100%"], "flex flex-col gap-2", "", hueAlphaContainer );
2326
+
2327
+ // Hue
2328
+ this.colorPickerTracker = document.createElement( 'div' );
2329
+ this.colorPickerTracker.className = "lexhuetracker";
2330
+ innerHueAlpha.appendChild( this.colorPickerTracker );
2331
+
2332
+ this.hueMarker = document.createElement( 'div' );
2333
+ this.hueMarker.className = "lexcolormarker";
2334
+ this.hueMarker.style.backgroundColor = `rgb(${ hueColor.css.r }, ${ hueColor.css.g }, ${ hueColor.css.b })`;
2335
+ this.colorPickerTracker.appendChild( this.hueMarker );
2336
+
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
+ const _fromHueX = ( hueX ) => {
2343
+ this.hueMarker.style.left = hueX + "px";
2344
+ this.currentColor.hsv.h = LX.remapRange( hueX, 0, this.colorPickerTracker.offsetWidth - this.markerSize, 0, 360 );
2345
+
2346
+ const hueColor = new Color( { h: this.currentColor.hsv.h, s: 1, v: 1 } );
2347
+ this.hueMarker.style.backgroundColor = `rgb(${ hueColor.css.r }, ${ hueColor.css.g }, ${ hueColor.css.b })`;
2348
+ this.colorPickerBackground.style.backgroundColor = `rgb(${ hueColor.css.r }, ${ hueColor.css.g }, ${ hueColor.css.b })`;
2349
+ this._updateColorValue();
2350
+ };
2351
+
2352
+ let innerMouseDownHue = e => {
2353
+ const doc = this.root.ownerDocument;
2354
+ doc.addEventListener( 'mousemove', innerMouseMoveHue );
2355
+ doc.addEventListener( 'mouseup', innerMouseUpHue );
2356
+ document.body.classList.add( 'noevents' );
2357
+ e.stopImmediatePropagation();
2358
+ e.stopPropagation();
2359
+
2360
+ const hueX = clamp( e.offsetX - this.markerHalfSize, 0, this.colorPickerTracker.offsetWidth - this.markerSize );
2361
+ _fromHueX( hueX );
2362
+ }
2363
+
2364
+ let innerMouseMoveHue = e => {
2365
+ let dX = e.movementX;
2366
+
2367
+ const rect = this.colorPickerTracker.getBoundingClientRect();
2368
+ const mouseX = e.offsetX - rect.x;
2369
+
2370
+ if ( dX != 0 && ( mouseX >= 0 || dX < 0 ) && ( mouseX < this.colorPickerTracker.offsetWidth || dX > 0 ) )
2371
+ {
2372
+ const hueX = LX.clamp( parseInt( this.hueMarker.style.left ) + dX, 0, this.colorPickerTracker.offsetWidth - this.markerSize );
2373
+ _fromHueX( hueX )
2374
+ }
2375
+
2376
+ e.stopPropagation();
2377
+ e.preventDefault();
2378
+ }
2379
+
2380
+ let innerMouseUpHue = e => {
2381
+ var doc = this.root.ownerDocument;
2382
+ doc.removeEventListener( 'mousemove', innerMouseMoveHue );
2383
+ doc.removeEventListener( 'mouseup', innerMouseUpHue );
2384
+ document.body.classList.remove( 'noevents' );
2385
+ }
2386
+
2387
+ this.colorPickerTracker.addEventListener( "mousedown", innerMouseDownHue );
2388
+
2389
+ // Alpha
2390
+ if( this.useAlpha )
2391
+ {
2392
+ this.alphaTracker = document.createElement( 'div' );
2393
+ this.alphaTracker.className = "lexalphatracker";
2394
+ this.alphaTracker.style.color = `rgb(${ this.currentColor.css.r }, ${ this.currentColor.css.g }, ${ this.currentColor.css.b })`;
2395
+ innerHueAlpha.appendChild( this.alphaTracker );
2396
+
2397
+ this.alphaMarker = document.createElement( 'div' );
2398
+ this.alphaMarker.className = "lexcolormarker";
2399
+ this.alphaMarker.style.backgroundColor = `rgb(${ this.currentColor.css.r }, ${ this.currentColor.css.g }, ${ this.currentColor.css.b },${ this.currentColor.css.a })`;
2400
+ this.alphaTracker.appendChild( this.alphaMarker );
2401
+
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
+ const _fromAlphaX = ( alphaX ) => {
2408
+ this.alphaMarker.style.left = alphaX + "px";
2409
+ this.currentColor.hsv.a = LX.remapRange( alphaX, 0, this.alphaTracker.offsetWidth - this.markerSize, 0, 1 );
2410
+ this._updateColorValue();
2411
+ // Update alpha marker once the color is updated
2412
+ this.alphaMarker.style.backgroundColor = `rgb(${ this.currentColor.css.r }, ${ this.currentColor.css.g }, ${ this.currentColor.css.b },${ this.currentColor.css.a })`;
2413
+ };
2414
+
2415
+ let innerMouseDownAlpha = e => {
2416
+ const doc = this.root.ownerDocument;
2417
+ doc.addEventListener( 'mousemove', innerMouseMoveAlpha );
2418
+ doc.addEventListener( 'mouseup', innerMouseUpAlpha );
2419
+ document.body.classList.add( 'noevents' );
2420
+ e.stopImmediatePropagation();
2421
+ e.stopPropagation();
2422
+ const alphaX = clamp( e.offsetX - this.markerHalfSize, 0, this.alphaTracker.offsetWidth - this.markerSize );
2423
+ _fromAlphaX( alphaX );
2424
+ }
2425
+
2426
+ let innerMouseMoveAlpha = e => {
2427
+ let dX = e.movementX;
2428
+
2429
+ const rect = this.alphaTracker.getBoundingClientRect();
2430
+ const mouseX = e.offsetX - rect.x;
2431
+
2432
+ if ( dX != 0 && ( mouseX >= 0 || dX < 0 ) && ( mouseX < this.alphaTracker.offsetWidth || dX > 0 ) )
2433
+ {
2434
+ const alphaX = LX.clamp( parseInt( this.alphaMarker.style.left ) + dX, 0, this.alphaTracker.offsetWidth - this.markerSize );
2435
+ _fromAlphaX( alphaX );
2436
+ }
2437
+
2438
+ e.stopPropagation();
2439
+ e.preventDefault();
2440
+ }
2441
+
2442
+ let innerMouseUpAlpha = e => {
2443
+ var doc = this.root.ownerDocument;
2444
+ doc.removeEventListener( 'mousemove', innerMouseMoveAlpha );
2445
+ doc.removeEventListener( 'mouseup', innerMouseUpAlpha );
2446
+ document.body.classList.remove( 'noevents' );
2447
+ }
2448
+
2449
+ this.alphaTracker.addEventListener( "mousedown", innerMouseDownAlpha );
2450
+ }
2451
+
2452
+ // Info display
2453
+ const colorLabel = LX.makeContainer( ["100%", "auto"], "flex flex-row gap-1", "", this.root );
2454
+
2455
+ colorLabel.appendChild( new Select( null, [ "CSS", "Hex", "HSV", "RGB" ], this.colorModel, v => {
2456
+ this.colorModel = v;
2457
+ this._updateColorValue( null, true );
2458
+ } ).root );
2459
+
2460
+ this.labelWidget = new TextInput( null, "", null, { inputClass: "bg-none", fit: true, disabled: true } );
2461
+ colorLabel.appendChild( this.labelWidget.root );
2462
+
2463
+ // Copy button
2464
+ {
2465
+ const copyButtonWidget = new Button(null, "copy", async () => {
2466
+ navigator.clipboard.writeText( this.labelWidget.value() );
2467
+ copyButtonWidget.root.querySelector( "input[type='checkbox']" ).style.pointerEvents = "none";
2468
+
2469
+ doAsync( () => {
2470
+ copyButtonWidget.root.swap( true );
2471
+ copyButtonWidget.root.querySelector( "input[type='checkbox']" ).style.pointerEvents = "auto";
2472
+ }, 3000 );
2473
+
2474
+ }, { swap: "check", icon: "copy", buttonClass: "bg-none", className: "ml-auto", title: "Copy" })
2475
+
2476
+ copyButtonWidget.root.querySelector( ".swap-on svg path" ).style.fill = "#42d065";
2477
+
2478
+ colorLabel.appendChild( copyButtonWidget.root );
2479
+ }
2480
+
2481
+ this._updateColorValue( hexValue, true );
2482
+
2483
+ doAsync( () => {
2484
+ this._adjustPosition();
2485
+
2486
+ this.root.focus();
2487
+
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
+ };
2495
+
2496
+ document.body.addEventListener( "mousedown", this._onClick, true );
2497
+ document.body.addEventListener( "focusin", this._onClick, true );
2498
+ }, 10 );
2499
+ }
2500
+
2501
+ fromHexColor( hexColor ) {
2502
+
2503
+ this.currentColor.setHex( hexColor );
2504
+
2505
+ // Decompose into HSV
2506
+ const { h, s, v } = this.currentColor.hsv;
2507
+ this._svToPosition( s, v );
2508
+
2509
+ const hueColor = new Color( { h, s: 1, v: 1 } );
2510
+ this.hueMarker.style.backgroundColor = this.colorPickerBackground.style.backgroundColor = `rgb(${ hueColor.css.r }, ${ hueColor.css.g }, ${ hueColor.css.b })`;
2511
+ this.hueMarker.style.left = LX.remapRange( h, 0, 360, -this.markerHalfSize, this.colorPickerTracker.offsetWidth - this.markerHalfSize ) + "px";
2512
+
2513
+ this._updateColorValue( hexColor );
2514
+ }
2515
+
2516
+ destroy() {
2517
+
2518
+ this._trigger.classList.remove( "triggered" );
2519
+
2520
+ delete this._trigger.picker;
1848
2521
 
1849
- let path = menuItem.id;
1850
- let p = parentDom;
2522
+ document.body.removeEventListener( "mousedown", this._onClick, true );
2523
+ document.body.removeEventListener( "focusin", this._onClick, true );
1851
2524
 
1852
- while( p )
1853
- {
1854
- path += "/" + p.id;
1855
- p = p.currentParent?.parentElement;
1856
- }
2525
+ LX.root.querySelectorAll( ".lexcolorpicker" ).forEach( m => { m.remove(); } );
1857
2526
 
1858
- LX.root.querySelectorAll( ".lexdropdownmenu" ).forEach( m => {
1859
- if( !path.includes( m.id ) )
1860
- {
1861
- m.currentParent.built = false;
1862
- m.remove();
1863
- }
1864
- } );
2527
+ ColorPicker.currentPicker = null;
2528
+ }
1865
2529
 
1866
- if( item.submenu )
1867
- {
1868
- if( menuItem.built )
1869
- {
1870
- return;
1871
- }
1872
- menuItem.built = true;
1873
- this._create( item.submenu, menuItem );
1874
- }
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
+ };
1875
2534
 
1876
- e.stopPropagation();
1877
- });
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
+ };
2539
+
2540
+ _updateColorValue( newHexValue, skipCallback = false ) {
2541
+
2542
+ this.currentColor.set( newHexValue ?? this.currentColor.hsv );
2543
+
2544
+ if( this.callback && !skipCallback )
2545
+ {
2546
+ this.callback( this.currentColor );
1878
2547
  }
1879
- }
2548
+
2549
+ this.intSatMarker.style.backgroundColor = this.currentColor.hex;
2550
+
2551
+ if( this.useAlpha )
2552
+ {
2553
+ this.alphaTracker.style.color = `rgb(${ this.currentColor.css.r }, ${ this.currentColor.css.g }, ${ this.currentColor.css.b })`;
2554
+ }
2555
+
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" )
2559
+ {
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( ' ' ) );
2573
+ }
2574
+ else // RGB
2575
+ {
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( ' ' ) );
2580
+ }
2581
+ };
1880
2582
 
1881
2583
  _adjustPosition() {
1882
2584
 
@@ -1938,16 +2640,9 @@ class DropdownMenu {
1938
2640
  this.root.style.left = `${ position[ 0 ] }px`;
1939
2641
  this.root.style.top = `${ position[ 1 ] }px`;
1940
2642
  }
1941
-
1942
- _addSeparator( parent ) {
1943
- const separator = document.createElement('div');
1944
- separator.className = "separator";
1945
- parent = parent ?? this.root;
1946
- parent.appendChild( separator );
1947
- }
1948
2643
  };
1949
2644
 
1950
- LX.DropdownMenu = DropdownMenu;
2645
+ LX.ColorPicker = ColorPicker;
1951
2646
 
1952
2647
  class Area {
1953
2648
 
@@ -3740,7 +4435,7 @@ class Menubar {
3740
4435
  // Otherwise, create it
3741
4436
  button = document.createElement('div');
3742
4437
  const disabled = options.disabled ?? false;
3743
- button.className = "lexmenubutton" + (disabled ? " disabled" : "");
4438
+ button.className = "lexmenubutton main" + (disabled ? " disabled" : "");
3744
4439
  button.title = name;
3745
4440
  button.innerHTML = "<a><image src='" + src + "' class='lexicon' style='height:32px;'></a>";
3746
4441
 
@@ -3810,57 +4505,14 @@ class Menubar {
3810
4505
 
3811
4506
  for( let i = 0; i < buttons.length; ++i )
3812
4507
  {
3813
- let data = buttons[ i ];
3814
- let button = document.createElement( "label" );
4508
+ const data = buttons[ i ];
3815
4509
  const title = data.title;
3816
- let disabled = data.disabled ?? false;
3817
- button.className = "lexmenubutton" + (disabled ? " disabled" : "");
3818
- button.title = title ?? "";
3819
- this.buttonContainer.appendChild( button );
3820
-
3821
- const icon = document.createElement( "a" );
3822
- icon.className = data.icon + " lexicon";
3823
- button.appendChild( icon );
3824
-
3825
- let trigger = icon;
3826
-
3827
- if( data.swap )
3828
- {
3829
- button.classList.add( "swap" );
3830
- icon.classList.add( "swap-off" );
3831
-
3832
- const input = document.createElement( "input" );
3833
- input.type = "checkbox";
3834
- button.prepend( input );
3835
- trigger = input;
3836
-
3837
- const swapIcon = document.createElement( "a" );
3838
- swapIcon.className = data.swap + " swap-on lexicon";
3839
- button.appendChild( swapIcon );
3840
-
3841
- button.swap = function() {
3842
- const swapInput = this.querySelector( "input" );
3843
- swapInput.checked = !swapInput.checked;
3844
- };
3845
-
3846
- // Set if swap has to be performed
3847
- button.setState = function( v ) {
3848
- const swapInput = this.querySelector( "input" );
3849
- swapInput.checked = v;
3850
- };
3851
- }
3852
-
3853
- trigger.addEventListener("click", e => {
3854
- if( data.callback && !disabled )
3855
- {
3856
- const swapInput = button.querySelector( "input" );
3857
- data.callback.call( this, e, swapInput?.checked );
3858
- }
3859
- });
4510
+ const button = new Button( title, "", data.callback, { title, buttonClass: "bg-none", disabled: data.disabled, icon: data.icon, hideName: true, swap: data.swap } )
4511
+ this.buttonContainer.appendChild( button.root );
3860
4512
 
3861
4513
  if( title )
3862
4514
  {
3863
- this.buttons[ title ] = button;
4515
+ this.buttons[ title ] = button.root;
3864
4516
  }
3865
4517
  }
3866
4518
  }
@@ -3947,7 +4599,7 @@ class SideBar {
3947
4599
 
3948
4600
  if( this.collapsable )
3949
4601
  {
3950
- const icon = LX.makeIcon( "sidebar", "Toggle Sidebar", "toggler" );
4602
+ const icon = LX.makeIcon( "sidebar", { title: "Toggle Sidebar", iconClass: "toggler" } );
3951
4603
  this.header.appendChild( icon );
3952
4604
 
3953
4605
  icon.addEventListener( "click", (e) => {
@@ -4434,7 +5086,7 @@ class SideBar {
4434
5086
 
4435
5087
  if( options.action )
4436
5088
  {
4437
- const actionIcon = LX.makeIcon( options.action.icon ?? "more-horizontal", options.action.name );
5089
+ const actionIcon = LX.makeIcon( options.action.icon ?? "more-horizontal", { title: options.action.name } );
4438
5090
  itemDom.appendChild( actionIcon );
4439
5091
 
4440
5092
  actionIcon.addEventListener( "click", (e) => {
@@ -4494,7 +5146,7 @@ class SideBar {
4494
5146
 
4495
5147
  if( suboptions.action )
4496
5148
  {
4497
- const actionIcon = LX.makeIcon( suboptions.action.icon ?? "more-horizontal", suboptions.action.name );
5149
+ const actionIcon = LX.makeIcon( suboptions.action.icon ?? "more-horizontal", { title: suboptions.action.name } );
4498
5150
  subentry.appendChild( actionIcon );
4499
5151
 
4500
5152
  actionIcon.addEventListener( "click", (e) => {
@@ -4563,14 +5215,15 @@ class Widget {
4563
5215
  static SEPARATOR = 26;
4564
5216
  static KNOB = 27;
4565
5217
  static SIZE = 28;
4566
- static PAD = 29;
4567
- static FORM = 30;
4568
- static DIAL = 31;
4569
- static COUNTER = 32;
4570
- static TABLE = 33;
4571
- static TABS = 34;
4572
- static LABEL = 35;
4573
- static BLANK = 36;
5218
+ static OTP = 29;
5219
+ static PAD = 30;
5220
+ static FORM = 31;
5221
+ static DIAL = 32;
5222
+ static COUNTER = 33;
5223
+ static TABLE = 34;
5224
+ static TABS = 35;
5225
+ static LABEL = 36;
5226
+ static BLANK = 37;
4574
5227
 
4575
5228
  static NO_CONTEXT_TYPES = [
4576
5229
  Widget.BUTTON,
@@ -4685,7 +5338,7 @@ class Widget {
4685
5338
 
4686
5339
  _addResetProperty( container, callback ) {
4687
5340
 
4688
- const domEl = LX.makeIcon( "rotate-left", "Reset" )
5341
+ const domEl = LX.makeIcon( "rotate-left", { title: "Reset" } )
4689
5342
  domEl.style.display = "none";
4690
5343
  domEl.style.marginRight = "6px";
4691
5344
  domEl.style.marginLeft = "0";
@@ -5815,40 +6468,17 @@ class Button extends Widget {
5815
6468
  super( Widget.BUTTON, name, null, options );
5816
6469
 
5817
6470
  this.onGetValue = () => {
5818
- return wValue.innerText;
6471
+ return wValue.querySelector( "input" )?.checked;
5819
6472
  };
5820
6473
 
5821
6474
  this.onSetValue = ( newValue, skipCallback, event ) => {
5822
6475
 
5823
- wValue.innerHTML = "";
5824
-
5825
- if( options.icon )
5826
- {
5827
- let icon = null;
5828
-
5829
- // @legacy
5830
- if( options.icon.includes( "fa-" ) )
5831
- {
5832
- icon = document.createElement( 'a' );
5833
- icon.className = options.icon;
5834
- }
5835
- else
5836
- {
5837
- icon = LX.makeIcon( options.icon );
5838
- }
5839
-
5840
- wValue.prepend( icon );
5841
- }
5842
- else if( options.img )
5843
- {
5844
- let img = document.createElement( 'img' );
5845
- img.src = options.img;
5846
- wValue.prepend( img );
5847
- }
5848
- else
6476
+ if( !( options.swap ?? false ) )
5849
6477
  {
5850
- wValue.innerHTML = `<span>${ ( newValue || "" ) }</span>`;
6478
+ return;
5851
6479
  }
6480
+
6481
+ this.root.setState( newValue, skipCallback );
5852
6482
  };
5853
6483
 
5854
6484
  this.onResize = ( rect ) => {
@@ -5872,25 +6502,98 @@ class Button extends Widget {
5872
6502
  wValue.classList.add( "selected" );
5873
6503
  }
5874
6504
 
5875
- this.onSetValue( value, true );
6505
+ if( options.icon )
6506
+ {
6507
+ let icon = null;
6508
+
6509
+ // @legacy
6510
+ if( options.icon.includes( "fa-" ) )
6511
+ {
6512
+ icon = document.createElement( 'a' );
6513
+ icon.className = options.icon + " lexicon";
6514
+ }
6515
+ else
6516
+ {
6517
+ icon = LX.makeIcon( options.icon );
6518
+ }
6519
+
6520
+ wValue.prepend( icon );
6521
+ }
6522
+ else if( options.img )
6523
+ {
6524
+ let img = document.createElement( 'img' );
6525
+ img.src = options.img;
6526
+ wValue.prepend( img );
6527
+ }
6528
+ else
6529
+ {
6530
+ wValue.innerHTML = `<span>${ ( value || "" ) }</span>`;
6531
+ }
5876
6532
 
5877
6533
  if( options.disabled )
5878
6534
  {
5879
6535
  wValue.setAttribute( "disabled", true );
5880
6536
  }
5881
6537
 
5882
- wValue.addEventListener( "click", e => {
6538
+ let trigger = wValue;
6539
+
6540
+ if( options.swap )
6541
+ {
6542
+ wValue.classList.add( "swap" );
6543
+ wValue.querySelector( "a" ).classList.add( "swap-off" );
6544
+
6545
+ const input = document.createElement( "input" );
6546
+ input.type = "checkbox";
6547
+ wValue.prepend( input );
6548
+
6549
+ let swapIcon = null;
6550
+
6551
+ // @legacy
6552
+ if( options.swap.includes( "fa-" ) )
6553
+ {
6554
+ swapIcon = document.createElement( 'a' );
6555
+ swapIcon.className = options.swap + " swap-on lexicon";
6556
+ }
6557
+ else
6558
+ {
6559
+ swapIcon = LX.makeIcon( options.swap, { iconClass: "swap-on" } );
6560
+ }
6561
+
6562
+ wValue.appendChild( swapIcon );
6563
+
6564
+ this.root.swap = function( skipCallback ) {
6565
+ const swapInput = wValue.querySelector( "input" );
6566
+ swapInput.checked = !swapInput.checked;
6567
+ if( !skipCallback )
6568
+ {
6569
+ trigger.click();
6570
+ }
6571
+ };
6572
+
6573
+ // Set if swap has to be performed
6574
+ this.root.setState = function( v, skipCallback ) {
6575
+ const swapInput = wValue.querySelector( "input" );
6576
+ swapInput.checked = v;
6577
+ if( !skipCallback )
6578
+ {
6579
+ trigger.click();
6580
+ }
6581
+ };
6582
+ }
6583
+
6584
+ trigger.addEventListener( "click", e => {
5883
6585
  if( options.selectable )
5884
6586
  {
5885
6587
  if( options.parent )
5886
6588
  {
5887
- options.parent.querySelectorAll(".lexbutton.selected").forEach( e => { if( e == wValue ) return; e.classList.remove( "selected" ) } );
6589
+ options.parent.querySelectorAll(".lexbutton.selected").forEach( b => { if( b == wValue ) return; b.classList.remove( "selected" ) } );
5888
6590
  }
5889
6591
 
5890
6592
  wValue.classList.toggle('selected');
5891
6593
  }
5892
6594
 
5893
- this._trigger( new IEvent( name, value, e ), callback );
6595
+ const swapInput = wValue.querySelector( "input" );
6596
+ this._trigger( new IEvent( name, swapInput?.checked ?? value, e ), callback );
5894
6597
  });
5895
6598
 
5896
6599
  if( options.tooltip )
@@ -6285,7 +6988,7 @@ class Select extends Widget {
6285
6988
 
6286
6989
  const selectRoot = selectedOption.root;
6287
6990
  const rect = selectRoot.getBoundingClientRect();
6288
- const nestedDialog = parent.parentElement.closest( "dialog" );
6991
+ const nestedDialog = parent.parentElement.closest( "dialog" ) ?? parent.parentElement.closest( ".lexcolorpicker" );
6289
6992
 
6290
6993
  // Manage vertical aspect
6291
6994
  {
@@ -7335,31 +8038,52 @@ class ColorInput extends Widget {
7335
8038
 
7336
8039
  constructor( name, value, callback, options = {} ) {
7337
8040
 
7338
- value = ( value.constructor === Array ) ? rgbToHex( value ) : value;
8041
+ const useAlpha = options.useAlpha ??
8042
+ ( ( value.constructor === Object && 'a' in value ) || ( value.constructor === String && [ 5, 9 ].includes( value.length ) ) );
8043
+
8044
+ const widgetColor = new Color( value );
8045
+
8046
+ // Force always hex internally
8047
+ value = useAlpha ? widgetColor.hex : widgetColor.hex.substr( 0, 7 );
7339
8048
 
7340
8049
  super( Widget.COLOR, name, value, options );
7341
8050
 
7342
8051
  this.onGetValue = () => {
7343
- return value;
8052
+ const currentColor = new Color( value );
8053
+ return options.useRGB ? currentColor.rgb : value;
7344
8054
  };
7345
8055
 
7346
8056
  this.onSetValue = ( newValue, skipCallback, event ) => {
7347
8057
 
7348
- if( color.useRGB )
8058
+ const newColor = new Color( newValue );
8059
+
8060
+ colorSampleRGB.style.color = value = newColor.hex.substr( 0, 7 );
8061
+
8062
+ if( useAlpha )
7349
8063
  {
7350
- newValue = hexToRgb( newValue );
8064
+ colorSampleAlpha.style.color = value = newColor.hex;
7351
8065
  }
7352
8066
 
7353
8067
  if( !this._skipTextUpdate )
7354
8068
  {
7355
- textWidget.set( newValue, true, event );
8069
+ textWidget.set( value, true, event );
7356
8070
  }
7357
8071
 
7358
- color.value = value = newValue;
7359
-
7360
8072
  if( !skipCallback )
7361
8073
  {
7362
- this._trigger( new IEvent( name, newValue, event ), callback );
8074
+ let retValue = value;
8075
+
8076
+ if( options.useRGB )
8077
+ {
8078
+ retValue = newColor.rgb;
8079
+
8080
+ if( !useAlpha )
8081
+ {
8082
+ delete retValue.a;
8083
+ }
8084
+ }
8085
+
8086
+ this._trigger( new IEvent( name, retValue, event ), callback );
7363
8087
  }
7364
8088
  };
7365
8089
 
@@ -7372,30 +8096,50 @@ class ColorInput extends Widget {
7372
8096
  container.className = "lexcolor";
7373
8097
  this.root.appendChild( container );
7374
8098
 
7375
- let color = document.createElement( 'input' );
7376
- color.style.width = "32px";
7377
- color.type = 'color';
7378
- color.className = "colorinput";
7379
- color.useRGB = options.useRGB ?? false;
7380
- color.value = value;
7381
- container.appendChild( color );
8099
+ let sampleContainer = LX.makeContainer( ["18px", "18px"], "flex flex-row bg-contrast rounded overflow-hidden", "", container );
8100
+ sampleContainer.tabIndex = "1";
8101
+ sampleContainer.addEventListener( "click", e => {
8102
+ if( ( options.disabled ?? false ) )
8103
+ {
8104
+ return;
8105
+ }
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
+ } );
8115
+ } );
8116
+
8117
+ let colorSampleRGB = document.createElement( 'div' );
8118
+ colorSampleRGB.className = "lexcolorsample";
8119
+ colorSampleRGB.style.color = value;
8120
+ sampleContainer.appendChild( colorSampleRGB );
7382
8121
 
7383
- if( options.disabled )
8122
+ let colorSampleAlpha = null;
8123
+
8124
+ if( useAlpha )
7384
8125
  {
7385
- color.disabled = true;
8126
+ colorSampleAlpha = document.createElement( 'div' );
8127
+ colorSampleAlpha.className = "lexcolorsample";
8128
+ colorSampleAlpha.style.color = value;
8129
+ sampleContainer.appendChild( colorSampleAlpha );
8130
+ }
8131
+ else
8132
+ {
8133
+ colorSampleRGB.style.width = "18px";
7386
8134
  }
7387
8135
 
7388
- color.addEventListener( "input", e => {
7389
- this.set( e.target.value, false, e );
7390
- }, false );
7391
-
7392
- const textWidget = new TextInput( null, color.value, v => {
8136
+ const textWidget = new TextInput( null, value, v => {
7393
8137
  this._skipTextUpdate = true;
7394
8138
  this.set( v );
7395
8139
  delete this._skipTextUpdate;
7396
- }, { width: "calc( 100% - 32px )", disabled: options.disabled });
8140
+ }, { width: "calc( 100% - 24px )", disabled: options.disabled });
7397
8141
 
7398
- textWidget.root.style.marginLeft = "4px";
8142
+ textWidget.root.style.marginLeft = "6px";
7399
8143
  container.appendChild( textWidget.root );
7400
8144
 
7401
8145
  doAsync( this.onResize.bind( this ) );
@@ -7705,7 +8449,6 @@ class NumberInput extends Widget {
7705
8449
  else if( e.altKey ) mult *= 0.1;
7706
8450
  value = ( +vecinput.valueAsNumber + mult * dt );
7707
8451
  this.set( value, false, e );
7708
- // vecinput.value = ( +new_value ).toFixed( 4 ).replace( /([0-9]+(\.[0-9]+[1-9])?)(\.?0+$)/, '$1' );
7709
8452
  }
7710
8453
 
7711
8454
  e.stopPropagation();
@@ -8103,6 +8846,164 @@ class SizeInput extends Widget {
8103
8846
 
8104
8847
  LX.SizeInput = SizeInput;
8105
8848
 
8849
+ /**
8850
+ * @class OTPInput
8851
+ * @description OTPInput Widget
8852
+ */
8853
+
8854
+ class OTPInput extends Widget {
8855
+
8856
+ constructor( name, value, callback, options = {} ) {
8857
+
8858
+ const pattern = options.pattern ?? "xxx-xxx";
8859
+ const patternSize = ( pattern.match(/x/g) || [] ).length;
8860
+
8861
+ value = String( value );
8862
+ if( !value.length )
8863
+ {
8864
+ value = "x".repeat( patternSize );
8865
+ }
8866
+
8867
+ super( Widget.OTP, name, value, options );
8868
+
8869
+ this.onGetValue = () => {
8870
+ return +value;
8871
+ };
8872
+
8873
+ this.onSetValue = ( newValue, skipCallback, event ) => {
8874
+
8875
+ value = newValue;
8876
+
8877
+ _refreshInput( value );
8878
+
8879
+ if( !skipCallback )
8880
+ {
8881
+ this._trigger( new IEvent( name, +newValue, event ), callback );
8882
+ }
8883
+ };
8884
+
8885
+ this.onResize = ( rect ) => {
8886
+ const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
8887
+ container.style.width = `calc( 100% - ${ realNameWidth }px)`;
8888
+ };
8889
+
8890
+ this.disabled = options.disabled ?? false;
8891
+
8892
+ const container = document.createElement( 'div' );
8893
+ container.className = "lexotp flex flex-row items-center";
8894
+ this.root.appendChild( container );
8895
+
8896
+ const groups = pattern.split( '-' );
8897
+
8898
+ const _refreshInput = ( valueString ) => {
8899
+
8900
+ container.innerHTML = "";
8901
+
8902
+ let itemsCount = 0;
8903
+ let activeSlot = 0;
8904
+
8905
+ for( let i = 0; i < groups.length; ++i )
8906
+ {
8907
+ const g = groups[ i ];
8908
+
8909
+ for( let j = 0; j < g.length; ++j )
8910
+ {
8911
+ let number = valueString[ itemsCount++ ];
8912
+ number = ( number == 'x' ? '' : number );
8913
+
8914
+ const slotDom = LX.makeContainer( ["36px", "30px"],
8915
+ "lexotpslot border-top border-bottom border-left px-3 cursor-text select-none font-medium outline-none", number, container );
8916
+ slotDom.tabIndex = "1";
8917
+
8918
+ if( this.disabled )
8919
+ {
8920
+ slotDom.classList.add( "disabled" );
8921
+ }
8922
+
8923
+ const otpIndex = itemsCount;
8924
+
8925
+ if( j == 0 )
8926
+ {
8927
+ slotDom.className += " rounded-l";
8928
+ }
8929
+ else if( j == ( g.length - 1 ) )
8930
+ {
8931
+ slotDom.className += " rounded-r border-right";
8932
+ }
8933
+
8934
+ slotDom.addEventListener( "click", () => {
8935
+ if( this.disabled ) { return; }
8936
+ container.querySelectorAll( ".lexotpslot" ).forEach( s => s.classList.remove( "active" ) );
8937
+ const activeDom = container.querySelectorAll( ".lexotpslot" )[ activeSlot ];
8938
+ activeDom.classList.add( "active" );
8939
+ activeDom.focus();
8940
+ } );
8941
+
8942
+ slotDom.addEventListener( "blur", () => {
8943
+ if( this.disabled ) { return; }
8944
+ doAsync( () => {
8945
+ if( container.contains( document.activeElement ) ) { return; }
8946
+ container.querySelectorAll( ".lexotpslot" ).forEach( s => s.classList.remove( "active" ) );
8947
+ }, 10 );
8948
+ } );
8949
+
8950
+ slotDom.addEventListener( "keyup", e => {
8951
+ if( this.disabled ) { return; }
8952
+ if( !/[^0-9]+/g.test( e.key ) )
8953
+ {
8954
+ const number = e.key;
8955
+ console.assert( parseInt( number ) != NaN );
8956
+
8957
+ slotDom.innerHTML = number;
8958
+ valueString = valueString.substring( 0, otpIndex - 1 ) + number + valueString.substring( otpIndex );
8959
+
8960
+ const nexActiveDom = container.querySelectorAll( ".lexotpslot" )[ activeSlot + 1 ];
8961
+ if( nexActiveDom )
8962
+ {
8963
+ container.querySelectorAll( ".lexotpslot" )[ activeSlot ].classList.remove( "active" );
8964
+ nexActiveDom.classList.add( "active" );
8965
+ nexActiveDom.focus();
8966
+ activeSlot++;
8967
+ }
8968
+ else
8969
+ {
8970
+ this.set( valueString );
8971
+ }
8972
+ }
8973
+ else if( e.key == "ArrowLeft" || e.key == "ArrowRight" )
8974
+ {
8975
+ const dt = ( e.key == "ArrowLeft" ) ? -1 : 1;
8976
+ const newActiveDom = container.querySelectorAll( ".lexotpslot" )[ activeSlot + dt ];
8977
+ if( newActiveDom )
8978
+ {
8979
+ container.querySelectorAll( ".lexotpslot" )[ activeSlot ].classList.remove( "active" );
8980
+ newActiveDom.classList.add( "active" );
8981
+ newActiveDom.focus();
8982
+ activeSlot += dt;
8983
+ }
8984
+ }
8985
+ else if( e.key == "Enter" && !valueString.includes( 'x' ) )
8986
+ {
8987
+ this.set( valueString );
8988
+ }
8989
+ } );
8990
+ }
8991
+
8992
+ if( i < ( groups.length - 1 ) )
8993
+ {
8994
+ LX.makeContainer( ["auto", "auto"], "mx-2", `-`, container );
8995
+ }
8996
+ }
8997
+
8998
+ console.assert( itemsCount == valueString.length, "OTP Value/Pattern Mismatch!" )
8999
+ }
9000
+
9001
+ _refreshInput( value );
9002
+ }
9003
+ }
9004
+
9005
+ LX.OTPInput = OTPInput;
9006
+
8106
9007
  /**
8107
9008
  * @class Pad
8108
9009
  * @description Pad Widget
@@ -8762,7 +9663,7 @@ class Table extends Widget {
8762
9663
 
8763
9664
  if( this.customFilters )
8764
9665
  {
8765
- const icon = LX.makeIcon( "circle-plus", null, "sm" );
9666
+ const icon = LX.makeIcon( "circle-plus", { svgClass: "sm" } );
8766
9667
 
8767
9668
  for( let f of this.customFilters )
8768
9669
  {
@@ -8787,7 +9688,6 @@ class Table extends Widget {
8787
9688
  headerContainer.appendChild( customFilterBtn.root );
8788
9689
  }
8789
9690
 
8790
- // const resetIcon = LX.makeIcon( "xmark", null, "sm" );
8791
9691
  this._resetCustomFiltersBtn = new Button(null, "resetButton", ( v ) => {
8792
9692
  this.activeCustomFilters = {};
8793
9693
  this.refresh();
@@ -8799,7 +9699,7 @@ class Table extends Widget {
8799
9699
 
8800
9700
  if( this.toggleColumns )
8801
9701
  {
8802
- const icon = LX.makeIcon( "sliders" );
9702
+ const icon = LX.makeIcon( "sliders-large" );
8803
9703
  const toggleColumnsBtn = new Button( "toggleColumnsBtn", icon.innerHTML + "View", (value, e) => {
8804
9704
  const menuOptions = data.head.map( ( colName, idx ) => {
8805
9705
  const item = {
@@ -8870,7 +9770,7 @@ class Table extends Widget {
8870
9770
  for( const el of body.childNodes )
8871
9771
  {
8872
9772
  data.checkMap[ el.getAttribute( "rowId" ) ] = this.checked;
8873
- el.querySelector( "input" ).checked = this.checked;
9773
+ el.querySelector( "input[type='checkbox']" ).checked = this.checked;
8874
9774
  }
8875
9775
  });
8876
9776
 
@@ -8882,7 +9782,7 @@ class Table extends Widget {
8882
9782
  {
8883
9783
  const th = document.createElement( 'th' );
8884
9784
  th.innerHTML = `<span>${ headData }</span>`;
8885
- th.querySelector( "span" ).appendChild( LX.makeIcon( "menu-arrows", null, "sm" ) );
9785
+ th.querySelector( "span" ).appendChild( LX.makeIcon( "menu-arrows", { svgClass: "sm" } ) );
8886
9786
 
8887
9787
  const idx = data.head.indexOf( headData );
8888
9788
  if( this.centered && this.centered.indexOf( idx ) > -1 )
@@ -9084,10 +9984,20 @@ class Table extends Widget {
9084
9984
  input.addEventListener( 'change', function() {
9085
9985
  data.checkMap[ rowId ] = this.checked;
9086
9986
 
9987
+ const headInput = table.querySelector( "thead input[type='checkbox']" );
9988
+
9087
9989
  if( !this.checked )
9088
9990
  {
9089
- const input = table.querySelector( "thead input[type='checkbox']" );
9090
- input.checked = data.checkMap[ ":root" ] = false;
9991
+ headInput.checked = data.checkMap[ ":root" ] = false;
9992
+ }
9993
+ else
9994
+ {
9995
+ const rowInputs = Array.from( table.querySelectorAll( "tbody input[type='checkbox']" ) );
9996
+ const uncheckedRowInputs = rowInputs.filter( i => { return !i.checked; } );
9997
+ if( !uncheckedRowInputs.length )
9998
+ {
9999
+ headInput.checked = data.checkMap[ ":root" ] = true;
10000
+ }
9091
10001
  }
9092
10002
  });
9093
10003
 
@@ -9123,7 +10033,7 @@ class Table extends Widget {
9123
10033
 
9124
10034
  if( action == "delete" )
9125
10035
  {
9126
- button = LX.makeIcon( "trash-can", "Delete Row" );
10036
+ button = LX.makeIcon( "trash-can", { title: "Delete Row" } );
9127
10037
  button.addEventListener( 'click', function() {
9128
10038
  // Don't need to refresh table..
9129
10039
  data.body.splice( r, 1 );
@@ -9132,7 +10042,7 @@ class Table extends Widget {
9132
10042
  }
9133
10043
  else if( action == "menu" )
9134
10044
  {
9135
- button = LX.makeIcon( "more-horizontal", "Menu" );
10045
+ button = LX.makeIcon( "more-horizontal", { title: "Menu" } );
9136
10046
  button.addEventListener( 'click', function( event ) {
9137
10047
  if( !options.onMenuAction )
9138
10048
  {
@@ -9148,7 +10058,7 @@ class Table extends Widget {
9148
10058
  else // custom actions
9149
10059
  {
9150
10060
  console.assert( action.constructor == Object );
9151
- button = LX.makeIcon( action.icon, action.title );
10061
+ button = LX.makeIcon( action.icon, { title: action.title } );
9152
10062
 
9153
10063
  if( action.callback )
9154
10064
  {
@@ -10198,6 +11108,22 @@ class Panel {
10198
11108
  return this._attachWidget( widget );
10199
11109
  }
10200
11110
 
11111
+ /**
11112
+ * @method addOTP
11113
+ * @param {String} name Widget name
11114
+ * @param {String} value Default numeric value in string format
11115
+ * @param {Function} callback Callback function on change
11116
+ * @param {Object} options:
11117
+ * hideName: Don't use name as label [false]
11118
+ * disabled: Make the widget disabled [false]
11119
+ * pattern: OTP numeric pattern
11120
+ */
11121
+
11122
+ addOTP( name, value, callback, options = {} ) {
11123
+ const widget = new OTPInput( name, value, callback, options );
11124
+ return this._attachWidget( widget );
11125
+ }
11126
+
10201
11127
  /**
10202
11128
  * @method addPad
10203
11129
  * @param {String} name Widget name
@@ -10430,7 +11356,7 @@ class Branch {
10430
11356
  // add widgets
10431
11357
  for( let w of this.widgets )
10432
11358
  {
10433
- p.root.appendChild( w.domEl );
11359
+ p.root.appendChild( w.root );
10434
11360
  }
10435
11361
  });
10436
11362
  dialog.widgets = this.widgets;
@@ -10734,7 +11660,7 @@ class Dialog {
10734
11660
 
10735
11661
  for( let w of that.widgets )
10736
11662
  {
10737
- branch.content.appendChild( w.domEl );
11663
+ branch.content.appendChild( w.root );
10738
11664
  }
10739
11665
 
10740
11666
  branch.widgets = that.widgets;
@@ -12375,7 +13301,7 @@ class AssetView {
12375
13301
 
12376
13302
  this.rightPanel.addText(null, this.path.join('/'), null, {
12377
13303
  inputClass: "nobg", disabled: true, signal: "@on_folder_change",
12378
- style: { fontWeight: "600", fontSize: "15px" }
13304
+ style: { fontWeight: "600", fontSize: "15px" }
12379
13305
  });
12380
13306
 
12381
13307
  this.rightPanel.endLine();
@@ -13112,7 +14038,7 @@ Element.prototype.addClass = function( className ) {
13112
14038
  }
13113
14039
 
13114
14040
  Element.prototype.getComputedSize = function() {
13115
- // Since we use "box-sizing: border-box" now,
14041
+ // Since we use "box-sizing: border-box" now,
13116
14042
  // it's all included in offsetWidth/offsetHeight
13117
14043
  return {
13118
14044
  width: this.offsetWidth,
@@ -13254,6 +14180,7 @@ LX.ICONS = {
13254
14180
  "copy": [448, 512, [], "regular", "M384 336l-192 0c-8.8 0-16-7.2-16-16l0-256c0-8.8 7.2-16 16-16l140.1 0L400 115.9 400 320c0 8.8-7.2 16-16 16zM192 384l192 0c35.3 0 64-28.7 64-64l0-204.1c0-12.7-5.1-24.9-14.1-33.9L366.1 14.1c-9-9-21.2-14.1-33.9-14.1L192 0c-35.3 0-64 28.7-64 64l0 256c0 35.3 28.7 64 64 64zM64 128c-35.3 0-64 28.7-64 64L0 448c0 35.3 28.7 64 64 64l192 0c35.3 0 64-28.7 64-64l0-32-48 0 0 32c0 8.8-7.2 16-16 16L64 464c-8.8 0-16-7.2-16-16l0-256c0-8.8 7.2-16 16-16l32 0 0-48-32 0z"],
13255
14181
  "paste": [512, 512, [], "regular", "M104.6 48L64 48C28.7 48 0 76.7 0 112L0 384c0 35.3 28.7 64 64 64l96 0 0-48-96 0c-8.8 0-16-7.2-16-16l0-272c0-8.8 7.2-16 16-16l16 0c0 17.7 14.3 32 32 32l72.4 0C202 108.4 227.6 96 256 96l62 0c-7.1-27.6-32.2-48-62-48l-40.6 0C211.6 20.9 188.2 0 160 0s-51.6 20.9-55.4 48zM144 56a16 16 0 1 1 32 0 16 16 0 1 1 -32 0zM448 464l-192 0c-8.8 0-16-7.2-16-16l0-256c0-8.8 7.2-16 16-16l140.1 0L464 243.9 464 448c0 8.8-7.2 16-16 16zM256 512l192 0c35.3 0 64-28.7 64-64l0-204.1c0-12.7-5.1-24.9-14.1-33.9l-67.9-67.9c-9-9-21.2-14.1-33.9-14.1L256 128c-35.3 0-64 28.7-64 64l0 256c0 35.3 28.7 64 64 64z"],
13256
14182
  "clipboard": [384, 512, [], "regular", "M280 64l40 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l40 0 9.6 0C121 27.5 153.3 0 192 0s71 27.5 78.4 64l9.6 0zM64 112c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16l-16 0 0 24c0 13.3-10.7 24-24 24l-88 0-88 0c-13.3 0-24-10.7-24-24l0-24-16 0zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"],
14183
+ "eye-dropper": [512, 512, [], "solid", "M341.6 29.2L240.1 130.8l-9.4-9.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-9.4-9.4L482.8 170.4c39-39 39-102.2 0-141.1s-102.2-39-141.1 0zM55.4 323.3c-15 15-23.4 35.4-23.4 56.6l0 42.4L5.4 462.2c-8.5 12.7-6.8 29.6 4 40.4s27.7 12.5 40.4 4L89.7 480l42.4 0c21.2 0 41.6-8.4 56.6-23.4L309.4 335.9l-45.3-45.3L143.4 411.3c-3 3-7.1 4.7-11.3 4.7L96 416l0-36.1c0-4.2 1.7-8.3 4.7-11.3L221.4 247.9l-45.3-45.3L55.4 323.3z"],
13257
14184
  "edit": [512, 512, [], "regular", "M441 58.9L453.1 71c9.4 9.4 9.4 24.6 0 33.9L424 134.1 377.9 88 407 58.9c9.4-9.4 24.6-9.4 33.9 0zM209.8 256.2L344 121.9 390.1 168 255.8 302.2c-2.9 2.9-6.5 5-10.4 6.1l-58.5 16.7 16.7-58.5c1.1-3.9 3.2-7.5 6.1-10.4zM373.1 25L175.8 222.2c-8.7 8.7-15 19.4-18.3 31.1l-28.6 100c-2.4 8.4-.1 17.4 6.1 23.6s15.2 8.5 23.6 6.1l100-28.6c11.8-3.4 22.5-9.7 31.1-18.3L487 138.9c28.1-28.1 28.1-73.7 0-101.8L474.9 25C446.8-3.1 401.2-3.1 373.1 25zM88 64C39.4 64 0 103.4 0 152L0 424c0 48.6 39.4 88 88 88l272 0c48.6 0 88-39.4 88-88l0-112c0-13.3-10.7-24-24-24s-24 10.7-24 24l0 112c0 22.1-17.9 40-40 40L88 464c-22.1 0-40-17.9-40-40l0-272c0-22.1 17.9-40 40-40l112 0c13.3 0 24-10.7 24-24s-10.7-24-24-24L88 64z"],
13258
14185
  "envelope": [512, 512, [], "regular", "M64 112c-8.8 0-16 7.2-16 16l0 22.1L220.5 291.7c20.7 17 50.4 17 71.1 0L464 150.1l0-22.1c0-8.8-7.2-16-16-16L64 112zM48 212.2L48 384c0 8.8 7.2 16 16 16l384 0c8.8 0 16-7.2 16-16l0-171.8L322 328.8c-38.4 31.5-93.7 31.5-132 0L48 212.2zM0 128C0 92.7 28.7 64 64 64l384 0c35.3 0 64 28.7 64 64l0 256c0 35.3-28.7 64-64 64L64 448c-35.3 0-64-28.7-64-64L0 128z"],
13259
14186
  "envelope-open": [512, 512, [], "regular", "M255.4 48.2c.2-.1 .4-.2 .6-.2s.4 .1 .6 .2L460.6 194c2.1 1.5 3.4 3.9 3.4 6.5l0 13.6L291.5 355.7c-20.7 17-50.4 17-71.1 0L48 214.1l0-13.6c0-2.6 1.2-5 3.4-6.5L255.4 48.2zM48 276.2L190 392.8c38.4 31.5 93.7 31.5 132 0L464 276.2 464 456c0 4.4-3.6 8-8 8L56 464c-4.4 0-8-3.6-8-8l0-179.8zM256 0c-10.2 0-20.2 3.2-28.5 9.1L23.5 154.9C8.7 165.4 0 182.4 0 200.5L0 456c0 30.9 25.1 56 56 56l400 0c30.9 0 56-25.1 56-56l0-255.5c0-18.1-8.7-35.1-23.4-45.6L284.5 9.1C276.2 3.2 266.2 0 256 0z"],