@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/autofocus.md
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
A dialog, a popover, a slide arriving: one of them opens and something inside
|
|
4
4
|
has to hold the keyboard. `autoFocus` is how each element takes part in that
|
|
5
5
|
decision. Handing the focus over answers a second question at the same time —
|
|
6
|
-
whether what receives it shows a focus ring — and navi answers that one too,
|
|
7
|
-
|
|
6
|
+
whether what receives it shows a focus ring — and navi answers that one too, at
|
|
7
|
+
the end of this page.
|
|
8
8
|
|
|
9
9
|
- [What we want](#what-we-want)
|
|
10
10
|
- [The ladder](#the-ladder)
|
|
@@ -21,8 +21,8 @@ at the end of this page.
|
|
|
21
21
|
## What we want
|
|
22
22
|
|
|
23
23
|
The focus is where the user is. So an opening has to answer one question — what
|
|
24
|
-
did the user come here to do? — and the answer is rarely "type": a
|
|
25
|
-
|
|
24
|
+
did the user come here to do? — and the answer is rarely "type": a popup that
|
|
25
|
+
explains something opens on the explanation.
|
|
26
26
|
|
|
27
27
|
On a phone the difference is not a nuance. Focusing a field raises the virtual
|
|
28
28
|
keyboard, the keyboard takes a third of the height, and the popup scrolls the
|
|
@@ -40,12 +40,14 @@ Whoever hands out the focus — a popup opening, a slide arriving — tries thes
|
|
|
40
40
|
in order, and stops at the first that leads somewhere focusable:
|
|
41
41
|
|
|
42
42
|
1. the element that held the focus when this container was last closed;
|
|
43
|
-
2. the first `autoFocus` — "put it here"
|
|
44
|
-
so a field naming itself wins over the surface around it;
|
|
43
|
+
2. the first `autoFocus` — "put it here", the container's own tried last;
|
|
45
44
|
3. the first focusable element — what one came to do;
|
|
46
|
-
4.
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
4. one `autoFocus="last-resort"`: the first found that holds no other, the
|
|
46
|
+
container's own only when nothing inside says it;
|
|
47
|
+
5. what held the focus before the opening (the trigger, for a press): the focus
|
|
48
|
+
stays outside, and
|
|
49
|
+
[when the opening places nothing](#when-the-opening-places-nothing) says what
|
|
50
|
+
follows.
|
|
49
51
|
|
|
50
52
|
Step 1 is why reopening a popup comes back to where the user was, rather than to
|
|
51
53
|
what the content asks for on a fresh open.
|
|
@@ -77,7 +79,7 @@ the opposite: "anything in here before me".
|
|
|
77
79
|
2 of the ladder, and the container's own mark is tried last there. The two can
|
|
78
80
|
be stated together without a conflict to resolve — the surface says where the
|
|
79
81
|
focus goes by default, a field that really is what the user came for says so
|
|
80
|
-
itself.
|
|
82
|
+
itself, on a touch device too.
|
|
81
83
|
|
|
82
84
|
```jsx
|
|
83
85
|
<Dialog autoFocus>
|
|
@@ -90,38 +92,32 @@ itself.
|
|
|
90
92
|
|
|
91
93
|
Where the keyboard is a virtual one — anything answering `pointer: coarse` — an
|
|
92
94
|
arrival drops step 3 of the ladder entirely: the focus goes where something
|
|
93
|
-
ASKED for it, and otherwise to the surface itself.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
95
|
+
ASKED for it, and otherwise to the surface itself. Step 4 still runs, though: a
|
|
96
|
+
`last-resort` inside is picked before a surface that is only `last-resort`
|
|
97
|
+
itself, the default — a chevron marked so takes the focus, and a field marked
|
|
98
|
+
so raises the keyboard.
|
|
99
|
+
|
|
100
|
+
Every arrival, not just a popup opening. A slide travelling into a
|
|
101
|
+
`SlideContainer` hands out the focus the same way and would lose the same thing
|
|
102
|
+
by landing on the first focusable — more of it, even, a screen having more above
|
|
103
|
+
the fold than a popup. Its surface is the `SlideContainer` box, which takes the
|
|
104
|
+
keyboard when the slide holds nothing that can, so the arrows keep working from
|
|
105
|
+
there.
|
|
101
106
|
|
|
102
107
|
The condition is the device, not the shape of what arrives and not the gesture
|
|
103
|
-
that brought it. A virtual keyboard
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
Withdrawing only the FIELDS would not be enough either. The first focusable is
|
|
111
|
-
wherever the content happens to put it — and in a popup that explains before it
|
|
112
|
-
asks, what comes first is the explanation, so the first focusable is far down:
|
|
113
|
-
the terms checkbox, the submit button. Landing there scrolls the popup to it and
|
|
114
|
-
the title is above the top edge again, keyboard or no keyboard. The cause
|
|
115
|
-
changes, the user sees the same thing.
|
|
116
|
-
|
|
117
|
-
Nothing to pass, and nothing to remember per call site.
|
|
108
|
+
that brought it. A virtual keyboard costs a third of the height whatever raised
|
|
109
|
+
it, and an arrival with no pointer in it at all — a popup opened by the page
|
|
110
|
+
loading, a travel asked for by code — is exactly the one that must not be
|
|
111
|
+
answered "no keyboard here". Withdrawing only the fields would not do either: in
|
|
112
|
+
a popup that explains before it asks, the first focusable is far down (the terms
|
|
113
|
+
checkbox, the submit button), and landing there scrolls the title away, keyboard
|
|
114
|
+
or no keyboard.
|
|
118
115
|
|
|
119
116
|
### Opting a field back in
|
|
120
117
|
|
|
121
|
-
Some popups — and some screens — really are opened to type in: one comment
|
|
122
|
-
one rename field.
|
|
123
|
-
|
|
124
|
-
ladder comes before step 3 was ever skipped.
|
|
118
|
+
Some popups — and some screens — really are opened to type in: one comment
|
|
119
|
+
box, one rename field. There, the field says so itself, and that beats the
|
|
120
|
+
device — step 2 of the ladder comes before step 3 was ever skipped.
|
|
125
121
|
|
|
126
122
|
```jsx
|
|
127
123
|
<Dialog>
|
|
@@ -130,18 +126,15 @@ ladder comes before step 3 was ever skipped.
|
|
|
130
126
|
</Dialog>
|
|
131
127
|
```
|
|
132
128
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
saying which is which is the caller's to make because nothing about the markup
|
|
138
|
-
can tell them apart.
|
|
129
|
+
A popup holding one field is not necessarily a popup opened to fill it — it is
|
|
130
|
+
often opened to READ what the field holds, and raising the keyboard over it then
|
|
131
|
+
costs the reading for nothing. Nothing about the markup tells the two apart, so
|
|
132
|
+
saying which is the caller's.
|
|
139
133
|
|
|
140
134
|
## What a field says about itself
|
|
141
135
|
|
|
142
|
-
- `autoFocus` — "I am what the user came for"
|
|
143
|
-
|
|
144
|
-
field opts back into a keyboard the surface would otherwise keep down.
|
|
136
|
+
- `autoFocus` — "I am what the user came for": the field of a popup opened to
|
|
137
|
+
type in it.
|
|
145
138
|
- `autoFocus="restore"` — "never on a fresh open, but bring me back". A field
|
|
146
139
|
the user was typing in when a popup over it closed: reopening returns to it,
|
|
147
140
|
opening for the first time does not raise a keyboard on it.
|
|
@@ -153,10 +146,8 @@ can tell them apart.
|
|
|
153
146
|
## When the opening places nothing
|
|
154
147
|
|
|
155
148
|
A popup can open on content that holds nothing focusable yet — content still
|
|
156
|
-
being built, a screen not yet interactive. The ladder then
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
That debt is settled two ways, whichever comes first:
|
|
149
|
+
being built, a screen not yet interactive. The ladder then places nothing inside
|
|
150
|
+
it, and that debt is settled two ways, whichever comes first:
|
|
160
151
|
|
|
161
152
|
- what arrives a moment later takes it — an `autoFocus` in content built during
|
|
162
153
|
the opening is honored, rather than deferring to a transfer that never
|
|
@@ -165,9 +156,8 @@ That debt is settled two ways, whichever comes first:
|
|
|
165
156
|
before the browser paints and long before the user can do anything. A surface
|
|
166
157
|
that says `autoFocus` about itself is placed by that second try.
|
|
167
158
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
which is the same popup behaving differently for no reason the user can see.
|
|
159
|
+
So the same popup places the focus the same way whether a click or the page
|
|
160
|
+
loading opened it.
|
|
171
161
|
|
|
172
162
|
## The ring is decided too
|
|
173
163
|
|
|
@@ -180,25 +170,25 @@ answers instead, and the answer is the **modality of what asked**:
|
|
|
180
170
|
arrow key: a ring;
|
|
181
171
|
- a finger or a mouse asked: no ring, whatever the code did in between;
|
|
182
172
|
- the element receiving it is **editable** — a text input, a textarea, a
|
|
183
|
-
contenteditable: a ring anyway. Someone about to type has to see where
|
|
173
|
+
contenteditable: a ring anyway. Someone about to type has to see where, so
|
|
174
|
+
opening a search field with a mouse click still rings.
|
|
184
175
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
Nothing to pass, and nothing to opt into: it is how every navi component moves
|
|
189
|
-
the focus.
|
|
176
|
+
The ladder answers it this way, and so does `moveFocusTo`
|
|
177
|
+
([below](#moving-the-focus-yourself)).
|
|
190
178
|
|
|
191
179
|
### In your CSS
|
|
192
180
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
181
|
+
Navi's "focused" is wider than the browser's: an element counts as focused while
|
|
182
|
+
an element **controlling** it holds the focus (a listbox, while the combobox
|
|
183
|
+
input with `aria-controls` on it does), and a **proxy** — the visible stand-in
|
|
184
|
+
for a hidden real control — while its real control does. That meaning lives in
|
|
185
|
+
`[data-focus-visible]`, the attribute navi writes on an element tracking the
|
|
186
|
+
pseudo-class: navi's own controls, and a `Box` of yours once its `style` holds a
|
|
187
|
+
`":focus-visible"` key. A native `:focus-visible` rule only gets the browser's
|
|
188
|
+
behaviour.
|
|
199
189
|
|
|
200
190
|
```css
|
|
201
|
-
.my_row
|
|
191
|
+
.my_row[data-focus-visible] {
|
|
202
192
|
outline: 2px solid var(--navi-focus-outline-color);
|
|
203
193
|
}
|
|
204
194
|
```
|
|
@@ -217,18 +207,21 @@ when something else took the focus meanwhile — the page arriving, focusing a
|
|
|
217
207
|
field of its own — that one keeps it.
|
|
218
208
|
|
|
219
209
|
So `{editing && <Dialog open>…</Dialog>}` unmounted from a button inside it
|
|
220
|
-
lands the focus back on the button that opened it, and a dialog left along
|
|
221
|
-
|
|
222
|
-
|
|
210
|
+
lands the focus back on the button that opened it, and a dialog left along with
|
|
211
|
+
the page around it lands it nowhere (`<body>`), exactly as if the page had
|
|
212
|
+
simply been removed.
|
|
223
213
|
|
|
224
214
|
## Moving the focus yourself
|
|
225
215
|
|
|
226
216
|
Prefer saying where the focus belongs over moving it: `autoFocus` on the field
|
|
227
217
|
or the surface (the ladder above), and navi places it at the right moment, with
|
|
228
|
-
the right ring, without scrolling
|
|
218
|
+
the right ring, without scrolling it into view.
|
|
229
219
|
|
|
230
220
|
When you really do have to move it — a control of your own handing over to
|
|
231
|
-
another — use `moveFocusTo` rather than `element.focus()
|
|
221
|
+
another — use `moveFocusTo` rather than `element.focus()`. A bare `.focus()`
|
|
222
|
+
answers "who" and leaves "is this visible" to the browser, which reads the last
|
|
223
|
+
thing that touched the DOM — your own call. `moveFocusTo` applies both decisions
|
|
224
|
+
above, no scroll-into-view and the ring of the modality:
|
|
232
225
|
|
|
233
226
|
```js
|
|
234
227
|
import { moveFocusTo } from "@jsenv/navi";
|
|
@@ -236,15 +229,5 @@ import { moveFocusTo } from "@jsenv/navi";
|
|
|
236
229
|
moveFocusTo(searchInputElement);
|
|
237
230
|
```
|
|
238
231
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
editable target ringing anyway). A bare `element.focus()` answers "who" and
|
|
242
|
-
leaves "is this visible" to the browser, and the browser reads the last thing
|
|
243
|
-
that touched the DOM — which was your own call.
|
|
244
|
-
|
|
245
|
-
Pass `focusVisible` when you know better than the last interaction does, and
|
|
246
|
-
`preventScroll: false` when the element really has to be brought into view:
|
|
247
|
-
|
|
248
|
-
```js
|
|
249
|
-
moveFocusTo(element, { focusVisible: true });
|
|
250
|
-
```
|
|
232
|
+
`focusVisible` is for when you know better than the last interaction does, and
|
|
233
|
+
`preventScroll: false` for an element that really has to be brought into view.
|