@jsenv/navi 0.29.60 → 0.29.62

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.
@@ -143,6 +143,14 @@ consistency across the app, not from any single call site.
143
143
  Read it before reading the pointer by hand — who owns a press between nested
144
144
  boxes, and what a touch may do, are decided before the first pixel moves and
145
145
  cannot be got right from outside navi.
146
+ - `docs/drag_to_travel.md` — a pointer pushing a whole screen aside
147
+ (`SlideContainer`, `RouteTravel`) and a popup pushed back towards its edge:
148
+ what the gesture is, and above all who owns a press several boxes want — a
149
+ scroller with room left, a nested travelling box, something being carried, a
150
+ surface in the top layer, and the grip a docked popup narrows itself to. Read
151
+ it before putting anything that reads the pointer inside a box that travels,
152
+ and before wondering why a page moved under a gesture meant for what was in
153
+ it.
146
154
  - `docs/MOBILE_LAYOUT_PITFALLS.md` — mobile-specific layout gotchas (viewport
147
155
  units, virtual keyboard, safe areas).
148
156
  - `docs/navigation.md` — how to build navigation: declaring routes
package/docs/actions.md CHANGED
@@ -158,6 +158,38 @@ To merely REMEMBER the value rather than send it, neither is the answer: bind a
158
158
  signal and drop the callback entirely — see
159
159
  [control_value.md](./control_value.md).
160
160
 
161
+ ## `uiAction` mirrors the state, it does not report a gesture
162
+
163
+ `uiAction` fires whenever the control's state changes, whoever changed it. The
164
+ user typing is one cause among several: a `value` prop coming back down after a
165
+ render, a popup control propagating its choice up to the picker holding it, a
166
+ group cascading a value into its children — all of them reach `uiAction` too, so
167
+ that a signal or a local variable listening to it never drifts out of sync.
168
+ Reading it as "the user did something" is the natural mistake.
169
+
170
+ The second argument says which one it was. Every event navi dispatches carries
171
+ the event that caused it, and `findEvent(event, type)` walks that chain back:
172
+
173
+ ```jsx
174
+ <Picker
175
+ clearable
176
+ uiAction={(value, event) => {
177
+ if (findEvent(event, "navi_clear_ui_state")) {
178
+ // the clear cross was pressed — for this row that means "back to the
179
+ // profile level", not "empty"
180
+ draft.level = profile.level;
181
+ return;
182
+ }
183
+ draft.level = value;
184
+ }}
185
+ />
186
+ ```
187
+
188
+ Useful types to match on: `navi_clear_ui_state` (the clear cross, or a
189
+ `--navi-clear` command), `navi_reset_ui_state`, and the browser events at the
190
+ root of the chain (`click`, `keydown`, `input`) — a change no user gesture
191
+ caused has none of them.
192
+
161
193
  ## Reruns
162
194
 
163
195
  Actions do not stay stale on their own: a resource's `POST` reruns the
@@ -84,19 +84,27 @@ scrolling while a screen slides. Same word, other gesture.
84
84
 
85
85
  ## Who owns a gesture
86
86
 
87
- Three things can claim a pointer that landed on a travelling box, and all three
87
+ Five things can claim a pointer that landed on a travelling box, and all five
88
88
  are read before the box moves:
89
89
 
90
- 1. **What says so itself.** A field, a `contenteditable`, or anything carrying
91
- `data-no-drag-travel`.
90
+ 1. **What says so itself.** A field, a `contenteditable`, a dedicated drag
91
+ handle (`data-drag-handle`), or anything carrying `data-no-drag-travel`.
92
92
  2. **A scroller in between with room left that way.** It keeps the gesture until
93
93
  it has no room left, and only then hands the travel over — so a row that
94
94
  scrolls sideways inside a page still scrolls sideways.
95
95
  3. **Another travelling box in between.** The innermost one takes the axes it
96
96
  walks, and leaves the ones it does not to whoever is above it.
97
- 4. **A surface in the top layer in between.** Nothing above it gets the gesture
97
+ 4. **Something in between that is picked up and carried.** A row taken out of a
98
+ list, a card carried across a board: it takes the axes it is dragged on and
99
+ leaves the others — see [Something being carried inside a
100
+ box](#something-being-carried-inside-a-box).
101
+ 5. **A surface in the top layer in between.** Nothing above it gets the gesture
98
102
  at all — see [A surface in the top layer](#a-surface-in-the-top-layer).
99
103
 
104
+ And one thing narrows it from the other end: a popup that names a **grip** reads
105
+ the press only there — see [A popup pushed back the way it
106
+ came](#a-popup-pushed-back-the-way-it-came).
107
+
100
108
  ### Boxes inside boxes
101
109
 
102
110
  A row of slides inside a page that walks between pages, a carousel inside a
@@ -117,6 +125,39 @@ last slide does not hand the gesture over mid-drag: it leans on its wall, the wa
117
125
  it does when it is alone. Travelling the box around it means starting the gesture
118
126
  outside it.
119
127
 
128
+ ### Something being carried inside a box
129
+
130
+ A drag reads the same press a travel does and holds the pointer from it, so the
131
+ two share a finger exactly as two travelling boxes do: what is picked up says
132
+ which axes it walks (`data-drag-source`, written from `data-drag-axis` by
133
+ `interactions={{ move, reorder, land, toss }}` — see `docs/interactions.md`), and
134
+ the box above keeps what is left. A list reordered along its own line inside a
135
+ row of slides swiped sideways: both gestures live, and neither had to be told
136
+ about the other.
137
+
138
+ When the two want the same axes — a piece carried both ways inside a sheet
139
+ pushed down to close it — nothing is left and the press is the piece's, whole.
140
+ That is the right way round: the box above is a surface, and the thing in it is
141
+ what the hand came for.
142
+
143
+ The exception is a **dedicated handle** (`data-drag-handle`), which has no axis:
144
+ it is a place whose only purpose is to be taken hold of, from the first pixel,
145
+ so it takes the press outright.
146
+
147
+ ### A popup pushed back the way it came
148
+
149
+ A `Dialog` docked to the bottom edge (`dockedOnSmallTouchScreen`) and a
150
+ `SidePanel` close by being pushed back the way they came in — a third consumer
151
+ of this same travel, `swipe_to_close.js`. A popup that names a **grip** reads the
152
+ press only there: for a `Dialog` that is its header, plus anything carrying
153
+ `data-swipe-grip`. Everything else it holds is content the finger came to operate
154
+ — a board a piece is dragged across, a map, a list — and a press there never
155
+ reaches the travel at all, whatever it is made of.
156
+
157
+ So a sheet with no header and nothing marked is not pushed down; it closes by
158
+ its own controls, the backdrop and Escape. A `SidePanel` names no grip and is
159
+ pushed from its whole surface, which suits a panel made of nothing else.
160
+
120
161
  ### A surface in the top layer
121
162
 
122
163
  A popover, a modal `<dialog>`, an element gone fullscreen: it is written inside
@@ -298,6 +339,12 @@ Two things a travel in hand must never lose:
298
339
  - **the hold belongs to a travel, not to the page.** Only the travel that took
299
340
  it may give it back — and it must give it back even when it ends after
300
341
  something else has replaced it, or the hold survives its owner;
342
+ - **a capture that goes while the pointer is still down was taken, not given.**
343
+ The ends a gesture has are the pointer going up and the pointer being
344
+ cancelled; a `lostpointercapture` before either means someone else asked for
345
+ the pointer (or the element it was held on left the document). What was being
346
+ carried goes back rather than landing wherever the hand happened to be, and a
347
+ travel comes home rather than committing;
301
348
  - **a gesture must hear its own end wherever it is delivered.** A pointer can be
302
349
  cancelled somewhere the box is not on the path (the document root, during a
303
350
  transition): missed, the gesture never ends, and whatever it was holding stays
@@ -370,10 +417,17 @@ hold:
370
417
  touch keeps being dispatched at the node it started on, and a travel may
371
418
  replace the DOM under the finger (a page that travels navigates), after which
372
419
  that node no longer passes through the box on its way up;
373
- - the pointer is captured **before** the caller is told the gesture started, and
374
- on the BOX rather than on what the finger landed on, for the same reason: what
375
- the caller does may take that target away, and a capture whose element leaves
376
- the document is a capture the browser drops.
420
+ - the pointer is captured on the BOX rather than on what the finger landed on,
421
+ for the same reason: what the caller does may take that target away, and a
422
+ capture whose element leaves the document is a capture the browser drops;
423
+ - and it is captured only once the travel has been ACCEPTED — not when the press
424
+ crossed its threshold. There is one capture per pointer for the whole document,
425
+ so taking it is taking it from whoever had it, who is then told the very thing
426
+ it is told when its own gesture ends. A travel that gives itself up one event
427
+ later (an axis this box does not walk, an `onStart` that refuses) would have
428
+ killed a gesture already carrying something. Until the capture is claimed the
429
+ moves are read from the window, filtered by pointer id, so nothing is missed
430
+ for not owning the pointer.
377
431
 
378
432
  And the refusal has to be _listened for_ from the grab, even though it only
379
433
  refuses later: whether a touchmove can be refused at all is decided when the
@@ -398,7 +452,12 @@ finger. The component knows, so the component says it.
398
452
  _Currently marked: the wheel viewport, the table resize handles, the cells of a
399
453
  table whose columns can be dragged._
400
454
 
401
- ## The two consumers
455
+ ## The two consumers that travel between screens
456
+
457
+ A popup being pushed back towards its edge is a travel too, and it is the simple
458
+ one: one box, one direction, no neighbour to bring in (see [A popup pushed back
459
+ the way it came](#a-popup-pushed-back-the-way-it-came)). The two below carry
460
+ screens, and everything the rest of this file weighs is about them.
402
461
 
403
462
  | | `SlideContainer` | `RouteTravel` |
404
463
  | ----------------------- | ---------------------------------- | ---------------------------------- |
@@ -623,6 +623,14 @@ container above it does not take the gesture:
623
623
 
624
624
  ## Things worth knowing before guessing
625
625
 
626
+ - **A drag says its axes to whoever else answers the press.** `data-drag-axis`
627
+ is written into the DOM as `data-drag-source`, and a box above that travels
628
+ under the same finger reads it before answering: a list reordered vertically
629
+ inside a row of slides swiped sideways leaves the sideways gesture alone, and a
630
+ piece carried both ways inside a bottom sheet takes the press whole. Nothing to
631
+ wire — see `docs/drag_to_travel.md`. A `Dialog` docked to the bottom edge goes
632
+ further and reads the press only on its header (plus anything carrying
633
+ `data-swipe-grip`), so its body is free whatever is in it.
626
634
  - **A hold does not take the context menu.** Declaring `longpress` says what a
627
635
  held finger does; a right click comes from the other button and keeps opening
628
636
  the browser's menu. Declare `contextmenu` beside it to make the right click do
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/navi",
3
- "version": "0.29.60",
3
+ "version": "0.29.62",
4
4
  "type": "module",
5
5
  "description": "Library of components including navigation to create frontend applications",
6
6
  "repository": {
@@ -29,7 +29,7 @@
29
29
  "prepublishOnly": "npm run build"
30
30
  },
31
31
  "dependencies": {
32
- "@jsenv/dom": "0.17.19",
32
+ "@jsenv/dom": "0.17.20",
33
33
  "@jsenv/humanize": "1.7.8",
34
34
  "@jsenv/validity": "0.4.2"
35
35
  },