@jsenv/navi 0.29.324 → 0.29.325

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.
@@ -159,8 +159,9 @@ to start when unsure which export solves a problem.
159
159
  `triggerNaviCommand` as the last resort, with the event forwarded and never
160
160
  invented; opening ON something; a press that opens and acts is a `Picker`,
161
161
  and its trigger wears the wait and the error (a card that lifts, `openOn`);
162
- Escape cancels; the close cross; one panel fed by a slot (`createSlot`,
163
- `open={isFilled}`); a popup that loads data. Read before passing `open`,
162
+ Escape cancels; the close cross; one panel fed by a slot (`createSlot`: one
163
+ `SlotFill` where the choice is made, `open` bound to the screen's signal, never
164
+ to `isFilled`); a popup that loads data. Read before passing `open`,
164
165
  calling `triggerNaviCommand`, or writing a close button.
165
166
  - `popup_backdrop.md` — three independent questions: a wall or not
166
167
  (`backdrop={false}`), what an outside press does, how far the page withdraws;
@@ -57,8 +57,28 @@ Two consequences worth knowing before writing CSS of your own:
57
57
  ## One dialog, two shapes
58
58
 
59
59
  `dockedOnSmallTouchScreen` is the whole small-screen story in one prop: on a
60
- small touch screen the dialog stops being a centered box and becomes a bottom
61
- sheet; everywhere else nothing changes.
60
+ small touch screen the dialog stops being a centered box and becomes a sheet
61
+ flush against one edge of the screen; everywhere else nothing changes.
62
+
63
+ The value says which edge. `true` rests the sheet on the **top** edge, out of
64
+ the virtual keyboard's way: a field tapped in the sheet raises the keyboard
65
+ below it, and the question and the field stay where the finger found them.
66
+ `"bottom"` rests it on the bottom edge, where the thumbs are — for a sheet one
67
+ reads and taps (a list, a couple of buttons, a confirmation) and that holds
68
+ nothing raising a keyboard. A bottom sheet one types into is reflowed into the
69
+ strip left above the keyboard at every keystroke, and the mistake is silent on
70
+ the desktop the author tests on — which is why the safe edge is the default
71
+ and the thumb-first one is the opt-in, and why a `"bottom"` sheet found holding
72
+ a field is warned about in dev, on any screen.
73
+
74
+ ```jsx
75
+ <Dialog dockedOnSmallTouchScreen /> // top: the keyboard cannot reach it
76
+ <Dialog dockedOnSmallTouchScreen="bottom" /> // no field in here, thumbs first
77
+ ```
78
+
79
+ The edge is the prop's own value rather than `positionArea` because
80
+ `positionArea` places _both_ shapes: `positionArea="bottom"` also pins the
81
+ centered box to the bottom of a desktop window.
62
82
 
63
83
  Both halves of the name matter. Touch alone would dock a tablet or a kiosk
64
84
  panel — a whole screen away from where the finger just tapped. Size alone would
@@ -70,38 +90,38 @@ re-resolves the dialog.
70
90
  What docking supplies — defaults only, so any single axis of the sheet can be
71
91
  adjusted without giving up the rest:
72
92
 
73
- | supplied | value | why |
74
- | --------------------- | ---------- | ----------------------------------------------------- |
75
- | `positionArea` | `"bottom"` | where the thumbs rest and where the keyboard comes up |
76
- | `marginWithContainer` | `0` | a sheet is flush, or it is not a sheet |
77
- | `expandX` | `true` | container-wide, same reason |
78
- | `scrollCapture` | `true` | a drag past the sheet's edge must not reach the page |
93
+ | supplied | value | why |
94
+ | --------------------- | -------- | ---------------------------------------------------- |
95
+ | `positionArea` | the edge | `"top"` unless the prop says `"bottom"`, see above |
96
+ | `marginWithContainer` | `0` | a sheet is flush, or it is not a sheet |
97
+ | `expandX` | `true` | container-wide, same reason |
98
+ | `scrollCapture` | `true` | a drag past the sheet's edge must not reach the page |
79
99
 
80
- A `layer="top"` dialog flush with an edge of the screen — a docked sheet on the
81
- bottom, anything with `marginWithContainer={0}` — keeps the band the device
100
+ A `layer="top"` dialog flush with an edge of the screen — a docked sheet,
101
+ anything with `marginWithContainer={0}` — keeps the band the device
82
102
  reserves there (`env(safe-area-inset-*)`: the notch, the home indicator,
83
103
  Safari's floating bar). The surface still reaches the edge; what it holds stops
84
104
  at the band. Only the device's own inset: the dialog is in front of the app's
85
105
  fixed bars, so `--navi-safe-area-inset-*`, which counts them too (see
86
106
  [`safe_area.md`](./safe_area.md)), is not what it reads.
87
107
 
88
- Plus a swipe-down-to-close, held by the sheet's `header` (a `Box` with the
108
+ Plus a swipe-to-close through the edge the sheet rests on (down for a bottom
109
+ sheet, up for a top one), held by the sheet's `header` (a `Box` with the
89
110
  `header` prop) and by anything carrying `data-swipe-grip` — never by the whole
90
111
  sheet, so a board something is dragged across keeps its own gestures. See
91
112
  [`drag_to_travel.md`](./drag_to_travel.md).
92
113
 
93
114
  **`expandY` (or `expand`) cancels docking outright.** A dialog already filling
94
- the height is on the bottom edge docking would bring it to; all docking could
95
- still do is take away the shape the caller asked for, and arm a swipe-down on
96
- something that never rose.
115
+ the height touches both edges; all docking could still do is take away the
116
+ shape the caller asked for, and arm a swipe on something that never rose.
97
117
 
98
118
  ## Saying the two shapes at once
99
119
 
100
120
  The sentence an app almost always wants is two sentences:
101
121
 
102
122
  > Keep this dialog between 12 and 16rem so it does not sprawl on a wide window
103
- > and does not collapse to its shortest line. **And when it is a bottom sheet,
104
- > forget all that: a sheet is flush and full width.**
123
+ > and does not collapse to its shortest line. **And when it is a sheet, forget
124
+ > all that: a sheet is flush and full width.**
105
125
 
106
126
  Both halves are written together, and each applies where it means something:
107
127
 
@@ -134,7 +154,7 @@ than capping the sheet with it. The container ceiling still holds, as always.
134
154
  > dialog that also sets `expandY` never docks, so the cap must never be
135
155
  > withdrawn there, and the line above withdraws it anyway. Beyond being wrong,
136
156
  > it duplicates a condition navi owns at every call site (it drifts the day
137
- > "docked" gains a rule), and it reads as a bug: it says nothing about bottom
157
+ > "docked" gains a rule), and it reads as a bug: it says nothing about docked
138
158
  > sheets to the next person. State both bounds plainly and let the dialog
139
159
  > resolve its own shape.
140
160
 
@@ -148,7 +168,7 @@ Since docking _supplies_ `expandX`, passing it explicitly takes the caller out
148
168
  of that default:
149
169
 
150
170
  ```jsx
151
- // The sheet stops being flush: a floating box at the bottom of the screen.
171
+ // The sheet stops being flush: a floating box against its edge of the screen.
152
172
  <Dialog dockedOnSmallTouchScreen expandX={false} />
153
173
  ```
154
174
 
@@ -272,15 +272,15 @@ so it takes the press outright.
272
272
 
273
273
  ### A popup pushed back the way it came
274
274
 
275
- A `Dialog` docked to the bottom edge (`dockedOnSmallTouchScreen`) and a
276
- `SidePanel` close by being pushed back the way they came in — a third consumer
275
+ A `Dialog` docked to an edge (`dockedOnSmallTouchScreen`) and a `SidePanel`
276
+ close by being pushed back the way they came in — a third consumer
277
277
  of this same travel, `swipe_to_close.js`. A popup that names a **grip** reads the
278
278
  press only there: for a `Dialog` that is its header, plus anything carrying
279
279
  `data-swipe-grip`. Everything else it holds is content the finger came to operate
280
280
  — a board a piece is dragged across, a map, a list — and a press there never
281
281
  reaches the travel at all, whatever it is made of.
282
282
 
283
- So a sheet with no header and nothing marked is not pushed down; it closes by
283
+ So a sheet with no header and nothing marked is not pushed back; it closes by
284
284
  its own controls, the backdrop and Escape. A `SidePanel` names no grip and is
285
285
  pushed from its whole surface, which suits a panel made of nothing else.
286
286
 
@@ -70,14 +70,16 @@ that stays live. `Popup` forwards `backdrop` in both modes, so which of popover
70
70
  or dialog the small-screen resolution picks says nothing about whether one
71
71
  press or two are needed.
72
72
 
73
- That is what a bottom sheet on a phone is made of. `dockedOnSmallTouchScreen`
74
- docks the dialog against its container, and `layer` is what that container is —
75
- `"top"` for the screen, `"local"` for the box the popup was declared in. A
76
- sheet flush with the bottom of the screen over a map still being read object by
77
- object is `layer="top"` for the shape and `backdrop={false}` for the map:
73
+ That is what a sheet on a phone is made of. `dockedOnSmallTouchScreen` docks
74
+ the dialog against an edge of its container (the top one, or `"bottom"` for a
75
+ sheet one only reads and taps — see [`dialog_shape.md`](./dialog_shape.md)),
76
+ and `layer` is what that container is — `"top"` for the screen, `"local"` for
77
+ the box the popup was declared in. A sheet flush with the bottom of the screen
78
+ over a map still being read object by object is `layer="top"` for the shape and
79
+ `backdrop={false}` for the map:
78
80
 
79
81
  ```jsx
80
- <Popup dockedOnSmallTouchScreen layer="top" backdrop={false}>
82
+ <Popup dockedOnSmallTouchScreen="bottom" layer="top" backdrop={false}>
81
83
  ```
82
84
 
83
85
  Going `layer="local"` to keep the map live instead confines the sheet to the
@@ -177,7 +179,7 @@ panel that closes on that press then reopens on the click that follows is a
177
179
  panel meant to stay open, blinking.
178
180
 
179
181
  ```jsx
180
- <SidePanel id="error_panel" closeOnClickOutside backdrop={false} open={isFilled}>
182
+ <SidePanel id="error_panel" signal={openCardIdSignal} closeOnClickOutside>
181
183
  …
182
184
  </SidePanel>
183
185
 
@@ -916,30 +916,65 @@ the page appeared (no entrance).
916
916
 
917
917
  A board with a detail pane wants one panel that stays open as long as something
918
918
  asks to be shown in it, and whose content changes from one card to the next
919
- without the panel closing and reopening. That is `open` driven by a slot:
920
- `createSlot(Renderer)` keeps the renderer mounted whether or not anything fills
921
- it, and hands it `isFilled`.
919
+ without the panel closing and reopening. That is a slot: `createSlot(Renderer)`
920
+ keeps the renderer mounted whether or not anything fills it, and the panel is
921
+ the renderer.
922
922
 
923
923
  ```jsx
924
- const Panel = ({ isFilled, children }) => (
925
- <SidePanel
926
- open={isFilled}
927
- onClose={() => {
928
- openCardIdSignal.value = null;
929
- }}
930
- >
924
+ const Panel = ({ children }) => (
925
+ <SidePanel id="error_panel" signal={openCardIdSignal} closeOnClickOutside>
931
926
  {children}
932
927
  </SidePanel>
933
928
  );
934
929
  const [PanelSlot, PanelSlotFill] = createSlot(Panel);
935
930
  ```
936
931
 
937
- `<PanelSlot />` sits once at the board level; the open card renders a
938
- `<PanelSlotFill>` holding what the panel shows. The slot holds one filler, the
939
- last to render: switching cards mounts the next fill in the render that unmounts
940
- the previous one, and the leaver, unmounting after, leaves the slot to the
941
- newcomer. `onClose` is the panel's own way out — the close cross, Escape, an
942
- outside press — and clears the state that renders the fill; the panel follows.
932
+ `<PanelSlot />` sits once at the board level, and so does the one
933
+ `<PanelSlotFill>`, rendered by the board with the named card's content as its
934
+ children:
935
+
936
+ ```jsx
937
+ const openCardId = openCardIdSignal.value;
938
+ return (
939
+ <Box>
940
+ {cards.map((card) => (
941
+ <Card key={card.id} card={card} />
942
+ ))}
943
+ {openCardId && (
944
+ <PanelSlotFill>
945
+ <CardDetail key={openCardId} id={openCardId} />
946
+ </PanelSlotFill>
947
+ )}
948
+ <PanelSlot />
949
+ </Box>
950
+ );
951
+ ```
952
+
953
+ Two rules hold this shape together, and each guards against a trap the other
954
+ shape falls into.
955
+
956
+ **One `SlotFill`, rendered where the choice is made.** The tempting shape is a
957
+ `SlotFill` inside each card, rendered by the card that is open. It works until
958
+ two cards cross: each card reads the signal on its own, so switching from A to
959
+ B is two separate renders, A's fill unmounting in one and B's mounting in the
960
+ other. Between the two the slot is empty, and its renderer is rendered on that
961
+ empty frame — whichever of A or B the tree walks first, the gap exists in one
962
+ of the two directions. The slot cannot tell that gap from a real emptying, and
963
+ does not try to: a fix there would have to guess the scheduler. Rendered once,
964
+ by the component that holds the choice, the fill never leaves; only its
965
+ children change, and there is no gap to fall into.
966
+
967
+ **The panel is bound to the screen's state, not to `isFilled`.** `isFilled` is
968
+ a fact about the render — whether a fill is mounted right now — and the signal
969
+ is a fact about the screen — which card the address names. `open={isFilled}`
970
+ turns any empty frame into a close, and its `onClose` then clears the very
971
+ state the next fill was waiting for. Bound to the signal, the panel is open
972
+ exactly while a card is named, its own ways out (the close cross, Escape, an
973
+ outside press) clear the signal themselves, and the fill only has to say what
974
+ the panel shows. `isFilled` stays the right `open` for a renderer that has no
975
+ such state — a toolbar or a status shown while anything fills it — fed by the
976
+ one fill above.
977
+
943
978
  A press on another card is not an outside press once the card names the panel
944
979
  with `data-navi-popup-inside` (see [popup_backdrop.md](./popup_backdrop.md)).
945
980
  The full example is `src/layout/demos/7_slot_demo.html`, "two side panels".
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/navi",
3
- "version": "0.29.324",
3
+ "version": "0.29.325",
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.65",
37
+ "@jsenv/dom": "0.17.66",
38
38
  "@jsenv/humanize": "1.8.3",
39
39
  "@jsenv/validity": "0.4.4"
40
40
  },