@easypost/easy-ui 1.0.0-alpha.120 → 1.0.0-alpha.122

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,24 @@
1
1
  # @easypost/easy-ui
2
2
 
3
+ ## 1.0.0-alpha.122
4
+
5
+ ### Minor Changes
6
+
7
+ - f17a7b7: Add `childNestingBehavior` and `selfNestingBehavior` props to `Modal.Trigger` and `ModalContainer` to control how modals stack when nested. The connection between a parent and a nested child can resolve to `stack` (both keep their backdrops — the default) or `replace` (the nested modal hides the modal beneath it).
8
+
9
+ Configure it from either end: `childNestingBehavior` is set on the parent, applies to its children, and cascades down the tree; `selfNestingBehavior` is set on the child, applies only to its own connection to its parent, does not cascade, and overrides the parent's `childNestingBehavior` for that connection. `selfNestingBehavior` is useful for surgically changing one nested modal in a large tree without touching its parent.
10
+
11
+ ### Patch Changes
12
+
13
+ - f17a7b7: Fix third-party overlays (e.g. Stripe Link/autofill) locking up when their modal (`allowsThirdPartyOverlays`) is nested inside another modal. A surrounding focus-trapping modal kept hiding the page (`inert`) and containing focus, which re-broke the injected overlay. A modal now automatically relaxes its focus trap and background hiding while it has an open `allowsThirdPartyOverlays` descendant, then restores them when that descendant closes. Modals without such a descendant are unaffected.
14
+ - f17a7b7: Fix react-aria overlays (e.g. `Select`, `Menu`) not closing on outside click when rendered inside a `Modal` that uses `allowsThirdPartyOverlays`. The modal box was tagged `data-react-aria-top-layer`, which made react-aria treat every click inside the modal as "not outside" for all overlays. Modals now keep nested overlays dismissable while preserving the third-party overlay behavior: the modal stays visible under a surrounding modal, clicking a nested modal no longer dismisses the one beneath it, and genuine third-party overlays (e.g. Stripe) still don't dismiss the modal.
15
+
16
+ ## 1.0.0-alpha.121
17
+
18
+ ### Minor Changes
19
+
20
+ - e47ec8f: Add `allowsThirdPartyOverlays` to `Modal.Trigger` and `ModalContainer`. When enabled, the modal stops trapping focus and stops applying `aria-hidden`/`inert` to the rest of the page, so third-party overlays that render outside the modal (e.g. Stripe Link/autofill, reCAPTCHA) remain focusable and clickable instead of locking up.
21
+
3
22
  ## 1.0.0-alpha.120
4
23
 
5
24
  ### Patch Changes
package/Modal/Modal.d.ts CHANGED
@@ -5,6 +5,7 @@ import { ModalHeader } from "./ModalHeader";
5
5
  import { ModalTrigger } from "./ModalTrigger";
6
6
  import { ModalContainer } from "./ModalContainer";
7
7
  import { useModalTrigger } from "./context";
8
+ import type { ModalNestingBehavior } from "./context";
8
9
  type ModalSize = "sm" | "md" | "lg" | "xl";
9
10
  export type ModalProps = {
10
11
  /**
@@ -49,4 +50,5 @@ export declare namespace Modal {
49
50
  var Footer: typeof ModalFooter;
50
51
  }
51
52
  export { ModalContainer, useModalTrigger };
53
+ export type { ModalNestingBehavior };
52
54
  //# sourceMappingURL=Modal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Modal.d.ts","sourceRoot":"","sources":["../../src/Modal/Modal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAc,SAAS,EAAmB,MAAM,OAAO,CAAC;AAGtE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG9C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAI5C,KAAK,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3C,MAAM,MAAM,UAAU,GAAG;IACvB;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;;;OAIG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,UAAU,qBAqCtC;yBArCe,KAAK;;;;;;AAsErB,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,CAAC"}
1
+ {"version":3,"file":"Modal.d.ts","sourceRoot":"","sources":["../../src/Modal/Modal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAc,SAAS,EAAmB,MAAM,OAAO,CAAC;AAGtE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG9C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAItD,KAAK,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3C,MAAM,MAAM,UAAU,GAAG;IACvB;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;;;OAIG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,UAAU,qBAqCtC;yBArCe,KAAK;;;;;;AAsErB,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,CAAC;AAC3C,YAAY,EAAE,oBAAoB,EAAE,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=Modal.nesting.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Modal.nesting.test.d.ts","sourceRoot":"","sources":["../../src/Modal/Modal.nesting.test.tsx"],"names":[],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"Modal.stories.d.ts","sourceRoot":"","sources":["../../src/Modal/Modal.stories.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAQvD,OAAO,EAAE,KAAK,EAAmC,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAM9C,KAAK,UAAU,GAAG,QAAQ,CAAC,OAAO,KAAK,CAAC,CAAC;AACzC,KAAK,iBAAiB,GAAG,QAAQ,CAAC,OAAO,YAAY,CAAC,CAAC;AAEvD,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,KAAK,CAQ5B,CAAC;AAEF,eAAe,IAAI,CAAC;AAEpB,eAAO,MAAM,MAAM,EAAE,UAkBpB,CAAC;AAEF,eAAO,MAAM,QAAQ,EAAE,UA6CtB,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,iBA6B5B,CAAC;AAEF,eAAO,MAAM,IAAI,EAAE,UAwBlB,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,iBAwBzB,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,iBAwBxB,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,iBAyBzB,CAAC;AAEF,eAAO,MAAM,MAAM,EAAE,iBA4FpB,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,UAqBxB,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,UA0B5B,CAAC"}
1
+ {"version":3,"file":"Modal.stories.d.ts","sourceRoot":"","sources":["../../src/Modal/Modal.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAcvD,OAAO,EAAE,KAAK,EAAmC,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,KAAK,UAAU,GAAG,QAAQ,CAAC,OAAO,KAAK,CAAC,CAAC;AACzC,KAAK,iBAAiB,GAAG,QAAQ,CAAC,OAAO,YAAY,CAAC,CAAC;AAEvD,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,KAAK,CAQ5B,CAAC;AAEF,eAAe,IAAI,CAAC;AAEpB,eAAO,MAAM,MAAM,EAAE,UAkBpB,CAAC;AAEF,eAAO,MAAM,QAAQ,EAAE,UA6CtB,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,iBA6B5B,CAAC;AAEF,eAAO,MAAM,IAAI,EAAE,UAwBlB,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,iBAwBzB,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,iBAwBxB,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,iBAyBzB,CAAC;AAEF,eAAO,MAAM,MAAM,EAAE,iBAgKpB,CAAC;AA8EF,eAAO,MAAM,UAAU,EAAE,UAqBxB,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,UA0B5B,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import React, { ReactNode } from "react";
2
+ import { ModalNestingBehavior } from "./context";
2
3
  type ModalContainerProps = {
3
4
  /**
4
5
  * Modal wrap content.
@@ -8,6 +9,29 @@ type ModalContainerProps = {
8
9
  * Whether or not the modal is dismissable.
9
10
  */
10
11
  isDismissable?: boolean;
12
+ /**
13
+ * Disables focus trapping and background aria-hiding so the modal can host
14
+ * third-party overlays (e.g. Stripe Link/autofill, reCAPTCHA) that render
15
+ * outside the modal. Use sparingly — see `ModalUnderlay` for the tradeoffs.
16
+ *
17
+ * @default false
18
+ */
19
+ allowsThirdPartyOverlays?: boolean;
20
+ /**
21
+ * Controls how this modal's nested children stack relative to it, and cascades
22
+ * to descendant modals. `stack` gives each child its own backdrop, and
23
+ * `replace` hides this modal while a child is open. When unset, it inherits the
24
+ * nearest ancestor modal's value (or `stack` at the root).
25
+ */
26
+ childNestingBehavior?: ModalNestingBehavior;
27
+ /**
28
+ * Controls how this modal stacks relative to its parent, overriding the
29
+ * parent's `childNestingBehavior` for just this modal. `stack` keeps both
30
+ * backdrops, and `replace` hides the parent while this modal is open. Local to
31
+ * this modal — it does not cascade. Useful for surgically changing one nested
32
+ * modal in a larger tree without touching its parent.
33
+ */
34
+ selfNestingBehavior?: ModalNestingBehavior;
11
35
  /**
12
36
  * Handler that is called when the overlay is closed.
13
37
  */
@@ -1 +1 @@
1
- {"version":3,"file":"ModalContainer.d.ts","sourceRoot":"","sources":["../../src/Modal/ModalContainer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAgB,SAAS,EAA0B,MAAM,OAAO,CAAC;AAM/E,KAAK,mBAAmB,GAAG;IACzB;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;OAEG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,qBA0CxD"}
1
+ {"version":3,"file":"ModalContainer.d.ts","sourceRoot":"","sources":["../../src/Modal/ModalContainer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAgB,SAAS,EAA0B,MAAM,OAAO,CAAC;AAI/E,OAAO,EAAE,oBAAoB,EAAwB,MAAM,WAAW,CAAC;AAEvE,KAAK,mBAAmB,GAAG;IACzB;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;OAEG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;;;;;OAMG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAEnC;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAE5C;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,oBAAoB,CAAC;IAE3C;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,qBA2DxD"}
@@ -1,4 +1,5 @@
1
1
  import React, { ReactElement } from "react";
2
+ import { ModalNestingBehavior } from "./context";
2
3
  export type CloseableModalElement = (close: () => void) => ReactElement;
3
4
  export type ModalTriggerProps = {
4
5
  /**
@@ -11,8 +12,33 @@ export type ModalTriggerProps = {
11
12
  defaultOpen?: boolean;
12
13
  /**
13
14
  * Whether or not the modal can be dismissed.
15
+ *
16
+ * @default true
14
17
  */
15
18
  isDismissable?: boolean;
19
+ /**
20
+ * Disables focus trapping and background aria-hiding so the modal can host
21
+ * third-party overlays (e.g. Stripe Link/autofill, reCAPTCHA) that render
22
+ * outside the modal. Use sparingly — see `ModalUnderlay` for the tradeoffs.
23
+ *
24
+ * @default false
25
+ */
26
+ allowsThirdPartyOverlays?: boolean;
27
+ /**
28
+ * Controls how this modal's nested children stack relative to it, and cascades
29
+ * to descendant modals. `stack` gives each child its own backdrop, and
30
+ * `replace` hides this modal while a child is open. When unset, it inherits the
31
+ * nearest ancestor modal's value (or `stack` at the root).
32
+ */
33
+ childNestingBehavior?: ModalNestingBehavior;
34
+ /**
35
+ * Controls how this modal stacks relative to its parent, overriding the
36
+ * parent's `childNestingBehavior` for just this modal. `stack` keeps both
37
+ * backdrops, and `replace` hides the parent while this modal is open. Local to
38
+ * this modal — it does not cascade. Useful for surgically changing one nested
39
+ * modal in a larger tree without touching its parent.
40
+ */
41
+ selfNestingBehavior?: ModalNestingBehavior;
16
42
  /**
17
43
  * Whether the modal is open by default (controlled).
18
44
  */
@@ -1 +1 @@
1
- {"version":3,"file":"ModalTrigger.d.ts","sourceRoot":"","sources":["../../src/Modal/ModalTrigger.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,YAAY,EAAgB,MAAM,OAAO,CAAC;AAM1D,MAAM,MAAM,qBAAqB,GAAG,CAAC,KAAK,EAAE,MAAM,IAAI,KAAK,YAAY,CAAC;AAExE,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;OAEG;IACH,QAAQ,EAAE,CAAC,YAAY,EAAE,qBAAqB,GAAG,YAAY,CAAC,CAAC;IAE/D;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;OAEG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;OAEG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;OAEG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CAC1C,CAAC;AAEF,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,qBA8BpD"}
1
+ {"version":3,"file":"ModalTrigger.d.ts","sourceRoot":"","sources":["../../src/Modal/ModalTrigger.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,YAAY,EAAgB,MAAM,OAAO,CAAC;AAI1D,OAAO,EAAE,oBAAoB,EAAwB,MAAM,WAAW,CAAC;AAEvE,MAAM,MAAM,qBAAqB,GAAG,CAAC,KAAK,EAAE,MAAM,IAAI,KAAK,YAAY,CAAC;AAExE,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;OAEG;IACH,QAAQ,EAAE,CAAC,YAAY,EAAE,qBAAqB,GAAG,YAAY,CAAC,CAAC;IAE/D;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;;;;;OAMG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAEnC;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAE5C;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,oBAAoB,CAAC;IAE3C;;OAEG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;OAEG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CAC1C,CAAC;AAEF,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,qBA2CpD"}
@@ -13,6 +13,19 @@ type ModalUnderlayProps = {
13
13
  * Whether or not the modal is dismissable.
14
14
  */
15
15
  isDismissable?: boolean;
16
+ /**
17
+ * When `true`, the modal stops trapping focus and stops hiding the rest of
18
+ * the page from assistive technologies (`aria-hidden`/`inert`). Use this only
19
+ * for modals that intentionally host third-party overlays — e.g. Stripe
20
+ * Link/autofill or reCAPTCHA — which render themselves into the document
21
+ * *outside* the modal. react-aria's focus trap and `inert` would otherwise
22
+ * blur and lock up those overlays. This trades away the modal's focus
23
+ * containment and background hiding, so reach for it only when a third-party
24
+ * overlay requires it.
25
+ *
26
+ * @default false
27
+ */
28
+ allowsThirdPartyOverlays?: boolean;
16
29
  };
17
30
  export declare function ModalUnderlay(props: ModalUnderlayProps): React.JSX.Element;
18
31
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"ModalUnderlay.d.ts","sourceRoot":"","sources":["../../src/Modal/ModalUnderlay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAMpD,KAAK,kBAAkB,GAAG;IACxB;;OAEG;IACH,KAAK,EAAE,mBAAmB,CAAC;IAE3B;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;OAEG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,qBAgCtD"}
1
+ {"version":3,"file":"ModalUnderlay.d.ts","sourceRoot":"","sources":["../../src/Modal/ModalUnderlay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,SAAS,EAAgC,MAAM,OAAO,CAAC;AAMvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAgBpD,KAAK,kBAAkB,GAAG;IACxB;;OAEG;IACH,KAAK,EAAE,mBAAmB,CAAC;IAE3B;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;OAEG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;;;;;;;;;;OAWG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC,CAAC;AAuBF,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,qBAUtD"}
@@ -2,6 +2,17 @@ import React, { ReactNode } from "react";
2
2
  import { FocusableElement } from "@react-types/shared";
3
3
  import { DOMAttributes, RefObject } from "react";
4
4
  import { OverlayTriggerState } from "react-stately";
5
+ /**
6
+ * Controls how one modal in a nested stack presents relative to the modal it
7
+ * connects to. The same value describes that connection from either end —
8
+ * `childNestingBehavior` (set on the parent, cascades down) and
9
+ * `selfNestingBehavior` (set on the child, local) — and resolves to:
10
+ *
11
+ * - `stack` — both modals keep their own backdrops; modals simply stack.
12
+ * - `replace` — the nested modal hides the modal beneath it, so only the topmost
13
+ * modal is visible.
14
+ */
15
+ export type ModalNestingBehavior = "stack" | "replace";
5
16
  export type ModalContextType = {
6
17
  dialogProps: DOMAttributes<FocusableElement>;
7
18
  titleProps: DOMAttributes<FocusableElement>;
@@ -14,17 +25,73 @@ export type ModalContextType = {
14
25
  type ModalTriggerContextType = {
15
26
  isDismissable: boolean;
16
27
  state: OverlayTriggerState;
17
- hasOpenNestedModal: boolean;
18
- setHasOpenNestedModal: (hasOpenNestedModal: boolean) => void;
28
+ /**
29
+ * Whether this modal currently has one or more open nested modals whose
30
+ * connection resolved to `replace`. When true, this modal hides itself so the
31
+ * nested modal takes its place.
32
+ */
33
+ hasReplacingChild: boolean;
34
+ /**
35
+ * The resolved behavior this modal imposes on its children's connections.
36
+ * Cascades: a child that sets neither `childNestingBehavior` nor
37
+ * `selfNestingBehavior` inherits this value.
38
+ */
39
+ childNestingBehavior: ModalNestingBehavior;
40
+ /**
41
+ * The resolved behavior of this modal's connection to its parent. Drives this
42
+ * modal's own rendering — suppressing its backdrop or hiding its parent.
43
+ */
44
+ selfNestingBehavior: ModalNestingBehavior;
45
+ /**
46
+ * Registers an open nested modal with this modal. The `replaces` argument
47
+ * records whether that nested modal's connection resolved to `replace`.
48
+ * Returns a cleanup that unregisters it. Callers invoke this from an effect
49
+ * tied to the nested modal's open state so the count stays accurate across
50
+ * open/close/unmount.
51
+ */
52
+ registerNested: (replaces: boolean) => () => void;
53
+ /**
54
+ * Whether an open descendant modal (at any depth) hosts third-party overlays
55
+ * (`allowsThirdPartyOverlays`). A focus-trapping modal reads this to stop
56
+ * hiding/trapping the page while such a descendant is open, so the descendant's
57
+ * injected overlay (e.g. Stripe Link) isn't `inert`'d or robbed of focus.
58
+ */
59
+ hasOpenThirdPartyDescendant: boolean;
60
+ /**
61
+ * Registers that this modal's subtree contains an open third-party-overlay
62
+ * modal, propagating up to every ancestor. Returns a cleanup that unregisters
63
+ * it. Like {@link registerNested}, callers invoke this from an effect tied to
64
+ * open state so the count stays accurate across open/close/unmount.
65
+ */
66
+ registerThirdPartyOverlay: () => () => void;
19
67
  };
20
68
  export type ModalTriggerProviderProps = Pick<ModalTriggerContextType, "state" | "isDismissable"> & {
21
69
  children: ReactNode;
70
+ /**
71
+ * How this modal's nested children present relative to it. Cascades to
72
+ * descendants. Defaults to the nearest ancestor's value, or `stack` at the
73
+ * root.
74
+ */
75
+ childNestingBehavior?: ModalNestingBehavior;
76
+ /**
77
+ * How this modal presents relative to its parent. Local to this modal (does
78
+ * not cascade) and overrides the parent's `childNestingBehavior` for this one
79
+ * connection. Defaults to the parent's `childNestingBehavior`.
80
+ */
81
+ selfNestingBehavior?: ModalNestingBehavior;
82
+ /**
83
+ * Whether this modal hosts third-party overlays itself. Used to register this
84
+ * modal's subtree with focus-trapping ancestors so they can relax while it's
85
+ * open. This is the underlay's behavior; the provider only needs it to drive
86
+ * registration.
87
+ */
88
+ allowsThirdPartyOverlays?: boolean;
22
89
  };
23
90
  export declare const ModalContext: React.Context<ModalContextType | null>;
24
91
  export declare const useModalContext: () => ModalContextType;
25
92
  export declare const ModalTriggerContext: React.Context<ModalTriggerContextType | null>;
26
93
  export declare const useModalTriggerContext: () => ModalTriggerContextType;
27
94
  export declare const useModalTrigger: () => OverlayTriggerState;
28
- export declare function ModalTriggerProvider({ state, isDismissable, children, }: ModalTriggerProviderProps): React.JSX.Element;
95
+ export declare function ModalTriggerProvider({ state, isDismissable, childNestingBehavior, selfNestingBehavior, allowsThirdPartyOverlays, children, }: ModalTriggerProviderProps): React.JSX.Element;
29
96
  export {};
30
97
  //# sourceMappingURL=context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/Modal/context.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EACL,aAAa,EACb,SAAS,EAKV,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEpD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IAC7C,UAAU,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IAC5C,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC1C,oBAAoB,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IACvD,oBAAoB,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;CACxD,CAAC;AAEF,KAAK,uBAAuB,GAAG;IAC7B,aAAa,EAAE,OAAO,CAAC;IACvB,KAAK,EAAE,mBAAmB,CAAC;IAC3B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,qBAAqB,EAAE,CAAC,kBAAkB,EAAE,OAAO,KAAK,IAAI,CAAC;CAC9D,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAC1C,uBAAuB,EACvB,OAAO,GAAG,eAAe,CAC1B,GAAG;IACF,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF,eAAO,MAAM,YAAY,wCAA+C,CAAC;AAEzE,eAAO,MAAM,eAAe,wBAM3B,CAAC;AAEF,eAAO,MAAM,mBAAmB,+CACqB,CAAC;AAEtD,eAAO,MAAM,sBAAsB,+BAMlC,CAAC;AAEF,eAAO,MAAM,eAAe,2BAG3B,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,EACnC,KAAK,EACL,aAAa,EACb,QAAQ,GACT,EAAE,yBAAyB,qBAU3B"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/Modal/context.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EACL,aAAa,EACb,SAAS,EAMV,MAAM,OAAO,CAAC;AAKf,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEpD;;;;;;;;;GASG;AACH,MAAM,MAAM,oBAAoB,GAAG,OAAO,GAAG,SAAS,CAAC;AAEvD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IAC7C,UAAU,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IAC5C,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC1C,oBAAoB,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IACvD,oBAAoB,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;CACxD,CAAC;AAEF,KAAK,uBAAuB,GAAG;IAC7B,aAAa,EAAE,OAAO,CAAC;IACvB,KAAK,EAAE,mBAAmB,CAAC;IAC3B;;;;OAIG;IACH,iBAAiB,EAAE,OAAO,CAAC;IAC3B;;;;OAIG;IACH,oBAAoB,EAAE,oBAAoB,CAAC;IAC3C;;;OAGG;IACH,mBAAmB,EAAE,oBAAoB,CAAC;IAC1C;;;;;;OAMG;IACH,cAAc,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;IAClD;;;;;OAKG;IACH,2BAA2B,EAAE,OAAO,CAAC;IACrC;;;;;OAKG;IACH,yBAAyB,EAAE,MAAM,MAAM,IAAI,CAAC;CAC7C,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAC1C,uBAAuB,EACvB,OAAO,GAAG,eAAe,CAC1B,GAAG;IACF,QAAQ,EAAE,SAAS,CAAC;IACpB;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,oBAAoB,CAAC;IAC3C;;;;;OAKG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC,CAAC;AAEF,eAAO,MAAM,YAAY,wCAA+C,CAAC;AAEzE,eAAO,MAAM,eAAe,wBAM3B,CAAC;AAEF,eAAO,MAAM,mBAAmB,+CACqB,CAAC;AAEtD,eAAO,MAAM,sBAAsB,+BAMlC,CAAC;AAEF,eAAO,MAAM,eAAe,2BAG3B,CAAC;AAiIF,wBAAgB,oBAAoB,CAAC,EACnC,KAAK,EACL,aAAa,EACb,oBAAoB,EACpB,mBAAmB,EACnB,wBAAgC,EAChC,QAAQ,GACT,EAAE,yBAAyB,qBAkB3B"}
package/Modal/index.js CHANGED
@@ -35,14 +35,16 @@ const jsxRuntime = require("react/jsx-runtime");
35
35
  const React = require("react");
36
36
  const utilities_css = require("../utilities/css.js");
37
37
  const utilities_useScrollbar = require("../utilities/useScrollbar.js");
38
+ const mergeProps = require("../__chunks__/mergeProps-DLPcevok.js");
38
39
  const Button = require("../__chunks__/Button-CQdi4TMV.js");
39
40
  const Close = require("../__chunks__/Close-BQ2IlPyf.js");
40
41
  const HorizontalStack = require("../__chunks__/HorizontalStack-CWdKKY8L.js");
41
42
  const Icon = require("../__chunks__/Icon-BRlbo3RF.js");
42
43
  const Text = require("../__chunks__/Text-DkzMEqky.js");
43
44
  const VerticalStack = require("../__chunks__/VerticalStack-BHkiocKU.js");
44
- const useModalOverlay = require("../__chunks__/useModalOverlay-C4Gci3MA.js");
45
+ const ariaHideOutside = require("../__chunks__/ariaHideOutside-Dl8SH3FU.js");
45
46
  const Overlay = require("../__chunks__/Overlay-BNP_t3v4.js");
47
+ const FocusScope = require("../__chunks__/FocusScope-CncDnO-E.js");
46
48
  const useOverlayTrigger = require("../__chunks__/useOverlayTrigger-DP9RbCAF.js");
47
49
  const useIntersectionDetection = require("../__chunks__/useIntersectionDetection-DI5n4zwA.js");
48
50
  const useDialog = require("../__chunks__/useDialog-uxNR0maK.js");
@@ -66,16 +68,75 @@ const useModalTrigger = () => {
66
68
  const modalTriggerContext = useModalTriggerContext();
67
69
  return modalTriggerContext.state;
68
70
  };
69
- function ModalTriggerProvider({ state, isDismissable, children }) {
70
- const [hasOpenNestedModal, setHasOpenNestedModal] = React.useState(false);
71
- const context = React.useMemo(() => {
72
- return {
73
- hasOpenNestedModal,
74
- setHasOpenNestedModal,
75
- state,
76
- isDismissable
71
+ function useModalNesting({ childNestingBehavior, selfNestingBehavior, allowsThirdPartyOverlays, isOpen }) {
72
+ var _a, _b;
73
+ const parentContext = React.useContext(ModalTriggerContext);
74
+ const [replacingChildCount, setReplacingChildCount] = React.useState(0);
75
+ const [thirdPartyDescendantCount, setThirdPartyDescendantCount] = React.useState(0);
76
+ const childNesting = (_a = childNestingBehavior != null ? childNestingBehavior : parentContext == null ? void 0 : parentContext.childNestingBehavior) != null ? _a : "stack";
77
+ const selfNesting = (_b = selfNestingBehavior != null ? selfNestingBehavior : parentContext == null ? void 0 : parentContext.childNestingBehavior) != null ? _b : "stack";
78
+ const registerNested = React.useCallback((replaces) => {
79
+ if (!replaces) {
80
+ return () => {
81
+ };
82
+ }
83
+ setReplacingChildCount((count) => count + 1);
84
+ let released = false;
85
+ return () => {
86
+ if (released) {
87
+ return;
88
+ }
89
+ released = true;
90
+ setReplacingChildCount((count) => Math.max(0, count - 1));
91
+ };
92
+ }, []);
93
+ const parentRegisterNested = parentContext == null ? void 0 : parentContext.registerNested;
94
+ const selfReplacesParent = selfNesting === "replace";
95
+ mergeProps.$f0a04ccd8dbdd83b$export$e5c5a5f917a5871c(() => {
96
+ if (!parentRegisterNested || !isOpen) {
97
+ return;
98
+ }
99
+ return parentRegisterNested(selfReplacesParent);
100
+ }, [parentRegisterNested, isOpen, selfReplacesParent]);
101
+ const registerThirdPartyOverlay = React.useCallback(() => {
102
+ setThirdPartyDescendantCount((count) => count + 1);
103
+ let released = false;
104
+ return () => {
105
+ if (released) {
106
+ return;
107
+ }
108
+ released = true;
109
+ setThirdPartyDescendantCount((count) => Math.max(0, count - 1));
77
110
  };
78
- }, [hasOpenNestedModal, state, isDismissable]);
111
+ }, []);
112
+ const parentRegisterThirdPartyOverlay = parentContext == null ? void 0 : parentContext.registerThirdPartyOverlay;
113
+ const hasThirdPartyInSubtree = allowsThirdPartyOverlays || thirdPartyDescendantCount > 0;
114
+ mergeProps.$f0a04ccd8dbdd83b$export$e5c5a5f917a5871c(() => {
115
+ if (!parentRegisterThirdPartyOverlay || !isOpen || !hasThirdPartyInSubtree) {
116
+ return;
117
+ }
118
+ return parentRegisterThirdPartyOverlay();
119
+ }, [parentRegisterThirdPartyOverlay, isOpen, hasThirdPartyInSubtree]);
120
+ return React.useMemo(() => ({
121
+ hasReplacingChild: replacingChildCount > 0,
122
+ childNestingBehavior: childNesting,
123
+ selfNestingBehavior: selfNesting,
124
+ registerNested,
125
+ hasOpenThirdPartyDescendant: thirdPartyDescendantCount > 0,
126
+ registerThirdPartyOverlay
127
+ }), [replacingChildCount, childNesting, selfNesting, registerNested, thirdPartyDescendantCount, registerThirdPartyOverlay]);
128
+ }
129
+ function ModalTriggerProvider({ state, isDismissable, childNestingBehavior, selfNestingBehavior, allowsThirdPartyOverlays = false, children }) {
130
+ const nesting = useModalNesting({
131
+ childNestingBehavior,
132
+ selfNestingBehavior,
133
+ allowsThirdPartyOverlays,
134
+ isOpen: state.isOpen
135
+ });
136
+ const context = React.useMemo(() => __spreadProps(__spreadValues({}, nesting), {
137
+ state,
138
+ isDismissable
139
+ }), [nesting, state, isDismissable]);
79
140
  return jsxRuntime.jsx(ModalTriggerContext.Provider, {
80
141
  value: context,
81
142
  children
@@ -215,34 +276,94 @@ function ModalHeader(props) {
215
276
  })
216
277
  });
217
278
  }
279
+ const MODAL_BOX_ATTRIBUTE = "data-easy-ui-modal-box";
280
+ function shouldCloseOnInteractOutside(target) {
281
+ return !target.closest(`[${MODAL_BOX_ATTRIBUTE}]`);
282
+ }
218
283
  function ModalUnderlay(props) {
219
- const _a = props, { state, children } = _a, overlayProps = __objRest(_a, ["state", "children"]);
220
- const { isDismissable = true } = overlayProps;
284
+ return props.allowsThirdPartyOverlays ? jsxRuntime.jsx(ThirdPartyOverlayUnderlay, __spreadValues({}, props)) : jsxRuntime.jsx(FocusTrappingUnderlay, __spreadValues({}, props));
285
+ }
286
+ function FocusTrappingUnderlay(props) {
287
+ const { state, children, isDismissable = true } = props;
288
+ const { hasOpenThirdPartyDescendant } = useModalTriggerContext();
289
+ const shouldTrap = !hasOpenThirdPartyDescendant;
221
290
  const ref = React.useRef(null);
222
- const { modalProps, underlayProps } = useModalOverlay.$8ac8429251c45e4b$export$dbc0f175b25fb0fb(__spreadProps(__spreadValues({}, overlayProps), {
291
+ const { overlayProps, underlayProps } = ariaHideOutside.$a11501f3d1d39e6c$export$ea8f71083e90600f({
292
+ isOpen: state.isOpen,
293
+ onClose: state.close,
223
294
  isDismissable,
224
- isKeyboardDismissDisabled: !isDismissable
225
- }), state, ref);
226
- const { hasOpenNestedModal } = useModalTriggerContext();
227
- const className = utilities_css.classNames(styles.underlayBg, hasOpenNestedModal && styles.underlayBgHidden);
295
+ isKeyboardDismissDisabled: !isDismissable,
296
+ shouldCloseOnInteractOutside
297
+ }, ref);
298
+ ariaHideOutside.$49c51c25361d4cd2$export$ee0f7cc6afcd1c18({
299
+ isDisabled: !state.isOpen
300
+ });
301
+ React.useEffect(() => {
302
+ if (state.isOpen && shouldTrap && ref.current) {
303
+ return ariaHideOutside.$5e3802645cc19319$export$1c3ebcada18427bf([ref.current], {
304
+ shouldUseInert: true
305
+ });
306
+ }
307
+ }, [state.isOpen, shouldTrap]);
308
+ return jsxRuntime.jsx(ModalUnderlayContent, {
309
+ modalProps: overlayProps,
310
+ underlayProps,
311
+ modalRef: ref,
312
+ shouldContainFocus: shouldTrap,
313
+ children
314
+ });
315
+ }
316
+ function ThirdPartyOverlayUnderlay(props) {
317
+ const { state, children, isDismissable = true } = props;
318
+ const ref = React.useRef(null);
319
+ const { overlayProps, underlayProps } = ariaHideOutside.$a11501f3d1d39e6c$export$ea8f71083e90600f({
320
+ isOpen: state.isOpen,
321
+ onClose: state.close,
322
+ isDismissable,
323
+ isKeyboardDismissDisabled: !isDismissable,
324
+ shouldCloseOnInteractOutside
325
+ }, ref);
326
+ ariaHideOutside.$49c51c25361d4cd2$export$ee0f7cc6afcd1c18({
327
+ isDisabled: !state.isOpen
328
+ });
329
+ return jsxRuntime.jsx(ModalUnderlayContent, {
330
+ modalProps: overlayProps,
331
+ underlayProps,
332
+ modalRef: ref,
333
+ keepVisibleUnderModal: true,
334
+ children
335
+ });
336
+ }
337
+ function ModalUnderlayContent({ modalProps, underlayProps, modalRef, children, keepVisibleUnderModal = false, shouldContainFocus = false }) {
338
+ const { hasReplacingChild } = useModalTriggerContext();
339
+ const className = utilities_css.classNames(styles.underlayBg, hasReplacingChild && styles.underlayBgHidden);
228
340
  return jsxRuntime.jsx(Overlay.$337b884510726a0d$export$c6fdb837b070b4ff, {
229
- children: jsxRuntime.jsx("div", __spreadProps(__spreadValues({
230
- className
231
- }, underlayProps), {
232
- children: jsxRuntime.jsxs("div", __spreadProps(__spreadValues({}, modalProps), {
233
- ref,
234
- className: styles.underlayBox,
235
- children: [jsxRuntime.jsx("div", {
236
- className: styles.underlayEdge
237
- }), children, jsxRuntime.jsx("div", {
238
- className: styles.underlayEdge
239
- })]
341
+ disableFocusManagement: true,
342
+ children: jsxRuntime.jsx(FocusScope.$9bf71ea28793e738$export$20e40289641fbbb6, {
343
+ contain: shouldContainFocus,
344
+ restoreFocus: true,
345
+ children: jsxRuntime.jsx("div", __spreadProps(__spreadValues({
346
+ className
347
+ }, underlayProps), {
348
+ children: jsxRuntime.jsxs("div", __spreadProps(__spreadValues(__spreadProps(__spreadValues({}, modalProps), {
349
+ ref: modalRef,
350
+ className: styles.underlayBox
351
+ }), {
352
+ [MODAL_BOX_ATTRIBUTE]: "true"
353
+ }), {
354
+ "data-live-announcer": keepVisibleUnderModal ? "true" : void 0,
355
+ children: [jsxRuntime.jsx("div", {
356
+ className: styles.underlayEdge
357
+ }), children, jsxRuntime.jsx("div", {
358
+ className: styles.underlayEdge
359
+ })]
360
+ }))
240
361
  }))
241
- }))
362
+ })
242
363
  });
243
364
  }
244
365
  function ModalTrigger(props) {
245
- const _a = props, { children, isDismissable = true } = _a, inTriggerProps = __objRest(_a, ["children", "isDismissable"]);
366
+ const _a = props, { children, isDismissable = true, allowsThirdPartyOverlays = false, childNestingBehavior, selfNestingBehavior } = _a, inTriggerProps = __objRest(_a, ["children", "isDismissable", "allowsThirdPartyOverlays", "childNestingBehavior", "selfNestingBehavior"]);
246
367
  const state = Overlay.$fc909762b330b746$export$61c6a8c84e605fb6(inTriggerProps);
247
368
  const { triggerProps, overlayProps } = useOverlayTrigger.$628037886ba31236$export$f9d5c8beee7d008d({
248
369
  type: "dialog"
@@ -254,6 +375,9 @@ function ModalTrigger(props) {
254
375
  return jsxRuntime.jsxs(ModalTriggerProvider, {
255
376
  state,
256
377
  isDismissable,
378
+ childNestingBehavior,
379
+ selfNestingBehavior,
380
+ allowsThirdPartyOverlays,
257
381
  children: [React.cloneElement(trigger, triggerProps), state.isOpen && jsxRuntime.jsx(ModalUnderlay, __spreadProps(__spreadValues({}, props), {
258
382
  state,
259
383
  children: React.cloneElement(typeof modal === "function" ? modal(state.close) : modal, overlayProps)
@@ -261,7 +385,7 @@ function ModalTrigger(props) {
261
385
  });
262
386
  }
263
387
  function ModalContainer(props) {
264
- const { children, isDismissable = true, onDismiss = () => {
388
+ const { children, isDismissable = true, allowsThirdPartyOverlays = false, childNestingBehavior, selfNestingBehavior, onDismiss = () => {
265
389
  } } = props;
266
390
  const childArray = React.Children.toArray(children);
267
391
  if (childArray.length > 1) {
@@ -291,9 +415,13 @@ function ModalContainer(props) {
291
415
  return jsxRuntime.jsx(ModalTriggerProvider, {
292
416
  state,
293
417
  isDismissable,
418
+ childNestingBehavior,
419
+ selfNestingBehavior,
420
+ allowsThirdPartyOverlays,
294
421
  children: state.isOpen && jsxRuntime.jsx(ModalUnderlay, {
295
422
  state,
296
423
  isDismissable,
424
+ allowsThirdPartyOverlays,
297
425
  children: lastChild ? React.cloneElement(lastChild, overlayProps) : null
298
426
  })
299
427
  });
package/Modal/index.mjs CHANGED
@@ -30,17 +30,19 @@ var __objRest = (source, exclude) => {
30
30
  return target;
31
31
  };
32
32
  import { jsx, jsxs } from "react/jsx-runtime";
33
- import React__default, { createContext, useState, useMemo, useContext, cloneElement, useRef } from "react";
33
+ import React__default, { createContext, useMemo, useContext, useState, useCallback, useRef, useEffect, cloneElement } from "react";
34
34
  import { classNames, variationName } from "../utilities/css.mjs";
35
35
  import { useScrollbar } from "../utilities/useScrollbar.mjs";
36
+ import { a as $f0a04ccd8dbdd83b$export$e5c5a5f917a5871c } from "../__chunks__/mergeProps-COnPag8r.mjs";
36
37
  import { B as Button } from "../__chunks__/Button-JL1YQxJ5.mjs";
37
38
  import { C as Close } from "../__chunks__/Close-Cq6Q6MN6.mjs";
38
39
  import { H as HorizontalStack } from "../__chunks__/HorizontalStack-CJavrBHm.mjs";
39
40
  import { I as Icon } from "../__chunks__/Icon-BIpaQJ_I.mjs";
40
41
  import { T as Text } from "../__chunks__/Text-D6oT5WJQ.mjs";
41
42
  import { V as VerticalStack } from "../__chunks__/VerticalStack-DoMqBfYD.mjs";
42
- import { $ as $8ac8429251c45e4b$export$dbc0f175b25fb0fb } from "../__chunks__/useModalOverlay-BmYXH7rq.mjs";
43
+ import { $ as $a11501f3d1d39e6c$export$ea8f71083e90600f, a as $49c51c25361d4cd2$export$ee0f7cc6afcd1c18, b as $5e3802645cc19319$export$1c3ebcada18427bf } from "../__chunks__/ariaHideOutside-BYq9cUwo.mjs";
43
44
  import { d as $337b884510726a0d$export$c6fdb837b070b4ff, a as $fc909762b330b746$export$61c6a8c84e605fb6 } from "../__chunks__/Overlay-Dkjxuk6V.mjs";
45
+ import { c as $9bf71ea28793e738$export$20e40289641fbbb6 } from "../__chunks__/FocusScope-B_FktBGw.mjs";
44
46
  import { $ as $628037886ba31236$export$f9d5c8beee7d008d } from "../__chunks__/useOverlayTrigger-CIVwezu3.mjs";
45
47
  import { u as useIntersectionDetection } from "../__chunks__/useIntersectionDetection-BvUmtDgl.mjs";
46
48
  import { $ as $40df3f8667284809$export$d55e7ee900f34e93 } from "../__chunks__/useDialog-nKMoC_Cs.mjs";
@@ -64,16 +66,75 @@ const useModalTrigger = () => {
64
66
  const modalTriggerContext = useModalTriggerContext();
65
67
  return modalTriggerContext.state;
66
68
  };
67
- function ModalTriggerProvider({ state, isDismissable, children }) {
68
- const [hasOpenNestedModal, setHasOpenNestedModal] = useState(false);
69
- const context = useMemo(() => {
70
- return {
71
- hasOpenNestedModal,
72
- setHasOpenNestedModal,
73
- state,
74
- isDismissable
69
+ function useModalNesting({ childNestingBehavior, selfNestingBehavior, allowsThirdPartyOverlays, isOpen }) {
70
+ var _a, _b;
71
+ const parentContext = useContext(ModalTriggerContext);
72
+ const [replacingChildCount, setReplacingChildCount] = useState(0);
73
+ const [thirdPartyDescendantCount, setThirdPartyDescendantCount] = useState(0);
74
+ const childNesting = (_a = childNestingBehavior != null ? childNestingBehavior : parentContext == null ? void 0 : parentContext.childNestingBehavior) != null ? _a : "stack";
75
+ const selfNesting = (_b = selfNestingBehavior != null ? selfNestingBehavior : parentContext == null ? void 0 : parentContext.childNestingBehavior) != null ? _b : "stack";
76
+ const registerNested = useCallback((replaces) => {
77
+ if (!replaces) {
78
+ return () => {
79
+ };
80
+ }
81
+ setReplacingChildCount((count) => count + 1);
82
+ let released = false;
83
+ return () => {
84
+ if (released) {
85
+ return;
86
+ }
87
+ released = true;
88
+ setReplacingChildCount((count) => Math.max(0, count - 1));
89
+ };
90
+ }, []);
91
+ const parentRegisterNested = parentContext == null ? void 0 : parentContext.registerNested;
92
+ const selfReplacesParent = selfNesting === "replace";
93
+ $f0a04ccd8dbdd83b$export$e5c5a5f917a5871c(() => {
94
+ if (!parentRegisterNested || !isOpen) {
95
+ return;
96
+ }
97
+ return parentRegisterNested(selfReplacesParent);
98
+ }, [parentRegisterNested, isOpen, selfReplacesParent]);
99
+ const registerThirdPartyOverlay = useCallback(() => {
100
+ setThirdPartyDescendantCount((count) => count + 1);
101
+ let released = false;
102
+ return () => {
103
+ if (released) {
104
+ return;
105
+ }
106
+ released = true;
107
+ setThirdPartyDescendantCount((count) => Math.max(0, count - 1));
75
108
  };
76
- }, [hasOpenNestedModal, state, isDismissable]);
109
+ }, []);
110
+ const parentRegisterThirdPartyOverlay = parentContext == null ? void 0 : parentContext.registerThirdPartyOverlay;
111
+ const hasThirdPartyInSubtree = allowsThirdPartyOverlays || thirdPartyDescendantCount > 0;
112
+ $f0a04ccd8dbdd83b$export$e5c5a5f917a5871c(() => {
113
+ if (!parentRegisterThirdPartyOverlay || !isOpen || !hasThirdPartyInSubtree) {
114
+ return;
115
+ }
116
+ return parentRegisterThirdPartyOverlay();
117
+ }, [parentRegisterThirdPartyOverlay, isOpen, hasThirdPartyInSubtree]);
118
+ return useMemo(() => ({
119
+ hasReplacingChild: replacingChildCount > 0,
120
+ childNestingBehavior: childNesting,
121
+ selfNestingBehavior: selfNesting,
122
+ registerNested,
123
+ hasOpenThirdPartyDescendant: thirdPartyDescendantCount > 0,
124
+ registerThirdPartyOverlay
125
+ }), [replacingChildCount, childNesting, selfNesting, registerNested, thirdPartyDescendantCount, registerThirdPartyOverlay]);
126
+ }
127
+ function ModalTriggerProvider({ state, isDismissable, childNestingBehavior, selfNestingBehavior, allowsThirdPartyOverlays = false, children }) {
128
+ const nesting = useModalNesting({
129
+ childNestingBehavior,
130
+ selfNestingBehavior,
131
+ allowsThirdPartyOverlays,
132
+ isOpen: state.isOpen
133
+ });
134
+ const context = useMemo(() => __spreadProps(__spreadValues({}, nesting), {
135
+ state,
136
+ isDismissable
137
+ }), [nesting, state, isDismissable]);
77
138
  return jsx(ModalTriggerContext.Provider, {
78
139
  value: context,
79
140
  children
@@ -213,34 +274,94 @@ function ModalHeader(props) {
213
274
  })
214
275
  });
215
276
  }
277
+ const MODAL_BOX_ATTRIBUTE = "data-easy-ui-modal-box";
278
+ function shouldCloseOnInteractOutside(target) {
279
+ return !target.closest(`[${MODAL_BOX_ATTRIBUTE}]`);
280
+ }
216
281
  function ModalUnderlay(props) {
217
- const _a = props, { state, children } = _a, overlayProps = __objRest(_a, ["state", "children"]);
218
- const { isDismissable = true } = overlayProps;
219
- const ref = React__default.useRef(null);
220
- const { modalProps, underlayProps } = $8ac8429251c45e4b$export$dbc0f175b25fb0fb(__spreadProps(__spreadValues({}, overlayProps), {
282
+ return props.allowsThirdPartyOverlays ? jsx(ThirdPartyOverlayUnderlay, __spreadValues({}, props)) : jsx(FocusTrappingUnderlay, __spreadValues({}, props));
283
+ }
284
+ function FocusTrappingUnderlay(props) {
285
+ const { state, children, isDismissable = true } = props;
286
+ const { hasOpenThirdPartyDescendant } = useModalTriggerContext();
287
+ const shouldTrap = !hasOpenThirdPartyDescendant;
288
+ const ref = useRef(null);
289
+ const { overlayProps, underlayProps } = $a11501f3d1d39e6c$export$ea8f71083e90600f({
290
+ isOpen: state.isOpen,
291
+ onClose: state.close,
221
292
  isDismissable,
222
- isKeyboardDismissDisabled: !isDismissable
223
- }), state, ref);
224
- const { hasOpenNestedModal } = useModalTriggerContext();
225
- const className = classNames(styles.underlayBg, hasOpenNestedModal && styles.underlayBgHidden);
293
+ isKeyboardDismissDisabled: !isDismissable,
294
+ shouldCloseOnInteractOutside
295
+ }, ref);
296
+ $49c51c25361d4cd2$export$ee0f7cc6afcd1c18({
297
+ isDisabled: !state.isOpen
298
+ });
299
+ useEffect(() => {
300
+ if (state.isOpen && shouldTrap && ref.current) {
301
+ return $5e3802645cc19319$export$1c3ebcada18427bf([ref.current], {
302
+ shouldUseInert: true
303
+ });
304
+ }
305
+ }, [state.isOpen, shouldTrap]);
306
+ return jsx(ModalUnderlayContent, {
307
+ modalProps: overlayProps,
308
+ underlayProps,
309
+ modalRef: ref,
310
+ shouldContainFocus: shouldTrap,
311
+ children
312
+ });
313
+ }
314
+ function ThirdPartyOverlayUnderlay(props) {
315
+ const { state, children, isDismissable = true } = props;
316
+ const ref = useRef(null);
317
+ const { overlayProps, underlayProps } = $a11501f3d1d39e6c$export$ea8f71083e90600f({
318
+ isOpen: state.isOpen,
319
+ onClose: state.close,
320
+ isDismissable,
321
+ isKeyboardDismissDisabled: !isDismissable,
322
+ shouldCloseOnInteractOutside
323
+ }, ref);
324
+ $49c51c25361d4cd2$export$ee0f7cc6afcd1c18({
325
+ isDisabled: !state.isOpen
326
+ });
327
+ return jsx(ModalUnderlayContent, {
328
+ modalProps: overlayProps,
329
+ underlayProps,
330
+ modalRef: ref,
331
+ keepVisibleUnderModal: true,
332
+ children
333
+ });
334
+ }
335
+ function ModalUnderlayContent({ modalProps, underlayProps, modalRef, children, keepVisibleUnderModal = false, shouldContainFocus = false }) {
336
+ const { hasReplacingChild } = useModalTriggerContext();
337
+ const className = classNames(styles.underlayBg, hasReplacingChild && styles.underlayBgHidden);
226
338
  return jsx($337b884510726a0d$export$c6fdb837b070b4ff, {
227
- children: jsx("div", __spreadProps(__spreadValues({
228
- className
229
- }, underlayProps), {
230
- children: jsxs("div", __spreadProps(__spreadValues({}, modalProps), {
231
- ref,
232
- className: styles.underlayBox,
233
- children: [jsx("div", {
234
- className: styles.underlayEdge
235
- }), children, jsx("div", {
236
- className: styles.underlayEdge
237
- })]
339
+ disableFocusManagement: true,
340
+ children: jsx($9bf71ea28793e738$export$20e40289641fbbb6, {
341
+ contain: shouldContainFocus,
342
+ restoreFocus: true,
343
+ children: jsx("div", __spreadProps(__spreadValues({
344
+ className
345
+ }, underlayProps), {
346
+ children: jsxs("div", __spreadProps(__spreadValues(__spreadProps(__spreadValues({}, modalProps), {
347
+ ref: modalRef,
348
+ className: styles.underlayBox
349
+ }), {
350
+ [MODAL_BOX_ATTRIBUTE]: "true"
351
+ }), {
352
+ "data-live-announcer": keepVisibleUnderModal ? "true" : void 0,
353
+ children: [jsx("div", {
354
+ className: styles.underlayEdge
355
+ }), children, jsx("div", {
356
+ className: styles.underlayEdge
357
+ })]
358
+ }))
238
359
  }))
239
- }))
360
+ })
240
361
  });
241
362
  }
242
363
  function ModalTrigger(props) {
243
- const _a = props, { children, isDismissable = true } = _a, inTriggerProps = __objRest(_a, ["children", "isDismissable"]);
364
+ const _a = props, { children, isDismissable = true, allowsThirdPartyOverlays = false, childNestingBehavior, selfNestingBehavior } = _a, inTriggerProps = __objRest(_a, ["children", "isDismissable", "allowsThirdPartyOverlays", "childNestingBehavior", "selfNestingBehavior"]);
244
365
  const state = $fc909762b330b746$export$61c6a8c84e605fb6(inTriggerProps);
245
366
  const { triggerProps, overlayProps } = $628037886ba31236$export$f9d5c8beee7d008d({
246
367
  type: "dialog"
@@ -252,6 +373,9 @@ function ModalTrigger(props) {
252
373
  return jsxs(ModalTriggerProvider, {
253
374
  state,
254
375
  isDismissable,
376
+ childNestingBehavior,
377
+ selfNestingBehavior,
378
+ allowsThirdPartyOverlays,
255
379
  children: [cloneElement(trigger, triggerProps), state.isOpen && jsx(ModalUnderlay, __spreadProps(__spreadValues({}, props), {
256
380
  state,
257
381
  children: cloneElement(typeof modal === "function" ? modal(state.close) : modal, overlayProps)
@@ -259,7 +383,7 @@ function ModalTrigger(props) {
259
383
  });
260
384
  }
261
385
  function ModalContainer(props) {
262
- const { children, isDismissable = true, onDismiss = () => {
386
+ const { children, isDismissable = true, allowsThirdPartyOverlays = false, childNestingBehavior, selfNestingBehavior, onDismiss = () => {
263
387
  } } = props;
264
388
  const childArray = React__default.Children.toArray(children);
265
389
  if (childArray.length > 1) {
@@ -289,9 +413,13 @@ function ModalContainer(props) {
289
413
  return jsx(ModalTriggerProvider, {
290
414
  state,
291
415
  isDismissable,
416
+ childNestingBehavior,
417
+ selfNestingBehavior,
418
+ allowsThirdPartyOverlays,
292
419
  children: state.isOpen && jsx(ModalUnderlay, {
293
420
  state,
294
421
  isDismissable,
422
+ allowsThirdPartyOverlays,
295
423
  children: lastChild ? cloneElement(lastChild, overlayProps) : null
296
424
  })
297
425
  });
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@easypost/easy-ui",
3
- "version": "1.0.0-alpha.120",
3
+ "version": "1.0.0-alpha.122",
4
4
  "sideEffects": [
5
5
  "**/*.css"
6
6
  ],
7
7
  "dependencies": {
8
+ "@react-aria/overlays": "^3.31.0",
8
9
  "@easypost/easy-ui-icons": "1.0.0-alpha.55",
9
10
  "@easypost/easy-ui-tokens": "1.0.0-alpha.17",
10
11
  "@react-aria/toast": "^3.0.9",