@carbon/react 1.77.0 → 1.78.0-rc.0

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 (126) hide show
  1. package/.playwright/INTERNAL_AVT_REPORT_DO_NOT_USE.json +945 -858
  2. package/es/components/Accordion/AccordionItem.js +1 -1
  3. package/es/components/BadgeIndicator/index.d.ts +23 -0
  4. package/es/components/BadgeIndicator/index.js +45 -0
  5. package/es/components/Breadcrumb/Breadcrumb.d.ts +5 -0
  6. package/es/components/Breadcrumb/Breadcrumb.js +8 -2
  7. package/es/components/Button/Button.d.ts +9 -0
  8. package/es/components/Button/Button.js +13 -0
  9. package/es/components/ComboBox/ComboBox.js +13 -9
  10. package/es/components/Dropdown/Dropdown.d.ts +1 -1
  11. package/es/components/Dropdown/Dropdown.js +3 -2
  12. package/es/components/FileUploader/FileUploader.js +1 -1
  13. package/es/components/FileUploader/FileUploaderItem.js +1 -1
  14. package/es/components/IconButton/index.d.ts +7 -1
  15. package/es/components/IconButton/index.js +18 -2
  16. package/es/components/IconIndicator/index.d.ts +1 -1
  17. package/es/components/InlineLoading/InlineLoading.js +2 -5
  18. package/es/components/Menu/MenuItem.js +1 -1
  19. package/es/components/Modal/Modal.d.ts +3 -3
  20. package/es/components/Modal/Modal.js +9 -3
  21. package/es/components/Modal/next/index.d.ts +1 -5
  22. package/es/components/MultiSelect/MultiSelect.js +3 -2
  23. package/es/components/MultiSelect/MultiSelectPropTypes.d.ts +5 -2
  24. package/es/components/MultiSelect/tools/sorting.js +8 -7
  25. package/es/components/Notification/Notification.js +1 -1
  26. package/es/components/NumberInput/NumberInput.js +12 -12
  27. package/es/components/OverflowMenu/OverflowMenu.d.ts +8 -20
  28. package/es/components/OverflowMenu/OverflowMenu.js +7 -9
  29. package/es/components/OverflowMenuItem/OverflowMenuItem.js +1 -1
  30. package/es/components/ProgressIndicator/ProgressIndicator.js +1 -1
  31. package/es/components/RadioTile/RadioTile.js +1 -1
  32. package/es/components/Select/Select.js +11 -2
  33. package/es/components/ShapeIndicator/index.d.ts +29 -0
  34. package/es/components/ShapeIndicator/index.js +92 -0
  35. package/es/components/Tabs/Tabs.d.ts +6 -4
  36. package/es/components/Tabs/Tabs.js +27 -8
  37. package/es/components/Tag/OperationalTag.d.ts +1 -36
  38. package/es/components/Tag/OperationalTag.js +7 -5
  39. package/es/components/Text/Text.d.ts +11 -9
  40. package/es/components/Text/Text.js +6 -6
  41. package/es/components/Text/TextDirection.d.ts +7 -9
  42. package/es/components/Text/TextDirection.js +5 -2
  43. package/es/components/Text/TextDirectionContext.d.ts +14 -0
  44. package/es/components/Text/TextDirectionContext.js +6 -2
  45. package/es/components/Text/createTextComponent.d.ts +5 -5
  46. package/es/components/Text/createTextComponent.js +5 -4
  47. package/es/components/Text/index.d.ts +6 -7
  48. package/es/components/Text/index.js +3 -2
  49. package/es/components/Tile/Tile.d.ts +6 -0
  50. package/es/components/Tile/Tile.js +5 -9
  51. package/es/components/Toggletip/index.d.ts +3 -2
  52. package/es/components/Tooltip/DefinitionTooltip.d.ts +4 -0
  53. package/es/components/Tooltip/DefinitionTooltip.js +7 -1
  54. package/es/components/Tooltip/Tooltip.d.ts +5 -66
  55. package/es/components/Tooltip/Tooltip.js +26 -26
  56. package/es/components/UIShell/HeaderGlobalAction.d.ts +9 -0
  57. package/es/components/UIShell/HeaderGlobalAction.js +16 -1
  58. package/es/index.d.ts +4 -1
  59. package/es/index.js +2 -1
  60. package/es/internal/FloatingMenu.d.ts +83 -0
  61. package/es/internal/FloatingMenu.js +216 -408
  62. package/es/internal/wrapFocus.js +1 -1
  63. package/lib/components/Accordion/AccordionItem.js +1 -1
  64. package/lib/components/BadgeIndicator/index.d.ts +23 -0
  65. package/lib/components/BadgeIndicator/index.js +56 -0
  66. package/lib/components/Breadcrumb/Breadcrumb.d.ts +5 -0
  67. package/lib/components/Breadcrumb/Breadcrumb.js +8 -2
  68. package/lib/components/Button/Button.d.ts +9 -0
  69. package/lib/components/Button/Button.js +13 -0
  70. package/lib/components/ComboBox/ComboBox.js +13 -9
  71. package/lib/components/Dropdown/Dropdown.d.ts +1 -1
  72. package/lib/components/Dropdown/Dropdown.js +2 -1
  73. package/lib/components/FileUploader/FileUploader.js +1 -1
  74. package/lib/components/FileUploader/FileUploaderItem.js +1 -1
  75. package/lib/components/IconButton/index.d.ts +7 -1
  76. package/lib/components/IconButton/index.js +18 -2
  77. package/lib/components/IconIndicator/index.d.ts +1 -1
  78. package/lib/components/InlineLoading/InlineLoading.js +2 -5
  79. package/lib/components/Menu/MenuItem.js +1 -1
  80. package/lib/components/Modal/Modal.d.ts +3 -3
  81. package/lib/components/Modal/Modal.js +9 -3
  82. package/lib/components/Modal/next/index.d.ts +1 -5
  83. package/lib/components/MultiSelect/MultiSelect.js +2 -1
  84. package/lib/components/MultiSelect/MultiSelectPropTypes.d.ts +5 -2
  85. package/lib/components/MultiSelect/tools/sorting.js +8 -7
  86. package/lib/components/Notification/Notification.js +1 -1
  87. package/lib/components/NumberInput/NumberInput.js +12 -12
  88. package/lib/components/OverflowMenu/OverflowMenu.d.ts +8 -20
  89. package/lib/components/OverflowMenu/OverflowMenu.js +7 -9
  90. package/lib/components/OverflowMenuItem/OverflowMenuItem.js +1 -1
  91. package/lib/components/ProgressIndicator/ProgressIndicator.js +1 -1
  92. package/lib/components/RadioTile/RadioTile.js +1 -1
  93. package/lib/components/Select/Select.js +11 -2
  94. package/lib/components/ShapeIndicator/index.d.ts +29 -0
  95. package/lib/components/ShapeIndicator/index.js +104 -0
  96. package/lib/components/Tabs/Tabs.d.ts +6 -4
  97. package/lib/components/Tabs/Tabs.js +42 -23
  98. package/lib/components/Tag/OperationalTag.d.ts +1 -36
  99. package/lib/components/Tag/OperationalTag.js +6 -4
  100. package/lib/components/Text/Text.d.ts +11 -9
  101. package/lib/components/Text/Text.js +5 -5
  102. package/lib/components/Text/TextDirection.d.ts +7 -9
  103. package/lib/components/Text/TextDirection.js +5 -2
  104. package/lib/components/Text/TextDirectionContext.d.ts +14 -0
  105. package/lib/components/Text/TextDirectionContext.js +6 -2
  106. package/lib/components/Text/createTextComponent.d.ts +5 -5
  107. package/lib/components/Text/createTextComponent.js +5 -4
  108. package/lib/components/Text/index.d.ts +6 -7
  109. package/lib/components/Text/index.js +4 -3
  110. package/lib/components/Tile/Tile.d.ts +6 -0
  111. package/lib/components/Tile/Tile.js +5 -9
  112. package/lib/components/Toggletip/index.d.ts +3 -2
  113. package/lib/components/Tooltip/DefinitionTooltip.d.ts +4 -0
  114. package/lib/components/Tooltip/DefinitionTooltip.js +7 -1
  115. package/lib/components/Tooltip/Tooltip.d.ts +5 -66
  116. package/lib/components/Tooltip/Tooltip.js +26 -26
  117. package/lib/components/UIShell/HeaderGlobalAction.d.ts +9 -0
  118. package/lib/components/UIShell/HeaderGlobalAction.js +16 -1
  119. package/lib/index.d.ts +4 -1
  120. package/lib/index.js +42 -40
  121. package/lib/internal/FloatingMenu.d.ts +83 -0
  122. package/lib/internal/FloatingMenu.js +216 -409
  123. package/lib/internal/wrapFocus.js +1 -1
  124. package/package.json +5 -5
  125. package/scss/components/badge-indicator/_badge-indicator.scss +9 -0
  126. package/scss/components/badge-indicator/_index.scss +9 -0
@@ -5,93 +5,34 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- import { defineProperty as _defineProperty } from '../_virtual/_rollupPluginBabelHelpers.js';
9
- import PropTypes from 'prop-types';
10
- import React__default from 'react';
8
+ import React__default, { useContext, useState, useRef, useCallback, useEffect, cloneElement } from 'react';
9
+ import * as FeatureFlags from '@carbon/feature-flags';
11
10
  import ReactDOM from 'react-dom';
12
11
  import window from 'window-or-global';
13
12
  import OptimizedResize from './OptimizedResize.js';
14
13
  import { selectorTabbable, selectorFocusable } from './keyboard/navigation.js';
14
+ import { PrefixContext } from './usePrefix.js';
15
15
  import { warning } from './warning.js';
16
16
  import wrapFocus, { wrapFocusWithoutSentinels } from './wrapFocus.js';
17
- import { PrefixContext } from './usePrefix.js';
18
- import * as FeatureFlags from '@carbon/feature-flags';
19
17
  import { match } from './keyboard/match.js';
20
18
  import { Tab } from './keyboard/keys.js';
21
19
 
22
- /**
23
- * The structure for the position of floating menu.
24
- * @typedef {object} FloatingMenu~position
25
- * @property {number} left The left position.
26
- * @property {number} top The top position.
27
- * @property {number} right The right position.
28
- * @property {number} bottom The bottom position.
29
- */
30
-
31
- /**
32
- * The structure for the size of floating menu.
33
- * @typedef {object} FloatingMenu~size
34
- * @property {number} width The width.
35
- * @property {number} height The height.
36
- */
37
-
38
- /**
39
- * The structure for the position offset of floating menu.
40
- * @typedef {object} FloatingMenu~offset
41
- * @property {number} top The top position.
42
- * @property {number} left The left position.
43
- */
44
-
45
- /**
46
- * The structure for the target container.
47
- * @typedef {object} FloatingMenu~container
48
- * @property {DOMRect} rect Return of element.getBoundingClientRect()
49
- * @property {string} position Position style (static, absolute, relative...)
50
- */
51
-
52
20
  const DIRECTION_LEFT = 'left';
53
21
  const DIRECTION_TOP = 'top';
54
22
  const DIRECTION_RIGHT = 'right';
55
23
  const DIRECTION_BOTTOM = 'bottom';
56
-
57
- /**
58
- * @param {FloatingMenu~offset} [oldMenuOffset={}] The old value.
59
- * @param {FloatingMenu~offset} [menuOffset={}] The new value.
60
- * @returns `true` if the parent component wants to change in the adjustment of the floating menu position.
61
- * @private
62
- */
63
- const hasChangeInOffset = function () {
64
- let oldMenuOffset = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
65
- let menuOffset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
66
- if (typeof oldMenuOffset !== typeof menuOffset) {
67
- return true;
68
- }
69
- if (Object(menuOffset) === menuOffset && typeof menuOffset !== 'function') {
70
- return oldMenuOffset.top !== menuOffset.top || oldMenuOffset.left !== menuOffset.left;
71
- }
72
- return oldMenuOffset !== menuOffset;
73
- };
74
-
75
24
  /**
76
- * @param {object} params The parameters.
77
- * @param {FloatingMenu~size} params.menuSize The size of the menu.
78
- * @param {FloatingMenu~position} params.refPosition The position of the triggering element.
79
- * @param {FloatingMenu~offset} [params.offset={ left: 0, top: 0 }] The position offset of the menu.
80
- * @param {string} [params.direction=bottom] The menu direction.
81
- * @param {number} [params.scrollX=0] The scroll position of the viewport.
82
- * @param {number} [params.scrollY=0] The scroll position of the viewport.
83
- * @param {FloatingMenu~container} [params.container] The size and position type of target element.
84
- * @returns {FloatingMenu~offset} The position of the menu, relative to the top-left corner of the viewport.
85
- * @private
25
+ * Computes the floating menu's position based on the menu size, trigger element
26
+ * position, offset, direction, and container.
86
27
  */
87
28
  const getFloatingPosition = _ref => {
88
29
  let {
89
30
  menuSize,
90
- refPosition = {},
91
- offset = {},
92
- direction = DIRECTION_BOTTOM,
93
- scrollX: pageXOffset = 0,
94
- scrollY: pageYOffset = 0,
31
+ refPosition,
32
+ offset,
33
+ direction,
34
+ scrollX,
35
+ scrollY,
95
36
  container
96
37
  } = _ref;
97
38
  const {
@@ -100,8 +41,8 @@ const getFloatingPosition = _ref => {
100
41
  right: refRight = 0,
101
42
  bottom: refBottom = 0
102
43
  } = refPosition;
103
- const scrollX = container.position !== 'static' ? 0 : pageXOffset;
104
- const scrollY = container.position !== 'static' ? 0 : pageYOffset;
44
+ const effectiveScrollX = container.position !== 'static' ? 0 : scrollX;
45
+ const effectiveScrollY = container.position !== 'static' ? 0 : scrollY;
105
46
  const relativeDiff = {
106
47
  top: container.position !== 'static' ? container.rect.top : 0,
107
48
  left: container.position !== 'static' ? container.rect.left : 0
@@ -116,366 +57,233 @@ const getFloatingPosition = _ref => {
116
57
  } = offset;
117
58
  const refCenterHorizontal = (refLeft + refRight) / 2;
118
59
  const refCenterVertical = (refTop + refBottom) / 2;
119
- return {
60
+ const positions = {
120
61
  [DIRECTION_LEFT]: () => ({
121
- left: refLeft - width + scrollX - left - relativeDiff.left,
122
- top: refCenterVertical - height / 2 + scrollY + top - 9 - relativeDiff.top
62
+ left: refLeft - width + effectiveScrollX - left - relativeDiff.left,
63
+ top: refCenterVertical - height / 2 + effectiveScrollY + top - 9 - relativeDiff.top
123
64
  }),
124
65
  [DIRECTION_TOP]: () => ({
125
- left: refCenterHorizontal - width / 2 + scrollX + left - relativeDiff.left,
126
- top: refTop - height + scrollY - top - relativeDiff.top
66
+ left: refCenterHorizontal - width / 2 + effectiveScrollX + left - relativeDiff.left,
67
+ top: refTop - height + effectiveScrollY - top - relativeDiff.top
127
68
  }),
128
69
  [DIRECTION_RIGHT]: () => ({
129
- left: refRight + scrollX + left - relativeDiff.left,
130
- top: refCenterVertical - height / 2 + scrollY + top + 3 - relativeDiff.top
70
+ left: refRight + effectiveScrollX + left - relativeDiff.left,
71
+ top: refCenterVertical - height / 2 + effectiveScrollY + top + 3 - relativeDiff.top
131
72
  }),
132
73
  [DIRECTION_BOTTOM]: () => ({
133
- left: refCenterHorizontal - width / 2 + scrollX + left - relativeDiff.left,
134
- top: refBottom + scrollY + top - relativeDiff.top
74
+ left: refCenterHorizontal - width / 2 + effectiveScrollX + left - relativeDiff.left,
75
+ top: refBottom + effectiveScrollY + top - relativeDiff.top
135
76
  })
136
- }[direction]();
77
+ };
78
+ return positions[direction]();
137
79
  };
138
-
139
- /**
140
- * A menu that is detached from the triggering element.
141
- * Useful when the container of the triggering element cannot have `overflow:visible` style, etc.
142
- */
143
- class FloatingMenu extends React__default.Component {
144
- constructor() {
145
- var _this;
146
- super(...arguments);
147
- _this = this;
148
- // `true` if the menu body is mounted and calculation of the position is in progress.
149
- _defineProperty(this, "_placeInProgress", false);
150
- _defineProperty(this, "state", {
151
- /**
152
- * The position of the menu, relative to the top-left corner of the viewport.
153
- * @type {FloatingMenu~offset}
154
- */
155
- floatingPosition: undefined
156
- });
157
- /**
158
- * The cached reference to the menu container.
159
- * Only used if React portal API is not available.
160
- * @type {Element}
161
- * @private
162
- */
163
- _defineProperty(this, "_menuContainer", null);
164
- /**
165
- * The cached reference to the menu body.
166
- * The reference is set via callback ref instead of object ref,
167
- * in order to hook the event when the element ref gets available,
168
- * which can be at a different timing from `cDM()`, presumably with SSR scenario.
169
- * @type {Element}
170
- * @private
171
- */
172
- _defineProperty(this, "_menuBody", null);
173
- /**
174
- * Focus sentinel refs for focus trap behavior
175
- */
176
- _defineProperty(this, "startSentinel", /*#__PURE__*/React__default.createRef());
177
- _defineProperty(this, "endSentinel", /*#__PURE__*/React__default.createRef());
178
- /**
179
- * Calculates the position in the viewport of floating menu,
180
- * once this component is mounted or updated upon change in the following props:
181
- *
182
- * * `menuOffset` (The adjustment that should be applied to the calculated floating menu's position)
183
- * * `menuDirection` (Where the floating menu menu should be placed relative to the trigger button)
184
- *
185
- * @private
186
- */
187
- _defineProperty(this, "_updateMenuSize", function () {
188
- let prevProps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
189
- let isAdjustment = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
190
- const menuBody = _this._menuBody;
80
+ const FloatingMenu = _ref2 => {
81
+ let {
82
+ children,
83
+ flipped,
84
+ focusTrap,
85
+ menuDirection = DIRECTION_BOTTOM,
86
+ menuOffset = {},
87
+ menuRef: externalMenuRef,
88
+ onPlace,
89
+ selectorPrimaryFocus,
90
+ styles,
91
+ target = () => document.body,
92
+ triggerRef,
93
+ updateOrientation
94
+ } = _ref2;
95
+ const prefix = useContext(PrefixContext);
96
+ const [floatingPosition, setFloatingPosition] = useState(undefined);
97
+ const menuBodyRef = useRef(null);
98
+ const startSentinelRef = useRef(null);
99
+ const endSentinelRef = useRef(null);
100
+ const placeInProgressRef = useRef(false);
101
+ const updateMenuPosition = useCallback(isAdjustment => {
102
+ const menuBody = menuBodyRef.current;
103
+ if (!menuBody) {
191
104
  process.env.NODE_ENV !== "production" ? warning(menuBody, 'The DOM node for menu body for calculating its position is not available. Skipping...') : void 0;
192
- if (!menuBody) {
193
- return;
194
- }
195
- const {
196
- menuOffset: oldMenuOffset = {},
197
- menuDirection: oldMenuDirection
198
- } = prevProps;
199
- const {
200
- menuOffset = {},
201
- menuDirection = DIRECTION_BOTTOM
202
- } = _this.props;
203
- if (hasChangeInOffset(oldMenuOffset, menuOffset) || oldMenuDirection !== menuDirection || isAdjustment) {
204
- const {
205
- flipped,
206
- triggerRef,
207
- updateOrientation = null
208
- } = _this.props;
209
- const {
210
- current: triggerEl
211
- } = triggerRef;
212
- const menuSize = menuBody.getBoundingClientRect();
213
- const refPosition = triggerEl && triggerEl.getBoundingClientRect();
214
- const offset = typeof menuOffset !== 'function' ? menuOffset : menuOffset(menuBody, menuDirection, triggerEl, flipped);
215
-
216
- // Optional function to allow parent component to check
217
- // if the orientation needs to be changed based on params
218
- if (updateOrientation) {
219
- updateOrientation({
220
- menuSize,
221
- refPosition,
222
- direction: menuDirection,
223
- offset,
224
- scrollX: window.pageXOffset,
225
- scrollY: window.pageYOffset,
226
- container: {
227
- rect: _this.props.target().getBoundingClientRect(),
228
- position: getComputedStyle(_this.props.target()).position
229
- }
230
- });
231
- }
232
- // Skips if either in the following condition:
233
- // a) Menu body has `display:none`
234
- // b) `menuOffset` as a callback returns `undefined` (The callback saw that it couldn't calculate the value)
235
- if (menuSize.width > 0 && menuSize.height > 0 || !offset) {
236
- _this.setState({
237
- floatingPosition: getFloatingPosition({
238
- menuSize,
239
- refPosition,
240
- direction: menuDirection,
241
- offset,
242
- scrollX: window.pageXOffset,
243
- scrollY: window.pageYOffset,
244
- container: {
245
- rect: _this.props.target().getBoundingClientRect(),
246
- position: getComputedStyle(_this.props.target()).position
247
- }
248
- })
249
- }, () => {
250
- if (!isAdjustment) {
251
- const newMenuSize = menuBody.getBoundingClientRect();
252
- if (newMenuSize !== menuSize) {
253
- _this._updateMenuSize(_this.props, true);
254
- }
255
- }
256
- });
105
+ return;
106
+ }
107
+ const triggerEl = triggerRef.current;
108
+ const menuSize = menuBody.getBoundingClientRect();
109
+ const refPosition = triggerEl ? triggerEl.getBoundingClientRect() : undefined;
110
+ const offsetValue = typeof menuOffset === 'function' ? menuOffset(menuBody, menuDirection, triggerEl, flipped) : menuOffset;
111
+ if (updateOrientation) {
112
+ updateOrientation({
113
+ menuSize,
114
+ refPosition,
115
+ direction: menuDirection,
116
+ offset: offsetValue,
117
+ scrollX: window.pageXOffset,
118
+ scrollY: window.pageYOffset,
119
+ container: {
120
+ rect: target().getBoundingClientRect(),
121
+ position: getComputedStyle(target()).position
257
122
  }
258
- }
259
- });
260
- /**
261
- * Set focus on floating menu content after menu placement.
262
- * @param {Element} menuBody The DOM element of the menu body.
263
- * @private
264
- */
265
- _defineProperty(this, "_focusMenuContent", menuBody => {
266
- const primaryFocusNode = menuBody.querySelector(this.props.selectorPrimaryFocus || null);
267
- const tabbableNode = menuBody.querySelector(selectorTabbable);
268
- const focusableNode = menuBody.querySelector(selectorFocusable);
269
- const focusTarget = primaryFocusNode ||
270
- // User defined focusable node
271
- tabbableNode ||
272
- // First sequentially focusable node
273
- focusableNode ||
274
- // First programmatic focusable node
275
- menuBody;
276
- focusTarget.focus();
277
- if (focusTarget === menuBody && process.env.NODE_ENV !== "production") {
278
- process.env.NODE_ENV !== "production" ? warning(focusableNode === null, 'Floating Menus must have at least a programmatically focusable child. ' + 'This can be accomplished by adding tabIndex="-1" to the content element.') : void 0;
279
- }
280
- });
281
- /**
282
- * A callback for called when menu body is mounted or unmounted.
283
- * @param {Element} menuBody The menu body being mounted. `null` if the menu body is being unmounted.
284
- */
285
- _defineProperty(this, "_menuRef", menuBody => {
286
- const {
287
- menuRef
288
- } = this.props;
289
- this._placeInProgress = !!menuBody;
290
- menuRef && menuRef(this._menuBody = menuBody);
291
- if (menuBody) {
292
- this._updateMenuSize();
293
- }
294
- });
295
- /**
296
- * @returns The child nodes, with styles containing the floating menu position.
297
- * @private
298
- */
299
- _defineProperty(this, "_getChildrenWithProps", () => {
300
- const {
301
- styles,
302
- children
303
- } = this.props;
304
- const {
305
- floatingPosition: pos
306
- } = this.state;
307
- // If no pos available, we need to hide the element (offscreen to the left)
308
- // This is done so we can measure the content before positioning it correctly.
309
- const positioningStyle = pos ? {
310
- left: `${pos.left}px`,
311
- top: `${pos.top}px`,
312
- right: 'auto'
313
- } : {
314
- visibility: 'hidden',
315
- top: '0px'
316
- };
317
- return /*#__PURE__*/React__default.cloneElement(children, {
318
- ref: this._menuRef,
319
- style: {
320
- ...styles,
321
- ...positioningStyle,
322
- position: 'absolute',
323
- opacity: 1
123
+ });
124
+ }
125
+
126
+ // Only set position if the menu has a valid size or if no offset is provided.
127
+ if (menuSize.width > 0 && menuSize.height > 0 || !offsetValue) {
128
+ const newFloatingPosition = getFloatingPosition({
129
+ menuSize,
130
+ refPosition: refPosition ?? {
131
+ left: 0,
132
+ top: 0,
133
+ right: 0,
134
+ bottom: 0
135
+ },
136
+ offset: offsetValue,
137
+ direction: menuDirection,
138
+ scrollX: window.pageXOffset,
139
+ scrollY: window.pageYOffset,
140
+ container: {
141
+ rect: target().getBoundingClientRect(),
142
+ position: getComputedStyle(target()).position
324
143
  }
325
144
  });
326
- });
327
- /**
328
- * Blur handler for when focus wrap behavior is enabled
329
- * @param {Event} event
330
- * @param {Element} event.target previously focused node
331
- * @param {Element} event.relatedTarget current focused node
332
- */
333
- _defineProperty(this, "handleBlur", _ref2 => {
334
- let {
335
- target: oldActiveNode,
336
- relatedTarget: currentActiveNode
337
- } = _ref2;
338
- if (currentActiveNode && oldActiveNode) {
339
- const {
340
- current: startSentinelNode
341
- } = this.startSentinel;
342
- const {
343
- current: endSentinelNode
344
- } = this.endSentinel;
345
- wrapFocus({
346
- bodyNode: this._menuBody,
347
- startSentinelNode,
348
- endSentinelNode,
349
- currentActiveNode,
350
- oldActiveNode
351
- });
145
+
146
+ // Only update if the position has actually changed.
147
+ if (!floatingPosition || floatingPosition.left !== newFloatingPosition.left || floatingPosition.top !== newFloatingPosition.top) {
148
+ setFloatingPosition(newFloatingPosition);
352
149
  }
353
- });
354
- /**
355
- * Keydown handler for when focus wrap behavior is enabled
356
- * @param {Event} event
357
- */
358
- _defineProperty(this, "handleKeyDown", event => {
359
- if (match(event, Tab) && this._menuBody) {
360
- wrapFocusWithoutSentinels({
361
- containerNode: this._menuBody,
362
- currentActiveNode: event.target,
363
- event
364
- });
150
+
151
+ // Re-check after setting the position if not already adjusting.
152
+ if (!isAdjustment) {
153
+ const newMenuSize = menuBody.getBoundingClientRect();
154
+ // TODO: Was there a bug in the old code? How could one `DOMRect` be
155
+ // compared to another using `!==`?
156
+ if (newMenuSize.width !== menuSize.width || newMenuSize.height !== menuSize.height) {
157
+ updateMenuPosition(true);
158
+ }
365
159
  }
366
- });
367
- }
368
- componentWillUnmount() {
369
- this.hResize.release();
370
- }
371
- componentDidMount() {
372
- this.hResize = OptimizedResize.add(() => {
373
- this._updateMenuSize();
374
- });
375
- }
376
- componentDidUpdate(prevProps) {
377
- this._updateMenuSize(prevProps);
378
- const {
379
- onPlace
380
- } = this.props;
381
- if (this._placeInProgress && this.state.floatingPosition) {
382
- if (this._menuBody && !this._menuBody.contains(document.activeElement)) {
383
- this._focusMenuContent(this._menuBody);
160
+ }
161
+ }, [triggerRef, menuOffset, menuDirection, flipped, target, updateOrientation, floatingPosition]);
162
+ const focusMenuContent = menuBody => {
163
+ const primaryFocusNode = selectorPrimaryFocus ? menuBody.querySelector(selectorPrimaryFocus) : null;
164
+ const tabbableNode = menuBody.querySelector(selectorTabbable);
165
+ const focusableNode = menuBody.querySelector(selectorFocusable);
166
+ const focusTarget = primaryFocusNode ||
167
+ // User defined focusable node
168
+ tabbableNode ||
169
+ // First sequentially focusable node
170
+ focusableNode ||
171
+ // First programmatic focusable node
172
+ menuBody;
173
+ focusTarget.focus();
174
+ if (focusTarget === menuBody && process.env.NODE_ENV !== "production") {
175
+ process.env.NODE_ENV !== "production" ? warning(focusableNode === null, 'Floating Menus must have at least a programmatically focusable child. This can be accomplished by adding tabIndex="-1" to the content element.') : void 0;
176
+ }
177
+ };
178
+ const handleMenuRef = node => {
179
+ menuBodyRef.current = node;
180
+ placeInProgressRef.current = !!node;
181
+ if (externalMenuRef) {
182
+ externalMenuRef(node);
183
+ }
184
+ if (node) {
185
+ updateMenuPosition();
186
+ }
187
+ };
188
+
189
+ // When the menu has been placed, focus the content and call onPlace.
190
+ useEffect(() => {
191
+ if (placeInProgressRef.current && floatingPosition && menuBodyRef.current) {
192
+ if (!menuBodyRef.current.contains(document.activeElement)) {
193
+ focusMenuContent(menuBodyRef.current);
384
194
  }
385
195
  if (typeof onPlace === 'function') {
386
- onPlace(this._menuBody);
387
- this._placeInProgress = false;
196
+ onPlace(menuBodyRef.current);
388
197
  }
198
+ placeInProgressRef.current = false;
389
199
  }
390
- }
391
- render() {
392
- const {
393
- context: prefix
394
- } = this;
395
- const focusTrapWithoutSentinels = FeatureFlags.enabled('enable-experimental-focus-wrap-without-sentinels');
396
- if (typeof document !== 'undefined') {
397
- const {
398
- focusTrap,
399
- target
400
- } = this.props;
401
- return /*#__PURE__*/ReactDOM.createPortal(
402
- /*#__PURE__*/
403
- //eslint-disable-next-line jsx-a11y/no-static-element-interactions
404
- React__default.createElement("div", {
405
- onBlur: focusTrap && !focusTrapWithoutSentinels ? this.handleBlur : () => {},
406
- onKeyDown: focusTrapWithoutSentinels ? this.handleKeyDown : () => {}
407
- }, !focusTrapWithoutSentinels && /*#__PURE__*/React__default.createElement("span", {
408
- ref: this.startSentinel,
409
- tabIndex: "0",
410
- role: "link",
411
- className: `${prefix}--visually-hidden`
412
- }, "Focus sentinel"), this._getChildrenWithProps(), !focusTrapWithoutSentinels && /*#__PURE__*/React__default.createElement("span", {
413
- ref: this.endSentinel,
414
- tabIndex: "0",
415
- role: "link",
416
- className: `${prefix}--visually-hidden`
417
- }, "Focus sentinel")), !target ? document.body : target());
418
- }
419
- return null;
420
- }
421
- }
422
- _defineProperty(FloatingMenu, "contextType", PrefixContext);
423
- _defineProperty(FloatingMenu, "propTypes", {
424
- /**
425
- * Contents to put into the floating menu.
426
- */
427
- children: PropTypes.object,
428
- /**
429
- * `true` if the menu alignment should be flipped.
430
- */
431
- flipped: PropTypes.bool,
432
- /**
433
- * Enable or disable focus trap behavior
434
- */
435
- focusTrap: PropTypes.bool,
436
- /**
437
- * Where to put the tooltip, relative to the trigger button.
438
- */
439
- menuDirection: PropTypes.oneOf([DIRECTION_LEFT, DIRECTION_TOP, DIRECTION_RIGHT, DIRECTION_BOTTOM]),
440
- /**
441
- * The adjustment of the floating menu position, considering the position of dropdown arrow, etc.
442
- */
443
- menuOffset: PropTypes.oneOfType([PropTypes.shape({
444
- top: PropTypes.number,
445
- left: PropTypes.number
446
- }), PropTypes.func]),
447
- /**
448
- * The callback called when the menu body has been mounted to/will be unmounted from the DOM.
449
- */
450
- menuRef: PropTypes.func,
451
- /**
452
- * The callback called when the menu body has been mounted and positioned.
453
- */
454
- onPlace: PropTypes.func,
455
- /**
456
- * Specify a CSS selector that matches the DOM element that should
457
- * be focused when the Modal opens
458
- */
459
- selectorPrimaryFocus: PropTypes.string,
460
- /**
461
- * The additional styles to put to the floating menu.
462
- */
463
- styles: PropTypes.object,
200
+ }, [floatingPosition, onPlace]);
201
+
202
+ // Attach a resize listener.
203
+ useEffect(() => {
204
+ const resizeHandler = OptimizedResize.add(() => {
205
+ updateMenuPosition();
206
+ });
207
+ return () => {
208
+ resizeHandler.release();
209
+ };
210
+ }, [triggerRef, menuOffset, menuDirection, flipped, target, updateOrientation]);
211
+
212
+ // Update menu position when key props change.
213
+ useEffect(() => {
214
+ updateMenuPosition();
215
+ }, [menuOffset, menuDirection, flipped, triggerRef, target, updateOrientation]);
216
+
464
217
  /**
465
- * The query selector indicating where the floating menu body should be placed.
218
+ * Clones the child element to add a `ref` and positioning styles.
466
219
  */
467
- target: PropTypes.func,
220
+ const getChildrenWithProps = () => {
221
+ const pos = floatingPosition;
222
+ const positioningStyle = pos ? {
223
+ left: `${pos.left}px`,
224
+ top: `${pos.top}px`,
225
+ right: 'auto'
226
+ } : {
227
+ visibility: 'hidden',
228
+ top: '0px'
229
+ };
230
+ return /*#__PURE__*/cloneElement(children, {
231
+ ref: handleMenuRef,
232
+ style: {
233
+ ...styles,
234
+ ...positioningStyle,
235
+ position: 'absolute',
236
+ opacity: 1
237
+ }
238
+ });
239
+ };
240
+
468
241
  /**
469
- * The element ref of the tooltip's trigger button.
242
+ * Blur handler used when focus trapping is enabled.
470
243
  */
471
- triggerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({
472
- current: PropTypes.any
473
- })]),
244
+ const handleBlur = event => {
245
+ if (menuBodyRef.current && startSentinelRef.current && endSentinelRef.current) {
246
+ wrapFocus({
247
+ bodyNode: menuBodyRef.current,
248
+ startTrapNode: startSentinelRef.current,
249
+ endTrapNode: endSentinelRef.current,
250
+ currentActiveNode: event.relatedTarget,
251
+ oldActiveNode: event.target
252
+ });
253
+ }
254
+ };
255
+
474
256
  /**
475
- * Optional function to change orientation of tooltip based on parent
257
+ * Keydown handler for focus wrapping when experimental focus trap is enabled.
476
258
  */
477
- updateOrientation: PropTypes.func
478
- });
479
- var FloatingMenu$1 = FloatingMenu;
259
+ const handleKeyDown = event => {
260
+ if (match(event, Tab) && menuBodyRef.current) {
261
+ wrapFocusWithoutSentinels({
262
+ containerNode: menuBodyRef.current,
263
+ currentActiveNode: event.target,
264
+ event
265
+ });
266
+ }
267
+ };
268
+ const focusTrapWithoutSentinels = FeatureFlags.enabled('enable-experimental-focus-wrap-without-sentinels');
269
+ if (typeof document !== 'undefined') {
270
+ const portalTarget = target ? target() : document.body;
271
+ return /*#__PURE__*/ReactDOM.createPortal(/*#__PURE__*/React__default.createElement("div", {
272
+ onBlur: focusTrap && !focusTrapWithoutSentinels ? handleBlur : undefined,
273
+ onKeyDown: focusTrapWithoutSentinels ? handleKeyDown : undefined
274
+ }, !focusTrapWithoutSentinels && /*#__PURE__*/React__default.createElement("span", {
275
+ ref: startSentinelRef,
276
+ tabIndex: 0,
277
+ role: "link",
278
+ className: `${prefix}--visually-hidden`
279
+ }, "Focus sentinel"), getChildrenWithProps(), !focusTrapWithoutSentinels && /*#__PURE__*/React__default.createElement("span", {
280
+ ref: endSentinelRef,
281
+ tabIndex: 0,
282
+ role: "link",
283
+ className: `${prefix}--visually-hidden`
284
+ }, "Focus sentinel")), portalTarget);
285
+ }
286
+ return null;
287
+ };
480
288
 
481
- export { DIRECTION_BOTTOM, DIRECTION_LEFT, DIRECTION_RIGHT, DIRECTION_TOP, FloatingMenu$1 as default };
289
+ export { DIRECTION_BOTTOM, DIRECTION_LEFT, DIRECTION_RIGHT, DIRECTION_TOP, FloatingMenu };