@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 +9 -7
- package/css/32-split.css +18 -8
- package/package.json +1 -1
- package/src/features/tabs/index.ts +24 -13
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.
|
|
492
|
-
tab
|
|
493
|
-
|
|
494
|
-
side
|
|
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;
|
|
498
|
-
position (the two halves while the split is closed),
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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))
|
|
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.
|
|
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.
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
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
|
-
|
|
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 =
|
|
1689
|
+
const displaced = shownIn(side);
|
|
1687
1690
|
if (!showIn(side, id)) {
|
|
1688
1691
|
return false;
|
|
1689
1692
|
}
|
|
1690
|
-
|
|
1691
|
-
|
|
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
|
|
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 ===
|
|
1729
|
+
const at = tabList.findIndex((t) => t.id === of);
|
|
1719
1730
|
return (tabList[at + 1] ?? tabList[at - 1])?.id ?? null;
|
|
1720
1731
|
}
|
|
1721
1732
|
|