@jsenv/navi 0.29.90 → 0.29.92
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/jsenv_navi.js +1280 -163
- package/dist/jsenv_navi.js.map +116 -29
- package/docs/AI_INSTRUCTIONS.md +14 -1
- package/docs/actions.md +14 -4
- package/docs/control_object.md +12 -0
- package/docs/control_value.md +73 -0
- package/package.json +1 -1
package/docs/AI_INSTRUCTIONS.md
CHANGED
|
@@ -144,6 +144,15 @@ consistency across the app, not from any single call site.
|
|
|
144
144
|
`signal` carrying something is an answer). Read it before a screen that
|
|
145
145
|
modifies an existing resource (`pristineKey`), and before reaching for
|
|
146
146
|
`canSendWhileUnchanged` because a submit "does nothing".
|
|
147
|
+
- `distributeChildStates` on a group — the way down asked once for ALL the
|
|
148
|
+
children, when they cannot be placed one at a time (four seats where who sits
|
|
149
|
+
down decides who moves). The mirror of `aggregateChildStates`; see
|
|
150
|
+
control_object.md. Read it before writing a `uiAction` whose only job is to
|
|
151
|
+
translate. Two neighbouring questions have answers already: a yes/no shown as
|
|
152
|
+
two rows needs no translation (`List.Item value={true}` beside
|
|
153
|
+
`value={false}` IS a boolean control), and a cross that means "back to the
|
|
154
|
+
usual answer" needs none either — clearing empties, and `placeholder` is
|
|
155
|
+
where that sentence is written (control_value.md).
|
|
147
156
|
- `docs/control_object.md` — a value made of several controls: `<ControlGroup>`
|
|
148
157
|
(the shape) vs `<Form>` (the shape plus a send), what a group's `name` does
|
|
149
158
|
and what a nameless one merges, and what a `<Picker type="object">` needs in
|
|
@@ -162,7 +171,11 @@ consistency across the app, not from any single call site.
|
|
|
162
171
|
the bounds of a span PUSH each other while they turn (`minDuration`) instead
|
|
163
172
|
of being refused at send, and `placeholder` is a position shown without being
|
|
164
173
|
an answer — for a span that is optional ("any time of day") on wheels that
|
|
165
|
-
have no blank row to land on.
|
|
174
|
+
have no blank row to land on. A clear (or a value written as `undefined`) puts
|
|
175
|
+
such a pair back on its placeholder and back to answering nothing, so a row's
|
|
176
|
+
cross means what it says. `hours` bounds what the wheels offer
|
|
177
|
+
(`{ min: 7, max: 21 }`, or the list itself) — rows nobody can land on are rows
|
|
178
|
+
in the way. Read this before writing a time input, and
|
|
166
179
|
before making a pair of wheels say "nothing set" by hand.
|
|
167
180
|
- `docs/control_group.md` — `<Group>`: several controls reading as one framed
|
|
168
181
|
object (one border per seam, radius on the outer corners only). Read it
|
package/docs/actions.md
CHANGED
|
@@ -72,6 +72,16 @@ const userAction = getUser.bindParams({ id: userIdSignal });
|
|
|
72
72
|
| `reset()` | Aborts what is running and puts the action back to idle, data and all. |
|
|
73
73
|
| `abort()` | Calls off the run in flight, keeping the data it had. |
|
|
74
74
|
|
|
75
|
+
### Aborting saves resources, it does not undo
|
|
76
|
+
|
|
77
|
+
`abort()` cancels what can still be cancelled — a `fetch` wired to the
|
|
78
|
+
callback's `signal` — and nothing more. The server may have done the work
|
|
79
|
+
before the cancellation reached it, or may not honor cancellations at all:
|
|
80
|
+
whether the work happened is known from the run's settlement alone. For that
|
|
81
|
+
reason a run's promise settles only when its callback settles, even after an
|
|
82
|
+
abort, and anything sequenced behind a run — an optimistic control's queued
|
|
83
|
+
request, for instance — waits for that settlement, never for the abort.
|
|
84
|
+
|
|
75
85
|
## Reading an action
|
|
76
86
|
|
|
77
87
|
```jsx
|
|
@@ -175,12 +185,12 @@ the event that caused it, and `findEvent(event, type)` walks that chain back:
|
|
|
175
185
|
clearable
|
|
176
186
|
uiAction={(value, event) => {
|
|
177
187
|
if (findEvent(event, "navi_clear_ui_state")) {
|
|
178
|
-
// the
|
|
179
|
-
//
|
|
180
|
-
|
|
188
|
+
// the cross was pressed — the row is empty because somebody emptied it,
|
|
189
|
+
// not because the last player left the list
|
|
190
|
+
askAgainLater();
|
|
181
191
|
return;
|
|
182
192
|
}
|
|
183
|
-
draft.
|
|
193
|
+
draft.players = value;
|
|
184
194
|
}}
|
|
185
195
|
/>
|
|
186
196
|
```
|
package/docs/control_object.md
CHANGED
|
@@ -152,6 +152,18 @@ are not all there yet:
|
|
|
152
152
|
group holds. Only a child controlled by a `value`/`checked` prop is left
|
|
153
153
|
alone: its owner decides.
|
|
154
154
|
|
|
155
|
+
`distributeChildUIState` is asked child by child, which is fine while each child
|
|
156
|
+
can be placed on its own. When it cannot — four seats where who sits down
|
|
157
|
+
decides who moves, a row whose first answer changes what the others may show —
|
|
158
|
+
`distributeChildStates` is the same way down asked ONCE for all of them:
|
|
159
|
+
|
|
160
|
+
```js
|
|
161
|
+
distributeChildStates: (groupValue, children) => new Map([[child, state], …])
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
the mirror of `aggregateChildStates`, which already sees every child. A child
|
|
165
|
+
the Map does not name is left where it is. Given both, the plural one wins.
|
|
166
|
+
|
|
155
167
|
A group with an `aggregateChildStates` of its own is outside all of this: what
|
|
156
168
|
it returns is taken as the truth, `undefined` included. That is the one way such
|
|
157
169
|
a group says **"I have nothing to say yet"** — return `undefined` while the
|
package/docs/control_value.md
CHANGED
|
@@ -10,6 +10,9 @@ somewhere else in the app.
|
|
|
10
10
|
- [What a signal holds, control by control](#what-a-signal-holds-control-by-control)
|
|
11
11
|
- [Empty keeps the shape of the question](#empty-keeps-the-shape-of-the-question)
|
|
12
12
|
- [Which controls take a `signal`](#which-controls-take-a-signal)
|
|
13
|
+
- [The PROP is what controls, not its value](#the-prop-is-what-controls-not-its-value)
|
|
14
|
+
- [A yes/no shown as two rows](#a-yesno-shown-as-two-rows)
|
|
15
|
+
- [Clearing, resetting, and what is shown meanwhile](#clearing-resetting-and-what-is-shown-meanwhile)
|
|
13
16
|
- [`value` and `signal` exclude each other](#value-and-signal-exclude-each-other)
|
|
14
17
|
- [A `stateSignal` brings more than a value](#a-statesignal-brings-more-than-a-value)
|
|
15
18
|
|
|
@@ -160,6 +163,76 @@ own `selected` — but not expect the two to arbitrate. An item that declares
|
|
|
160
163
|
it. On screen that reads as a list where clicking does nothing, so navi says it
|
|
161
164
|
in dev the moment the two claims meet: bind one end or the other, not both.
|
|
162
165
|
|
|
166
|
+
## The PROP is what controls, not its value
|
|
167
|
+
|
|
168
|
+
`value` (and `checked`, and a row's `selected`) makes a control controlled by
|
|
169
|
+
being **there**. Its value is a separate question: `value={undefined}` says "I
|
|
170
|
+
hold this one, and right now it holds nothing" — the control shows nothing, and
|
|
171
|
+
nothing else can fill it. That is a real state and it is the one asked for.
|
|
172
|
+
|
|
173
|
+
The trap is the shorthand for an optional prop:
|
|
174
|
+
|
|
175
|
+
```jsx
|
|
176
|
+
// WRONG — the key is always there, so the control is always controlled,
|
|
177
|
+
// and the group above it can never fill it
|
|
178
|
+
<Input checked={bound ? undefined : checked} />
|
|
179
|
+
|
|
180
|
+
// RIGHT — the prop is there only when you are the one answering
|
|
181
|
+
<Input {...(bound ? {} : { checked })} />
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Passing a prop the code knows will always be `undefined` is the same mistake
|
|
185
|
+
written once instead of twice: leave it out. navi says so in dev when a group
|
|
186
|
+
tries to place a child that has claimed itself this way.
|
|
187
|
+
|
|
188
|
+
## A yes/no shown as two rows
|
|
189
|
+
|
|
190
|
+
A checkbox is one way to ask a yes/no; two rows one can compare — "Publique, we
|
|
191
|
+
propose it to players looking for this kind of game" against "Privée, it travels
|
|
192
|
+
only by the link you send" — is another, and it is the same value:
|
|
193
|
+
|
|
194
|
+
```jsx
|
|
195
|
+
<Picker name="visibility" signal={isPublicSignal}>
|
|
196
|
+
<List selectable>
|
|
197
|
+
<List.Item value={true}>Publique …</List.Item>
|
|
198
|
+
<List.Item value={false}>Privée …</List.Item>
|
|
199
|
+
</List>
|
|
200
|
+
</Picker>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Nothing translates: the row holds the boolean, the form carries the boolean.
|
|
204
|
+
What cannot be done is give a row `undefined` to mean "no value" — `undefined`
|
|
205
|
+
is what UNCHECKED means, here as in HTML, so such a row can never be ticked.
|
|
206
|
+
A value the control can hold is a value one can see; "nothing chosen" is the
|
|
207
|
+
absence of a row, not a row.
|
|
208
|
+
|
|
209
|
+
## Clearing, resetting, and what is shown meanwhile
|
|
210
|
+
|
|
211
|
+
Three things that look alike and are not:
|
|
212
|
+
|
|
213
|
+
| gesture / prop | what it does |
|
|
214
|
+
| ------------------------- | ----------------------------------------------------------------------------------- |
|
|
215
|
+
| `--navi-clear`, the cross | the control holds its own empty — `""`, `[]`, `{}`, unchecked |
|
|
216
|
+
| `--navi-reset` | the control goes back to its `defaultValue` |
|
|
217
|
+
| `defaultValue` | where it starts, and where a reset goes back to — a real value, sent like any other |
|
|
218
|
+
| `placeholder` | what is SHOWN while it holds nothing, and never a value |
|
|
219
|
+
|
|
220
|
+
So a row whose cross means "back to the one from my profile" needs nothing of
|
|
221
|
+
its own: clearing empties it, and `placeholder` is where that sentence is
|
|
222
|
+
written.
|
|
223
|
+
|
|
224
|
+
```jsx
|
|
225
|
+
<Picker clearable placeholder="Celui de mon profil" signal={sideSignal} />
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
What the app then reads is `undefined` (or the empty of the row's type), which
|
|
229
|
+
is what "nothing chosen here, use the usual answer" already means everywhere
|
|
230
|
+
else — the same rule as an emptied signal falling back on its default.
|
|
231
|
+
|
|
232
|
+
A control that cannot show emptiness — a pair of wheels has no blank row to land
|
|
233
|
+
on — takes the same `placeholder` as a POSITION instead of a word (see
|
|
234
|
+
`TimeWheel`): shown, and still not an answer.
|
|
235
|
+
|
|
163
236
|
## `value` and `signal` exclude each other
|
|
164
237
|
|
|
165
238
|
`value` (or `checked`) says "you hold it", `signal` says "the signal holds it".
|