lexgui 0.4.0 → 0.4.1

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,7 +8,7 @@
8
8
  */
9
9
 
10
10
  var LX = {
11
- version: "0.4.0",
11
+ version: "0.4.1",
12
12
  ready: false,
13
13
  components: [], // Specific pre-build components
14
14
  signals: {}, // Events and triggers
@@ -20,13 +20,13 @@ LX.MOUSE_LEFT_CLICK = 0;
20
20
  LX.MOUSE_MIDDLE_CLICK = 1;
21
21
  LX.MOUSE_RIGHT_CLICK = 2;
22
22
 
23
- LX.MOUSE_DOUBLE_CLICK = 2;
24
- LX.MOUSE_TRIPLE_CLICK = 3;
23
+ LX.MOUSE_DOUBLE_CLICK = 2;
24
+ LX.MOUSE_TRIPLE_CLICK = 3;
25
25
 
26
- LX.CURVE_MOVEOUT_CLAMP = 0;
26
+ LX.CURVE_MOVEOUT_CLAMP = 0;
27
27
  LX.CURVE_MOVEOUT_DELETE = 1;
28
28
 
29
- LX.DRAGGABLE_Z_INDEX = 101;
29
+ LX.DRAGGABLE_Z_INDEX = 101;
30
30
 
31
31
  function clamp( num, min, max ) { return Math.min( Math.max( num, min ), max ); }
32
32
  function round( number, precision ) { return precision == 0 ? Math.floor( number ) : +(( number ).toFixed( precision ?? 2 ).replace( /([0-9]+(\.[0-9]+[1-9])?)(\.?0+$)/, '$1' )); }
@@ -926,7 +926,19 @@ function init( options = { } )
926
926
  this.container = document.getElementById( options.container );
927
927
  }
928
928
 
929
- document.documentElement.setAttribute( "data-strictVP", ( options.strictViewport ?? true ) ? "true" : "false" );
929
+ this.usingStrictViewport = options.strictViewport ?? true;
930
+ document.documentElement.setAttribute( "data-strictVP", ( this.usingStrictViewport ) ? "true" : "false" );
931
+
932
+ if( !this.usingStrictViewport )
933
+ {
934
+ document.addEventListener( "scroll", e => {
935
+ // Get all active menuboxes
936
+ const mbs = document.body.querySelectorAll( ".lexmenubox" );
937
+ mbs.forEach( ( mb ) => {
938
+ mb._updatePosition();
939
+ } );
940
+ } );
941
+ }
930
942
 
931
943
  this.commandbar = _createCommandbar( this.container );
932
944
 
@@ -2063,7 +2075,7 @@ class Area {
2063
2075
  }
2064
2076
 
2065
2077
  // Generate DOM elements after adding all entries
2066
- sidebar._build();
2078
+ sidebar.update();
2067
2079
 
2068
2080
  LX.menubars.push( sidebar );
2069
2081
 
@@ -2673,6 +2685,195 @@ class Menubar {
2673
2685
  this.shorts = { };
2674
2686
  }
2675
2687
 
2688
+ _resetMenubar() {
2689
+
2690
+ // Menu entries are in the menubar..
2691
+ this.root.querySelectorAll(".lexmenuentry").forEach( _entry => {
2692
+ _entry.classList.remove( 'selected' );
2693
+ _entry.built = false;
2694
+ } );
2695
+
2696
+ // Menuboxes are in the root area!
2697
+ LX.root.querySelectorAll(".lexmenubox").forEach(e => e.remove());
2698
+
2699
+ // Next time we need to click again
2700
+ this.focused = false;
2701
+ }
2702
+
2703
+ _createSubmenu( o, k, c, d ) {
2704
+
2705
+ let menuElement = document.createElement('div');
2706
+ menuElement.className = "lexmenubox";
2707
+ menuElement.tabIndex = "0";
2708
+ c.currentMenu = menuElement;
2709
+ menuElement.parentEntry = c;
2710
+
2711
+ const isSubMenu = c.classList.contains( "lexmenuboxentry" );
2712
+ if( isSubMenu )
2713
+ {
2714
+ menuElement.dataset[ "submenu" ] = true;
2715
+ }
2716
+
2717
+ menuElement._updatePosition = () => {
2718
+
2719
+ // Remove transitions for this change..
2720
+ const transition = menuElement.style.transition;
2721
+ menuElement.style.transition = "none";
2722
+ flushCss( menuElement );
2723
+
2724
+ doAsync( () => {
2725
+ let rect = c.getBoundingClientRect();
2726
+ rect.x += document.scrollingElement.scrollLeft;
2727
+ rect.y += document.scrollingElement.scrollTop;
2728
+ menuElement.style.left = ( isSubMenu ? ( rect.x + rect.width ) : rect.x ) + "px";
2729
+ menuElement.style.top = ( isSubMenu ? rect.y : ( ( rect.y + rect.height ) ) - 4 ) + "px";
2730
+
2731
+ menuElement.style.transition = transition;
2732
+ } );
2733
+ };
2734
+
2735
+ menuElement._updatePosition();
2736
+
2737
+ doAsync( () => {
2738
+ menuElement.dataset[ "open" ] = true;
2739
+ }, 10 );
2740
+
2741
+ LX.root.appendChild( menuElement );
2742
+
2743
+ for( var i = 0; i < o[ k ].length; ++i )
2744
+ {
2745
+ const subitem = o[ k ][ i ];
2746
+ const subkey = Object.keys( subitem )[ 0 ];
2747
+ const hasSubmenu = subitem[ subkey ].length;
2748
+ const isCheckbox = subitem[ 'type' ] == 'checkbox';
2749
+ let subentry = document.createElement('div');
2750
+ subentry.className = "lexmenuboxentry";
2751
+ subentry.className += (i == o[k].length - 1 ? " last" : "") + ( subitem.disabled ? " disabled" : "" );
2752
+
2753
+ if( subkey == '' )
2754
+ {
2755
+ subentry.className = " lexseparator";
2756
+ }
2757
+ else
2758
+ {
2759
+ subentry.id = subkey;
2760
+ let subentrycont = document.createElement('div');
2761
+ subentrycont.innerHTML = "";
2762
+ subentrycont.classList = "lexmenuboxentrycontainer";
2763
+ subentry.appendChild(subentrycont);
2764
+ const icon = this.icons[ subkey ];
2765
+ if( isCheckbox )
2766
+ {
2767
+ subentrycont.innerHTML += "<input type='checkbox' >";
2768
+ }
2769
+ else if( icon )
2770
+ {
2771
+ subentrycont.innerHTML += "<a class='" + icon + " fa-sm'></a>";
2772
+ }
2773
+ else
2774
+ {
2775
+ subentrycont.innerHTML += "<a class='fa-solid fa-sm noicon'></a>";
2776
+ subentrycont.classList.add( "noicon" );
2777
+
2778
+ }
2779
+ subentrycont.innerHTML += "<div class='lexentryname'>" + subkey + "</div>";
2780
+ }
2781
+
2782
+ let checkboxInput = subentry.querySelector('input');
2783
+ if( checkboxInput )
2784
+ {
2785
+ checkboxInput.checked = subitem.checked ?? false;
2786
+ checkboxInput.addEventListener('change', e => {
2787
+ subitem.checked = checkboxInput.checked;
2788
+ const f = subitem[ 'callback' ];
2789
+ if( f )
2790
+ {
2791
+ f.call( this, subitem.checked, subkey, subentry );
2792
+ this._resetMenubar();
2793
+ }
2794
+ e.stopPropagation();
2795
+ e.stopImmediatePropagation();
2796
+ })
2797
+ }
2798
+
2799
+ menuElement.appendChild( subentry );
2800
+
2801
+ // Nothing more for separators
2802
+ if( subkey == '' )
2803
+ {
2804
+ continue;
2805
+ }
2806
+
2807
+ menuElement.addEventListener('keydown', e => {
2808
+ e.preventDefault();
2809
+ let short = this.shorts[ subkey ];
2810
+ if(!short) return;
2811
+ // check if it's a letter or other key
2812
+ short = short.length == 1 ? short.toLowerCase() : short;
2813
+ if( short == e.key )
2814
+ {
2815
+ subentry.click()
2816
+ }
2817
+ });
2818
+
2819
+ // Add callback
2820
+ subentry.addEventListener("click", e => {
2821
+ if( checkboxInput )
2822
+ {
2823
+ subitem.checked = !subitem.checked;
2824
+ }
2825
+ const f = subitem[ 'callback' ];
2826
+ if( f )
2827
+ {
2828
+ f.call( this, checkboxInput ? subitem.checked : subkey, checkboxInput ? subkey : subentry );
2829
+ this._resetMenubar();
2830
+ }
2831
+ e.stopPropagation();
2832
+ e.stopImmediatePropagation();
2833
+ });
2834
+
2835
+ // Add icon if has submenu, else check for shortcut
2836
+ if( !hasSubmenu)
2837
+ {
2838
+ if( this.shorts[ subkey ] )
2839
+ {
2840
+ let shortEl = document.createElement('div');
2841
+ shortEl.className = "lexentryshort";
2842
+ shortEl.innerText = this.shorts[ subkey ];
2843
+ subentry.appendChild( shortEl );
2844
+ }
2845
+ continue;
2846
+ }
2847
+
2848
+ let submenuIcon = document.createElement('a');
2849
+ submenuIcon.className = "fa-solid fa-angle-right fa-xs";
2850
+ subentry.appendChild( submenuIcon );
2851
+
2852
+ subentry.addEventListener("mouseover", e => {
2853
+ if( subentry.built )
2854
+ {
2855
+ return;
2856
+ }
2857
+ subentry.built = true;
2858
+ this._createSubmenu( subitem, subkey, subentry, ++d );
2859
+ e.stopPropagation();
2860
+ });
2861
+
2862
+ subentry.addEventListener("mouseleave", e => {
2863
+ if( subentry.currentMenu && ( subentry.currentMenu != e.toElement ) )
2864
+ {
2865
+ d = -1; // Reset depth
2866
+ delete subentry.built;
2867
+ subentry.currentMenu.remove();
2868
+ delete subentry.currentMenu;
2869
+ }
2870
+ });
2871
+ }
2872
+
2873
+ // Set final width
2874
+ menuElement.style.width = menuElement.offsetWidth + "px";
2875
+ }
2876
+
2676
2877
  /**
2677
2878
  * @method add
2678
2879
  * @param {Object} options:
@@ -2776,176 +2977,11 @@ class Menubar {
2776
2977
  }
2777
2978
  }
2778
2979
 
2779
- const _resetMenubar = function() {
2780
- // Menu entries are in the menubar..
2781
- that.root.querySelectorAll(".lexmenuentry").forEach( _entry => {
2782
- _entry.classList.remove( 'selected' );
2783
- _entry.built = false;
2784
- } );
2785
- // Menuboxes are in the root area!
2786
- LX.root.querySelectorAll(".lexmenubox").forEach(e => e.remove());
2787
- // Next time we need to click again
2788
- that.focused = false;
2789
- };
2790
-
2791
- const create_submenu = function( o, k, c, d ) {
2792
-
2793
- let menuElement = document.createElement('div');
2794
- menuElement.className = "lexmenubox";
2795
- menuElement.tabIndex = "0";
2796
- c.currentMenu = menuElement;
2797
- const isSubMenu = c.classList.contains( "lexmenuboxentry" );
2798
- if( isSubMenu ) menuElement.dataset[ "submenu" ] = true;
2799
- var rect = c.getBoundingClientRect();
2800
- menuElement.style.left = ( isSubMenu ? ( rect.x + rect.width ) : rect.left ) + "px";
2801
- menuElement.style.top = ( isSubMenu ? rect.y : rect.bottom - 4 ) + "px";
2802
- rect = menuElement.getBoundingClientRect();
2803
-
2804
- doAsync( () => {
2805
- menuElement.dataset[ "open" ] = true;
2806
- }, 10 );
2807
-
2808
- LX.root.appendChild( menuElement );
2809
-
2810
- for( var i = 0; i < o[ k ].length; ++i )
2811
- {
2812
- const subitem = o[ k ][ i ];
2813
- const subkey = Object.keys( subitem )[ 0 ];
2814
- const hasSubmenu = subitem[ subkey ].length;
2815
- const isCheckbox = subitem[ 'type' ] == 'checkbox';
2816
- let subentry = document.createElement('div');
2817
- subentry.className = "lexmenuboxentry";
2818
- subentry.className += (i == o[k].length - 1 ? " last" : "") + ( subitem.disabled ? " disabled" : "" );
2819
-
2820
- if( subkey == '' )
2821
- {
2822
- subentry.className = " lexseparator";
2823
- }
2824
- else
2825
- {
2826
- subentry.id = subkey;
2827
- let subentrycont = document.createElement('div');
2828
- subentrycont.innerHTML = "";
2829
- subentrycont.classList = "lexmenuboxentrycontainer";
2830
- subentry.appendChild(subentrycont);
2831
- const icon = that.icons[ subkey ];
2832
- if( isCheckbox )
2833
- {
2834
- subentrycont.innerHTML += "<input type='checkbox' >";
2835
- }
2836
- else if( icon )
2837
- {
2838
- subentrycont.innerHTML += "<a class='" + icon + " fa-sm'></a>";
2839
- }
2840
- else
2841
- {
2842
- subentrycont.innerHTML += "<a class='fa-solid fa-sm noicon'></a>";
2843
- subentrycont.classList.add( "noicon" );
2844
-
2845
- }
2846
- subentrycont.innerHTML += "<div class='lexentryname'>" + subkey + "</div>";
2847
- }
2848
-
2849
- let checkboxInput = subentry.querySelector('input');
2850
- if( checkboxInput )
2851
- {
2852
- checkboxInput.checked = subitem.checked ?? false;
2853
- checkboxInput.addEventListener('change', e => {
2854
- subitem.checked = checkboxInput.checked;
2855
- const f = subitem[ 'callback' ];
2856
- if( f )
2857
- {
2858
- f.call( this, subitem.checked, subkey, subentry );
2859
- _resetMenubar();
2860
- }
2861
- e.stopPropagation();
2862
- e.stopImmediatePropagation();
2863
- })
2864
- }
2865
-
2866
- menuElement.appendChild( subentry );
2867
-
2868
- // Nothing more for separators
2869
- if( subkey == '' )
2870
- {
2871
- continue;
2872
- }
2873
-
2874
- menuElement.addEventListener('keydown', function(e) {
2875
- e.preventDefault();
2876
- let short = that.shorts[ subkey ];
2877
- if(!short) return;
2878
- // check if it's a letter or other key
2879
- short = short.length == 1 ? short.toLowerCase() : short;
2880
- if( short == e.key )
2881
- {
2882
- subentry.click()
2883
- }
2884
- });
2885
-
2886
- // Add callback
2887
- subentry.addEventListener("click", e => {
2888
- if( checkboxInput )
2889
- {
2890
- subitem.checked = !subitem.checked;
2891
- }
2892
- const f = subitem[ 'callback' ];
2893
- if( f )
2894
- {
2895
- f.call( this, checkboxInput ? subitem.checked : subkey, checkboxInput ? subkey : subentry );
2896
- _resetMenubar();
2897
- }
2898
- e.stopPropagation();
2899
- e.stopImmediatePropagation();
2900
- });
2901
-
2902
- // Add icon if has submenu, else check for shortcut
2903
- if( !hasSubmenu)
2904
- {
2905
- if( that.shorts[ subkey ] )
2906
- {
2907
- let shortEl = document.createElement('div');
2908
- shortEl.className = "lexentryshort";
2909
- shortEl.innerText = that.shorts[ subkey ];
2910
- subentry.appendChild( shortEl );
2911
- }
2912
- continue;
2913
- }
2914
-
2915
- let submenuIcon = document.createElement('a');
2916
- submenuIcon.className = "fa-solid fa-angle-right fa-xs";
2917
- subentry.appendChild( submenuIcon );
2918
-
2919
- subentry.addEventListener("mouseover", e => {
2920
- if( subentry.built )
2921
- {
2922
- return;
2923
- }
2924
- subentry.built = true;
2925
- create_submenu( subitem, subkey, subentry, ++d );
2926
- e.stopPropagation();
2927
- });
2928
-
2929
- subentry.addEventListener("mouseleave", (e) => {
2930
- if( subentry.currentMenu && ( subentry.currentMenu != e.toElement ) )
2931
- {
2932
- d = -1; // Reset depth
2933
- delete subentry.built;
2934
- subentry.currentMenu.remove();
2935
- delete subentry.currentMenu;
2936
- }
2937
- });
2938
- }
2939
-
2940
- // Set final width
2941
- menuElement.style.width = menuElement.offsetWidth + "px";
2942
- };
2943
-
2944
2980
  const _showEntry = () => {
2945
- _resetMenubar();
2981
+ this._resetMenubar();
2946
2982
  entry.classList.add( "selected" );
2947
2983
  entry.built = true;
2948
- create_submenu( item, key, entry, -1 );
2984
+ this._createSubmenu( item, key, entry, -1 );
2949
2985
  };
2950
2986
 
2951
2987
  entry.addEventListener("click", () => {
@@ -2976,7 +3012,7 @@ class Menubar {
2976
3012
  return;
2977
3013
  }
2978
3014
 
2979
- _resetMenubar();
3015
+ this._resetMenubar();
2980
3016
  });
2981
3017
  }
2982
3018
  }
@@ -3231,20 +3267,19 @@ class SideBar {
3231
3267
 
3232
3268
  /**
3233
3269
  * @param {Object} options
3234
- * inset: TODO
3235
- * filter: TODO
3270
+ * filter: Add search bar to filter entries [false]
3236
3271
  * skipHeader: Do not use sidebar header [false]
3237
3272
  * headerImg: Image to be shown as avatar
3238
3273
  * headerIcon: Icon to be shown as avatar (from LX.ICONS)
3239
- * headerTitle
3240
- * headerSubtitle
3274
+ * headerTitle: Header title
3275
+ * headerSubtitle: Header subtitle
3241
3276
  * skipFooter: Do not use sidebar footer [false]
3242
3277
  * footerImg: Image to be shown as avatar
3243
3278
  * footerIcon: Icon to be shown as avatar (from LX.ICONS)
3244
- * footerTitle
3245
- * footerSubtitle
3279
+ * footerTitle: Footer title
3280
+ * footerSubtitle: Footer subtitle
3246
3281
  * collapsable: Sidebar can toggle between collapsed/expanded [true]
3247
- * collapseToIcons: When Sidebar collapses, icons remains visible [true]
3282
+ * collapseToIcons: When Sidebar collapses, icons remains visible [true]
3248
3283
  * onHeaderPressed: Function to call when header is pressed
3249
3284
  * onFooterPressed: Function to call when footer is pressed
3250
3285
  */
@@ -3254,12 +3289,12 @@ class SideBar {
3254
3289
  this.root = document.createElement( 'div' );
3255
3290
  this.root.className = "lexsidebar";
3256
3291
 
3257
- window.sidebar = this;
3258
-
3259
3292
  this.collapsable = options.collapsable ?? true;
3260
- this.collapseWidth = ( options.collapseToIcons ?? true ) ? "58px" : "0px";
3293
+ this._collapseWidth = ( options.collapseToIcons ?? true ) ? "58px" : "0px";
3261
3294
  this.collapsed = false;
3262
3295
 
3296
+ this.filterString = "";
3297
+
3263
3298
  doAsync( () => {
3264
3299
 
3265
3300
  this.root.parentElement.ogWidth = this.root.parentElement.style.width;
@@ -3272,7 +3307,7 @@ class SideBar {
3272
3307
  }
3273
3308
  });
3274
3309
 
3275
- }, 100 );
3310
+ }, 10 );
3276
3311
 
3277
3312
  // This account for header, footer and all inner paddings
3278
3313
  let contentOffset = 32;
@@ -3342,6 +3377,19 @@ class SideBar {
3342
3377
  contentOffset += 52;
3343
3378
  }
3344
3379
 
3380
+ // Entry filter
3381
+ if( !( options.filter ?? false ) )
3382
+ {
3383
+ const panel = new Panel();
3384
+ panel.addText(null, "", (value, event) => {
3385
+ this.filterString = value;
3386
+ this.update();
3387
+ }, { placeholder: "Search...", icon: "fa-solid fa-magnifying-glass" });
3388
+ this.filter = panel.root.childNodes[ 0 ];
3389
+ this.root.appendChild( this.filter );
3390
+ contentOffset += 31;
3391
+ }
3392
+
3345
3393
  // Content
3346
3394
  {
3347
3395
  this.content = document.createElement( 'div' );
@@ -3409,21 +3457,22 @@ class SideBar {
3409
3457
 
3410
3458
  /**
3411
3459
  * @method toggleCollapsed
3460
+ * @param {Boolean} force: Force collapsed state
3412
3461
  */
3413
3462
 
3414
- toggleCollapsed() {
3463
+ toggleCollapsed( force ) {
3415
3464
 
3416
3465
  if( !this.collapsable )
3417
3466
  {
3418
3467
  return;
3419
3468
  }
3420
3469
 
3421
- this.collapsed = !this.collapsed;
3470
+ this.collapsed = force ?? !this.collapsed;
3422
3471
 
3423
3472
  if( this.collapsed )
3424
3473
  {
3425
3474
  this.root.classList.add( "collapsing" );
3426
- this.root.parentElement.style.width = this.collapseWidth;
3475
+ this.root.parentElement.style.width = this._collapseWidth;
3427
3476
  }
3428
3477
  else
3429
3478
  {
@@ -3432,6 +3481,11 @@ class SideBar {
3432
3481
  this.root.parentElement.style.width = this.root.parentElement.ogWidth;
3433
3482
  }
3434
3483
 
3484
+ if( !this.resizeObserver )
3485
+ {
3486
+ throw( "Wait until ResizeObserver has been created!" );
3487
+ }
3488
+
3435
3489
  this.resizeObserver.observe( this.root.parentElement );
3436
3490
 
3437
3491
  doAsync( () => {
@@ -3490,7 +3544,6 @@ class SideBar {
3490
3544
  const lastPath = tokens[tokens.length - 1];
3491
3545
  this.icons[ lastPath ] = options.icon;
3492
3546
 
3493
-
3494
3547
  let idx = 0;
3495
3548
 
3496
3549
  const _insertEntry = ( token, list ) => {
@@ -3545,10 +3598,19 @@ class SideBar {
3545
3598
  if( !entry )
3546
3599
  return;
3547
3600
 
3548
- entry.domEl.click();
3601
+ entry.dom.click();
3549
3602
  }
3550
3603
 
3551
- _build() {
3604
+ update() {
3605
+
3606
+ // Reset first
3607
+
3608
+ this.content.innerHTML = "";
3609
+
3610
+ for( let item of this.items )
3611
+ {
3612
+ delete item.dom;
3613
+ }
3552
3614
 
3553
3615
  for( let item of this.items )
3554
3616
  {
@@ -3561,12 +3623,18 @@ class SideBar {
3561
3623
  }
3562
3624
 
3563
3625
  let key = Object.keys( item )[ 0 ];
3626
+
3627
+ if( this.filterString.length && !key.toLowerCase().includes( this.filterString.toLowerCase() ) )
3628
+ {
3629
+ continue;
3630
+ }
3631
+
3564
3632
  let pKey = key.replace( /\s/g, '' ).replaceAll( '.', '' );
3565
3633
  let currentGroup = null;
3566
3634
 
3567
3635
  let entry = document.createElement( 'div' );
3568
3636
  entry.className = "lexsidebarentry " + ( options.className ?? "" );
3569
- entry.id = pKey;
3637
+ entry.id = item.name = pKey;
3570
3638
 
3571
3639
  if( item.group )
3572
3640
  {
@@ -3637,7 +3705,7 @@ class SideBar {
3637
3705
 
3638
3706
  let itemDom = document.createElement( 'div' );
3639
3707
  entry.appendChild( itemDom );
3640
- item.dom = itemDom;
3708
+ item.dom = entry;
3641
3709
 
3642
3710
  if( options.type == "checkbox" )
3643
3711
  {
@@ -3749,12 +3817,17 @@ class SideBar {
3749
3817
  this.content.appendChild( subentryContainer );
3750
3818
  }
3751
3819
 
3752
- for( var i = 0; i < item[ key ].length; ++i )
3820
+ for( let i = 0; i < item[ key ].length; ++i )
3753
3821
  {
3754
3822
  const subitem = item[ key ][ i ];
3755
3823
  const suboptions = subitem.options ?? {};
3756
3824
  const subkey = Object.keys( subitem )[ 0 ];
3757
3825
 
3826
+ if( this.filterString.length && !subkey.toLowerCase().includes( this.filterString.toLowerCase() ) )
3827
+ {
3828
+ continue;
3829
+ }
3830
+
3758
3831
  let subentry = document.createElement( 'div' );
3759
3832
  subentry.innerHTML = `<span>${ subkey }</span>`;
3760
3833
 
@@ -5285,7 +5358,7 @@ class Panel {
5285
5358
 
5286
5359
  var resolve = ( function( val, event ) {
5287
5360
 
5288
- if( !widget.valid() )
5361
+ if( !widget.valid() || ( this._lastValueTriggered == val ) )
5289
5362
  {
5290
5363
  return;
5291
5364
  }
@@ -5298,6 +5371,8 @@ class Panel {
5298
5371
  this._trigger( new IEvent( name, val, event ), callback );
5299
5372
  }
5300
5373
 
5374
+ this._lastValueTriggered = val;
5375
+
5301
5376
  }).bind( this );
5302
5377
 
5303
5378
  const trigger = options.trigger ?? 'default';
@@ -9563,7 +9638,7 @@ class ContextMenu {
9563
9638
  }
9564
9639
  }
9565
9640
 
9566
- _adjust_position( div, margin, useAbsolute = false ) {
9641
+ _adjustPosition( div, margin, useAbsolute = false ) {
9567
9642
 
9568
9643
  let rect = div.getBoundingClientRect();
9569
9644
 
@@ -9604,7 +9679,7 @@ class ContextMenu {
9604
9679
  }
9605
9680
  }
9606
9681
 
9607
- _create_submenu( o, k, c, d ) {
9682
+ _createSubmenu( o, k, c, d ) {
9608
9683
 
9609
9684
  this.root.querySelectorAll( ".lexcontextmenu" ).forEach( cm => cm.remove() );
9610
9685
 
@@ -9616,7 +9691,7 @@ class ContextMenu {
9616
9691
  {
9617
9692
  const subitem = o[ k ][ i ];
9618
9693
  const subkey = Object.keys( subitem )[ 0 ];
9619
- this._create_entry(subitem, subkey, contextmenu, d);
9694
+ this._createEntry(subitem, subkey, contextmenu, d);
9620
9695
  }
9621
9696
 
9622
9697
  var rect = c.getBoundingClientRect();
@@ -9624,10 +9699,10 @@ class ContextMenu {
9624
9699
  contextmenu.style.marginTop = 3.5 - c.offsetHeight + "px";
9625
9700
 
9626
9701
  // Set final width
9627
- this._adjust_position( contextmenu, 6, true );
9702
+ this._adjustPosition( contextmenu, 6, true );
9628
9703
  }
9629
9704
 
9630
- _create_entry( o, k, c, d ) {
9705
+ _createEntry( o, k, c, d ) {
9631
9706
 
9632
9707
  const hasSubmenu = o[ k ].length;
9633
9708
  let entry = document.createElement('div');
@@ -9672,7 +9747,7 @@ class ContextMenu {
9672
9747
  return;
9673
9748
 
9674
9749
  if( LX.OPEN_CONTEXTMENU_ENTRY == 'click' )
9675
- this._create_submenu( o, k, entry, ++d );
9750
+ this._createSubmenu( o, k, entry, ++d );
9676
9751
  });
9677
9752
 
9678
9753
  if( !hasSubmenu )
@@ -9688,7 +9763,7 @@ class ContextMenu {
9688
9763
  if(entry.built)
9689
9764
  return;
9690
9765
  entry.built = true;
9691
- this._create_submenu( o, k, entry, ++d );
9766
+ this._createSubmenu( o, k, entry, ++d );
9692
9767
  e.stopPropagation();
9693
9768
  });
9694
9769
  }
@@ -9700,7 +9775,7 @@ class ContextMenu {
9700
9775
  }
9701
9776
 
9702
9777
  onCreate() {
9703
- doAsync( () => this._adjust_position( this.root, 6 ) );
9778
+ doAsync( () => this._adjustPosition( this.root, 6 ) );
9704
9779
  }
9705
9780
 
9706
9781
  add( path, options = {} ) {
@@ -9730,13 +9805,13 @@ class ContextMenu {
9730
9805
 
9731
9806
  if( found )
9732
9807
  {
9733
- insert( tokens[idx++], found );
9808
+ insert( tokens[ idx++ ], found );
9734
9809
  }
9735
9810
  else
9736
9811
  {
9737
9812
  let item = {};
9738
9813
  item[ token ] = [];
9739
- const nextToken = tokens[idx++];
9814
+ const nextToken = tokens[ idx++ ];
9740
9815
  // Check if last token -> add callback
9741
9816
  if( !nextToken )
9742
9817
  {
@@ -9756,13 +9831,15 @@ class ContextMenu {
9756
9831
 
9757
9832
  const setParent = _item => {
9758
9833
 
9759
- let key = Object.keys(_item)[0];
9834
+ let key = Object.keys( _item )[ 0 ];
9760
9835
  let children = _item[ key ];
9761
9836
 
9762
- if(!children.length)
9837
+ if( !children.length )
9838
+ {
9763
9839
  return;
9840
+ }
9764
9841
 
9765
- if(children.find( c => Object.keys(c)[0] == key ) == null)
9842
+ if( children.find( c => Object.keys(c)[0] == key ) == null )
9766
9843
  {
9767
9844
  const parent = {};
9768
9845
  parent[ key ] = [];
@@ -9772,14 +9849,18 @@ class ContextMenu {
9772
9849
 
9773
9850
  for( var child of _item[ key ] )
9774
9851
  {
9775
- let k = Object.keys(child)[0];
9776
- for( var i = 0; i < child[k].length; ++i )
9777
- setParent(child);
9852
+ let k = Object.keys( child )[ 0 ];
9853
+ for( var i = 0; i < child[ k ].length; ++i )
9854
+ {
9855
+ setParent( child );
9856
+ }
9778
9857
  }
9779
9858
  };
9780
9859
 
9781
9860
  for( let item of this.items )
9782
- setParent(item);
9861
+ {
9862
+ setParent( item );
9863
+ }
9783
9864
 
9784
9865
  // Create elements
9785
9866
 
@@ -9789,9 +9870,11 @@ class ContextMenu {
9789
9870
  let pKey = "eId" + getSupportedDOMName( key );
9790
9871
 
9791
9872
  // Item already created
9792
- const id = "#" + (item.id ?? pKey);
9793
- if( !this.root.querySelector(id) )
9794
- this._create_entry(item, key, this.root, -1);
9873
+ const id = "#" + ( item.id ?? pKey );
9874
+ if( !this.root.querySelector( id ) )
9875
+ {
9876
+ this._createEntry( item, key, this.root, -1 );
9877
+ }
9795
9878
  }
9796
9879
  }
9797
9880