@funstack/ui 0.1.0-alpha.1 → 0.1.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/accordion/accordion-context.mjs.map +1 -1
- package/dist/accordion/accordion-item.mjs +18 -4
- package/dist/accordion/accordion-item.mjs.map +1 -1
- package/dist/accordion/accordion-panel.mjs +2 -3
- package/dist/accordion/accordion-panel.mjs.map +1 -1
- package/dist/accordion/accordion-root.d.mts.map +1 -1
- package/dist/accordion/accordion-root.mjs +32 -14
- package/dist/accordion/accordion-root.mjs.map +1 -1
- package/dist/accordion/accordion-trigger.mjs +4 -5
- package/dist/accordion/accordion-trigger.mjs.map +1 -1
- package/dist/checkbox/checkbox.d.mts +5 -3
- package/dist/checkbox/checkbox.d.mts.map +1 -1
- package/dist/checkbox/checkbox.mjs +25 -9
- package/dist/checkbox/checkbox.mjs.map +1 -1
- package/dist/checkbox-group/checkbox-group-context.mjs.map +1 -1
- package/dist/checkbox-group/checkbox-group-item.d.mts +2 -1
- package/dist/checkbox-group/checkbox-group-item.d.mts.map +1 -1
- package/dist/checkbox-group/checkbox-group-item.mjs +3 -1
- package/dist/checkbox-group/checkbox-group-item.mjs.map +1 -1
- package/dist/checkbox-group/checkbox-group-root.d.mts +5 -2
- package/dist/checkbox-group/checkbox-group-root.d.mts.map +1 -1
- package/dist/checkbox-group/checkbox-group-root.mjs +24 -5
- package/dist/checkbox-group/checkbox-group-root.mjs.map +1 -1
- package/dist/combobox/combobox-clear.d.mts +34 -0
- package/dist/combobox/combobox-clear.d.mts.map +1 -0
- package/dist/combobox/combobox-clear.mjs +58 -0
- package/dist/combobox/combobox-clear.mjs.map +1 -0
- package/dist/combobox/combobox-context.mjs +23 -0
- package/dist/combobox/combobox-context.mjs.map +1 -0
- package/dist/combobox/combobox-group.d.mts +72 -0
- package/dist/combobox/combobox-group.d.mts.map +1 -0
- package/dist/combobox/combobox-group.mjs +104 -0
- package/dist/combobox/combobox-group.mjs.map +1 -0
- package/dist/combobox/combobox-input.d.mts +42 -0
- package/dist/combobox/combobox-input.d.mts.map +1 -0
- package/dist/combobox/combobox-input.mjs +102 -0
- package/dist/combobox/combobox-input.mjs.map +1 -0
- package/dist/combobox/combobox-label.d.mts +22 -0
- package/dist/combobox/combobox-label.d.mts.map +1 -0
- package/dist/combobox/combobox-label.mjs +32 -0
- package/dist/combobox/combobox-label.mjs.map +1 -0
- package/dist/combobox/combobox-list.d.mts +37 -0
- package/dist/combobox/combobox-list.d.mts.map +1 -0
- package/dist/combobox/combobox-list.mjs +70 -0
- package/dist/combobox/combobox-list.mjs.map +1 -0
- package/dist/combobox/combobox-option.d.mts +45 -0
- package/dist/combobox/combobox-option.d.mts.map +1 -0
- package/dist/combobox/combobox-option.mjs +88 -0
- package/dist/combobox/combobox-option.mjs.map +1 -0
- package/dist/combobox/combobox-popup.d.mts +49 -0
- package/dist/combobox/combobox-popup.d.mts.map +1 -0
- package/dist/combobox/combobox-popup.mjs +86 -0
- package/dist/combobox/combobox-popup.mjs.map +1 -0
- package/dist/combobox/combobox-root.d.mts +135 -0
- package/dist/combobox/combobox-root.d.mts.map +1 -0
- package/dist/combobox/combobox-root.mjs +523 -0
- package/dist/combobox/combobox-root.mjs.map +1 -0
- package/dist/combobox/combobox-trigger.d.mts +36 -0
- package/dist/combobox/combobox-trigger.d.mts.map +1 -0
- package/dist/combobox/combobox-trigger.mjs +92 -0
- package/dist/combobox/combobox-trigger.mjs.map +1 -0
- package/dist/combobox/index.d.mts +36 -0
- package/dist/combobox/index.d.mts.map +1 -0
- package/dist/combobox/index.mjs +36 -0
- package/dist/combobox/index.mjs.map +1 -0
- package/dist/dialog/dialog-close.d.mts +17 -10
- package/dist/dialog/dialog-close.d.mts.map +1 -1
- package/dist/dialog/dialog-close.mjs +31 -20
- package/dist/dialog/dialog-close.mjs.map +1 -1
- package/dist/dialog/dialog-context.mjs.map +1 -1
- package/dist/dialog/dialog-form.d.mts +12 -5
- package/dist/dialog/dialog-form.d.mts.map +1 -1
- package/dist/dialog/dialog-form.mjs +16 -28
- package/dist/dialog/dialog-form.mjs.map +1 -1
- package/dist/dialog/dialog-popup.d.mts +10 -4
- package/dist/dialog/dialog-popup.d.mts.map +1 -1
- package/dist/dialog/dialog-popup.mjs +106 -41
- package/dist/dialog/dialog-popup.mjs.map +1 -1
- package/dist/dialog/dialog-root.d.mts +14 -8
- package/dist/dialog/dialog-root.d.mts.map +1 -1
- package/dist/dialog/dialog-root.mjs +66 -11
- package/dist/dialog/dialog-root.mjs.map +1 -1
- package/dist/dialog/dialog-trigger.d.mts +3 -2
- package/dist/dialog/dialog-trigger.d.mts.map +1 -1
- package/dist/dialog/dialog-trigger.mjs.map +1 -1
- package/dist/dialog/index.d.mts +4 -2
- package/dist/dialog/index.d.mts.map +1 -1
- package/dist/dialog/index.mjs +4 -2
- package/dist/dialog/index.mjs.map +1 -1
- package/dist/field/field-control.d.mts +33 -0
- package/dist/field/field-control.d.mts.map +1 -0
- package/dist/field/field-control.mjs +68 -0
- package/dist/field/field-control.mjs.map +1 -0
- package/dist/field/field-description.d.mts +24 -0
- package/dist/field/field-description.d.mts.map +1 -0
- package/dist/field/field-description.mjs +34 -0
- package/dist/field/field-description.mjs.map +1 -0
- package/dist/field/field-error.d.mts +30 -0
- package/dist/field/field-error.d.mts.map +1 -0
- package/dist/field/field-error.mjs +42 -0
- package/dist/field/field-error.mjs.map +1 -0
- package/dist/field/field-label.d.mts +25 -0
- package/dist/field/field-label.d.mts.map +1 -0
- package/dist/field/field-label.mjs +46 -0
- package/dist/field/field-label.mjs.map +1 -0
- package/dist/field/field-root.d.mts +53 -0
- package/dist/field/field-root.d.mts.map +1 -0
- package/dist/field/field-root.mjs +317 -0
- package/dist/field/field-root.mjs.map +1 -0
- package/dist/field/index.d.mts +18 -0
- package/dist/field/index.d.mts.map +1 -0
- package/dist/field/index.mjs +17 -0
- package/dist/field/index.mjs.map +1 -0
- package/dist/form/form-root.d.mts +58 -0
- package/dist/form/form-root.d.mts.map +1 -0
- package/dist/form/form-root.mjs +146 -0
- package/dist/form/form-root.mjs.map +1 -0
- package/dist/form/form-submit.d.mts +31 -0
- package/dist/form/form-submit.d.mts.map +1 -0
- package/dist/form/form-submit.mjs +52 -0
- package/dist/form/form-submit.mjs.map +1 -0
- package/dist/form/index.d.mts +11 -0
- package/dist/form/index.d.mts.map +1 -0
- package/dist/form/index.mjs +11 -0
- package/dist/form/index.mjs.map +1 -0
- package/dist/index.d.mts +46 -2
- package/dist/index.mjs +8 -1
- package/dist/internal/content-focus-probe.mjs +42 -0
- package/dist/internal/content-focus-probe.mjs.map +1 -0
- package/dist/internal/field-context.d.mts +19 -0
- package/dist/internal/field-context.d.mts.map +1 -0
- package/dist/internal/field-context.mjs +14 -0
- package/dist/internal/field-context.mjs.map +1 -0
- package/dist/internal/focus-first-invalid.mjs +67 -0
- package/dist/internal/focus-first-invalid.mjs.map +1 -0
- package/dist/internal/form-status-watcher.mjs +31 -0
- package/dist/internal/form-status-watcher.mjs.map +1 -0
- package/dist/internal/modal-registry.mjs +39 -0
- package/dist/internal/modal-registry.mjs.map +1 -0
- package/dist/internal/platform-attributes.d.mts +12 -1
- package/dist/internal/platform-attributes.d.mts.map +1 -1
- package/dist/internal/types.d.mts +11 -1
- package/dist/internal/types.d.mts.map +1 -1
- package/dist/internal/use-controllable-action.mjs +35 -13
- package/dist/internal/use-controllable-action.mjs.map +1 -1
- package/dist/internal/use-field-control.mjs +107 -0
- package/dist/internal/use-field-control.mjs.map +1 -0
- package/dist/internal/use-form-owner-event.mjs +44 -0
- package/dist/internal/use-form-owner-event.mjs.map +1 -0
- package/dist/internal/use-form-reset.mjs +12 -31
- package/dist/internal/use-form-reset.mjs.map +1 -1
- package/dist/internal/use-hydrated-mount.mjs +21 -0
- package/dist/internal/use-hydrated-mount.mjs.map +1 -0
- package/dist/internal/use-latest.mjs +32 -0
- package/dist/internal/use-latest.mjs.map +1 -0
- package/dist/internal/use-modal-registration.mjs +47 -0
- package/dist/internal/use-modal-registration.mjs.map +1 -0
- package/dist/internal/use-native-group-reconciliation.mjs +6 -5
- package/dist/internal/use-native-group-reconciliation.mjs.map +1 -1
- package/dist/internal/use-native-select-value.mjs +184 -0
- package/dist/internal/use-native-select-value.mjs.map +1 -0
- package/dist/internal/use-popover-popup.mjs +8 -22
- package/dist/internal/use-popover-popup.mjs.map +1 -1
- package/dist/internal/use-popover-root.mjs +58 -23
- package/dist/internal/use-popover-root.mjs.map +1 -1
- package/dist/internal/validity.d.mts +24 -0
- package/dist/internal/validity.d.mts.map +1 -0
- package/dist/internal/validity.mjs +108 -0
- package/dist/internal/validity.mjs.map +1 -0
- package/dist/menu/menu-group.d.mts.map +1 -1
- package/dist/menu/menu-group.mjs +3 -2
- package/dist/menu/menu-group.mjs.map +1 -1
- package/dist/menu/menu-radio-group.mjs +9 -4
- package/dist/menu/menu-radio-group.mjs.map +1 -1
- package/dist/menu/menu-root.mjs.map +1 -1
- package/dist/radio-group/radio-group-root.d.mts +4 -2
- package/dist/radio-group/radio-group-root.d.mts.map +1 -1
- package/dist/radio-group/radio-group-root.mjs +34 -8
- package/dist/radio-group/radio-group-root.mjs.map +1 -1
- package/dist/select/index.d.mts +30 -0
- package/dist/select/index.d.mts.map +1 -0
- package/dist/select/index.mjs +30 -0
- package/dist/select/index.mjs.map +1 -0
- package/dist/select/select-context.mjs +13 -0
- package/dist/select/select-context.mjs.map +1 -0
- package/dist/select/select-dom.mjs +44 -0
- package/dist/select/select-dom.mjs.map +1 -0
- package/dist/select/select-group-label.d.mts +22 -0
- package/dist/select/select-group-label.d.mts.map +1 -0
- package/dist/select/select-group-label.mjs +39 -0
- package/dist/select/select-group-label.mjs.map +1 -0
- package/dist/select/select-group.d.mts +28 -0
- package/dist/select/select-group.d.mts.map +1 -0
- package/dist/select/select-group.mjs +47 -0
- package/dist/select/select-group.mjs.map +1 -0
- package/dist/select/select-option.d.mts +42 -0
- package/dist/select/select-option.d.mts.map +1 -0
- package/dist/select/select-option.mjs +65 -0
- package/dist/select/select-option.mjs.map +1 -0
- package/dist/select/select-root.d.mts +68 -0
- package/dist/select/select-root.d.mts.map +1 -0
- package/dist/select/select-root.mjs +114 -0
- package/dist/select/select-root.mjs.map +1 -0
- package/dist/select/select-separator.d.mts +20 -0
- package/dist/select/select-separator.d.mts.map +1 -0
- package/dist/select/select-separator.mjs +30 -0
- package/dist/select/select-separator.mjs.map +1 -0
- package/dist/select/select-trigger.d.mts +29 -0
- package/dist/select/select-trigger.d.mts.map +1 -0
- package/dist/select/select-trigger.mjs +48 -0
- package/dist/select/select-trigger.mjs.map +1 -0
- package/dist/select/select-value.d.mts +31 -0
- package/dist/select/select-value.d.mts.map +1 -0
- package/dist/select/select-value.mjs +101 -0
- package/dist/select/select-value.mjs.map +1 -0
- package/dist/slider/index.d.mts +32 -0
- package/dist/slider/index.d.mts.map +1 -0
- package/dist/slider/index.mjs +27 -0
- package/dist/slider/index.mjs.map +1 -0
- package/dist/slider/slider-arithmetic.mjs +120 -0
- package/dist/slider/slider-arithmetic.mjs.map +1 -0
- package/dist/slider/slider-context.mjs +13 -0
- package/dist/slider/slider-context.mjs.map +1 -0
- package/dist/slider/slider-indicator.d.mts +22 -0
- package/dist/slider/slider-indicator.d.mts.map +1 -0
- package/dist/slider/slider-indicator.mjs +36 -0
- package/dist/slider/slider-indicator.mjs.map +1 -0
- package/dist/slider/slider-label.d.mts +23 -0
- package/dist/slider/slider-label.d.mts.map +1 -0
- package/dist/slider/slider-label.mjs +44 -0
- package/dist/slider/slider-label.mjs.map +1 -0
- package/dist/slider/slider-root.d.mts +111 -0
- package/dist/slider/slider-root.d.mts.map +1 -0
- package/dist/slider/slider-root.mjs +582 -0
- package/dist/slider/slider-root.mjs.map +1 -0
- package/dist/slider/slider-thumb.d.mts +58 -0
- package/dist/slider/slider-thumb.d.mts.map +1 -0
- package/dist/slider/slider-thumb.mjs +145 -0
- package/dist/slider/slider-thumb.mjs.map +1 -0
- package/dist/slider/slider-track.d.mts +29 -0
- package/dist/slider/slider-track.d.mts.map +1 -0
- package/dist/slider/slider-track.mjs +54 -0
- package/dist/slider/slider-track.mjs.map +1 -0
- package/dist/slider/slider-value.d.mts +34 -0
- package/dist/slider/slider-value.d.mts.map +1 -0
- package/dist/slider/slider-value.mjs +38 -0
- package/dist/slider/slider-value.mjs.map +1 -0
- package/dist/switch/switch.d.mts +5 -3
- package/dist/switch/switch.d.mts.map +1 -1
- package/dist/switch/switch.mjs +24 -8
- package/dist/switch/switch.mjs.map +1 -1
- package/dist/tabs/tabs-root.mjs +13 -3
- package/dist/tabs/tabs-root.mjs.map +1 -1
- package/dist/toast/index.d.mts +25 -0
- package/dist/toast/index.d.mts.map +1 -0
- package/dist/toast/index.mjs +26 -0
- package/dist/toast/index.mjs.map +1 -0
- package/dist/toast/toast-action.d.mts +39 -0
- package/dist/toast/toast-action.d.mts.map +1 -0
- package/dist/toast/toast-action.mjs +64 -0
- package/dist/toast/toast-action.mjs.map +1 -0
- package/dist/toast/toast-close.d.mts +25 -0
- package/dist/toast/toast-close.d.mts.map +1 -0
- package/dist/toast/toast-close.mjs +46 -0
- package/dist/toast/toast-close.mjs.map +1 -0
- package/dist/toast/toast-context.mjs +19 -0
- package/dist/toast/toast-context.mjs.map +1 -0
- package/dist/toast/toast-description.d.mts +24 -0
- package/dist/toast/toast-description.d.mts.map +1 -0
- package/dist/toast/toast-description.mjs +35 -0
- package/dist/toast/toast-description.mjs.map +1 -0
- package/dist/toast/toast-root.d.mts +43 -0
- package/dist/toast/toast-root.d.mts.map +1 -0
- package/dist/toast/toast-root.mjs +204 -0
- package/dist/toast/toast-root.mjs.map +1 -0
- package/dist/toast/toast-title.d.mts +24 -0
- package/dist/toast/toast-title.d.mts.map +1 -0
- package/dist/toast/toast-title.mjs +33 -0
- package/dist/toast/toast-title.mjs.map +1 -0
- package/dist/toast/toast-viewport.d.mts +52 -0
- package/dist/toast/toast-viewport.d.mts.map +1 -0
- package/dist/toast/toast-viewport.mjs +621 -0
- package/dist/toast/toast-viewport.mjs.map +1 -0
- package/dist/toast/toast.d.mts +119 -0
- package/dist/toast/toast.d.mts.map +1 -0
- package/dist/toast/toast.mjs +153 -0
- package/dist/toast/toast.mjs.map +1 -0
- package/package.json +7 -1
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createActionDetails } from "../internal/action-details.mjs";
|
|
3
|
+
import { dataAttr } from "../internal/data-attr.mjs";
|
|
4
|
+
import { useLatest } from "../internal/use-latest.mjs";
|
|
5
|
+
import { useRender } from "../internal/use-render.mjs";
|
|
6
|
+
import { useDevWarning } from "../internal/use-dev-warning.mjs";
|
|
7
|
+
import { DialogContext } from "../dialog/dialog-context.mjs";
|
|
8
|
+
import { getServerTopmostModal, getTopmostModal, subscribeModals } from "../internal/modal-registry.mjs";
|
|
9
|
+
import { ToastViewportContext } from "./toast-context.mjs";
|
|
10
|
+
import { toast, toastManagerInternals } from "./toast.mjs";
|
|
11
|
+
import { use, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore, useTransition } from "react";
|
|
12
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
13
|
+
import { createPortal } from "react-dom";
|
|
14
|
+
//#region src/toast/toast-viewport.tsx
|
|
15
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
16
|
+
const DEFAULT_HOTKEY = ["F8"];
|
|
17
|
+
const MODIFIER_NAMES = {
|
|
18
|
+
altKey: "Alt",
|
|
19
|
+
ctrlKey: "Ctrl",
|
|
20
|
+
metaKey: "Meta",
|
|
21
|
+
shiftKey: "Shift"
|
|
22
|
+
};
|
|
23
|
+
/** `["altKey", "KeyT"]` → `"Alt+T"`, `["F8"]` → `"F8"`. */
|
|
24
|
+
function hotkeyLabel(hotkey) {
|
|
25
|
+
return hotkey.map((key) => MODIFIER_NAMES[key] ?? key.replace(/^Key(?=[A-Z]$)/, "").replace(/^Digit/, "")).join("+");
|
|
26
|
+
}
|
|
27
|
+
/** The region's built-in accessible name (conventions 11): `"Notifications (F8)"`. */
|
|
28
|
+
function regionLabel(hotkey) {
|
|
29
|
+
if (hotkey === null || hotkey.length === 0) return "Notifications";
|
|
30
|
+
return `Notifications (${hotkeyLabel(hotkey)})`;
|
|
31
|
+
}
|
|
32
|
+
/** The listed keys and modifiers, and no unlisted modifier (Ctrl+F8 is not F8). */
|
|
33
|
+
function matchesHotkey(hotkey, event) {
|
|
34
|
+
const flags = event;
|
|
35
|
+
for (const modifier of Object.keys(MODIFIER_NAMES)) if (flags[modifier] === true !== hotkey.includes(modifier)) return false;
|
|
36
|
+
return hotkey.every((key) => key in MODIFIER_NAMES || event.code === key);
|
|
37
|
+
}
|
|
38
|
+
/** No store: `useSyncExternalStore` only tells the hydration render from a client render. */
|
|
39
|
+
const subscribeNothing = () => () => {};
|
|
40
|
+
const onClient = () => true;
|
|
41
|
+
const onServerOrHydrating = () => false;
|
|
42
|
+
/**
|
|
43
|
+
* The element the region is portalled into for its whole client life. The library moves this
|
|
44
|
+
* element (between its inline anchor and the topmost modal dialog); React never sees the move, so
|
|
45
|
+
* the toasts keep their state (an Undo in flight stays pending). `display: contents`: no box.
|
|
46
|
+
*/
|
|
47
|
+
function createHost() {
|
|
48
|
+
if (typeof document === "undefined") return null;
|
|
49
|
+
const host = document.createElement("div");
|
|
50
|
+
host.style.display = "contents";
|
|
51
|
+
return host;
|
|
52
|
+
}
|
|
53
|
+
function makeEntry(options, flags) {
|
|
54
|
+
return {
|
|
55
|
+
options,
|
|
56
|
+
...flags,
|
|
57
|
+
item: {
|
|
58
|
+
...options,
|
|
59
|
+
id: options.id,
|
|
60
|
+
pending: flags.promisePending || flags.actionPending,
|
|
61
|
+
ending: flags.ending
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function withFlags(entry, flags) {
|
|
66
|
+
return makeEntry(entry.options, {
|
|
67
|
+
promisePending: flags.promisePending ?? entry.promisePending,
|
|
68
|
+
actionPending: flags.actionPending ?? entry.actionPending,
|
|
69
|
+
ending: flags.ending ?? entry.ending
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/** Rendered entries (the first `limit` open ones plus every ending one) and the queued count. */
|
|
73
|
+
function partition(entries, limit) {
|
|
74
|
+
const visible = [];
|
|
75
|
+
const queued = [];
|
|
76
|
+
let open = 0;
|
|
77
|
+
for (const entry of entries) if (entry.ending) visible.push(entry);
|
|
78
|
+
else if (open < limit) {
|
|
79
|
+
visible.push(entry);
|
|
80
|
+
open++;
|
|
81
|
+
} else queued.push(entry);
|
|
82
|
+
return {
|
|
83
|
+
visible,
|
|
84
|
+
queued
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/** Plain text of a `ReactNode` that is text (queued toasts have no DOM to read yet). */
|
|
88
|
+
function plainText(node) {
|
|
89
|
+
if (typeof node === "string") return node;
|
|
90
|
+
if (typeof node === "number" || typeof node === "bigint") return String(node);
|
|
91
|
+
if (Array.isArray(node)) return node.map(plainText).join("");
|
|
92
|
+
return "";
|
|
93
|
+
}
|
|
94
|
+
/** Document order. */
|
|
95
|
+
function byDocumentPosition(a, b) {
|
|
96
|
+
if (a === b) return 0;
|
|
97
|
+
return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
|
|
98
|
+
}
|
|
99
|
+
function anyPause(sources) {
|
|
100
|
+
return sources.hover || sources.focus || sources.hidden || sources.blurred;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The toast region: a `<section popover="manual">` named "Notifications (F8)", shown in the top
|
|
104
|
+
* layer while it has toasts. Owns the queue (urgent state fed by `toast()`), the timers and their
|
|
105
|
+
* pausing, the hotkey, focus return, announcements through `document.ariaNotify()`, and the move
|
|
106
|
+
* into the topmost open modal `Dialog` (a region outside it would be inert).
|
|
107
|
+
*
|
|
108
|
+
* Owned keys (conventions 1.2): `popover`, `aria-label` (the consumer's own `aria-label` wins
|
|
109
|
+
* verbatim), `data-paused`, `data-empty`, and the pointer / focus handlers.
|
|
110
|
+
*/
|
|
111
|
+
function ToastViewport({ manager = toast, limit = 3, duration = 5e3, hotkey = DEFAULT_HOTKEY, announce, children, className, style, render, ...rest }) {
|
|
112
|
+
const [entries, setEntries] = useState([]);
|
|
113
|
+
const entriesRef = useRef(entries);
|
|
114
|
+
const listener = useRef(null);
|
|
115
|
+
listener.current ??= {
|
|
116
|
+
restore: (toasts) => handlers.current.restore(toasts),
|
|
117
|
+
apply: (commands) => handlers.current.apply(commands),
|
|
118
|
+
retain: () => handlers.current.retain()
|
|
119
|
+
};
|
|
120
|
+
const [rejected, setRejected] = useState(() => toastManagerInternals(manager).isTaken(listener.current));
|
|
121
|
+
const [, startCloseTransition] = useTransition();
|
|
122
|
+
const regionRef = useRef(null);
|
|
123
|
+
const roots = useRef(/* @__PURE__ */ new Map());
|
|
124
|
+
const suspended = useRef(/* @__PURE__ */ new Set());
|
|
125
|
+
const timers = useRef(/* @__PURE__ */ new Map());
|
|
126
|
+
const pauses = useRef({
|
|
127
|
+
hover: false,
|
|
128
|
+
focus: false,
|
|
129
|
+
hidden: false,
|
|
130
|
+
blurred: false
|
|
131
|
+
});
|
|
132
|
+
const [paused, setPaused] = useState(false);
|
|
133
|
+
/** Where focus goes back to once the last toast holding it leaves (design 7). */
|
|
134
|
+
const returnTarget = useRef(null);
|
|
135
|
+
const lastAnnounced = useRef(/* @__PURE__ */ new Map());
|
|
136
|
+
/** Per closing toast: the animations already running before `data-ending` applied. */
|
|
137
|
+
const exitBaselines = useRef(/* @__PURE__ */ new Map());
|
|
138
|
+
const announceBatch = useRef([]);
|
|
139
|
+
const addedSinceEffect = useRef(false);
|
|
140
|
+
const mounted = useRef(false);
|
|
141
|
+
const latest = useLatest({
|
|
142
|
+
limit,
|
|
143
|
+
duration,
|
|
144
|
+
hotkey,
|
|
145
|
+
announce
|
|
146
|
+
});
|
|
147
|
+
const container = useSyncExternalStore(subscribeModals, getTopmostModal, getServerTopmostModal);
|
|
148
|
+
const write = (next) => {
|
|
149
|
+
entriesRef.current = next;
|
|
150
|
+
setEntries(next);
|
|
151
|
+
};
|
|
152
|
+
const effectiveDuration = (entry) => {
|
|
153
|
+
const own = entry.options.duration;
|
|
154
|
+
if (own !== void 0) return own;
|
|
155
|
+
return entry.options.action !== void 0 ? null : latest.current.duration;
|
|
156
|
+
};
|
|
157
|
+
const syncTimers = () => {
|
|
158
|
+
const map = timers.current;
|
|
159
|
+
const { visible } = partition(entriesRef.current, latest.current.limit);
|
|
160
|
+
const now = Date.now();
|
|
161
|
+
const live = /* @__PURE__ */ new Set();
|
|
162
|
+
const halted = !mounted.current || anyPause(pauses.current);
|
|
163
|
+
for (const entry of visible) {
|
|
164
|
+
if (entry.ending) continue;
|
|
165
|
+
const id = entry.options.id;
|
|
166
|
+
const ms = effectiveDuration(entry);
|
|
167
|
+
if (ms === null) continue;
|
|
168
|
+
live.add(id);
|
|
169
|
+
let timer = map.get(id);
|
|
170
|
+
if (timer === void 0 || timer.duration !== ms) {
|
|
171
|
+
if (timer?.handle != null) clearTimeout(timer.handle);
|
|
172
|
+
timer = {
|
|
173
|
+
duration: ms,
|
|
174
|
+
remaining: ms,
|
|
175
|
+
startedAt: 0,
|
|
176
|
+
handle: null
|
|
177
|
+
};
|
|
178
|
+
map.set(id, timer);
|
|
179
|
+
}
|
|
180
|
+
const run = !halted && !entry.item.pending && !suspended.current.has(id);
|
|
181
|
+
if (run && timer.handle === null) {
|
|
182
|
+
const current = timer;
|
|
183
|
+
current.startedAt = now;
|
|
184
|
+
current.handle = setTimeout(() => {
|
|
185
|
+
current.handle = null;
|
|
186
|
+
current.remaining = 0;
|
|
187
|
+
close([id], "timeout", void 0);
|
|
188
|
+
}, Math.max(0, current.remaining));
|
|
189
|
+
} else if (!run && timer.handle !== null) {
|
|
190
|
+
clearTimeout(timer.handle);
|
|
191
|
+
timer.handle = null;
|
|
192
|
+
timer.remaining -= now - timer.startedAt;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
for (const [id, timer] of map) {
|
|
196
|
+
if (live.has(id)) continue;
|
|
197
|
+
if (timer.handle !== null) clearTimeout(timer.handle);
|
|
198
|
+
map.delete(id);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
const setPause = (source, value) => {
|
|
202
|
+
if (pauses.current[source] === value) return;
|
|
203
|
+
pauses.current[source] = value;
|
|
204
|
+
syncTimers();
|
|
205
|
+
setPaused(anyPause(pauses.current));
|
|
206
|
+
};
|
|
207
|
+
/** Open toasts' Root elements in document order. */
|
|
208
|
+
const sortedRoots = (exclude) => {
|
|
209
|
+
const open = new Set(entriesRef.current.filter((e) => !e.ending).map((e) => e.options.id));
|
|
210
|
+
const list = [];
|
|
211
|
+
for (const [id, element] of roots.current) {
|
|
212
|
+
if (!open.has(id) || exclude?.has(id) || !element.isConnected) continue;
|
|
213
|
+
list.push({
|
|
214
|
+
id,
|
|
215
|
+
element
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
return list.sort((a, b) => byDocumentPosition(a.element, b.element));
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* Before toasts close: if focus is inside one of them, move it to the next toast, else the
|
|
222
|
+
* previous one, else where it was before the region was entered, else the body (design 7).
|
|
223
|
+
*/
|
|
224
|
+
const moveFocusOut = (closing) => {
|
|
225
|
+
const doc = regionRef.current?.ownerDocument ?? document;
|
|
226
|
+
const active = doc.activeElement;
|
|
227
|
+
if (!(active instanceof HTMLElement)) return;
|
|
228
|
+
const all = sortedRoots();
|
|
229
|
+
const index = all.findIndex(({ element }) => element.contains(active));
|
|
230
|
+
if (index === -1 || !closing.has(all[index].id)) return;
|
|
231
|
+
const after = all.slice(index + 1).find(({ id }) => !closing.has(id));
|
|
232
|
+
const before = all.slice(0, index).reverse().find(({ id }) => !closing.has(id));
|
|
233
|
+
const target = after ?? before;
|
|
234
|
+
if (target !== void 0) {
|
|
235
|
+
target.element.focus();
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const stored = returnTarget.current;
|
|
239
|
+
if (stored !== null && stored.isConnected && !regionRef.current?.contains(stored)) {
|
|
240
|
+
stored.focus();
|
|
241
|
+
if (doc.activeElement === stored) return;
|
|
242
|
+
}
|
|
243
|
+
active.blur();
|
|
244
|
+
};
|
|
245
|
+
/**
|
|
246
|
+
* Records the animations running in each closing toast before `data-ending` applies: the exit
|
|
247
|
+
* waits only for the ones the ending state started, not for a running progress bar.
|
|
248
|
+
*/
|
|
249
|
+
const snapshotExitBaselines = (ids) => {
|
|
250
|
+
for (const id of ids) {
|
|
251
|
+
const element = roots.current.get(id);
|
|
252
|
+
if (element === void 0 || !element.isConnected) continue;
|
|
253
|
+
if (typeof element.getAnimations !== "function") continue;
|
|
254
|
+
exitBaselines.current.set(id, new Set(element.getAnimations({ subtree: true })));
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
const clearTimer = (id) => {
|
|
258
|
+
const timer = timers.current.get(id);
|
|
259
|
+
if (timer?.handle != null) clearTimeout(timer.handle);
|
|
260
|
+
timers.current.delete(id);
|
|
261
|
+
};
|
|
262
|
+
const scheduleCloseActions = (closed, reason, event) => {
|
|
263
|
+
for (const entry of closed) {
|
|
264
|
+
const closeAction = entry.options.closeAction;
|
|
265
|
+
if (closeAction === void 0) continue;
|
|
266
|
+
const details = createActionDetails(event, {
|
|
267
|
+
id: entry.options.id,
|
|
268
|
+
reason
|
|
269
|
+
});
|
|
270
|
+
queueMicrotask(() => {
|
|
271
|
+
if (!mounted.current) return;
|
|
272
|
+
startCloseTransition(async () => {
|
|
273
|
+
await closeAction(details);
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
const close = (ids, reason, event) => {
|
|
279
|
+
const wanted = new Set(ids);
|
|
280
|
+
const closing = entriesRef.current.filter((e) => !e.ending && wanted.has(e.options.id));
|
|
281
|
+
if (closing.length === 0) return;
|
|
282
|
+
const closingIds = new Set(closing.map((e) => e.options.id));
|
|
283
|
+
moveFocusOut(closingIds);
|
|
284
|
+
snapshotExitBaselines(closingIds);
|
|
285
|
+
write(entriesRef.current.map((e) => closingIds.has(e.options.id) ? withFlags(e, { ending: true }) : e));
|
|
286
|
+
syncTimers();
|
|
287
|
+
scheduleCloseActions(closing, reason, event);
|
|
288
|
+
};
|
|
289
|
+
const drop = (id) => {
|
|
290
|
+
const next = entriesRef.current.filter((e) => !(e.ending && e.options.id === id));
|
|
291
|
+
if (next.length === entriesRef.current.length) return;
|
|
292
|
+
lastAnnounced.current.delete(id);
|
|
293
|
+
exitBaselines.current.delete(id);
|
|
294
|
+
write(next);
|
|
295
|
+
};
|
|
296
|
+
const apply = (commands) => {
|
|
297
|
+
const next = [...entriesRef.current];
|
|
298
|
+
const dismissed = [];
|
|
299
|
+
for (const command of commands) {
|
|
300
|
+
if (command.kind === "dismiss") {
|
|
301
|
+
for (let i = 0; i < next.length; i++) {
|
|
302
|
+
const entry = next[i];
|
|
303
|
+
if (entry.ending) continue;
|
|
304
|
+
if (command.id !== void 0 && entry.options.id !== command.id) continue;
|
|
305
|
+
dismissed.push(entry);
|
|
306
|
+
next[i] = withFlags(entry, { ending: true });
|
|
307
|
+
}
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
const index = next.findIndex((e) => e.options.id === command.id);
|
|
311
|
+
const existing = index === -1 ? void 0 : next[index];
|
|
312
|
+
if (command.kind === "add") {
|
|
313
|
+
const options = {
|
|
314
|
+
...command.options,
|
|
315
|
+
id: command.id
|
|
316
|
+
};
|
|
317
|
+
if (existing !== void 0 && !existing.ending) {
|
|
318
|
+
next[index] = makeEntry({
|
|
319
|
+
...existing.options,
|
|
320
|
+
...options
|
|
321
|
+
}, {
|
|
322
|
+
promisePending: command.pending || existing.promisePending,
|
|
323
|
+
actionPending: existing.actionPending,
|
|
324
|
+
ending: false
|
|
325
|
+
});
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
if (existing !== void 0) {
|
|
329
|
+
next.splice(index, 1);
|
|
330
|
+
clearTimer(command.id);
|
|
331
|
+
lastAnnounced.current.delete(command.id);
|
|
332
|
+
exitBaselines.current.delete(command.id);
|
|
333
|
+
}
|
|
334
|
+
next.push(makeEntry(options, {
|
|
335
|
+
promisePending: command.pending,
|
|
336
|
+
actionPending: false,
|
|
337
|
+
ending: false
|
|
338
|
+
}));
|
|
339
|
+
addedSinceEffect.current = true;
|
|
340
|
+
} else {
|
|
341
|
+
if (existing === void 0) {
|
|
342
|
+
if (isDev && !command.settle) console.error(`toast.update: no toast with id "${command.id}"; the update is ignored.`);
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
if (existing.ending) continue;
|
|
346
|
+
next[index] = makeEntry(command.settle ? {
|
|
347
|
+
...command.options,
|
|
348
|
+
id: command.id
|
|
349
|
+
} : {
|
|
350
|
+
...existing.options,
|
|
351
|
+
...command.options,
|
|
352
|
+
id: command.id
|
|
353
|
+
}, {
|
|
354
|
+
promisePending: command.settle ? false : existing.promisePending,
|
|
355
|
+
actionPending: existing.actionPending,
|
|
356
|
+
ending: false
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (dismissed.length === 0) {
|
|
361
|
+
write(next);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
const stays = new Set(next.filter((e) => !e.ending).map((e) => e.options.id));
|
|
365
|
+
const leaving = new Set(dismissed.map((e) => e.options.id).filter((id) => !stays.has(id)));
|
|
366
|
+
moveFocusOut(leaving);
|
|
367
|
+
snapshotExitBaselines(leaving);
|
|
368
|
+
write(next);
|
|
369
|
+
syncTimers();
|
|
370
|
+
scheduleCloseActions(dismissed, "dismiss", void 0);
|
|
371
|
+
};
|
|
372
|
+
const setActionPending = (id, pending) => {
|
|
373
|
+
const index = entriesRef.current.findIndex((e) => e.options.id === id);
|
|
374
|
+
if (index === -1) return;
|
|
375
|
+
const entry = entriesRef.current[index];
|
|
376
|
+
if (entry.actionPending === pending) return;
|
|
377
|
+
const next = [...entriesRef.current];
|
|
378
|
+
next[index] = withFlags(entry, { actionPending: pending });
|
|
379
|
+
write(next);
|
|
380
|
+
};
|
|
381
|
+
const flushAnnouncements = () => {
|
|
382
|
+
const batch = announceBatch.current;
|
|
383
|
+
announceBatch.current = [];
|
|
384
|
+
if (batch.length === 0) return;
|
|
385
|
+
const doc = regionRef.current?.ownerDocument ?? document;
|
|
386
|
+
if (typeof doc.ariaNotify !== "function") return;
|
|
387
|
+
const priority = batch.some((a) => a.priority === "high") ? "high" : "normal";
|
|
388
|
+
doc.ariaNotify(batch.map((a) => a.text).join(" "), { priority });
|
|
389
|
+
};
|
|
390
|
+
const queueAnnouncement = (id, text, priority) => {
|
|
391
|
+
const map = lastAnnounced.current;
|
|
392
|
+
if (map.has(id) && map.get(id) === text) return;
|
|
393
|
+
map.set(id, text);
|
|
394
|
+
if (text === null || text.trim() === "") return;
|
|
395
|
+
if (announceBatch.current.length === 0) queueMicrotask(flushAnnouncements);
|
|
396
|
+
announceBatch.current.push({
|
|
397
|
+
text,
|
|
398
|
+
priority
|
|
399
|
+
});
|
|
400
|
+
};
|
|
401
|
+
const restore = (toasts) => {
|
|
402
|
+
if (entriesRef.current.length > 0) return;
|
|
403
|
+
for (const t of toasts) if (t.announced !== void 0) lastAnnounced.current.set(t.options.id, t.announced);
|
|
404
|
+
write(toasts.map((t) => makeEntry(t.options, {
|
|
405
|
+
promisePending: t.pending,
|
|
406
|
+
actionPending: false,
|
|
407
|
+
ending: false
|
|
408
|
+
})));
|
|
409
|
+
addedSinceEffect.current = true;
|
|
410
|
+
};
|
|
411
|
+
const retain = () => {
|
|
412
|
+
const announced = lastAnnounced.current;
|
|
413
|
+
return entriesRef.current.filter((e) => !e.ending).map((e) => ({
|
|
414
|
+
options: e.options,
|
|
415
|
+
pending: e.promisePending,
|
|
416
|
+
announced: announced.has(e.options.id) ? announced.get(e.options.id) : void 0
|
|
417
|
+
}));
|
|
418
|
+
};
|
|
419
|
+
const handlers = useLatest({
|
|
420
|
+
apply,
|
|
421
|
+
restore,
|
|
422
|
+
retain,
|
|
423
|
+
close,
|
|
424
|
+
drop,
|
|
425
|
+
setActionPending,
|
|
426
|
+
queueAnnouncement,
|
|
427
|
+
setPause,
|
|
428
|
+
syncTimers
|
|
429
|
+
});
|
|
430
|
+
const context = useMemo(() => ({
|
|
431
|
+
close: (ids, reason, event) => handlers.current.close(ids, reason, event),
|
|
432
|
+
drop: (id) => handlers.current.drop(id),
|
|
433
|
+
setActionPending: (id, pending) => handlers.current.setActionPending(id, pending),
|
|
434
|
+
setSuspended: (id, value) => {
|
|
435
|
+
if (value) suspended.current.add(id);
|
|
436
|
+
else suspended.current.delete(id);
|
|
437
|
+
handlers.current.syncTimers();
|
|
438
|
+
},
|
|
439
|
+
registerRoot: (id, element) => {
|
|
440
|
+
if (element === null) roots.current.delete(id);
|
|
441
|
+
else roots.current.set(id, element);
|
|
442
|
+
},
|
|
443
|
+
focusSibling: (id, direction) => {
|
|
444
|
+
const all = sortedRoots();
|
|
445
|
+
const index = all.findIndex((r) => r.id === id);
|
|
446
|
+
if (index === -1) return;
|
|
447
|
+
all[index + direction]?.element.focus();
|
|
448
|
+
},
|
|
449
|
+
announce: (id, text, priority) => handlers.current.queueAnnouncement(id, text, priority),
|
|
450
|
+
exitBaseline: (id) => exitBaselines.current.get(id),
|
|
451
|
+
announcer: () => latest.current.announce
|
|
452
|
+
}), [handlers, latest]);
|
|
453
|
+
useEffect(() => {
|
|
454
|
+
const unsubscribe = toastManagerInternals(manager).subscribe(listener.current);
|
|
455
|
+
if (unsubscribe === null) {
|
|
456
|
+
setRejected(true);
|
|
457
|
+
if (isDev) console.error("Toast.Viewport: another Toast.Viewport already renders this manager; this one renders nothing. Use one Viewport per manager, or `createToastManager()` for a second queue.");
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
setRejected(false);
|
|
461
|
+
return unsubscribe;
|
|
462
|
+
}, [manager]);
|
|
463
|
+
useEffect(() => {
|
|
464
|
+
if (rejected) return;
|
|
465
|
+
mounted.current = true;
|
|
466
|
+
const doc = regionRef.current?.ownerDocument ?? document;
|
|
467
|
+
const view = doc.defaultView ?? window;
|
|
468
|
+
pauses.current.hidden = doc.visibilityState === "hidden";
|
|
469
|
+
const onVisibility = () => handlers.current.setPause("hidden", doc.visibilityState === "hidden");
|
|
470
|
+
const onWindowBlur = (event) => {
|
|
471
|
+
if (!(event.target instanceof Node)) handlers.current.setPause("blurred", true);
|
|
472
|
+
};
|
|
473
|
+
const onWindowFocus = (event) => {
|
|
474
|
+
if (!(event.target instanceof Node)) handlers.current.setPause("blurred", false);
|
|
475
|
+
};
|
|
476
|
+
doc.addEventListener("visibilitychange", onVisibility);
|
|
477
|
+
view.addEventListener("blur", onWindowBlur);
|
|
478
|
+
view.addEventListener("focus", onWindowFocus);
|
|
479
|
+
handlers.current.syncTimers();
|
|
480
|
+
setPaused(anyPause(pauses.current));
|
|
481
|
+
const map = timers.current;
|
|
482
|
+
return () => {
|
|
483
|
+
mounted.current = false;
|
|
484
|
+
doc.removeEventListener("visibilitychange", onVisibility);
|
|
485
|
+
view.removeEventListener("blur", onWindowBlur);
|
|
486
|
+
view.removeEventListener("focus", onWindowFocus);
|
|
487
|
+
for (const timer of map.values()) if (timer.handle !== null) clearTimeout(timer.handle);
|
|
488
|
+
map.clear();
|
|
489
|
+
};
|
|
490
|
+
}, [handlers, rejected]);
|
|
491
|
+
const hotkeyKey = hotkey === null ? null : hotkey.join("\0");
|
|
492
|
+
useEffect(() => {
|
|
493
|
+
if (hotkeyKey === null || rejected) return;
|
|
494
|
+
const keys = hotkeyKey.split("\0");
|
|
495
|
+
const doc = regionRef.current?.ownerDocument ?? document;
|
|
496
|
+
const onKeyDown = (event) => {
|
|
497
|
+
if (event.defaultPrevented || !matchesHotkey(keys, event)) return;
|
|
498
|
+
const newest = sortedRoots().at(-1);
|
|
499
|
+
if (newest === void 0) return;
|
|
500
|
+
event.preventDefault();
|
|
501
|
+
const active = doc.activeElement;
|
|
502
|
+
if (active instanceof HTMLElement && !regionRef.current?.contains(active)) returnTarget.current = active;
|
|
503
|
+
newest.element.focus();
|
|
504
|
+
};
|
|
505
|
+
doc.addEventListener("keydown", onKeyDown);
|
|
506
|
+
return () => doc.removeEventListener("keydown", onKeyDown);
|
|
507
|
+
}, [hotkeyKey, rejected]);
|
|
508
|
+
const portalled = useSyncExternalStore(subscribeNothing, onClient, onServerOrHydrating);
|
|
509
|
+
const [host] = useState(createHost);
|
|
510
|
+
const anchorRef = useRef(null);
|
|
511
|
+
useLayoutEffect(() => {
|
|
512
|
+
if (host === null) return;
|
|
513
|
+
return () => host.remove();
|
|
514
|
+
}, [host]);
|
|
515
|
+
useLayoutEffect(() => {
|
|
516
|
+
if (host === null || !portalled) return;
|
|
517
|
+
const target = container ?? anchorRef.current;
|
|
518
|
+
if (target !== null && host.parentNode !== target) target.append(host);
|
|
519
|
+
});
|
|
520
|
+
useLayoutEffect(() => {
|
|
521
|
+
const region = regionRef.current;
|
|
522
|
+
const current = entriesRef.current;
|
|
523
|
+
if (pauses.current.focus && (region === null || !region.contains(region.ownerDocument.activeElement))) {
|
|
524
|
+
returnTarget.current = null;
|
|
525
|
+
setPause("focus", false);
|
|
526
|
+
}
|
|
527
|
+
if (region !== null && region.isConnected && region.hasAttribute("popover")) {
|
|
528
|
+
let open = false;
|
|
529
|
+
try {
|
|
530
|
+
open = region.matches(":popover-open");
|
|
531
|
+
} catch {
|
|
532
|
+
open = false;
|
|
533
|
+
}
|
|
534
|
+
try {
|
|
535
|
+
if (current.length > 0) {
|
|
536
|
+
if (!open) region.showPopover();
|
|
537
|
+
else if (addedSinceEffect.current) {
|
|
538
|
+
region.hidePopover();
|
|
539
|
+
region.showPopover();
|
|
540
|
+
}
|
|
541
|
+
} else if (open) region.hidePopover();
|
|
542
|
+
} catch {}
|
|
543
|
+
}
|
|
544
|
+
addedSinceEffect.current = false;
|
|
545
|
+
const { visible, queued } = partition(current, latest.current.limit);
|
|
546
|
+
for (const entry of queued) {
|
|
547
|
+
const id = entry.options.id;
|
|
548
|
+
if (lastAnnounced.current.has(id)) continue;
|
|
549
|
+
const resolver = entry.options.announce ?? latest.current.announce;
|
|
550
|
+
const text = resolver ? resolver(entry.item) : [plainText(entry.options.title), plainText(entry.options.description)].filter((t) => t !== "").join(" ");
|
|
551
|
+
queueAnnouncement(id, text, entry.options.priority ?? "normal");
|
|
552
|
+
}
|
|
553
|
+
for (const entry of visible) if (entry.ending && !roots.current.has(entry.options.id)) drop(entry.options.id);
|
|
554
|
+
syncTimers();
|
|
555
|
+
});
|
|
556
|
+
const insideDialog = use(DialogContext) !== null;
|
|
557
|
+
useDevWarning(insideDialog, "Toast.Viewport: rendered inside a Dialog. The region would close with the dialog; render the Viewport once near the root of the app (it moves into open modal Dialogs by itself).");
|
|
558
|
+
const { visible, queued } = partition(entries, limit);
|
|
559
|
+
const state = {
|
|
560
|
+
toasts: visible.map((e) => e.item),
|
|
561
|
+
queued: queued.length,
|
|
562
|
+
paused
|
|
563
|
+
};
|
|
564
|
+
const regionCallbackRef = useCallback((element) => {
|
|
565
|
+
regionRef.current = element;
|
|
566
|
+
return () => {
|
|
567
|
+
if (regionRef.current === element) regionRef.current = null;
|
|
568
|
+
};
|
|
569
|
+
}, []);
|
|
570
|
+
const onPointerEnter = () => setPause("hover", true);
|
|
571
|
+
const onPointerLeave = () => setPause("hover", false);
|
|
572
|
+
const onFocus = (event) => {
|
|
573
|
+
const region = event.currentTarget;
|
|
574
|
+
const from = event.relatedTarget;
|
|
575
|
+
if (!(from instanceof Node) || !region.contains(from)) returnTarget.current ??= from instanceof HTMLElement ? from : null;
|
|
576
|
+
setPause("focus", true);
|
|
577
|
+
};
|
|
578
|
+
const onBlur = (event) => {
|
|
579
|
+
const to = event.relatedTarget;
|
|
580
|
+
if (to instanceof Node && event.currentTarget.contains(to)) return;
|
|
581
|
+
returnTarget.current = null;
|
|
582
|
+
setPause("focus", false);
|
|
583
|
+
};
|
|
584
|
+
const libraryProps = {
|
|
585
|
+
popover: "manual",
|
|
586
|
+
"aria-label": rest["aria-label"] ?? regionLabel(hotkey),
|
|
587
|
+
"data-paused": dataAttr(paused),
|
|
588
|
+
"data-empty": dataAttr(state.toasts.length === 0),
|
|
589
|
+
onPointerEnter,
|
|
590
|
+
onPointerLeave,
|
|
591
|
+
onFocus,
|
|
592
|
+
onBlur
|
|
593
|
+
};
|
|
594
|
+
const region = useRender({
|
|
595
|
+
defaultElement: "section",
|
|
596
|
+
render,
|
|
597
|
+
state,
|
|
598
|
+
className,
|
|
599
|
+
style,
|
|
600
|
+
children,
|
|
601
|
+
props: rest,
|
|
602
|
+
libraryProps,
|
|
603
|
+
ref: regionCallbackRef
|
|
604
|
+
});
|
|
605
|
+
if (rejected) return null;
|
|
606
|
+
const content = /* @__PURE__ */ jsx(ToastViewportContext, {
|
|
607
|
+
value: context,
|
|
608
|
+
children: region
|
|
609
|
+
});
|
|
610
|
+
if (!portalled || host === null) return content;
|
|
611
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
|
|
612
|
+
ref: anchorRef,
|
|
613
|
+
style: ANCHOR_STYLE
|
|
614
|
+
}), createPortal(content, host)] });
|
|
615
|
+
}
|
|
616
|
+
const ANCHOR_STYLE = { display: "contents" };
|
|
617
|
+
ToastViewport.displayName = "ToastViewport";
|
|
618
|
+
//#endregion
|
|
619
|
+
export { ToastViewport };
|
|
620
|
+
|
|
621
|
+
//# sourceMappingURL=toast-viewport.mjs.map
|