@jsenv/navi 0.29.324 → 0.29.326
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 +247 -125
- package/dist/dev/jsenv_navi.js.map +11 -11
- package/dist/jsenv_navi.js +181 -129
- package/dist/jsenv_navi.js.map +11 -11
- package/docs/AI_INSTRUCTIONS.md +3 -2
- package/docs/dialog_shape.md +38 -18
- package/docs/drag_to_travel.md +3 -3
- package/docs/popup_backdrop.md +9 -7
- package/docs/popup_open.md +51 -16
- package/package.json +2 -2
package/docs/AI_INSTRUCTIONS.md
CHANGED
|
@@ -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
|
-
`
|
|
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;
|
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
|
|
@@ -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"
|
|
182
|
+
<SidePanel id="error_panel" signal={openCardIdSignal} closeOnClickOutside>
|
|
181
183
|
…
|
|
182
184
|
</SidePanel>
|
|
183
185
|
|
package/docs/popup_open.md
CHANGED
|
@@ -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
|
|
920
|
-
|
|
921
|
-
|
|
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 = ({
|
|
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
|
|
938
|
-
`<PanelSlotFill
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
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.
|
|
3
|
+
"version": "0.29.326",
|
|
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
|
},
|