lexgui 0.6.11 → 0.7.0

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.
Files changed (46) hide show
  1. package/README.md +14 -9
  2. package/build/{components → extensions}/audio.js +11 -11
  3. package/build/{components → extensions}/codeeditor.js +109 -74
  4. package/build/{components → extensions}/docmaker.js +10 -3
  5. package/build/{components → extensions}/imui.js +19 -12
  6. package/build/{components → extensions}/nodegraph.js +1 -1
  7. package/build/{components → extensions}/timeline.js +150 -94
  8. package/build/{components → extensions}/videoeditor.js +1 -1
  9. package/build/lexgui-docs.css +9 -9
  10. package/build/lexgui.css +489 -223
  11. package/build/lexgui.js +1771 -777
  12. package/build/lexgui.min.css +2 -2
  13. package/build/lexgui.min.js +1 -1
  14. package/build/lexgui.module.js +1803 -809
  15. package/build/lexgui.module.min.js +1 -1
  16. package/changelog.md +90 -21
  17. package/demo.js +52 -32
  18. package/examples/{all_widgets.html → all-components.html} +22 -4
  19. package/examples/{area_tabs.html → area-tabs.html} +3 -3
  20. package/examples/{asset_view.html → asset-view.html} +3 -3
  21. package/examples/{code_editor.html → code-editor.html} +4 -4
  22. package/examples/dialogs.html +3 -3
  23. package/examples/editor.html +27 -18
  24. package/examples/{immediate_ui.html → immediate-ui.html} +3 -3
  25. package/examples/index.html +8 -8
  26. package/examples/{node_graph.html → node-graph.html} +3 -3
  27. package/examples/previews/all-components.png +0 -0
  28. package/examples/previews/area-tabs.png +0 -0
  29. package/examples/previews/asset-view.png +0 -0
  30. package/examples/previews/code-editor.png +0 -0
  31. package/examples/previews/dialogs.png +0 -0
  32. package/examples/previews/editor.png +0 -0
  33. package/examples/previews/node-graph.png +0 -0
  34. package/examples/previews/side-bar.png +0 -0
  35. package/examples/previews/timeline.png +0 -0
  36. package/examples/{side_bar.html → side-bar.html} +3 -3
  37. package/examples/timeline.html +5 -5
  38. package/examples/{video_editor.html → video-editor.html} +3 -3
  39. package/examples/{video_editor2.html → video-editor2.html} +3 -3
  40. package/package.json +2 -2
  41. package/examples/previews/all_widgets.png +0 -0
  42. package/examples/previews/area_tabs.png +0 -0
  43. package/examples/previews/asset_view.png +0 -0
  44. package/examples/previews/code_editor.png +0 -0
  45. package/examples/previews/node_graph.png +0 -0
  46. package/examples/previews/side_bar.png +0 -0
package/README.md CHANGED
@@ -1,6 +1,9 @@
1
+ ![logo](./images/icon_dark.png#gh-dark-mode-only)
2
+ ![logo](./images/icon_light.png#gh-light-mode-only)
3
+
1
4
  # lexgui.js
2
5
 
3
- **lexgui.js** is a lightweight JavaScript library for building web interfaces. No bloated frameworks, no unnecessary complexity, just pure HTML, CSS, and JavaScript magic. It gives you an easy API for crafting dynamic, interactive editor interfaces without the headache of big libraries.
6
+ **lexgui.js** is a lightweight JavaScript library for building web user interfaces. No bloated frameworks, no unnecessary complexity, just pure HTML, CSS, and JavaScript magic. It gives you an easy API for crafting dynamic, interactive editor interfaces without the headache of big libraries.
4
7
 
5
8
  NPM Package: [npmjs.com/package/lexgui](https://www.npmjs.com/package/lexgui)
6
9
 
@@ -18,12 +21,14 @@ NPM Package: [npmjs.com/package/lexgui](https://www.npmjs.com/package/lexgui)
18
21
  </tr>
19
22
  </table>
20
23
 
21
- ## Component Features
24
+ ## Extension Features
22
25
 
23
- - [x] Asset Browser
24
26
  - [x] Timeline
25
27
  - [x] Code Editor
26
- - [ ] Node Graphs (WIP)
28
+ - [x] Docs Maker
29
+ - [ ] Node Graph Editor (WIP)
30
+ - [ ] Video Editor (WIP)
31
+ - [ ] Immediate UI (WIP)
27
32
 
28
33
  ## Quick start
29
34
 
@@ -40,7 +45,7 @@ NPM Package: [npmjs.com/package/lexgui](https://www.npmjs.com/package/lexgui)
40
45
  {
41
46
  "imports": {
42
47
  "lexgui": "https://cdn.skypack.dev/lexgui@^<version>/build/lexgui.module.js",
43
- "lexgui/components/": "https://cdn.skypack.dev/lexgui@^<version>/build/components/"
48
+ "lexgui/extensions/": "https://cdn.skypack.dev/lexgui@^<version>/build/extensions/"
44
49
  }
45
50
  }
46
51
  </script>
@@ -56,8 +61,8 @@ NPM Package: [npmjs.com/package/lexgui](https://www.npmjs.com/package/lexgui)
56
61
  ```js
57
62
  import { LX } from 'lexgui';
58
63
 
59
- // Using components
60
- import { CodeEditor } from 'lexgui/components/codeeditor.js';
64
+ // Using extensions
65
+ import { CodeEditor } from 'lexgui/extensions/codeeditor.js';
61
66
 
62
67
  // Create main area
63
68
  let area = await LX.init();
@@ -68,7 +73,7 @@ let panel = area.addPanel();
68
73
  // Start branch/section
69
74
  panel.branch("Section Title");
70
75
 
71
- // Add some widgets
76
+ // Add some components
72
77
  panel.addButton(null, "Click me, Im Full Width...");
73
78
  panel.addText("Text", "Warning text", null, { warning: true });
74
79
 
@@ -82,7 +87,7 @@ The library documentation is available at [lexgui.js/docs/](https://jxarco.githu
82
87
 
83
88
  ## Examples
84
89
 
85
- Look at this [examples](https://jxarco.github.io/lexgui.js/examples/) to see how to create the different widgets and components!
90
+ Look at this [examples](https://jxarco.github.io/lexgui.js/examples/) to see how to create the different components and integrate the extensions!
86
91
 
87
92
  ## Projects using lexgui.js
88
93
 
@@ -5,14 +5,14 @@ if( !LX )
5
5
  throw("lexgui.js missing!");
6
6
  }
7
7
 
8
- LX.components.push( 'Audio' );
8
+ LX.extensions.push( 'Audio' );
9
9
 
10
10
  /**
11
11
  * @class Knob
12
- * @description Knob Widget
12
+ * @description Knob Component
13
13
  */
14
14
 
15
- class Knob extends LX.Widget {
15
+ class Knob extends LX.BaseComponent {
16
16
 
17
17
  constructor( name, value, min, max, callback, options = {} ) {
18
18
 
@@ -22,7 +22,7 @@ class Knob extends LX.Widget {
22
22
  value = options.precision ? LX.round( value, options.precision ) : value;
23
23
  }
24
24
 
25
- super( LX.Widget.KNOB, name, value, options );
25
+ super( LX.BaseComponent.KNOB, name, value, options );
26
26
 
27
27
  this.onGetValue = () => {
28
28
  return innerKnobCircle.value;
@@ -30,7 +30,7 @@ class Knob extends LX.Widget {
30
30
 
31
31
  this.onSetValue = ( newValue, skipCallback ) => {
32
32
  innerSetValue( newValue );
33
- LX.Widget._dispatchEvent( innerKnobCircle, "change", skipCallback );
33
+ LX.BaseComponent._dispatchEvent( innerKnobCircle, "change", skipCallback );
34
34
  };
35
35
 
36
36
  this.onResize = ( rect ) => {
@@ -107,7 +107,7 @@ class Knob extends LX.Widget {
107
107
  // Reset button (default value)
108
108
  if( !skipCallback )
109
109
  {
110
- let btn = this.root.querySelector( ".lexwidgetname .lexicon" );
110
+ let btn = this.root.querySelector( ".lexcomponentname .lexicon" );
111
111
  if( btn ) btn.style.display = val != innerKnobCircle.iValue ? "block": "none";
112
112
 
113
113
  if( !( snapEnabled && !mustSnap ) )
@@ -157,7 +157,7 @@ class Knob extends LX.Widget {
157
157
  else if(e.altKey) mult *= 0.1;
158
158
  let new_value = (innerKnobCircle.value - mult * dt);
159
159
  innerKnobCircle.value = new_value;
160
- LX.Widget._dispatchEvent( innerKnobCircle, 'change' );
160
+ LX.BaseComponent._dispatchEvent( innerKnobCircle, 'change' );
161
161
  }
162
162
 
163
163
  e.stopPropagation();
@@ -175,7 +175,7 @@ class Knob extends LX.Widget {
175
175
  if( snapEnabled )
176
176
  {
177
177
  mustSnap = true;
178
- LX.Widget._dispatchEvent( innerKnobCircle, 'change' );
178
+ LX.BaseComponent._dispatchEvent( innerKnobCircle, 'change' );
179
179
  }
180
180
 
181
181
  if( document.pointerLockElement )
@@ -196,7 +196,7 @@ LX.Knob = Knob;
196
196
 
197
197
  /**
198
198
  * @method addKnob
199
- * @param {String} name Widget name
199
+ * @param {String} name Component name
200
200
  * @param {Number} value Knob value
201
201
  * @param {Number} min Min Knob value
202
202
  * @param {Number} max Max Knob value
@@ -207,6 +207,6 @@ LX.Knob = Knob;
207
207
  */
208
208
 
209
209
  LX.Panel.prototype.addKnob = function( name, value, min, max, callback, options = {} ) {
210
- const widget = new Knob( name, value, min, max, callback, options );
211
- return this._attachWidget( widget );
210
+ const component = new Knob( name, value, min, max, callback, options );
211
+ return this._attachComponent( component );
212
212
  }
@@ -4,7 +4,7 @@ if(!LX) {
4
4
  throw("lexgui.js missing!");
5
5
  }
6
6
 
7
- LX.components.push( 'CodeEditor' );
7
+ LX.extensions.push( 'CodeEditor' );
8
8
 
9
9
  function swapElements( obj, a, b ) {
10
10
  [obj[a], obj[b]] = [obj[b], obj[a]];
@@ -458,7 +458,7 @@ class CodeEditor {
458
458
  const dX = ( e.deltaY > 0.0 ? 10.0 : -10.0 ) * ( e.shiftKey ? 1.0 : 0.0 );
459
459
  if( dX != 0.0 ) this.setScrollBarValue( 'horizontal', dX );
460
460
  }
461
- });
461
+ }, { passive: true });
462
462
  }
463
463
  }
464
464
 
@@ -822,8 +822,9 @@ class CodeEditor {
822
822
  this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
823
823
  this.cursorToString( cursor, this.code.lines[ ln ] );
824
824
 
825
- const last_char = ( this.code.clientWidth / this.charWidth )|0;
826
- this.setScrollLeft( cursor.position >= last_char ? ( cursor.position - last_char ) * this.charWidth : 0 );
825
+ var viewportSizeX = ( this.codeScroller.clientWidth + this.getScrollLeft() ) - CodeEditor.LINE_GUTTER_WIDTH; // Gutter offset
826
+ if( ( cursor.position * this.charWidth ) >= viewportSizeX )
827
+ this.setScrollLeft( this.code.lines[ ln ].length * this.charWidth );
827
828
 
828
829
  // Merge cursors
829
830
  this.mergeCursors( ln );
@@ -1097,39 +1098,50 @@ class CodeEditor {
1097
1098
  this.loadedTabs = { };
1098
1099
  this.openedTabs = { };
1099
1100
 
1101
+ const onLoadAll = () => {
1102
+ // Create inspector panel when the initial state is complete
1103
+ // and we have at least 1 tab opened
1104
+ this.infoPanel = this._createInfoPanel();
1105
+ if( this.infoPanel )
1106
+ {
1107
+ area.attach( this.infoPanel );
1108
+ }
1109
+
1110
+ // Wait until the fonts are all loaded
1111
+ document.fonts.ready.then(() => {
1112
+ this.charWidth = this._measureChar( "a", true );
1113
+ });
1114
+
1115
+ window.editor = this;
1116
+ };
1117
+
1100
1118
  if( options.allowAddScripts ?? true )
1101
1119
  {
1102
1120
  this.addTab("+", false, "New File");
1103
1121
  }
1104
1122
 
1105
- this.addTab( options.name || "untitled", true, options.title, { language: options.highlight ?? "Plain Text" } );
1106
-
1107
- // Create inspector panel
1108
- let panel = this._createPanelInfo();
1109
- if( panel ) area.attach( panel );
1123
+ if( options.files )
1124
+ {
1125
+ console.assert( options.files.constructor === Array, "_files_ must be an Array!" );
1126
+ const numFiles = options.files.length;
1127
+ let filesLoaded = 0;
1110
1128
 
1111
- const fontUrl = "https://raw.githubusercontent.com/jxarco/lexgui.js/master/" + "/data/CommitMono-400-Regular.otf";
1112
- const commitMono = new FontFace(
1113
- "CommitMono",
1114
- `url(${ fontUrl })`,
1129
+ for( let url of options.files )
1115
1130
  {
1116
- style: "normal",
1117
- weight: "400",
1118
- display: "swap"
1131
+ this.loadFile( url, { callback: () => {
1132
+ filesLoaded++;
1133
+ if( filesLoaded == numFiles )
1134
+ {
1135
+ onLoadAll();
1136
+ }
1137
+ }});
1119
1138
  }
1120
- );
1121
-
1122
- // Add to the document.fonts (FontFaceSet)
1123
- document.fonts.add( commitMono );
1124
-
1125
- // Load the font
1126
- commitMono.load();
1127
-
1128
- // Wait until the fonts are all loaded
1129
- document.fonts.ready.then(() => {
1130
- // console.log("commitMono loaded")
1131
- this.charWidth = this._measureChar( "a", true );
1132
- });
1139
+ }
1140
+ else
1141
+ {
1142
+ this.addTab( options.name || "untitled", true, options.title, { language: options.highlight ?? "Plain Text" } );
1143
+ onLoadAll();
1144
+ }
1133
1145
  }
1134
1146
 
1135
1147
  static getInstances()
@@ -1287,6 +1299,11 @@ class CodeEditor {
1287
1299
  this._changeLanguageFromExtension( LX.getExtension( name ) );
1288
1300
  }
1289
1301
  }
1302
+
1303
+ if( options.callback )
1304
+ {
1305
+ options.callback( text );
1306
+ }
1290
1307
  };
1291
1308
 
1292
1309
  if( file.constructor == String )
@@ -1560,7 +1577,7 @@ class CodeEditor {
1560
1577
  this._changeLanguage( 'Plain Text' );
1561
1578
  }
1562
1579
 
1563
- _createPanelInfo() {
1580
+ _createInfoPanel() {
1564
1581
 
1565
1582
  if( !this.skipInfo )
1566
1583
  {
@@ -2859,18 +2876,18 @@ class CodeEditor {
2859
2876
  }
2860
2877
 
2861
2878
  const lang = this.languages[ this.highlight ];
2862
- const local_line_num = this.toLocalLine( linenum );
2863
- const gutter_line = "<span class='line-gutter'>" + (linenum + 1) + "</span>";
2879
+ const localLineNum = this.toLocalLine( linenum );
2880
+ const gutterLineHtml = "<span class='line-gutter'>" + (linenum + 1) + "</span>";
2864
2881
 
2865
2882
  const UPDATE_LINE = ( html ) => {
2866
2883
  if( !force ) // Single line update
2867
2884
  {
2868
- this.code.childNodes[ local_line_num ].innerHTML = gutter_line + html;
2885
+ this.code.childNodes[ localLineNum ].innerHTML = gutterLineHtml + html;
2869
2886
  this._setActiveLine( linenum );
2870
2887
  this._clearTmpVariables();
2871
2888
  }
2872
2889
  else // Update all lines at once
2873
- return "<pre>" + ( gutter_line + html ) + "</pre>";
2890
+ return "<pre>" + ( gutterLineHtml + html ) + "</pre>";
2874
2891
  }
2875
2892
 
2876
2893
  // multi-line strings not supported by now
@@ -2883,14 +2900,15 @@ class CodeEditor {
2883
2900
  // Single line
2884
2901
  if( !force )
2885
2902
  {
2886
- LX.deleteElement( this.code.childNodes[ local_line_num ] );
2887
- this.code.insertChildAtIndex( document.createElement( 'pre' ), local_line_num );
2903
+ LX.deleteElement( this.code.childNodes[ localLineNum ] );
2904
+ this.code.insertChildAtIndex( document.createElement( 'pre' ), localLineNum );
2888
2905
  }
2889
2906
 
2890
2907
  // Early out check for no highlighting languages
2891
2908
  if( this.highlight == 'Plain Text' )
2892
2909
  {
2893
- return UPDATE_LINE( linestring );
2910
+ const plainTextHtml = linestring.replaceAll('<', '&lt;').replaceAll('>', '&gt;');
2911
+ return UPDATE_LINE( plainTextHtml );
2894
2912
  }
2895
2913
 
2896
2914
  this._currentLineNumber = linenum;
@@ -2899,9 +2917,11 @@ class CodeEditor {
2899
2917
  const tokensToEvaluate = this._getTokensFromLine( linestring );
2900
2918
 
2901
2919
  if( !tokensToEvaluate.length )
2902
- return "<pre><span class='line-gutter'>" + linenum + "</span></pre>";
2920
+ {
2921
+ return "<pre><span class='line-gutter'>" + linenum + "</span></pre>";
2922
+ }
2903
2923
 
2904
- var line_inner_html = "";
2924
+ var lineInnerHtml = "";
2905
2925
 
2906
2926
  // Process all tokens
2907
2927
  for( var i = 0; i < tokensToEvaluate.length; ++i )
@@ -2929,7 +2949,7 @@ class CodeEditor {
2929
2949
  this._buildingBlockComment = linenum;
2930
2950
  }
2931
2951
 
2932
- line_inner_html += this._evaluateToken( {
2952
+ lineInnerHtml += this._evaluateToken( {
2933
2953
  token: token,
2934
2954
  prev: prev,
2935
2955
  prevWithSpaces: tokensToEvaluate[ i - 1 ],
@@ -2942,7 +2962,7 @@ class CodeEditor {
2942
2962
  } );
2943
2963
  }
2944
2964
 
2945
- return UPDATE_LINE( line_inner_html );
2965
+ return UPDATE_LINE( lineInnerHtml );
2946
2966
  }
2947
2967
 
2948
2968
  _lineHasComment( linestring ) {
@@ -3564,12 +3584,12 @@ class CodeEditor {
3564
3584
  this.restartBlink();
3565
3585
 
3566
3586
  // Add horizontal scroll
3567
-
3568
- doAsync(() => {
3569
- var viewportSizeX = ( this.codeScroller.clientWidth + this.getScrollLeft() ) - CodeEditor.LINE_GUTTER_WIDTH; // Gutter offset
3570
- if( (cursor.position * this.charWidth) >= viewportSizeX )
3571
- this.setScrollLeft( this.getScrollLeft() + this.charWidth );
3572
- });
3587
+ const currentScrollLeft = this.getScrollLeft();
3588
+ var viewportSizeX = ( this.codeScroller.clientWidth + currentScrollLeft ) - CodeEditor.LINE_GUTTER_WIDTH; // Gutter offset
3589
+ if( (cursor.position * this.charWidth) >= viewportSizeX )
3590
+ {
3591
+ this.setScrollLeft( currentScrollLeft + this.charWidth );
3592
+ }
3573
3593
  }
3574
3594
 
3575
3595
  cursorToLeft( key, cursor ) {
@@ -3585,11 +3605,12 @@ class CodeEditor {
3585
3605
 
3586
3606
  // Add horizontal scroll
3587
3607
 
3588
- doAsync(() => {
3589
- var viewportSizeX = this.getScrollLeft(); // Gutter offset
3590
- if( ( ( cursor.position - 1 ) * this.charWidth ) < viewportSizeX )
3591
- this.setScrollLeft( this.getScrollLeft() - this.charWidth );
3592
- });
3608
+ const currentScrollLeft = this.getScrollLeft();
3609
+ var viewportSizeX = currentScrollLeft; // Gutter offset
3610
+ if( ( ( cursor.position - 1 ) * this.charWidth ) < viewportSizeX )
3611
+ {
3612
+ this.setScrollLeft( currentScrollLeft - this.charWidth );
3613
+ }
3593
3614
  }
3594
3615
 
3595
3616
  cursorToTop( cursor, resetLeft = false ) {
@@ -3600,30 +3621,40 @@ class CodeEditor {
3600
3621
  this.restartBlink();
3601
3622
 
3602
3623
  if( resetLeft )
3624
+ {
3603
3625
  this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
3626
+ }
3604
3627
 
3605
- doAsync(() => {
3606
- var first_line = ( this.getScrollTop() / this.lineHeight )|0;
3607
- if( (cursor.line - 1) < first_line )
3608
- this.setScrollTop( this.getScrollTop() - this.lineHeight );
3609
- });
3628
+ const currentScrollTop = this.getScrollTop();
3629
+ var firstLine = ( currentScrollTop / this.lineHeight )|0;
3630
+ if( (cursor.line - 1) < firstLine )
3631
+ {
3632
+ this.setScrollTop( currentScrollTop - this.lineHeight );
3633
+ }
3610
3634
  }
3611
3635
 
3612
3636
  cursorToBottom( cursor, resetLeft = false ) {
3613
3637
 
3614
3638
  cursor._top += this.lineHeight;
3615
- cursor.style.top = "calc(" + cursor._top + "px)";
3639
+ cursor.style.top = `calc(${ cursor._top }px)`;
3616
3640
 
3617
3641
  this.restartBlink();
3618
3642
 
3619
3643
  if( resetLeft )
3644
+ {
3620
3645
  this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
3646
+ }
3621
3647
 
3622
- doAsync(() => {
3623
- var last_line = ( ( this.codeScroller.offsetHeight + this.getScrollTop() ) / this.lineHeight )|0;
3624
- if( cursor.line >= last_line )
3625
- this.setScrollTop( this.getScrollTop() + this.lineHeight );
3626
- });
3648
+ const currentScrollTop = this.getScrollTop();
3649
+ const tabsHeight = this.tabs.root.getBoundingClientRect().height;
3650
+ const infoPanelHeight = this.skipInfo ? 0 : this.infoPanel.root.getBoundingClientRect().height;
3651
+ const scrollerHeight = this.codeScroller.offsetHeight;
3652
+
3653
+ var lastLine = ( ( scrollerHeight - tabsHeight - infoPanelHeight + currentScrollTop ) / this.lineHeight )|0;
3654
+ if( cursor.line >= lastLine )
3655
+ {
3656
+ this.setScrollTop( currentScrollTop + this.lineHeight );
3657
+ }
3627
3658
  }
3628
3659
 
3629
3660
  cursorToString( cursor, text, reverse ) {
@@ -3632,7 +3663,9 @@ class CodeEditor {
3632
3663
  return;
3633
3664
 
3634
3665
  for( let char of text )
3666
+ {
3635
3667
  reverse ? this.cursorToLeft( char, cursor ) : this.cursorToRight( char, cursor );
3668
+ }
3636
3669
  }
3637
3670
 
3638
3671
  cursorToPosition( cursor, position ) {
@@ -3756,9 +3789,8 @@ class CodeEditor {
3756
3789
  }
3757
3790
 
3758
3791
  _removeSpaces( cursor ) {
3759
-
3760
- // Remove indentation
3761
3792
  const lidx = cursor.line;
3793
+ // Remove indentation
3762
3794
  let lineStart = firstNonspaceIndex( this.code.lines[ lidx ] );
3763
3795
 
3764
3796
  // Nothing to remove... we are at the start of the line
@@ -3792,29 +3824,29 @@ class CodeEditor {
3792
3824
  }
3793
3825
 
3794
3826
  getScrollLeft() {
3795
-
3796
3827
  if( !this.codeScroller ) return 0;
3797
3828
  return this.codeScroller.scrollLeft;
3798
3829
  }
3799
3830
 
3800
3831
  getScrollTop() {
3801
-
3802
3832
  if( !this.codeScroller ) return 0;
3803
3833
  return this.codeScroller.scrollTop;
3804
3834
  }
3805
3835
 
3806
3836
  setScrollLeft( value ) {
3807
-
3808
3837
  if( !this.codeScroller ) return;
3809
- this.codeScroller.scrollLeft = value;
3810
- this.setScrollBarValue( 'horizontal', 0 );
3838
+ doAsync( () => {
3839
+ this.codeScroller.scrollLeft = value;
3840
+ this.setScrollBarValue( 'horizontal', 0 );
3841
+ }, 20 );
3811
3842
  }
3812
3843
 
3813
3844
  setScrollTop( value ) {
3814
-
3815
3845
  if( !this.codeScroller ) return;
3816
- this.codeScroller.scrollTop = value;
3817
- this.setScrollBarValue( 'vertical' );
3846
+ doAsync( () => {
3847
+ this.codeScroller.scrollTop = value;
3848
+ this.setScrollBarValue( 'vertical' );
3849
+ }, 20 );
3818
3850
  }
3819
3851
 
3820
3852
  resize( pMaxLength, onResize ) {
@@ -3824,12 +3856,15 @@ class CodeEditor {
3824
3856
  // Update max viewport
3825
3857
  const maxLineLength = pMaxLength ?? this.getMaxLineLength();
3826
3858
  const scrollWidth = maxLineLength * this.charWidth + CodeEditor.LINE_GUTTER_WIDTH;
3859
+
3860
+ const tabsHeight = this.tabs.root.getBoundingClientRect().height;
3861
+ const infoPanelHeight = this.skipInfo ? 0 : this.infoPanel.root.getBoundingClientRect().height;
3827
3862
  const scrollHeight = this.code.lines.length * this.lineHeight;
3828
3863
 
3829
3864
  this._lastMaxLineLength = maxLineLength;
3830
3865
 
3831
3866
  this.codeSizer.style.minWidth = scrollWidth + "px";
3832
- this.codeSizer.style.minHeight = scrollHeight + "px";
3867
+ this.codeSizer.style.minHeight = ( scrollHeight + tabsHeight + infoPanelHeight ) + "px";
3833
3868
 
3834
3869
  this.resizeScrollBars();
3835
3870
 
@@ -190,19 +190,24 @@ function MAKE_CODE( text, language = "js" )
190
190
 
191
191
  window.MAKE_CODE = MAKE_CODE;
192
192
 
193
- function MAKE_LIST( list, type )
193
+ function MAKE_LIST( list, type, target )
194
194
  {
195
195
  const validTypes = [ 'bullet', 'numbered' ];
196
196
  console.assert( list && list.length > 0 && validTypes.includes(type), "Invalid list type or empty list" + type );
197
197
  const typeString = type == 'bullet' ? 'ul' : 'ol';
198
198
  let ul = document.createElement( typeString );
199
+ target = target ?? mainContainer;
200
+ target.appendChild( ul );
199
201
  for( var el of list ) {
202
+ if( el.constructor === Array ) {
203
+ MAKE_LIST( el, type, ul );
204
+ return;
205
+ }
200
206
  let li = document.createElement( 'li' );
201
207
  li.className = "leading-loose";
202
208
  li.innerHTML = el;
203
209
  ul.appendChild( li );
204
210
  }
205
- mainContainer.appendChild( ul );
206
211
  }
207
212
 
208
213
  function MAKE_BULLET_LIST( list )
@@ -316,7 +321,9 @@ window.INLINE_LINK = INLINE_LINK;
316
321
  function INLINE_PAGE( string, page )
317
322
  {
318
323
  console.assert(string && page);
319
- return `<a onclick="loadPage('` + page + `')">` + string + `</a>`;
324
+ const startPage = page.replace(".html", "");
325
+ const tabName = window.setPath( startPage );
326
+ return `<a onclick="loadPage('${ page }', true, '${ tabName }')">${ string }</a>`;
320
327
  }
321
328
 
322
329
  window.INLINE_PAGE = INLINE_PAGE;
@@ -4,7 +4,7 @@ if(!LX) {
4
4
  throw("lexgui.js missing!");
5
5
  }
6
6
 
7
- LX.components.push( 'ImUI' );
7
+ LX.extensions.push( 'ImUI' );
8
8
 
9
9
  function swapElements (obj, a, b) {
10
10
  [obj[a], obj[b]] = [obj[b], obj[a]];
@@ -49,9 +49,9 @@ class ImUI {
49
49
  // },
50
50
  // );
51
51
 
52
- // Widgets
52
+ // Components
53
53
 
54
- this.widgets = { };
54
+ this.components = { };
55
55
 
56
56
  // Mouse state
57
57
 
@@ -157,10 +157,10 @@ class ImUI {
157
157
 
158
158
  // Store slider value
159
159
 
160
- if(!this.widgets[ text ])
161
- this.widgets[ text ] = { value: value };
160
+ if(!this.components[ text ])
161
+ this.components[ text ] = { value: value };
162
162
  else
163
- value = this.widgets[ text ].value;
163
+ value = this.components[ text ].value;
164
164
 
165
165
  // Element properties
166
166
 
@@ -196,7 +196,7 @@ class ImUI {
196
196
  if(active)
197
197
  {
198
198
  value = LX.clamp((this.mousePosition.x - min) / (max - min), 0.0, 1.0);
199
- this.widgets[ text ].value = value;
199
+ this.components[ text ].value = value;
200
200
  }
201
201
 
202
202
  let valueSize = new LX.vec2( fullSize.x * value, size.y );
@@ -240,10 +240,14 @@ class ImUI {
240
240
 
241
241
  // Store slider value
242
242
 
243
- if(!this.widgets[ text ])
244
- this.widgets[ text ] = { value: value };
243
+ if( !this.components[ text ] )
244
+ {
245
+ this.components[ text ] = { value: value };
246
+ }
245
247
  else
246
- value = this.widgets[ text ].value;
248
+ {
249
+ value = this.components[ text ].value;
250
+ }
247
251
 
248
252
  // Element properties
249
253
 
@@ -280,8 +284,11 @@ class ImUI {
280
284
  if( pressed )
281
285
  {
282
286
  value = !value;
283
- this.widgets[ text ].value = value;
284
- if(callback) callback( value );
287
+ this.components[ text ].value = value;
288
+ if( callback )
289
+ {
290
+ callback( value );
291
+ }
285
292
  }
286
293
 
287
294
  ctx.fillStyle = value ? (active ? "#ddd" : (hovered ? "#6074e7" : "#3e57e4")) :
@@ -4,7 +4,7 @@ if(!LX) {
4
4
  throw("lexgui.js missing!");
5
5
  }
6
6
 
7
- LX.components.push( 'GraphEditor' );
7
+ LX.extensions.push( 'GraphEditor' );
8
8
 
9
9
  class BoundingBox {
10
10