lexgui 0.1.27 → 0.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,12 +8,16 @@
8
8
  */
9
9
 
10
10
  var LX = {
11
- version: "0.1.27",
11
+ version: "0.1.29",
12
12
  ready: false,
13
13
  components: [], // specific pre-build components
14
14
  signals: {} // events and triggers
15
15
  };
16
16
 
17
+ LX.MOUSE_LEFT_CLICK = 0;
18
+ LX.MOUSE_MIDDLE_CLICK = 1;
19
+ LX.MOUSE_RIGHT_CLICK = 2;
20
+
17
21
  LX.MOUSE_DOUBLE_CLICK = 2;
18
22
  LX.MOUSE_TRIPLE_CLICK = 3;
19
23
 
@@ -75,27 +79,31 @@ function getBase64Image(img) {
75
79
 
76
80
  LX.getBase64Image = getBase64Image;
77
81
 
78
- function hexToRgb(string) {
79
- const red = parseInt(string.substring(1, 3), 16) / 255;
80
- const green = parseInt(string.substring(3, 5), 16) / 255;
81
- const blue = parseInt(string.substring(5, 7), 16) / 255;
82
- return [red, green, blue];
82
+ function hexToRgb( hexStr ) {
83
+ const red = parseInt( hexStr.substring( 1, 3 ), 16 ) / 255;
84
+ const green = parseInt( hexStr.substring( 3, 5 ), 16 ) / 255;
85
+ const blue = parseInt( hexStr.substring( 5, 7 ), 16 ) / 255;
86
+ return [ red, green, blue ];
83
87
  }
84
88
 
85
- function rgbToHex(rgb) {
89
+ LX.hexToRgb = hexToRgb;
90
+
91
+ function rgbToHex( rgb ) {
86
92
  let hex = "#";
87
- for(let c of rgb) {
88
- c = Math.floor(c * 255);
89
- hex += c.toString(16);
93
+ for( let c of rgb ) {
94
+ c = Math.floor( c * 255 );
95
+ hex += c.toString( 16 );
90
96
  }
91
97
  return hex;
92
98
  }
93
99
 
100
+ LX.rgbToHex = rgbToHex;
101
+
94
102
  function simple_guidGenerator() {
95
103
  var S4 = function() {
96
104
  return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
97
105
  };
98
- return (S4()+"-"+S4());
106
+ return (S4()+"-"+S4()+"-"+S4());
99
107
  }
100
108
 
101
109
  // Timer that works everywhere (from litegraph.js)
@@ -138,59 +146,73 @@ class vec2 {
138
146
  set ( x, y ) { this.x = x; this.y = y; }
139
147
  add ( v, v0 = new vec2() ) { v0.set( this.x + v.x, this.y + v.y ); return v0; }
140
148
  sub ( v, v0 = new vec2() ) { v0.set( this.x - v.x, this.y - v.y ); return v0; }
141
- mul ( v, v0 = new vec2() ) { if( v.constructor == Number ) { v = new vec2(v) } v = v0.set( this.x * v.x, this.y * v.y ); return v0; }
142
- div ( v, v0 = new vec2() ) { v0.set( this.x / v.x, this.y / v.y ); return v0; }
149
+ mul ( v, v0 = new vec2() ) { if( v.constructor == Number ) { v = new vec2( v ) } v0.set( this.x * v.x, this.y * v.y ); return v0; }
150
+ div ( v, v0 = new vec2() ) { if( v.constructor == Number ) { v = new vec2( v ) } v0.set( this.x / v.x, this.y / v.y ); return v0; }
151
+ abs ( v0 = new vec2() ) { v0.set( Math.abs( this.x ), Math.abs( this.y ) ); return v0; }
143
152
  };
144
153
 
145
154
  LX.vec2 = vec2;
146
155
 
147
156
  // Other utils
148
157
 
149
- function makeDraggable( domEl, targetClass ) {
158
+ function makeDraggable( domEl, options = { } ) {
150
159
 
151
160
  let offsetX;
152
161
  let offsetY;
153
162
  let currentTarget = null;
163
+ let targetClass = options.targetClass;
164
+
165
+ let id = LX.UTILS.uidGenerator();
166
+ domEl[ 'draggable-id' ] = id;
167
+
168
+ const defaultMoveFunc = e => {
169
+ if( !currentTarget ) return;
170
+ let left = e.clientX - offsetX;
171
+ let top = e.clientY - offsetY;
172
+ if( left > 3 && ( left + domEl.offsetWidth + 6 ) <= window.innerWidth )
173
+ domEl.style.left = left + 'px';
174
+ if( top > 3 && ( top + domEl.offsetHeight + 6 ) <= window.innerHeight )
175
+ domEl.style.top = top + 'px';
176
+ };
177
+
178
+ const customMoveFunc = e => {
179
+ if( !currentTarget ) return;
180
+ if( options.onMove )
181
+ options.onMove( currentTarget );
182
+ };
154
183
 
155
- domEl.setAttribute('draggable', true);
156
- domEl.addEventListener("mousedown", function(e) {
157
- // e.stopPropagation();
158
- // e.stopImmediatePropagation();
184
+ let onMove = options.onMove ? customMoveFunc : defaultMoveFunc;
185
+ let onDragStart = options.onDragStart;
186
+
187
+ domEl.setAttribute( 'draggable', true );
188
+ domEl.addEventListener( "mousedown", function( e ) {
159
189
  currentTarget = (e.target.classList.contains(targetClass) || !targetClass) ? e.target : null;
160
- });
190
+ } );
161
191
 
162
- domEl.addEventListener("dragstart", function(e) {
192
+ domEl.addEventListener( "dragstart", function( e ) {
163
193
  e.preventDefault();
164
194
  e.stopPropagation();
165
195
  e.stopImmediatePropagation();
166
- if(!currentTarget) return;
196
+ if( !currentTarget ) return;
167
197
  // Remove image when dragging
168
198
  var img = new Image();
169
199
  img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=';
170
- e.dataTransfer.setDragImage(img, 0, 0);
200
+ e.dataTransfer.setDragImage( img, 0, 0 );
171
201
  e.dataTransfer.effectAllowed = "move";
172
202
  const rect = e.target.getBoundingClientRect();
173
203
  offsetX = e.clientX - rect.x;
174
204
  offsetY = e.clientY - rect.y;
175
- document.addEventListener("mousemove", moveFunc );
176
- }, false);
205
+ document.addEventListener( "mousemove", onMove );
206
+ if( onDragStart )
207
+ onDragStart( currentTarget, e );
208
+ }, false );
177
209
 
178
- const moveFunc = (e) => {
179
- if(!currentTarget) return;
180
- let left = e.clientX - offsetX;
181
- let top = e.clientY - offsetY;
182
- if(left > 3 && (left + domEl.offsetWidth + 6) <= window.innerWidth)
183
- domEl.style.left = left + 'px';
184
- if(top > 3 && (top + domEl.offsetHeight + 6) <= window.innerHeight)
185
- domEl.style.top = top + 'px';
186
- };
187
-
188
- document.addEventListener('mouseup', () => {
189
- if(currentTarget) {
210
+ document.addEventListener( 'mouseup', () => {
211
+ if( currentTarget ) {
190
212
  currentTarget = null;
191
- document.removeEventListener("mousemove", moveFunc );
213
+ document.removeEventListener( "mousemove", onMove );
192
214
  }
193
- });
215
+ } );
194
216
  }
195
217
 
196
218
  LX.makeDraggable = makeDraggable;
@@ -294,6 +316,7 @@ function create_global_searchbar( root ) {
294
316
  }
295
317
 
296
318
  const add_element = (t, c, p, i) => {
319
+
297
320
  if(!t.length) return;
298
321
 
299
322
  if(ref_previous) ref_previous.classList.remove('last');
@@ -329,15 +352,20 @@ function create_global_searchbar( root ) {
329
352
 
330
353
  const propagate_add = ( item, filter, path ) => {
331
354
 
332
- const key = Object.keys(item)[0];
333
- if( (path + key).toLowerCase().includes(filter) ) {
334
- if(item.callback)
335
- add_element(key, item.callback, path, item);
355
+ const key = Object.keys( item )[ 0 ];
356
+ let name = item.name ?? path + key;
357
+ if( name.toLowerCase().includes( filter ) ) {
358
+ if( item.callback )
359
+ add_element( item.name ?? key, item.callback, path, item );
336
360
  }
337
361
 
362
+ // is sidebar..
363
+ if( item.name )
364
+ return;
365
+
338
366
  path += key + " > ";
339
367
 
340
- for( let c of item[key] )
368
+ for( let c of item[ key ] )
341
369
  propagate_add( c, filter, path );
342
370
  };
343
371
 
@@ -347,7 +375,7 @@ function create_global_searchbar( root ) {
347
375
 
348
376
  for( let m of LX.menubars )
349
377
  for( let i of m.items ) {
350
- propagate_add( i, filter, "");
378
+ propagate_add( i, filter, "" );
351
379
  }
352
380
 
353
381
  if( LX.has('CodeEditor') )
@@ -865,7 +893,7 @@ class Area {
865
893
  }
866
894
 
867
895
  // Create areas
868
- var area1 = new Area({ no_append: true, className: "split" + (options.menubar ? "" : " origin") });
896
+ var area1 = new Area({ no_append: true, className: "split" + (options.menubar || options.sidebar ? "" : " origin") });
869
897
  var area2 = new Area({ no_append: true, className: "split"});
870
898
 
871
899
  area1.parentArea = this;
@@ -1186,25 +1214,40 @@ class Area {
1186
1214
  addMenubar( callback, options = {} ) {
1187
1215
 
1188
1216
  let menubar = new Menubar(options);
1189
- LX.menubars.push( menubar );
1190
1217
 
1191
1218
  if(callback) callback( menubar );
1192
1219
 
1193
- // Hack to get content height
1194
- // let d = document.createElement('div');
1195
- // d.appendChild(menubar.root);
1196
- // document.body.appendChild(d);
1197
- // const height = menubar.root.clientHeight;
1198
- // d.remove();
1220
+ LX.menubars.push( menubar );
1221
+
1199
1222
  const height = 48; // pixels
1200
1223
 
1201
1224
  const [bar, content] = this.split({type: 'vertical', sizes: [height, null], resize: false, menubar: true});
1202
1225
  bar.attach( menubar );
1203
1226
  bar.is_menubar = true;
1204
- window.content = content;
1205
1227
  return menubar;
1206
1228
  }
1207
1229
 
1230
+ /**
1231
+ * @method addSidebar
1232
+ * @param {Function} callback Function to fill the sidebar
1233
+ */
1234
+
1235
+ addSidebar( callback, options = {} ) {
1236
+
1237
+ let sidebar = new SideBar( options );
1238
+
1239
+ if( callback ) callback( sidebar );
1240
+
1241
+ LX.menubars.push( sidebar );
1242
+
1243
+ const width = 64; // pixels
1244
+
1245
+ const [bar, content] = this.split( { type: 'horizontal', sizes: [ width, null ], resize: false, sidebar: true } );
1246
+ bar.attach( sidebar );
1247
+ bar.is_sidebar = true;
1248
+ return sidebar;
1249
+ }
1250
+
1208
1251
  /**
1209
1252
  * @method addOverlayButtons
1210
1253
  * @param {Array} buttons Buttons info
@@ -1778,7 +1821,7 @@ class Menubar {
1778
1821
  let entry = document.createElement('div');
1779
1822
  entry.className = "lexmenuentry";
1780
1823
  entry.id = pKey;
1781
- entry.innerText = key;
1824
+ entry.innerHTML = "<span>" + key + "</span>";
1782
1825
  if(options.position == "left") {
1783
1826
  this.root.prepend( entry );
1784
1827
  }
@@ -1802,7 +1845,7 @@ class Menubar {
1802
1845
  var rect = c.getBoundingClientRect();
1803
1846
  contextmenu.style.left = (isSubMenu ? rect.width : rect.left) + "px";
1804
1847
  // Entries use css to set top relative to parent
1805
- contextmenu.style.top = (isSubMenu ? 0 : rect.bottom) + "px";
1848
+ contextmenu.style.top = (isSubMenu ? 0 : rect.bottom - 4) + "px";
1806
1849
  c.appendChild( contextmenu );
1807
1850
 
1808
1851
  contextmenu.focus();
@@ -1928,11 +1971,16 @@ class Menubar {
1928
1971
  return;
1929
1972
  }
1930
1973
 
1931
- this.root.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
1974
+ // Manage selected
1975
+ this.root.querySelectorAll(".lexmenuentry").forEach( e => e.classList.remove( 'selected' ) );
1976
+ entry.classList.add( "selected" );
1977
+
1978
+ this.root.querySelectorAll(".lexcontextmenu").forEach( e => e.remove() );
1932
1979
  create_submenu( item, key, entry, -1 );
1933
1980
  });
1934
1981
 
1935
1982
  entry.addEventListener("mouseleave", () => {
1983
+ this.root.querySelectorAll(".lexmenuentry").forEach( e => e.classList.remove( 'selected' ) );
1936
1984
  this.root.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
1937
1985
  });
1938
1986
  }
@@ -2120,6 +2168,83 @@ class Menubar {
2120
2168
 
2121
2169
  LX.Menubar = Menubar;
2122
2170
 
2171
+ /**
2172
+ * @class SideBar
2173
+ */
2174
+
2175
+ class SideBar {
2176
+
2177
+ constructor( options = {} ) {
2178
+
2179
+ this.root = document.createElement( 'div' );
2180
+ this.root.className = "lexsidebar";
2181
+
2182
+ this.footer = document.createElement( 'div' );
2183
+ this.footer.className = "lexsidebarfooter";
2184
+ this.root.appendChild( this.footer );
2185
+
2186
+ this.items = [ ];
2187
+ }
2188
+
2189
+ /**
2190
+ * @method add
2191
+ * @param {*} options:
2192
+ * callback: Function to call on each item
2193
+ * bottom: Bool to set item at the bottom as helper button (not selectable)
2194
+ */
2195
+
2196
+ add( key, options = {} ) {
2197
+
2198
+ if( options.constructor == Function )
2199
+ options = { callback: options };
2200
+
2201
+ let pKey = key.replace( /\s/g, '' ).replaceAll( '.', '' );
2202
+
2203
+ if( this.items.findIndex( (v, i) => v.key == pKey ) > -1 )
2204
+ {
2205
+ console.warn( `'${key}' already created in Sidebar` );
2206
+ return;
2207
+ }
2208
+
2209
+ let entry = document.createElement( 'div' );
2210
+ entry.className = "lexsidebarentry";
2211
+ entry.id = pKey;
2212
+ entry.title = key;
2213
+
2214
+ if( options.bottom )
2215
+ {
2216
+ this.footer.appendChild( entry );
2217
+ }else
2218
+ {
2219
+ this.root.appendChild( entry );
2220
+ }
2221
+
2222
+ // Reappend footer in root
2223
+ this.root.appendChild( this.footer );
2224
+
2225
+ let button = document.createElement( 'button' );
2226
+ button.innerHTML = "<i class='"+ (options.icon ?? "") + "'></i>";
2227
+ entry.appendChild( button );
2228
+
2229
+ entry.addEventListener("click", () => {
2230
+
2231
+ const f = options.callback;
2232
+ if( f ) f.call( this, key, entry );
2233
+
2234
+ // Manage selected
2235
+ if( !options.bottom )
2236
+ {
2237
+ this.root.querySelectorAll(".lexsidebarentry").forEach( e => e.classList.remove( 'selected' ) );
2238
+ entry.classList.add( "selected" );
2239
+ }
2240
+ });
2241
+
2242
+ this.items.push( { name: pKey, domEl: entry, callback: options.callback } );
2243
+ }
2244
+ };
2245
+
2246
+ LX.SideBar = SideBar;
2247
+
2123
2248
  /**
2124
2249
  * @class Widget
2125
2250
  */
@@ -3339,99 +3464,105 @@ class Panel {
3339
3464
 
3340
3465
  addText( name, value, callback, options = {} ) {
3341
3466
 
3342
- let widget = this.create_widget(name, Widget.TEXT, options);
3467
+ let widget = this.create_widget( name, Widget.TEXT, options );
3343
3468
  widget.onGetValue = () => {
3344
3469
  return wValue.value;
3345
3470
  };
3346
- widget.onSetValue = (new_value) => {
3347
- this.disabled ? wValue.innerText = new_value : wValue.value = new_value;
3348
- Panel._dispatch_event(wValue, "focusout");
3471
+ widget.onSetValue = newValue => {
3472
+ this.disabled ? wValue.innerText = newValue : wValue.value = newValue;
3473
+ Panel._dispatch_event( wValue, "focusout" );
3349
3474
  };
3350
3475
 
3351
3476
  let element = widget.domEl;
3352
3477
 
3353
3478
  // Add reset functionality
3354
- if(widget.name && !(options.skipReset ?? false)) {
3355
- Panel._add_reset_property(element.domName, function() {
3479
+ if( widget.name && !( options.skipReset ?? false ) ) {
3480
+ Panel._add_reset_property( element.domName, function() {
3356
3481
  wValue.value = wValue.iValue;
3357
3482
  this.style.display = "none";
3358
- Panel._dispatch_event(wValue, "focusout");
3359
- });
3483
+ Panel._dispatch_event( wValue, "focusout" );
3484
+ } );
3360
3485
  }
3361
3486
 
3362
3487
  // Add widget value
3363
3488
 
3364
- let container = document.createElement('div');
3365
- container.className = "lextext" + (options.warning ? " lexwarning" : "");
3489
+ let container = document.createElement( 'div' );
3490
+ container.className = "lextext" + ( options.warning ? " lexwarning" : "" );
3366
3491
  container.style.width = options.inputWidth || "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + " )";
3367
3492
  container.style.display = "flex";
3368
3493
 
3369
- this.disabled = (options.disabled || options.warning) ?? ( options.url ? true : false );
3494
+ this.disabled = ( options.disabled || options.warning ) ?? ( options.url ? true : false );
3370
3495
  let wValue = null;
3371
3496
 
3372
3497
  if( !this.disabled )
3373
3498
  {
3374
- wValue = document.createElement('input');
3499
+ wValue = document.createElement( 'input' );
3375
3500
  wValue.type = options.type || "";
3376
3501
  wValue.value = wValue.iValue = value || "";
3377
3502
  wValue.style.width = "100%";
3378
3503
  wValue.style.textAlign = options.float ?? "";
3379
3504
 
3380
- if(options.placeholder) wValue.setAttribute("placeholder", options.placeholder);
3505
+ if( options.placeholder )
3506
+ wValue.setAttribute( "placeholder", options.placeholder );
3381
3507
 
3382
- var resolve = (function(val, event) {
3383
- let btn = element.querySelector(".lexwidgetname .lexicon");
3384
- if(btn) btn.style.display = (val != wValue.iValue ? "block" : "none");
3385
- this._trigger( new IEvent(name, val, event), callback );
3386
- }).bind(this);
3508
+ var resolve = ( function( val, event ) {
3509
+ let btn = element.querySelector( ".lexwidgetname .lexicon" );
3510
+ if( btn ) btn.style.display = ( val != wValue.iValue ? "block" : "none" );
3511
+ this._trigger( new IEvent( name, val, event ), callback );
3512
+ }).bind( this );
3387
3513
 
3388
3514
  const trigger = options.trigger ?? 'default';
3389
3515
 
3390
- if(trigger == 'default')
3516
+ if( trigger == 'default' )
3391
3517
  {
3392
- wValue.addEventListener("keyup", function(e){
3518
+ wValue.addEventListener( "keyup", function( e ){
3393
3519
  if(e.key == 'Enter')
3394
- resolve(e.target.value, e);
3520
+ resolve( e.target.value, e );
3395
3521
  });
3396
- wValue.addEventListener("focusout", function(e){
3397
- resolve(e.target.value, e);
3522
+ wValue.addEventListener( "focusout", function( e ){
3523
+ resolve( e.target.value, e );
3398
3524
  });
3399
3525
  }
3400
- else if(trigger == 'input')
3526
+ else if( trigger == 'input' )
3401
3527
  {
3402
- wValue.addEventListener("input", function(e){
3403
- resolve(e.target.value, e);
3528
+ wValue.addEventListener("input", function( e ){
3529
+ resolve( e.target.value, e );
3404
3530
  });
3405
3531
  }
3406
3532
 
3407
- if(options.icon)
3533
+ wValue.addEventListener( "mousedown", function( e ){
3534
+ e.stopImmediatePropagation();
3535
+ e.stopPropagation();
3536
+ });
3537
+
3538
+ if( options.icon )
3408
3539
  {
3409
- let icon = document.createElement('a');
3540
+ let icon = document.createElement( 'a' );
3410
3541
  icon.className = "inputicon " + options.icon;
3411
- container.appendChild(icon);
3542
+ container.appendChild( icon );
3412
3543
  }
3413
3544
 
3414
3545
  } else
3415
3546
  {
3416
- wValue = document.createElement(options.url ? 'a' : 'div');
3417
- if(options.url)
3547
+ wValue = document.createElement( options.url ? 'a' : 'div' );
3548
+ if( options.url )
3418
3549
  {
3419
3550
  wValue.href = options.url;
3420
3551
  wValue.target = "_blank";
3421
3552
  }
3422
3553
  const icon = options.warning ? '<i class="fa-solid fa-triangle-exclamation"></i>' : '';
3423
- wValue.innerHTML = (icon + value) || "";
3554
+ wValue.innerHTML = ( icon + value ) || "";
3424
3555
  wValue.style.width = "100%";
3425
3556
  wValue.style.textAlign = options.float ?? "";
3426
3557
  }
3427
3558
 
3428
- Object.assign(wValue.style, options.style ?? {});
3559
+ Object.assign( wValue.style, options.style ?? {} );
3429
3560
 
3430
- container.appendChild(wValue);
3431
- element.appendChild(container);
3561
+ container.appendChild( wValue );
3562
+ element.appendChild( container );
3432
3563
 
3433
3564
  // Remove branch padding and margins
3434
- if(!widget.name) {
3565
+ if( !widget.name ) {
3435
3566
  element.className += " noname";
3436
3567
  container.style.width = "100%";
3437
3568
  }
@@ -3844,7 +3975,7 @@ class Panel {
3844
3975
 
3845
3976
  // Add dropdown widget button
3846
3977
  let buttonName = value;
3847
- buttonName += "<a class='fa-solid fa-angle-down' style='float:right; margin-right: 6px;'></a>";
3978
+ buttonName += "<a class='fa-solid fa-angle-down' style='float:right; margin-right: 3px;'></a>";
3848
3979
 
3849
3980
  this.queue(container);
3850
3981
 
@@ -3853,11 +3984,8 @@ class Panel {
3853
3984
  delete list.unfocus_event;
3854
3985
  return;
3855
3986
  }
3856
- let container = selectedOption.parentElement.parentElement.parentElement.parentElement; // there must be a nicer way...
3857
- let rect = event.currentTarget.getBoundingClientRect();
3858
- let y_pos = container.classList.contains('lexdialog') ? rect.top - 5 + rect.height : rect.y + rect.height - 5;
3859
- element.querySelector(".lexoptions").style.top = y_pos + 'px';
3860
- element.querySelector(".lexoptions").style.width = (event.currentTarget.clientWidth) + 2 + 'px';
3987
+ element.querySelector(".lexoptions").style.top = (selectedOption.offsetTop + selectedOption.offsetHeight) + 'px';
3988
+ element.querySelector(".lexoptions").style.width = (event.currentTarget.clientWidth) + 'px';
3861
3989
  element.querySelector(".lexoptions").toggleAttribute('hidden');
3862
3990
  list.focus();
3863
3991
  }, { buttonClass: 'array', skipInlineCount: true });
@@ -3873,24 +4001,24 @@ class Panel {
3873
4001
  selectedOption.querySelector("span").innerHTML = selectedOption.querySelector("span").innerHTML.replaceAll(selectedOption.querySelector("span").innerText, v);
3874
4002
  }
3875
4003
 
3876
- //Add dropdown options container
3877
- let list = document.createElement('ul');
4004
+ // Add dropdown options container
4005
+ let list = document.createElement( 'ul' );
3878
4006
  list.tabIndex = -1;
3879
4007
  list.className = "lexoptions";
3880
4008
  list.hidden = true;
3881
4009
 
3882
- list.addEventListener('focusout', function(e) {
4010
+ list.addEventListener( 'focusout', function( e ) {
3883
4011
  e.stopPropagation();
3884
4012
  e.stopImmediatePropagation();
3885
- if(e.relatedTarget === selectedOption.querySelector("button")) {
4013
+ if(e.relatedTarget === selectedOption.querySelector( 'button' )) {
3886
4014
  this.unfocus_event = true;
3887
- setTimeout(() => delete this.unfocus_event, 200);
3888
- } else if (e.relatedTarget && e.relatedTarget.tagName == "INPUT") {
4015
+ setTimeout( () => delete this.unfocus_event, 200 );
4016
+ } else if ( e.relatedTarget && e.relatedTarget.tagName == "INPUT" ) {
3889
4017
  return;
3890
- }else if (e.target.id == 'input-filter') {
4018
+ }else if ( e.target.id == 'input-filter' ) {
3891
4019
  return;
3892
4020
  }
3893
- this.toggleAttribute('hidden', true);
4021
+ this.toggleAttribute( 'hidden', true );
3894
4022
  });
3895
4023
 
3896
4024
  // Add filter options
@@ -4187,7 +4315,7 @@ class Panel {
4187
4315
 
4188
4316
  // Add dropdown array button
4189
4317
 
4190
- const itemNameWidth = "3%";
4318
+ const itemNameWidth = "4%";
4191
4319
 
4192
4320
  var container = document.createElement('div');
4193
4321
  container.className = "lexarray";
@@ -4195,7 +4323,7 @@ class Panel {
4195
4323
 
4196
4324
  this.queue( container );
4197
4325
 
4198
- const angle_down = `<a class='fa-solid fa-angle-down' style='float:right; margin-right: 6px;'></a>`;
4326
+ const angle_down = `<a class='fa-solid fa-angle-down' style='float:right; margin-right: 3px;'></a>`;
4199
4327
 
4200
4328
  let buttonName = "Array (size " + values.length + ")";
4201
4329
  buttonName += angle_down;
@@ -4263,7 +4391,7 @@ class Panel {
4263
4391
  }
4264
4392
 
4265
4393
  buttonName = "Add item";
4266
- buttonName += "<a class='fa-solid fa-plus' style='float:right; margin-right: 6px; margin-top: 2px;'></a>";
4394
+ buttonName += "<a class='fa-solid fa-plus' style='float:right; margin-right: 3px; margin-top: 2px;'></a>";
4267
4395
  this.addButton(null, buttonName, (v, event) => {
4268
4396
  values.push( "" );
4269
4397
  updateItems();
@@ -4782,7 +4910,10 @@ class Panel {
4782
4910
  doc.addEventListener("mouseup",inner_mouseup);
4783
4911
  lastY = e.pageY;
4784
4912
  document.body.classList.add('nocursor');
4913
+ document.body.classList.add('noevents');
4785
4914
  drag_icon.classList.remove('hidden');
4915
+ e.stopImmediatePropagation();
4916
+ e.stopPropagation();
4786
4917
  }
4787
4918
 
4788
4919
  function inner_mousemove(e) {
@@ -4806,6 +4937,7 @@ class Panel {
4806
4937
  doc.removeEventListener("mousemove",inner_mousemove);
4807
4938
  doc.removeEventListener("mouseup",inner_mouseup);
4808
4939
  document.body.classList.remove('nocursor');
4940
+ document.body.classList.remove('noevents');
4809
4941
  drag_icon.classList.add('hidden');
4810
4942
  }
4811
4943
 
@@ -4952,7 +5084,10 @@ class Panel {
4952
5084
  doc.addEventListener("mouseup",inner_mouseup);
4953
5085
  lastY = e.pageY;
4954
5086
  document.body.classList.add('nocursor');
5087
+ document.body.classList.add('noevents');
4955
5088
  drag_icon.classList.remove('hidden');
5089
+ e.stopImmediatePropagation();
5090
+ e.stopPropagation();
4956
5091
  }
4957
5092
 
4958
5093
  function inner_mousemove(e) {
@@ -4984,6 +5119,7 @@ class Panel {
4984
5119
  doc.removeEventListener("mousemove",inner_mousemove);
4985
5120
  doc.removeEventListener("mouseup",inner_mouseup);
4986
5121
  document.body.classList.remove('nocursor');
5122
+ document.body.classList.remove('noevents');
4987
5123
  drag_icon.classList.add('hidden');
4988
5124
  }
4989
5125
 
@@ -5136,11 +5272,11 @@ class Panel {
5136
5272
 
5137
5273
  addFile( name, callback, options = { } ) {
5138
5274
 
5139
- if(!name) {
5140
- throw("Set Widget Name!");
5275
+ if( !name ) {
5276
+ throw( "Set Widget Name!" );
5141
5277
  }
5142
5278
 
5143
- let widget = this.create_widget(name, Widget.FILE, options);
5279
+ let widget = this.create_widget( name, Widget.FILE, options );
5144
5280
  let element = widget.domEl;
5145
5281
 
5146
5282
  let local = options.local ?? true;
@@ -5148,31 +5284,36 @@ class Panel {
5148
5284
  let read = options.read ?? true;
5149
5285
 
5150
5286
  // Create hidden input
5151
- let input = document.createElement('input');
5287
+ let input = document.createElement( 'input' );
5152
5288
  input.style.width = "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + " - 10%)";
5153
5289
  input.type = 'file';
5154
- if(options.placeholder)
5290
+
5291
+ if( options.placeholder )
5155
5292
  input.placeholder = options.placeholder;
5156
5293
 
5157
- input.addEventListener('change', function(e) {
5294
+ input.addEventListener( 'change', function( e ) {
5295
+
5158
5296
  const files = e.target.files;
5159
5297
  if( !files.length ) return;
5160
- if(read)
5298
+ if( read )
5161
5299
  {
5300
+ if( options.onBeforeRead )
5301
+ options.onBeforeRead();
5302
+
5162
5303
  const reader = new FileReader();
5163
5304
 
5164
- if(type === 'text') reader.readAsText(files[0]);
5165
- else if(type === 'buffer') reader.readAsArrayBuffer(files[0]);
5166
- else if(type === 'bin') reader.readAsBinaryString(files[0]);
5167
- else if(type === 'url') reader.readAsDataURL(files[0]);
5305
+ if( type === 'text' ) reader.readAsText( files[ 0 ] );
5306
+ else if( type === 'buffer' ) reader.readAsArrayBuffer( files[ 0 ] );
5307
+ else if( type === 'bin' ) reader.readAsBinaryString( files[ 0 ] );
5308
+ else if( type === 'url' ) reader.readAsDataURL( files[ 0 ] );
5168
5309
 
5169
- reader.onload = (e) => { callback.call( this, e.target.result, files[0] ) } ;
5310
+ reader.onload = e => { callback.call( this, e.target.result, files[ 0 ] ) } ;
5170
5311
  }
5171
- else callback(files[0]);
5172
-
5312
+ else
5313
+ callback( files[ 0 ] );
5173
5314
  });
5174
5315
 
5175
- element.appendChild(input);
5316
+ element.appendChild( input );
5176
5317
 
5177
5318
  this.queue( element );
5178
5319
 
@@ -5180,9 +5321,9 @@ class Panel {
5180
5321
  {
5181
5322
  this.addButton(null, "<a style='margin-top: 0px;' class='fa-solid fa-gear'></a>", () => {
5182
5323
 
5183
- new Dialog("Load Settings", p => {
5184
- p.addDropdown("Type", ['text', 'buffer', 'bin', 'url'], type, v => { type = v } );
5185
- p.addButton(null, "Reload", v => { input.dispatchEvent( new Event('change') ) } );
5324
+ new Dialog( "Load Settings", p => {
5325
+ p.addDropdown( "Type", [ 'text', 'buffer', 'bin', 'url' ], type, v => { type = v } );
5326
+ p.addButton( null, "Reload", v => { input.dispatchEvent( new Event( 'change' ) ) } );
5186
5327
  });
5187
5328
 
5188
5329
  }, { className: "micro", skipInlineCount: true });
@@ -5713,8 +5854,8 @@ class Dialog {
5713
5854
  this.root = root;
5714
5855
  this.title = titleDiv;
5715
5856
 
5716
- if(draggable)
5717
- makeDraggable( root, 'lexdialogtitle' );
5857
+ if( draggable )
5858
+ makeDraggable( root, { targetClass: 'lexdialogtitle' } );
5718
5859
 
5719
5860
  // Process position and size
5720
5861
  if(size.length && typeof(size[0]) != "string")
@@ -5736,6 +5877,7 @@ class Dialog {
5736
5877
  }
5737
5878
 
5738
5879
  destroy() {
5880
+
5739
5881
  this.root.remove();
5740
5882
  }
5741
5883
 
@@ -5750,6 +5892,14 @@ class Dialog {
5750
5892
  this.root.style.left = x + "px";
5751
5893
  this.root.style.top = y + "px";
5752
5894
  }
5895
+
5896
+ setTitle( title ) {
5897
+
5898
+ const titleDOM = this.root.querySelector( '.lexdialogtitle' );
5899
+ if( !titleDOM )
5900
+ return;
5901
+ titleDOM.innerText = title;
5902
+ }
5753
5903
  }
5754
5904
 
5755
5905
  LX.Dialog = Dialog;
@@ -5779,8 +5929,10 @@ class PocketDialog extends Dialog {
5779
5929
 
5780
5930
  // Custom
5781
5931
  this.root.classList.add( "pocket" );
5782
- this.root.style.left = "calc(100% - " + (this.root.offsetWidth + 6) + "px)";
5783
- this.root.style.top = "0px";
5932
+ if( !options.position ) {
5933
+ this.root.style.left = "calc(100% - " + (this.root.offsetWidth + 6) + "px)";
5934
+ this.root.style.top = "0px";
5935
+ }
5784
5936
  this.panel.root.style.width = "calc( 100% - 12px )";
5785
5937
  this.panel.root.style.height = "calc( 100% - 40px )";
5786
5938
  this.dock_pos = PocketDialog.TOP;
@@ -5819,7 +5971,7 @@ class PocketDialog extends Dialog {
5819
5971
  this.root.style.top = "calc(100% - " + (this.root.offsetHeight + 6) + "px)";
5820
5972
  break;
5821
5973
  case 'l':
5822
- this.root.style.left = "0px";
5974
+ this.root.style.left = options.position ? options.position[ 1 ] : "0px";
5823
5975
  break;
5824
5976
  }
5825
5977
  }
@@ -5924,7 +6076,7 @@ class ContextMenu {
5924
6076
  contextmenu.style.marginTop = 3.5 - c.offsetHeight + "px";
5925
6077
 
5926
6078
  // Set final width
5927
- contextmenu.style.width = contextmenu.offsetWidth + "px";
6079
+ // contextmenu.style.width = contextmenu.offsetWidth + "px";
5928
6080
  this._adjust_position( contextmenu, 6, true );
5929
6081
  }
5930
6082
 
@@ -6130,8 +6282,8 @@ class Curve {
6130
6282
  element.style.minHeight = "50px";
6131
6283
  element.style.width = options.width || "100%";
6132
6284
 
6133
- element.bgcolor = options.bgcolor || "#15181c";
6134
- element.pointscolor = options.pointscolor || "#7b8ae2";
6285
+ element.bgcolor = options.bgcolor || LX.getThemeColor("global-dark-background");
6286
+ element.pointscolor = options.pointscolor || LX.getThemeColor("global-selected-light");
6135
6287
  element.linecolor = options.linecolor || "#555";
6136
6288
 
6137
6289
  element.value = value || [];
@@ -7372,9 +7524,15 @@ Object.defineProperty(String.prototype, 'lastChar', {
7372
7524
  configurable: true
7373
7525
  });
7374
7526
 
7375
- Element.prototype.insertChildAtIndex = function(child, index = Infinity) {
7376
- if (index >= this.children.length) this.appendChild(child);
7377
- else this.insertBefore(child, this.children[index]);
7527
+ Element.prototype.insertChildAtIndex = function( child, index = Infinity ) {
7528
+ if ( index >= this.children.length ) this.appendChild( child );
7529
+ else this.insertBefore( child, this.children[index] );
7530
+ }
7531
+
7532
+ Element.prototype.hasClass = function( list ) {
7533
+ list = [].concat( list );
7534
+ var r = list.filter( v => this.classList.contains( v ) );
7535
+ return !!r.length;
7378
7536
  }
7379
7537
 
7380
7538
  Element.prototype.getComputedSize = function() {
@@ -7390,7 +7548,7 @@ LX.UTILS = {
7390
7548
  compareThreshold( v, p, n, t ) { return Math.abs(v - p) >= t || Math.abs(v - n) >= t },
7391
7549
  compareThresholdRange( v0, v1, t0, t1 ) { return v0 >= t0 && v0 <= t1 || v1 >= t0 && v1 <= t1 || v0 <= t0 && v1 >= t1},
7392
7550
  clamp (num, min, max) { return Math.min(Math.max(num, min), max) },
7393
-
7551
+ uidGenerator: simple_guidGenerator,
7394
7552
  getControlPoints( x0, y0, x1, y1, x2, y2, t ) {
7395
7553
 
7396
7554
  // x0,y0,x1,y1 are the coordinates of the end (knot) pts of this segment
@@ -7415,7 +7573,6 @@ LX.UTILS = {
7415
7573
 
7416
7574
  return [p1x,p1y,p2x,p2y]
7417
7575
  },
7418
-
7419
7576
  drawSpline( ctx, pts, t ) {
7420
7577
 
7421
7578
  ctx.save();