@jsenv/navi 0.29.418 → 0.29.419
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 +192 -43
- package/dist/dev/jsenv_navi.js.map +10 -10
- package/dist/jsenv_navi.js +190 -41
- package/dist/jsenv_navi.js.map +10 -10
- package/docs/AI_INSTRUCTIONS.md +177 -261
- package/docs/actions.md +164 -251
- package/docs/autofocus.md +66 -83
- package/docs/control_object.md +117 -214
- package/docs/control_value.md +121 -185
- package/docs/create_and_edit.md +159 -254
- package/docs/css_architecture.md +196 -260
- package/docs/data_states.md +41 -44
- package/docs/dialog_shape.md +91 -134
- package/docs/drag_interactions.md +115 -174
- package/docs/drag_to_travel.md +10 -9
- package/docs/dynamic_import.md +119 -136
- package/docs/error_handling.md +75 -127
- package/docs/field_validation.md +49 -89
- package/docs/form_changed.md +61 -78
- package/docs/group.md +141 -0
- package/docs/i18n.md +31 -76
- package/docs/interactions.md +6 -4
- package/docs/list_action.md +36 -51
- package/docs/list_refresh.md +73 -115
- package/docs/mobile_touch.md +56 -62
- package/docs/navigation.md +377 -723
- package/docs/network_policy.md +61 -75
- package/docs/pan_zoom.md +62 -103
- package/docs/popup_backdrop.md +128 -168
- package/docs/popup_lift.md +179 -268
- package/docs/popup_open.md +380 -651
- package/docs/resource.md +228 -408
- package/docs/route_transitions.md +333 -482
- package/docs/safe_area.md +102 -89
- package/docs/scroll.md +175 -335
- package/docs/state_binding.md +2 -1
- package/docs/testid.md +18 -23
- package/docs/typography.md +111 -141
- package/docs/view_transitions.md +106 -104
- package/docs/z_index.md +61 -109
- package/package.json +2 -2
- package/docs/badge_list.md +0 -83
- package/docs/control_group.md +0 -168
- package/docs/mobile_layout_pitfalls.md +0 -145
package/docs/control_value.md
CHANGED
|
@@ -37,9 +37,8 @@ something nobody can change.
|
|
|
37
37
|
|
|
38
38
|
## A bound signal works in both directions
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
writes it**.
|
|
40
|
+
`signal` is not a seed. The control writes every change into it, **and follows
|
|
41
|
+
it when something else writes it**:
|
|
43
42
|
|
|
44
43
|
```jsx
|
|
45
44
|
const minutesSignal = useSignal(0);
|
|
@@ -58,32 +57,35 @@ minutesSignal.value = 30;
|
|
|
58
57
|
|
|
59
58
|
"Every change" includes the ones the control did not decide: a group placing
|
|
60
59
|
its children, a picker filling its popup at open, and the same picker putting
|
|
61
|
-
back what it held when Escape cancels.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
when the
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
60
|
+
back what it held when Escape cancels. So the signal always says where the
|
|
61
|
+
control actually is — which is what lets a settings sheet reopen on the tab it
|
|
62
|
+
was left on ([control_object.md](./control_object.md#a-settings-sheet)). A
|
|
63
|
+
picker's own signal follows its popup the same way, gesture by gesture, and so
|
|
64
|
+
does its `uiAction`. What waits for the close is the picker's `action` — and a
|
|
65
|
+
suggestion nobody touched, which becomes the answer when the popup closes on it.
|
|
66
|
+
|
|
67
|
+
The write-back replaces the `uiAction` that copies the value into a signal:
|
|
68
|
+
|
|
69
|
+
```jsx
|
|
70
|
+
// ✗ the control is told what it now holds, and the app writes it back down
|
|
71
|
+
<Picker value={side} uiAction={(value) => (sideSignal.value = value)} />
|
|
72
|
+
// ✓
|
|
73
|
+
<Picker signal={sideSignal} />
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The follow goes all the way up: a bound control inside a group — two wheels in
|
|
77
|
+
a `WheelGroup`, a field in a `ControlGroup` — makes that group re-aggregate when
|
|
78
|
+
its signal is written, and the form above sees the new value: the wheels roll,
|
|
78
79
|
and the submit lights up. Which is why a **button** offering such a value is not
|
|
79
|
-
a hand-written signal write — see
|
|
80
|
+
a hand-written signal write — see
|
|
81
|
+
[below](#a-button-that-proposes-a-value-is---navi-update).
|
|
80
82
|
|
|
81
83
|
## A picker fills its popup: the control inside already knows
|
|
82
84
|
|
|
83
85
|
A picker mirrors the control in its popup, both ways
|
|
84
|
-
([popup_open.md](./popup_open.md#composing-a-value-or-doing-work))
|
|
85
|
-
first half
|
|
86
|
-
|
|
86
|
+
([popup_open.md](./popup_open.md#composing-a-value-or-doing-work)), and the
|
|
87
|
+
first half happens **at open**: the picker fills the control with the value it
|
|
88
|
+
holds, before anything is shown.
|
|
87
89
|
|
|
88
90
|
```jsx
|
|
89
91
|
// the row matching the picker's value is already selected, painted and
|
|
@@ -101,20 +103,16 @@ with the value it holds, before anything is shown.
|
|
|
101
103
|
|
|
102
104
|
So a `current`, a `selected` per row, or a `value` computed from what the picker
|
|
103
105
|
holds is not "being explicit": it is a second answer to a question already
|
|
104
|
-
answered, and
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
What is left for the app is what to make of the selection, not what it is:
|
|
109
|
-
a row is marked (`aria-selected`) and painted by the list itself, so anything
|
|
110
|
-
extra hangs off that marker in CSS rather than off a prop.
|
|
106
|
+
answered, and the one that goes stale — the picker's value moves on a cancel, a
|
|
107
|
+
`--navi-update`, a signal written elsewhere, and none of those pass through that
|
|
108
|
+
prop. The list marks the selected row itself (`aria-selected`); anything extra
|
|
109
|
+
hangs off that marker in CSS.
|
|
111
110
|
|
|
112
111
|
## A button that proposes a value is `--navi-update`
|
|
113
112
|
|
|
114
|
-
A shortcut beside a control — "Tous niveaux"
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
signal to write by hand:
|
|
113
|
+
A shortcut beside a control — "Tous niveaux" next to a list of levels, "1h /
|
|
114
|
+
1h30 / 2h" next to a pair of wheels, a suggestion under a field — is a value
|
|
115
|
+
offered to that control. It is not an action, and not a signal to write by hand:
|
|
118
116
|
|
|
119
117
|
```jsx
|
|
120
118
|
<WheelGroup id="duration" signal={durationSignal}>
|
|
@@ -131,25 +129,21 @@ signal to write by hand:
|
|
|
131
129
|
</Button>
|
|
132
130
|
```
|
|
133
131
|
|
|
134
|
-
- the **value** is the button's own `value`, whatever shape
|
|
132
|
+
- the **value** is the button's own `value`, whatever its shape — a string, an
|
|
135
133
|
array of levels, an object of two wheels;
|
|
136
|
-
- the **target** is `commandFor`, naming the control's id — left out, the
|
|
137
|
-
control around the button
|
|
134
|
+
- the **target** is `commandFor`, naming the control's id — left out, the
|
|
135
|
+
nearest control around the button, which is what a button placed inside the
|
|
138
136
|
control it proposes to wants;
|
|
139
|
-
-
|
|
137
|
+
- the press goes through the same gate as every other interaction, so a
|
|
140
138
|
read-only, disabled or busy control **refuses it and says why**.
|
|
141
139
|
|
|
142
|
-
That last point is the
|
|
143
|
-
|
|
140
|
+
That last point is the reason, and the counter-example is what everybody writes
|
|
141
|
+
first:
|
|
144
142
|
|
|
145
143
|
```jsx
|
|
146
144
|
// ✗ not gated — plain DOM. On a read-only sheet the button greys out and fires
|
|
147
145
|
// all the same, rewriting a value nobody is allowed to change.
|
|
148
|
-
<Button
|
|
149
|
-
onClick={() => {
|
|
150
|
-
durationSignal.value = { hours: 1, minutes: 30 };
|
|
151
|
-
}}
|
|
152
|
-
>
|
|
146
|
+
<Button onClick={() => (durationSignal.value = { hours: 1, minutes: 30 })}>
|
|
153
147
|
1h30
|
|
154
148
|
</Button>
|
|
155
149
|
```
|
|
@@ -159,20 +153,17 @@ to a control: moving something else on screen, seeding state before anything is
|
|
|
159
153
|
drawn.
|
|
160
154
|
|
|
161
155
|
The id goes **on the control**, and a group is one — `ControlGroup`, `Form`,
|
|
162
|
-
`WheelGroup`, `List selectable`. Put
|
|
163
|
-
|
|
164
|
-
than letting the press do nothing
|
|
165
|
-
|
|
156
|
+
`WheelGroup`, `List selectable`. Put on a layout box around the control, the
|
|
157
|
+
command finds an element that holds no value, and navi says so in dev rather
|
|
158
|
+
than letting the press do nothing; an id that matches nothing is a dev warning
|
|
159
|
+
too, naming the id it looked for.
|
|
166
160
|
|
|
167
161
|
### `--navi-update:smooth`: the control is seen answering
|
|
168
162
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
moves under a finger.
|
|
174
|
-
|
|
175
|
-
That is what the `:smooth` argument asks for:
|
|
163
|
+
On a phone the thumb covers the shortcut while the eye is on the control, and a
|
|
164
|
+
value swapped at once shows neither which control changed nor by how much.
|
|
165
|
+
`:smooth` asks for the control to be seen moving to the value, the way it moves
|
|
166
|
+
under a finger:
|
|
176
167
|
|
|
177
168
|
```jsx
|
|
178
169
|
<Button command="--navi-update:smooth" commandFor={hoursId} value={evening}>
|
|
@@ -180,37 +171,26 @@ That is what the `:smooth` argument asks for:
|
|
|
180
171
|
</Button>
|
|
181
172
|
```
|
|
182
173
|
|
|
183
|
-
- **The control decides how it moves, and when the value lands.**
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
`uiAction` about it, with the press that asked for it.
|
|
204
|
-
- **`prefers-reduced-motion` keeps the instant swap.** The option describes
|
|
205
|
-
how the change is shown, and whoever asked to see less motion is answered
|
|
206
|
-
first.
|
|
207
|
-
|
|
208
|
-
The same request is available from JS, for what is not a button —
|
|
209
|
-
`dispatchRequestSetUIState(el, value, { behavior: "smooth" })` — and it
|
|
210
|
-
survives a group: sent to a `TimeRangeWheel`, it reaches each of its wheels.
|
|
211
|
-
|
|
212
|
-
_Reference: the wheel (`wheel.jsx`, `pendingBehaviorRef`) and the spin
|
|
213
|
-
(`picker_spin.jsx`, `travelsPending`) are the controls honouring it._
|
|
174
|
+
- **The control decides how it moves, and when the value lands.** A wheel takes
|
|
175
|
+
the value at once — whoever reads the control right after the press (a form,
|
|
176
|
+
`--navi-send`, a signal) gets it — and glides to the row, the short way round
|
|
177
|
+
when it loops. A spin plays the one travel a chevron would play, whatever the
|
|
178
|
+
distance, and the value lands with the slides (`duration`, 250 ms by
|
|
179
|
+
default); a value past `min`/`max` is refused the way that chevron refuses
|
|
180
|
+
it, and left alone. An `editable` spin (a `NumberSpin`, so a `TimeSpin`) is a
|
|
181
|
+
field, and nothing travels around a field: the value is swapped in place. A
|
|
182
|
+
control that does not know the argument answers like a plain `--navi-update`.
|
|
183
|
+
- **A gesture on the control itself is never fought.** A wheel being dragged or
|
|
184
|
+
flung keeps reporting its own rows, and goes to the requested value once the
|
|
185
|
+
finger's movement is over. A value a wheel merely caught up with is not a
|
|
186
|
+
choice: it fires no settle, no `action`. A spin's landing IS the answer being
|
|
187
|
+
given, so it tells `uiAction`, with the press that asked for it.
|
|
188
|
+
- **`prefers-reduced-motion` keeps the instant swap**: whoever asked to see less
|
|
189
|
+
motion is answered first.
|
|
190
|
+
|
|
191
|
+
From JS, for what is not a button:
|
|
192
|
+
`dispatchRequestSetUIState(el, value, { behavior: "smooth" })` — sent to a
|
|
193
|
+
group such as `TimeRangeWheel`, it reaches each of its wheels.
|
|
214
194
|
|
|
215
195
|
## `signal` + `defaultValue`: the answer and where it starts
|
|
216
196
|
|
|
@@ -242,14 +222,12 @@ or taken back (the action failed, the popup was cancelled).
|
|
|
242
222
|
|
|
243
223
|
This holds for a control, for a group given a `defaultValue` (a selectable list,
|
|
244
224
|
a `ControlGroup`), and for everything inside a picker's popup, which stays
|
|
245
|
-
mounted while closed. So the record goes in as `defaultValue` and nothing
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
open, which undoes the person's edits, but what the outside moved during the
|
|
252
|
-
opening stays moved.
|
|
225
|
+
mounted while closed. So the record goes in as `defaultValue` and nothing else —
|
|
226
|
+
no `key` to remount the popup when it changes: a key also destroys whatever is
|
|
227
|
+
being typed when the answer to ANOTHER field arrives. A picker's cancel follows
|
|
228
|
+
the same line: it undoes the person's edits
|
|
229
|
+
([popup_open.md](./popup_open.md#what-cancel-actually-undoes)), and what the
|
|
230
|
+
outside moved during the opening stays moved.
|
|
253
231
|
|
|
254
232
|
## Drawing what a control holds
|
|
255
233
|
|
|
@@ -257,6 +235,8 @@ A picker's trigger usually shows the choice (a label for a code, a summary of
|
|
|
257
235
|
an object). Read it from the picker rather than keeping a copy:
|
|
258
236
|
|
|
259
237
|
```jsx
|
|
238
|
+
// a `ui` component is handed `value`, `loading` and `interactive`;
|
|
239
|
+
// a `ui` element reads the same three with usePickerState()
|
|
260
240
|
const VisibilityLabel = ({ value }) => <Text>{labelOf(value)}</Text>;
|
|
261
241
|
|
|
262
242
|
<Picker name="visibility" defaultValue={user.visibility} ui={VisibilityLabel}>
|
|
@@ -264,13 +244,10 @@ const VisibilityLabel = ({ value }) => <Text>{labelOf(value)}</Text>;
|
|
|
264
244
|
</Picker>;
|
|
265
245
|
```
|
|
266
246
|
|
|
267
|
-
`ui={Component}` is handed `value`, `loading` and `interactive`. A `ui` given as
|
|
268
|
-
an element reads the same three with `usePickerState()`.
|
|
269
|
-
|
|
270
247
|
What not to write: `const s = useSignal(defaultValue)` bound as `signal={s}`,
|
|
271
248
|
just to have something to read. `useSignal` reads its argument once, so the
|
|
272
249
|
signal never hears the record move, and a bound signal takes precedence over the
|
|
273
|
-
control's own `defaultValue
|
|
250
|
+
control's own `defaultValue`: the field is stuck on its first value. A signal is
|
|
274
251
|
for a value the app owns and writes itself (a url param, state shared between
|
|
275
252
|
screens), not a mirror kept for drawing.
|
|
276
253
|
|
|
@@ -287,13 +264,12 @@ attribute:
|
|
|
287
264
|
| `List selectable multiple`, checkbox group | the array of selected values |
|
|
288
265
|
|
|
289
266
|
A group (a selectable list, a checkbox group) writes its whole selection into
|
|
290
|
-
the signal,
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
[create_and_edit.md](./create_and_edit.md#two-screens-two-states).
|
|
267
|
+
the signal, which its children put together between them. A `<Form>` (or a
|
|
268
|
+
`<ControlGroup>`) takes one the same way, holding the whole object: its named
|
|
269
|
+
children are filled from it, move when something else writes it, and write back
|
|
270
|
+
what they change. One signal for a screen whose values arrive together and where
|
|
271
|
+
no field needs a url of its own — the fields then take nothing but their `name`
|
|
272
|
+
(see [create_and_edit.md](./create_and_edit.md#two-screens-two-states)).
|
|
297
273
|
|
|
298
274
|
## Empty keeps the shape of the question
|
|
299
275
|
|
|
@@ -309,36 +285,28 @@ it holds something. A list of days nobody picked is `[]`, not `""`:
|
|
|
309
285
|
| radio, checkbox holding a value of its own | absent |
|
|
310
286
|
|
|
311
287
|
This is what a clear (`--navi-clear`, a row's cross) leaves behind and what the
|
|
312
|
-
object around it carries, so
|
|
313
|
-
is not needed, and not needed only after having watched the wrong shape reach
|
|
314
|
-
the server.
|
|
288
|
+
object around it carries, so `value.days || []` is not needed.
|
|
315
289
|
|
|
316
290
|
A checkbox is a member of a set, the way HTML has it: checked it carries its
|
|
317
291
|
`value` (`"on"` when it was given none), unchecked it carries nothing at all —
|
|
318
292
|
which is what lets several checkboxes sharing a name aggregate into an array. A
|
|
319
|
-
checkbox that is a yes/no
|
|
320
|
-
|
|
321
|
-
|
|
293
|
+
checkbox that is a yes/no says so with `value={true}`, and is then `true` or
|
|
294
|
+
`false`. Its bound `signal` holds the boolean either way: what a signal on a
|
|
295
|
+
checkbox is about is whether it is checked.
|
|
322
296
|
|
|
323
297
|
## Which controls take a `signal`
|
|
324
298
|
|
|
325
299
|
All of them: `Input` (every type), `Picker`, `Select`, `Wheel`, `Spin`,
|
|
326
|
-
`List selectable` (single and multiple), and control groups in general
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
`SlideContainer` takes one too, though it is layout rather than a control: the
|
|
331
|
-
area it shows is a piece of state like any other, and binding it is what lets
|
|
332
|
-
something else read where the slides are — or move them by writing it. Bind it
|
|
333
|
-
to a `stateSignal` a route declares as a search param and the area is in the
|
|
334
|
-
address, with no route per slide — see
|
|
335
|
-
[navigation.md](./navigation.md#a-slidecontainer-in-the-url-a-position-that-is-not-a-place-one-came-from).
|
|
300
|
+
`List selectable` (single and multiple), and control groups in general — they
|
|
301
|
+
go through the same state controller. `SlideContainer` takes one too, for the
|
|
302
|
+
area it shows (see
|
|
303
|
+
[navigation.md](./navigation.md#a-slidecontainer-in-the-url-a-position-that-is-not-a-place-one-came-from)).
|
|
336
304
|
|
|
337
305
|
Inside a `List selectable` you can bind the list, or give each `List.Item` its
|
|
338
306
|
own `selected` — but not expect the two to arbitrate. An item that declares
|
|
339
307
|
`selected` is answering for itself, and the list's signal does not reposition
|
|
340
|
-
it
|
|
341
|
-
|
|
308
|
+
it: a list where clicking does nothing. navi says it in dev the moment the two
|
|
309
|
+
claims meet — bind one end or the other, not both.
|
|
342
310
|
|
|
343
311
|
## The PROP is what controls, not its value
|
|
344
312
|
|
|
@@ -364,9 +332,9 @@ tries to place a child that has claimed itself this way.
|
|
|
364
332
|
|
|
365
333
|
## A yes/no shown as two rows
|
|
366
334
|
|
|
367
|
-
A checkbox is one way to ask a yes/no; two rows one can compare — "Publique
|
|
368
|
-
|
|
369
|
-
|
|
335
|
+
A checkbox is one way to ask a yes/no; two rows one can compare — "Publique" and
|
|
336
|
+
"Privée", each with the sentence that tells them apart — is another, and it is
|
|
337
|
+
the same value:
|
|
370
338
|
|
|
371
339
|
```jsx
|
|
372
340
|
<Picker name="visibility" signal={isPublicSignal}>
|
|
@@ -380,8 +348,7 @@ only by the link you send" — is another, and it is the same value:
|
|
|
380
348
|
Nothing translates: the row holds the boolean, the form carries the boolean.
|
|
381
349
|
What cannot be done is give a row `undefined` to mean "no value" — `undefined`
|
|
382
350
|
is what UNCHECKED means, here as in HTML, so such a row can never be ticked.
|
|
383
|
-
|
|
384
|
-
absence of a row, not a row.
|
|
351
|
+
"Nothing chosen" is the absence of a row, not a row.
|
|
385
352
|
|
|
386
353
|
## Clearing, resetting, and what is shown meanwhile
|
|
387
354
|
|
|
@@ -396,84 +363,53 @@ Three things that look alike and are not:
|
|
|
396
363
|
|
|
397
364
|
So a row whose cross means "back to the one from my profile" needs nothing of
|
|
398
365
|
its own: clearing empties it, and `placeholder` is where that sentence is
|
|
399
|
-
written.
|
|
366
|
+
written. The app then reads `undefined` (or the empty of the row's type), which
|
|
367
|
+
is what "nothing chosen here, use the usual answer" means everywhere else.
|
|
400
368
|
|
|
401
369
|
```jsx
|
|
402
370
|
<Picker clearable placeholder="Celui de mon profil" signal={sideSignal} />
|
|
403
371
|
```
|
|
404
372
|
|
|
405
|
-
What the app then reads is `undefined` (or the empty of the row's type), which
|
|
406
|
-
is what "nothing chosen here, use the usual answer" already means everywhere
|
|
407
|
-
else — the same rule as an emptied signal falling back on its default.
|
|
408
|
-
|
|
409
373
|
A control that cannot show emptiness — a pair of wheels has no blank row to land
|
|
410
374
|
on — takes the same `placeholder` as a POSITION instead of a word (see
|
|
411
|
-
`TimeWheel`): shown, and still not an answer.
|
|
375
|
+
`TimeWheel`): shown, and still not an answer. A clear, or a value written as
|
|
376
|
+
`undefined`, puts it back on that position and back to answering nothing.
|
|
412
377
|
|
|
413
378
|
## `value` and `signal` exclude each other
|
|
414
379
|
|
|
415
380
|
`value` (or `checked`) says "you hold it", `signal` says "the signal holds it".
|
|
416
381
|
Passing both is a call site to fix: **the signal wins and the other prop is
|
|
417
382
|
ignored** — on a leaf control as on a group (a selectable list, a checkbox
|
|
418
|
-
group) — and navi says so in dev.
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
Replacing `value` with `signal` also means dropping the `uiAction` that wrote
|
|
422
|
-
the signal by hand — writing it is exactly what the binding does. Keep
|
|
423
|
-
`uiAction` only for what is not "remember the value": logging, a side effect,
|
|
424
|
-
something else moving with it.
|
|
383
|
+
group) — and navi says so in dev. What is left for a `uiAction` once the signal
|
|
384
|
+
is bound: [state_binding.md](./state_binding.md#what-is-left-for-the-callback).
|
|
425
385
|
|
|
426
386
|
## A `stateSignal` brings more than a value
|
|
427
387
|
|
|
428
|
-
A plain signal (`useSignal`, `signal()`) is enough to bind a control.
|
|
429
|
-
`stateSignal` also carries its own `options`, and a control reads them so it
|
|
388
|
+
A plain signal (`useSignal`, `signal()`) is enough to bind a control, both ways.
|
|
389
|
+
A `stateSignal` also carries its own `options`, and a control reads them so it
|
|
430
390
|
does not have to be told twice: `type` (which decides the input type and the
|
|
431
391
|
validation messages), `min`, `max`, `step`, and its **default**, which seeds
|
|
432
392
|
`defaultValue`/`defaultChecked` — so a reset goes back to the signal's original
|
|
433
393
|
default rather than to whatever it happened to hold at the last render.
|
|
434
394
|
|
|
435
|
-
That is the only difference. A plain signal binds the same way in both
|
|
436
|
-
directions; it just has nothing extra to say.
|
|
437
|
-
|
|
438
395
|
## A time of day: typed, or turned
|
|
439
396
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
shows values that exist. Two things only the wheels have:
|
|
450
|
-
|
|
451
|
-
- the bounds of a span PUSH each other while they turn (`minDuration`) instead
|
|
452
|
-
of being refused at send;
|
|
453
|
-
- `placeholder` is a position shown without being an answer — for a span that
|
|
454
|
-
is optional ("any time of day") on wheels that have no blank row to land on. A
|
|
455
|
-
clear, or a value written as `undefined`, puts such a pair back on its
|
|
456
|
-
placeholder and back to answering nothing, so a row's cross means what it says
|
|
457
|
-
(the rule of [clearing](#clearing-resetting-and-what-is-shown-meanwhile)).
|
|
458
|
-
|
|
459
|
-
`hours` bounds what the wheels offer (`{ min: 7, max: 21 }`, or the list
|
|
460
|
-
itself): rows nobody can land on are rows in the way.
|
|
461
|
-
|
|
462
|
-
Reference: `src/control/picker/preset/spin_time.jsx`,
|
|
463
|
-
`src/control/wheel/wheel_time.jsx`.
|
|
397
|
+
`TimeSpin`/`TimeRangeSpin` are fields one types in, `TimeWheel`/`TimeRangeWheel`
|
|
398
|
+
are wheels one turns; both carry a single `"HH:MM"` (or `{ start, end }` for a
|
|
399
|
+
span), so a form holds one field either way. Prefer the wheels whenever a
|
|
400
|
+
half-written value would be nonsense: a time typed digit by digit goes through
|
|
401
|
+
states that are not times ("1" on its way to "18"), while a wheel only ever
|
|
402
|
+
shows values that exist. Only the wheels push the bounds of a span apart as they
|
|
403
|
+
turn (`minDuration`, which the spins check at send) and take a `placeholder` as
|
|
404
|
+
a position. Their props are in their JSDoc
|
|
405
|
+
(`src/control/picker/preset/spin_time.jsx`, `src/control/wheel/wheel_time.jsx`).
|
|
464
406
|
|
|
465
407
|
## See also
|
|
466
408
|
|
|
467
|
-
- [state_binding.md](./state_binding.md) — the same rule beyond controls
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
- [
|
|
471
|
-
|
|
472
|
-
- [
|
|
473
|
-
|
|
474
|
-
- [control_group.md](./control_group.md) — several controls reading as one
|
|
475
|
-
framed object
|
|
476
|
-
- [actions.md](./actions.md#action-or-uiaction) — `action` or `uiAction`: which
|
|
477
|
-
one carries loading and error, and which one carries nothing
|
|
478
|
-
- [popup_open.md](./popup_open.md#escape-cancels-the-other-gestures-keep) — what
|
|
479
|
-
a cancelled popup does to the value inside it
|
|
409
|
+
- [state_binding.md](./state_binding.md) — the same rule beyond controls
|
|
410
|
+
- [form_changed.md](./form_changed.md) — which fields a form counts as already
|
|
411
|
+
answered, and when it sends nothing
|
|
412
|
+
- [control_object.md](./control_object.md) — several controls answering one
|
|
413
|
+
object
|
|
414
|
+
- [group.md](./group.md) — several controls drawn as one frame
|
|
415
|
+
- [actions.md](./actions.md#action-or-uiaction) — `action` or `uiAction`
|