lexgui 8.1.2 → 8.2.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.
Files changed (65) hide show
  1. package/build/components/AlertDialog.d.ts +7 -7
  2. package/build/components/Avatar.d.ts +15 -0
  3. package/build/components/Counter.d.ts +9 -9
  4. package/build/components/Dialog.d.ts +20 -20
  5. package/build/components/Footer.d.ts +14 -14
  6. package/build/components/Menubar.d.ts +59 -59
  7. package/build/components/NodeTree.d.ts +26 -1
  8. package/build/components/Vector.d.ts +1 -0
  9. package/build/core/Area.d.ts +143 -143
  10. package/build/core/Event.d.ts +0 -20
  11. package/build/core/Namespace.js +1 -1
  12. package/build/core/Namespace.js.map +1 -1
  13. package/build/core/Panel.d.ts +538 -538
  14. package/build/extensions/AssetView.d.ts +137 -136
  15. package/build/extensions/AssetView.js +193 -155
  16. package/build/extensions/AssetView.js.map +1 -1
  17. package/build/extensions/Audio.js +163 -163
  18. package/build/extensions/Audio.js.map +1 -1
  19. package/build/extensions/CodeEditor.d.ts +358 -350
  20. package/build/extensions/CodeEditor.js +302 -270
  21. package/build/extensions/CodeEditor.js.map +1 -1
  22. package/build/extensions/DocMaker.d.ts +27 -27
  23. package/build/extensions/DocMaker.js +15 -11
  24. package/build/extensions/DocMaker.js.map +1 -1
  25. package/build/extensions/GraphEditor.js +2754 -2760
  26. package/build/extensions/GraphEditor.js.map +1 -1
  27. package/build/extensions/ImUi.js +227 -227
  28. package/build/extensions/Timeline.d.ts +668 -670
  29. package/build/extensions/Timeline.js +71 -79
  30. package/build/extensions/Timeline.js.map +1 -1
  31. package/build/extensions/VideoEditor.d.ts +38 -16
  32. package/build/extensions/VideoEditor.js +294 -180
  33. package/build/extensions/VideoEditor.js.map +1 -1
  34. package/build/extensions/index.d.ts +8 -8
  35. package/build/extensions/index.js +10 -10
  36. package/build/index.all.d.ts +2 -2
  37. package/build/index.css.d.ts +3 -4
  38. package/build/index.d.ts +57 -56
  39. package/build/lexgui.all.js +1877 -1520
  40. package/build/lexgui.all.js.map +1 -1
  41. package/build/lexgui.all.min.js +1 -1
  42. package/build/lexgui.all.module.js +1875 -1516
  43. package/build/lexgui.all.module.js.map +1 -1
  44. package/build/lexgui.all.module.min.js +1 -1
  45. package/build/lexgui.css +6123 -5556
  46. package/build/lexgui.js +997 -814
  47. package/build/lexgui.js.map +1 -1
  48. package/build/lexgui.min.css +2 -3
  49. package/build/lexgui.min.js +1 -1
  50. package/build/lexgui.module.js +995 -810
  51. package/build/lexgui.module.js.map +1 -1
  52. package/build/lexgui.module.min.js +1 -1
  53. package/changelog.md +65 -2
  54. package/demo.js +167 -65
  55. package/examples/all-components.html +40 -55
  56. package/examples/asset-view.html +27 -0
  57. package/examples/code-editor.html +12 -1
  58. package/examples/dialogs.html +13 -2
  59. package/examples/editor.html +9 -49
  60. package/examples/index.html +2 -2
  61. package/examples/side-bar.html +1 -1
  62. package/examples/timeline.html +2 -2
  63. package/examples/video-editor.html +1 -1
  64. package/examples/video-editor2.html +2 -2
  65. package/package.json +7 -4
package/changelog.md CHANGED
@@ -2,7 +2,70 @@
2
2
 
3
3
  ## dev
4
4
 
5
- ## 8.1.2 (master)
5
+ ## 8.2.1 (master)
6
+
7
+ CodeEditor:
8
+ - Fixed `options.onReady` being called before processing lines.
9
+
10
+ VideoEditor:
11
+ - Added `resizeVideo` and `resizeControls` to update area sizes. They are automatically called by resize.
12
+ - Added `controlsComponents` object that holds all buttons and timebar that will be displayed in the ui.
13
+ - Added a `createControls` function to create different button layouts. It creates all buttons and then calls a layout creator.
14
+ - Added `createControlsLayout_0` and `createControlsLayout_1` with default button layouts.
15
+ - Added `controlsLayout` option to specify which default layout to use or a custom function.
16
+ - Added `onChangeState` and `onChangeLoop` user callbacks.
17
+ - Added `resetCropBtn`. Only displayed in layout 1.
18
+
19
+ VideoEditor TimeBar:
20
+ - `mousemove` and `mouseup` events now attach to document instead of canvas, for a better user experience.
21
+ - Added `unbind` function to remove document events.
22
+ - Fixed marker grabbing when both overlapped.
23
+
24
+ Expose DocMaker extension in LX.
25
+ Minor CSS fixes.
26
+
27
+ ## 8.2
28
+
29
+ Use Tailwind (finally) to create and compile CSS.
30
+ Removed custom utility classes, refactored theme colors and every theme variable used in LX.
31
+ Cleaned main CSS, move generic component styling to classes.
32
+ Added `LX.twMerge` and `LX.mergeClass` to merge elements class names and allow overrides.
33
+ `LX.getCSSVariable` (old `LX.getThemeColor`) now allows to get colors with applied alpha using `color/{alpha}`.
34
+ Added new Avatar Component (see docs).
35
+ Added support for primary color theming using `LX.setThemeColor`.
36
+ Avatar Component in now used in Sidebar header and footer.
37
+ Added support for `oklch` format in ColorInput, new utils `LX.oklchToHex`.
38
+ Added Select `options.side` to force dropdown list alignment.
39
+ Added Vector `options.skipLock` to skip the lock button.
40
+ Fixed modal scroll bleed.
41
+ Fixed Commandbar position when page has vertical scroll.
42
+ Minor fixes Area Tabs' and Sidebar's CSS.
43
+
44
+ NodeTree:
45
+ - Refactored Event system to be compatible with AssetView events, using also cancellable events.
46
+
47
+ AssetView:
48
+ - Removed `options.onRefreshContent`.
49
+ - Added new `refreshContent`, `beforeRefreshContent` events.
50
+ - Added new `nodeDragged`, `beforeNodeDragged` events for Explorer node movements.
51
+ - Entering a folder now selects its folder item in the Explorer.
52
+
53
+ CodeEditor:
54
+ - Fixed NextOcurrence (Ctrl+D) bug.
55
+ - Fixed some issues with deleting using multiple cursors.
56
+ - Fixed Cursor deleting bug when merging multiple cursors.
57
+ - `options.callback` renamed to `options.onReady`, which is now called once the Editor is ready and visible.
58
+ - Pair keys `(), {}, "", ...` now only pair if no next char (or space).
59
+ - Added 3rd parameter to `setText(text, langString, _detectLanguage_)` to allow forcing language detection.
60
+
61
+ Other API Changes:
62
+ - `LX.getThemeColor` and `LX.setThemeColor` renamed to `LX.getCSSVariable` and `LX.setCSSVariable`.
63
+ - `LX.setSystemTheme` renamed to `LX.setSystemMode`.
64
+ - `LX.getTheme`, `LX.setTheme`, and `LX.switchTheme` renamed to `LX.getMode`, `LX.setMode`, and `LX.switchMode`.
65
+ - ComboButtons Component `options.float` now accepts `start, center, end` instead of `left, center, right`.
66
+ - Breadcrumb items `title` renamed to `name`.
67
+
68
+ ## 8.1.2
6
69
 
7
70
  Removed legacy code `CurvesTimeline` from `editor.html` example.
8
71
 
@@ -10,7 +73,7 @@ VideoEditor:
10
73
  - Forced area to have position relative so cropArea is correctly positioned.
11
74
 
12
75
  Timeline:
13
- - Autmatically calls `updateTheme` color on instance creation.
76
+ - Automatically calls `updateTheme` color on instance creation.
14
77
 
15
78
  ## 8.1.1
16
79
 
package/demo.js CHANGED
@@ -4,9 +4,12 @@ import { CodeEditor } from 'lexgui/extensions/CodeEditor.js';
4
4
  window.LX = LX;
5
5
 
6
6
  const area = await LX.init( { layoutMode: "document", rootClass: "wrapper" } );
7
- const starterTheme = LX.getTheme();
7
+ const starterTheme = LX.getMode();
8
8
  const mobile = navigator && /Android|iPhone/i.test( navigator.userAgent );
9
9
  const localHost = window.location.protocol !== "https:";
10
+ const themes = [
11
+ 'Amber', 'Blue', 'Green', 'Neutral', 'Orange', 'Purple', 'Red', 'Rose', 'Teal', 'Violet', 'Yellow'
12
+ ]
10
13
  const menubarButtons = [
11
14
  { name: "Docs", callback: () => { window.open("./docs/") } },
12
15
  { name: "Examples", callback: () => { window.open("./examples/") } },
@@ -33,10 +36,10 @@ if( mobile )
33
36
  }, {
34
37
  headerTitle: `lexgui.js`,
35
38
  headerSubtitle: `v${ LX.version }`,
36
- headerImage: "./images/favicon.png",
39
+ headerIcon: "UserRound",
37
40
  footerTitle: "jxarco",
38
- footerSubtitle: "alexroco.30@gmail.com",
39
- footerImage: "https://avatars.githubusercontent.com/u/25059187?s=400&u=ad8907b748c13e4e1a7cdd3882826acb6a2928b5&v=4",
41
+ footerSubtitle: "alexroco.30@gmail.com",
42
+ footerImage: "https://avatars.githubusercontent.com/u/25059187?v=4",
40
43
  collapsed: false,
41
44
  collapsable: false,
42
45
  displaySelected: false
@@ -46,9 +49,10 @@ else
46
49
  {
47
50
  menubar = area.addMenubar( menubarButtons );
48
51
 
49
- const commandButton = new LX.Button(null, `Search command...<span class="ml-auto">${ LX.makeKbd( ["Ctrl", "Space"], false, "bg-tertiary border px-1 rounded" ).innerHTML }</span>`, () => { LX.setCommandbarState( true ) }, {
50
- width: "256px", className: "right", buttonClass: "p-4 fg-tertiary bg-primary" }
52
+ const commandButton = new LX.Button(null, `Search command...`, () => { LX.setCommandbarState( true ) }, {
53
+ width: "256px", className: "right", buttonClass: "p-4 border-input bg-card text-muted-foreground" }
51
54
  );
55
+ commandButton.root.querySelector( 'button' ).appendChild( LX.makeKbd( ["Ctrl", "Space"], false, "ml-auto" ) );
52
56
  menubar.root.appendChild( commandButton.root );
53
57
  }
54
58
 
@@ -69,7 +73,7 @@ menubar.addButtons( [
69
73
  title: "Switch Theme",
70
74
  icon: starterTheme == "dark" ? "Moon" : "Sun",
71
75
  swap: starterTheme == "dark" ? "Sun" : "Moon",
72
- callback: (value, event) => { LX.switchTheme() }
76
+ callback: (value, event) => { LX.switchMode() }
73
77
  },
74
78
  {
75
79
  title: "Switch Spacing",
@@ -82,24 +86,26 @@ if( mobile )
82
86
  {
83
87
  menubar.root.querySelector( ".lexmenubuttons" ).style.marginLeft = "auto";
84
88
 
85
- const commandButton = new LX.Button(null, LX.makeIcon( "Menu" ).innerHTML, () => {
89
+ const menuButton = new LX.Button(null, "MenuButton", () => {
86
90
  window.__currentSheet = new LX.Sheet("256px", [ sheetArea ] );
87
- }, { buttonClass: "p-4 bg-none fg-tertiary" } );
88
- menubar.root.prepend( commandButton.root );
91
+ }, { icon: "Menu", buttonClass: "p-4 bg-none" } );
92
+ menubar.root.prepend( menuButton.root );
89
93
  }
90
94
 
95
+ LX._registerIconsAndColors( "./" );
96
+
91
97
  // Header
92
98
  {
93
- const header = LX.makeContainer( [ null, "auto" ], "flex flex-col gap-4 pt-8 pb-4 items-center", `
94
- <a href="docs?p=changelog" class="flex flex-row gap-1 items-center text-sm p-1 px-4 rounded-full fg-primary decoration-none hover:bg-secondary cursor-pointer"><span class="flex bg-accent w-2 h-2 rounded-full"></span>
95
- New Components: Spinner, Pagination and more${ LX.makeIcon( "ArrowRight", { svgClass: "sm" } ).innerHTML }</a>
96
- <p class="fg-primary font-medium tracking-tight leading-none text-center text-balance text-5xl xs:text-4xl">Build your Application Interface</p>
97
- <p class="fg-primary font-light text-xl xs:text-lg text-center text-balance leading-normal max-w-3xl">A modern-style UI kit, inspired by shadcn, built for the web. Pure JavaScript, CSS, zero dependencies. Fully Open Source.</p>
99
+ const header = LX.makeContainer( [ null, "auto" ], "flex flex-col gap-4 p-8 pb-4 items-center", `
100
+ <a href="docs?p=changelog" class="flex flex-row gap-1 items-center text-sm p-1 px-4 rounded-full text-secondary-foreground decoration-none hover:bg-secondary cursor-pointer"><span class="flex bg-info w-2 h-2 rounded-full"></span>
101
+ New Components: Avatar, Spinner, Pagination and more${ LX.makeIcon( "ArrowRight", { svgClass: "sm" } ).innerHTML }</a>
102
+ <p class="fg text-secondary-foreground font-medium tracking-tight leading-none text-center text-balance sm:text-5xl text-4xl">Build your Application Interface</p>
103
+ <p class="text-secondary-foreground font-light text-xl xs:text-lg text-center text-balance leading-normal max-w-3xl">A modern-style UI kit, inspired by shadcn, built for the web. Pure HTML, JavaScript, and Tailwind CSS. Fully Open Source.</p>
98
104
  `, area );
99
105
 
100
106
  const headerButtons = LX.makeContainer( [ "auto", "auto" ], "flex flex-row mt-2", ``, header );
101
- const getStartedButton = new LX.Button( null, `Get Started <span class="text-lg">${ LX.version }</span>`, () => window.open( "./docs/", "_blank" ), { buttonClass: "contrast" } );
102
- const componentsButton = new LX.Button( null, "View Components", () => window.open( "./docs/?p=components", "_blank" ), { buttonClass: "tertiary" } );
107
+ const getStartedButton = new LX.Button( null, `Get Started <span class="text-base">${ LX.version }</span>`, () => window.open( "./docs/", "_blank" ), { buttonClass: "primary" } );
108
+ const componentsButton = new LX.Button( null, "View Components", () => window.open( "./docs/?p=components", "_blank" ), { buttonClass: "ghost" } );
103
109
  headerButtons.appendChild( getStartedButton.root );
104
110
  headerButtons.appendChild( componentsButton.root );
105
111
  }
@@ -113,7 +119,13 @@ if( mobile )
113
119
  {
114
120
  tabs = area.addTabs( { parentClass: "p-4", sizes: [ "auto", "auto" ], contentClass: "p-6 pt-0" } );
115
121
 
116
- const editorContainer = LX.makeContainer( [ null, "800px" ], "flex flex-col bg-primary border rounded-lg overflow-hidden" );
122
+ const tabsParent = tabs.root.parentElement;
123
+ tabsParent.className = LX.mergeClass( tabsParent.className, 'flex flex-row justify-between' );
124
+
125
+ const themeSelect = new LX.Select( null, themes, 'Neutral', (v) => LX.setThemeColor( v.toLowerCase() ), { overflowContainerY: null, align: "end" } );
126
+ tabsParent.appendChild( themeSelect.root );
127
+
128
+ const editorContainer = LX.makeContainer( [ null, "800px" ], "flex flex-col border-color rounded-lg overflow-hidden" );
117
129
  tabs.add( "Editor", editorContainer, { selected: true } );
118
130
 
119
131
  const editorArea = new LX.Area({ className: "rounded-lg" });
@@ -209,7 +221,7 @@ if( mobile )
209
221
  left.onresize = resizeCanvas;
210
222
  left.addOverlayButtons( [
211
223
  [
212
- { name: "Select", icon: "MousePointer", selectable: true },
224
+ { name: "Select", icon: "MousePointer", selectable: true, selected: true },
213
225
  { name: "Move", icon: "Move", selectable: true },
214
226
  { name: "Rotate", icon: "RotateRight", selectable: true }
215
227
  ],
@@ -285,18 +297,69 @@ if( mobile )
285
297
 
286
298
  window.tree = panel.addTree("Scene Tree", sceneData, {
287
299
  icons: treeIcons,
288
- addDefault: true,
289
- onevent: (event) => {
290
- switch(event.type) {
291
- case LX.TreeEvent.NODE_CONTEXTMENU:
292
- const m = event.panel;
293
- m.add( "Components/Transform");
294
- m.add( "Components/MeshRenderer");
295
- break;
296
- }
297
- }
300
+ addDefault: true
298
301
  });
299
302
 
303
+ // Tree events
304
+ {
305
+ window.tree.on( 'contextMenu', ( event ) => {
306
+ return [
307
+ { name: "Components/Transform" },
308
+ { name: "Components/MeshRenderer" }
309
+ ]
310
+ } );
311
+
312
+ // Click events
313
+ {
314
+ window.tree.on( "select", ( event, resolve ) => {
315
+ console.log(`${ event.items[ 0 ].id } selected` );
316
+ } );
317
+
318
+ window.tree.on( "dblClick", ( event ) => {
319
+ console.log(`${ event.items[ 0 ].id } dbl clicked` );
320
+ } );
321
+ }
322
+
323
+ // Rename events
324
+ {
325
+ window.tree.on( "beforeRename", ( event, resolve ) => {
326
+ LX.prompt("Perform Rename Action?", "Server Message", () => {
327
+ resolve();
328
+ })
329
+ } );
330
+
331
+ window.tree.on( "rename", ( event ) => {
332
+ console.log(`${ event.oldName } renamed to ${ event.newName }`);
333
+ } );
334
+ }
335
+
336
+ // Delete events
337
+ {
338
+ window.tree.on( "beforeDelete", ( event, resolve ) => {
339
+ LX.prompt("Perform Delete Action?", "Server Message", () => {
340
+ resolve();
341
+ })
342
+ } );
343
+
344
+ window.tree.on( "delete", ( event ) => {
345
+ console.log(event.items[ 0 ].id + " deleted");
346
+ } );
347
+ }
348
+
349
+ // Move events
350
+ {
351
+ window.tree.on( "beforeMove", ( event, resolve ) => {
352
+ LX.prompt("Perform Move Action?", "Server Message", () => {
353
+ resolve();
354
+ })
355
+ } );
356
+
357
+ window.tree.on( "move", ( event ) => {
358
+ console.log(`${ event.items[ 0 ].id } moved to ${ event.to.id }` );
359
+ } );
360
+ }
361
+ }
362
+
300
363
  // add components to panel branch
301
364
  panel.branch("Canvas", { icon: "Palette", filter: true });
302
365
  panel.addColor("Background", "#b7a9b1", null);
@@ -304,6 +367,7 @@ if( mobile )
304
367
  panel.addColor("Font Color", "#303b8d", null);
305
368
  panel.addNumber("Font Size", 36, null, { min: 1, max: 48, step: 1, units: "px"});
306
369
  panel.addSelect("Font Family", ["Arial", "GeistSans", "Inter", "Monospace", "CascadiaCode"], "GeistSans");
370
+ panel.addSeparator();
307
371
  panel.addRange("Threshold Range", [2, 7], (v) => console.log(v), { min: 0, max: 10, step: 1, className: "accent" });
308
372
  panel.addVector2("2D Position", [300, 350], null, { min: 0, max: 1024 });
309
373
  const opacityValues = [
@@ -318,7 +382,7 @@ if( mobile )
318
382
 
319
383
  panel.branch("Node", { icon: "Box" });
320
384
  panel.addText("Name", "node_1");
321
- panel.addCheckbox("Visibility", true, null, { className: "accent" });
385
+ panel.addCheckbox("Visibility", true, null);
322
386
  panel.addLayers("Layers", 10, null);
323
387
 
324
388
  const map2Dpoints = [
@@ -337,7 +401,7 @@ if( mobile )
337
401
  panel.addVector3( "Position", [0.0, 0.0, 0.0] );
338
402
  panel.addVector4( "Rotation", [0.0, 0.0, 0.0, 1.0] );
339
403
  panel.addVector3( "Scale", [1.0, 1.0, 1.0] );
340
- panel.addButton(null, "Export", () => { console.log("Exported!") }, { buttonClass: "contrast", xmustConfirm: true,
404
+ panel.addButton(null, "Export", () => { console.log("Exported!") }, { buttonClass: "primary", xmustConfirm: true,
341
405
  // confirmSide: "left",
342
406
  // confirmAlign: "start",
343
407
  // confirmText: "Yeah",
@@ -363,7 +427,7 @@ if( mobile )
363
427
  }
364
428
 
365
429
  {
366
- const panel = new LX.Panel( { className: "rounded-lg border p-4 flex flex-col gap-4" } );
430
+ const panel = new LX.Panel( { className: "rounded-lg border-color p-4 flex flex-col gap-4" } );
367
431
  panel.addColor("Background", "#b7a9b1", null);
368
432
  panel.addText("Text", "LexGUI.js @jxarco", null, {placeholder: "e.g. ColorPicker", icon: "Type"});
369
433
  panel.addColor("Font Color", "#303b8d", null);
@@ -418,12 +482,12 @@ if( mobile )
418
482
  // Mail
419
483
  if( !mobile )
420
484
  {
421
- const mailContainer = LX.makeContainer( [ null, "800px" ], "flex flex-col bg-primary border rounded-lg overflow-hidden" );
422
- tabs.add( "Mail", mailContainer, { xselected: true, badge: { content: "5", className: "sm fg-white bg-error", asChild: true } } );
485
+ const mailContainer = LX.makeContainer( [ null, "800px" ], "flex flex-col border-color rounded-lg overflow-hidden" );
486
+ tabs.add( "Mail", mailContainer, { xselected: true, badge: { content: "5", className: "destructive px-1", asChild: true } } );
423
487
 
424
488
  const mailArea = new LX.Area();
425
489
  mailContainer.appendChild( mailArea.root );
426
- const badgeClass = "ml-auto bg-none font-medium";
490
+ const badgeClass = "xs ml-auto bg-none font-medium";
427
491
 
428
492
  const sidebar = mailArea.addSidebar( m => {
429
493
  m.add( "Inbox", { selected: true, icon: "Inbox", content: LX.badge("128", badgeClass, { asElement: true }) } );
@@ -439,10 +503,10 @@ if( mobile )
439
503
  m.add( "Shopping ", { icon: "ShoppingCart" } );
440
504
  m.add( "Promotions", { icon: "Flag", content: LX.badge("21", badgeClass, { asElement: true }) } );
441
505
  }, {
442
- className: "border-right",
506
+ className: "border-r-color",
443
507
  headerTitle: "jxarco",
444
508
  headerSubtitle: "alexroco.30@gmail.com",
445
- headerImage: "images/favicon.png",
509
+ headerIcon: "UserRound",
446
510
  skipFooter: true,
447
511
  displaySelected: true,
448
512
  onHeaderPressed: (e, element) => { }
@@ -455,10 +519,10 @@ if( mobile )
455
519
 
456
520
  // Manage Inbox
457
521
  {
458
- const inboxTabs = left.addTabs({ parentClass: "flex p-3 items-end border-bottom", sizes: [ "auto", "auto" ], float: "end" });
522
+ const inboxTabs = left.addTabs({ parentClass: "flex p-3 items-end border-b-color", sizes: [ "auto", "auto" ], float: "end" });
459
523
  const tabsRowContainer = inboxTabs.root.parentElement;
460
524
 
461
- const mailSectionTitle = LX.makeContainer( [ "auto", "auto" ], "mr-auto ml-2 self-center text-xxl font-semibold", "Inbox" );
525
+ const mailSectionTitle = LX.makeContainer( [ "auto", "auto" ], "mr-auto ml-2 place-self-center text-2xl font-semibold", "Inbox" );
462
526
  tabsRowContainer.prepend( mailSectionTitle );
463
527
 
464
528
  window.__showMailList = ( container, unreadOnly = false ) => {
@@ -480,7 +544,7 @@ if( mobile )
480
544
 
481
545
  const selected = ( idx == 0 );
482
546
  const msgContent = LX.makeContainer( [ "100%", "auto" ],
483
- `flex flex-col border p-3 rounded-lg gap-2 select-none hover:bg-mix cursor-pointer ${ selected ? "bg-secondary" : "" }`, "", mailContainer );
547
+ `flex flex-col border-color p-3 rounded-lg gap-2 select-none cursor-pointer ${ selected ? "bg-secondary" : "" }`, "", mailContainer );
484
548
 
485
549
  // Name, subject, date
486
550
  {
@@ -489,20 +553,20 @@ if( mobile )
489
553
 
490
554
  // Name + Date
491
555
  {
492
- const msgName = LX.makeContainer( [ "auto", "auto" ], "flex font-semibold text-md gap-2", "", msgNameDate );
556
+ const msgName = LX.makeContainer( [ "auto", "auto" ], "flex font-semibold text-sm gap-2", "", msgNameDate );
493
557
  msgName.innerHTML = mail.name;
494
- msgName.innerHTML += ( mail.read ? "" : `<span class="rounded-full self-center bg-accent" style="width: 8px; height: 8px"></span>` );
495
- const msgDate = LX.makeContainer( [ "auto", "auto" ], "fg-tertiary text-sm ml-auto self-center", mail.date, msgNameDate );
558
+ msgName.innerHTML += ( mail.read ? "" : `<span class="rounded-full place-self-center bg-accent" style="width: 8px; height: 8px"></span>` );
559
+ const msgDate = LX.makeContainer( [ "auto", "auto" ], "text-muted-foreground text-xs ml-auto place-self-center", mail.date, msgNameDate );
496
560
  }
497
561
 
498
- const msgSubject = LX.makeContainer( [ "100%", "auto" ], "font-semibold text-sm", mail.subject, msgInfo );
562
+ const msgSubject = LX.makeContainer( [ "100%", "auto" ], "font-semibold text-xs", mail.subject, msgInfo );
499
563
  }
500
564
 
501
- const msgText = LX.makeContainer( [ "100%", "auto" ], "text-sm line-clamp-2 fg-tertiary", mail.content, msgContent );
565
+ const msgText = LX.makeContainer( [ "100%", "auto" ], "text-xs line-clamp-2 text-muted-foreground", mail.content, msgContent );
502
566
  const msgTags = LX.makeContainer( [ "100%", "auto" ], "flex flex-row gap-0.5 font-semibold", "", msgContent );
503
567
  for( const tag of mail.tags )
504
568
  {
505
- msgTags.appendChild( LX.badge( tag, "sm", { asElement: true } ) );
569
+ msgTags.appendChild( LX.badge( tag, "xs", { asElement: true } ) );
506
570
  }
507
571
 
508
572
  LX.listen( msgContent, "click", function() {
@@ -532,18 +596,18 @@ if( mobile )
532
596
  {
533
597
  // Buttons
534
598
  {
535
- const mailPreviewHeader = LX.makeContainer( [ "100%", "43.5px" ], "flex flex-row border-bottom p-1", "", right );
599
+ const mailPreviewHeader = LX.makeContainer( [ "100%", "43.5px" ], "flex flex-row border-b-color p-1", "", right );
536
600
 
537
601
  mailPreviewHeader.appendChild( new LX.Button( null, "", null, { title: "Archive", tooltip: true, buttonClass: "bg-none", icon: "Archive" } ).root );
538
602
  mailPreviewHeader.appendChild( new LX.Button( null, "", null, { title: "Move to junk", tooltip: true, buttonClass: "bg-none", icon: "ArchiveX" } ).root );
539
603
  mailPreviewHeader.appendChild( new LX.Button( null, "", null, { title: "Move to trash", tooltip: true, buttonClass: "bg-none", icon: "Trash3" } ).root );
540
- mailPreviewHeader.appendChild( LX.makeContainer( [ "1px", "35%" ], "border-right self-center ml-2 mr-2" ) );
604
+ mailPreviewHeader.appendChild( LX.makeContainer( [ "1px", "35%" ], "border-r-color place-self-center ml-2 mr-2" ) );
541
605
  mailPreviewHeader.appendChild( new LX.Button( null, "", null, { title: "Snooze", tooltip: true, buttonClass: "bg-none", icon: "Clock" } ).root );
542
606
 
543
607
  mailPreviewHeader.appendChild( new LX.Button( null, "", null, { title: "Reply", tooltip: true, buttonClass: "bg-none", className: "ml-auto", icon: "Reply" } ).root );
544
608
  mailPreviewHeader.appendChild( new LX.Button( null, "", null, { title: "Reply all", tooltip: true, buttonClass: "bg-none", icon: "ReplyAll" } ).root );
545
609
  mailPreviewHeader.appendChild( new LX.Button( null, "", null, { title: "Forward", tooltip: true, buttonClass: "bg-none", icon: "Forward" } ).root );
546
- mailPreviewHeader.appendChild( LX.makeContainer( [ "1px", "35%" ], "border-right self-center ml-2 mr-2" ) );
610
+ mailPreviewHeader.appendChild( LX.makeContainer( [ "1px", "35%" ], "border-r-color place-self-center ml-2 mr-2" ) );
547
611
  mailPreviewHeader.appendChild( new LX.Button( null, "", (value, event) => {
548
612
  new LX.DropdownMenu( event.target, [
549
613
  { name: "Mark as unread" },
@@ -562,20 +626,20 @@ if( mobile )
562
626
 
563
627
  previewDataContent.innerHTML = "";
564
628
 
565
- const mailPreviewInfo = LX.makeContainer( [ "100%", "auto" ], "flex flex-row border-bottom p-4 gap-3", "", previewDataContent );
566
- const avatarContainer = LX.makeContainer( [ "40px", "40px" ], "bg-tertiary rounded-full content-center", "", mailPreviewInfo );
567
-
568
629
  const mailNames = mail.name.split( ' ' );
569
- const avatarIcon = LX.makeContainer( [ "auto", "auto" ], "font-medium self-center", mailNames[ 0 ][ 0 ] + mailNames[ 1 ][ 0 ], avatarContainer );
630
+ const mailPreviewInfo = LX.makeContainer( [ "100%", "auto" ], "flex flex-row border-b-color p-4 gap-3", "", previewDataContent );
631
+
632
+ const avatar = new LX.Avatar( { imgSource: mail.avatar, fallback: mailNames[ 0 ][ 0 ] + mailNames[ 1 ][ 0 ], className: 'size-10', fallbackClass: "text-foreground" } );
633
+ mailPreviewInfo.appendChild( avatar.root );
570
634
 
571
635
  const senderData = LX.makeContainer( [ "auto", "auto" ], "flex flex-col gap-0.5", `
572
- <div class="text-md font-semibold">${ mail.name }</div>
573
- <div class="text-sm">${ mail.subject }</div>
574
- <div class="text-sm">Reply-To: ${ mail.email }</div>
636
+ <div class="text-sm font-semibold">${ mail.name }</div>
637
+ <div class="text-xs">${ mail.subject }</div>
638
+ <div class="text-xs">Reply-To: ${ mail.email }</div>
575
639
  `, mailPreviewInfo );
576
640
 
577
- const exactDate = LX.makeContainer( [ "auto", "auto" ], "flex flex-row text-sm fg-tertiary ml-auto", mail.exactDate, mailPreviewInfo );
578
- const mailPreviewContent = LX.makeContainer( [ "100%", "515px" ], "flex flex-row border-bottom text-md whitespace-pre-wrap p-4", mail.content, previewDataContent );
641
+ const exactDate = LX.makeContainer( [ "auto", "auto" ], "flex flex-row text-xs text-muted-foreground ml-auto", mail.exactDate, mailPreviewInfo );
642
+ const mailPreviewContent = LX.makeContainer( [ "100%", "515px" ], "flex flex-row border-b-color text-sm whitespace-pre-wrap p-4", mail.content, previewDataContent );
579
643
  const previewFooter = LX.makeContainer( [ "100%", "auto" ], "flex flex-col p-2", "", previewDataContent );
580
644
 
581
645
  const msgReplyTextArea = new LX.TextArea(null, "", null,
@@ -585,14 +649,14 @@ if( mobile )
585
649
 
586
650
  const previewButtons = LX.makeContainer( [ "100%", "auto" ], "flex flex-row p-1", "", previewFooter );
587
651
 
588
- const muteToggle = new LX.Toggle( null, false, null, { label: "Mute this thread", className: "contrast" } );
652
+ const muteToggle = new LX.Toggle( null, false, null, { label: "Mute this thread" } );
589
653
  previewButtons.appendChild( muteToggle.root );
590
654
 
591
655
  const sendButton = new LX.Button( null, "Send", () => {
592
656
  LX.toast( "Message sent!", "To:" + mail.email, { timeout: 5000, action: { name: "Undo", callback: ( toast, actionName, event ) => {
593
657
  toast.close();
594
658
  } } } );
595
- }, { className: "ml-auto", buttonClass: "contrast" } );
659
+ }, { className: "ml-auto", buttonClass: "primary" } );
596
660
  previewButtons.appendChild( sendButton.root );
597
661
  };
598
662
  }
@@ -602,12 +666,12 @@ if( mobile )
602
666
  // Tasks
603
667
  if( !mobile )
604
668
  {
605
- const tasksContainer = LX.makeContainer( [ null, "auto" ], "col bg-background border rounded-lg p-6 overflow-hidden" );
669
+ const tasksContainer = LX.makeContainer( [ null, "auto" ], "col bg-background border-color rounded-lg p-6 overflow-hidden" );
606
670
  tabs.add( "Tasks", tasksContainer, { xselected: true } );
607
671
 
608
672
  const header = LX.makeContainer( [ null, "auto" ], "col p-4", `
609
673
  <h2>Welcome back!</h2>
610
- <p class="fg-tertiary">Here's a list of your tasks for this month!</p>
674
+ <p class="text-muted-foreground text-sm">Here's a list of your tasks for this month!</p>
611
675
  `, tasksContainer );
612
676
 
613
677
  const tableComponent = new LX.Table(null, {
@@ -646,7 +710,7 @@ if( mobile )
646
710
  { name: "Make a copy" },
647
711
  { name: "Favourite" },
648
712
  null,
649
- { name: "Delete", icon: "Trash2", className: "fg-error" },
713
+ { name: "Delete", icon: "Trash2", className: "text-destructive" },
650
714
  ]
651
715
  }
652
716
  });
@@ -656,7 +720,7 @@ if( mobile )
656
720
  // Code
657
721
  if( !mobile )
658
722
  {
659
- const codeContainer = LX.makeContainer( [ "auto", "800px" ], "flex flex-col border rounded-lg overflow-hidden" );
723
+ const codeContainer = LX.makeContainer( [ "auto", "800px" ], "flex flex-col border-color rounded-lg overflow-hidden" );
660
724
  tabs.add( "Code", codeContainer );
661
725
 
662
726
  const codeArea = new LX.Area();
@@ -668,6 +732,44 @@ if( mobile )
668
732
  // disableEdition: true,
669
733
  // fileExplorer: false
670
734
  });
735
+
736
+ editor.setText(`interface Vec2 {
737
+ x: number;
738
+ y: number;
739
+ }
740
+
741
+ type Callback<T> = (value: T) => void;
742
+
743
+ class Timer {
744
+ private startTime = 0;
745
+
746
+ start(): void {
747
+ this.startTime = performance.now();
748
+ }
749
+
750
+ elapsed(): number {
751
+ return performance.now() - this.startTime;
752
+ }
753
+ }
754
+
755
+ async function wait(ms: number): Promise<void> {
756
+ return new Promise(resolve => setTimeout(resolve, ms));
757
+ }
758
+
759
+ const onTick: Callback<number> = time => {
760
+ console.log(\`Elapsed: \${time.toFixed(2)}ms\`);
761
+ };
762
+
763
+ async function main() {
764
+ const timer = new Timer();
765
+ timer.start();
766
+
767
+ await wait(500);
768
+ onTick(timer.elapsed());
769
+ }
770
+
771
+ main();
772
+ `, undefined, true ); // Force detect language!
671
773
  }
672
774
 
673
775
  // Audio
@@ -683,7 +785,7 @@ if( mobile )
683
785
  // Footer
684
786
  {
685
787
  const footer = new LX.Footer( {
686
- className: "border-top",
788
+ className: "border-t-color",
687
789
  parent: LX.root,
688
790
  columns: [
689
791
  {