@@ -59,3 +59,86 @@ export function useIFrameHeight(onIframeLoaded = null) {
59
59
useIFramePluginEvents ( { 'plugin.resize' : receiveResizeMessage } ) ;
60
60
return [ hasLoaded , iframeHeight ] ;
61
61
}
62
+
63
+ /**
64
+ * Custom hook that adds functionality to skip to a specific content section on the page
65
+ * when a specified skip link is activated by pressing the "Enter" key, "Space" key, or by clicking the link.
66
+ *
67
+ * @param {string } [targetElementId='main-content'] - The ID of the element to skip to when the link is activated.
68
+ * @param {string } [skipLinkSelector='a[href="#main-content"]'] - The CSS selector for the skip link.
69
+ * @param {number } [scrollOffset=100] - The offset to apply when scrolling to the target element (in pixels).
70
+ *
71
+ * @returns {React.RefObject<HTMLElement> } - A ref object pointing to the skip link element.
72
+ */
73
+ export function useScrollToContent (
74
+ targetElementId = 'main-content' ,
75
+ skipLinkSelector = 'a[href="#main-content"]' ,
76
+ scrollOffset = 100 ,
77
+ ) {
78
+ const skipLinkElementRef = useRef ( null ) ;
79
+
80
+ /**
81
+ * Scrolls the page to the target element and sets focus.
82
+ *
83
+ * @param {HTMLElement } targetElement - The target element to scroll to and focus.
84
+ */
85
+ const scrollToTarget = ( targetElement ) => {
86
+ const targetPosition = targetElement . getBoundingClientRect ( ) . top + window . scrollY ;
87
+ window . scrollTo ( { top : targetPosition - scrollOffset , behavior : 'smooth' } ) ;
88
+
89
+ if ( typeof targetElement . focus === 'function' ) {
90
+ targetElement . focus ( { preventScroll : true } ) ;
91
+ } else {
92
+ // eslint-disable-next-line no-console
93
+ console . warn ( `Element with ID "${ targetElementId } " exists but is not focusable.` ) ;
94
+ }
95
+ } ;
96
+
97
+ /**
98
+ * Determines if the event should trigger the skip to content action.
99
+ *
100
+ * @param {KeyboardEvent|MouseEvent } event - The event triggered by the user.
101
+ * @returns {boolean } - True if the event should trigger the skip to content action, otherwise false.
102
+ */
103
+ const shouldTriggerSkip = ( event ) => event . key === 'Enter' || event . key === ' ' || event . type === 'click' ;
104
+
105
+ /**
106
+ * Handles the keydown and click events on the skip link.
107
+ *
108
+ * @param {KeyboardEvent|MouseEvent } event - The event triggered by the user.
109
+ */
110
+ const handleSkipAction = useCallback ( ( event ) => {
111
+ if ( shouldTriggerSkip ( event ) ) {
112
+ event . preventDefault ( ) ;
113
+ const targetElement = document . getElementById ( targetElementId ) ;
114
+ if ( targetElement ) {
115
+ scrollToTarget ( targetElement ) ;
116
+ } else {
117
+ // eslint-disable-next-line no-console
118
+ console . warn ( `Element with ID "${ targetElementId } " not found.` ) ;
119
+ }
120
+ }
121
+ } , [ targetElementId , scrollOffset ] ) ;
122
+
123
+ useEffect ( ( ) => {
124
+ const skipLinkElement = document . querySelector ( skipLinkSelector ) ;
125
+ skipLinkElementRef . current = skipLinkElement ;
126
+
127
+ if ( skipLinkElement ) {
128
+ skipLinkElement . addEventListener ( 'keydown' , handleSkipAction ) ;
129
+ skipLinkElement . addEventListener ( 'click' , handleSkipAction ) ;
130
+ } else {
131
+ // eslint-disable-next-line no-console
132
+ console . warn ( `Skip link with selector "${ skipLinkSelector } " not found.` ) ;
133
+ }
134
+
135
+ return ( ) => {
136
+ if ( skipLinkElement ) {
137
+ skipLinkElement . removeEventListener ( 'keydown' , handleSkipAction ) ;
138
+ skipLinkElement . removeEventListener ( 'click' , handleSkipAction ) ;
139
+ }
140
+ } ;
141
+ } , [ skipLinkSelector , handleSkipAction ] ) ;
142
+
143
+ return skipLinkElementRef ;
144
+ }
0 commit comments