@instructure/ui-menu 11.6.0 → 11.6.1-snapshot-129

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.
Files changed (180) hide show
  1. package/CHANGELOG.md +41 -297
  2. package/es/Menu/{MenuItem → v1/MenuItem}/index.js +2 -2
  3. package/es/Menu/{MenuItemGroup → v1/MenuItemGroup}/index.js +1 -1
  4. package/es/Menu/{MenuItemSeparator → v1/MenuItemSeparator}/index.js +1 -1
  5. package/es/Menu/{index.js → v1/index.js} +3 -3
  6. package/es/Menu/v2/MenuItem/index.js +262 -0
  7. package/es/Menu/v2/MenuItem/props.js +26 -0
  8. package/es/Menu/v2/MenuItem/styles.js +146 -0
  9. package/es/Menu/v2/MenuItemGroup/index.js +175 -0
  10. package/es/Menu/v2/MenuItemGroup/props.js +26 -0
  11. package/es/Menu/v2/MenuItemGroup/styles.js +60 -0
  12. package/es/Menu/v2/MenuItemSeparator/index.js +70 -0
  13. package/es/Menu/v2/MenuItemSeparator/props.js +29 -0
  14. package/es/Menu/v2/MenuItemSeparator/styles.js +46 -0
  15. package/es/Menu/v2/index.js +405 -0
  16. package/es/Menu/v2/props.js +26 -0
  17. package/es/Menu/v2/styles.js +61 -0
  18. package/es/{index.js → exports/a.js} +1 -1
  19. package/es/exports/b.js +24 -0
  20. package/es/{MenuContext.js → utils/v1/MenuContext.js} +2 -0
  21. package/lib/Menu/{MenuItem → v1/MenuItem}/index.js +4 -4
  22. package/lib/Menu/{MenuItemGroup → v1/MenuItemGroup}/index.js +1 -1
  23. package/lib/Menu/{MenuItemSeparator → v1/MenuItemSeparator}/index.js +1 -1
  24. package/lib/Menu/v1/index.js +432 -0
  25. package/lib/Menu/v2/MenuItem/index.js +271 -0
  26. package/lib/Menu/v2/MenuItem/props.js +31 -0
  27. package/lib/Menu/v2/MenuItem/styles.js +152 -0
  28. package/lib/Menu/v2/MenuItemGroup/index.js +183 -0
  29. package/lib/Menu/v2/MenuItemGroup/props.js +31 -0
  30. package/lib/Menu/v2/MenuItemGroup/styles.js +66 -0
  31. package/lib/Menu/v2/MenuItemSeparator/index.js +75 -0
  32. package/lib/Menu/v2/MenuItemSeparator/props.js +34 -0
  33. package/lib/Menu/v2/MenuItemSeparator/styles.js +52 -0
  34. package/lib/Menu/{index.js → v2/index.js} +4 -5
  35. package/lib/Menu/v2/props.js +31 -0
  36. package/lib/Menu/v2/styles.js +66 -0
  37. package/lib/{index.js → exports/a.js} +5 -5
  38. package/lib/exports/b.js +30 -0
  39. package/lib/{MenuContext.js → utils/v1/MenuContext.js} +1 -0
  40. package/package.json +45 -23
  41. package/src/Menu/{MenuItem → v1/MenuItem}/index.tsx +2 -2
  42. package/src/Menu/{MenuItemGroup → v1/MenuItemGroup}/index.tsx +1 -1
  43. package/src/Menu/{MenuItemSeparator → v1/MenuItemSeparator}/index.tsx +1 -1
  44. package/src/Menu/{index.tsx → v1/index.tsx} +3 -3
  45. package/src/Menu/{props.ts → v1/props.ts} +1 -1
  46. package/src/Menu/v2/MenuItem/index.tsx +318 -0
  47. package/src/Menu/v2/MenuItem/props.ts +132 -0
  48. package/src/Menu/v2/MenuItem/styles.ts +172 -0
  49. package/src/Menu/v2/MenuItemGroup/index.tsx +245 -0
  50. package/src/Menu/v2/MenuItemGroup/props.ts +105 -0
  51. package/src/Menu/v2/MenuItemGroup/styles.ts +66 -0
  52. package/src/Menu/v2/MenuItemSeparator/index.tsx +79 -0
  53. package/src/Menu/v2/MenuItemSeparator/props.ts +48 -0
  54. package/src/Menu/v2/MenuItemSeparator/styles.ts +52 -0
  55. package/src/Menu/v2/README.md +104 -0
  56. package/src/Menu/v2/index.tsx +514 -0
  57. package/src/Menu/v2/props.ts +213 -0
  58. package/src/Menu/v2/styles.ts +68 -0
  59. package/src/{index.ts → exports/a.ts} +5 -5
  60. package/src/exports/b.ts +29 -0
  61. package/src/{MenuContext.ts → utils/v1/MenuContext.ts} +3 -3
  62. package/tsconfig.build.tsbuildinfo +1 -1
  63. package/types/Menu/{MenuItem → v1/MenuItem}/index.d.ts +3 -3
  64. package/types/Menu/v1/MenuItem/index.d.ts.map +1 -0
  65. package/types/Menu/v1/MenuItem/props.d.ts.map +1 -0
  66. package/types/Menu/v1/MenuItem/styles.d.ts.map +1 -0
  67. package/types/Menu/v1/MenuItem/theme.d.ts.map +1 -0
  68. package/types/Menu/v1/MenuItemGroup/index.d.ts.map +1 -0
  69. package/types/Menu/v1/MenuItemGroup/props.d.ts.map +1 -0
  70. package/types/Menu/v1/MenuItemGroup/styles.d.ts.map +1 -0
  71. package/types/Menu/v1/MenuItemGroup/theme.d.ts.map +1 -0
  72. package/types/Menu/v1/MenuItemSeparator/index.d.ts.map +1 -0
  73. package/types/Menu/v1/MenuItemSeparator/props.d.ts.map +1 -0
  74. package/types/Menu/v1/MenuItemSeparator/styles.d.ts.map +1 -0
  75. package/types/Menu/v1/MenuItemSeparator/theme.d.ts.map +1 -0
  76. package/types/Menu/{index.d.ts → v1/index.d.ts} +3 -3
  77. package/types/Menu/v1/index.d.ts.map +1 -0
  78. package/types/Menu/{props.d.ts → v1/props.d.ts} +1 -1
  79. package/types/Menu/v1/props.d.ts.map +1 -0
  80. package/types/Menu/v1/styles.d.ts.map +1 -0
  81. package/types/Menu/v1/theme.d.ts.map +1 -0
  82. package/types/Menu/v2/MenuItem/index.d.ts +66 -0
  83. package/types/Menu/v2/MenuItem/index.d.ts.map +1 -0
  84. package/types/Menu/v2/MenuItem/props.d.ts +73 -0
  85. package/types/Menu/v2/MenuItem/props.d.ts.map +1 -0
  86. package/types/Menu/v2/MenuItem/styles.d.ts +15 -0
  87. package/types/Menu/v2/MenuItem/styles.d.ts.map +1 -0
  88. package/types/Menu/v2/MenuItemGroup/index.d.ts +52 -0
  89. package/types/Menu/v2/MenuItemGroup/index.d.ts.map +1 -0
  90. package/types/Menu/v2/MenuItemGroup/props.d.ts +51 -0
  91. package/types/Menu/v2/MenuItemGroup/props.d.ts.map +1 -0
  92. package/types/Menu/v2/MenuItemGroup/styles.d.ts +15 -0
  93. package/types/Menu/v2/MenuItemGroup/styles.d.ts.map +1 -0
  94. package/types/Menu/v2/MenuItemSeparator/index.d.ts +21 -0
  95. package/types/Menu/v2/MenuItemSeparator/index.d.ts.map +1 -0
  96. package/types/Menu/v2/MenuItemSeparator/props.d.ts +11 -0
  97. package/types/Menu/v2/MenuItemSeparator/props.d.ts.map +1 -0
  98. package/types/Menu/v2/MenuItemSeparator/styles.d.ts +15 -0
  99. package/types/Menu/v2/MenuItemSeparator/styles.d.ts.map +1 -0
  100. package/types/Menu/v2/index.d.ts +118 -0
  101. package/types/Menu/v2/index.d.ts.map +1 -0
  102. package/types/Menu/v2/props.d.ts +138 -0
  103. package/types/Menu/v2/props.d.ts.map +1 -0
  104. package/types/Menu/v2/styles.d.ts +15 -0
  105. package/types/Menu/v2/styles.d.ts.map +1 -0
  106. package/types/exports/a.d.ts +6 -0
  107. package/types/exports/a.d.ts.map +1 -0
  108. package/types/exports/b.d.ts +6 -0
  109. package/types/exports/b.d.ts.map +1 -0
  110. package/types/utils/v1/MenuContext.d.ts +13 -0
  111. package/types/utils/v1/MenuContext.d.ts.map +1 -0
  112. package/types/Menu/MenuItem/index.d.ts.map +0 -1
  113. package/types/Menu/MenuItem/props.d.ts.map +0 -1
  114. package/types/Menu/MenuItem/styles.d.ts.map +0 -1
  115. package/types/Menu/MenuItem/theme.d.ts.map +0 -1
  116. package/types/Menu/MenuItemGroup/index.d.ts.map +0 -1
  117. package/types/Menu/MenuItemGroup/props.d.ts.map +0 -1
  118. package/types/Menu/MenuItemGroup/styles.d.ts.map +0 -1
  119. package/types/Menu/MenuItemGroup/theme.d.ts.map +0 -1
  120. package/types/Menu/MenuItemSeparator/index.d.ts.map +0 -1
  121. package/types/Menu/MenuItemSeparator/props.d.ts.map +0 -1
  122. package/types/Menu/MenuItemSeparator/styles.d.ts.map +0 -1
  123. package/types/Menu/MenuItemSeparator/theme.d.ts.map +0 -1
  124. package/types/Menu/index.d.ts.map +0 -1
  125. package/types/Menu/props.d.ts.map +0 -1
  126. package/types/Menu/styles.d.ts.map +0 -1
  127. package/types/Menu/theme.d.ts.map +0 -1
  128. package/types/MenuContext.d.ts +0 -14
  129. package/types/MenuContext.d.ts.map +0 -1
  130. package/types/index.d.ts +0 -6
  131. package/types/index.d.ts.map +0 -1
  132. /package/es/Menu/{MenuItem → v1/MenuItem}/props.js +0 -0
  133. /package/es/Menu/{MenuItem → v1/MenuItem}/styles.js +0 -0
  134. /package/es/Menu/{MenuItem → v1/MenuItem}/theme.js +0 -0
  135. /package/es/Menu/{MenuItemGroup → v1/MenuItemGroup}/props.js +0 -0
  136. /package/es/Menu/{MenuItemGroup → v1/MenuItemGroup}/styles.js +0 -0
  137. /package/es/Menu/{MenuItemGroup → v1/MenuItemGroup}/theme.js +0 -0
  138. /package/es/Menu/{MenuItemSeparator → v1/MenuItemSeparator}/props.js +0 -0
  139. /package/es/Menu/{MenuItemSeparator → v1/MenuItemSeparator}/styles.js +0 -0
  140. /package/es/Menu/{MenuItemSeparator → v1/MenuItemSeparator}/theme.js +0 -0
  141. /package/es/Menu/{props.js → v1/props.js} +0 -0
  142. /package/es/Menu/{styles.js → v1/styles.js} +0 -0
  143. /package/es/Menu/{theme.js → v1/theme.js} +0 -0
  144. /package/lib/Menu/{MenuItem → v1/MenuItem}/props.js +0 -0
  145. /package/lib/Menu/{MenuItem → v1/MenuItem}/styles.js +0 -0
  146. /package/lib/Menu/{MenuItem → v1/MenuItem}/theme.js +0 -0
  147. /package/lib/Menu/{MenuItemGroup → v1/MenuItemGroup}/props.js +0 -0
  148. /package/lib/Menu/{MenuItemGroup → v1/MenuItemGroup}/styles.js +0 -0
  149. /package/lib/Menu/{MenuItemGroup → v1/MenuItemGroup}/theme.js +0 -0
  150. /package/lib/Menu/{MenuItemSeparator → v1/MenuItemSeparator}/props.js +0 -0
  151. /package/lib/Menu/{MenuItemSeparator → v1/MenuItemSeparator}/styles.js +0 -0
  152. /package/lib/Menu/{MenuItemSeparator → v1/MenuItemSeparator}/theme.js +0 -0
  153. /package/lib/Menu/{props.js → v1/props.js} +0 -0
  154. /package/lib/Menu/{styles.js → v1/styles.js} +0 -0
  155. /package/lib/Menu/{theme.js → v1/theme.js} +0 -0
  156. /package/src/Menu/{MenuItem → v1/MenuItem}/props.ts +0 -0
  157. /package/src/Menu/{MenuItem → v1/MenuItem}/styles.ts +0 -0
  158. /package/src/Menu/{MenuItem → v1/MenuItem}/theme.ts +0 -0
  159. /package/src/Menu/{MenuItemGroup → v1/MenuItemGroup}/props.ts +0 -0
  160. /package/src/Menu/{MenuItemGroup → v1/MenuItemGroup}/styles.ts +0 -0
  161. /package/src/Menu/{MenuItemGroup → v1/MenuItemGroup}/theme.ts +0 -0
  162. /package/src/Menu/{MenuItemSeparator → v1/MenuItemSeparator}/props.ts +0 -0
  163. /package/src/Menu/{MenuItemSeparator → v1/MenuItemSeparator}/styles.ts +0 -0
  164. /package/src/Menu/{MenuItemSeparator → v1/MenuItemSeparator}/theme.ts +0 -0
  165. /package/src/Menu/{README.md → v1/README.md} +0 -0
  166. /package/src/Menu/{styles.ts → v1/styles.ts} +0 -0
  167. /package/src/Menu/{theme.ts → v1/theme.ts} +0 -0
  168. /package/types/Menu/{MenuItem → v1/MenuItem}/props.d.ts +0 -0
  169. /package/types/Menu/{MenuItem → v1/MenuItem}/styles.d.ts +0 -0
  170. /package/types/Menu/{MenuItem → v1/MenuItem}/theme.d.ts +0 -0
  171. /package/types/Menu/{MenuItemGroup → v1/MenuItemGroup}/index.d.ts +0 -0
  172. /package/types/Menu/{MenuItemGroup → v1/MenuItemGroup}/props.d.ts +0 -0
  173. /package/types/Menu/{MenuItemGroup → v1/MenuItemGroup}/styles.d.ts +0 -0
  174. /package/types/Menu/{MenuItemGroup → v1/MenuItemGroup}/theme.d.ts +0 -0
  175. /package/types/Menu/{MenuItemSeparator → v1/MenuItemSeparator}/index.d.ts +0 -0
  176. /package/types/Menu/{MenuItemSeparator → v1/MenuItemSeparator}/props.d.ts +0 -0
  177. /package/types/Menu/{MenuItemSeparator → v1/MenuItemSeparator}/styles.d.ts +0 -0
  178. /package/types/Menu/{MenuItemSeparator → v1/MenuItemSeparator}/theme.d.ts +0 -0
  179. /package/types/Menu/{styles.d.ts → v1/styles.d.ts} +0 -0
  180. /package/types/Menu/{theme.d.ts → v1/theme.d.ts} +0 -0
@@ -0,0 +1,514 @@
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 { ComponentElement, Children, Component, ReactElement } from 'react'
26
+ import keycode from 'keycode'
27
+
28
+ import { Popover } from '@instructure/ui-popover/latest'
29
+ import {
30
+ safeCloneElement,
31
+ matchComponentTypes,
32
+ withDeterministicId
33
+ } from '@instructure/ui-react-utils'
34
+ import { logError as error } from '@instructure/console'
35
+ import { containsActiveElement } from '@instructure/ui-dom-utils'
36
+
37
+ import { MenuContext } from '../../utils/v1/MenuContext'
38
+ import { MenuItem } from './MenuItem'
39
+ import type { MenuItemProps } from './MenuItem/props'
40
+ import { MenuItemGroup } from './MenuItemGroup'
41
+ import type { MenuGroupProps } from './MenuItemGroup/props'
42
+ import { MenuItemSeparator } from './MenuItemSeparator'
43
+ import type { MenuSeparatorProps } from './MenuItemSeparator/props'
44
+ import { withStyle } from '@instructure/emotion'
45
+
46
+ import generateStyle from './styles'
47
+
48
+ import { allowedProps } from './props'
49
+ import type { MenuProps } from './props'
50
+
51
+ type MenuChild = ComponentElement<MenuProps, Menu>
52
+ type MenuItemChild = ComponentElement<MenuItemProps, MenuItem>
53
+ type MenuGroupChild = ComponentElement<MenuGroupProps, MenuItemGroup>
54
+ type MenuSeparatorChild = ComponentElement<
55
+ MenuSeparatorProps,
56
+ MenuItemSeparator
57
+ >
58
+
59
+ /**
60
+ ---
61
+ category: components
62
+ ---
63
+ **/
64
+ @withDeterministicId()
65
+ @withStyle(generateStyle)
66
+ class Menu extends Component<MenuProps> {
67
+ static readonly componentId = 'Menu'
68
+ static allowedProps = allowedProps
69
+ static defaultProps = {
70
+ label: null,
71
+ disabled: false,
72
+ trigger: null,
73
+ placement: 'bottom center',
74
+ defaultShow: false,
75
+ mountNode: null,
76
+ constrain: 'window',
77
+ shouldHideOnSelect: true,
78
+ shouldFocusTriggerOnClose: true,
79
+ withArrow: true,
80
+ offsetX: 0,
81
+ offsetY: 0
82
+ }
83
+
84
+ static Item = MenuItem
85
+ static Group = MenuItemGroup
86
+ static Separator = MenuItemSeparator
87
+
88
+ state = { hasFocus: false }
89
+ _rootNode = null
90
+ _menuItems: MenuItem[] = []
91
+ _popover: Popover | null = null
92
+ _trigger: MenuItem | (React.ReactInstance & { focus?: () => void }) | null =
93
+ null
94
+ _menu: HTMLElement | null = null
95
+ _labelId = this.props.deterministicId!('Menu__label')
96
+
97
+ _activeSubMenu?: Menu | null
98
+ _id: string
99
+
100
+ ref: Element | null = null
101
+
102
+ handleRef = (el: HTMLElement | null) => {
103
+ const { menuRef } = this.props
104
+ this._menu = el
105
+ if (typeof menuRef === 'function') {
106
+ menuRef(el)
107
+ }
108
+ // If there is no trigger `<ul>` is the ref, otherwise the trigger
109
+ if (!this.props.trigger) {
110
+ this.ref = el
111
+ }
112
+ }
113
+
114
+ constructor(props: MenuProps) {
115
+ super(props)
116
+
117
+ this._id = this.props.id || props.deterministicId!()
118
+ }
119
+ componentDidMount() {
120
+ this.props.makeStyles?.()
121
+ }
122
+
123
+ componentDidUpdate() {
124
+ this.props.makeStyles?.()
125
+ }
126
+
127
+ static contextType = MenuContext
128
+
129
+ registerMenuItem = (item: MenuItem) => {
130
+ this._menuItems.push(item)
131
+ }
132
+
133
+ removeMenuItem = (item: MenuItem) => {
134
+ const index = this.getMenuItemIndex(item)
135
+ error(index >= 0, '[Menu] Could not find registered menu item.')
136
+ if (index >= 0) {
137
+ this._menuItems.splice(index, 1)
138
+ }
139
+ }
140
+
141
+ get menuItems() {
142
+ return this._menuItems
143
+ }
144
+
145
+ getMenuItemIndex = (item: MenuItem) => {
146
+ return this._menuItems.findIndex((i) => i === item)
147
+ }
148
+
149
+ handleTriggerKeyDown = (event: React.KeyboardEvent) => {
150
+ if (this.props.type === 'flyout' && event.keyCode === keycode.codes.right) {
151
+ event.persist()
152
+ this.show(event)
153
+ }
154
+ }
155
+
156
+ handleTriggerMouseOver = (event: React.MouseEvent) => {
157
+ if (this.props.type === 'flyout') {
158
+ this.show(event)
159
+ }
160
+ }
161
+
162
+ handleToggle = (shown: boolean) => {
163
+ if (typeof this.props.onToggle === 'function') {
164
+ this.props.onToggle(shown, this)
165
+ }
166
+ }
167
+
168
+ handleMenuKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
169
+ const key = event && event.keyCode
170
+ const { down, up, tab, left } = keycode.codes
171
+ const pgdn = keycode.codes['page down']
172
+ const pgup = keycode.codes['page up']
173
+
174
+ if (key === down || key === pgdn) {
175
+ event.preventDefault()
176
+ event.stopPropagation()
177
+ this.moveFocus(1)
178
+ this.hideActiveSubMenu(event)
179
+ } else if (key === up || key === pgup) {
180
+ event.preventDefault()
181
+ event.stopPropagation()
182
+ this.moveFocus(-1)
183
+ this.hideActiveSubMenu(event)
184
+ } else if (key === tab || key === left) {
185
+ event.persist()
186
+ this.hide(event)
187
+ }
188
+
189
+ if (typeof this.props.onKeyDown === 'function') {
190
+ this.props.onKeyDown(event)
191
+ }
192
+ }
193
+
194
+ handleMenuItemSelect: MenuProps['onSelect'] = (
195
+ event,
196
+ value,
197
+ selected,
198
+ item
199
+ ) => {
200
+ if (this.props.shouldHideOnSelect) {
201
+ this.hide(event)
202
+ }
203
+
204
+ if (typeof this.props.onSelect === 'function') {
205
+ this.props.onSelect(event, value, selected, item)
206
+ }
207
+ }
208
+
209
+ handleMenuItemFocus = () => {
210
+ this.setState({ hasFocus: true })
211
+ }
212
+
213
+ handleMenuItemBlur = () => {
214
+ this.setState({ hasFocus: this.focusedIndex >= 0 })
215
+ }
216
+
217
+ handleMenuItemMouseOver: MenuItemProps['onMouseOver'] = (event, menuItem) => {
218
+ if (this._activeSubMenu && menuItem !== this._activeSubMenu._trigger) {
219
+ this.hideActiveSubMenu(event)
220
+ }
221
+ }
222
+
223
+ hideActiveSubMenu = (event: React.MouseEvent | React.KeyboardEvent) => {
224
+ if (this._activeSubMenu) {
225
+ this._activeSubMenu.hide(event)
226
+ this._activeSubMenu = null
227
+ }
228
+ }
229
+
230
+ handleSubMenuToggle: MenuProps['onToggle'] = (shown, subMenu) => {
231
+ if (shown) {
232
+ this._activeSubMenu = subMenu
233
+ }
234
+ }
235
+
236
+ handleSubMenuDismiss = (
237
+ event: React.UIEvent | React.FocusEvent,
238
+ documentClick: boolean
239
+ ) => {
240
+ if (
241
+ (event && (event as React.KeyboardEvent).keyCode === keycode.codes.tab) ||
242
+ documentClick
243
+ ) {
244
+ this.hide(event)
245
+ }
246
+ }
247
+
248
+ hide = (event: React.UIEvent | React.FocusEvent) => {
249
+ if (this._popover) {
250
+ this._popover.hide(event)
251
+ }
252
+ }
253
+
254
+ show = (event: React.MouseEvent | React.KeyboardEvent) => {
255
+ if (this._popover) {
256
+ this._popover.show(event)
257
+ }
258
+ }
259
+
260
+ focus() {
261
+ if (this.shown) {
262
+ error(!!this._menu?.focus, '[Menu] Could not focus the menu.')
263
+ this._menu!.focus()
264
+ } else {
265
+ error(!!this._trigger?.focus, '[Menu] Could not focus the trigger.')
266
+ this._trigger!.focus!()
267
+ }
268
+ }
269
+
270
+ focused() {
271
+ if (this.shown) {
272
+ return containsActiveElement(this._menu) || this.state.hasFocus
273
+ } else {
274
+ return containsActiveElement(this._trigger)
275
+ }
276
+ }
277
+
278
+ get focusedIndex() {
279
+ return this.menuItems.findIndex((item) => {
280
+ return item && item.focused === true
281
+ })
282
+ }
283
+
284
+ moveFocus(step: number) {
285
+ const count = this.menuItems ? this.menuItems.length : 0
286
+
287
+ if (count <= 0) {
288
+ return
289
+ }
290
+
291
+ const current = this.focusedIndex < 0 && step < 0 ? 0 : this.focusedIndex
292
+
293
+ const nextItem = this.menuItems[(current + count + step) % count]
294
+
295
+ error(
296
+ typeof nextItem !== 'undefined' && typeof nextItem.focus !== 'undefined',
297
+ '[Menu] Could not focus next menu item.'
298
+ )
299
+
300
+ nextItem.focus()
301
+ }
302
+
303
+ get shown() {
304
+ return this._popover ? this._popover.shown : true
305
+ }
306
+
307
+ renderChildren() {
308
+ const { children, disabled } = this.props
309
+
310
+ let count = 0
311
+
312
+ return Children.map(
313
+ children as
314
+ | MenuChild
315
+ | MenuItemChild
316
+ | MenuGroupChild
317
+ | MenuSeparatorChild,
318
+ (child) => {
319
+ if (
320
+ !matchComponentTypes(child, [
321
+ 'MenuItemSeparator',
322
+ 'MenuItem',
323
+ 'MenuItemGroup',
324
+ 'Menu'
325
+ ])
326
+ ) {
327
+ return
328
+ }
329
+
330
+ count += 1
331
+
332
+ const isTabbable = !this.state.hasFocus && count === 1
333
+
334
+ if (
335
+ matchComponentTypes<MenuSeparatorChild>(child, ['MenuItemSeparator'])
336
+ ) {
337
+ return child
338
+ }
339
+
340
+ const menuItemChild = child
341
+
342
+ const controls =
343
+ menuItemChild.props['aria-controls'] ||
344
+ menuItemChild.props.controls ||
345
+ this.props['aria-controls'] ||
346
+ this.props.controls
347
+
348
+ if (matchComponentTypes<MenuItemChild>(child, ['MenuItem'])) {
349
+ return safeCloneElement(child, {
350
+ controls,
351
+ children: child.props.children,
352
+ disabled: disabled || child.props.disabled,
353
+ onFocus: this.handleMenuItemFocus,
354
+ onBlur: this.handleMenuItemBlur,
355
+ onSelect: this.handleMenuItemSelect,
356
+ onMouseOver: this.handleMenuItemMouseOver,
357
+ tabIndex: isTabbable ? 0 : -1
358
+ })
359
+ }
360
+
361
+ if (matchComponentTypes<MenuGroupChild>(child, ['MenuItemGroup'])) {
362
+ return safeCloneElement(child, {
363
+ label: child.props.label,
364
+ controls,
365
+ disabled: disabled || child.props.disabled,
366
+ onFocus: this.handleMenuItemFocus,
367
+ onBlur: this.handleMenuItemBlur,
368
+ onSelect: this.handleMenuItemSelect,
369
+ onMouseOver: this.handleMenuItemMouseOver,
370
+ isTabbable
371
+ })
372
+ }
373
+
374
+ if (matchComponentTypes(child, ['Menu'])) {
375
+ const submenuDisabled = disabled || child.props.disabled
376
+
377
+ return safeCloneElement(child, {
378
+ type: 'flyout',
379
+ controls,
380
+ disabled: submenuDisabled,
381
+ onSelect: this.handleMenuItemSelect,
382
+ placement: 'end top',
383
+ offsetX: -5,
384
+ offsetY: 5,
385
+ withArrow: false,
386
+ onToggle: this.handleSubMenuToggle,
387
+ onDismiss: this.handleSubMenuDismiss,
388
+ trigger: (
389
+ <MenuItem
390
+ onMouseOver={this.handleMenuItemMouseOver}
391
+ onFocus={this.handleMenuItemFocus}
392
+ onBlur={this.handleMenuItemBlur}
393
+ tabIndex={isTabbable ? 0 : -1}
394
+ type="flyout"
395
+ disabled={submenuDisabled}
396
+ renderLabelInfo={child.props.renderLabelInfo}
397
+ >
398
+ {child.props.title || child.props.label}
399
+ </MenuItem>
400
+ )
401
+ })
402
+ }
403
+ return
404
+ }
405
+ )
406
+ }
407
+
408
+ renderMenu() {
409
+ const { disabled, label, trigger, onKeyUp } = this.props
410
+
411
+ const labelledBy = this.props['aria-labelledby']
412
+ const controls = this.props['aria-controls']
413
+
414
+ return (
415
+ <MenuContext.Provider
416
+ value={{
417
+ removeMenuItem: this.removeMenuItem,
418
+ registerMenuItem: this.registerMenuItem
419
+ }}
420
+ >
421
+ <div
422
+ role="menu"
423
+ aria-label={label}
424
+ tabIndex={0}
425
+ css={this.props.styles?.menu}
426
+ aria-labelledby={labelledBy || (trigger ? this._labelId : undefined)}
427
+ aria-controls={controls}
428
+ aria-disabled={disabled ? 'true' : undefined}
429
+ onKeyDown={this.handleMenuKeyDown}
430
+ onKeyUp={onKeyUp}
431
+ ref={this.handleRef}
432
+ data-cid="Menu"
433
+ >
434
+ {this.renderChildren()}
435
+ </div>
436
+ </MenuContext.Provider>
437
+ )
438
+ }
439
+
440
+ render() {
441
+ const {
442
+ show,
443
+ defaultShow,
444
+ placement,
445
+ withArrow,
446
+ trigger,
447
+ mountNode,
448
+ popoverRef,
449
+ disabled,
450
+ onDismiss,
451
+ onFocus,
452
+ onMouseOver,
453
+ positionContainerDisplay,
454
+ offsetX,
455
+ offsetY
456
+ } = this.props
457
+
458
+ return trigger ? (
459
+ <Popover
460
+ data-cid="Menu"
461
+ isShowingContent={show}
462
+ defaultIsShowingContent={defaultShow}
463
+ onHideContent={(event, { documentClick }) => {
464
+ if (typeof onDismiss === 'function') {
465
+ onDismiss(event, documentClick)
466
+ }
467
+ this.handleToggle(false)
468
+ }}
469
+ onShowContent={() => this.handleToggle(true)}
470
+ mountNode={mountNode}
471
+ placement={placement}
472
+ withArrow={withArrow}
473
+ id={this._id}
474
+ on={['click']}
475
+ shouldContainFocus
476
+ shouldReturnFocus
477
+ onFocus={onFocus}
478
+ onMouseOver={onMouseOver}
479
+ positionContainerDisplay={positionContainerDisplay}
480
+ offsetX={offsetX}
481
+ offsetY={offsetY}
482
+ elementRef={(element) => {
483
+ this.ref = element
484
+ }}
485
+ ref={(el) => {
486
+ this._popover = el
487
+ if (typeof popoverRef === 'function') {
488
+ popoverRef(el)
489
+ }
490
+ }}
491
+ renderTrigger={safeCloneElement(trigger as ReactElement, {
492
+ ref: (el: (React.ReactInstance & { ref?: Element }) | null) => {
493
+ this._trigger = el
494
+ },
495
+ 'aria-haspopup': true,
496
+ id: this._labelId,
497
+ onMouseOver: this.handleTriggerMouseOver,
498
+ onKeyDown: this.handleTriggerKeyDown,
499
+ disabled: (trigger as ReactElement<any>).props.disabled || disabled
500
+ })}
501
+ defaultFocusElement={() =>
502
+ this._popover?._contentElement?.querySelector('[class$="menuItem"]')
503
+ }
504
+ >
505
+ {this.renderMenu()}
506
+ </Popover>
507
+ ) : (
508
+ this.renderMenu()
509
+ )
510
+ }
511
+ }
512
+
513
+ export default Menu
514
+ export { Menu, MenuItem, MenuItemGroup, MenuItemSeparator }