-
Notifications
You must be signed in to change notification settings - Fork 669
/
Overview.bs
1767 lines (1477 loc) · 78 KB
/
Overview.bs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<pre class='metadata'>
Title: CSS Spatial Navigation Level 1
Shortname: css-nav
Level: 1
Status: ED
Work Status: exploring
Group: csswg
TR: https://www.w3.org/TR/css-nav-1/
ED: https://drafts.csswg.org/css-nav-1/
Previous Version: https://www.w3.org/TR/2019/WD-css-nav-1-20190423/
Previous Version: https://www.w3.org/TR/2019/WD-css-nav-1-20191126/
Editor: Jihye Hong, LG Electronics, jh.hong@lge.com, w3cid 79168
Editor: Florian Rivoal, Invited Expert, https://florian.rivoal.net, w3cid 43241
Abstract: This specification defines a general model for navigating the focus using the arrow keys,
as well as related CSS, JavaScript features and Events.
At risk: {{getSpatialNavigationContainer()}}
At risk: {{focusableAreas()}}
At risk: 'spatial-navigation-contain'
At risk: ''spatial-navigation-action: scroll''
At risk: 'spatial-navigation-function'
</pre>
<pre class="anchors">
spec: ui-events; urlPrefix: https://w3c.github.io/uievents/;
type: event;
text: keydown
text: click
type: dfn;
text: event target
spec: html; urlPrefix: https://html.spec.whatwg.org/multipage/;
urlPrefix: interaction.html
type: dfn;
text: control group
text: currently focused area of a top-level browsing context
text: DOM anchor
text: expressly inert
text: focusable area
text: sequential focus navigation order
text: sequential focus navigation starting point
text: sequential navigation search algorithm
text: tabindex; url: #attr-tabindex
urlPrefix: rendering.html
type: dfn;
text: being rendered; url: #being-rendered
urlPrefix: semantics-other.html
type: dfn;
text: actually disabled; url: #concept-element-disabled
urlPrefix: browsers.html
type: dfn;
text: browsing context
urlPrefix: dom.html
type: dfn;
text: the body element; url: #the-body-element-2
urlPrefix: infrastructure.html
type: dfn;
text: removed; url: #nodes-are-removed
spec: dom; urlPrefix: https://dom.spec.whatwg.org/
type: dfn;
text: document element
spec: feature-policy; urlPrefix: https://w3c.github.io/webappsec-feature-policy/;
type: dfn;
text: is enabled; url: #is-feature-enabled
text: default allowlist; url: #default-allowlist
text: policy-controlled feature; url: #policy-controlled-feature
spec: overscroll-behavior; urlPrefix: https://drafts.csswg.org/css-overscroll-behavior-1/;
type: dfn;
text: scroll boundary
spec: css2; urlPrefix: https://drafts.csswg.org/css2/
urlPrefix: box.html
type: dfn;
text: border box; url: #x14
urlPrefix: zindex.html
type: dfn;
text: painting order; url: #painting-order
</pre>
<pre class=link-defaults>
spec:html; type:method; for:HTMLOrSVGElement; text:focus()
</pre>
<style>
code.key {
border: solid 1px;
border-radius: 0.5ch;
padding: 1px 5px;
}
.output {
background: white;
padding: 1em;
}
</style>
<style>
#api-check:checked ~ .api,
#api-check:checked ~ * .api {
display: none;
}
#cssapi-check:checked ~ .cssapi,
#cssapi-check:checked ~ * .cssapi {
display: none;
}
#verbose-check:checked ~ .verbose,
#verbose-check:checked ~ * .verbose {
display: none;
}
</style>
This specification is rather long.
To make it easier to read and focus on a particular area,
a few checkboxes are provided below.
Checking them hides part of the specification.
This is only meant as a reading aid,
the specification remains the full document.
<input type=checkbox id=api-check> <label for=api-check>Hide JavaScript APIs, including events</label><br>
<input type=checkbox id=cssapi-check> <label for=cssapi-check>Hide CSS properties that enable selecting behavior variants, and related information</label><br>
<input type=checkbox id=verbose-check> <label for=verbose-check>Hide informative sections that explain and summarize normative sections without adding more information</label><br>
<h2 id="intro" class=non-normative>
Introduction</h2>
<em>This section is not normative.</em>
Historically, most browsers have not offered features to let the user move the focus directionally.
Some, such as TV browsers, have enabled the user to move the focus using the arrow keys out of necessity,
since no other input mechanism is available on a typical TV remote control.
Others have enabled different key combinations to control spatial navigation,
such as pressing the <code class=key>Shift</code> key together with arrow keys.
This ability to move around the page directionally is called <dfn lt="spatial navigation | spatialNavigation" export>spatial navigation</dfn>.
<a>Spatial navigation</a> can be useful for a web page built using a grid-like layout,
or other predominantly non linear layouts.
The figure below represents a photo gallery arranged in a grid layout.
If the user presses the <code class=key>Tab</code> key to move focus around the images,
they need to press the key many times to reach the desired image element.
<figure>
<img alt="When elements are laid out in a grid pattern, spatial navigation makes it much easier to predict and control where focus should move to." src="images/gallery-app.png" style="width: 500px;">
<figcaption>Photo gallery application example using a grid layout</figcaption>
</figure>
Also, <a>spatial navigation</a> moves the focus to the predictable element for users
because it moves the focus among focusable elements depending on their position.
Sometimes elements on the page aren’t arranged independently of their source order.
Therefore unlike <a>spatial navigation</a>, sequential navigation using the <code class=key>Tab</code> key makes focus navigation unpredictable.
While arrow keys are naturally suited to control spatial navigation,
no previous specification describes how that should work,
or how it may be controlled.
<span class=api>This specification introduces a processing model for spatial navigation,
as well as APIs
enabling the author to control and override how spatial navigation works.</span>
Note: Some aspects of this specification, such as the JavaScript Events and APIs
could also be extended to sequential navigation,
in order to make sure that keyboard navigation has a consistent and well defined model in general.
Note: As a general principle,
keyboard navigation,
and spatial navigation in particular,
should be possible to use and control without JavaScript<span class=cssapi>,
and declarative solutions are therefore preferred.
Since spatial navigation depends on layout,
that means CSS is typically the right mechanism to define
spatial navigation related controls</span>.
<span class=api>However, in the spirit of the <a href="https://github.com/extensibleweb/manifesto">Extensible Web Manifesto</a> [[EXTENSIBLE]],
we feel it is important to provide the right JavaScript primitives
to let the author experiment and explore the problem space.
More declarative features may be added later,
based on feedback and experience acquired through such JavaScript usage.</span>
Note: A few features are marked <dfn noexport>at-risk</dfn>.
The editors of this specification believe
they represent an important part of the user or author experience
of the features defined in the specification.
At the same time, the core functionality of this specification
can be implemented without implementing these
so it seems possible that implementers may choose to down-prioritize them
to reduce the scope of the first implementation.
While it is hoped that these features will be implemented as well,
they are marked at-risk in recognition that they might not be at first.
<h2 id=interaction>
Module interaction</h2>
This document depends on the Infra Standard [[!infra]].
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" are to be interpreted as described in RFC 2119. [[!RFC2119]]
<div class=cssapi>
<h3 id="values">
CSS Property Value Definitions</h3>
This specification follows the <a href="https://www.w3.org/TR/CSS2/about.html#property-defs">CSS property definition conventions</a> from [[!CSS2]]
using the <a href="https://www.w3.org/TR/css-values-3/#value-defs">value definition syntax</a> from [[!CSS-VALUES-3]].
Value types not defined in this specification are defined in CSS Values & Units [[!CSS-VALUES-3]].
Combination with other CSS modules may expand the definitions of these value types.
In addition to the property-specific values listed in their definitions,
all properties defined in this specification
also accept the <a>CSS-wide keywords</a> as their property value.
For readability they have not been repeated explicitly.
</div>
<div class=verbose>
<h2 id=overview class=non-normative>
Overview</h2>
<em>This section is not normative.</em>
Using a UA-defined mechanism
(typically arrow keys, possibly in combination with modifier keys like <code class=key>Shift</code> or <code class=key>Control</code>),
the user may ask the user agent to navigate in a particular direction.
This will either
move the focus from its current location to a new focusable item in the direction requested,
or scroll if there is no appropriate item.
More specifically,
the user agent will first search for visible and focusable items
in the direction indicated
within the current <a>spatial navigation container</a>
(<span class=cssapi>by default, </span>the root element, scrollable elements, and iframes<span class=cssapi>,
but other elements can be made into <a>spatial navigation containers</a>
using the 'spatial-navigation-contain' property</span>).
If it finds any, it will pick the best one for that direction,
and move the focus there.
If it does not, it will scroll the <a>spatial navigation container</a> in the requested direction
instead of moving focus.
Doing so may uncover focusable elements
which would then be eligible targets to move the focus to
next time spatial navigation in the same direction is requested.
If the <a>spatial navigation container</a> cannot be scrolled,
either because it is not a scrollable element
or because it is already scrolled to the maximum in that direction,
the user agent will select the next <a>spatial navigation container</a> up the ancestry chain,
and recursively repeat the above process
until it finds some element to focus or scroll,
or reaches the root element.
Note: As a consequence of this processing model,
the elements that are reachable by sequential navigation
and by spatial navigation are almost the same.
Elements that are currently outside of the viewport of a scrollable element
can only be reached by spatial navigation once they have been scrolled into view.
Therefore, elements that cannot be scrolled into view by default.
<div class=api>
At key points during this search for the appropriate response to the spatial navigation request,
the user agent will fire events.
These enable the author to prevent the upcoming action
(by calling {{preventDefault()}}),
and if desired to provide an alternate action,
such as using the {{HTMLOrSVGElement/focus()}} method on a different
element of the author's choosing.
To help the author write such alternate actions,
and as part of exposing underlying platform primitives as per the <a href="https://github.com/extensibleweb/manifesto">Extensible Web</a> principles,
this specification also defines JavaScript APIs
that expose key constructs of the underlying model.
See [[#js-api]] for details about the JavaScript API,
[[#events-nav-type]] for details about the various events,
and [[#declarative]] for details about the CSS properties.
</div>
<div class='example'>
This example shows how a series of focusable elements
arranged in a scrollable element
would be navigated when using spatial navigation.
For the sake of keeping the description simple,
this example assumes a user agent where spatial navigation is triggered using arrow keys.
<figure>
<img alt="" src="images/spatnav-scroll-visible-1.png" style="width: 200px;">
<img alt="" src="images/spatnav-scroll-visible-2.png" style="width: 200px;">
<figcaption>Moving focus to the visible element in the <a>spatial navigation container</a>.</figcaption>
</figure>
On the left of figure 2, "Box 2" is focused.
Pressing the <code class=key>ArrowDown</code> key moves the focus to
"Box 3" without scrolling because "Box 3" is visible in the <a>scrollport</a> of the <a>spatial navigation container</a>.
<figure>
<img alt="" src="images/spatnav-scroll-invisible-1.png" style="width: 160px;">
<img alt="" src="images/spatnav-scroll-invisible-2.png" style="width: 160px;">
<img alt="" src="images/spatnav-scroll-invisible-3.png" style="width: 160px;">
<img alt="" src="images/spatnav-scroll-invisible-4.png" style="width: 160px;">
<figcaption>Moving focus to the hidden element in the <a>spatial navigation container</a>.</figcaption>
</figure>
On the first of figure 3, under "Box 3", there isn't any visible element in the <a>scrollport</a>.
Therefore, the effect of pressing the <code class=key>ArrowDown</code> is to scroll down, as shown in the second.
The next press of the <code class=key>ArrowDown</code> key makes "Box 4" come into the <a>scrollport</a>,
and the focus will move to it when there is additional pressing the <code class=key>ArrowDown</code>, as the fourth.
This example uses the markup as follows:
<pre class="lang-css">
#scroller {
width: 700px;
height: 700px;
overflow-x: hidden;
overflow-y: auto;
}
.box {
width: 150px;
height: 110px;
background-color: blue;
}
.box:focus {
background-color: red;
}
</pre>
<pre class="lang-html">
<div id="scroller">
<div class="box" tabindex="0">Box 1</div>
<div class="box" tabindex="0">Box 2</div>
<div class="box" tabindex="0">Box 3</div>
<div class="box" tabindex="0">Box 4</div>
</div>
</pre>
</div>
</div>
<h2 id=triggering>
Triggering Spatial Navigation</h2>
When the user triggers spatial navigation in a given direction,
the user agent must run the <a>spatial navigation steps</a> in that direction.
This specification does not define what UI mechanism user agents should offer to users to trigger spatial navigation.
This intentionally left for user agents to decide.
<div class=note>Note:
It is expected that user agents on devices with limited input capabilities,
such as TVs operated with a remote control,
feature phones,
or devices operated with a game controller,
will use spatial navigation as their primary or exclusive navigation mechanism.
</div>
Although user agents can implement the processing model and APIs
defined by the specification,
this specification recommends that
user agents should offer a means for users to trigger spatial navigation directly,
without having to use the APIs.
Note: Conversely, the author should assume that spatial navigation may be triggered
by the user agent in response to user actions
even if the author has not invoked any of the APIs.
Regardless of the actual mechanism chosen to trigger spatial navigation,
the following requirements apply:
* If the mechanism the user must use to trigger spatial navigation
would normally fire a {{UIEvent}},
the event must be fired prior to running the <a>spatial navigation steps</a>
and these steps must not be run if that event's <a>canceled flag</a>
gets set.
<div class=example>
Gaming devices may trigger spatial navigation based on pressing the D-pad.
This would result in firing a <a event>keydown</a> event
with the key set to one of
<code class=key>ArrowDown</code>,
<code class=key>ArrowLeft</code>,
<code class=key>ArrowRight</code>,
or <code class=key>ArrowUp</code>,
followed if not canceled by running the <a>spatial navigation steps</a><span class=api>,
including firing the relevant {{NavigationEvent}}s</span>.
A user agent on a desktop computer that triggers spatial navigation
using the arrow keys of the keyboard
would follow the same sequence.
</div>
* If the mechanism the user must use to trigger spatial navigation
would also perform other actions in some contexts,
the user agents should in these contexts
give priority to these other actions
and execute them instead of spatial navigation.
It must not trigger both.
<div class=example>
In a user agent that triggers spatial navigation
using the arrow keys without modifier keys,
and uses these same arrow keys to move
the text insertion caret when an editable element is focused,
the arrow keys should by default to moving the caret.
Spatial navigation would only be triggered by the arrow keys
when the focused element is not editable
or when it is editable, but the caret cannot move any further in the requested direction.
</div>
An exception is made for scrolling:
since spatial navigation itself handles scrolling
(in addition to moving the focus)
user agents should not offer the same mechanism to trigger both spatial navigation
and the scrolling behavior separate from spatial navigation.
However, user agents may offer a way for the user to switch between different modes,
or offer both based on different UI mechanisms.
<div class=example>
A user agent may have a setting to let the user choose
between using the arrow keys without modifier keys
for spatial navigation or for scrolling.
Another one may offer scrolling on arrow keys without modifiers,
and spatial navigation on arrow keys when pressed together
with the <code class=key>Shift</code> key,
or on the <code class=key>W</code> <code class=key>A</code> <code class=key>S</code> <code class=key>D</code> keys.
Offering only spatial navigation or only scrolling
as responses to pressing arrow keys would also be possibilities.
</div>
<div class=api>
<h2 id="js-api">
JavaScript API</h2>
<h3 id=high-level-api>
Triggering Navigation Programmatically</h3>
The {{Window/navigate()}} method enables the author to trigger spatial navigation programmatically,
as if the user had done so manually
(for instance, by pressing the arrow keys in a browser where that is the way to trigger spatial navigation).
Note: As this triggers the same processing model as manual navigation,
all the same results should be expected:
the same chain of events will be fired and
the same element will be scrolled or focused.
Note: The author can use this to trigger spatial navigation
based on a different UI mechanism than the one assigned by the user agent,
such as mapping to different keys,
or triggering spatial navigation from a clickable on-screen directional pad,
or in reaction to other events than UI ones.
It could also be used when an author wants to interrupt navigation to do some asynchronous operation
(e.g. load more content in an infinite scroller) then resume the navigation where they canceled.
Note: This API is also useful for testing purposes,
as there it is difficult to trigger spatial navigation
that does not depend on vendor specific UI conventions.
<pre class="idl">
enum SpatialNavigationDirection {
"up",
"down",
"left",
"right",
};
partial interface Window {
undefined navigate(SpatialNavigationDirection dir);
};
</pre>
<div algorithm="windowNavigate steps">
When the <dfn method for="Window" lt="navigate(dir)">navigate(dir)</dfn> method is called,
the user agent must run the following step:
* If direction <var>dir</var> is <code>"up"</code>, <code>"down"</code>, <code>"left"</code>, or <code>"right"</code>,
run the <a>spatial navigation steps</a> in <var>dir</var>.
Issue(3387): The name of this API is under discussion
</div>
<h3 id=low-level-api>
Low level APIs</h3>
These APIs are designed to be low level constructs following the processing model closely.
As such, they should be easy to use by the author who wants to extend or override the way spatial navigation works.
<pre class="idl">
enum FocusableAreaSearchMode {
"visible",
"all"
};
dictionary FocusableAreasOption {
FocusableAreaSearchMode mode;
};
dictionary SpatialNavigationSearchOptions {
sequence<Node>? candidates;
Node? container;
};
partial interface Element {
Node getSpatialNavigationContainer();
sequence<Node> focusableAreas(optional FocusableAreasOption option = {});
Node? spatialNavigationSearch(SpatialNavigationDirection dir, optional SpatialNavigationSearchOptions options = {});
};
</pre>
Note: The way the direction is expressed allows us to expand to more than 4-way navigation
later if this is found necessary.
More directional keywords or a numerical angle could be added.
Note: the {{focusableAreas()}} and {{getSpatialNavigationContainer()}} methods are <a>at-risk</a>.
When these methods are called,
the user agent must run the steps described below:
<div algorithm="getSpatialNavigationContainer steps">
: <dfn method for=Element lt="getSpatialNavigationContainer()">getSpatialNavigationContainer()</dfn>
::
1. Return the nearest ancestor of the element that is a <a>spatial navigation container</a>,
or the <a>document</a> if the nearest <a>spatial navigation container</a> is the viewport.
Note: If the element is a <a>spatial navigation container</a>, {{getSpatialNavigationContainer()}} also returns
the nearest <a>spatial navigation container</a>, not the element itself.
</div>
<div algorithm="focusableAreas steps">
: <dfn method for=Element lt="focusableAreas(option)">focusableAreas(<var>option</var>)</dfn>
::
1. Let <var>visibleOnly</var> be <code>false</code>
if <var>option</var> is present and its value is equal to <code>'all'</code>,
or <code>true</code> otherwise.
2. Let <var>areas</var> be the result of <a>finding focusable areas</a> within the element with <var>visibleOnly</var> as argument.
4. Return <var>areas</var>
</div>
<div class=example id=focusAreas-visible>
The following code shows how to get all the visible focusable elements in the current page using {{Element/focusableAreas()}}.
If the method finds a <a>spatial navigation container</a>, it recursively finds focusable areas inside it.
Because the value of the {{FocusableAreasOption/mode}} attribute in this method is <code>visible</code>,
the focusable elements which aren't inside the <a>scrollport</a> are excluded from the result.
<pre><code highlight=markup>
<body>
<button></button>
<div style="width:300px; height:200px; overflow-x: scroll;">
<button style="left:25px;"></button>
<button style="left:150px;"></button>
<button style="left:350px;"></button>
</div>
</body>
</code></pre>
<pre><code highlight=javascript>
const focusableAreas = document.body.focusableAreas({mode: 'visible'});
focusableAreas && focusableAreas.forEach(focusable => {
focusable.style.outline = '5px solid red';
});
</code></pre>
The figure below is the result of this code.
<figure>
<img alt="An image about focusableAreas()" src="images/focusableareas-visible-example.png" style="width: 450px;">
<figcaption>Find all visible focusable areas inside the document.</figcaption>
</figure>
</div>
<div algorithm="spatialNavigationSearch steps">
: <dfn method for=Element lt="spatialNavigationSearch(options)">spatialNavigationSearch(<var>dir</var>, <var>options</var>)</dfn>
::
1. Let <var>direction</var> be the value of <var>dir</var>.
2. Let <var>container</var> be
* if the value of {{SpatialNavigationSearchOptions/container}} attribute of <var>options</var> is not null,
* itself, if it is <a>spatial navigation container</a>.
* its nearest <a>spatial navigation container</a> ancestor, otherwise.
* else the element's nearest <a>spatial navigation container</a> ancestor.
3. Let <var>areas</var> be
* the value of {{SpatialNavigationSearchOptions/candidates}} attribute of <var>options</var> if it is not <code>null</code>,
* result of <a>finding focusable areas</a> within <var>container</var> otherwise.
4. Return the result of <a>selecting the best candidate</a> among <var>areas</var> within <var>container</var> in <var>direction</var> from the element.
Note: When neither a container nor a list of candidates is provided,
this only searches through the visible focusable areas of the nearest
<a>spatial navigation container</a> ancestor.
<em>If there isn't any, this does not climb further up the ancestry chain,
and the result will be <code>null</code>.</em>
</div>
</div>
<div class=api>
<h2 id="events-navigationevent">
Navigation Events</h2>
<h3 id="interface-focusevent">
Interface NavigationEvent</h3>
The {{NavigationEvent}} interface provides specific contextual information associated with spatial navigation.
To create an instance of the {{NavigationEvent}} interface, use the {{NavigationEvent}} constructor,
passing an optional {{NavigationEventInit}} dictionary.
<pre class=idl>
[Exposed=Window]
interface NavigationEvent : UIEvent {
constructor(DOMString type,
optional NavigationEventInit eventInitDict = {});
readonly attribute SpatialNavigationDirection dir;
readonly attribute EventTarget? relatedTarget;
};
dictionary NavigationEventInit : UIEventInit {
SpatialNavigationDirection dir;
EventTarget? relatedTarget = null;
};
</pre>
<div class=verbose>
<h3 id="events-nav-type" class="non-normative">
Navigation Event Types</h3>
<em>This section and its subsections are not normative.</em>
The Navigation event types are summarized below.
For full normative details, see [[#processing-model]].
<h4 id="event-type-navbeforefocus" class="non-normative">
<dfn event for=NavigationEvent>navbeforefocus</dfn></h4>
This event occurs when there is a valid result of <a>selecting the best candidate</a>.
<table class="def">
<tbody>
<tr>
<th>Type
<td><strong><code>navbeforefocus</code></strong>
<tr>
<th>Interface
<td>{{NavigationEvent}}
<tr>
<th>Bubbles
<td>Yes
<tr>
<th>Cancelable
<td>Yes
<tr>
<th>Attributes of the event
<td><dl>
<dt>{{Event}}.{{Event/target}}
<dd>The focused element or if no element focused,
then <a>the body element</a> if available, otherwise the root element
<dt>{{NavigationEvent}}.{{NavigationEvent/relatedTarget}}
<dd>The DOM anchor of the focusable area that will be focused
<dt>{{NavigationEvent}}.{{NavigationEvent/dir}}
<dd>The direction of the navigation as requested by the user
</dl>
</tbody>
</table>
The user agent <dfn lt="dispatches navbeforefocus event | dispatching navbeforefocus event">dispatches <a event>navbeforefocus</a> event</dfn>
before spatial navigation moves the focus.
The <a>event target</a> is the element which has focus and
the {{NavigationEvent/relatedTarget}} is the element which is about to receive focus.
If <a>navigation-override</a> is disabled in the [=node document=] of <var>eventTarget</var> for
the <a spec=html for="/">origin</a> of the [=active document=] of the [=top-level browsing context=], this event won't be dispatched.
<div class='example'>
This example shows the [=event order=] when pressing the <code class=key>ArrowRight</code>
key.
For the sake of keeping the description simple,
this example assumes a user agent where spatial navigation is triggered using arrow keys.
<table class="complex data">
<thead>
<tr>
<th>
<th>Event type
<th>{{KeyboardEvent}}.{{KeyboardEvent/key}}
<th>Notes
</thead>
<tbody>
<tr>
<td>1
<td>keydown
<td><code class=key>ArrowRight</code>
<td>MUST be a key which can activate spatial navigation,
such as the arrow keys, or spatial navigation is not activated.
<tr>
<td>2
<td>navbeforefocus
<td>
<td>Sent if the candidates for spatial navigation is not <code>null</code>,
or this is not generated.
<tr>
<td>3
<td>focusin
<td>
<td>Sent before the target element receives focus.
<tr>
<td>4
<td>focus
<td>
<td>Sent after the target element receives focus.
</tbody>
</table>
</div>
<div class=example id=delegation>
The following code changes the behavior of spatial navigation
so that when a scroll container would get focused,
if it has at least one visible focusable descendant,
the focus is automatically transferred to it,
recursively.
<pre><code highlight=javascript>
document.addEventListener('navbeforefocus', e => {
e.preventDefault();
let nextTarget = e.relatedTarget;
if (isSpatialNavigationContainer(nextTarget)) {
const areas = nextTarget.focusableAreas();
if (areas.length > 0) {
nextTarget = nextTarget.spatialNavigationSearch(e.dir, { candidates: areas });
}
}
nextTarget.focus();
});
function isSpatialNavigationContainer(element) {
return (!element.parentElement) ||
(element.nodeName === 'IFRAME') ||
(isScrollContainer(element)) ||
(isCSSSpatNavContain(element));
}
</code></pre>
</div>
<h4 id="event-type-navnotarget" class="non-normative">
<dfn event for=NavigationEvent>navnotarget</dfn></h4>
This event occurs before going up the tree to search candidates in the
nearest ancestor <a>spatial navigation container</a> when spatial navigation has failed to find any candidate
within the current <a>spatial navigation container</a>,
and in cases where the <a>spatial navigation container</a> is scrollable,
when it cannot be scrolled further.
<table class="def">
<tbody>
<tr>
<th>Type
<td><strong><code>navnotarget</code></strong>
<tr>
<th>Interface
<td>{{NavigationEvent}}
<tr>
<th>Bubbles
<td>Yes
<tr>
<th>Cancelable
<td>Yes
<tr>
<th>Attributes of the event
<td><dl>
<dt>{{Event}}.{{Event/target}}
<dd>The focused element or if no element focused,
then <a>the body element</a> if available, otherwise the root element
<dt>{{NavigationEvent}}.{{NavigationEvent/relatedTarget}}
<dd>The <a>spatial navigation container</a> that was searched in.
<dt>{{NavigationEvent}}.{{NavigationEvent/dir}}
<dd>The direction of the navigation as requested by the user
</dl>
</tbody>
</table>
The user agent <dfn lt="dispatches navnotarget event | dispatching navnotarget event">dispatches <a event>navnotarget</a> event</dfn>
with initializing the <a>event target</a> as the element which has focus and
the {{NavigationEvent/relatedTarget}} as <a>spatial navigation container</a> of the <a>event target</a>.
If <a>navigation-override</a> is disabled in the [=node document=] of <var>eventTarget</var> for
the <a spec=html for="/">origin</a> of the [=active document=] of the [=top-level browsing context=], this event won't be dispatched.
<div class='example'>
This example shows the [=event order=] when pressing the <code class=key>ArrowDown</code>
key in the situation like the following figure.
For the sake of keeping the description simple,
this example assumes a user agent where spatial navigation is triggered using arrow keys.
<figure>
<img alt="An image about navnotarget" src="images/navnotarget-example-1.png" style="width: 200px;">
<figcaption>Moving focus when there isn't any candidate in the
<a>scroll container</a>.</figcaption>
</figure>
<table class="complex data">
<thead>
<tr>
<th>
<th>Event type
<th>Event target
<th><code>relatedTarget</code>
<th>Notes
</thead>
<tbody>
<tr>
<td>1
<td>keydown
<td><code>#box2</code>
<td>N/A
<td>MUST be a key which can activate spatial navigation,
such as the arrow keys,
otherwise spatial navigation is not triggered.
<tr>
<td>2
<td>navnotarget
<td><code>#box2</code>
<td><code>#scrollContainer</code>
<td>Sent if <code>#scrollContainer</code> doesn't contain any candidate and
cannot be scrolled,
otherwise this would not be generated.
<tr>
<td>3
<td>navbeforefocus
<td><code>#box2</code>
<td><code>#box3</code>
<td>Sent if the candidates in <code>#container</code> is not <code>null</code>,
otherwise this would not be fired.
<tr>
<td>4
<td>focusin
<td><code>#box3</code>
<td><code>#box2</code>
<td>Sent before the target element receives focus.
<tr>
<td>5
<td>focus
<td><code>#box3</code>
<td><code>#box2</code>
<td>Sent after the target element receives focus.
</tbody>
</table>
The result of this example is the figure as follows:
<figure>
<img alt="An image of the result about navnotarget" src="images/navnotarget-example-2.png" style="width: 200px;">
<figcaption>The result of moving focus when there isn't any candidate in the <a>scrollport</a>
and <a>scroll container</a> cannot be scrolled.</figcaption>
</figure>
This example uses the markup as follows:
<pre class="lang-css">
#container {
width: 900px;
height: 1400px;
}
#scrollContainer {
width: 700px;
height: 700px;
overflow-x: hidden;
overflow-y: auto;
}
.item {
width: 150px;
height: 110px;
background-color: blue;
}
.item:focus {
background-color: red;
}
</pre>
<pre class="lang-html">
<div id="container">
<div id="scrollContainer">
<div id="box1" class="item" tabindex="0">Box 1</div>
<div id="box2" class="item" tabindex="0">Box 2</div>
</div>
<div id="box3" class="item" tabindex="0">Box 3</div>
</div>
</pre>
</div>
<div class=example id=loop>
The following code changes the behavior of spatial navigation
to trap the focus within a <a>spatial navigation container</a> which is vertically scrollable.
When no further focusable elements can be found in the requested direction
and the <a>spatial navigation container</a> cannot be scrolled any further,
the focus loops back to the other side instead of moving outside of it.
However, the focus can still be moved outside by sequential navigation,
mouse interaction,
or programmatic calls to {{focus()}}.
<pre><code highlight=javascript>
scrollContainer.addEventListener('navnotarget', e => {
let nextTarget = null;
const verticalDir = ['up', 'down'];
const candidates = e.relatedTarget.focusableAreas({'mode': 'all'});
// Prevent default only when navigation direction is on y-axis
if (verticalDir.includes(e.dir) && (candidates.length > 0)) {
e.preventDefault();
if (e.dir === 'down') {
nextTarget = candidates[0];
} else if (e.dir === 'up') {
nextTarget = candidates[candidates.length-1];
}
nextTarget.focus();
}
});
</code></pre>
</div>
</div>
</div>
<h2 id=policy-feature>
The <a>navigation-override</a> <a>policy-controlled feature</a></h2>
The <dfn>navigation-override</dfn> <a>policy-controlled feature</a> controls
the availability of mechanisms that enables the page author
to take control over the behavior of spatial navigation,
or to cancel it outright.
* The feature name is "<code>navigation-override</code>"
* The <a>default allowlist</a> for <a>navigation-override</a> is "<code>self</code>"
As defined in further details in [[#nav]],
if <a>navigation-override</a> is disabled in a document,
the navigation events (see [[#events-navigationevent]]) will not be fired.
Note: This is to prevent a hostile iframe from using these events
in order to hijack the focus.
We recognize that there exist other mechanisms predating spatial navigation
that malicious the author could use
to interfere with the user's ability to control where the focus goes.
Despite that, it seems worthwhile to attempt not to increase this attack surface,
although it is possible that such attacks are already sufficiently easy to perform
that this is a lost cause.
Further feedback on this topic,
based on experience with implementation or with mitigating such attacks,
is very welcome.
<h2 id=processing-model>
Processing Model</h2>
<div class=verbose>
The [[#overview]] section gives a high level idea of how spatial navigation works,
to help readers of this specification build a general mental model.
It uses intuitive but imprecise terminology,
and glosses over many details
for the sake of readability.
This section defines the corresponding normative behavior
and aims for as much detail as necessary
to fully define the behavior.
</div>
<h3 id=glossary>
Glossary</h3>
The following term definitions have been specified to explain the processing model for spatial navigation.
See the links within the definitions for more information.
The <dfn>boundary box</dfn> of an object is defined as follows:
* if the object is a point, the boundary box is that point
* if the object is a [=box=] or [=box fragment=], the boundary box is the <a>border box</a> of that box or fragment
* if the object is a <a>focusable area</a> which is not an element, the boundary box is the axis-aligned the bounding box of that <a>focusable area</a>
The <dfn>inside area</dfn> of an object is defined as follows:
* if the object is a <a>scroll container</a>, its <a>optimal viewing region</a>
* if the object is a <a>document</a>, the viewport of its <a>browsing context</a>
* if the object is a [=box=] or [=box fragment=], its <a>boundary box</a>
* otherwise, the <a>optimal viewing region</a> of its nearest ancestor <a>scroll container</a>
NOTE: If an object is offscreen, the <a>inside area</a> should be the nearest visible ancestor container.
Issue(w3c/csswg-drafts#2324): CSS should have a term for “border box taking into account corner shaping properties like border-radius”.
The <dfn>search origin</dfn> is the origin for searching next target.
The <dfn>spatial navigation starting point</dfn> is the origin for searching next target which is set by the user agent. It is initially unset and it can be element or point.
Note: For example, the user agent could set it to the position of the user's click if the user clicks on the document contents,
and unset when the focus is moved (by spatial navigation or any other means).
If the user agent sets both a <a>spatial navigation starting point</a> and a <a>sequential focus navigation starting point</a>,
they must not be set differently.
<h3 id=grouping>
Groupings of elements</h3>
While the processing model for spatial navigation
is to work from the layout of the document
and the relative position of focusable elements,
the user agent is required to prioritize finding elements
from a local logical grouping,
only looking for focusable elements outside of the grouping
if a suitable one cannot be found inside it (see [[#nav]] for details).