beez-ui 2.6.0 → 2.6.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.
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Keeps toasts usable while a modal overlay is open. Toasts sit above every overlay, but a modal `Dialog` or `Sheet`
3
+ * treats a press on them as a press outside and closes, so a toast's action or close button would dismiss the form
4
+ * that raised it instead of running.
5
+ */
6
+ import { TOAST_LAYER_SELECTOR } from "../constants/toast.js";
7
+ /**
8
+ * Tells whether an event target belongs to the toast layer.
9
+ * @param target - Target of the outside interaction.
10
+ * @returns `true` when the target is a toast or one of its controls.
11
+ */
12
+ export function isToastLayerTarget(target) {
13
+ return target instanceof Element && target.closest(TOAST_LAYER_SELECTOR) !== null;
14
+ }
15
+ /**
16
+ * Lets keyboard focus enter the toast layer while a modal traps focus. Radix's focus trap listens for `focusin` and
17
+ * `focusout` on the document and pulls focus back into the modal whenever it lands outside, so Sonner's hotkey could
18
+ * never move focus to a toast's controls. Focus changes that land on a toast stop at the body, after React has run
19
+ * its own handlers and before the document-level trap sees them; every other focus change still reaches the trap.
20
+ * Because the trap never sees focus leave, it cannot pull it back either: when the focused toast control is removed
21
+ * (an action or close button dismisses its toast), focus returns to the element it came from instead of the body.
22
+ * @param ownerDocument - Document whose body hosts the app and the modal portals.
23
+ * @returns Removes the listeners.
24
+ */
25
+ export function letFocusReachToastLayer(ownerDocument) {
26
+ const { body } = ownerDocument;
27
+ let focusOrigin = null;
28
+ let focusedToastElement = null;
29
+ const restoreFocusAfterRemoval = new MutationObserver(() => {
30
+ if (focusedToastElement?.isConnected !== false) return;
31
+ const activeElement = ownerDocument.activeElement;
32
+ if (activeElement === null || activeElement === body) focusOrigin?.focus({ preventScroll: true });
33
+ stopTrackingToastFocus();
34
+ });
35
+ const stopTrackingToastFocus = () => {
36
+ restoreFocusAfterRemoval.disconnect();
37
+ focusOrigin = null;
38
+ focusedToastElement = null;
39
+ };
40
+ const stopFocusIntoToast = (event) => {
41
+ const focusedElement = event.type === "focusin" ? event.target : event.relatedTarget;
42
+ if (!isToastLayerTarget(focusedElement)) {
43
+ if (event.type === "focusin") stopTrackingToastFocus();
44
+ return;
45
+ }
46
+ event.stopPropagation();
47
+ if (event.type !== "focusin") return;
48
+ if (focusedToastElement === null) {
49
+ focusOrigin = event.relatedTarget instanceof HTMLElement ? event.relatedTarget : null;
50
+ restoreFocusAfterRemoval.observe(body, {
51
+ childList: true,
52
+ subtree: true
53
+ });
54
+ }
55
+ focusedToastElement = focusedElement;
56
+ };
57
+ body.addEventListener("focusin", stopFocusIntoToast);
58
+ body.addEventListener("focusout", stopFocusIntoToast);
59
+ return () => {
60
+ stopTrackingToastFocus();
61
+ body.removeEventListener("focusin", stopFocusIntoToast);
62
+ body.removeEventListener("focusout", stopFocusIntoToast);
63
+ };
64
+ }
65
+ /**
66
+ * Wraps a modal's `onInteractOutside` so presses on toasts never dismiss it. The consumer's handler still runs first
67
+ * and can keep preventing dismissal for its own reasons.
68
+ * @param handler - The consumer's `onInteractOutside`, if any.
69
+ * @returns The handler to give the Radix content.
70
+ */
71
+ export function keepOpenOnToastInteraction(handler) {
72
+ return (event) => {
73
+ handler?.(event);
74
+ if (isToastLayerTarget(event.target)) event.preventDefault();
75
+ };
76
+ }
package/dist/sonner.d.ts CHANGED
@@ -1,2 +1,7 @@
1
- /** Exposes the shared notification implementation and its public types. */
1
+ /**
2
+ * Exposes the shared notification implementation and its public types. `Toaster` is beez-ui's own, which keeps
3
+ * Sonner's look and props but lets toasts work while a modal is open; this local export takes precedence over the
4
+ * one `export *` would bring from Sonner.
5
+ */
2
6
  export * from "sonner";
7
+ export { Toaster } from "./components/toaster.js";
package/dist/sonner.js CHANGED
@@ -1,3 +1,8 @@
1
1
  "use client";
2
- /** Exposes the shared notification implementation and its public types. */
2
+ /**
3
+ * Exposes the shared notification implementation and its public types. `Toaster` is beez-ui's own, which keeps
4
+ * Sonner's look and props but lets toasts work while a modal is open; this local export takes precedence over the
5
+ * one `export *` would bring from Sonner.
6
+ */
3
7
  export * from "sonner";
8
+ export { Toaster } from "./components/toaster.js";