@atlaskit/modal-dialog 16.2.1 → 16.2.3
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.
|
@@ -65,9 +65,38 @@ var allowlistElements = function allowlistElements(element, callback) {
|
|
|
65
65
|
}
|
|
66
66
|
return true;
|
|
67
67
|
};
|
|
68
|
-
|
|
68
|
+
|
|
69
|
+
// Analytics-wrapped close handler. Extracted so both rendering paths build it
|
|
70
|
+
// identically.
|
|
71
|
+
function useModalCloseHandler(providedOnClose) {
|
|
72
|
+
return usePlatformLeafEventHandler({
|
|
73
|
+
fn: providedOnClose || noop,
|
|
74
|
+
action: 'closed',
|
|
75
|
+
componentName: 'modalDialog',
|
|
76
|
+
packageName: "@atlaskit/modal-dialog",
|
|
77
|
+
packageVersion: "16.2.2"
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Top-layer rendering path (platform-dst-top-layer).
|
|
83
|
+
*
|
|
84
|
+
* Replaces Portal, FocusLock, ScrollLock, Blanket, Positioner, and z-index
|
|
85
|
+
* management with native <dialog> via @atlaskit/top-layer/dialog.
|
|
86
|
+
*
|
|
87
|
+
* Key decisions:
|
|
88
|
+
* - Animation: CSS transitions via @starting-style / allow-discrete.
|
|
89
|
+
* - Close gating: onDialogClose only forwards allowed reasons
|
|
90
|
+
* (see notes/guides/dialog-close-flow.md).
|
|
91
|
+
* - onClose event param: undefined - consumers should use close reason.
|
|
92
|
+
* - Focus restoration: native <dialog> behavior replaces react-focus-lock's
|
|
93
|
+
* returnFocus (see accessibility-criteria.md).
|
|
94
|
+
*
|
|
95
|
+
* Every hook here runs unconditionally: this component only mounts on the
|
|
96
|
+
* top-layer path, so it never shares a hook sequence with the legacy path.
|
|
97
|
+
*/
|
|
98
|
+
function ModalWrapperTopLayer(props) {
|
|
69
99
|
var autoFocus = props.autoFocus,
|
|
70
|
-
focusLockAllowlist = props.focusLockAllowlist,
|
|
71
100
|
_props$shouldCloseOnE = props.shouldCloseOnEscapePress,
|
|
72
101
|
shouldCloseOnEscapePress = _props$shouldCloseOnE === void 0 ? true : _props$shouldCloseOnE,
|
|
73
102
|
_props$shouldCloseOnO = props.shouldCloseOnOverlayClick,
|
|
@@ -88,253 +117,244 @@ var InternalModalWrapper = /*#__PURE__*/forwardRef(function (props, ref) {
|
|
|
88
117
|
onOpenComplete = props.onOpenComplete,
|
|
89
118
|
label = props.label,
|
|
90
119
|
testId = props.testId,
|
|
91
|
-
isFullScreen = props.isFullScreen
|
|
92
|
-
_props$UNSAFE_shouldD = props.UNSAFE_shouldDisableMotionUplift,
|
|
93
|
-
UNSAFE_shouldDisableMotionUplift = _props$UNSAFE_shouldD === void 0 ? false : _props$UNSAFE_shouldD;
|
|
120
|
+
isFullScreen = props.isFullScreen;
|
|
94
121
|
var calculatedStackIndex = useModalStack({
|
|
95
122
|
onStackChange: onStackChange
|
|
96
123
|
});
|
|
97
124
|
var stackIndex = stackIndexOverride || calculatedStackIndex;
|
|
98
|
-
var
|
|
99
|
-
|
|
100
|
-
// If no ref is provided, autofocus on first element
|
|
101
|
-
var autoFocusLock = !(_typeof(autoFocus) === 'object');
|
|
102
|
-
var onCloseHandler = usePlatformLeafEventHandler({
|
|
103
|
-
fn: providedOnClose || noop,
|
|
104
|
-
action: 'closed',
|
|
105
|
-
componentName: 'modalDialog',
|
|
106
|
-
packageName: "@atlaskit/modal-dialog",
|
|
107
|
-
packageVersion: "16.2.0"
|
|
108
|
-
});
|
|
109
|
-
var onBlanketClicked = useCallback(function (e) {
|
|
110
|
-
if (shouldCloseOnOverlayClick) {
|
|
111
|
-
onCloseHandler(e);
|
|
112
|
-
}
|
|
113
|
-
}, [shouldCloseOnOverlayClick, onCloseHandler]);
|
|
114
|
-
|
|
115
|
-
// Stable callback to avoid re-renders when focusLockAllowlist is not provided.
|
|
116
|
-
var allowListCallback = useCallback(function (element) {
|
|
117
|
-
return allowlistElements(element, focusLockAllowlist);
|
|
118
|
-
}, [focusLockAllowlist]);
|
|
119
|
-
|
|
120
|
-
// Called outside the feature-flag branch to keep hook order stable.
|
|
121
|
-
// Legacy path: FadeIn calls onFinish. Top-layer path: called directly.
|
|
125
|
+
var onCloseHandler = useModalCloseHandler(providedOnClose);
|
|
122
126
|
var _useExitingPersistenc = useExitingPersistence(),
|
|
123
127
|
isExiting = _useExitingPersistenc.isExiting,
|
|
124
128
|
onExitFinish = _useExitingPersistenc.onFinish;
|
|
125
129
|
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
usePreventProgrammaticScroll();
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// On the top-layer path, the Dialog primitive registers with the observer
|
|
134
|
-
// directly, so we skip registration here to avoid double-counting.
|
|
135
|
-
// Safe conditional hook: feature flags are resolved once at startup.
|
|
136
|
-
if (!fg('platform-dst-top-layer')) {
|
|
137
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
138
|
-
useNotifyOpenLayerObserver({
|
|
139
|
-
type: 'modal',
|
|
140
|
-
// Always open — modal is conditionally rendered when visible.
|
|
141
|
-
isOpen: true,
|
|
142
|
-
// No-op: no current use case for programmatic close via OpenLayerObserver.
|
|
143
|
-
onClose: noop
|
|
144
|
-
});
|
|
145
|
-
}
|
|
130
|
+
// Native <dialog> always restores focus on close - no opt-out via shouldReturnFocus.
|
|
131
|
+
var defaultTestId = testId || 'modal-dialog';
|
|
132
|
+
var id = useId();
|
|
133
|
+
var titleId = "modal-dialog-title-".concat(id);
|
|
146
134
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
*
|
|
150
|
-
* Replaces Portal, FocusLock, ScrollLock, Blanket, Positioner, and z-index
|
|
151
|
-
* management with native <dialog> via @atlaskit/top-layer/dialog.
|
|
152
|
-
*
|
|
153
|
-
* Key decisions:
|
|
154
|
-
* - Animation: CSS transitions via @starting-style / allow-discrete.
|
|
155
|
-
* - Close gating: onDialogClose only forwards allowed reasons
|
|
156
|
-
* (see notes/guides/dialog-close-flow.md).
|
|
157
|
-
* - onClose event param: undefined - consumers should use close reason.
|
|
158
|
-
* - Focus restoration: native <dialog> behavior replaces react-focus-lock's
|
|
159
|
-
* returnFocus (see accessibility-criteria.md).
|
|
160
|
-
*/
|
|
161
|
-
if (fg('platform-dst-top-layer')) {
|
|
162
|
-
// Native <dialog> always restores focus on close - no opt-out via shouldReturnFocus.
|
|
163
|
-
var defaultTestId = testId || 'modal-dialog';
|
|
135
|
+
// Content container ref - used for onOpenComplete/onCloseComplete callbacks.
|
|
136
|
+
var contentRef = useRef(null);
|
|
164
137
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
138
|
+
// Cache last content element for onCloseComplete after children unmount
|
|
139
|
+
// (with reduced motion, contentRef clears before onExitFinish fires).
|
|
140
|
+
var lastContentElRef = useRef(null);
|
|
141
|
+
if (contentRef.current) {
|
|
142
|
+
lastContentElRef.current = contentRef.current;
|
|
143
|
+
}
|
|
168
144
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
145
|
+
// Native <dialog> ref - needed for ExitingPersistence to call dialog.close().
|
|
146
|
+
var dialogRef = useRef(null);
|
|
147
|
+
var modalDialogContext = useMemo(function () {
|
|
148
|
+
return {
|
|
149
|
+
testId: defaultTestId,
|
|
150
|
+
titleId: titleId,
|
|
151
|
+
onClose: onCloseHandler,
|
|
152
|
+
hasProvidedOnClose: Boolean(providedOnClose),
|
|
153
|
+
isFullScreen: isFullScreen !== null && isFullScreen !== void 0 ? isFullScreen : false
|
|
154
|
+
};
|
|
155
|
+
}, [defaultTestId, titleId, onCloseHandler, providedOnClose, isFullScreen]);
|
|
172
156
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
var
|
|
177
|
-
if (
|
|
178
|
-
|
|
157
|
+
// Only forward close when the reason is allowed by props.
|
|
158
|
+
// Passes a synthetic event to satisfy the KeyboardOrMouseEvent contract.
|
|
159
|
+
var onDialogClose = useCallback(function (_ref) {
|
|
160
|
+
var reason = _ref.reason;
|
|
161
|
+
if (reason === 'escape' && shouldCloseOnEscapePress) {
|
|
162
|
+
onCloseHandler(createCloseEvent({
|
|
163
|
+
reason: reason
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
if (reason === 'overlay-click' && shouldCloseOnOverlayClick) {
|
|
167
|
+
onCloseHandler(createCloseEvent({
|
|
168
|
+
reason: reason
|
|
169
|
+
}));
|
|
179
170
|
}
|
|
171
|
+
}, [onCloseHandler, shouldCloseOnEscapePress, shouldCloseOnOverlayClick]);
|
|
180
172
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
173
|
+
// ExitingPersistence: isExiting → isOpen={false} → Dialog exit animation →
|
|
174
|
+
// onExitFinish → onCloseComplete + unmount.
|
|
175
|
+
var handleDialogExitFinish = useCallback(function () {
|
|
176
|
+
var _contentRef$current;
|
|
177
|
+
var el = (_contentRef$current = contentRef.current) !== null && _contentRef$current !== void 0 ? _contentRef$current : lastContentElRef.current;
|
|
178
|
+
if (onCloseComplete && el) {
|
|
179
|
+
onCloseComplete(el);
|
|
180
|
+
}
|
|
181
|
+
lastContentElRef.current = null;
|
|
182
|
+
onExitFinish === null || onExitFinish === void 0 || onExitFinish();
|
|
183
|
+
}, [onExitFinish, onCloseComplete]);
|
|
184
184
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
isFullScreen: isFullScreen !== null && isFullScreen !== void 0 ? isFullScreen : false
|
|
193
|
-
};
|
|
194
|
-
}, [defaultTestId, titleId, onCloseHandler, providedOnClose, isFullScreen]);
|
|
185
|
+
// Fire onOpenComplete after mount.
|
|
186
|
+
useEffect(function () {
|
|
187
|
+
if (onOpenComplete && contentRef.current) {
|
|
188
|
+
onOpenComplete(contentRef.current, true);
|
|
189
|
+
}
|
|
190
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
191
|
+
}, []);
|
|
195
192
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (
|
|
207
|
-
|
|
208
|
-
reason: reason
|
|
209
|
-
}));
|
|
193
|
+
// Honor `shouldReturnFocus={ref}` on unmount.
|
|
194
|
+
// Native <dialog>.close() restores focus to the trigger that opened it,
|
|
195
|
+
// but the consumer asked for focus to go to a specific element instead.
|
|
196
|
+
// Run this in an unmount cleanup so it fires after dialog.close()
|
|
197
|
+
// (which fires in the Dialog's effect cleanup).
|
|
198
|
+
var shouldReturnFocusRef = useRef(shouldReturnFocus);
|
|
199
|
+
shouldReturnFocusRef.current = shouldReturnFocus;
|
|
200
|
+
useEffect(function () {
|
|
201
|
+
return function () {
|
|
202
|
+
var target = shouldReturnFocusRef.current;
|
|
203
|
+
if (_typeof(target) === 'object' && target.current) {
|
|
204
|
+
target.current.focus();
|
|
210
205
|
}
|
|
211
|
-
}
|
|
206
|
+
};
|
|
207
|
+
}, []);
|
|
212
208
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
var handleDialogExitFinish = useCallback(function () {
|
|
217
|
-
var _contentRef$current;
|
|
218
|
-
var el = (_contentRef$current = contentRef.current) !== null && _contentRef$current !== void 0 ? _contentRef$current : lastContentElRef.current;
|
|
219
|
-
if (onCloseComplete && el) {
|
|
220
|
-
onCloseComplete(el);
|
|
221
|
-
}
|
|
222
|
-
lastContentElRef.current = null;
|
|
223
|
-
onExitFinish === null || onExitFinish === void 0 || onExitFinish();
|
|
224
|
-
}, [onExitFinish, onCloseComplete]);
|
|
209
|
+
// Focus a ref-targeted element after mount (when autoFocus is a ref).
|
|
210
|
+
// When true, native <dialog>.showModal() handles focus automatically.
|
|
211
|
+
useAutoFocus(_typeof(autoFocus) === 'object' ? autoFocus : undefined, _typeof(autoFocus) === 'object');
|
|
225
212
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
onOpenComplete(contentRef.current, true);
|
|
231
|
-
}
|
|
232
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
233
|
-
}, []);
|
|
213
|
+
// Chrome cross-origin iframe DnD workaround (crbug.com/362301053)
|
|
214
|
+
useEffect(function () {
|
|
215
|
+
return combine(disableDraggingToCrossOriginIFramesForElement(), disableDraggingToCrossOriginIFramesForTextSelection(), disableDraggingToCrossOriginIFramesForExternal());
|
|
216
|
+
}, []);
|
|
234
217
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
241
|
-
var shouldReturnFocusRef = useRef(shouldReturnFocus);
|
|
242
|
-
shouldReturnFocusRef.current = shouldReturnFocus;
|
|
243
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
244
|
-
useEffect(function () {
|
|
245
|
-
return function () {
|
|
246
|
-
var target = shouldReturnFocusRef.current;
|
|
247
|
-
if (_typeof(target) === 'object' && target.current) {
|
|
248
|
-
target.current.focus();
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
|
-
}, []);
|
|
218
|
+
// Responsive layout via ID-scoped <style> (same pattern as Dialog's hideBackdrop).
|
|
219
|
+
// ID selector beats Compiled atomic classes without !important and supports @media.
|
|
220
|
+
var namedWidth = getDialogWidth(width !== null && width !== void 0 ? width : 'medium');
|
|
221
|
+
var dialogId = "modal-dialog-".concat(id);
|
|
222
|
+
var escapedDialogId = CSS.escape(dialogId);
|
|
252
223
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
224
|
+
// Percentage widths need special handling in the top layer.
|
|
225
|
+
// In legacy, the percentage resolved against the Positioner's max-width
|
|
226
|
+
// (100vw - 120px). In the top layer, the <dialog>'s containing block is the
|
|
227
|
+
// viewport (100vw), so a raw percentage would produce a wider modal.
|
|
228
|
+
// Transform e.g. '42%' → 'calc(42 * (100vw - 120px) / 100)' to match legacy.
|
|
229
|
+
var resolvedWidth = namedWidth.endsWith('%') ? "calc(".concat(parseFloat(namedWidth), " * (100vw - 120px) / 100)") : namedWidth;
|
|
230
|
+
var dialogStyle = isFullScreen ? {
|
|
231
|
+
width: '100vw',
|
|
232
|
+
height: '100vh',
|
|
233
|
+
margin: '0'
|
|
234
|
+
} : {
|
|
235
|
+
width: "min(".concat(resolvedWidth, ", 100vw)")
|
|
236
|
+
};
|
|
257
237
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}, []);
|
|
238
|
+
// Shift stacked background modals down by space.100 (8px) per level.
|
|
239
|
+
if (stackIndex > 0) {
|
|
240
|
+
dialogStyle['transform'] = "translateY(calc(".concat(stackIndex, "px * ", "var(--ds-space-100, 8px)", "))");
|
|
241
|
+
}
|
|
263
242
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
243
|
+
// Mobile: viewport fill. Desktop (≥ 30rem): gutter margins, auto height.
|
|
244
|
+
// Content-div height set via #id > div to beat Compiled's atomic specificity.
|
|
245
|
+
var desktopMargin = shouldScrollInViewport ? '60px auto' : '60px auto auto';
|
|
246
|
+
var resolvedHeight = dialogHeight(height);
|
|
247
|
+
// Body-scroll: specified height or auto. Viewport-scroll: uses min-height.
|
|
248
|
+
var desktopContentHeight = shouldScrollInViewport ? 'auto' : resolvedHeight;
|
|
249
|
+
var desktopContentMinHeight = shouldScrollInViewport ? resolvedHeight : 'auto';
|
|
250
|
+
// Viewport-scroll: the legacy Positioner was a fixed 100vh container that
|
|
251
|
+
// scrolled internally, so the modal section could fill (100vh - 60px top gutter).
|
|
252
|
+
// In the top layer the <dialog> sizes to content with height:auto, so we need
|
|
253
|
+
// an explicit min-height to ensure the dialog stretches to the same visible area.
|
|
254
|
+
var desktopDialogMinHeight = shouldScrollInViewport ? 'min-height:calc(100vh - 60px);' : '';
|
|
255
|
+
// Doubled-ID selector (#id#id > div) at specificity (2,0,1) beats
|
|
256
|
+
// Compiled atomic classes at (0,1,0) (increaseSpecificity is disabled).
|
|
257
|
+
var dialogPositionStyles = isFullScreen ? '' : // Mobile: edge-to-edge. Desktop (≥ 30rem): 60px gutters, max-width.
|
|
258
|
+
"#".concat(escapedDialogId, "#").concat(escapedDialogId, "{margin:0;height:100vh}#").concat(escapedDialogId, "#").concat(escapedDialogId, ">div{height:100%}@media(min-width:30rem){#").concat(escapedDialogId, "#").concat(escapedDialogId, "{margin:").concat(desktopMargin, ";height:auto;").concat(desktopDialogMinHeight, "max-width:calc(100vw - 120px)}#").concat(escapedDialogId, "#").concat(escapedDialogId, ">div{height:").concat(desktopContentHeight, ";min-height:").concat(desktopContentMinHeight, "}}");
|
|
259
|
+
return /*#__PURE__*/React.createElement(Dialog, _extends({
|
|
260
|
+
ref: dialogRef,
|
|
261
|
+
id: dialogId,
|
|
262
|
+
onClose: onDialogClose,
|
|
263
|
+
onExitFinish: handleDialogExitFinish,
|
|
264
|
+
shouldAnimate: !isFullScreen,
|
|
265
|
+
isOpen: !isExiting,
|
|
266
|
+
shouldHideBackdrop: stackIndex > 0 || Boolean(isBlanketHidden)
|
|
267
|
+
// Dialog requires at least one of `label` or `labelledBy` (string, not undefined).
|
|
268
|
+
// Prefer the consumer-provided `label`; otherwise reference the internal `titleId`.
|
|
269
|
+
}, label ? {
|
|
270
|
+
label: label
|
|
271
|
+
} : {
|
|
272
|
+
labelledBy: titleId
|
|
273
|
+
}, {
|
|
274
|
+
testId: defaultTestId
|
|
275
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop
|
|
276
|
+
,
|
|
277
|
+
style: dialogStyle
|
|
278
|
+
}), /*#__PURE__*/React.createElement(DialogScrollLock, {
|
|
279
|
+
isOpen: true
|
|
280
|
+
}), dialogPositionStyles &&
|
|
281
|
+
/*#__PURE__*/
|
|
282
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-global-styles
|
|
283
|
+
React.createElement("style", null, dialogPositionStyles), /*#__PURE__*/React.createElement("div", {
|
|
284
|
+
ref: contentRef,
|
|
285
|
+
className: ax([topLayerStyles.content, !isFullScreen && topLayerStyles.borderRadius, !isFullScreen && fg('platform-dst-shape-theme-default') && topLayerStyles.borderRadiusT26, !isFullScreen && !shouldScrollInViewport && "_bolhzwhf", !isFullScreen && shouldScrollInViewport && "_1tke1kxc _c71lglyw"])
|
|
286
|
+
}, /*#__PURE__*/React.createElement(ModalContext.Provider, {
|
|
287
|
+
value: modalDialogContext
|
|
288
|
+
}, /*#__PURE__*/React.createElement(ScrollContext.Provider, {
|
|
289
|
+
value: shouldScrollInViewport
|
|
290
|
+
}, children))));
|
|
291
|
+
}
|
|
269
292
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
293
|
+
/**
|
|
294
|
+
* Legacy rendering path (Portal + FocusLock + ScrollLock + Blanket).
|
|
295
|
+
*
|
|
296
|
+
* Every hook here runs unconditionally: this component only mounts on the
|
|
297
|
+
* legacy path, so it never shares a hook sequence with the top-layer path.
|
|
298
|
+
*/
|
|
299
|
+
var ModalWrapperLegacy = /*#__PURE__*/forwardRef(function (props, ref) {
|
|
300
|
+
var autoFocus = props.autoFocus,
|
|
301
|
+
focusLockAllowlist = props.focusLockAllowlist,
|
|
302
|
+
_props$shouldCloseOnE2 = props.shouldCloseOnEscapePress,
|
|
303
|
+
shouldCloseOnEscapePress = _props$shouldCloseOnE2 === void 0 ? true : _props$shouldCloseOnE2,
|
|
304
|
+
_props$shouldCloseOnO2 = props.shouldCloseOnOverlayClick,
|
|
305
|
+
shouldCloseOnOverlayClick = _props$shouldCloseOnO2 === void 0 ? true : _props$shouldCloseOnO2,
|
|
306
|
+
_props$shouldScrollIn2 = props.shouldScrollInViewport,
|
|
307
|
+
shouldScrollInViewport = _props$shouldScrollIn2 === void 0 ? false : _props$shouldScrollIn2,
|
|
308
|
+
_props$shouldReturnFo2 = props.shouldReturnFocus,
|
|
309
|
+
shouldReturnFocus = _props$shouldReturnFo2 === void 0 ? true : _props$shouldReturnFo2,
|
|
310
|
+
stackIndexOverride = props.stackIndex,
|
|
311
|
+
providedOnClose = props.onClose,
|
|
312
|
+
_props$onStackChange2 = props.onStackChange,
|
|
313
|
+
onStackChange = _props$onStackChange2 === void 0 ? noop : _props$onStackChange2,
|
|
314
|
+
isBlanketHidden = props.isBlanketHidden,
|
|
315
|
+
children = props.children,
|
|
316
|
+
height = props.height,
|
|
317
|
+
width = props.width,
|
|
318
|
+
onCloseComplete = props.onCloseComplete,
|
|
319
|
+
onOpenComplete = props.onOpenComplete,
|
|
320
|
+
label = props.label,
|
|
321
|
+
testId = props.testId,
|
|
322
|
+
isFullScreen = props.isFullScreen,
|
|
323
|
+
_props$UNSAFE_shouldD = props.UNSAFE_shouldDisableMotionUplift,
|
|
324
|
+
UNSAFE_shouldDisableMotionUplift = _props$UNSAFE_shouldD === void 0 ? false : _props$UNSAFE_shouldD;
|
|
325
|
+
var calculatedStackIndex = useModalStack({
|
|
326
|
+
onStackChange: onStackChange
|
|
327
|
+
});
|
|
328
|
+
var stackIndex = stackIndexOverride || calculatedStackIndex;
|
|
329
|
+
var isForeground = stackIndex === 0;
|
|
283
330
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
331
|
+
// If no ref is provided, autofocus on first element
|
|
332
|
+
var autoFocusLock = !(_typeof(autoFocus) === 'object');
|
|
333
|
+
var onCloseHandler = useModalCloseHandler(providedOnClose);
|
|
334
|
+
var onBlanketClicked = useCallback(function (e) {
|
|
335
|
+
if (shouldCloseOnOverlayClick) {
|
|
336
|
+
onCloseHandler(e);
|
|
287
337
|
}
|
|
338
|
+
}, [shouldCloseOnOverlayClick, onCloseHandler]);
|
|
288
339
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
//
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
id: dialogId,
|
|
308
|
-
onClose: onDialogClose,
|
|
309
|
-
onExitFinish: handleDialogExitFinish,
|
|
310
|
-
shouldAnimate: !isFullScreen,
|
|
311
|
-
isOpen: !isExiting,
|
|
312
|
-
shouldHideBackdrop: stackIndex > 0 || Boolean(isBlanketHidden)
|
|
313
|
-
// Dialog requires at least one of `label` or `labelledBy` (string, not undefined).
|
|
314
|
-
// Prefer the consumer-provided `label`; otherwise reference the internal `titleId`.
|
|
315
|
-
}, label ? {
|
|
316
|
-
label: label
|
|
317
|
-
} : {
|
|
318
|
-
labelledBy: titleId
|
|
319
|
-
}, {
|
|
320
|
-
testId: defaultTestId
|
|
321
|
-
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop
|
|
322
|
-
,
|
|
323
|
-
style: dialogStyle
|
|
324
|
-
}), /*#__PURE__*/React.createElement(DialogScrollLock, {
|
|
325
|
-
isOpen: true
|
|
326
|
-
}), dialogPositionStyles &&
|
|
327
|
-
/*#__PURE__*/
|
|
328
|
-
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-global-styles
|
|
329
|
-
React.createElement("style", null, dialogPositionStyles), /*#__PURE__*/React.createElement("div", {
|
|
330
|
-
ref: contentRef,
|
|
331
|
-
className: ax([topLayerStyles.content, !isFullScreen && topLayerStyles.borderRadius, !isFullScreen && fg('platform-dst-shape-theme-default') && topLayerStyles.borderRadiusT26, !isFullScreen && !shouldScrollInViewport && "_bolhzwhf", !isFullScreen && shouldScrollInViewport && "_1tke1kxc _c71lglyw"])
|
|
332
|
-
}, /*#__PURE__*/React.createElement(ModalContext.Provider, {
|
|
333
|
-
value: modalDialogContext
|
|
334
|
-
}, /*#__PURE__*/React.createElement(ScrollContext.Provider, {
|
|
335
|
-
value: shouldScrollInViewport
|
|
336
|
-
}, children))));
|
|
337
|
-
}
|
|
340
|
+
// Stable callback to avoid re-renders when focusLockAllowlist is not provided.
|
|
341
|
+
var allowListCallback = useCallback(function (element) {
|
|
342
|
+
return allowlistElements(element, focusLockAllowlist);
|
|
343
|
+
}, [focusLockAllowlist]);
|
|
344
|
+
|
|
345
|
+
// Prevent background scroll (top-layer path uses DialogScrollLock instead).
|
|
346
|
+
usePreventProgrammaticScroll();
|
|
347
|
+
|
|
348
|
+
// Register with the open layer observer. On the top-layer path the Dialog
|
|
349
|
+
// primitive does this itself, which is why it lives in this legacy-only
|
|
350
|
+
// component rather than the shared dispatcher.
|
|
351
|
+
useNotifyOpenLayerObserver({
|
|
352
|
+
type: 'modal',
|
|
353
|
+
// Always open — modal is conditionally rendered when visible.
|
|
354
|
+
isOpen: true,
|
|
355
|
+
// No-op: no current use case for programmatic close via OpenLayerObserver.
|
|
356
|
+
onClose: noop
|
|
357
|
+
});
|
|
338
358
|
var modalDialogWithBlanket = /*#__PURE__*/React.createElement(Blanket, {
|
|
339
359
|
isTinted: !isBlanketHidden,
|
|
340
360
|
onBlanketClicked: onBlanketClicked,
|
|
@@ -397,5 +417,24 @@ var InternalModalWrapper = /*#__PURE__*/forwardRef(function (props, ref) {
|
|
|
397
417
|
})));
|
|
398
418
|
});
|
|
399
419
|
|
|
420
|
+
// Choose the rendering implementation at the component boundary rather than
|
|
421
|
+
// gating hooks inside a single component. Each implementation owns its own
|
|
422
|
+
// hooks unconditionally, so a runtime feature-flag change swaps component types
|
|
423
|
+
// (a clean remount) instead of changing the hook order of a mounted component.
|
|
424
|
+
var InternalModalWrapper = /*#__PURE__*/forwardRef(function (props, ref) {
|
|
425
|
+
if (fg('platform-dst-top-layer')) {
|
|
426
|
+
// TODO: the top-layer path does not forward the external `ref` (parity
|
|
427
|
+
// with the pre-refactor behavior, where the top-layer branch never
|
|
428
|
+
// consumed it). The public type still advertises `RefAttributes`, so a
|
|
429
|
+
// consumer ref silently no-ops here. Follow up by forwarding `ref` to a
|
|
430
|
+
// sensible element (for example the content `div`) once ref parity is
|
|
431
|
+
// intentionally desired.
|
|
432
|
+
return /*#__PURE__*/React.createElement(ModalWrapperTopLayer, props);
|
|
433
|
+
}
|
|
434
|
+
return /*#__PURE__*/React.createElement(ModalWrapperLegacy, _extends({
|
|
435
|
+
ref: ref
|
|
436
|
+
}, props));
|
|
437
|
+
});
|
|
438
|
+
|
|
400
439
|
// eslint-disable-next-line @repo/internal/react/require-jsdoc
|
|
401
440
|
export default InternalModalWrapper;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/modal-dialog",
|
|
3
|
-
"version": "16.2.
|
|
3
|
+
"version": "16.2.3",
|
|
4
4
|
"description": "A modal dialog displays content that requires user interaction, in a layer above the page.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"@atlaskit/ds-lib": "^8.0.0",
|
|
42
42
|
"@atlaskit/icon": "^37.2.0",
|
|
43
43
|
"@atlaskit/layering": "^5.0.0",
|
|
44
|
-
"@atlaskit/motion": "^
|
|
44
|
+
"@atlaskit/motion": "^8.0.0",
|
|
45
45
|
"@atlaskit/platform-feature-flags": "^2.1.0",
|
|
46
46
|
"@atlaskit/portal": "^6.1.0",
|
|
47
47
|
"@atlaskit/pragmatic-drag-and-drop": "^2.0.0",
|
|
@@ -75,15 +75,15 @@
|
|
|
75
75
|
"@atlaskit/dropdown-menu": "^17.2.0",
|
|
76
76
|
"@atlaskit/flag": "^18.2.0",
|
|
77
77
|
"@atlaskit/form": "^16.1.0",
|
|
78
|
-
"@atlaskit/heading": "^
|
|
79
|
-
"@atlaskit/link": "^
|
|
78
|
+
"@atlaskit/heading": "^7.0.0",
|
|
79
|
+
"@atlaskit/link": "^5.0.0",
|
|
80
80
|
"@atlaskit/popup": "^5.2.0",
|
|
81
81
|
"@atlaskit/radio": "^9.1.0",
|
|
82
82
|
"@atlaskit/section-message": "^9.2.0",
|
|
83
83
|
"@atlaskit/select": "^22.6.0",
|
|
84
84
|
"@atlaskit/spotlight": "^3.0.0",
|
|
85
85
|
"@atlaskit/textfield": "^9.1.0",
|
|
86
|
-
"@atlaskit/tooltip": "^
|
|
86
|
+
"@atlaskit/tooltip": "^24.0.0",
|
|
87
87
|
"@atlassian/feature-flags-test-utils": "^1.2.0",
|
|
88
88
|
"@atlassian/ssr-tests": "workspace:^",
|
|
89
89
|
"@atlassian/structured-docs-types": "workspace:^",
|