mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-09 08:59:40 +02:00
feat(tiles): the grid opens and closes with a short animation
Owner request: "when clicking on the tile button first make this animation nicer". Recorded before: the click froze the page, six empty tiles cut in at once, each tile's history then scrolled in visibly as its load landed, and closing showed an empty single view for about a quarter of a second before its own replay scrolled in. The grid's own motion, on by default (not an entrance-animations.js theme, which are off by default): - Opening: each tile fades and settles in (opacity, translateY 6px, scale .97), 180 ms, 24 ms apart in reading order: the last of six is done at 300 ms. Its terminal stays transparent until the load queue reports its first capture done, then fades in whole (160 ms), so no replay scrolls by; a 15 s backstop shows it should that never come. A tile added later enters the same way. - Closing with the toggle (button, Ctrl+Shift+G; owner answer: only these): the close stays synchronous, and a still copy of the tiles (clones: no xterm, socket or listener; inert, aria-hidden, no pointer) dims at once over the stage, holds until the single view's selectSession has replayed its session (at most 700 ms), then fades out. No empty single view between the two. A reopen drops a copy still showing; a web tab hides it. - A re-form to another count fades the old grid's copy out at once while the new tiles enter. - The count menu fades in (140 ms). Every one animates opacity and transform only, so FitAddon measures the final cell and each tile still sends one PTY resize (#464); nothing moves, and no copy is made, under prefers-reduced-motion. Tests (tile-grid-motion.test.ts): the stagger, the reveal and its backstop, the same fits and connects with and without motion, the held copy (released on settle, by its cap, removed on the last tile-leave or its fallback), only the toggle animates, a reopen purges, the zoomed tile alone, the re-form, reduced motion, and a CSS guard that every new keyframe touches only opacity and transform. The vm harness gains style.setProperty, cloneNode, isConnected and lastElementChild. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,7 +22,14 @@ export class FakeEl {
|
||||
id = '';
|
||||
className = '';
|
||||
dataset: Record<string, string> = {};
|
||||
style: Record<string, string> = {};
|
||||
/** Inline style; `setProperty` (custom properties) writes into it too. */
|
||||
style: Record<string, string> = Object.defineProperty({} as Record<string, string>, 'setProperty', {
|
||||
value(this: Record<string, string>, name: string, value: string) {
|
||||
this[name] = value;
|
||||
},
|
||||
enumerable: false,
|
||||
});
|
||||
inert = false;
|
||||
children: FakeEl[] = [];
|
||||
parentElement: FakeEl | null = null;
|
||||
attrs: Record<string, string> = {};
|
||||
@@ -90,6 +97,29 @@ export class FakeEl {
|
||||
get firstChild() {
|
||||
return this.children[0] ?? null;
|
||||
}
|
||||
get lastElementChild() {
|
||||
return this.children.at(-1) ?? null;
|
||||
}
|
||||
/** In the fake document: its ancestors end at `main` or `body`. */
|
||||
get isConnected(): boolean {
|
||||
let n: FakeEl = this;
|
||||
while (n.parentElement) n = n.parentElement;
|
||||
return n === main || n === body;
|
||||
}
|
||||
/** A copy of this element and (deep) its subtree: classes, data, style, attributes, text; no listeners. */
|
||||
cloneNode(deep = false): FakeEl {
|
||||
const copy = new FakeEl();
|
||||
copy.id = this.id;
|
||||
copy.className = this.className;
|
||||
copy.dataset = { ...this.dataset };
|
||||
for (const [k, v] of Object.entries(this.style)) copy.style[k] = v;
|
||||
copy.attrs = { ...this.attrs };
|
||||
copy.textContent = this.textContent;
|
||||
copy.title = this.title;
|
||||
copy.hidden = this.hidden;
|
||||
if (deep) for (const child of this.children) copy.appendChild(child.cloneNode(true));
|
||||
return copy;
|
||||
}
|
||||
get nextSibling() {
|
||||
const siblings = this.parentElement?.children ?? [];
|
||||
return siblings[siblings.indexOf(this) + 1] ?? null;
|
||||
@@ -324,6 +354,8 @@ export function makeGridApp(ids: string[] = ['s-a', 's-b', 's-c']): GridApp {
|
||||
/** Resets the shared fake DOM and tile registry between tests. */
|
||||
export function resetGridHarness() {
|
||||
FakeTile.all = [];
|
||||
// A test's prefers-reduced-motion answer (window.matchMedia) goes with it.
|
||||
delete windowStub.matchMedia;
|
||||
focusedEl = null;
|
||||
idleCallbacks.length = 0;
|
||||
rafCallbacks.length = 0;
|
||||
|
||||
Reference in New Issue
Block a user