@cplieger/web-terminal-ui 8.1.0 → 8.1.2
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 +10 -8
- package/css/00-tokens.css +1 -1
- package/css/30-tabs.css +66 -80
- package/css/32-split.css +18 -8
- package/package.json +1 -1
- package/src/features/tabs/index.ts +24 -13
package/README.md
CHANGED
|
@@ -164,7 +164,7 @@ are importable from `…/features/<name>` (`clipboard`, `context-menu`,
|
|
|
164
164
|
background activity that outlives a turn (a workflow run, in the
|
|
165
165
|
`web-terminal-kiro` case). It is a rounded-square ring rather than a disc, so
|
|
166
166
|
it does not read as another status dot, and band width carries its three
|
|
167
|
-
states: a 2px band
|
|
167
|
+
states: a 2px band whose centre closes and reopens while a background task is running, a
|
|
168
168
|
1px band with a halo while one is paused and resumable, and a 2px band with
|
|
169
169
|
that same halo while one is blocked on the user. The state and the count are
|
|
170
170
|
announced in the tab's accessible name and repeated as the mark's hover
|
|
@@ -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/00-tokens.css
CHANGED
|
@@ -178,7 +178,7 @@
|
|
|
178
178
|
with nothing enforcing it.
|
|
179
179
|
Shared with marotte's `--dot-beat-dur` (01-tokens.css) for the reason the
|
|
180
180
|
--status-* inks are: one signal across the two apps, beat included. */
|
|
181
|
-
--dot-beat-dur: 1.
|
|
181
|
+
--dot-beat-dur: 1.6s;
|
|
182
182
|
--dur-standard: 0.2s;
|
|
183
183
|
--dur-enter: 0.25s;
|
|
184
184
|
--dur-exit: 0.15s;
|
package/css/30-tabs.css
CHANGED
|
@@ -993,7 +993,8 @@
|
|
|
993
993
|
three states the channel is BAND WIDTH, in a deliberate 2:1 ratio, because
|
|
994
994
|
waiting and input share one ink.
|
|
995
995
|
|
|
996
|
-
A
|
|
996
|
+
A 3px radius of its own (--wt-mark-radius, which the working state's clip reads
|
|
997
|
+
too), not --radius-sm: the smallest token rung is 4px, which on
|
|
997
998
|
a 9px box reads as a circle and defeats the silhouette.
|
|
998
999
|
|
|
999
1000
|
It costs ZERO layout while no state is set. The footprint cancel is a negative
|
|
@@ -1010,12 +1011,13 @@
|
|
|
1010
1011
|
/* waiting and input both speak through this ink and separate by band alone;
|
|
1011
1012
|
working overrides it. */
|
|
1012
1013
|
--wt-mark-ink: var(--status-input);
|
|
1014
|
+
--wt-mark-radius: 3px;
|
|
1013
1015
|
|
|
1014
1016
|
width: var(--wt-mark-size);
|
|
1015
1017
|
height: var(--wt-mark-size);
|
|
1016
1018
|
flex: none;
|
|
1017
1019
|
position: relative;
|
|
1018
|
-
border-radius:
|
|
1020
|
+
border-radius: var(--wt-mark-radius);
|
|
1019
1021
|
/* Zero-width at rest, so the ring is drawn entirely by a state's band. The
|
|
1020
1022
|
colour rides --wt-mark-ink here rather than in each state, so a state sets
|
|
1021
1023
|
one variable and one width. */
|
|
@@ -1051,86 +1053,64 @@
|
|
|
1051
1053
|
opacity var(--dur-enter) var(--ease-enter) var(--dur-standard);
|
|
1052
1054
|
}
|
|
1053
1055
|
|
|
1054
|
-
/* Working: the heavier bare band, plus a
|
|
1055
|
-
layer
|
|
1056
|
-
|
|
1057
|
-
|
|
1056
|
+
/* Working: the heavier bare band, plus a hole that closes on the beat. Its own
|
|
1057
|
+
compositing layer so the overlay layers snap against the ring rather than the
|
|
1058
|
+
page: their offset from it is whole pixels (the rounded inset below), so the
|
|
1059
|
+
closing circle stays centred however the chip centres a 9px mark (17.5px down). */
|
|
1058
1060
|
:where(.wt-root) .wt-activity-mark[data-activity="working"] {
|
|
1059
1061
|
--wt-mark-ink: var(--status-working);
|
|
1060
|
-
/* The band, named because the
|
|
1061
|
-
the mark's inradius less this. Paired with the declaration it describes so the
|
|
1062
|
-
two cannot drift apart on width. */
|
|
1062
|
+
/* The band, named because the closing overlay below derives the hole from it. */
|
|
1063
1063
|
--wt-mark-band: 2px;
|
|
1064
1064
|
|
|
1065
1065
|
border-width: var(--wt-mark-band);
|
|
1066
1066
|
transform: translateZ(0);
|
|
1067
|
+
/* The overlays' clip. `overflow` clips at the padding box, which under border-box
|
|
1068
|
+
sizing IS the hole; where supported the clip is the mark's own outline instead,
|
|
1069
|
+
because the hole's rounded inner corners as a clip edge leave an anti-aliased
|
|
1070
|
+
seam against the band's own (measured at dpr 2: four pixels 15% dark at the
|
|
1071
|
+
peak). Over the band the ink paints the band's own colour. */
|
|
1072
|
+
overflow: hidden;
|
|
1067
1073
|
}
|
|
1068
1074
|
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
EVERY NUMBER HERE IS A RELATION, and the port that copied marotte's LITERALS is
|
|
1086
|
-
what left this rule running with nothing visible to show. Two of them were
|
|
1087
|
-
arithmetic for an 8px mark with a 2px band: `inset: -1px` put the overlay on that
|
|
1088
|
-
mark's own border box, and the core stop `70%` is where the core reaches the
|
|
1089
|
-
hole's CORNER there — sqrt(2) * (size / 2 - band) / (size / 2) = 70.71%. At 9px
|
|
1090
|
-
and 2px the same relation is 78.57%, so the copied 70% ended the solid core at
|
|
1091
|
-
2.45px while the mask opens only at 2.5px: core and visible mask did not
|
|
1092
|
-
intersect at all, the beat lit nothing the mask kept, and 4 pixels of a 9x9 mark
|
|
1093
|
-
moved between trough and peak against marotte's 20 (measured at dpr 1, both
|
|
1094
|
-
marks, sRGB). Written as relations they survive any size: the inset IS the band,
|
|
1095
|
-
the core stop IS the hole's corner radius, the mask's hole IS its inradius. So
|
|
1096
|
-
port marotte's `.tab-run-dot` (`12-tabs.css`, "THE GLOW BEAT") — the same mark
|
|
1097
|
-
solving the same problem, with the mask from the start — by its relations, never
|
|
1098
|
-
by its numbers. sqrt(2) is spelled to four decimals because the synced stylelint
|
|
1099
|
-
sets `number-max-precision: 4` outside colour functions.
|
|
1100
|
-
|
|
1101
|
-
NOTHING HERE EXTENDS PAST THE MARK. `box-sizing: border-box` (01-scope.css) puts
|
|
1102
|
-
the padding box — an absolutely positioned child's containing block — INSIDE the
|
|
1103
|
-
band, so `inset: calc(-1 * var(--wt-mark-band))` grows the overlay back out to
|
|
1104
|
-
exactly the mark's own border box and no further. Read it as "fill the mark", not
|
|
1105
|
-
as "overhang it": a larger inset would reach outside, where a feathered edge's
|
|
1106
|
-
visible radius moves with the beat and renders differently at every pixel
|
|
1107
|
-
density. `border-radius: 50%` is the same discipline on the last literal, and it
|
|
1108
|
-
is inert BY CONSTRUCTION rather than by measurement, which is why no assertion
|
|
1109
|
-
pins it: for side S and radius r the clip's 45° boundary is (S/2 - r) * sqrt(2) + r,
|
|
1110
|
-
monotonically decreasing in r, so the inscribed circle is the WORST case for
|
|
1111
|
-
reaching the hole's corners — and it still clears them for any band at or above
|
|
1112
|
-
S/2 * (1 - 1/sqrt(2)), 1.32px here against 2px. Do not assert it, and do not drop
|
|
1113
|
-
it either: it is what keeps a literal radius out of a rule whose whole defect was
|
|
1114
|
-
literals. */
|
|
1075
|
+
@supports (clip-path: inset(0 round 1px)) {
|
|
1076
|
+
:where(.wt-root) .wt-activity-mark[data-activity="working"] {
|
|
1077
|
+
overflow: visible;
|
|
1078
|
+
clip-path: inset(0 round var(--wt-mark-radius));
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
/* The close: the square hole fills from its corners, passes through its inscribed
|
|
1083
|
+
circle and shrinks to nothing, then reopens. Compositor-only: ::before is ink
|
|
1084
|
+
around a transparent circle of the hole's corner radius sqrt(2) * h, scaled down
|
|
1085
|
+
to --wt-mark-close and clipped by the parent; half-size (h + 1px) /
|
|
1086
|
+
--wt-mark-close keeps its edge under the band at the peak (a 71px layer, ~20 KB
|
|
1087
|
+
at dpr 1). Scaling leaves a 0.35px pinhole that reads as a dark pixel, so ::after
|
|
1088
|
+
fades a fill over the hole as the scale bottoms out. Every length is a relation to
|
|
1089
|
+
--wt-mark-size and --wt-mark-band (sqrt(2) to the stylelint's 4 decimals). */
|
|
1115
1090
|
:where(.wt-root) .wt-activity-mark[data-activity="working"]::before {
|
|
1091
|
+
--wt-mark-hole: calc(var(--wt-mark-size) / 2 - var(--wt-mark-band));
|
|
1092
|
+
--wt-mark-close: 0.1;
|
|
1093
|
+
|
|
1116
1094
|
content: "";
|
|
1117
1095
|
position: absolute;
|
|
1118
|
-
inset:
|
|
1119
|
-
border-radius: 50%;
|
|
1096
|
+
inset: round(down, var(--wt-mark-hole) - (var(--wt-mark-hole) + 1px) / var(--wt-mark-close), 1px);
|
|
1120
1097
|
background: radial-gradient(
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
transparent 92%
|
|
1098
|
+
circle,
|
|
1099
|
+
transparent 0 calc(1.4142 * var(--wt-mark-hole)),
|
|
1100
|
+
var(--wt-mark-ink) calc(1.4142 * var(--wt-mark-hole) + 0.5px)
|
|
1125
1101
|
);
|
|
1126
|
-
opacity: 0;
|
|
1127
1102
|
animation: wt-activity-breath var(--dot-beat-dur) ease-in-out infinite;
|
|
1103
|
+
will-change: transform;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
:where(.wt-root) .wt-activity-mark[data-activity="working"]::after {
|
|
1107
|
+
content: "";
|
|
1108
|
+
position: absolute;
|
|
1109
|
+
inset: -1px;
|
|
1110
|
+
background: var(--wt-mark-ink);
|
|
1111
|
+
opacity: 0;
|
|
1112
|
+
animation: wt-activity-seal var(--dot-beat-dur) ease-in-out infinite;
|
|
1128
1113
|
will-change: opacity;
|
|
1129
|
-
mask: radial-gradient(
|
|
1130
|
-
closest-side,
|
|
1131
|
-
transparent 0 calc(var(--wt-mark-size) / 2 - var(--wt-mark-band)),
|
|
1132
|
-
#000 calc(var(--wt-mark-size) / 2 - var(--wt-mark-band))
|
|
1133
|
-
);
|
|
1134
1114
|
}
|
|
1135
1115
|
|
|
1136
1116
|
/* Waiting: the thin band plus a static halo. Stopped and resumable, nobody being
|
|
@@ -1205,25 +1185,30 @@
|
|
|
1205
1185
|
}
|
|
1206
1186
|
}
|
|
1207
1187
|
|
|
1208
|
-
/* The activity mark's
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
The dot's glow ENVELOPE, at a lower CEILING, and the ceiling is where the two
|
|
1212
|
-
deliberately part. 0.55 rather than 1 is marotte's number for the same mark
|
|
1213
|
-
(`12-tabs.css`), and it is what keeps a strip of busy tabs reading as one pulse
|
|
1214
|
-
instead of a row of flashes — the dot can afford 1 because its overlay lifts a
|
|
1215
|
-
solid disc of its own hue and has almost no headroom above its own backdrop,
|
|
1216
|
-
while this one is masked onto a 2px band over the page and has all of it. Used by
|
|
1217
|
-
the mark alone, so the value moves nothing else. */
|
|
1188
|
+
/* The activity mark's close: open, shut, open, on the dot glow's envelope so the
|
|
1189
|
+
hole shuts as the disc beside it peaks. Transform only, so it never repaints. */
|
|
1218
1190
|
@keyframes wt-activity-breath {
|
|
1219
1191
|
0%,
|
|
1220
1192
|
70%,
|
|
1193
|
+
100% {
|
|
1194
|
+
transform: scale(1);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
35% {
|
|
1198
|
+
transform: scale(var(--wt-mark-close));
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
@keyframes wt-activity-seal {
|
|
1203
|
+
0%,
|
|
1204
|
+
20%,
|
|
1205
|
+
50%,
|
|
1221
1206
|
100% {
|
|
1222
1207
|
opacity: 0;
|
|
1223
1208
|
}
|
|
1224
1209
|
|
|
1225
1210
|
35% {
|
|
1226
|
-
opacity:
|
|
1211
|
+
opacity: 1;
|
|
1227
1212
|
}
|
|
1228
1213
|
}
|
|
1229
1214
|
|
|
@@ -1270,13 +1255,14 @@
|
|
|
1270
1255
|
by band alone (3px bare / 1px halo / 2px halo).
|
|
1271
1256
|
|
|
1272
1257
|
Re-pointing --wt-mark-band rather than declaring `border-width` keeps the number
|
|
1273
|
-
in the one place the state's own rule reads it from, the
|
|
1258
|
+
in the one place the state's own rule reads it from, the overlay's hole included
|
|
1274
1259
|
— which is moot here only because the overlay is gone one rule below. */
|
|
1275
1260
|
:where(.wt-root) .wt-activity-mark[data-activity="working"] {
|
|
1276
1261
|
--wt-mark-band: 3px;
|
|
1277
1262
|
}
|
|
1278
1263
|
|
|
1279
|
-
:where(.wt-root) .wt-activity-mark[data-activity="working"]::before
|
|
1264
|
+
:where(.wt-root) .wt-activity-mark[data-activity="working"]::before,
|
|
1265
|
+
:where(.wt-root) .wt-activity-mark[data-activity="working"]::after {
|
|
1280
1266
|
content: none;
|
|
1281
1267
|
}
|
|
1282
1268
|
}
|
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.2",
|
|
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
|
|