@instructure/ui-a11y-utils 8.12.0 → 8.12.1-snapshot.15

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.
@@ -27,7 +27,6 @@ import keycode from 'keycode'
27
27
  import {
28
28
  contains,
29
29
  addEventListener,
30
- getFrameDocumentSafe,
31
30
  ownerDocument,
32
31
  findTabbable
33
32
  } from '@instructure/ui-dom-utils'
@@ -36,66 +35,54 @@ import { logError as error } from '@instructure/console'
36
35
 
37
36
  import { ScreenReaderFocusRegion } from './ScreenReaderFocusRegion'
38
37
  import { KeyboardFocusRegion } from './KeyboardFocusRegion'
38
+ import { FocusRegionOptions } from './FocusRegionOptions'
39
39
 
40
40
  class FocusRegion {
41
- // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'element' implicitly has an 'any' type.
42
- constructor(element, options) {
43
- // @ts-expect-error ts-migrate(2339) FIXME: Property '_options' does not exist on type 'FocusR... Remove this comment to see the full error message
41
+ private _contextElement: Node | Element | null = null
42
+ private _preventCloseOnDocumentClick = false
43
+ private _options: FocusRegionOptions
44
+ private readonly _screenReaderFocusRegion: ScreenReaderFocusRegion
45
+ private readonly _keyboardFocusRegion: KeyboardFocusRegion
46
+ private readonly _id: string
47
+ private _listeners: ReturnType<typeof addEventListener>[] = []
48
+ private _active = false
49
+
50
+ constructor(element: Element | Node, options: FocusRegionOptions) {
44
51
  this._options = options || {
45
52
  shouldCloseOnDocumentClick: true,
46
- shouldCloseOnEscape: true,
47
- // @ts-expect-error ts-migrate(6133) FIXME: 'event' is declared but its value is never read.
48
- onDismiss: (event) => {}
53
+ shouldCloseOnEscape: true
49
54
  }
50
55
  this._contextElement = element
51
- // @ts-expect-error ts-migrate(2339) FIXME: Property '_screenReaderFocusRegion' does not exist... Remove this comment to see the full error message
52
56
  this._screenReaderFocusRegion = new ScreenReaderFocusRegion(
53
57
  element,
54
58
  options
55
59
  )
56
- // @ts-expect-error ts-migrate(2339) FIXME: Property '_keyboardFocusRegion' does not exist on ... Remove this comment to see the full error message
57
60
  this._keyboardFocusRegion = new KeyboardFocusRegion(element, options)
58
- // @ts-expect-error ts-migrate(2339) FIXME: Property '_id' does not exist on type 'FocusRegion... Remove this comment to see the full error message
59
61
  this._id = uid()
60
62
  }
61
63
 
62
- _contextElement = null
63
- _preventCloseOnDocumentClick = false
64
- _listeners = []
65
- _active = false
66
-
67
- // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'element' implicitly has an 'any' type.
68
- updateElement(element) {
64
+ updateElement(element: Element | Node) {
69
65
  this._contextElement = element
70
- // @ts-expect-error ts-migrate(2339) FIXME: Property '_keyboardFocusRegion' does not exist on ... Remove this comment to see the full error message
71
66
  if (this._keyboardFocusRegion) {
72
- // @ts-expect-error ts-migrate(2339) FIXME: Property '_keyboardFocusRegion' does not exist on ... Remove this comment to see the full error message
73
67
  this._keyboardFocusRegion.updateElement(element)
74
68
  }
75
- // @ts-expect-error ts-migrate(2339) FIXME: Property '_screenReaderFocusRegion' does not exist... Remove this comment to see the full error message
76
69
  if (this._screenReaderFocusRegion) {
77
- // @ts-expect-error ts-migrate(2339) FIXME: Property '_screenReaderFocusRegion' does not exist... Remove this comment to see the full error message
78
70
  this._screenReaderFocusRegion.updateElement(element)
79
71
  }
80
72
  }
81
73
 
82
- // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'event' implicitly has an 'any' type.
83
- handleDismiss = (event, documentClick) => {
84
- // @ts-expect-error ts-migrate(2339) FIXME: Property '_options' does not exist on type 'FocusR... Remove this comment to see the full error message
85
- this._options.onDismiss(event, documentClick)
74
+ handleDismiss = (event: Event, documentClick?: boolean) => {
75
+ this._options.onDismiss?.(event, documentClick)
86
76
  }
87
77
 
88
- // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'event' implicitly has an 'any' type.
89
- captureDocumentClick = (event) => {
78
+ captureDocumentClick = (event: MouseEvent) => {
90
79
  const { target } = event
91
80
  this._preventCloseOnDocumentClick =
92
- event.button !== 0 || contains(this._contextElement, target)
81
+ event.button !== 0 || contains(this._contextElement, target as Node)
93
82
  }
94
83
 
95
- // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'event' implicitly has an 'any' type.
96
- handleDocumentClick = (event) => {
84
+ handleDocumentClick = (event: MouseEvent) => {
97
85
  if (
98
- // @ts-expect-error ts-migrate(2339) FIXME: Property '_options' does not exist on type 'FocusR... Remove this comment to see the full error message
99
86
  this._options.shouldCloseOnDocumentClick &&
100
87
  !this._preventCloseOnDocumentClick
101
88
  ) {
@@ -103,30 +90,24 @@ class FocusRegion {
103
90
  }
104
91
  }
105
92
 
106
- // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'event' implicitly has an 'any' type.
107
- handleFrameClick = (event, frame) => {
93
+ handleFrameClick = (event: MouseEvent, frame: HTMLIFrameElement) => {
108
94
  if (!contains(this._contextElement, frame)) {
109
95
  // dismiss if frame is not within the region
110
96
  this.handleDismiss(event, true)
111
97
  }
112
98
  }
113
99
 
114
- // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'event' implicitly has an 'any' type.
115
- handleKeyUp = (event) => {
100
+ handleKeyUp = (event: KeyboardEvent) => {
116
101
  if (
117
- // @ts-expect-error ts-migrate(2339) FIXME: Property '_options' does not exist on type 'FocusR... Remove this comment to see the full error message
118
102
  this._options.shouldCloseOnEscape &&
119
- // @ts-expect-error ts-migrate(2339) FIXME: Property 'escape' does not exist on type 'CodesMap... Remove this comment to see the full error message
120
- event.keyCode === keycode.codes.escape &&
103
+ event.keyCode === keycode.codes.esc &&
121
104
  !event.defaultPrevented
122
105
  ) {
123
- // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
124
106
  this.handleDismiss(event)
125
107
  }
126
108
  }
127
109
 
128
110
  get id() {
129
- // @ts-expect-error ts-migrate(2339) FIXME: Property '_id' does not exist on type 'FocusRegion... Remove this comment to see the full error message
130
111
  return this._id
131
112
  }
132
113
 
@@ -144,63 +125,59 @@ class FocusRegion {
144
125
  if (!this._active) {
145
126
  const doc = ownerDocument(this._contextElement)
146
127
 
147
- // @ts-expect-error ts-migrate(2339) FIXME: Property '_keyboardFocusRegion' does not exist on ... Remove this comment to see the full error message
148
128
  this._keyboardFocusRegion.activate()
149
- // @ts-expect-error ts-migrate(2339) FIXME: Property '_screenReaderFocusRegion' does not exist... Remove this comment to see the full error message
150
129
  this._screenReaderFocusRegion.activate()
151
130
 
152
- // @ts-expect-error ts-migrate(2339) FIXME: Property '_options' does not exist on type 'FocusR... Remove this comment to see the full error message
153
131
  if (this._options.shouldCloseOnDocumentClick) {
154
132
  this._listeners.push(
155
- // @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ remove(): void; }' is not assi... Remove this comment to see the full error message
156
- addEventListener(doc, 'click', this.captureDocumentClick, true)
133
+ addEventListener(
134
+ doc,
135
+ 'click',
136
+ this.captureDocumentClick as EventListener,
137
+ true
138
+ )
157
139
  )
158
140
  this._listeners.push(
159
- // @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ remove(): void; }' is not assi... Remove this comment to see the full error message
160
- addEventListener(doc, 'click', this.handleDocumentClick)
141
+ addEventListener(
142
+ doc,
143
+ 'click',
144
+ this.handleDocumentClick as EventListener
145
+ )
161
146
  )
162
147
 
163
148
  Array.from(doc.getElementsByTagName('iframe')).forEach((el) => {
164
149
  // listen for mouseup events on any iframes in the document
165
- const frameDoc = getFrameDocumentSafe(el)
166
-
150
+ const frameDoc = el.contentDocument
167
151
  if (frameDoc) {
168
152
  this._listeners.push(
169
- // @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ remove(): void; }' is not assi... Remove this comment to see the full error message
170
153
  addEventListener(frameDoc, 'mouseup', (event) => {
171
- this.handleFrameClick(event, el)
154
+ this.handleFrameClick(event as MouseEvent, el)
172
155
  })
173
156
  )
174
157
  }
175
158
  })
176
159
  }
177
160
 
178
- // @ts-expect-error ts-migrate(2339) FIXME: Property '_options' does not exist on type 'FocusR... Remove this comment to see the full error message
179
161
  if (this._options.shouldCloseOnEscape) {
180
- // @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ remove(): void; }' is not assi... Remove this comment to see the full error message
181
- this._listeners.push(addEventListener(doc, 'keyup', this.handleKeyUp))
162
+ this._listeners.push(
163
+ addEventListener(doc, 'keyup', this.handleKeyUp as EventListener)
164
+ )
182
165
  }
183
166
 
184
167
  this._active = true
185
168
  }
186
169
  }
187
170
 
188
- deactivate({ keyboard = true } = {}) {
171
+ deactivate({ keyboard = true }: { keyboard?: boolean } = {}) {
189
172
  if (this._active) {
190
173
  this._listeners.forEach((listener) => {
191
- // @ts-expect-error ts-migrate(2339) FIXME: Property 'remove' does not exist on type 'never'.
192
174
  listener.remove()
193
175
  })
194
176
  this._listeners = []
195
-
196
177
  if (keyboard) {
197
- // @ts-expect-error ts-migrate(2339) FIXME: Property '_keyboardFocusRegion' does not exist on ... Remove this comment to see the full error message
198
178
  this._keyboardFocusRegion.deactivate()
199
179
  }
200
-
201
- // @ts-expect-error ts-migrate(2339) FIXME: Property '_screenReaderFocusRegion' does not exist... Remove this comment to see the full error message
202
180
  this._screenReaderFocusRegion.deactivate()
203
-
204
181
  this._active = false
205
182
  }
206
183
  }
@@ -210,7 +187,6 @@ class FocusRegion {
210
187
  this._active,
211
188
  `[FocusRegion] Cannot call '.focus()' on a region that is not currently active.`
212
189
  )
213
- // @ts-expect-error ts-migrate(2339) FIXME: Property '_keyboardFocusRegion' does not exist on ... Remove this comment to see the full error message
214
190
  this._keyboardFocusRegion.focus()
215
191
  }
216
192
 
@@ -219,7 +195,6 @@ class FocusRegion {
219
195
  !this._active,
220
196
  `[FocusRegion] Cannot call '.blur()' on a region that is currently active.`
221
197
  )
222
- // @ts-expect-error ts-migrate(2339) FIXME: Property '_keyboardFocusRegion' does not exist on ... Remove this comment to see the full error message
223
198
  this._keyboardFocusRegion.blur()
224
199
  }
225
200
  }
@@ -230,11 +205,13 @@ export {
230
205
  * ---
231
206
  * category: utilities/a11y
232
207
  * ---
233
- * @module FocusRegion
234
- * Class for focus operations.
208
+ *
209
+ * Class for focus operations, manages [ScreenReaderFocusRegion](#ScreenReaderFocusRegion)
210
+ * and [KeyboardFocusRegion](#KeyboardFocusRegion) for the given DOM element.
235
211
  * - Scoping focus within a given context (DOM node),
236
212
  * - Mark active element for focus later
237
213
  * - Return focus to the marked element
214
+ * @module FocusRegion
238
215
  */
239
216
  FocusRegion
240
217
  }
@@ -21,15 +21,25 @@
21
21
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
22
  * SOFTWARE.
23
23
  */
24
+
24
25
  import { logError as error } from '@instructure/console'
25
26
  import { FocusRegion } from './FocusRegion'
26
-
27
- // @ts-expect-error ts-migrate(7034) FIXME: Variable 'ENTRIES' implicitly has type 'any[]' in ... Remove this comment to see the full error message
28
- let ENTRIES = []
27
+ import { FocusRegionOptions } from './FocusRegionOptions'
28
+
29
+ type Entry = {
30
+ id: string
31
+ element: Element | Node
32
+ region: FocusRegion
33
+ children: Entry[]
34
+ parent?: Entry
35
+ }
36
+ let ENTRIES: Entry[] = []
29
37
 
30
38
  class FocusRegionManager {
31
- // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'element' implicitly has an 'any' type.
32
- static focusRegion = (element, idOrOptions = {}) => {
39
+ static focusRegion = (
40
+ element: Element | Node,
41
+ idOrOptions: string | FocusRegionOptions = {}
42
+ ) => {
33
43
  let entry
34
44
  if (typeof idOrOptions === 'string') {
35
45
  entry = FocusRegionManager.getEntry(element, idOrOptions)
@@ -45,60 +55,57 @@ class FocusRegionManager {
45
55
  `[FocusRegionManager] Could not focus region with element: ${element}`
46
56
  )
47
57
  }
58
+ return
48
59
  }
49
60
 
50
- // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'element' implicitly has an 'any' type.
51
- static activateRegion = (element, options) => {
61
+ static activateRegion = (
62
+ element: Element | Node,
63
+ options: FocusRegionOptions
64
+ ) => {
52
65
  const { region } = FocusRegionManager.addEntry(element, options)
53
66
  return region
54
67
  }
55
68
 
56
69
  static getActiveEntry = () => {
57
- // @ts-expect-error ts-migrate(7005) FIXME: Variable 'ENTRIES' implicitly has an 'any[]' type.
58
70
  return ENTRIES.find(({ region }) => region.focused)
59
71
  }
60
72
 
61
- // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'element' implicitly has an 'any' type.
62
- static findEntry = (element, id) => {
73
+ static findEntry = (element: Element | Node, id?: string) => {
63
74
  let index
64
75
  if (id) {
65
- // @ts-expect-error ts-migrate(7005) FIXME: Variable 'ENTRIES' implicitly has an 'any[]' type.
66
76
  index = ENTRIES.findIndex((entry) => entry.id === id)
67
77
  } else {
68
- // @ts-expect-error ts-migrate(7005) FIXME: Variable 'ENTRIES' implicitly has an 'any[]' type.
69
78
  index = ENTRIES.findIndex((entry) => entry.element === element)
70
79
  }
71
80
  return index
72
81
  }
73
82
 
74
- // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'element' implicitly has an 'any' type.
75
- static getEntry = (element, id) => {
76
- // @ts-expect-error ts-migrate(7005) FIXME: Variable 'ENTRIES' implicitly has an 'any[]' type.
83
+ static getEntry = (element: Element | Node, id?: string) => {
77
84
  return ENTRIES[FocusRegionManager.findEntry(element, id)]
78
85
  }
79
86
 
80
- // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'element' implicitly has an 'any' type.
81
- static addEntry = (element, options = {}) => {
87
+ static addEntry = (
88
+ element: Element | Node,
89
+ options: FocusRegionOptions = {}
90
+ ) => {
82
91
  const region = new FocusRegion(element, options)
83
92
  const activeEntry = FocusRegionManager.getActiveEntry()
84
93
 
85
94
  const { keyboardFocusable } = region
86
95
 
87
- // @ts-expect-error ts-migrate(7005) FIXME: Variable 'ENTRIES' implicitly has an 'any[]' type.
88
96
  ENTRIES.forEach(({ region }) => {
89
97
  if (region) {
90
98
  // If the active region is triggering a new focus region that does not have
91
99
  // keyboard focusable content, don't deactivate the active region's keyboard
92
100
  // focus region
93
- region.deactivate(
94
- region.focused && !keyboardFocusable && { keyboard: false }
95
- )
101
+ const keyboard =
102
+ region.focused && !keyboardFocusable ? { keyboard: false } : undefined
103
+ region.deactivate(keyboard)
96
104
  }
97
105
  })
98
106
 
99
107
  region.activate()
100
108
 
101
- // @ts-expect-error ts-migrate(2339) FIXME: Property 'shouldFocusOnOpen' does not exist on typ... Remove this comment to see the full error message
102
109
  if (options.shouldFocusOnOpen) {
103
110
  region.focus()
104
111
  }
@@ -117,22 +124,16 @@ class FocusRegionManager {
117
124
  return entry
118
125
  }
119
126
 
120
- // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'element' implicitly has an 'any' type.
121
- static removeEntry = (element, id) => {
127
+ static removeEntry = (element: Element | Node, id?: string) => {
122
128
  const index = FocusRegionManager.findEntry(element, id)
123
- // @ts-expect-error ts-migrate(7005) FIXME: Variable 'ENTRIES' implicitly has an 'any[]' type.
124
129
  const entry = ENTRIES[index]
125
-
126
130
  if (index > -1) {
127
- // @ts-expect-error ts-migrate(7005) FIXME: Variable 'ENTRIES' implicitly has an 'any[]' type.
128
131
  ENTRIES.splice(index, 1)
129
132
  }
130
-
131
133
  return entry
132
134
  }
133
135
 
134
- // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'element' implicitly has an 'any' type.
135
- static isFocused = (element, id) => {
136
+ static isFocused = (element: Element | Node, id?: string) => {
136
137
  const entry = FocusRegionManager.getActiveEntry()
137
138
  if (id) {
138
139
  return entry && entry.region && entry.id === id
@@ -145,8 +146,7 @@ class FocusRegionManager {
145
146
  ENTRIES = []
146
147
  }
147
148
 
148
- // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'element' implicitly has an 'any' type.
149
- static blurRegion = (element, id) => {
149
+ static blurRegion = (element: Element | Node, id?: string) => {
150
150
  const entry = FocusRegionManager.removeEntry(element, id)
151
151
 
152
152
  if (entry) {
@@ -157,7 +157,6 @@ class FocusRegionManager {
157
157
 
158
158
  // and any regions created from it
159
159
  if (children) {
160
- // @ts-expect-error ts-migrate(7031) FIXME: Binding element 'id' implicitly has an 'any' type.
161
160
  children.forEach(({ id, element }) => {
162
161
  const entry = FocusRegionManager.removeEntry(element, id)
163
162
  entry && entry.region && entry.region.deactivate()
@@ -178,11 +177,12 @@ export {
178
177
  * ---
179
178
  * category: utilities/a11y
180
179
  * ---
181
- * @module FocusManager
182
- * Class for focus operations.
180
+ *
181
+ * Class for focus operations, manages multiple [FocusRegion](#FocusRegion)s.
183
182
  * - Scoping focus within a given context,
184
183
  * - Mark active element for focus later
185
184
  * - Return focus to the marked element
185
+ * @module FocusManager
186
186
  */
187
187
  FocusRegionManager
188
188
  }
@@ -0,0 +1,80 @@
1
+ /*
2
+ * The MIT License (MIT)
3
+ *
4
+ * Copyright (c) 2015 - present Instructure, Inc.
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ import type { LiveRegion, UIElement } from '@instructure/shared-types'
26
+
27
+ export type FocusRegionOptions = {
28
+ /**
29
+ * Function called when tab focus leaves the focusable content. This only
30
+ * occurs when `shouldContainFocus` is set to false.
31
+ */
32
+ onBlur?: (event: Event) => void
33
+ /**
34
+ * Function called when a focus region is dismissed. This can happen when
35
+ * the user presses the escape key and `shouldCloseOnEscape` is true or
36
+ * when an IFrame is clicked or when anything outside the focus region
37
+ * is clicked if `shouldCloseOnDocumentClick` is true.
38
+ * @param event The event triggered the dismissal
39
+ * @param documentClick Whether the dismissal was triggered by a mouse click.
40
+ */
41
+ onDismiss?: (event: Event, documentClick?: boolean) => void
42
+ /**
43
+ * An element or a function returning an element to focus by default
44
+ */
45
+ defaultFocusElement?: UIElement
46
+ /**
47
+ * An element, function returning an element, or array of elements that will not be hidden from
48
+ * the screen reader when the focus region is active
49
+ */
50
+ liveRegion?: LiveRegion
51
+ /**
52
+ * When set to true or its an array that includes the 'keyboard' string,
53
+ * the keyboard and screenreader focus is trapped; when set to 'screenreader'
54
+ * only the screenreader focus is trapped.
55
+ */
56
+ shouldContainFocus?: boolean | ('keyboard' | 'screenreader')[]
57
+ /**
58
+ * When set to true the keyboard focus is returned to the active element
59
+ * before the focus region was activated.
60
+ */
61
+ shouldReturnFocus?: boolean
62
+ /**
63
+ * When set to true the `onDismiss` function is called on a click outside
64
+ * the focus region.
65
+ */
66
+ shouldCloseOnDocumentClick?: boolean
67
+ /**
68
+ * When set to true the `onDismiss` function is called on the `Escape`
69
+ * keypress
70
+ */
71
+ shouldCloseOnEscape?: boolean
72
+ /**
73
+ * When set to true, the `defaultFocusElement` is focused on initialization.
74
+ */
75
+ shouldFocusOnOpen?: boolean
76
+ /**
77
+ * provides a reference to the underlying html root element
78
+ */
79
+ elementRef?: (element: Element | null) => void
80
+ }