react-aria
Version:
Spectrum UI components in React
1 lines • 28.5 kB
Source Map (JSON)
{"mappings":"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","sources":["packages/react-aria/src/landmark/useLandmark.ts"],"sourcesContent":["/*\n * Copyright 2022 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {AriaLabelingProps, DOMAttributes, FocusableElement, RefObject} from '@react-types/shared';\nimport {getEventTarget, nodeContains} from '../utils/shadowdom/DOMFunctions';\nimport {useCallback, useEffect, useState} from 'react';\nimport {useLayoutEffect} from '../utils/useLayoutEffect';\nimport {useSyncExternalStore} from 'use-sync-external-store/shim/index.js';\n\nexport type AriaLandmarkRole =\n | 'main'\n | 'region'\n | 'search'\n | 'navigation'\n | 'form'\n | 'banner'\n | 'contentinfo'\n | 'complementary';\n\nexport interface AriaLandmarkProps extends AriaLabelingProps {\n role: AriaLandmarkRole;\n focus?: (direction: 'forward' | 'backward') => void;\n}\n\nexport interface LandmarkAria {\n landmarkProps: DOMAttributes;\n}\n\n// Increment this version number whenever the\n// LandmarkManagerApi or Landmark interfaces change.\nconst LANDMARK_API_VERSION = 1;\n\n// Minimal API for LandmarkManager that must continue to work between versions.\n// Changes to this interface are considered breaking. New methods/properties are\n// safe to add, but changes or removals are not allowed (same as public APIs).\ninterface LandmarkManagerApi {\n version: number;\n createLandmarkController(): LandmarkController;\n registerLandmark(landmark: Landmark): () => void;\n}\n\n// Changes to this interface are considered breaking.\n// New properties MUST be optional so that registering a landmark\n// from an older version of useLandmark against a newer version of\n// LandmarkManager does not crash.\ninterface Landmark {\n ref: RefObject<FocusableElement | null>;\n role: AriaLandmarkRole;\n label?: string;\n lastFocused?: FocusableElement;\n focus: (direction: 'forward' | 'backward') => void;\n blur: () => void;\n}\n\nexport interface LandmarkControllerOptions {\n /**\n * The element from which to start navigating.\n *\n * @default document.activeElement\n */\n from?: FocusableElement;\n}\n\n/** A LandmarkController allows programmatic navigation of landmarks. */\nexport interface LandmarkController {\n /** Moves focus to the next landmark. */\n focusNext(opts?: LandmarkControllerOptions): boolean;\n /** Moves focus to the previous landmark. */\n focusPrevious(opts?: LandmarkControllerOptions): boolean;\n /** Moves focus to the main landmark. */\n focusMain(): boolean;\n /** Moves focus either forward or backward in the landmark sequence. */\n navigate(direction: 'forward' | 'backward', opts?: LandmarkControllerOptions): boolean;\n /**\n * Disposes the landmark controller. When no landmarks are registered, and no\n * controllers are active, the landmark keyboard listeners are removed from the page.\n */\n dispose(): void;\n}\n\n// Symbol under which the singleton landmark manager instance is attached to the document.\nconst landmarkSymbol = Symbol.for('react-aria-landmark-manager');\n\nfunction subscribe(fn: () => void) {\n document.addEventListener('react-aria-landmark-manager-change', fn);\n return () => document.removeEventListener('react-aria-landmark-manager-change', fn);\n}\n\nfunction getLandmarkManager(): LandmarkManagerApi | null {\n if (typeof document === 'undefined') {\n return null;\n }\n\n // Reuse an existing instance if it has the same or greater version.\n let instance = document[landmarkSymbol];\n if (instance && instance.version >= LANDMARK_API_VERSION) {\n return instance;\n }\n\n // Otherwise, create a new instance and dispatch an event so anything using the existing\n // instance updates and re-registers their landmarks with the new one.\n document[landmarkSymbol] = new LandmarkManager();\n document.dispatchEvent(new CustomEvent('react-aria-landmark-manager-change'));\n return document[landmarkSymbol];\n}\n\n// Subscribes a React component to the current landmark manager instance.\nfunction useLandmarkManager(): LandmarkManagerApi | null {\n return useSyncExternalStore(subscribe, getLandmarkManager, getLandmarkManager);\n}\n\nclass LandmarkManager implements LandmarkManagerApi {\n private landmarks: Array<Landmark> = [];\n private isListening = false;\n private refCount = 0;\n public version = LANDMARK_API_VERSION;\n\n constructor() {\n this.f6Handler = this.f6Handler.bind(this);\n this.focusinHandler = this.focusinHandler.bind(this);\n this.focusoutHandler = this.focusoutHandler.bind(this);\n }\n\n private setupIfNeeded() {\n if (this.isListening) {\n return;\n }\n document.addEventListener('keydown', this.f6Handler, {capture: true});\n document.addEventListener('focusin', this.focusinHandler, {capture: true});\n document.addEventListener('focusout', this.focusoutHandler, {capture: true});\n this.isListening = true;\n }\n\n private teardownIfNeeded() {\n if (!this.isListening || this.landmarks.length > 0 || this.refCount > 0) {\n return;\n }\n document.removeEventListener('keydown', this.f6Handler, {capture: true});\n document.removeEventListener('focusin', this.focusinHandler, {capture: true});\n document.removeEventListener('focusout', this.focusoutHandler, {capture: true});\n this.isListening = false;\n }\n\n private focusLandmark(landmark: FocusableElement, direction: 'forward' | 'backward') {\n this.landmarks.find(l => l.ref.current === landmark)?.focus?.(direction);\n }\n\n /**\n * Return set of landmarks with a specific role.\n */\n private getLandmarksByRole(role: AriaLandmarkRole) {\n return new Set(this.landmarks.filter(l => l.role === role));\n }\n\n /**\n * Return first landmark with a specific role.\n */\n private getLandmarkByRole(role: AriaLandmarkRole) {\n return this.landmarks.find(l => l.role === role);\n }\n\n private addLandmark(newLandmark: Landmark) {\n this.setupIfNeeded();\n if (\n this.landmarks.find(landmark => landmark.ref === newLandmark.ref) ||\n !newLandmark.ref.current\n ) {\n return;\n }\n\n if (\n this.landmarks.filter(landmark => landmark.role === 'main').length > 1 &&\n process.env.NODE_ENV !== 'production'\n ) {\n console.error('Page can contain no more than one landmark with the role \"main\".');\n }\n\n if (this.landmarks.length === 0) {\n this.landmarks = [newLandmark];\n this.checkLabels(newLandmark.role);\n return;\n }\n\n // Binary search to insert new landmark based on position in document relative to existing landmarks.\n // https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition\n let start = 0;\n let end = this.landmarks.length - 1;\n while (start <= end) {\n let mid = Math.floor((start + end) / 2);\n let comparedPosition = newLandmark.ref.current.compareDocumentPosition(\n this.landmarks[mid].ref.current as Node\n );\n let isNewAfterExisting = Boolean(\n comparedPosition & Node.DOCUMENT_POSITION_PRECEDING ||\n comparedPosition & Node.DOCUMENT_POSITION_CONTAINS\n );\n\n if (isNewAfterExisting) {\n start = mid + 1;\n } else {\n end = mid - 1;\n }\n }\n\n this.landmarks.splice(start, 0, newLandmark);\n this.checkLabels(newLandmark.role);\n }\n\n private updateLandmark(landmark: Pick<Landmark, 'ref'> & Partial<Landmark>) {\n let index = this.landmarks.findIndex(l => l.ref === landmark.ref);\n if (index >= 0) {\n this.landmarks[index] = {...this.landmarks[index], ...landmark};\n this.checkLabels(this.landmarks[index].role);\n }\n }\n\n private removeLandmark(ref: RefObject<Element | null>) {\n this.landmarks = this.landmarks.filter(landmark => landmark.ref !== ref);\n this.teardownIfNeeded();\n }\n\n /**\n * Warn if there are 2+ landmarks with the same role but no label.\n * Labels for landmarks with the same role must also be unique.\n *\n * See https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/.\n */\n private checkLabels(role: AriaLandmarkRole) {\n let landmarksWithRole = this.getLandmarksByRole(role);\n if (landmarksWithRole.size > 1) {\n let duplicatesWithoutLabel = [...landmarksWithRole].filter(landmark => !landmark.label);\n if (duplicatesWithoutLabel.length > 0 && process.env.NODE_ENV !== 'production') {\n console.warn(\n `Page contains more than one landmark with the '${role}' role. If two or more landmarks on a page share the same role, all must be labeled with an aria-label or aria-labelledby attribute: `,\n duplicatesWithoutLabel.map(landmark => landmark.ref.current)\n );\n } else if (process.env.NODE_ENV !== 'production') {\n let labels = [...landmarksWithRole].map(landmark => landmark.label);\n let duplicateLabels = labels.filter((item, index) => labels.indexOf(item) !== index);\n\n duplicateLabels.forEach(label => {\n console.warn(\n `Page contains more than one landmark with the '${role}' role and '${label}' label. If two or more landmarks on a page share the same role, they must have unique labels: `,\n [...landmarksWithRole]\n .filter(landmark => landmark.label === label)\n .map(landmark => landmark.ref.current)\n );\n });\n }\n }\n }\n\n /**\n * Get the landmark that is the closest parent in the DOM.\n * Returns undefined if no parent is a landmark.\n */\n private closestLandmark(element: FocusableElement) {\n let landmarkMap = new Map(this.landmarks.map(l => [l.ref.current, l]));\n let currentElement = element;\n while (\n currentElement &&\n !landmarkMap.has(currentElement) &&\n currentElement !== document.body &&\n currentElement.parentElement\n ) {\n currentElement = currentElement.parentElement;\n }\n return landmarkMap.get(currentElement);\n }\n\n /**\n * Gets the next landmark, in DOM focus order, or previous if backwards is specified.\n * If last landmark, next should be the first landmark.\n * If not inside a landmark, will return first landmark.\n * Returns undefined if there are no landmarks.\n */\n private getNextLandmark(element: FocusableElement, {backward}: {backward?: boolean}) {\n let currentLandmark = this.closestLandmark(element);\n let nextLandmarkIndex = backward ? this.landmarks.length - 1 : 0;\n if (currentLandmark) {\n nextLandmarkIndex = this.landmarks.indexOf(currentLandmark) + (backward ? -1 : 1);\n }\n\n let wrapIfNeeded = () => {\n // When we reach the end of the landmark sequence, fire a custom event that can be listened for by applications.\n // If this event is canceled, we return immediately. This can be used to implement landmark navigation across iframes.\n if (nextLandmarkIndex < 0) {\n if (\n !element.dispatchEvent(\n new CustomEvent('react-aria-landmark-navigation', {\n detail: {direction: 'backward'},\n bubbles: true,\n cancelable: true\n })\n )\n ) {\n return true;\n }\n\n nextLandmarkIndex = this.landmarks.length - 1;\n } else if (nextLandmarkIndex >= this.landmarks.length) {\n if (\n !element.dispatchEvent(\n new CustomEvent('react-aria-landmark-navigation', {\n detail: {direction: 'forward'},\n bubbles: true,\n cancelable: true\n })\n )\n ) {\n return true;\n }\n\n nextLandmarkIndex = 0;\n }\n\n if (nextLandmarkIndex < 0 || nextLandmarkIndex >= this.landmarks.length) {\n return true;\n }\n\n return false;\n };\n\n if (wrapIfNeeded()) {\n return undefined;\n }\n\n // Skip over hidden landmarks.\n let i = nextLandmarkIndex;\n while (this.landmarks[nextLandmarkIndex].ref.current?.closest('[aria-hidden=true]')) {\n nextLandmarkIndex += backward ? -1 : 1;\n if (wrapIfNeeded()) {\n return undefined;\n }\n\n if (nextLandmarkIndex === i) {\n break;\n }\n }\n\n return this.landmarks[nextLandmarkIndex];\n }\n\n /**\n * Look at next landmark. If an element was previously focused inside, restore focus there.\n * If not, focus the landmark itself.\n * If no landmarks at all, or none with focusable elements, don't move focus.\n */\n private f6Handler(e: KeyboardEvent) {\n if (e.key === 'F6') {\n // If alt key pressed, focus main landmark, otherwise navigate forward or backward based on shift key.\n let handled = e.altKey\n ? this.focusMain()\n : this.navigate(getEventTarget(e) as FocusableElement, e.shiftKey);\n if (handled) {\n e.preventDefault();\n e.stopPropagation();\n }\n }\n }\n\n private focusMain() {\n let main = this.getLandmarkByRole('main');\n if (main && main.ref.current && main.ref.current.isConnected) {\n this.focusLandmark(main.ref.current, 'forward');\n return true;\n }\n\n return false;\n }\n\n private navigate(from: FocusableElement, backward: boolean) {\n let nextLandmark = this.getNextLandmark(from, {\n backward\n });\n\n if (!nextLandmark) {\n return false;\n }\n\n // If something was previously focused in the next landmark, then return focus to it\n if (nextLandmark.lastFocused) {\n let lastFocused = nextLandmark.lastFocused;\n if (nodeContains(document.body, lastFocused)) {\n lastFocused.focus();\n return true;\n }\n }\n\n // Otherwise, focus the landmark itself\n if (nextLandmark.ref.current && nextLandmark.ref.current.isConnected) {\n this.focusLandmark(nextLandmark.ref.current, backward ? 'backward' : 'forward');\n return true;\n }\n\n return false;\n }\n\n /**\n * Sets lastFocused for a landmark, if focus is moved within that landmark.\n * Lets the last focused landmark know it was blurred if something else is focused.\n */\n private focusinHandler(e: FocusEvent) {\n let currentLandmark = this.closestLandmark(getEventTarget(e) as FocusableElement);\n if (currentLandmark && currentLandmark.ref.current !== getEventTarget(e)) {\n this.updateLandmark({\n ref: currentLandmark.ref,\n lastFocused: getEventTarget(e) as FocusableElement\n });\n }\n let previousFocusedElement = e.relatedTarget as FocusableElement;\n if (previousFocusedElement) {\n let closestPreviousLandmark = this.closestLandmark(previousFocusedElement);\n if (\n closestPreviousLandmark &&\n closestPreviousLandmark.ref.current === previousFocusedElement\n ) {\n closestPreviousLandmark.blur();\n }\n }\n }\n\n /**\n * Track if the focus is lost to the body. If it is, do cleanup on the landmark that last had\n * focus.\n */\n private focusoutHandler(e: FocusEvent) {\n let previousFocusedElement = getEventTarget(e) as FocusableElement;\n let nextFocusedElement = e.relatedTarget;\n // the === document seems to be a jest thing for focus to go there on generic blur event such as landmark.blur();\n // browsers appear to send focus instead to document.body and the relatedTarget is null when that happens\n if (!nextFocusedElement || nextFocusedElement === document) {\n let closestPreviousLandmark = this.closestLandmark(previousFocusedElement);\n if (\n closestPreviousLandmark &&\n closestPreviousLandmark.ref.current === previousFocusedElement\n ) {\n closestPreviousLandmark.blur();\n }\n }\n }\n\n public createLandmarkController(): LandmarkController {\n let instance: LandmarkManager | null = this;\n instance.refCount++;\n instance.setupIfNeeded();\n return {\n navigate(direction, opts) {\n let element = opts?.from || (document!.activeElement as FocusableElement);\n return instance!.navigate(element, direction === 'backward');\n },\n focusNext(opts) {\n let element = opts?.from || (document!.activeElement as FocusableElement);\n return instance!.navigate(element, false);\n },\n focusPrevious(opts) {\n let element = opts?.from || (document!.activeElement as FocusableElement);\n return instance!.navigate(element, true);\n },\n focusMain() {\n return instance!.focusMain();\n },\n dispose() {\n if (instance) {\n instance.refCount--;\n instance.teardownIfNeeded();\n instance = null;\n }\n }\n };\n }\n\n public registerLandmark(landmark: Landmark): () => void {\n if (this.landmarks.find(l => l.ref === landmark.ref)) {\n this.updateLandmark(landmark);\n } else {\n this.addLandmark(landmark);\n }\n\n return () => this.removeLandmark(landmark.ref);\n }\n}\n\n/** Creates a LandmarkController, which allows programmatic navigation of landmarks. */\nexport function UNSTABLE_createLandmarkController(): LandmarkController {\n // Get the current landmark manager and create a controller using it.\n let instance: LandmarkManagerApi | null = getLandmarkManager();\n let controller = instance?.createLandmarkController();\n\n let unsubscribe = subscribe(() => {\n // If the landmark manager changes, dispose the old\n // controller and create a new one.\n controller?.dispose();\n instance = getLandmarkManager();\n controller = instance?.createLandmarkController();\n });\n\n // Return a wrapper that proxies requests to the current controller instance.\n return {\n navigate(direction, opts) {\n return controller!.navigate(direction, opts);\n },\n focusNext(opts) {\n return controller!.focusNext(opts);\n },\n focusPrevious(opts) {\n return controller!.focusPrevious(opts);\n },\n focusMain() {\n return controller!.focusMain();\n },\n dispose() {\n controller?.dispose();\n unsubscribe();\n controller = undefined;\n instance = null;\n }\n };\n}\n\n/**\n * Provides landmark navigation in an application. Call this with a role and label to register a\n * landmark navigable with F6.\n *\n * @param props - Props for the landmark.\n * @param ref - Ref to the landmark.\n */\nexport function useLandmark(\n props: AriaLandmarkProps,\n ref: RefObject<FocusableElement | null>\n): LandmarkAria {\n const {role, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, focus} = props;\n let manager = useLandmarkManager();\n let label = ariaLabel || ariaLabelledby;\n let [isLandmarkFocused, setIsLandmarkFocused] = useState(false);\n\n let defaultFocus = useCallback(() => {\n setIsLandmarkFocused(true);\n }, [setIsLandmarkFocused]);\n\n let blur = useCallback(() => {\n setIsLandmarkFocused(false);\n }, [setIsLandmarkFocused]);\n\n useLayoutEffect(() => {\n if (manager) {\n return manager.registerLandmark({ref, label, role, focus: focus || defaultFocus, blur});\n }\n }, [manager, label, ref, role, focus, defaultFocus, blur]);\n\n useEffect(() => {\n if (isLandmarkFocused) {\n ref.current?.focus();\n }\n }, [isLandmarkFocused, ref]);\n\n return {\n landmarkProps: {\n role,\n tabIndex: isLandmarkFocused ? -1 : undefined,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledby\n }\n };\n}\n"],"names":[],"version":3,"file":"useLandmark.mjs.map"}