@jsenv/navi 0.29.489 → 0.29.490
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/dist/dev/jsenv_navi.js +4472 -1171
- package/dist/dev/jsenv_navi.js.map +103 -72
- package/dist/dev/jsenv_navi_side_effects.js +1 -0
- package/dist/dev/jsenv_navi_side_effects.js.map +2 -2
- package/dist/jsenv_navi.js +4374 -1169
- package/dist/jsenv_navi.js.map +103 -72
- package/dist/jsenv_navi_side_effects.js +1 -0
- package/dist/jsenv_navi_side_effects.js.map +2 -2
- package/docs/AI_INSTRUCTIONS.md +7 -5
- package/docs/actions.md +17 -0
- package/docs/control_value.md +50 -3
- package/docs/create_and_edit.md +29 -16
- package/docs/css_architecture.md +29 -1
- package/docs/drag_interactions.md +90 -11
- package/docs/error_handling.md +9 -2
- package/docs/form_changed.md +20 -5
- package/docs/group.md +5 -1
- package/docs/i18n.md +28 -13
- package/docs/interactions.md +38 -6
- package/docs/list_refresh.md +4 -1
- package/docs/navigation.md +23 -0
- package/docs/network_policy.md +14 -3
- package/docs/popup_lift.md +15 -3
- package/docs/popup_open.md +110 -10
- package/docs/resource.md +120 -19
- package/docs/safe_area.md +14 -3
- package/docs/scroll.md +5 -1
- package/docs/testid.md +7 -4
- package/package.json +3 -3
package/docs/form_changed.md
CHANGED
|
@@ -27,9 +27,12 @@ what follows the send still follows it — the popup closes, the slide moves on,
|
|
|
27
27
|
the page changes. The user is done either way; there was simply nothing to
|
|
28
28
|
send.
|
|
29
29
|
|
|
30
|
-
`canSendWhileUnchanged` turns that off, for a form
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
`canSendWhileUnchanged` turns that off, for a form whose send is the act itself
|
|
31
|
+
rather than its fields: a request that exists even empty (join, ask, raise a
|
|
32
|
+
hand), a step that moves on, a creation whose screen opens already filled —
|
|
33
|
+
from the url, from last time's answers, from the defaults a `stateSignal`
|
|
34
|
+
declares — and a send whose duplicates are the point (a single button firing a
|
|
35
|
+
notification).
|
|
33
36
|
|
|
34
37
|
```jsx
|
|
35
38
|
<Form action={notify} canSendWhileUnchanged>
|
|
@@ -89,11 +92,13 @@ are what keep an empty one from being sent.
|
|
|
89
92
|
|
|
90
93
|
A creation lands on the page the server just made, and its id comes back with
|
|
91
94
|
the response — too late for a prop. Do it in the action, which is where the
|
|
92
|
-
answer is
|
|
93
|
-
|
|
95
|
+
answer is. A creation that may open already filled — a draft restored from the
|
|
96
|
+
url — takes `canSendWhileUnchanged`, or its press does nothing at all: no send,
|
|
97
|
+
and the navigation that lives in the action never runs:
|
|
94
98
|
|
|
95
99
|
```jsx
|
|
96
100
|
<Form
|
|
101
|
+
canSendWhileUnchanged
|
|
97
102
|
action={async (value) => {
|
|
98
103
|
const game = await createGame(value);
|
|
99
104
|
navTo(`/games/${game.id}`);
|
|
@@ -201,6 +206,16 @@ does not register with the group around it. Nothing goes up (its value is not
|
|
|
201
206
|
collected, the form stays unchanged whatever it does) and nothing comes down
|
|
202
207
|
(the form's reset, its validation cascade).
|
|
203
208
|
|
|
209
|
+
Enter is the exception. In a text field, standalone or not, Enter sends the
|
|
210
|
+
group around it (`--navi-send`), the way Enter submits an HTML form; in a
|
|
211
|
+
picker's popup it sends the picker — its own value, not the field's — and
|
|
212
|
+
closes it. A field with an `action` of its own commits that action instead. A
|
|
213
|
+
field that helps find the answer says which list it drives with `navi-list`:
|
|
214
|
+
Enter then picks the row under the arrows, and does nothing while none is. A
|
|
215
|
+
field whose Enter must send nothing at all — a street typed before its
|
|
216
|
+
suggestion filled the city — says `enterSendsNothing`; the browser's own Enter
|
|
217
|
+
(a datalist choice) still happens.
|
|
218
|
+
|
|
204
219
|
What it does **not** say is "ignore everything around me". `disabled`,
|
|
205
220
|
`readOnly` and `loading` travel on their own contexts and go on reaching it,
|
|
206
221
|
deliberately: a door with no value of its own can still write into the form
|
package/docs/group.md
CHANGED
|
@@ -43,7 +43,11 @@ Live examples: `src/control/demos/15_group_demo.html`.
|
|
|
43
43
|
- **Seams**: a member with a member before it is pulled back by one border
|
|
44
44
|
width, so the two borders along a seam become one line — the member's own
|
|
45
45
|
`--border-width` when it declares one, else the group's `--group-border-width`
|
|
46
|
-
(default: `--navi-control-border-width`).
|
|
46
|
+
(default: `--navi-control-border-width`). Members that draw no border have
|
|
47
|
+
no line to share: `seamColor` (and `seamWidth`, 1px by default) sets them
|
|
48
|
+
that far apart and paints the gap — `<SplitButton seamColor="…">` for two
|
|
49
|
+
borderless halves. In css, `--group-seam-color` and `--group-seam-width`,
|
|
50
|
+
set on the group or around it.
|
|
47
51
|
- **Corners**: a member loses the radius on each side where it faces another
|
|
48
52
|
member. A single member keeps its own radius — a group of one looks like the
|
|
49
53
|
control alone.
|
package/docs/i18n.md
CHANGED
|
@@ -192,25 +192,40 @@ which pins one region beside navi's dates and ignores `setSupportedLanguages`.
|
|
|
192
192
|
Each helper takes `lang` and otherwise reads the signal above; the component is
|
|
193
193
|
for JSX, the function for a string (a `title`, a notification).
|
|
194
194
|
|
|
195
|
-
| Value
|
|
196
|
-
|
|
|
197
|
-
| a number
|
|
198
|
-
| a
|
|
199
|
-
| a
|
|
200
|
-
| a
|
|
201
|
-
| a
|
|
202
|
-
| a
|
|
203
|
-
|
|
|
204
|
-
|
|
|
205
|
-
|
|
|
206
|
-
|
|
|
207
|
-
|
|
|
195
|
+
| Value | Component | String |
|
|
196
|
+
| ------------------------------- | -------------------------------------------------- | ------------------------------------------------ |
|
|
197
|
+
| a number | `<Quantity>` | `formatNumber` |
|
|
198
|
+
| a size in bytes | | `formatByteSize` |
|
|
199
|
+
| a day | `<Time type="date">` | `formatDay` |
|
|
200
|
+
| a month | `<Time type="month">` | `formatMonth` |
|
|
201
|
+
| a day and its time | `<Time type="datetime">` | `formatDatetime` |
|
|
202
|
+
| a time of day | `<Time type="time">` | `formatTimeOfDay`, or `formatTime` from a `Date` |
|
|
203
|
+
| a clock from a count | `<Time type="hour"\|"minute" format="timestring">` | |
|
|
204
|
+
| two times of day | `<TimeRange>` | `formatTimeRange` |
|
|
205
|
+
| a day relative to today | `dayLabel` on `<Time type="date">`, beside the day | `formatDayRelative` |
|
|
206
|
+
| an instant, from now | `<Time type="relative">` | `formatTimeRelative` |
|
|
207
|
+
| a duration | `<Time type="duration">` | `formatDuration` and its per-unit siblings |
|
|
208
|
+
| the time elapsed since, ticking | `<Time type="duration" since={instant}>` | |
|
|
209
|
+
| items read as one list | `formatList` over elements | `formatList` |
|
|
208
210
|
|
|
209
211
|
A spelled day writes its year only when it is not in `now`'s year (`year:
|
|
210
212
|
"auto"`, see `formatDay`): appending the year by hand writes it twice for a
|
|
211
213
|
date in another year. A capital letter at the start of a value is
|
|
212
214
|
`<Text capitalize>`, not a sliced string.
|
|
213
215
|
|
|
216
|
+
The shape of a number is asked of `formatNumber` too: it takes
|
|
217
|
+
`Intl.NumberFormat`'s options in the reader's language — a fixed precision
|
|
218
|
+
(`formatNumber(seconds, { minimumFractionDigits: 1, maximumFractionDigits: 1 })`
|
|
219
|
+
→ "1,0"), a sign (`signDisplay: "exceptZero"` → "+3") — and `<Quantity>`
|
|
220
|
+
forwards the fraction digits and the sign. A time written to the second (a log
|
|
221
|
+
line, a "checked at") is `precision: "second"` on `formatTime`,
|
|
222
|
+
`formatDatetime`, `formatTimeOfDay` and `<Time type="time"|"datetime">`.
|
|
223
|
+
|
|
224
|
+
A list whose items are elements — a `<TimeRange>` per span — goes through
|
|
225
|
+
`formatList` as well: given an element, it returns a fragment with each element
|
|
226
|
+
at its place between the language's words, where a hand-written " et " would
|
|
227
|
+
join them in one language only.
|
|
228
|
+
|
|
214
229
|
## Changing what navi says
|
|
215
230
|
|
|
216
231
|
`naviI18n` holds navi's own texts. Register on it to override a default or to
|
package/docs/interactions.md
CHANGED
|
@@ -93,6 +93,27 @@ turned down where it is read (see
|
|
|
93
93
|
[`refuse`](./drag_interactions.md#the-hand-pulls-and-nothing-follows-refuse),
|
|
94
94
|
which the carrying interactions answer with).
|
|
95
95
|
|
|
96
|
+
A value can also say what it is besides what it does, as
|
|
97
|
+
`{ answer, verb, optimistic }`, `answer` being one of the four:
|
|
98
|
+
|
|
99
|
+
```jsx
|
|
100
|
+
<List.Item
|
|
101
|
+
interactions={{
|
|
102
|
+
reorder: { answer: saveOrder, verb: "PUT" },
|
|
103
|
+
toss: { answer: remove, verb: "DELETE" },
|
|
104
|
+
swipe_left: { answer: archive, verb: "PATCH" },
|
|
105
|
+
}}
|
|
106
|
+
/>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`verb` says the answer writes, which a function cannot say about itself. Under a
|
|
110
|
+
[network policy](./network_policy.md) holding writes the interaction does not
|
|
111
|
+
happen, and the policy's words are said where the hand made it — before anything
|
|
112
|
+
moves, for a gesture: a carried thing is not picked up, a swiped row resists like
|
|
113
|
+
a wall. `optimistic` is for a `reorder` or a `land` drawn at once and written
|
|
114
|
+
after ([drag_interactions.md](./drag_interactions.md#answered-at-once-optimistic)).
|
|
115
|
+
A falsy `answer` is "not this one" too.
|
|
116
|
+
|
|
96
117
|
### The interactions navi detects
|
|
97
118
|
|
|
98
119
|
| Key | Read from |
|
|
@@ -199,9 +220,15 @@ finger was — is `findEvent(event, ["pointerdown", "pointerup", "click",
|
|
|
199
220
|
Everything goes through the interaction gate of the **nearest control** — itself,
|
|
200
221
|
an ancestor, or the one control it wraps, in that order. So a disabled, read-only
|
|
201
222
|
or busy control answers a swipe the way it answers a click: it says why, where the
|
|
202
|
-
interaction happened, and nothing runs. A
|
|
203
|
-
|
|
204
|
-
|
|
223
|
+
interaction happened, and nothing runs. A gesture asks the gate where the hand
|
|
224
|
+
meets it, not once it has played: a carried thing is not picked up — the gate
|
|
225
|
+
speaks at the instant the grab would have been felt — and a swiped row resists
|
|
226
|
+
the way a side nothing is declared for does, the gate speaking the first time the
|
|
227
|
+
hand pulls that way. A row that goes all the way out before being told it was
|
|
228
|
+
never free to go is a press that looks accepted and fails after. A `Box` with no
|
|
229
|
+
control anywhere near it still answers a callback; only the two requests
|
|
230
|
+
(`"request_action"`, `"request_ui_action"`) have nothing to ask, and say so in
|
|
231
|
+
dev.
|
|
205
232
|
|
|
206
233
|
A `Box` that lays out **several** controls — a row of badges, a toolbar — belongs
|
|
207
234
|
to none of them: its interactions are its own, answered with no gate. A click on
|
|
@@ -276,8 +303,11 @@ element that declares the gesture.
|
|
|
276
303
|
|
|
277
304
|
While the answer takes time, the element **stays where the gesture left it**, and
|
|
278
305
|
comes back once it settles — a failure leaves the row in place to be tried
|
|
279
|
-
again
|
|
280
|
-
|
|
306
|
+
again, and says why on it once it is back, read the way a refused drag's answer
|
|
307
|
+
is ([drag_interactions.md](./drag_interactions.md#carrying-something-move-reorder-land-toss-leave)):
|
|
308
|
+
rejecting with nothing comes back without a word. What a success does to it is
|
|
309
|
+
yours (a list that redemands its rows, a row that leaves): navi does not make it
|
|
310
|
+
disappear.
|
|
281
311
|
|
|
282
312
|
## Twice, whichever hand it is
|
|
283
313
|
|
|
@@ -540,7 +570,9 @@ teardown, so a travelling container above it does not take the gesture (see
|
|
|
540
570
|
## Reference
|
|
541
571
|
|
|
542
572
|
- `src/control/interaction/interaction_registry.js` — the prop, the four values,
|
|
543
|
-
the registry.
|
|
573
|
+
`{ answer, verb, optimistic }`, the registry.
|
|
574
|
+
- `src/control/interaction/interaction_refusal.js` — how a refusal is said,
|
|
575
|
+
before and after.
|
|
544
576
|
- `src/control/interaction/interaction_press.js` — swipes, holds and the two
|
|
545
577
|
taps, and what a swipe writes on the element.
|
|
546
578
|
- `src/control/interaction/interaction_keyboard.js`,
|
package/docs/list_refresh.md
CHANGED
|
@@ -125,7 +125,10 @@ same (next section).
|
|
|
125
125
|
A composition is about the **values** its params hold, not the reader instance:
|
|
126
126
|
`GET_RANGE.bindParams({ scope: "thread" })` called from two places reads and
|
|
127
127
|
writes the same one. A verb in `rerunOn.GET_RANGE`, or `reader.invalidate()`,
|
|
128
|
-
drops the compositions — they stand for an order that is gone
|
|
128
|
+
drops the compositions — they stand for an order that is gone;
|
|
129
|
+
`reader.revalidate()` keeps them and has the lists on screen ask again for
|
|
130
|
+
their window, for a collection that may have moved (the app back from the
|
|
131
|
+
background) — and
|
|
129
132
|
`memoryBudget` (1000 ranks by default) trims the ranks far from any window,
|
|
130
133
|
which are asked for again if the user goes back to them.
|
|
131
134
|
|
package/docs/navigation.md
CHANGED
|
@@ -318,6 +318,12 @@ param that qualifies a screen is not a place, and one entry per write turns a
|
|
|
318
318
|
single back-press into as many as the user moved. A state whose values ARE places
|
|
319
319
|
says so — see [`history: "push"`](#a-state-whose-values-are-places-history-push).
|
|
320
320
|
|
|
321
|
+
One answer may be kept as several params — a level and its reach,
|
|
322
|
+
`?levels=…&level_exact=…` — and drawn by one control whose value is the object.
|
|
323
|
+
`stateSignalGroup({ levels: levelsSignal, level_exact: levelExactSignal })` is
|
|
324
|
+
that object: read from the params, following each of them, and written back
|
|
325
|
+
into them as one move — one history entry.
|
|
326
|
+
|
|
321
327
|
#### A value written per frame
|
|
322
328
|
|
|
323
329
|
Every write reaches the address, synchronously, and browsers refuse an address
|
|
@@ -380,6 +386,13 @@ be written about it (see
|
|
|
380
386
|
[route_transitions.md](./route_transitions.md#a-page-opened-from-anywhere)), and
|
|
381
387
|
its `matchingSignal` read outside the tree.
|
|
382
388
|
|
|
389
|
+
Which page is on screen is the containers' answer, not the routes': several can
|
|
390
|
+
match one address, and only the order of the branches says which renders.
|
|
391
|
+
`renderedPageSignal` holds it — `{ route, params }` of the deepest page the
|
|
392
|
+
routers render, a section's own sub-router included, the fallback's
|
|
393
|
+
`routeFallback()` when nothing matches. Naming the screen being left, counting
|
|
394
|
+
where people go: read it there rather than walking a copy of the route table.
|
|
395
|
+
|
|
383
396
|
A section with a shared prefix owns its own sub-router: one leaf on
|
|
384
397
|
`/dashboard/` whose element renders its own `<Route>` tree and chrome, handing its
|
|
385
398
|
local state down through `elementProps`. Pages sharing a layout but no prefix
|
|
@@ -578,6 +591,16 @@ A, B, A, B… growth, and the scroll comes back — and one step forward too. On
|
|
|
578
591
|
towards entries of this document, and never when the push carries explicit
|
|
579
592
|
state. An arrow that must ALWAYS behave as a back is `navBack()`.
|
|
580
593
|
|
|
594
|
+
Further back, a push is an arrival even when it means a return: a bar entry
|
|
595
|
+
leading back to a thread whose address names the item one was reading
|
|
596
|
+
(`/games?at=<id>`) is, by its path, a new arrival at the top — the same push from
|
|
597
|
+
a notification would be one. Only the link knows, and it says so:
|
|
598
|
+
`<Link asReturn>`, `navTo(url, { asReturn: true })`. The push still stacks its
|
|
599
|
+
entry, and lands like a back: the document where that address was last left,
|
|
600
|
+
its lists on the positions they kept there
|
|
601
|
+
([scroll.md](./scroll.md#where-the-list-opens-and-where-it-is)). An address never
|
|
602
|
+
left opens at its top as usual.
|
|
603
|
+
|
|
581
604
|
### A page that is not whole yet
|
|
582
605
|
|
|
583
606
|
A position is put back into whatever the page shows at that moment. When that is
|
package/docs/network_policy.md
CHANGED
|
@@ -111,15 +111,26 @@ all, so it is left alone, and so is a function handed to a control's `action`:
|
|
|
111
111
|
it is wrapped into an action with no verb, even when it calls one, so the
|
|
112
112
|
control is not held and the write fails after the press with a
|
|
113
113
|
`NetworkPolicyError` ([resource.md](./resource.md#a-function-calling-the-verb-or-the-instance)).
|
|
114
|
-
Such a control says what its function does to the server with
|
|
115
|
-
`<Input type="checkbox" boolean action={(on) => saveSetting("weather", on)}
|
|
116
|
-
actionVerb="PATCH"
|
|
114
|
+
Such a control, or a `<Form>`, says what its function does to the server with
|
|
115
|
+
`actionVerb` — `<Input type="checkbox" boolean action={(on) => saveSetting("weather", on)}
|
|
116
|
+
actionVerb="PATCH" />`, `<Form action={sendDraft} actionVerb="POST">` — and is
|
|
117
|
+
then held up front, with the policy's message: a form's fields and its submit
|
|
118
|
+
alike.
|
|
117
119
|
`createAction(callback, { meta: { verb } })` says the same of an action declared
|
|
118
120
|
once, and opts it into what the action layer holds — a control bound to a write verb is read-only, a completed `"GET"`
|
|
119
121
|
is not rerun — but its callback is still called when it runs: answering from
|
|
120
122
|
the store and settling with a `NetworkPolicyError` are done by the callbacks a
|
|
121
123
|
`resource()` wraps.
|
|
122
124
|
|
|
125
|
+
A gesture's answer is a function navi cannot see into either, and says what it
|
|
126
|
+
writes in its declaration —
|
|
127
|
+
`interactions={{ reorder: { answer: saveOrder, verb: "PUT" }, toss: { answer: remove, verb: "DELETE" } }}`
|
|
128
|
+
([interactions.md](./interactions.md#the-four-values)). Under the policy it does
|
|
129
|
+
not happen, and the policy's message is said where the hand made it, before
|
|
130
|
+
anything moves: a carried thing is not picked up, a swiped row resists as a wall
|
|
131
|
+
does. Undeclared, the gesture plays to the end and the write it calls fails
|
|
132
|
+
after — said on the element, the way any refused answer is.
|
|
133
|
+
|
|
123
134
|
## Holding writes while reads go out
|
|
124
135
|
|
|
125
136
|
`reads: "network"` keeps the first half of the policy and drops the second: the
|
package/docs/popup_lift.md
CHANGED
|
@@ -7,7 +7,8 @@ edit sheet; a drawing in a corner becomes the drawing full width. The page
|
|
|
7
7
|
recedes behind a wall, the box leaves its place, travels and grows, and comes
|
|
8
8
|
back into its place on close — or, with
|
|
9
9
|
`animation={{ open: "scaling", close: "lifting" }}`, only lands somewhere on
|
|
10
|
-
close
|
|
10
|
+
close, which a `Popover` does too (see
|
|
11
|
+
[lifting on the way back only](#lifting-on-the-way-back-only)).
|
|
11
12
|
|
|
12
13
|
It is not a way to open a dialog with a nicer entrance. A dialog that shows
|
|
13
14
|
something else than what was pressed — a menu, a confirmation, a form the
|
|
@@ -255,6 +256,16 @@ does the opening's own exit play: while a closing lift runs, the dialog's
|
|
|
255
256
|
transitions are off, or `scaling`'s exit would keep the dialog painted into the
|
|
256
257
|
picture of the state it closes into.
|
|
257
258
|
|
|
259
|
+
**A popover lands the same way.** A `Popover` — a `Popup` or a `Picker` in
|
|
260
|
+
popover mode — takes the same `animation={{ open, close: "lifting" }}` and
|
|
261
|
+
`liftAnchor`: an invitation shown out of the top bar, whose "Later" puts the
|
|
262
|
+
app's tile away in the settings wheel, flies the tile into the wheel as it
|
|
263
|
+
closes. Left without `liftAnchor`, the lifted node goes back into the anchor the
|
|
264
|
+
popover was placed against. The popover itself leaves with the page's picture,
|
|
265
|
+
fading as the node travels; its own exit is off for the same reason as the
|
|
266
|
+
dialog's. Only the close lifts: a popover opens placed against its anchor,
|
|
267
|
+
never out of it, so a plain `"lifting"` gives it no movement either way.
|
|
268
|
+
|
|
258
269
|
**The box it lands in is rendered by the close.** `liftAnchor` is read once the
|
|
259
270
|
close has been made, `onClose` included, inside the transition: a state written
|
|
260
271
|
in `onClose` has rendered by the time the landing is looked up. It is also the
|
|
@@ -275,8 +286,9 @@ and dev warns.
|
|
|
275
286
|
|
|
276
287
|
**Tell the collecting close from the others.** Escape, the back button and a
|
|
277
288
|
press on the wall close the dialog too, and usually mean "not now" rather than
|
|
278
|
-
"collect". A `Dialog`'s `onClose` receives who asked
|
|
279
|
-
button of a `--navi-close`), and only the close that
|
|
289
|
+
"collect". A `Dialog`'s or a `Popover`'s `onClose` receives who asked
|
|
290
|
+
(`e.detail.requester`, the button of a `--navi-close`), and only the close that
|
|
291
|
+
collects writes the state.
|
|
280
292
|
The others land nowhere: the fixed `liftAnchor` names nothing, so they wait out
|
|
281
293
|
those 300 ms, fade out, and dev warns.
|
|
282
294
|
|
package/docs/popup_open.md
CHANGED
|
@@ -206,7 +206,12 @@ names none, and on whoever asked when nothing else says. In that order:
|
|
|
206
206
|
the press is the only thing that knows that point.
|
|
207
207
|
2. **the `anchor` prop** — where this popup opens when nobody says.
|
|
208
208
|
3. **`detail.source`** — who asked. A button therefore opens the popup on
|
|
209
|
-
itself, and a popup passed as its own source is its own anchor.
|
|
209
|
+
itself, and a popup passed as its own source is its own anchor. A source
|
|
210
|
+
bigger than what the popup comes out of — a whole row pressed, a crest
|
|
211
|
+
inside it — names that part: `navi-command-proxy-for="<id>"` on the button
|
|
212
|
+
makes the element with that id the source the popup reads, what a Popover
|
|
213
|
+
is placed against and what a lifting Dialog lifts from and lands back into.
|
|
214
|
+
The press, the value and the focus stay the button's.
|
|
210
215
|
|
|
211
216
|
A `Popover` is positioned against it; a `Dialog` only reads it for
|
|
212
217
|
`sizeFromAnchor` and `animation="lifting"` (see [popup_lift.md](./popup_lift.md)).
|
|
@@ -471,6 +476,40 @@ can veto: `onClose` is the close happening, not a request to close. The refusals
|
|
|
471
476
|
are navi's: a close over a control mid-action, and a `Picker`'s close over a
|
|
472
477
|
value that does not validate.
|
|
473
478
|
|
|
479
|
+
### Awaiting the answer: `askPopup`
|
|
480
|
+
|
|
481
|
+
A gesture whose outcome is a question waits for it where the gesture is: a card
|
|
482
|
+
thrown off the list asks "delete it?" and either goes or flies home, a player
|
|
483
|
+
dropped on the release zone asks "remove them from the game?". The question is a
|
|
484
|
+
popup, and the answer is how it closed. `askPopup` opens it as `--navi-open`
|
|
485
|
+
does and settles once it has closed:
|
|
486
|
+
|
|
487
|
+
```js
|
|
488
|
+
import { askPopup } from "@jsenv/navi";
|
|
489
|
+
|
|
490
|
+
toss: async (event) => {
|
|
491
|
+
const { confirmed } = await askPopup(deleteDialogRef.current, event, {
|
|
492
|
+
value: alert.id,
|
|
493
|
+
});
|
|
494
|
+
if (!confirmed) {
|
|
495
|
+
throw undefined; // the card goes home, nothing said
|
|
496
|
+
}
|
|
497
|
+
},
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
`confirmed` is a yes: a `--navi-confirm`, or a send the popup closed on — a
|
|
501
|
+
`<Form>` inside it, a row's `--navi-send`. Everything else is a no, every way
|
|
502
|
+
out included: Escape, the backdrop, the cross, a link leaving, Back, the popup
|
|
503
|
+
leaving the page. It settles after `onClose`, and after the yes's own action:
|
|
504
|
+
a button's command waits for its action, so a confirm that deletes has deleted.
|
|
505
|
+
Asked again while the popup is still open, the earlier question is answered no
|
|
506
|
+
and the new one waits for the close. Nothing opening — no popup found, a press
|
|
507
|
+
that closed it a moment ago — rejects with `undefined`, which a gesture's answer
|
|
508
|
+
reads as a refusal to say nothing about.
|
|
509
|
+
|
|
510
|
+
The popup stays the popup: uncontrolled, with its own say over closing, and
|
|
511
|
+
nothing in it knows it was asked rather than opened.
|
|
512
|
+
|
|
474
513
|
### Closing when a button also runs an action
|
|
475
514
|
|
|
476
515
|
A button that carries both runs them in that order: the action first, the
|
|
@@ -800,6 +839,54 @@ it — the shape of a popup that is a LAYER over the screen, whose address is
|
|
|
800
839
|
[navigation.md](./navigation.md#a-layer-over-the-screen-what-its-address-may-say)'s
|
|
801
840
|
decision.
|
|
802
841
|
|
|
842
|
+
To land on a page with such a popup open — "see the details" leading to a
|
|
843
|
+
profile with its results sheet over it — the link says which, the way the
|
|
844
|
+
popup's `navState` says it:
|
|
845
|
+
|
|
846
|
+
```jsx
|
|
847
|
+
<Link
|
|
848
|
+
route={PROFILE}
|
|
849
|
+
routeParams={{ id }}
|
|
850
|
+
open={{ id: "results", type: "push" }}
|
|
851
|
+
>
|
|
852
|
+
Voir le détail du calcul
|
|
853
|
+
</Link>
|
|
854
|
+
// or navTo(PROFILE.buildUrl({ id }), { open: { id: "results", type: "push" } })
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
The page's entry is pushed, then the popup's own, as its opening would write
|
|
858
|
+
it: closing the sheet leaves the reader on the profile, and Back then leaves
|
|
859
|
+
the profile. The popup need not be mounted yet — it reads its key when the
|
|
860
|
+
page mounts it. A popup whose open state is a url param needs none of this:
|
|
861
|
+
its `buildUrl` already says it.
|
|
862
|
+
|
|
863
|
+
### State that belongs to the popup: `ownSignals`
|
|
864
|
+
|
|
865
|
+
What a popup was opened on beside its `value` — the seat of a game a share
|
|
866
|
+
sheet is about — and what is chosen inside it — the result slide shown in a
|
|
867
|
+
level sheet — often lives in the address too, so a reload or a return finds
|
|
868
|
+
it. It means nothing once the popup is closed, and navi writes it away with
|
|
869
|
+
the open state:
|
|
870
|
+
|
|
871
|
+
```jsx
|
|
872
|
+
<Dialog
|
|
873
|
+
signal={gameShareSignal}
|
|
874
|
+
value={game.id}
|
|
875
|
+
ownSignals={[gameShareSlotSignal]}
|
|
876
|
+
>
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
Every close empties them (writes `undefined`, a state signal's default, out of
|
|
880
|
+
the url), whatever closes the popup — its cross, a keep-close landing back,
|
|
881
|
+
Escape, Back — and at the moment that close says: a keep-close lands on an
|
|
882
|
+
address without them, a cancel going back empties what the entry behind still
|
|
883
|
+
held, written by an `onOpen` before the push. A `navState` popup closed by a
|
|
884
|
+
link leaving keeps them on the entry left, where Back finds the popup open on
|
|
885
|
+
what it was showing. A signal two popups share (a sheet and the
|
|
886
|
+
plate opening it both reading `?rank_result=`) is emptied by whichever
|
|
887
|
+
closes. `Dialog`, `Popover`, `SidePanel`, `Popup` and `Expandable` take it; a
|
|
888
|
+
`Picker` writes its own in `onClose`, which runs before its close lands.
|
|
889
|
+
|
|
803
890
|
### `open`, and what it costs
|
|
804
891
|
|
|
805
892
|
`open` is the one-way half of `signal` — the parent re-renders with a boolean
|
|
@@ -980,6 +1067,17 @@ the gesture that opens the popup; `useAsyncData` reads it (see
|
|
|
980
1067
|
and aborts of a screen left ([network_policy.md](./network_policy.md)) apply; a
|
|
981
1068
|
reload keeps the open state, so it keeps the request too.
|
|
982
1069
|
|
|
1070
|
+
**The request leaves at the press, the content is built at the hover.** A press
|
|
1071
|
+
on what opens the popup — a `--navi-open` or `--navi-toggle` button — preruns
|
|
1072
|
+
the route actions its opening changes, read as if the popup were already open:
|
|
1073
|
+
the request leaves at the pointerdown, not when the open state is written. The
|
|
1074
|
+
content may be built earlier: a `Picker`, or a popup given an `anchor`, builds
|
|
1075
|
+
it when the pointer rests there (see `mount`, below). On a computer the rows
|
|
1076
|
+
therefore arrive a press later than the content they fill. That is the price of
|
|
1077
|
+
a read keyed by the popup — one sheet per card, one request per card: a pointer
|
|
1078
|
+
crossing a list of cards has chosen none of them, and a hover that asked would
|
|
1079
|
+
ask for each card it crossed.
|
|
1080
|
+
|
|
983
1081
|
The popup owning its request — `useAsyncData(action, { run: true })` — is the
|
|
984
1082
|
fallback, for a parameter chosen inside the popup and dying with it: a filter
|
|
985
1083
|
the sheet itself holds and nothing else remembers. Hand-written, with `run()`
|
|
@@ -996,15 +1094,17 @@ not leave with the close. A `Picker` told no value whose value something reads
|
|
|
996
1094
|
is the exception: it builds its popup as soon as that value is read (see
|
|
997
1095
|
[above](#a-press-that-opens-a-popup-and-acts-on-it)).
|
|
998
1096
|
|
|
999
|
-
A `Dialog` nobody has asked for yet is not even built
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1097
|
+
A `Dialog` or a `Popover` nobody has asked for yet is not even built — nor a
|
|
1098
|
+
`Popup` or a `SidePanel`, which render one of the two. It stands in as one bare
|
|
1099
|
+
element (a `<dialog>`, a `<div>`) — the id a command finds, the element a press
|
|
1100
|
+
announces itself on — and the popup proper is built at the first request: an
|
|
1101
|
+
open, a press on what opens it, its `signal` or `open` saying open. From then on
|
|
1102
|
+
it stays built. So a list whose rows each hold a few popups (a sheet per seat, a
|
|
1103
|
+
share sheet per card) pays one element per closed popup at every mount, not a
|
|
1104
|
+
popup: a row may carry the popups of its own actions. It is built at once when
|
|
1105
|
+
something needs it from the start — open at mount, a `navState`, an `anchor` (a
|
|
1106
|
+
hover builds the content ahead of the click), or a `mount` other than the
|
|
1107
|
+
default. A `Picker`'s popup is the picker's own, built with it.
|
|
1008
1108
|
|
|
1009
1109
|
"Closed" is two states, not one — never opened yet, and closed again after an
|
|
1010
1110
|
opening — so the `mount` prop answers both at once:
|