@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 +6 -2
- package/es/Modal/v1/ModalBody/index.js +24 -3
- package/es/Modal/v1/index.js +25 -17
- package/es/Modal/v2/ModalBody/index.js +24 -3
- package/es/Modal/v2/index.js +29 -21
- package/lib/Modal/v1/ModalBody/index.js +24 -2
- package/lib/Modal/v1/index.js +25 -17
- package/lib/Modal/v2/ModalBody/index.js +24 -2
- package/lib/Modal/v2/index.js +29 -21
- package/package.json +21 -21
- package/src/Modal/v1/ModalBody/index.tsx +35 -3
- package/src/Modal/v1/README.md +1 -0
- package/src/Modal/v1/index.tsx +34 -26
- package/src/Modal/v2/ModalBody/index.tsx +35 -3
- package/src/Modal/v2/README.md +9 -10
- package/src/Modal/v2/index.tsx +38 -31
- package/tsconfig.build.tsbuildinfo +1 -1
- package/types/Modal/v1/ModalBody/index.d.ts +3 -0
- package/types/Modal/v1/ModalBody/index.d.ts.map +1 -1
- package/types/Modal/v1/index.d.ts.map +1 -1
- package/types/Modal/v2/ModalBody/index.d.ts +3 -0
- package/types/Modal/v2/ModalBody/index.d.ts.map +1 -1
- package/types/Modal/v2/index.d.ts +1 -1
- package/types/Modal/v2/index.d.ts.map +1 -1
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-
|
|
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
|
-
|
|
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
|
|
140
|
+
// check if there is a scrollbar and no focusable children, if so, the
|
|
141
|
+
// element has to be tabbable
|
|
121
142
|
,
|
|
122
|
-
...(
|
|
143
|
+
...(needsTabIndex ? {
|
|
123
144
|
tabIndex: 0,
|
|
124
145
|
'aria-label': value.bodyScrollAriaLabel
|
|
125
146
|
} : {}),
|
package/es/Modal/v1/index.js
CHANGED
|
@@ -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(
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
|
141
|
+
// check if there is a scrollbar and no focusable children, if so, the
|
|
142
|
+
// element has to be tabbable
|
|
122
143
|
,
|
|
123
|
-
...(
|
|
144
|
+
...(needsTabIndex ? {
|
|
124
145
|
tabIndex: 0,
|
|
125
146
|
'aria-label': value.bodyScrollAriaLabel
|
|
126
147
|
} : {}),
|
package/es/Modal/v2/index.js
CHANGED
|
@@ -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
|
|
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:
|
|
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(
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
|
257
|
-
}) : this.renderDialog(passthroughProps
|
|
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
|
|
146
|
+
// check if there is a scrollbar and no focusable children, if so, the
|
|
147
|
+
// element has to be tabbable
|
|
126
148
|
,
|
|
127
|
-
...(
|
|
149
|
+
...(needsTabIndex ? {
|
|
128
150
|
tabIndex: 0,
|
|
129
151
|
'aria-label': value.bodyScrollAriaLabel
|
|
130
152
|
} : {}),
|
package/lib/Modal/v1/index.js
CHANGED
|
@@ -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)(
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
|
147
|
+
// check if there is a scrollbar and no focusable children, if so, the
|
|
148
|
+
// element has to be tabbable
|
|
127
149
|
,
|
|
128
|
-
...(
|
|
150
|
+
...(needsTabIndex ? {
|
|
129
151
|
tabIndex: 0,
|
|
130
152
|
'aria-label': value.bodyScrollAriaLabel
|
|
131
153
|
} : {}),
|
package/lib/Modal/v2/index.js
CHANGED
|
@@ -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
|
|
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:
|
|
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)(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
|
282
|
-
}) : this.renderDialog(passthroughProps
|
|
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-
|
|
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.
|
|
18
|
-
"@instructure/emotion": "11.7.4-snapshot-
|
|
19
|
-
"@instructure/
|
|
20
|
-
"@instructure/
|
|
21
|
-
"@instructure/
|
|
22
|
-
"@instructure/ui-dialog": "11.7.4-snapshot-
|
|
23
|
-
"@instructure/ui-dom-utils": "11.7.4-snapshot-
|
|
24
|
-
"@instructure/ui-motion": "11.7.4-snapshot-
|
|
25
|
-
"@instructure/ui-overlays": "11.7.4-snapshot-
|
|
26
|
-
"@instructure/ui-
|
|
27
|
-
"@instructure/ui-
|
|
28
|
-
"@instructure/ui-
|
|
29
|
-
"@instructure/ui-
|
|
30
|
-
"@instructure/ui-
|
|
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.
|
|
34
|
-
"@testing-library/react": "
|
|
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": "^
|
|
37
|
-
"@instructure/ui-
|
|
38
|
-
"@instructure/ui-
|
|
39
|
-
"@instructure/ui-
|
|
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"
|