@jsenv/navi 0.29.464 → 0.29.465

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.
@@ -147,9 +147,11 @@ start when unsure which export solves a problem.
147
147
  own actions). Before passing `open`, calling `triggerNaviCommand`, or
148
148
  writing a close button.
149
149
  - `popup_backdrop.md` — three independent questions: a wall or not, what an
150
- outside press does, how far the page withdraws; and the browser's
151
- `theme-color` that a popup paints over. Before CSS for a backdrop, or a
152
- `theme-color` meta.
150
+ outside press does, how far the page withdraws; popups that hand over to
151
+ each other in one press (`PopupGroup`: the wall around a box of the page);
152
+ and the browser's `theme-color` that a popup paints over. Before CSS for a
153
+ backdrop, a `theme-color` meta, or making a popup's neighbours pressable
154
+ above its wall.
153
155
  - `popup_lift.md` — `animation="lifting"`: the pressed card brought to the front.
154
156
  Before giving a `Dialog` or a `Picker` `animation="lifting"`.
155
157
  - `dialog_shape.md` — bounds rather than a width, the container ceiling, the
@@ -55,10 +55,12 @@ absorb at all.
55
55
 
56
56
  **It is what decides whether a popup is modal.** `Popover` is never modal,
57
57
  either layer, and neither is a `layer="local"` dialog. A top-layer `Dialog` is
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
58
+ modal exactly when it has a wall of its own: with one it is `showModal()`'d and
59
+ the browser makes everything behind genuinely inert; without one it goes to the
60
+ same top layer through the Popover API, placed against the screen just the
61
+ same, over a page that stays live — or, in a `PopupGroup`, over a page the
62
+ group's wall covers (see
63
+ [Popups that hand over to each other](#popups-that-hand-over-to-each-other-popupgroup)). `Popup` forwards `backdrop` in both modes, so which of
62
64
  popover or dialog the small-screen resolution picks says nothing about whether
63
65
  one press or two are needed.
64
66
 
@@ -77,14 +79,17 @@ Going `layer="local"` to keep the map live instead confines the sheet to the box
77
79
  it was declared in — full width and flush against _that_, which on a partial
78
80
  container reads as a sheet that failed rather than as a sheet.
79
81
 
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
82
+ Outside a `PopupGroup`, what a dialog that is not modal — wall-less, or
83
+ `layer="local"` — gives up is the one thing only a modal gets natively: the
84
+ hardware/gesture back button no longer dismisses it. No web API hooks into that outside the browser's own
83
85
  modal-dismissal stack, which only a genuine `showModal()` element joins: an
84
86
  accepted limitation, not an oversight. A wall-less `Dialog` does not trap the
85
87
  focus either, deliberately — a page meant to be reachable is meant to be
86
88
  reachable with the keyboard too. (A `SidePanel` that closes on an outside press
87
- does trap Tab in popover mode: it sets `focusCapture`.)
89
+ does trap Tab in popover mode: it sets `focusCapture`.) In a group the group is
90
+ the modal: the back gesture closes the open popup as a cancel, and the keyboard
91
+ stays in the group (see
92
+ [Popups that hand over to each other](#popups-that-hand-over-to-each-other-popupgroup)).
88
93
 
89
94
  `pressOutside="capture"` and `backdrop={false}` contradict each other —
90
95
  absorbing is what a wall does — and navi warns rather than silently behaving
@@ -183,7 +188,8 @@ beside the board still closes when a card is pressed. Unlike the outside marker,
183
188
  this one answers for its whole subtree: it adds a whole thing to the popup's
184
189
  ground — the card, its count, its buttons — and nothing in it is a dismissal. It
185
190
  reads a press the page hears, so it belongs with a popup that has no wall
186
- (`backdrop={false}`): behind a wall, no card is pressable.
191
+ (`backdrop={false}`), or with a popup of a `PopupGroup` and a box of that group:
192
+ behind a wall, no card is pressable.
187
193
 
188
194
  ### `pointer-events: none` and `inert` are not this
189
195
 
@@ -196,6 +202,116 @@ the marked box, either makes the marker unreachable (navi warns about
196
202
  `pointer-events: none` is the one useful case: the press falls to the marked
197
203
  box, which is the answer wanted.
198
204
 
205
+ ## Popups that hand over to each other: `PopupGroup`
206
+
207
+ A wall spends the first press, and between popups that follow one another that
208
+ is the wrong press to spend. On a row of score cells, each a picker, going from
209
+ one cell to the next costs a press to dismiss and a second one to open, though
210
+ the user pointed at the next cell straight away. `backdrop={false}` gives the
211
+ press back, but leaves the whole page live; `"invisible"` hides the dim and
212
+ gives nothing back.
213
+
214
+ `PopupGroup` keeps the wall and moves it: around a box of the page, with that
215
+ box in front.
216
+
217
+ ```jsx
218
+ <PopupGroup backdropColor="rgb(6 10 20 / 60%)">
219
+ <Picker mode="popover">…</Picker>
220
+ <Picker mode="popover">…</Picker>
221
+ <Button action={addSet}>+</Button>
222
+ </PopupGroup>
223
+ ```
224
+
225
+ While one of its popups is open, what the group holds stays lit and answers for
226
+ real — hover, the pressed state, a gesture, the focus — and the rest of the
227
+ screen is behind the wall, which can now be dimmed: the screen says what
228
+ answers. A row of score cells, a menu bar, a toolbar, the players of a court
229
+ each opening a bubble.
230
+
231
+ ### Inside is the DOM
232
+
233
+ A popup is a member by being rendered inside the group: no name, no id.
234
+ `Popover`, `Dialog` and what is built on them count — `Popup`, `SidePanel`,
235
+ `SplitButton`, a `Picker`'s popover, dialog or callout. Every other control the
236
+ group holds is pressable too: a "+" or a delete button needs nothing.
237
+
238
+ **What is not a descendant of the group is behind the wall, wherever it is
239
+ painted.** While a popup of the group is open, the group is the modal, and the
240
+ browser makes everything else inert: a neighbour lifted above the wall with a
241
+ `z-index`, or rendered elsewhere through a portal, is painted and dead.
242
+
243
+ A popup opened from inside a member is in front of it, not a peer of it: the
244
+ group stops at each popup. What is lit is the group's whole box, its background
245
+ and border with it, so the group is the smallest box that holds the peers — on a
246
+ court that also draws its links and its empty seats, a layer that holds only
247
+ the players.
248
+
249
+ ### What a press does while one is open
250
+
251
+ - On another popup's trigger: the open popup closes — a `Picker` notes its
252
+ value — and that one opens, in the same press.
253
+ - On anything else in the group: the open popup closes, and the press does what
254
+ it does there.
255
+ - On the wall: the open popup closes, and nothing else hears the press.
256
+ - Inside the open popup, or on a box marked `data-navi-popup-inside` for it:
257
+ nothing closes.
258
+
259
+ One popup of the group is open at a time: a press on another trigger, or a
260
+ popup opened from the keyboard or from code, asks the open one to close first.
261
+ A popup that refuses to close (a value it cannot note) stays open: it keeps the
262
+ press, and nothing else opens; one opened from the keyboard or from code opens
263
+ beside it.
264
+
265
+ **`pressOutside` stays the popup's, read against a wall that is always there.**
266
+ `"close"` and `"cancel"` work as everywhere, `"capture"` spends the press and
267
+ stays open, and `"ignore"` closes like `"close"`: there is no page under it to
268
+ reach. `backdrop={false}` on a member changes nothing.
269
+
270
+ ### The group is the modal, its popups are not
271
+
272
+ A `Dialog` in a group is never `showModal()`'d: it goes to the top layer
273
+ through the Popover API, above the group. It keeps what a modal gives all the
274
+ same, since the group is one: everything outside is inert, the keyboard reaches
275
+ only what the group holds, and Escape and the back gesture close the open popup
276
+ as a cancel, wherever the focus is in the group. A group works inside a sheet,
277
+ and closes with it.
278
+
279
+ **The wall is the group's.** `backdropVariant`, `backdropColor` and
280
+ `backdropFilter` go on `<PopupGroup>`, whose default paint is the `close` pair,
281
+ like a top-layer `Dialog`'s. A member's own are not read, and navi warns in
282
+ dev; neither is what its `"capture"` or its `animation="lifting"` would paint —
283
+ `backdropVariant="lift"` on the group is that wall. Above the screen, the
284
+ browser's colour is the group's wall over the page's colour, and the group's
285
+ `themeColor` answers for it; a member flush with the top edge still lays its
286
+ surface over that, and a member's own `themeColor`, when it names a colour,
287
+ gives the colour while it is open.
288
+
289
+ **The page behind stays still while a `Popover` or a `Dialog` of the group is
290
+ open**, the wheel or the finger on the wall or over a part of the group with
291
+ nothing to scroll. A callout of the group lets the page scroll, as a callout
292
+ does anywhere, and goes away on that scroll when told to
293
+ (`calloutCloseByScroll`) — where the group stands in the page's own scroll. In
294
+ a sheet, a list that scrolls on its own or a fixed bar, a gesture on the wall
295
+ could only scroll the document behind, never the box the group stands in: the
296
+ page stays still there too, and a wheel over the bubble still scrolls that
297
+ box.
298
+
299
+ ### While one is open, the group leaves the page
300
+
301
+ To stand in front, the group is shown in the top layer where it stands. An
302
+ invisible placeholder keeps its place in the page — the element just before it,
303
+ marked `navi-out-of-flow` (see
304
+ [`css_architecture.md`](./css_architecture.md#counting-children-what-navi-puts-in-your-tree))
305
+ — and the group stays pinned to that place: it follows a scroll, a resize, a
306
+ sheet that moves, and is clipped as its ancestors clip it. At rest it is a
307
+ `<dialog>` the app styles as it would a `<div>`.
308
+
309
+ Two things follow from the group being the modal. A `Dialog` rendered among
310
+ the group's peers is a member, never modal; one opened from inside a member is
311
+ in front of it, and modal as anywhere. And a drag that starts in the group
312
+ while one of its popups is open cannot land outside it until that press is
313
+ over.
314
+
199
315
  ## Painting one popup: two props
200
316
 
201
317
  ```jsx
@@ -333,6 +449,7 @@ renderer:
333
449
  | `Dialog layer="top"` | the native `::backdrop` pseudo-element of the `<dialog>` |
334
450
  | `Dialog layer="local"` | a sibling `div.navi_dialog_backdrop`, before the dialog's wrapper |
335
451
  | `Popover` | a sibling `.navi_popover_backdrop` |
452
+ | a popup of a group | the native `::backdrop` of the `PopupGroup`'s `<dialog>` |
336
453
 
337
454
  The pseudo-element inherits custom properties from the dialog — which is what
338
455
  makes the tokens above reach it at all. The sibling elements do not: they
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/navi",
3
- "version": "0.29.464",
3
+ "version": "0.29.465",
4
4
  "type": "module",
5
5
  "description": "Library of components including navigation to create frontend applications",
6
6
  "repository": {
@@ -34,7 +34,7 @@
34
34
  "preact": "^11.0.0-rc.2"
35
35
  },
36
36
  "dependencies": {
37
- "@jsenv/dom": "0.17.90",
37
+ "@jsenv/dom": "0.17.91",
38
38
  "@jsenv/humanize": "1.8.7",
39
39
  "@jsenv/validity": "0.4.5"
40
40
  },