@jsenv/navi 0.29.417 → 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.
Files changed (45) hide show
  1. package/README.md +1 -1
  2. package/dist/dev/jsenv_navi.js +347 -83
  3. package/dist/dev/jsenv_navi.js.map +12 -12
  4. package/dist/jsenv_navi.js +345 -81
  5. package/dist/jsenv_navi.js.map +12 -12
  6. package/docs/AI_INSTRUCTIONS.md +177 -258
  7. package/docs/actions.md +164 -251
  8. package/docs/autofocus.md +66 -83
  9. package/docs/control_object.md +117 -214
  10. package/docs/control_value.md +121 -185
  11. package/docs/create_and_edit.md +159 -254
  12. package/docs/css_architecture.md +196 -260
  13. package/docs/data_states.md +41 -44
  14. package/docs/dialog_shape.md +91 -134
  15. package/docs/drag_interactions.md +291 -454
  16. package/docs/drag_to_travel.md +152 -748
  17. package/docs/dynamic_import.md +119 -136
  18. package/docs/error_handling.md +75 -127
  19. package/docs/field_validation.md +49 -89
  20. package/docs/form_changed.md +61 -78
  21. package/docs/group.md +141 -0
  22. package/docs/i18n.md +31 -76
  23. package/docs/interactions.md +170 -326
  24. package/docs/list_action.md +36 -51
  25. package/docs/list_refresh.md +73 -115
  26. package/docs/mobile_touch.md +56 -62
  27. package/docs/navigation.md +377 -723
  28. package/docs/network_policy.md +61 -75
  29. package/docs/pan_zoom.md +62 -103
  30. package/docs/popup_backdrop.md +128 -168
  31. package/docs/popup_lift.md +179 -268
  32. package/docs/popup_open.md +380 -651
  33. package/docs/resource.md +228 -408
  34. package/docs/route_transitions.md +333 -473
  35. package/docs/safe_area.md +102 -89
  36. package/docs/scroll.md +175 -317
  37. package/docs/state_binding.md +40 -84
  38. package/docs/testid.md +18 -23
  39. package/docs/typography.md +111 -141
  40. package/docs/view_transitions.md +106 -104
  41. package/docs/z_index.md +61 -109
  42. package/package.json +2 -2
  43. package/docs/badge_list.md +0 -83
  44. package/docs/control_group.md +0 -168
  45. package/docs/mobile_layout_pitfalls.md +0 -145
@@ -14,29 +14,24 @@ It answers three questions, and they are independent:
14
14
  not painted at all. That is the paint: `backdropVariant`, `backdropColor`,
15
15
  `backdropFilter`.
16
16
 
17
- Keeping them apart is the whole point of this page. A popup that must close on
18
- an outside click — because that click is the way out of the screen — may also
19
- need the page behind to stop competing for the eye. How much it withdraws says
20
- nothing about what the click does, and neither says whether the page behind is
21
- still reachable.
17
+ Keeping them apart is the whole point of this page: how much the page withdraws
18
+ says nothing about what the click does, and neither says whether the page behind
19
+ is still reachable.
22
20
 
23
21
  ## Is there a backdrop at all
24
22
 
25
- A backdrop is a wall: a full-screen element in front of the page, which wins
26
- hit-testing. That is how it absorbs a press — not by handling the event and
27
- stopping it, but by being what the pointer hits. Nothing behind it hears
28
- anything.
23
+ A backdrop is a wall: an element in front of the page — the screen, or the box
24
+ a `layer="local"` popup is confined to — which wins hit-testing. That is how it
25
+ absorbs a press: not by handling the event and stopping it, but by being what
26
+ the pointer hits. Nothing behind it hears anything.
29
27
 
30
28
  So a popup that closes on an outside press spends that press: the first press
31
- dismisses, and a second one is needed to do the thing the user was already
32
- pointing at. That is right when the page has withdrawn — the dim says the page
33
- is off, and a press on it means "come back", nothing more.
34
-
35
- It is wrong when nothing withdrew. A bubble opened over a map, a plan, a canvas
36
- — a page that looks exactly as pressable as it did a second ago — makes the
37
- next press a natural continuation of the gesture, not a dismissal. Taking it to
38
- close the bubble spends it on something the user never asked for, and on a page
39
- that gave no sign it would.
29
+ dismisses, and a second one is needed to do what the user was already pointing
30
+ at. That is right when the page has withdrawn — the dim says the page is off,
31
+ and a press on it means "come back". It is wrong when nothing withdrew: over a
32
+ map, a plan, a canvas that looks exactly as pressable as a second ago, the next
33
+ press continues the gesture, and taking it to close the bubble spends it on
34
+ something the user never asked for.
40
35
 
41
36
  `backdrop={false}` is how that popup says there is no wall:
42
37
 
@@ -46,66 +41,63 @@ that gave no sign it would.
46
41
 
47
42
  The popup then hears an outside press from the document itself, and takes
48
43
  nothing from it: no `preventDefault`, no `stopPropagation`. It closes, and the
49
- same press is answered by whatever it landed on — one gesture, one press.
50
-
51
- What it listens to is `pointerdown`, the press itself rather than what the
52
- browser makes of it afterwards. A page that is still live is a page whose own
53
- elements arbitrate their presses, and a cancelled `pointerdown` — what a drag
54
- source and a control keeping the focus both do — suppresses every mouse event
55
- that would have followed. So the popup dismisses on the press, on a finger as
56
- on a mouse, whatever the element under it decides to do with that press.
57
-
58
- **It is not `backdropVariant="invisible"`.** A wall that is not painted is
59
- still a wall, and it still eats the press; the two props answer different
60
- questions. `"invisible"` is for a popup that must absorb — a menu whose
61
- dismissing click must not also press what is under it — without dimming the
62
- page for it. `backdrop={false}` is for a popup that must not absorb at all.
44
+ same press is answered by whatever it landed on — one gesture, one press. It
45
+ listens to `pointerdown`, the press itself, since a cancelled `pointerdown` (a
46
+ drag source, a control keeping the focus) suppresses every mouse event after
47
+ it: the popup dismisses on the press, whatever the element under it does with
48
+ it.
49
+
50
+ **It is not `backdropVariant="invisible"`.** A wall that is not painted is still
51
+ a wall, and it still eats the press. `"invisible"` is for a popup that must
52
+ absorb — a menu whose dismissing click must not also press what is under it —
53
+ without dimming the page for it; `backdrop={false}` is for a popup that must not
54
+ absorb at all.
63
55
 
64
56
  **It is what decides whether a popup is modal.** `Popover` is never modal,
65
57
  either layer, and neither is a `layer="local"` dialog. A top-layer `Dialog` is
66
- modal exactly when it has a wall: with one it is `showModal()`'d and the
67
- browser makes everything behind genuinely inert; without one it goes to the
68
- same top layer through the Popover
69
- API (`popover="manual"`), placed against the screen just the same, over a page
70
- that stays live. `Popup` forwards `backdrop` in both modes, so which of popover
71
- or dialog the small-screen resolution picks says nothing about whether one
72
- press or two are needed.
73
-
74
- That is what a sheet on a phone is made of. `dockedOnSmallTouchScreen` docks
75
- the dialog against an edge of its container (the top one, or `"bottom"` for a
76
- sheet one only reads and taps — see [`dialog_shape.md`](./dialog_shape.md)),
77
- and `layer` is what that container is — `"top"` for the screen, `"local"` for
78
- the box the popup was declared in. A sheet flush with the bottom of the screen
79
- over a map still being read object by object is `layer="top"` for the shape and
80
- `backdrop={false}` for the map:
58
+ modal exactly when it has a wall: with one it is `showModal()`'d and the browser
59
+ makes everything behind genuinely inert; without one it goes to the same top
60
+ layer through the Popover API, placed against the screen just the same, over a
61
+ page that stays live. `Popup` forwards `backdrop` in both modes, so which of
62
+ popover or dialog the small-screen resolution picks says nothing about whether
63
+ one press or two are needed.
64
+
65
+ That is what a sheet on a phone is made of. `dockedOnSmallTouchScreen` docks the
66
+ dialog against an edge of its container (see
67
+ [`dialog_shape.md`](./dialog_shape.md)), and `layer` is what that container is —
68
+ `"top"` for the screen, `"local"` for the box the popup was declared in. A sheet
69
+ flush with the bottom of the screen over a map still being read object by
70
+ object is `layer="top"` for the shape and `backdrop={false}` for the map:
81
71
 
82
72
  ```jsx
83
73
  <Popup dockedOnSmallTouchScreen="bottom" layer="top" backdrop={false}>
84
74
  ```
85
75
 
86
- Going `layer="local"` to keep the map live instead confines the sheet to the
87
- box it was declared in — full width and flush against _that_, which on a
88
- partial container reads as a sheet that failed rather than as a sheet.
76
+ Going `layer="local"` to keep the map live instead confines the sheet to the box
77
+ it was declared in — full width and flush against _that_, which on a partial
78
+ container reads as a sheet that failed rather than as a sheet.
89
79
 
90
- What a wall-less dialog gives up is the one thing only a modal gets natively:
91
- the hardware/gesture back button no longer dismisses it (same accepted
92
- limitation as `layer="local"`, see [`dialog_shape.md`](./dialog_shape.md)).
93
- Focus is not trapped either, deliberately — a page meant to be reachable is
94
- meant to be reachable with the keyboard too.
80
+ What a dialog that is not modal — wall-less, or `layer="local"` — gives up is
81
+ the one thing only a modal gets natively: the hardware/gesture back button no
82
+ longer dismisses it. No web API hooks into that outside the browser's own
83
+ modal-dismissal stack, which only a genuine `showModal()` element joins: an
84
+ accepted limitation, not an oversight. A wall-less `Dialog` does not trap the
85
+ focus either, deliberately — a page meant to be reachable is meant to be
86
+ reachable with the keyboard too. (A `SidePanel` that closes on an outside press
87
+ does trap Tab in popover mode: it sets `focusCapture`.)
95
88
 
96
- `pressOutside="capture"` and `backdrop={false}` contradict
97
- each other — absorbing is what a wall does — and navi warns rather than
98
- silently behaving like `"ignore"`.
89
+ `pressOutside="capture"` and `backdrop={false}` contradict each other —
90
+ absorbing is what a wall does — and navi warns rather than silently behaving
91
+ like `"ignore"`.
99
92
 
100
93
  ## Where the outside begins
101
94
 
102
95
  A popup reads "outside" from its own border box. What the press landed on
103
96
  settles nothing by itself — a press on a real backdrop and a press on the
104
- popup's own padding both report the popup element as their target, there being
105
- no `::backdrop` node to be one — so the rectangle is what tells them apart.
106
-
107
- That works as long as the box and what the popup paints are the same thing. A
108
- popup with no surface of its own is the case where they are not:
97
+ popup's own padding both report the popup element as their target — so the
98
+ rectangle is what tells them apart. That works as long as the box and what the
99
+ popup paints are the same thing. A popup with no surface of its own is the case
100
+ where they are not:
109
101
 
110
102
  ```jsx
111
103
  <Dialog
@@ -119,7 +111,7 @@ popup with no surface of its own is the case where they are not:
119
111
  Here the sheet is whatever the children paint, and everything between them is
120
112
  backdrop to the eye and inside the box to the code. A press just above the box
121
113
  dismisses the popup; the same press two pixels lower, on the empty half of a
122
- row, is ignored — the rectangle says it landed on the popup.
114
+ row, is ignored.
123
115
 
124
116
  `data-navi-popup-outside` is how the caller says which of its own boxes are not
125
117
  the surface:
@@ -132,30 +124,25 @@ the surface:
132
124
 
133
125
  A press on that row — left of the wheel, right of it, or anywhere in the height
134
126
  it reserves while the wheel is hidden — does exactly what the same press on the
135
- backdrop does, `pressOutside` and all: `"cancel"`
136
- reverts, `"capture"` absorbs it, `"ignore"` (a `Popover`'s default, where no
137
- backdrop is rendered at all) leaves it without an answer.
138
-
139
- It is opt-in because navi cannot infer it: a background can come from anywhere,
140
- and the caller who chose the transparency is the one who knows which box is
141
- decoration and which is paper.
127
+ backdrop does, `pressOutside` and all. It is opt-in because navi cannot infer
128
+ it: only the caller who chose the transparency knows which box is decoration and
129
+ which is paper.
142
130
 
143
131
  **The marker answers for the element it is on, never for its descendants.** The
144
- wheel above is painted, so a press on it is a press on the popup — which is
145
- what lets one marker cover a whole row without swallowing the controls it
146
- holds. Mark the box whose own background is the see-through part; a nested box
147
- that is see-through too needs its own marker.
148
-
149
- **Space beside something, not space between two things.** The row above is the
150
- whole of what it marks: what the wheel does not cover is plainly nothing, and a
151
- press there reads as a press on the page. The gap between two rows of a column
152
- does not read that way — it is a seam of one thing the eye holds together,
153
- nobody dismisses a sheet by aiming between two of its lines, and on a phone
154
- that gap is where a thumb lands on its way to the control below. So the marker
155
- goes on a box that IS free space — the height a slot reserves while its control
156
- is hidden, the margin beside a centred control — and never on the column that
157
- stacks the sheet's parts: a layout box is mostly its gaps, and marking one
158
- turns every gap into a dismissal.
132
+ wheel above is painted, so a press on it is a press on the popup — which is what
133
+ lets one marker cover a whole row without swallowing the controls it holds. A
134
+ nested box that is see-through too needs its own marker.
135
+
136
+ **Space beside something, not space between two things.** What the wheel does
137
+ not cover is plainly nothing, and a press there reads as a press on the page.
138
+ The gap between two rows of a column does not read that way — it is a seam of
139
+ one thing the eye holds together, and on a phone it is where a thumb lands on
140
+ its way to the control below. So the marker goes on a box that IS free space —
141
+ the height a slot reserves while its control is hidden, the margin beside a
142
+ centred control — and never on the column that stacks the sheet's parts: a
143
+ layout box is mostly its gaps, and marking one turns every gap into a dismissal.
144
+ Navi does not warn about a marked container: from the outside the two are the
145
+ same box, and only the caller knows which one it wrote.
159
146
 
160
147
  A box that only reserves space while something is away gives the marker back
161
148
  when that something returns:
@@ -166,10 +153,6 @@ when that something returns:
166
153
  </Box>
167
154
  ```
168
155
 
169
- Navi does not warn about a marked container: the column whose gaps must not
170
- dismiss and the row whose sides must are the same box from the outside, and
171
- only the caller knows which one it wrote.
172
-
173
156
  ### A box of the page that is not outside
174
157
 
175
158
  `data-navi-popup-inside` is the other direction: a box of the page the caller
@@ -194,48 +177,35 @@ panel meant to stay open, blinking.
194
177
  </Box>
195
178
  ```
196
179
 
197
- The value names the popup by `id`, the way `commandfor` does, and the exemption
198
- is that popup's alone: a menu open beside the board still closes when a card is
199
- pressed. Several popups are named with a space between the ids.
200
-
201
- Unlike the outside marker, this one answers for its whole subtree: the outside
202
- marker carves free space out of a surface whose controls stay surface, this one
203
- adds a whole thing to the popup's ground — the card, its count, its buttons —
204
- and nothing in it is a dismissal.
205
-
206
- It reads a press the page hears, so it belongs with a popup that has no wall
180
+ The value names the popup by `id`, the way `commandfor` does — several ids with
181
+ a space between them — and the exemption is that popup's alone: a menu open
182
+ beside the board still closes when a card is pressed. Unlike the outside marker,
183
+ this one answers for its whole subtree: it adds a whole thing to the popup's
184
+ ground — the card, its count, its buttons — and nothing in it is a dismissal. It
185
+ reads a press the page hears, so it belongs with a popup that has no wall
207
186
  (`backdrop={false}`): behind a wall, no card is pressable.
208
187
 
209
188
  ### `pointer-events: none` and `inert` are not this
210
189
 
211
190
  Neither says "this is backdrop", and reaching for them here is the natural
212
- mistake:
213
-
214
- - `pointer-events: none` takes the box out of hit-testing, so the press is
215
- answered by the nearest ancestor that is still in it — still a descendant of
216
- the popup, still inside. Putting it on the marked box makes the marker
217
- unreachable; navi warns about that in dev. Putting it on a decoration
218
- _inside_ a marked box is the one useful case: the press falls to the marked
219
- box, which is the answer wanted.
220
- - `inert` speaks to the keyboard and to assistive technology. It does nothing
221
- at all for a press.
191
+ mistake. Both take the box out of hit-testing — `inert` as well as speaking to
192
+ the keyboard and to assistive technology — so the press is answered by the
193
+ nearest ancestor still in it: still a descendant of the popup, still inside. On
194
+ the marked box, either makes the marker unreachable (navi warns about
195
+ `pointer-events: none` in dev). On a decoration _inside_ a marked box,
196
+ `pointer-events: none` is the one useful case: the press falls to the marked
197
+ box, which is the answer wanted.
222
198
 
223
199
  ## Painting one popup: two props
224
200
 
225
201
  ```jsx
226
- <Dialog
227
- backdropColor="rgb(6 10 20 / 88%)"
228
- backdropFilter="blur(4px)"
229
- >
202
+ <Dialog backdropColor="rgb(6 10 20 / 88%)" backdropFilter="blur(4px)">
230
203
  ```
231
204
 
232
- `backdropColor` is the wash (any CSS color), `backdropFilter` what that wash
233
- does to the picture underneath (any `backdrop-filter` value — `blur()`,
234
- `saturate()`, `grayscale()`). Either alone is fine: a blur over navi's default
235
- dim keeps the page recognisable without darkening it further.
236
-
237
- Both are forwarded by `Popup`, `SidePanel`, `Picker` and `SplitButton`, next to
238
- `backdropVariant`.
205
+ `backdropColor` is the wash, `backdropFilter` what it does to the picture
206
+ underneath; either alone is fine — a blur over navi's default dim keeps the page
207
+ recognisable without darkening it further. `Popup`, `SidePanel`, `Picker` and
208
+ `SplitButton` forward both, next to `backdropVariant`.
239
209
 
240
210
  ## Painting every popup: the theme tokens
241
211
 
@@ -249,7 +219,12 @@ kind of backdrop has a colour **and** a filter, and they travel together:
249
219
  | `backdropVariant="discrete"` | `--navi-backdrop-discrete-background`, `--navi-backdrop-discrete-backdrop-filter` |
250
220
  | `animation="lifting"`, `backdropVariant="lift"` | `--navi-backdrop-lift-background`, `--navi-backdrop-lift-backdrop-filter` |
251
221
 
252
- Only `capture` blurs out of the box among the three above: the rest of the page
222
+ The `close` pair is also a top-layer `Dialog`'s base paint, so `"ignore"` still
223
+ dims there. A `layer="local"` dialog paints nothing under `"ignore"` — its wall
224
+ is still there — and a `Popover` renders no backdrop at all. `"ignore"` is
225
+ `SidePanel`'s default.
226
+
227
+ Only `capture` blurs out of the box among the first three: the rest of the page
253
228
  is genuinely unreachable then, so it reads as clearly secondary. Nothing else
254
229
  about `capture` makes the blur its own — set the `close` filter token and every
255
230
  popup that closes on an outside click blurs too.
@@ -257,37 +232,24 @@ popup that closes on an outside click blurs too.
257
232
  ## The one animation that decides its own backdrop
258
233
 
259
234
  Every kind above is keyed on what the popup _does_. `animation="lifting"` is
260
- keyed on what the popup _is_: not a surface shown over the page, but the box
261
- the anchor became. That changes the answer to "how far does what is behind
235
+ keyed on what the popup _is_: not a surface shown over the page, but the box the
236
+ anchor became. That changes the answer to "how far does what is behind
262
237
  withdraw?", so it brings its own pair — opaque, blurred — rather than the 8%
263
238
  wash a popup shown over a page still being read wants.
264
239
 
265
- Three things follow from the morph and all three point the same way. What is
266
- lifted is looked at — a card, a plan, a picture is brought forward because it
267
- could not be read where it was, and anything still legible behind competes
268
- with it. Such a popup often paints no surface of its own (the thing that was
269
- lifted already has its frame, so
270
- `backgroundColor="transparent"`), which makes the backdrop the background of
271
- the popup's own content rather than something merely behind it. And the
272
- movement itself is the browser interpolating a picture of the document: a crisp
273
- page behind a box that is still travelling reads as two things moving at once.
274
-
275
- It is a default, not a rule. `backdropColor`/`backdropFilter` win over it as
276
- they win over everything, `backdropVariant` overrides it, and
277
- `backdropVariant="discrete"` is how a lifting popup asks for the light wash
278
- back.
279
-
280
- `backdropVariant="lift"` is the same thing said the other way round: that wall,
281
- on a popup that does not morph. What made it the right answer above is the
282
- lifted thing being what the eye is on, and a popup can be that without having
283
- travelled — a picture opened full, a plan, a card that paints its own surface
284
- (`backgroundColor="transparent"`) so the wall is its background. The movement
285
- is one reason to want it, not the only one.
286
-
287
- `backdropVariant` covers the other direction too: `"discrete"` for a
288
- barely-there dim, `"invisible"` for no paint at all. None of them changes what
289
- the outside click does — the wall is still there and still catches it. Whether
290
- there is a wall to paint is `backdrop`, above.
240
+ Three things follow from the morph, and all three point the same way. What is
241
+ lifted is looked at — brought forward because it could not be read where it was
242
+ — and anything still legible behind competes with it. Such a popup often paints
243
+ no surface of its own (`backgroundColor="transparent"`), which makes the
244
+ backdrop the background of its content. And the movement is the browser
245
+ interpolating a picture of the document: a crisp page behind a box still
246
+ travelling reads as two things moving at once.
247
+
248
+ It is a default, not a rule: the paint props win over it as they win over
249
+ everything, and `backdropVariant="discrete"` is how a lifting popup asks for the
250
+ light wash back. `backdropVariant="lift"` is the same thing said the other way
251
+ round: that wall, on a popup that does not morph but whose content is what the
252
+ eye is on — a picture opened full, a plan, a card painting its own surface.
291
253
 
292
254
  `"invisible"` is the one kind with no filter token: it paints nothing, and a
293
255
  filter would still be seen.
@@ -297,11 +259,11 @@ filter would still be seen.
297
259
  **The backdrop is not inside the popup**, and which element it is depends on the
298
260
  renderer:
299
261
 
300
- | popup | its backdrop |
301
- | ---------------------- | ---------------------------------------------------------------------------------------------------- |
302
- | `Dialog layer="top"` | the native `::backdrop` pseudo-element of the `<dialog>` |
303
- | `Dialog layer="local"` | a sibling `div.navi_dialog_backdrop`, before the dialog |
304
- | `Popover` | a sibling `.navi_popover_backdrop` (see popover.jsx's top comment for why it cannot be a descendant) |
262
+ | popup | its backdrop |
263
+ | ---------------------- | ----------------------------------------------------------------- |
264
+ | `Dialog layer="top"` | the native `::backdrop` pseudo-element of the `<dialog>` |
265
+ | `Dialog layer="local"` | a sibling `div.navi_dialog_backdrop`, before the dialog's wrapper |
266
+ | `Popover` | a sibling `.navi_popover_backdrop` |
305
267
 
306
268
  The pseudo-element inherits custom properties from the dialog — which is what
307
269
  makes the tokens above reach it at all. The sibling elements do not: they
@@ -310,19 +272,17 @@ itself lands on the paint under one renderer and silently does nothing under the
310
272
  other, and a stylesheet rule hanging off the popup's own class cannot reach the
311
273
  sibling at all except through a `:has()` that writes navi's DOM shape into the
312
274
  app's stylesheet — the kind of selector that stops matching the day navi moves a
313
- box.
314
-
315
- The props exist for that: navi sets them on the element that paints, whichever
316
- one it is, so the same two lines hold under both renderers.
275
+ box. The props exist for that: navi sets them on the element that paints,
276
+ whichever one it is.
317
277
 
318
278
  ## What wins over what
319
279
 
320
- Painting is resolved through two variables the popup and its backdrop carry,
321
- `--backdrop-background` and `--backdrop-filter`. Navi's own rules — the ones
322
- keyed on `pressOutside` and on `backdropVariant` — write them
323
- as defaults; the props write them inline on the same element, which beats every
324
- rule. So `backdropColor` wins over `backdropVariant="invisible"`, and a variant
325
- is only ever what the caller did not say.
326
-
327
- A `Popover` with `pressOutside="ignore"` renders no backdrop at
328
- all: there is nothing to paint, and both props are ignored.
280
+ Painting is resolved through two variables, `--backdrop-background` and
281
+ `--backdrop-filter`, on the element that paints — the `<dialog>` of a top-layer
282
+ one, whose `::backdrop` inherits them, the sibling element otherwise. Navi's own
283
+ rules — keyed on `pressOutside`, `animation="lifting"` and `backdropVariant` —
284
+ write them as defaults; the props write them inline on the same element, which
285
+ beats every rule. So `backdropColor` wins over `backdropVariant="invisible"`,
286
+ and a variant is only ever what the caller did not say. A `Popover` with
287
+ `pressOutside="ignore"` has no backdrop, so there is nothing for either prop to
288
+ paint.