@atlaskit/modal-dialog 16.3.1 → 16.3.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,21 @@
1
1
  # @atlaskit/modal-dialog
2
2
 
3
+ ## 16.3.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [`ccb9237bc10d9`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/ccb9237bc10d9) -
8
+ Add the `dismissedBy` API to control Dialog dismissal consistently across browsers. Align Dialog
9
+ with Popover by notifying `onClose` after native dismissal, ensure both primitives unmount after
10
+ native dismissal, and simplify Modal close handling behind `platform-dst-top-layer`.
11
+ - Updated dependencies
12
+
13
+ ## 16.3.2
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies
18
+
3
19
  ## 16.3.1
4
20
 
5
21
  ### Patch Changes
@@ -13,6 +13,7 @@ var React = _react;
13
13
  var _runtime = require("@compiled/react/runtime");
14
14
  var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
15
15
  var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
16
+ var _bindEventListener = require("bind-event-listener");
16
17
  var _reactFocusLock = _interopRequireDefault(require("react-focus-lock"));
17
18
  var _reactScrolllock = _interopRequireWildcard(require("react-scrolllock"));
18
19
  var _analyticsNext = require("@atlaskit/analytics-next");
@@ -91,7 +92,7 @@ function useModalCloseHandler(providedOnClose) {
91
92
  action: 'closed',
92
93
  componentName: 'modalDialog',
93
94
  packageName: "@atlaskit/modal-dialog",
94
- packageVersion: "16.3.0"
95
+ packageVersion: "16.3.2"
95
96
  });
96
97
  }
97
98
  function getScrollMode(_ref) {
@@ -149,6 +150,17 @@ function getTopLayerSurfaceHeight(input) {
149
150
  }
150
151
  return input;
151
152
  }
153
+ function getDialogDismissedBy(_ref2) {
154
+ var shouldCloseOnEscapePress = _ref2.shouldCloseOnEscapePress,
155
+ shouldCloseOnOverlayClick = _ref2.shouldCloseOnOverlayClick;
156
+ if (shouldCloseOnEscapePress && shouldCloseOnOverlayClick) {
157
+ return 'escape-and-outside-click';
158
+ }
159
+ if (shouldCloseOnEscapePress) {
160
+ return 'escape';
161
+ }
162
+ return 'none';
163
+ }
152
164
 
153
165
  /**
154
166
  * Top-layer rendering path (platform-dst-top-layer).
@@ -158,8 +170,7 @@ function getTopLayerSurfaceHeight(input) {
158
170
  *
159
171
  * Key decisions:
160
172
  * - Animation: CSS transitions via @starting-style / allow-discrete.
161
- * - Close gating: onDialogClose only forwards allowed reasons
162
- * (see notes/guides/dialog-close-flow.md).
173
+ * - Close gating: Dialog only forwards allowed reasons.
163
174
  * - onClose event param: undefined - consumers should use close reason.
164
175
  * - Focus restoration: native <dialog> behavior replaces react-focus-lock's
165
176
  * returnFocus (see accessibility-criteria.md).
@@ -226,21 +237,38 @@ function ModalWrapperTopLayer(props) {
226
237
  };
227
238
  }, [defaultTestId, titleId, onCloseHandler, providedOnClose, isFullScreen]);
228
239
 
229
- // Only forward close when the reason is allowed by props.
230
- // Passes a synthetic event to satisfy the KeyboardOrMouseEvent contract.
231
- var onDialogClose = (0, _react.useCallback)(function (_ref2) {
232
- var reason = _ref2.reason;
233
- if (reason === 'escape' && shouldCloseOnEscapePress) {
234
- onCloseHandler((0, _dialog.createCloseEvent)({
235
- reason: reason
236
- }));
237
- }
238
- if (reason === 'overlay-click' && shouldCloseOnOverlayClick) {
239
- onCloseHandler((0, _dialog.createCloseEvent)({
240
- reason: reason
241
- }));
240
+ // Dialog has already applied the close behavior configured by `dismissedBy`.
241
+ // Pass a synthetic event to satisfy the KeyboardOrMouseEvent contract.
242
+ var onDialogClose = (0, _react.useCallback)(function (_ref3) {
243
+ var reason = _ref3.reason;
244
+ onCloseHandler((0, _dialog.createCloseEvent)({
245
+ reason: reason
246
+ }));
247
+ }, [onCloseHandler]);
248
+ var dismissedBy = getDialogDismissedBy({
249
+ shouldCloseOnEscapePress: shouldCloseOnEscapePress,
250
+ shouldCloseOnOverlayClick: shouldCloseOnOverlayClick
251
+ });
252
+ var shouldShimCloseOnOverlayClick = shouldCloseOnOverlayClick && !shouldCloseOnEscapePress;
253
+
254
+ // Required until we fully remove `shouldCloseOnEscapePress`.
255
+ // Dialog's `dismissedBy` options do not support outside click without Escape (because it is a bad pattern)
256
+ (0, _react.useEffect)(function () {
257
+ var dialog = dialogRef.current;
258
+ if (!dialog || !shouldShimCloseOnOverlayClick) {
259
+ return;
242
260
  }
243
- }, [onCloseHandler, shouldCloseOnEscapePress, shouldCloseOnOverlayClick]);
261
+ return (0, _bindEventListener.bind)(dialog, {
262
+ type: 'click',
263
+ listener: function listener(event) {
264
+ if (event.target === event.currentTarget) {
265
+ onDialogClose({
266
+ reason: 'overlay-click'
267
+ });
268
+ }
269
+ }
270
+ });
271
+ }, [onDialogClose, shouldShimCloseOnOverlayClick]);
244
272
 
245
273
  // ExitingPersistence: isExiting → isOpen={false} → Dialog exit animation →
246
274
  // onExitFinish → onCloseComplete + unmount.
@@ -293,6 +321,7 @@ function ModalWrapperTopLayer(props) {
293
321
  return /*#__PURE__*/React.createElement(_dialog.Dialog, (0, _extends2.default)({
294
322
  ref: dialogRef,
295
323
  onClose: onDialogClose,
324
+ dismissedBy: dismissedBy,
296
325
  onExitFinish: handleDialogExitFinish,
297
326
  shouldAnimate: !isFullScreen,
298
327
  isOpen: !isExiting,
@@ -4,6 +4,7 @@ import "./modal-wrapper.compiled.css";
4
4
  import * as React from 'react';
5
5
  import { ax, ix } from "@compiled/react/runtime";
6
6
  import { forwardRef, useCallback, useEffect, useMemo, useRef } from 'react';
7
+ import { bind } from 'bind-event-listener';
7
8
  import FocusLock from 'react-focus-lock';
8
9
  import ScrollLock, { TouchScrollable } from 'react-scrolllock';
9
10
  import { usePlatformLeafEventHandler } from '@atlaskit/analytics-next';
@@ -81,7 +82,7 @@ function useModalCloseHandler(providedOnClose) {
81
82
  action: 'closed',
82
83
  componentName: 'modalDialog',
83
84
  packageName: "@atlaskit/modal-dialog",
84
- packageVersion: "16.3.0"
85
+ packageVersion: "16.3.2"
85
86
  });
86
87
  }
87
88
  function getScrollMode({
@@ -140,6 +141,18 @@ function getTopLayerSurfaceHeight(input) {
140
141
  }
141
142
  return input;
142
143
  }
144
+ function getDialogDismissedBy({
145
+ shouldCloseOnEscapePress,
146
+ shouldCloseOnOverlayClick
147
+ }) {
148
+ if (shouldCloseOnEscapePress && shouldCloseOnOverlayClick) {
149
+ return 'escape-and-outside-click';
150
+ }
151
+ if (shouldCloseOnEscapePress) {
152
+ return 'escape';
153
+ }
154
+ return 'none';
155
+ }
143
156
 
144
157
  /**
145
158
  * Top-layer rendering path (platform-dst-top-layer).
@@ -149,8 +162,7 @@ function getTopLayerSurfaceHeight(input) {
149
162
  *
150
163
  * Key decisions:
151
164
  * - Animation: CSS transitions via @starting-style / allow-discrete.
152
- * - Close gating: onDialogClose only forwards allowed reasons
153
- * (see notes/guides/dialog-close-flow.md).
165
+ * - Close gating: Dialog only forwards allowed reasons.
154
166
  * - onClose event param: undefined - consumers should use close reason.
155
167
  * - Focus restoration: native <dialog> behavior replaces react-focus-lock's
156
168
  * returnFocus (see accessibility-criteria.md).
@@ -211,22 +223,39 @@ function ModalWrapperTopLayer(props) {
211
223
  isFullScreen: isFullScreen !== null && isFullScreen !== void 0 ? isFullScreen : false
212
224
  }), [defaultTestId, titleId, onCloseHandler, providedOnClose, isFullScreen]);
213
225
 
214
- // Only forward close when the reason is allowed by props.
215
- // Passes a synthetic event to satisfy the KeyboardOrMouseEvent contract.
226
+ // Dialog has already applied the close behavior configured by `dismissedBy`.
227
+ // Pass a synthetic event to satisfy the KeyboardOrMouseEvent contract.
216
228
  const onDialogClose = useCallback(({
217
229
  reason
218
230
  }) => {
219
- if (reason === 'escape' && shouldCloseOnEscapePress) {
220
- onCloseHandler(createCloseEvent({
221
- reason
222
- }));
223
- }
224
- if (reason === 'overlay-click' && shouldCloseOnOverlayClick) {
225
- onCloseHandler(createCloseEvent({
226
- reason
227
- }));
231
+ onCloseHandler(createCloseEvent({
232
+ reason
233
+ }));
234
+ }, [onCloseHandler]);
235
+ const dismissedBy = getDialogDismissedBy({
236
+ shouldCloseOnEscapePress,
237
+ shouldCloseOnOverlayClick
238
+ });
239
+ const shouldShimCloseOnOverlayClick = shouldCloseOnOverlayClick && !shouldCloseOnEscapePress;
240
+
241
+ // Required until we fully remove `shouldCloseOnEscapePress`.
242
+ // Dialog's `dismissedBy` options do not support outside click without Escape (because it is a bad pattern)
243
+ useEffect(() => {
244
+ const dialog = dialogRef.current;
245
+ if (!dialog || !shouldShimCloseOnOverlayClick) {
246
+ return;
228
247
  }
229
- }, [onCloseHandler, shouldCloseOnEscapePress, shouldCloseOnOverlayClick]);
248
+ return bind(dialog, {
249
+ type: 'click',
250
+ listener(event) {
251
+ if (event.target === event.currentTarget) {
252
+ onDialogClose({
253
+ reason: 'overlay-click'
254
+ });
255
+ }
256
+ }
257
+ });
258
+ }, [onDialogClose, shouldShimCloseOnOverlayClick]);
230
259
 
231
260
  // ExitingPersistence: isExiting → isOpen={false} → Dialog exit animation →
232
261
  // onExitFinish → onCloseComplete + unmount.
@@ -279,6 +308,7 @@ function ModalWrapperTopLayer(props) {
279
308
  return /*#__PURE__*/React.createElement(Dialog, _extends({
280
309
  ref: dialogRef,
281
310
  onClose: onDialogClose,
311
+ dismissedBy: dismissedBy,
282
312
  onExitFinish: handleDialogExitFinish,
283
313
  shouldAnimate: !isFullScreen,
284
314
  isOpen: !isExiting,
@@ -5,6 +5,7 @@ import "./modal-wrapper.compiled.css";
5
5
  import * as React from 'react';
6
6
  import { ax, ix } from "@compiled/react/runtime";
7
7
  import { forwardRef, useCallback, useEffect, useMemo, useRef } from 'react';
8
+ import { bind } from 'bind-event-listener';
8
9
  import FocusLock from 'react-focus-lock';
9
10
  import ScrollLock, { TouchScrollable } from 'react-scrolllock';
10
11
  import { usePlatformLeafEventHandler } from '@atlaskit/analytics-next';
@@ -82,7 +83,7 @@ function useModalCloseHandler(providedOnClose) {
82
83
  action: 'closed',
83
84
  componentName: 'modalDialog',
84
85
  packageName: "@atlaskit/modal-dialog",
85
- packageVersion: "16.3.0"
86
+ packageVersion: "16.3.2"
86
87
  });
87
88
  }
88
89
  function getScrollMode(_ref) {
@@ -140,6 +141,17 @@ function getTopLayerSurfaceHeight(input) {
140
141
  }
141
142
  return input;
142
143
  }
144
+ function getDialogDismissedBy(_ref2) {
145
+ var shouldCloseOnEscapePress = _ref2.shouldCloseOnEscapePress,
146
+ shouldCloseOnOverlayClick = _ref2.shouldCloseOnOverlayClick;
147
+ if (shouldCloseOnEscapePress && shouldCloseOnOverlayClick) {
148
+ return 'escape-and-outside-click';
149
+ }
150
+ if (shouldCloseOnEscapePress) {
151
+ return 'escape';
152
+ }
153
+ return 'none';
154
+ }
143
155
 
144
156
  /**
145
157
  * Top-layer rendering path (platform-dst-top-layer).
@@ -149,8 +161,7 @@ function getTopLayerSurfaceHeight(input) {
149
161
  *
150
162
  * Key decisions:
151
163
  * - Animation: CSS transitions via @starting-style / allow-discrete.
152
- * - Close gating: onDialogClose only forwards allowed reasons
153
- * (see notes/guides/dialog-close-flow.md).
164
+ * - Close gating: Dialog only forwards allowed reasons.
154
165
  * - onClose event param: undefined - consumers should use close reason.
155
166
  * - Focus restoration: native <dialog> behavior replaces react-focus-lock's
156
167
  * returnFocus (see accessibility-criteria.md).
@@ -217,21 +228,38 @@ function ModalWrapperTopLayer(props) {
217
228
  };
218
229
  }, [defaultTestId, titleId, onCloseHandler, providedOnClose, isFullScreen]);
219
230
 
220
- // Only forward close when the reason is allowed by props.
221
- // Passes a synthetic event to satisfy the KeyboardOrMouseEvent contract.
222
- var onDialogClose = useCallback(function (_ref2) {
223
- var reason = _ref2.reason;
224
- if (reason === 'escape' && shouldCloseOnEscapePress) {
225
- onCloseHandler(createCloseEvent({
226
- reason: reason
227
- }));
228
- }
229
- if (reason === 'overlay-click' && shouldCloseOnOverlayClick) {
230
- onCloseHandler(createCloseEvent({
231
- reason: reason
232
- }));
231
+ // Dialog has already applied the close behavior configured by `dismissedBy`.
232
+ // Pass a synthetic event to satisfy the KeyboardOrMouseEvent contract.
233
+ var onDialogClose = useCallback(function (_ref3) {
234
+ var reason = _ref3.reason;
235
+ onCloseHandler(createCloseEvent({
236
+ reason: reason
237
+ }));
238
+ }, [onCloseHandler]);
239
+ var dismissedBy = getDialogDismissedBy({
240
+ shouldCloseOnEscapePress: shouldCloseOnEscapePress,
241
+ shouldCloseOnOverlayClick: shouldCloseOnOverlayClick
242
+ });
243
+ var shouldShimCloseOnOverlayClick = shouldCloseOnOverlayClick && !shouldCloseOnEscapePress;
244
+
245
+ // Required until we fully remove `shouldCloseOnEscapePress`.
246
+ // Dialog's `dismissedBy` options do not support outside click without Escape (because it is a bad pattern)
247
+ useEffect(function () {
248
+ var dialog = dialogRef.current;
249
+ if (!dialog || !shouldShimCloseOnOverlayClick) {
250
+ return;
233
251
  }
234
- }, [onCloseHandler, shouldCloseOnEscapePress, shouldCloseOnOverlayClick]);
252
+ return bind(dialog, {
253
+ type: 'click',
254
+ listener: function listener(event) {
255
+ if (event.target === event.currentTarget) {
256
+ onDialogClose({
257
+ reason: 'overlay-click'
258
+ });
259
+ }
260
+ }
261
+ });
262
+ }, [onDialogClose, shouldShimCloseOnOverlayClick]);
235
263
 
236
264
  // ExitingPersistence: isExiting → isOpen={false} → Dialog exit animation →
237
265
  // onExitFinish → onCloseComplete + unmount.
@@ -284,6 +312,7 @@ function ModalWrapperTopLayer(props) {
284
312
  return /*#__PURE__*/React.createElement(Dialog, _extends({
285
313
  ref: dialogRef,
286
314
  onClose: onDialogClose,
315
+ dismissedBy: dismissedBy,
287
316
  onExitFinish: handleDialogExitFinish,
288
317
  shouldAnimate: !isFullScreen,
289
318
  isOpen: !isExiting,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/modal-dialog",
3
- "version": "16.3.1",
3
+ "version": "16.3.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/"
@@ -48,10 +48,10 @@
48
48
  "@atlaskit/primitives": "^22.2.0",
49
49
  "@atlaskit/react-compiler-gating": "^0.2.0",
50
50
  "@atlaskit/theme": "^28.0.0",
51
- "@atlaskit/tokens": "^16.4.0",
52
- "@atlaskit/top-layer": "^3.2.0",
51
+ "@atlaskit/tokens": "^16.7.0",
52
+ "@atlaskit/top-layer": "^4.1.0",
53
53
  "@babel/runtime": "^7.0.0",
54
- "@compiled/react": "^1.0.0",
54
+ "@compiled/react": "^1.0.2",
55
55
  "bind-event-listener": "^3.0.0",
56
56
  "raf-schd": "^4.0.3",
57
57
  "react-focus-lock": "^2.9.5",
@@ -69,12 +69,12 @@
69
69
  "@atlaskit/banner": "^15.1.0",
70
70
  "@atlaskit/breadcrumbs": "^17.5.0",
71
71
  "@atlaskit/checkbox": "^19.0.0",
72
- "@atlaskit/code": "^18.2.0",
72
+ "@atlaskit/code": "^19.0.0",
73
73
  "@atlaskit/datetime-picker": "^18.3.0",
74
- "@atlaskit/docs": "^12.1.0",
74
+ "@atlaskit/docs": "^12.2.0",
75
75
  "@atlaskit/dropdown-menu": "^18.0.0",
76
76
  "@atlaskit/flag": "^18.2.0",
77
- "@atlaskit/form": "^16.1.0",
77
+ "@atlaskit/form": "^17.0.0",
78
78
  "@atlaskit/heading": "^7.0.0",
79
79
  "@atlaskit/link": "^5.0.0",
80
80
  "@atlaskit/popup": "^6.0.0",
@@ -105,26 +105,6 @@
105
105
  "react",
106
106
  "ui"
107
107
  ],
108
- "techstack": {
109
- "@atlassian/frontend": {
110
- "import-structure": "atlassian-conventions"
111
- },
112
- "@repo/internal": {
113
- "dom-events": "use-bind-event-listener",
114
- "design-system": "v1",
115
- "ui-components": "lite-mode",
116
- "analytics": "analytics-next",
117
- "design-tokens": [
118
- "color",
119
- "spacing"
120
- ],
121
- "deprecation": "no-deprecated-imports",
122
- "styling": [
123
- "static",
124
- "emotion"
125
- ]
126
- }
127
- },
128
108
  "platform-feature-flags": {
129
109
  "platform_modal-dialog-heading-icon-a11y-fix": {
130
110
  "type": "boolean"