@atlaskit/modal-dialog 16.2.5 → 16.3.0

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,16 @@
1
1
  # @atlaskit/modal-dialog
2
2
 
3
+ ## 16.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`a50c282feb7d3`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/a50c282feb7d3) -
8
+ Fixes various layout issues in the top layer modal variant.
9
+
10
+ ### Patch Changes
11
+
12
+ - Updated dependencies
13
+
3
14
  ## 16.2.5
4
15
 
5
16
  ### Patch Changes
@@ -89,4 +89,29 @@ var FullScreenModalDialog = exports.FullScreenModalDialog = /*#__PURE__*/(0, _re
89
89
  ref: ref
90
90
  }));
91
91
  });
92
- FullScreenModalDialog.displayName = 'FullScreenModalDialog';
92
+ FullScreenModalDialog.displayName = 'FullScreenModalDialog';
93
+
94
+ /**
95
+ * @deprecated Volt migration shim. Import from the explicit subpath instead.
96
+ * Retained only while consumers migrate off this re-export.
97
+ */
98
+
99
+ /**
100
+ * @deprecated Volt migration shim. Import from the explicit subpath instead.
101
+ * Retained only while consumers migrate off this re-export.
102
+ */
103
+
104
+ /**
105
+ * @deprecated Volt migration shim. Import from the explicit subpath instead.
106
+ * Retained only while consumers migrate off this re-export.
107
+ */
108
+
109
+ /**
110
+ * @deprecated Volt migration shim. Import from the explicit subpath instead.
111
+ * Retained only while consumers migrate off this re-export.
112
+ */
113
+
114
+ /**
115
+ * @deprecated Volt migration shim. Import from the explicit subpath instead.
116
+ * Retained only while consumers migrate off this re-export.
117
+ */
@@ -1,23 +1,28 @@
1
+ ._2rkoidpf{border-radius:0}
1
2
  ._152tze3t{inset-block-start:var(--ds-space-0,0)}
2
3
  ._18m91wug{overflow-y:auto}
4
+ ._18m9ewfl{overflow-y:visible}
5
+ ._18u0idpf{margin-left:0}
6
+ ._19pkidpf{margin-top:0}
3
7
  ._1bsbauwl{width:100vw}
4
8
  ._1dg11kw7>form:only-child{flex-direction:inherit}
5
9
  ._1e02ze3t{inset-inline-start:var(--ds-space-0,0)}
6
10
  ._1e0c1txw{display:flex}
7
11
  ._1mq81kw7>form:only-child{display:inherit}
8
12
  ._1q1l1bhr{--ds-elevation-surface-current:var(--ds-surface-overlay,#fff)}
9
- ._1tke1kxc{min-height:100vh}
13
+ ._1reoewfl{overflow-x:visible}
14
+ ._2hwxidpf{margin-right:0}
10
15
  ._2lx21bp4{flex-direction:column}
11
16
  ._4t3i1kxc{height:100vh}
12
- ._4t3i1osq{height:100%}
13
17
  ._8am5i4x0{-webkit-overflow-scrolling:touch}
14
18
  ._bfhk1bhr{background-color:var(--ds-surface-overlay,#fff)}
15
- ._c71lglyw{max-height:none}
16
19
  ._kqsw1n9t{position:fixed}
17
20
  ._lcxv1wug{pointer-events:auto}
21
+ ._ln19p27b{scrollbar-gutter:stable}
18
22
  ._m01u1kw7>form:only-child{max-height:inherit}
23
+ ._otyridpf{margin-bottom:0}
19
24
  ._syazi7uo{color:var(--ds-text,#292a2e)}._128mdkaa:focus-visible{outline-width:var(--ds-border-width-focused,2px)}
20
25
  ._1ah3dkaa:focus-visible{outline-offset:var(--ds-border-width-focused,2px)}
21
26
  ._mizu1v1w:focus-visible{outline-color:var(--ds-border-focused,#4688ec)}
22
27
  ._ra3xnqa1:focus-visible{outline-style:solid}
23
- @media (min-width:30rem){._bolhzwhf._bolhzwhf{max-height:calc(100vh - 119px)}._epkxfajl{border-radius:var(--ds-radius-small,3px)}._epkxpb1k{border-radius:var(--ds-radius-xlarge,9pt)}._zg7p130s{box-shadow:var(--ds-shadow-overlay,0 8px 9pt #1e1f2126,0 0 1px #1e1f214f)}}
28
+ @media (min-width:30rem){._1jyk17ks{height:var(--modal-dialog-height)}._g7116m9r{width:var(--modal-dialog-width)}._we1i18uh{max-width:calc(100vw - 90pt)}._1sg41wqb{margin-top:60px}._maxs1wug{margin-right:auto}._1jvh1wqb{margin-bottom:60px}._1uca1wug{margin-left:auto}._1mpv184x{min-height:min-content}._e8uo1ris{max-height:max-content}._epkxpb1k{border-radius:var(--ds-radius-xlarge,9pt)}._zg7p130s{box-shadow:var(--ds-shadow-overlay,0 8px 9pt #1e1f2126,0 0 1px #1e1f214f)}}
@@ -38,34 +38,42 @@ var _element = require("../pragmatic-drag-and-drop/disable-dragging-to-cross-ori
38
38
  var _external = require("../pragmatic-drag-and-drop/disable-dragging-to-cross-origin-iframes/external");
39
39
  var _textSelection = require("../pragmatic-drag-and-drop/disable-dragging-to-cross-origin-iframes/text-selection");
40
40
  var _scrollContext = require("../scroll-context");
41
- var _dialogHeight = require("./dialog-height");
42
- var _dialogWidth = require("./dialog-width");
41
+ var _width = require("../width");
43
42
  var _modalDialog = _interopRequireDefault(require("./modal-dialog"));
44
43
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof3(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
45
44
  var fillScreenStyles = null;
46
45
 
46
+ /**
47
+ * Styles applied to the <dialog> wrapper, NOT the visual modal surface.
48
+ */
49
+ var dialogStyles = {
50
+ 'body-scroll': "_1reoewfl _18m9ewfl _19pkidpf _2hwxidpf _otyridpf _18u0idpf _1sg41wqb _maxs1wug _1jvh1wqb _1uca1wug",
51
+ 'viewport-scroll': "_ln19p27b _1bsbauwl _4t3i1kxc",
52
+ 'full-screen': ""
53
+ };
54
+
47
55
  // Visual styles for modal content inside native <dialog>.
48
56
  // Uses cssMap (not css) to avoid triggering no-nested-styles lint rule.
49
57
 
50
58
  var LOCAL_CURRENT_SURFACE_CSS_VAR = '--ds-elevation-surface-current';
51
- var topLayerStyles = {
52
- content: "_1e0c1txw _4t3i1osq _2lx21bp4 _bfhk1bhr _syazi7uo _1q1l1bhr _lcxv1wug _1mq81kw7 _m01u1kw7 _1dg11kw7 _mizu1v1w _1ah3dkaa _ra3xnqa1 _128mdkaa _zg7p130s",
53
- borderRadius: "_epkxfajl",
54
- borderRadiusT26: "_epkxpb1k"
55
- };
56
59
 
57
- // Scroll-mode styles for the content div.
58
- // Height overrides use ID-scoped <style> (see dialogPositionStyles) because
59
- // Compiled atomic classes have specificity (0,1,0) (increaseSpecificity is disabled).
60
- // The doubled-ID selector (#id#id > div) at (2,0,1) reliably wins.
61
- // Only non-height properties needing the && boost remain here.
62
-
63
- var topLayerBodyScrollStyles = null;
64
- var topLayerViewportScrollStyles = null;
60
+ /**
61
+ * Styles applied to the visual modal surface.
62
+ */
63
+ var surfaceStyles = {
64
+ root: "_1e0c1txw _2lx21bp4 _bfhk1bhr _syazi7uo _1q1l1bhr _lcxv1wug _1mq81kw7 _m01u1kw7 _1dg11kw7 _mizu1v1w _1ah3dkaa _ra3xnqa1 _128mdkaa _zg7p130s",
65
+ borderRadius: "_2rkoidpf _epkxpb1k"
66
+ };
67
+ var topLayerScrollModeStyles = {
68
+ 'full-screen': "_1bsbauwl _4t3i1kxc _kqsw1n9t _152tze3t _1e02ze3t _18m91wug _8am5i4x0",
69
+ 'viewport-scroll': "_4t3i1kxc _1sg41wqb _maxs1wug _1jvh1wqb _1uca1wug _1mpv184x _1jyk17ks _g7116m9r _we1i18uh",
70
+ 'body-scroll': "_1bsbauwl _4t3i1kxc _1jyk17ks _g7116m9r _we1i18uh"
71
+ };
72
+ var topLayerAutoHeightStyles = null;
65
73
  var allowlistElements = function allowlistElements(element, callback) {
66
74
  // Allow focus outside modal when AUI dialog is visible
67
75
  // eslint-disable-next-line @atlaskit/platform/no-direct-document-usage -- legacy FocusLock allowlist
68
- if (Boolean(document.querySelector('.aui-blanket:not([hidden])'))) {
76
+ if (document.querySelector('.aui-blanket:not([hidden])')) {
69
77
  return false;
70
78
  }
71
79
  // Optional callback to let consumers exclude elements from focus lock
@@ -83,9 +91,64 @@ function useModalCloseHandler(providedOnClose) {
83
91
  action: 'closed',
84
92
  componentName: 'modalDialog',
85
93
  packageName: "@atlaskit/modal-dialog",
86
- packageVersion: "16.2.4"
94
+ packageVersion: "16.2.5"
87
95
  });
88
96
  }
97
+ function getScrollMode(_ref) {
98
+ var shouldScrollInViewport = _ref.shouldScrollInViewport,
99
+ isFullScreen = _ref.isFullScreen;
100
+ if (isFullScreen) {
101
+ return 'full-screen';
102
+ }
103
+ if (shouldScrollInViewport) {
104
+ return 'viewport-scroll';
105
+ }
106
+ return 'body-scroll';
107
+ }
108
+ function isWidthName(value) {
109
+ return _width.width.values.includes(value);
110
+ }
111
+ function getTopLayerSurfaceWidth(input) {
112
+ if (!input) {
113
+ return 'auto';
114
+ }
115
+ if (typeof input === 'number') {
116
+ return "".concat(input, "px");
117
+ }
118
+ if (isWidthName(input)) {
119
+ return "".concat(_width.width.widths[input], "px");
120
+ }
121
+ if (input.endsWith('%')) {
122
+ // Percentage widths need special handling in the top layer.
123
+ // In legacy, the percentage resolved against the Positioner's max-width
124
+ // (100vw - 120px). In the top layer, the <dialog>'s containing block is the
125
+ // viewport (100vw), so a raw percentage would produce a wider modal.
126
+ // Transform e.g. '42%' → 'calc(42 * (100vw - 120px) / 100)' to match legacy.
127
+ return "calc(".concat(parseFloat(input), " * (100vw - 120px) / 100)");
128
+ }
129
+ return input;
130
+ }
131
+ function getTopLayerSurfaceHeight(input) {
132
+ if (!input) {
133
+ // Although this value would ordinarily fill the viewport height,
134
+ // there is also a `max-height: max-content` applied to keep it constrained.
135
+ // But we need to provide the modal with a fixed height (non-keyword, non-percentage)
136
+ // in order for child elements with 100% height to work correctly.
137
+ return 'calc(100vh - 120px)';
138
+ }
139
+ if (typeof input === 'number') {
140
+ return "".concat(input, "px");
141
+ }
142
+ if (input.endsWith('%')) {
143
+ // Percentage heights need special handling in the top layer.
144
+ // In legacy, the percentage resolved against the Positioner's max-height
145
+ // (100vh - 120px). In the top layer, the <dialog>'s containing block is the
146
+ // viewport (100vh), so a raw percentage would produce a taller modal.
147
+ // Transform e.g. '42%' → 'calc(42 * (100vh - 120px) / 100)' to match legacy.
148
+ return "calc(".concat(parseFloat(input), " * (100vh - 120px) / 100)");
149
+ }
150
+ return input;
151
+ }
89
152
 
90
153
  /**
91
154
  * Top-layer rendering path (platform-dst-top-layer).
@@ -114,23 +177,23 @@ function ModalWrapperTopLayer(props) {
114
177
  shouldScrollInViewport = _props$shouldScrollIn === void 0 ? false : _props$shouldScrollIn,
115
178
  _props$shouldReturnFo = props.shouldReturnFocus,
116
179
  shouldReturnFocus = _props$shouldReturnFo === void 0 ? true : _props$shouldReturnFo,
117
- stackIndexOverride = props.stackIndex,
118
180
  providedOnClose = props.onClose,
119
181
  _props$onStackChange = props.onStackChange,
120
182
  onStackChange = _props$onStackChange === void 0 ? _noop.default : _props$onStackChange,
121
183
  isBlanketHidden = props.isBlanketHidden,
122
184
  children = props.children,
123
185
  height = props.height,
124
- width = props.width,
186
+ _props$width = props.width,
187
+ width = _props$width === void 0 ? 'medium' : _props$width,
125
188
  onCloseComplete = props.onCloseComplete,
126
189
  onOpenComplete = props.onOpenComplete,
127
190
  label = props.label,
128
191
  testId = props.testId,
129
- isFullScreen = props.isFullScreen;
130
- var calculatedStackIndex = (0, _useModalStack.default)({
192
+ _props$isFullScreen = props.isFullScreen,
193
+ isFullScreen = _props$isFullScreen === void 0 ? false : _props$isFullScreen;
194
+ (0, _useModalStack.default)({
131
195
  onStackChange: onStackChange
132
196
  });
133
- var stackIndex = stackIndexOverride || calculatedStackIndex;
134
197
  var onCloseHandler = useModalCloseHandler(providedOnClose);
135
198
  var _useExitingPersistenc = (0, _exitingPersistence.useExitingPersistence)(),
136
199
  isExiting = _useExitingPersistenc.isExiting,
@@ -165,8 +228,8 @@ function ModalWrapperTopLayer(props) {
165
228
 
166
229
  // Only forward close when the reason is allowed by props.
167
230
  // Passes a synthetic event to satisfy the KeyboardOrMouseEvent contract.
168
- var onDialogClose = (0, _react.useCallback)(function (_ref) {
169
- var reason = _ref.reason;
231
+ var onDialogClose = (0, _react.useCallback)(function (_ref2) {
232
+ var reason = _ref2.reason;
170
233
  if (reason === 'escape' && shouldCloseOnEscapePress) {
171
234
  onCloseHandler((0, _dialog.createCloseEvent)({
172
235
  reason: reason
@@ -223,56 +286,17 @@ function ModalWrapperTopLayer(props) {
223
286
  (0, _react.useEffect)(function () {
224
287
  return (0, _combine.combine)((0, _element.disableDraggingToCrossOriginIFramesForElement)(), (0, _textSelection.disableDraggingToCrossOriginIFramesForTextSelection)(), (0, _external.disableDraggingToCrossOriginIFramesForExternal)());
225
288
  }, []);
226
-
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);
232
-
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
- };
246
-
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
- }
251
-
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, "}}");
289
+ var scrollMode = getScrollMode({
290
+ isFullScreen: isFullScreen,
291
+ shouldScrollInViewport: shouldScrollInViewport
292
+ });
268
293
  return /*#__PURE__*/React.createElement(_dialog.Dialog, (0, _extends2.default)({
269
294
  ref: dialogRef,
270
- id: dialogId,
271
295
  onClose: onDialogClose,
272
296
  onExitFinish: handleDialogExitFinish,
273
297
  shouldAnimate: !isFullScreen,
274
298
  isOpen: !isExiting,
275
- shouldHideBackdrop: stackIndex > 0 || Boolean(isBlanketHidden)
299
+ shouldHideBackdrop: isBlanketHidden
276
300
  // Dialog requires at least one of `label` or `labelledBy` (string, not undefined).
277
301
  // Prefer the consumer-provided `label`; otherwise reference the internal `titleId`.
278
302
  }, label ? {
@@ -280,18 +304,17 @@ function ModalWrapperTopLayer(props) {
280
304
  } : {
281
305
  labelledBy: titleId
282
306
  }, {
283
- testId: defaultTestId
284
- // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop
285
- ,
286
- style: dialogStyle
307
+ testId: defaultTestId,
308
+ xcss: dialogStyles[scrollMode]
287
309
  }), /*#__PURE__*/React.createElement(_dialogScrollLock.DialogScrollLock, {
288
310
  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", {
311
+ }), /*#__PURE__*/React.createElement("div", {
293
312
  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"])
313
+ style: {
314
+ '--modal-dialog-height': getTopLayerSurfaceHeight(height),
315
+ '--modal-dialog-width': getTopLayerSurfaceWidth(width)
316
+ },
317
+ className: (0, _runtime.ax)([surfaceStyles.root, !isFullScreen && surfaceStyles.borderRadius, topLayerScrollModeStyles[scrollMode], !height && !isFullScreen && "_e8uo1ris"])
295
318
  }, /*#__PURE__*/React.createElement(_context.ModalContext.Provider, {
296
319
  value: modalDialogContext
297
320
  }, /*#__PURE__*/React.createElement(_scrollContext.ScrollContext.Provider, {
@@ -46,8 +46,29 @@ export const FullScreenModalDialog = /*#__PURE__*/forwardRef((props, ref) => {
46
46
  }));
47
47
  });
48
48
  FullScreenModalDialog.displayName = 'FullScreenModalDialog';
49
+
50
+ /**
51
+ * @deprecated Volt migration shim. Import from the explicit subpath instead.
52
+ * Retained only while consumers migrate off this re-export.
53
+ */
49
54
  export { default as ModalTransition } from './modal-transition';
55
+ /**
56
+ * @deprecated Volt migration shim. Import from the explicit subpath instead.
57
+ * Retained only while consumers migrate off this re-export.
58
+ */
50
59
  export { default as ModalHeader } from './modal-header';
60
+ /**
61
+ * @deprecated Volt migration shim. Import from the explicit subpath instead.
62
+ * Retained only while consumers migrate off this re-export.
63
+ */
51
64
  export { default as ModalTitle } from './modal-title';
65
+ /**
66
+ * @deprecated Volt migration shim. Import from the explicit subpath instead.
67
+ * Retained only while consumers migrate off this re-export.
68
+ */
52
69
  export { default as ModalBody } from './modal-body';
70
+ /**
71
+ * @deprecated Volt migration shim. Import from the explicit subpath instead.
72
+ * Retained only while consumers migrate off this re-export.
73
+ */
53
74
  export { default as ModalFooter } from './modal-footer';
@@ -1,23 +1,28 @@
1
+ ._2rkoidpf{border-radius:0}
1
2
  ._152tze3t{inset-block-start:var(--ds-space-0,0)}
2
3
  ._18m91wug{overflow-y:auto}
4
+ ._18m9ewfl{overflow-y:visible}
5
+ ._18u0idpf{margin-left:0}
6
+ ._19pkidpf{margin-top:0}
3
7
  ._1bsbauwl{width:100vw}
4
8
  ._1dg11kw7>form:only-child{flex-direction:inherit}
5
9
  ._1e02ze3t{inset-inline-start:var(--ds-space-0,0)}
6
10
  ._1e0c1txw{display:flex}
7
11
  ._1mq81kw7>form:only-child{display:inherit}
8
12
  ._1q1l1bhr{--ds-elevation-surface-current:var(--ds-surface-overlay,#fff)}
9
- ._1tke1kxc{min-height:100vh}
13
+ ._1reoewfl{overflow-x:visible}
14
+ ._2hwxidpf{margin-right:0}
10
15
  ._2lx21bp4{flex-direction:column}
11
16
  ._4t3i1kxc{height:100vh}
12
- ._4t3i1osq{height:100%}
13
17
  ._8am5i4x0{-webkit-overflow-scrolling:touch}
14
18
  ._bfhk1bhr{background-color:var(--ds-surface-overlay,#fff)}
15
- ._c71lglyw{max-height:none}
16
19
  ._kqsw1n9t{position:fixed}
17
20
  ._lcxv1wug{pointer-events:auto}
21
+ ._ln19p27b{scrollbar-gutter:stable}
18
22
  ._m01u1kw7>form:only-child{max-height:inherit}
23
+ ._otyridpf{margin-bottom:0}
19
24
  ._syazi7uo{color:var(--ds-text,#292a2e)}._128mdkaa:focus-visible{outline-width:var(--ds-border-width-focused,2px)}
20
25
  ._1ah3dkaa:focus-visible{outline-offset:var(--ds-border-width-focused,2px)}
21
26
  ._mizu1v1w:focus-visible{outline-color:var(--ds-border-focused,#4688ec)}
22
27
  ._ra3xnqa1:focus-visible{outline-style:solid}
23
- @media (min-width:30rem){._bolhzwhf._bolhzwhf{max-height:calc(100vh - 119px)}._epkxfajl{border-radius:var(--ds-radius-small,3px)}._epkxpb1k{border-radius:var(--ds-radius-xlarge,9pt)}._zg7p130s{box-shadow:var(--ds-shadow-overlay,0 8px 9pt #1e1f2126,0 0 1px #1e1f214f)}}
28
+ @media (min-width:30rem){._1jyk17ks{height:var(--modal-dialog-height)}._g7116m9r{width:var(--modal-dialog-width)}._we1i18uh{max-width:calc(100vw - 90pt)}._1sg41wqb{margin-top:60px}._maxs1wug{margin-right:auto}._1jvh1wqb{margin-bottom:60px}._1uca1wug{margin-left:auto}._1mpv184x{min-height:min-content}._e8uo1ris{max-height:max-content}._epkxpb1k{border-radius:var(--ds-radius-xlarge,9pt)}._zg7p130s{box-shadow:var(--ds-shadow-overlay,0 8px 9pt #1e1f2126,0 0 1px #1e1f214f)}}
@@ -29,33 +29,41 @@ import { disableDraggingToCrossOriginIFramesForElement } from '../pragmatic-drag
29
29
  import { disableDraggingToCrossOriginIFramesForExternal } from '../pragmatic-drag-and-drop/disable-dragging-to-cross-origin-iframes/external';
30
30
  import { disableDraggingToCrossOriginIFramesForTextSelection } from '../pragmatic-drag-and-drop/disable-dragging-to-cross-origin-iframes/text-selection';
31
31
  import { ScrollContext } from '../scroll-context';
32
- import { dialogHeight } from './dialog-height';
33
- import { dialogWidth as getDialogWidth } from './dialog-width';
32
+ import { width } from '../width';
34
33
  import { default as ModalDialog } from './modal-dialog';
35
34
  const fillScreenStyles = null;
36
35
 
36
+ /**
37
+ * Styles applied to the <dialog> wrapper, NOT the visual modal surface.
38
+ */
39
+ const dialogStyles = {
40
+ 'body-scroll': "_1reoewfl _18m9ewfl _19pkidpf _2hwxidpf _otyridpf _18u0idpf _1sg41wqb _maxs1wug _1jvh1wqb _1uca1wug",
41
+ 'viewport-scroll': "_ln19p27b _1bsbauwl _4t3i1kxc",
42
+ 'full-screen': ""
43
+ };
44
+
37
45
  // Visual styles for modal content inside native <dialog>.
38
46
  // Uses cssMap (not css) to avoid triggering no-nested-styles lint rule.
39
47
 
40
48
  const LOCAL_CURRENT_SURFACE_CSS_VAR = '--ds-elevation-surface-current';
41
- const topLayerStyles = {
42
- content: "_1e0c1txw _4t3i1osq _2lx21bp4 _bfhk1bhr _syazi7uo _1q1l1bhr _lcxv1wug _1mq81kw7 _m01u1kw7 _1dg11kw7 _mizu1v1w _1ah3dkaa _ra3xnqa1 _128mdkaa _zg7p130s",
43
- borderRadius: "_epkxfajl",
44
- borderRadiusT26: "_epkxpb1k"
45
- };
46
49
 
47
- // Scroll-mode styles for the content div.
48
- // Height overrides use ID-scoped <style> (see dialogPositionStyles) because
49
- // Compiled atomic classes have specificity (0,1,0) (increaseSpecificity is disabled).
50
- // The doubled-ID selector (#id#id > div) at (2,0,1) reliably wins.
51
- // Only non-height properties needing the && boost remain here.
52
-
53
- const topLayerBodyScrollStyles = null;
54
- const topLayerViewportScrollStyles = null;
50
+ /**
51
+ * Styles applied to the visual modal surface.
52
+ */
53
+ const surfaceStyles = {
54
+ root: "_1e0c1txw _2lx21bp4 _bfhk1bhr _syazi7uo _1q1l1bhr _lcxv1wug _1mq81kw7 _m01u1kw7 _1dg11kw7 _mizu1v1w _1ah3dkaa _ra3xnqa1 _128mdkaa _zg7p130s",
55
+ borderRadius: "_2rkoidpf _epkxpb1k"
56
+ };
57
+ const topLayerScrollModeStyles = {
58
+ 'full-screen': "_1bsbauwl _4t3i1kxc _kqsw1n9t _152tze3t _1e02ze3t _18m91wug _8am5i4x0",
59
+ 'viewport-scroll': "_4t3i1kxc _1sg41wqb _maxs1wug _1jvh1wqb _1uca1wug _1mpv184x _1jyk17ks _g7116m9r _we1i18uh",
60
+ 'body-scroll': "_1bsbauwl _4t3i1kxc _1jyk17ks _g7116m9r _we1i18uh"
61
+ };
62
+ const topLayerAutoHeightStyles = null;
55
63
  const allowlistElements = (element, callback) => {
56
64
  // Allow focus outside modal when AUI dialog is visible
57
65
  // eslint-disable-next-line @atlaskit/platform/no-direct-document-usage -- legacy FocusLock allowlist
58
- if (Boolean(document.querySelector('.aui-blanket:not([hidden])'))) {
66
+ if (document.querySelector('.aui-blanket:not([hidden])')) {
59
67
  return false;
60
68
  }
61
69
  // Optional callback to let consumers exclude elements from focus lock
@@ -73,9 +81,65 @@ function useModalCloseHandler(providedOnClose) {
73
81
  action: 'closed',
74
82
  componentName: 'modalDialog',
75
83
  packageName: "@atlaskit/modal-dialog",
76
- packageVersion: "16.2.4"
84
+ packageVersion: "16.2.5"
77
85
  });
78
86
  }
87
+ function getScrollMode({
88
+ shouldScrollInViewport,
89
+ isFullScreen
90
+ }) {
91
+ if (isFullScreen) {
92
+ return 'full-screen';
93
+ }
94
+ if (shouldScrollInViewport) {
95
+ return 'viewport-scroll';
96
+ }
97
+ return 'body-scroll';
98
+ }
99
+ function isWidthName(value) {
100
+ return width.values.includes(value);
101
+ }
102
+ function getTopLayerSurfaceWidth(input) {
103
+ if (!input) {
104
+ return 'auto';
105
+ }
106
+ if (typeof input === 'number') {
107
+ return `${input}px`;
108
+ }
109
+ if (isWidthName(input)) {
110
+ return `${width.widths[input]}px`;
111
+ }
112
+ if (input.endsWith('%')) {
113
+ // Percentage widths need special handling in the top layer.
114
+ // In legacy, the percentage resolved against the Positioner's max-width
115
+ // (100vw - 120px). In the top layer, the <dialog>'s containing block is the
116
+ // viewport (100vw), so a raw percentage would produce a wider modal.
117
+ // Transform e.g. '42%' → 'calc(42 * (100vw - 120px) / 100)' to match legacy.
118
+ return `calc(${parseFloat(input)} * (100vw - 120px) / 100)`;
119
+ }
120
+ return input;
121
+ }
122
+ function getTopLayerSurfaceHeight(input) {
123
+ if (!input) {
124
+ // Although this value would ordinarily fill the viewport height,
125
+ // there is also a `max-height: max-content` applied to keep it constrained.
126
+ // But we need to provide the modal with a fixed height (non-keyword, non-percentage)
127
+ // in order for child elements with 100% height to work correctly.
128
+ return 'calc(100vh - 120px)';
129
+ }
130
+ if (typeof input === 'number') {
131
+ return `${input}px`;
132
+ }
133
+ if (input.endsWith('%')) {
134
+ // Percentage heights need special handling in the top layer.
135
+ // In legacy, the percentage resolved against the Positioner's max-height
136
+ // (100vh - 120px). In the top layer, the <dialog>'s containing block is the
137
+ // viewport (100vh), so a raw percentage would produce a taller modal.
138
+ // Transform e.g. '42%' → 'calc(42 * (100vh - 120px) / 100)' to match legacy.
139
+ return `calc(${parseFloat(input)} * (100vh - 120px) / 100)`;
140
+ }
141
+ return input;
142
+ }
79
143
 
80
144
  /**
81
145
  * Top-layer rendering path (platform-dst-top-layer).
@@ -101,23 +165,21 @@ function ModalWrapperTopLayer(props) {
101
165
  shouldCloseOnOverlayClick = true,
102
166
  shouldScrollInViewport = false,
103
167
  shouldReturnFocus = true,
104
- stackIndex: stackIndexOverride,
105
168
  onClose: providedOnClose,
106
169
  onStackChange = noop,
107
170
  isBlanketHidden,
108
171
  children,
109
172
  height,
110
- width,
173
+ width = 'medium',
111
174
  onCloseComplete,
112
175
  onOpenComplete,
113
176
  label,
114
177
  testId,
115
- isFullScreen
178
+ isFullScreen = false
116
179
  } = props;
117
- const calculatedStackIndex = useModalStack({
180
+ useModalStack({
118
181
  onStackChange
119
182
  });
120
- const stackIndex = stackIndexOverride || calculatedStackIndex;
121
183
  const onCloseHandler = useModalCloseHandler(providedOnClose);
122
184
  const {
123
185
  isExiting,
@@ -210,57 +272,17 @@ function ModalWrapperTopLayer(props) {
210
272
  useEffect(() => {
211
273
  return combine(disableDraggingToCrossOriginIFramesForElement(), disableDraggingToCrossOriginIFramesForTextSelection(), disableDraggingToCrossOriginIFramesForExternal());
212
274
  }, []);
213
-
214
- // Responsive layout via ID-scoped <style> (same pattern as Dialog's hideBackdrop).
215
- // ID selector beats Compiled atomic classes without !important and supports @media.
216
- const namedWidth = getDialogWidth(width !== null && width !== void 0 ? width : 'medium');
217
- const dialogId = `modal-dialog-${id}`;
218
- const escapedDialogId = CSS.escape(dialogId);
219
-
220
- // Percentage widths need special handling in the top layer.
221
- // In legacy, the percentage resolved against the Positioner's max-width
222
- // (100vw - 120px). In the top layer, the <dialog>'s containing block is the
223
- // viewport (100vw), so a raw percentage would produce a wider modal.
224
- // Transform e.g. '42%' → 'calc(42 * (100vw - 120px) / 100)' to match legacy.
225
- const resolvedWidth = namedWidth.endsWith('%') ? `calc(${parseFloat(namedWidth)} * (100vw - 120px) / 100)` : namedWidth;
226
- const dialogStyle = isFullScreen ? {
227
- width: '100vw',
228
- height: '100vh',
229
- margin: '0'
230
- } : {
231
- width: `min(${resolvedWidth}, 100vw)`
232
- };
233
-
234
- // Shift stacked background modals down by space.100 (8px) per level.
235
- if (stackIndex > 0) {
236
- dialogStyle['transform'] = `translateY(calc(${stackIndex}px * ${"var(--ds-space-100, 8px)"}))`;
237
- }
238
-
239
- // Mobile: viewport fill. Desktop (≥ 30rem): gutter margins, auto height.
240
- // Content-div height set via #id > div to beat Compiled's atomic specificity.
241
- const desktopMargin = shouldScrollInViewport ? '60px auto' : '60px auto auto';
242
- const resolvedHeight = dialogHeight(height);
243
- // Body-scroll: specified height or auto. Viewport-scroll: uses min-height.
244
- const desktopContentHeight = shouldScrollInViewport ? 'auto' : resolvedHeight;
245
- const desktopContentMinHeight = shouldScrollInViewport ? resolvedHeight : 'auto';
246
- // Viewport-scroll: the legacy Positioner was a fixed 100vh container that
247
- // scrolled internally, so the modal section could fill (100vh - 60px top gutter).
248
- // In the top layer the <dialog> sizes to content with height:auto, so we need
249
- // an explicit min-height to ensure the dialog stretches to the same visible area.
250
- const desktopDialogMinHeight = shouldScrollInViewport ? 'min-height:calc(100vh - 60px);' : '';
251
- // Doubled-ID selector (#id#id > div) at specificity (2,0,1) beats
252
- // Compiled atomic classes at (0,1,0) (increaseSpecificity is disabled).
253
- const dialogPositionStyles = isFullScreen ? '' :
254
- // Mobile: edge-to-edge. Desktop (≥ 30rem): 60px gutters, max-width.
255
- `#${escapedDialogId}#${escapedDialogId}{margin:0;height:100vh}#${escapedDialogId}#${escapedDialogId}>div{height:100%}@media(min-width:30rem){#${escapedDialogId}#${escapedDialogId}{margin:${desktopMargin};height:auto;${desktopDialogMinHeight}max-width:calc(100vw - 120px)}#${escapedDialogId}#${escapedDialogId}>div{height:${desktopContentHeight};min-height:${desktopContentMinHeight}}}`;
275
+ const scrollMode = getScrollMode({
276
+ isFullScreen,
277
+ shouldScrollInViewport
278
+ });
256
279
  return /*#__PURE__*/React.createElement(Dialog, _extends({
257
280
  ref: dialogRef,
258
- id: dialogId,
259
281
  onClose: onDialogClose,
260
282
  onExitFinish: handleDialogExitFinish,
261
283
  shouldAnimate: !isFullScreen,
262
284
  isOpen: !isExiting,
263
- shouldHideBackdrop: stackIndex > 0 || Boolean(isBlanketHidden)
285
+ shouldHideBackdrop: isBlanketHidden
264
286
  // Dialog requires at least one of `label` or `labelledBy` (string, not undefined).
265
287
  // Prefer the consumer-provided `label`; otherwise reference the internal `titleId`.
266
288
  }, label ? {
@@ -268,18 +290,17 @@ function ModalWrapperTopLayer(props) {
268
290
  } : {
269
291
  labelledBy: titleId
270
292
  }, {
271
- testId: defaultTestId
272
- // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop
273
- ,
274
- style: dialogStyle
293
+ testId: defaultTestId,
294
+ xcss: dialogStyles[scrollMode]
275
295
  }), /*#__PURE__*/React.createElement(DialogScrollLock, {
276
296
  isOpen: true
277
- }), dialogPositionStyles &&
278
- /*#__PURE__*/
279
- // eslint-disable-next-line @atlaskit/ui-styling-standard/no-global-styles
280
- React.createElement("style", null, dialogPositionStyles), /*#__PURE__*/React.createElement("div", {
297
+ }), /*#__PURE__*/React.createElement("div", {
281
298
  ref: contentRef,
282
- className: ax([topLayerStyles.content, !isFullScreen && topLayerStyles.borderRadius, !isFullScreen && fg('platform-dst-shape-theme-default') && topLayerStyles.borderRadiusT26, !isFullScreen && !shouldScrollInViewport && "_bolhzwhf", !isFullScreen && shouldScrollInViewport && "_1tke1kxc _c71lglyw"])
299
+ style: {
300
+ '--modal-dialog-height': getTopLayerSurfaceHeight(height),
301
+ '--modal-dialog-width': getTopLayerSurfaceWidth(width)
302
+ },
303
+ className: ax([surfaceStyles.root, !isFullScreen && surfaceStyles.borderRadius, topLayerScrollModeStyles[scrollMode], !height && !isFullScreen && "_e8uo1ris"])
283
304
  }, /*#__PURE__*/React.createElement(ModalContext.Provider, {
284
305
  value: modalDialogContext
285
306
  }, /*#__PURE__*/React.createElement(ScrollContext.Provider, {
@@ -46,8 +46,29 @@ export var FullScreenModalDialog = /*#__PURE__*/forwardRef(function (props, ref)
46
46
  }));
47
47
  });
48
48
  FullScreenModalDialog.displayName = 'FullScreenModalDialog';
49
+
50
+ /**
51
+ * @deprecated Volt migration shim. Import from the explicit subpath instead.
52
+ * Retained only while consumers migrate off this re-export.
53
+ */
49
54
  export { default as ModalTransition } from './modal-transition';
55
+ /**
56
+ * @deprecated Volt migration shim. Import from the explicit subpath instead.
57
+ * Retained only while consumers migrate off this re-export.
58
+ */
50
59
  export { default as ModalHeader } from './modal-header';
60
+ /**
61
+ * @deprecated Volt migration shim. Import from the explicit subpath instead.
62
+ * Retained only while consumers migrate off this re-export.
63
+ */
51
64
  export { default as ModalTitle } from './modal-title';
65
+ /**
66
+ * @deprecated Volt migration shim. Import from the explicit subpath instead.
67
+ * Retained only while consumers migrate off this re-export.
68
+ */
52
69
  export { default as ModalBody } from './modal-body';
70
+ /**
71
+ * @deprecated Volt migration shim. Import from the explicit subpath instead.
72
+ * Retained only while consumers migrate off this re-export.
73
+ */
53
74
  export { default as ModalFooter } from './modal-footer';
@@ -1,23 +1,28 @@
1
+ ._2rkoidpf{border-radius:0}
1
2
  ._152tze3t{inset-block-start:var(--ds-space-0,0)}
2
3
  ._18m91wug{overflow-y:auto}
4
+ ._18m9ewfl{overflow-y:visible}
5
+ ._18u0idpf{margin-left:0}
6
+ ._19pkidpf{margin-top:0}
3
7
  ._1bsbauwl{width:100vw}
4
8
  ._1dg11kw7>form:only-child{flex-direction:inherit}
5
9
  ._1e02ze3t{inset-inline-start:var(--ds-space-0,0)}
6
10
  ._1e0c1txw{display:flex}
7
11
  ._1mq81kw7>form:only-child{display:inherit}
8
12
  ._1q1l1bhr{--ds-elevation-surface-current:var(--ds-surface-overlay,#fff)}
9
- ._1tke1kxc{min-height:100vh}
13
+ ._1reoewfl{overflow-x:visible}
14
+ ._2hwxidpf{margin-right:0}
10
15
  ._2lx21bp4{flex-direction:column}
11
16
  ._4t3i1kxc{height:100vh}
12
- ._4t3i1osq{height:100%}
13
17
  ._8am5i4x0{-webkit-overflow-scrolling:touch}
14
18
  ._bfhk1bhr{background-color:var(--ds-surface-overlay,#fff)}
15
- ._c71lglyw{max-height:none}
16
19
  ._kqsw1n9t{position:fixed}
17
20
  ._lcxv1wug{pointer-events:auto}
21
+ ._ln19p27b{scrollbar-gutter:stable}
18
22
  ._m01u1kw7>form:only-child{max-height:inherit}
23
+ ._otyridpf{margin-bottom:0}
19
24
  ._syazi7uo{color:var(--ds-text,#292a2e)}._128mdkaa:focus-visible{outline-width:var(--ds-border-width-focused,2px)}
20
25
  ._1ah3dkaa:focus-visible{outline-offset:var(--ds-border-width-focused,2px)}
21
26
  ._mizu1v1w:focus-visible{outline-color:var(--ds-border-focused,#4688ec)}
22
27
  ._ra3xnqa1:focus-visible{outline-style:solid}
23
- @media (min-width:30rem){._bolhzwhf._bolhzwhf{max-height:calc(100vh - 119px)}._epkxfajl{border-radius:var(--ds-radius-small,3px)}._epkxpb1k{border-radius:var(--ds-radius-xlarge,9pt)}._zg7p130s{box-shadow:var(--ds-shadow-overlay,0 8px 9pt #1e1f2126,0 0 1px #1e1f214f)}}
28
+ @media (min-width:30rem){._1jyk17ks{height:var(--modal-dialog-height)}._g7116m9r{width:var(--modal-dialog-width)}._we1i18uh{max-width:calc(100vw - 90pt)}._1sg41wqb{margin-top:60px}._maxs1wug{margin-right:auto}._1jvh1wqb{margin-bottom:60px}._1uca1wug{margin-left:auto}._1mpv184x{min-height:min-content}._e8uo1ris{max-height:max-content}._epkxpb1k{border-radius:var(--ds-radius-xlarge,9pt)}._zg7p130s{box-shadow:var(--ds-shadow-overlay,0 8px 9pt #1e1f2126,0 0 1px #1e1f214f)}}
@@ -30,33 +30,41 @@ import { disableDraggingToCrossOriginIFramesForElement } from '../pragmatic-drag
30
30
  import { disableDraggingToCrossOriginIFramesForExternal } from '../pragmatic-drag-and-drop/disable-dragging-to-cross-origin-iframes/external';
31
31
  import { disableDraggingToCrossOriginIFramesForTextSelection } from '../pragmatic-drag-and-drop/disable-dragging-to-cross-origin-iframes/text-selection';
32
32
  import { ScrollContext } from '../scroll-context';
33
- import { dialogHeight } from './dialog-height';
34
- import { dialogWidth as getDialogWidth } from './dialog-width';
33
+ import { width } from '../width';
35
34
  import { default as ModalDialog } from './modal-dialog';
36
35
  var fillScreenStyles = null;
37
36
 
37
+ /**
38
+ * Styles applied to the <dialog> wrapper, NOT the visual modal surface.
39
+ */
40
+ var dialogStyles = {
41
+ 'body-scroll': "_1reoewfl _18m9ewfl _19pkidpf _2hwxidpf _otyridpf _18u0idpf _1sg41wqb _maxs1wug _1jvh1wqb _1uca1wug",
42
+ 'viewport-scroll': "_ln19p27b _1bsbauwl _4t3i1kxc",
43
+ 'full-screen': ""
44
+ };
45
+
38
46
  // Visual styles for modal content inside native <dialog>.
39
47
  // Uses cssMap (not css) to avoid triggering no-nested-styles lint rule.
40
48
 
41
49
  var LOCAL_CURRENT_SURFACE_CSS_VAR = '--ds-elevation-surface-current';
42
- var topLayerStyles = {
43
- content: "_1e0c1txw _4t3i1osq _2lx21bp4 _bfhk1bhr _syazi7uo _1q1l1bhr _lcxv1wug _1mq81kw7 _m01u1kw7 _1dg11kw7 _mizu1v1w _1ah3dkaa _ra3xnqa1 _128mdkaa _zg7p130s",
44
- borderRadius: "_epkxfajl",
45
- borderRadiusT26: "_epkxpb1k"
46
- };
47
50
 
48
- // Scroll-mode styles for the content div.
49
- // Height overrides use ID-scoped <style> (see dialogPositionStyles) because
50
- // Compiled atomic classes have specificity (0,1,0) (increaseSpecificity is disabled).
51
- // The doubled-ID selector (#id#id > div) at (2,0,1) reliably wins.
52
- // Only non-height properties needing the && boost remain here.
53
-
54
- var topLayerBodyScrollStyles = null;
55
- var topLayerViewportScrollStyles = null;
51
+ /**
52
+ * Styles applied to the visual modal surface.
53
+ */
54
+ var surfaceStyles = {
55
+ root: "_1e0c1txw _2lx21bp4 _bfhk1bhr _syazi7uo _1q1l1bhr _lcxv1wug _1mq81kw7 _m01u1kw7 _1dg11kw7 _mizu1v1w _1ah3dkaa _ra3xnqa1 _128mdkaa _zg7p130s",
56
+ borderRadius: "_2rkoidpf _epkxpb1k"
57
+ };
58
+ var topLayerScrollModeStyles = {
59
+ 'full-screen': "_1bsbauwl _4t3i1kxc _kqsw1n9t _152tze3t _1e02ze3t _18m91wug _8am5i4x0",
60
+ 'viewport-scroll': "_4t3i1kxc _1sg41wqb _maxs1wug _1jvh1wqb _1uca1wug _1mpv184x _1jyk17ks _g7116m9r _we1i18uh",
61
+ 'body-scroll': "_1bsbauwl _4t3i1kxc _1jyk17ks _g7116m9r _we1i18uh"
62
+ };
63
+ var topLayerAutoHeightStyles = null;
56
64
  var allowlistElements = function allowlistElements(element, callback) {
57
65
  // Allow focus outside modal when AUI dialog is visible
58
66
  // eslint-disable-next-line @atlaskit/platform/no-direct-document-usage -- legacy FocusLock allowlist
59
- if (Boolean(document.querySelector('.aui-blanket:not([hidden])'))) {
67
+ if (document.querySelector('.aui-blanket:not([hidden])')) {
60
68
  return false;
61
69
  }
62
70
  // Optional callback to let consumers exclude elements from focus lock
@@ -74,9 +82,64 @@ function useModalCloseHandler(providedOnClose) {
74
82
  action: 'closed',
75
83
  componentName: 'modalDialog',
76
84
  packageName: "@atlaskit/modal-dialog",
77
- packageVersion: "16.2.4"
85
+ packageVersion: "16.2.5"
78
86
  });
79
87
  }
88
+ function getScrollMode(_ref) {
89
+ var shouldScrollInViewport = _ref.shouldScrollInViewport,
90
+ isFullScreen = _ref.isFullScreen;
91
+ if (isFullScreen) {
92
+ return 'full-screen';
93
+ }
94
+ if (shouldScrollInViewport) {
95
+ return 'viewport-scroll';
96
+ }
97
+ return 'body-scroll';
98
+ }
99
+ function isWidthName(value) {
100
+ return width.values.includes(value);
101
+ }
102
+ function getTopLayerSurfaceWidth(input) {
103
+ if (!input) {
104
+ return 'auto';
105
+ }
106
+ if (typeof input === 'number') {
107
+ return "".concat(input, "px");
108
+ }
109
+ if (isWidthName(input)) {
110
+ return "".concat(width.widths[input], "px");
111
+ }
112
+ if (input.endsWith('%')) {
113
+ // Percentage widths need special handling in the top layer.
114
+ // In legacy, the percentage resolved against the Positioner's max-width
115
+ // (100vw - 120px). In the top layer, the <dialog>'s containing block is the
116
+ // viewport (100vw), so a raw percentage would produce a wider modal.
117
+ // Transform e.g. '42%' → 'calc(42 * (100vw - 120px) / 100)' to match legacy.
118
+ return "calc(".concat(parseFloat(input), " * (100vw - 120px) / 100)");
119
+ }
120
+ return input;
121
+ }
122
+ function getTopLayerSurfaceHeight(input) {
123
+ if (!input) {
124
+ // Although this value would ordinarily fill the viewport height,
125
+ // there is also a `max-height: max-content` applied to keep it constrained.
126
+ // But we need to provide the modal with a fixed height (non-keyword, non-percentage)
127
+ // in order for child elements with 100% height to work correctly.
128
+ return 'calc(100vh - 120px)';
129
+ }
130
+ if (typeof input === 'number') {
131
+ return "".concat(input, "px");
132
+ }
133
+ if (input.endsWith('%')) {
134
+ // Percentage heights need special handling in the top layer.
135
+ // In legacy, the percentage resolved against the Positioner's max-height
136
+ // (100vh - 120px). In the top layer, the <dialog>'s containing block is the
137
+ // viewport (100vh), so a raw percentage would produce a taller modal.
138
+ // Transform e.g. '42%' → 'calc(42 * (100vh - 120px) / 100)' to match legacy.
139
+ return "calc(".concat(parseFloat(input), " * (100vh - 120px) / 100)");
140
+ }
141
+ return input;
142
+ }
80
143
 
81
144
  /**
82
145
  * Top-layer rendering path (platform-dst-top-layer).
@@ -105,23 +168,23 @@ function ModalWrapperTopLayer(props) {
105
168
  shouldScrollInViewport = _props$shouldScrollIn === void 0 ? false : _props$shouldScrollIn,
106
169
  _props$shouldReturnFo = props.shouldReturnFocus,
107
170
  shouldReturnFocus = _props$shouldReturnFo === void 0 ? true : _props$shouldReturnFo,
108
- stackIndexOverride = props.stackIndex,
109
171
  providedOnClose = props.onClose,
110
172
  _props$onStackChange = props.onStackChange,
111
173
  onStackChange = _props$onStackChange === void 0 ? noop : _props$onStackChange,
112
174
  isBlanketHidden = props.isBlanketHidden,
113
175
  children = props.children,
114
176
  height = props.height,
115
- width = props.width,
177
+ _props$width = props.width,
178
+ width = _props$width === void 0 ? 'medium' : _props$width,
116
179
  onCloseComplete = props.onCloseComplete,
117
180
  onOpenComplete = props.onOpenComplete,
118
181
  label = props.label,
119
182
  testId = props.testId,
120
- isFullScreen = props.isFullScreen;
121
- var calculatedStackIndex = useModalStack({
183
+ _props$isFullScreen = props.isFullScreen,
184
+ isFullScreen = _props$isFullScreen === void 0 ? false : _props$isFullScreen;
185
+ useModalStack({
122
186
  onStackChange: onStackChange
123
187
  });
124
- var stackIndex = stackIndexOverride || calculatedStackIndex;
125
188
  var onCloseHandler = useModalCloseHandler(providedOnClose);
126
189
  var _useExitingPersistenc = useExitingPersistence(),
127
190
  isExiting = _useExitingPersistenc.isExiting,
@@ -156,8 +219,8 @@ function ModalWrapperTopLayer(props) {
156
219
 
157
220
  // Only forward close when the reason is allowed by props.
158
221
  // Passes a synthetic event to satisfy the KeyboardOrMouseEvent contract.
159
- var onDialogClose = useCallback(function (_ref) {
160
- var reason = _ref.reason;
222
+ var onDialogClose = useCallback(function (_ref2) {
223
+ var reason = _ref2.reason;
161
224
  if (reason === 'escape' && shouldCloseOnEscapePress) {
162
225
  onCloseHandler(createCloseEvent({
163
226
  reason: reason
@@ -214,56 +277,17 @@ function ModalWrapperTopLayer(props) {
214
277
  useEffect(function () {
215
278
  return combine(disableDraggingToCrossOriginIFramesForElement(), disableDraggingToCrossOriginIFramesForTextSelection(), disableDraggingToCrossOriginIFramesForExternal());
216
279
  }, []);
217
-
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);
223
-
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
- };
237
-
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
- }
242
-
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, "}}");
280
+ var scrollMode = getScrollMode({
281
+ isFullScreen: isFullScreen,
282
+ shouldScrollInViewport: shouldScrollInViewport
283
+ });
259
284
  return /*#__PURE__*/React.createElement(Dialog, _extends({
260
285
  ref: dialogRef,
261
- id: dialogId,
262
286
  onClose: onDialogClose,
263
287
  onExitFinish: handleDialogExitFinish,
264
288
  shouldAnimate: !isFullScreen,
265
289
  isOpen: !isExiting,
266
- shouldHideBackdrop: stackIndex > 0 || Boolean(isBlanketHidden)
290
+ shouldHideBackdrop: isBlanketHidden
267
291
  // Dialog requires at least one of `label` or `labelledBy` (string, not undefined).
268
292
  // Prefer the consumer-provided `label`; otherwise reference the internal `titleId`.
269
293
  }, label ? {
@@ -271,18 +295,17 @@ function ModalWrapperTopLayer(props) {
271
295
  } : {
272
296
  labelledBy: titleId
273
297
  }, {
274
- testId: defaultTestId
275
- // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop
276
- ,
277
- style: dialogStyle
298
+ testId: defaultTestId,
299
+ xcss: dialogStyles[scrollMode]
278
300
  }), /*#__PURE__*/React.createElement(DialogScrollLock, {
279
301
  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", {
302
+ }), /*#__PURE__*/React.createElement("div", {
284
303
  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"])
304
+ style: {
305
+ '--modal-dialog-height': getTopLayerSurfaceHeight(height),
306
+ '--modal-dialog-width': getTopLayerSurfaceWidth(width)
307
+ },
308
+ className: ax([surfaceStyles.root, !isFullScreen && surfaceStyles.borderRadius, topLayerScrollModeStyles[scrollMode], !height && !isFullScreen && "_e8uo1ris"])
286
309
  }, /*#__PURE__*/React.createElement(ModalContext.Provider, {
287
310
  value: modalDialogContext
288
311
  }, /*#__PURE__*/React.createElement(ScrollContext.Provider, {
@@ -3,8 +3,28 @@ import type { ModalDialogProps } from './types';
3
3
  export interface FullScreenModalDialogProps extends Omit<ModalDialogProps, 'width' | 'height' | 'shouldScrollInViewport' | 'shouldCloseOnOverlayClick' | 'isBlanketHidden'> {
4
4
  }
5
5
  export declare const FullScreenModalDialog: React.ForwardRefExoticComponent<React.PropsWithoutRef<FullScreenModalDialogProps> & React.RefAttributes<HTMLElement>>;
6
+ /**
7
+ * @deprecated Volt migration shim. Import from the explicit subpath instead.
8
+ * Retained only while consumers migrate off this re-export.
9
+ */
6
10
  export { default as ModalTransition } from './modal-transition';
11
+ /**
12
+ * @deprecated Volt migration shim. Import from the explicit subpath instead.
13
+ * Retained only while consumers migrate off this re-export.
14
+ */
7
15
  export { default as ModalHeader } from './modal-header';
16
+ /**
17
+ * @deprecated Volt migration shim. Import from the explicit subpath instead.
18
+ * Retained only while consumers migrate off this re-export.
19
+ */
8
20
  export { default as ModalTitle } from './modal-title';
21
+ /**
22
+ * @deprecated Volt migration shim. Import from the explicit subpath instead.
23
+ * Retained only while consumers migrate off this re-export.
24
+ */
9
25
  export { default as ModalBody } from './modal-body';
26
+ /**
27
+ * @deprecated Volt migration shim. Import from the explicit subpath instead.
28
+ * Retained only while consumers migrate off this re-export.
29
+ */
10
30
  export { default as ModalFooter } from './modal-footer';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/modal-dialog",
3
- "version": "16.2.5",
3
+ "version": "16.3.0",
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/"
@@ -39,17 +39,17 @@
39
39
  "@atlaskit/button": "^25.0.0",
40
40
  "@atlaskit/css": "^1.0.0",
41
41
  "@atlaskit/ds-lib": "^8.0.0",
42
- "@atlaskit/icon": "^37.2.0",
42
+ "@atlaskit/icon": "^37.3.0",
43
43
  "@atlaskit/layering": "^5.0.0",
44
44
  "@atlaskit/motion": "^8.0.0",
45
45
  "@atlaskit/platform-feature-flags": "^2.1.0",
46
- "@atlaskit/portal": "^6.1.0",
46
+ "@atlaskit/portal": "^6.2.0",
47
47
  "@atlaskit/pragmatic-drag-and-drop": "^2.0.0",
48
48
  "@atlaskit/primitives": "^22.2.0",
49
49
  "@atlaskit/react-compiler-gating": "^0.2.0",
50
- "@atlaskit/theme": "^26.1.0",
50
+ "@atlaskit/theme": "^27.0.0",
51
51
  "@atlaskit/tokens": "^16.3.0",
52
- "@atlaskit/top-layer": "^3.0.0",
52
+ "@atlaskit/top-layer": "^3.1.0",
53
53
  "@babel/runtime": "^7.0.0",
54
54
  "@compiled/react": "^1.0.0",
55
55
  "bind-event-listener": "^3.0.0",
@@ -68,7 +68,7 @@
68
68
  "@atlaskit/avatar-group": "^13.2.0",
69
69
  "@atlaskit/banner": "^15.1.0",
70
70
  "@atlaskit/breadcrumbs": "^17.5.0",
71
- "@atlaskit/checkbox": "^18.2.0",
71
+ "@atlaskit/checkbox": "^19.0.0",
72
72
  "@atlaskit/code": "^18.2.0",
73
73
  "@atlaskit/datetime-picker": "^18.2.0",
74
74
  "@atlaskit/docs": "^12.1.0",
@@ -77,12 +77,12 @@
77
77
  "@atlaskit/form": "^16.1.0",
78
78
  "@atlaskit/heading": "^7.0.0",
79
79
  "@atlaskit/link": "^5.0.0",
80
- "@atlaskit/popup": "^5.2.0",
80
+ "@atlaskit/popup": "^6.0.0",
81
81
  "@atlaskit/radio": "^9.1.0",
82
- "@atlaskit/section-message": "^9.2.0",
82
+ "@atlaskit/section-message": "^10.0.0",
83
83
  "@atlaskit/select": "^22.6.0",
84
84
  "@atlaskit/spotlight": "^3.0.0",
85
- "@atlaskit/textfield": "^9.1.0",
85
+ "@atlaskit/textfield": "^10.0.0",
86
86
  "@atlaskit/tooltip": "^24.0.0",
87
87
  "@atlassian/feature-flags-test-utils": "^1.2.0",
88
88
  "@atlassian/ssr-tests": "workspace:^",