@celestia-island/hikari 0.55.310 → 0.55.311
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
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* frame maps its surface phases onto `hk-ride--from` / `hk-ride--to`):
|
|
12
12
|
*
|
|
13
13
|
* frame height : chrome + content × 0.9 ⇄ chrome + content
|
|
14
|
-
*
|
|
14
|
+
* frame : translateY(10px) scale(0.97) ⇄ translateY(0) scale(1)
|
|
15
|
+
* content/body : never transformed (the whole window zooms)
|
|
15
16
|
*
|
|
16
17
|
* The retired grammars, oldest first: the 2026-11-04 whole-frame
|
|
17
18
|
* `scale(1, 0.9)`, the 2026-11-05 body-only uniform `scale(0.9)`, the
|
|
@@ -323,8 +324,8 @@ const CENTERING =
|
|
|
323
324
|
/** Frame ride states: the centering translate is a PERMANENT prefix and
|
|
324
325
|
* only the appended scaleY changes (2026-11-06: the window's height rides
|
|
325
326
|
* the open/close about its center). */
|
|
326
|
-
const FRAME_FROM = `${CENTERING}
|
|
327
|
-
const FRAME_TO = `${CENTERING}
|
|
327
|
+
const FRAME_FROM = `${CENTERING} translateY(10px) scale(0.97)`;
|
|
328
|
+
const FRAME_TO = `${CENTERING} translateY(0) scale(1)`;
|
|
328
329
|
|
|
329
330
|
/** Exact compiled transition values (winner()-normalized) — pinned whole,
|
|
330
331
|
* not by prefix: an embedded fourth time value (delay) or a smuggled extra
|
|
@@ -333,10 +334,10 @@ const FRAME_TO = `${CENTERING} scaleY(1)`;
|
|
|
333
334
|
* R2 finding 2026-11-06 (S1): prefix `toContain` let both survive. */
|
|
334
335
|
const ENTER_TRANSITION =
|
|
335
336
|
"opacity var(--hk-modal-duration, 0.42s) var(--hk-modal-ease-out, cubic-bezier(0.33, 1, 0.68, 1)), " +
|
|
336
|
-
"
|
|
337
|
+
"transform var(--hk-modal-duration, 0.42s) var(--hk-modal-ease-out, cubic-bezier(0.33, 1, 0.68, 1))";
|
|
337
338
|
const LEAVE_TRANSITION =
|
|
338
339
|
"opacity var(--hk-modal-duration, 0.32s) var(--hk-modal-ease-in, cubic-bezier(0.45, 0, 0.55, 1)), " +
|
|
339
|
-
"
|
|
340
|
+
"transform var(--hk-modal-duration, 0.32s) var(--hk-modal-ease-in, cubic-bezier(0.45, 0, 0.55, 1))";
|
|
340
341
|
/** The ride's own geometry lives in the shared container sheet; the modal
|
|
341
342
|
* frame maps its phases onto these two ends. */
|
|
342
343
|
const RIDE_SHEET = (() => {
|
|
@@ -355,17 +356,24 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
|
|
|
355
356
|
expect(selectors.some((s) => s.includes("hk-modal-content-enter-from"))).toBe(true);
|
|
356
357
|
});
|
|
357
358
|
|
|
358
|
-
it("
|
|
359
|
-
//
|
|
360
|
-
//
|
|
361
|
-
//
|
|
362
|
-
//
|
|
359
|
+
it("rides the whole window on the centering translate plus a zoom pair", () => {
|
|
360
|
+
// B3 (user direction 2026-11-07, fifth revision): the frame's transform
|
|
361
|
+
// is the PERMANENT centering translate with a translateY+scale appended
|
|
362
|
+
// — a compositor-only motion. Rest keeps the bare translate; the two
|
|
363
|
+
// ride ends are the contracted zoom pair, byte-identical across the
|
|
364
|
+
// draggable variant. Anything else (a bare scale, a lost shift token,
|
|
365
|
+
// a different function order) is the regression this pins.
|
|
363
366
|
for (const state of [...RIDE, { name: "rest", classes: [] as string[] }]) {
|
|
367
|
+
const compressed = state.classes.some((c) =>
|
|
368
|
+
c.includes("enter-from") || c.includes("leave-to"));
|
|
369
|
+
const expected = state.classes.length === 0
|
|
370
|
+
? CENTERING
|
|
371
|
+
: compressed ? FRAME_FROM : FRAME_TO;
|
|
364
372
|
for (const [label, frame] of [
|
|
365
373
|
["", frameWith(...state.classes)],
|
|
366
374
|
["draggable ", draggableFrameWith(...state.classes)],
|
|
367
375
|
] as const) {
|
|
368
|
-
expect(winner(frame, "transform", DESKTOP), `${label}${state.name}`).toBe(
|
|
376
|
+
expect(winner(frame, "transform", DESKTOP), `${label}${state.name}`).toBe(expected);
|
|
369
377
|
for (const prop of ["scale", "zoom", "translate", "rotate", "transform-origin"]) {
|
|
370
378
|
expect(winner(frame, prop, DESKTOP), `${label}${state.name} ${prop}`).toBeUndefined();
|
|
371
379
|
}
|
|
@@ -416,14 +424,14 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
|
|
|
416
424
|
"will-change",
|
|
417
425
|
DESKTOP,
|
|
418
426
|
);
|
|
419
|
-
expect(enterWill).toBe("opacity");
|
|
427
|
+
expect(enterWill).toBe("opacity, transform");
|
|
420
428
|
// The close direction promotes the same pair of layers.
|
|
421
429
|
const leaveWill = winner(
|
|
422
430
|
frameWith("hk-modal-content-leave-to", "hk-modal-content-leave-active"),
|
|
423
431
|
"will-change",
|
|
424
432
|
DESKTOP,
|
|
425
433
|
);
|
|
426
|
-
expect(leaveWill).toBe("opacity");
|
|
434
|
+
expect(leaveWill).toBe("opacity, transform");
|
|
427
435
|
});
|
|
428
436
|
|
|
429
437
|
it("rides the HEIGHT through the shared container contract, chrome untouched", () => {
|
|
@@ -626,17 +634,6 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
|
|
|
626
634
|
"transition",
|
|
627
635
|
"will-change",
|
|
628
636
|
"opacity",
|
|
629
|
-
// The from-pair carries the ride's height end, and the -active pair
|
|
630
|
-
// locks the content region out of the flex shrink for the flight
|
|
631
|
-
// (the 0.81-compounding guard, 2026-11-07 R3 finding).
|
|
632
|
-
"height",
|
|
633
|
-
"flex",
|
|
634
|
-
// …and the -active pair pins the footer band to the frame's bottom
|
|
635
|
-
// edge for the flight (it would otherwise be pushed out by the
|
|
636
|
-
// content region's locked natural box).
|
|
637
|
-
"position",
|
|
638
|
-
"inset-inline",
|
|
639
|
-
"bottom",
|
|
640
637
|
]);
|
|
641
638
|
// Chrome ride states now also carry `transform` (the 2026-11-06 frame
|
|
642
639
|
// height ride) — but nothing else beyond opacity/transition/will-change.
|
|
@@ -718,12 +715,7 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
|
|
|
718
715
|
const bodyTransform = winner(body, "transform", DESKTOP) ?? "";
|
|
719
716
|
// The contracted body grammar: nothing but the vertical compression
|
|
720
717
|
// at the ride's two ends (and no transform at rest).
|
|
721
|
-
|
|
722
|
-
c.includes("enter-from") || c.includes("leave-to"));
|
|
723
|
-
const expected = expectCompressed
|
|
724
|
-
? "scaleY(var(--hk-ride-factor, 0.9))"
|
|
725
|
-
: "scaleY(1)";
|
|
726
|
-
expect(bodyTransform, `body ${state.name}`).toBe(expected);
|
|
718
|
+
expect(bodyTransform, `body ${state.name}`).toBe("");
|
|
727
719
|
expect(winner(body, "scale", DESKTOP), `body ${state.name}`).toBeUndefined();
|
|
728
720
|
expect(winner(body, "translate", DESKTOP), `body ${state.name}`).toBeUndefined();
|
|
729
721
|
expect(winner(body, "zoom", DESKTOP), `body ${state.name}`).toBeUndefined();
|
|
@@ -732,7 +724,10 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
|
|
|
732
724
|
// the only `scaleY` in the choreography lives on the content region's
|
|
733
725
|
// shared contract (pinned by the ride test above).
|
|
734
726
|
const frameTransform = winner(frame, "transform", DESKTOP) ?? "";
|
|
735
|
-
|
|
727
|
+
const compressed = state.classes.some((c) =>
|
|
728
|
+
c.includes("enter-from") || c.includes("leave-to"));
|
|
729
|
+
const frameExpected = compressed ? FRAME_FROM : FRAME_TO;
|
|
730
|
+
expect(frameTransform, `frame ${state.name}`).toBe(frameExpected);
|
|
736
731
|
expect(frameTransform, `frame ${state.name}`).not.toMatch(FRAME_ESCAPES);
|
|
737
732
|
expect(winner(frame, "scale", DESKTOP), `frame ${state.name}`).toBeUndefined();
|
|
738
733
|
expect(winner(frame, "zoom", DESKTOP), `frame ${state.name}`).toBeUndefined();
|
|
@@ -57,11 +57,11 @@
|
|
|
57
57
|
z-index: 1;
|
|
58
58
|
top: 50%;
|
|
59
59
|
left: 50%;
|
|
60
|
-
// The centering translate is the frame's
|
|
61
|
-
// (rest, enter-from/to, leave-from/to)
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
60
|
+
// The centering translate is the frame's PERMANENT transform prefix:
|
|
61
|
+
// every state (rest, enter-from/to, leave-from/to) carries this exact
|
|
62
|
+
// translate, and the open/close choreography only ever APPENDS a
|
|
63
|
+
// translateY + scale to it (see the desktop block) — so the ride can
|
|
64
|
+
// never re-seat the frame and dragged windows keep their shift.
|
|
65
65
|
transform: translate(
|
|
66
66
|
calc(-50% + var(--hk-modal-shift-x, 0px)),
|
|
67
67
|
calc(-50% + var(--hk-modal-shift-y, 0px))
|
|
@@ -137,126 +137,60 @@
|
|
|
137
137
|
// ------
|
|
138
138
|
|
|
139
139
|
@media (min-width: 768px) {
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
transition:
|
|
156
|
-
opacity var(--hk-modal-duration, 0.42s)
|
|
157
|
-
var(--hk-modal-ease-out, cubic-bezier(0.33, 1, 0.68, 1)),
|
|
158
|
-
height var(--hk-modal-duration, 0.42s)
|
|
159
|
-
var(--hk-modal-ease-out, cubic-bezier(0.33, 1, 0.68, 1)),
|
|
160
|
-
max-height var(--duration-fast, 0.15s) var(--ease-standard, cubic-bezier(0.4, 0, 0.2, 1)),
|
|
161
|
-
clip-path var(--duration-fast, 0.15s) var(--ease-standard, cubic-bezier(0.4, 0, 0.2, 1));
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// The content's compression leg is resident for the same reason — and it
|
|
165
|
-
// is what makes the ENTER start at exactly scaleY(0.9): a freshly
|
|
166
|
-
// inserted element's first style never transitions, so the from-pair
|
|
167
|
-
// paints at 0.9 and only ever animates from there.
|
|
168
|
-
.hk-modal-body {
|
|
169
|
-
transition: transform var(--hk-modal-duration, 0.42s)
|
|
170
|
-
var(--hk-modal-ease-out, cubic-bezier(0.33, 1, 0.68, 1));
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Open: the window's height rides while the chrome keeps its size.
|
|
140
|
+
// Whole-window zoom + gentle rise + fade (user direction 2026-11-07,
|
|
141
|
+
// fifth revision, chosen from a side-by-side comparison page):
|
|
142
|
+
//
|
|
143
|
+
// transform: translateY(10px) scale(0.97) -> translateY(0) scale(1)
|
|
144
|
+
// opacity : 0 -> 1
|
|
145
|
+
//
|
|
146
|
+
// A COMPOSITOR animation end to end: no layout property moves, so nothing
|
|
147
|
+
// reflows per frame and the backdrop-filtered fixed layer never re-rasters
|
|
148
|
+
// — the previous height-ride choreography (0.55.307–0.55.310) animated the
|
|
149
|
+
// frame's layout height and read as jank on content-heavy windows. The 3%
|
|
150
|
+
// scale is imperceptible as glyph distortion while still giving the window
|
|
151
|
+
// a sense of arriving; the 10px rise adds direction. The centering
|
|
152
|
+
// translate stays the frame's PERMANENT transform prefix (byte-identical
|
|
153
|
+
// in every state, `--hk-modal-shift-*` included) so the ride can never
|
|
154
|
+
// re-seat the frame and dragged windows keep their shift.
|
|
174
155
|
.hk-modal-content-enter-active {
|
|
175
156
|
transition:
|
|
176
157
|
opacity var(--hk-modal-duration, 0.42s)
|
|
177
158
|
var(--hk-modal-ease-out, cubic-bezier(0.33, 1, 0.68, 1)),
|
|
178
|
-
|
|
159
|
+
transform var(--hk-modal-duration, 0.42s)
|
|
179
160
|
var(--hk-modal-ease-out, cubic-bezier(0.33, 1, 0.68, 1));
|
|
180
|
-
will-change: opacity;
|
|
161
|
+
will-change: opacity, transform;
|
|
181
162
|
}
|
|
182
163
|
|
|
183
|
-
// Close: the
|
|
184
|
-
//
|
|
164
|
+
// Close: the true reverse — the window settles back to 0.97 and dips
|
|
165
|
+
// 10px while it fades. Width, height and chrome never move in either
|
|
166
|
+
// direction.
|
|
185
167
|
.hk-modal-content-leave-active {
|
|
186
168
|
transition:
|
|
187
169
|
opacity var(--hk-modal-duration, 0.32s)
|
|
188
170
|
var(--hk-modal-ease-in, cubic-bezier(0.45, 0, 0.55, 1)),
|
|
189
|
-
|
|
171
|
+
transform var(--hk-modal-duration, 0.32s)
|
|
190
172
|
var(--hk-modal-ease-in, cubic-bezier(0.45, 0, 0.55, 1));
|
|
191
|
-
will-change: opacity;
|
|
173
|
+
will-change: opacity, transform;
|
|
192
174
|
}
|
|
193
175
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
// so the compressed height and the 0.9 content scale are both real at
|
|
197
|
-
// frame one — the phase-mapped classes that shipped one tick late are
|
|
198
|
-
// exactly what made the open look like clipping (2026-11-07 reports).
|
|
199
|
-
// `!important` outranks the size morph's inline pin for the flight.
|
|
200
|
-
.hk-modal-content[data-hk-ride-ready].hk-modal-content-enter-from,
|
|
201
|
-
.hk-modal-content[data-hk-ride-ready].hk-modal-content-leave-to {
|
|
176
|
+
.hk-modal-content-enter-from,
|
|
177
|
+
.hk-modal-content-leave-to {
|
|
202
178
|
opacity: 0;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
179
|
+
transform: translate(
|
|
180
|
+
calc(-50% + var(--hk-modal-shift-x, 0px)),
|
|
181
|
+
calc(-50% + var(--hk-modal-shift-y, 0px))
|
|
182
|
+
)
|
|
183
|
+
translateY(10px) scale(0.97);
|
|
206
184
|
}
|
|
207
185
|
|
|
208
|
-
// The natural end is an EXPLICIT calc too: the frame's base height is
|
|
209
|
-
// `auto`, and a transition cannot interpolate px -> auto. Without this
|
|
210
|
-
// leg the -active body lock made `auto` resolve to (almost) the
|
|
211
|
-
// compressed height, so the frame never grew while the content's scale
|
|
212
|
-
// unfolded underneath it — the "clipped open" the user reported. When the
|
|
213
|
-
// classes come off at rest, the morph's pin (the same px) catches the
|
|
214
|
-
// handoff with no visible change.
|
|
215
186
|
.hk-modal-content-enter-to,
|
|
216
187
|
.hk-modal-content-leave-from {
|
|
217
188
|
opacity: 1;
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
.hk-modal-content-enter-from > .hk-modal-body,
|
|
224
|
-
.hk-modal-content-leave-to > .hk-modal-body {
|
|
225
|
-
transform: scaleY(var(--hk-ride-factor, 0.9));
|
|
226
|
-
transform-origin: 50% 0;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
.hk-modal-content-enter-to > .hk-modal-body,
|
|
230
|
-
.hk-modal-content-leave-from > .hk-modal-body {
|
|
231
|
-
transform: scaleY(1);
|
|
232
|
-
transform-origin: 50% 0;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// The content region keeps its NATURAL layout height for the whole
|
|
236
|
-
// flight (or the slot shrink and the scale would compound to 0.81 and
|
|
237
|
-
// leave a gap above the footer), and the footer pins to the frame's
|
|
238
|
-
// bottom edge so it stays visible while the content's natural box
|
|
239
|
-
// overflows underneath it. The -active pair spans the flight window in
|
|
240
|
-
// both directions.
|
|
241
|
-
.hk-modal-content-enter-active > .hk-modal-body,
|
|
242
|
-
.hk-modal-content-leave-active > .hk-modal-body {
|
|
243
|
-
flex: 0 0 auto;
|
|
244
|
-
height: var(--hk-ride-content, auto);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
.hk-modal-content-enter-active > .hk-modal-footer,
|
|
248
|
-
.hk-modal-content-leave-active > .hk-modal-footer {
|
|
249
|
-
position: absolute;
|
|
250
|
-
inset-inline: 0;
|
|
251
|
-
bottom: 0;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Close-direction compression timing for the content (the frame's height
|
|
255
|
-
// leg above is 0.32s ease-in; a 0.42s content leg would trail it and open
|
|
256
|
-
// a gap above the pinned footer).
|
|
257
|
-
.hk-modal-content-leave-active > .hk-modal-body {
|
|
258
|
-
transition: transform var(--hk-modal-duration, 0.32s)
|
|
259
|
-
var(--hk-modal-ease-in, cubic-bezier(0.45, 0, 0.55, 1));
|
|
189
|
+
transform: translate(
|
|
190
|
+
calc(-50% + var(--hk-modal-shift-x, 0px)),
|
|
191
|
+
calc(-50% + var(--hk-modal-shift-y, 0px))
|
|
192
|
+
)
|
|
193
|
+
translateY(0) scale(1);
|
|
260
194
|
}
|
|
261
195
|
}
|
|
262
196
|
|
|
@@ -735,7 +669,7 @@
|
|
|
735
669
|
.hk-modal-content {
|
|
736
670
|
transition: none;
|
|
737
671
|
}
|
|
738
|
-
// The
|
|
672
|
+
// The zoom choreography flattens with it: the ride states fall back to
|
|
739
673
|
// the bare centering translate (same specificity as the desktop state
|
|
740
674
|
// rules above, later in the sheet, so the flattened state wins by source
|
|
741
675
|
// order — one painted frame at opacity 0 hides the flip).
|
|
@@ -746,11 +680,8 @@
|
|
|
746
680
|
calc(-50% + var(--hk-modal-shift-y, 0px))
|
|
747
681
|
);
|
|
748
682
|
}
|
|
749
|
-
// The content region carries no transform of its own
|
|
750
|
-
//
|
|
751
|
-
// at (0,2,0) they would NOT outrank a future ride rule at (0,4,0), so the
|
|
752
|
-
// family sheet's own reduced-motion block (which repeats the full
|
|
753
|
-
// selector) is the one that actually flattens the content scale.
|
|
683
|
+
// The content region carries no transform of its own in this choreography
|
|
684
|
+
// (the whole window zooms; the body is never scaled). Belt-and-braces.
|
|
754
685
|
.hk-modal-content .hk-modal-body {
|
|
755
686
|
transform: none;
|
|
756
687
|
transition: none;
|
|
@@ -18,7 +18,6 @@ import { useSurfaceTransition } from "../composables/useSurfaceTransition";
|
|
|
18
18
|
import { useSurfaceMachine } from "../composables/useSurfaceMachine";
|
|
19
19
|
import { useSurfaceContentHold } from "../composables/useSurfaceContentHold";
|
|
20
20
|
import { useSizeMorph, type RideEntry } from "../composables/useSizeMorph";
|
|
21
|
-
import { useHeightRide } from "../composables/useHeightRide";
|
|
22
21
|
|
|
23
22
|
import { STEPFLOW_SWAP_EVENT } from "./HkStepFlow";
|
|
24
23
|
|
|
@@ -332,14 +331,8 @@ export default defineComponent({
|
|
|
332
331
|
// finding). Riding the block keeps the scrollport's content
|
|
333
332
|
// untransformed; the block's own poke below the frame lands
|
|
334
333
|
// off-screen on a bottom-docked sheet.
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
// on the same element: an inline style outranks the ride's class
|
|
338
|
-
// rule, and the close would stop compressing (2026-11-07 user
|
|
339
|
-
// report: the window folded "together with the chrome"). The body
|
|
340
|
-
// ride matters for the ≤767px sheet sweep, where the ride geometry
|
|
341
|
-
// is disabled — so the two never legitimately overlap.
|
|
342
|
-
if (bodyRef.value && !desktopRide()) out.push({ el: bodyRef.value });
|
|
334
|
+
if (bodyRef.value) out.push({ el: bodyRef.value });
|
|
335
|
+
if (bodyRef.value) out.push({ el: bodyRef.value });
|
|
343
336
|
return out;
|
|
344
337
|
},
|
|
345
338
|
// Republish the morph's real sweep span and its landing on the body
|
|
@@ -361,47 +354,11 @@ export default defineComponent({
|
|
|
361
354
|
},
|
|
362
355
|
});
|
|
363
356
|
|
|
364
|
-
// Desktop open/close: the WINDOW's own height rides, the chrome
|
|
365
|
-
// (title bar, sub-header, footer) keeps its natural size, and only the
|
|
366
|
-
// content region is visually compressed. The measurement half lives in
|
|
367
|
-
// the family primitive (useHeightRide); this sheet maps its surface
|
|
368
|
-
// states onto the ride's two ends (see the choreography block below).
|
|
369
|
-
const ride = useHeightRide(contentRef, bodyRef, { factor: 0.9 });
|
|
370
|
-
|
|
371
357
|
/** The window ride's own state, translated from the surface phases:
|
|
372
358
|
* the open/close endpoints are the compressed end (`--from`) and the
|
|
373
359
|
* natural end (`--to`). Between and after them no ride class is
|
|
374
360
|
* present, so the frame keeps its laid-out height (or the morph's
|
|
375
361
|
* px pin) and the geometry contract never leaks into the rest state. */
|
|
376
|
-
// The body element mounts with the frame on every open, and the inline
|
|
377
|
-
// variables die with the previous frame. Republish the split cached from
|
|
378
|
-
// the last rest measurement FIRST (so the first painted frame is already
|
|
379
|
-
// correct), then — if this is the very first open — measure as soon as
|
|
380
|
-
// the element lands (the sheet keeps the frame at `auto` until then, so
|
|
381
|
-
// that measurement sees honest natural geometry).
|
|
382
|
-
watch(
|
|
383
|
-
bodyRef,
|
|
384
|
-
(el) => {
|
|
385
|
-
if (!el) return;
|
|
386
|
-
// ALWAYS measure: the component instance survives a close, so a
|
|
387
|
-
// cached split may belong to different content (this modal is reused
|
|
388
|
-
// across tabs/entries). Publishing the cache first made the second
|
|
389
|
-
// and later opens ride the PREVIOUS geometry, cropping new content
|
|
390
|
-
// and then snapping when the cache expired (R5 finding).
|
|
391
|
-
void nextTick(() => ride.refresh());
|
|
392
|
-
},
|
|
393
|
-
{ flush: "post" },
|
|
394
|
-
);
|
|
395
|
-
|
|
396
|
-
// The ride's geometry lives behind `@media (min-width: 768px)`, so the
|
|
397
|
-
// phase->class mapping alone cannot tell whether the ride actually owns
|
|
398
|
-
// the frame (on a phone the classes land but nothing happens). This
|
|
399
|
-
// predicate gates every "is the ride flying" decision below.
|
|
400
|
-
const desktopRide = (): boolean =>
|
|
401
|
-
typeof window !== "undefined" &&
|
|
402
|
-
window.matchMedia("(min-width: 768px)").matches;
|
|
403
|
-
|
|
404
|
-
|
|
405
362
|
let previouslyFocused: HTMLElement | null = null;
|
|
406
363
|
let unmounted = false;
|
|
407
364
|
|
|
@@ -620,10 +577,6 @@ export default defineComponent({
|
|
|
620
577
|
],
|
|
621
578
|
onPhase: (from, to, event) => {
|
|
622
579
|
if (to === "openingFrom") {
|
|
623
|
-
// Measure BEFORE the enter classes land: the ride's `height:
|
|
624
|
-
// calc()` needs the rest chrome/content split, and a frame that is
|
|
625
|
-
// already compressed reports the compressed box.
|
|
626
|
-
ride.refresh();
|
|
627
580
|
// Open-request bookkeeping (was the modelValue watcher's open
|
|
628
581
|
// arm): registration, overlay registry, back-guard, focus
|
|
629
582
|
// capture.
|
|
@@ -653,71 +606,13 @@ export default defineComponent({
|
|
|
653
606
|
void nextTick(() => {
|
|
654
607
|
const p = machine.phase.value;
|
|
655
608
|
if (p !== "openingFrom" && p !== "openingTo") return;
|
|
656
|
-
|
|
657
|
-
// frame, the morph must not write anything to it: arming installs
|
|
658
|
-
// a ResizeObserver on the body, and the ride's own class change
|
|
659
|
-
// (locking the body to its measured height) fires it — the
|
|
660
|
-
// remeasure then lands `transition: none` mid-flight, which kills
|
|
661
|
-
// the ride's running height/scaleY transitions and pins the
|
|
662
|
-
// COMPRESSED used height as if it were natural (2026-11-07 user
|
|
663
|
-
// report: the open "replays" — ride animates part-way, snaps to
|
|
664
|
-
// the bad pin, then re-animates to full height). The morph arms at
|
|
665
|
-
// the open edge instead, where the ride classes are already off
|
|
666
|
-
// and the pin equals the used height (no visible change). The
|
|
667
|
-
// phone keeps the enter-edge arm: its sheet choreography rides
|
|
668
|
-
// the morph's clip sweep, and the ride geometry is gated off.
|
|
669
|
-
if (!desktopRide()) morph.start();
|
|
609
|
+
if (p === "openingFrom" || p === "openingTo") morph.start();
|
|
670
610
|
});
|
|
671
611
|
} else if (to === "open") {
|
|
672
612
|
surfTrack.cancel();
|
|
673
|
-
// Re-measure at the landed rest geometry so the NEXT ride starts
|
|
674
|
-
// from this box (content may have changed while the surface opened).
|
|
675
|
-
void nextTick(() => ride.refresh());
|
|
676
613
|
onAfterEnter();
|
|
677
|
-
// Idempotent re-arm for the fast paths (see openingFrom)
|
|
678
|
-
|
|
679
|
-
// `open` (its deadline is advisory): arming the morph right now
|
|
680
|
-
// would land `transition: none` + a px pin on a frame whose height
|
|
681
|
-
// is mid-transition, snapping it to the end value (2026-11-07
|
|
682
|
-
// user report: the window "jumps" the last third of its growth).
|
|
683
|
-
// Wait for the height leg to actually land (watchdog: the budget
|
|
684
|
-
// plus slack), then arm — the pin then equals the used height and
|
|
685
|
-
// the handoff is invisible. The phone has no ride; arm at once.
|
|
686
|
-
if (desktopRide()) {
|
|
687
|
-
const f = contentRef.value;
|
|
688
|
-
const armNow = (): void => {
|
|
689
|
-
if (machine.phase.value !== "open" || unmounted) return;
|
|
690
|
-
morph.start();
|
|
691
|
-
};
|
|
692
|
-
// No live CSS duration (test DOMs, reduced motion, the global
|
|
693
|
-
// animation switch): nothing is animating, arm at once — the
|
|
694
|
-
// same zero-duration semantics the surface machine uses.
|
|
695
|
-
const rawDuration = f ? getComputedStyle(f).transitionDuration : "";
|
|
696
|
-
const durMs = rawDuration
|
|
697
|
-
.split(",")
|
|
698
|
-
.map((part) => parseFloat(part))
|
|
699
|
-
.filter((v) => Number.isFinite(v))
|
|
700
|
-
.reduce((a, b) => Math.max(a, b), 0);
|
|
701
|
-
if (!f || durMs === 0) {
|
|
702
|
-
armNow();
|
|
703
|
-
} else {
|
|
704
|
-
let done = false;
|
|
705
|
-
const finish = (): void => {
|
|
706
|
-
if (done) return;
|
|
707
|
-
done = true;
|
|
708
|
-
f.removeEventListener("transitionend", onHeightEnd);
|
|
709
|
-
clearTimeout(guard);
|
|
710
|
-
armNow();
|
|
711
|
-
};
|
|
712
|
-
const onHeightEnd = (e: TransitionEvent): void => {
|
|
713
|
-
if (e.target === f && e.propertyName === "height") finish();
|
|
714
|
-
};
|
|
715
|
-
const guard = setTimeout(finish, 600);
|
|
716
|
-
f.addEventListener("transitionend", onHeightEnd);
|
|
717
|
-
}
|
|
718
|
-
} else {
|
|
719
|
-
morph.start();
|
|
720
|
-
}
|
|
614
|
+
// Idempotent re-arm for the fast paths (see openingFrom).
|
|
615
|
+
morph.start();
|
|
721
616
|
// Flush the growth deferred through the unfold. One tick later
|
|
722
617
|
// the transition classes are off the frame, so the base height
|
|
723
618
|
// transition animates the pin change instead of the enter's
|
|
@@ -726,9 +621,6 @@ export default defineComponent({
|
|
|
726
621
|
if (machine.phase.value === "open") morph.remeasure();
|
|
727
622
|
});
|
|
728
623
|
} else if (to === "closingFrom") {
|
|
729
|
-
// Same rule on the way out: the frame is at its open (natural)
|
|
730
|
-
// height right now, so this is the last honest measurement.
|
|
731
|
-
ride.refresh();
|
|
732
624
|
// Close-request bookkeeping (was the watcher's close arm): the
|
|
733
625
|
// registries forget the surface at request time; the finalize
|
|
734
626
|
// edge below completes the teardown at leave end.
|
|
@@ -1185,7 +1077,6 @@ export default defineComponent({
|
|
|
1185
1077
|
<div
|
|
1186
1078
|
ref={contentRef}
|
|
1187
1079
|
class={["hk-modal-content", props.contentClass, ...machine.classesFor("hk-modal-content")]}
|
|
1188
|
-
data-hk-ride-ready={ride.hasCache() ? "" : undefined}
|
|
1189
1080
|
role="dialog"
|
|
1190
1081
|
aria-modal="true"
|
|
1191
1082
|
aria-label={resolvedSurfaceName.value}
|
|
@@ -1193,7 +1084,6 @@ export default defineComponent({
|
|
|
1193
1084
|
style={{
|
|
1194
1085
|
maxWidth: resolvedWidth.value,
|
|
1195
1086
|
zIndex: contentZ.value,
|
|
1196
|
-
...ride.styleVars(),
|
|
1197
1087
|
|
|
1198
1088
|
}}
|
|
1199
1089
|
onClick={onContentClick}
|