@react-aria/landmark 3.0.0-alpha.5 → 3.0.0-alpha.7
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/dist/import.mjs +408 -0
- package/dist/main.js +182 -60
- package/dist/main.js.map +1 -1
- package/dist/module.js +182 -61
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +12 -6
- package/src/index.ts +2 -2
- package/src/useLandmark.ts +265 -88
package/src/useLandmark.ts
CHANGED
|
@@ -13,81 +13,153 @@
|
|
|
13
13
|
import {AriaLabelingProps, DOMAttributes, FocusableElement} from '@react-types/shared';
|
|
14
14
|
import {MutableRefObject, useCallback, useEffect, useState} from 'react';
|
|
15
15
|
import {useLayoutEffect} from '@react-aria/utils';
|
|
16
|
+
import {useSyncExternalStore} from 'use-sync-external-store/shim/index.js';
|
|
16
17
|
|
|
17
18
|
export type AriaLandmarkRole = 'main' | 'region' | 'search' | 'navigation' | 'form' | 'banner' | 'contentinfo' | 'complementary';
|
|
18
19
|
|
|
19
20
|
export interface AriaLandmarkProps extends AriaLabelingProps {
|
|
20
|
-
role: AriaLandmarkRole
|
|
21
|
+
role: AriaLandmarkRole,
|
|
22
|
+
focus?: (direction: 'forward' | 'backward') => void
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
export interface LandmarkAria {
|
|
24
26
|
landmarkProps: DOMAttributes
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
// Increment this version number whenever the
|
|
30
|
+
// LandmarkManagerApi or Landmark interfaces change.
|
|
31
|
+
const LANDMARK_API_VERSION = 1;
|
|
32
|
+
|
|
33
|
+
// Minimal API for LandmarkManager that must continue to work between versions.
|
|
34
|
+
// Changes to this interface are considered breaking. New methods/properties are
|
|
35
|
+
// safe to add, but changes or removals are not allowed (same as public APIs).
|
|
36
|
+
interface LandmarkManagerApi {
|
|
37
|
+
version: number,
|
|
38
|
+
createLandmarkController(): LandmarkController,
|
|
39
|
+
registerLandmark(landmark: Landmark): () => void
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Changes to this interface are considered breaking.
|
|
43
|
+
// New properties MUST be optional so that registering a landmark
|
|
44
|
+
// from an older version of useLandmark against a newer version of
|
|
45
|
+
// LandmarkManager does not crash.
|
|
46
|
+
interface Landmark {
|
|
28
47
|
ref: MutableRefObject<Element>,
|
|
29
48
|
role: AriaLandmarkRole,
|
|
30
49
|
label?: string,
|
|
31
50
|
lastFocused?: FocusableElement,
|
|
32
|
-
focus: () => void,
|
|
51
|
+
focus: (direction: 'forward' | 'backward') => void,
|
|
33
52
|
blur: () => void
|
|
34
|
-
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface LandmarkControllerOptions {
|
|
56
|
+
/**
|
|
57
|
+
* The element from which to start navigating.
|
|
58
|
+
* @default document.activeElement
|
|
59
|
+
*/
|
|
60
|
+
from?: Element
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** A LandmarkController allows programmatic navigation of landmarks. */
|
|
64
|
+
export interface LandmarkController {
|
|
65
|
+
/** Moves focus to the next landmark. */
|
|
66
|
+
focusNext(opts?: LandmarkControllerOptions): boolean,
|
|
67
|
+
/** Moves focus to the previous landmark. */
|
|
68
|
+
focusPrevious(opts?: LandmarkControllerOptions): boolean,
|
|
69
|
+
/** Moves focus to the main landmark. */
|
|
70
|
+
focusMain(): boolean,
|
|
71
|
+
/** Moves focus either forward or backward in the landmark sequence. */
|
|
72
|
+
navigate(direction: 'forward' | 'backward', opts?: LandmarkControllerOptions): boolean,
|
|
73
|
+
/**
|
|
74
|
+
* Disposes the landmark controller. When no landmarks are registered, and no
|
|
75
|
+
* controllers are active, the landmark keyboard listeners are removed from the page.
|
|
76
|
+
*/
|
|
77
|
+
dispose(): void
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Symbol under which the singleton landmark manager instance is attached to the document.
|
|
81
|
+
const landmarkSymbol = Symbol.for('react-aria-landmark-manager');
|
|
82
|
+
|
|
83
|
+
function subscribe(fn: () => void) {
|
|
84
|
+
document.addEventListener('react-aria-landmark-manager-change', fn);
|
|
85
|
+
return () => document.removeEventListener('react-aria-landmark-manager-change', fn);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getLandmarkManager(): LandmarkManagerApi | null {
|
|
89
|
+
if (typeof document === 'undefined') {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Reuse an existing instance if it has the same or greater version.
|
|
94
|
+
let instance = document[landmarkSymbol];
|
|
95
|
+
if (instance && instance.version >= LANDMARK_API_VERSION) {
|
|
96
|
+
return instance;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Otherwise, create a new instance and dispatch an event so anything using the existing
|
|
100
|
+
// instance updates and re-registers their landmarks with the new one.
|
|
101
|
+
document[landmarkSymbol] = new LandmarkManager();
|
|
102
|
+
document.dispatchEvent(new CustomEvent('react-aria-landmark-manager-change'));
|
|
103
|
+
return document[landmarkSymbol];
|
|
104
|
+
}
|
|
35
105
|
|
|
36
|
-
|
|
106
|
+
// Subscribes a React component to the current landmark manager instance.
|
|
107
|
+
function useLandmarkManager(): LandmarkManagerApi | null {
|
|
108
|
+
return useSyncExternalStore(subscribe, getLandmarkManager, getLandmarkManager);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
class LandmarkManager implements LandmarkManagerApi {
|
|
37
112
|
private landmarks: Array<Landmark> = [];
|
|
38
|
-
private static instance: LandmarkManager;
|
|
39
113
|
private isListening = false;
|
|
114
|
+
private refCount = 0;
|
|
115
|
+
public version = LANDMARK_API_VERSION;
|
|
40
116
|
|
|
41
|
-
|
|
117
|
+
constructor() {
|
|
42
118
|
this.f6Handler = this.f6Handler.bind(this);
|
|
43
119
|
this.focusinHandler = this.focusinHandler.bind(this);
|
|
44
120
|
this.focusoutHandler = this.focusoutHandler.bind(this);
|
|
45
121
|
}
|
|
46
122
|
|
|
47
|
-
|
|
48
|
-
if (
|
|
49
|
-
|
|
123
|
+
private setupIfNeeded() {
|
|
124
|
+
if (this.isListening) {
|
|
125
|
+
return;
|
|
50
126
|
}
|
|
51
|
-
|
|
52
|
-
return LandmarkManager.instance;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
private setup() {
|
|
56
127
|
document.addEventListener('keydown', this.f6Handler, {capture: true});
|
|
57
128
|
document.addEventListener('focusin', this.focusinHandler, {capture: true});
|
|
58
129
|
document.addEventListener('focusout', this.focusoutHandler, {capture: true});
|
|
59
130
|
this.isListening = true;
|
|
60
131
|
}
|
|
61
132
|
|
|
62
|
-
private
|
|
133
|
+
private teardownIfNeeded() {
|
|
134
|
+
if (!this.isListening || this.landmarks.length > 0 || this.refCount > 0) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
63
137
|
document.removeEventListener('keydown', this.f6Handler, {capture: true});
|
|
64
138
|
document.removeEventListener('focusin', this.focusinHandler, {capture: true});
|
|
65
139
|
document.removeEventListener('focusout', this.focusoutHandler, {capture: true});
|
|
66
140
|
this.isListening = false;
|
|
67
141
|
}
|
|
68
142
|
|
|
69
|
-
private focusLandmark(landmark: Element) {
|
|
70
|
-
this.landmarks.find(l => l.ref.current === landmark)?.focus();
|
|
143
|
+
private focusLandmark(landmark: Element, direction: 'forward' | 'backward') {
|
|
144
|
+
this.landmarks.find(l => l.ref.current === landmark)?.focus(direction);
|
|
71
145
|
}
|
|
72
146
|
|
|
73
147
|
/**
|
|
74
148
|
* Return set of landmarks with a specific role.
|
|
75
149
|
*/
|
|
76
|
-
|
|
150
|
+
private getLandmarksByRole(role: AriaLandmarkRole) {
|
|
77
151
|
return new Set(this.landmarks.filter(l => l.role === role));
|
|
78
152
|
}
|
|
79
153
|
|
|
80
154
|
/**
|
|
81
155
|
* Return first landmark with a specific role.
|
|
82
156
|
*/
|
|
83
|
-
|
|
157
|
+
private getLandmarkByRole(role: AriaLandmarkRole) {
|
|
84
158
|
return this.landmarks.find(l => l.role === role);
|
|
85
159
|
}
|
|
86
160
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
this.setup();
|
|
90
|
-
}
|
|
161
|
+
private addLandmark(newLandmark: Landmark) {
|
|
162
|
+
this.setupIfNeeded();
|
|
91
163
|
if (this.landmarks.find(landmark => landmark.ref === newLandmark.ref)) {
|
|
92
164
|
return;
|
|
93
165
|
}
|
|
@@ -98,6 +170,7 @@ class LandmarkManager {
|
|
|
98
170
|
|
|
99
171
|
if (this.landmarks.length === 0) {
|
|
100
172
|
this.landmarks = [newLandmark];
|
|
173
|
+
this.checkLabels(newLandmark.role);
|
|
101
174
|
return;
|
|
102
175
|
}
|
|
103
176
|
|
|
@@ -119,9 +192,10 @@ class LandmarkManager {
|
|
|
119
192
|
}
|
|
120
193
|
|
|
121
194
|
this.landmarks.splice(start, 0, newLandmark);
|
|
195
|
+
this.checkLabels(newLandmark.role);
|
|
122
196
|
}
|
|
123
197
|
|
|
124
|
-
|
|
198
|
+
private updateLandmark(landmark: Pick<Landmark, 'ref'> & Partial<Landmark>) {
|
|
125
199
|
let index = this.landmarks.findIndex(l => l.ref === landmark.ref);
|
|
126
200
|
if (index >= 0) {
|
|
127
201
|
this.landmarks[index] = {...this.landmarks[index], ...landmark};
|
|
@@ -129,11 +203,9 @@ class LandmarkManager {
|
|
|
129
203
|
}
|
|
130
204
|
}
|
|
131
205
|
|
|
132
|
-
|
|
206
|
+
private removeLandmark(ref: MutableRefObject<Element>) {
|
|
133
207
|
this.landmarks = this.landmarks.filter(landmark => landmark.ref !== ref);
|
|
134
|
-
|
|
135
|
-
this.teardown();
|
|
136
|
-
}
|
|
208
|
+
this.teardownIfNeeded();
|
|
137
209
|
}
|
|
138
210
|
|
|
139
211
|
/**
|
|
@@ -172,7 +244,7 @@ class LandmarkManager {
|
|
|
172
244
|
private closestLandmark(element: Element) {
|
|
173
245
|
let landmarkMap = new Map(this.landmarks.map(l => [l.ref.current, l]));
|
|
174
246
|
let currentElement = element;
|
|
175
|
-
while (!landmarkMap.has(currentElement) && currentElement !== document.body) {
|
|
247
|
+
while (currentElement && !landmarkMap.has(currentElement) && currentElement !== document.body) {
|
|
176
248
|
currentElement = currentElement.parentElement;
|
|
177
249
|
}
|
|
178
250
|
return landmarkMap.get(currentElement);
|
|
@@ -184,22 +256,52 @@ class LandmarkManager {
|
|
|
184
256
|
* If not inside a landmark, will return first landmark.
|
|
185
257
|
* Returns undefined if there are no landmarks.
|
|
186
258
|
*/
|
|
187
|
-
|
|
188
|
-
if (this.landmarks.length === 0) {
|
|
189
|
-
return undefined;
|
|
190
|
-
}
|
|
191
|
-
|
|
259
|
+
private getNextLandmark(element: Element, {backward}: {backward?: boolean }) {
|
|
192
260
|
let currentLandmark = this.closestLandmark(element);
|
|
193
|
-
let nextLandmarkIndex = backward ? -1 : 0;
|
|
261
|
+
let nextLandmarkIndex = backward ? this.landmarks.length - 1 : 0;
|
|
194
262
|
if (currentLandmark) {
|
|
195
|
-
nextLandmarkIndex = this.landmarks.
|
|
263
|
+
nextLandmarkIndex = this.landmarks.indexOf(currentLandmark) + (backward ? -1 : 1);
|
|
196
264
|
}
|
|
197
265
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
266
|
+
let wrapIfNeeded = () => {
|
|
267
|
+
// When we reach the end of the landmark sequence, fire a custom event that can be listened for by applications.
|
|
268
|
+
// If this event is canceled, we return immediately. This can be used to implement landmark navigation across iframes.
|
|
269
|
+
if (nextLandmarkIndex < 0) {
|
|
270
|
+
if (!element.dispatchEvent(new CustomEvent('react-aria-landmark-navigation', {detail: {direction: 'backward'}, bubbles: true, cancelable: true}))) {
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
nextLandmarkIndex = this.landmarks.length - 1;
|
|
275
|
+
} else if (nextLandmarkIndex >= this.landmarks.length) {
|
|
276
|
+
if (!element.dispatchEvent(new CustomEvent('react-aria-landmark-navigation', {detail: {direction: 'forward'}, bubbles: true, cancelable: true}))) {
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
nextLandmarkIndex = 0;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (nextLandmarkIndex < 0 || nextLandmarkIndex >= this.landmarks.length) {
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return false;
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
if (wrapIfNeeded()) {
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Skip over hidden landmarks.
|
|
295
|
+
let i = nextLandmarkIndex;
|
|
296
|
+
while (this.landmarks[nextLandmarkIndex].ref.current.closest('[aria-hidden=true]')) {
|
|
297
|
+
nextLandmarkIndex += backward ? -1 : 1;
|
|
298
|
+
if (wrapIfNeeded()) {
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (nextLandmarkIndex === i) {
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
203
305
|
}
|
|
204
306
|
|
|
205
307
|
return this.landmarks[nextLandmarkIndex];
|
|
@@ -210,49 +312,59 @@ class LandmarkManager {
|
|
|
210
312
|
* If not, focus the landmark itself.
|
|
211
313
|
* If no landmarks at all, or none with focusable elements, don't move focus.
|
|
212
314
|
*/
|
|
213
|
-
|
|
315
|
+
private f6Handler(e: KeyboardEvent) {
|
|
214
316
|
if (e.key === 'F6') {
|
|
215
|
-
|
|
216
|
-
e.
|
|
317
|
+
// If alt key pressed, focus main landmark, otherwise navigate forward or backward based on shift key.
|
|
318
|
+
let handled = e.altKey ? this.focusMain() : this.navigate(e.target as Element, e.shiftKey);
|
|
319
|
+
if (handled) {
|
|
320
|
+
e.preventDefault();
|
|
321
|
+
e.stopPropagation();
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
217
325
|
|
|
218
|
-
|
|
219
|
-
|
|
326
|
+
private focusMain() {
|
|
327
|
+
let main = this.getLandmarkByRole('main');
|
|
328
|
+
if (main && document.contains(main.ref.current)) {
|
|
329
|
+
this.focusLandmark(main.ref.current, 'forward');
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
220
332
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
225
335
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
this.focusLandmark(main.ref.current);
|
|
231
|
-
}
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
336
|
+
private navigate(from: Element, backward: boolean) {
|
|
337
|
+
let nextLandmark = this.getNextLandmark(from, {
|
|
338
|
+
backward
|
|
339
|
+
});
|
|
234
340
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
if (document.body.contains(lastFocused)) {
|
|
239
|
-
lastFocused.focus();
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
341
|
+
if (!nextLandmark) {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
243
344
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
345
|
+
// If something was previously focused in the next landmark, then return focus to it
|
|
346
|
+
if (nextLandmark.lastFocused) {
|
|
347
|
+
let lastFocused = nextLandmark.lastFocused;
|
|
348
|
+
if (document.body.contains(lastFocused)) {
|
|
349
|
+
lastFocused.focus();
|
|
350
|
+
return true;
|
|
247
351
|
}
|
|
248
352
|
}
|
|
353
|
+
|
|
354
|
+
// Otherwise, focus the landmark itself
|
|
355
|
+
if (document.contains(nextLandmark.ref.current)) {
|
|
356
|
+
this.focusLandmark(nextLandmark.ref.current, backward ? 'backward' : 'forward');
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return false;
|
|
249
361
|
}
|
|
250
362
|
|
|
251
363
|
/**
|
|
252
364
|
* Sets lastFocused for a landmark, if focus is moved within that landmark.
|
|
253
365
|
* Lets the last focused landmark know it was blurred if something else is focused.
|
|
254
366
|
*/
|
|
255
|
-
|
|
367
|
+
private focusinHandler(e: FocusEvent) {
|
|
256
368
|
let currentLandmark = this.closestLandmark(e.target as Element);
|
|
257
369
|
if (currentLandmark && currentLandmark.ref.current !== e.target) {
|
|
258
370
|
this.updateLandmark({ref: currentLandmark.ref, lastFocused: e.target as FocusableElement});
|
|
@@ -269,7 +381,7 @@ class LandmarkManager {
|
|
|
269
381
|
/**
|
|
270
382
|
* Track if the focus is lost to the body. If it is, do cleanup on the landmark that last had focus.
|
|
271
383
|
*/
|
|
272
|
-
|
|
384
|
+
private focusoutHandler(e: FocusEvent) {
|
|
273
385
|
let previousFocusedElement = e.target as Element;
|
|
274
386
|
let nextFocusedElement = e.relatedTarget;
|
|
275
387
|
// the === document seems to be a jest thing for focus to go there on generic blur event such as landmark.blur();
|
|
@@ -281,6 +393,78 @@ class LandmarkManager {
|
|
|
281
393
|
}
|
|
282
394
|
}
|
|
283
395
|
}
|
|
396
|
+
|
|
397
|
+
public createLandmarkController(): LandmarkController {
|
|
398
|
+
let instance = this;
|
|
399
|
+
instance.refCount++;
|
|
400
|
+
instance.setupIfNeeded();
|
|
401
|
+
return {
|
|
402
|
+
navigate(direction, opts) {
|
|
403
|
+
return instance.navigate(opts?.from || document.activeElement, direction === 'backward');
|
|
404
|
+
},
|
|
405
|
+
focusNext(opts) {
|
|
406
|
+
return instance.navigate(opts?.from || document.activeElement, false);
|
|
407
|
+
},
|
|
408
|
+
focusPrevious(opts) {
|
|
409
|
+
return instance.navigate(opts?.from || document.activeElement, true);
|
|
410
|
+
},
|
|
411
|
+
focusMain() {
|
|
412
|
+
return instance.focusMain();
|
|
413
|
+
},
|
|
414
|
+
dispose() {
|
|
415
|
+
instance.refCount--;
|
|
416
|
+
instance.teardownIfNeeded();
|
|
417
|
+
instance = null;
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
public registerLandmark(landmark: Landmark): () => void {
|
|
423
|
+
if (this.landmarks.find(l => l.ref === landmark.ref)) {
|
|
424
|
+
this.updateLandmark(landmark);
|
|
425
|
+
} else {
|
|
426
|
+
this.addLandmark(landmark);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return () => this.removeLandmark(landmark.ref);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/** Creates a LandmarkController, which allows programmatic navigation of landmarks. */
|
|
434
|
+
export function createLandmarkController(): LandmarkController {
|
|
435
|
+
// Get the current landmark manager and create a controller using it.
|
|
436
|
+
let instance = getLandmarkManager();
|
|
437
|
+
let controller = instance.createLandmarkController();
|
|
438
|
+
|
|
439
|
+
let unsubscribe = subscribe(() => {
|
|
440
|
+
// If the landmark manager changes, dispose the old
|
|
441
|
+
// controller and create a new one.
|
|
442
|
+
controller.dispose();
|
|
443
|
+
instance = getLandmarkManager();
|
|
444
|
+
controller = instance.createLandmarkController();
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// Return a wrapper that proxies requests to the current controller instance.
|
|
448
|
+
return {
|
|
449
|
+
navigate(direction, opts) {
|
|
450
|
+
return controller.navigate(direction, opts);
|
|
451
|
+
},
|
|
452
|
+
focusNext(opts) {
|
|
453
|
+
return controller.focusNext(opts);
|
|
454
|
+
},
|
|
455
|
+
focusPrevious(opts) {
|
|
456
|
+
return controller.focusPrevious(opts);
|
|
457
|
+
},
|
|
458
|
+
focusMain() {
|
|
459
|
+
return controller.focusMain();
|
|
460
|
+
},
|
|
461
|
+
dispose() {
|
|
462
|
+
controller.dispose();
|
|
463
|
+
unsubscribe();
|
|
464
|
+
controller = null;
|
|
465
|
+
instance = null;
|
|
466
|
+
}
|
|
467
|
+
};
|
|
284
468
|
}
|
|
285
469
|
|
|
286
470
|
/**
|
|
@@ -292,13 +476,14 @@ export function useLandmark(props: AriaLandmarkProps, ref: MutableRefObject<Focu
|
|
|
292
476
|
const {
|
|
293
477
|
role,
|
|
294
478
|
'aria-label': ariaLabel,
|
|
295
|
-
'aria-labelledby': ariaLabelledby
|
|
479
|
+
'aria-labelledby': ariaLabelledby,
|
|
480
|
+
focus
|
|
296
481
|
} = props;
|
|
297
|
-
let manager =
|
|
482
|
+
let manager = useLandmarkManager();
|
|
298
483
|
let label = ariaLabel || ariaLabelledby;
|
|
299
484
|
let [isLandmarkFocused, setIsLandmarkFocused] = useState(false);
|
|
300
485
|
|
|
301
|
-
let
|
|
486
|
+
let defaultFocus = useCallback(() => {
|
|
302
487
|
setIsLandmarkFocused(true);
|
|
303
488
|
}, [setIsLandmarkFocused]);
|
|
304
489
|
|
|
@@ -307,18 +492,8 @@ export function useLandmark(props: AriaLandmarkProps, ref: MutableRefObject<Focu
|
|
|
307
492
|
}, [setIsLandmarkFocused]);
|
|
308
493
|
|
|
309
494
|
useLayoutEffect(() => {
|
|
310
|
-
manager.
|
|
311
|
-
|
|
312
|
-
return () => {
|
|
313
|
-
manager.removeLandmark(ref);
|
|
314
|
-
};
|
|
315
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
316
|
-
}, []);
|
|
317
|
-
|
|
318
|
-
useLayoutEffect(() => {
|
|
319
|
-
manager.updateLandmark({ref, label, role, focus, blur});
|
|
320
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
321
|
-
}, [label, ref, role]);
|
|
495
|
+
return manager.registerLandmark({ref, label, role, focus: focus || defaultFocus, blur});
|
|
496
|
+
}, [manager, label, ref, role, focus, defaultFocus, blur]);
|
|
322
497
|
|
|
323
498
|
useEffect(() => {
|
|
324
499
|
if (isLandmarkFocused) {
|
|
@@ -329,7 +504,9 @@ export function useLandmark(props: AriaLandmarkProps, ref: MutableRefObject<Focu
|
|
|
329
504
|
return {
|
|
330
505
|
landmarkProps: {
|
|
331
506
|
role,
|
|
332
|
-
tabIndex: isLandmarkFocused ? -1 : undefined
|
|
507
|
+
tabIndex: isLandmarkFocused ? -1 : undefined,
|
|
508
|
+
'aria-label': ariaLabel,
|
|
509
|
+
'aria-labelledby': ariaLabelledby
|
|
333
510
|
}
|
|
334
511
|
};
|
|
335
512
|
}
|