@jsenv/navi 0.29.78 → 0.29.80

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.
@@ -110,6 +110,14 @@ consistency across the app, not from any single call site.
110
110
  `smallTouchScreenSignal` in an app to change a dialog's size, before passing
111
111
  `expandX={false}` to stop a dialog sprawling, and before writing CSS to make a
112
112
  dialog fit the screen.
113
+ - `docs/autofocus.md` — who gets the keyboard when a popup opens or a slide
114
+ arrives: the ladder navi walks, what `autoFocus` means on a surface (`true`
115
+ = the surface takes it, for a popup that is READ before it is filled) versus
116
+ on a field, why a docked dialog keeps the keyboard down on a phone, and what
117
+ happens when the opening finds nothing to focus. Read it before wondering
118
+ where the focus went when a popup opened, and before removing `autoFocus`
119
+ from fields to stop a virtual keyboard rising — the surface is what decides,
120
+ not the absence of a field asking.
113
121
  - `docs/scroll.md` — where scrolling happens: what turns `Box`
114
122
  `header`/`body`/`footer` on, `FixedBar` space, `List`'s `scroller`, scroll
115
123
  inside a `Dialog`/`Popover`, and what a scroll does to hover
@@ -0,0 +1,115 @@
1
+ # Where the keyboard goes when something opens
2
+
3
+ A dialog, a popover, a slide arriving: one of them opens and something inside
4
+ has to hold the keyboard. `autoFocus` is how each element takes part in that
5
+ decision.
6
+
7
+ - [What we want](#what-we-want)
8
+ - [The ladder](#the-ladder)
9
+ - [A popup that is read before it is filled](#a-popup-that-is-read-before-it-is-filled)
10
+ - [The most precise wins](#the-most-precise-wins)
11
+ - [Docked on a small touch screen: fields are withdrawn](#docked-on-a-small-touch-screen-fields-are-withdrawn)
12
+ - [What a field says about itself](#what-a-field-says-about-itself)
13
+ - [When the opening places nothing](#when-the-opening-places-nothing)
14
+
15
+ ## What we want
16
+
17
+ The focus is where the user is. So an opening has to answer one question — what
18
+ did the user come here to do? — and the answer is rarely "type": a picker opens
19
+ on its search box, but a popup that explains something opens on the explanation.
20
+
21
+ On a phone the difference is not a nuance. Focusing a field raises the virtual
22
+ keyboard, the keyboard takes a third of the height, and the popup scrolls the
23
+ focused field into what is left. Everything above it — the title, the sentence
24
+ saying why the field is asked for — is already past the top edge when the user
25
+ first looks at the popup. Nobody scrolls back up to read what they were never
26
+ shown, so a popup that opens on its field is a popup whose text does not exist.
27
+
28
+ Hence the rule: **the surface is read, then touched.** The keyboard rises when
29
+ the user asks for it, or when a field says it is what the user came for.
30
+
31
+ ## The ladder
32
+
33
+ Whoever hands out the focus — a popup opening, a slide arriving — tries these
34
+ in order, and stops at the first that leads somewhere focusable:
35
+
36
+ 1. the element that held the focus when this container was last closed;
37
+ 2. the first `autoFocus` — "put it here";
38
+ 3. the first focusable element — what one came to do;
39
+ 4. the deepest `autoFocus="last-resort"`, the container itself included;
40
+ 5. nothing, and the caller decides what that means.
41
+
42
+ Step 1 is why reopening a popup comes back to where the user was, rather than to
43
+ what the content asks for on a fresh open.
44
+
45
+ ## A popup that is read before it is filled
46
+
47
+ `autoFocus` (the plain boolean `true`) on the `Dialog`/`Popover` itself:
48
+
49
+ ```jsx
50
+ <Dialog autoFocus>
51
+ <Heading>Almost there</Heading>
52
+ <Text>We need a first name so the others know who joined.</Text>
53
+ <Input name="first_name" />
54
+ </Dialog>
55
+ ```
56
+
57
+ The focus lands on the surface, which is focusable for exactly this
58
+ (`tabIndex={-1}`). No keyboard rises, nothing is scrolled, and Tab starts from
59
+ the beginning of the reading order — the user reads, then reaches the field by
60
+ the route the content lays out.
61
+
62
+ This is the value to reach for whenever the popup's first job is to say
63
+ something. It is not the same as `"last-resort"`, which is the default and means
64
+ the opposite: "anything in here before me".
65
+
66
+ ## The most precise wins
67
+
68
+ `autoFocus` on a field beats `autoFocus` on the surface around it (step 2 above
69
+ comes before step 4). The two can be stated together without a conflict to
70
+ resolve: the surface says where the focus goes by default, a field that really
71
+ is what the user came for says so itself.
72
+
73
+ ```jsx
74
+ <Dialog autoFocus>
75
+ <Text>Search the catalog</Text>
76
+ <Input name="query" autoFocus /> {/* … except here */}
77
+ </Dialog>
78
+ ```
79
+
80
+ ## Docked on a small touch screen: fields are withdrawn
81
+
82
+ A `Dialog` with `dockedOnSmallTouchScreen` becomes a bottom sheet on a phone —
83
+ the one shape the keyboard hurts most, since the sheet starts at the very edge
84
+ the keyboard covers. There, step 3 of the ladder does not consider fields at
85
+ all: rather than the first text input it finds, the opening falls through to
86
+ what remains, usually the surface itself.
87
+
88
+ Nothing to pass, and nothing to remember per call site. A field that wants the
89
+ keyboard on a phone still says so with its own `autoFocus`, which is where that
90
+ decision belongs.
91
+
92
+ ## What a field says about itself
93
+
94
+ - `autoFocus` — "I am what the user came for". A picker's search box on a
95
+ desktop, the one field of a one-field popup.
96
+ - `autoFocus="restore"` — "never on a fresh open, but bring me back". A field
97
+ the user was typing in when a popup over it closed: reopening returns to it,
98
+ opening for the first time does not raise a keyboard on it.
99
+ - `autoFocus="last-resort"` — "anything else in here before me". Said by a poor
100
+ place to arrive that is still better than nowhere: a close button, a chevron.
101
+ A container says it about its own contents, which is the default for
102
+ `Dialog`/`Popover`.
103
+
104
+ ## When the opening places nothing
105
+
106
+ A popup can open on content that holds nothing focusable yet — content still
107
+ being built, a screen not yet interactive. The ladder then comes back empty and
108
+ the opening places no focus at all.
109
+
110
+ What arrives a moment later is allowed to take it: an opening that placed
111
+ nothing owes the focus to whatever appears next, and an `autoFocus` in that
112
+ content is honored rather than deferring to a transfer that never happened.
113
+ Without this, the same popup would land the focus in a different place — or
114
+ nowhere — depending on whether it was opened by a click or by the page loading,
115
+ which is the same popup behaving differently for no reason the user can see.
@@ -13,6 +13,9 @@ What opens a `Dialog` or a `Popover`, and who owns the fact that it is open.
13
13
  - [When `open` is the right answer, and what it costs](#when-open-is-the-right-answer-and-what-it-costs)
14
14
  - [What the popup holds while it is closed](#what-the-popup-holds-while-it-is-closed)
15
15
 
16
+ Where the focus goes once it is open is its own subject — see
17
+ [autofocus.md](./autofocus.md).
18
+
16
19
  ## The popup owns its open state
17
20
 
18
21
  A `Dialog`/`Popover` with no `open` prop keeps its own open state and listens
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/navi",
3
- "version": "0.29.78",
3
+ "version": "0.29.80",
4
4
  "type": "module",
5
5
  "description": "Library of components including navigation to create frontend applications",
6
6
  "repository": {