2626 * so any consumer can override it per-instance from CSS (or via the
2727 * `animationDuration` prop, wired to --popup-animation-duration through
2828 * Box's styleCSSVars) without touching this file: `--popup-animation-duration`,
29- * `--popup-scale-from`, `--popup-border-radius`. `slide-from-*`'s own
29+ * `--popup-scale-from`, `--popup-cover-travel`, `--popup-border-radius`.
30+ * `slide-from-*`'s own
3031 * 100%-of-own-size distance is hardcoded for now rather than exposed as a
3132 * variable — fine to revisit if a consumer ever needs to override it.
3233 *
6465 * so it slides in instead. The word names *where it comes from*: placed
6566 * "top" (a point/corner), it slides in from the top.
6667 *
68+ * `animation="cover-from-top"`: a slide reads through what it shows first,
69+ * and a box travelling its own height from above shows its bottom first —
70+ * the tail of whatever it holds, then, in the last frame, its head. That is
71+ * the wrong way round for a panel whose head sits at the edge it comes from
72+ * (a top-docked SidePanel: title and tabs at its top). So here the cut alone
73+ * does the reveal — the same curtain descending from the container's top
74+ * edge a full slide draws — while the box travels only `--popup-cover-travel`
75+ * (default 15%) of its own height: the head is on screen from the first
76+ * frame, settling its last few pixels into place. The exit is the same
77+ * movement backwards and still reads as a slide, the head leading. One
78+ * direction only: a bottom sheet leads with its head by itself, and a
79+ * left/right panel keeps its head on the axis it does not travel.
80+ *
6781 * `animation="expand-*"` (a real anchor only, explicit opt-in — "scaling"
6882 * reads better overall, see popover.jsx's top comment): grows out of the
6983 * anchor's own edge via `scale` + `transform-origin` instead of a translate, which
@@ -89,6 +103,7 @@ export const popupCss = /* css */ `
89103 .navi_dialog {
90104 --popup-animation-duration: 0.18s;
91105 --popup-scale-from: 0.9;
106+ --popup-cover-travel: 15%;
92107
93108 --popup-opacity-duration: var(--popup-animation-duration);
94109 --popup-translate-duration: var(--popup-animation-duration);
@@ -119,6 +134,31 @@ export const popupCss = /* css */ `
119134 var(--popup-translate-duration);
120135 transition-timing-function: ease;
121136 transition-behavior: allow-discrete;
137+
138+ /* Where the area the popup was placed in has its edges, in the popup's
139+ own coordinates: --container-position-room-* is how far past each of
140+ its own edges the popup may still paint before reaching that edge
141+ (applyNewPosition in @jsenv/dom), so the negated value puts a cut ON
142+ it. Read by the kinds below that cut their travel (slide, cover): each
143+ takes the side(s) it needs. A popup that was never placed reads
144+ 100vmax and is not cut at all.
145+ Outside that area is either the glass beside an app narrowed with
146+ --navi-app-max-width (layout/safe_area.js) or whatever surrounds a
147+ container — neither the popup's to paint, and a popup in the top layer
148+ answers to no overflow of the document. The popup itself is never cut
149+ either: the rooms floor at 0. */
150+ --x-popup-cut-top-at-area: calc(
151+ -1 * var(--container-position-room-top, 100vmax)
152+ );
153+ --x-popup-cut-right-at-area: calc(
154+ -1 * var(--container-position-room-right, 100vmax)
155+ );
156+ --x-popup-cut-bottom-at-area: calc(
157+ -1 * var(--container-position-room-bottom, 100vmax)
158+ );
159+ --x-popup-cut-left-at-area: calc(
160+ -1 * var(--container-position-room-left, 100vmax)
161+ );
122162 }
123163
124164 /* box-shadow fades in/out alongside any animation kind, instead of
@@ -213,30 +253,8 @@ export const popupCss = /* css */ `
213253 &[navi-animation="slide-from-bottom-left"],
214254 &[navi-animation="slide-from-bottom-right"] {
215255 opacity: 1;
216- /* Where the area the popup was placed in has its edges, in the popup's
217- own coordinates: --container-position-room-* is how far past each of
218- its own edges the popup may still paint before reaching that edge
219- (applyNewPosition in @jsenv/dom), so the negated value puts a cut ON
220- it. Only the side(s) the travel comes from take one (see the direction
221- rules above); the rest stay far outside the box, uncut. A popup that
222- was never placed reads 100vmax and is not cut at all.
223- Outside that area is either the glass beside an app narrowed with
224- --navi-app-max-width (layout/safe_area.js) or whatever surrounds a
225- container — neither the popup's to paint, and a popup in the top layer
226- answers to no overflow of the document. The popup itself is never cut
227- either: the rooms floor at 0. */
228- --x-popup-cut-top-at-area: calc(
229- -1 * var(--container-position-room-top, 100vmax)
230- );
231- --x-popup-cut-right-at-area: calc(
232- -1 * var(--container-position-room-right, 100vmax)
233- );
234- --x-popup-cut-bottom-at-area: calc(
235- -1 * var(--container-position-room-bottom, 100vmax)
236- );
237- --x-popup-cut-left-at-area: calc(
238- -1 * var(--container-position-room-left, 100vmax)
239- );
256+ /* Only the side(s) the travel comes from take a cut (see the direction
257+ rules above); the rest stay far outside the box, uncut. */
240258 --x-popup-travel-x: calc(var(--x-popup-slide-x, 0) * 100%);
241259 --x-popup-travel-y: calc(var(--x-popup-slide-y, -1) * 100%);
242260 clip-path: inset(
@@ -263,6 +281,40 @@ export const popupCss = /* css */ `
263281 transform: translate(var(--x-popup-travel-x), var(--x-popup-travel-y));
264282 }
265283 }
284+
285+ /* cover — a top-docked popup unrolling from the top edge of its area (see
286+ this file's top comment): the cut does the reveal, the box travels
287+ --popup-cover-travel of its own height. The far cut sits where the
288+ popup's shadow ends (--navi-popup-box-shadow reaches about 50px) or
289+ where the room does, whichever is nearer, so the reveal finishes on
290+ the box and its shadow at the pace of the box's own settle. On the
291+ box's own edge it would shave the shadow off for good; on a distant
292+ container edge it would run far ahead of the box. */
293+ &[navi-animation="cover-from-top"] {
294+ opacity: 1;
295+ --x-popup-cut-top: var(--x-popup-cut-top-at-area);
296+ --x-popup-cut-bottom: max(var(--x-popup-cut-bottom-at-area), -60px);
297+ --x-popup-travel-y: calc(-1 * var(--popup-cover-travel));
298+ clip-path: inset(
299+ var(--x-popup-cut-top) -100vmax var(--x-popup-cut-bottom) -100vmax
300+ );
301+
302+ transform: translate(0px, 0px);
303+
304+ &[aria-expanded="false"] {
305+ /* The near cut follows the travel, so it holds the area's top line at
306+ every instant (the slide family's arithmetic); the far cut starts on
307+ that same line and descends to its open place. What is on screen is
308+ the descending curtain a full slide draws, holding the top of the
309+ popup instead of its tail. */
310+ clip-path: inset(
311+ calc(var(--x-popup-cut-top) - var(--x-popup-travel-y)) -100vmax
312+ calc(100% + var(--x-popup-travel-y) - var(--x-popup-cut-top))
313+ -100vmax
314+ );
315+ transform: translate(0px, var(--x-popup-travel-y));
316+ }
317+ }
266318 }
267319
268320 .navi_popover {
0 commit comments