@jobber/components 8.27.1 → 8.27.3-FIXproper-e2c0fc2.2

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.
@@ -7,6 +7,7 @@ require('../Modal/index.cjs');
7
7
  require('react-dom');
8
8
  require('classnames');
9
9
  require('framer-motion');
10
+ require('../nestedEscape-cjs.js');
10
11
  require('../Heading-cjs.js');
11
12
  require('../Typography-cjs.js');
12
13
  require('../Button-cjs.js');
@@ -5,6 +5,7 @@ import '../Modal/index.mjs';
5
5
  import 'react-dom';
6
6
  import 'classnames';
7
7
  import 'framer-motion';
8
+ import '../nestedEscape-es.js';
8
9
  import '../Heading-es.js';
9
10
  import '../Typography-es.js';
10
11
  import '../Button-es.js';
@@ -25,6 +25,7 @@ require('../Heading-cjs.js');
25
25
  require('../BottomSheet-cjs.js');
26
26
  require('../DrawerTitle-cjs.js');
27
27
  require('../DrawerDescription-cjs.js');
28
+ require('../nestedEscape-cjs.js');
28
29
 
29
30
 
30
31
 
@@ -23,3 +23,4 @@ import '../Heading-es.js';
23
23
  import '../BottomSheet-es.js';
24
24
  import '../DrawerTitle-es.js';
25
25
  import '../DrawerDescription-es.js';
26
+ import '../nestedEscape-es.js';
@@ -20,6 +20,7 @@ require('@jobber/hooks');
20
20
  require('../Modal/index.cjs');
21
21
  require('react-dom');
22
22
  require('framer-motion');
23
+ require('../nestedEscape-cjs.js');
23
24
  require('../Heading-cjs.js');
24
25
  require('../ButtonDismiss-cjs.js');
25
26
  require('../noop-cjs.js');
@@ -18,6 +18,7 @@ import '@jobber/hooks';
18
18
  import '../Modal/index.mjs';
19
19
  import 'react-dom';
20
20
  import 'framer-motion';
21
+ import '../nestedEscape-es.js';
21
22
  import '../Heading-es.js';
22
23
  import '../ButtonDismiss-es.js';
23
24
  import '../noop-es.js';
@@ -41,6 +41,7 @@ require('../Content-cjs.js');
41
41
  require('../ProgressBar-cjs.js');
42
42
  require('../ConfirmationModal-cjs.js');
43
43
  require('../Modal/index.cjs');
44
+ require('../nestedEscape-cjs.js');
44
45
  require('../AtlantisPortalContent-cjs.js');
45
46
  require('../Markdown-cjs.js');
46
47
  require('../Emphasis-cjs.js');
@@ -39,6 +39,7 @@ import '../Content-es.js';
39
39
  import '../ProgressBar-es.js';
40
40
  import '../ConfirmationModal-es.js';
41
41
  import '../Modal/index.mjs';
42
+ import '../nestedEscape-es.js';
42
43
  import '../AtlantisPortalContent-es.js';
43
44
  import '../Markdown-es.js';
44
45
  import '../Emphasis-es.js';
@@ -5,6 +5,7 @@ var ReactDOM = require('react-dom');
5
5
  var classnames = require('classnames');
6
6
  var framerMotion = require('framer-motion');
7
7
  var jobberHooks = require('@jobber/hooks');
8
+ var nestedEscape = require('../nestedEscape-cjs.js');
8
9
  var Heading = require('../Heading-cjs.js');
9
10
  var Button = require('../Button-cjs.js');
10
11
  var ButtonDismiss = require('../ButtonDismiss-cjs.js');
@@ -42,7 +43,17 @@ function ModalLegacy({ open = false, title, size, dismissible = true, children,
42
43
  const containerClassName = classnames(styles$1.container, size === "fullScreen" && styles$1.containerFullScreen);
43
44
  jobberHooks.useRefocusOnActivator(open);
44
45
  const modalRef = jobberHooks.useFocusTrap(open);
45
- const template = (React.createElement(framerMotion.AnimatePresence, null, open && (React.createElement("div", { ref: modalRef, role: "dialog", className: containerClassName, tabIndex: 0, "aria-modal": "true", "aria-labelledby": title ? MODAL_HEADER_ID : undefined, "aria-label": ariaLabel, onKeyDown: handleKeyDown },
46
+ // Contain Escape so a Modal nested with a Base UI Dialog closes only the
47
+ // top-most layer.
48
+ React.useEffect(() => {
49
+ if (!open)
50
+ return;
51
+ return nestedEscape.addModalEscapeContainment({
52
+ getModal: () => modalRef.current,
53
+ onRequestClose,
54
+ });
55
+ }, [open, onRequestClose]);
56
+ const template = (React.createElement(framerMotion.AnimatePresence, null, open && (React.createElement("div", { ref: modalRef, role: "dialog", className: containerClassName, tabIndex: 0, "aria-modal": "true", "aria-labelledby": title ? MODAL_HEADER_ID : undefined, "aria-label": ariaLabel },
46
57
  React.createElement(framerMotion.motion.div, { key: styles$1.overlay, className: styles$1.overlay, onClick: onRequestClose, initial: { opacity: 0 }, animate: { opacity: 0.8 }, exit: { opacity: 0 }, transition: { duration: 0.2 } }),
47
58
  React.createElement(framerMotion.motion.div, { key: styles$1.modal, className: modalClassName, initial: { scale: 0.9, opacity: 0 }, animate: { scale: 1, opacity: 1 }, exit: { scale: 0.9, opacity: 0 }, transition: {
48
59
  duration: 0.2,
@@ -54,22 +65,6 @@ function ModalLegacy({ open = false, title, size, dismissible = true, children,
54
65
  return (globalThis === null || globalThis === void 0 ? void 0 : globalThis.document)
55
66
  ? ReactDOM.createPortal(template, document.body)
56
67
  : template;
57
- function handleRequestClose() {
58
- if (open && onRequestClose) {
59
- onRequestClose();
60
- }
61
- }
62
- function handleKeyDown(event) {
63
- if (event.key === "Escape") {
64
- // Stop the Escape from bubbling to outer overlays (e.g. a Base UI Dialog
65
- // this Modal is nested within, whose dismissal listens at the document
66
- // level). Without this, one Escape closes both layers; instead only the
67
- // top-most layer — this Modal — should close. Mirrors SideDrawer's
68
- // Escape containment.
69
- event.stopPropagation();
70
- handleRequestClose();
71
- }
72
- }
73
68
  }
74
69
  function Header({ title, dismissible, onRequestClose }) {
75
70
  return (React.createElement("div", { className: styles$1.header, "data-testid": "modal-header" },
@@ -3,6 +3,7 @@ import ReactDOM__default from 'react-dom';
3
3
  import classnames from 'classnames';
4
4
  import { AnimatePresence, motion } from 'framer-motion';
5
5
  import { useRefocusOnActivator, useFocusTrap } from '@jobber/hooks';
6
+ import { a as addModalEscapeContainment } from '../nestedEscape-es.js';
6
7
  import { H as Heading } from '../Heading-es.js';
7
8
  import { B as Button } from '../Button-es.js';
8
9
  import { B as ButtonDismiss } from '../ButtonDismiss-es.js';
@@ -40,7 +41,17 @@ function ModalLegacy({ open = false, title, size, dismissible = true, children,
40
41
  const containerClassName = classnames(styles$1.container, size === "fullScreen" && styles$1.containerFullScreen);
41
42
  useRefocusOnActivator(open);
42
43
  const modalRef = useFocusTrap(open);
43
- const template = (React__default.createElement(AnimatePresence, null, open && (React__default.createElement("div", { ref: modalRef, role: "dialog", className: containerClassName, tabIndex: 0, "aria-modal": "true", "aria-labelledby": title ? MODAL_HEADER_ID : undefined, "aria-label": ariaLabel, onKeyDown: handleKeyDown },
44
+ // Contain Escape so a Modal nested with a Base UI Dialog closes only the
45
+ // top-most layer.
46
+ React__default.useEffect(() => {
47
+ if (!open)
48
+ return;
49
+ return addModalEscapeContainment({
50
+ getModal: () => modalRef.current,
51
+ onRequestClose,
52
+ });
53
+ }, [open, onRequestClose]);
54
+ const template = (React__default.createElement(AnimatePresence, null, open && (React__default.createElement("div", { ref: modalRef, role: "dialog", className: containerClassName, tabIndex: 0, "aria-modal": "true", "aria-labelledby": title ? MODAL_HEADER_ID : undefined, "aria-label": ariaLabel },
44
55
  React__default.createElement(motion.div, { key: styles$1.overlay, className: styles$1.overlay, onClick: onRequestClose, initial: { opacity: 0 }, animate: { opacity: 0.8 }, exit: { opacity: 0 }, transition: { duration: 0.2 } }),
45
56
  React__default.createElement(motion.div, { key: styles$1.modal, className: modalClassName, initial: { scale: 0.9, opacity: 0 }, animate: { scale: 1, opacity: 1 }, exit: { scale: 0.9, opacity: 0 }, transition: {
46
57
  duration: 0.2,
@@ -52,22 +63,6 @@ function ModalLegacy({ open = false, title, size, dismissible = true, children,
52
63
  return (globalThis === null || globalThis === void 0 ? void 0 : globalThis.document)
53
64
  ? ReactDOM__default.createPortal(template, document.body)
54
65
  : template;
55
- function handleRequestClose() {
56
- if (open && onRequestClose) {
57
- onRequestClose();
58
- }
59
- }
60
- function handleKeyDown(event) {
61
- if (event.key === "Escape") {
62
- // Stop the Escape from bubbling to outer overlays (e.g. a Base UI Dialog
63
- // this Modal is nested within, whose dismissal listens at the document
64
- // level). Without this, one Escape closes both layers; instead only the
65
- // top-most layer — this Modal — should close. Mirrors SideDrawer's
66
- // Escape containment.
67
- event.stopPropagation();
68
- handleRequestClose();
69
- }
70
- }
71
66
  }
72
67
  function Header({ title, dismissible, onRequestClose }) {
73
68
  return (React__default.createElement("div", { className: styles$1.header, "data-testid": "modal-header" },
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Escape-key containment between the legacy prop-based `Modal` and Base UI's
3
+ * `Dialog`.
4
+ *
5
+ * The problem: both overlays listen for Escape at the `document` level and both
6
+ * portal their content to `<body>`. When one is nested inside the other, a
7
+ * single Escape reaches both listeners, so both layers close. Only the top-most
8
+ * layer should close.
9
+ *
10
+ * The legacy `Modal` can't join Base UI's floating-element tree, so this module
11
+ * coordinates the two with a small, explicit protocol:
12
+ *
13
+ * 1. Top-most check — because each overlay portals to `<body>`, the focused
14
+ * content's *nearest* `[role="dialog"]` ancestor is always its own
15
+ * container. So the Modal only acts when that nearest dialog is itself; if
16
+ * it's a different (nested) dialog, the Modal defers. See
17
+ * {@link isTopMostOverlayForEscape}.
18
+ *
19
+ * 2. Deferral signal — when the Modal is top-most it stamps the native event
20
+ * during the CAPTURE phase (which runs before Base UI's bubble-phase
21
+ * dismissal). An outer Base UI `Dialog` reads that flag in its
22
+ * `onOpenChange` handler and cancels, so the Dialog stays open while the
23
+ * Modal closes. See {@link markEscapeHandledByModal} /
24
+ * {@link isEscapeHandledByModal}.
25
+ *
26
+ * Note: this is a DOM-proximity heuristic rather than a true overlay stack. It
27
+ * holds only because both overlays portal to `<body>` and expose a dialog role.
28
+ */
29
+ /**
30
+ * Whether `container` is the top-most overlay for this Escape — i.e. the
31
+ * Escape originated inside `container` and not inside a more deeply nested
32
+ * dialog/overlay. Returns `true` when the Escape happened outside any dialog
33
+ * (e.g. focus not yet inside the Modal) so the Modal keeps its historical
34
+ * focus-independent close behaviour.
35
+ */
36
+ export declare function isTopMostOverlayForEscape(event: KeyboardEvent, container: Element | null): boolean;
37
+ /** Marks the Escape as owned by a Modal so an outer Dialog can defer. */
38
+ export declare function markEscapeHandledByModal(event: KeyboardEvent): void;
39
+ /** Whether this Escape was already claimed by a nested Modal. */
40
+ export declare function isEscapeHandledByModal(event: Event | undefined): boolean;
41
+ /**
42
+ * Wires up Escape containment for a legacy `Modal` and returns a cleanup
43
+ * function. While active it:
44
+ *
45
+ * - marks the event in the CAPTURE phase when the Modal is top-most, so an
46
+ * outer Base UI `Dialog` defers instead of closing; and
47
+ * - closes the Modal in the BUBBLE phase when it is top-most. Bubble phase
48
+ * means a nested overlay (Select, Popover, DatePicker, …) that stops the
49
+ * Escape below the document prevents this from firing, so the Modal
50
+ * correctly stays open in that case.
51
+ *
52
+ * `getContainer` is read lazily so the current Modal element is always used.
53
+ */
54
+ export declare function addModalEscapeContainment({ getModal, onRequestClose, }: {
55
+ getModal: () => Element | null;
56
+ onRequestClose?: () => void;
57
+ }): () => void;
@@ -12,6 +12,7 @@ var Heading = require('./Heading-cjs.js');
12
12
  var BottomSheet = require('./BottomSheet-cjs.js');
13
13
  var DrawerTitle = require('./DrawerTitle-cjs.js');
14
14
  var DrawerDescription = require('./DrawerDescription-cjs.js');
15
+ var nestedEscape = require('./nestedEscape-cjs.js');
15
16
 
16
17
  // Copied from Menu's constant so the two surfaces agree on what "mobile" means.
17
18
  // (The proposal calls for copying shared Menu values into the component first
@@ -306,6 +307,14 @@ function useDialogShell() {
306
307
  function Dialog(_a) {
307
308
  var { onRequestClose, size = "base", type = "dialog", finalFocus, children, onOpenChange } = _a, rest = tslib_es6.__rest(_a, ["onRequestClose", "size", "type", "finalFocus", "children", "onOpenChange"]);
308
309
  function handleOpenChange(nextOpen, eventDetails) {
310
+ if (!nextOpen &&
311
+ eventDetails.reason === "escape-key" &&
312
+ nestedEscape.isEscapeHandledByModal(eventDetails.event)) {
313
+ // A legacy Modal nested inside this Dialog owns this Escape and will close
314
+ // itself; keep the Dialog open so a single Escape closes only the Modal.
315
+ eventDetails.cancel();
316
+ return;
317
+ }
309
318
  if (!nextOpen) {
310
319
  onRequestClose === null || onRequestClose === void 0 ? void 0 : onRequestClose(eventDetails);
311
320
  }
@@ -10,6 +10,7 @@ import { H as Heading } from './Heading-es.js';
10
10
  import { a as BottomSheet, B as BottomSheetScrollAreaContext } from './BottomSheet-es.js';
11
11
  import { D as DrawerTitle, a as DrawerClose, b as DialogTitle$1, c as DialogClose$1 } from './DrawerTitle-es.js';
12
12
  import { D as DrawerDescription, a as DialogBackdrop, b as DialogPopup, c as DialogDescription, d as createDialogHandle } from './DrawerDescription-es.js';
13
+ import { i as isEscapeHandledByModal } from './nestedEscape-es.js';
13
14
 
14
15
  // Copied from Menu's constant so the two surfaces agree on what "mobile" means.
15
16
  // (The proposal calls for copying shared Menu values into the component first
@@ -304,6 +305,14 @@ function useDialogShell() {
304
305
  function Dialog(_a) {
305
306
  var { onRequestClose, size = "base", type = "dialog", finalFocus, children, onOpenChange } = _a, rest = __rest(_a, ["onRequestClose", "size", "type", "finalFocus", "children", "onOpenChange"]);
306
307
  function handleOpenChange(nextOpen, eventDetails) {
308
+ if (!nextOpen &&
309
+ eventDetails.reason === "escape-key" &&
310
+ isEscapeHandledByModal(eventDetails.event)) {
311
+ // A legacy Modal nested inside this Dialog owns this Escape and will close
312
+ // itself; keep the Dialog open so a single Escape closes only the Modal.
313
+ eventDetails.cancel();
314
+ return;
315
+ }
307
316
  if (!nextOpen) {
308
317
  onRequestClose === null || onRequestClose === void 0 ? void 0 : onRequestClose(eventDetails);
309
318
  }
package/dist/index.cjs CHANGED
@@ -180,6 +180,7 @@ require('./clamp-cjs.js');
180
180
  require('./BottomSheet-cjs.js');
181
181
  require('./DrawerTitle-cjs.js');
182
182
  require('./DrawerDescription-cjs.js');
183
+ require('./nestedEscape-cjs.js');
183
184
  require('./useFormFieldFocus-cjs.js');
184
185
  require('filesize');
185
186
  require('./GridCell-cjs.js');
package/dist/index.mjs CHANGED
@@ -178,6 +178,7 @@ import './clamp-es.js';
178
178
  import './BottomSheet-es.js';
179
179
  import './DrawerTitle-es.js';
180
180
  import './DrawerDescription-es.js';
181
+ import './nestedEscape-es.js';
181
182
  import './useFormFieldFocus-es.js';
182
183
  import 'filesize';
183
184
  import './GridCell-es.js';
@@ -0,0 +1,94 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Escape-key containment between the legacy prop-based `Modal` and Base UI's
5
+ * `Dialog`.
6
+ *
7
+ * The problem: both overlays listen for Escape at the `document` level and both
8
+ * portal their content to `<body>`. When one is nested inside the other, a
9
+ * single Escape reaches both listeners, so both layers close. Only the top-most
10
+ * layer should close.
11
+ *
12
+ * The legacy `Modal` can't join Base UI's floating-element tree, so this module
13
+ * coordinates the two with a small, explicit protocol:
14
+ *
15
+ * 1. Top-most check — because each overlay portals to `<body>`, the focused
16
+ * content's *nearest* `[role="dialog"]` ancestor is always its own
17
+ * container. So the Modal only acts when that nearest dialog is itself; if
18
+ * it's a different (nested) dialog, the Modal defers. See
19
+ * {@link isTopMostOverlayForEscape}.
20
+ *
21
+ * 2. Deferral signal — when the Modal is top-most it stamps the native event
22
+ * during the CAPTURE phase (which runs before Base UI's bubble-phase
23
+ * dismissal). An outer Base UI `Dialog` reads that flag in its
24
+ * `onOpenChange` handler and cancels, so the Dialog stays open while the
25
+ * Modal closes. See {@link markEscapeHandledByModal} /
26
+ * {@link isEscapeHandledByModal}.
27
+ *
28
+ * Note: this is a DOM-proximity heuristic rather than a true overlay stack. It
29
+ * holds only because both overlays portal to `<body>` and expose a dialog role.
30
+ */
31
+ const ESCAPE_KEY = "Escape";
32
+ /** Selector matching either an accessible dialog or alert dialog surface. */
33
+ const DIALOG_ROLE_SELECTOR = '[role="dialog"], [role="alertdialog"]';
34
+ /** Property stamped on the native Escape event to signal Modal ownership. */
35
+ const HANDLED_FLAG = "atlantisHandledByNestedModal";
36
+ /**
37
+ * Whether `container` is the top-most overlay for this Escape — i.e. the
38
+ * Escape originated inside `container` and not inside a more deeply nested
39
+ * dialog/overlay. Returns `true` when the Escape happened outside any dialog
40
+ * (e.g. focus not yet inside the Modal) so the Modal keeps its historical
41
+ * focus-independent close behaviour.
42
+ */
43
+ function isTopMostOverlayForEscape(event, container) {
44
+ var _a;
45
+ const target = event.target;
46
+ const nearestDialog = (_a = target === null || target === void 0 ? void 0 : target.closest) === null || _a === void 0 ? void 0 : _a.call(target, DIALOG_ROLE_SELECTOR);
47
+ return !nearestDialog || nearestDialog === container;
48
+ }
49
+ /** Marks the Escape as owned by a Modal so an outer Dialog can defer. */
50
+ function markEscapeHandledByModal(event) {
51
+ event[HANDLED_FLAG] = true;
52
+ }
53
+ /** Whether this Escape was already claimed by a nested Modal. */
54
+ function isEscapeHandledByModal(event) {
55
+ return Boolean(event === null || event === void 0 ? void 0 : event[HANDLED_FLAG]);
56
+ }
57
+ /**
58
+ * Wires up Escape containment for a legacy `Modal` and returns a cleanup
59
+ * function. While active it:
60
+ *
61
+ * - marks the event in the CAPTURE phase when the Modal is top-most, so an
62
+ * outer Base UI `Dialog` defers instead of closing; and
63
+ * - closes the Modal in the BUBBLE phase when it is top-most. Bubble phase
64
+ * means a nested overlay (Select, Popover, DatePicker, …) that stops the
65
+ * Escape below the document prevents this from firing, so the Modal
66
+ * correctly stays open in that case.
67
+ *
68
+ * `getContainer` is read lazily so the current Modal element is always used.
69
+ */
70
+ function addModalEscapeContainment({ getModal, onRequestClose, }) {
71
+ function markEscape(event) {
72
+ if (event.key !== ESCAPE_KEY)
73
+ return;
74
+ if (!isTopMostOverlayForEscape(event, getModal()))
75
+ return;
76
+ markEscapeHandledByModal(event);
77
+ }
78
+ function closeOnEscape(event) {
79
+ if (event.key !== ESCAPE_KEY)
80
+ return;
81
+ if (!isTopMostOverlayForEscape(event, getModal()))
82
+ return;
83
+ onRequestClose === null || onRequestClose === void 0 ? void 0 : onRequestClose();
84
+ }
85
+ document.addEventListener("keydown", markEscape, true);
86
+ document.addEventListener("keydown", closeOnEscape);
87
+ return () => {
88
+ document.removeEventListener("keydown", markEscape, true);
89
+ document.removeEventListener("keydown", closeOnEscape);
90
+ };
91
+ }
92
+
93
+ exports.addModalEscapeContainment = addModalEscapeContainment;
94
+ exports.isEscapeHandledByModal = isEscapeHandledByModal;
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Escape-key containment between the legacy prop-based `Modal` and Base UI's
3
+ * `Dialog`.
4
+ *
5
+ * The problem: both overlays listen for Escape at the `document` level and both
6
+ * portal their content to `<body>`. When one is nested inside the other, a
7
+ * single Escape reaches both listeners, so both layers close. Only the top-most
8
+ * layer should close.
9
+ *
10
+ * The legacy `Modal` can't join Base UI's floating-element tree, so this module
11
+ * coordinates the two with a small, explicit protocol:
12
+ *
13
+ * 1. Top-most check — because each overlay portals to `<body>`, the focused
14
+ * content's *nearest* `[role="dialog"]` ancestor is always its own
15
+ * container. So the Modal only acts when that nearest dialog is itself; if
16
+ * it's a different (nested) dialog, the Modal defers. See
17
+ * {@link isTopMostOverlayForEscape}.
18
+ *
19
+ * 2. Deferral signal — when the Modal is top-most it stamps the native event
20
+ * during the CAPTURE phase (which runs before Base UI's bubble-phase
21
+ * dismissal). An outer Base UI `Dialog` reads that flag in its
22
+ * `onOpenChange` handler and cancels, so the Dialog stays open while the
23
+ * Modal closes. See {@link markEscapeHandledByModal} /
24
+ * {@link isEscapeHandledByModal}.
25
+ *
26
+ * Note: this is a DOM-proximity heuristic rather than a true overlay stack. It
27
+ * holds only because both overlays portal to `<body>` and expose a dialog role.
28
+ */
29
+ const ESCAPE_KEY = "Escape";
30
+ /** Selector matching either an accessible dialog or alert dialog surface. */
31
+ const DIALOG_ROLE_SELECTOR = '[role="dialog"], [role="alertdialog"]';
32
+ /** Property stamped on the native Escape event to signal Modal ownership. */
33
+ const HANDLED_FLAG = "atlantisHandledByNestedModal";
34
+ /**
35
+ * Whether `container` is the top-most overlay for this Escape — i.e. the
36
+ * Escape originated inside `container` and not inside a more deeply nested
37
+ * dialog/overlay. Returns `true` when the Escape happened outside any dialog
38
+ * (e.g. focus not yet inside the Modal) so the Modal keeps its historical
39
+ * focus-independent close behaviour.
40
+ */
41
+ function isTopMostOverlayForEscape(event, container) {
42
+ var _a;
43
+ const target = event.target;
44
+ const nearestDialog = (_a = target === null || target === void 0 ? void 0 : target.closest) === null || _a === void 0 ? void 0 : _a.call(target, DIALOG_ROLE_SELECTOR);
45
+ return !nearestDialog || nearestDialog === container;
46
+ }
47
+ /** Marks the Escape as owned by a Modal so an outer Dialog can defer. */
48
+ function markEscapeHandledByModal(event) {
49
+ event[HANDLED_FLAG] = true;
50
+ }
51
+ /** Whether this Escape was already claimed by a nested Modal. */
52
+ function isEscapeHandledByModal(event) {
53
+ return Boolean(event === null || event === void 0 ? void 0 : event[HANDLED_FLAG]);
54
+ }
55
+ /**
56
+ * Wires up Escape containment for a legacy `Modal` and returns a cleanup
57
+ * function. While active it:
58
+ *
59
+ * - marks the event in the CAPTURE phase when the Modal is top-most, so an
60
+ * outer Base UI `Dialog` defers instead of closing; and
61
+ * - closes the Modal in the BUBBLE phase when it is top-most. Bubble phase
62
+ * means a nested overlay (Select, Popover, DatePicker, …) that stops the
63
+ * Escape below the document prevents this from firing, so the Modal
64
+ * correctly stays open in that case.
65
+ *
66
+ * `getContainer` is read lazily so the current Modal element is always used.
67
+ */
68
+ function addModalEscapeContainment({ getModal, onRequestClose, }) {
69
+ function markEscape(event) {
70
+ if (event.key !== ESCAPE_KEY)
71
+ return;
72
+ if (!isTopMostOverlayForEscape(event, getModal()))
73
+ return;
74
+ markEscapeHandledByModal(event);
75
+ }
76
+ function closeOnEscape(event) {
77
+ if (event.key !== ESCAPE_KEY)
78
+ return;
79
+ if (!isTopMostOverlayForEscape(event, getModal()))
80
+ return;
81
+ onRequestClose === null || onRequestClose === void 0 ? void 0 : onRequestClose();
82
+ }
83
+ document.addEventListener("keydown", markEscape, true);
84
+ document.addEventListener("keydown", closeOnEscape);
85
+ return () => {
86
+ document.removeEventListener("keydown", markEscape, true);
87
+ document.removeEventListener("keydown", closeOnEscape);
88
+ };
89
+ }
90
+
91
+ export { addModalEscapeContainment as a, isEscapeHandledByModal as i };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobber/components",
3
- "version": "8.27.1",
3
+ "version": "8.27.3-FIXproper-e2c0fc2.2+e2c0fc29",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -545,5 +545,5 @@
545
545
  "> 1%",
546
546
  "IE 10"
547
547
  ],
548
- "gitHead": "54328e5ade908b51ca935c5e2854f019cc57e57d"
548
+ "gitHead": "e2c0fc291df3952458a5b6151abdc433402c7286"
549
549
  }