@atlaskit/top-layer 1.6.1 → 1.7.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 +23 -0
- package/README.md +13 -0
- package/dist/cjs/dialog/dialog-content.js +16 -1
- package/dist/cjs/internal/use-safari-escape-fix.js +62 -0
- package/dist/cjs/popover/popover.compiled.css +1 -0
- package/dist/cjs/popover/popover.js +1 -1
- package/dist/es2019/dialog/dialog-content.js +17 -1
- package/dist/es2019/internal/use-safari-escape-fix.js +58 -0
- package/dist/es2019/popover/popover.compiled.css +1 -0
- package/dist/es2019/popover/popover.js +1 -1
- package/dist/esm/dialog/dialog-content.js +16 -1
- package/dist/esm/internal/use-safari-escape-fix.js +57 -0
- package/dist/esm/popover/popover.compiled.css +1 -0
- package/dist/esm/popover/popover.js +1 -1
- package/dist/types/internal/use-safari-escape-fix.d.ts +12 -0
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @atlaskit/top-layer
|
|
2
2
|
|
|
3
|
+
## 1.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`406346f89178f`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/406346f89178f) -
|
|
8
|
+
Fix a Safari (WebKit) layout bug where `Popover` content could collapse to `0px`. When a
|
|
9
|
+
`max-height: 100%` flex column with a `flex: 1 1 auto; overflow: auto` child is placed inside a
|
|
10
|
+
popover, the user-agent `height: fit-content` default (an indefinite block size) makes the column
|
|
11
|
+
collapse on WebKit. The `Popover` reset now sets `height: auto` to remove the indefinite block
|
|
12
|
+
size; width is left unchanged so anchor-width matching is unaffected.
|
|
13
|
+
|
|
14
|
+
The `Dialog` primitive is intentionally left unchanged for that reset: applying the same reset
|
|
15
|
+
there would stretch the modal `<dialog>` to fill the viewport and force a layout-centering opinion
|
|
16
|
+
onto an otherwise unopinionated primitive. See `notes/decisions/safari-popover-flex-collapse.md`.
|
|
17
|
+
|
|
18
|
+
Also fix a Safari (WebKit) bug where pressing `Escape` while a `popover="auto"` (for example a
|
|
19
|
+
menu) is open inside a modal `Dialog` closed the entire dialog instead of only the popover. On
|
|
20
|
+
WebKit the same Escape also fires the dialog's native `cancel` event. The `Dialog` primitive now
|
|
21
|
+
snapshots — at Escape keydown, before the browser's close action — whether a light-dismissable
|
|
22
|
+
(`auto`/`hint`) popover is open inside it, and if so keeps the dialog open so only the popover is
|
|
23
|
+
dismissed, matching Chromium and Firefox. See
|
|
24
|
+
`notes/decisions/safari-escape-nested-popover-in-dialog.md`.
|
|
25
|
+
|
|
3
26
|
## 1.6.1
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -178,6 +178,19 @@ details.
|
|
|
178
178
|
| Background inertness (modal dialogs) | ✓ |
|
|
179
179
|
| Close reasons (Dialog) | ✓ |
|
|
180
180
|
|
|
181
|
+
## Known limitations
|
|
182
|
+
|
|
183
|
+
- **Safari `<dialog>` flex-collapse.** On WebKit, a `max-height: 100%` flex column with a
|
|
184
|
+
`flex: 1 1 auto; overflow: auto` child placed directly inside a `Dialog` collapses to `0px` (the
|
|
185
|
+
block-axis `fit-content` user-agent default is an indefinite height). `Popover` sets
|
|
186
|
+
`height: auto` to avoid this; `Dialog` deliberately does not, to keep the primitive free of layout
|
|
187
|
+
opinions (see
|
|
188
|
+
[`notes/decisions/safari-popover-flex-collapse.md`](notes/decisions/safari-popover-flex-collapse.md)).
|
|
189
|
+
A consumer that builds this shape inside a `Dialog` should give the column a definite height or
|
|
190
|
+
apply its own `height: auto` reset on the surface.
|
|
191
|
+
- TODO: expand this into full public consumer documentation (the recommended workaround and a
|
|
192
|
+
worked example) before the top-layer `Dialog` is broadly adopted.
|
|
193
|
+
|
|
181
194
|
## Documentation
|
|
182
195
|
|
|
183
196
|
See [`notes/`](notes/) for project documentation, architecture decisions, migration records, and
|
|
@@ -16,6 +16,7 @@ var _noop = _interopRequireDefault(require("@atlaskit/ds-lib/noop"));
|
|
|
16
16
|
var _openLayerObserver = require("@atlaskit/layering/experimental/open-layer-observer");
|
|
17
17
|
var _useAnimatedVisibility = require("../internal/use-animated-visibility");
|
|
18
18
|
var _useFocusWrap = require("../internal/use-focus-wrap");
|
|
19
|
+
var _useSafariEscapeFix2 = require("../internal/use-safari-escape-fix");
|
|
19
20
|
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" != _typeof(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); }
|
|
20
21
|
// These animation styles use the non-strict Compiled cssMap because they rely
|
|
21
22
|
// on `@starting-style`, `[open]`, and `allow-discrete`. Keep them in this
|
|
@@ -124,6 +125,14 @@ var Dialog = exports.Dialog = /*#__PURE__*/(0, _react.forwardRef)(function Dialo
|
|
|
124
125
|
}
|
|
125
126
|
}, [isOpen]);
|
|
126
127
|
|
|
128
|
+
// Safari bug: escape closes open dialog and popovers, rather than just innermost popover
|
|
129
|
+
// See notes/decisions/safari-escape-nested-popover-in-dialog.md
|
|
130
|
+
var _useSafariEscapeFix = (0, _useSafariEscapeFix2.useSafariEscapeFix)({
|
|
131
|
+
dialogRef: ownRef,
|
|
132
|
+
isVisible: isVisible
|
|
133
|
+
}),
|
|
134
|
+
shouldIgnoreEscape = _useSafariEscapeFix.shouldIgnoreEscape;
|
|
135
|
+
|
|
127
136
|
// Handle native Escape (cancel event)
|
|
128
137
|
var handleCancel = (0, _react.useCallback)(function (event) {
|
|
129
138
|
// Only handle a cancel targeting THIS dialog. Native `cancel` does not
|
|
@@ -135,10 +144,16 @@ var Dialog = exports.Dialog = /*#__PURE__*/(0, _react.forwardRef)(function Dialo
|
|
|
135
144
|
return;
|
|
136
145
|
}
|
|
137
146
|
event.preventDefault();
|
|
147
|
+
// Spurious Safari `cancel`: the keydown snapshot saw an open child
|
|
148
|
+
// popover, so this Escape belongs to that popover (light-dismissed
|
|
149
|
+
// natively). Keep the dialog open instead of forwarding to `onClose`.
|
|
150
|
+
if (shouldIgnoreEscape()) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
138
153
|
onClose({
|
|
139
154
|
reason: 'escape'
|
|
140
155
|
});
|
|
141
|
-
}, [onClose]);
|
|
156
|
+
}, [onClose, shouldIgnoreEscape]);
|
|
142
157
|
|
|
143
158
|
// Handle backdrop click
|
|
144
159
|
// Attached via bind-event-listener rather than a React prop so we avoid
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.useSafariEscapeFix = useSafariEscapeFix;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
var _bindEventListener = require("bind-event-listener");
|
|
9
|
+
var _isSafari = require("@atlaskit/ds-lib/is-safari");
|
|
10
|
+
/**
|
|
11
|
+
* Safari-only guard: WebKit fires dialog `cancel` on Escape even when a child
|
|
12
|
+
* `auto`/`hint` popover consumed it, closing the dialog too.
|
|
13
|
+
* See notes/decisions/safari-escape-nested-popover-in-dialog.md
|
|
14
|
+
*/
|
|
15
|
+
function useSafariEscapeFix(_ref) {
|
|
16
|
+
var dialogRef = _ref.dialogRef,
|
|
17
|
+
isVisible = _ref.isVisible;
|
|
18
|
+
var childPopoverOpenAtEscapeRef = (0, _react.useRef)(false);
|
|
19
|
+
(0, _react.useEffect)(function () {
|
|
20
|
+
if (!(0, _isSafari.isSafari)()) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
var dialog = dialogRef.current;
|
|
24
|
+
if (!dialog) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Bind to `document`, not the dialog: on Safari focus can be on `<body>`
|
|
29
|
+
// (buttons are not focused on click), where a dialog-scoped listener would
|
|
30
|
+
// never fire. The query below stays scoped to this dialog's subtree.
|
|
31
|
+
return (0, _bindEventListener.bind)(document, {
|
|
32
|
+
type: 'keydown',
|
|
33
|
+
listener: function listener(event) {
|
|
34
|
+
if (event.key !== 'Escape') {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
childPopoverOpenAtEscapeRef.current = Boolean(dialog.querySelector('[popover="auto"]:popover-open, [popover="hint"]:popover-open'));
|
|
39
|
+
} catch (_unused) {
|
|
40
|
+
// `:popover-open` is unsupported in some environments (e.g. jsdom);
|
|
41
|
+
// treat as "no child popover" so a plain Escape still closes the dialog.
|
|
42
|
+
childPopoverOpenAtEscapeRef.current = false;
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
options: {
|
|
46
|
+
capture: true
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}, [dialogRef, isVisible]);
|
|
50
|
+
var api = (0, _react.useMemo)(function () {
|
|
51
|
+
return {
|
|
52
|
+
shouldIgnoreEscape: function shouldIgnoreEscape() {
|
|
53
|
+
if (childPopoverOpenAtEscapeRef.current) {
|
|
54
|
+
childPopoverOpenAtEscapeRef.current = false;
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}, []);
|
|
61
|
+
return api;
|
|
62
|
+
}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
._1reoewfl{overflow-x:visible}
|
|
15
15
|
._1s9z1a5r{transition-behavior:allow-discrete}
|
|
16
16
|
._2hwxidpf{margin-right:0}
|
|
17
|
+
._4t3i1wug{height:auto}
|
|
17
18
|
._5sagdn4k{animation-duration:var(--ds-duration-xshort,.1s)}
|
|
18
19
|
._bfhk1j28{background-color:transparent}
|
|
19
20
|
._ca0qidpf{padding-top:0}
|
|
@@ -42,7 +42,7 @@ var popoverAnimationStyles = {
|
|
|
42
42
|
root: "_y44vof1f _k8m011k7 _1oecdn4k _1s9z1a5r _1o51q7pw _5sagdn4k _1pgl1lbj _15ns179n _12t71ign _n70peoah _1psg1ign _o8i7indk _bl8aglyw _156yru3m _8rmvglyw _2qm2ru3m"
|
|
43
43
|
};
|
|
44
44
|
var styles = {
|
|
45
|
-
root: "_19itglyw _ca0qidpf _u5f3idpf _n3tdidpf _19bvidpf _19pkidpf _2hwxidpf _otyridpf _18u0idpf _1r041wug _1reoewfl _18m9ewfl _bfhk1j28"
|
|
45
|
+
root: "_19itglyw _ca0qidpf _u5f3idpf _n3tdidpf _19bvidpf _19pkidpf _2hwxidpf _otyridpf _18u0idpf _1r041wug _1reoewfl _18m9ewfl _bfhk1j28 _4t3i1wug"
|
|
46
46
|
};
|
|
47
47
|
/**
|
|
48
48
|
* Roles registered with the open layer observer as `popup`, so `closeLayers()`
|
|
@@ -8,6 +8,7 @@ import noop from '@atlaskit/ds-lib/noop';
|
|
|
8
8
|
import { useNotifyOpenLayerObserver } from '@atlaskit/layering/experimental/open-layer-observer';
|
|
9
9
|
import { useAnimatedVisibility } from '../internal/use-animated-visibility';
|
|
10
10
|
import { useFocusWrap } from '../internal/use-focus-wrap';
|
|
11
|
+
import { useSafariEscapeFix } from '../internal/use-safari-escape-fix';
|
|
11
12
|
// These animation styles use the non-strict Compiled cssMap because they rely
|
|
12
13
|
// on `@starting-style`, `[open]`, and `allow-discrete`. Keep them in this
|
|
13
14
|
// component so the Compiled transform can statically extract every referenced
|
|
@@ -116,6 +117,15 @@ export const Dialog = /*#__PURE__*/forwardRef(function Dialog({
|
|
|
116
117
|
}
|
|
117
118
|
}, [isOpen]);
|
|
118
119
|
|
|
120
|
+
// Safari bug: escape closes open dialog and popovers, rather than just innermost popover
|
|
121
|
+
// See notes/decisions/safari-escape-nested-popover-in-dialog.md
|
|
122
|
+
const {
|
|
123
|
+
shouldIgnoreEscape
|
|
124
|
+
} = useSafariEscapeFix({
|
|
125
|
+
dialogRef: ownRef,
|
|
126
|
+
isVisible
|
|
127
|
+
});
|
|
128
|
+
|
|
119
129
|
// Handle native Escape (cancel event)
|
|
120
130
|
const handleCancel = useCallback(event => {
|
|
121
131
|
// Only handle a cancel targeting THIS dialog. Native `cancel` does not
|
|
@@ -127,10 +137,16 @@ export const Dialog = /*#__PURE__*/forwardRef(function Dialog({
|
|
|
127
137
|
return;
|
|
128
138
|
}
|
|
129
139
|
event.preventDefault();
|
|
140
|
+
// Spurious Safari `cancel`: the keydown snapshot saw an open child
|
|
141
|
+
// popover, so this Escape belongs to that popover (light-dismissed
|
|
142
|
+
// natively). Keep the dialog open instead of forwarding to `onClose`.
|
|
143
|
+
if (shouldIgnoreEscape()) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
130
146
|
onClose({
|
|
131
147
|
reason: 'escape'
|
|
132
148
|
});
|
|
133
|
-
}, [onClose]);
|
|
149
|
+
}, [onClose, shouldIgnoreEscape]);
|
|
134
150
|
|
|
135
151
|
// Handle backdrop click
|
|
136
152
|
// Attached via bind-event-listener rather than a React prop so we avoid
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { bind } from 'bind-event-listener';
|
|
3
|
+
import { isSafari } from '@atlaskit/ds-lib/is-safari';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Safari-only guard: WebKit fires dialog `cancel` on Escape even when a child
|
|
7
|
+
* `auto`/`hint` popover consumed it, closing the dialog too.
|
|
8
|
+
* See notes/decisions/safari-escape-nested-popover-in-dialog.md
|
|
9
|
+
*/
|
|
10
|
+
export function useSafariEscapeFix({
|
|
11
|
+
dialogRef,
|
|
12
|
+
isVisible
|
|
13
|
+
}) {
|
|
14
|
+
const childPopoverOpenAtEscapeRef = useRef(false);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (!isSafari()) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const dialog = dialogRef.current;
|
|
20
|
+
if (!dialog) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Bind to `document`, not the dialog: on Safari focus can be on `<body>`
|
|
25
|
+
// (buttons are not focused on click), where a dialog-scoped listener would
|
|
26
|
+
// never fire. The query below stays scoped to this dialog's subtree.
|
|
27
|
+
return bind(document, {
|
|
28
|
+
type: 'keydown',
|
|
29
|
+
listener(event) {
|
|
30
|
+
if (event.key !== 'Escape') {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
childPopoverOpenAtEscapeRef.current = Boolean(dialog.querySelector('[popover="auto"]:popover-open, [popover="hint"]:popover-open'));
|
|
35
|
+
} catch {
|
|
36
|
+
// `:popover-open` is unsupported in some environments (e.g. jsdom);
|
|
37
|
+
// treat as "no child popover" so a plain Escape still closes the dialog.
|
|
38
|
+
childPopoverOpenAtEscapeRef.current = false;
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
options: {
|
|
42
|
+
capture: true
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}, [dialogRef, isVisible]);
|
|
46
|
+
const api = useMemo(() => {
|
|
47
|
+
return {
|
|
48
|
+
shouldIgnoreEscape() {
|
|
49
|
+
if (childPopoverOpenAtEscapeRef.current) {
|
|
50
|
+
childPopoverOpenAtEscapeRef.current = false;
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}, []);
|
|
57
|
+
return api;
|
|
58
|
+
}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
._1reoewfl{overflow-x:visible}
|
|
15
15
|
._1s9z1a5r{transition-behavior:allow-discrete}
|
|
16
16
|
._2hwxidpf{margin-right:0}
|
|
17
|
+
._4t3i1wug{height:auto}
|
|
17
18
|
._5sagdn4k{animation-duration:var(--ds-duration-xshort,.1s)}
|
|
18
19
|
._bfhk1j28{background-color:transparent}
|
|
19
20
|
._ca0qidpf{padding-top:0}
|
|
@@ -33,7 +33,7 @@ const popoverAnimationStyles = {
|
|
|
33
33
|
root: "_y44vof1f _k8m011k7 _1oecdn4k _1s9z1a5r _1o51q7pw _5sagdn4k _1pgl1lbj _15ns179n _12t71ign _n70peoah _1psg1ign _o8i7indk _bl8aglyw _156yru3m _8rmvglyw _2qm2ru3m"
|
|
34
34
|
};
|
|
35
35
|
const styles = {
|
|
36
|
-
root: "_19itglyw _ca0qidpf _u5f3idpf _n3tdidpf _19bvidpf _19pkidpf _2hwxidpf _otyridpf _18u0idpf _1r041wug _1reoewfl _18m9ewfl _bfhk1j28"
|
|
36
|
+
root: "_19itglyw _ca0qidpf _u5f3idpf _n3tdidpf _19bvidpf _19pkidpf _2hwxidpf _otyridpf _18u0idpf _1r041wug _1reoewfl _18m9ewfl _bfhk1j28 _4t3i1wug"
|
|
37
37
|
};
|
|
38
38
|
/**
|
|
39
39
|
* Roles registered with the open layer observer as `popup`, so `closeLayers()`
|
|
@@ -8,6 +8,7 @@ import noop from '@atlaskit/ds-lib/noop';
|
|
|
8
8
|
import { useNotifyOpenLayerObserver } from '@atlaskit/layering/experimental/open-layer-observer';
|
|
9
9
|
import { useAnimatedVisibility } from '../internal/use-animated-visibility';
|
|
10
10
|
import { useFocusWrap } from '../internal/use-focus-wrap';
|
|
11
|
+
import { useSafariEscapeFix } from '../internal/use-safari-escape-fix';
|
|
11
12
|
// These animation styles use the non-strict Compiled cssMap because they rely
|
|
12
13
|
// on `@starting-style`, `[open]`, and `allow-discrete`. Keep them in this
|
|
13
14
|
// component so the Compiled transform can statically extract every referenced
|
|
@@ -115,6 +116,14 @@ export var Dialog = /*#__PURE__*/forwardRef(function Dialog(_ref, ref) {
|
|
|
115
116
|
}
|
|
116
117
|
}, [isOpen]);
|
|
117
118
|
|
|
119
|
+
// Safari bug: escape closes open dialog and popovers, rather than just innermost popover
|
|
120
|
+
// See notes/decisions/safari-escape-nested-popover-in-dialog.md
|
|
121
|
+
var _useSafariEscapeFix = useSafariEscapeFix({
|
|
122
|
+
dialogRef: ownRef,
|
|
123
|
+
isVisible: isVisible
|
|
124
|
+
}),
|
|
125
|
+
shouldIgnoreEscape = _useSafariEscapeFix.shouldIgnoreEscape;
|
|
126
|
+
|
|
118
127
|
// Handle native Escape (cancel event)
|
|
119
128
|
var handleCancel = useCallback(function (event) {
|
|
120
129
|
// Only handle a cancel targeting THIS dialog. Native `cancel` does not
|
|
@@ -126,10 +135,16 @@ export var Dialog = /*#__PURE__*/forwardRef(function Dialog(_ref, ref) {
|
|
|
126
135
|
return;
|
|
127
136
|
}
|
|
128
137
|
event.preventDefault();
|
|
138
|
+
// Spurious Safari `cancel`: the keydown snapshot saw an open child
|
|
139
|
+
// popover, so this Escape belongs to that popover (light-dismissed
|
|
140
|
+
// natively). Keep the dialog open instead of forwarding to `onClose`.
|
|
141
|
+
if (shouldIgnoreEscape()) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
129
144
|
onClose({
|
|
130
145
|
reason: 'escape'
|
|
131
146
|
});
|
|
132
|
-
}, [onClose]);
|
|
147
|
+
}, [onClose, shouldIgnoreEscape]);
|
|
133
148
|
|
|
134
149
|
// Handle backdrop click
|
|
135
150
|
// Attached via bind-event-listener rather than a React prop so we avoid
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { bind } from 'bind-event-listener';
|
|
3
|
+
import { isSafari } from '@atlaskit/ds-lib/is-safari';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Safari-only guard: WebKit fires dialog `cancel` on Escape even when a child
|
|
7
|
+
* `auto`/`hint` popover consumed it, closing the dialog too.
|
|
8
|
+
* See notes/decisions/safari-escape-nested-popover-in-dialog.md
|
|
9
|
+
*/
|
|
10
|
+
export function useSafariEscapeFix(_ref) {
|
|
11
|
+
var dialogRef = _ref.dialogRef,
|
|
12
|
+
isVisible = _ref.isVisible;
|
|
13
|
+
var childPopoverOpenAtEscapeRef = useRef(false);
|
|
14
|
+
useEffect(function () {
|
|
15
|
+
if (!isSafari()) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
var dialog = dialogRef.current;
|
|
19
|
+
if (!dialog) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Bind to `document`, not the dialog: on Safari focus can be on `<body>`
|
|
24
|
+
// (buttons are not focused on click), where a dialog-scoped listener would
|
|
25
|
+
// never fire. The query below stays scoped to this dialog's subtree.
|
|
26
|
+
return bind(document, {
|
|
27
|
+
type: 'keydown',
|
|
28
|
+
listener: function listener(event) {
|
|
29
|
+
if (event.key !== 'Escape') {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
childPopoverOpenAtEscapeRef.current = Boolean(dialog.querySelector('[popover="auto"]:popover-open, [popover="hint"]:popover-open'));
|
|
34
|
+
} catch (_unused) {
|
|
35
|
+
// `:popover-open` is unsupported in some environments (e.g. jsdom);
|
|
36
|
+
// treat as "no child popover" so a plain Escape still closes the dialog.
|
|
37
|
+
childPopoverOpenAtEscapeRef.current = false;
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
options: {
|
|
41
|
+
capture: true
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}, [dialogRef, isVisible]);
|
|
45
|
+
var api = useMemo(function () {
|
|
46
|
+
return {
|
|
47
|
+
shouldIgnoreEscape: function shouldIgnoreEscape() {
|
|
48
|
+
if (childPopoverOpenAtEscapeRef.current) {
|
|
49
|
+
childPopoverOpenAtEscapeRef.current = false;
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}, []);
|
|
56
|
+
return api;
|
|
57
|
+
}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
._1reoewfl{overflow-x:visible}
|
|
15
15
|
._1s9z1a5r{transition-behavior:allow-discrete}
|
|
16
16
|
._2hwxidpf{margin-right:0}
|
|
17
|
+
._4t3i1wug{height:auto}
|
|
17
18
|
._5sagdn4k{animation-duration:var(--ds-duration-xshort,.1s)}
|
|
18
19
|
._bfhk1j28{background-color:transparent}
|
|
19
20
|
._ca0qidpf{padding-top:0}
|
|
@@ -33,7 +33,7 @@ var popoverAnimationStyles = {
|
|
|
33
33
|
root: "_y44vof1f _k8m011k7 _1oecdn4k _1s9z1a5r _1o51q7pw _5sagdn4k _1pgl1lbj _15ns179n _12t71ign _n70peoah _1psg1ign _o8i7indk _bl8aglyw _156yru3m _8rmvglyw _2qm2ru3m"
|
|
34
34
|
};
|
|
35
35
|
var styles = {
|
|
36
|
-
root: "_19itglyw _ca0qidpf _u5f3idpf _n3tdidpf _19bvidpf _19pkidpf _2hwxidpf _otyridpf _18u0idpf _1r041wug _1reoewfl _18m9ewfl _bfhk1j28"
|
|
36
|
+
root: "_19itglyw _ca0qidpf _u5f3idpf _n3tdidpf _19bvidpf _19pkidpf _2hwxidpf _otyridpf _18u0idpf _1r041wug _1reoewfl _18m9ewfl _bfhk1j28 _4t3i1wug"
|
|
37
37
|
};
|
|
38
38
|
/**
|
|
39
39
|
* Roles registered with the open layer observer as `popup`, so `closeLayers()`
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Safari-only guard: WebKit fires dialog `cancel` on Escape even when a child
|
|
4
|
+
* `auto`/`hint` popover consumed it, closing the dialog too.
|
|
5
|
+
* See notes/decisions/safari-escape-nested-popover-in-dialog.md
|
|
6
|
+
*/
|
|
7
|
+
export declare function useSafariEscapeFix({ dialogRef, isVisible, }: {
|
|
8
|
+
dialogRef: RefObject<HTMLDialogElement | null>;
|
|
9
|
+
isVisible: boolean;
|
|
10
|
+
}): {
|
|
11
|
+
shouldIgnoreEscape: () => boolean;
|
|
12
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/top-layer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Low-level layering primitives based on popover and dialog",
|
|
5
5
|
"homepage": "https://atlassian.design/components/top-layer",
|
|
6
6
|
"repository": "https://bitbucket.org/atlassian/atlassian-frontend-mirror",
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
},
|
|
30
30
|
"integrationTests": {
|
|
31
31
|
"additionalBrowsers": [
|
|
32
|
-
"desktop-firefox"
|
|
32
|
+
"desktop-firefox",
|
|
33
|
+
"desktop-webkit"
|
|
33
34
|
]
|
|
34
35
|
}
|
|
35
36
|
},
|
|
@@ -39,7 +40,7 @@
|
|
|
39
40
|
"@atlaskit/ds-lib": "^8.0.0",
|
|
40
41
|
"@atlaskit/layering": "^4.1.0",
|
|
41
42
|
"@atlaskit/react-compiler-gating": "^0.2.0",
|
|
42
|
-
"@atlaskit/tokens": "^15.
|
|
43
|
+
"@atlaskit/tokens": "^15.8.0",
|
|
43
44
|
"@babel/runtime": "^7.0.0",
|
|
44
45
|
"@compiled/react": "^0.20.0",
|
|
45
46
|
"bind-event-listener": "^3.0.0",
|