@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_object.md
CHANGED
|
@@ -31,13 +31,13 @@ its own `action`/`uiAction`/`command` if you want one.
|
|
|
31
31
|
// worth { street, city, zip }
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
It is also the brick composite controls are built from:
|
|
35
|
-
`aggregateChildStates` / `distributeChildUIState`
|
|
36
|
-
|
|
37
|
-
fields that are one date
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
It is also the brick composite controls are built from: with
|
|
35
|
+
`aggregateChildStates` / `distributeChildUIState` (see its JSDoc) a group takes
|
|
36
|
+
and hands back a single value of your own — two wheels that are one number of
|
|
37
|
+
minutes, three fields that are one date — and is driven by one `value`/`signal`
|
|
38
|
+
like any other control. `TimeRangeSpin` is a `ControlGroup` of two `TimeSpin`s;
|
|
39
|
+
`SpinGroup` (which a `TimeSpin` is), `WheelGroup` and `InputDuration` use the
|
|
40
|
+
same mechanism for their own members.
|
|
41
41
|
|
|
42
42
|
## `<Form>`: the shape, plus a send
|
|
43
43
|
|
|
@@ -45,10 +45,8 @@ A `<Form>` aggregates exactly the same way. What it adds is everything about
|
|
|
45
45
|
**sending**: the reference it measures against, the refusal to act when nothing
|
|
46
46
|
changed (see [form_changed.md](./form_changed.md)), the submit button and its
|
|
47
47
|
`readOnlyWhileFormUnchanged`, what follows a successful send (`command`), and a
|
|
48
|
-
`<form>` element with the browser's own submit/reset.
|
|
49
|
-
|
|
50
|
-
So the choice is not about the value, it is about whether this cluster is a
|
|
51
|
-
**question with a send**:
|
|
48
|
+
`<form>` element with the browser's own submit/reset. So the choice is not
|
|
49
|
+
about the value, it is about whether this cluster is a **question with a send**:
|
|
52
50
|
|
|
53
51
|
- a shape inside a bigger whole → `ControlGroup`;
|
|
54
52
|
- something the user sends → `Form`.
|
|
@@ -58,11 +56,9 @@ the `<form>` element — and it is a **second question**, not a sub-object of th
|
|
|
58
56
|
first: what it holds is its own, and the form around it never sees it. Not in
|
|
59
57
|
the value it sends, not in its "nothing changed", and not in the constraints it
|
|
60
58
|
checks before sending — a required field in a popup nobody opened cannot refuse
|
|
61
|
-
the outer submit and point at something the screen is not showing.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
"nothing changed", its own submit story. Grouping three fields into a sub-object
|
|
65
|
-
should cost none of that — that is a `ControlGroup`.
|
|
59
|
+
the outer submit and point at something the screen is not showing. Grouping
|
|
60
|
+
three fields into a sub-object should cost none of that — that is a
|
|
61
|
+
`ControlGroup`.
|
|
66
62
|
|
|
67
63
|
## Naming, and what a nameless group does
|
|
68
64
|
|
|
@@ -70,10 +66,9 @@ A group's `name` is the key its value lands under, in the group above it — a
|
|
|
70
66
|
`ControlGroup`, or a `Form` inside something that is not a form (a picker, say).
|
|
71
67
|
A form inside a form lands nowhere: it answers for itself, named or not.
|
|
72
68
|
|
|
73
|
-
Left nameless, a group is a **grouping**: it
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
there.
|
|
69
|
+
Left nameless, a group is a **grouping**: it holds its children together (shared
|
|
70
|
+
navigation, a visual cluster) without claiming a key, and what it holds is
|
|
71
|
+
merged into the object around it as if its children had been written there.
|
|
77
72
|
|
|
78
73
|
```jsx
|
|
79
74
|
<ControlGroup name="when">
|
|
@@ -86,11 +81,10 @@ there.
|
|
|
86
81
|
// worth { day, hours, minutes } — the WheelGroup adds no key of its own
|
|
87
82
|
```
|
|
88
83
|
|
|
89
|
-
A nameless **leaf** is a different story:
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
[form_changed.md](./form_changed.md#a-control-that-answers-for-itself)).
|
|
84
|
+
A nameless **leaf** is a different story: a control whose value has nowhere to
|
|
85
|
+
go, and it is warned about — unless it is not answering this group's question at
|
|
86
|
+
all, which it says with `standalone`
|
|
87
|
+
([form_changed.md](./form_changed.md#a-control-that-answers-for-itself)).
|
|
94
88
|
|
|
95
89
|
## A picker whose value is an object
|
|
96
90
|
|
|
@@ -98,39 +92,33 @@ its own — says so with `standalone` and leaves the group entirely (see
|
|
|
98
92
|
aggregates (next to `type="array"`, whose value is a selection):
|
|
99
93
|
|
|
100
94
|
```jsx
|
|
101
|
-
<Picker name="when" type="object"
|
|
95
|
+
<Picker name="when" type="object" signal={whenSignal}>
|
|
102
96
|
<ControlGroup>
|
|
103
|
-
<DaySpin name="day"
|
|
97
|
+
<DaySpin name="day" />
|
|
104
98
|
<WheelGroup>
|
|
105
|
-
<Wheel name="hours"
|
|
106
|
-
<Wheel name="minutes"
|
|
99
|
+
<Wheel name="hours" />
|
|
100
|
+
<Wheel name="minutes" />
|
|
107
101
|
</WheelGroup>
|
|
108
102
|
</ControlGroup>
|
|
109
103
|
</Picker>
|
|
104
|
+
// whenSignal holds { day, hours, minutes }
|
|
110
105
|
```
|
|
111
106
|
|
|
112
|
-
Two things to get right:
|
|
113
|
-
|
|
114
107
|
- **One control in the popup, and it must be the group.** A picker syncs with a
|
|
115
108
|
single control: the first one receives the picker's whole value and is the
|
|
116
|
-
only one read back
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
a long list, a "select all" beside it: they are tools, and a tool says so with
|
|
130
|
-
`standalone`. The picker then walks past it and talks to the list, which is
|
|
131
|
-
what a popup made of one choice and the means to reach it needs. Never put it
|
|
132
|
-
on the control that IS the value — the picker would have nobody left to fill,
|
|
133
|
-
and the popup would open blank on a value it holds.
|
|
109
|
+
only one read back; a second one beside it is neither filled nor collected,
|
|
110
|
+
and navi says so in dev. Wrap them in one `ControlGroup` (or one `Form`, for a
|
|
111
|
+
popup with a send of its own — only while the picker is not inside a form: a
|
|
112
|
+
form inside a form answers for itself, leaving the picker nothing to fill).
|
|
113
|
+
- **The value travels by name.** The group hands each named child its own key;
|
|
114
|
+
a nameless grouping inside receives the whole object and picks out what it
|
|
115
|
+
names. Give such a picker a scalar type and the whole object lands on one
|
|
116
|
+
control — which is how `"[object Object]"` ends up in a url.
|
|
117
|
+
- **A control that helps FIND the answer is not the answer.** A search box
|
|
118
|
+
above a long list, a "select all" beside it are tools, marked `standalone`:
|
|
119
|
+
the picker walks past them to the list. Never put it on the control that IS
|
|
120
|
+
the value — the picker would have nobody left to fill, and the popup would
|
|
121
|
+
open blank on a value it holds.
|
|
134
122
|
|
|
135
123
|
```jsx
|
|
136
124
|
<Picker name="place_ids" type="array">
|
|
@@ -143,109 +131,56 @@ Two things to get right:
|
|
|
143
131
|
|
|
144
132
|
A popup can also hold **no** answer for the picker: several lists that are the
|
|
145
133
|
same one choice, each acting on every touch, nothing read back on close. Mark
|
|
146
|
-
them all `standalone` and the picker talks to none of them —
|
|
147
|
-
|
|
148
|
-
|
|
134
|
+
them all `standalone` and the picker talks to none of them — rather than one
|
|
135
|
+
`ControlGroup` around them, whose value would be one key per list where the
|
|
136
|
+
answer is a single value.
|
|
149
137
|
|
|
150
138
|
## A group holds what it was given
|
|
151
139
|
|
|
152
140
|
A group's value looks like it is made of its children, and mostly it is — but
|
|
153
|
-
|
|
154
|
-
are not all there yet:
|
|
141
|
+
not while the children are not all there yet:
|
|
155
142
|
|
|
156
143
|
- a group **told** a value holds it whole, before any child has registered to
|
|
157
|
-
show it
|
|
158
|
-
|
|
144
|
+
show it: items still loading, a popup built at open, a row scrolled out of a
|
|
145
|
+
virtualized list do not make the value smaller;
|
|
159
146
|
- **a child mounting or unmounting is not somebody answering.** While children
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
- a child arriving **after** the value did is placed from what the group holds
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
the
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
here:
|
|
198
|
-
|
|
199
|
-
- **the group's value has to be the view that carries the MOST.** Four seats say
|
|
200
|
-
who plays AND who sits where; a list of who plays says half of that. Make the
|
|
201
|
-
seating the value and the list is derived from it (`value.filter(Boolean)`);
|
|
202
|
-
make the list the value and every gesture on a seat has to be guessed at. The
|
|
203
|
-
rule to apply: between two views of one answer, the value is the one the other
|
|
204
|
-
can be computed from;
|
|
205
|
-
- **the Map must name EVERY child**, the derived view included. A child the Map
|
|
206
|
-
does not name is left where it is — which reads as "the list never fills";
|
|
207
|
-
- **name the children you WANT, never exclude the one you don't.** A group holds
|
|
208
|
-
more than the views: a search box, a filter, whatever the popup needs. Reading
|
|
209
|
-
the seats as "every child except the list" collects those too, and the value
|
|
210
|
-
ends up with five slots for four seats. Finding them by name says what it
|
|
211
|
-
means;
|
|
212
|
-
- **a gesture that moves two children must never leave the value in an
|
|
213
|
-
in-between.** Dragging somebody from one seat to another is one intention and
|
|
214
|
-
two writes, and the group aggregates between them: for one instant the person
|
|
215
|
-
is in the list and in no seat, which a value that IS the seating has no room
|
|
216
|
-
for. Measured, that instant costs the person — the aggregate drops them and
|
|
217
|
-
the distribute unticks them before the second write lands. Two ways out, and
|
|
218
|
-
the first is the one to prefer:
|
|
219
|
-
|
|
220
|
-
1. **write the group's value once.** `rowSignal.value = slots` with both seats
|
|
221
|
-
already moved: there is no in-between at all. Available when the row is
|
|
222
|
-
where the placement lives;
|
|
223
|
-
2. **write the destination first**, when the seats are the truth and must stay
|
|
224
|
-
separate signals. The person is somewhere at every observable moment (in
|
|
225
|
-
two seats for an instant, which the rule then resolves), instead of nowhere.
|
|
226
|
-
Source-first loses them — measured, and silently.
|
|
227
|
-
|
|
228
|
-
Wrapping the two writes in a signal `batch()` does NOT help: the group
|
|
229
|
-
aggregates on each child's change, not on the render that follows.
|
|
230
|
-
|
|
231
|
-
- **and when the in-between cannot be avoided, the aggregate says so.** Two
|
|
232
|
-
people swapping seats means one leaves before the other arrives, whichever
|
|
233
|
-
order the writes take. What is avoidable is PUBLISHING that half-state: an
|
|
234
|
-
aggregate that returns **what the group already holds** says "not yet, ask me
|
|
235
|
-
again" — nothing is placed, nothing is handed to the row above, and the next
|
|
236
|
-
assembly publishes the whole answer. Returning `undefined` says the opposite
|
|
237
|
-
("there is no answer"), and wipes the row.
|
|
238
|
-
|
|
239
|
-
```js
|
|
240
|
-
// "not yet": the moving person is picked and seated nowhere, and it is not
|
|
241
|
-
// somebody who was just ticked — so this is a gesture in flight
|
|
242
|
-
const midMove = picked.some(
|
|
243
|
-
(id) => !seatedNow.includes(id) && lastAnswer.includes(id),
|
|
244
|
-
);
|
|
245
|
-
if (midMove) {
|
|
246
|
-
return lastAnswer; // what the group already holds
|
|
247
|
-
}
|
|
248
|
-
```
|
|
147
|
+
arrive, their aggregate is a partial reading; the group takes it for its value
|
|
148
|
+
only once it derived that value itself (nobody handed it one), or once a child
|
|
149
|
+
really acts;
|
|
150
|
+
- a child arriving **after** the value did is placed from what the group holds;
|
|
151
|
+
one arriving with something of its own to show is answering, and keeps it. A
|
|
152
|
+
child bound to a `signal` is placed like any other — bound is not frozen — and
|
|
153
|
+
the placement writes the signal, so an `<Input type="hidden" signal>` carrying
|
|
154
|
+
a piece of the answer shows what the group holds. Only a child controlled by a
|
|
155
|
+
`value`/`checked` prop is left alone: its owner decides.
|
|
156
|
+
|
|
157
|
+
When children cannot be placed one at a time — four seats where who sits down
|
|
158
|
+
decides who moves — `distributeChildStates(groupValue, children) => Map` places
|
|
159
|
+
them all at once (see the `ControlGroup` JSDoc). It closes the loop: the group
|
|
160
|
+
holds ONE answer its children are views OF, so when one speaks the others are
|
|
161
|
+
placed again — without it a seat keeps showing somebody the list no longer
|
|
162
|
+
holds, above all in a picker's popup, where the façade never echoes the value
|
|
163
|
+
back down. What follows:
|
|
164
|
+
|
|
165
|
+
- **the group's value is the view that carries the MOST.** Four seats say who
|
|
166
|
+
plays AND who sits where, a list of who plays says half of that: make the
|
|
167
|
+
seating the value and derive the list (`value.filter(Boolean)`). Between two
|
|
168
|
+
views of one answer, the value is the one the other can be computed from;
|
|
169
|
+
- **the Map names EVERY child**, the derived view included (one it does not name
|
|
170
|
+
is left where it is: "the list never fills"), and **by name** — "every child
|
|
171
|
+
except the list" also collects the search box the popup holds;
|
|
172
|
+
- **a gesture that moves two children never leaves the value in an
|
|
173
|
+
in-between.** A drag from one seat to another is two writes, and the group
|
|
174
|
+
aggregates between them: for an instant the person is in no seat, and is
|
|
175
|
+
dropped. Write the group's value once, both seats already moved; or, when the
|
|
176
|
+
seats must stay separate signals, write the destination first (two seats for
|
|
177
|
+
an instant, never none) — source-first loses them silently. A signal `batch()`
|
|
178
|
+
does NOT help: the group aggregates on each child's change, not on the render;
|
|
179
|
+
- **an in-between that cannot be avoided** (two people swapping seats) is not
|
|
180
|
+
published: an aggregate returning what the group already holds says "not
|
|
181
|
+
yet" — nothing is placed or handed up, and the next assembly publishes the
|
|
182
|
+
whole answer. Returning `undefined` says "there is no answer", and wipes the
|
|
183
|
+
row.
|
|
249
184
|
|
|
250
185
|
```jsx
|
|
251
186
|
// the seats are found by name, never as "everything that is not the list"
|
|
@@ -286,25 +221,22 @@ AND which levels, when it starts AND when it ends — hands back a sub-object:
|
|
|
286
221
|
```
|
|
287
222
|
|
|
288
223
|
Flattening those into the sent object is the caller's business
|
|
289
|
-
(`{ ...value.level, ...value.hours }`)
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
them.
|
|
224
|
+
(`{ ...value.level, ...value.hours }`): a picker with no name of its own could
|
|
225
|
+
not be collected, and one merging its keys into the object around it would take
|
|
226
|
+
the row's identity away — nothing would say which row a key came from, nor which
|
|
227
|
+
row to put an incoming value back on. A `<ControlGroup>` with no name IS that
|
|
228
|
+
merge, for the case where there is no row: several controls in one screen, no
|
|
229
|
+
door between them.
|
|
296
230
|
|
|
297
231
|
## A settings sheet
|
|
298
232
|
|
|
299
233
|
A popup that is not one choice but a handful of settings — four tabs, a select,
|
|
300
234
|
a field, a button that answers with a place — and ONE answer, which must reach
|
|
301
|
-
the app only when the popup closes
|
|
302
|
-
open, and Escape must leave things exactly as they were found.
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
follows every gesture, which is what the popup shows), so what is inside acts on
|
|
307
|
-
nothing until the user is done.
|
|
235
|
+
the app only when the popup closes: the list behind it must not move while it is
|
|
236
|
+
open, and Escape must leave things exactly as they were found. It is the object
|
|
237
|
+
picker above, with the group in its popup: a picker's `action` runs on close and
|
|
238
|
+
only on close (its `uiAction` follows every gesture, which is what the popup
|
|
239
|
+
shows).
|
|
308
240
|
|
|
309
241
|
```jsx
|
|
310
242
|
<Picker
|
|
@@ -327,70 +259,41 @@ nothing until the user is done.
|
|
|
327
259
|
</SlideContainer>
|
|
328
260
|
</ControlGroup>
|
|
329
261
|
</Picker>
|
|
262
|
+
// `save` receives, on close and once:
|
|
263
|
+
// { origin: "city", city: "Antibes", radius: "30", department: "" }
|
|
330
264
|
```
|
|
331
265
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
**Every field answers, including the ones the current tab does not use.** A
|
|
354
|
-
field on another tab stays mounted, keeps what was typed in it, and comes back
|
|
355
|
-
in the object. Which of them count is read from `origin` by whoever receives the
|
|
356
|
-
value — the group has no opinion about it, and does not need one. Deriving
|
|
357
|
-
something narrower (a stored string, an id) is that reader's business too:
|
|
358
|
-
`action` receives the object and stores whatever it wants, `value` hands the
|
|
359
|
-
object back.
|
|
360
|
-
|
|
361
|
-
A group CAN be worth a single value of its own — `aggregateChildStates` and
|
|
362
|
-
`distributeChildUIState`, see the top of this file — and a sheet is precisely
|
|
363
|
-
where that is the wrong reach: it throws away the state that makes the answer
|
|
364
|
-
readable, starting with the tab.
|
|
365
|
-
|
|
366
|
-
**A button inside is not a field.** The one asking for a position acts on the
|
|
367
|
-
press, like anywhere else; what it answers goes into a control that IS a field
|
|
368
|
-
(a read-only one showing the place it found), and the group picks it up from
|
|
369
|
-
there.
|
|
370
|
-
|
|
371
|
-
The façade stays on what is saved by reading the app's own state
|
|
372
|
-
(`value={zone}` + a `ui` rendered from `zone`), while the picker holds the
|
|
373
|
-
draft: the summary behind the open dialog then shows the answer, not the
|
|
374
|
-
attempt.
|
|
375
|
-
|
|
376
|
-
Seen working — the tabs, the geolocation button, the cancel — in
|
|
266
|
+
**Which tab is showing is part of the answer**: the same fields mean different
|
|
267
|
+
things depending on the tab they were filled on. A tab bar is a navigation
|
|
268
|
+
(`<Nav slideContainer>` + `<Link slide>`), not a field, so the current area
|
|
269
|
+
reaches the value through an `<Input type="hidden">` bound to the container's
|
|
270
|
+
signal (a tab bar made of radios is a field already). That signal is written
|
|
271
|
+
both ways — a value handed DOWN writes it too, on open and when Escape puts back
|
|
272
|
+
what the picker held — so the sheet reopens on the tab of the answer, not on the
|
|
273
|
+
one being tried.
|
|
274
|
+
|
|
275
|
+
**Every field answers**, including those on another tab: they stay mounted and
|
|
276
|
+
come back in the object, and whoever receives it reads `origin` to know which
|
|
277
|
+
count. Aggregating the sheet to a single value of its own
|
|
278
|
+
(`aggregateChildStates`) throws away what makes the answer readable, starting
|
|
279
|
+
with the tab. **A button inside is not a field**: the one asking for a position
|
|
280
|
+
acts on the press, and what it answers goes into a control that IS one (a
|
|
281
|
+
read-only field showing the place found).
|
|
282
|
+
|
|
283
|
+
The façade reads the app's own state (`value={zone}` + a `ui` rendered from
|
|
284
|
+
`zone`) while the picker holds the draft, so the summary behind the open dialog
|
|
285
|
+
shows the answer, not the attempt. Demo:
|
|
377
286
|
`control/demos/picker/9_picker_settings_sheet_demo.html`.
|
|
378
287
|
|
|
379
288
|
## `Group` is not `ControlGroup`
|
|
380
289
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
the eye, one border per seam. See [control_group.md](./control_group.md).
|
|
385
|
-
- `<ControlGroup>` is about the **value** — several controls reading as one
|
|
386
|
-
object to the action.
|
|
387
|
-
|
|
388
|
-
They compose: a `Group` around controls that also happen to be a
|
|
389
|
-
`ControlGroup`'s children is fine, and either can exist without the other.
|
|
290
|
+
`<Group>` is about the **frame** — several controls reading as one object to the
|
|
291
|
+
eye ([group.md](./group.md)); `<ControlGroup>` is about the **value**. They
|
|
292
|
+
compose, and either can exist without the other.
|
|
390
293
|
|
|
391
294
|
## See also
|
|
392
295
|
|
|
393
296
|
- [form_changed.md](./form_changed.md) — what a form sends, and what it measures
|
|
394
297
|
against
|
|
395
298
|
- [control_value.md](./control_value.md) — who holds a single control's value
|
|
396
|
-
- [
|
|
299
|
+
- [group.md](./group.md) — the visual `<Group>`
|