@celestia-island/hikari 0.55.39 → 0.55.40
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
|
@@ -410,10 +410,15 @@ describe("useSizeMorph clip reveal", () => {
|
|
|
410
410
|
// the box keeps the OLD pin while the top edge folds down through
|
|
411
411
|
// the closing inset — the pin swap lands only at the sweep's end.
|
|
412
412
|
h.setNatural(320);
|
|
413
|
-
|
|
413
|
+
h.remeasure();
|
|
414
|
+
// Mid-warmup: the explicit inset(0) start holds (regression
|
|
415
|
+
// contract — never a cleared/none start).
|
|
416
|
+
await busFrames(1);
|
|
417
|
+
expect(h.frame.style.clipPath).toBe("inset(0px 0 0 0 round 0px 0px 0px 0px)");
|
|
418
|
+
expect(h.frame.style.height).toBe("400px");
|
|
414
419
|
await settle();
|
|
415
|
-
//
|
|
416
|
-
//
|
|
420
|
+
// Sweep in flight: clip folding toward inset(80px), height still
|
|
421
|
+
// the old pin.
|
|
417
422
|
expect(h.frame.style.clipPath).toBe("inset(80px 0 0 0 round 0px 0px 0px 0px)");
|
|
418
423
|
expect(h.frame.style.height).toBe("400px");
|
|
419
424
|
// The sweep ends: atomic re-pin — height jumps to the target with
|
|
@@ -458,6 +463,33 @@ describe("useSizeMorph clip reveal", () => {
|
|
|
458
463
|
expect(h.frame.style.clipPath).toBe("inset(0px 0 0 0 round 0px 0px 0px 0px)");
|
|
459
464
|
});
|
|
460
465
|
|
|
466
|
+
it("lands the sweep via the watchdog when transitionend never fires", async () => {
|
|
467
|
+
const h = mountHarness(400);
|
|
468
|
+
h.frame.style.setProperty("--hk-sheet-morph", "clip");
|
|
469
|
+
h.start();
|
|
470
|
+
|
|
471
|
+
h.setNatural(320);
|
|
472
|
+
FakeResizeObserver.instances[0]!.callback();
|
|
473
|
+
await settle();
|
|
474
|
+
expect(h.frame.style.clipPath).toBe("inset(80px 0 0 0 round 0px 0px 0px 0px)");
|
|
475
|
+
expect(h.frame.style.height).toBe("400px");
|
|
476
|
+
|
|
477
|
+
// Round-6 regression: a WebView that never delivers clip-path
|
|
478
|
+
// transitionend used to freeze the frame here forever. The watchdog
|
|
479
|
+
// (duration + 350ms grace; happy-dom resolves no computed duration,
|
|
480
|
+
// so the 150ms fallback applies -> ~500ms) must land the same
|
|
481
|
+
// atomic re-pin.
|
|
482
|
+
await new Promise((resolve) => setTimeout(resolve, 620));
|
|
483
|
+
expect(h.frame.style.height).toBe("320px");
|
|
484
|
+
expect(h.frame.style.clipPath).toBe("");
|
|
485
|
+
expect(h.frame.style.willChange).toBe("clip-path");
|
|
486
|
+
|
|
487
|
+
// The watchdog is disarmed on the normal close: a later unrelated
|
|
488
|
+
// transitionend must be a no-op (no double landing, no throw).
|
|
489
|
+
fireTransitionEnd(h.frame, "clip-path");
|
|
490
|
+
expect(h.frame.style.height).toBe("320px");
|
|
491
|
+
});
|
|
492
|
+
|
|
461
493
|
it("never clips without the mode flag (desktop height morph intact)", async () => {
|
|
462
494
|
const h = mountHarness(300);
|
|
463
495
|
h.start();
|
|
@@ -547,7 +579,10 @@ describe("useSizeMorph clip reveal", () => {
|
|
|
547
579
|
h.setNatural(310);
|
|
548
580
|
h.remeasure();
|
|
549
581
|
expect(h.frame.style.height).toBe("380px");
|
|
550
|
-
|
|
582
|
+
// Explicit inset(0) start — the round-6 regression contract: a
|
|
583
|
+
// cleared (none) start point made Chromium skip the clip transition
|
|
584
|
+
// AND its transitionend, freezing the sheet clipped at its old pin.
|
|
585
|
+
expect(h.frame.style.clipPath).toBe("inset(0px 0 0 0 round 0px 0px 0px 0px)");
|
|
551
586
|
await settle();
|
|
552
587
|
expect(h.frame.style.clipPath).toBe("inset(70px 0 0 0 round 0px 0px 0px 0px)");
|
|
553
588
|
fireTransitionEnd(h.frame, "clip-path");
|
|
@@ -160,6 +160,14 @@ export function useSizeMorph(
|
|
|
160
160
|
let revealReport: AnimationHandle | null = null;
|
|
161
161
|
let revealDir: "reveal" | "conceal" | null = null;
|
|
162
162
|
let concealTo: number | null = null;
|
|
163
|
+
/** Sweep watchdog: transitionend is the normal closer, but a lost
|
|
164
|
+
* event (a WebView that skips clip-path end events entirely — the
|
|
165
|
+
* 2026-09-21 round-6 regression: a none→inset() start point made
|
|
166
|
+
* Chromium drop the transition AND its events, freezing the sheet
|
|
167
|
+
* clipped at its old pin) must not freeze the frame forever. The
|
|
168
|
+
* timer fires the exact same landing path and is disarmed on every
|
|
169
|
+
* normal close. */
|
|
170
|
+
let revealWatchdog: ReturnType<typeof setTimeout> | null = null;
|
|
163
171
|
/** Resident layer promotion on clip-mode surfaces: applied at arm time
|
|
164
172
|
* and cleared on hold/release. Promoting at the STEP-change moment
|
|
165
173
|
* (0.55.38's per-sweep will-change) destroyed the old layer one frame
|
|
@@ -205,6 +213,10 @@ export function useSizeMorph(
|
|
|
205
213
|
revealReport.disconnect();
|
|
206
214
|
revealReport = null;
|
|
207
215
|
}
|
|
216
|
+
if (revealWatchdog) {
|
|
217
|
+
clearTimeout(revealWatchdog);
|
|
218
|
+
revealWatchdog = null;
|
|
219
|
+
}
|
|
208
220
|
if (revealEl && revealEnd) {
|
|
209
221
|
revealEl.removeEventListener("transitionend", revealEnd);
|
|
210
222
|
}
|
|
@@ -278,7 +290,13 @@ export function useSizeMorph(
|
|
|
278
290
|
revealEl = f;
|
|
279
291
|
revealEnd = onEnd;
|
|
280
292
|
revealDir = dir;
|
|
281
|
-
|
|
293
|
+
const durationMs = transitionDurationMs(f);
|
|
294
|
+
revealReport = reportTransition(durationMs);
|
|
295
|
+
// Watchdog (see revealWatchdog): same landing, forced, if the
|
|
296
|
+
// transitionend never arrives.
|
|
297
|
+
revealWatchdog = setTimeout(() => {
|
|
298
|
+
if (revealEl === f && revealDir === dir) stopReveal();
|
|
299
|
+
}, durationMs + 350);
|
|
282
300
|
f.style.clipPath =
|
|
283
301
|
dir === "reveal"
|
|
284
302
|
? `inset(0px 0 0 0 round ${radii})`
|
|
@@ -423,10 +441,15 @@ export function useSizeMorph(
|
|
|
423
441
|
f.style.clipPath = `inset(${delta}px 0 0 0 round ${radii})`;
|
|
424
442
|
} else if (conceal) {
|
|
425
443
|
radii = cornerRadii(f);
|
|
426
|
-
// The box stays pinned at the OLD height
|
|
427
|
-
//
|
|
428
|
-
// clip
|
|
444
|
+
// The box stays pinned at the OLD height, and the clip start is an
|
|
445
|
+
// EXPLICIT inset(0) — not a cleared inline clip. Chromium does not
|
|
446
|
+
// interpolate clip-path between none and inset(), so a none start
|
|
447
|
+
// made the sweep jump to its end state and never fire
|
|
448
|
+
// transitionend, freezing the sheet clipped at its old pin
|
|
449
|
+
// (2026-09-21 round-6 regression report: "the top half is just cut
|
|
450
|
+
// off and then it stays there"). inset(0)→inset(N) interpolates.
|
|
429
451
|
f.style.height = `${pinned}px`;
|
|
452
|
+
f.style.clipPath = `inset(0px 0 0 0 round ${radii})`;
|
|
430
453
|
} else if (pinned > 0) {
|
|
431
454
|
f.style.height = `${pinned}px`;
|
|
432
455
|
}
|