lit-shell.js 1.1.0 → 1.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 (43) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +438 -9
  3. package/dist/client/browser-bundle.js +61 -1
  4. package/dist/client/browser-bundle.js.map +3 -3
  5. package/dist/client/index.d.ts +1 -0
  6. package/dist/client/index.d.ts.map +1 -1
  7. package/dist/client/index.js +1 -0
  8. package/dist/client/index.js.map +1 -1
  9. package/dist/client/terminal-client.d.ts +19 -0
  10. package/dist/client/terminal-client.d.ts.map +1 -1
  11. package/dist/client/terminal-client.js +61 -0
  12. package/dist/client/terminal-client.js.map +1 -1
  13. package/dist/server/index.d.ts +1 -0
  14. package/dist/server/index.d.ts.map +1 -1
  15. package/dist/server/index.js +1 -0
  16. package/dist/server/index.js.map +1 -1
  17. package/dist/server/session-manager.d.ts +6 -0
  18. package/dist/server/session-manager.d.ts.map +1 -1
  19. package/dist/server/session-manager.js +12 -2
  20. package/dist/server/session-manager.js.map +1 -1
  21. package/dist/server/terminal-server.d.ts.map +1 -1
  22. package/dist/server/terminal-server.js +23 -4
  23. package/dist/server/terminal-server.js.map +1 -1
  24. package/dist/shared/types.d.ts +6 -0
  25. package/dist/shared/types.d.ts.map +1 -1
  26. package/dist/ui/browser-bundle.js +1625 -96
  27. package/dist/ui/browser-bundle.js.map +4 -4
  28. package/dist/ui/index.d.ts +1 -0
  29. package/dist/ui/index.d.ts.map +1 -1
  30. package/dist/ui/index.js +1 -0
  31. package/dist/ui/index.js.map +1 -1
  32. package/dist/ui/lit-shell-terminal.d.ts +225 -6
  33. package/dist/ui/lit-shell-terminal.d.ts.map +1 -1
  34. package/dist/ui/lit-shell-terminal.js +1605 -60
  35. package/dist/ui/lit-shell-terminal.js.map +1 -1
  36. package/dist/ui/styles.d.ts.map +1 -1
  37. package/dist/ui/styles.js +22 -0
  38. package/dist/ui/styles.js.map +1 -1
  39. package/dist/version.d.ts +6 -0
  40. package/dist/version.d.ts.map +1 -0
  41. package/dist/version.js +6 -0
  42. package/dist/version.js.map +1 -0
  43. package/package.json +1 -1
@@ -10,6 +10,9 @@ var __decorateClass = (decorators, target, key, kind) => {
10
10
  return result;
11
11
  };
12
12
 
13
+ // src/version.ts
14
+ var VERSION = "1.2.1";
15
+
13
16
  // node_modules/@lit/reactive-element/css-tag.js
14
17
  var t = globalThis;
15
18
  var e = t.ShadowRoot && (void 0 === t.ShadyCSS || t.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype;
@@ -748,6 +751,28 @@ var buttonStyles = i`
748
751
  opacity: 0.5;
749
752
  cursor: not-allowed;
750
753
  }
754
+
755
+ button.btn-primary,
756
+ .btn-primary {
757
+ background: var(--ls-status-connected);
758
+ color: #ffffff;
759
+ }
760
+
761
+ button.btn-primary:hover,
762
+ .btn-primary:hover {
763
+ background: #16a34a;
764
+ }
765
+
766
+ button.btn-danger,
767
+ .btn-danger {
768
+ background: var(--ls-status-disconnected);
769
+ color: #ffffff;
770
+ }
771
+
772
+ button.btn-danger:hover,
773
+ .btn-danger:hover {
774
+ background: #dc2626;
775
+ }
751
776
  `;
752
777
 
753
778
  // src/client/terminal-client.ts
@@ -760,6 +785,8 @@ var TerminalClient = class {
760
785
  this.serverInfo = null;
761
786
  this.reconnectAttempts = 0;
762
787
  this.reconnectTimeout = null;
788
+ this.previousSessionId = null;
789
+ this.isReconnecting = false;
763
790
  // Event handlers
764
791
  this.connectHandlers = [];
765
792
  this.disconnectHandlers = [];
@@ -776,6 +803,7 @@ var TerminalClient = class {
776
803
  this.clientJoinedHandlers = [];
777
804
  this.clientLeftHandlers = [];
778
805
  this.sessionClosedHandlers = [];
806
+ this.reconnectWithSessionHandlers = [];
779
807
  // Promise resolvers for spawn/join
780
808
  this.spawnResolve = null;
781
809
  this.spawnReject = null;
@@ -810,17 +838,25 @@ var TerminalClient = class {
810
838
  this.state = "connected";
811
839
  this.reconnectAttempts = 0;
812
840
  this.connectHandlers.forEach((handler) => handler());
841
+ if (this.isReconnecting && this.previousSessionId) {
842
+ this.checkPreviousSessionAndNotify();
843
+ }
844
+ this.isReconnecting = false;
813
845
  resolve();
814
846
  };
815
847
  this.ws.onclose = () => {
816
848
  const wasConnected = this.state === "connected";
817
849
  this.state = "disconnected";
850
+ if (this.sessionId) {
851
+ this.previousSessionId = this.sessionId;
852
+ }
818
853
  this.sessionId = null;
819
854
  this.sessionInfo = null;
820
855
  if (wasConnected) {
821
856
  this.disconnectHandlers.forEach((handler) => handler());
822
857
  }
823
858
  if (this.config.reconnect && this.reconnectAttempts < this.config.maxReconnectAttempts) {
859
+ this.isReconnecting = true;
824
860
  this.scheduleReconnect();
825
861
  }
826
862
  };
@@ -1295,6 +1331,51 @@ var TerminalClient = class {
1295
1331
  getServerInfo() {
1296
1332
  return this.serverInfo;
1297
1333
  }
1334
+ /**
1335
+ * Get previous session ID (available after disconnect)
1336
+ */
1337
+ getPreviousSessionId() {
1338
+ return this.previousSessionId;
1339
+ }
1340
+ /**
1341
+ * Clear previous session ID (call after user declines to rejoin)
1342
+ */
1343
+ clearPreviousSessionId() {
1344
+ this.previousSessionId = null;
1345
+ }
1346
+ /**
1347
+ * Called when reconnected and previous session is available
1348
+ */
1349
+ onReconnectWithSession(handler) {
1350
+ this.reconnectWithSessionHandlers.push(handler);
1351
+ }
1352
+ /**
1353
+ * Check if previous session exists and notify handlers
1354
+ */
1355
+ async checkPreviousSessionAndNotify() {
1356
+ if (!this.previousSessionId)
1357
+ return;
1358
+ try {
1359
+ const sessions = await this.listSessions();
1360
+ const previousSession = sessions.find(
1361
+ (s4) => s4.sessionId === this.previousSessionId
1362
+ );
1363
+ if (previousSession && previousSession.accepting) {
1364
+ this.reconnectWithSessionHandlers.forEach((handler) => {
1365
+ try {
1366
+ handler(this.previousSessionId);
1367
+ } catch (e5) {
1368
+ console.error("[lit-shell] Error in reconnectWithSession handler:", e5);
1369
+ }
1370
+ });
1371
+ } else {
1372
+ this.previousSessionId = null;
1373
+ }
1374
+ } catch (e5) {
1375
+ console.error("[lit-shell] Failed to check previous session:", e5);
1376
+ this.previousSessionId = null;
1377
+ }
1378
+ }
1298
1379
  };
1299
1380
 
1300
1381
  // src/ui/lit-shell-terminal.ts
@@ -1310,8 +1391,16 @@ var LitShellTerminal = class extends i4 {
1310
1391
  this.noHeader = false;
1311
1392
  this.autoConnect = false;
1312
1393
  this.autoSpawn = false;
1394
+ this.container = "";
1395
+ this.containerShell = "";
1396
+ this.containerUser = "";
1397
+ this.containerCwd = "";
1398
+ this.showConnectionPanel = false;
1399
+ this.showSettings = false;
1400
+ this.showStatusBar = false;
1401
+ this.showTabs = false;
1313
1402
  this.fontSize = 14;
1314
- this.fontFamily = 'Menlo, Monaco, "Courier New", monospace';
1403
+ this.fontFamily = '"Cascadia Mono", "Cascadia Code", Consolas, "Ubuntu Mono", "DejaVu Sans Mono", "Liberation Mono", Hack, "Fira Code", "JetBrains Mono", Menlo, Monaco, "Courier New", monospace';
1315
1404
  this.client = null;
1316
1405
  this.terminal = null;
1317
1406
  this.fitAddon = null;
@@ -1320,17 +1409,73 @@ var LitShellTerminal = class extends i4 {
1320
1409
  this.loading = false;
1321
1410
  this.error = null;
1322
1411
  this.sessionInfo = null;
1412
+ this.containers = [];
1413
+ this.serverInfo = null;
1414
+ this.selectedContainer = "";
1415
+ this.selectedShell = "/bin/sh";
1416
+ this.connectionMode = "local";
1417
+ this.orphanTimeout = 36e5;
1418
+ this.useTmux = false;
1419
+ this.availableSessions = [];
1420
+ this.selectedSessionId = "";
1421
+ this.clientCount = 1;
1422
+ this.settingsMenuOpen = false;
1423
+ this.showReconnectDialog = false;
1424
+ this.reconnectSessionId = null;
1425
+ this.isMobile = false;
1426
+ this.showTouchKeyboard = true;
1427
+ this.showExtraKeyRows = false;
1428
+ this.statusMessage = "";
1429
+ this.statusType = "info";
1430
+ this.tabs = [];
1431
+ this.activeTabId = "";
1432
+ this.tabCounter = 0;
1323
1433
  // xterm.js module (loaded dynamically)
1324
1434
  this.xtermModule = null;
1325
1435
  this.fitAddonModule = null;
1326
1436
  this.resizeObserver = null;
1437
+ this.ctrlPressed = false;
1438
+ this.altPressed = false;
1327
1439
  }
1328
1440
  connectedCallback() {
1329
1441
  super.connectedCallback();
1442
+ this.setAttribute("data-version", VERSION);
1443
+ this.detectMobile();
1444
+ if (this.showTabs && this.tabs.length === 0) {
1445
+ this.createTab();
1446
+ }
1330
1447
  if (this.autoConnect && this.url) {
1331
1448
  this.connect();
1332
1449
  }
1333
1450
  }
1451
+ /**
1452
+ * Detect if running on a mobile device
1453
+ */
1454
+ detectMobile() {
1455
+ const hasTouch = navigator.maxTouchPoints > 0;
1456
+ const isMobileWidth = window.matchMedia("(max-width: 768px)").matches;
1457
+ const mobileUA = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
1458
+ navigator.userAgent
1459
+ );
1460
+ this.isMobile = hasTouch && (isMobileWidth || mobileUA);
1461
+ this.updateMobileAttribute();
1462
+ window.matchMedia("(max-width: 768px)").addEventListener("change", (e5) => {
1463
+ if (navigator.maxTouchPoints > 0) {
1464
+ this.isMobile = e5.matches;
1465
+ this.updateMobileAttribute();
1466
+ }
1467
+ });
1468
+ }
1469
+ /**
1470
+ * Update mobile attribute for CSS targeting
1471
+ */
1472
+ updateMobileAttribute() {
1473
+ if (this.isMobile) {
1474
+ this.setAttribute("mobile", "");
1475
+ } else {
1476
+ this.removeAttribute("mobile");
1477
+ }
1478
+ }
1334
1479
  disconnectedCallback() {
1335
1480
  super.disconnectedCallback();
1336
1481
  this.cleanup();
@@ -1352,29 +1497,25 @@ var LitShellTerminal = class extends i4 {
1352
1497
  throw new Error("Failed to load xterm.js. Make sure it is available.");
1353
1498
  }
1354
1499
  }
1355
- await this.loadXtermStyles();
1500
+ await this.injectXtermCSS();
1356
1501
  }
1357
1502
  /**
1358
- * Load xterm.css into the shadow DOM
1359
- * This is necessary because CSS loaded in the main document doesn't apply inside shadow DOM
1503
+ * Inject xterm.js CSS into shadow DOM
1360
1504
  */
1361
- async loadXtermStyles() {
1505
+ async injectXtermCSS() {
1362
1506
  if (!this.shadowRoot)
1363
1507
  return;
1364
1508
  if (this.shadowRoot.querySelector("#xterm-styles"))
1365
1509
  return;
1366
1510
  try {
1367
1511
  const response = await fetch("https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css");
1368
- if (!response.ok) {
1369
- throw new Error(`Failed to fetch xterm.css: ${response.status}`);
1370
- }
1371
- const cssText = await response.text();
1512
+ const css = await response.text();
1372
1513
  const style = document.createElement("style");
1373
1514
  style.id = "xterm-styles";
1374
- style.textContent = cssText;
1375
- this.shadowRoot.appendChild(style);
1376
- } catch (error) {
1377
- console.warn("[lit-shell] Failed to load xterm.css:", error);
1515
+ style.textContent = css;
1516
+ this.shadowRoot.prepend(style);
1517
+ } catch (e5) {
1518
+ console.warn("[lit-shell] Failed to load xterm CSS:", e5);
1378
1519
  }
1379
1520
  }
1380
1521
  /**
@@ -1404,33 +1545,133 @@ var LitShellTerminal = class extends i4 {
1404
1545
  });
1405
1546
  this.client.onError((err) => {
1406
1547
  this.error = err.message;
1548
+ this.setStatus(err.message, "error");
1407
1549
  this.dispatchEvent(
1408
1550
  new CustomEvent("error", { detail: { error: err }, bubbles: true, composed: true })
1409
1551
  );
1410
1552
  });
1553
+ const client = this.client;
1411
1554
  this.client.onData((data) => {
1412
- if (this.terminal) {
1555
+ if (this.showTabs) {
1556
+ const tab = this.tabs.find((t4) => t4.client === client);
1557
+ if (tab?.terminal) {
1558
+ tab.terminal.write(data);
1559
+ }
1560
+ } else if (this.terminal) {
1413
1561
  this.terminal.write(data);
1414
1562
  }
1415
1563
  });
1416
1564
  this.client.onExit((code) => {
1417
- this.sessionActive = false;
1418
- this.sessionInfo = null;
1419
- if (this.terminal) {
1420
- this.terminal.writeln("");
1421
- this.terminal.writeln(`\x1B[1;33m[Process exited with code: ${code}]\x1B[0m`);
1565
+ if (this.showTabs) {
1566
+ const tab = this.tabs.find((t4) => t4.client === client);
1567
+ if (tab) {
1568
+ tab.sessionActive = false;
1569
+ tab.sessionInfo = null;
1570
+ if (tab.terminal) {
1571
+ tab.terminal.writeln("");
1572
+ tab.terminal.writeln(`\x1B[1;33m[Process exited with code: ${code}]\x1B[0m`);
1573
+ }
1574
+ if (tab.id === this.activeTabId) {
1575
+ this.sessionActive = false;
1576
+ this.sessionInfo = null;
1577
+ }
1578
+ this.tabs = [...this.tabs];
1579
+ }
1580
+ } else {
1581
+ this.sessionActive = false;
1582
+ this.sessionInfo = null;
1583
+ if (this.terminal) {
1584
+ this.terminal.writeln("");
1585
+ this.terminal.writeln(`\x1B[1;33m[Process exited with code: ${code}]\x1B[0m`);
1586
+ }
1422
1587
  }
1423
1588
  this.dispatchEvent(
1424
1589
  new CustomEvent("exit", { detail: { exitCode: code }, bubbles: true, composed: true })
1425
1590
  );
1426
1591
  });
1427
1592
  this.client.onSpawned((info) => {
1593
+ if (this.showTabs) {
1594
+ const tab = this.tabs.find((t4) => t4.client === client);
1595
+ if (tab) {
1596
+ tab.sessionInfo = info;
1597
+ tab.sessionActive = true;
1598
+ tab.label = info.container || info.shell.split("/").pop() || "Terminal";
1599
+ this.tabs = [...this.tabs];
1600
+ }
1601
+ }
1428
1602
  this.sessionInfo = info;
1603
+ this.setStatus(`Session started: ${info.container || info.shell}`, "success");
1429
1604
  this.dispatchEvent(
1430
1605
  new CustomEvent("spawned", { detail: { session: info }, bubbles: true, composed: true })
1431
1606
  );
1432
1607
  });
1608
+ this.client.onServerInfo((info) => {
1609
+ this.serverInfo = info;
1610
+ if (info.dockerEnabled) {
1611
+ this.connectionMode = "docker";
1612
+ this.client?.requestContainerList();
1613
+ }
1614
+ this.selectedShell = info.defaultShell;
1615
+ });
1616
+ this.client.onContainerList((containers) => {
1617
+ this.containers = containers;
1618
+ if (containers.length > 0 && !this.selectedContainer) {
1619
+ this.selectedContainer = containers[0].name;
1620
+ }
1621
+ });
1622
+ this.client.onSessionList((sessions) => {
1623
+ this.availableSessions = sessions;
1624
+ if (sessions.length > 0 && !this.selectedSessionId) {
1625
+ this.selectedSessionId = sessions[0].sessionId;
1626
+ }
1627
+ });
1628
+ this.client.onClientJoined((sessionId, count) => {
1629
+ if (this.showTabs) {
1630
+ const tab = this.tabs.find((t4) => t4.client === client);
1631
+ if (tab) {
1632
+ tab.clientCount = count;
1633
+ this.tabs = [...this.tabs];
1634
+ }
1635
+ }
1636
+ this.clientCount = count;
1637
+ this.setStatus(`Client joined (${count} total)`, "info");
1638
+ });
1639
+ this.client.onClientLeft((sessionId, count) => {
1640
+ if (this.showTabs) {
1641
+ const tab = this.tabs.find((t4) => t4.client === client);
1642
+ if (tab) {
1643
+ tab.clientCount = count;
1644
+ this.tabs = [...this.tabs];
1645
+ }
1646
+ }
1647
+ this.clientCount = count;
1648
+ this.setStatus(`Client left (${count} remaining)`, "info");
1649
+ });
1650
+ this.client.onSessionClosed((sessionId, reason) => {
1651
+ if (this.showTabs) {
1652
+ const tab = this.tabs.find((t4) => t4.client === client && t4.sessionInfo?.sessionId === sessionId);
1653
+ if (tab) {
1654
+ tab.sessionActive = false;
1655
+ tab.sessionInfo = null;
1656
+ this.tabs = [...this.tabs];
1657
+ }
1658
+ }
1659
+ if (this.sessionInfo?.sessionId === sessionId) {
1660
+ this.sessionActive = false;
1661
+ this.sessionInfo = null;
1662
+ this.setStatus(`Session closed: ${reason}`, "info");
1663
+ }
1664
+ client.requestSessionList();
1665
+ });
1666
+ this.client.onReconnectWithSession((sessionId) => {
1667
+ this.reconnectSessionId = sessionId;
1668
+ this.showReconnectDialog = true;
1669
+ });
1433
1670
  await this.client.connect();
1671
+ this.client.requestSessionList();
1672
+ if (this.showTabs) {
1673
+ this.syncStateToActiveTab();
1674
+ }
1434
1675
  } catch (err) {
1435
1676
  this.error = err instanceof Error ? err.message : "Connection failed";
1436
1677
  } finally {
@@ -1464,16 +1705,27 @@ var LitShellTerminal = class extends i4 {
1464
1705
  cwd: options?.cwd || this.cwd || void 0,
1465
1706
  cols: this.terminal?.cols || this.cols,
1466
1707
  rows: this.terminal?.rows || this.rows,
1467
- env: options?.env
1708
+ env: options?.env,
1709
+ // Docker container options
1710
+ container: options?.container || this.container || void 0,
1711
+ containerShell: options?.containerShell || this.containerShell || void 0,
1712
+ containerUser: options?.containerUser || this.containerUser || void 0,
1713
+ containerCwd: options?.containerCwd || this.containerCwd || void 0
1468
1714
  };
1469
1715
  const info = await this.client.spawn(spawnOptions);
1470
1716
  this.sessionActive = true;
1471
1717
  this.sessionInfo = info;
1718
+ if (this.showTabs) {
1719
+ this.syncStateToActiveTab();
1720
+ }
1721
+ this.client.requestSessionList();
1472
1722
  if (this.terminal) {
1473
1723
  this.terminal.focus();
1474
1724
  }
1725
+ return info;
1475
1726
  } catch (err) {
1476
1727
  this.error = err instanceof Error ? err.message : "Failed to spawn session";
1728
+ throw err;
1477
1729
  } finally {
1478
1730
  this.loading = false;
1479
1731
  }
@@ -1486,7 +1738,12 @@ var LitShellTerminal = class extends i4 {
1486
1738
  return;
1487
1739
  await this.loadXterm();
1488
1740
  await this.updateComplete;
1489
- const container = this.shadowRoot?.querySelector(".terminal-container");
1741
+ let container = null;
1742
+ if (this.showTabs && this.activeTabId) {
1743
+ container = this.shadowRoot?.querySelector(`.tab-terminal-container[data-tab-id="${this.activeTabId}"]`) ?? null;
1744
+ } else {
1745
+ container = this.shadowRoot?.querySelector(".terminal-container") ?? null;
1746
+ }
1490
1747
  if (!container)
1491
1748
  return;
1492
1749
  const terminalTheme = this.getTerminalTheme();
@@ -1516,30 +1773,48 @@ var LitShellTerminal = class extends i4 {
1516
1773
  this.client.resize(cols, rows);
1517
1774
  }
1518
1775
  });
1519
- this.resizeObserver = new ResizeObserver(() => {
1520
- if (this.fitAddon) {
1521
- this.fitAddon.fit();
1522
- }
1523
- });
1776
+ if (!this.resizeObserver) {
1777
+ this.resizeObserver = new ResizeObserver(() => {
1778
+ if (this.showTabs) {
1779
+ const activeTab = this.getActiveTab();
1780
+ if (activeTab?.fitAddon) {
1781
+ activeTab.fitAddon.fit();
1782
+ }
1783
+ } else if (this.fitAddon) {
1784
+ this.fitAddon.fit();
1785
+ }
1786
+ });
1787
+ }
1524
1788
  this.resizeObserver.observe(container);
1789
+ if (this.showTabs) {
1790
+ this.syncStateToActiveTab();
1791
+ }
1525
1792
  }
1526
1793
  /**
1527
1794
  * Get terminal theme based on component theme
1528
1795
  */
1529
1796
  getTerminalTheme() {
1530
- if (this.theme === "light") {
1797
+ let effectiveTheme = this.theme;
1798
+ if (this.theme === "auto") {
1799
+ effectiveTheme = window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
1800
+ }
1801
+ if (effectiveTheme === "light") {
1531
1802
  return {
1532
1803
  background: "#ffffff",
1533
1804
  foreground: "#1f2937",
1534
1805
  cursor: "#1f2937",
1535
- selection: "#b4d5fe"
1806
+ cursorAccent: "#ffffff",
1807
+ selection: "#b4d5fe",
1808
+ selectionForeground: "#1f2937"
1536
1809
  };
1537
1810
  }
1538
1811
  return {
1539
1812
  background: "#1e1e1e",
1540
1813
  foreground: "#cccccc",
1541
1814
  cursor: "#ffffff",
1542
- selection: "#264f78"
1815
+ cursorAccent: "#1e1e1e",
1816
+ selection: "#264f78",
1817
+ selectionForeground: "#ffffff"
1543
1818
  };
1544
1819
  }
1545
1820
  /**
@@ -1602,83 +1877,811 @@ var LitShellTerminal = class extends i4 {
1602
1877
  }
1603
1878
  this.fitAddon = null;
1604
1879
  }
1605
- render() {
1880
+ // ==================== Tab Management Methods ====================
1881
+ /**
1882
+ * Get the active tab
1883
+ */
1884
+ getActiveTab() {
1885
+ return this.tabs.find((t4) => t4.id === this.activeTabId);
1886
+ }
1887
+ /**
1888
+ * Create a new tab
1889
+ */
1890
+ createTab(label) {
1891
+ this.tabCounter++;
1892
+ const tab = {
1893
+ id: `tab-${this.tabCounter}`,
1894
+ label: label || `Terminal ${this.tabCounter}`,
1895
+ client: null,
1896
+ terminal: null,
1897
+ fitAddon: null,
1898
+ connected: false,
1899
+ sessionActive: false,
1900
+ sessionInfo: null,
1901
+ clientCount: 1,
1902
+ containerEl: null
1903
+ };
1904
+ this.tabs = [...this.tabs, tab];
1905
+ this.switchTab(tab.id);
1906
+ return tab;
1907
+ }
1908
+ /**
1909
+ * Switch to a tab
1910
+ */
1911
+ switchTab(tabId) {
1912
+ const tab = this.tabs.find((t4) => t4.id === tabId);
1913
+ if (!tab)
1914
+ return;
1915
+ this.activeTabId = tabId;
1916
+ this.client = tab.client;
1917
+ this.terminal = tab.terminal;
1918
+ this.fitAddon = tab.fitAddon;
1919
+ this.connected = tab.connected;
1920
+ this.sessionActive = tab.sessionActive;
1921
+ this.sessionInfo = tab.sessionInfo;
1922
+ this.clientCount = tab.clientCount;
1923
+ this.updateComplete.then(() => {
1924
+ if (tab.terminal) {
1925
+ tab.terminal.focus();
1926
+ }
1927
+ if (tab.fitAddon) {
1928
+ tab.fitAddon.fit();
1929
+ }
1930
+ });
1931
+ }
1932
+ /**
1933
+ * Close a tab
1934
+ */
1935
+ closeTab(tabId) {
1936
+ const tabIndex = this.tabs.findIndex((t4) => t4.id === tabId);
1937
+ if (tabIndex === -1)
1938
+ return;
1939
+ const tab = this.tabs[tabIndex];
1940
+ if (tab.terminal) {
1941
+ tab.terminal.dispose();
1942
+ }
1943
+ if (tab.client) {
1944
+ tab.client.disconnect();
1945
+ }
1946
+ this.tabs = this.tabs.filter((t4) => t4.id !== tabId);
1947
+ if (this.activeTabId === tabId && this.tabs.length > 0) {
1948
+ const newIndex = Math.max(0, tabIndex - 1);
1949
+ this.switchTab(this.tabs[newIndex].id);
1950
+ }
1951
+ if (this.tabs.length === 0) {
1952
+ this.activeTabId = "";
1953
+ this.client = null;
1954
+ this.terminal = null;
1955
+ this.fitAddon = null;
1956
+ this.connected = false;
1957
+ this.sessionActive = false;
1958
+ this.sessionInfo = null;
1959
+ }
1960
+ }
1961
+ /**
1962
+ * Update the active tab's state from component state
1963
+ */
1964
+ syncStateToActiveTab() {
1965
+ const tab = this.getActiveTab();
1966
+ if (tab) {
1967
+ tab.client = this.client;
1968
+ tab.terminal = this.terminal;
1969
+ tab.fitAddon = this.fitAddon;
1970
+ tab.connected = this.connected;
1971
+ tab.sessionActive = this.sessionActive;
1972
+ tab.sessionInfo = this.sessionInfo;
1973
+ tab.clientCount = this.clientCount;
1974
+ this.tabs = [...this.tabs];
1975
+ }
1976
+ }
1977
+ /**
1978
+ * Render the tab bar
1979
+ */
1980
+ renderTabBar() {
1981
+ if (!this.showTabs)
1982
+ return A;
1606
1983
  return b2`
1607
- ${this.noHeader ? A : b2`
1608
- <div class="header">
1609
- <div class="header-title">
1610
- <span>Terminal</span>
1611
- ${this.sessionInfo ? b2`<span style="font-weight: normal; font-size: 12px; color: var(--ls-text-muted)">
1612
- ${this.sessionInfo.shell}
1613
- </span>` : A}
1614
- </div>
1615
- <div class="header-actions">
1616
- ${!this.connected ? b2`<button @click=${this.connect} ?disabled=${this.loading}>
1617
- ${this.loading ? "Connecting..." : "Connect"}
1618
- </button>` : !this.sessionActive ? b2`<button @click=${() => this.spawn()} ?disabled=${this.loading}>
1619
- ${this.loading ? "Spawning..." : "Start"}
1620
- </button>` : b2`<button @click=${this.kill}>Stop</button>`}
1621
- <button @click=${this.clear} ?disabled=${!this.sessionActive}>Clear</button>
1622
- <div class="status">
1623
- <span class="status-dot ${this.connected ? "connected" : ""}"></span>
1624
- <span>${this.connected ? "Connected" : "Disconnected"}</span>
1625
- </div>
1626
- </div>
1627
- </div>
1628
- `}
1629
-
1630
- <div class="terminal-container">
1631
- ${this.loading && !this.terminal ? b2`<div class="loading"><span class="loading-spinner">⏳</span> Loading...</div>` : this.error && !this.terminal ? b2`<div class="error">❌ ${this.error}</div>` : A}
1984
+ <div class="tab-bar">
1985
+ ${this.tabs.map(
1986
+ (tab) => b2`
1987
+ <button
1988
+ class="tab ${tab.id === this.activeTabId ? "active" : ""}"
1989
+ @click=${() => this.switchTab(tab.id)}
1990
+ >
1991
+ <span class="tab-status ${tab.sessionActive ? "connected" : ""}"></span>
1992
+ <span>${tab.label}</span>
1993
+ ${this.tabs.length > 1 ? b2`
1994
+ <button
1995
+ class="tab-close"
1996
+ @click=${(e5) => {
1997
+ e5.stopPropagation();
1998
+ this.closeTab(tab.id);
1999
+ }}
2000
+ title="Close tab"
2001
+ >
2002
+ ×
2003
+ </button>
2004
+ ` : A}
2005
+ </button>
2006
+ `
2007
+ )}
2008
+ <button class="tab-add" @click=${() => this.createTab()} title="New tab">
2009
+ +
2010
+ </button>
1632
2011
  </div>
1633
2012
  `;
1634
2013
  }
1635
- };
1636
- LitShellTerminal.styles = [
1637
- sharedStyles,
1638
- themeStyles,
1639
- buttonStyles,
1640
- i`
1641
- :host {
1642
- display: flex;
1643
- flex-direction: column;
1644
- height: 100%;
1645
- min-height: 200px;
1646
- border: 1px solid var(--ls-border);
1647
- border-radius: 4px;
1648
- overflow: hidden;
2014
+ // ==================== End Tab Management Methods ====================
2015
+ /**
2016
+ * Set status message
2017
+ */
2018
+ setStatus(message, type = "info") {
2019
+ this.statusMessage = message;
2020
+ this.statusType = type;
2021
+ if (type !== "error") {
2022
+ setTimeout(() => {
2023
+ if (this.statusMessage === message) {
2024
+ this.statusMessage = "";
2025
+ }
2026
+ }, 5e3);
2027
+ }
2028
+ }
2029
+ /**
2030
+ * Clear status message
2031
+ */
2032
+ clearStatus() {
2033
+ this.statusMessage = "";
2034
+ this.statusType = "info";
2035
+ }
2036
+ /**
2037
+ * Handle theme change
2038
+ */
2039
+ handleThemeChange(e5) {
2040
+ const select = e5.target;
2041
+ this.theme = select.value;
2042
+ this.applyTerminalTheme();
2043
+ this.dispatchEvent(new CustomEvent("theme-change", {
2044
+ detail: { theme: this.theme },
2045
+ bubbles: true,
2046
+ composed: true
2047
+ }));
2048
+ }
2049
+ /**
2050
+ * Apply current theme to xterm.js terminal
2051
+ */
2052
+ applyTerminalTheme() {
2053
+ if (!this.terminal)
2054
+ return;
2055
+ const terminalTheme = this.getTerminalTheme();
2056
+ this.terminal.options.theme = terminalTheme;
2057
+ }
2058
+ /**
2059
+ * Apply current font size to xterm.js terminal
2060
+ */
2061
+ applyTerminalFontSize() {
2062
+ if (!this.terminal)
2063
+ return;
2064
+ this.terminal.options.fontSize = this.fontSize;
2065
+ if (this.fitAddon) {
2066
+ this.fitAddon.fit();
2067
+ }
2068
+ }
2069
+ /**
2070
+ * Handle connection mode change
2071
+ */
2072
+ handleModeChange(e5) {
2073
+ const select = e5.target;
2074
+ this.connectionMode = select.value;
2075
+ if ((this.connectionMode === "docker" || this.connectionMode === "docker-attach") && this.client && this.connected) {
2076
+ this.client.requestContainerList();
2077
+ }
2078
+ if (this.connectionMode === "join" && this.client && this.connected) {
2079
+ this.client.requestSessionList();
2080
+ }
2081
+ }
2082
+ /**
2083
+ * Refresh session list
2084
+ */
2085
+ refreshSessions() {
2086
+ if (this.client && this.connected) {
2087
+ this.client.requestSessionList();
2088
+ }
2089
+ }
2090
+ /**
2091
+ * Join an existing session
2092
+ */
2093
+ async join(sessionId, requestHistory = true) {
2094
+ if (!this.client || !this.connected) {
2095
+ throw new Error("Not connected to server");
2096
+ }
2097
+ this.loading = true;
2098
+ this.error = null;
2099
+ try {
2100
+ await this.initTerminalUI();
2101
+ const session = await this.client.join({
2102
+ sessionId,
2103
+ requestHistory,
2104
+ historyLimit: 5e4
2105
+ });
2106
+ this.sessionActive = true;
2107
+ this.sessionInfo = {
2108
+ sessionId: session.sessionId,
2109
+ shell: session.shell,
2110
+ cwd: session.cwd,
2111
+ cols: session.cols,
2112
+ rows: session.rows,
2113
+ createdAt: session.createdAt || /* @__PURE__ */ new Date(),
2114
+ container: session.container
2115
+ };
2116
+ this.clientCount = session.clientCount;
2117
+ if (this.showTabs) {
2118
+ this.syncStateToActiveTab();
1649
2119
  }
1650
-
1651
- .header {
1652
- display: flex;
1653
- align-items: center;
1654
- justify-content: space-between;
1655
- padding: 8px 12px;
1656
- background: var(--ls-bg-header);
1657
- border-bottom: 1px solid var(--ls-border);
2120
+ this.setStatus(`Joined session (${session.clientCount} clients)`, "success");
2121
+ if (this.terminal) {
2122
+ this.terminal.focus();
1658
2123
  }
1659
-
1660
- .header-title {
1661
- display: flex;
1662
- align-items: center;
1663
- gap: 8px;
1664
- font-weight: 600;
2124
+ return session;
2125
+ } catch (err) {
2126
+ this.error = err instanceof Error ? err.message : "Failed to join session";
2127
+ throw err;
2128
+ } finally {
2129
+ this.loading = false;
2130
+ }
2131
+ }
2132
+ /**
2133
+ * Leave current session without killing it
2134
+ */
2135
+ leave() {
2136
+ if (this.client && this.sessionInfo) {
2137
+ this.client.leave(this.sessionInfo.sessionId);
2138
+ this.sessionActive = false;
2139
+ this.sessionInfo = null;
2140
+ this.setStatus("Left session", "info");
2141
+ }
2142
+ }
2143
+ /**
2144
+ * Handle connect from connection panel
2145
+ */
2146
+ async handlePanelConnect() {
2147
+ if (!this.connected) {
2148
+ await this.connect();
2149
+ }
2150
+ if (this.connected) {
2151
+ if (this.connectionMode === "join" && this.selectedSessionId) {
2152
+ await this.join(this.selectedSessionId);
2153
+ } else if (this.connectionMode === "docker-attach" && this.selectedContainer) {
2154
+ const options = {
2155
+ container: this.selectedContainer,
2156
+ attachMode: true,
2157
+ orphanTimeout: this.orphanTimeout
2158
+ };
2159
+ await this.spawn(options);
2160
+ } else {
2161
+ const options = {
2162
+ orphanTimeout: this.orphanTimeout,
2163
+ useTmux: this.useTmux
2164
+ };
2165
+ if (this.connectionMode === "docker" && this.selectedContainer) {
2166
+ options.container = this.selectedContainer;
2167
+ options.containerShell = this.selectedShell || "/bin/sh";
2168
+ } else {
2169
+ options.shell = this.selectedShell || void 0;
2170
+ }
2171
+ await this.spawn(options);
1665
2172
  }
2173
+ }
2174
+ }
2175
+ /**
2176
+ * Toggle settings menu
2177
+ */
2178
+ toggleSettingsMenu() {
2179
+ this.settingsMenuOpen = !this.settingsMenuOpen;
2180
+ }
2181
+ /**
2182
+ * Handle reconnect dialog - Yes button
2183
+ */
2184
+ async handleReconnectYes() {
2185
+ if (!this.reconnectSessionId || !this.client)
2186
+ return;
2187
+ this.showReconnectDialog = false;
2188
+ try {
2189
+ await this.initTerminalUI();
2190
+ await this.join(this.reconnectSessionId, true);
2191
+ this.setStatus("Rejoined previous session", "success");
2192
+ } catch (err) {
2193
+ this.setStatus(`Failed to rejoin: ${err instanceof Error ? err.message : "Unknown error"}`, "error");
2194
+ }
2195
+ this.reconnectSessionId = null;
2196
+ this.client?.clearPreviousSessionId();
2197
+ }
2198
+ /**
2199
+ * Handle reconnect dialog - No button
2200
+ */
2201
+ handleReconnectNo() {
2202
+ this.showReconnectDialog = false;
2203
+ this.reconnectSessionId = null;
2204
+ this.client?.clearPreviousSessionId();
2205
+ }
2206
+ /**
2207
+ * Render reconnect dialog
2208
+ */
2209
+ renderReconnectDialog() {
2210
+ if (!this.showReconnectDialog)
2211
+ return A;
2212
+ return b2`
2213
+ <div class="reconnect-dialog-overlay">
2214
+ <div class="reconnect-dialog">
2215
+ <h3>Session Available</h3>
2216
+ <p>Your previous session is still active. Would you like to rejoin?</p>
2217
+ <div class="reconnect-dialog-buttons">
2218
+ <button class="btn-primary" @click=${this.handleReconnectYes}>
2219
+ Yes, Rejoin
2220
+ </button>
2221
+ <button class="btn-secondary" @click=${this.handleReconnectNo}>
2222
+ No, Start New
2223
+ </button>
2224
+ </div>
2225
+ </div>
2226
+ </div>
2227
+ `;
2228
+ }
2229
+ /**
2230
+ * Render connection panel
2231
+ */
2232
+ renderConnectionPanel() {
2233
+ if (!this.showConnectionPanel)
2234
+ return A;
2235
+ const runningContainers = this.containers.filter((c4) => c4.state === "running");
2236
+ const acceptingSessions = this.availableSessions.filter((s4) => s4.accepting);
2237
+ return b2`
2238
+ <div class="connection-panel">
2239
+ <div class="connection-panel-title">
2240
+ <span>Connection</span>
2241
+ ${this.availableSessions.length > 0 ? b2`<span style="font-size: 11px; color: var(--ls-status-connected);">${this.availableSessions.length} session(s) available</span>` : A}
2242
+ ${this.serverInfo?.dockerEnabled ? b2`<span style="font-size: 11px; color: var(--ls-text-muted);">Docker enabled</span>` : A}
2243
+ </div>
2244
+ <div class="connection-form">
2245
+ <div class="form-group">
2246
+ <label>Mode</label>
2247
+ <select
2248
+ .value=${this.connectionMode}
2249
+ @change=${this.handleModeChange}
2250
+ ?disabled=${this.sessionActive}
2251
+ >
2252
+ <option value="local">New Local Shell</option>
2253
+ ${this.serverInfo?.dockerEnabled ? b2`
2254
+ <option value="docker">Docker Exec (new shell)</option>
2255
+ <option value="docker-attach">Docker Attach (main process)</option>
2256
+ ` : A}
2257
+ ${acceptingSessions.length > 0 ? b2`<option value="join">Join Existing Session</option>` : A}
2258
+ </select>
2259
+ </div>
1666
2260
 
1667
- .header-actions {
1668
- display: flex;
1669
- gap: 8px;
1670
- }
2261
+ ${this.connectionMode === "join" ? b2`
2262
+ <div class="form-group">
2263
+ <label style="display: flex; justify-content: space-between; align-items: center;">
2264
+ <span>Session</span>
2265
+ <button
2266
+ style="font-size: 10px; padding: 2px 6px;"
2267
+ @click=${this.refreshSessions}
2268
+ ?disabled=${!this.connected}
2269
+ >Refresh</button>
2270
+ </label>
2271
+ <select
2272
+ .value=${this.selectedSessionId}
2273
+ @change=${(e5) => this.selectedSessionId = e5.target.value}
2274
+ ?disabled=${this.sessionActive}
2275
+ >
2276
+ ${acceptingSessions.length === 0 ? b2`<option value="">No sessions available</option>` : acceptingSessions.map((s4) => b2`
2277
+ <option value=${s4.sessionId}>
2278
+ ${s4.label || s4.sessionId.substring(0, 12)}
2279
+ (${s4.type === "local" ? s4.shell : s4.container || s4.type})
2280
+ - ${s4.clientCount} client(s)
2281
+ </option>
2282
+ `)}
2283
+ </select>
2284
+ </div>
2285
+ ` : this.connectionMode === "docker" || this.connectionMode === "docker-attach" ? b2`
2286
+ <div class="form-group">
2287
+ <label>Container</label>
2288
+ <select
2289
+ .value=${this.selectedContainer}
2290
+ @change=${(e5) => this.selectedContainer = e5.target.value}
2291
+ ?disabled=${this.sessionActive}
2292
+ >
2293
+ ${runningContainers.length === 0 ? b2`<option value="">No containers running</option>` : runningContainers.map((c4) => b2`
2294
+ <option value=${c4.name}>${c4.name} (${c4.image})</option>
2295
+ `)}
2296
+ </select>
2297
+ </div>
2298
+ ` : A}
1671
2299
 
1672
- .status {
1673
- display: flex;
1674
- align-items: center;
1675
- gap: 6px;
1676
- font-size: 12px;
1677
- color: var(--ls-text-muted);
1678
- }
2300
+ ${this.connectionMode !== "join" && this.connectionMode !== "docker-attach" ? b2`
2301
+ <div class="form-group">
2302
+ <label>Shell</label>
2303
+ <select
2304
+ .value=${this.selectedShell}
2305
+ @change=${(e5) => this.selectedShell = e5.target.value}
2306
+ ?disabled=${this.sessionActive}
2307
+ >
2308
+ ${this.serverInfo?.allowedShells.length ? this.serverInfo.allowedShells.map((s4) => b2`<option value=${s4}>${s4}</option>`) : b2`
2309
+ <option value="/bin/bash">/bin/bash</option>
2310
+ <option value="/bin/sh">/bin/sh</option>
2311
+ <option value="/bin/zsh">/bin/zsh</option>
2312
+ `}
2313
+ </select>
2314
+ </div>
2315
+ ` : A}
1679
2316
 
1680
- .status-dot {
1681
- width: 8px;
2317
+ ${this.connectionMode === "local" || this.connectionMode === "docker" ? b2`
2318
+ <div class="form-group">
2319
+ <label>Session Timeout</label>
2320
+ <select
2321
+ .value=${String(this.orphanTimeout)}
2322
+ @change=${(e5) => this.orphanTimeout = parseInt(e5.target.value)}
2323
+ ?disabled=${this.sessionActive}
2324
+ >
2325
+ <option value="60000">1 minute</option>
2326
+ <option value="300000">5 minutes</option>
2327
+ <option value="900000">15 minutes</option>
2328
+ <option value="3600000">1 hour</option>
2329
+ <option value="21600000">6 hours</option>
2330
+ <option value="86400000">24 hours</option>
2331
+ <option value="604800000">1 week</option>
2332
+ </select>
2333
+ </div>
2334
+ ` : A}
2335
+
2336
+ ${this.connectionMode === "docker" ? b2`
2337
+ <div class="form-group">
2338
+ <label style="display: flex; align-items: center; gap: 6px;">
2339
+ <input
2340
+ type="checkbox"
2341
+ .checked=${this.useTmux}
2342
+ @change=${(e5) => this.useTmux = e5.target.checked}
2343
+ ?disabled=${this.sessionActive}
2344
+ />
2345
+ Use tmux (persist forever)
2346
+ </label>
2347
+ </div>
2348
+ ` : A}
2349
+
2350
+ <div class="form-group">
2351
+ ${!this.connected ? b2`<button class="btn-primary" @click=${this.handlePanelConnect} ?disabled=${this.loading}>
2352
+ ${this.loading ? "Connecting..." : "Connect"}
2353
+ </button>` : !this.sessionActive ? b2`<button class="btn-primary" @click=${this.handlePanelConnect} ?disabled=${this.loading || this.connectionMode === "join" && !this.selectedSessionId || (this.connectionMode === "docker" || this.connectionMode === "docker-attach") && !this.selectedContainer}>
2354
+ ${this.loading ? "Starting..." : this.connectionMode === "join" ? "Join Session" : this.connectionMode === "docker-attach" ? "Attach" : "Start Session"}
2355
+ </button>` : b2`<button class="btn-danger" @click=${this.kill}>
2356
+ ${this.clientCount > 1 ? "Leave Session" : "Stop Session"}
2357
+ </button>`}
2358
+ </div>
2359
+ </div>
2360
+ </div>
2361
+ `;
2362
+ }
2363
+ /**
2364
+ * Render settings dropdown
2365
+ */
2366
+ renderSettingsDropdown() {
2367
+ if (!this.showSettings)
2368
+ return A;
2369
+ return b2`
2370
+ <div class="settings-dropdown">
2371
+ <button @click=${this.toggleSettingsMenu} title="Settings">
2372
+ ⚙️
2373
+ </button>
2374
+ ${this.settingsMenuOpen ? b2`
2375
+ <div class="settings-menu">
2376
+ <div class="settings-menu-item">
2377
+ <span>Theme</span>
2378
+ <select
2379
+ .value=${this.theme}
2380
+ @change=${this.handleThemeChange}
2381
+ >
2382
+ <option value="dark">Dark</option>
2383
+ <option value="light">Light</option>
2384
+ <option value="auto">Auto</option>
2385
+ </select>
2386
+ </div>
2387
+ <div class="settings-divider"></div>
2388
+ <div class="settings-menu-item">
2389
+ <span>Font Size</span>
2390
+ <select
2391
+ .value=${String(this.fontSize)}
2392
+ @change=${(e5) => {
2393
+ this.fontSize = parseInt(e5.target.value);
2394
+ this.applyTerminalFontSize();
2395
+ }}
2396
+ >
2397
+ <option value="12">12px</option>
2398
+ <option value="14">14px</option>
2399
+ <option value="16">16px</option>
2400
+ <option value="18">18px</option>
2401
+ </select>
2402
+ </div>
2403
+ <div class="settings-divider"></div>
2404
+ <div class="settings-menu-item" @click=${this.clear}>
2405
+ <span>Clear Terminal</span>
2406
+ </div>
2407
+ </div>
2408
+ ` : A}
2409
+ </div>
2410
+ `;
2411
+ }
2412
+ /**
2413
+ * Send a special key sequence to the terminal
2414
+ */
2415
+ sendKey(key) {
2416
+ if (!this.client || !this.sessionActive)
2417
+ return;
2418
+ let sequence = key;
2419
+ if (this.ctrlPressed) {
2420
+ if (key.length === 1 && key >= "a" && key <= "z") {
2421
+ sequence = String.fromCharCode(key.charCodeAt(0) - 96);
2422
+ } else if (key.length === 1 && key >= "A" && key <= "Z") {
2423
+ sequence = String.fromCharCode(key.charCodeAt(0) - 64);
2424
+ }
2425
+ this.ctrlPressed = false;
2426
+ }
2427
+ if (this.altPressed) {
2428
+ sequence = "\x1B" + sequence;
2429
+ this.altPressed = false;
2430
+ }
2431
+ this.client.write(sequence);
2432
+ this.terminal?.focus();
2433
+ }
2434
+ /**
2435
+ * Send ANSI escape sequence
2436
+ */
2437
+ sendEscape(code) {
2438
+ if (!this.client || !this.sessionActive)
2439
+ return;
2440
+ this.client.write("\x1B" + code);
2441
+ this.terminal?.focus();
2442
+ }
2443
+ /**
2444
+ * Send control character directly
2445
+ */
2446
+ sendCtrl(char) {
2447
+ if (!this.client || !this.sessionActive)
2448
+ return;
2449
+ const code = char.toUpperCase().charCodeAt(0) - 64;
2450
+ this.client.write(String.fromCharCode(code));
2451
+ this.terminal?.focus();
2452
+ }
2453
+ /**
2454
+ * Toggle Ctrl modifier
2455
+ */
2456
+ toggleCtrl() {
2457
+ this.ctrlPressed = !this.ctrlPressed;
2458
+ this.altPressed = false;
2459
+ }
2460
+ /**
2461
+ * Toggle Alt modifier
2462
+ */
2463
+ toggleAlt() {
2464
+ this.altPressed = !this.altPressed;
2465
+ this.ctrlPressed = false;
2466
+ }
2467
+ /**
2468
+ * Toggle extra key rows visibility
2469
+ */
2470
+ toggleExtraRows() {
2471
+ this.showExtraKeyRows = !this.showExtraKeyRows;
2472
+ }
2473
+ /**
2474
+ * Toggle touch keyboard visibility
2475
+ */
2476
+ toggleTouchKeyboard() {
2477
+ this.showTouchKeyboard = !this.showTouchKeyboard;
2478
+ }
2479
+ /**
2480
+ * Render touch keyboard for mobile
2481
+ */
2482
+ renderTouchKeyboard() {
2483
+ if (!this.isMobile)
2484
+ return A;
2485
+ return b2`
2486
+ <!-- Toggle bar to show/hide keyboard -->
2487
+ <div class="touch-keyboard-toggle">
2488
+ <button @click=${this.toggleTouchKeyboard} title="${this.showTouchKeyboard ? "Hide" : "Show"} keyboard">
2489
+ ${this.showTouchKeyboard ? "\u25BC" : "\u25B2"}
2490
+ </button>
2491
+ </div>
2492
+
2493
+ ${this.showTouchKeyboard ? b2`
2494
+ <div class="touch-keyboard">
2495
+ <!-- Row 1: ESC, navigation, special -->
2496
+ <div class="touch-keyboard-row">
2497
+ <button class="touch-key" @click=${() => this.sendEscape("")}>ESC</button>
2498
+ <button class="touch-key" @click=${() => this.sendKey("/")}>/</button>
2499
+ <button class="touch-key" @click=${() => this.sendKey("-")}>-</button>
2500
+ <button class="touch-key" @click=${() => this.sendEscape("[H")}>HOME</button>
2501
+ <button class="touch-key" @click=${() => this.sendEscape("[A")}>↑</button>
2502
+ <button class="touch-key" @click=${() => this.sendEscape("[F")}>END</button>
2503
+ <button class="touch-key" @click=${() => this.sendEscape("[5~")}>PGUP</button>
2504
+ </div>
2505
+
2506
+ <!-- Row 2: TAB, modifiers, arrows -->
2507
+ <div class="touch-keyboard-row">
2508
+ <button class="touch-key" @click=${() => this.sendKey(" ")}>TAB</button>
2509
+ <button class="touch-key toggle-btn ${this.ctrlPressed ? "active" : ""}" @click=${this.toggleCtrl}>CTRL</button>
2510
+ <button class="touch-key toggle-btn ${this.altPressed ? "active" : ""}" @click=${this.toggleAlt}>ALT</button>
2511
+ <button class="touch-key" @click=${() => this.sendEscape("[D")}>←</button>
2512
+ <button class="touch-key" @click=${() => this.sendEscape("[B")}>↓</button>
2513
+ <button class="touch-key" @click=${() => this.sendEscape("[C")}>→</button>
2514
+ <button class="touch-key" @click=${() => this.sendEscape("[6~")}>PGDN</button>
2515
+ </div>
2516
+
2517
+ <!-- Expandable extra rows -->
2518
+ <div class="touch-keyboard-extra ${this.showExtraKeyRows ? "expanded" : ""}">
2519
+ <!-- Row 3: Common control sequences -->
2520
+ <div class="touch-keyboard-row">
2521
+ <button class="touch-key danger" @click=${() => this.sendCtrl("C")}>^C</button>
2522
+ <button class="touch-key" @click=${() => this.sendCtrl("D")}>^D</button>
2523
+ <button class="touch-key" @click=${() => this.sendCtrl("Z")}>^Z</button>
2524
+ <button class="touch-key" @click=${() => this.sendCtrl("L")}>^L</button>
2525
+ <button class="touch-key" @click=${() => this.sendCtrl("A")}>^A</button>
2526
+ <button class="touch-key" @click=${() => this.sendCtrl("E")}>^E</button>
2527
+ <button class="touch-key" @click=${() => this.sendCtrl("R")}>^R</button>
2528
+ </div>
2529
+ </div>
2530
+
2531
+ <!-- Toggle for extra rows -->
2532
+ <div class="touch-keyboard-row">
2533
+ <button
2534
+ class="touch-key wide"
2535
+ @click=${this.toggleExtraRows}
2536
+ title="${this.showExtraKeyRows ? "Hide" : "Show"} extra keys"
2537
+ >
2538
+ ${this.showExtraKeyRows ? "\u25B2 Less" : "\u25BC More"}
2539
+ </button>
2540
+ </div>
2541
+ </div>
2542
+ ` : A}
2543
+ `;
2544
+ }
2545
+ // ==================== End Touch Keyboard Methods ====================
2546
+ /**
2547
+ * Render status bar
2548
+ */
2549
+ renderStatusBar() {
2550
+ if (!this.showStatusBar)
2551
+ return A;
2552
+ return b2`
2553
+ <div class="status-bar">
2554
+ <div class="status-bar-left">
2555
+ <span class="status-dot ${this.connected ? "connected" : ""}"></span>
2556
+ <span>${this.connected ? this.sessionActive ? "Session active" : "Connected" : "Disconnected"}</span>
2557
+ ${this.sessionInfo ? b2`
2558
+ <span style="color: var(--ls-text-muted)">|</span>
2559
+ <span>${this.sessionInfo.container || this.sessionInfo.shell}</span>
2560
+ <span style="color: var(--ls-text-muted)">${this.sessionInfo.cols}x${this.sessionInfo.rows}</span>
2561
+ ` : A}
2562
+ </div>
2563
+ <div class="status-bar-right">
2564
+ ${this.statusMessage ? b2`
2565
+ <span class="${this.statusType === "error" ? "status-bar-error" : this.statusType === "success" ? "status-bar-success" : ""}">
2566
+ ${this.statusType === "error" ? "\u26A0\uFE0F" : this.statusType === "success" ? "\u2713" : ""}
2567
+ ${this.statusMessage}
2568
+ </span>
2569
+ <button
2570
+ style="background: none; border: none; cursor: pointer; padding: 0; font-size: 10px;"
2571
+ @click=${this.clearStatus}
2572
+ title="Dismiss"
2573
+ >✕</button>
2574
+ ` : A}
2575
+ </div>
2576
+ </div>
2577
+ `;
2578
+ }
2579
+ render() {
2580
+ return b2`
2581
+ ${this.noHeader ? A : b2`
2582
+ <div class="header">
2583
+ <div class="header-title">
2584
+ <span>Terminal</span>
2585
+ ${this.sessionInfo ? b2`<span style="font-weight: normal; font-size: 12px; color: var(--ls-text-muted)">
2586
+ ${this.sessionInfo.container ? `${this.sessionInfo.container} (${this.sessionInfo.shell})` : this.sessionInfo.shell}
2587
+ </span>` : A}
2588
+ </div>
2589
+ <div class="header-actions">
2590
+ ${!this.showConnectionPanel ? b2`
2591
+ ${!this.connected ? b2`<button @click=${this.connect} ?disabled=${this.loading}>
2592
+ ${this.loading ? "Connecting..." : "Connect"}
2593
+ </button>` : !this.sessionActive ? b2`<button @click=${() => this.spawn()} ?disabled=${this.loading}>
2594
+ ${this.loading ? "Spawning..." : "Start"}
2595
+ </button>` : b2`<button @click=${this.kill}>Stop</button>`}
2596
+ ` : A}
2597
+ <button @click=${this.clear} ?disabled=${!this.sessionActive}>Clear</button>
2598
+ ${this.renderSettingsDropdown()}
2599
+ ${!this.showStatusBar ? b2`
2600
+ <div class="status">
2601
+ <span class="status-dot ${this.connected ? "connected" : ""}"></span>
2602
+ <span>${this.connected ? "Connected" : "Disconnected"}</span>
2603
+ </div>
2604
+ ` : A}
2605
+ </div>
2606
+ </div>
2607
+ `}
2608
+
2609
+ ${this.renderConnectionPanel()}
2610
+ ${this.renderTabBar()}
2611
+
2612
+ ${this.showTabs && this.tabs.length > 0 ? b2`
2613
+ <div class="terminals-wrapper">
2614
+ ${this.tabs.map(
2615
+ (tab) => b2`
2616
+ <div
2617
+ class="tab-terminal-container ${tab.id === this.activeTabId ? "active" : ""}"
2618
+ data-tab-id=${tab.id}
2619
+ >
2620
+ ${this.loading && tab.id === this.activeTabId && !tab.terminal ? b2`<div class="loading"><span class="loading-spinner">⏳</span> Loading...</div>` : this.error && tab.id === this.activeTabId && !tab.terminal ? b2`<div class="error">❌ ${this.error}</div>` : A}
2621
+ </div>
2622
+ `
2623
+ )}
2624
+ </div>
2625
+ ` : b2`
2626
+ <div class="terminal-container">
2627
+ ${this.loading && !this.terminal ? b2`<div class="loading"><span class="loading-spinner">⏳</span> Loading...</div>` : this.error && !this.terminal ? b2`<div class="error">❌ ${this.error}</div>` : A}
2628
+ </div>
2629
+ `}
2630
+
2631
+ ${this.renderStatusBar()}
2632
+ ${this.renderTouchKeyboard()}
2633
+ ${this.renderReconnectDialog()}
2634
+ `;
2635
+ }
2636
+ };
2637
+ /** lit-shell.js version */
2638
+ LitShellTerminal.VERSION = VERSION;
2639
+ LitShellTerminal.styles = [
2640
+ sharedStyles,
2641
+ themeStyles,
2642
+ buttonStyles,
2643
+ i`
2644
+ :host {
2645
+ display: flex;
2646
+ flex-direction: column;
2647
+ height: 100%;
2648
+ min-height: 200px;
2649
+ border: 1px solid var(--ls-border);
2650
+ border-radius: 4px;
2651
+ overflow: hidden;
2652
+ }
2653
+
2654
+ .header {
2655
+ display: flex;
2656
+ align-items: center;
2657
+ justify-content: space-between;
2658
+ padding: 8px 12px;
2659
+ background: var(--ls-bg-header);
2660
+ border-bottom: 1px solid var(--ls-border);
2661
+ }
2662
+
2663
+ .header-title {
2664
+ display: flex;
2665
+ align-items: center;
2666
+ gap: 8px;
2667
+ font-weight: 600;
2668
+ }
2669
+
2670
+ .header-actions {
2671
+ display: flex;
2672
+ gap: 8px;
2673
+ }
2674
+
2675
+ .status {
2676
+ display: flex;
2677
+ align-items: center;
2678
+ gap: 6px;
2679
+ font-size: 12px;
2680
+ color: var(--ls-text-muted);
2681
+ }
2682
+
2683
+ .status-dot {
2684
+ width: 8px;
1682
2685
  height: 8px;
1683
2686
  border-radius: 50%;
1684
2687
  background: var(--ls-status-disconnected);
@@ -1736,6 +2739,441 @@ LitShellTerminal.styles = [
1736
2739
  :host([no-header]) .header {
1737
2740
  display: none;
1738
2741
  }
2742
+
2743
+ /* Connection panel */
2744
+ .connection-panel {
2745
+ padding: 12px;
2746
+ background: var(--ls-bg-header);
2747
+ border-bottom: 1px solid var(--ls-border);
2748
+ }
2749
+
2750
+ .connection-panel-title {
2751
+ font-weight: 600;
2752
+ margin-bottom: 12px;
2753
+ display: flex;
2754
+ align-items: center;
2755
+ gap: 8px;
2756
+ }
2757
+
2758
+ .connection-form {
2759
+ display: grid;
2760
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
2761
+ gap: 10px;
2762
+ align-items: end;
2763
+ }
2764
+
2765
+ .form-group {
2766
+ display: flex;
2767
+ flex-direction: column;
2768
+ gap: 4px;
2769
+ }
2770
+
2771
+ .form-group label {
2772
+ font-size: 11px;
2773
+ text-transform: uppercase;
2774
+ color: var(--ls-text-muted);
2775
+ letter-spacing: 0.5px;
2776
+ }
2777
+
2778
+ .form-group select,
2779
+ .form-group input {
2780
+ padding: 6px 10px;
2781
+ border: 1px solid var(--ls-border);
2782
+ border-radius: 4px;
2783
+ background: var(--ls-bg);
2784
+ color: var(--ls-text);
2785
+ font-size: 13px;
2786
+ }
2787
+
2788
+ .form-group select:focus,
2789
+ .form-group input:focus {
2790
+ outline: none;
2791
+ border-color: var(--ls-status-connected);
2792
+ }
2793
+
2794
+ /* Settings dropdown */
2795
+ .settings-dropdown {
2796
+ position: relative;
2797
+ }
2798
+
2799
+ .settings-menu {
2800
+ position: absolute;
2801
+ top: 100%;
2802
+ right: 0;
2803
+ margin-top: 4px;
2804
+ min-width: 180px;
2805
+ background: var(--ls-bg-header);
2806
+ border: 1px solid var(--ls-border);
2807
+ border-radius: 4px;
2808
+ box-shadow: 0 4px 12px rgba(0,0,0,0.3);
2809
+ z-index: 100;
2810
+ padding: 8px 0;
2811
+ }
2812
+
2813
+ .settings-menu-item {
2814
+ display: flex;
2815
+ align-items: center;
2816
+ justify-content: space-between;
2817
+ padding: 8px 12px;
2818
+ font-size: 13px;
2819
+ cursor: pointer;
2820
+ }
2821
+
2822
+ .settings-menu-item:hover {
2823
+ background: var(--ls-btn-hover);
2824
+ }
2825
+
2826
+ .settings-menu-item select {
2827
+ padding: 4px 8px;
2828
+ border: 1px solid var(--ls-border);
2829
+ border-radius: 3px;
2830
+ background: var(--ls-bg);
2831
+ color: var(--ls-text);
2832
+ font-size: 12px;
2833
+ }
2834
+
2835
+ .settings-divider {
2836
+ height: 1px;
2837
+ background: var(--ls-border);
2838
+ margin: 4px 0;
2839
+ }
2840
+
2841
+ /* Status bar */
2842
+ .status-bar {
2843
+ display: flex;
2844
+ align-items: center;
2845
+ justify-content: space-between;
2846
+ padding: 4px 12px;
2847
+ background: var(--ls-bg-header);
2848
+ border-top: 1px solid var(--ls-border);
2849
+ font-size: 12px;
2850
+ color: var(--ls-text-muted);
2851
+ }
2852
+
2853
+ .status-bar-left {
2854
+ display: flex;
2855
+ align-items: center;
2856
+ gap: 12px;
2857
+ }
2858
+
2859
+ .status-bar-right {
2860
+ display: flex;
2861
+ align-items: center;
2862
+ gap: 8px;
2863
+ }
2864
+
2865
+ .status-bar-error {
2866
+ color: #ef4444;
2867
+ display: flex;
2868
+ align-items: center;
2869
+ gap: 4px;
2870
+ }
2871
+
2872
+ .status-bar-success {
2873
+ color: var(--ls-status-connected);
2874
+ }
2875
+
2876
+ /* Tab bar styles */
2877
+ .tab-bar {
2878
+ display: flex;
2879
+ align-items: center;
2880
+ background: var(--ls-bg-header);
2881
+ border-bottom: 1px solid var(--ls-border);
2882
+ padding: 0 4px;
2883
+ gap: 2px;
2884
+ min-height: 32px;
2885
+ overflow-x: auto;
2886
+ }
2887
+
2888
+ .tab-bar::-webkit-scrollbar {
2889
+ height: 4px;
2890
+ }
2891
+
2892
+ .tab-bar::-webkit-scrollbar-thumb {
2893
+ background: var(--ls-border);
2894
+ border-radius: 2px;
2895
+ }
2896
+
2897
+ .tab {
2898
+ display: flex;
2899
+ align-items: center;
2900
+ gap: 6px;
2901
+ padding: 6px 12px;
2902
+ background: transparent;
2903
+ border: none;
2904
+ border-bottom: 2px solid transparent;
2905
+ color: var(--ls-text-muted);
2906
+ font-size: 12px;
2907
+ cursor: pointer;
2908
+ white-space: nowrap;
2909
+ transition: all 0.15s ease;
2910
+ }
2911
+
2912
+ .tab:hover {
2913
+ background: var(--ls-btn-hover);
2914
+ color: var(--ls-text);
2915
+ }
2916
+
2917
+ .tab.active {
2918
+ color: var(--ls-text);
2919
+ border-bottom-color: var(--ls-status-connected);
2920
+ background: var(--ls-bg);
2921
+ }
2922
+
2923
+ .tab-status {
2924
+ width: 6px;
2925
+ height: 6px;
2926
+ border-radius: 50%;
2927
+ background: var(--ls-status-disconnected);
2928
+ }
2929
+
2930
+ .tab-status.connected {
2931
+ background: var(--ls-status-connected);
2932
+ }
2933
+
2934
+ .tab-close {
2935
+ display: flex;
2936
+ align-items: center;
2937
+ justify-content: center;
2938
+ width: 16px;
2939
+ height: 16px;
2940
+ border-radius: 3px;
2941
+ background: none;
2942
+ border: none;
2943
+ color: var(--ls-text-muted);
2944
+ font-size: 14px;
2945
+ cursor: pointer;
2946
+ opacity: 0;
2947
+ transition: opacity 0.15s ease;
2948
+ }
2949
+
2950
+ .tab:hover .tab-close {
2951
+ opacity: 1;
2952
+ }
2953
+
2954
+ .tab-close:hover {
2955
+ background: var(--ls-btn-hover);
2956
+ color: var(--ls-text);
2957
+ }
2958
+
2959
+ .tab-add {
2960
+ display: flex;
2961
+ align-items: center;
2962
+ justify-content: center;
2963
+ width: 28px;
2964
+ height: 28px;
2965
+ border-radius: 4px;
2966
+ background: none;
2967
+ border: none;
2968
+ color: var(--ls-text-muted);
2969
+ font-size: 18px;
2970
+ cursor: pointer;
2971
+ margin-left: 4px;
2972
+ }
2973
+
2974
+ .tab-add:hover {
2975
+ background: var(--ls-btn-hover);
2976
+ color: var(--ls-text);
2977
+ }
2978
+
2979
+ /* Multi-terminal container */
2980
+ .terminals-wrapper {
2981
+ flex: 1;
2982
+ position: relative;
2983
+ overflow: hidden;
2984
+ }
2985
+
2986
+ .tab-terminal-container {
2987
+ position: absolute;
2988
+ top: 0;
2989
+ left: 0;
2990
+ right: 0;
2991
+ bottom: 0;
2992
+ padding: 4px;
2993
+ background: var(--ls-terminal-bg);
2994
+ display: none;
2995
+ }
2996
+
2997
+ .tab-terminal-container.active {
2998
+ display: block;
2999
+ }
3000
+
3001
+ .tab-terminal-container .xterm {
3002
+ height: 100%;
3003
+ }
3004
+
3005
+ /* Reconnect dialog */
3006
+ .reconnect-dialog-overlay {
3007
+ position: absolute;
3008
+ top: 0;
3009
+ left: 0;
3010
+ right: 0;
3011
+ bottom: 0;
3012
+ background: rgba(0, 0, 0, 0.7);
3013
+ display: flex;
3014
+ align-items: center;
3015
+ justify-content: center;
3016
+ z-index: 1000;
3017
+ }
3018
+
3019
+ .reconnect-dialog {
3020
+ background: var(--ls-bg-header);
3021
+ border: 1px solid var(--ls-border);
3022
+ border-radius: 8px;
3023
+ padding: 24px;
3024
+ max-width: 320px;
3025
+ text-align: center;
3026
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
3027
+ }
3028
+
3029
+ .reconnect-dialog h3 {
3030
+ margin: 0 0 12px 0;
3031
+ font-size: 16px;
3032
+ color: var(--ls-text);
3033
+ }
3034
+
3035
+ .reconnect-dialog p {
3036
+ margin: 0 0 20px 0;
3037
+ font-size: 14px;
3038
+ color: var(--ls-text-muted);
3039
+ }
3040
+
3041
+ .reconnect-dialog-buttons {
3042
+ display: flex;
3043
+ gap: 12px;
3044
+ justify-content: center;
3045
+ }
3046
+
3047
+ .reconnect-dialog-buttons button {
3048
+ padding: 8px 20px;
3049
+ border-radius: 4px;
3050
+ font-size: 14px;
3051
+ cursor: pointer;
3052
+ transition: background 0.15s ease;
3053
+ }
3054
+
3055
+ .reconnect-dialog-buttons .btn-primary {
3056
+ background: var(--ls-status-connected);
3057
+ color: white;
3058
+ border: none;
3059
+ }
3060
+
3061
+ .reconnect-dialog-buttons .btn-primary:hover {
3062
+ background: #1da34d;
3063
+ }
3064
+
3065
+ .reconnect-dialog-buttons .btn-secondary {
3066
+ background: transparent;
3067
+ color: var(--ls-text-muted);
3068
+ border: 1px solid var(--ls-border);
3069
+ }
3070
+
3071
+ .reconnect-dialog-buttons .btn-secondary:hover {
3072
+ background: var(--ls-btn-hover);
3073
+ color: var(--ls-text);
3074
+ }
3075
+
3076
+ /* Touch keyboard for mobile */
3077
+ .touch-keyboard {
3078
+ display: flex;
3079
+ flex-direction: column;
3080
+ background: var(--ls-bg-header);
3081
+ border-top: 1px solid var(--ls-border);
3082
+ padding: 4px;
3083
+ gap: 4px;
3084
+ }
3085
+
3086
+ .touch-keyboard-row {
3087
+ display: flex;
3088
+ gap: 4px;
3089
+ justify-content: center;
3090
+ }
3091
+
3092
+ .touch-key {
3093
+ display: flex;
3094
+ align-items: center;
3095
+ justify-content: center;
3096
+ min-width: 40px;
3097
+ height: 36px;
3098
+ padding: 0 8px;
3099
+ background: var(--ls-bg);
3100
+ border: 1px solid var(--ls-border);
3101
+ border-radius: 4px;
3102
+ color: var(--ls-text);
3103
+ font-size: 12px;
3104
+ font-family: inherit;
3105
+ cursor: pointer;
3106
+ touch-action: manipulation;
3107
+ user-select: none;
3108
+ -webkit-user-select: none;
3109
+ transition: background 0.1s ease;
3110
+ }
3111
+
3112
+ .touch-key:active {
3113
+ background: var(--ls-btn-hover);
3114
+ }
3115
+
3116
+ .touch-key.wide {
3117
+ min-width: 50px;
3118
+ }
3119
+
3120
+ .touch-key.danger {
3121
+ color: #ef4444;
3122
+ border-color: #ef4444;
3123
+ }
3124
+
3125
+ .touch-key.toggle-btn {
3126
+ background: var(--ls-bg-header);
3127
+ min-width: 30px;
3128
+ font-size: 14px;
3129
+ }
3130
+
3131
+ .touch-key.toggle-btn.active {
3132
+ background: var(--ls-status-connected);
3133
+ color: white;
3134
+ border-color: var(--ls-status-connected);
3135
+ }
3136
+
3137
+ .touch-keyboard-toggle {
3138
+ display: flex;
3139
+ align-items: center;
3140
+ justify-content: center;
3141
+ padding: 2px;
3142
+ background: var(--ls-bg-header);
3143
+ border-top: 1px solid var(--ls-border);
3144
+ }
3145
+
3146
+ .touch-keyboard-toggle button {
3147
+ background: none;
3148
+ border: none;
3149
+ color: var(--ls-text-muted);
3150
+ font-size: 18px;
3151
+ padding: 4px 12px;
3152
+ cursor: pointer;
3153
+ }
3154
+
3155
+ .touch-keyboard-toggle button:active {
3156
+ color: var(--ls-text);
3157
+ }
3158
+
3159
+ /* Extra key rows (collapsible) */
3160
+ .touch-keyboard-extra {
3161
+ overflow: hidden;
3162
+ max-height: 0;
3163
+ opacity: 0;
3164
+ transition: max-height 0.2s ease, opacity 0.2s ease;
3165
+ }
3166
+
3167
+ .touch-keyboard-extra.expanded {
3168
+ max-height: 100px;
3169
+ opacity: 1;
3170
+ }
3171
+
3172
+ /* Hide touch keyboard on desktop */
3173
+ :host(:not([mobile])) .touch-keyboard,
3174
+ :host(:not([mobile])) .touch-keyboard-toggle {
3175
+ display: none;
3176
+ }
1739
3177
  `
1740
3178
  ];
1741
3179
  __decorateClass([
@@ -1765,6 +3203,30 @@ __decorateClass([
1765
3203
  __decorateClass([
1766
3204
  n4({ type: Boolean, attribute: "auto-spawn" })
1767
3205
  ], LitShellTerminal.prototype, "autoSpawn", 2);
3206
+ __decorateClass([
3207
+ n4({ type: String })
3208
+ ], LitShellTerminal.prototype, "container", 2);
3209
+ __decorateClass([
3210
+ n4({ type: String, attribute: "container-shell" })
3211
+ ], LitShellTerminal.prototype, "containerShell", 2);
3212
+ __decorateClass([
3213
+ n4({ type: String, attribute: "container-user" })
3214
+ ], LitShellTerminal.prototype, "containerUser", 2);
3215
+ __decorateClass([
3216
+ n4({ type: String, attribute: "container-cwd" })
3217
+ ], LitShellTerminal.prototype, "containerCwd", 2);
3218
+ __decorateClass([
3219
+ n4({ type: Boolean, attribute: "show-connection-panel" })
3220
+ ], LitShellTerminal.prototype, "showConnectionPanel", 2);
3221
+ __decorateClass([
3222
+ n4({ type: Boolean, attribute: "show-settings" })
3223
+ ], LitShellTerminal.prototype, "showSettings", 2);
3224
+ __decorateClass([
3225
+ n4({ type: Boolean, attribute: "show-status-bar" })
3226
+ ], LitShellTerminal.prototype, "showStatusBar", 2);
3227
+ __decorateClass([
3228
+ n4({ type: Boolean, attribute: "show-tabs" })
3229
+ ], LitShellTerminal.prototype, "showTabs", 2);
1768
3230
  __decorateClass([
1769
3231
  n4({ type: Number, attribute: "font-size" })
1770
3232
  ], LitShellTerminal.prototype, "fontSize", 2);
@@ -1795,11 +3257,78 @@ __decorateClass([
1795
3257
  __decorateClass([
1796
3258
  r5()
1797
3259
  ], LitShellTerminal.prototype, "sessionInfo", 2);
3260
+ __decorateClass([
3261
+ r5()
3262
+ ], LitShellTerminal.prototype, "containers", 2);
3263
+ __decorateClass([
3264
+ r5()
3265
+ ], LitShellTerminal.prototype, "serverInfo", 2);
3266
+ __decorateClass([
3267
+ r5()
3268
+ ], LitShellTerminal.prototype, "selectedContainer", 2);
3269
+ __decorateClass([
3270
+ r5()
3271
+ ], LitShellTerminal.prototype, "selectedShell", 2);
3272
+ __decorateClass([
3273
+ r5()
3274
+ ], LitShellTerminal.prototype, "connectionMode", 2);
3275
+ __decorateClass([
3276
+ r5()
3277
+ ], LitShellTerminal.prototype, "orphanTimeout", 2);
3278
+ __decorateClass([
3279
+ r5()
3280
+ ], LitShellTerminal.prototype, "useTmux", 2);
3281
+ __decorateClass([
3282
+ r5()
3283
+ ], LitShellTerminal.prototype, "availableSessions", 2);
3284
+ __decorateClass([
3285
+ r5()
3286
+ ], LitShellTerminal.prototype, "selectedSessionId", 2);
3287
+ __decorateClass([
3288
+ r5()
3289
+ ], LitShellTerminal.prototype, "clientCount", 2);
3290
+ __decorateClass([
3291
+ r5()
3292
+ ], LitShellTerminal.prototype, "settingsMenuOpen", 2);
3293
+ __decorateClass([
3294
+ r5()
3295
+ ], LitShellTerminal.prototype, "showReconnectDialog", 2);
3296
+ __decorateClass([
3297
+ r5()
3298
+ ], LitShellTerminal.prototype, "reconnectSessionId", 2);
3299
+ __decorateClass([
3300
+ r5()
3301
+ ], LitShellTerminal.prototype, "isMobile", 2);
3302
+ __decorateClass([
3303
+ r5()
3304
+ ], LitShellTerminal.prototype, "showTouchKeyboard", 2);
3305
+ __decorateClass([
3306
+ r5()
3307
+ ], LitShellTerminal.prototype, "showExtraKeyRows", 2);
3308
+ __decorateClass([
3309
+ r5()
3310
+ ], LitShellTerminal.prototype, "statusMessage", 2);
3311
+ __decorateClass([
3312
+ r5()
3313
+ ], LitShellTerminal.prototype, "statusType", 2);
3314
+ __decorateClass([
3315
+ r5()
3316
+ ], LitShellTerminal.prototype, "tabs", 2);
3317
+ __decorateClass([
3318
+ r5()
3319
+ ], LitShellTerminal.prototype, "activeTabId", 2);
3320
+ __decorateClass([
3321
+ r5()
3322
+ ], LitShellTerminal.prototype, "ctrlPressed", 2);
3323
+ __decorateClass([
3324
+ r5()
3325
+ ], LitShellTerminal.prototype, "altPressed", 2);
1798
3326
  LitShellTerminal = __decorateClass([
1799
3327
  t3("lit-shell-terminal")
1800
3328
  ], LitShellTerminal);
1801
3329
  export {
1802
3330
  LitShellTerminal,
3331
+ VERSION,
1803
3332
  buttonStyles,
1804
3333
  sharedStyles,
1805
3334
  themeStyles