@jsenv/navi 0.29.94 → 0.29.95

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.
@@ -177,18 +177,47 @@ it. It matters most inside a picker's popup, where nothing else would bring the
177
177
  value back down: a picker's value IS what the control in its popup holds, so the
178
178
  façade never echoes it back.
179
179
 
180
- A group with an `aggregateChildStates` of its own is outside all of this: what
181
- it returns is taken as the truth, `undefined` included. That is the one way such
182
- a group says **"I have nothing to say yet"** — return `undefined` while the
183
- children it needs are missing, and navi leaves the value alone instead of
184
- publishing an empty shape upward. A group written to always return its keys
185
- (`{ mode: undefined, levels: [] }`) is claiming an answer nobody gave, and that
186
- claim overwrites the row above it the moment the popup opens.
187
-
188
- Together they are what makes the two-hop case work — the form fills the row, the
189
- row fills the control in its popup whatever order the pieces turn up in. Get
190
- one of them wrong and the symptom is always the same: a value that was there
191
- before the popup opened, and empty after.
180
+ Three things follow, and all three were paid for twice before being written
181
+ here:
182
+
183
+ - **the group's value has to be the view that carries the MOST.** Four seats say
184
+ who plays AND who sits where; a list of who plays says half of that. Make the
185
+ seating the value and the list is derived from it (`value.filter(Boolean)`);
186
+ make the list the value and every gesture on a seat has to be guessed at. The
187
+ rule to apply: between two views of one answer, the value is the one the other
188
+ can be computed from;
189
+ - **the Map must name EVERY child**, the derived view included. A child the Map
190
+ does not name is left where it is which reads as "the list never fills";
191
+ - **a gesture that moves two children writes the GROUP's value, not the two
192
+ children.** Dragging somebody from one seat to another is one intention and
193
+ two writes; written one after the other, the group re-places between them, the
194
+ rule sees a free seat and puts the person back where they were. Written as one
195
+ change of the group's value there is nothing in between. (Wrapping the two
196
+ writes in a signal `batch()` does NOT do it: the group aggregates on each
197
+ child's change, not on the render that follows.)
198
+
199
+ ```jsx
200
+ // the value IS the seating; the list is a view of it
201
+ <ControlGroup
202
+ aggregateChildStates={(children) => {…}} // seats, minus who the list dropped, plus who it added
203
+ distributeChildStates={(slots, children) =>
204
+ new Map([
205
+ ...seats.map((seat, i) => [seat, slots[i]]),
206
+ [list, slots.filter(Boolean)],
207
+ ])
208
+ }
209
+ >
210
+ ```
211
+
212
+ ```js
213
+ // and a drag is one write
214
+ const moveTo = (from, to) => {
215
+ const slots = [...rowSignal.value];
216
+ slots[to] = slots[from];
217
+ slots[from] = undefined;
218
+ rowSignal.value = slots;
219
+ };
220
+ ```
192
221
 
193
222
  ## One line, one key
194
223
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/navi",
3
- "version": "0.29.94",
3
+ "version": "0.29.95",
4
4
  "type": "module",
5
5
  "description": "Library of components including navigation to create frontend applications",
6
6
  "repository": {