@ultraviolet/ui 1.11.0 → 1.11.1
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/dist/index.d.ts
CHANGED
|
@@ -24,11 +24,10 @@ type ActionBarProps = {
|
|
|
24
24
|
*/
|
|
25
25
|
rank?: number;
|
|
26
26
|
role?: string;
|
|
27
|
-
'aria-modal'?: 'true' | 'false';
|
|
28
27
|
className?: string;
|
|
29
28
|
'data-testid'?: string;
|
|
30
29
|
};
|
|
31
|
-
declare const ActionBar: ({ children, role, rank,
|
|
30
|
+
declare const ActionBar: ({ children, role, rank, className, "data-testid": dataTestId, }: ActionBarProps) => react.ReactPortal;
|
|
32
31
|
|
|
33
32
|
type ScreenSize = keyof typeof consoleLightTheme.screens;
|
|
34
33
|
type SCWUITheme = typeof consoleLightTheme;
|
|
@@ -33,14 +33,12 @@ const ActionBar = _ref5 => {
|
|
|
33
33
|
children,
|
|
34
34
|
role = 'dialog',
|
|
35
35
|
rank = 0,
|
|
36
|
-
'aria-modal': ariaModal = 'true',
|
|
37
36
|
className,
|
|
38
37
|
'data-testid': dataTestId
|
|
39
38
|
} = _ref5;
|
|
40
39
|
return /*#__PURE__*/createPortal(jsx(StyledDiv, {
|
|
41
40
|
rank: rank,
|
|
42
41
|
role: role,
|
|
43
|
-
"aria-modal": ariaModal,
|
|
44
42
|
className: className,
|
|
45
43
|
"data-testid": dataTestId,
|
|
46
44
|
children: children
|
|
@@ -73,6 +73,7 @@ const Dialog = _ref9 => {
|
|
|
73
73
|
backdropCss
|
|
74
74
|
} = _ref9;
|
|
75
75
|
const containerRef = useRef(document.createElement('div'));
|
|
76
|
+
const dialogRef = useRef(null);
|
|
76
77
|
const onCloseRef = useRef(onClose);
|
|
77
78
|
|
|
78
79
|
// Portal to put the modal in
|
|
@@ -80,6 +81,7 @@ const Dialog = _ref9 => {
|
|
|
80
81
|
const element = containerRef.current;
|
|
81
82
|
if (open) {
|
|
82
83
|
document.body.appendChild(element);
|
|
84
|
+
dialogRef.current?.focus();
|
|
83
85
|
}
|
|
84
86
|
return () => {
|
|
85
87
|
if (document.body.contains(element)) {
|
|
@@ -97,14 +99,26 @@ const Dialog = _ref9 => {
|
|
|
97
99
|
useEffect(() => {
|
|
98
100
|
const handleEscPress = event => {
|
|
99
101
|
if (event.key === 'Escape' && hideOnEsc) {
|
|
102
|
+
event.preventDefault();
|
|
103
|
+
event.stopPropagation();
|
|
100
104
|
onCloseRef.current();
|
|
101
105
|
}
|
|
102
106
|
};
|
|
103
107
|
if (open) {
|
|
104
|
-
document.addEventListener('keyup', handleEscPress
|
|
108
|
+
document.body.addEventListener('keyup', handleEscPress, {
|
|
109
|
+
capture: true
|
|
110
|
+
});
|
|
111
|
+
document.body.addEventListener('keydown', handleEscPress, {
|
|
112
|
+
capture: true
|
|
113
|
+
});
|
|
105
114
|
}
|
|
106
115
|
return () => {
|
|
107
|
-
document.removeEventListener('keyup', handleEscPress
|
|
116
|
+
document.body.removeEventListener('keyup', handleEscPress, {
|
|
117
|
+
capture: true
|
|
118
|
+
});
|
|
119
|
+
document.body.removeEventListener('keydown', handleEscPress, {
|
|
120
|
+
capture: true
|
|
121
|
+
});
|
|
108
122
|
};
|
|
109
123
|
}, [open, onCloseRef, hideOnEsc]);
|
|
110
124
|
|
|
@@ -117,6 +131,11 @@ const Dialog = _ref9 => {
|
|
|
117
131
|
}
|
|
118
132
|
}, [preventBodyScroll, open]);
|
|
119
133
|
|
|
134
|
+
// Stop focus to prevent unexpected body loose focus
|
|
135
|
+
const stopFocus = useCallback(event => {
|
|
136
|
+
event.stopPropagation();
|
|
137
|
+
}, []);
|
|
138
|
+
|
|
120
139
|
// Stop click to prevent unexpected dialog close
|
|
121
140
|
const stopClick = useCallback(event => {
|
|
122
141
|
event.stopPropagation();
|
|
@@ -126,15 +145,53 @@ const Dialog = _ref9 => {
|
|
|
126
145
|
const stopKeyUp = useCallback(event => {
|
|
127
146
|
event.stopPropagation();
|
|
128
147
|
}, []);
|
|
148
|
+
|
|
149
|
+
// Enable focus trap inside the modal
|
|
150
|
+
const handleFocusTrap = useCallback(event => {
|
|
151
|
+
event.stopPropagation();
|
|
152
|
+
if (event.key === 'Escape') {
|
|
153
|
+
event.preventDefault();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const isTabPressed = event.key === 'Tab';
|
|
157
|
+
if (!isTabPressed) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const focusableEls = dialogRef.current?.querySelectorAll('a[href]:not([disabled]), button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled])') ?? [];
|
|
161
|
+
|
|
162
|
+
// Handle case when no interactive element are within the modal (including close icon)
|
|
163
|
+
if (focusableEls.length === 0) {
|
|
164
|
+
event.preventDefault();
|
|
165
|
+
}
|
|
166
|
+
const firstFocusableEl = focusableEls[0];
|
|
167
|
+
const lastFocusableEl = focusableEls[focusableEls.length - 1];
|
|
168
|
+
if (event.shiftKey) {
|
|
169
|
+
if (document.activeElement === firstFocusableEl) {
|
|
170
|
+
lastFocusableEl.focus();
|
|
171
|
+
event.preventDefault();
|
|
172
|
+
}
|
|
173
|
+
} else if (document.activeElement === lastFocusableEl) {
|
|
174
|
+
firstFocusableEl.focus();
|
|
175
|
+
event.preventDefault();
|
|
176
|
+
}
|
|
177
|
+
}, []);
|
|
178
|
+
|
|
179
|
+
// Prevent default behaviour on Escape
|
|
180
|
+
const stopCancel = event => {
|
|
181
|
+
event.preventDefault();
|
|
182
|
+
event.stopPropagation();
|
|
183
|
+
};
|
|
129
184
|
return /*#__PURE__*/createPortal(jsx(StyledBackdrop, {
|
|
130
185
|
"data-open": open,
|
|
131
186
|
onClick: hideOnClickOutside ? onClose : undefined,
|
|
132
187
|
className: backdropClassName,
|
|
133
188
|
css: backdropCss,
|
|
134
189
|
"data-testid": dataTestId ? `${dataTestId}-backdrop` : undefined,
|
|
190
|
+
onFocus: stopFocus,
|
|
135
191
|
children: jsx(StyledDialog, {
|
|
136
192
|
css: dialogCss,
|
|
137
193
|
onKeyUp: stopKeyUp,
|
|
194
|
+
onKeyDown: handleFocusTrap,
|
|
138
195
|
className: className,
|
|
139
196
|
id: id,
|
|
140
197
|
"data-testid": dataTestId,
|
|
@@ -143,6 +200,10 @@ const Dialog = _ref9 => {
|
|
|
143
200
|
"data-size": size,
|
|
144
201
|
open: open,
|
|
145
202
|
onClick: stopClick,
|
|
203
|
+
onCancel: stopCancel,
|
|
204
|
+
onClose: stopCancel,
|
|
205
|
+
"aria-modal": true,
|
|
206
|
+
ref: dialogRef,
|
|
146
207
|
children: open ? children : null
|
|
147
208
|
})
|
|
148
209
|
}), containerRef.current);
|
|
@@ -17,6 +17,11 @@ const Disclosure = _ref => {
|
|
|
17
17
|
element?.removeEventListener('click', handleOpen);
|
|
18
18
|
};
|
|
19
19
|
}, [handleOpen, disclosureRef]);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!visible) {
|
|
22
|
+
disclosureRef.current?.focus();
|
|
23
|
+
}
|
|
24
|
+
}, [visible, disclosureRef]);
|
|
20
25
|
if (typeof disclosure === 'function') {
|
|
21
26
|
return disclosure({
|
|
22
27
|
visible,
|
|
@@ -30,7 +35,9 @@ const Disclosure = _ref => {
|
|
|
30
35
|
if ( /*#__PURE__*/isValidElement(disclosure)) {
|
|
31
36
|
return /*#__PURE__*/cloneElement(disclosure, {
|
|
32
37
|
...disclosure.props,
|
|
33
|
-
ref: disclosureRef
|
|
38
|
+
ref: disclosureRef,
|
|
39
|
+
'aria-controls': id,
|
|
40
|
+
'aria-haspopup': 'dialog'
|
|
34
41
|
});
|
|
35
42
|
}
|
|
36
43
|
return null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultraviolet/ui",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.1",
|
|
4
4
|
"description": "Ultraviolet UI",
|
|
5
5
|
"homepage": "https://github.com/scaleway/ultraviolet#readme",
|
|
6
6
|
"repository": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"react-dom": "18.2.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@babel/core": "7.22.
|
|
42
|
+
"@babel/core": "7.22.15",
|
|
43
43
|
"@emotion/babel-plugin": "11.11.0",
|
|
44
44
|
"@emotion/react": "11.11.1",
|
|
45
45
|
"@emotion/styled": "11.11.0",
|
|
@@ -68,6 +68,6 @@
|
|
|
68
68
|
"react-use-clipboard": "1.0.9",
|
|
69
69
|
"reakit": "1.3.11",
|
|
70
70
|
"@ultraviolet/themes": "1.2.1",
|
|
71
|
-
"@ultraviolet/icons": "
|
|
71
|
+
"@ultraviolet/icons": "2.0.0"
|
|
72
72
|
}
|
|
73
73
|
}
|