@celestia-island/hikari 0.55.57 → 0.55.58

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@celestia-island/hikari",
3
- "version": "0.55.57",
3
+ "version": "0.55.58",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Hikari Vue 3 component library — production-grade UI components based on shittim-chest design system",
@@ -1184,12 +1184,19 @@ describe("useSizeMorph heightMorph (round 18)", () => {
1184
1184
  expect(h.frame.style.transition).toContain("300ms");
1185
1185
  // No clip staging, no riders — this is a plain height animation.
1186
1186
  expect(h.frame.style.clipPath).toBe("");
1187
+ // The resident layer promotion is LIFTED for the animation's
1188
+ // duration (round 19: the per-frame texture re-allocation of a
1189
+ // composited layer during height animation composites as
1190
+ // transparent on phone GPUs — the background flash-through).
1191
+ expect(h.frame.style.willChange).toBe("");
1187
1192
 
1188
- // The transitionend cleans the inline override.
1193
+ // The transitionend cleans the inline override AND restores the
1194
+ // resident promotion (the frame is at rest).
1189
1195
  const ev = new Event("transitionend");
1190
1196
  Object.defineProperty(ev, "propertyName", { value: "height" });
1191
1197
  h.frame.dispatchEvent(ev);
1192
1198
  expect(h.frame.style.transition).toBe("");
1199
+ expect(h.frame.style.willChange).toBe("clip-path");
1193
1200
  h.stop();
1194
1201
  });
1195
1202
 
@@ -936,25 +936,47 @@ export function useSizeMorph(
936
936
  // 2. Re-establish the OLD pin under no transition, flush it.
937
937
  f.style.height = `${pinned}px`;
938
938
  void f.offsetHeight;
939
- // 3. Animate to the new pin under an inline height transition —
940
- // the CSS engine owns every frame; there is no staging task, no
941
- // warmup and no per-frame JS at all.
939
+ // 3. DEMOTE the frame from its resident composited layer for the
940
+ // animation's duration. A `will-change: clip-path` layer whose
941
+ // element changes layout size per-frame re-allocates its GPU
942
+ // texture every frame — on a phone GPU the gap between the old
943
+ // texture being discarded and the new one rastering composites
944
+ // as TRANSPARENT, flashing the page behind the sheet through
945
+ // (round-19 report: "background content keeps flashing"). With
946
+ // the promotion lifted, the height animation's per-frame paint
947
+ // happens on the main thread, which is slower but never leaves
948
+ // a frame where the sheet's surface is missing.
949
+ const residentWill_ = f.style.willChange;
950
+ if (residentWill) {
951
+ f.style.willChange = "";
952
+ residentWill = false;
953
+ }
942
954
  f.style.transition =
943
955
  `height ${durationMs}ms cubic-bezier(0.4, 0, 0.2, 1)`;
944
956
  f.style.height = `${next}px`;
945
957
  pinned = next;
946
- // 4. Clean the inline transition up on the animation's own clock.
958
+ // 4. Clean up on the animation's own clock: restore the transition
959
+ // list and the resident layer promotion together — the frame is
960
+ // at its final geometry and at rest, so the re-promotion's
961
+ // raster has all the time it needs.
962
+ const restore = (): void => {
963
+ f.removeEventListener("transitionend", onEnd);
964
+ f.style.transition = "";
965
+ if (residentWill_ === "clip-path") {
966
+ f.style.willChange = "clip-path";
967
+ residentWill = true;
968
+ }
969
+ };
947
970
  const onEnd = (ev: TransitionEvent): void => {
948
971
  if (ev.target === f && ev.propertyName === "height") {
949
- f.removeEventListener("transitionend", onEnd);
950
- f.style.transition = "";
972
+ restore();
951
973
  }
952
974
  };
953
975
  f.addEventListener("transitionend", onEnd);
954
- setTimeout(() => {
955
- f.removeEventListener("transitionend", onEnd);
956
- f.style.transition = "";
957
- }, durationMs + 350);
976
+ const watchdog = setTimeout(restore, durationMs + 350);
977
+ // If the element goes away mid-flight, don't leak the timer.
978
+ const origClear = watchdog;
979
+ void origClear; // timer fires restore() which is idempotent
958
980
  }
959
981
 
960
982
  return { start, stop, hold, remeasure, heightMorph };