@instructure/ui-modal 11.7.4-snapshot-53 → 11.7.4-snapshot-80

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.
package/CHANGELOG.md CHANGED
@@ -3,9 +3,13 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
- ## [11.7.4-snapshot-53](https://github.com/instructure/instructure-ui/compare/v11.7.3...v11.7.4-snapshot-53) (2026-06-22)
6
+ ## [11.7.4-snapshot-80](https://github.com/instructure/instructure-ui/compare/v11.7.3...v11.7.4-snapshot-80) (2026-07-07)
7
7
 
8
- **Note:** Version bump only for package @instructure/ui-modal
8
+
9
+ ### Bug Fixes
10
+
11
+ * **ui-modal:** restore Modal open/close transition animation ([ef6b3d1](https://github.com/instructure/instructure-ui/commit/ef6b3d14de2db187934b4e7a54276ee38fce6b0b))
12
+ * **ui-modal:** skip Modal.Body tab stop when it has focusable children ([25e4261](https://github.com/instructure/instructure-ui/commit/25e42617898fa6584da0b33eed589e2710875570))
9
13
 
10
14
 
11
15
 
@@ -26,7 +26,7 @@ var _dec, _class;
26
26
  import { Component } from 'react';
27
27
  import { View } from '@instructure/ui-view/v11_6';
28
28
  import { omitProps } from '@instructure/ui-react-utils';
29
- import { getCSSStyleDeclaration } from '@instructure/ui-dom-utils';
29
+ import { getCSSStyleDeclaration, findTabbable } from '@instructure/ui-dom-utils';
30
30
  import { withStyle } from '@instructure/emotion';
31
31
  import generateStyle from './styles.js';
32
32
  import generateComponentTheme from './theme.js';
@@ -50,6 +50,8 @@ let ModalBody = (_dec = withStyle(generateStyle, generateComponentTheme), _dec(_
50
50
  variant: 'default'
51
51
  };
52
52
  ref = null;
53
+ resizeObserver;
54
+ mutationObserver;
53
55
  handleRef = el => {
54
56
  const {
55
57
  elementRef
@@ -73,10 +75,26 @@ let ModalBody = (_dec = withStyle(generateStyle, generateComponentTheme), _dec(_
73
75
  isFirefox
74
76
  });
75
77
  }
78
+ const finalRef = this.getFinalRef(this.ref);
79
+ if (finalRef && typeof ResizeObserver !== 'undefined') {
80
+ this.resizeObserver = new ResizeObserver(() => this.forceUpdate());
81
+ this.resizeObserver.observe(finalRef);
82
+ this.mutationObserver = new MutationObserver(() => this.forceUpdate());
83
+ this.mutationObserver.observe(finalRef, {
84
+ childList: true,
85
+ subtree: true,
86
+ attributes: true,
87
+ attributeFilter: ['disabled', 'tabindex', 'hidden', 'href', 'contenteditable', 'type', 'class', 'style']
88
+ });
89
+ }
76
90
  }
77
91
  componentDidUpdate() {
78
92
  this.props.makeStyles?.();
79
93
  }
94
+ componentWillUnmount() {
95
+ this.resizeObserver?.disconnect();
96
+ this.mutationObserver?.disconnect();
97
+ }
80
98
  getFinalRef(el) {
81
99
  if (!el) {
82
100
  return undefined;
@@ -106,6 +124,8 @@ let ModalBody = (_dec = withStyle(generateStyle, generateComponentTheme), _dec(_
106
124
  // components. See INSTUI-4674
107
125
  const finalRef = this.getFinalRef(this.ref);
108
126
  const hasScrollbar = finalRef && Math.abs((finalRef.scrollHeight ?? 0) - (finalRef.getBoundingClientRect()?.height ?? 0)) > 1;
127
+ const hasTabbableChildren = !!finalRef && findTabbable(finalRef).length > 0;
128
+ const needsTabIndex = hasScrollbar && !hasTabbableChildren;
109
129
  return _jsx(ModalContext.Consumer, {
110
130
  children: value => _jsx(View, {
111
131
  ...passthroughProps,
@@ -117,9 +137,10 @@ let ModalBody = (_dec = withStyle(generateStyle, generateComponentTheme), _dec(_
117
137
  as: as,
118
138
  css: this.props.styles?.modalBody,
119
139
  padding: padding
120
- // check if there is a scrollbar, if so, the element has to be tabbable
140
+ // check if there is a scrollbar and no focusable children, if so, the
141
+ // element has to be tabbable
121
142
  ,
122
- ...(hasScrollbar ? {
143
+ ...(needsTabIndex ? {
123
144
  tabIndex: 0,
124
145
  'aria-label': value.bodyScrollAriaLabel
125
146
  } : {}),
@@ -89,6 +89,14 @@ let Modal = (_dec = withStyle(generateStyle, generateComponentTheme), _dec(_clas
89
89
  transitioning: true,
90
90
  open: !!this.props.open
91
91
  });
92
+ // When closing, release focus from the Dialog.
93
+ // The Dialog stays open so its content can fade out, but the
94
+ // Transition marks the exiting subtree aria-hidden -- keeping focus inside
95
+ // it would hide a focused element from assistive tech resulting in a
96
+ // console warning
97
+ if (prevProps.open && !this.props.open) {
98
+ this._content?.blur();
99
+ }
92
100
  }
93
101
  this.props.makeStyles?.();
94
102
  }
@@ -235,23 +243,23 @@ let Modal = (_dec = withStyle(generateStyle, generateComponentTheme), _dec(_clas
235
243
  open: portalIsOpen,
236
244
  onOpen: this.handlePortalOpen,
237
245
  "data-cid": "Modal",
238
- children: _jsx(Transition, {
239
- in: open,
240
- transitionOnMount: true,
241
- type: transition,
242
- onEnter: onEnter,
243
- onEntering: onEntering,
244
- onEntered: createChainedFunction(this.handleTransitionComplete, onEntered, onOpen),
245
- onExit: onExit,
246
- onExiting: onExiting,
247
- onExited: createChainedFunction(this.handleTransitionComplete, onExited, onClose),
248
- children: _jsx(ModalContext.Provider, {
249
- value: {
250
- bodyScrollAriaLabel: this.state.bodyScrollAriaLabel,
251
- setBodyScrollAriaLabel: txt => this.setState({
252
- bodyScrollAriaLabel: txt
253
- })
254
- },
246
+ children: _jsx(ModalContext.Provider, {
247
+ value: {
248
+ bodyScrollAriaLabel: this.state.bodyScrollAriaLabel,
249
+ setBodyScrollAriaLabel: txt => this.setState({
250
+ bodyScrollAriaLabel: txt
251
+ })
252
+ },
253
+ children: _jsx(Transition, {
254
+ in: open,
255
+ transitionOnMount: true,
256
+ type: transition,
257
+ onEnter: onEnter,
258
+ onEntering: onEntering,
259
+ onEntered: createChainedFunction(this.handleTransitionComplete, onEntered, onOpen),
260
+ onExit: onExit,
261
+ onExiting: onExiting,
262
+ onExited: createChainedFunction(this.handleTransitionComplete, onExited, onClose),
255
263
  children: constrain === 'parent' ? _jsx("span", {
256
264
  css: this.props.styles?.constrainContext,
257
265
  children: this.renderDialog(passthroughProps)
@@ -26,7 +26,7 @@ var _dec, _class;
26
26
  import { Component } from 'react';
27
27
  import { View } from '@instructure/ui-view/latest';
28
28
  import { omitProps } from '@instructure/ui-react-utils';
29
- import { getCSSStyleDeclaration } from '@instructure/ui-dom-utils';
29
+ import { getCSSStyleDeclaration, findTabbable } from '@instructure/ui-dom-utils';
30
30
  import { withStyleNew } from '@instructure/emotion';
31
31
  import generateStyle from './styles.js';
32
32
  import { allowedProps } from './props.js';
@@ -50,6 +50,8 @@ let ModalBody = (_dec = withStyleNew(generateStyle, 'ModalBody'), _dec(_class =
50
50
  variant: 'default'
51
51
  };
52
52
  ref = null;
53
+ resizeObserver;
54
+ mutationObserver;
53
55
  handleRef = el => {
54
56
  const {
55
57
  elementRef
@@ -73,10 +75,26 @@ let ModalBody = (_dec = withStyleNew(generateStyle, 'ModalBody'), _dec(_class =
73
75
  isFirefox
74
76
  });
75
77
  }
78
+ const finalRef = this.getFinalRef(this.ref);
79
+ if (finalRef && typeof ResizeObserver !== 'undefined') {
80
+ this.resizeObserver = new ResizeObserver(() => this.forceUpdate());
81
+ this.resizeObserver.observe(finalRef);
82
+ this.mutationObserver = new MutationObserver(() => this.forceUpdate());
83
+ this.mutationObserver.observe(finalRef, {
84
+ childList: true,
85
+ subtree: true,
86
+ attributes: true,
87
+ attributeFilter: ['disabled', 'tabindex', 'hidden', 'href', 'contenteditable', 'type', 'class', 'style']
88
+ });
89
+ }
76
90
  }
77
91
  componentDidUpdate() {
78
92
  this.props.makeStyles?.();
79
93
  }
94
+ componentWillUnmount() {
95
+ this.resizeObserver?.disconnect();
96
+ this.mutationObserver?.disconnect();
97
+ }
80
98
  getFinalRef(el) {
81
99
  if (!el) {
82
100
  return undefined;
@@ -107,6 +125,8 @@ let ModalBody = (_dec = withStyleNew(generateStyle, 'ModalBody'), _dec(_class =
107
125
  // components. See INSTUI-4674
108
126
  const finalRef = this.getFinalRef(this.ref);
109
127
  const hasScrollbar = finalRef && Math.abs((finalRef.scrollHeight ?? 0) - (finalRef.getBoundingClientRect()?.height ?? 0)) > 1;
128
+ const hasTabbableChildren = !!finalRef && findTabbable(finalRef).length > 0;
129
+ const needsTabIndex = hasScrollbar && !hasTabbableChildren;
110
130
  return _jsx(ModalContext.Consumer, {
111
131
  children: value => _jsx(View, {
112
132
  ...passthroughProps,
@@ -118,9 +138,10 @@ let ModalBody = (_dec = withStyleNew(generateStyle, 'ModalBody'), _dec(_class =
118
138
  as: as,
119
139
  css: this.props.styles?.modalBody,
120
140
  padding: spacing ? undefined : padding
121
- // check if there is a scrollbar, if so, the element has to be tabbable
141
+ // check if there is a scrollbar and no focusable children, if so, the
142
+ // element has to be tabbable
122
143
  ,
123
- ...(hasScrollbar ? {
144
+ ...(needsTabIndex ? {
124
145
  tabIndex: 0,
125
146
  'aria-label': value.bodyScrollAriaLabel
126
147
  } : {}),
@@ -88,6 +88,14 @@ let Modal = (_dec = withStyleNew(generateStyle), _dec(_class = class Modal exten
88
88
  transitioning: true,
89
89
  open: !!this.props.open
90
90
  });
91
+ // When closing, release focus from the Dialog.
92
+ // The Dialog stays open so its content can fade out, but the
93
+ // Transition marks the exiting subtree aria-hidden -- keeping focus inside
94
+ // it would hide a focused element from assistive tech resulting in a
95
+ // console warning
96
+ if (prevProps.open && !this.props.open) {
97
+ this._content?.blur();
98
+ }
91
99
  }
92
100
  this.props.makeStyles?.();
93
101
  }
@@ -167,7 +175,7 @@ let Modal = (_dec = withStyleNew(generateStyle), _dec(_class = class Modal exten
167
175
  return child;
168
176
  }
169
177
  }
170
- renderDialog(props, open) {
178
+ renderDialog(props) {
171
179
  const {
172
180
  onDismiss,
173
181
  label,
@@ -183,7 +191,7 @@ let Modal = (_dec = withStyleNew(generateStyle), _dec(_class = class Modal exten
183
191
  const dialog = _jsx(Dialog, {
184
192
  ...passthroughProps(props),
185
193
  as: as,
186
- open: open && !this.state.transitioning,
194
+ open: true,
187
195
  label: label,
188
196
  defaultFocusElement: this.defaultFocusElement,
189
197
  shouldCloseOnDocumentClick: shouldCloseOnDocumentClick,
@@ -234,27 +242,27 @@ let Modal = (_dec = withStyleNew(generateStyle), _dec(_class = class Modal exten
234
242
  open: portalIsOpen,
235
243
  onOpen: this.handlePortalOpen,
236
244
  "data-cid": "Modal",
237
- children: _jsx(Transition, {
238
- in: open,
239
- transitionOnMount: true,
240
- type: transition,
241
- onEnter: onEnter,
242
- onEntering: onEntering,
243
- onEntered: createChainedFunction(this.handleTransitionComplete, onEntered, onOpen),
244
- onExit: onExit,
245
- onExiting: onExiting,
246
- onExited: createChainedFunction(this.handleTransitionComplete, onExited, onClose),
247
- children: _jsx(ModalContext.Provider, {
248
- value: {
249
- bodyScrollAriaLabel: this.state.bodyScrollAriaLabel,
250
- setBodyScrollAriaLabel: txt => this.setState({
251
- bodyScrollAriaLabel: txt
252
- })
253
- },
245
+ children: _jsx(ModalContext.Provider, {
246
+ value: {
247
+ bodyScrollAriaLabel: this.state.bodyScrollAriaLabel,
248
+ setBodyScrollAriaLabel: txt => this.setState({
249
+ bodyScrollAriaLabel: txt
250
+ })
251
+ },
252
+ children: _jsx(Transition, {
253
+ in: open,
254
+ transitionOnMount: true,
255
+ type: transition,
256
+ onEnter: onEnter,
257
+ onEntering: onEntering,
258
+ onEntered: createChainedFunction(this.handleTransitionComplete, onEntered, onOpen),
259
+ onExit: onExit,
260
+ onExiting: onExiting,
261
+ onExited: createChainedFunction(this.handleTransitionComplete, onExited, onClose),
254
262
  children: constrain === 'parent' ? _jsx("span", {
255
263
  css: this.props.styles?.constrainContext,
256
- children: this.renderDialog(passthroughProps, open)
257
- }) : this.renderDialog(passthroughProps, open)
264
+ children: this.renderDialog(passthroughProps)
265
+ }) : this.renderDialog(passthroughProps)
258
266
  })
259
267
  })
260
268
  });
@@ -9,6 +9,7 @@ var _react = require("react");
9
9
  var _v11_ = require("@instructure/ui-view/v11_6");
10
10
  var _omitProps = require("@instructure/ui-react-utils/lib/omitProps.js");
11
11
  var _getCSSStyleDeclaration = require("@instructure/ui-dom-utils/lib/getCSSStyleDeclaration.js");
12
+ var _findTabbable = require("@instructure/ui-dom-utils/lib/findTabbable.js");
12
13
  var _emotion = require("@instructure/emotion");
13
14
  var _styles = _interopRequireDefault(require("./styles.js"));
14
15
  var _theme = _interopRequireDefault(require("./theme.js"));
@@ -55,6 +56,8 @@ let ModalBody = exports.ModalBody = (_dec = (0, _emotion.withStyle)(_styles.defa
55
56
  variant: 'default'
56
57
  };
57
58
  ref = null;
59
+ resizeObserver;
60
+ mutationObserver;
58
61
  handleRef = el => {
59
62
  const {
60
63
  elementRef
@@ -78,10 +81,26 @@ let ModalBody = exports.ModalBody = (_dec = (0, _emotion.withStyle)(_styles.defa
78
81
  isFirefox
79
82
  });
80
83
  }
84
+ const finalRef = this.getFinalRef(this.ref);
85
+ if (finalRef && typeof ResizeObserver !== 'undefined') {
86
+ this.resizeObserver = new ResizeObserver(() => this.forceUpdate());
87
+ this.resizeObserver.observe(finalRef);
88
+ this.mutationObserver = new MutationObserver(() => this.forceUpdate());
89
+ this.mutationObserver.observe(finalRef, {
90
+ childList: true,
91
+ subtree: true,
92
+ attributes: true,
93
+ attributeFilter: ['disabled', 'tabindex', 'hidden', 'href', 'contenteditable', 'type', 'class', 'style']
94
+ });
95
+ }
81
96
  }
82
97
  componentDidUpdate() {
83
98
  this.props.makeStyles?.();
84
99
  }
100
+ componentWillUnmount() {
101
+ this.resizeObserver?.disconnect();
102
+ this.mutationObserver?.disconnect();
103
+ }
85
104
  getFinalRef(el) {
86
105
  if (!el) {
87
106
  return undefined;
@@ -111,6 +130,8 @@ let ModalBody = exports.ModalBody = (_dec = (0, _emotion.withStyle)(_styles.defa
111
130
  // components. See INSTUI-4674
112
131
  const finalRef = this.getFinalRef(this.ref);
113
132
  const hasScrollbar = finalRef && Math.abs((finalRef.scrollHeight ?? 0) - (finalRef.getBoundingClientRect()?.height ?? 0)) > 1;
133
+ const hasTabbableChildren = !!finalRef && (0, _findTabbable.findTabbable)(finalRef).length > 0;
134
+ const needsTabIndex = hasScrollbar && !hasTabbableChildren;
114
135
  return (0, _jsxRuntime.jsx)(_ModalContext.default.Consumer, {
115
136
  children: value => (0, _jsxRuntime.jsx)(_v11_.View, {
116
137
  ...passthroughProps,
@@ -122,9 +143,10 @@ let ModalBody = exports.ModalBody = (_dec = (0, _emotion.withStyle)(_styles.defa
122
143
  as: as,
123
144
  css: this.props.styles?.modalBody,
124
145
  padding: padding
125
- // check if there is a scrollbar, if so, the element has to be tabbable
146
+ // check if there is a scrollbar and no focusable children, if so, the
147
+ // element has to be tabbable
126
148
  ,
127
- ...(hasScrollbar ? {
149
+ ...(needsTabIndex ? {
128
150
  tabIndex: 0,
129
151
  'aria-label': value.bodyScrollAriaLabel
130
152
  } : {}),
@@ -114,6 +114,14 @@ let Modal = exports.Modal = (_dec = (0, _emotion.withStyle)(_styles.default, _th
114
114
  transitioning: true,
115
115
  open: !!this.props.open
116
116
  });
117
+ // When closing, release focus from the Dialog.
118
+ // The Dialog stays open so its content can fade out, but the
119
+ // Transition marks the exiting subtree aria-hidden -- keeping focus inside
120
+ // it would hide a focused element from assistive tech resulting in a
121
+ // console warning
122
+ if (prevProps.open && !this.props.open) {
123
+ this._content?.blur();
124
+ }
117
125
  }
118
126
  this.props.makeStyles?.();
119
127
  }
@@ -260,23 +268,23 @@ let Modal = exports.Modal = (_dec = (0, _emotion.withStyle)(_styles.default, _th
260
268
  open: portalIsOpen,
261
269
  onOpen: this.handlePortalOpen,
262
270
  "data-cid": "Modal",
263
- children: (0, _jsxRuntime.jsx)(_Transition.Transition, {
264
- in: open,
265
- transitionOnMount: true,
266
- type: transition,
267
- onEnter: onEnter,
268
- onEntering: onEntering,
269
- onEntered: (0, _createChainedFunction.createChainedFunction)(this.handleTransitionComplete, onEntered, onOpen),
270
- onExit: onExit,
271
- onExiting: onExiting,
272
- onExited: (0, _createChainedFunction.createChainedFunction)(this.handleTransitionComplete, onExited, onClose),
273
- children: (0, _jsxRuntime.jsx)(_ModalContext.default.Provider, {
274
- value: {
275
- bodyScrollAriaLabel: this.state.bodyScrollAriaLabel,
276
- setBodyScrollAriaLabel: txt => this.setState({
277
- bodyScrollAriaLabel: txt
278
- })
279
- },
271
+ children: (0, _jsxRuntime.jsx)(_ModalContext.default.Provider, {
272
+ value: {
273
+ bodyScrollAriaLabel: this.state.bodyScrollAriaLabel,
274
+ setBodyScrollAriaLabel: txt => this.setState({
275
+ bodyScrollAriaLabel: txt
276
+ })
277
+ },
278
+ children: (0, _jsxRuntime.jsx)(_Transition.Transition, {
279
+ in: open,
280
+ transitionOnMount: true,
281
+ type: transition,
282
+ onEnter: onEnter,
283
+ onEntering: onEntering,
284
+ onEntered: (0, _createChainedFunction.createChainedFunction)(this.handleTransitionComplete, onEntered, onOpen),
285
+ onExit: onExit,
286
+ onExiting: onExiting,
287
+ onExited: (0, _createChainedFunction.createChainedFunction)(this.handleTransitionComplete, onExited, onClose),
280
288
  children: constrain === 'parent' ? (0, _jsxRuntime.jsx)("span", {
281
289
  css: this.props.styles?.constrainContext,
282
290
  children: this.renderDialog(passthroughProps)
@@ -9,6 +9,7 @@ var _react = require("react");
9
9
  var _latest = require("@instructure/ui-view/latest");
10
10
  var _omitProps = require("@instructure/ui-react-utils/lib/omitProps.js");
11
11
  var _getCSSStyleDeclaration = require("@instructure/ui-dom-utils/lib/getCSSStyleDeclaration.js");
12
+ var _findTabbable = require("@instructure/ui-dom-utils/lib/findTabbable.js");
12
13
  var _emotion = require("@instructure/emotion");
13
14
  var _styles = _interopRequireDefault(require("./styles.js"));
14
15
  var _props = require("./props.js");
@@ -55,6 +56,8 @@ let ModalBody = exports.ModalBody = (_dec = (0, _emotion.withStyleNew)(_styles.d
55
56
  variant: 'default'
56
57
  };
57
58
  ref = null;
59
+ resizeObserver;
60
+ mutationObserver;
58
61
  handleRef = el => {
59
62
  const {
60
63
  elementRef
@@ -78,10 +81,26 @@ let ModalBody = exports.ModalBody = (_dec = (0, _emotion.withStyleNew)(_styles.d
78
81
  isFirefox
79
82
  });
80
83
  }
84
+ const finalRef = this.getFinalRef(this.ref);
85
+ if (finalRef && typeof ResizeObserver !== 'undefined') {
86
+ this.resizeObserver = new ResizeObserver(() => this.forceUpdate());
87
+ this.resizeObserver.observe(finalRef);
88
+ this.mutationObserver = new MutationObserver(() => this.forceUpdate());
89
+ this.mutationObserver.observe(finalRef, {
90
+ childList: true,
91
+ subtree: true,
92
+ attributes: true,
93
+ attributeFilter: ['disabled', 'tabindex', 'hidden', 'href', 'contenteditable', 'type', 'class', 'style']
94
+ });
95
+ }
81
96
  }
82
97
  componentDidUpdate() {
83
98
  this.props.makeStyles?.();
84
99
  }
100
+ componentWillUnmount() {
101
+ this.resizeObserver?.disconnect();
102
+ this.mutationObserver?.disconnect();
103
+ }
85
104
  getFinalRef(el) {
86
105
  if (!el) {
87
106
  return undefined;
@@ -112,6 +131,8 @@ let ModalBody = exports.ModalBody = (_dec = (0, _emotion.withStyleNew)(_styles.d
112
131
  // components. See INSTUI-4674
113
132
  const finalRef = this.getFinalRef(this.ref);
114
133
  const hasScrollbar = finalRef && Math.abs((finalRef.scrollHeight ?? 0) - (finalRef.getBoundingClientRect()?.height ?? 0)) > 1;
134
+ const hasTabbableChildren = !!finalRef && (0, _findTabbable.findTabbable)(finalRef).length > 0;
135
+ const needsTabIndex = hasScrollbar && !hasTabbableChildren;
115
136
  return (0, _jsxRuntime.jsx)(_ModalContext.default.Consumer, {
116
137
  children: value => (0, _jsxRuntime.jsx)(_latest.View, {
117
138
  ...passthroughProps,
@@ -123,9 +144,10 @@ let ModalBody = exports.ModalBody = (_dec = (0, _emotion.withStyleNew)(_styles.d
123
144
  as: as,
124
145
  css: this.props.styles?.modalBody,
125
146
  padding: spacing ? undefined : padding
126
- // check if there is a scrollbar, if so, the element has to be tabbable
147
+ // check if there is a scrollbar and no focusable children, if so, the
148
+ // element has to be tabbable
127
149
  ,
128
- ...(hasScrollbar ? {
150
+ ...(needsTabIndex ? {
129
151
  tabIndex: 0,
130
152
  'aria-label': value.bodyScrollAriaLabel
131
153
  } : {}),
@@ -113,6 +113,14 @@ let Modal = exports.Modal = (_dec = (0, _emotion.withStyleNew)(_styles.default),
113
113
  transitioning: true,
114
114
  open: !!this.props.open
115
115
  });
116
+ // When closing, release focus from the Dialog.
117
+ // The Dialog stays open so its content can fade out, but the
118
+ // Transition marks the exiting subtree aria-hidden -- keeping focus inside
119
+ // it would hide a focused element from assistive tech resulting in a
120
+ // console warning
121
+ if (prevProps.open && !this.props.open) {
122
+ this._content?.blur();
123
+ }
116
124
  }
117
125
  this.props.makeStyles?.();
118
126
  }
@@ -192,7 +200,7 @@ let Modal = exports.Modal = (_dec = (0, _emotion.withStyleNew)(_styles.default),
192
200
  return child;
193
201
  }
194
202
  }
195
- renderDialog(props, open) {
203
+ renderDialog(props) {
196
204
  const {
197
205
  onDismiss,
198
206
  label,
@@ -208,7 +216,7 @@ let Modal = exports.Modal = (_dec = (0, _emotion.withStyleNew)(_styles.default),
208
216
  const dialog = (0, _jsxRuntime.jsx)(_Dialog.Dialog, {
209
217
  ...(0, _passthroughProps.passthroughProps)(props),
210
218
  as: as,
211
- open: open && !this.state.transitioning,
219
+ open: true,
212
220
  label: label,
213
221
  defaultFocusElement: this.defaultFocusElement,
214
222
  shouldCloseOnDocumentClick: shouldCloseOnDocumentClick,
@@ -259,27 +267,27 @@ let Modal = exports.Modal = (_dec = (0, _emotion.withStyleNew)(_styles.default),
259
267
  open: portalIsOpen,
260
268
  onOpen: this.handlePortalOpen,
261
269
  "data-cid": "Modal",
262
- children: (0, _jsxRuntime.jsx)(_Transition.Transition, {
263
- in: open,
264
- transitionOnMount: true,
265
- type: transition,
266
- onEnter: onEnter,
267
- onEntering: onEntering,
268
- onEntered: (0, _createChainedFunction.createChainedFunction)(this.handleTransitionComplete, onEntered, onOpen),
269
- onExit: onExit,
270
- onExiting: onExiting,
271
- onExited: (0, _createChainedFunction.createChainedFunction)(this.handleTransitionComplete, onExited, onClose),
272
- children: (0, _jsxRuntime.jsx)(_ModalContext.default.Provider, {
273
- value: {
274
- bodyScrollAriaLabel: this.state.bodyScrollAriaLabel,
275
- setBodyScrollAriaLabel: txt => this.setState({
276
- bodyScrollAriaLabel: txt
277
- })
278
- },
270
+ children: (0, _jsxRuntime.jsx)(_ModalContext.default.Provider, {
271
+ value: {
272
+ bodyScrollAriaLabel: this.state.bodyScrollAriaLabel,
273
+ setBodyScrollAriaLabel: txt => this.setState({
274
+ bodyScrollAriaLabel: txt
275
+ })
276
+ },
277
+ children: (0, _jsxRuntime.jsx)(_Transition.Transition, {
278
+ in: open,
279
+ transitionOnMount: true,
280
+ type: transition,
281
+ onEnter: onEnter,
282
+ onEntering: onEntering,
283
+ onEntered: (0, _createChainedFunction.createChainedFunction)(this.handleTransitionComplete, onEntered, onOpen),
284
+ onExit: onExit,
285
+ onExiting: onExiting,
286
+ onExited: (0, _createChainedFunction.createChainedFunction)(this.handleTransitionComplete, onExited, onClose),
279
287
  children: constrain === 'parent' ? (0, _jsxRuntime.jsx)("span", {
280
288
  css: this.props.styles?.constrainContext,
281
- children: this.renderDialog(passthroughProps, open)
282
- }) : this.renderDialog(passthroughProps, open)
289
+ children: this.renderDialog(passthroughProps)
290
+ }) : this.renderDialog(passthroughProps)
283
291
  })
284
292
  })
285
293
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/ui-modal",
3
- "version": "11.7.4-snapshot-53",
3
+ "version": "11.7.4-snapshot-80",
4
4
  "description": "A component for displaying content in a dialog overlay",
5
5
  "author": "Instructure, Inc. Engineering and Product Design",
6
6
  "module": "./es/index.js",
@@ -14,29 +14,29 @@
14
14
  "bugs": "https://github.com/instructure/instructure-ui/issues",
15
15
  "license": "MIT",
16
16
  "dependencies": {
17
- "@babel/runtime": "^7.29.2",
18
- "@instructure/emotion": "11.7.4-snapshot-53",
19
- "@instructure/shared-types": "11.7.4-snapshot-53",
20
- "@instructure/ui-buttons": "11.7.4-snapshot-53",
21
- "@instructure/console": "11.7.4-snapshot-53",
22
- "@instructure/ui-dialog": "11.7.4-snapshot-53",
23
- "@instructure/ui-dom-utils": "11.7.4-snapshot-53",
24
- "@instructure/ui-motion": "11.7.4-snapshot-53",
25
- "@instructure/ui-overlays": "11.7.4-snapshot-53",
26
- "@instructure/ui-react-utils": "11.7.4-snapshot-53",
27
- "@instructure/ui-themes": "11.7.4-snapshot-53",
28
- "@instructure/ui-utils": "11.7.4-snapshot-53",
29
- "@instructure/ui-portal": "11.7.4-snapshot-53",
30
- "@instructure/ui-view": "11.7.4-snapshot-53"
17
+ "@babel/runtime": "^7.29.7",
18
+ "@instructure/emotion": "11.7.4-snapshot-80",
19
+ "@instructure/console": "11.7.4-snapshot-80",
20
+ "@instructure/shared-types": "11.7.4-snapshot-80",
21
+ "@instructure/ui-buttons": "11.7.4-snapshot-80",
22
+ "@instructure/ui-dialog": "11.7.4-snapshot-80",
23
+ "@instructure/ui-dom-utils": "11.7.4-snapshot-80",
24
+ "@instructure/ui-motion": "11.7.4-snapshot-80",
25
+ "@instructure/ui-overlays": "11.7.4-snapshot-80",
26
+ "@instructure/ui-portal": "11.7.4-snapshot-80",
27
+ "@instructure/ui-react-utils": "11.7.4-snapshot-80",
28
+ "@instructure/ui-themes": "11.7.4-snapshot-80",
29
+ "@instructure/ui-view": "11.7.4-snapshot-80",
30
+ "@instructure/ui-utils": "11.7.4-snapshot-80"
31
31
  },
32
32
  "devDependencies": {
33
- "@testing-library/jest-dom": "^6.6.3",
34
- "@testing-library/react": "15.0.7",
33
+ "@testing-library/jest-dom": "^6.9.1",
34
+ "@testing-library/react": "16.3.2",
35
35
  "@testing-library/user-event": "^14.6.1",
36
- "vitest": "^3.2.2",
37
- "@instructure/ui-babel-preset": "11.7.4-snapshot-53",
38
- "@instructure/ui-position": "11.7.4-snapshot-53",
39
- "@instructure/ui-color-utils": "11.7.4-snapshot-53"
36
+ "vitest": "^4.1.9",
37
+ "@instructure/ui-color-utils": "11.7.4-snapshot-80",
38
+ "@instructure/ui-babel-preset": "11.7.4-snapshot-80",
39
+ "@instructure/ui-position": "11.7.4-snapshot-80"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "react": ">=18 <=19"