@jsenv/navi 0.29.418 → 0.29.419
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/README.md +1 -1
- package/dist/dev/jsenv_navi.js +192 -43
- package/dist/dev/jsenv_navi.js.map +10 -10
- package/dist/jsenv_navi.js +190 -41
- package/dist/jsenv_navi.js.map +10 -10
- package/docs/AI_INSTRUCTIONS.md +177 -261
- package/docs/actions.md +164 -251
- package/docs/autofocus.md +66 -83
- package/docs/control_object.md +117 -214
- package/docs/control_value.md +121 -185
- package/docs/create_and_edit.md +159 -254
- package/docs/css_architecture.md +196 -260
- package/docs/data_states.md +41 -44
- package/docs/dialog_shape.md +91 -134
- package/docs/drag_interactions.md +115 -174
- package/docs/drag_to_travel.md +10 -9
- package/docs/dynamic_import.md +119 -136
- package/docs/error_handling.md +75 -127
- package/docs/field_validation.md +49 -89
- package/docs/form_changed.md +61 -78
- package/docs/group.md +141 -0
- package/docs/i18n.md +31 -76
- package/docs/interactions.md +6 -4
- package/docs/list_action.md +36 -51
- package/docs/list_refresh.md +73 -115
- package/docs/mobile_touch.md +56 -62
- package/docs/navigation.md +377 -723
- package/docs/network_policy.md +61 -75
- package/docs/pan_zoom.md +62 -103
- package/docs/popup_backdrop.md +128 -168
- package/docs/popup_lift.md +179 -268
- package/docs/popup_open.md +380 -651
- package/docs/resource.md +228 -408
- package/docs/route_transitions.md +333 -482
- package/docs/safe_area.md +102 -89
- package/docs/scroll.md +175 -335
- package/docs/state_binding.md +2 -1
- package/docs/testid.md +18 -23
- package/docs/typography.md +111 -141
- package/docs/view_transitions.md +106 -104
- package/docs/z_index.md +61 -109
- package/package.json +2 -2
- package/docs/badge_list.md +0 -83
- package/docs/control_group.md +0 -168
- package/docs/mobile_layout_pitfalls.md +0 -145
package/docs/popup_lift.md
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
`animation="lifting"` on a `Dialog` (and on a `Picker` in `mode="dialog"`) is
|
|
4
4
|
for one situation: the thing the user pressed is the thing the popup shows,
|
|
5
|
-
brought to the front to be looked at or written in. A card in a feed becomes
|
|
6
|
-
|
|
5
|
+
brought to the front to be looked at or written in. A card in a feed becomes its
|
|
6
|
+
edit sheet; a drawing in a corner becomes the drawing full width. The page
|
|
7
7
|
recedes behind a wall, the box leaves its place, travels and grows, and comes
|
|
8
|
-
back into its place on close — or, with
|
|
9
|
-
|
|
10
|
-
only](#lifting-on-the-way-back-only)).
|
|
8
|
+
back into its place on close — or, with
|
|
9
|
+
`animation={{ open: "scaling", close: "lifting" }}`, only lands somewhere on
|
|
10
|
+
close (see [lifting on the way back only](#lifting-on-the-way-back-only)).
|
|
11
11
|
|
|
12
12
|
It is not a way to open a dialog with a nicer entrance. A dialog that shows
|
|
13
13
|
something else than what was pressed — a menu, a confirmation, a form the
|
|
@@ -25,14 +25,14 @@ and everything below follows from that one fact.
|
|
|
25
25
|
- [What it costs, and where the time goes](#what-it-costs-and-where-the-time-goes)
|
|
26
26
|
- [The wall, and the frame before the movement](#the-wall-and-the-frame-before-the-movement)
|
|
27
27
|
- [What the browser does around it](#what-the-browser-does-around-it)
|
|
28
|
-
- [Measuring](#measuring)
|
|
29
28
|
|
|
30
29
|
## What is lifted is named
|
|
31
30
|
|
|
32
31
|
`data-lift` marks the one node that IS the trigger once in front: a node inside
|
|
33
|
-
the popup, or the popup itself when the whole dialog is that node.
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
the popup, or the popup itself when the whole dialog is that node. A dialog
|
|
33
|
+
holding the lifted card plus a badge above and buttons below lifts the card; a
|
|
34
|
+
dialog that is the card wears `data-lift` itself. The lift takes the first
|
|
35
|
+
`data-lift` the popup holds.
|
|
36
36
|
|
|
37
37
|
```jsx
|
|
38
38
|
<Picker mode="dialog" animation="lifting" ui={<GameCard game={game} />} …>
|
|
@@ -46,31 +46,27 @@ That names one end. The other is the anchor — the box the opening came out of,
|
|
|
46
46
|
and the box the closing goes back into unless `liftAnchor` names another (see
|
|
47
47
|
[a row of cards](#a-row-of-cards-one-popup-that-walks)).
|
|
48
48
|
|
|
49
|
-
The opening waits for
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
with no movement, and dev warns.
|
|
49
|
+
The opening waits for the lifted node: a movement started before it exists would
|
|
50
|
+
carry the card into an empty box. The dialog is opened at once and held
|
|
51
|
+
unpainted — its wall already fading in, the page's scroll already held — and the
|
|
52
|
+
lift starts the moment `data-lift` is in the DOM. Past a second without it, the
|
|
53
|
+
dialog is shown where it stands, with no movement, and dev warns.
|
|
55
54
|
|
|
56
55
|
**Render the lifted node at once.** Waiting is a safety net, not the design: a
|
|
57
56
|
sheet whose card only exists once its data has landed pays that latency on
|
|
58
57
|
every opening. Render the card immediately from what the trigger already holds
|
|
59
58
|
(a read-only copy, a skeleton with the card's box), and let the fields fill in
|
|
60
|
-
inside it
|
|
61
|
-
|
|
62
|
-
form seeded from a `defaultValue`, and the most expensive one (see
|
|
63
|
-
[costs](#what-it-costs-and-where-the-time-goes)).
|
|
59
|
+
inside it — and keep it built across closes unless it must be rebuilt on every
|
|
60
|
+
opening (see [costs](#what-it-costs-and-where-the-time-goes)).
|
|
64
61
|
|
|
65
62
|
## The trigger's box is the card's box
|
|
66
63
|
|
|
67
64
|
The movement starts from the trigger's box — the `Picker`'s root, or a
|
|
68
|
-
`Dialog`'s `anchor`. That box must be the card's: a trigger wider than the
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
demo bench: the picker root at 364px around a 260px card.
|
|
65
|
+
`Dialog`'s `anchor`. That box must be the card's: a trigger wider than the card
|
|
66
|
+
it shows (a bare picker stretched by a column flex, an `expandX` whose `ui` does
|
|
67
|
+
not fill it) starts the lift from a box the card does not fill. The painted box
|
|
68
|
+
then wears the card's width from the first frame while the card sits in its
|
|
69
|
+
corner, and the width never reads as changing.
|
|
74
70
|
|
|
75
71
|
Check it once, at rest: the picker root's `getBoundingClientRect()` equals the
|
|
76
72
|
card's. `variant="bare"` with a `ui` that is the card, in a container that does
|
|
@@ -79,76 +75,69 @@ usual shape.
|
|
|
79
75
|
|
|
80
76
|
## Two kinds: a card, or a scene
|
|
81
77
|
|
|
82
|
-
`lift="box"` (default) is one object at two sizes —
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
whole for most of the way; the arriving one only shows near the end.
|
|
87
|
-
|
|
88
|
-
`lift="scene"` is one scene through two frames — a drawing enlarged. Both
|
|
89
|
-
pictures are drawn at the box's width and centred in it, so the content does
|
|
90
|
-
scale with the box, and a thumbnail lands on the middle of the whole at every
|
|
91
|
-
width.
|
|
78
|
+
`lift="box"` (default) is one object at two sizes — each picture keeps its own
|
|
79
|
+
size, and the box gains room; `lift="scene"` is one scene through two frames —
|
|
80
|
+
both pictures are drawn at the box's width, centred. Choose the kind by what
|
|
81
|
+
grows: text and controls keep their scale (`box`), a drawing does not (`scene`).
|
|
92
82
|
|
|
93
83
|
Two traps, both about the thumbnail:
|
|
94
84
|
|
|
95
85
|
- **A scene under `lift="box"`** keeps the thumbnail at its own size in the
|
|
96
|
-
corner of a growing, empty box, then swaps.
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
it. A thumbnail letterboxed inside a box of another aspect ratio (an SVG
|
|
86
|
+
corner of a growing, empty box, then swaps. Dev warns when the lifted node is,
|
|
87
|
+
box for box, an `<svg>`, `<img>`, `<picture>`, `<canvas>` or `<video>` under
|
|
88
|
+
`box`.
|
|
89
|
+
- **A thumbnail that is not the scene framed.** `scene` assumes the thumbnail is
|
|
90
|
+
either the whole drawing smaller, or a band cut from its middle as wide as it.
|
|
91
|
+
A thumbnail letterboxed inside a box of another aspect ratio (an SVG
|
|
103
92
|
`meet`-fitted into a 2:1 frame), or a crop taken off-centre, cannot land on
|
|
104
|
-
the whole: the drawing seems to slide to one side, then reappear centred.
|
|
105
|
-
|
|
106
|
-
|
|
93
|
+
the whole: the drawing seems to slide to one side, then reappear centred. Give
|
|
94
|
+
the trigger's frame the scene's own aspect ratio, and crop from the middle if
|
|
95
|
+
you crop.
|
|
107
96
|
|
|
108
97
|
## The lifted node paints itself
|
|
109
98
|
|
|
110
99
|
The moving box wears the lifted node's background for the length of the
|
|
111
|
-
movement, so where the box has grown past the picture it carries, it is the
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
(`
|
|
119
|
-
|
|
120
|
-
the box uncovers — except when the two boxes are the same size (`box`) or the
|
|
121
|
-
same aspect ratio (`scene`): both pictures then fit the box the whole way,
|
|
122
|
-
nothing needs uncovering, and the ink travels with the box.
|
|
100
|
+
movement, so where the box has grown past the picture it carries, it is the card
|
|
101
|
+
that has grown. That paint is read off `data-lift` — and, when that node paints
|
|
102
|
+
nothing, off the first descendant that has its box and paints. So a transparent
|
|
103
|
+
wrapper around the card is fine, and a card whose colour lives on a nested
|
|
104
|
+
element of a different size is not: publish the colour on the box that is the
|
|
105
|
+
card (a gradient or an image travels as well). A shadow or a halo is in the
|
|
106
|
+
picture but cut at the box's edge, except when the two boxes are the same size
|
|
107
|
+
(`box`) or the same aspect ratio (`scene`): both pictures then fit the box the
|
|
108
|
+
whole way, and the ink travels with it.
|
|
123
109
|
|
|
124
110
|
Corners are not paint: they are written per box, on purpose — the same card at
|
|
125
|
-
two sizes does not want the same round
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
corners itself, or on the card inside it, reads the same.
|
|
111
|
+
two sizes does not want the same round. The moving box leaves with the corners
|
|
112
|
+
of the box it leaves and arrives with those of the box it arrives on, each read
|
|
113
|
+
as the first round found going down through the nodes that are that box — so a
|
|
114
|
+
bare trigger carrying its corners itself, or on the card inside it, reads the
|
|
115
|
+
same.
|
|
131
116
|
|
|
132
117
|
## Same width, or a wider box
|
|
133
118
|
|
|
134
|
-
On a `Picker`, `dialogSizeFromAnchor` makes the trigger's
|
|
135
|
-
dialog
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
and the width change is animated — the
|
|
140
|
-
gaining room on the right, then the wider
|
|
119
|
+
On a `Picker`, `dialogSizeFromAnchor` alone makes the trigger's width both the
|
|
120
|
+
dialog's floor and its ceiling (a `dialogMaxWidth` of the caller's still wins
|
|
121
|
+
over the ceiling): the same card, brought forward — the box travels and may grow
|
|
122
|
+
in height, never in width. On a `Dialog`, `sizeFromAnchor` is only the floor,
|
|
123
|
+
and `maxWidth="var(--anchor-width)"` adds the ceiling. Left out, the sheet can
|
|
124
|
+
take the room it has (`dialogExpandX`), and the width change is animated — the
|
|
125
|
+
card keeps its scale inside a box gaining room on the right, then the wider
|
|
126
|
+
layout arrives.
|
|
141
127
|
|
|
142
128
|
Both read well; what does not is a box that is neither: a card whose width the
|
|
143
129
|
sheet changes by a few pixels for no reason the eye can name. Decide.
|
|
144
130
|
|
|
145
131
|
## A row of cards: one popup that walks
|
|
146
132
|
|
|
147
|
-
A row of small drawings — trophies on a profile, photos, badges — where
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
133
|
+
A row of small drawings — trophies on a profile, photos, badges — where pressing
|
|
134
|
+
one brings it to the front, big, and from there the next one is reached without
|
|
135
|
+
going back to the row. The popup is then about the whole row, and the press only
|
|
136
|
+
says where it opens: one `Dialog` for the row, lifting, holding a
|
|
137
|
+
`SlideContainer` the walk moves through. A picker per drawing is the first thing
|
|
138
|
+
one writes, and a dead end — each would have to hold the whole row to be
|
|
139
|
+
walkable (the third case of
|
|
140
|
+
[a shared popup](./popup_open.md#when-a-shared-popup-is-still-the-right-answer)).
|
|
152
141
|
|
|
153
142
|
```jsx
|
|
154
143
|
const currentKeySignal = useSignal(undefined);
|
|
@@ -190,88 +179,55 @@ const currentKeySignal = useSignal(undefined);
|
|
|
190
179
|
</Dialog>
|
|
191
180
|
```
|
|
192
181
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
the
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
**
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
**The arrows reach the walk from anywhere on the surface.** Only slides go in a
|
|
236
|
-
`SlideContainer`, so the chevrons pinned to the edges of a full-screen surface
|
|
237
|
-
are outside it, and the keyboard, once it lands on one of them, walks nothing.
|
|
238
|
-
`data-slide-container-follows={SLIDES_ID}` on the `Dialog` — the outermost
|
|
239
|
-
element, which is what holds the keyboard when nothing in it does — makes the
|
|
240
|
-
whole surface a follower.
|
|
241
|
-
|
|
242
|
-
**Where it comes back to is named too.** The opening's anchor is the drawing
|
|
243
|
-
that was pressed, and a walk that moved on has something else in front by the
|
|
244
|
-
time it closes: left alone, the silver cup flies home into the gold cup's
|
|
245
|
-
tile. `liftAnchor` says the other end — same grammar as `anchor` (element, ref
|
|
246
|
-
or id), read at the close rather than kept from the opening, so what names the
|
|
247
|
-
tile currently in front is read then. An id built from the signal the walk is
|
|
248
|
-
bound to is the shortest way to say it, the row's tiles carrying the matching
|
|
249
|
-
ids. Left out, the box comes back where it came from, which is right exactly
|
|
250
|
-
as long as nothing walked.
|
|
251
|
-
|
|
252
|
-
When the walk signal is the address's — the popup's `signal`, so the sheet
|
|
253
|
-
comes back open on the same card after leaving the page (see
|
|
254
|
-
[popup_open.md](./popup_open.md#signal--the-app-holds-it-both-ways)) — a close
|
|
255
|
-
that comes through the address, the back button, empties it before the popup
|
|
256
|
-
knows it is closing, and a name built from it at that render says nothing:
|
|
257
|
-
the box flies home into the first tile. Name the tile from the last key the
|
|
258
|
-
walk held — a ref written while the signal holds one — not from the signal
|
|
259
|
-
itself.
|
|
260
|
-
|
|
261
|
-
**A press on the surface that dismisses is `data-navi-popup-outside`.** Marking
|
|
262
|
-
the see-through box as backdrop (see
|
|
263
|
-
[popup_backdrop.md](./popup_backdrop.md)) is read on the press itself. A close
|
|
264
|
-
written by hand on a click has to tell a click from the end of a swipe — and
|
|
265
|
-
that guard is the sign the marker was missed.
|
|
182
|
+
The drawing it opens on is the command's `value`
|
|
183
|
+
([opening it ON something](./popup_open.md#opening-it-on-something)), and
|
|
184
|
+
`onOpen` writes it into the container's own signal: the press seeds the walk,
|
|
185
|
+
and from then on the chevrons, the arrows and a thumb write the same signal.
|
|
186
|
+
|
|
187
|
+
**`data-lift` moves with the walk, and has to be right on the first frame.** The
|
|
188
|
+
lifted node is the current slide's drawing — a condition on the signal — and the
|
|
189
|
+
lift takes the first `data-lift` on the frame the popup opens.
|
|
190
|
+
`mount="while-opened"` is what makes that the drawing the open named, the
|
|
191
|
+
content being built after `onOpen`: content kept across openings still carries
|
|
192
|
+
the mark of the drawing the walk was left on. The closing reads it the same way,
|
|
193
|
+
and a mark left on a slide the walk moved off is a picture taken off screen: the
|
|
194
|
+
box flies in from outside the surface.
|
|
195
|
+
|
|
196
|
+
**The button is the drawing and nothing else**: its box is what travels (see
|
|
197
|
+
[above](#the-triggers-box-is-the-cards-box)), so a count floating in a corner or
|
|
198
|
+
a level written underneath belongs outside the button, positioned against the
|
|
199
|
+
tile. **No `anchor` prop** either: the button pressed is the anchor
|
|
200
|
+
([the anchor](./popup_open.md#the-anchor), third rule), and an `anchor` prop
|
|
201
|
+
wins over the press.
|
|
202
|
+
|
|
203
|
+
**The arrows reach the walk from anywhere on the surface.** The chevrons pinned
|
|
204
|
+
to the edges of a full-screen surface are outside the `SlideContainer` — only
|
|
205
|
+
slides go in it — so the keyboard, once it lands on one of them, walks nothing.
|
|
206
|
+
`data-slide-container-follows={SLIDES_ID}` on the `Dialog`, the outermost
|
|
207
|
+
element and what holds the keyboard when nothing in it does, makes the whole
|
|
208
|
+
surface a follower.
|
|
209
|
+
|
|
210
|
+
**Where it comes back to is named too.** A walk that moved on has something else
|
|
211
|
+
in front by the time it closes: left alone, the silver cup flies home into the
|
|
212
|
+
gold cup's tile. `liftAnchor` says the other end, read at the close — an id
|
|
213
|
+
built from the walk's signal, the tiles carrying the matching ids. When that
|
|
214
|
+
signal is the address's (the popup's `signal`, see
|
|
215
|
+
[popup_open.md](./popup_open.md#signal--the-app-holds-it-both-ways)), a close
|
|
216
|
+
through the address, the back button, empties it before the popup knows it is
|
|
217
|
+
closing, and a name built from it at that render names no tile: build it from
|
|
218
|
+
the last key the walk held — a ref written while the signal holds one.
|
|
219
|
+
|
|
220
|
+
**A press on the surface that dismisses is `data-navi-popup-outside`** (see
|
|
221
|
+
[popup_backdrop.md](./popup_backdrop.md#where-the-outside-begins)), read on the
|
|
222
|
+
press itself. A close written by hand on a click has to tell a click from the
|
|
223
|
+
end of a swipe — and that guard is the sign the marker was missed.
|
|
266
224
|
|
|
267
225
|
## Lifting on the way back only
|
|
268
226
|
|
|
269
|
-
Sometimes the opening is not a lift and the closing is. A banner
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
nothing morphs on the way in. But on the way out one box does travel into
|
|
274
|
-
another, and that other box did not exist when the reveal opened.
|
|
227
|
+
Sometimes the opening is not a lift and the closing is. A banner opens a
|
|
228
|
+
full-screen reveal, the crest big in a halo; collecting it sends the crest down
|
|
229
|
+
into its place on the rank plate that replaces the banner — a box that did not
|
|
230
|
+
exist when the reveal opened.
|
|
275
231
|
|
|
276
232
|
```jsx
|
|
277
233
|
<Dialog
|
|
@@ -292,143 +248,98 @@ another, and that other box did not exist when the reveal opened.
|
|
|
292
248
|
</Dialog>
|
|
293
249
|
```
|
|
294
250
|
|
|
295
|
-
**The opening is whatever `open` says** (`"auto"` included)
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
251
|
+
**The opening is whatever `open` says** (`"auto"` included): no wait for
|
|
252
|
+
`data-lift`, no opaque wall, and `data-lift` is read at the close alone. Nor
|
|
253
|
+
does the opening's own exit play: while a closing lift runs, the dialog's
|
|
254
|
+
transitions are off, or `scaling`'s exit would keep the dialog painted into the
|
|
255
|
+
picture of the state it closes into.
|
|
256
|
+
|
|
257
|
+
**The box it lands in is rendered by the close.** `liftAnchor` is read once the
|
|
258
|
+
close has been made, `onClose` included, inside the transition: a state written
|
|
259
|
+
in `onClose` has rendered by the time the landing is looked up. It is also the
|
|
260
|
+
one place that state can be written — before the close, the reveal would
|
|
261
|
+
disappear from the picture being left; after it, the picture of the arrival is
|
|
262
|
+
already taken — and not by unmounting the dialog with the component that holds
|
|
263
|
+
it, which takes it off screen without a close, and without a lift. The dialog
|
|
264
|
+
may go away with the state it writes: the picture of the reveal was taken
|
|
265
|
+
before. The id goes on the element that is exactly the small crest, not on the
|
|
266
|
+
plate around it, for the reason
|
|
267
|
+
[the trigger's box is the card's box](#the-triggers-box-is-the-cards-box).
|
|
308
268
|
|
|
309
269
|
**It is waited for, briefly.** When `liftAnchor` names nothing yet, navi waits
|
|
310
|
-
for it to appear,
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
270
|
+
up to 300 ms for it to appear, the screen frozen on the reveal meanwhile — a
|
|
271
|
+
render, not a fetch: the landing must be drawable from what the page already
|
|
272
|
+
holds. Past the wait, the dialog closes without landing (its picture fades out)
|
|
273
|
+
and dev warns.
|
|
314
274
|
|
|
315
275
|
**Tell the collecting close from the others.** Escape, the back button and a
|
|
316
|
-
press on the wall
|
|
317
|
-
|
|
318
|
-
of a `--navi-close`)
|
|
319
|
-
others
|
|
320
|
-
|
|
321
|
-
**The dialog may go away with the state.** A reveal shown in place of the
|
|
322
|
-
plate usually lives in the same branch as the banner and is unmounted by the
|
|
323
|
-
state it writes. That is fine: the picture of the reveal was taken before the
|
|
324
|
-
close.
|
|
325
|
-
|
|
326
|
-
**The landing box is the crest's box**, for the same reason the trigger's box
|
|
327
|
-
is the card's box (see [above](#the-triggers-box-is-the-cards-box)): the id
|
|
328
|
-
goes on the element that is exactly the small crest, not on the plate around
|
|
329
|
-
it.
|
|
330
|
-
|
|
331
|
-
**The opening's own exit does not play.** While a closing lift runs, the
|
|
332
|
-
dialog's transitions are off. `scaling`'s exit would keep the dialog painted
|
|
333
|
-
into the picture of the state it closes into.
|
|
276
|
+
press on the wall close the dialog too, and usually mean "not now" rather than
|
|
277
|
+
"collect". A `Dialog`'s `onClose` receives who asked (`e.detail.requester`, the
|
|
278
|
+
button of a `--navi-close`), and only the close that collects writes the state.
|
|
279
|
+
The others land nowhere: the fixed `liftAnchor` names nothing, so they wait out
|
|
280
|
+
those 300 ms, fade out, and dev warns.
|
|
334
281
|
|
|
335
282
|
## What it costs, and where the time goes
|
|
336
283
|
|
|
337
|
-
Measured at CPU ×6 on the demo bench
|
|
338
|
-
(`src/control/demos/picker/12_picker_card_demo.html#lift-bench`),
|
|
339
|
-
a card of a real edit sheet's weight (≈215 elements, 13 fields, 62 picker
|
|
340
|
-
nodes):
|
|
341
|
-
|
|
342
|
-
| opening | tap → first moving frame |
|
|
343
|
-
| ------------------------------------------- | ------------------------ |
|
|
344
|
-
| the sheet rebuilt on every opening | 355–416 ms |
|
|
345
|
-
| the sheet already built (`from-first-open`) | 63–94 ms |
|
|
346
|
-
| a drawing (9 elements) | ≈140 ms |
|
|
347
|
-
|
|
348
284
|
Navi's own share of an opening — showing the dialog, placing it, the two
|
|
349
|
-
pictures — is the
|
|
350
|
-
|
|
351
|
-
|
|
285
|
+
pictures — is the smaller part. The rest is the content: preact and the
|
|
286
|
+
components of the sheet, spread thin with no single hot spot, plus `showModal()`
|
|
287
|
+
and one layout. So the lever is the content:
|
|
352
288
|
|
|
353
289
|
- **Build less, or earlier.** A sheet that is the same across openings keeps
|
|
354
|
-
`mount="from-first-open"`. A sheet that must be rebuilt (`while-opened`)
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
[a row of cards](#a-row-of-cards-one-popup-that-walks)
|
|
290
|
+
`mount="from-first-open"`. A sheet that must be rebuilt (`while-opened`) pays
|
|
291
|
+
its build on every tap; keep it as light as the page allows. A popup whose
|
|
292
|
+
lifted node changes from one opening to the next has no choice, and
|
|
293
|
+
[a row of cards](#a-row-of-cards-one-popup-that-walks) rebuilds every slide
|
|
294
|
+
on every opening.
|
|
358
295
|
- **Do not rebuild by accident.** A sheet whose code or data is tied to the
|
|
359
|
-
address (`?edit=<id>` driving a route action) is thrown away when the
|
|
360
|
-
|
|
296
|
+
address (`?edit=<id>` driving a route action) is thrown away when the address
|
|
297
|
+
clears and rebuilt through a `null` render on the next opening —
|
|
361
298
|
`from-first-open` then rebuilds anyway. Keep the code loaded across closes,
|
|
362
299
|
and seed the sheet from what the trigger holds.
|
|
363
300
|
- **Warm what a first opening creates.** Intl formatters (a day spin's labels)
|
|
364
|
-
are created on the first opening and cached; a page that will lift a card
|
|
365
|
-
|
|
301
|
+
are created on the first opening and cached; a page that will lift a card can
|
|
302
|
+
render one such label at idle.
|
|
366
303
|
|
|
367
|
-
The movement itself is the browser's
|
|
368
|
-
|
|
369
|
-
included.
|
|
304
|
+
The movement itself is the browser's: once it starts, the content's cost is
|
|
305
|
+
over.
|
|
370
306
|
|
|
371
307
|
## The wall, and the frame before the movement
|
|
372
308
|
|
|
373
|
-
The frame the dialog opens on is the picture the movement starts from
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
the
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
What that frame does not do is say a tap was taken. That answer belongs to
|
|
387
|
-
the trigger, which is what the movement starts from anyway: a hold has the
|
|
388
|
-
finger still on it, and a `Picker`'s trigger wears `aria-expanded="true"` from
|
|
389
|
-
the open on, for an app that wants it pressed in while the sheet builds.
|
|
390
|
-
|
|
391
|
-
`animation="lifting"` brings its own wall — opaque and blurred
|
|
392
|
-
(`--navi-backdrop-lift-*`), the page it came out of being what the movement
|
|
393
|
-
leaves; `backdropVariant="discrete"` asks for the light wash back, and
|
|
394
|
-
`backdropVariant="lift"` asks for that wall on a popup that does not move. See
|
|
395
|
-
`popup_backdrop.md`.
|
|
309
|
+
The frame the dialog opens on is the picture the movement starts from. On it the
|
|
310
|
+
card is still an element of the page, under the wall, and the movement then
|
|
311
|
+
draws it on its own, sharp, above the wall — so any wall on that frame is worn
|
|
312
|
+
by the card for the length of the build, then taken off at once when the
|
|
313
|
+
movement starts: a flash. The wall arrives from nothing, over the movement.
|
|
314
|
+
|
|
315
|
+
What that frame does not do is say a tap was taken. That answer belongs to the
|
|
316
|
+
trigger, which is what the movement starts from anyway: a hold has the finger
|
|
317
|
+
still on it, and a `Picker`'s trigger wears `aria-expanded="true"` from the open
|
|
318
|
+
on, for an app that wants it pressed in while the sheet builds. The wall's own
|
|
319
|
+
paint is
|
|
320
|
+
[popup_backdrop.md](./popup_backdrop.md#the-one-animation-that-decides-its-own-backdrop)'s.
|
|
396
321
|
|
|
397
322
|
## What the browser does around it
|
|
398
323
|
|
|
399
|
-
- **The page is photographed too**, deliberately:
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
bare page; in an app it showed only past a few thousand pixels of scroll).
|
|
324
|
+
- **The page is photographed too**, deliberately: the dialog and its wall live
|
|
325
|
+
in the top layer, which the browser paints during a transition only as part of
|
|
326
|
+
the root's picture (see
|
|
327
|
+
[view_transitions.md](./view_transitions.md#the-top-layer-is-painted-through-the-roots-picture)).
|
|
404
328
|
The price is a page frozen and unpressable while the movement plays: under a
|
|
405
|
-
modal wall at the opening, and
|
|
329
|
+
modal wall at the opening, and for the popup's animation duration at the
|
|
330
|
+
closing.
|
|
406
331
|
- **Fixed bars stay in the page's picture**, under the wall like everything
|
|
407
|
-
else. The popup is placed in the room between them, and
|
|
408
|
-
to that room on the way
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
not move, and past the clip to the room between the bars. Scroll gestures
|
|
417
|
-
on the background are cancelled until the movement is over, the way
|
|
418
|
-
`scrollCapture` cancels them while a dialog is open.
|
|
332
|
+
else. The popup is placed in the room between them, and the moving box is
|
|
333
|
+
clipped to that room on the way, so a card half under the bottom bar leaves
|
|
334
|
+
from under it and comes back under it. Naming the bars to draw them over the
|
|
335
|
+
box was tried: a bar photographed on its own has no wall over it.
|
|
336
|
+
- **The page is held still for the movement.** Its picture is frozen for the
|
|
337
|
+
length of the transition while the browser keeps following the live anchor,
|
|
338
|
+
so a scroll would carry the arriving box along under a page that does not
|
|
339
|
+
move. Scroll gestures on the background are cancelled until the movement is
|
|
340
|
+
over, the way `scrollCapture` cancels them while a dialog is open.
|
|
419
341
|
- **The closing is photographed whole**, the picture of the popup taken before
|
|
420
|
-
the close takes it off screen
|
|
421
|
-
all go through it
|
|
422
|
-
browser does not close it before the picture.
|
|
342
|
+
the close takes it off screen: Escape, the wall, `--navi-close` and `navBack`
|
|
343
|
+
all go through it.
|
|
423
344
|
- **One lift at a time.** A document has one view transition; a lift started
|
|
424
345
|
while another plays replaces it, and the replaced one is released cleanly.
|
|
425
|
-
|
|
426
|
-
## Measuring
|
|
427
|
-
|
|
428
|
-
Movement is measured, never eyeballed (see the animations skill). The bench in
|
|
429
|
-
`12_picker_card_demo.html#lift-bench` reports, per opening, tap → dialog open
|
|
430
|
-
and tap → first moving frame, plus a census of what the dialog holds, next to
|
|
431
|
-
the numbers of a real app's sheet; throttle the CPU in devtools to read a
|
|
432
|
-
phone's. A re-rasterized screenshot lies about a running transition: to see
|
|
433
|
-
what the screen shows, record a video (`recordVideo`, then frames through
|
|
434
|
-
ffmpeg) and, for a size, track the colour of the card frame by frame.
|