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.
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  var LX = {
9
- version: "0.5.5",
9
+ version: "0.5.7",
10
10
  ready: false,
11
11
  components: [], // Specific pre-build components
12
12
  signals: {}, // Events and triggers
@@ -216,37 +216,141 @@ LX.getBase64Image = getBase64Image;
216
216
 
217
217
  /**
218
218
  * @method hexToRgb
219
- * @description Convert a hexadecimal string to a valid RGB color array
220
- * @param {String} hexStr Hexadecimal color
219
+ * @description Convert a hexadecimal string to a valid RGB color
220
+ * @param {String} hex Hexadecimal color
221
221
  */
222
- function hexToRgb( hexStr )
222
+ function hexToRgb( hex )
223
223
  {
224
- const red = parseInt( hexStr.substring( 1, 3 ), 16 ) / 255;
225
- const green = parseInt( hexStr.substring( 3, 5 ), 16 ) / 255;
226
- const blue = parseInt( hexStr.substring( 5, 7 ), 16 ) / 255;
227
- return [ red, green, blue ];
224
+ const hexPattern = /^#(?:[A-Fa-f0-9]{3,4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$/;
225
+ if( !hexPattern.test( hex ) )
226
+ {
227
+ throw( `Invalid Hex Color: ${ hex }` );
228
+ }
229
+
230
+ hex = hex.replace( /^#/, '' );
231
+
232
+ // Expand shorthand form (#RGB or #RGBA)
233
+ if( hex.length === 3 || hex.length === 4 )
234
+ {
235
+ hex = hex.split( '' ).map( c => c + c ).join( '' );
236
+ }
237
+
238
+ const bigint = parseInt( hex, 16 );
239
+
240
+ const r = ( ( bigint >> ( hex.length === 8 ? 24 : 16 ) ) & 255 ) / 255;
241
+ const g = ( ( bigint >> ( hex.length === 8 ? 16 : 8 ) ) & 255 ) / 255;
242
+ const b = ( ( bigint >> ( hex.length === 8 ? 8 : 0 ) ) & 255 ) / 255;
243
+ const a = ( hex.length === 8 ? ( bigint & 255 ) : ( hex.length === 4 ? parseInt( hex.slice( -2 ), 16 ) : 255 ) ) / 255;
244
+
245
+ return { r, g, b, a };
228
246
  }
229
247
 
230
248
  LX.hexToRgb = hexToRgb;
231
249
 
250
+ /**
251
+ * @method hexToHsv
252
+ * @description Convert a hexadecimal string to HSV (0..360|0..1|0..1)
253
+ * @param {String} hexStr Hexadecimal color
254
+ */
255
+ function hexToHsv( hexStr )
256
+ {
257
+ const rgb = hexToRgb( hexStr );
258
+ return rgbToHsv( rgb );
259
+ }
260
+
261
+ LX.hexToHsv = hexToHsv;
262
+
232
263
  /**
233
264
  * @method rgbToHex
234
- * @description Convert a RGB color array to a hexadecimal string
235
- * @param {Array} rgb Array containing R, G, B, A*
265
+ * @description Convert a RGB color to a hexadecimal string
266
+ * @param {Object} rgb Object containing RGB color
267
+ * @param {Number} scale Use 255 for 0..255 range or 1 for 0..1 range
236
268
  */
237
- function rgbToHex( rgb )
269
+ function rgbToHex( rgb, scale = 255 )
238
270
  {
239
- let hex = "#";
240
- for( let c of rgb )
241
- {
242
- c = Math.floor( c * 255 );
243
- hex += c.toString( 16 );
244
- }
245
- return hex;
271
+ const rgbArray = [ rgb.r, rgb.g, rgb.b ];
272
+ if( rgb.a != undefined ) rgbArray.push( rgb.a );
273
+
274
+ return (
275
+ "#" +
276
+ rgbArray.map( c => {
277
+ c = Math.floor( c * scale );
278
+ const hex = c.toString(16);
279
+ return hex.length === 1 ? ( '0' + hex ) : hex;
280
+ }).join("")
281
+ );
246
282
  }
247
283
 
248
284
  LX.rgbToHex = rgbToHex;
249
285
 
286
+ /**
287
+ * @method rgbToCss
288
+ * @description Convert a RGB color (0..1) to a CSS color format
289
+ * @param {Object} rgb Object containing RGB color
290
+ */
291
+ function rgbToCss( rgb )
292
+ {
293
+ return { r: Math.floor( rgb.r * 255 ), g: Math.floor( rgb.g * 255 ), b: Math.floor( rgb.b * 255 ), a: rgb.a };
294
+ }
295
+
296
+ LX.rgbToCss = rgbToCss;
297
+
298
+ /**
299
+ * @method rgbToHsv
300
+ * @description Convert a RGB color (0..1) array to HSV (0..360|0..1|0..1)
301
+ * @param {Object} rgb Array containing R, G, B
302
+ */
303
+ function rgbToHsv( rgb )
304
+ {
305
+ let { r, g, b, a } = rgb;
306
+ a = a ?? 1;
307
+
308
+ const max = Math.max(r, g, b);
309
+ const min = Math.min(r, g, b);
310
+ const d = max - min;
311
+ let h = 0;
312
+
313
+ if (d !== 0) {
314
+ if (max === r) { h = ((g - b) / d) % 6 }
315
+ else if (max === g) { h = (b - r) / d + 2 }
316
+ else { h = (r - g) / d + 4 }
317
+ h *= 60
318
+ if (h < 0) { h += 360 }
319
+ }
320
+
321
+ const s = max === 0 ? 0 : (d / max);
322
+ const v = max;
323
+
324
+ return { h, s, v, a };
325
+ }
326
+
327
+ LX.rgbToHsv = rgbToHsv;
328
+
329
+ /**
330
+ * @method hsvToRgb
331
+ * @description Convert an HSV color (0..360|0..1|0..1) array to RGB (0..1|0..255)
332
+ * @param {Array} hsv Array containing H, S, V
333
+ */
334
+ function hsvToRgb( hsv )
335
+ {
336
+ const { h, s, v, a } = hsv;
337
+ const c = v * s;
338
+ const x = c * (1 - Math.abs( ( (h / 60) % 2 ) - 1) )
339
+ const m = v - c;
340
+ let r = 0, g = 0, b = 0;
341
+
342
+ if( h < 60 ) { r = c; g = x; b = 0; }
343
+ else if ( h < 120 ) { r = x; g = c; b = 0; }
344
+ else if ( h < 180 ) { r = 0; g = c; b = x; }
345
+ else if ( h < 240 ) { r = 0; g = x; b = c; }
346
+ else if ( h < 300 ) { r = x; g = 0; b = c; }
347
+ else { r = c; g = 0; b = x; }
348
+
349
+ return { r: ( r + m ), g: ( g + m ), b: ( b + m ), a };
350
+ }
351
+
352
+ LX.hsvToRgb = hsvToRgb;
353
+
250
354
  /**
251
355
  * @method measureRealWidth
252
356
  * @description Measure the pixel width of a text
@@ -578,18 +682,60 @@ function makeCodeSnippet( code, size, options = { } )
578
682
 
579
683
  LX.makeCodeSnippet = makeCodeSnippet;
580
684
 
685
+ /**
686
+ * @method makeKbd
687
+ * @description Kbd element to display a keyboard key.
688
+ * @param {Array} keys
689
+ * @param {String} extraClass
690
+ */
691
+ function makeKbd( keys, extraClass = "" )
692
+ {
693
+ const specialKeys = {
694
+ "Ctrl": '⌃',
695
+ "Enter": '↩',
696
+ "Shift": '⇧',
697
+ "CapsLock": '⇪',
698
+ "Meta": '⌘',
699
+ "Option": '⌥',
700
+ "Alt": '⌥',
701
+ "Tab": '⇥',
702
+ "ArrowUp": '↑',
703
+ "ArrowDown": '↓',
704
+ "ArrowLeft": '←',
705
+ "ArrowRight": '→',
706
+ "Space": '␣'
707
+ };
708
+
709
+ const kbd = LX.makeContainer( ["auto", "auto"], "flex flex-row ml-auto" );
710
+
711
+ for( const k of keys )
712
+ {
713
+ LX.makeContainer( ["auto", "auto"], "self-center text-xs fg-secondary select-none", specialKeys[ k ] ?? k, kbd );
714
+ }
715
+
716
+ return kbd;
717
+ }
718
+
719
+ LX.makeKbd = makeKbd;
720
+
581
721
  /**
582
722
  * @method makeIcon
583
723
  * @description Gets an SVG element using one of LX.ICONS
584
724
  * @param {String} iconName
585
- * @param {String} iconTitle
586
- * @param {String} extraClass
725
+ * @param {Object} options
726
+ * iconTitle
727
+ * extraClass
728
+ * svgClass
587
729
  */
588
- function makeIcon( iconName, iconTitle, extraClass = "" )
730
+ function makeIcon( iconName, options = { } )
589
731
  {
590
732
  let data = LX.ICONS[ iconName ];
591
733
  console.assert( data, `No icon named _${ iconName }_` );
592
734
 
735
+ const iconTitle = options.iconTitle;
736
+ const iconClass = options.iconClass;
737
+ const svgClass = options.svgClass;
738
+
593
739
  // Just another name for the same icon..
594
740
  if( data.constructor == String )
595
741
  {
@@ -599,9 +745,9 @@ function makeIcon( iconName, iconTitle, extraClass = "" )
599
745
  const svg = document.createElementNS( "http://www.w3.org/2000/svg", "svg" );
600
746
  svg.setAttribute( "viewBox", `0 0 ${ data[ 0 ] } ${ data[ 1 ] }` );
601
747
 
602
- if( extraClass )
748
+ if( svgClass )
603
749
  {
604
- svg.classList.add( extraClass );
750
+ svg.classList.add( svgClass );
605
751
  }
606
752
 
607
753
  if( data[ 5 ] )
@@ -629,7 +775,7 @@ function makeIcon( iconName, iconTitle, extraClass = "" )
629
775
 
630
776
  const icon = document.createElement( "a" );
631
777
  icon.title = iconTitle ?? "";
632
- icon.className = "lexicon " + extraClass;
778
+ icon.className = "lexicon " + ( iconClass ?? "" );
633
779
  icon.appendChild( svg );
634
780
 
635
781
  return icon;
@@ -716,7 +862,7 @@ function registerCommandbarEntry( name, callback )
716
862
 
717
863
  LX.registerCommandbarEntry = registerCommandbarEntry;
718
864
 
719
- // Math classes
865
+ // Utils classes
720
866
 
721
867
  class vec2 {
722
868
 
@@ -744,6 +890,77 @@ class vec2 {
744
890
 
745
891
  LX.vec2 = vec2;
746
892
 
893
+ class Color {
894
+
895
+ constructor( value ) {
896
+
897
+ Object.defineProperty( Color.prototype, "rgb", {
898
+ get: function() { return this._rgb; },
899
+ set: function( v ) { this._fromRGB( v ) }, enumerable: true, configurable: true
900
+ });
901
+
902
+ Object.defineProperty( Color.prototype, "hex", {
903
+ get: function() { return this._hex; },
904
+ set: function( v ) { this._fromHex( v ) }, enumerable: true, configurable: true
905
+ });
906
+
907
+ Object.defineProperty( Color.prototype, "hsv", {
908
+ get: function() { return this._hsv; },
909
+ set: function( v ) { this._fromHSV( v ) }, enumerable: true, configurable: true
910
+ });
911
+
912
+ this.set( value );
913
+ }
914
+
915
+ set( value ) {
916
+
917
+ if ( typeof value === 'string' && value.startsWith( '#' ) )
918
+ {
919
+ this._fromHex( value );
920
+ }
921
+ else if( 'r' in value && 'g' in value && 'b' in value)
922
+ {
923
+ value.a = value.a ?? 1.0;
924
+ this._fromRGB( value );
925
+ }
926
+ else if( 'h' in value && 's' in value && 'v' in value )
927
+ {
928
+ value.a = value.a ?? 1.0;
929
+ this._fromHSV( value );
930
+ }
931
+ else
932
+ {
933
+ throw( "Bad color model!", value );
934
+ }
935
+ }
936
+
937
+ setHSV( hsv ) { this._fromHSV( hsv ); }
938
+ setRGB( rgb ) { this._fromRGB( rgb ); }
939
+ setHex( hex ) { this._fromHex( hex ); }
940
+
941
+ _fromHex( hex ) {
942
+ this._fromRGB( hexToRgb( hex ) );
943
+ }
944
+
945
+ _fromRGB( rgb ) {
946
+ this._rgb = rgb;
947
+ this._hsv = rgbToHsv( rgb );
948
+ this._hex = rgbToHex( rgb );
949
+ this.css = rgbToCss( this._rgb );
950
+ }
951
+
952
+ _fromHSV( hsv ) {
953
+ this._hsv = hsv;
954
+ this._rgb = hsvToRgb( hsv );
955
+ this._hex = rgbToHex( this._rgb );
956
+ this.css = rgbToCss( this._rgb );
957
+ }
958
+ }
959
+
960
+ LX.Color = Color;
961
+
962
+ // Command bar creation
963
+
747
964
  function _createCommandbar( root )
748
965
  {
749
966
  let commandbar = document.createElement( "dialog" );
@@ -1157,6 +1374,19 @@ function init( options = { } )
1157
1374
 
1158
1375
  LX.init = init;
1159
1376
 
1377
+ /**
1378
+ * @method setStrictViewport
1379
+ * @param {Boolean} value
1380
+ */
1381
+
1382
+ function setStrictViewport( value )
1383
+ {
1384
+ this.usingStrictViewport = value ?? true;
1385
+ document.documentElement.setAttribute( "data-strictVP", ( this.usingStrictViewport ) ? "true" : "false" );
1386
+ }
1387
+
1388
+ LX.setStrictViewport = setStrictViewport;
1389
+
1160
1390
  /**
1161
1391
  * @method setCommandbarState
1162
1392
  * @param {Boolean} value
@@ -1801,6 +2031,13 @@ class DropdownMenu {
1801
2031
  submenuIcon.className = "fa-solid fa-angle-right fa-xs";
1802
2032
  menuItem.appendChild( submenuIcon );
1803
2033
  }
2034
+ else if( item.kbd )
2035
+ {
2036
+ item.kbd = [].concat( item.kbd );
2037
+
2038
+ const kbd = LX.makeKbd( item.kbd );
2039
+ menuItem.appendChild( kbd );
2040
+ }
1804
2041
 
1805
2042
  if( item.icon )
1806
2043
  {
@@ -1838,39 +2075,504 @@ class DropdownMenu {
1838
2075
  } );
1839
2076
  }
1840
2077
 
1841
- menuItem.addEventListener("mouseover", e => {
2078
+ menuItem.addEventListener("mouseover", e => {
2079
+
2080
+ let path = menuItem.id;
2081
+ let p = parentDom;
2082
+
2083
+ while( p )
2084
+ {
2085
+ path += "/" + p.id;
2086
+ p = p.currentParent?.parentElement;
2087
+ }
2088
+
2089
+ LX.root.querySelectorAll( ".lexdropdownmenu" ).forEach( m => {
2090
+ if( !path.includes( m.id ) )
2091
+ {
2092
+ m.currentParent.built = false;
2093
+ m.remove();
2094
+ }
2095
+ } );
2096
+
2097
+ if( item.submenu )
2098
+ {
2099
+ if( menuItem.built )
2100
+ {
2101
+ return;
2102
+ }
2103
+ menuItem.built = true;
2104
+ this._create( item.submenu, menuItem );
2105
+ }
2106
+
2107
+ e.stopPropagation();
2108
+ });
2109
+ }
2110
+ }
2111
+
2112
+ _adjustPosition() {
2113
+
2114
+ const position = [ 0, 0 ];
2115
+
2116
+ // Place menu using trigger position and user options
2117
+ {
2118
+ const rect = this._trigger.getBoundingClientRect();
2119
+
2120
+ let alignWidth = true;
2121
+
2122
+ switch( this.side )
2123
+ {
2124
+ case "left":
2125
+ position[ 0 ] += ( rect.x - this.root.offsetWidth );
2126
+ alignWidth = false;
2127
+ break;
2128
+ case "right":
2129
+ position[ 0 ] += ( rect.x + rect.width );
2130
+ alignWidth = false;
2131
+ break;
2132
+ case "top":
2133
+ position[ 1 ] += ( rect.y - this.root.offsetHeight );
2134
+ alignWidth = true;
2135
+ break;
2136
+ case "bottom":
2137
+ position[ 1 ] += ( rect.y + rect.height );
2138
+ alignWidth = true;
2139
+ break;
2140
+ default:
2141
+ break;
2142
+ }
2143
+
2144
+ switch( this.align )
2145
+ {
2146
+ case "start":
2147
+ if( alignWidth ) { position[ 0 ] += rect.x; }
2148
+ else { position[ 1 ] += rect.y; }
2149
+ break;
2150
+ case "center":
2151
+ if( alignWidth ) { position[ 0 ] += ( rect.x + rect.width * 0.5 ) - this.root.offsetWidth * 0.5; }
2152
+ else { position[ 1 ] += ( rect.y + rect.height * 0.5 ) - this.root.offsetHeight * 0.5; }
2153
+ break;
2154
+ case "end":
2155
+ if( alignWidth ) { position[ 0 ] += rect.x - this.root.offsetWidth + rect.width; }
2156
+ else { position[ 1 ] += rect.y - this.root.offsetHeight + rect.height; }
2157
+ break;
2158
+ default:
2159
+ break;
2160
+ }
2161
+ }
2162
+
2163
+ if( this.avoidCollisions )
2164
+ {
2165
+ position[ 0 ] = LX.clamp( position[ 0 ], 0, window.innerWidth - this.root.offsetWidth - this._windowPadding );
2166
+ position[ 1 ] = LX.clamp( position[ 1 ], 0, window.innerHeight - this.root.offsetHeight - this._windowPadding );
2167
+ }
2168
+
2169
+ this.root.style.left = `${ position[ 0 ] }px`;
2170
+ this.root.style.top = `${ position[ 1 ] }px`;
2171
+ }
2172
+
2173
+ _addSeparator( parent ) {
2174
+ const separator = document.createElement('div');
2175
+ separator.className = "separator";
2176
+ parent = parent ?? this.root;
2177
+ parent.appendChild( separator );
2178
+ }
2179
+ };
2180
+
2181
+ LX.DropdownMenu = DropdownMenu;
2182
+
2183
+ /**
2184
+ * @class ColorPicker
2185
+ */
2186
+
2187
+ class ColorPicker {
2188
+
2189
+ static currentPicker = false;
2190
+
2191
+ constructor( hexValue, trigger, options = {} ) {
2192
+
2193
+ console.assert( trigger, "ColorPicker needs a DOM element as trigger!" );
2194
+
2195
+ this._windowPadding = 4;
2196
+ this.side = options.side ?? "bottom";
2197
+ this.align = options.align ?? "center";
2198
+ this.avoidCollisions = options.avoidCollisions ?? true;
2199
+ this.colorModel = options.colorModel ?? "Hex";
2200
+ this.useAlpha = options.useAlpha ?? false;
2201
+ this.callback = options.onChange;
2202
+
2203
+ if( !this.callback )
2204
+ {
2205
+ console.warn( "Define a callback in _options.onChange_ to allow getting new Color values!" );
2206
+ }
2207
+
2208
+ if( ColorPicker.currentPicker )
2209
+ {
2210
+ ColorPicker.currentPicker.destroy();
2211
+ return;
2212
+ }
2213
+
2214
+ this._trigger = trigger;
2215
+ trigger.classList.add( "triggered" );
2216
+ trigger.picker = this;
2217
+
2218
+ this.root = document.createElement( "div" );
2219
+ this.root.tabIndex = "1";
2220
+ this.root.className = "lexcolorpicker";
2221
+ this.root.dataset["side"] = this.side;
2222
+ LX.root.appendChild( this.root );
2223
+
2224
+ this.root.addEventListener( "keydown", (e) => {
2225
+ if( e.key == "Escape" )
2226
+ {
2227
+ e.preventDefault();
2228
+ e.stopPropagation();
2229
+ this.destroy();
2230
+ }
2231
+ } )
2232
+
2233
+ ColorPicker.currentPicker = this;
2234
+
2235
+ this.markerHalfSize = 8;
2236
+ this.markerSize = this.markerHalfSize * 2;
2237
+ this.currentColor = new Color( hexValue );
2238
+
2239
+ const hueColor = new Color( { h: this.currentColor.hsv.h, s: 1, v: 1 } );
2240
+
2241
+ // Intensity, Sat
2242
+ this.colorPickerBackground = document.createElement( 'div' );
2243
+ this.colorPickerBackground.className = "lexcolorpickerbg";
2244
+ this.colorPickerBackground.style.backgroundColor = `rgb(${ hueColor.css.r }, ${ hueColor.css.g }, ${ hueColor.css.b })`;
2245
+ this.root.appendChild( this.colorPickerBackground );
2246
+
2247
+ this.intSatMarker = document.createElement( 'div' );
2248
+ this.intSatMarker.className = "lexcolormarker";
2249
+ this.intSatMarker.style.backgroundColor = this.currentColor.hex;
2250
+ this.colorPickerBackground.appendChild( this.intSatMarker );
2251
+
2252
+ doAsync( this._svToPosition.bind( this, this.currentColor.hsv.s, this.currentColor.hsv.v ) );
2253
+
2254
+ let innerMouseDown = e => {
2255
+ var doc = this.root.ownerDocument;
2256
+ doc.addEventListener( 'mousemove', innerMouseMove );
2257
+ doc.addEventListener( 'mouseup', innerMouseUp );
2258
+ document.body.classList.add( 'noevents' );
2259
+ e.stopImmediatePropagation();
2260
+ e.stopPropagation();
2261
+
2262
+ const currentLeft = ( e.offsetX - this.markerHalfSize );
2263
+ this.intSatMarker.style.left = currentLeft + "px";
2264
+ const currentTop = ( e.offsetY - this.markerHalfSize );
2265
+ this.intSatMarker.style.top = currentTop + "px";
2266
+ this._positionToSv( currentLeft, currentTop );
2267
+ this._updateColorValue();
2268
+ }
2269
+
2270
+ let innerMouseMove = e => {
2271
+ const dX = e.movementX;
2272
+ const dY = e.movementY;
2273
+
2274
+ const rect = this.colorPickerBackground.getBoundingClientRect();
2275
+ const mouseX = e.offsetX - rect.x;
2276
+ const mouseY = e.offsetY - rect.y;
2277
+
2278
+ if ( dX != 0 && ( mouseX >= 0 || dX < 0 ) && ( mouseX < this.colorPickerBackground.offsetWidth || dX > 0 ) )
2279
+ {
2280
+ this.intSatMarker.style.left = LX.clamp( parseInt( this.intSatMarker.style.left ) + dX, -this.markerHalfSize, this.colorPickerBackground.offsetWidth - this.markerHalfSize ) + "px";
2281
+ }
2282
+
2283
+ if ( dY != 0 && ( mouseY >= 0 || dY < 0 ) && ( mouseY < this.colorPickerBackground.offsetHeight || dY > 0 ) )
2284
+ {
2285
+ this.intSatMarker.style.top = LX.clamp( parseInt( this.intSatMarker.style.top ) + dY, -this.markerHalfSize, this.colorPickerBackground.offsetHeight - this.markerHalfSize ) + "px";
2286
+ }
2287
+
2288
+ this._positionToSv( parseInt( this.intSatMarker.style.left ), parseInt( this.intSatMarker.style.top ) );
2289
+ this._updateColorValue();
2290
+
2291
+ e.stopPropagation();
2292
+ e.preventDefault();
2293
+ }
2294
+
2295
+ let innerMouseUp = e => {
2296
+ var doc = this.root.ownerDocument;
2297
+ doc.removeEventListener( 'mousemove', innerMouseMove );
2298
+ doc.removeEventListener( 'mouseup', innerMouseUp );
2299
+ document.body.classList.remove( 'noevents' );
2300
+ }
2301
+
2302
+ this.colorPickerBackground.addEventListener( "mousedown", innerMouseDown );
2303
+
2304
+ const hueAlphaContainer = LX.makeContainer( ["100%", "auto"], "flex flex-row gap-1 items-center", "", this.root );
2305
+
2306
+ if( window.EyeDropper )
2307
+ {
2308
+ hueAlphaContainer.appendChild( new Button(null, "eyedrop", async () => {
2309
+ const eyeDropper = new EyeDropper()
2310
+ try {
2311
+ const result = await eyeDropper.open();
2312
+ this.fromHexColor( result.sRGBHex );
2313
+ } catch ( err ) {
2314
+ // console.error("EyeDropper cancelled or failed: ", err)
2315
+ }
2316
+ }, { icon: "eye-dropper", buttonClass: "bg-none", title: "Sample Color" }).root );
2317
+ }
2318
+
2319
+ const innerHueAlpha = LX.makeContainer( ["100%", "100%"], "flex flex-col gap-2", "", hueAlphaContainer );
2320
+
2321
+ // Hue
2322
+ this.colorPickerTracker = document.createElement( 'div' );
2323
+ this.colorPickerTracker.className = "lexhuetracker";
2324
+ innerHueAlpha.appendChild( this.colorPickerTracker );
2325
+
2326
+ this.hueMarker = document.createElement( 'div' );
2327
+ this.hueMarker.className = "lexcolormarker";
2328
+ this.hueMarker.style.backgroundColor = `rgb(${ hueColor.css.r }, ${ hueColor.css.g }, ${ hueColor.css.b })`;
2329
+ this.colorPickerTracker.appendChild( this.hueMarker );
2330
+
2331
+ doAsync( () => {
2332
+ const hueLeft = LX.remapRange( this.currentColor.hsv.h, 0, 360, 0, this.colorPickerTracker.offsetWidth - this.markerSize );
2333
+ this.hueMarker.style.left = hueLeft + "px";
2334
+ } );
2335
+
2336
+ const _fromHueX = ( hueX ) => {
2337
+ this.hueMarker.style.left = hueX + "px";
2338
+ this.currentColor.hsv.h = LX.remapRange( hueX, 0, this.colorPickerTracker.offsetWidth - this.markerSize, 0, 360 );
2339
+
2340
+ const hueColor = new Color( { h: this.currentColor.hsv.h, s: 1, v: 1 } );
2341
+ this.hueMarker.style.backgroundColor = `rgb(${ hueColor.css.r }, ${ hueColor.css.g }, ${ hueColor.css.b })`;
2342
+ this.colorPickerBackground.style.backgroundColor = `rgb(${ hueColor.css.r }, ${ hueColor.css.g }, ${ hueColor.css.b })`;
2343
+ this._updateColorValue();
2344
+ };
2345
+
2346
+ let innerMouseDownHue = e => {
2347
+ const doc = this.root.ownerDocument;
2348
+ doc.addEventListener( 'mousemove', innerMouseMoveHue );
2349
+ doc.addEventListener( 'mouseup', innerMouseUpHue );
2350
+ document.body.classList.add( 'noevents' );
2351
+ e.stopImmediatePropagation();
2352
+ e.stopPropagation();
2353
+
2354
+ const hueX = clamp( e.offsetX - this.markerHalfSize, 0, this.colorPickerTracker.offsetWidth - this.markerSize );
2355
+ _fromHueX( hueX );
2356
+ }
2357
+
2358
+ let innerMouseMoveHue = e => {
2359
+ let dX = e.movementX;
2360
+
2361
+ const rect = this.colorPickerTracker.getBoundingClientRect();
2362
+ const mouseX = e.offsetX - rect.x;
2363
+
2364
+ if ( dX != 0 && ( mouseX >= 0 || dX < 0 ) && ( mouseX < this.colorPickerTracker.offsetWidth || dX > 0 ) )
2365
+ {
2366
+ const hueX = LX.clamp( parseInt( this.hueMarker.style.left ) + dX, 0, this.colorPickerTracker.offsetWidth - this.markerSize );
2367
+ _fromHueX( hueX )
2368
+ }
2369
+
2370
+ e.stopPropagation();
2371
+ e.preventDefault();
2372
+ }
2373
+
2374
+ let innerMouseUpHue = e => {
2375
+ var doc = this.root.ownerDocument;
2376
+ doc.removeEventListener( 'mousemove', innerMouseMoveHue );
2377
+ doc.removeEventListener( 'mouseup', innerMouseUpHue );
2378
+ document.body.classList.remove( 'noevents' );
2379
+ }
2380
+
2381
+ this.colorPickerTracker.addEventListener( "mousedown", innerMouseDownHue );
2382
+
2383
+ // Alpha
2384
+ if( this.useAlpha )
2385
+ {
2386
+ this.alphaTracker = document.createElement( 'div' );
2387
+ this.alphaTracker.className = "lexalphatracker";
2388
+ this.alphaTracker.style.color = `rgb(${ this.currentColor.css.r }, ${ this.currentColor.css.g }, ${ this.currentColor.css.b })`;
2389
+ innerHueAlpha.appendChild( this.alphaTracker );
2390
+
2391
+ this.alphaMarker = document.createElement( 'div' );
2392
+ this.alphaMarker.className = "lexcolormarker";
2393
+ this.alphaMarker.style.backgroundColor = `rgb(${ this.currentColor.css.r }, ${ this.currentColor.css.g }, ${ this.currentColor.css.b },${ this.currentColor.css.a })`;
2394
+ this.alphaTracker.appendChild( this.alphaMarker );
2395
+
2396
+ doAsync( () => {
2397
+ const alphaLeft = LX.remapRange( this.currentColor.hsv.a, 0, 1, 0, this.alphaTracker.offsetWidth - this.markerSize );
2398
+ this.alphaMarker.style.left = alphaLeft + "px";
2399
+ } );
2400
+
2401
+ const _fromAlphaX = ( alphaX ) => {
2402
+ this.alphaMarker.style.left = alphaX + "px";
2403
+ this.currentColor.hsv.a = LX.remapRange( alphaX, 0, this.alphaTracker.offsetWidth - this.markerSize, 0, 1 );
2404
+ this._updateColorValue();
2405
+ // Update alpha marker once the color is updated
2406
+ this.alphaMarker.style.backgroundColor = `rgb(${ this.currentColor.css.r }, ${ this.currentColor.css.g }, ${ this.currentColor.css.b },${ this.currentColor.css.a })`;
2407
+ };
2408
+
2409
+ let innerMouseDownAlpha = e => {
2410
+ const doc = this.root.ownerDocument;
2411
+ doc.addEventListener( 'mousemove', innerMouseMoveAlpha );
2412
+ doc.addEventListener( 'mouseup', innerMouseUpAlpha );
2413
+ document.body.classList.add( 'noevents' );
2414
+ e.stopImmediatePropagation();
2415
+ e.stopPropagation();
2416
+ const alphaX = clamp( e.offsetX - this.markerHalfSize, 0, this.alphaTracker.offsetWidth - this.markerSize );
2417
+ _fromAlphaX( alphaX );
2418
+ }
2419
+
2420
+ let innerMouseMoveAlpha = e => {
2421
+ let dX = e.movementX;
2422
+
2423
+ const rect = this.alphaTracker.getBoundingClientRect();
2424
+ const mouseX = e.offsetX - rect.x;
2425
+
2426
+ if ( dX != 0 && ( mouseX >= 0 || dX < 0 ) && ( mouseX < this.alphaTracker.offsetWidth || dX > 0 ) )
2427
+ {
2428
+ const alphaX = LX.clamp( parseInt( this.alphaMarker.style.left ) + dX, 0, this.alphaTracker.offsetWidth - this.markerSize );
2429
+ _fromAlphaX( alphaX );
2430
+ }
2431
+
2432
+ e.stopPropagation();
2433
+ e.preventDefault();
2434
+ }
2435
+
2436
+ let innerMouseUpAlpha = e => {
2437
+ var doc = this.root.ownerDocument;
2438
+ doc.removeEventListener( 'mousemove', innerMouseMoveAlpha );
2439
+ doc.removeEventListener( 'mouseup', innerMouseUpAlpha );
2440
+ document.body.classList.remove( 'noevents' );
2441
+ }
2442
+
2443
+ this.alphaTracker.addEventListener( "mousedown", innerMouseDownAlpha );
2444
+ }
2445
+
2446
+ // Info display
2447
+ const colorLabel = LX.makeContainer( ["100%", "auto"], "flex flex-row gap-1", "", this.root );
2448
+
2449
+ colorLabel.appendChild( new Select( null, [ "CSS", "Hex", "HSV", "RGB" ], this.colorModel, v => {
2450
+ this.colorModel = v;
2451
+ this._updateColorValue( null, true );
2452
+ } ).root );
2453
+
2454
+ this.labelWidget = new TextInput( null, "", null, { inputClass: "bg-none", fit: true, disabled: true } );
2455
+ colorLabel.appendChild( this.labelWidget.root );
2456
+
2457
+ // Copy button
2458
+ {
2459
+ const copyButtonWidget = new Button(null, "copy", async () => {
2460
+ navigator.clipboard.writeText( this.labelWidget.value() );
2461
+ copyButtonWidget.root.querySelector( "input[type='checkbox']" ).style.pointerEvents = "none";
2462
+
2463
+ doAsync( () => {
2464
+ copyButtonWidget.root.swap( true );
2465
+ copyButtonWidget.root.querySelector( "input[type='checkbox']" ).style.pointerEvents = "auto";
2466
+ }, 3000 );
2467
+
2468
+ }, { swap: "check", icon: "copy", buttonClass: "bg-none", className: "ml-auto", title: "Copy" })
2469
+
2470
+ copyButtonWidget.root.querySelector( ".swap-on svg path" ).style.fill = "#42d065";
2471
+
2472
+ colorLabel.appendChild( copyButtonWidget.root );
2473
+ }
2474
+
2475
+ this._updateColorValue( hexValue, true );
2476
+
2477
+ doAsync( () => {
2478
+ this._adjustPosition();
2479
+
2480
+ this.root.focus();
2481
+
2482
+ this._onClick = e => {
2483
+ if( e.target && ( this.root.contains( e.target ) || e.target == this._trigger ) )
2484
+ {
2485
+ return;
2486
+ }
2487
+ this.destroy();
2488
+ };
2489
+
2490
+ document.body.addEventListener( "mousedown", this._onClick, true );
2491
+ document.body.addEventListener( "focusin", this._onClick, true );
2492
+ }, 10 );
2493
+ }
2494
+
2495
+ fromHexColor( hexColor ) {
2496
+
2497
+ this.currentColor.setHex( hexColor );
2498
+
2499
+ // Decompose into HSV
2500
+ const { h, s, v } = this.currentColor.hsv;
2501
+ this._svToPosition( s, v );
2502
+
2503
+ const hueColor = new Color( { h, s: 1, v: 1 } );
2504
+ this.hueMarker.style.backgroundColor = this.colorPickerBackground.style.backgroundColor = `rgb(${ hueColor.css.r }, ${ hueColor.css.g }, ${ hueColor.css.b })`;
2505
+ this.hueMarker.style.left = LX.remapRange( h, 0, 360, -this.markerHalfSize, this.colorPickerTracker.offsetWidth - this.markerHalfSize ) + "px";
2506
+
2507
+ this._updateColorValue( hexColor );
2508
+ }
2509
+
2510
+ destroy() {
2511
+
2512
+ this._trigger.classList.remove( "triggered" );
2513
+
2514
+ delete this._trigger.picker;
1842
2515
 
1843
- let path = menuItem.id;
1844
- let p = parentDom;
2516
+ document.body.removeEventListener( "mousedown", this._onClick, true );
2517
+ document.body.removeEventListener( "focusin", this._onClick, true );
1845
2518
 
1846
- while( p )
1847
- {
1848
- path += "/" + p.id;
1849
- p = p.currentParent?.parentElement;
1850
- }
2519
+ LX.root.querySelectorAll( ".lexcolorpicker" ).forEach( m => { m.remove(); } );
1851
2520
 
1852
- LX.root.querySelectorAll( ".lexdropdownmenu" ).forEach( m => {
1853
- if( !path.includes( m.id ) )
1854
- {
1855
- m.currentParent.built = false;
1856
- m.remove();
1857
- }
1858
- } );
2521
+ ColorPicker.currentPicker = null;
2522
+ }
1859
2523
 
1860
- if( item.submenu )
1861
- {
1862
- if( menuItem.built )
1863
- {
1864
- return;
1865
- }
1866
- menuItem.built = true;
1867
- this._create( item.submenu, menuItem );
1868
- }
2524
+ _svToPosition( s, v ) {
2525
+ this.intSatMarker.style.left = `${ LX.remapRange( s, 0, 1, -this.markerHalfSize, this.colorPickerBackground.offsetWidth - this.markerHalfSize ) }px`;
2526
+ this.intSatMarker.style.top = `${ LX.remapRange( 1 - v, 0, 1, -this.markerHalfSize, this.colorPickerBackground.offsetHeight - this.markerHalfSize ) }px`
2527
+ };
1869
2528
 
1870
- e.stopPropagation();
1871
- });
2529
+ _positionToSv( left, top ) {
2530
+ this.currentColor.hsv.s = LX.remapRange( left, -this.markerHalfSize, this.colorPickerBackground.offsetWidth - this.markerHalfSize, 0, 1 );
2531
+ this.currentColor.hsv.v = 1 - LX.remapRange( top, -this.markerHalfSize, this.colorPickerBackground.offsetHeight - this.markerHalfSize, 0, 1 );
2532
+ };
2533
+
2534
+ _updateColorValue( newHexValue, skipCallback = false ) {
2535
+
2536
+ this.currentColor.set( newHexValue ?? this.currentColor.hsv );
2537
+
2538
+ if( this.callback && !skipCallback )
2539
+ {
2540
+ this.callback( this.currentColor );
1872
2541
  }
1873
- }
2542
+
2543
+ this.intSatMarker.style.backgroundColor = this.currentColor.hex;
2544
+
2545
+ if( this.useAlpha )
2546
+ {
2547
+ this.alphaTracker.style.color = `rgb(${ this.currentColor.css.r }, ${ this.currentColor.css.g }, ${ this.currentColor.css.b })`;
2548
+ }
2549
+
2550
+ const toFixed = ( s, n = 2) => { return s.toFixed( n ).replace( /([0-9]+(\.[0-9]+[1-9])?)(\.?0+$)/, '$1' ) };
2551
+
2552
+ if( this.colorModel == "CSS" )
2553
+ {
2554
+ const { r, g, b, a } = this.currentColor.css;
2555
+ this.labelWidget.set( `rgb${ this.useAlpha ? 'a' : '' }(${ r },${ g },${ b }${ this.useAlpha ? ',' + toFixed( a ) : '' })` );
2556
+ }
2557
+ else if( this.colorModel == "Hex" )
2558
+ {
2559
+ this.labelWidget.set( ( this.useAlpha ? this.currentColor.hex : this.currentColor.hex.substr( 0, 7 ) ).toUpperCase() );
2560
+ }
2561
+ else if( this.colorModel == "HSV" )
2562
+ {
2563
+ const { h, s, v, a } = this.currentColor.hsv;
2564
+ const components = [ Math.floor( h ) + 'º', Math.floor( s * 100 ) + '%', Math.floor( v * 100 ) + '%' ];
2565
+ if( this.useAlpha ) components.push( toFixed( a ) );
2566
+ this.labelWidget.set( components.join( ' ' ) );
2567
+ }
2568
+ else // RGB
2569
+ {
2570
+ const { r, g, b, a } = this.currentColor.rgb;
2571
+ const components = [ toFixed( r ), toFixed( g ), toFixed( b ) ];
2572
+ if( this.useAlpha ) components.push( toFixed( a ) );
2573
+ this.labelWidget.set( components.join( ' ' ) );
2574
+ }
2575
+ };
1874
2576
 
1875
2577
  _adjustPosition() {
1876
2578
 
@@ -1932,16 +2634,9 @@ class DropdownMenu {
1932
2634
  this.root.style.left = `${ position[ 0 ] }px`;
1933
2635
  this.root.style.top = `${ position[ 1 ] }px`;
1934
2636
  }
1935
-
1936
- _addSeparator( parent ) {
1937
- const separator = document.createElement('div');
1938
- separator.className = "separator";
1939
- parent = parent ?? this.root;
1940
- parent.appendChild( separator );
1941
- }
1942
2637
  };
1943
2638
 
1944
- LX.DropdownMenu = DropdownMenu;
2639
+ LX.ColorPicker = ColorPicker;
1945
2640
 
1946
2641
  class Area {
1947
2642
 
@@ -3734,7 +4429,7 @@ class Menubar {
3734
4429
  // Otherwise, create it
3735
4430
  button = document.createElement('div');
3736
4431
  const disabled = options.disabled ?? false;
3737
- button.className = "lexmenubutton" + (disabled ? " disabled" : "");
4432
+ button.className = "lexmenubutton main" + (disabled ? " disabled" : "");
3738
4433
  button.title = name;
3739
4434
  button.innerHTML = "<a><image src='" + src + "' class='lexicon' style='height:32px;'></a>";
3740
4435
 
@@ -3804,57 +4499,14 @@ class Menubar {
3804
4499
 
3805
4500
  for( let i = 0; i < buttons.length; ++i )
3806
4501
  {
3807
- let data = buttons[ i ];
3808
- let button = document.createElement( "label" );
4502
+ const data = buttons[ i ];
3809
4503
  const title = data.title;
3810
- let disabled = data.disabled ?? false;
3811
- button.className = "lexmenubutton" + (disabled ? " disabled" : "");
3812
- button.title = title ?? "";
3813
- this.buttonContainer.appendChild( button );
3814
-
3815
- const icon = document.createElement( "a" );
3816
- icon.className = data.icon + " lexicon";
3817
- button.appendChild( icon );
3818
-
3819
- let trigger = icon;
3820
-
3821
- if( data.swap )
3822
- {
3823
- button.classList.add( "swap" );
3824
- icon.classList.add( "swap-off" );
3825
-
3826
- const input = document.createElement( "input" );
3827
- input.type = "checkbox";
3828
- button.prepend( input );
3829
- trigger = input;
3830
-
3831
- const swapIcon = document.createElement( "a" );
3832
- swapIcon.className = data.swap + " swap-on lexicon";
3833
- button.appendChild( swapIcon );
3834
-
3835
- button.swap = function() {
3836
- const swapInput = this.querySelector( "input" );
3837
- swapInput.checked = !swapInput.checked;
3838
- };
3839
-
3840
- // Set if swap has to be performed
3841
- button.setState = function( v ) {
3842
- const swapInput = this.querySelector( "input" );
3843
- swapInput.checked = v;
3844
- };
3845
- }
3846
-
3847
- trigger.addEventListener("click", e => {
3848
- if( data.callback && !disabled )
3849
- {
3850
- const swapInput = button.querySelector( "input" );
3851
- data.callback.call( this, e, swapInput?.checked );
3852
- }
3853
- });
4504
+ const button = new Button( title, "", data.callback, { title, buttonClass: "bg-none", disabled: data.disabled, icon: data.icon, hideName: true, swap: data.swap } )
4505
+ this.buttonContainer.appendChild( button.root );
3854
4506
 
3855
4507
  if( title )
3856
4508
  {
3857
- this.buttons[ title ] = button;
4509
+ this.buttons[ title ] = button.root;
3858
4510
  }
3859
4511
  }
3860
4512
  }
@@ -3941,7 +4593,7 @@ class SideBar {
3941
4593
 
3942
4594
  if( this.collapsable )
3943
4595
  {
3944
- const icon = LX.makeIcon( "sidebar", "Toggle Sidebar", "toggler" );
4596
+ const icon = LX.makeIcon( "sidebar", { title: "Toggle Sidebar", iconClass: "toggler" } );
3945
4597
  this.header.appendChild( icon );
3946
4598
 
3947
4599
  icon.addEventListener( "click", (e) => {
@@ -4428,7 +5080,7 @@ class SideBar {
4428
5080
 
4429
5081
  if( options.action )
4430
5082
  {
4431
- const actionIcon = LX.makeIcon( options.action.icon ?? "more-horizontal", options.action.name );
5083
+ const actionIcon = LX.makeIcon( options.action.icon ?? "more-horizontal", { title: options.action.name } );
4432
5084
  itemDom.appendChild( actionIcon );
4433
5085
 
4434
5086
  actionIcon.addEventListener( "click", (e) => {
@@ -4488,7 +5140,7 @@ class SideBar {
4488
5140
 
4489
5141
  if( suboptions.action )
4490
5142
  {
4491
- const actionIcon = LX.makeIcon( suboptions.action.icon ?? "more-horizontal", suboptions.action.name );
5143
+ const actionIcon = LX.makeIcon( suboptions.action.icon ?? "more-horizontal", { title: suboptions.action.name } );
4492
5144
  subentry.appendChild( actionIcon );
4493
5145
 
4494
5146
  actionIcon.addEventListener( "click", (e) => {
@@ -4557,14 +5209,15 @@ class Widget {
4557
5209
  static SEPARATOR = 26;
4558
5210
  static KNOB = 27;
4559
5211
  static SIZE = 28;
4560
- static PAD = 29;
4561
- static FORM = 30;
4562
- static DIAL = 31;
4563
- static COUNTER = 32;
4564
- static TABLE = 33;
4565
- static TABS = 34;
4566
- static LABEL = 35;
4567
- static BLANK = 36;
5212
+ static OTP = 29;
5213
+ static PAD = 30;
5214
+ static FORM = 31;
5215
+ static DIAL = 32;
5216
+ static COUNTER = 33;
5217
+ static TABLE = 34;
5218
+ static TABS = 35;
5219
+ static LABEL = 36;
5220
+ static BLANK = 37;
4568
5221
 
4569
5222
  static NO_CONTEXT_TYPES = [
4570
5223
  Widget.BUTTON,
@@ -4679,7 +5332,7 @@ class Widget {
4679
5332
 
4680
5333
  _addResetProperty( container, callback ) {
4681
5334
 
4682
- const domEl = LX.makeIcon( "rotate-left", "Reset" )
5335
+ const domEl = LX.makeIcon( "rotate-left", { title: "Reset" } )
4683
5336
  domEl.style.display = "none";
4684
5337
  domEl.style.marginRight = "6px";
4685
5338
  domEl.style.marginLeft = "0";
@@ -5809,40 +6462,17 @@ class Button extends Widget {
5809
6462
  super( Widget.BUTTON, name, null, options );
5810
6463
 
5811
6464
  this.onGetValue = () => {
5812
- return wValue.innerText;
6465
+ return wValue.querySelector( "input" )?.checked;
5813
6466
  };
5814
6467
 
5815
6468
  this.onSetValue = ( newValue, skipCallback, event ) => {
5816
6469
 
5817
- wValue.innerHTML = "";
5818
-
5819
- if( options.icon )
5820
- {
5821
- let icon = null;
5822
-
5823
- // @legacy
5824
- if( options.icon.includes( "fa-" ) )
5825
- {
5826
- icon = document.createElement( 'a' );
5827
- icon.className = options.icon;
5828
- }
5829
- else
5830
- {
5831
- icon = LX.makeIcon( options.icon );
5832
- }
5833
-
5834
- wValue.prepend( icon );
5835
- }
5836
- else if( options.img )
5837
- {
5838
- let img = document.createElement( 'img' );
5839
- img.src = options.img;
5840
- wValue.prepend( img );
5841
- }
5842
- else
6470
+ if( !( options.swap ?? false ) )
5843
6471
  {
5844
- wValue.innerHTML = `<span>${ ( newValue || "" ) }</span>`;
6472
+ return;
5845
6473
  }
6474
+
6475
+ this.root.setState( newValue, skipCallback );
5846
6476
  };
5847
6477
 
5848
6478
  this.onResize = ( rect ) => {
@@ -5866,25 +6496,98 @@ class Button extends Widget {
5866
6496
  wValue.classList.add( "selected" );
5867
6497
  }
5868
6498
 
5869
- this.onSetValue( value, true );
6499
+ if( options.icon )
6500
+ {
6501
+ let icon = null;
6502
+
6503
+ // @legacy
6504
+ if( options.icon.includes( "fa-" ) )
6505
+ {
6506
+ icon = document.createElement( 'a' );
6507
+ icon.className = options.icon + " lexicon";
6508
+ }
6509
+ else
6510
+ {
6511
+ icon = LX.makeIcon( options.icon );
6512
+ }
6513
+
6514
+ wValue.prepend( icon );
6515
+ }
6516
+ else if( options.img )
6517
+ {
6518
+ let img = document.createElement( 'img' );
6519
+ img.src = options.img;
6520
+ wValue.prepend( img );
6521
+ }
6522
+ else
6523
+ {
6524
+ wValue.innerHTML = `<span>${ ( value || "" ) }</span>`;
6525
+ }
5870
6526
 
5871
6527
  if( options.disabled )
5872
6528
  {
5873
6529
  wValue.setAttribute( "disabled", true );
5874
6530
  }
5875
6531
 
5876
- wValue.addEventListener( "click", e => {
6532
+ let trigger = wValue;
6533
+
6534
+ if( options.swap )
6535
+ {
6536
+ wValue.classList.add( "swap" );
6537
+ wValue.querySelector( "a" ).classList.add( "swap-off" );
6538
+
6539
+ const input = document.createElement( "input" );
6540
+ input.type = "checkbox";
6541
+ wValue.prepend( input );
6542
+
6543
+ let swapIcon = null;
6544
+
6545
+ // @legacy
6546
+ if( options.swap.includes( "fa-" ) )
6547
+ {
6548
+ swapIcon = document.createElement( 'a' );
6549
+ swapIcon.className = options.swap + " swap-on lexicon";
6550
+ }
6551
+ else
6552
+ {
6553
+ swapIcon = LX.makeIcon( options.swap, { iconClass: "swap-on" } );
6554
+ }
6555
+
6556
+ wValue.appendChild( swapIcon );
6557
+
6558
+ this.root.swap = function( skipCallback ) {
6559
+ const swapInput = wValue.querySelector( "input" );
6560
+ swapInput.checked = !swapInput.checked;
6561
+ if( !skipCallback )
6562
+ {
6563
+ trigger.click();
6564
+ }
6565
+ };
6566
+
6567
+ // Set if swap has to be performed
6568
+ this.root.setState = function( v, skipCallback ) {
6569
+ const swapInput = wValue.querySelector( "input" );
6570
+ swapInput.checked = v;
6571
+ if( !skipCallback )
6572
+ {
6573
+ trigger.click();
6574
+ }
6575
+ };
6576
+ }
6577
+
6578
+ trigger.addEventListener( "click", e => {
5877
6579
  if( options.selectable )
5878
6580
  {
5879
6581
  if( options.parent )
5880
6582
  {
5881
- options.parent.querySelectorAll(".lexbutton.selected").forEach( e => { if( e == wValue ) return; e.classList.remove( "selected" ) } );
6583
+ options.parent.querySelectorAll(".lexbutton.selected").forEach( b => { if( b == wValue ) return; b.classList.remove( "selected" ) } );
5882
6584
  }
5883
6585
 
5884
6586
  wValue.classList.toggle('selected');
5885
6587
  }
5886
6588
 
5887
- this._trigger( new IEvent( name, value, e ), callback );
6589
+ const swapInput = wValue.querySelector( "input" );
6590
+ this._trigger( new IEvent( name, swapInput?.checked ?? value, e ), callback );
5888
6591
  });
5889
6592
 
5890
6593
  if( options.tooltip )
@@ -6279,7 +6982,7 @@ class Select extends Widget {
6279
6982
 
6280
6983
  const selectRoot = selectedOption.root;
6281
6984
  const rect = selectRoot.getBoundingClientRect();
6282
- const nestedDialog = parent.parentElement.closest( "dialog" );
6985
+ const nestedDialog = parent.parentElement.closest( "dialog" ) ?? parent.parentElement.closest( ".lexcolorpicker" );
6283
6986
 
6284
6987
  // Manage vertical aspect
6285
6988
  {
@@ -7329,31 +8032,52 @@ class ColorInput extends Widget {
7329
8032
 
7330
8033
  constructor( name, value, callback, options = {} ) {
7331
8034
 
7332
- value = ( value.constructor === Array ) ? rgbToHex( value ) : value;
8035
+ const useAlpha = options.useAlpha ??
8036
+ ( ( value.constructor === Object && 'a' in value ) || ( value.constructor === String && [ 5, 9 ].includes( value.length ) ) );
8037
+
8038
+ const widgetColor = new Color( value );
8039
+
8040
+ // Force always hex internally
8041
+ value = useAlpha ? widgetColor.hex : widgetColor.hex.substr( 0, 7 );
7333
8042
 
7334
8043
  super( Widget.COLOR, name, value, options );
7335
8044
 
7336
8045
  this.onGetValue = () => {
7337
- return value;
8046
+ const currentColor = new Color( value );
8047
+ return options.useRGB ? currentColor.rgb : value;
7338
8048
  };
7339
8049
 
7340
8050
  this.onSetValue = ( newValue, skipCallback, event ) => {
7341
8051
 
7342
- if( color.useRGB )
8052
+ const newColor = new Color( newValue );
8053
+
8054
+ colorSampleRGB.style.color = value = newColor.hex.substr( 0, 7 );
8055
+
8056
+ if( useAlpha )
7343
8057
  {
7344
- newValue = hexToRgb( newValue );
8058
+ colorSampleAlpha.style.color = value = newColor.hex;
7345
8059
  }
7346
8060
 
7347
8061
  if( !this._skipTextUpdate )
7348
8062
  {
7349
- textWidget.set( newValue, true, event );
8063
+ textWidget.set( value, true, event );
7350
8064
  }
7351
8065
 
7352
- color.value = value = newValue;
7353
-
7354
8066
  if( !skipCallback )
7355
8067
  {
7356
- this._trigger( new IEvent( name, newValue, event ), callback );
8068
+ let retValue = value;
8069
+
8070
+ if( options.useRGB )
8071
+ {
8072
+ retValue = newColor.rgb;
8073
+
8074
+ if( !useAlpha )
8075
+ {
8076
+ delete retValue.a;
8077
+ }
8078
+ }
8079
+
8080
+ this._trigger( new IEvent( name, retValue, event ), callback );
7357
8081
  }
7358
8082
  };
7359
8083
 
@@ -7366,30 +8090,50 @@ class ColorInput extends Widget {
7366
8090
  container.className = "lexcolor";
7367
8091
  this.root.appendChild( container );
7368
8092
 
7369
- let color = document.createElement( 'input' );
7370
- color.style.width = "32px";
7371
- color.type = 'color';
7372
- color.className = "colorinput";
7373
- color.useRGB = options.useRGB ?? false;
7374
- color.value = value;
7375
- container.appendChild( color );
8093
+ let sampleContainer = LX.makeContainer( ["18px", "18px"], "flex flex-row bg-contrast rounded overflow-hidden", "", container );
8094
+ sampleContainer.tabIndex = "1";
8095
+ sampleContainer.addEventListener( "click", e => {
8096
+ if( ( options.disabled ?? false ) )
8097
+ {
8098
+ return;
8099
+ }
8100
+ new ColorPicker( value, sampleContainer, {
8101
+ colorModel: options.useRGB ? "RGB" : "Hex",
8102
+ useAlpha,
8103
+ onChange: ( color ) => {
8104
+ this._fromColorPicker = true;
8105
+ this.set( color.hex );
8106
+ delete this._fromColorPicker;
8107
+ }
8108
+ } );
8109
+ } );
8110
+
8111
+ let colorSampleRGB = document.createElement( 'div' );
8112
+ colorSampleRGB.className = "lexcolorsample";
8113
+ colorSampleRGB.style.color = value;
8114
+ sampleContainer.appendChild( colorSampleRGB );
7376
8115
 
7377
- if( options.disabled )
8116
+ let colorSampleAlpha = null;
8117
+
8118
+ if( useAlpha )
7378
8119
  {
7379
- color.disabled = true;
8120
+ colorSampleAlpha = document.createElement( 'div' );
8121
+ colorSampleAlpha.className = "lexcolorsample";
8122
+ colorSampleAlpha.style.color = value;
8123
+ sampleContainer.appendChild( colorSampleAlpha );
8124
+ }
8125
+ else
8126
+ {
8127
+ colorSampleRGB.style.width = "18px";
7380
8128
  }
7381
8129
 
7382
- color.addEventListener( "input", e => {
7383
- this.set( e.target.value, false, e );
7384
- }, false );
7385
-
7386
- const textWidget = new TextInput( null, color.value, v => {
8130
+ const textWidget = new TextInput( null, value, v => {
7387
8131
  this._skipTextUpdate = true;
7388
8132
  this.set( v );
7389
8133
  delete this._skipTextUpdate;
7390
- }, { width: "calc( 100% - 32px )", disabled: options.disabled });
8134
+ }, { width: "calc( 100% - 24px )", disabled: options.disabled });
7391
8135
 
7392
- textWidget.root.style.marginLeft = "4px";
8136
+ textWidget.root.style.marginLeft = "6px";
7393
8137
  container.appendChild( textWidget.root );
7394
8138
 
7395
8139
  doAsync( this.onResize.bind( this ) );
@@ -7699,7 +8443,6 @@ class NumberInput extends Widget {
7699
8443
  else if( e.altKey ) mult *= 0.1;
7700
8444
  value = ( +vecinput.valueAsNumber + mult * dt );
7701
8445
  this.set( value, false, e );
7702
- // vecinput.value = ( +new_value ).toFixed( 4 ).replace( /([0-9]+(\.[0-9]+[1-9])?)(\.?0+$)/, '$1' );
7703
8446
  }
7704
8447
 
7705
8448
  e.stopPropagation();
@@ -8097,6 +8840,164 @@ class SizeInput extends Widget {
8097
8840
 
8098
8841
  LX.SizeInput = SizeInput;
8099
8842
 
8843
+ /**
8844
+ * @class OTPInput
8845
+ * @description OTPInput Widget
8846
+ */
8847
+
8848
+ class OTPInput extends Widget {
8849
+
8850
+ constructor( name, value, callback, options = {} ) {
8851
+
8852
+ const pattern = options.pattern ?? "xxx-xxx";
8853
+ const patternSize = ( pattern.match(/x/g) || [] ).length;
8854
+
8855
+ value = String( value );
8856
+ if( !value.length )
8857
+ {
8858
+ value = "x".repeat( patternSize );
8859
+ }
8860
+
8861
+ super( Widget.OTP, name, value, options );
8862
+
8863
+ this.onGetValue = () => {
8864
+ return +value;
8865
+ };
8866
+
8867
+ this.onSetValue = ( newValue, skipCallback, event ) => {
8868
+
8869
+ value = newValue;
8870
+
8871
+ _refreshInput( value );
8872
+
8873
+ if( !skipCallback )
8874
+ {
8875
+ this._trigger( new IEvent( name, +newValue, event ), callback );
8876
+ }
8877
+ };
8878
+
8879
+ this.onResize = ( rect ) => {
8880
+ const realNameWidth = ( this.root.domName?.offsetWidth ?? 0 );
8881
+ container.style.width = `calc( 100% - ${ realNameWidth }px)`;
8882
+ };
8883
+
8884
+ this.disabled = options.disabled ?? false;
8885
+
8886
+ const container = document.createElement( 'div' );
8887
+ container.className = "lexotp flex flex-row items-center";
8888
+ this.root.appendChild( container );
8889
+
8890
+ const groups = pattern.split( '-' );
8891
+
8892
+ const _refreshInput = ( valueString ) => {
8893
+
8894
+ container.innerHTML = "";
8895
+
8896
+ let itemsCount = 0;
8897
+ let activeSlot = 0;
8898
+
8899
+ for( let i = 0; i < groups.length; ++i )
8900
+ {
8901
+ const g = groups[ i ];
8902
+
8903
+ for( let j = 0; j < g.length; ++j )
8904
+ {
8905
+ let number = valueString[ itemsCount++ ];
8906
+ number = ( number == 'x' ? '' : number );
8907
+
8908
+ const slotDom = LX.makeContainer( ["36px", "30px"],
8909
+ "lexotpslot border-top border-bottom border-left px-3 cursor-text select-none font-medium outline-none", number, container );
8910
+ slotDom.tabIndex = "1";
8911
+
8912
+ if( this.disabled )
8913
+ {
8914
+ slotDom.classList.add( "disabled" );
8915
+ }
8916
+
8917
+ const otpIndex = itemsCount;
8918
+
8919
+ if( j == 0 )
8920
+ {
8921
+ slotDom.className += " rounded-l";
8922
+ }
8923
+ else if( j == ( g.length - 1 ) )
8924
+ {
8925
+ slotDom.className += " rounded-r border-right";
8926
+ }
8927
+
8928
+ slotDom.addEventListener( "click", () => {
8929
+ if( this.disabled ) { return; }
8930
+ container.querySelectorAll( ".lexotpslot" ).forEach( s => s.classList.remove( "active" ) );
8931
+ const activeDom = container.querySelectorAll( ".lexotpslot" )[ activeSlot ];
8932
+ activeDom.classList.add( "active" );
8933
+ activeDom.focus();
8934
+ } );
8935
+
8936
+ slotDom.addEventListener( "blur", () => {
8937
+ if( this.disabled ) { return; }
8938
+ doAsync( () => {
8939
+ if( container.contains( document.activeElement ) ) { return; }
8940
+ container.querySelectorAll( ".lexotpslot" ).forEach( s => s.classList.remove( "active" ) );
8941
+ }, 10 );
8942
+ } );
8943
+
8944
+ slotDom.addEventListener( "keyup", e => {
8945
+ if( this.disabled ) { return; }
8946
+ if( !/[^0-9]+/g.test( e.key ) )
8947
+ {
8948
+ const number = e.key;
8949
+ console.assert( parseInt( number ) != NaN );
8950
+
8951
+ slotDom.innerHTML = number;
8952
+ valueString = valueString.substring( 0, otpIndex - 1 ) + number + valueString.substring( otpIndex );
8953
+
8954
+ const nexActiveDom = container.querySelectorAll( ".lexotpslot" )[ activeSlot + 1 ];
8955
+ if( nexActiveDom )
8956
+ {
8957
+ container.querySelectorAll( ".lexotpslot" )[ activeSlot ].classList.remove( "active" );
8958
+ nexActiveDom.classList.add( "active" );
8959
+ nexActiveDom.focus();
8960
+ activeSlot++;
8961
+ }
8962
+ else
8963
+ {
8964
+ this.set( valueString );
8965
+ }
8966
+ }
8967
+ else if( e.key == "ArrowLeft" || e.key == "ArrowRight" )
8968
+ {
8969
+ const dt = ( e.key == "ArrowLeft" ) ? -1 : 1;
8970
+ const newActiveDom = container.querySelectorAll( ".lexotpslot" )[ activeSlot + dt ];
8971
+ if( newActiveDom )
8972
+ {
8973
+ container.querySelectorAll( ".lexotpslot" )[ activeSlot ].classList.remove( "active" );
8974
+ newActiveDom.classList.add( "active" );
8975
+ newActiveDom.focus();
8976
+ activeSlot += dt;
8977
+ }
8978
+ }
8979
+ else if( e.key == "Enter" && !valueString.includes( 'x' ) )
8980
+ {
8981
+ this.set( valueString );
8982
+ }
8983
+ } );
8984
+ }
8985
+
8986
+ if( i < ( groups.length - 1 ) )
8987
+ {
8988
+ LX.makeContainer( ["auto", "auto"], "mx-2", `-`, container );
8989
+ }
8990
+ }
8991
+
8992
+ console.assert( itemsCount == valueString.length, "OTP Value/Pattern Mismatch!" )
8993
+ }
8994
+
8995
+ _refreshInput( value );
8996
+ }
8997
+ }
8998
+
8999
+ LX.OTPInput = OTPInput;
9000
+
8100
9001
  /**
8101
9002
  * @class Pad
8102
9003
  * @description Pad Widget
@@ -8756,7 +9657,7 @@ class Table extends Widget {
8756
9657
 
8757
9658
  if( this.customFilters )
8758
9659
  {
8759
- const icon = LX.makeIcon( "circle-plus", null, "sm" );
9660
+ const icon = LX.makeIcon( "circle-plus", { svgClass: "sm" } );
8760
9661
 
8761
9662
  for( let f of this.customFilters )
8762
9663
  {
@@ -8781,7 +9682,6 @@ class Table extends Widget {
8781
9682
  headerContainer.appendChild( customFilterBtn.root );
8782
9683
  }
8783
9684
 
8784
- // const resetIcon = LX.makeIcon( "xmark", null, "sm" );
8785
9685
  this._resetCustomFiltersBtn = new Button(null, "resetButton", ( v ) => {
8786
9686
  this.activeCustomFilters = {};
8787
9687
  this.refresh();
@@ -8793,7 +9693,7 @@ class Table extends Widget {
8793
9693
 
8794
9694
  if( this.toggleColumns )
8795
9695
  {
8796
- const icon = LX.makeIcon( "sliders" );
9696
+ const icon = LX.makeIcon( "sliders-large" );
8797
9697
  const toggleColumnsBtn = new Button( "toggleColumnsBtn", icon.innerHTML + "View", (value, e) => {
8798
9698
  const menuOptions = data.head.map( ( colName, idx ) => {
8799
9699
  const item = {
@@ -8864,7 +9764,7 @@ class Table extends Widget {
8864
9764
  for( const el of body.childNodes )
8865
9765
  {
8866
9766
  data.checkMap[ el.getAttribute( "rowId" ) ] = this.checked;
8867
- el.querySelector( "input" ).checked = this.checked;
9767
+ el.querySelector( "input[type='checkbox']" ).checked = this.checked;
8868
9768
  }
8869
9769
  });
8870
9770
 
@@ -8876,7 +9776,7 @@ class Table extends Widget {
8876
9776
  {
8877
9777
  const th = document.createElement( 'th' );
8878
9778
  th.innerHTML = `<span>${ headData }</span>`;
8879
- th.querySelector( "span" ).appendChild( LX.makeIcon( "menu-arrows", null, "sm" ) );
9779
+ th.querySelector( "span" ).appendChild( LX.makeIcon( "menu-arrows", { svgClass: "sm" } ) );
8880
9780
 
8881
9781
  const idx = data.head.indexOf( headData );
8882
9782
  if( this.centered && this.centered.indexOf( idx ) > -1 )
@@ -9078,10 +9978,20 @@ class Table extends Widget {
9078
9978
  input.addEventListener( 'change', function() {
9079
9979
  data.checkMap[ rowId ] = this.checked;
9080
9980
 
9981
+ const headInput = table.querySelector( "thead input[type='checkbox']" );
9982
+
9081
9983
  if( !this.checked )
9082
9984
  {
9083
- const input = table.querySelector( "thead input[type='checkbox']" );
9084
- input.checked = data.checkMap[ ":root" ] = false;
9985
+ headInput.checked = data.checkMap[ ":root" ] = false;
9986
+ }
9987
+ else
9988
+ {
9989
+ const rowInputs = Array.from( table.querySelectorAll( "tbody input[type='checkbox']" ) );
9990
+ const uncheckedRowInputs = rowInputs.filter( i => { return !i.checked; } );
9991
+ if( !uncheckedRowInputs.length )
9992
+ {
9993
+ headInput.checked = data.checkMap[ ":root" ] = true;
9994
+ }
9085
9995
  }
9086
9996
  });
9087
9997
 
@@ -9117,7 +10027,7 @@ class Table extends Widget {
9117
10027
 
9118
10028
  if( action == "delete" )
9119
10029
  {
9120
- button = LX.makeIcon( "trash-can", "Delete Row" );
10030
+ button = LX.makeIcon( "trash-can", { title: "Delete Row" } );
9121
10031
  button.addEventListener( 'click', function() {
9122
10032
  // Don't need to refresh table..
9123
10033
  data.body.splice( r, 1 );
@@ -9126,7 +10036,7 @@ class Table extends Widget {
9126
10036
  }
9127
10037
  else if( action == "menu" )
9128
10038
  {
9129
- button = LX.makeIcon( "more-horizontal", "Menu" );
10039
+ button = LX.makeIcon( "more-horizontal", { title: "Menu" } );
9130
10040
  button.addEventListener( 'click', function( event ) {
9131
10041
  if( !options.onMenuAction )
9132
10042
  {
@@ -9142,7 +10052,7 @@ class Table extends Widget {
9142
10052
  else // custom actions
9143
10053
  {
9144
10054
  console.assert( action.constructor == Object );
9145
- button = LX.makeIcon( action.icon, action.title );
10055
+ button = LX.makeIcon( action.icon, { title: action.title } );
9146
10056
 
9147
10057
  if( action.callback )
9148
10058
  {
@@ -10192,6 +11102,22 @@ class Panel {
10192
11102
  return this._attachWidget( widget );
10193
11103
  }
10194
11104
 
11105
+ /**
11106
+ * @method addOTP
11107
+ * @param {String} name Widget name
11108
+ * @param {String} value Default numeric value in string format
11109
+ * @param {Function} callback Callback function on change
11110
+ * @param {Object} options:
11111
+ * hideName: Don't use name as label [false]
11112
+ * disabled: Make the widget disabled [false]
11113
+ * pattern: OTP numeric pattern
11114
+ */
11115
+
11116
+ addOTP( name, value, callback, options = {} ) {
11117
+ const widget = new OTPInput( name, value, callback, options );
11118
+ return this._attachWidget( widget );
11119
+ }
11120
+
10195
11121
  /**
10196
11122
  * @method addPad
10197
11123
  * @param {String} name Widget name
@@ -10424,7 +11350,7 @@ class Branch {
10424
11350
  // add widgets
10425
11351
  for( let w of this.widgets )
10426
11352
  {
10427
- p.root.appendChild( w.domEl );
11353
+ p.root.appendChild( w.root );
10428
11354
  }
10429
11355
  });
10430
11356
  dialog.widgets = this.widgets;
@@ -10728,7 +11654,7 @@ class Dialog {
10728
11654
 
10729
11655
  for( let w of that.widgets )
10730
11656
  {
10731
- branch.content.appendChild( w.domEl );
11657
+ branch.content.appendChild( w.root );
10732
11658
  }
10733
11659
 
10734
11660
  branch.widgets = that.widgets;
@@ -12369,7 +13295,7 @@ class AssetView {
12369
13295
 
12370
13296
  this.rightPanel.addText(null, this.path.join('/'), null, {
12371
13297
  inputClass: "nobg", disabled: true, signal: "@on_folder_change",
12372
- style: { fontWeight: "600", fontSize: "15px" }
13298
+ style: { fontWeight: "600", fontSize: "15px" }
12373
13299
  });
12374
13300
 
12375
13301
  this.rightPanel.endLine();
@@ -13106,7 +14032,7 @@ Element.prototype.addClass = function( className ) {
13106
14032
  }
13107
14033
 
13108
14034
  Element.prototype.getComputedSize = function() {
13109
- // Since we use "box-sizing: border-box" now,
14035
+ // Since we use "box-sizing: border-box" now,
13110
14036
  // it's all included in offsetWidth/offsetHeight
13111
14037
  return {
13112
14038
  width: this.offsetWidth,
@@ -13248,6 +14174,7 @@ LX.ICONS = {
13248
14174
  "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"],
13249
14175
  "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"],
13250
14176
  "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"],
14177
+ "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"],
13251
14178
  "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"],
13252
14179
  "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"],
13253
14180
  "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"],