@jsenv/navi 0.29.323 → 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.
- package/README.md +2 -0
- package/dist/dev/jsenv_navi.js +312 -244
- package/dist/dev/jsenv_navi.js.map +22 -22
- package/dist/jsenv_navi.js +279 -244
- package/dist/jsenv_navi.js.map +22 -22
- package/docs/AI_INSTRUCTIONS.md +6 -3
- package/docs/dialog_shape.md +38 -18
- package/docs/drag_to_travel.md +3 -3
- package/docs/popup_backdrop.md +39 -6
- package/docs/popup_open.md +67 -0
- package/package.json +2 -2
package/docs/AI_INSTRUCTIONS.md
CHANGED
|
@@ -159,11 +159,14 @@ 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;
|
|
163
|
-
`
|
|
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`,
|
|
165
|
+
calling `triggerNaviCommand`, or writing a close button.
|
|
164
166
|
- `popup_backdrop.md` — three independent questions: a wall or not
|
|
165
167
|
(`backdrop={false}`), what an outside press does, how far the page withdraws;
|
|
166
|
-
`data-navi-popup-outside
|
|
168
|
+
`data-navi-popup-outside`, and `data-navi-popup-inside` for a box of the
|
|
169
|
+
page whose press is not a dismissal. Read before writing CSS for a backdrop.
|
|
167
170
|
- `dialog_shape.md` — bounds rather than a width, the container ceiling, the
|
|
168
171
|
centered box versus the bottom sheet, `marginWithContainer`. Read before
|
|
169
172
|
deriving `smallTouchScreenSignal` in an app or writing CSS to make a dialog
|
package/docs/dialog_shape.md
CHANGED
|
@@ -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
|
|
61
|
-
|
|
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
|
|
74
|
-
| --------------------- |
|
|
75
|
-
| `positionArea` | `"
|
|
76
|
-
| `marginWithContainer` | `0`
|
|
77
|
-
| `expandX` | `true`
|
|
78
|
-
| `scrollCapture` | `true`
|
|
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
|
|
81
|
-
|
|
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-
|
|
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
|
|
95
|
-
|
|
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
|
|
104
|
-
>
|
|
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
|
|
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
|
|
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
|
|
package/docs/drag_to_travel.md
CHANGED
|
@@ -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
|
|
276
|
-
|
|
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
|
|
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
|
|
package/docs/popup_backdrop.md
CHANGED
|
@@ -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
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
@@ -167,6 +169,37 @@ Navi does not warn about a marked container: the column whose gaps must not
|
|
|
167
169
|
dismiss and the row whose sides must are the same box from the outside, and
|
|
168
170
|
only the caller knows which one it wrote.
|
|
169
171
|
|
|
172
|
+
### A box of the page that is not outside
|
|
173
|
+
|
|
174
|
+
`data-navi-popup-inside` is the other direction: a box of the page the caller
|
|
175
|
+
says belongs to a popup, so that a press on it is not outside that popup even
|
|
176
|
+
though it lies beyond the border box. A board with one panel for all its cards
|
|
177
|
+
is the case: pressing another card is what changes what the panel shows, and a
|
|
178
|
+
panel that closes on that press then reopens on the click that follows is a
|
|
179
|
+
panel meant to stay open, blinking.
|
|
180
|
+
|
|
181
|
+
```jsx
|
|
182
|
+
<SidePanel id="error_panel" signal={openCardIdSignal} closeOnClickOutside>
|
|
183
|
+
…
|
|
184
|
+
</SidePanel>
|
|
185
|
+
|
|
186
|
+
<Box data-navi-popup-inside="error_panel" interactions={{ click: openCard }}>
|
|
187
|
+
…
|
|
188
|
+
</Box>
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
The value names the popup by `id`, the way `commandfor` does, and the exemption
|
|
192
|
+
is that popup's alone: a menu open beside the board still closes when a card is
|
|
193
|
+
pressed. Several popups are named with a space between the ids.
|
|
194
|
+
|
|
195
|
+
Unlike the outside marker, this one answers for its whole subtree: the outside
|
|
196
|
+
marker carves free space out of a surface whose controls stay surface, this one
|
|
197
|
+
adds a whole thing to the popup's ground — the card, its count, its buttons —
|
|
198
|
+
and nothing in it is a dismissal.
|
|
199
|
+
|
|
200
|
+
It reads a press the page hears, so it belongs with a popup that has no wall
|
|
201
|
+
(`backdrop={false}`, or a popover): behind a wall, no card is pressable.
|
|
202
|
+
|
|
170
203
|
### `pointer-events: none` and `inert` are not this
|
|
171
204
|
|
|
172
205
|
Neither says "this is backdrop", and reaching for them here is the natural
|
package/docs/popup_open.md
CHANGED
|
@@ -912,6 +912,73 @@ afterwards. `defaultOpen="interaction"` means the mount _is_ the opening (the
|
|
|
912
912
|
entrance animation plays); any other truthy value means it was already open when
|
|
913
913
|
the page appeared (no entrance).
|
|
914
914
|
|
|
915
|
+
### One panel, fed by a slot
|
|
916
|
+
|
|
917
|
+
A board with a detail pane wants one panel that stays open as long as something
|
|
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 a slot: `createSlot(Renderer)`
|
|
920
|
+
keeps the renderer mounted whether or not anything fills it, and the panel is
|
|
921
|
+
the renderer.
|
|
922
|
+
|
|
923
|
+
```jsx
|
|
924
|
+
const Panel = ({ children }) => (
|
|
925
|
+
<SidePanel id="error_panel" signal={openCardIdSignal} closeOnClickOutside>
|
|
926
|
+
{children}
|
|
927
|
+
</SidePanel>
|
|
928
|
+
);
|
|
929
|
+
const [PanelSlot, PanelSlotFill] = createSlot(Panel);
|
|
930
|
+
```
|
|
931
|
+
|
|
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
|
+
|
|
978
|
+
A press on another card is not an outside press once the card names the panel
|
|
979
|
+
with `data-navi-popup-inside` (see [popup_backdrop.md](./popup_backdrop.md)).
|
|
980
|
+
The full example is `src/layout/demos/7_slot_demo.html`, "two side panels".
|
|
981
|
+
|
|
915
982
|
## A popup that loads data
|
|
916
983
|
|
|
917
984
|
Being open is where the user is, and what a popup draws belongs to the screen
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/navi",
|
|
3
|
-
"version": "0.29.
|
|
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.
|
|
37
|
+
"@jsenv/dom": "0.17.66",
|
|
38
38
|
"@jsenv/humanize": "1.8.3",
|
|
39
39
|
"@jsenv/validity": "0.4.4"
|
|
40
40
|
},
|