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