@cplieger/web-terminal-ui 8.1.0 → 8.1.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.
package/README.md CHANGED
@@ -488,15 +488,17 @@ Three ways in, all in the tab chrome, none a key chord:
488
488
  Shift+F10 on the focused tab, where the browser raises the menu event; Safari
489
489
  binds no key to it, so a keyboard-only person there reaches the menu through
490
490
  an assistive technology's own command). A snapped tab shows on the named side
491
- and its pane is selected. A tab the other pane shows trades places with the
492
- tab on the named side, so both stay on screen (into an empty pane it simply
493
- moves); any other tab there becomes an ordinary tab. Snapping a tab onto the
494
- side that already shows it changes nothing.
491
+ and its pane is selected. When the snap opens the split, the other pane keeps
492
+ the tab the single view showed (or, when that is the tab being moved, its
493
+ split partner), so neither pane opens empty. A tab the other pane shows trades
494
+ places with the tab on the named side, so both stay on screen (into an empty
495
+ pane it simply moves); any other tab there becomes an ordinary tab. Snapping a
496
+ tab onto the side that already shows it changes nothing.
495
497
  - **Dragging a tab** out of the row onto either pane of the terminal area,
496
498
  following the same rule. While the drag lasts the pane under the pointer is
497
- highlighted; the two highlights match the panes' widths at the divider's
498
- position (the two halves while the split is closed), and a release decides
499
- by the divider's centre.
499
+ highlighted; each highlight covers that pane's terminal area at the divider's
500
+ position (the two halves while the split is closed), inset a little with the
501
+ tab chips' rounded border, and a release decides by the divider's centre.
500
502
 
501
503
  A tab click follows one rule: an empty pane fills first, otherwise the selected
502
504
  pane's tab is replaced. A new tab ("+") also fills an empty pane first, but with
package/css/32-split.css CHANGED
@@ -11,6 +11,12 @@
11
11
  calc((100% - var(--wt-split-gutter)) * var(--wt-split-ratio)),
12
12
  1px
13
13
  );
14
+ /* Content's bottom edge: tab bar plus safe area, or switcher reserve plus
15
+ keyboard, whichever is lower. Shared by the divider and the drop halves. */
16
+ --wt-split-bottom: max(
17
+ calc(var(--wt-tabbar-h, 0px) + var(--safe-bottom, 0px)),
18
+ calc(var(--wt-reserve-bottom, 0px) + var(--kb-inset, 0px))
19
+ );
14
20
 
15
21
  display: grid;
16
22
  grid-template-columns: minmax(0, 1fr);
@@ -107,10 +113,7 @@
107
113
  justify-self: center;
108
114
  inline-size: 24px;
109
115
  margin-inline: -7px;
110
- margin-block-end: max(
111
- calc(var(--wt-tabbar-h, 0px) + var(--safe-bottom, 0px)),
112
- calc(var(--wt-reserve-bottom, 0px) + var(--kb-inset, 0px))
113
- );
116
+ margin-block-end: var(--wt-split-bottom);
114
117
  cursor: col-resize;
115
118
  touch-action: none;
116
119
  z-index: 4;
@@ -193,23 +196,30 @@
193
196
  pointer-events: none;
194
197
  }
195
198
 
199
+ /* Confined to each pane's terminal box (--vv-top to --wt-split-bottom) so
200
+ neither half runs under the translucent strip. The --sp-2 inset is the
201
+ strip's own chip padding; the border reuses the active chip's tokens because
202
+ a half means "this pane will show the tab". */
196
203
  :where(.wt-root.wt-split.wt-drop-left)::before,
197
204
  :where(.wt-root.wt-split.wt-drop-right)::after {
198
205
  content: "";
199
206
  position: absolute;
200
- inset-block: 0 var(--wt-tabbar-h, 0);
207
+ box-sizing: border-box;
208
+ inset-block: calc(var(--vv-top, 0px) + var(--sp-2)) calc(var(--wt-split-bottom) + var(--sp-2));
201
209
  background: color-mix(in oklch, var(--accent) 12%, transparent);
210
+ border: 1px solid var(--tab-active-border);
211
+ border-radius: var(--radius-inner);
202
212
  pointer-events: none;
203
213
  z-index: 5;
204
214
  }
205
215
 
206
216
  :where(.wt-root.wt-split.wt-drop-left)::before {
207
- inset-inline-start: 0;
208
- inline-size: var(--wt-split-left);
217
+ inset-inline-start: var(--sp-2);
218
+ inline-size: calc(var(--wt-split-left) - 2 * var(--sp-2));
209
219
  }
210
220
 
211
221
  :where(.wt-root.wt-split.wt-drop-right)::after {
212
- inset-inline: calc(var(--wt-split-left) + var(--wt-split-gutter)) 0;
222
+ inset-inline: calc(var(--wt-split-left) + var(--wt-split-gutter) + var(--sp-2)) var(--sp-2);
213
223
  }
214
224
 
215
225
  :where(.wt-root.wt-split.wt-animate.wt-split-opening),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cplieger/web-terminal-ui",
3
- "version": "8.1.0",
3
+ "version": "8.1.1",
4
4
  "type": "module",
5
5
  "description": "Reference touch-first browser UI for @cplieger/web-terminal-engine: a composable kernel + feature modules (tabs, activity monitor, mobile toolbar, context menu, IME, predictive echo, viewport handling) and CSS. One createTerminal() call.",
6
6
  "license": "MPL-2.0",
@@ -106,10 +106,12 @@ export interface TabsApi {
106
106
  * pane already shows has that pane selected instead. */
107
107
  switchTo(id: string): void;
108
108
  /** Show a tab on one side of the split, opening it when closed, and select its
109
- * pane. A tab the other pane shows trades places with the tab on that side
110
- * (or leaves its pane empty when the side showed none); any other tab there
111
- * becomes ordinary. A tab already shown on that side changes nothing. False
112
- * when refused: an unknown id, an invalid side, or a split the pane row is too
109
+ * pane. Opening the split keeps the single view's tab on the other side — or,
110
+ * when the moved tab IS it, its split partner — so neither pane opens empty. A
111
+ * tab the other pane shows trades places with the tab on that side (or leaves
112
+ * its pane empty when the side showed none); any other tab there becomes
113
+ * ordinary. A tab already shown on that side changes nothing. False when
114
+ * refused: an unknown id, an invalid side, or a split the pane row is too
113
115
  * narrow to open. */
114
116
  snap(id: string, side: PaneSide): boolean;
115
117
  /** The current tabs, first-to-last by creation; `active` marks a tab a pane
@@ -1678,17 +1680,27 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
1678
1680
  return true;
1679
1681
  }
1680
1682
  const split = ctx.shell.split;
1681
- if (!split.isOpen() && (!split.canOpen() || !split.open())) {
1683
+ const opening = !split.isOpen();
1684
+ if (opening && (!split.canOpen() || !split.open())) {
1682
1685
  return false;
1683
1686
  }
1684
1687
  // Emptying the tab's old pane drops a keyboard its input held to the body.
1685
1688
  const typing = keyboardInPaneInput();
1686
- const displaced = from === null ? null : shownIn(side);
1689
+ const displaced = shownIn(side);
1687
1690
  if (!showIn(side, id)) {
1688
1691
  return false;
1689
1692
  }
1690
- if (from !== null && displaced !== null) {
1691
- showIn(from, displaced, { keepSelection: true });
1693
+ // Opening pairs the moved tab with the single view's tab, or its partner
1694
+ // when they are one, so no pane opens empty; an open split only re-homes
1695
+ // the displaced tab, into a pane the move emptied.
1696
+ const back = displaced ?? (opening ? splitPartner(id) : null);
1697
+ if (
1698
+ (from !== null || opening) &&
1699
+ back !== null &&
1700
+ back !== id &&
1701
+ shownIn(otherSide(side)) === null
1702
+ ) {
1703
+ showIn(otherSide(side), back, { keepSelection: true });
1692
1704
  }
1693
1705
  if (typing) {
1694
1706
  focusInput();
@@ -1702,20 +1714,19 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
1702
1714
  split.close();
1703
1715
  return;
1704
1716
  }
1705
- const partner = splitPartner();
1717
+ const partner = splitPartner(selectedId());
1706
1718
  if (partner === null) {
1707
1719
  void create(true);
1708
1720
  } else if (split.open()) {
1709
1721
  showIn("right", partner, { keepSelection: true });
1710
1722
  }
1711
1723
  }
1712
- function splitPartner(): string | null {
1713
- const current = selectedId();
1714
- const used = recent.find((id) => id !== current && tabList.some((t) => t.id === id));
1724
+ function splitPartner(of: string | null): string | null {
1725
+ const used = recent.find((id) => id !== of && tabList.some((t) => t.id === id));
1715
1726
  if (used !== undefined) {
1716
1727
  return used;
1717
1728
  }
1718
- const at = tabList.findIndex((t) => t.id === current);
1729
+ const at = tabList.findIndex((t) => t.id === of);
1719
1730
  return (tabList[at + 1] ?? tabList[at - 1])?.id ?? null;
1720
1731
  }
1721
1732