@celestia-island/hikari 0.55.54 → 0.55.55
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/package.json
CHANGED
|
@@ -308,11 +308,12 @@ describe("HkModal publishes its own fold for the step flow", () => {
|
|
|
308
308
|
await nextTick();
|
|
309
309
|
expect(rig.morphToken()).toBe("");
|
|
310
310
|
|
|
311
|
-
// The morph window: the freeze lifts and the override lands
|
|
312
|
-
|
|
311
|
+
// The morph window: the freeze lifts and the override lands (the
|
|
312
|
+
// stepflow requests a 1ms snap — see STEP_MORPH_SNAP_MS).
|
|
313
|
+
rig.announceSwap({ delta: 120, durationMs: 1, phase: "morph" });
|
|
313
314
|
await nextTick();
|
|
314
|
-
expect(rig.morphToken()).toBe("
|
|
315
|
-
await vi.advanceTimersByTimeAsync(
|
|
315
|
+
expect(rig.morphToken()).toBe("1ms");
|
|
316
|
+
await vi.advanceTimersByTimeAsync(500);
|
|
316
317
|
expect(rig.morphToken()).toBe("");
|
|
317
318
|
});
|
|
318
319
|
|
|
@@ -458,9 +459,9 @@ describe("HkModal rides its chrome and gates the slide window", () => {
|
|
|
458
459
|
await vi.advanceTimersByTimeAsync(700);
|
|
459
460
|
expect(frame.style.height).toBe("656px");
|
|
460
461
|
// …and a later morph event still works (no stranded freeze).
|
|
461
|
-
rig.announceSwap({ delta: -40, durationMs:
|
|
462
|
+
rig.announceSwap({ delta: -40, durationMs: 1, phase: "morph" });
|
|
462
463
|
await nextTick();
|
|
463
|
-
expect(rig.morphToken()).toBe("
|
|
464
|
+
expect(rig.morphToken()).toBe("1ms");
|
|
464
465
|
await vi.advanceTimersByTimeAsync(700);
|
|
465
466
|
expect(rig.morphToken()).toBe("");
|
|
466
467
|
restoreBox();
|
|
@@ -227,7 +227,7 @@ describe("HkStepFlow split-window swap (motion enabled)", () => {
|
|
|
227
227
|
expect(t.container.querySelectorAll(".hk-stepflow-body").length).toBe(1);
|
|
228
228
|
expect(t.container.querySelector(".hk-stepflow-body")?.textContent).toBe("b-body");
|
|
229
229
|
expect(events).toHaveLength(2);
|
|
230
|
-
expect(events[1]!.detail).toMatchObject({ phase: "morph", durationMs:
|
|
230
|
+
expect(events[1]!.detail).toMatchObject({ phase: "morph", durationMs: 1 });
|
|
231
231
|
expect(typeof events[1]!.detail.delta).toBe("number");
|
|
232
232
|
});
|
|
233
233
|
|
|
@@ -78,6 +78,14 @@ export const STEPFLOW_SWAP_EVENT = "hk-stepflow-swap";
|
|
|
78
78
|
* the swap without a transitionend. */
|
|
79
79
|
const SWAP_WATCHDOG_GRACE_MS = 350;
|
|
80
80
|
|
|
81
|
+
/** The morph window's sweep length: near-instant. A sustained clip
|
|
82
|
+
* animation re-rasters the moving edge per frame — the phone flicker
|
|
83
|
+
* the round-17 report caught in the height window even after the
|
|
84
|
+
* content had settled. One paint, no edge travel (the 2026-09-21
|
|
85
|
+
* shrink-snap decision, applied to stepflow morphs in both
|
|
86
|
+
* directions). */
|
|
87
|
+
const STEP_MORPH_SNAP_MS = 1;
|
|
88
|
+
|
|
81
89
|
/** Fade level above which an entering body counts as the visible one. */
|
|
82
90
|
const FADED_IN = 0.05;
|
|
83
91
|
|
|
@@ -216,7 +224,14 @@ export default defineComponent({
|
|
|
216
224
|
}
|
|
217
225
|
|
|
218
226
|
/** Settle the slide: recycle the old node and hand the sheet its new
|
|
219
|
-
* height — the morph window plays alone from here.
|
|
227
|
+
* height — the morph window plays alone from here. The morph
|
|
228
|
+
* requests a NEAR-INSTANT sweep (1ms): a sustained clip animation
|
|
229
|
+
* re-rasters the moving edge every frame, which on the phone GPU
|
|
230
|
+
* read as the height window flickering even with the content
|
|
231
|
+
* settled (round-17 report; the same mechanism that made shrinks
|
|
232
|
+
* snap in 2026-09-21). The height change still lands as its own
|
|
233
|
+
* window — it just completes in one paint instead of 300ms of
|
|
234
|
+
* edge travel. */
|
|
220
235
|
function endSwap(): void {
|
|
221
236
|
if (!swap) return;
|
|
222
237
|
const handle = swap;
|
|
@@ -229,7 +244,7 @@ export default defineComponent({
|
|
|
229
244
|
bodies.value = bodies.value.filter((b) => b.id !== handle.leavingId);
|
|
230
245
|
announce({
|
|
231
246
|
delta: newH - handle.oldH,
|
|
232
|
-
durationMs:
|
|
247
|
+
durationMs: STEP_MORPH_SNAP_MS,
|
|
233
248
|
phase: "morph",
|
|
234
249
|
});
|
|
235
250
|
swapPhase.value = "idle";
|
|
@@ -27,12 +27,13 @@
|
|
|
27
27
|
// top, single-line label below), and horizontal link segments join the
|
|
28
28
|
// neighbouring node centers on an overlay — continuing past a side's edge
|
|
29
29
|
// with a fading gradient where further hidden steps go on.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
//
|
|
31
|
+
// The LAYOUT is unconditional: scoping this rule with :not(.hk-scroll-pin)
|
|
32
|
+
// (as a round-16 sticky fix briefly did) deletes the whole three-column
|
|
33
|
+
// grid from every sticky-header timeline — the windowed wizard collapsed
|
|
34
|
+
// to a block stack (circles bunched left, links gone). Only the relative
|
|
35
|
+
// ANCHOR needs the escape, and it gets its own rule below.
|
|
36
|
+
.hk-timeline[data-mode="window"] {
|
|
36
37
|
display: grid;
|
|
37
38
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
38
39
|
align-items: start;
|
|
@@ -45,6 +46,14 @@
|
|
|
45
46
|
--hk-timeline-link-clearance: 20px;
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
// The relative anchor for the links overlay: a sticky step header (the
|
|
50
|
+
// scroll-pin contract) is its own containing block and must keep
|
|
51
|
+
// position: sticky — the escape keeps THIS property only from
|
|
52
|
+
// out-specifying it (the same fight the full-mode halo rule lost).
|
|
53
|
+
.hk-timeline[data-mode="window"]:not(.hk-scroll-pin) {
|
|
54
|
+
position: relative;
|
|
55
|
+
}
|
|
56
|
+
|
|
48
57
|
.hk-timeline[data-mode="window"] .hk-timeline-window {
|
|
49
58
|
display: flex;
|
|
50
59
|
flex-direction: column;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
2
5
|
import { createApp, defineComponent, h, nextTick, ref } from "vue";
|
|
3
6
|
|
|
4
7
|
import HkTimeline, { computeTimelineWindow } from "./HkTimeline";
|
|
@@ -487,3 +490,22 @@ describe("HkTimeline navigation motion (round 16)", () => {
|
|
|
487
490
|
expect(host().getAttribute("data-dir")).toBe("back");
|
|
488
491
|
});
|
|
489
492
|
});
|
|
493
|
+
|
|
494
|
+
describe("HkTimeline window-mode layout under the sticky pin (round 17)", () => {
|
|
495
|
+
// Round-17 regression: scoping the whole window rule with
|
|
496
|
+
// :not(.hk-scroll-pin) deleted the grid layout from every
|
|
497
|
+
// sticky-header timeline — the windowed wizard collapsed to a block
|
|
498
|
+
// stack (circles left, links gone). happy-dom does not resolve the
|
|
499
|
+
// SCSS, so this pins the SOURCE structure: the layout rule is
|
|
500
|
+
// unconditional; only the relative anchor escapes.
|
|
501
|
+
it("keeps the window layout unconditional and escapes only position", () => {
|
|
502
|
+
const src = readFileSync(join(dirname(fileURLToPath(import.meta.url)), "HkTimeline.scss"), "utf-8");
|
|
503
|
+
const layout = src.match(/\.hk-timeline\[data-mode="window"\]\s*\{([\s\S]*?)\n\}/)![1]!;
|
|
504
|
+
expect(layout).toContain("display: grid");
|
|
505
|
+
expect(layout).toContain("grid-template-columns: repeat(3, minmax(0, 1fr))");
|
|
506
|
+
expect(layout, "the layout rule must NOT carry the escape").not.toContain(":not(");
|
|
507
|
+
expect(layout, "position belongs to the escaped rule").not.toContain("position");
|
|
508
|
+
const anchor = src.match(/\.hk-timeline\[data-mode="window"\]:not\(\.hk-scroll-pin\)\s*\{([\s\S]*?)\n\}/)![1]!;
|
|
509
|
+
expect(anchor.trim()).toBe("position: relative;");
|
|
510
|
+
});
|
|
511
|
+
});
|