@cplieger/web-terminal-ui 3.4.2 → 3.4.3

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/css/06-mobile.css CHANGED
@@ -254,6 +254,18 @@
254
254
  margin-inline-start: 0;
255
255
  }
256
256
 
257
+ /* When the tab list expands the switch button collapses too (its job is done:
258
+ the list is open), mirroring the keyboard button's collapse in the same
259
+ animation (07-animations.css animates width + margin + opacity). Placed after
260
+ the .wt-switcher-multi reveal so it wins when both apply (expanded, two or
261
+ more tabs). */
262
+ .wt-switcher-expanded .wt-switcher-switch {
263
+ width: 0;
264
+ opacity: 0;
265
+ margin-inline-start: calc(-1 * var(--sp-1));
266
+ pointer-events: none;
267
+ }
268
+
257
269
  @media (hover: hover) {
258
270
  .wt-switcher-switch:hover {
259
271
  background: var(--tab-hover-bg);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cplieger/web-terminal-ui",
3
- "version": "3.4.2",
3
+ "version": "3.4.3",
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": "GPL-3.0-or-later",
@@ -160,10 +160,12 @@ export interface TabsOptions {
160
160
  const CLOSE_SVG = `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6 18 18M18 6 6 18"/></svg>`;
161
161
  const NEW_SVG = `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>`;
162
162
  const KB_SVG = `<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="4" y="4" width="16" height="16" rx="3.5"/><path d="M15 15 9.5 9.5M9.5 13V9.5H13"/></svg>`;
163
- // Two-arrows "switch" glyph for the mobile switcher's dedicated open/close
164
- // button (a latest-wins background-tab notification dot rides on it — see
165
- // switchButtonHTML). Same viewBox + stroke=currentcolor treatment as the others.
166
- const SWITCH_SVG = `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 9h13M14 6l3 3-3 3M20 15H7M10 12l-3 3 3 3"/></svg>`;
163
+ // Two-overlapping-windows glyph for the mobile switcher's dedicated open/close
164
+ // button: the browser-style "tab switcher" icon, more recognisable than the
165
+ // prior swap-arrows (which read like a keyboard Tab key). A latest-wins
166
+ // background-tab notification dot rides on it (see switchButtonHTML). Same
167
+ // viewBox + stroke=currentcolor treatment as the others.
168
+ const SWITCH_SVG = `<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="8" width="13" height="13" rx="2"/><path d="M8 8V6a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-3"/></svg>`;
167
169
 
168
170
  // chipContent is the ONE builder for a tab chip's content — a status dot, a
169
171
  // label, and a close (x) — shared by all three chip sites: the desktop strip
@@ -1099,33 +1101,43 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
1099
1101
  from = first - el.getBoundingClientRect().top; // survivor: old spot -> new
1100
1102
  } else {
1101
1103
  from = dir === "next" ? pitch : -pitch; // newcomer: in from the trailing edge
1102
- el.style.opacity = "0";
1104
+ el.style.opacity = "0"; // fades in as it rotates to its slot (below)
1103
1105
  }
1104
1106
  el.style.transition = "none";
1105
1107
  el.style.transform = `translateY(${String(Math.round(from))}px)`;
1106
1108
  }
1107
1109
  const exit = dir === "next" ? -pitch : pitch; // leaving row off the leading edge
1108
- requestAnimationFrame(() => {
1109
- // Couple opacity to the SAME easing + duration as the transform so a
1110
- // row's fade tracks its DISTANCE from its target slot (every reel row
1111
- // travels one pitch): opacity is a linear function of remaining
1112
- // distance, so a row is transparent a pitch away (at the clipped
1113
- // edge) and only fully opaque once it settles. Entering rows fade IN
1114
- // as they rotate in and the leaving row fades OUT as it exits — no
1115
- // hard black cutoff at the list edges, and no permanent edge fade.
1116
- // (Was opacity 0.2s ease-out, decoupled from the motion, so a row hit
1117
- // full opacity while still at the edge — the reported hard cut.)
1118
- const trans =
1119
- "transform 0.25s cubic-bezier(0.2, 0, 0, 1), opacity 0.25s cubic-bezier(0.2, 0, 0, 1)";
1120
- for (const el of rowEls.values()) {
1121
- el.style.transition = trans;
1122
- el.style.transform = "translateY(0)";
1123
- el.style.opacity = "";
1124
- }
1125
- ghost.style.transition = trans;
1126
- ghost.style.transform = `translateY(${String(Math.round(exit))}px)`;
1127
- ghost.style.opacity = "0";
1128
- });
1110
+ // The leaving row starts at its captured spot, fully opaque.
1111
+ ghost.style.transition = "none";
1112
+ ghost.style.transform = "translateY(0)";
1113
+ ghost.style.opacity = "1";
1114
+ // Commit the from-state (transforms + opacities) with a forced reflow
1115
+ // BEFORE the to-state, so BOTH the transform and the opacity transitions
1116
+ // fire from it. The prior code used a bare rAF (letting the browser
1117
+ // collapse from->to into one recalc) and reverted the entering row's
1118
+ // opacity to "" (no explicit end value), so the fade never animated
1119
+ // (the reported "no fade in / fade out"). The modern display/visibility
1120
+ // transition (transition-behavior: allow-discrete + @starting-style,
1121
+ // Baseline 2024) does NOT apply here: these rows are reused and moved by
1122
+ // a JS transform FLIP, not toggled via display:none, so the reliable
1123
+ // path is a real reflow plus an explicit opacity transition.
1124
+ swList.getBoundingClientRect(); // read forces the reflow (commit the from-state)
1125
+ // Couple opacity to the SAME easing + duration as the transform so a
1126
+ // row's fade tracks its DISTANCE from its target slot (each reel row
1127
+ // travels one pitch): a row is transparent a pitch away (at the clipped
1128
+ // edge) and only fully opaque once it settles. Entering rows fade IN as
1129
+ // they rotate in, the leaving row fades OUT as it exits: no hard cutoff
1130
+ // at the list edges and no permanent edge mask.
1131
+ const trans =
1132
+ "transform 0.25s cubic-bezier(0.2, 0, 0, 1), opacity 0.25s cubic-bezier(0.2, 0, 0, 1)";
1133
+ for (const el of rowEls.values()) {
1134
+ el.style.transition = trans;
1135
+ el.style.transform = "translateY(0)";
1136
+ el.style.opacity = "1";
1137
+ }
1138
+ ghost.style.transition = trans;
1139
+ ghost.style.transform = `translateY(${String(Math.round(exit))}px)`;
1140
+ ghost.style.opacity = "0";
1129
1141
  reelTimer = setTimeout(endReelNow, REEL_MS);
1130
1142
  };
1131
1143
  }