Sign In

scrollsheet

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

scrollsheet - npm Package Compare versions

Comparing version
1.0.0-beta.4
to
1.0.0-beta.5
+230
dist/auto/drawer-Daga_OBC.mjs
"use client";
import { c as Slot, d as useSheetContext, i as Content$1, n as Description, r as Title, s as Trigger, t as Close, u as Root$1 } from "./misc-Bf6n3Ykh.mjs";
import { t as cn } from "./cn-DgpbGWy-.mjs";
import * as React from "react";
import { Fragment, jsx } from "react/jsx-runtime";
import { createPortal } from "react-dom";
//#region packages/scrollsheet/src/handle.tsx
function detentLabel(spec) {
if (spec === "full") return "Full";
if (spec === "medium") return "Half";
if (spec === "content") return "Fit content";
if (typeof spec === "number") return `${Math.round(spec * 100)}%`;
return spec ?? "";
}
const Handle$1 = React.forwardRef(function Handle({ asChild, onClick, onKeyDown, className, variant = "inside", ...props }, ref) {
const ctx = useSheetContext("Handle");
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => setMounted(true), []);
const outside = variant === "outside" && ctx.side === "bottom" && !(mounted && !ctx.canvasEl);
const variantAttr = outside ? "outside" : variant === "floating" ? "floating" : void 0;
const specs = ctx.detents;
const horizontal = ctx.side === "left" || ctx.side === "right";
const multi = specs.length >= 2;
const activeIndex = specs.findIndex((s) => Object.is(s, ctx.activeDetent));
const move = (delta) => {
if (!multi) return false;
const index = specs.findIndex((s) => Object.is(s, ctx.activeDetent));
const next = specs[index + delta];
if (next === void 0) return false;
ctx.setActiveDetent(next);
return true;
};
const sliderAria = multi ? {
role: "slider",
"aria-orientation": horizontal ? "horizontal" : "vertical",
"aria-valuemin": 0,
"aria-valuemax": specs.length - 1,
...activeIndex >= 0 ? {
"aria-valuenow": activeIndex,
"aria-valuetext": detentLabel(specs[activeIndex])
} : {}
} : {};
const button = jsx(asChild ? Slot : "button", {
...asChild ? {} : { type: "button" },
"aria-label": horizontal ? "Adjust sheet width" : "Adjust sheet height",
...sliderAria,
...props,
ref,
className: cn("scrollsheet-handle", className),
"data-scrollsheet-handle": true,
"data-scrollsheet-handle-variant": variantAttr,
onClick: (event) => {
onClick?.(event);
if (event.defaultPrevented) return;
if (!multi) return;
const index = specs.findIndex((s) => Object.is(s, ctx.activeDetent));
const next = specs[(index + 1) % specs.length];
if (next !== void 0) ctx.setActiveDetent(next);
},
onKeyDown: (event) => {
onKeyDown?.(event);
if (event.defaultPrevented) return;
const expandKey = horizontal ? "ArrowRight" : "ArrowUp";
const collapseKey = horizontal ? "ArrowLeft" : "ArrowDown";
if (event.key === expandKey) {
event.preventDefault();
move(1);
} else if (event.key === collapseKey) {
event.preventDefault();
if (!move(-1) && ctx.dismissible) ctx.setOpen(false);
} else if (multi && event.key === "Home") {
event.preventDefault();
const first = specs[0];
if (first !== void 0) ctx.setActiveDetent(first);
} else if (multi && event.key === "End") {
event.preventDefault();
const last = specs[specs.length - 1];
if (last !== void 0) ctx.setActiveDetent(last);
}
}
});
if (outside) return ctx.canvasEl ? createPortal(button, ctx.canvasEl) : null;
return button;
});
//#endregion
//#region packages/scrollsheet/src/drawer/index.tsx
const wrapperClaims = new Map();
const VAUL_CLOSE_THRESHOLD = .25;
function toDetentSpec(point) {
if (typeof point === "number") return point;
if (point === "fit-content" || point === "content") return "content";
if (/^\d+(\.\d+)?px$/.test(point)) return `${Number.parseFloat(point)}px`;
const parsed = Number.parseFloat(point);
return Number.isFinite(parsed) ? parsed : "content";
}
function resolveFadeFromIndex(snapPoints, fadeFromIndex) {
if (!snapPoints || snapPoints.length === 0) return void 0;
const point = snapPoints[fadeFromIndex ?? snapPoints.length - 1];
return point !== void 0 ? toDetentSpec(point) : void 0;
}
function resolveCloseThreshold(snapPoints, closeThreshold) {
if (snapPoints !== void 0 && snapPoints.length > 0) return void 0;
return Math.min(1, Math.max(0, 1 - (closeThreshold ?? VAUL_CLOSE_THRESHOLD)));
}
function composeOpenChange(onOpenChange, onClose) {
if (!onOpenChange && !onClose) return void 0;
return (next) => {
if (!next) onClose?.();
onOpenChange?.(next);
};
}
function Root(props) {
const { children, open, defaultOpen, onOpenChange, onClose, dismissible, snapPoints, activeSnapPoint, setActiveSnapPoint, onAnimationEnd, autoFocus, nested, direction, modal, shouldScaleBackground, setBackgroundColorOnScale, noBodyStyles, disablePreventScroll, preventScrollRestoration, repositionInputs, scrollLockTimeout, closeThreshold, fadeFromIndex, snapToSequentialPoint, handleOnly, onDrag, onRelease, container, fixed, actionsRef, backdropDismissible, escapeDismissible, keyboardExpands, onTravel, scrollbar } = props;
const detents = snapPoints && snapPoints.length > 0 ? snapPoints.map(toDetentSpec) : void 0;
const activeDetent = activeSnapPoint != null ? toDetentSpec(activeSnapPoint) : void 0;
const largestUndimmedDetent = resolveFadeFromIndex(snapPoints, fadeFromIndex);
const resolvedCloseThreshold = resolveCloseThreshold(snapPoints, closeThreshold);
const handleRelease = onRelease ? (event, willRemainOpen) => onRelease(event, willRemainOpen) : void 0;
const handleOpenChange = composeOpenChange(onOpenChange, onClose);
const handleActiveDetentChange = (detent) => {
if (!setActiveSnapPoint) return;
const match = snapPoints?.find((point) => Object.is(toDetentSpec(point), detent));
setActiveSnapPoint(match ?? detent);
};
React.useEffect(() => {
if (!shouldScaleBackground) return;
const existing = document.querySelector("[data-scrollsheet-background]");
if (existing && !wrapperClaims.has(existing)) return;
const wrapper = existing ?? document.querySelector("[data-vaul-drawer-wrapper]");
if (!wrapper) return;
const count = wrapperClaims.get(wrapper) ?? 0;
wrapperClaims.set(wrapper, count + 1);
if (count === 0) wrapper.setAttribute("data-scrollsheet-background", "");
return () => {
const remaining = (wrapperClaims.get(wrapper) ?? 1) - 1;
if (remaining <= 0) {
wrapperClaims.delete(wrapper);
wrapper.removeAttribute("data-scrollsheet-background");
} else wrapperClaims.set(wrapper, remaining);
};
}, [shouldScaleBackground]);
return jsx(DirectionContext.Provider, {
value: direction ?? "bottom",
children: jsx(Root$1, {
open,
defaultOpen,
onOpenChange: handleOpenChange,
onOpenChangeComplete: onAnimationEnd,
dismissible,
detents,
activeDetent,
onActiveDetentChange: setActiveSnapPoint ? handleActiveDetentChange : void 0,
side: direction,
modal,
backgroundEffect: shouldScaleBackground ? "scale" : "none",
largestUndimmedDetent,
handleOnly,
sequentialDetents: snapToSequentialPoint,
closeThreshold: resolvedCloseThreshold,
onRelease: handleRelease,
actionsRef,
backdropDismissible,
escapeDismissible,
keyboardExpands,
onTravel,
scrollbar,
children
})
});
}
const DirectionContext = React.createContext("bottom");
const NestedRoot = Root;
function Portal({ children, container }) {
return jsx(Fragment, { children });
}
function Overlay(_props) {
return null;
}
function hasMultipleSnapPoints(detents) {
return detents.length > 1;
}
const Content = React.forwardRef(function Content({ onPointerDownOutside, onOpenAutoFocus, onEscapeKeyDown, onCloseAutoFocus, onInteractOutside, onFocusOutside, forceMount, ...props }, ref) {
const ctx = useSheetContext("Content");
const direction = React.useContext(DirectionContext);
const snapPointsActive = hasMultipleSnapPoints(ctx.detents);
return jsx(Content$1, {
...props,
ref,
"data-vaul-drawer": "",
"data-vaul-drawer-direction": direction,
"data-vaul-snap-points": snapPointsActive ? "true" : "false"
});
});
function composeHandleClick(preventCycle, onClick) {
return (event) => {
onClick?.(event);
if (preventCycle) event.preventDefault();
};
}
const Handle = React.forwardRef(function Handle({ preventCycle, onClick, ...props }, ref) {
const ctx = useSheetContext("Handle");
return jsx(Handle$1, {
...props,
ref,
"data-vaul-handle": "",
"data-vaul-drawer-visible": ctx.open ? "true" : "false",
onClick: composeHandleClick(preventCycle, onClick)
});
});
const DrawerClose = React.forwardRef(function DrawerClose({ children, ...props }, ref) {
return jsx(Close, {
...props,
ref,
children: children ?? null
});
});
const Drawer = {
Root,
NestedRoot,
Trigger,
Portal,
Overlay,
Content,
Close: DrawerClose,
Title,
Description,
Handle
};
//#endregion
export { NestedRoot as a, Root as c, hasMultipleSnapPoints as d, resolveCloseThreshold as f, Handle as i, composeHandleClick as l, Handle$1 as m, Drawer as n, Overlay as o, resolveFadeFromIndex as p, DrawerClose as r, Portal as s, Content as t, composeOpenChange as u };
import { a as SheetTitleProps, c as SheetContentProps, g as DetentSpec, i as SheetDescriptionProps, l as SheetTriggerProps, p as SheetRootProps, r as SheetCloseProps } from "./misc-D1d_S8fO.mjs";
import * as React from "react";
//#region packages/scrollsheet/src/handle.d.ts
interface SheetHandleProps extends React.ComponentProps<"button"> {
/** Render the child element instead of a `<button>`, merging props into it. */
asChild?: boolean;
/**
* Where the pill sits. `'inside'` (default) flows at the top of the
* sheet's content. `'floating'` overlays the content — absolutely
* positioned over the panel's top edge, for full-bleed content (maps,
* photos) a flow pill would push down. `'outside'` floats in the backdrop
* above the sheet's top edge (bottom sheets only; other sides render as
* `'inside'`) — it rides the canvas layer outside the panel's clip and
* drags, clicks, and keys exactly like the others.
* @default 'inside'
*/
variant?: "inside" | "floating" | "outside";
}
/**
* The grabber pill. Click cycles detents; ArrowUp/ArrowDown (or Left/Right
* for side sheets) move between them, Home/End jump to the first/last — so
* multi-detent sheets are fully keyboard-operable. With two or more detents
* it exposes itself as a slider (role, value min/max/now, value text) so
* screen readers announce the current stop, not just "button".
*
* The handle is optional. It's purely a click/keyboard affordance layered on
* top of the drag engine, which listens on the whole panel, not the handle —
* omit `<Sheet.Handle>` and the whole panel still drags and dismisses, in
* every mode (modal and non-modal) and every side (bottom/top/left/right).
* There's no boolean prop for this; omission is the API.
*/
declare const Handle$1: React.ForwardRefExoticComponent<Omit<SheetHandleProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
//#endregion
//#region packages/scrollsheet/src/drawer/index.d.ts
/** vaul's snap point shape: a 0–1 fraction, an absolute `'###px'` string, or `'fit-content'`. */
type VaulSnapPoint = number | string;
/**
* Resolves vaul's `fadeFromIndex` against `snapPoints` into a
* `largestUndimmedDetent` spec, mirroring real vaul's own default (its
* `src/index.tsx`: `fadeFromIndex = snapPoints && snapPoints.length - 1`) —
* omitted with `snapPoints` set defaults to the *topmost* snap point index
* (no dim until the last snap point), not "no undimmed range at all"
* (scrollsheet's own default when `largestUndimmedDetent` is never touched).
* No `snapPoints`, or an out-of-range explicit index, resolves to
* `undefined` (today's full-dim-range behavior) — pulled out as its own pure
* function so this index math is unit-testable independent of rendering.
*/
declare function resolveFadeFromIndex(snapPoints: readonly VaulSnapPoint[] | undefined, fadeFromIndex: number | undefined): DetentSpec | undefined;
/**
* Resolves vaul's `closeThreshold` against whether `snapPoints` is set,
* CONVERTING between opposite conventions: vaul counts the fraction dragged
* AWAY (its 0.25 default = dismiss after a quarter-height drag), scrollsheet
* counts the fraction still VISIBLE (`isBelowCloseThreshold`: dismiss when
* `revealed < firstDetent * closeThreshold`, so higher = easier). A raw
* passthrough inverts the migrated feel — vaul's 0.25 became "drag 75% to
* dismiss", harder than scrollsheet's own 0.5 default instead of easier —
* so the mapping is `1 - value`, and an omitted prop resolves to vaul's own
* `CLOSE_THRESHOLD` default of 0.25 (=> 0.75 here), never `Sheet.Root`'s
* unrelated 0.5.
* `snapPoints` set makes `closeThreshold` dead code in real vaul (its
* `onRelease` returns through the snap-points branch before ever reading
* it), matched here by always resolving to `undefined` in that case.
* Pulled out as its own pure function, mirroring `resolveFadeFromIndex`,
* so the conversion math is unit-testable independent of rendering.
*/
declare function resolveCloseThreshold(snapPoints: readonly VaulSnapPoint[] | undefined, closeThreshold: number | undefined): number | undefined;
/**
* Composes vaul's `onClose` on top of `onOpenChange`: `onClose` fires
* whenever the transition target is `false`, then `onOpenChange` always
* fires. Real vaul's `closeDrawer()` calls `onClose` from every dismiss
* path (swipe past threshold, Esc, backdrop, imperative); scrollsheet's own
* `onOpenChange` is already the single funnel every one of *its* dismiss
* paths goes through (`useControllableState`'s `onChange`, which fires
* exactly once per real open/false transition), so layering `onClose` on
* top of that wiring reproduces "every dismiss path" without a second one.
* Returns `undefined` when neither callback is given, matching the
* conditional-wrapper convention `onRelease`'s cast uses below. Pulled out
* as its own pure function so the composition is unit-testable without a
* live DOM/click event.
*/
declare function composeOpenChange(onOpenChange: ((open: boolean) => void) | undefined, onClose: (() => void) | undefined): ((open: boolean) => void) | undefined;
/**
* vaul's own props, translated — plus (via the Pick) the scrollsheet-native
* props that have no vaul counterpart and forward to `Sheet.Root`
* untranslated: `backdropDismissible` (the escape hatch vaul served with the
* Radix onPointerDownOutside/onInteractOutside preventDefault idiom),
* `escapeDismissible` (same for onEscapeKeyDown), `keyboardExpands`,
* `onTravel` (the live counterpart of vaul's ignored `onDrag`), `scrollbar`,
* and `actionsRef`. Only props with no vaul name-collision are forwarded —
* anything vaul also has (`closeThreshold`, `modal`, `dismissible`, …) keeps
* its translated vaul semantics above.
*/
interface DrawerRootProps extends Pick<SheetRootProps, "actionsRef" | "backdropDismissible" | "escapeDismissible" | "keyboardExpands" | "onTravel" | "scrollbar"> {
children?: React.ReactNode;
open?: boolean;
defaultOpen?: boolean;
onOpenChange?: (open: boolean) => void;
/**
* Fires whenever the drawer closes — every dismiss path (swipe past
* threshold, Esc, backdrop tap, imperative `close()`) funnels through the
* same `onOpenChange` wiring this is layered on top of, matching real
* vaul's `closeDrawer()`, which calls `onClose` on every one of those
* paths too.
*/
onClose?: () => void;
/** @default true */
dismissible?: boolean;
/** Fractions (0–1), `'###px'` strings, or `'fit-content'` — translated to scrollsheet `detents`. */
snapPoints?: readonly VaulSnapPoint[];
/** Optionally-controlled active snap point; `null` means "no explicit snap point". */
activeSnapPoint?: VaulSnapPoint | null;
setActiveSnapPoint?: (snapPoint: VaulSnapPoint | null) => void;
/** Fires when the open/close transition actually completes — wired to `onOpenChangeComplete`, exact rather than vaul's timer. */
onAnimationEnd?: (open: boolean) => void;
/** Ignored — the native `<dialog>` manages focus itself. */
autoFocus?: boolean;
/** Ignored — nesting is automatic: a `<Drawer.Content>` rendered inside another registers itself. */
nested?: boolean;
/** Maps to scrollsheet `side` — all four edges. */
direction?: "top" | "bottom" | "left" | "right";
/** Maps to scrollsheet `modal` — `false` renders non-modal (Popover top layer, page stays interactive). */
modal?: boolean;
/**
* Maps to `backgroundEffect="scale"`; vaul's own `[data-vaul-drawer-wrapper]`
* is picked up as the target. Left false (vaul's default), this maps to
* `'none'`, not unset — vaul never scales the page unless asked, so the
* scrollsheet default for full-height sheets must not leak in here.
*/
shouldScaleBackground?: boolean;
/**
* Maps to scrollsheet `closeThreshold` — but only when `snapPoints` is
* unset. In real vaul, `closeThreshold` is dead code once `snapPoints`
* exist (its `onRelease` returns through the snap-points branch before
* ever reading it); this compat layer matches that rather than giving the
* prop unconditional live effect. Passing both together warns once in dev
* — use the native `Sheet.Root closeThreshold` directly if you want it to
* combine with detents.
*
* Omitted (with `snapPoints` unset) resolves to vaul's own default of
* `0.25` (`CLOSE_THRESHOLD` in vaul's `src/constants.ts`) — not
* scrollsheet's own `0.5` default, which is twice the drag distance.
*/
closeThreshold?: number;
/**
* Maps to `largestUndimmedDetent`, resolved against `snapPoints` at this
* index. Omitted (with `snapPoints` set) defaults to `snapPoints.length -
* 1` — vaul's own default, meaning no dim until the topmost snap point —
* not "dim across the full range" (scrollsheet's own default when
* `largestUndimmedDetent` is never touched at all).
*/
fadeFromIndex?: number;
/** Maps to `sequentialDetents`. */
snapToSequentialPoint?: boolean;
/** Maps to `handleOnly` directly. */
handleOnly?: boolean;
/**
* Maps to `onRelease`. Typed as vaul's own `React.PointerEvent<HTMLDivElement>`
* (rather than scrollsheet's wider native-`PointerEvent` type) so a
* handler already typed against vaul's declaration still assigns cleanly —
* see the internal cast in `Root` for why this is safe at runtime.
*/
onRelease?: (event: React.PointerEvent<HTMLDivElement>, open: boolean) => void;
setBackgroundColorOnScale?: boolean;
noBodyStyles?: boolean;
disablePreventScroll?: boolean;
preventScrollRestoration?: boolean;
repositionInputs?: boolean;
scrollLockTimeout?: number;
onDrag?: (event: React.PointerEvent, percentageDragged: number) => void;
container?: HTMLElement | null;
/**
* Ignored — real vaul's `fixed` swaps its own keyboard-avoidance strategy
* (resize instead of translate); scrollsheet's `useKeyboardViewport` has
* no equivalent toggle.
*/
fixed?: boolean;
}
declare function Root(props: DrawerRootProps): React.JSX.Element;
/**
* Nested drawers just work: a `<Drawer.Content>` rendered inside another
* `<Drawer.Content>` automatically registers with the parent's stacking
* context (the parent recedes while the child is open), the way iOS stacks
* sheets. `NestedRoot` is an alias of `Root` kept for drop-in compatibility
* with vaul's API surface — there's nothing distinct for it to do.
*/
declare const NestedRoot: typeof Root;
type DrawerNestedRootProps = DrawerRootProps;
interface DrawerPortalProps {
children?: React.ReactNode;
/** Ignored — the native `<dialog>` always renders into the browser's top layer. */
container?: HTMLElement | null;
}
declare function Portal({ children, container }: DrawerPortalProps): React.JSX.Element;
type DrawerOverlayProps = React.ComponentProps<"div">;
/**
* scrollsheet draws its own backdrop (`.scrollsheet-backdrop`, themeable via
* the `--scrollsheet-backdrop` CSS variable) that tracks scroll progress, so
* there's nothing for a separate overlay element to render. This accepts
* vaul's `<Drawer.Overlay className .../>` so existing JSX doesn't crash —
* restyle the backdrop through the CSS variable instead.
*/
declare function Overlay(_props: DrawerOverlayProps): null;
/**
* Whether translated `snapPoints` produced more than one distinct detent —
* feeds Content's `data-vaul-snap-points` attribute. vaul's own
* CSS-selector convention treats a single snap point the same as none
* (scrollsheet's own single-detent default also resolves to `false` here).
* Pulled out as its own pure function, mirroring `resolveFadeFromIndex` /
* `resolveCloseThreshold`, so it's unit-testable independent of rendering
* (`<Drawer.Content>`'s actual DOM output is behind a client-only mount
* gate — see content.tsx — so SSR can't observe the attribute directly).
*/
declare function hasMultipleSnapPoints(detents: readonly DetentSpec[]): boolean;
interface DrawerContentProps extends SheetContentProps {
onPointerDownOutside?: (event: CustomEvent) => void;
onOpenAutoFocus?: (event: Event) => void;
onEscapeKeyDown?: (event: KeyboardEvent) => void;
onCloseAutoFocus?: (event: Event) => void;
onInteractOutside?: (event: CustomEvent) => void;
onFocusOutside?: (event: CustomEvent) => void;
forceMount?: boolean;
}
declare const Content: React.ForwardRefExoticComponent<Omit<DrawerContentProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
/**
* Composes the click handler Handle passes to `SheetHandle`: the caller's
* own `onClick` always fires first, then — when `preventCycle` is set —
* `event.preventDefault()`, the same signal `SheetHandle`'s own
* click-to-cycle logic already checks (`if (event.defaultPrevented) return`
* in handle.tsx) to skip advancing to the next detent. Mirrors real vaul's
* `preventCycle`, read inside its own `handleCycleSnapPoints` guard. Pulled
* out as its own pure function so the composition is unit-testable without
* a live DOM/click event.
*/
declare function composeHandleClick(preventCycle: boolean | undefined, onClick: ((event: React.MouseEvent<HTMLButtonElement>) => void) | undefined): (event: React.MouseEvent<HTMLButtonElement>) => void;
interface DrawerHandleProps extends SheetHandleProps {
/**
* Suppresses the click-to-cycle-detents behavior — the handle still
* renders, drags, and is keyboard-operable as usual, but a click no
* longer advances to the next detent. Mirrors real vaul's own
* `preventCycle`, which guards the same click path (vaul's handle has no
* keyboard cycling to suppress).
*/
preventCycle?: boolean;
}
declare const Handle: React.ForwardRefExoticComponent<Omit<DrawerHandleProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
/**
* Sheet.Close's self-closing form renders a styled ✕ default; vaul's own
* `<Drawer.Close />` renders an empty unstyled button. A migrating user
* must not get a surprise icon button from a version swap, so the compat
* Close pins children to null (defined, so the styled-default branch never
* arms) unless the caller passed some.
*/
declare const DrawerClose: React.ForwardRefExoticComponent<Omit<SheetCloseProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
/** Namespace-style access matching vaul's `<Drawer.Root>…</Drawer.Root>` shape. */
declare const Drawer: {
Root: typeof Root;
NestedRoot: typeof Root;
Trigger: React.ForwardRefExoticComponent<Omit<SheetTriggerProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
Portal: typeof Portal;
Overlay: typeof Overlay;
Content: React.ForwardRefExoticComponent<Omit<DrawerContentProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
Close: React.ForwardRefExoticComponent<Omit<SheetCloseProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
Title: React.ForwardRefExoticComponent<Omit<SheetTitleProps, "ref"> & React.RefAttributes<HTMLHeadingElement>>;
Description: React.ForwardRefExoticComponent<Omit<SheetDescriptionProps, "ref"> & React.RefAttributes<HTMLParagraphElement>>;
Handle: React.ForwardRefExoticComponent<Omit<DrawerHandleProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
};
//#endregion
export { SheetHandleProps as S, composeOpenChange as _, DrawerHandleProps as a, resolveFadeFromIndex as b, DrawerPortalProps as c, NestedRoot as d, Overlay as f, composeHandleClick as g, VaulSnapPoint as h, DrawerContentProps as i, DrawerRootProps as l, Root as m, Drawer as n, DrawerNestedRootProps as o, Portal as p, DrawerClose as r, DrawerOverlayProps as s, Content as t, Handle as u, hasMultipleSnapPoints as v, Handle$1 as x, resolveCloseThreshold as y };

Sorry, the diff of this file is too big to display

import * as React from "react";
//#region packages/scrollsheet/src/internal/detents.d.ts
/**
* Detents — the stops a sheet can rest at, in the spirit of
* UISheetPresentationController's `detents`.
*
* A detent resolves to the visible height of the sheet in px.
* Accepted forms:
* - 'full' → viewport height minus top inset
* - 'medium' → 50% of viewport
* - 'content' → natural content height (measured, capped at full)
* - number in (0, 1] → fraction of viewport
* - `${number}px` → absolute pixels
*/
type DetentSpec = "full" | "medium" | "content" | number | `${number}px`;
//#endregion
//#region packages/scrollsheet/src/motion/geometry.d.ts
/**
* Per-side axis geometry.
*
* The whole engine (content.tsx) is written once against an abstract
* "revealed px" concept: 0 = fully hidden, `maxDetent` = fully revealed. The
* DOM's scroll position is the mechanism, but which raw value corresponds to
* "revealed" depends on which canvas edge the panel is pinned to:
* bottom/right sit at the canvas's *far* end, so raw scroll IS revealed px
* directly; top/left sit at the *near* end (flush with the screen when fully
* open), so raw scroll is mirrored: revealed = maxDetent - rawScroll.
* `mapScroll` is the self-inverse conversion between the two spaces.
*/
type Side = "bottom" | "top" | "left" | "right";
//#endregion
//#region packages/scrollsheet/src/context.d.ts
/**
* onTravel's third argument. Reused and mutated in place every travel frame
* (see content.tsx's updateTravel) — read it synchronously, don't retain the
* reference across frames.
*/
interface TravelInfo {
/** [0, maxDetent] — the full resolved travel range in px, this frame. */
range: readonly [number, number];
/**
* 0-1 progress toward EACH configured detent, keyed by the detent's
* resolved height in px (not its spec — a spec like 'content' isn't a
* stable identity across resizes, but its resolved height this frame is
* what a consumer interpolates against).
*/
progressAtDetents: ReadonlyMap<number, number>;
}
//#endregion
//#region packages/scrollsheet/src/root.d.ts
/** Imperative escape hatch — see `actionsRef` on `<Sheet.Root>`. */
interface SheetActions {
open(): void;
close(): void;
/**
* Moves the sheet to `detent` through the same `setActiveDetent` path a
* controlled `activeDetent` change uses, so it animates identically. Warns
* in dev (once) if `detent` isn't one of this sheet's configured
* `detents` — only the panel's *rest position* resolves to the nearest
* configured detent in that case; `activeDetent`/`onActiveDetentChange`
* (and Handle's `aria-valuenow`/`aria-valuetext`) still reflect the
* literal value passed in here, not the resolved one.
*/
snapTo(detent: DetentSpec): void;
}
interface SheetRootProps {
children?: React.ReactNode;
/** Controlled open state. */
open?: boolean;
defaultOpen?: boolean;
onOpenChange?: (open: boolean) => void;
/**
* Fires once the open/close *transition* finishes (not just the state
* flip) — `true` when the sheet has fully settled open, `false` once it's
* fully closed and unmounted. Backed by the same phase timers that drive
* `data-scrollsheet-state`/`data-scrollsheet-side` (see Content) — no
* separate timer of its own.
*/
onOpenChangeComplete?: (open: boolean) => void;
/**
* Imperative escape hatch for cases a controlled `open`/`activeDetent`
* prop is awkward for (deep links, push notifications, a descendant
* component reaching up without prop-drilling): `ref.current?.close()` /
* `.open()` / `.snapTo(detent)`. Thin wrappers over the same `setOpen`/
* `setActiveDetent` the rest of the sheet uses — not a separate code path.
*/
actionsRef?: React.Ref<SheetActions>;
/**
* The stops the sheet can rest at, UISheetPresentationController-style.
* `'content'` (natural height), `'medium'` (50%), `'full'`, a 0–1 fraction,
* or `'320px'`.
* @default ['content']
*/
detents?: readonly DetentSpec[];
/** Controlled active detent (one of `detents`). */
activeDetent?: DetentSpec;
onActiveDetentChange?: (detent: DetentSpec) => void;
/**
* Master switch: when false, swipe/drag-to-dismiss, backdrop tap and Esc
* all stop closing the sheet. `escapeDismissible`/`backdropDismissible`
* below let you turn off just one of the pointer/keyboard paths while
* leaving the others (and swipe-to-dismiss, which only this prop gates)
* alone — both default to whatever `dismissible` is.
* @default true
*/
dismissible?: boolean;
/** Whether Esc closes the sheet. @default `dismissible` */
escapeDismissible?: boolean;
/** Whether tapping the backdrop/empty track closes the sheet. @default `dismissible` */
backdropDismissible?: boolean;
/** CSP nonce for the injected style tag. */
nonce?: string;
/**
* Blend the page's `<meta name="theme-color">` toward black as the sheet
* travels, so the browser chrome (iOS Safari tints both the status bar area
* and the bottom toolbar from this tag; Android tints the status bar) dims
* along with the backdrop. Every theme-color tag is dimmed from its own
* base color, so the usual media-scoped light/dark pair works and stays
* correct even if the OS scheme flips while the sheet is open. On a
* bottom-anchored attached sheet, the strip behind the bottom toolbar takes
* the panel's own color instead (via a fixed sentinel element), so the bar
* reads as part of the sheet, not the dimmed page. No-ops if no
* tag holds a parseable color, or on platforms that ignore theme-color.
* @default false
*/
themeColorDimming?: boolean;
/**
* Fires on every frame the sheet travels: revealed px, 0-1 progress
* (against the first detent, same number `--scrollsheet-progress` uses),
* and `info` — per-detent progress plus the resolved travel range. `info`
* is reused and mutated in place every frame (this is the highest-
* frequency callback in the library); read it synchronously, don't store
* the reference. Keep this handler cheap — it runs on every travel frame.
*/
onTravel?: (revealedPx: number, progress: number, info: TravelInfo) => void;
/**
* Which edge the sheet is anchored to, or `"center"` for a centered modal
* dialog: content-sized, consumer CSS owns width, zoom+fade instead of
* travel, no detents or drag. @default 'bottom'
*/
side?: Side | "center";
/**
* Overrides the resolved presentation once the viewport reaches
* `desktopBreakpoint` — `side` stays the base presentation below it.
* Resolved via a `matchMedia` subscription: server render and the first
* client paint both resolve to `side` (never read from `window` during
* render — that's what would warn on hydration), the desktop check lands
* after mount. Crossing the breakpoint while the sheet is open
* re-presents instantly, no morph. Unset: `side` applies at every width,
* today's behavior exactly.
*/
desktopSide?: Side | "center";
/**
* The min-width (px) `desktopSide` takes over at. Only meaningful paired
* with `desktopSide` — set alone, it warns once in dev and does nothing.
* @default 768
*/
desktopBreakpoint?: number;
/**
* When false, the page behind stays fully interactive and scrollable — no
* backdrop, no focus trap, no inert-ing the rest of the page. Renders on a
* `<div popover="manual">` (top layer, non-blocking) where supported,
* falling back to a non-modal `<dialog>` (`.show()`) otherwise.
* @default true
*/
modal?: boolean;
/**
* Applies an iOS-style card effect to the page element marked
* `data-scrollsheet-background`, driven by this sheet's own travel
* progress. `'scale'` scales/insets/rounds it; `'parallax'` only shifts it.
* No-ops if no element is marked.
*
* Left unset, a full-height bottom sheet in the mobile presentation gets
* `'scale'` — that is how the platform presents a sheet that covers the
* screen. Desktop drawers dock beside the page and never move it, and a
* detached (floating-card) sheet never claims the full screen, so neither
* defaults on. `'none'` opts out anywhere.
*/
backgroundEffect?: "scale" | "parallax" | "none";
/**
* Explicit target for backgroundEffect, instead of the default document-wide
* `[data-scrollsheet-background]` query. Pass the same ref you'd otherwise
* mark with the attribute. Skips the ownership check the implicit default
* applies (an explicit ref is unambiguous about which page element it
* means) — matches how an explicit `backgroundEffect` prop today already
* bypasses the implicit default's full-height/opener-containment check.
*/
backgroundRef?: React.RefObject<HTMLElement | null>;
/**
* Inner-content-overflow scrollbar style, for the panel's own scroll at
* max detent (or any content taller than the panel). `'overlay'`: a thin
* auto-hiding thumb (iOS/macOS-style). `'hidden'`: no thumb, no native
* scrollbar either. `'native'`: let the browser draw
* its own.
* @default 'overlay'
*/
scrollbar?: "overlay" | "hidden" | "native";
/**
* Scoped to exactly two things: the backdrop's opacity and
* `themeColorDimming`. Both stay fully transparent/undimmed while the
* sheet is at or below this detent's resolved height, then interpolate
* from there up to the next detent above it (or the tallest detent, if
* this is already the tallest). Unset (default): dims across the full
* range from closed to the first detent, today's behavior. Does *not*
* affect `onTravel`'s progress argument, `--scrollsheet-progress`,
* stacking recede, or `backgroundEffect` — those all track the sheet's
* full-range travel 1:1 regardless of this prop.
*/
largestUndimmedDetent?: DetentSpec;
/**
* Drag sessions (mouse, and touch on non-modal sheets) only start from
* `<Sheet.Handle>` — a pointerdown elsewhere on the panel no-ops. Modal
* touch (native scroll drives the gesture) is restricted via
* `touch-action` instead, except at the tallest detent, where the panel's
* own content scroll still needs it.
* @default false
*/
handleOnly?: boolean;
/**
* No drag sessions at all, handle included — detent changes are
* programmatic (`activeDetent`, `actionsRef`) or click/keyboard
* (`<Sheet.Handle>`) only. Esc and backdrop dismissal are unaffected.
* @default false
*/
disableDrag?: boolean;
/**
* A release never skips over an intermediate detent — the resolved target
* is clamped to the immediate neighbor of the detent the gesture started
* from (or, for a native-scroll settle, the last settled detent).
* @default false
*/
sequentialDetents?: boolean;
/**
* Fraction (0-1) of the first detent below which a release dismisses the
* sheet, replacing the built-in half-of-first-detent rule.
* @default 0.5
*/
closeThreshold?: number;
/**
* Bottom sheets only. When the software keyboard opens with a text field
* focused inside the sheet, promote to the tallest detent so the field
* has the whole remaining viewport to scroll within — a short peek detent
* can't show a field the keyboard would otherwise cover. Restores the
* previous detent on blur unless something else (a drag, a controlled
* change) moved the sheet in between. Gated on the keyboard actually
* appearing (a nonzero measured inset), never on focus alone, so a
* desktop click into an input doesn't expand anything. Promotion goes
* through the same `setActiveDetent` path a controlled change uses — a
* controlled sheet must apply `onActiveDetentChange` back to
* `activeDetent` or this prop has no visible effect.
* @default false
*/
keyboardExpands?: boolean;
/**
* Fires once a drag session resolves, with the real pointer event (the
* `buttons===0` recovery path may pass the triggering `pointermove`
* instead of a `pointerup`) and whether the sheet is staying open —
* `false` only when the release resolved to a dismiss.
*/
onRelease?: (event: PointerEvent, willRemainOpen: boolean) => void;
}
declare function Root({ children, open: openProp, defaultOpen, onOpenChange, onOpenChangeComplete, actionsRef, detents, activeDetent: activeDetentProp, onActiveDetentChange, dismissible, escapeDismissible, backdropDismissible, nonce, themeColorDimming, onTravel, side, desktopSide, desktopBreakpoint: desktopBreakpointProp, modal, backgroundEffect, backgroundRef, scrollbar, largestUndimmedDetent, handleOnly, disableDrag, sequentialDetents, closeThreshold, keyboardExpands, onRelease }: SheetRootProps): React.JSX.Element;
//#endregion
//#region packages/scrollsheet/src/trigger.d.ts
interface SheetTriggerProps extends React.ComponentProps<"button"> {
/**
* Render the child element instead of a `<button>`, merging the trigger's
* props (aria, onClick) into it — for custom button components or links.
*/
asChild?: boolean;
}
declare const Trigger: React.ForwardRefExoticComponent<Omit<SheetTriggerProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
//#endregion
//#region packages/scrollsheet/src/content.d.ts
interface SheetContentProps extends React.ComponentProps<"div"> {
/** Accessible name when no <Sheet.Title> is rendered. */
"aria-label"?: string;
/**
* Stretches [data-scrollsheet-body] to fill the panel (flex column, the
* body itself flex:1/min-height:0) instead of sizing to its natural content
* height. Promotes the docs "Full-height content" recipe into the library —
* write your own inner-scroll child (flex:1, overflow-y:auto) with no CSS
* of your own required on the wrapper.
* @default false
*/
fill?: boolean;
/**
* Render the child element instead of scrollsheet's own panel `<div>`,
* merging the panel's props (role, tabIndex, ref, `data-scrollsheet-*`)
* into it — matches Radix `Dialog.Content asChild`: the child replaces the
* panel outright rather than nesting inside it. The child must be a single
* non-Fragment element; whatever it was given as its own children is
* hoisted into the body wrapper below (detent measurement and stacking
* still need a real DOM descendant there regardless of which element ends
* up as the panel).
* If `children` isn't a single non-Fragment element (missing, a string,
* multiple elements, a `<>...</>` Fragment…), `asChild` is ignored for
* that render — the default panel `<div>` is used instead, with a
* one-time dev warning.
* @default false
*/
asChild?: boolean;
}
declare const Content: React.ForwardRefExoticComponent<Omit<SheetContentProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
//#endregion
//#region packages/scrollsheet/src/misc.d.ts
interface SheetTitleProps extends React.ComponentProps<"h2"> {
/** Render the child element instead of an `<h2>`, merging props into it. */
asChild?: boolean;
}
declare const Title: React.ForwardRefExoticComponent<Omit<SheetTitleProps, "ref"> & React.RefAttributes<HTMLHeadingElement>>;
interface SheetDescriptionProps extends React.ComponentProps<"p"> {
/** Render the child element instead of a `<p>`, merging props into it. */
asChild?: boolean;
}
declare const Description: React.ForwardRefExoticComponent<Omit<SheetDescriptionProps, "ref"> & React.RefAttributes<HTMLParagraphElement>>;
interface SheetCloseProps extends React.ComponentProps<"button"> {
/** Render the child element instead of a `<button>`, merging props into it. */
asChild?: boolean;
}
declare const Close: React.ForwardRefExoticComponent<Omit<SheetCloseProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
//#endregion
export { SheetTitleProps as a, SheetContentProps as c, Root as d, SheetActions as f, DetentSpec as g, Side as h, SheetDescriptionProps as i, SheetTriggerProps as l, TravelInfo as m, Description as n, Title as o, SheetRootProps as p, SheetCloseProps as r, Content as s, Close as t, Trigger as u };
"use client";
import * as React from "react";
//#region packages/scrollsheet/src/internal/dev-warn.ts
const warned = new Set();
function warnOnce(key, message) {
if (warned.has(key)) return;
warned.add(key);
console.warn(message);
}
//#endregion
//#region packages/scrollsheet/src/internal/env.ts
let cached = null;
function env() {
if (cached) return cached;
const hasCSS = typeof CSS !== "undefined" && typeof CSS.supports === "function";
cached = {
scrollTimeline: hasCSS && CSS.supports("animation-timeline: scroll()"),
scrollend: typeof window !== "undefined" && "onscrollend" in window,
linearEasing: hasCSS && CSS.supports("transition-timing-function", "linear(0, 1)")
};
return cached;
}
function hasDialogSupport() {
return typeof HTMLDialogElement !== "undefined" && "showModal" in HTMLDialogElement.prototype;
}
function hasPopoverSupport() {
return typeof HTMLElement !== "undefined" && "showPopover" in HTMLElement.prototype;
}
function hasClosedBySupport() {
return typeof HTMLDialogElement !== "undefined" && "closedBy" in HTMLDialogElement.prototype;
}
function hasCloseWatcherSupport() {
return typeof window !== "undefined" && "CloseWatcher" in window;
}
let warnedNoDialogSupport = false;
function warnMissingDialogSupport() {
if (warnedNoDialogSupport) return;
warnedNoDialogSupport = true;
console.warn("scrollsheet: this browser has no <dialog> support — falling back to a plain modal (no detents, drag, or animation). Content and dismissal work as normal.");
}
let warnedUndimmedDetentOutOfRange = false;
function warnLargestUndimmedDetentOutOfRange() {
if (warnedUndimmedDetentOutOfRange) return;
warnedUndimmedDetentOutOfRange = true;
console.warn("scrollsheet: largestUndimmedDetent resolved to a height above every configured detent — the backdrop and themeColorDimming will stay undimmed for nearly all of the sheet's travel. Choose a largestUndimmedDetent within the configured detents range.");
}
let warnedUnresolvableSnapToDetent = false;
function warnUnresolvableSnapToDetent() {
if (warnedUnresolvableSnapToDetent) return;
warnedUnresolvableSnapToDetent = true;
console.warn("scrollsheet: actionsRef.snapTo() was called with a detent spec that isn't in this sheet's `detents` list — the panel will rest at the nearest configured detent, but `activeDetent`/`onActiveDetentChange` (and Handle's aria-valuenow/aria-valuetext) will still reflect the literal spec you passed in, not the resolved one.");
}
let warnedContentAsChildInvalidChild = false;
function warnContentAsChildInvalidChild() {
if (warnedContentAsChildInvalidChild) return;
warnedContentAsChildInvalidChild = true;
console.warn("scrollsheet: Sheet.Content asChild expects children to be a single non-Fragment React element — falling back to the default panel <div> (asChild ignored) instead.");
}
function warnCoreStylesMissing() {
warnOnce("css", "scrollsheet: import 'scrollsheet/styles.css'");
}
function prefersReducedMotion() {
return typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
}
function warnDesktopBreakpointWithoutSide() {
warnOnce("desktop-breakpoint", "scrollsheet: desktopBreakpoint is set without desktopSide — it has no effect on its own.");
}
//#endregion
//#region packages/scrollsheet/src/internal/inject-styles.ts
function createStyleInjector(css, dataAttr) {
const injectedRoots = new WeakSet();
function injectInto(root, nonce) {
if (!css) return;
if (injectedRoots.has(root)) return;
const existing = root === document ? document.querySelector(`style[${dataAttr}]`) : root.querySelector(`style[${dataAttr}]`);
const marked = root;
if (existing || marked[Symbol.for(dataAttr)]) {
injectedRoots.add(root);
return;
}
const style = document.createElement("style");
style.setAttribute(dataAttr, "");
if (nonce) style.nonce = nonce;
style.textContent = css;
(root === document ? document.head : root).appendChild(style);
marked[Symbol.for(dataAttr)] = true;
injectedRoots.add(root);
}
function injectDocument(nonce) {
if (typeof document === "undefined") return;
injectInto(document, nonce);
}
function injectShadowRoot(root, nonce) {
if (!css) return;
if (typeof CSSStyleSheet === "undefined" || !("adoptedStyleSheets" in root)) {
injectInto(root, nonce);
return;
}
if (injectedRoots.has(root)) return;
const marker = Symbol.for(dataAttr);
const marked = root;
if (!marked[marker] && !root.querySelector(`style[${dataAttr}]`)) {
const sheet = new CSSStyleSheet();
sheet.replaceSync(css);
root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];
}
marked[marker] = true;
injectedRoots.add(root);
}
return {
injectDocument,
injectShadowRoot
};
}
//#endregion
//#region packages/scrollsheet/src/internal/use-close-watcher.ts
function useCloseWatcher({ present, nonModal, escapeDismissible, onClose }) {
const onCloseRef = React.useRef(onClose);
React.useInsertionEffect(() => {
onCloseRef.current = onClose;
});
React.useEffect(() => {
if (!present || !nonModal || !escapeDismissible) return;
if (!hasCloseWatcherSupport()) return;
if (typeof window.matchMedia !== "function" || !window.matchMedia("(hover: none) and (pointer: coarse)").matches) return;
const Ctor = window.CloseWatcher;
const watcher = new Ctor();
watcher.onclose = () => onCloseRef.current();
return () => watcher.destroy();
}, [
present,
nonModal,
escapeDismissible
]);
}
//#endregion
//#region packages/scrollsheet/src/internal/cn.ts
function cn(...classes) {
const joined = classes.filter(Boolean).join(" ");
return joined.length > 0 ? joined : void 0;
}
//#endregion
export { hasClosedBySupport as a, prefersReducedMotion as c, warnDesktopBreakpointWithoutSide as d, warnLargestUndimmedDetentOutOfRange as f, warnOnce as h, env as i, warnContentAsChildInvalidChild as l, warnUnresolvableSnapToDetent as m, useCloseWatcher as n, hasDialogSupport as o, warnMissingDialogSupport as p, createStyleInjector as r, hasPopoverSupport as s, cn as t, warnCoreStylesMissing as u };
"use client";
import { c as Slot, d as useSheetContext, i as Content$1, n as Description, r as Title, s as Trigger, t as Close, u as Root$1 } from "./misc-VkdphfSa.mjs";
import { h as warnOnce, t as cn } from "./cn-Cs7ZylHz.mjs";
import * as React from "react";
import { Fragment, jsx } from "react/jsx-runtime";
import { createPortal } from "react-dom";
//#region packages/scrollsheet/src/handle.tsx
function detentLabel(spec) {
if (spec === "full") return "Full";
if (spec === "medium") return "Half";
if (spec === "content") return "Fit content";
if (typeof spec === "number") return `${Math.round(spec * 100)}%`;
return spec ?? "";
}
const Handle$1 = React.forwardRef(function Handle({ asChild, onClick, onKeyDown, className, variant = "inside", ...props }, ref) {
const ctx = useSheetContext("Handle");
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => setMounted(true), []);
const outside = variant === "outside" && ctx.side === "bottom" && !(mounted && !ctx.canvasEl);
const variantAttr = outside ? "outside" : variant === "floating" ? "floating" : void 0;
const specs = ctx.detents;
const horizontal = ctx.side === "left" || ctx.side === "right";
const multi = specs.length >= 2;
const activeIndex = specs.findIndex((s) => Object.is(s, ctx.activeDetent));
const move = (delta) => {
if (!multi) return false;
const index = specs.findIndex((s) => Object.is(s, ctx.activeDetent));
const next = specs[index + delta];
if (next === void 0) return false;
ctx.setActiveDetent(next);
return true;
};
const sliderAria = multi ? {
role: "slider",
"aria-orientation": horizontal ? "horizontal" : "vertical",
"aria-valuemin": 0,
"aria-valuemax": specs.length - 1,
...activeIndex >= 0 ? {
"aria-valuenow": activeIndex,
"aria-valuetext": detentLabel(specs[activeIndex])
} : {}
} : {};
const button = jsx(asChild ? Slot : "button", {
...asChild ? {} : { type: "button" },
"aria-label": horizontal ? "Adjust sheet width" : "Adjust sheet height",
...sliderAria,
...props,
ref,
className: cn("scrollsheet-handle", className),
"data-scrollsheet-handle": true,
"data-scrollsheet-handle-variant": variantAttr,
onClick: (event) => {
onClick?.(event);
if (event.defaultPrevented) return;
if (!multi) return;
const index = specs.findIndex((s) => Object.is(s, ctx.activeDetent));
const next = specs[(index + 1) % specs.length];
if (next !== void 0) ctx.setActiveDetent(next);
},
onKeyDown: (event) => {
onKeyDown?.(event);
if (event.defaultPrevented) return;
const expandKey = horizontal ? "ArrowRight" : "ArrowUp";
const collapseKey = horizontal ? "ArrowLeft" : "ArrowDown";
if (event.key === expandKey) {
event.preventDefault();
move(1);
} else if (event.key === collapseKey) {
event.preventDefault();
if (!move(-1) && ctx.dismissible) ctx.setOpen(false);
} else if (multi && event.key === "Home") {
event.preventDefault();
const first = specs[0];
if (first !== void 0) ctx.setActiveDetent(first);
} else if (multi && event.key === "End") {
event.preventDefault();
const last = specs[specs.length - 1];
if (last !== void 0) ctx.setActiveDetent(last);
}
}
});
if (outside) return ctx.canvasEl ? createPortal(button, ctx.canvasEl) : null;
return button;
});
//#endregion
//#region packages/scrollsheet/src/drawer/index.tsx
const wrapperClaims = new Map();
const VAUL_CLOSE_THRESHOLD = .25;
const collectIgnored = (entries) => entries.filter(([, v]) => v !== void 0).map(([k]) => k);
function toDetentSpec(point) {
if (typeof point === "number") return point;
if (point === "fit-content" || point === "content") return "content";
if (/^\d+(\.\d+)?px$/.test(point)) return `${Number.parseFloat(point)}px`;
const parsed = Number.parseFloat(point);
return Number.isFinite(parsed) ? parsed : "content";
}
function resolveFadeFromIndex(snapPoints, fadeFromIndex) {
if (!snapPoints || snapPoints.length === 0) return void 0;
const point = snapPoints[fadeFromIndex ?? snapPoints.length - 1];
return point !== void 0 ? toDetentSpec(point) : void 0;
}
function resolveCloseThreshold(snapPoints, closeThreshold) {
if (snapPoints !== void 0 && snapPoints.length > 0) return void 0;
return Math.min(1, Math.max(0, 1 - (closeThreshold ?? VAUL_CLOSE_THRESHOLD)));
}
function composeOpenChange(onOpenChange, onClose) {
if (!onOpenChange && !onClose) return void 0;
return (next) => {
if (!next) onClose?.();
onOpenChange?.(next);
};
}
function Root(props) {
const { children, open, defaultOpen, onOpenChange, onClose, dismissible, snapPoints, activeSnapPoint, setActiveSnapPoint, onAnimationEnd, autoFocus, nested, direction, modal, shouldScaleBackground, setBackgroundColorOnScale, noBodyStyles, disablePreventScroll, preventScrollRestoration, repositionInputs, scrollLockTimeout, closeThreshold, fadeFromIndex, snapToSequentialPoint, handleOnly, onDrag, onRelease, container, fixed, actionsRef, backdropDismissible, escapeDismissible, keyboardExpands, onTravel, scrollbar } = props;
{
const ignored = collectIgnored([
["setBackgroundColorOnScale", setBackgroundColorOnScale],
["noBodyStyles", noBodyStyles],
["disablePreventScroll", disablePreventScroll],
["preventScrollRestoration", preventScrollRestoration],
["repositionInputs", repositionInputs],
["scrollLockTimeout", scrollLockTimeout],
["onDrag", onDrag],
["container", container],
["fixed", fixed],
["autoFocus", autoFocus],
["nested", nested]
]);
if (ignored.length > 0) warnOnce("root-ignored-props", `[scrollsheet Drawer] These <Drawer.Root> props from vaul have no effect in this compat layer and are ignored: ${ignored.join(", ")}. See the vaul migration notes in the README.${onDrag !== void 0 ? " onDrag: the native onTravel prop works on this same <Drawer.Root>." : ""}`);
}
const detents = snapPoints && snapPoints.length > 0 ? snapPoints.map(toDetentSpec) : void 0;
const activeDetent = activeSnapPoint != null ? toDetentSpec(activeSnapPoint) : void 0;
const largestUndimmedDetent = resolveFadeFromIndex(snapPoints, fadeFromIndex);
const resolvedCloseThreshold = resolveCloseThreshold(snapPoints, closeThreshold);
if (snapPoints !== void 0 && snapPoints.length > 0 && closeThreshold !== void 0) warnOnce("close-threshold-with-snap-points", "[scrollsheet Drawer] <Drawer.Root closeThreshold> has no effect when snapPoints is set — this matches real vaul, where closeThreshold is dead code once snapPoints exist (its onRelease returns through the snap-points branch before ever reading it). Use the native Sheet.Root `closeThreshold` (a live 0-1 fraction of the first detent, works alongside detents) if you want the upgrade.");
const handleRelease = onRelease ? (event, willRemainOpen) => onRelease(event, willRemainOpen) : void 0;
const handleOpenChange = composeOpenChange(onOpenChange, onClose);
const handleActiveDetentChange = (detent) => {
if (!setActiveSnapPoint) return;
const match = snapPoints?.find((point) => Object.is(toDetentSpec(point), detent));
setActiveSnapPoint(match ?? detent);
};
React.useEffect(() => {
if (!shouldScaleBackground) return;
const existing = document.querySelector("[data-scrollsheet-background]");
if (existing && !wrapperClaims.has(existing)) return;
const wrapper = existing ?? document.querySelector("[data-vaul-drawer-wrapper]");
if (!wrapper) return;
const count = wrapperClaims.get(wrapper) ?? 0;
wrapperClaims.set(wrapper, count + 1);
if (count === 0) wrapper.setAttribute("data-scrollsheet-background", "");
return () => {
const remaining = (wrapperClaims.get(wrapper) ?? 1) - 1;
if (remaining <= 0) {
wrapperClaims.delete(wrapper);
wrapper.removeAttribute("data-scrollsheet-background");
} else wrapperClaims.set(wrapper, remaining);
};
}, [shouldScaleBackground]);
return jsx(DirectionContext.Provider, {
value: direction ?? "bottom",
children: jsx(Root$1, {
open,
defaultOpen,
onOpenChange: handleOpenChange,
onOpenChangeComplete: onAnimationEnd,
dismissible,
detents,
activeDetent,
onActiveDetentChange: setActiveSnapPoint ? handleActiveDetentChange : void 0,
side: direction,
modal,
backgroundEffect: shouldScaleBackground ? "scale" : "none",
largestUndimmedDetent,
handleOnly,
sequentialDetents: snapToSequentialPoint,
closeThreshold: resolvedCloseThreshold,
onRelease: handleRelease,
actionsRef,
backdropDismissible,
escapeDismissible,
keyboardExpands,
onTravel,
scrollbar,
children
})
});
}
const DirectionContext = React.createContext("bottom");
const NestedRoot = Root;
function Portal({ children, container }) {
if (container !== void 0) warnOnce("portal-container", "[scrollsheet Drawer] <Drawer.Portal container> has no effect — scrollsheet's <dialog> always renders into the browser's top layer (effectively document.body), so there's no separate container to portal into.");
return jsx(Fragment, { children });
}
function Overlay(_props) {
return null;
}
function hasMultipleSnapPoints(detents) {
return detents.length > 1;
}
const Content = React.forwardRef(function Content({ onPointerDownOutside, onOpenAutoFocus, onEscapeKeyDown, onCloseAutoFocus, onInteractOutside, onFocusOutside, forceMount, ...props }, ref) {
const ctx = useSheetContext("Content");
const direction = React.useContext(DirectionContext);
const snapPointsActive = hasMultipleSnapPoints(ctx.detents);
{
const ignored = collectIgnored([
["onPointerDownOutside", onPointerDownOutside],
["onOpenAutoFocus", onOpenAutoFocus],
["onEscapeKeyDown", onEscapeKeyDown],
["onCloseAutoFocus", onCloseAutoFocus],
["onInteractOutside", onInteractOutside],
["onFocusOutside", onFocusOutside],
["forceMount", forceMount]
]);
if (ignored.length > 0) warnOnce("content-ignored-props", `[scrollsheet Drawer] These <Drawer.Content> props from vaul have no effect in this compat layer and are stripped before reaching the DOM: ${ignored.join(", ")}. See the vaul migration notes in the README.${onPointerDownOutside !== void 0 || onInteractOutside !== void 0 || onEscapeKeyDown !== void 0 ? " To block backdrop-tap dismissal, set backdropDismissible={false} on <Drawer.Root>; to block Esc, escapeDismissible={false}." : ""}`);
}
return jsx(Content$1, {
...props,
ref,
"data-vaul-drawer": "",
"data-vaul-drawer-direction": direction,
"data-vaul-snap-points": snapPointsActive ? "true" : "false"
});
});
function composeHandleClick(preventCycle, onClick) {
return (event) => {
onClick?.(event);
if (preventCycle) event.preventDefault();
};
}
const Handle = React.forwardRef(function Handle({ preventCycle, onClick, ...props }, ref) {
const ctx = useSheetContext("Handle");
return jsx(Handle$1, {
...props,
ref,
"data-vaul-handle": "",
"data-vaul-drawer-visible": ctx.open ? "true" : "false",
onClick: composeHandleClick(preventCycle, onClick)
});
});
const DrawerClose = React.forwardRef(function DrawerClose({ children, ...props }, ref) {
return jsx(Close, {
...props,
ref,
children: children ?? null
});
});
const Drawer = {
Root,
NestedRoot,
Trigger,
Portal,
Overlay,
Content,
Close: DrawerClose,
Title,
Description,
Handle
};
//#endregion
export { NestedRoot as a, Root as c, hasMultipleSnapPoints as d, resolveCloseThreshold as f, Handle as i, composeHandleClick as l, Handle$1 as m, Drawer as n, Overlay as o, resolveFadeFromIndex as p, DrawerClose as r, Portal as s, Content as t, composeOpenChange as u };

Sorry, the diff of this file is too big to display

"use client";
import { c as prefersReducedMotion, h as warnOnce, n as useCloseWatcher, r as createStyleInjector, t as cn } from "./cn-Cs7ZylHz.mjs";
import * as React from "react";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
import { createPortal } from "react-dom";
//#region packages/scrollsheet/src/toast/state.ts
let toasts = [];
const listeners = new Set();
let uid = 0;
const MAX_HISTORY_SIZE = 100;
let history = [];
function trimHistory() {
let toRemove = history.length - MAX_HISTORY_SIZE;
if (toRemove <= 0) return;
const liveIds = new Set(toasts.map((t) => t.id));
history = history.filter((record) => {
if (toRemove > 0 && !liveIds.has(record.id)) {
toRemove -= 1;
return false;
}
return true;
});
}
function recordHistory(record) {
const index = history.findIndex((t) => t.id === record.id);
if (index === -1) {
history = [...history, record];
trimHistory();
return;
}
history = history.map((t, i) => i === index ? record : t);
}
function nextId() {
uid += 1;
return uid;
}
function publish() {
for (const listener of listeners) listener();
}
function subscribe(listener) {
listeners.add(listener);
return () => listeners.delete(listener);
}
function getSnapshot() {
return toasts;
}
const EMPTY_TOASTS = [];
function getServerSnapshot() {
return EMPTY_TOASTS;
}
function upsert(id, patch, fallbackType) {
const resolvedId = id ?? nextId();
const index = toasts.findIndex((t) => t.id === resolvedId);
const type = patch.type ?? fallbackType;
let record;
if (index === -1) {
record = {
dismissible: true,
...patch,
id: resolvedId,
type,
createdAt: Date.now()
};
toasts = [...toasts, record];
} else {
record = {
...toasts[index],
...patch,
id: resolvedId,
type
};
toasts = toasts.map((t, i) => i === index ? record : t);
}
recordHistory(record);
publish();
return resolvedId;
}
function dismiss(id) {
if (id === void 0) {
const swept = new Set(toasts);
for (const t of swept) {
if (!toasts.includes(t)) continue;
t.onDismiss?.(t);
}
toasts = toasts.filter((t) => !swept.has(t));
publish();
return;
}
const existing = toasts.find((t) => t.id === id);
if (!existing) return id;
existing.onDismiss?.(existing);
toasts = toasts.filter((t) => t !== existing);
publish();
return id;
}
function expire(id) {
const existing = toasts.find((t) => t.id === id);
if (!existing) return;
existing.onAutoClose?.(existing);
toasts = toasts.filter((t) => t !== existing);
publish();
}
function baseToast(message, data) {
return upsert(data?.id, {
...data,
title: message
}, "default");
}
function withType(type) {
return (message, data) => upsert(data?.id, {
...data,
title: message
}, type);
}
const toastImpl = baseToast;
toastImpl.success = withType("success");
toastImpl.error = withType("error");
toastImpl.info = withType("info");
toastImpl.warning = withType("warning");
toastImpl.loading = withType("loading");
toastImpl.message = withType("default");
toastImpl.custom = (jsx, data) => {
const id = data?.id ?? nextId();
return upsert(id, {
...data,
jsx: jsx(id)
}, "default");
};
function isHttpResponse(value) {
return typeof value === "object" && value !== null && "ok" in value && typeof value.ok === "boolean" && "status" in value && typeof value.status === "number";
}
function isPromiseExtendedResult(value) {
return typeof value === "object" && value !== null && !React.isValidElement(value);
}
function applyPromiseSettlement(id, resolved, fallbackTitle, description, type) {
if (isPromiseExtendedResult(resolved)) {
const { message, ...rest } = resolved;
upsert(id, {
description,
...rest,
title: message ?? fallbackTitle,
type
}, type);
return;
}
upsert(id, {
title: resolved ?? fallbackTitle,
description,
type
}, type);
}
function promiseImpl(promiseOrFn, data) {
const { loading, success, error, finally: onFinally, description, ...rest } = data;
const id = upsert(data.id, {
...rest,
description: typeof description === "function" ? void 0 : description,
title: loading,
type: "loading"
}, "loading");
const settle = typeof promiseOrFn === "function" ? promiseOrFn() : promiseOrFn;
let settled;
const chain = settle.then(async (result) => {
if (isHttpResponse(result) && !result.ok) {
settled = {
ok: false,
reason: result
};
const statusMessage = `HTTP error! status: ${result.status}`;
const resolvedDescription = typeof description === "function" ? await description(statusMessage) : description;
const resolved = typeof error === "function" ? await error(statusMessage) : error;
applyPromiseSettlement(id, resolved, "Error", resolvedDescription, "error");
return;
}
if (result instanceof Error) {
settled = {
ok: false,
reason: result
};
const resolvedDescription = typeof description === "function" ? await description(result) : description;
const resolved = typeof error === "function" ? await error(result) : error;
applyPromiseSettlement(id, resolved, "Error", resolvedDescription, "error");
return;
}
settled = {
ok: true,
value: result
};
const resolvedDescription = typeof description === "function" ? await description(result) : description;
const resolved = typeof success === "function" ? await success(result) : success;
applyPromiseSettlement(id, resolved, "Success", resolvedDescription, "success");
}).catch(async (err) => {
settled = {
ok: false,
reason: err
};
const resolvedDescription = typeof description === "function" ? await description(err) : description;
const resolved = typeof error === "function" ? await error(err) : error;
applyPromiseSettlement(id, resolved, "Error", resolvedDescription, "error");
}).finally(() => onFinally?.());
const unwrap = () => chain.then(() => {
if (!settled) throw new Error("scrollsheet toast.promise: chain settled with no outcome");
if (settled.ok) return settled.value;
throw settled.reason;
});
return Object.assign(id, { unwrap });
}
toastImpl.promise = promiseImpl;
toastImpl.dismiss = dismiss;
toastImpl.getToasts = () => toasts;
toastImpl.getHistory = () => history;
const toast = toastImpl;
function useSonner() {
return { toasts: React.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) };
}
const useToasts = useSonner;
function selectToasterToasts(all, toasterId) {
return all.filter((t) => toasterId === void 0 ? t.toasterId === void 0 : t.toasterId === toasterId);
}
function selectPositionToasts(all, position, defaultPosition) {
return all.filter((t) => t.position === void 0 ? position === defaultPosition : t.position === position);
}
function selectToastWindow(toasts, visibleToasts) {
const newestFirst = [...toasts].reverse();
return {
visible: newestFirst.slice(0, visibleToasts),
hidden: newestFirst.slice(visibleToasts)
};
}
//#endregion
//#region packages/scrollsheet/src/toast/toast-styles.ts
const { injectDocument, injectShadowRoot } = createStyleInjector(`.scrollsheet-toast{border-radius:var(--scrollsheet-toast-radius,14px);color:#18181b;background:#fff;border:1px solid #00000014;align-items:flex-start;gap:10px;padding:16px;font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif;font-size:13px;display:flex;position:relative;box-shadow:0 4px 12px #0000001a}.scrollsheet-toast[data-scrollsheet-custom]{box-shadow:none;background:0 0;border:none;padding:0}.scrollsheet-toast-icon{color:#fff;background:#6b7280;border-radius:50%;flex:none;place-items:center;width:18px;height:18px;margin-top:1px;font-size:11px;line-height:1;display:grid}.scrollsheet-toast-icon[data-type=success]{background:#22c55e}.scrollsheet-toast-icon[data-type=error]{background:#ef4444}.scrollsheet-toast-icon[data-type=warning]{background:#f59e0b}.scrollsheet-toast-icon[data-type=info]{background:#3b82f6}.scrollsheet-toast-icon[data-type=loading]{background:0 0}.scrollsheet-toast-spinner{border:2px solid #00000026;border-top-color:#0000008c;border-radius:50%;width:14px;height:14px;animation:.6s linear infinite scrollsheet-toast-spin}@keyframes scrollsheet-toast-spin{to{transform:rotate(360deg)}}@media (prefers-reduced-motion:reduce){.scrollsheet-toast-spinner{animation-duration:1.6s}}.scrollsheet-toast-body{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.scrollsheet-toast-title{font-size:13px;font-weight:500;line-height:1.35}.scrollsheet-toast-description{color:#0009;font-size:13px;line-height:1.4}.scrollsheet-toast-actions{flex:none;align-items:center;gap:6px;display:flex}.scrollsheet-toast-action,.scrollsheet-toast-cancel{cursor:pointer;border:none;border-radius:4px;height:24px;padding:0 8px;font-size:12px;font-weight:500}.scrollsheet-toast-action{color:#fff;background:#18181b}.scrollsheet-toast-cancel{color:#18181b;background:#0000000f}.scrollsheet-toast-close{color:#00000080;cursor:pointer;background:0 0;border:none;border-radius:50%;flex:none;place-items:center;width:20px;height:20px;padding:0;font-size:14px;line-height:1;display:grid}.scrollsheet-toast-close:hover{background:#0000000f}[data-scrollsheet-toaster]{z-index:2147483647;width:min(356px, calc(100vw - 2 * var(--scrollsheet-toast-offset,var(--sonner-offset,24px))));outline:none;margin:0;padding:0;list-style:none;transition:transform .4s;position:fixed}[data-scrollsheet-toaster][data-x-position=right]{right:var(--scrollsheet-toast-offset,var(--sonner-offset,24px))}[data-scrollsheet-toaster][data-x-position=left]{left:var(--scrollsheet-toast-offset,var(--sonner-offset,24px))}[data-scrollsheet-toaster][data-x-position=center]{left:50%;transform:translate(-50%)}[data-scrollsheet-toaster][data-y-position=top]{top:var(--scrollsheet-toast-offset,var(--sonner-offset,24px))}[data-scrollsheet-toaster][data-y-position=bottom]{bottom:var(--scrollsheet-toast-offset,var(--sonner-offset,24px))}.scrollsheet-toast{--scrollsheet-toast-lift:-1;--scrollsheet-toast-y:translateY(100%);opacity:0;transform:var(--scrollsheet-toast-y);touch-action:none;box-sizing:border-box;overflow-wrap:anywhere;transition:transform .25s,opacity .25s,height .25s;position:absolute;left:0;right:0}.scrollsheet-toast[data-y-position=top]{--scrollsheet-toast-lift:1;--scrollsheet-toast-y:translateY(-100%);top:0}.scrollsheet-toast[data-y-position=bottom]{--scrollsheet-toast-lift:-1;--scrollsheet-toast-y:translateY(100%);bottom:0}.scrollsheet-toast[data-mounted]{--scrollsheet-toast-y:translateY(0);opacity:1}.scrollsheet-toast[data-expanded=false][data-front=false]{--scrollsheet-toast-y:translateY(calc(var(--scrollsheet-toast-lift) * var(--scrollsheet-toast-gap,var(--sonner-gap,14px)) * var(--scrollsheet-toast-toasts-before,var(--sonner-toasts-before,0)))) scale(calc(1 - var(--scrollsheet-toast-toasts-before,var(--sonner-toasts-before,0)) * .05));height:var(--scrollsheet-toast-front-height,var(--sonner-front-height,auto));overflow:hidden}.scrollsheet-toast[data-expanded=false][data-front=false]>*{opacity:0}.scrollsheet-toast[data-mounted][data-expanded=true]{--scrollsheet-toast-y:translateY(calc(var(--scrollsheet-toast-lift) * var(--scrollsheet-toast-stack-offset,var(--sonner-stack-offset,0px))));height:var(--scrollsheet-toast-initial-height,var(--sonner-initial-height,auto))}.scrollsheet-toast[data-visible=false]{opacity:0;pointer-events:none}.scrollsheet-toast[data-removed]{pointer-events:none}.scrollsheet-toast[data-removed][data-front=true]{--scrollsheet-toast-y:translateY(calc(var(--scrollsheet-toast-lift) * -100%));opacity:0}.scrollsheet-toast[data-removed][data-front=false][data-expanded=true]{--scrollsheet-toast-y:translateY(calc(var(--scrollsheet-toast-lift) * var(--scrollsheet-toast-stack-offset,var(--sonner-stack-offset,0px)) + var(--scrollsheet-toast-lift) * -100%));opacity:0}.scrollsheet-toast[data-removed][data-front=false][data-expanded=false]{--scrollsheet-toast-y:translateY(40%);opacity:0}.scrollsheet-toast[data-swiping=true]{transform:var(--scrollsheet-toast-y) translateY(var(--scrollsheet-toast-swipe-y,var(--sonner-swipe-y,0px))) translateX(var(--scrollsheet-toast-swipe-x,var(--sonner-swipe-x,0px)));transition:none}.scrollsheet-toast[data-swiped=true]{-webkit-user-select:none;user-select:none}.scrollsheet-toast[data-swipe-out=true]{animation:.2s ease-out forwards scrollsheet-toast-swipe-out}.scrollsheet-toast[data-swipe-direction=left]{--scrollsheet-toast-swipe-out-x:-1}.scrollsheet-toast[data-swipe-direction=right]{--scrollsheet-toast-swipe-out-x:1}.scrollsheet-toast[data-swipe-direction=up]{--scrollsheet-toast-swipe-out-y:-1}.scrollsheet-toast[data-swipe-direction=down]{--scrollsheet-toast-swipe-out-y:1}@keyframes scrollsheet-toast-swipe-out{0%{transform:var(--scrollsheet-toast-y) translateY(var(--scrollsheet-toast-swipe-y,var(--sonner-swipe-y,0px))) translateX(var(--scrollsheet-toast-swipe-x,var(--sonner-swipe-x,0px)));opacity:1}to{transform:var(--scrollsheet-toast-y) translateY(calc(var(--scrollsheet-toast-swipe-y,var(--sonner-swipe-y,0px)) + var(--scrollsheet-toast-swipe-out-y,var(--sonner-swipe-out-y,0)) * 100%)) translateX(calc(var(--scrollsheet-toast-swipe-x,var(--sonner-swipe-x,0px)) + var(--scrollsheet-toast-swipe-out-x,var(--sonner-swipe-out-x,0)) * 100%));opacity:0}}@media (prefers-reduced-motion:reduce){[data-scrollsheet-toaster],.scrollsheet-toast{transition:none!important;animation:none!important}}@media (max-width:600px){[data-scrollsheet-toaster]{right:var(--scrollsheet-toast-mobile-offset,var(--sonner-mobile-offset,16px));left:var(--scrollsheet-toast-mobile-offset,var(--sonner-mobile-offset,16px));width:100%}[data-scrollsheet-toaster] .scrollsheet-toast{width:calc(100% - var(--scrollsheet-toast-mobile-offset,var(--sonner-mobile-offset,16px)) * 2);left:0;right:0}[data-scrollsheet-toaster][data-x-position=left]{left:var(--scrollsheet-toast-mobile-offset,var(--sonner-mobile-offset,16px))}[data-scrollsheet-toaster][data-y-position=bottom]{bottom:var(--scrollsheet-toast-mobile-offset,var(--sonner-mobile-offset,16px))}[data-scrollsheet-toaster][data-y-position=top]{top:var(--scrollsheet-toast-mobile-offset,var(--sonner-mobile-offset,16px))}[data-scrollsheet-toaster][data-x-position=center]{left:var(--scrollsheet-toast-mobile-offset,var(--sonner-mobile-offset,16px));right:var(--scrollsheet-toast-mobile-offset,var(--sonner-mobile-offset,16px));transform:none}}`, "data-sonner-toast-styles");
function injectToastStyles(nonce) {
injectDocument(nonce);
}
function injectToastStylesInto(root, nonce) {
injectShadowRoot(root, nonce);
}
//#endregion
//#region packages/scrollsheet/src/toast/toaster.tsx
function resolveVisibleToasts(count) {
if (count === void 0) return 3;
return Math.max(1, Math.floor(count));
}
function useMountedFlag() {
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => {
if (prefersReducedMotion()) {
setMounted(true);
return;
}
let raf2 = 0;
const raf1 = requestAnimationFrame(() => {
raf2 = requestAnimationFrame(() => setMounted(true));
});
return () => {
cancelAnimationFrame(raf1);
cancelAnimationFrame(raf2);
};
}, []);
return mounted;
}
function useIsDocumentHidden() {
const [hidden, setHidden] = React.useState(() => typeof document !== "undefined" && document.hidden);
React.useEffect(() => {
const onVisibilityChange = () => setHidden(document.hidden);
document.addEventListener("visibilitychange", onVisibilityChange);
return () => document.removeEventListener("visibilitychange", onVisibilityChange);
}, []);
return hidden;
}
const EMPTY_RECORDS = [];
const EMPTY_MAP = new Map();
function toIdMap(records) {
return new Map(records.map((r) => [r.id, r]));
}
function sameIds(a, b) {
if (a.size !== b.size) return false;
for (const id of a.keys()) if (!b.has(id)) return false;
return true;
}
function useToastExit(live, queued = EMPTY_RECORDS, exitMs = 220) {
const [exiting, setExiting] = React.useState(() => EMPTY_MAP);
const [prevLive, setPrevLive] = React.useState(() => toIdMap(live));
const timersRef = React.useRef(new Map());
const liveIds = new Set(live.map((r) => r.id));
let renderExiting = exiting;
if (!sameIds(prevLive, liveIds)) {
const queuedIds = new Set(queued.map((r) => r.id));
const justRemoved = [];
for (const [id, record] of prevLive) {
if (liveIds.has(id)) continue;
if (queuedIds.has(id)) continue;
justRemoved.push(record);
}
setPrevLive(toIdMap(live));
if (justRemoved.length > 0) {
const next = new Map(exiting);
for (const record of justRemoved) next.set(record.id, record);
renderExiting = next;
setExiting(next);
}
}
React.useEffect(() => {
const reduced = prefersReducedMotion();
for (const [id] of exiting) {
if (timersRef.current.has(id)) continue;
const timer = setTimeout(() => {
timersRef.current.delete(id);
setExiting((prev) => {
if (!prev.has(id)) return prev;
const next = new Map(prev);
next.delete(id);
return next;
});
}, reduced ? 0 : exitMs);
timersRef.current.set(id, timer);
}
for (const [id, timer] of timersRef.current) if (!exiting.has(id)) {
clearTimeout(timer);
timersRef.current.delete(id);
}
}, [exiting, exitMs]);
React.useEffect(() => {
const timers = timersRef.current;
return () => {
for (const timer of timers.values()) clearTimeout(timer);
timers.clear();
};
}, []);
const rows = live.map((record) => ({
record,
exiting: false
}));
for (const [id, record] of renderExiting) if (!liveIds.has(id)) rows.push({
record,
exiting: true
});
return rows;
}
//#endregion
//#region packages/scrollsheet/src/toast/shell/shell-selectors.ts
function splitPosition(position) {
const [y, x] = position.split("-");
return {
y,
x
};
}
function computePossiblePositions(defaultPosition, toasts) {
const seen = new Set([defaultPosition]);
for (const t of toasts) if (t.position) seen.add(t.position);
return [...seen];
}
function computeRowOffsets(newestFirst, heights, gap) {
let cumulativeHeight = 0;
return newestFirst.map((record, index) => {
const stackOffset = index * gap + cumulativeHeight;
cumulativeHeight += heights.get(record.id) ?? 0;
return {
id: record.id,
index,
toastsBefore: index,
stackOffset
};
});
}
function findNewestDismissible(toasts) {
for (let i = toasts.length - 1; i >= 0; i -= 1) {
const t = toasts[i];
if (t && t.dismissible !== false) return t;
}
}
const SWIPE_VELOCITY_THRESHOLD = .11;
function getDefaultSwipeDirections(position) {
const { y, x } = splitPosition(position);
const directions = [y];
if (x === "left" || x === "right") directions.push(x);
return directions;
}
function lockSwipeAxis(dx, dy) {
if (Math.abs(dx) <= 1 && Math.abs(dy) <= 1) return null;
return Math.abs(dx) > Math.abs(dy) ? "x" : "y";
}
function dampenSwipeDelta(delta) {
const dampened = delta * (1 / (1.5 + Math.abs(delta) / 20));
return Math.abs(dampened) < Math.abs(delta) ? dampened : delta;
}
function computeSwipeAxisAmount(axis, delta, directions) {
const negativeDir = axis === "x" ? "left" : "top";
const positiveDir = axis === "x" ? "right" : "bottom";
if (!(directions.includes(negativeDir) || directions.includes(positiveDir))) return 0;
return directions.includes(negativeDir) && delta < 0 || directions.includes(positiveDir) && delta > 0 ? delta : dampenSwipeDelta(delta);
}
function isSwipeReleaseAllowed(axis, amount, directions) {
if (axis === "x") return directions.includes(amount > 0 ? "right" : "left");
return directions.includes(amount > 0 ? "bottom" : "top");
}
function shouldDismissOnSwipeRelease(amount, velocity, thresholdPx = 45, velocityThreshold = SWIPE_VELOCITY_THRESHOLD) {
return Math.abs(amount) >= thresholdPx || velocity > velocityThreshold;
}
function resolveSwipeOutDirection(axis, amount) {
if (axis === "x") return amount > 0 ? "right" : "left";
return amount > 0 ? "down" : "up";
}
//#endregion
//#region packages/scrollsheet/src/toast/shell/use-toast-swipe.ts
const ZERO_AMOUNT = {
x: 0,
y: 0
};
function useToastSwipe(elRef, enabled, directions, onSwipeDismiss) {
const draggingRef = React.useRef(false);
const axisRef = React.useRef(null);
const startRef = React.useRef(ZERO_AMOUNT);
const amountRef = React.useRef(ZERO_AMOUNT);
const dragStartRef = React.useRef(0);
const onSwipeDismissRef = React.useRef(onSwipeDismiss);
onSwipeDismissRef.current = onSwipeDismiss;
const onPointerDown = React.useCallback((event) => {
if (!enabled || event.button !== 0) return;
if (event.target.tagName === "BUTTON") return;
const el = elRef.current;
if (!el) return;
draggingRef.current = true;
axisRef.current = null;
amountRef.current = ZERO_AMOUNT;
startRef.current = {
x: event.clientX,
y: event.clientY
};
dragStartRef.current = Date.now();
el.setPointerCapture(event.pointerId);
el.setAttribute("data-swiping", "true");
}, [enabled, elRef]);
const onPointerMove = React.useCallback((event) => {
if (!draggingRef.current) return;
const el = elRef.current;
if (!el) return;
if ((window.getSelection?.()?.toString().length ?? 0) > 0) return;
const start = startRef.current;
const dx = event.clientX - start.x;
const dy = event.clientY - start.y;
if (axisRef.current === null) axisRef.current = lockSwipeAxis(dx, dy);
const axis = axisRef.current;
if (axis === null) return;
const resolved = computeSwipeAxisAmount(axis, axis === "x" ? dx : dy, directions);
const amount = axis === "x" ? {
x: resolved,
y: 0
} : {
x: 0,
y: resolved
};
amountRef.current = amount;
if (resolved !== 0) el.setAttribute("data-swiped", "true");
el.style.setProperty("--scrollsheet-toast-swipe-x", `${amount.x}px`);
el.style.setProperty("--scrollsheet-toast-swipe-y", `${amount.y}px`);
}, [elRef, directions]);
const release = React.useCallback(() => {
if (!draggingRef.current) return;
draggingRef.current = false;
const el = elRef.current;
const axis = axisRef.current;
axisRef.current = null;
if (!el) return;
if (axis === null) {
el.setAttribute("data-swiping", "false");
return;
}
const amount = axis === "x" ? amountRef.current.x : amountRef.current.y;
const elapsed = Math.max(1, Date.now() - dragStartRef.current);
const velocity = Math.abs(amount) / elapsed;
if (isSwipeReleaseAllowed(axis, amount, directions) && shouldDismissOnSwipeRelease(amount, velocity)) {
const direction = resolveSwipeOutDirection(axis, amount);
el.setAttribute("data-swipe-direction", direction);
if (prefersReducedMotion()) {
el.setAttribute("data-swipe-out", "true");
onSwipeDismissRef.current();
return;
}
const onAnimEnd = () => {
el.removeEventListener("animationend", onAnimEnd);
onSwipeDismissRef.current();
};
el.addEventListener("animationend", onAnimEnd);
el.setAttribute("data-swipe-out", "true");
return;
}
el.setAttribute("data-swiping", "false");
el.setAttribute("data-swiped", "false");
el.style.setProperty("--scrollsheet-toast-swipe-x", "0px");
el.style.setProperty("--scrollsheet-toast-swipe-y", "0px");
}, [elRef, directions]);
return {
onPointerDown,
onPointerMove,
onPointerUp: React.useCallback((event) => {
if (elRef.current?.hasPointerCapture(event.pointerId)) elRef.current.releasePointerCapture(event.pointerId);
release();
}, [elRef, release]),
onPointerCancel: React.useCallback(() => release(), [release])
};
}
//#endregion
//#region packages/scrollsheet/src/toast/shell/toast-row.tsx
function ToastIcon({ type, icons, spinnerClassName }) {
if (type === "loading") {
if (icons?.loading) return jsx(Fragment, { children: icons.loading });
return jsx("span", { className: spinnerClassName });
}
switch (type) {
case "success": return jsx(Fragment, { children: icons?.success ?? "✓" });
case "error": return jsx(Fragment, { children: icons?.error ?? "✕" });
case "warning": return jsx(Fragment, { children: icons?.warning ?? "!" });
case "info": return jsx(Fragment, { children: icons?.info ?? "i" });
default: return null;
}
}
function ToastRow({ record, index, total, toastsBefore, stackOffset, height, frontHeight, visible, expanded, removed, yPosition, xPosition, closeButton, directions, onDismiss, observe, toasterClassNames, icons, toasterCloseButtonAriaLabel, toasterStyle, sonnerCompat }) {
const role = record.type === "error" ? "alert" : "status";
const mounted = useMountedFlag();
const elRef = React.useRef(null);
const dismissible = record.dismissible !== false;
const isFront = index === 0;
const setRefs = React.useCallback((el) => {
elRef.current = el;
observe(record.id, el);
}, [observe, record.id]);
const swipeHandlers = useToastSwipe(elRef, !removed && dismissible && record.type !== "loading", directions, () => onDismiss(record));
React.useEffect(() => {
const el = elRef.current;
if (el) el.inert = removed;
}, [removed]);
const style = {
"--scrollsheet-toast-toasts-before": toastsBefore,
"--scrollsheet-toast-stack-offset": `${stackOffset}px`,
"--scrollsheet-toast-front-height": frontHeight !== void 0 ? `${frontHeight}px` : "0px",
"--scrollsheet-toast-initial-height": height !== void 0 ? `${height}px` : "auto",
zIndex: Math.max(0, total - index),
...toasterStyle,
...record.style
};
const legacy = (name) => sonnerCompat ? name : void 0;
const rootClassName = cn("scrollsheet-toast", legacy("sonner-toast"), record.className, toasterClassNames?.toast, record.classNames?.toast, toasterClassNames?.default, toasterClassNames?.[record.type], record.classNames?.[record.type]);
const motionAttrs = {
"data-mounted": mounted ? "" : void 0,
"data-removed": removed ? "" : void 0,
"data-visible": visible ? "true" : "false",
"data-front": isFront ? "true" : "false",
"data-expanded": expanded ? "true" : "false",
"data-y-position": yPosition,
"data-x-position": xPosition,
"data-index": index,
"data-dismissible": dismissible ? "true" : "false",
"data-swiping": "false",
"data-swiped": "false",
"aria-hidden": removed ? "true" : void 0
};
if (record.jsx !== void 0) return jsx("div", {
ref: setRefs,
className: rootClassName,
"data-scrollsheet-toast": "",
"data-sonner-toast": sonnerCompat ? "" : void 0,
"data-scrollsheet-custom": "",
"data-sonner-custom": sonnerCompat ? "" : void 0,
"data-testid": record.testId,
role,
style,
...motionAttrs,
...swipeHandlers,
children: record.jsx
});
const showCloseButton = closeButton && dismissible && record.type !== "loading";
const hasActions = Boolean(record.action || record.cancel || showCloseButton);
const closeButtonAriaLabel = record.closeButtonAriaLabel ?? toasterCloseButtonAriaLabel ?? "Close toast";
return jsxs("div", {
ref: setRefs,
className: rootClassName,
"data-scrollsheet-toast": "",
"data-sonner-toast": sonnerCompat ? "" : void 0,
"data-type": record.type,
"data-testid": record.testId,
role,
style,
...motionAttrs,
...swipeHandlers,
children: [
jsx("span", {
className: cn("scrollsheet-toast-icon", legacy("sonner-toast-icon"), toasterClassNames?.icon, record.classNames?.icon),
"data-type": record.type,
"aria-hidden": "true",
children: record.icon ?? jsx(ToastIcon, {
type: record.type,
icons,
spinnerClassName: cn("scrollsheet-toast-spinner", legacy("sonner-toast-spinner"), toasterClassNames?.loader, record.classNames?.loader)
})
}),
jsxs("div", {
className: cn("scrollsheet-toast-body", legacy("sonner-toast-body"), toasterClassNames?.content, record.classNames?.content),
children: [record.title !== void 0 && jsx("div", {
className: cn("scrollsheet-toast-title", legacy("sonner-toast-title"), toasterClassNames?.title, record.classNames?.title),
children: record.title
}), record.description !== void 0 && jsx("div", {
className: cn("scrollsheet-toast-description", legacy("sonner-toast-description"), toasterClassNames?.description, record.classNames?.description),
children: record.description
})]
}),
hasActions && jsxs("div", {
className: cn("scrollsheet-toast-actions", legacy("sonner-toast-actions")),
children: [
record.cancel && jsx("button", {
type: "button",
className: cn("scrollsheet-toast-cancel", legacy("sonner-toast-cancel"), toasterClassNames?.cancelButton, record.classNames?.cancelButton),
onClick: (event) => {
if (!dismissible) return;
record.cancel?.onClick(event);
onDismiss(record);
},
children: record.cancel.label
}),
record.action && jsx("button", {
type: "button",
className: cn("scrollsheet-toast-action", legacy("sonner-toast-action"), toasterClassNames?.actionButton, record.classNames?.actionButton),
onClick: (event) => {
record.action?.onClick(event);
if (event.defaultPrevented) return;
onDismiss(record);
},
children: record.action.label
}),
showCloseButton && jsx("button", {
type: "button",
className: cn("scrollsheet-toast-close", legacy("sonner-toast-close"), toasterClassNames?.closeButton, record.classNames?.closeButton),
"aria-label": closeButtonAriaLabel,
onClick: () => onDismiss(record),
children: icons?.close ?? "×"
})
]
})
]
});
}
//#endregion
//#region packages/scrollsheet/src/toast/shell/use-toast-heights.ts
function useToastHeights() {
const [heights, setHeights] = React.useState(() => new Map());
const observersRef = React.useRef(new Map());
const setHeight = React.useCallback((id, height) => {
setHeights((prev) => prev.get(id) === height ? prev : new Map(prev).set(id, height));
}, []);
const forget = React.useCallback((id) => {
observersRef.current.get(id)?.disconnect();
observersRef.current.delete(id);
setHeights((prev) => {
if (!prev.has(id)) return prev;
const next = new Map(prev);
next.delete(id);
return next;
});
}, []);
const observe = React.useCallback((id, el) => {
const observers = observersRef.current;
observers.get(id)?.disconnect();
observers.delete(id);
if (!el || typeof ResizeObserver === "undefined") return;
const ro = new ResizeObserver(() => setHeight(id, el.getBoundingClientRect().height));
ro.observe(el);
observers.set(id, ro);
setHeight(id, el.getBoundingClientRect().height);
}, [setHeight]);
React.useEffect(() => {
const observers = observersRef.current;
return () => {
for (const ro of observers.values()) ro.disconnect();
observers.clear();
};
}, []);
return {
heights,
observe,
forget
};
}
//#endregion
//#region packages/scrollsheet/src/toast/shell/toaster-shell.tsx
const DEFAULT_HOTKEY = ["altKey", "KeyT"];
function PositionGroup({ position, defaultPosition, toasts, visibleToasts, gap, expanded, closeButton, swipeDirections, toastOptions, icons, className, baseStyle, sonnerCompat, registerListEl, dismissRow, onHoverEnter, onHoverLeave, onInteractingChange }) {
const directions = React.useMemo(() => swipeDirections ?? getDefaultSwipeDirections(position), [swipeDirections, position]);
const positionToasts = React.useMemo(() => selectPositionToasts(toasts, position, defaultPosition), [
toasts,
position,
defaultPosition
]);
const newestFirst = React.useMemo(() => [...positionToasts].reverse(), [positionToasts]);
const window_ = React.useMemo(() => selectToastWindow(positionToasts, visibleToasts), [positionToasts, visibleToasts]);
const visibleIds = React.useMemo(() => new Set(window_.visible.map((t) => t.id)), [window_]);
const { heights, observe, forget } = useToastHeights();
const exitRows = useToastExit(newestFirst);
const exitingIds = React.useMemo(() => new Set(exitRows.filter((r) => r.exiting).map((r) => r.record.id)), [exitRows]);
React.useEffect(() => {
for (const id of exitingIds) forget(id);
}, [exitingIds, forget]);
const offsets = React.useMemo(() => computeRowOffsets(newestFirst, heights, gap), [
newestFirst,
heights,
gap
]);
const offsetById = React.useMemo(() => new Map(offsets.map((o) => [o.id, o])), [offsets]);
const lastOffsetRef = React.useRef(new Map());
for (const o of offsets) lastOffsetRef.current.set(o.id, o);
if (lastOffsetRef.current.size > offsets.length) {
const keep = new Set([...offsets.map((o) => o.id), ...exitingIds]);
for (const id of [...lastOffsetRef.current.keys()]) if (!keep.has(id)) lastOffsetRef.current.delete(id);
}
const frontHeight = heights.get(newestFirst[0]?.id ?? "");
const { y, x } = splitPosition(position);
const listRef = React.useCallback((el) => registerListEl(position, el), [registerListEl, position]);
if (exitRows.length === 0) return null;
return jsx("ol", {
ref: listRef,
"data-scrollsheet-toaster": "",
"data-sonner-toaster": sonnerCompat ? "" : void 0,
"data-scrollsheet-theme": "light",
"data-sonner-theme": sonnerCompat ? "light" : void 0,
"data-y-position": y,
"data-x-position": x,
tabIndex: -1,
className,
style: {
...baseStyle,
"--scrollsheet-toast-gap": `${gap}px`,
"--scrollsheet-toast-front-height": frontHeight !== void 0 ? `${frontHeight}px` : void 0
},
onMouseEnter: onHoverEnter,
onMouseMove: onHoverEnter,
onMouseLeave: onHoverLeave,
onPointerDown: (event) => {
if (event.target.dataset.dismissible === "false") return;
onInteractingChange(true);
},
onPointerUp: () => onInteractingChange(false),
children: exitRows.map(({ record, exiting }) => {
const offset = offsetById.get(record.id) ?? lastOffsetRef.current.get(record.id);
const index = offset?.index ?? newestFirst.length;
return jsx(ToastRow, {
record,
index,
total: newestFirst.length,
toastsBefore: offset?.toastsBefore ?? index,
stackOffset: offset?.stackOffset ?? 0,
height: heights.get(record.id),
frontHeight,
visible: visibleIds.has(record.id),
expanded,
removed: exiting,
yPosition: y,
xPosition: x,
closeButton,
directions,
onDismiss: dismissRow,
observe,
toasterClassNames: toastOptions?.classNames,
sonnerCompat,
icons,
toasterCloseButtonAriaLabel: toastOptions?.closeButtonAriaLabel,
toasterStyle: toastOptions?.style
}, record.id);
})
});
}
function resolveShellPosition(position) {
return position ?? "bottom-right";
}
function ToasterShell({ id, toasterId, position, theme, richColors, expand: forceExpand, visibleToasts: visibleToastsProp, closeButton = false, duration: durationProp, gap = 14, offset, swipeDirections, toastOptions, icons, className, style, containerAriaLabel = "Notifications", hotkey = DEFAULT_HOTKEY, nonce, sonnerCompat = true }) {
const resolvedId = id ?? toasterId;
if (toasterId !== void 0) warnOnce("toaster-id-deprecated", "[scrollsheet Toaster] Toaster's \"toasterId\" prop is deprecated — use \"id\" instead.");
const { toasts: allToasts } = useSonner();
const toasts = React.useMemo(() => selectToasterToasts(allToasts, resolvedId), [allToasts, resolvedId]);
const defaultPosition = resolveShellPosition(position);
const possiblePositions = React.useMemo(() => computePossiblePositions(defaultPosition, toasts), [defaultPosition, toasts]);
const visibleToasts = resolveVisibleToasts(visibleToastsProp);
const defaultDuration = durationProp ?? toastOptions?.duration ?? 4e3;
if (theme !== void 0 && theme !== "light") warnOnce("shell-theme", `[scrollsheet Toaster] theme="${theme}" isn't implemented yet — v1 always renders the static light card real Sonner ships by default.`);
if (richColors) warnOnce("shell-rich-colors", "[scrollsheet Toaster] richColors isn't implemented yet — toasts render with their default icon color only.");
React.useEffect(() => {
injectToastStyles(nonce);
}, [nonce]);
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => setMounted(true), []);
const [hovered, setHovered] = React.useState(false);
const [hotkeyExpanded, setHotkeyExpanded] = React.useState(false);
const [interacting, setInteracting] = React.useState(false);
const expanded = Boolean(forceExpand) || hovered || hotkeyExpanded;
const isDocumentHidden = useIsDocumentHidden();
const handleHoverEnter = React.useCallback(() => setHovered(true), []);
const handleHoverLeave = React.useCallback(() => {
if (!interacting) setHovered(false);
}, [interacting]);
const dismissRow = React.useCallback((record) => {
toast.dismiss(record.id);
}, []);
const listElsRef = React.useRef(new Map());
const registerListEl = React.useCallback((pos, el) => {
if (el) listElsRef.current.set(pos, el);
else listElsRef.current.delete(pos);
}, []);
React.useEffect(() => {
if (hotkey.length === 0) return;
const handleKeyDown = (event) => {
if (hotkey.every((key) => event[key] || event.code === key)) {
setHotkeyExpanded(true);
const [firstList] = listElsRef.current.values();
firstList?.focus({ preventScroll: true });
}
if (event.code !== "Escape") return;
const active = document.activeElement;
const focusedEntry = [...listElsRef.current.entries()].find(([, el]) => active === el || el.contains(active));
if (!focusedEntry) return;
if (hotkeyExpanded) {
setHotkeyExpanded(false);
return;
}
const [focusedPosition] = focusedEntry;
const positionToasts = selectPositionToasts(toasts, focusedPosition, defaultPosition);
const front = positionToasts[positionToasts.length - 1];
if (front) dismissRow(front);
};
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, [
hotkey,
hotkeyExpanded,
toasts,
defaultPosition,
dismissRow
]);
const timersRef = React.useRef(new Map());
React.useEffect(() => {
const timers = timersRef.current;
const liveIds = new Set(toasts.map((t) => t.id));
for (const [id, entry] of timers) {
if (liveIds.has(id)) continue;
if (entry.timer) clearTimeout(entry.timer);
timers.delete(id);
}
const paused = expanded || interacting || isDocumentHidden;
for (const t of toasts) {
if (t.type === "loading") {
const stale = timers.get(t.id);
if (stale) {
if (stale.timer) clearTimeout(stale.timer);
timers.delete(t.id);
}
continue;
}
const ms = t.duration ?? toastOptions?.duration ?? defaultDuration;
const existing = timers.get(t.id);
if (!existing || existing.record !== t) {
if (existing?.timer) clearTimeout(existing.timer);
const entry = {
timer: null,
startedAt: 0,
remaining: ms,
record: t
};
timers.set(t.id, entry);
if (Number.isFinite(ms) && !paused) {
entry.startedAt = Date.now();
entry.timer = setTimeout(() => {
timersRef.current.delete(t.id);
expire(t.id);
}, ms);
}
continue;
}
if (paused) {
if (existing.timer) {
clearTimeout(existing.timer);
const elapsed = Date.now() - existing.startedAt;
existing.remaining = Math.max(0, existing.remaining - elapsed);
existing.timer = null;
}
continue;
}
if (existing.timer || !Number.isFinite(existing.remaining)) continue;
existing.startedAt = Date.now();
existing.timer = setTimeout(() => {
timersRef.current.delete(t.id);
expire(t.id);
}, existing.remaining);
}
}, [
toasts,
expanded,
interacting,
isDocumentHidden,
defaultDuration,
toastOptions?.duration
]);
React.useEffect(() => {
const timers = timersRef.current;
return () => {
for (const entry of timers.values()) if (entry.timer) clearTimeout(entry.timer);
timers.clear();
};
}, []);
useCloseWatcher({
present: toasts.length > 0,
nonModal: true,
escapeDismissible: true,
onClose: () => {
const target = findNewestDismissible(toasts);
if (target) dismissRow(target);
}
});
if (!mounted || typeof document === "undefined") return null;
const offsetValue = offset !== void 0 ? typeof offset === "number" ? `${offset}px` : offset : void 0;
const baseStyle = {
...style,
"--scrollsheet-toast-offset": offsetValue
};
return createPortal(jsx("section", {
"aria-label": containerAriaLabel,
tabIndex: -1,
"aria-live": "polite",
"aria-relevant": "additions text",
"aria-atomic": "false",
suppressHydrationWarning: true,
"data-react-aria-top-layer": "",
children: possiblePositions.map((groupPosition) => jsx(PositionGroup, {
position: groupPosition,
defaultPosition,
toasts,
visibleToasts,
gap,
expanded,
closeButton,
swipeDirections,
toastOptions,
icons,
className,
baseStyle,
sonnerCompat,
registerListEl,
dismissRow,
onHoverEnter: handleHoverEnter,
onHoverLeave: handleHoverLeave,
onInteractingChange: setInteracting
}, groupPosition))
}), document.body);
}
//#endregion
export { useSonner as a, toast as i, resolveVisibleToasts as n, useToasts as o, injectToastStylesInto as r, ToasterShell as t };
"use client";
import * as React from "react";
//#region packages/scrollsheet/src/internal/dev-warn.ts
const warned = new Set();
function warnOnce(key, message) {
if (warned.has(key)) return;
warned.add(key);
console.warn(message);
}
//#endregion
//#region packages/scrollsheet/src/internal/env.ts
let cached = null;
function env() {
if (cached) return cached;
const hasCSS = typeof CSS !== "undefined" && typeof CSS.supports === "function";
cached = {
scrollTimeline: hasCSS && CSS.supports("animation-timeline: scroll()"),
scrollend: typeof window !== "undefined" && "onscrollend" in window,
linearEasing: hasCSS && CSS.supports("transition-timing-function", "linear(0, 1)")
};
return cached;
}
function hasDialogSupport() {
return typeof HTMLDialogElement !== "undefined" && "showModal" in HTMLDialogElement.prototype;
}
function hasPopoverSupport() {
return typeof HTMLElement !== "undefined" && "showPopover" in HTMLElement.prototype;
}
function hasClosedBySupport() {
return typeof HTMLDialogElement !== "undefined" && "closedBy" in HTMLDialogElement.prototype;
}
function hasCloseWatcherSupport() {
return typeof window !== "undefined" && "CloseWatcher" in window;
}
let warnedNoDialogSupport = false;
function warnMissingDialogSupport() {
if (warnedNoDialogSupport) return;
warnedNoDialogSupport = true;
console.warn("scrollsheet: this browser has no <dialog> support — falling back to a plain modal (no detents, drag, or animation). Content and dismissal work as normal.");
}
let warnedUndimmedDetentOutOfRange = false;
function warnLargestUndimmedDetentOutOfRange() {
if (warnedUndimmedDetentOutOfRange) return;
warnedUndimmedDetentOutOfRange = true;
console.warn("scrollsheet: largestUndimmedDetent resolved to a height above every configured detent — the backdrop and themeColorDimming will stay undimmed for nearly all of the sheet's travel. Choose a largestUndimmedDetent within the configured detents range.");
}
let warnedUnresolvableSnapToDetent = false;
function warnUnresolvableSnapToDetent() {
if (warnedUnresolvableSnapToDetent) return;
warnedUnresolvableSnapToDetent = true;
console.warn("scrollsheet: actionsRef.snapTo() was called with a detent spec that isn't in this sheet's `detents` list — the panel will rest at the nearest configured detent, but `activeDetent`/`onActiveDetentChange` (and Handle's aria-valuenow/aria-valuetext) will still reflect the literal spec you passed in, not the resolved one.");
}
let warnedContentAsChildInvalidChild = false;
function warnContentAsChildInvalidChild() {
if (warnedContentAsChildInvalidChild) return;
warnedContentAsChildInvalidChild = true;
console.warn("scrollsheet: Sheet.Content asChild expects children to be a single non-Fragment React element — falling back to the default panel <div> (asChild ignored) instead.");
}
function warnCoreStylesMissing() {
warnOnce("css", "scrollsheet: import 'scrollsheet/styles.css'");
}
function prefersReducedMotion() {
return typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
}
function warnDesktopBreakpointWithoutSide() {
warnOnce("desktop-breakpoint", "scrollsheet: desktopBreakpoint is set without desktopSide — it has no effect on its own.");
}
//#endregion
//#region packages/scrollsheet/src/internal/inject-styles.ts
function createStyleInjector(css, dataAttr) {
const injectedRoots = new WeakSet();
function injectInto(root, nonce) {
if (!css) return;
if (injectedRoots.has(root)) return;
const existing = root === document ? document.querySelector(`style[${dataAttr}]`) : root.querySelector(`style[${dataAttr}]`);
const marked = root;
if (existing || marked[Symbol.for(dataAttr)]) {
injectedRoots.add(root);
return;
}
const style = document.createElement("style");
style.setAttribute(dataAttr, "");
if (nonce) style.nonce = nonce;
style.textContent = css;
(root === document ? document.head : root).appendChild(style);
marked[Symbol.for(dataAttr)] = true;
injectedRoots.add(root);
}
function injectDocument(nonce) {
if (typeof document === "undefined") return;
injectInto(document, nonce);
}
function injectShadowRoot(root, nonce) {
if (!css) return;
if (typeof CSSStyleSheet === "undefined" || !("adoptedStyleSheets" in root)) {
injectInto(root, nonce);
return;
}
if (injectedRoots.has(root)) return;
const marker = Symbol.for(dataAttr);
const marked = root;
if (!marked[marker] && !root.querySelector(`style[${dataAttr}]`)) {
const sheet = new CSSStyleSheet();
sheet.replaceSync(css);
root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];
}
marked[marker] = true;
injectedRoots.add(root);
}
return {
injectDocument,
injectShadowRoot
};
}
//#endregion
//#region packages/scrollsheet/src/internal/use-close-watcher.ts
function useCloseWatcher({ present, nonModal, escapeDismissible, onClose }) {
const onCloseRef = React.useRef(onClose);
React.useInsertionEffect(() => {
onCloseRef.current = onClose;
});
React.useEffect(() => {
if (!present || !nonModal || !escapeDismissible) return;
if (!hasCloseWatcherSupport()) return;
if (typeof window.matchMedia !== "function" || !window.matchMedia("(hover: none) and (pointer: coarse)").matches) return;
const Ctor = window.CloseWatcher;
const watcher = new Ctor();
watcher.onclose = () => onCloseRef.current();
return () => watcher.destroy();
}, [
present,
nonModal,
escapeDismissible
]);
}
//#endregion
//#region packages/scrollsheet/src/internal/cn.ts
function cn(...classes) {
const joined = classes.filter(Boolean).join(" ");
return joined.length > 0 ? joined : void 0;
}
//#endregion
export { hasClosedBySupport as a, prefersReducedMotion as c, warnDesktopBreakpointWithoutSide as d, warnLargestUndimmedDetentOutOfRange as f, warnOnce as h, env as i, warnContentAsChildInvalidChild as l, warnUnresolvableSnapToDetent as m, useCloseWatcher as n, hasDialogSupport as o, warnMissingDialogSupport as p, createStyleInjector as r, hasPopoverSupport as s, cn as t, warnCoreStylesMissing as u };
"use client";
import { i as Content$1, l as Root$1, n as Description, o as Trigger, r as Title, s as Slot, t as Close, u as useSheetContext } from "./misc-BJVcpoIS.mjs";
import { h as warnOnce, t as cn } from "./cn-Cs7ZylHz.mjs";
import * as React from "react";
import { Fragment, jsx } from "react/jsx-runtime";
import { createPortal } from "react-dom";
//#region packages/scrollsheet/src/handle.tsx
function detentLabel(spec) {
if (spec === "full") return "Full";
if (spec === "medium") return "Half";
if (spec === "content") return "Fit content";
if (typeof spec === "number") return `${Math.round(spec * 100)}%`;
return spec ?? "";
}
const Handle$1 = React.forwardRef(function Handle({ asChild, onClick, onKeyDown, className, variant = "inside", ...props }, ref) {
const ctx = useSheetContext("Handle");
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => setMounted(true), []);
const outside = variant === "outside" && ctx.side === "bottom" && !(mounted && !ctx.canvasEl);
const variantAttr = outside ? "outside" : variant === "floating" ? "floating" : void 0;
const specs = ctx.detents;
const horizontal = ctx.side === "left" || ctx.side === "right";
const multi = specs.length >= 2;
const activeIndex = specs.findIndex((s) => Object.is(s, ctx.activeDetent));
const move = (delta) => {
if (!multi) return false;
const index = specs.findIndex((s) => Object.is(s, ctx.activeDetent));
const next = specs[index + delta];
if (next === void 0) return false;
ctx.setActiveDetent(next);
return true;
};
const sliderAria = multi ? {
role: "slider",
"aria-orientation": horizontal ? "horizontal" : "vertical",
"aria-valuemin": 0,
"aria-valuemax": specs.length - 1,
...activeIndex >= 0 ? {
"aria-valuenow": activeIndex,
"aria-valuetext": detentLabel(specs[activeIndex])
} : {}
} : {};
const button = jsx(asChild ? Slot : "button", {
...asChild ? {} : { type: "button" },
"aria-label": horizontal ? "Adjust sheet width" : "Adjust sheet height",
...sliderAria,
...props,
ref,
className: cn("scrollsheet-handle", className),
"data-scrollsheet-handle": true,
"data-scrollsheet-handle-variant": variantAttr,
onClick: (event) => {
onClick?.(event);
if (event.defaultPrevented) return;
if (!multi) return;
const index = specs.findIndex((s) => Object.is(s, ctx.activeDetent));
const next = specs[(index + 1) % specs.length];
if (next !== void 0) ctx.setActiveDetent(next);
},
onKeyDown: (event) => {
onKeyDown?.(event);
if (event.defaultPrevented) return;
const expandKey = horizontal ? "ArrowRight" : "ArrowUp";
const collapseKey = horizontal ? "ArrowLeft" : "ArrowDown";
if (event.key === expandKey) {
event.preventDefault();
move(1);
} else if (event.key === collapseKey) {
event.preventDefault();
if (!move(-1) && ctx.dismissible) ctx.setOpen(false);
} else if (multi && event.key === "Home") {
event.preventDefault();
const first = specs[0];
if (first !== void 0) ctx.setActiveDetent(first);
} else if (multi && event.key === "End") {
event.preventDefault();
const last = specs[specs.length - 1];
if (last !== void 0) ctx.setActiveDetent(last);
}
}
});
if (outside) return ctx.canvasEl ? createPortal(button, ctx.canvasEl) : null;
return button;
});
//#endregion
//#region packages/scrollsheet/src/drawer/index.tsx
const wrapperClaims = new Map();
const VAUL_CLOSE_THRESHOLD = .25;
const collectIgnored = (entries) => entries.filter(([, v]) => v !== void 0).map(([k]) => k);
function toDetentSpec(point) {
if (typeof point === "number") return point;
if (point === "fit-content" || point === "content") return "content";
if (/^\d+(\.\d+)?px$/.test(point)) return `${Number.parseFloat(point)}px`;
const parsed = Number.parseFloat(point);
return Number.isFinite(parsed) ? parsed : "content";
}
function resolveFadeFromIndex(snapPoints, fadeFromIndex) {
if (!snapPoints || snapPoints.length === 0) return void 0;
const point = snapPoints[fadeFromIndex ?? snapPoints.length - 1];
return point !== void 0 ? toDetentSpec(point) : void 0;
}
function resolveCloseThreshold(snapPoints, closeThreshold) {
if (snapPoints !== void 0 && snapPoints.length > 0) return void 0;
return Math.min(1, Math.max(0, 1 - (closeThreshold ?? VAUL_CLOSE_THRESHOLD)));
}
function composeOpenChange(onOpenChange, onClose) {
if (!onOpenChange && !onClose) return void 0;
return (next) => {
if (!next) onClose?.();
onOpenChange?.(next);
};
}
function Root(props) {
const { children, open, defaultOpen, onOpenChange, onClose, dismissible, snapPoints, activeSnapPoint, setActiveSnapPoint, onAnimationEnd, autoFocus, nested, direction, modal, shouldScaleBackground, setBackgroundColorOnScale, noBodyStyles, disablePreventScroll, preventScrollRestoration, repositionInputs, scrollLockTimeout, closeThreshold, fadeFromIndex, snapToSequentialPoint, handleOnly, onDrag, onRelease, container, fixed, actionsRef, backdropDismissible, escapeDismissible, keyboardExpands, onTravel, scrollbar } = props;
{
const ignored = collectIgnored([
["setBackgroundColorOnScale", setBackgroundColorOnScale],
["noBodyStyles", noBodyStyles],
["disablePreventScroll", disablePreventScroll],
["preventScrollRestoration", preventScrollRestoration],
["repositionInputs", repositionInputs],
["scrollLockTimeout", scrollLockTimeout],
["onDrag", onDrag],
["container", container],
["fixed", fixed],
["autoFocus", autoFocus],
["nested", nested]
]);
if (ignored.length > 0) warnOnce("root-ignored-props", `[scrollsheet Drawer] These <Drawer.Root> props from vaul have no effect in this compat layer and are ignored: ${ignored.join(", ")}. See the vaul migration notes in the README.${onDrag !== void 0 ? " onDrag: the native onTravel prop works on this same <Drawer.Root>." : ""}`);
}
const detents = snapPoints && snapPoints.length > 0 ? snapPoints.map(toDetentSpec) : void 0;
const activeDetent = activeSnapPoint != null ? toDetentSpec(activeSnapPoint) : void 0;
const largestUndimmedDetent = resolveFadeFromIndex(snapPoints, fadeFromIndex);
const resolvedCloseThreshold = resolveCloseThreshold(snapPoints, closeThreshold);
if (snapPoints !== void 0 && snapPoints.length > 0 && closeThreshold !== void 0) warnOnce("close-threshold-with-snap-points", "[scrollsheet Drawer] <Drawer.Root closeThreshold> has no effect when snapPoints is set — this matches real vaul, where closeThreshold is dead code once snapPoints exist (its onRelease returns through the snap-points branch before ever reading it). Use the native Sheet.Root `closeThreshold` (a live 0-1 fraction of the first detent, works alongside detents) if you want the upgrade.");
const handleRelease = onRelease ? (event, willRemainOpen) => onRelease(event, willRemainOpen) : void 0;
const handleOpenChange = composeOpenChange(onOpenChange, onClose);
const handleActiveDetentChange = (detent) => {
if (!setActiveSnapPoint) return;
const match = snapPoints?.find((point) => Object.is(toDetentSpec(point), detent));
setActiveSnapPoint(match ?? detent);
};
React.useEffect(() => {
if (!shouldScaleBackground) return;
const existing = document.querySelector("[data-scrollsheet-background]");
if (existing && !wrapperClaims.has(existing)) return;
const wrapper = existing ?? document.querySelector("[data-vaul-drawer-wrapper]");
if (!wrapper) return;
const count = wrapperClaims.get(wrapper) ?? 0;
wrapperClaims.set(wrapper, count + 1);
if (count === 0) wrapper.setAttribute("data-scrollsheet-background", "");
return () => {
const remaining = (wrapperClaims.get(wrapper) ?? 1) - 1;
if (remaining <= 0) {
wrapperClaims.delete(wrapper);
wrapper.removeAttribute("data-scrollsheet-background");
} else wrapperClaims.set(wrapper, remaining);
};
}, [shouldScaleBackground]);
return jsx(DirectionContext.Provider, {
value: direction ?? "bottom",
children: jsx(Root$1, {
open,
defaultOpen,
onOpenChange: handleOpenChange,
onOpenChangeComplete: onAnimationEnd,
dismissible,
detents,
activeDetent,
onActiveDetentChange: setActiveSnapPoint ? handleActiveDetentChange : void 0,
side: direction,
modal,
backgroundEffect: shouldScaleBackground ? "scale" : "none",
largestUndimmedDetent,
handleOnly,
sequentialDetents: snapToSequentialPoint,
closeThreshold: resolvedCloseThreshold,
onRelease: handleRelease,
actionsRef,
backdropDismissible,
escapeDismissible,
keyboardExpands,
onTravel,
scrollbar,
children
})
});
}
const DirectionContext = React.createContext("bottom");
const NestedRoot = Root;
function Portal({ children, container }) {
if (container !== void 0) warnOnce("portal-container", "[scrollsheet Drawer] <Drawer.Portal container> has no effect — scrollsheet's <dialog> always renders into the browser's top layer (effectively document.body), so there's no separate container to portal into.");
return jsx(Fragment, { children });
}
function Overlay(_props) {
return null;
}
function hasMultipleSnapPoints(detents) {
return detents.length > 1;
}
const Content = React.forwardRef(function Content({ onPointerDownOutside, onOpenAutoFocus, onEscapeKeyDown, onCloseAutoFocus, onInteractOutside, onFocusOutside, forceMount, ...props }, ref) {
const ctx = useSheetContext("Content");
const direction = React.useContext(DirectionContext);
const snapPointsActive = hasMultipleSnapPoints(ctx.detents);
{
const ignored = collectIgnored([
["onPointerDownOutside", onPointerDownOutside],
["onOpenAutoFocus", onOpenAutoFocus],
["onEscapeKeyDown", onEscapeKeyDown],
["onCloseAutoFocus", onCloseAutoFocus],
["onInteractOutside", onInteractOutside],
["onFocusOutside", onFocusOutside],
["forceMount", forceMount]
]);
if (ignored.length > 0) warnOnce("content-ignored-props", `[scrollsheet Drawer] These <Drawer.Content> props from vaul have no effect in this compat layer and are stripped before reaching the DOM: ${ignored.join(", ")}. See the vaul migration notes in the README.${onPointerDownOutside !== void 0 || onInteractOutside !== void 0 || onEscapeKeyDown !== void 0 ? " To block backdrop-tap dismissal, set backdropDismissible={false} on <Drawer.Root>; to block Esc, escapeDismissible={false}." : ""}`);
}
return jsx(Content$1, {
...props,
ref,
"data-vaul-drawer": "",
"data-vaul-drawer-direction": direction,
"data-vaul-snap-points": snapPointsActive ? "true" : "false"
});
});
function composeHandleClick(preventCycle, onClick) {
return (event) => {
onClick?.(event);
if (preventCycle) event.preventDefault();
};
}
const Handle = React.forwardRef(function Handle({ preventCycle, onClick, ...props }, ref) {
const ctx = useSheetContext("Handle");
return jsx(Handle$1, {
...props,
ref,
"data-vaul-handle": "",
"data-vaul-drawer-visible": ctx.open ? "true" : "false",
onClick: composeHandleClick(preventCycle, onClick)
});
});
const DrawerClose = React.forwardRef(function DrawerClose({ children, ...props }, ref) {
return jsx(Close, {
...props,
ref,
children: children ?? null
});
});
const Drawer = {
Root,
NestedRoot,
Trigger,
Portal,
Overlay,
Content,
Close: DrawerClose,
Title,
Description,
Handle
};
//#endregion
export { NestedRoot as a, Root as c, hasMultipleSnapPoints as d, resolveCloseThreshold as f, Handle as i, composeHandleClick as l, Handle$1 as m, Drawer as n, Overlay as o, resolveFadeFromIndex as p, DrawerClose as r, Portal as s, Content as t, composeOpenChange as u };

Sorry, the diff of this file is too big to display

"use client";
import { c as prefersReducedMotion, h as warnOnce, n as useCloseWatcher, r as createStyleInjector, t as cn } from "./cn-Cs7ZylHz.mjs";
import * as React from "react";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
import { createPortal } from "react-dom";
//#region packages/scrollsheet/src/toast/state.ts
let toasts = [];
const listeners = new Set();
let uid = 0;
const MAX_HISTORY_SIZE = 100;
let history = [];
function trimHistory() {
let toRemove = history.length - MAX_HISTORY_SIZE;
if (toRemove <= 0) return;
const liveIds = new Set(toasts.map((t) => t.id));
history = history.filter((record) => {
if (toRemove > 0 && !liveIds.has(record.id)) {
toRemove -= 1;
return false;
}
return true;
});
}
function recordHistory(record) {
const index = history.findIndex((t) => t.id === record.id);
if (index === -1) {
history = [...history, record];
trimHistory();
return;
}
history = history.map((t, i) => i === index ? record : t);
}
function nextId() {
uid += 1;
return uid;
}
function publish() {
for (const listener of listeners) listener();
}
function subscribe(listener) {
listeners.add(listener);
return () => listeners.delete(listener);
}
function getSnapshot() {
return toasts;
}
const EMPTY_TOASTS = [];
function getServerSnapshot() {
return EMPTY_TOASTS;
}
function upsert(id, patch, fallbackType) {
const resolvedId = id ?? nextId();
const index = toasts.findIndex((t) => t.id === resolvedId);
const type = patch.type ?? fallbackType;
let record;
if (index === -1) {
record = {
dismissible: true,
...patch,
id: resolvedId,
type,
createdAt: Date.now()
};
toasts = [...toasts, record];
} else {
record = {
...toasts[index],
...patch,
id: resolvedId,
type
};
toasts = toasts.map((t, i) => i === index ? record : t);
}
recordHistory(record);
publish();
return resolvedId;
}
function dismiss(id) {
if (id === void 0) {
const swept = new Set(toasts);
for (const t of swept) {
if (!toasts.includes(t)) continue;
t.onDismiss?.(t);
}
toasts = toasts.filter((t) => !swept.has(t));
publish();
return;
}
const existing = toasts.find((t) => t.id === id);
if (!existing) return id;
existing.onDismiss?.(existing);
toasts = toasts.filter((t) => t !== existing);
publish();
return id;
}
function expire(id) {
const existing = toasts.find((t) => t.id === id);
if (!existing) return;
existing.onAutoClose?.(existing);
toasts = toasts.filter((t) => t !== existing);
publish();
}
function baseToast(message, data) {
return upsert(data?.id, {
...data,
title: message
}, "default");
}
function withType(type) {
return (message, data) => upsert(data?.id, {
...data,
title: message
}, type);
}
const toastImpl = baseToast;
toastImpl.success = withType("success");
toastImpl.error = withType("error");
toastImpl.info = withType("info");
toastImpl.warning = withType("warning");
toastImpl.loading = withType("loading");
toastImpl.message = withType("default");
toastImpl.custom = (jsx, data) => {
const id = data?.id ?? nextId();
return upsert(id, {
...data,
jsx: jsx(id)
}, "default");
};
function isHttpResponse(value) {
return typeof value === "object" && value !== null && "ok" in value && typeof value.ok === "boolean" && "status" in value && typeof value.status === "number";
}
function isPromiseExtendedResult(value) {
return typeof value === "object" && value !== null && !React.isValidElement(value);
}
function applyPromiseSettlement(id, resolved, fallbackTitle, description, type) {
if (isPromiseExtendedResult(resolved)) {
const { message, ...rest } = resolved;
upsert(id, {
description,
...rest,
title: message ?? fallbackTitle,
type
}, type);
return;
}
upsert(id, {
title: resolved ?? fallbackTitle,
description,
type
}, type);
}
function promiseImpl(promiseOrFn, data) {
const { loading, success, error, finally: onFinally, description, ...rest } = data;
const id = upsert(data.id, {
...rest,
description: typeof description === "function" ? void 0 : description,
title: loading,
type: "loading"
}, "loading");
const settle = typeof promiseOrFn === "function" ? promiseOrFn() : promiseOrFn;
let settled;
const chain = settle.then(async (result) => {
if (isHttpResponse(result) && !result.ok) {
settled = {
ok: false,
reason: result
};
const statusMessage = `HTTP error! status: ${result.status}`;
const resolvedDescription = typeof description === "function" ? await description(statusMessage) : description;
const resolved = typeof error === "function" ? await error(statusMessage) : error;
applyPromiseSettlement(id, resolved, "Error", resolvedDescription, "error");
return;
}
if (result instanceof Error) {
settled = {
ok: false,
reason: result
};
const resolvedDescription = typeof description === "function" ? await description(result) : description;
const resolved = typeof error === "function" ? await error(result) : error;
applyPromiseSettlement(id, resolved, "Error", resolvedDescription, "error");
return;
}
settled = {
ok: true,
value: result
};
const resolvedDescription = typeof description === "function" ? await description(result) : description;
const resolved = typeof success === "function" ? await success(result) : success;
applyPromiseSettlement(id, resolved, "Success", resolvedDescription, "success");
}).catch(async (err) => {
settled = {
ok: false,
reason: err
};
const resolvedDescription = typeof description === "function" ? await description(err) : description;
const resolved = typeof error === "function" ? await error(err) : error;
applyPromiseSettlement(id, resolved, "Error", resolvedDescription, "error");
}).finally(() => onFinally?.());
const unwrap = () => chain.then(() => {
if (!settled) throw new Error("scrollsheet toast.promise: chain settled with no outcome");
if (settled.ok) return settled.value;
throw settled.reason;
});
return Object.assign(id, { unwrap });
}
toastImpl.promise = promiseImpl;
toastImpl.dismiss = dismiss;
toastImpl.getToasts = () => toasts;
toastImpl.getHistory = () => history;
const toast = toastImpl;
function useSonner() {
return { toasts: React.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) };
}
const useToasts = useSonner;
function selectToasterToasts(all, toasterId) {
return all.filter((t) => toasterId === void 0 ? t.toasterId === void 0 : t.toasterId === toasterId);
}
function selectPositionToasts(all, position, defaultPosition) {
return all.filter((t) => t.position === void 0 ? position === defaultPosition : t.position === position);
}
function selectToastWindow(toasts, visibleToasts) {
const newestFirst = [...toasts].reverse();
return {
visible: newestFirst.slice(0, visibleToasts),
hidden: newestFirst.slice(visibleToasts)
};
}
//#endregion
//#region packages/scrollsheet/src/toast/toast-styles.ts
const { injectDocument, injectShadowRoot } = createStyleInjector("", "data-sonner-toast-styles");
function injectToastStyles(nonce) {
injectDocument(nonce);
}
function injectToastStylesInto(root, nonce) {
injectShadowRoot(root, nonce);
}
//#endregion
//#region packages/scrollsheet/src/toast/toaster.tsx
function resolveVisibleToasts(count) {
if (count === void 0) return 3;
return Math.max(1, Math.floor(count));
}
function useMountedFlag() {
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => {
if (prefersReducedMotion()) {
setMounted(true);
return;
}
let raf2 = 0;
const raf1 = requestAnimationFrame(() => {
raf2 = requestAnimationFrame(() => setMounted(true));
});
return () => {
cancelAnimationFrame(raf1);
cancelAnimationFrame(raf2);
};
}, []);
return mounted;
}
function useIsDocumentHidden() {
const [hidden, setHidden] = React.useState(() => typeof document !== "undefined" && document.hidden);
React.useEffect(() => {
const onVisibilityChange = () => setHidden(document.hidden);
document.addEventListener("visibilitychange", onVisibilityChange);
return () => document.removeEventListener("visibilitychange", onVisibilityChange);
}, []);
return hidden;
}
const EMPTY_RECORDS = [];
const EMPTY_MAP = new Map();
function toIdMap(records) {
return new Map(records.map((r) => [r.id, r]));
}
function sameIds(a, b) {
if (a.size !== b.size) return false;
for (const id of a.keys()) if (!b.has(id)) return false;
return true;
}
function useToastExit(live, queued = EMPTY_RECORDS, exitMs = 220) {
const [exiting, setExiting] = React.useState(() => EMPTY_MAP);
const [prevLive, setPrevLive] = React.useState(() => toIdMap(live));
const timersRef = React.useRef(new Map());
const liveIds = new Set(live.map((r) => r.id));
let renderExiting = exiting;
if (!sameIds(prevLive, liveIds)) {
const queuedIds = new Set(queued.map((r) => r.id));
const justRemoved = [];
for (const [id, record] of prevLive) {
if (liveIds.has(id)) continue;
if (queuedIds.has(id)) continue;
justRemoved.push(record);
}
setPrevLive(toIdMap(live));
if (justRemoved.length > 0) {
const next = new Map(exiting);
for (const record of justRemoved) next.set(record.id, record);
renderExiting = next;
setExiting(next);
}
}
React.useEffect(() => {
const reduced = prefersReducedMotion();
for (const [id] of exiting) {
if (timersRef.current.has(id)) continue;
const timer = setTimeout(() => {
timersRef.current.delete(id);
setExiting((prev) => {
if (!prev.has(id)) return prev;
const next = new Map(prev);
next.delete(id);
return next;
});
}, reduced ? 0 : exitMs);
timersRef.current.set(id, timer);
}
for (const [id, timer] of timersRef.current) if (!exiting.has(id)) {
clearTimeout(timer);
timersRef.current.delete(id);
}
}, [exiting, exitMs]);
React.useEffect(() => {
const timers = timersRef.current;
return () => {
for (const timer of timers.values()) clearTimeout(timer);
timers.clear();
};
}, []);
const rows = live.map((record) => ({
record,
exiting: false
}));
for (const [id, record] of renderExiting) if (!liveIds.has(id)) rows.push({
record,
exiting: true
});
return rows;
}
//#endregion
//#region packages/scrollsheet/src/toast/shell/shell-selectors.ts
function splitPosition(position) {
const [y, x] = position.split("-");
return {
y,
x
};
}
function computePossiblePositions(defaultPosition, toasts) {
const seen = new Set([defaultPosition]);
for (const t of toasts) if (t.position) seen.add(t.position);
return [...seen];
}
function computeRowOffsets(newestFirst, heights, gap) {
let cumulativeHeight = 0;
return newestFirst.map((record, index) => {
const stackOffset = index * gap + cumulativeHeight;
cumulativeHeight += heights.get(record.id) ?? 0;
return {
id: record.id,
index,
toastsBefore: index,
stackOffset
};
});
}
function findNewestDismissible(toasts) {
for (let i = toasts.length - 1; i >= 0; i -= 1) {
const t = toasts[i];
if (t && t.dismissible !== false) return t;
}
}
const SWIPE_VELOCITY_THRESHOLD = .11;
function getDefaultSwipeDirections(position) {
const { y, x } = splitPosition(position);
const directions = [y];
if (x === "left" || x === "right") directions.push(x);
return directions;
}
function lockSwipeAxis(dx, dy) {
if (Math.abs(dx) <= 1 && Math.abs(dy) <= 1) return null;
return Math.abs(dx) > Math.abs(dy) ? "x" : "y";
}
function dampenSwipeDelta(delta) {
const dampened = delta * (1 / (1.5 + Math.abs(delta) / 20));
return Math.abs(dampened) < Math.abs(delta) ? dampened : delta;
}
function computeSwipeAxisAmount(axis, delta, directions) {
const negativeDir = axis === "x" ? "left" : "top";
const positiveDir = axis === "x" ? "right" : "bottom";
if (!(directions.includes(negativeDir) || directions.includes(positiveDir))) return 0;
return directions.includes(negativeDir) && delta < 0 || directions.includes(positiveDir) && delta > 0 ? delta : dampenSwipeDelta(delta);
}
function isSwipeReleaseAllowed(axis, amount, directions) {
if (axis === "x") return directions.includes(amount > 0 ? "right" : "left");
return directions.includes(amount > 0 ? "bottom" : "top");
}
function shouldDismissOnSwipeRelease(amount, velocity, thresholdPx = 45, velocityThreshold = SWIPE_VELOCITY_THRESHOLD) {
return Math.abs(amount) >= thresholdPx || velocity > velocityThreshold;
}
function resolveSwipeOutDirection(axis, amount) {
if (axis === "x") return amount > 0 ? "right" : "left";
return amount > 0 ? "down" : "up";
}
//#endregion
//#region packages/scrollsheet/src/toast/shell/use-toast-swipe.ts
const ZERO_AMOUNT = {
x: 0,
y: 0
};
function useToastSwipe(elRef, enabled, directions, onSwipeDismiss) {
const draggingRef = React.useRef(false);
const axisRef = React.useRef(null);
const startRef = React.useRef(ZERO_AMOUNT);
const amountRef = React.useRef(ZERO_AMOUNT);
const dragStartRef = React.useRef(0);
const onSwipeDismissRef = React.useRef(onSwipeDismiss);
onSwipeDismissRef.current = onSwipeDismiss;
const onPointerDown = React.useCallback((event) => {
if (!enabled || event.button !== 0) return;
if (event.target.tagName === "BUTTON") return;
const el = elRef.current;
if (!el) return;
draggingRef.current = true;
axisRef.current = null;
amountRef.current = ZERO_AMOUNT;
startRef.current = {
x: event.clientX,
y: event.clientY
};
dragStartRef.current = Date.now();
el.setPointerCapture(event.pointerId);
el.setAttribute("data-swiping", "true");
}, [enabled, elRef]);
const onPointerMove = React.useCallback((event) => {
if (!draggingRef.current) return;
const el = elRef.current;
if (!el) return;
if ((window.getSelection?.()?.toString().length ?? 0) > 0) return;
const start = startRef.current;
const dx = event.clientX - start.x;
const dy = event.clientY - start.y;
if (axisRef.current === null) axisRef.current = lockSwipeAxis(dx, dy);
const axis = axisRef.current;
if (axis === null) return;
const resolved = computeSwipeAxisAmount(axis, axis === "x" ? dx : dy, directions);
const amount = axis === "x" ? {
x: resolved,
y: 0
} : {
x: 0,
y: resolved
};
amountRef.current = amount;
if (resolved !== 0) el.setAttribute("data-swiped", "true");
el.style.setProperty("--scrollsheet-toast-swipe-x", `${amount.x}px`);
el.style.setProperty("--scrollsheet-toast-swipe-y", `${amount.y}px`);
}, [elRef, directions]);
const release = React.useCallback(() => {
if (!draggingRef.current) return;
draggingRef.current = false;
const el = elRef.current;
const axis = axisRef.current;
axisRef.current = null;
if (!el) return;
if (axis === null) {
el.setAttribute("data-swiping", "false");
return;
}
const amount = axis === "x" ? amountRef.current.x : amountRef.current.y;
const elapsed = Math.max(1, Date.now() - dragStartRef.current);
const velocity = Math.abs(amount) / elapsed;
if (isSwipeReleaseAllowed(axis, amount, directions) && shouldDismissOnSwipeRelease(amount, velocity)) {
const direction = resolveSwipeOutDirection(axis, amount);
el.setAttribute("data-swipe-direction", direction);
if (prefersReducedMotion()) {
el.setAttribute("data-swipe-out", "true");
onSwipeDismissRef.current();
return;
}
const onAnimEnd = () => {
el.removeEventListener("animationend", onAnimEnd);
onSwipeDismissRef.current();
};
el.addEventListener("animationend", onAnimEnd);
el.setAttribute("data-swipe-out", "true");
return;
}
el.setAttribute("data-swiping", "false");
el.setAttribute("data-swiped", "false");
el.style.setProperty("--scrollsheet-toast-swipe-x", "0px");
el.style.setProperty("--scrollsheet-toast-swipe-y", "0px");
}, [elRef, directions]);
return {
onPointerDown,
onPointerMove,
onPointerUp: React.useCallback((event) => {
if (elRef.current?.hasPointerCapture(event.pointerId)) elRef.current.releasePointerCapture(event.pointerId);
release();
}, [elRef, release]),
onPointerCancel: React.useCallback(() => release(), [release])
};
}
//#endregion
//#region packages/scrollsheet/src/toast/shell/toast-row.tsx
function ToastIcon({ type, icons, spinnerClassName }) {
if (type === "loading") {
if (icons?.loading) return jsx(Fragment, { children: icons.loading });
return jsx("span", { className: spinnerClassName });
}
switch (type) {
case "success": return jsx(Fragment, { children: icons?.success ?? "✓" });
case "error": return jsx(Fragment, { children: icons?.error ?? "✕" });
case "warning": return jsx(Fragment, { children: icons?.warning ?? "!" });
case "info": return jsx(Fragment, { children: icons?.info ?? "i" });
default: return null;
}
}
function ToastRow({ record, index, total, toastsBefore, stackOffset, height, frontHeight, visible, expanded, removed, yPosition, xPosition, closeButton, directions, onDismiss, observe, toasterClassNames, icons, toasterCloseButtonAriaLabel, toasterStyle, sonnerCompat }) {
const role = record.type === "error" ? "alert" : "status";
const mounted = useMountedFlag();
const elRef = React.useRef(null);
const dismissible = record.dismissible !== false;
const isFront = index === 0;
const setRefs = React.useCallback((el) => {
elRef.current = el;
observe(record.id, el);
}, [observe, record.id]);
const swipeHandlers = useToastSwipe(elRef, !removed && dismissible && record.type !== "loading", directions, () => onDismiss(record));
React.useEffect(() => {
const el = elRef.current;
if (el) el.inert = removed;
}, [removed]);
const style = {
"--scrollsheet-toast-toasts-before": toastsBefore,
"--scrollsheet-toast-stack-offset": `${stackOffset}px`,
"--scrollsheet-toast-front-height": frontHeight !== void 0 ? `${frontHeight}px` : "0px",
"--scrollsheet-toast-initial-height": height !== void 0 ? `${height}px` : "auto",
zIndex: Math.max(0, total - index),
...toasterStyle,
...record.style
};
const legacy = (name) => sonnerCompat ? name : void 0;
const rootClassName = cn("scrollsheet-toast", legacy("sonner-toast"), record.className, toasterClassNames?.toast, record.classNames?.toast, toasterClassNames?.default, toasterClassNames?.[record.type], record.classNames?.[record.type]);
const motionAttrs = {
"data-mounted": mounted ? "" : void 0,
"data-removed": removed ? "" : void 0,
"data-visible": visible ? "true" : "false",
"data-front": isFront ? "true" : "false",
"data-expanded": expanded ? "true" : "false",
"data-y-position": yPosition,
"data-x-position": xPosition,
"data-index": index,
"data-dismissible": dismissible ? "true" : "false",
"data-swiping": "false",
"data-swiped": "false",
"aria-hidden": removed ? "true" : void 0
};
if (record.jsx !== void 0) return jsx("div", {
ref: setRefs,
className: rootClassName,
"data-scrollsheet-toast": "",
"data-sonner-toast": sonnerCompat ? "" : void 0,
"data-scrollsheet-custom": "",
"data-sonner-custom": sonnerCompat ? "" : void 0,
"data-testid": record.testId,
role,
style,
...motionAttrs,
...swipeHandlers,
children: record.jsx
});
const showCloseButton = closeButton && dismissible && record.type !== "loading";
const hasActions = Boolean(record.action || record.cancel || showCloseButton);
const closeButtonAriaLabel = record.closeButtonAriaLabel ?? toasterCloseButtonAriaLabel ?? "Close toast";
return jsxs("div", {
ref: setRefs,
className: rootClassName,
"data-scrollsheet-toast": "",
"data-sonner-toast": sonnerCompat ? "" : void 0,
"data-type": record.type,
"data-testid": record.testId,
role,
style,
...motionAttrs,
...swipeHandlers,
children: [
jsx("span", {
className: cn("scrollsheet-toast-icon", legacy("sonner-toast-icon"), toasterClassNames?.icon, record.classNames?.icon),
"data-type": record.type,
"aria-hidden": "true",
children: record.icon ?? jsx(ToastIcon, {
type: record.type,
icons,
spinnerClassName: cn("scrollsheet-toast-spinner", legacy("sonner-toast-spinner"), toasterClassNames?.loader, record.classNames?.loader)
})
}),
jsxs("div", {
className: cn("scrollsheet-toast-body", legacy("sonner-toast-body"), toasterClassNames?.content, record.classNames?.content),
children: [record.title !== void 0 && jsx("div", {
className: cn("scrollsheet-toast-title", legacy("sonner-toast-title"), toasterClassNames?.title, record.classNames?.title),
children: record.title
}), record.description !== void 0 && jsx("div", {
className: cn("scrollsheet-toast-description", legacy("sonner-toast-description"), toasterClassNames?.description, record.classNames?.description),
children: record.description
})]
}),
hasActions && jsxs("div", {
className: cn("scrollsheet-toast-actions", legacy("sonner-toast-actions")),
children: [
record.cancel && jsx("button", {
type: "button",
className: cn("scrollsheet-toast-cancel", legacy("sonner-toast-cancel"), toasterClassNames?.cancelButton, record.classNames?.cancelButton),
onClick: (event) => {
if (!dismissible) return;
record.cancel?.onClick(event);
onDismiss(record);
},
children: record.cancel.label
}),
record.action && jsx("button", {
type: "button",
className: cn("scrollsheet-toast-action", legacy("sonner-toast-action"), toasterClassNames?.actionButton, record.classNames?.actionButton),
onClick: (event) => {
record.action?.onClick(event);
if (event.defaultPrevented) return;
onDismiss(record);
},
children: record.action.label
}),
showCloseButton && jsx("button", {
type: "button",
className: cn("scrollsheet-toast-close", legacy("sonner-toast-close"), toasterClassNames?.closeButton, record.classNames?.closeButton),
"aria-label": closeButtonAriaLabel,
onClick: () => onDismiss(record),
children: icons?.close ?? "×"
})
]
})
]
});
}
//#endregion
//#region packages/scrollsheet/src/toast/shell/use-toast-heights.ts
function useToastHeights() {
const [heights, setHeights] = React.useState(() => new Map());
const observersRef = React.useRef(new Map());
const setHeight = React.useCallback((id, height) => {
setHeights((prev) => prev.get(id) === height ? prev : new Map(prev).set(id, height));
}, []);
const forget = React.useCallback((id) => {
observersRef.current.get(id)?.disconnect();
observersRef.current.delete(id);
setHeights((prev) => {
if (!prev.has(id)) return prev;
const next = new Map(prev);
next.delete(id);
return next;
});
}, []);
const observe = React.useCallback((id, el) => {
const observers = observersRef.current;
observers.get(id)?.disconnect();
observers.delete(id);
if (!el || typeof ResizeObserver === "undefined") return;
const ro = new ResizeObserver(() => setHeight(id, el.getBoundingClientRect().height));
ro.observe(el);
observers.set(id, ro);
setHeight(id, el.getBoundingClientRect().height);
}, [setHeight]);
React.useEffect(() => {
const observers = observersRef.current;
return () => {
for (const ro of observers.values()) ro.disconnect();
observers.clear();
};
}, []);
return {
heights,
observe,
forget
};
}
//#endregion
//#region packages/scrollsheet/src/toast/shell/toaster-shell.tsx
const DEFAULT_HOTKEY = ["altKey", "KeyT"];
function PositionGroup({ position, defaultPosition, toasts, visibleToasts, gap, expanded, closeButton, swipeDirections, toastOptions, icons, className, baseStyle, sonnerCompat, registerListEl, dismissRow, onHoverEnter, onHoverLeave, onInteractingChange }) {
const directions = React.useMemo(() => swipeDirections ?? getDefaultSwipeDirections(position), [swipeDirections, position]);
const positionToasts = React.useMemo(() => selectPositionToasts(toasts, position, defaultPosition), [
toasts,
position,
defaultPosition
]);
const newestFirst = React.useMemo(() => [...positionToasts].reverse(), [positionToasts]);
const window_ = React.useMemo(() => selectToastWindow(positionToasts, visibleToasts), [positionToasts, visibleToasts]);
const visibleIds = React.useMemo(() => new Set(window_.visible.map((t) => t.id)), [window_]);
const { heights, observe, forget } = useToastHeights();
const exitRows = useToastExit(newestFirst);
const exitingIds = React.useMemo(() => new Set(exitRows.filter((r) => r.exiting).map((r) => r.record.id)), [exitRows]);
React.useEffect(() => {
for (const id of exitingIds) forget(id);
}, [exitingIds, forget]);
const offsets = React.useMemo(() => computeRowOffsets(newestFirst, heights, gap), [
newestFirst,
heights,
gap
]);
const offsetById = React.useMemo(() => new Map(offsets.map((o) => [o.id, o])), [offsets]);
const lastOffsetRef = React.useRef(new Map());
for (const o of offsets) lastOffsetRef.current.set(o.id, o);
if (lastOffsetRef.current.size > offsets.length) {
const keep = new Set([...offsets.map((o) => o.id), ...exitingIds]);
for (const id of [...lastOffsetRef.current.keys()]) if (!keep.has(id)) lastOffsetRef.current.delete(id);
}
const frontHeight = heights.get(newestFirst[0]?.id ?? "");
const { y, x } = splitPosition(position);
const listRef = React.useCallback((el) => registerListEl(position, el), [registerListEl, position]);
if (exitRows.length === 0) return null;
return jsx("ol", {
ref: listRef,
"data-scrollsheet-toaster": "",
"data-sonner-toaster": sonnerCompat ? "" : void 0,
"data-scrollsheet-theme": "light",
"data-sonner-theme": sonnerCompat ? "light" : void 0,
"data-y-position": y,
"data-x-position": x,
tabIndex: -1,
className,
style: {
...baseStyle,
"--scrollsheet-toast-gap": `${gap}px`,
"--scrollsheet-toast-front-height": frontHeight !== void 0 ? `${frontHeight}px` : void 0
},
onMouseEnter: onHoverEnter,
onMouseMove: onHoverEnter,
onMouseLeave: onHoverLeave,
onPointerDown: (event) => {
if (event.target.dataset.dismissible === "false") return;
onInteractingChange(true);
},
onPointerUp: () => onInteractingChange(false),
children: exitRows.map(({ record, exiting }) => {
const offset = offsetById.get(record.id) ?? lastOffsetRef.current.get(record.id);
const index = offset?.index ?? newestFirst.length;
return jsx(ToastRow, {
record,
index,
total: newestFirst.length,
toastsBefore: offset?.toastsBefore ?? index,
stackOffset: offset?.stackOffset ?? 0,
height: heights.get(record.id),
frontHeight,
visible: visibleIds.has(record.id),
expanded,
removed: exiting,
yPosition: y,
xPosition: x,
closeButton,
directions,
onDismiss: dismissRow,
observe,
toasterClassNames: toastOptions?.classNames,
sonnerCompat,
icons,
toasterCloseButtonAriaLabel: toastOptions?.closeButtonAriaLabel,
toasterStyle: toastOptions?.style
}, record.id);
})
});
}
function resolveShellPosition(position) {
return position ?? "bottom-right";
}
function ToasterShell({ id, toasterId, position, theme, richColors, expand: forceExpand, visibleToasts: visibleToastsProp, closeButton = false, duration: durationProp, gap = 14, offset, swipeDirections, toastOptions, icons, className, style, containerAriaLabel = "Notifications", hotkey = DEFAULT_HOTKEY, nonce, sonnerCompat = true }) {
const resolvedId = id ?? toasterId;
if (toasterId !== void 0) warnOnce("toaster-id-deprecated", "[scrollsheet Toaster] Toaster's \"toasterId\" prop is deprecated — use \"id\" instead.");
const { toasts: allToasts } = useSonner();
const toasts = React.useMemo(() => selectToasterToasts(allToasts, resolvedId), [allToasts, resolvedId]);
const defaultPosition = resolveShellPosition(position);
const possiblePositions = React.useMemo(() => computePossiblePositions(defaultPosition, toasts), [defaultPosition, toasts]);
const visibleToasts = resolveVisibleToasts(visibleToastsProp);
const defaultDuration = durationProp ?? toastOptions?.duration ?? 4e3;
if (theme !== void 0 && theme !== "light") warnOnce("shell-theme", `[scrollsheet Toaster] theme="${theme}" isn't implemented yet — v1 always renders the static light card real Sonner ships by default.`);
if (richColors) warnOnce("shell-rich-colors", "[scrollsheet Toaster] richColors isn't implemented yet — toasts render with their default icon color only.");
React.useEffect(() => {
injectToastStyles(nonce);
}, [nonce]);
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => setMounted(true), []);
const [hovered, setHovered] = React.useState(false);
const [hotkeyExpanded, setHotkeyExpanded] = React.useState(false);
const [interacting, setInteracting] = React.useState(false);
const expanded = Boolean(forceExpand) || hovered || hotkeyExpanded;
const isDocumentHidden = useIsDocumentHidden();
const handleHoverEnter = React.useCallback(() => setHovered(true), []);
const handleHoverLeave = React.useCallback(() => {
if (!interacting) setHovered(false);
}, [interacting]);
const dismissRow = React.useCallback((record) => {
toast.dismiss(record.id);
}, []);
const listElsRef = React.useRef(new Map());
const registerListEl = React.useCallback((pos, el) => {
if (el) listElsRef.current.set(pos, el);
else listElsRef.current.delete(pos);
}, []);
React.useEffect(() => {
if (hotkey.length === 0) return;
const handleKeyDown = (event) => {
if (hotkey.every((key) => event[key] || event.code === key)) {
setHotkeyExpanded(true);
const [firstList] = listElsRef.current.values();
firstList?.focus({ preventScroll: true });
}
if (event.code !== "Escape") return;
const active = document.activeElement;
const focusedEntry = [...listElsRef.current.entries()].find(([, el]) => active === el || el.contains(active));
if (!focusedEntry) return;
if (hotkeyExpanded) {
setHotkeyExpanded(false);
return;
}
const [focusedPosition] = focusedEntry;
const positionToasts = selectPositionToasts(toasts, focusedPosition, defaultPosition);
const front = positionToasts[positionToasts.length - 1];
if (front) dismissRow(front);
};
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, [
hotkey,
hotkeyExpanded,
toasts,
defaultPosition,
dismissRow
]);
const timersRef = React.useRef(new Map());
React.useEffect(() => {
const timers = timersRef.current;
const liveIds = new Set(toasts.map((t) => t.id));
for (const [id, entry] of timers) {
if (liveIds.has(id)) continue;
if (entry.timer) clearTimeout(entry.timer);
timers.delete(id);
}
const paused = expanded || interacting || isDocumentHidden;
for (const t of toasts) {
if (t.type === "loading") {
const stale = timers.get(t.id);
if (stale) {
if (stale.timer) clearTimeout(stale.timer);
timers.delete(t.id);
}
continue;
}
const ms = t.duration ?? toastOptions?.duration ?? defaultDuration;
const existing = timers.get(t.id);
if (!existing || existing.record !== t) {
if (existing?.timer) clearTimeout(existing.timer);
const entry = {
timer: null,
startedAt: 0,
remaining: ms,
record: t
};
timers.set(t.id, entry);
if (Number.isFinite(ms) && !paused) {
entry.startedAt = Date.now();
entry.timer = setTimeout(() => {
timersRef.current.delete(t.id);
expire(t.id);
}, ms);
}
continue;
}
if (paused) {
if (existing.timer) {
clearTimeout(existing.timer);
const elapsed = Date.now() - existing.startedAt;
existing.remaining = Math.max(0, existing.remaining - elapsed);
existing.timer = null;
}
continue;
}
if (existing.timer || !Number.isFinite(existing.remaining)) continue;
existing.startedAt = Date.now();
existing.timer = setTimeout(() => {
timersRef.current.delete(t.id);
expire(t.id);
}, existing.remaining);
}
}, [
toasts,
expanded,
interacting,
isDocumentHidden,
defaultDuration,
toastOptions?.duration
]);
React.useEffect(() => {
const timers = timersRef.current;
return () => {
for (const entry of timers.values()) if (entry.timer) clearTimeout(entry.timer);
timers.clear();
};
}, []);
useCloseWatcher({
present: toasts.length > 0,
nonModal: true,
escapeDismissible: true,
onClose: () => {
const target = findNewestDismissible(toasts);
if (target) dismissRow(target);
}
});
if (!mounted || typeof document === "undefined") return null;
const offsetValue = offset !== void 0 ? typeof offset === "number" ? `${offset}px` : offset : void 0;
const baseStyle = {
...style,
"--scrollsheet-toast-offset": offsetValue
};
return createPortal(jsx("section", {
"aria-label": containerAriaLabel,
tabIndex: -1,
"aria-live": "polite",
"aria-relevant": "additions text",
"aria-atomic": "false",
suppressHydrationWarning: true,
"data-react-aria-top-layer": "",
children: possiblePositions.map((groupPosition) => jsx(PositionGroup, {
position: groupPosition,
defaultPosition,
toasts,
visibleToasts,
gap,
expanded,
closeButton,
swipeDirections,
toastOptions,
icons,
className,
baseStyle,
sonnerCompat,
registerListEl,
dismissRow,
onHoverEnter: handleHoverEnter,
onHoverLeave: handleHoverLeave,
onInteractingChange: setInteracting
}, groupPosition))
}), document.body);
}
//#endregion
export { useSonner as a, toast as i, resolveVisibleToasts as n, useToasts as o, injectToastStylesInto as r, ToasterShell as t };
"use client";
import { i as Content$1, l as Root$1, n as Description, o as Trigger, r as Title, s as Slot, t as Close, u as useSheetContext } from "./misc-DQmFNK1F.mjs";
import { t as cn } from "./cn-DgpbGWy-.mjs";
import * as React from "react";
import { Fragment, jsx } from "react/jsx-runtime";
import { createPortal } from "react-dom";
//#region packages/scrollsheet/src/handle.tsx
function detentLabel(spec) {
if (spec === "full") return "Full";
if (spec === "medium") return "Half";
if (spec === "content") return "Fit content";
if (typeof spec === "number") return `${Math.round(spec * 100)}%`;
return spec ?? "";
}
const Handle$1 = React.forwardRef(function Handle({ asChild, onClick, onKeyDown, className, variant = "inside", ...props }, ref) {
const ctx = useSheetContext("Handle");
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => setMounted(true), []);
const outside = variant === "outside" && ctx.side === "bottom" && !(mounted && !ctx.canvasEl);
const variantAttr = outside ? "outside" : variant === "floating" ? "floating" : void 0;
const specs = ctx.detents;
const horizontal = ctx.side === "left" || ctx.side === "right";
const multi = specs.length >= 2;
const activeIndex = specs.findIndex((s) => Object.is(s, ctx.activeDetent));
const move = (delta) => {
if (!multi) return false;
const index = specs.findIndex((s) => Object.is(s, ctx.activeDetent));
const next = specs[index + delta];
if (next === void 0) return false;
ctx.setActiveDetent(next);
return true;
};
const sliderAria = multi ? {
role: "slider",
"aria-orientation": horizontal ? "horizontal" : "vertical",
"aria-valuemin": 0,
"aria-valuemax": specs.length - 1,
...activeIndex >= 0 ? {
"aria-valuenow": activeIndex,
"aria-valuetext": detentLabel(specs[activeIndex])
} : {}
} : {};
const button = jsx(asChild ? Slot : "button", {
...asChild ? {} : { type: "button" },
"aria-label": horizontal ? "Adjust sheet width" : "Adjust sheet height",
...sliderAria,
...props,
ref,
className: cn("scrollsheet-handle", className),
"data-scrollsheet-handle": true,
"data-scrollsheet-handle-variant": variantAttr,
onClick: (event) => {
onClick?.(event);
if (event.defaultPrevented) return;
if (!multi) return;
const index = specs.findIndex((s) => Object.is(s, ctx.activeDetent));
const next = specs[(index + 1) % specs.length];
if (next !== void 0) ctx.setActiveDetent(next);
},
onKeyDown: (event) => {
onKeyDown?.(event);
if (event.defaultPrevented) return;
const expandKey = horizontal ? "ArrowRight" : "ArrowUp";
const collapseKey = horizontal ? "ArrowLeft" : "ArrowDown";
if (event.key === expandKey) {
event.preventDefault();
move(1);
} else if (event.key === collapseKey) {
event.preventDefault();
if (!move(-1) && ctx.dismissible) ctx.setOpen(false);
} else if (multi && event.key === "Home") {
event.preventDefault();
const first = specs[0];
if (first !== void 0) ctx.setActiveDetent(first);
} else if (multi && event.key === "End") {
event.preventDefault();
const last = specs[specs.length - 1];
if (last !== void 0) ctx.setActiveDetent(last);
}
}
});
if (outside) return ctx.canvasEl ? createPortal(button, ctx.canvasEl) : null;
return button;
});
//#endregion
//#region packages/scrollsheet/src/drawer/index.tsx
const wrapperClaims = new Map();
const VAUL_CLOSE_THRESHOLD = .25;
function toDetentSpec(point) {
if (typeof point === "number") return point;
if (point === "fit-content" || point === "content") return "content";
if (/^\d+(\.\d+)?px$/.test(point)) return `${Number.parseFloat(point)}px`;
const parsed = Number.parseFloat(point);
return Number.isFinite(parsed) ? parsed : "content";
}
function resolveFadeFromIndex(snapPoints, fadeFromIndex) {
if (!snapPoints || snapPoints.length === 0) return void 0;
const point = snapPoints[fadeFromIndex ?? snapPoints.length - 1];
return point !== void 0 ? toDetentSpec(point) : void 0;
}
function resolveCloseThreshold(snapPoints, closeThreshold) {
if (snapPoints !== void 0 && snapPoints.length > 0) return void 0;
return Math.min(1, Math.max(0, 1 - (closeThreshold ?? VAUL_CLOSE_THRESHOLD)));
}
function composeOpenChange(onOpenChange, onClose) {
if (!onOpenChange && !onClose) return void 0;
return (next) => {
if (!next) onClose?.();
onOpenChange?.(next);
};
}
function Root(props) {
const { children, open, defaultOpen, onOpenChange, onClose, dismissible, snapPoints, activeSnapPoint, setActiveSnapPoint, onAnimationEnd, autoFocus, nested, direction, modal, shouldScaleBackground, setBackgroundColorOnScale, noBodyStyles, disablePreventScroll, preventScrollRestoration, repositionInputs, scrollLockTimeout, closeThreshold, fadeFromIndex, snapToSequentialPoint, handleOnly, onDrag, onRelease, container, fixed, actionsRef, backdropDismissible, escapeDismissible, keyboardExpands, onTravel, scrollbar } = props;
const detents = snapPoints && snapPoints.length > 0 ? snapPoints.map(toDetentSpec) : void 0;
const activeDetent = activeSnapPoint != null ? toDetentSpec(activeSnapPoint) : void 0;
const largestUndimmedDetent = resolveFadeFromIndex(snapPoints, fadeFromIndex);
const resolvedCloseThreshold = resolveCloseThreshold(snapPoints, closeThreshold);
const handleRelease = onRelease ? (event, willRemainOpen) => onRelease(event, willRemainOpen) : void 0;
const handleOpenChange = composeOpenChange(onOpenChange, onClose);
const handleActiveDetentChange = (detent) => {
if (!setActiveSnapPoint) return;
const match = snapPoints?.find((point) => Object.is(toDetentSpec(point), detent));
setActiveSnapPoint(match ?? detent);
};
React.useEffect(() => {
if (!shouldScaleBackground) return;
const existing = document.querySelector("[data-scrollsheet-background]");
if (existing && !wrapperClaims.has(existing)) return;
const wrapper = existing ?? document.querySelector("[data-vaul-drawer-wrapper]");
if (!wrapper) return;
const count = wrapperClaims.get(wrapper) ?? 0;
wrapperClaims.set(wrapper, count + 1);
if (count === 0) wrapper.setAttribute("data-scrollsheet-background", "");
return () => {
const remaining = (wrapperClaims.get(wrapper) ?? 1) - 1;
if (remaining <= 0) {
wrapperClaims.delete(wrapper);
wrapper.removeAttribute("data-scrollsheet-background");
} else wrapperClaims.set(wrapper, remaining);
};
}, [shouldScaleBackground]);
return jsx(DirectionContext.Provider, {
value: direction ?? "bottom",
children: jsx(Root$1, {
open,
defaultOpen,
onOpenChange: handleOpenChange,
onOpenChangeComplete: onAnimationEnd,
dismissible,
detents,
activeDetent,
onActiveDetentChange: setActiveSnapPoint ? handleActiveDetentChange : void 0,
side: direction,
modal,
backgroundEffect: shouldScaleBackground ? "scale" : "none",
largestUndimmedDetent,
handleOnly,
sequentialDetents: snapToSequentialPoint,
closeThreshold: resolvedCloseThreshold,
onRelease: handleRelease,
actionsRef,
backdropDismissible,
escapeDismissible,
keyboardExpands,
onTravel,
scrollbar,
children
})
});
}
const DirectionContext = React.createContext("bottom");
const NestedRoot = Root;
function Portal({ children, container }) {
return jsx(Fragment, { children });
}
function Overlay(_props) {
return null;
}
function hasMultipleSnapPoints(detents) {
return detents.length > 1;
}
const Content = React.forwardRef(function Content({ onPointerDownOutside, onOpenAutoFocus, onEscapeKeyDown, onCloseAutoFocus, onInteractOutside, onFocusOutside, forceMount, ...props }, ref) {
const ctx = useSheetContext("Content");
const direction = React.useContext(DirectionContext);
const snapPointsActive = hasMultipleSnapPoints(ctx.detents);
return jsx(Content$1, {
...props,
ref,
"data-vaul-drawer": "",
"data-vaul-drawer-direction": direction,
"data-vaul-snap-points": snapPointsActive ? "true" : "false"
});
});
function composeHandleClick(preventCycle, onClick) {
return (event) => {
onClick?.(event);
if (preventCycle) event.preventDefault();
};
}
const Handle = React.forwardRef(function Handle({ preventCycle, onClick, ...props }, ref) {
const ctx = useSheetContext("Handle");
return jsx(Handle$1, {
...props,
ref,
"data-vaul-handle": "",
"data-vaul-drawer-visible": ctx.open ? "true" : "false",
onClick: composeHandleClick(preventCycle, onClick)
});
});
const DrawerClose = React.forwardRef(function DrawerClose({ children, ...props }, ref) {
return jsx(Close, {
...props,
ref,
children: children ?? null
});
});
const Drawer = {
Root,
NestedRoot,
Trigger,
Portal,
Overlay,
Content,
Close: DrawerClose,
Title,
Description,
Handle
};
//#endregion
export { NestedRoot as a, Root as c, hasMultipleSnapPoints as d, resolveCloseThreshold as f, Handle as i, composeHandleClick as l, Handle$1 as m, Drawer as n, Overlay as o, resolveFadeFromIndex as p, DrawerClose as r, Portal as s, Content as t, composeOpenChange as u };
import { a as SheetTitleProps, c as SheetContentProps, h as DetentSpec, i as SheetDescriptionProps, l as SheetTriggerProps, p as SheetRootProps, r as SheetCloseProps } from "./misc-GeCxhsLP.mjs";
import * as React from "react";
//#region packages/scrollsheet/src/handle.d.ts
interface SheetHandleProps extends React.ComponentProps<"button"> {
/** Render the child element instead of a `<button>`, merging props into it. */
asChild?: boolean;
/**
* Where the pill sits. `'inside'` (default) flows at the top of the
* sheet's content. `'floating'` overlays the content — absolutely
* positioned over the panel's top edge, for full-bleed content (maps,
* photos) a flow pill would push down. `'outside'` floats in the backdrop
* above the sheet's top edge (bottom sheets only; other sides render as
* `'inside'`) — it rides the canvas layer outside the panel's clip and
* drags, clicks, and keys exactly like the others.
* @default 'inside'
*/
variant?: "inside" | "floating" | "outside";
}
/**
* The grabber pill. Click cycles detents; ArrowUp/ArrowDown (or Left/Right
* for side sheets) move between them, Home/End jump to the first/last — so
* multi-detent sheets are fully keyboard-operable. With two or more detents
* it exposes itself as a slider (role, value min/max/now, value text) so
* screen readers announce the current stop, not just "button".
*
* The handle is optional. It's purely a click/keyboard affordance layered on
* top of the drag engine, which listens on the whole panel, not the handle —
* omit `<Sheet.Handle>` and the whole panel still drags and dismisses, in
* every mode (modal and non-modal) and every side (bottom/top/left/right).
* There's no boolean prop for this; omission is the API.
*/
declare const Handle$1: React.ForwardRefExoticComponent<Omit<SheetHandleProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
//#endregion
//#region packages/scrollsheet/src/drawer/index.d.ts
/** vaul's snap point shape: a 0–1 fraction, an absolute `'###px'` string, or `'fit-content'`. */
type VaulSnapPoint = number | string;
/**
* Resolves vaul's `fadeFromIndex` against `snapPoints` into a
* `largestUndimmedDetent` spec, mirroring real vaul's own default (its
* `src/index.tsx`: `fadeFromIndex = snapPoints && snapPoints.length - 1`) —
* omitted with `snapPoints` set defaults to the *topmost* snap point index
* (no dim until the last snap point), not "no undimmed range at all"
* (scrollsheet's own default when `largestUndimmedDetent` is never touched).
* No `snapPoints`, or an out-of-range explicit index, resolves to
* `undefined` (today's full-dim-range behavior) — pulled out as its own pure
* function so this index math is unit-testable independent of rendering.
*/
declare function resolveFadeFromIndex(snapPoints: readonly VaulSnapPoint[] | undefined, fadeFromIndex: number | undefined): DetentSpec | undefined;
/**
* Resolves vaul's `closeThreshold` against whether `snapPoints` is set,
* CONVERTING between opposite conventions: vaul counts the fraction dragged
* AWAY (its 0.25 default = dismiss after a quarter-height drag), scrollsheet
* counts the fraction still VISIBLE (`isBelowCloseThreshold`: dismiss when
* `revealed < firstDetent * closeThreshold`, so higher = easier). A raw
* passthrough inverts the migrated feel — vaul's 0.25 became "drag 75% to
* dismiss", harder than scrollsheet's own 0.5 default instead of easier —
* so the mapping is `1 - value`, and an omitted prop resolves to vaul's own
* `CLOSE_THRESHOLD` default of 0.25 (=> 0.75 here), never `Sheet.Root`'s
* unrelated 0.5.
* `snapPoints` set makes `closeThreshold` dead code in real vaul (its
* `onRelease` returns through the snap-points branch before ever reading
* it), matched here by always resolving to `undefined` in that case.
* Pulled out as its own pure function, mirroring `resolveFadeFromIndex`,
* so the conversion math is unit-testable independent of rendering.
*/
declare function resolveCloseThreshold(snapPoints: readonly VaulSnapPoint[] | undefined, closeThreshold: number | undefined): number | undefined;
/**
* Composes vaul's `onClose` on top of `onOpenChange`: `onClose` fires
* whenever the transition target is `false`, then `onOpenChange` always
* fires. Real vaul's `closeDrawer()` calls `onClose` from every dismiss
* path (swipe past threshold, Esc, backdrop, imperative); scrollsheet's own
* `onOpenChange` is already the single funnel every one of *its* dismiss
* paths goes through (`useControllableState`'s `onChange`, which fires
* exactly once per real open/false transition), so layering `onClose` on
* top of that wiring reproduces "every dismiss path" without a second one.
* Returns `undefined` when neither callback is given, matching the
* conditional-wrapper convention `onRelease`'s cast uses below. Pulled out
* as its own pure function so the composition is unit-testable without a
* live DOM/click event.
*/
declare function composeOpenChange(onOpenChange: ((open: boolean) => void) | undefined, onClose: (() => void) | undefined): ((open: boolean) => void) | undefined;
/**
* vaul's own props, translated — plus (via the Pick) the scrollsheet-native
* props that have no vaul counterpart and forward to `Sheet.Root`
* untranslated: `backdropDismissible` (the escape hatch vaul served with the
* Radix onPointerDownOutside/onInteractOutside preventDefault idiom),
* `escapeDismissible` (same for onEscapeKeyDown), `keyboardExpands`,
* `onTravel` (the live counterpart of vaul's ignored `onDrag`), `scrollbar`,
* and `actionsRef`. Only props with no vaul name-collision are forwarded —
* anything vaul also has (`closeThreshold`, `modal`, `dismissible`, …) keeps
* its translated vaul semantics above.
*/
interface DrawerRootProps extends Pick<SheetRootProps, "actionsRef" | "backdropDismissible" | "escapeDismissible" | "keyboardExpands" | "onTravel" | "scrollbar"> {
children?: React.ReactNode;
open?: boolean;
defaultOpen?: boolean;
onOpenChange?: (open: boolean) => void;
/**
* Fires whenever the drawer closes — every dismiss path (swipe past
* threshold, Esc, backdrop tap, imperative `close()`) funnels through the
* same `onOpenChange` wiring this is layered on top of, matching real
* vaul's `closeDrawer()`, which calls `onClose` on every one of those
* paths too.
*/
onClose?: () => void;
/** @default true */
dismissible?: boolean;
/** Fractions (0–1), `'###px'` strings, or `'fit-content'` — translated to scrollsheet `detents`. */
snapPoints?: readonly VaulSnapPoint[];
/** Optionally-controlled active snap point; `null` means "no explicit snap point". */
activeSnapPoint?: VaulSnapPoint | null;
setActiveSnapPoint?: (snapPoint: VaulSnapPoint | null) => void;
/** Fires when the open/close transition actually completes — wired to `onOpenChangeComplete`, exact rather than vaul's timer. */
onAnimationEnd?: (open: boolean) => void;
/** Ignored — the native `<dialog>` manages focus itself. */
autoFocus?: boolean;
/** Ignored — nesting is automatic: a `<Drawer.Content>` rendered inside another registers itself. */
nested?: boolean;
/** Maps to scrollsheet `side` — all four edges. */
direction?: "top" | "bottom" | "left" | "right";
/** Maps to scrollsheet `modal` — `false` renders non-modal (Popover top layer, page stays interactive). */
modal?: boolean;
/**
* Maps to `backgroundEffect="scale"`; vaul's own `[data-vaul-drawer-wrapper]`
* is picked up as the target. Left false (vaul's default), this maps to
* `'none'`, not unset — vaul never scales the page unless asked, so the
* scrollsheet default for full-height sheets must not leak in here.
*/
shouldScaleBackground?: boolean;
/**
* Maps to scrollsheet `closeThreshold` — but only when `snapPoints` is
* unset. In real vaul, `closeThreshold` is dead code once `snapPoints`
* exist (its `onRelease` returns through the snap-points branch before
* ever reading it); this compat layer matches that rather than giving the
* prop unconditional live effect. Passing both together warns once in dev
* — use the native `Sheet.Root closeThreshold` directly if you want it to
* combine with detents.
*
* Omitted (with `snapPoints` unset) resolves to vaul's own default of
* `0.25` (`CLOSE_THRESHOLD` in vaul's `src/constants.ts`) — not
* scrollsheet's own `0.5` default, which is twice the drag distance.
*/
closeThreshold?: number;
/**
* Maps to `largestUndimmedDetent`, resolved against `snapPoints` at this
* index. Omitted (with `snapPoints` set) defaults to `snapPoints.length -
* 1` — vaul's own default, meaning no dim until the topmost snap point —
* not "dim across the full range" (scrollsheet's own default when
* `largestUndimmedDetent` is never touched at all).
*/
fadeFromIndex?: number;
/** Maps to `sequentialDetents`. */
snapToSequentialPoint?: boolean;
/** Maps to `handleOnly` directly. */
handleOnly?: boolean;
/**
* Maps to `onRelease`. Typed as vaul's own `React.PointerEvent<HTMLDivElement>`
* (rather than scrollsheet's wider native-`PointerEvent` type) so a
* handler already typed against vaul's declaration still assigns cleanly —
* see the internal cast in `Root` for why this is safe at runtime.
*/
onRelease?: (event: React.PointerEvent<HTMLDivElement>, open: boolean) => void;
setBackgroundColorOnScale?: boolean;
noBodyStyles?: boolean;
disablePreventScroll?: boolean;
preventScrollRestoration?: boolean;
repositionInputs?: boolean;
scrollLockTimeout?: number;
onDrag?: (event: React.PointerEvent, percentageDragged: number) => void;
container?: HTMLElement | null;
/**
* Ignored — real vaul's `fixed` swaps its own keyboard-avoidance strategy
* (resize instead of translate); scrollsheet's `useKeyboardViewport` has
* no equivalent toggle.
*/
fixed?: boolean;
}
declare function Root(props: DrawerRootProps): React.JSX.Element;
/**
* Nested drawers just work: a `<Drawer.Content>` rendered inside another
* `<Drawer.Content>` automatically registers with the parent's stacking
* context (the parent recedes while the child is open), the way iOS stacks
* sheets. `NestedRoot` is an alias of `Root` kept for drop-in compatibility
* with vaul's API surface — there's nothing distinct for it to do.
*/
declare const NestedRoot: typeof Root;
type DrawerNestedRootProps = DrawerRootProps;
interface DrawerPortalProps {
children?: React.ReactNode;
/** Ignored — the native `<dialog>` always renders into the browser's top layer. */
container?: HTMLElement | null;
}
declare function Portal({ children, container }: DrawerPortalProps): React.JSX.Element;
type DrawerOverlayProps = React.ComponentProps<"div">;
/**
* scrollsheet draws its own backdrop (`.scrollsheet-backdrop`, themeable via
* the `--scrollsheet-backdrop` CSS variable) that tracks scroll progress, so
* there's nothing for a separate overlay element to render. This accepts
* vaul's `<Drawer.Overlay className .../>` so existing JSX doesn't crash —
* restyle the backdrop through the CSS variable instead.
*/
declare function Overlay(_props: DrawerOverlayProps): null;
/**
* Whether translated `snapPoints` produced more than one distinct detent —
* feeds Content's `data-vaul-snap-points` attribute. vaul's own
* CSS-selector convention treats a single snap point the same as none
* (scrollsheet's own single-detent default also resolves to `false` here).
* Pulled out as its own pure function, mirroring `resolveFadeFromIndex` /
* `resolveCloseThreshold`, so it's unit-testable independent of rendering
* (`<Drawer.Content>`'s actual DOM output is behind a client-only mount
* gate — see content.tsx — so SSR can't observe the attribute directly).
*/
declare function hasMultipleSnapPoints(detents: readonly DetentSpec[]): boolean;
interface DrawerContentProps extends SheetContentProps {
onPointerDownOutside?: (event: CustomEvent) => void;
onOpenAutoFocus?: (event: Event) => void;
onEscapeKeyDown?: (event: KeyboardEvent) => void;
onCloseAutoFocus?: (event: Event) => void;
onInteractOutside?: (event: CustomEvent) => void;
onFocusOutside?: (event: CustomEvent) => void;
forceMount?: boolean;
}
declare const Content: React.ForwardRefExoticComponent<Omit<DrawerContentProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
/**
* Composes the click handler Handle passes to `SheetHandle`: the caller's
* own `onClick` always fires first, then — when `preventCycle` is set —
* `event.preventDefault()`, the same signal `SheetHandle`'s own
* click-to-cycle logic already checks (`if (event.defaultPrevented) return`
* in handle.tsx) to skip advancing to the next detent. Mirrors real vaul's
* `preventCycle`, read inside its own `handleCycleSnapPoints` guard. Pulled
* out as its own pure function so the composition is unit-testable without
* a live DOM/click event.
*/
declare function composeHandleClick(preventCycle: boolean | undefined, onClick: ((event: React.MouseEvent<HTMLButtonElement>) => void) | undefined): (event: React.MouseEvent<HTMLButtonElement>) => void;
interface DrawerHandleProps extends SheetHandleProps {
/**
* Suppresses the click-to-cycle-detents behavior — the handle still
* renders, drags, and is keyboard-operable as usual, but a click no
* longer advances to the next detent. Mirrors real vaul's own
* `preventCycle`, which guards the same click path (vaul's handle has no
* keyboard cycling to suppress).
*/
preventCycle?: boolean;
}
declare const Handle: React.ForwardRefExoticComponent<Omit<DrawerHandleProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
/**
* Sheet.Close's self-closing form renders a styled ✕ default; vaul's own
* `<Drawer.Close />` renders an empty unstyled button. A migrating user
* must not get a surprise icon button from a version swap, so the compat
* Close pins children to null (defined, so the styled-default branch never
* arms) unless the caller passed some.
*/
declare const DrawerClose: React.ForwardRefExoticComponent<Omit<SheetCloseProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
/** Namespace-style access matching vaul's `<Drawer.Root>…</Drawer.Root>` shape. */
declare const Drawer: {
Root: typeof Root;
NestedRoot: typeof Root;
Trigger: React.ForwardRefExoticComponent<Omit<SheetTriggerProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
Portal: typeof Portal;
Overlay: typeof Overlay;
Content: React.ForwardRefExoticComponent<Omit<DrawerContentProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
Close: React.ForwardRefExoticComponent<Omit<SheetCloseProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
Title: React.ForwardRefExoticComponent<Omit<SheetTitleProps, "ref"> & React.RefAttributes<HTMLHeadingElement>>;
Description: React.ForwardRefExoticComponent<Omit<SheetDescriptionProps, "ref"> & React.RefAttributes<HTMLParagraphElement>>;
Handle: React.ForwardRefExoticComponent<Omit<DrawerHandleProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
};
//#endregion
export { SheetHandleProps as S, composeOpenChange as _, DrawerHandleProps as a, resolveFadeFromIndex as b, DrawerPortalProps as c, NestedRoot as d, Overlay as f, composeHandleClick as g, VaulSnapPoint as h, DrawerContentProps as i, DrawerRootProps as l, Root as m, Drawer as n, DrawerNestedRootProps as o, Portal as p, DrawerClose as r, DrawerOverlayProps as s, Content as t, Handle as u, hasMultipleSnapPoints as v, Handle$1 as x, resolveCloseThreshold as y };

Sorry, the diff of this file is too big to display

import { t as Side } from "./geometry-C78uYrYJ.mjs";
import * as React from "react";
//#region packages/scrollsheet/src/internal/detents.d.ts
/**
* Detents — the stops a sheet can rest at, in the spirit of
* UISheetPresentationController's `detents`.
*
* A detent resolves to the visible height of the sheet in px.
* Accepted forms:
* - 'full' → viewport height minus top inset
* - 'medium' → 50% of viewport
* - 'content' → natural content height (measured, capped at full)
* - number in (0, 1] → fraction of viewport
* - `${number}px` → absolute pixels
*/
type DetentSpec = "full" | "medium" | "content" | number | `${number}px`;
//#endregion
//#region packages/scrollsheet/src/context.d.ts
/**
* onTravel's third argument. Reused and mutated in place every travel frame
* (see content.tsx's updateTravel) — read it synchronously, don't retain the
* reference across frames.
*/
interface TravelInfo {
/** [0, maxDetent] — the full resolved travel range in px, this frame. */
range: readonly [number, number];
/**
* 0-1 progress toward EACH configured detent, keyed by the detent's
* resolved height in px (not its spec — a spec like 'content' isn't a
* stable identity across resizes, but its resolved height this frame is
* what a consumer interpolates against).
*/
progressAtDetents: ReadonlyMap<number, number>;
}
//#endregion
//#region packages/scrollsheet/src/root.d.ts
/** Imperative escape hatch — see `actionsRef` on `<Sheet.Root>`. */
interface SheetActions {
open(): void;
close(): void;
/**
* Moves the sheet to `detent` through the same `setActiveDetent` path a
* controlled `activeDetent` change uses, so it animates identically. Warns
* in dev (once) if `detent` isn't one of this sheet's configured
* `detents` — only the panel's *rest position* resolves to the nearest
* configured detent in that case; `activeDetent`/`onActiveDetentChange`
* (and Handle's `aria-valuenow`/`aria-valuetext`) still reflect the
* literal value passed in here, not the resolved one.
*/
snapTo(detent: DetentSpec): void;
}
interface SheetRootProps {
children?: React.ReactNode;
/** Controlled open state. */
open?: boolean;
defaultOpen?: boolean;
onOpenChange?: (open: boolean) => void;
/**
* Fires once the open/close *transition* finishes (not just the state
* flip) — `true` when the sheet has fully settled open, `false` once it's
* fully closed and unmounted. Backed by the same phase timers that drive
* `data-scrollsheet-state`/`data-scrollsheet-side` (see Content) — no
* separate timer of its own.
*/
onOpenChangeComplete?: (open: boolean) => void;
/**
* Imperative escape hatch for cases a controlled `open`/`activeDetent`
* prop is awkward for (deep links, push notifications, a descendant
* component reaching up without prop-drilling): `ref.current?.close()` /
* `.open()` / `.snapTo(detent)`. Thin wrappers over the same `setOpen`/
* `setActiveDetent` the rest of the sheet uses — not a separate code path.
*/
actionsRef?: React.Ref<SheetActions>;
/**
* The stops the sheet can rest at, UISheetPresentationController-style.
* `'content'` (natural height), `'medium'` (50%), `'full'`, a 0–1 fraction,
* or `'320px'`.
* @default ['content']
*/
detents?: readonly DetentSpec[];
/** Controlled active detent (one of `detents`). */
activeDetent?: DetentSpec;
onActiveDetentChange?: (detent: DetentSpec) => void;
/**
* Master switch: when false, swipe/drag-to-dismiss, backdrop tap and Esc
* all stop closing the sheet. `escapeDismissible`/`backdropDismissible`
* below let you turn off just one of the pointer/keyboard paths while
* leaving the others (and swipe-to-dismiss, which only this prop gates)
* alone — both default to whatever `dismissible` is.
* @default true
*/
dismissible?: boolean;
/** Whether Esc closes the sheet. @default `dismissible` */
escapeDismissible?: boolean;
/** Whether tapping the backdrop/empty track closes the sheet. @default `dismissible` */
backdropDismissible?: boolean;
/** CSP nonce for the injected style tag. */
nonce?: string;
/**
* Blend the page's `<meta name="theme-color">` toward black as the sheet
* travels, so the browser chrome (iOS Safari tints both the status bar area
* and the bottom toolbar from this tag; Android tints the status bar) dims
* along with the backdrop. Every theme-color tag is dimmed from its own
* base color, so the usual media-scoped light/dark pair works and stays
* correct even if the OS scheme flips while the sheet is open. On a
* bottom-anchored attached sheet, the strip behind the bottom toolbar takes
* the panel's own color instead (via a fixed sentinel element), so the bar
* reads as part of the sheet, not the dimmed page. No-ops if no
* tag holds a parseable color, or on platforms that ignore theme-color.
* @default false
*/
themeColorDimming?: boolean;
/**
* Fires on every frame the sheet travels: revealed px, 0-1 progress
* (against the first detent, same number `--scrollsheet-progress` uses),
* and `info` — per-detent progress plus the resolved travel range. `info`
* is reused and mutated in place every frame (this is the highest-
* frequency callback in the library); read it synchronously, don't store
* the reference. Keep this handler cheap — it runs on every travel frame.
*/
onTravel?: (revealedPx: number, progress: number, info: TravelInfo) => void;
/**
* Which edge the sheet is anchored to, or `"center"` for a centered modal
* dialog: content-sized, consumer CSS owns width, zoom+fade instead of
* travel, no detents or drag. @default 'bottom'
*/
side?: Side | "center";
/**
* Overrides the resolved presentation once the viewport reaches
* `desktopBreakpoint` — `side` stays the base presentation below it.
* Resolved via a `matchMedia` subscription: server render and the first
* client paint both resolve to `side` (never read from `window` during
* render — that's what would warn on hydration), the desktop check lands
* after mount. Crossing the breakpoint while the sheet is open
* re-presents instantly, no morph. Unset: `side` applies at every width,
* today's behavior exactly.
*/
desktopSide?: Side | "center";
/**
* The min-width (px) `desktopSide` takes over at. Only meaningful paired
* with `desktopSide` — set alone, it warns once in dev and does nothing.
* @default 768
*/
desktopBreakpoint?: number;
/**
* When false, the page behind stays fully interactive and scrollable — no
* backdrop, no focus trap, no inert-ing the rest of the page. Renders on a
* `<div popover="manual">` (top layer, non-blocking) where supported,
* falling back to a non-modal `<dialog>` (`.show()`) otherwise.
* @default true
*/
modal?: boolean;
/**
* Applies an iOS-style card effect to the page element marked
* `data-scrollsheet-background`, driven by this sheet's own travel
* progress. `'scale'` scales/insets/rounds it; `'parallax'` only shifts it.
* No-ops if no element is marked.
*
* Left unset, a full-height bottom sheet in the mobile presentation gets
* `'scale'` — that is how the platform presents a sheet that covers the
* screen. Desktop drawers dock beside the page and never move it, and a
* detached (floating-card) sheet never claims the full screen, so neither
* defaults on. `'none'` opts out anywhere.
*/
backgroundEffect?: "scale" | "parallax" | "none";
/**
* Explicit target for backgroundEffect, instead of the default document-wide
* `[data-scrollsheet-background]` query. Pass the same ref you'd otherwise
* mark with the attribute. Skips the ownership check the implicit default
* applies (an explicit ref is unambiguous about which page element it
* means) — matches how an explicit `backgroundEffect` prop today already
* bypasses the implicit default's full-height/opener-containment check.
*/
backgroundRef?: React.RefObject<HTMLElement | null>;
/**
* Inner-content-overflow scrollbar style, for the panel's own scroll at
* max detent (or any content taller than the panel). `'overlay'`: a thin
* auto-hiding thumb (iOS/macOS-style). `'hidden'`: no thumb, no native
* scrollbar either. `'native'`: let the browser draw
* its own.
* @default 'overlay'
*/
scrollbar?: "overlay" | "hidden" | "native";
/**
* Scoped to exactly two things: the backdrop's opacity and
* `themeColorDimming`. Both stay fully transparent/undimmed while the
* sheet is at or below this detent's resolved height, then interpolate
* from there up to the next detent above it (or the tallest detent, if
* this is already the tallest). Unset (default): dims across the full
* range from closed to the first detent, today's behavior. Does *not*
* affect `onTravel`'s progress argument, `--scrollsheet-progress`,
* stacking recede, or `backgroundEffect` — those all track the sheet's
* full-range travel 1:1 regardless of this prop.
*/
largestUndimmedDetent?: DetentSpec;
/**
* Drag sessions (mouse, and touch on non-modal sheets) only start from
* `<Sheet.Handle>` — a pointerdown elsewhere on the panel no-ops. Modal
* touch (native scroll drives the gesture) is restricted via
* `touch-action` instead, except at the tallest detent, where the panel's
* own content scroll still needs it.
* @default false
*/
handleOnly?: boolean;
/**
* No drag sessions at all, handle included — detent changes are
* programmatic (`activeDetent`, `actionsRef`) or click/keyboard
* (`<Sheet.Handle>`) only. Esc and backdrop dismissal are unaffected.
* @default false
*/
disableDrag?: boolean;
/**
* A release never skips over an intermediate detent — the resolved target
* is clamped to the immediate neighbor of the detent the gesture started
* from (or, for a native-scroll settle, the last settled detent).
* @default false
*/
sequentialDetents?: boolean;
/**
* Fraction (0-1) of the first detent below which a release dismisses the
* sheet, replacing the built-in half-of-first-detent rule.
* @default 0.5
*/
closeThreshold?: number;
/**
* Bottom sheets only. When the software keyboard opens with a text field
* focused inside the sheet, promote to the tallest detent so the field
* has the whole remaining viewport to scroll within — a short peek detent
* can't show a field the keyboard would otherwise cover. Restores the
* previous detent on blur unless something else (a drag, a controlled
* change) moved the sheet in between. Gated on the keyboard actually
* appearing (a nonzero measured inset), never on focus alone, so a
* desktop click into an input doesn't expand anything. Promotion goes
* through the same `setActiveDetent` path a controlled change uses — a
* controlled sheet must apply `onActiveDetentChange` back to
* `activeDetent` or this prop has no visible effect.
* @default false
*/
keyboardExpands?: boolean;
/**
* Fires once a drag session resolves, with the real pointer event (the
* `buttons===0` recovery path may pass the triggering `pointermove`
* instead of a `pointerup`) and whether the sheet is staying open —
* `false` only when the release resolved to a dismiss.
*/
onRelease?: (event: PointerEvent, willRemainOpen: boolean) => void;
}
declare function Root({ children, open: openProp, defaultOpen, onOpenChange, onOpenChangeComplete, actionsRef, detents, activeDetent: activeDetentProp, onActiveDetentChange, dismissible, escapeDismissible, backdropDismissible, nonce, themeColorDimming, onTravel, side, desktopSide, desktopBreakpoint: desktopBreakpointProp, modal, backgroundEffect, backgroundRef, scrollbar, largestUndimmedDetent, handleOnly, disableDrag, sequentialDetents, closeThreshold, keyboardExpands, onRelease }: SheetRootProps): React.JSX.Element;
//#endregion
//#region packages/scrollsheet/src/trigger.d.ts
interface SheetTriggerProps extends React.ComponentProps<"button"> {
/**
* Render the child element instead of a `<button>`, merging the trigger's
* props (aria, onClick) into it — for custom button components or links.
*/
asChild?: boolean;
}
declare const Trigger: React.ForwardRefExoticComponent<Omit<SheetTriggerProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
//#endregion
//#region packages/scrollsheet/src/content.d.ts
interface SheetContentProps extends React.ComponentProps<"div"> {
/** Accessible name when no <Sheet.Title> is rendered. */
"aria-label"?: string;
/**
* Stretches [data-scrollsheet-body] to fill the panel (flex column, the
* body itself flex:1/min-height:0) instead of sizing to its natural content
* height. Promotes the docs "Full-height content" recipe into the library —
* write your own inner-scroll child (flex:1, overflow-y:auto) with no CSS
* of your own required on the wrapper.
* @default false
*/
fill?: boolean;
/**
* Render the child element instead of scrollsheet's own panel `<div>`,
* merging the panel's props (role, tabIndex, ref, `data-scrollsheet-*`)
* into it — matches Radix `Dialog.Content asChild`: the child replaces the
* panel outright rather than nesting inside it. The child must be a single
* non-Fragment element; whatever it was given as its own children is
* hoisted into the body wrapper below (detent measurement and stacking
* still need a real DOM descendant there regardless of which element ends
* up as the panel).
* If `children` isn't a single non-Fragment element (missing, a string,
* multiple elements, a `<>...</>` Fragment…), `asChild` is ignored for
* that render — the default panel `<div>` is used instead, with a
* one-time dev warning.
* @default false
*/
asChild?: boolean;
}
declare const Content: React.ForwardRefExoticComponent<Omit<SheetContentProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
//#endregion
//#region packages/scrollsheet/src/misc.d.ts
interface SheetTitleProps extends React.ComponentProps<"h2"> {
/** Render the child element instead of an `<h2>`, merging props into it. */
asChild?: boolean;
}
declare const Title: React.ForwardRefExoticComponent<Omit<SheetTitleProps, "ref"> & React.RefAttributes<HTMLHeadingElement>>;
interface SheetDescriptionProps extends React.ComponentProps<"p"> {
/** Render the child element instead of a `<p>`, merging props into it. */
asChild?: boolean;
}
declare const Description: React.ForwardRefExoticComponent<Omit<SheetDescriptionProps, "ref"> & React.RefAttributes<HTMLParagraphElement>>;
interface SheetCloseProps extends React.ComponentProps<"button"> {
/** Render the child element instead of a `<button>`, merging props into it. */
asChild?: boolean;
}
declare const Close: React.ForwardRefExoticComponent<Omit<SheetCloseProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
//#endregion
export { SheetTitleProps as a, SheetContentProps as c, Root as d, SheetActions as f, DetentSpec as h, SheetDescriptionProps as i, SheetTriggerProps as l, TravelInfo as m, Description as n, Title as o, SheetRootProps as p, SheetCloseProps as r, Content as s, Close as t, Trigger as u };
+1
-1

@@ -1,2 +0,2 @@

import { a as SheetTitleProps, c as SheetContentProps, i as SheetDescriptionProps, l as SheetTriggerProps, n as Description, o as Title, p as SheetRootProps, r as SheetCloseProps, u as Trigger } from "./misc-B-HikMKY.mjs";
import { a as SheetTitleProps, c as SheetContentProps, i as SheetDescriptionProps, l as SheetTriggerProps, n as Description, o as Title, p as SheetRootProps, r as SheetCloseProps, u as Trigger } from "./misc-D1d_S8fO.mjs";
import * as React from "react";

@@ -3,0 +3,0 @@ //#region packages/scrollsheet/src/dialog/index.d.ts

"use client";
"use client";
import { d as useSheetContext, i as Content$1, l as composeRefs, n as Description, r as Title, s as Trigger, t as Close, u as Root$1 } from "./misc-DCcFJprt.mjs";
import { d as useSheetContext, i as Content$1, l as composeRefs, n as Description, r as Title, s as Trigger, t as Close, u as Root$1 } from "./misc-Bf6n3Ykh.mjs";
import * as React from "react";

@@ -5,0 +5,0 @@ import { Fragment, jsx } from "react/jsx-runtime";

@@ -1,3 +0,3 @@

import { a as SheetTitleProps, i as SheetDescriptionProps, l as SheetTriggerProps, n as Description, o as Title, r as SheetCloseProps, u as Trigger } from "./misc-B-HikMKY.mjs";
import { _ as composeOpenChange, a as DrawerHandleProps, b as resolveFadeFromIndex, c as DrawerPortalProps, d as NestedRoot, f as Overlay, g as composeHandleClick, h as VaulSnapPoint, i as DrawerContentProps, l as DrawerRootProps, m as Root, n as Drawer, o as DrawerNestedRootProps, p as Portal, r as DrawerClose, s as DrawerOverlayProps, t as Content, u as Handle, v as hasMultipleSnapPoints, y as resolveCloseThreshold } from "./index-BH5TewIe.mjs";
import { a as SheetTitleProps, i as SheetDescriptionProps, l as SheetTriggerProps, n as Description, o as Title, r as SheetCloseProps, u as Trigger } from "./misc-D1d_S8fO.mjs";
import { _ as composeOpenChange, a as DrawerHandleProps, b as resolveFadeFromIndex, c as DrawerPortalProps, d as NestedRoot, f as Overlay, g as composeHandleClick, h as VaulSnapPoint, i as DrawerContentProps, l as DrawerRootProps, m as Root, n as Drawer, o as DrawerNestedRootProps, p as Portal, r as DrawerClose, s as DrawerOverlayProps, t as Content, u as Handle, v as hasMultipleSnapPoints, y as resolveCloseThreshold } from "./index-D3EEqjHp.mjs";
export { DrawerClose as Close, Content, Description, Drawer, type SheetCloseProps as DrawerCloseProps, DrawerContentProps, type SheetDescriptionProps as DrawerDescriptionProps, DrawerHandleProps, DrawerNestedRootProps, DrawerOverlayProps, DrawerPortalProps, DrawerRootProps, type SheetTitleProps as DrawerTitleProps, type SheetTriggerProps as DrawerTriggerProps, Handle, NestedRoot, Overlay, Portal, Root, Title, Trigger, VaulSnapPoint, composeHandleClick, composeOpenChange, hasMultipleSnapPoints, resolveCloseThreshold, resolveFadeFromIndex };
"use client";
"use client";
import { n as Description, r as Title, s as Trigger } from "./misc-DCcFJprt.mjs";
import { a as NestedRoot, c as Root, d as hasMultipleSnapPoints, f as resolveCloseThreshold, i as Handle, l as composeHandleClick, n as Drawer, o as Overlay, p as resolveFadeFromIndex, r as DrawerClose, s as Portal, t as Content, u as composeOpenChange } from "./drawer-BJyI4TQS.mjs";
import { n as Description, r as Title, s as Trigger } from "./misc-Bf6n3Ykh.mjs";
import { a as NestedRoot, c as Root, d as hasMultipleSnapPoints, f as resolveCloseThreshold, i as Handle, l as composeHandleClick, n as Drawer, o as Overlay, p as resolveFadeFromIndex, r as DrawerClose, s as Portal, t as Content, u as composeOpenChange } from "./drawer-Daga_OBC.mjs";
export { DrawerClose as Close, Content, Description, Drawer, Handle, NestedRoot, Overlay, Portal, Root, Title, Trigger, composeHandleClick, composeOpenChange, hasMultipleSnapPoints, resolveCloseThreshold, resolveFadeFromIndex };

@@ -1,4 +0,4 @@

import { a as SheetTitleProps, c as SheetContentProps, d as Root, f as SheetActions, g as DetentSpec, h as Side, i as SheetDescriptionProps, l as SheetTriggerProps, m as TravelInfo, n as Description, o as Title, p as SheetRootProps, r as SheetCloseProps, s as Content, t as Close, u as Trigger } from "./misc-B-HikMKY.mjs";
import { a as SheetTitleProps, c as SheetContentProps, d as Root, f as SheetActions, g as DetentSpec, h as Side, i as SheetDescriptionProps, l as SheetTriggerProps, m as TravelInfo, n as Description, o as Title, p as SheetRootProps, r as SheetCloseProps, s as Content, t as Close, u as Trigger } from "./misc-D1d_S8fO.mjs";
import { Dialog, DialogContentProps, DialogOverlayProps, DialogPortalProps, DialogRootProps } from "./dialog.mjs";
import { S as SheetHandleProps, a as DrawerHandleProps, c as DrawerPortalProps, h as VaulSnapPoint, i as DrawerContentProps, l as DrawerRootProps, n as Drawer, o as DrawerNestedRootProps, s as DrawerOverlayProps, x as Handle } from "./index-BH5TewIe.mjs";
import { S as SheetHandleProps, a as DrawerHandleProps, c as DrawerPortalProps, h as VaulSnapPoint, i as DrawerContentProps, l as DrawerRootProps, n as Drawer, o as DrawerNestedRootProps, s as DrawerOverlayProps, x as Handle } from "./index-D3EEqjHp.mjs";
import { a as SonnerPosition, c as ToastData, d as ToastPromiseData, f as ToastRecord, g as useToasts, h as useSonner, l as ToastIcons, m as toast, n as ToasterShell, o as ToastAction, p as ToastType, r as ToasterProps, s as ToastClassnames, t as injectToastStylesInto, u as ToastPosition } from "./index-CCNu5CBr.mjs";

@@ -5,0 +5,0 @@ //#region packages/scrollsheet/src/motion/spring.d.ts

"use client";
import { a as injectStylesInto, i as Content, n as Description, o as spring, r as Title, s as Trigger, t as Close, u as Root } from "./misc-DCcFJprt.mjs";
import { a as injectStylesInto, i as Content, n as Description, o as spring, r as Title, s as Trigger, t as Close, u as Root } from "./misc-Bf6n3Ykh.mjs";
import { o as hasDialogSupport } from "./cn-DgpbGWy-.mjs";
import { m as Handle, n as Drawer } from "./drawer-BJyI4TQS.mjs";
import { m as Handle, n as Drawer } from "./drawer-Daga_OBC.mjs";
import { Dialog } from "./dialog.mjs";

@@ -6,0 +6,0 @@ import { a as useSonner, i as toast, o as useToasts, r as injectToastStylesInto, t as ToasterShell } from "./toast-BEuD1_sZ.mjs";

"use client";
"use client";
import { d as useSheetContext, i as Content$1, l as composeRefs, n as Description, r as Title, s as Trigger, t as Close, u as Root$1 } from "./misc-DMsqVKo9.mjs";
import { m as warnOnce } from "./cn-eC4hyhVS.mjs";
import { d as useSheetContext, i as Content$1, l as composeRefs, n as Description, r as Title, s as Trigger, t as Close, u as Root$1 } from "./misc-VkdphfSa.mjs";
import { h as warnOnce } from "./cn-Cs7ZylHz.mjs";
import * as React from "react";

@@ -6,0 +6,0 @@ import { Fragment, jsx } from "react/jsx-runtime";

"use client";
"use client";
import { n as Description, r as Title, s as Trigger } from "./misc-DMsqVKo9.mjs";
import { a as NestedRoot, c as Root, d as hasMultipleSnapPoints, f as resolveCloseThreshold, i as Handle, l as composeHandleClick, n as Drawer, o as Overlay, p as resolveFadeFromIndex, r as DrawerClose, s as Portal, t as Content, u as composeOpenChange } from "./drawer-DF--O9O7.mjs";
import { n as Description, r as Title, s as Trigger } from "./misc-VkdphfSa.mjs";
import { a as NestedRoot, c as Root, d as hasMultipleSnapPoints, f as resolveCloseThreshold, i as Handle, l as composeHandleClick, n as Drawer, o as Overlay, p as resolveFadeFromIndex, r as DrawerClose, s as Portal, t as Content, u as composeOpenChange } from "./drawer-DqGJtMsN.mjs";
export { DrawerClose as Close, Content, Description, Drawer, Handle, NestedRoot, Overlay, Portal, Root, Title, Trigger, composeHandleClick, composeOpenChange, hasMultipleSnapPoints, resolveCloseThreshold, resolveFadeFromIndex };
"use client";
import { a as injectStylesInto, i as Content, n as Description, o as spring, r as Title, s as Trigger, t as Close, u as Root } from "./misc-DMsqVKo9.mjs";
import { o as hasDialogSupport } from "./cn-eC4hyhVS.mjs";
import { m as Handle, n as Drawer } from "./drawer-DF--O9O7.mjs";
import { a as injectStylesInto, i as Content, n as Description, o as spring, r as Title, s as Trigger, t as Close, u as Root } from "./misc-VkdphfSa.mjs";
import { o as hasDialogSupport } from "./cn-Cs7ZylHz.mjs";
import { m as Handle, n as Drawer } from "./drawer-DqGJtMsN.mjs";
import { Dialog } from "./dialog.mjs";
import { a as useSonner, i as toast, o as useToasts, r as injectToastStylesInto, t as ToasterShell } from "./toast-CmxAlNdX.mjs";
import { a as useSonner, i as toast, o as useToasts, r as injectToastStylesInto, t as ToasterShell } from "./toast-o4mAr-oF.mjs";
//#region packages/scrollsheet/src/index.ts

@@ -8,0 +8,0 @@ function isSupported() {

"use client";
"use client";
import { a as useSonner, i as toast, n as resolveVisibleToasts, o as useToasts, r as injectToastStylesInto, t as ToasterShell } from "./toast-CmxAlNdX.mjs";
import { a as useSonner, i as toast, n as resolveVisibleToasts, o as useToasts, r as injectToastStylesInto, t as ToasterShell } from "./toast-o4mAr-oF.mjs";
export { ToasterShell as Toaster, injectToastStylesInto, resolveVisibleToasts, toast, useSonner, useToasts };
"use client";
"use client";
import { c as composeRefs, i as Content$1, l as Root$1, n as Description, o as Trigger, r as Title, t as Close, u as useSheetContext } from "./misc-BSxJRAef.mjs";
import { m as warnOnce } from "./cn-eC4hyhVS.mjs";
import { c as composeRefs, i as Content$1, l as Root$1, n as Description, o as Trigger, r as Title, t as Close, u as useSheetContext } from "./misc-BJVcpoIS.mjs";
import { h as warnOnce } from "./cn-Cs7ZylHz.mjs";
import * as React from "react";

@@ -6,0 +6,0 @@ import { Fragment, jsx } from "react/jsx-runtime";

"use client";
"use client";
import { n as Description, o as Trigger, r as Title } from "./misc-BSxJRAef.mjs";
import { a as NestedRoot, c as Root, d as hasMultipleSnapPoints, f as resolveCloseThreshold, i as Handle, l as composeHandleClick, n as Drawer, o as Overlay, p as resolveFadeFromIndex, r as DrawerClose, s as Portal, t as Content, u as composeOpenChange } from "./drawer-BkFqMDfO.mjs";
import { n as Description, o as Trigger, r as Title } from "./misc-BJVcpoIS.mjs";
import { a as NestedRoot, c as Root, d as hasMultipleSnapPoints, f as resolveCloseThreshold, i as Handle, l as composeHandleClick, n as Drawer, o as Overlay, p as resolveFadeFromIndex, r as DrawerClose, s as Portal, t as Content, u as composeOpenChange } from "./drawer-3cpXDElf.mjs";
export { DrawerClose as Close, Content, Description, Drawer, Handle, NestedRoot, Overlay, Portal, Root, Title, Trigger, composeHandleClick, composeOpenChange, hasMultipleSnapPoints, resolveCloseThreshold, resolveFadeFromIndex };
"use client";
import { a as injectStylesInto, i as Content, l as Root, n as Description, o as Trigger, r as Title, t as Close } from "./misc-BSxJRAef.mjs";
import { o as hasDialogSupport } from "./cn-eC4hyhVS.mjs";
import { a as injectStylesInto, i as Content, l as Root, n as Description, o as Trigger, r as Title, t as Close } from "./misc-BJVcpoIS.mjs";
import { o as hasDialogSupport } from "./cn-Cs7ZylHz.mjs";
import { a as spring } from "./animate-BlH_-zDl.mjs";
import { m as Handle, n as Drawer } from "./drawer-BkFqMDfO.mjs";
import { m as Handle, n as Drawer } from "./drawer-3cpXDElf.mjs";
import "./motion.mjs";
import { Dialog } from "./dialog.mjs";
import { a as useSonner, i as toast, o as useToasts, r as injectToastStylesInto, t as ToasterShell } from "./toast-BZbhVYBy.mjs";
import { a as useSonner, i as toast, o as useToasts, r as injectToastStylesInto, t as ToasterShell } from "./toast-C6dvZqSe.mjs";
//#region packages/scrollsheet/src/index.ts

@@ -10,0 +10,0 @@ function isSupported() {

"use client";
"use client";
import { a as useSonner, i as toast, n as resolveVisibleToasts, o as useToasts, r as injectToastStylesInto, t as ToasterShell } from "./toast-BZbhVYBy.mjs";
import { a as useSonner, i as toast, n as resolveVisibleToasts, o as useToasts, r as injectToastStylesInto, t as ToasterShell } from "./toast-C6dvZqSe.mjs";
export { ToasterShell as Toaster, injectToastStylesInto, resolveVisibleToasts, toast, useSonner, useToasts };

@@ -1,2 +0,2 @@

import { a as SheetTitleProps, c as SheetContentProps, i as SheetDescriptionProps, l as SheetTriggerProps, n as Description, o as Title, p as SheetRootProps, r as SheetCloseProps, u as Trigger } from "./misc-DD6-LNnA.mjs";
import { a as SheetTitleProps, c as SheetContentProps, i as SheetDescriptionProps, l as SheetTriggerProps, n as Description, o as Title, p as SheetRootProps, r as SheetCloseProps, u as Trigger } from "./misc-GeCxhsLP.mjs";
import * as React from "react";

@@ -3,0 +3,0 @@ //#region packages/scrollsheet/src/dialog/index.d.ts

"use client";
"use client";
import { c as composeRefs, i as Content$1, l as Root$1, n as Description, o as Trigger, r as Title, t as Close, u as useSheetContext } from "./misc-D0efWKP-.mjs";
import { c as composeRefs, i as Content$1, l as Root$1, n as Description, o as Trigger, r as Title, t as Close, u as useSheetContext } from "./misc-DQmFNK1F.mjs";
import * as React from "react";

@@ -5,0 +5,0 @@ import { Fragment, jsx } from "react/jsx-runtime";

@@ -1,3 +0,3 @@

import { a as SheetTitleProps, i as SheetDescriptionProps, l as SheetTriggerProps, n as Description, o as Title, r as SheetCloseProps, u as Trigger } from "./misc-DD6-LNnA.mjs";
import { _ as composeOpenChange, a as DrawerHandleProps, b as resolveFadeFromIndex, c as DrawerPortalProps, d as NestedRoot, f as Overlay, g as composeHandleClick, h as VaulSnapPoint, i as DrawerContentProps, l as DrawerRootProps, m as Root, n as Drawer, o as DrawerNestedRootProps, p as Portal, r as DrawerClose, s as DrawerOverlayProps, t as Content, u as Handle, v as hasMultipleSnapPoints, y as resolveCloseThreshold } from "./index-BqqKGTeu.mjs";
import { a as SheetTitleProps, i as SheetDescriptionProps, l as SheetTriggerProps, n as Description, o as Title, r as SheetCloseProps, u as Trigger } from "./misc-GeCxhsLP.mjs";
import { _ as composeOpenChange, a as DrawerHandleProps, b as resolveFadeFromIndex, c as DrawerPortalProps, d as NestedRoot, f as Overlay, g as composeHandleClick, h as VaulSnapPoint, i as DrawerContentProps, l as DrawerRootProps, m as Root, n as Drawer, o as DrawerNestedRootProps, p as Portal, r as DrawerClose, s as DrawerOverlayProps, t as Content, u as Handle, v as hasMultipleSnapPoints, y as resolveCloseThreshold } from "./index-D0yJ5f-N.mjs";
export { DrawerClose as Close, Content, Description, Drawer, type SheetCloseProps as DrawerCloseProps, DrawerContentProps, type SheetDescriptionProps as DrawerDescriptionProps, DrawerHandleProps, DrawerNestedRootProps, DrawerOverlayProps, DrawerPortalProps, DrawerRootProps, type SheetTitleProps as DrawerTitleProps, type SheetTriggerProps as DrawerTriggerProps, Handle, NestedRoot, Overlay, Portal, Root, Title, Trigger, VaulSnapPoint, composeHandleClick, composeOpenChange, hasMultipleSnapPoints, resolveCloseThreshold, resolveFadeFromIndex };
"use client";
"use client";
import { n as Description, o as Trigger, r as Title } from "./misc-D0efWKP-.mjs";
import { a as NestedRoot, c as Root, d as hasMultipleSnapPoints, f as resolveCloseThreshold, i as Handle, l as composeHandleClick, n as Drawer, o as Overlay, p as resolveFadeFromIndex, r as DrawerClose, s as Portal, t as Content, u as composeOpenChange } from "./drawer-CG8clauA.mjs";
import { n as Description, o as Trigger, r as Title } from "./misc-DQmFNK1F.mjs";
import { a as NestedRoot, c as Root, d as hasMultipleSnapPoints, f as resolveCloseThreshold, i as Handle, l as composeHandleClick, n as Drawer, o as Overlay, p as resolveFadeFromIndex, r as DrawerClose, s as Portal, t as Content, u as composeOpenChange } from "./drawer-BNEgYQZg.mjs";
export { DrawerClose as Close, Content, Description, Drawer, Handle, NestedRoot, Overlay, Portal, Root, Title, Trigger, composeHandleClick, composeOpenChange, hasMultipleSnapPoints, resolveCloseThreshold, resolveFadeFromIndex };

@@ -1,5 +0,5 @@

import { a as SheetTitleProps, c as SheetContentProps, d as Root, f as SheetActions, h as DetentSpec, i as SheetDescriptionProps, l as SheetTriggerProps, m as TravelInfo, n as Description, o as Title, p as SheetRootProps, r as SheetCloseProps, s as Content, t as Close, u as Trigger } from "./misc-DD6-LNnA.mjs";
import { a as SheetTitleProps, c as SheetContentProps, d as Root, f as SheetActions, h as DetentSpec, i as SheetDescriptionProps, l as SheetTriggerProps, m as TravelInfo, n as Description, o as Title, p as SheetRootProps, r as SheetCloseProps, s as Content, t as Close, u as Trigger } from "./misc-GeCxhsLP.mjs";
import { t as Side } from "./geometry-C78uYrYJ.mjs";
import { Dialog, DialogContentProps, DialogOverlayProps, DialogPortalProps, DialogRootProps } from "./dialog.mjs";
import { S as SheetHandleProps, a as DrawerHandleProps, c as DrawerPortalProps, h as VaulSnapPoint, i as DrawerContentProps, l as DrawerRootProps, n as Drawer, o as DrawerNestedRootProps, s as DrawerOverlayProps, x as Handle } from "./index-BqqKGTeu.mjs";
import { S as SheetHandleProps, a as DrawerHandleProps, c as DrawerPortalProps, h as VaulSnapPoint, i as DrawerContentProps, l as DrawerRootProps, n as Drawer, o as DrawerNestedRootProps, s as DrawerOverlayProps, x as Handle } from "./index-D0yJ5f-N.mjs";
import { a as SpringConfig, c as spring, o as SpringCurve } from "./index-DbIsUM4v.mjs";

@@ -6,0 +6,0 @@ import { a as SonnerPosition, c as ToastData, d as ToastPromiseData, f as ToastRecord, g as useToasts, h as useSonner, l as ToastIcons, m as toast, n as ToasterShell, o as ToastAction, p as ToastType, r as ToasterProps, s as ToastClassnames, t as injectToastStylesInto, u as ToastPosition } from "./index-CCNu5CBr.mjs";

"use client";
import { a as injectStylesInto, i as Content, l as Root, n as Description, o as Trigger, r as Title, t as Close } from "./misc-D0efWKP-.mjs";
import { a as injectStylesInto, i as Content, l as Root, n as Description, o as Trigger, r as Title, t as Close } from "./misc-DQmFNK1F.mjs";
import { o as hasDialogSupport } from "./cn-DgpbGWy-.mjs";
import { a as spring } from "./animate-BlH_-zDl.mjs";
import { m as Handle, n as Drawer } from "./drawer-CG8clauA.mjs";
import { m as Handle, n as Drawer } from "./drawer-BNEgYQZg.mjs";
import "./motion.mjs";

@@ -7,0 +7,0 @@ import { Dialog } from "./dialog.mjs";

@@ -1,1 +0,1 @@

.scrollsheet-dialog[data-scrollsheet-side=bottom]{--scrollsheet-keyboard-lift:var(--scrollsheet-keyboard,0px)}.scrollsheet-dialog{width:100%;height:calc(100% + var(--scrollsheet-keyboard-lift,0px));--scrollsheet-ease:linear(0, 0.042 3.4%, 0.136 6.9%, 0.37 13.8%, 0.481 17.2%, 0.58 20.7%, 0.664 24.1%, 0.735 27.6%, 0.793 31%, 0.839 34.5%, 0.876 37.9%, 0.906 41.4%, 0.928 44.8%, 0.946 48.3%, 0.96 51.7%, 0.978 58.6%, 0.988 65.5%, 0.994 72.4%, 0.998 82.8%, 1);--scrollsheet-dur:475ms;background:0 0;border:none;outline:none;max-width:none;max-height:none;margin:0;padding:0;position:fixed;inset:0;overflow:clip}.scrollsheet-dialog::backdrop{background:0 0}:where(.scrollsheet-backdrop){background:var(--scrollsheet-backdrop,#00000052);opacity:0;pointer-events:none;position:fixed;inset:0}.scrollsheet-dialog[data-scrollsheet-state=opening] .scrollsheet-backdrop{opacity:min(var(--scrollsheet-dim,0), 1);transition:opacity var(--scrollsheet-dur) var(--scrollsheet-ease)}.scrollsheet-dialog[data-scrollsheet-state=open] .scrollsheet-backdrop{opacity:min(var(--scrollsheet-dim,0), 1);transition:none}.scrollsheet-dialog[data-scrollsheet-state=closing] .scrollsheet-backdrop{opacity:0;transition:opacity var(--scrollsheet-dur) var(--scrollsheet-ease)}:where(.scrollsheet-top-chrome){height:calc(env(safe-area-inset-top,0px) + 8px);inset:0 0 auto}:where(.scrollsheet-bottom-chrome){height:calc(env(safe-area-inset-bottom,0px) + 32px);inset:auto 0 0}:where(.scrollsheet-top-chrome)~:where([data-scrollsheet-backdrop]){top:calc(env(safe-area-inset-top,0px) + 8px)}:where(.scrollsheet-bottom-chrome)~:where([data-scrollsheet-backdrop]){bottom:calc(env(safe-area-inset-bottom,0px) + 32px)}.scrollsheet-dialog:is([data-scrollsheet-state=pre],[data-scrollsheet-state=opening]) :is(.scrollsheet-top-chrome,.scrollsheet-bottom-chrome){opacity:min(var(--scrollsheet-dim,0), 1);transition:none}.scrollsheet-bottom-sentinel{height:min(calc(max(env(safe-area-inset-bottom,0px), 24px) + 45vh), var(--scrollsheet-reveal,24px));pointer-events:none;position:fixed;inset:auto 0 0}.scrollsheet-track{left:0;right:0;top:var(--scrollsheet-vv-top,0px);height:calc(var(--scrollsheet-vv-height,100%) + var(--scrollsheet-keyboard-lift,0px));overscroll-behavior:contain;scroll-snap-type:y mandatory;scrollbar-width:none;position:fixed;overflow-y:scroll}.scrollsheet-track::-webkit-scrollbar{display:none}.scrollsheet-dialog:is([data-scrollsheet-side=left],[data-scrollsheet-side=right]) .scrollsheet-track{scroll-snap-type:x mandatory;overscroll-behavior-x:none;overflow:scroll hidden}.scrollsheet-dialog[data-scrollsheet-modal=false]{pointer-events:none}.scrollsheet-dialog[data-scrollsheet-modal=false] .scrollsheet-panel{pointer-events:auto;touch-action:none}.scrollsheet-canvas{height:calc(100% + var(--scrollsheet-max-detent,0px));position:relative}.scrollsheet-dialog:is([data-scrollsheet-side=left],[data-scrollsheet-side=right]) .scrollsheet-canvas{width:calc(100% + var(--scrollsheet-max-detent,0px));height:100%}.scrollsheet-canvas:after{content:"";top:calc(100% - var(--scrollsheet-keyboard,0px));--scrollsheet-od:calc(120vh + var(--scrollsheet-keyboard,0px));height:0;box-shadow:0 calc(var(--scrollsheet-od) / 2) 0 calc(var(--scrollsheet-od) / 2) var(--scrollsheet-panel-bg,transparent);pointer-events:none;position:absolute;left:0;right:0}.scrollsheet-dialog[data-scrollsheet-side=top] .scrollsheet-canvas:after{box-shadow:0 -60vh 0 60vh var(--scrollsheet-panel-bg,transparent);top:auto;bottom:100%}.scrollsheet-dialog[data-scrollsheet-side=left] .scrollsheet-canvas:after{width:0;height:auto;box-shadow:-60vw 0 0 60vw var(--scrollsheet-panel-bg,transparent);inset:0 100% 0 auto}.scrollsheet-dialog[data-scrollsheet-side=right] .scrollsheet-canvas:after{width:0;height:auto;box-shadow:60vw 0 0 60vw var(--scrollsheet-panel-bg,transparent);inset:0 auto 0 100%}.scrollsheet-dialog[data-scrollsheet-detached] .scrollsheet-canvas:after{box-shadow:none}.scrollsheet-snap{pointer-events:none;scroll-snap-align:start;width:100%;height:1px;position:absolute;left:0}.scrollsheet-dialog:is([data-scrollsheet-side=left],[data-scrollsheet-side=right]) .scrollsheet-snap{width:1px;height:100%;top:0;left:auto}.scrollsheet-panel{--scrollsheet-eg:calc(var(--scrollsheet-inset-bottom,0px) + var(--scrollsheet-keyboard,0px));bottom:var(--scrollsheet-eg);left:var(--scrollsheet-inset-x,0px);right:var(--scrollsheet-inset-x,0px);height:var(--scrollsheet-max-detent,auto);max-height:var(--scrollsheet-vv-height,none);will-change:transform;padding-bottom:max(var(--scrollsheet-safe-area,env(safe-area-inset-bottom,0px)), 0px);position:absolute;overflow:hidden;transform:translateY(100%)}.scrollsheet-dialog[data-scrollsheet-side=top] .scrollsheet-panel{bottom:auto;top:var(--scrollsheet-inset-top,0px);left:var(--scrollsheet-inset-x,0px);right:var(--scrollsheet-inset-x,0px);height:var(--scrollsheet-max-detent,auto);padding-bottom:0;padding-top:max(var(--scrollsheet-safe-area-top,env(safe-area-inset-top,0px)), 0px);transform:translateY(-100%)}.scrollsheet-dialog:is([data-scrollsheet-side=left],[data-scrollsheet-side=right]) .scrollsheet-panel{top:var(--scrollsheet-inset-y,0px);bottom:var(--scrollsheet-inset-y,0px);width:var(--scrollsheet-max-detent,auto);height:auto;padding-top:max(var(--scrollsheet-safe-area-top,env(safe-area-inset-top,0px)), 0px)}.scrollsheet-dialog[data-scrollsheet-side=left] .scrollsheet-panel{right:auto;left:var(--scrollsheet-inset-left,0px);padding-left:max(var(--scrollsheet-safe-area-left,env(safe-area-inset-left,0px)), 0px);transform:translate(-100%)}.scrollsheet-dialog[data-scrollsheet-side=right] .scrollsheet-panel{left:auto;right:var(--scrollsheet-inset-right,0px);padding-right:max(var(--scrollsheet-safe-area-right,env(safe-area-inset-right,0px)), 0px);transform:translate(100%)}.scrollsheet-dialog:is([data-scrollsheet-side=top],[data-scrollsheet-side=left]) .scrollsheet-panel{justify-content:flex-end;display:flex}.scrollsheet-dialog[data-scrollsheet-side=top] .scrollsheet-panel{flex-direction:column}.scrollsheet-dialog[data-scrollsheet-side=left] .scrollsheet-panel{flex-direction:row}.scrollsheet-dialog[data-scrollsheet-side=left] .scrollsheet-panel>*{flex:0 0 100%;min-width:0}:where(.scrollsheet-panel){scrollbar-width:none}:where(.scrollsheet-panel)::-webkit-scrollbar{display:none}:where([data-scrollsheet-nested-scroll]){scrollbar-width:none}:where([data-scrollsheet-nested-scroll])::-webkit-scrollbar{display:none}:where(.scrollsheet-dialog[data-scrollsheet-scrollbar=overlay] [data-scrollsheet-nested-scroll]){position:relative}:where(.scrollsheet-panel){--scrollsheet-radius:20px;background:var(--scrollsheet-bg,#fff);color:var(--scrollsheet-fg,#1d1d1f);border-radius:var(--scrollsheet-radius) var(--scrollsheet-radius) 0 0;box-shadow:var(--scrollsheet-shadow,0 -8px 32px #00000029)}@media (prefers-color-scheme:dark){:where(.scrollsheet-panel){background:var(--scrollsheet-bg,#1c1c1e);color:var(--scrollsheet-fg,#f5f5f7)}}.scrollsheet-dialog[data-scrollsheet-side=top] :where(.scrollsheet-panel){border-radius:0 0 var(--scrollsheet-radius) var(--scrollsheet-radius);box-shadow:var(--scrollsheet-shadow,0 8px 32px #00000029)}.scrollsheet-dialog[data-scrollsheet-side=left] :where(.scrollsheet-panel){box-shadow:var(--scrollsheet-shadow,8px 0 32px #00000029);border-radius:0}.scrollsheet-dialog[data-scrollsheet-side=right] :where(.scrollsheet-panel){box-shadow:var(--scrollsheet-shadow,-8px 0 32px #00000029);border-radius:0}@media (min-width:768px){:where(.scrollsheet-dialog[data-scrollsheet-side=bottom]) :where(.scrollsheet-panel),:where(.scrollsheet-dialog[data-scrollsheet-side=bottom]) .scrollsheet-canvas:after{width:min(var(--scrollsheet-max-inline,640px), calc(100% - 2 * var(--scrollsheet-desktop-margin,24px)));margin-inline:auto var(--scrollsheet-desktop-margin,24px)}.scrollsheet-dialog[data-scrollsheet-side=bottom] .scrollsheet-panel{--scrollsheet-inset-bottom:var(--scrollsheet-desktop-margin,24px);--scrollsheet-dg:calc(var(--scrollsheet-desktop-margin,24px) + var(--scrollsheet-inset-x,0px) + 32px)}.scrollsheet-dialog[data-scrollsheet-side=bottom]:where([data-scrollsheet-detached]) .scrollsheet-panel,.scrollsheet-dialog[data-scrollsheet-side=bottom][data-scrollsheet-state=closing]:where([data-scrollsheet-detached]) .scrollsheet-panel{transform:translate3d(calc(100% + var(--scrollsheet-dg,32px)), 0, 0)}.scrollsheet-dialog[data-scrollsheet-side=bottom]:where([data-scrollsheet-detached][data-scrollsheet-rtl]) .scrollsheet-panel,.scrollsheet-dialog[data-scrollsheet-side=bottom][data-scrollsheet-state=closing]:where([data-scrollsheet-detached][data-scrollsheet-rtl]) .scrollsheet-panel{transform:translate3d(calc(-100% - var(--scrollsheet-dg,32px)), 0, 0)}.scrollsheet-dialog[data-scrollsheet-side=top] .scrollsheet-panel{top:var(--scrollsheet-desktop-margin,24px);border-radius:var(--scrollsheet-radius)}.scrollsheet-dialog[data-scrollsheet-side=bottom] :where(.scrollsheet-handle){opacity:0;pointer-events:none;width:5px;height:44px;margin:-22px 0 0;position:absolute;top:50%;left:10px}.scrollsheet-dialog[data-scrollsheet-side=bottom] :where(.scrollsheet-handle:focus-visible){opacity:1;pointer-events:auto}.scrollsheet-dialog[data-scrollsheet-side=bottom][data-scrollsheet-has-handle] :where(.scrollsheet-body){padding-top:18px}:where(.scrollsheet-dialog[data-scrollsheet-side=top]) :where(.scrollsheet-panel),:where(.scrollsheet-dialog[data-scrollsheet-side=top]) .scrollsheet-canvas:after{max-width:var(--scrollsheet-max-inline,640px);margin-inline:auto}}.scrollsheet-dialog[data-scrollsheet-detached] :where(.scrollsheet-panel){padding-bottom:max(var(--scrollsheet-safe-area,0px), 0px);border-radius:var(--scrollsheet-radius)}.scrollsheet-dialog[data-scrollsheet-detached][data-scrollsheet-side=bottom] :where(.scrollsheet-panel){--scrollsheet-eg:calc(max(var(--scrollsheet-inset-bottom,0px), env(safe-area-inset-bottom,0px)) + var(--scrollsheet-keyboard,0px));bottom:var(--scrollsheet-eg)}.scrollsheet-dialog[data-scrollsheet-scrollbar=native] .scrollsheet-panel{scrollbar-width:auto}.scrollsheet-dialog[data-scrollsheet-scrollbar=native] .scrollsheet-panel::-webkit-scrollbar{display:block}.scrollsheet-dialog[data-scrollsheet-scrollbar=native] [data-scrollsheet-nested-scroll]{scrollbar-width:auto}.scrollsheet-dialog[data-scrollsheet-scrollbar=native] [data-scrollsheet-nested-scroll]::-webkit-scrollbar{display:block}.scrollsheet-dialog[data-scrollsheet-state=opening] .scrollsheet-panel,.scrollsheet-dialog[data-scrollsheet-state=open] .scrollsheet-panel{transform:translate(0,0)}.scrollsheet-dialog[data-scrollsheet-state=closing] .scrollsheet-panel{transform:translate3d(0, calc(100% + var(--scrollsheet-eg,0px) + 32px), 0)}.scrollsheet-dialog[data-scrollsheet-side=top][data-scrollsheet-state=closing] .scrollsheet-panel{transform:translate3d(0, calc(-100% - var(--scrollsheet-inset-top,0px) - 32px), 0)}.scrollsheet-dialog[data-scrollsheet-side=left][data-scrollsheet-state=closing] .scrollsheet-panel{transform:translate3d(calc(-100% - var(--scrollsheet-inset-left,0px) - 32px), 0, 0)}.scrollsheet-dialog[data-scrollsheet-side=right][data-scrollsheet-state=closing] .scrollsheet-panel{transform:translate3d(calc(100% + var(--scrollsheet-inset-right,0px) + 32px), 0, 0)}.scrollsheet-dialog:is([data-scrollsheet-at-max],[data-scrollsheet-kb]) .scrollsheet-panel{overflow-y:auto}.scrollsheet-dialog[data-scrollsheet-kb][data-scrollsheet-side=bottom] .scrollsheet-panel{--scrollsheet-eg:calc(var(--scrollsheet-inset-bottom,0px) + var(--scrollsheet-keyboard,0px) + var(--scrollsheet-kb-hang,0px));box-shadow:none;border-bottom-right-radius:0;border-bottom-left-radius:0}.scrollsheet-kb-fade{display:none}.scrollsheet-dialog[data-scrollsheet-kb][data-scrollsheet-side=bottom] .scrollsheet-kb-fade{left:var(--scrollsheet-inset-x,0px);right:var(--scrollsheet-inset-x,0px);bottom:calc(var(--scrollsheet-keyboard,0px) + var(--scrollsheet-kb-hang,0px));background:linear-gradient(to bottom, transparent, var(--scrollsheet-panel-bg,#fff) 75%);pointer-events:none;z-index:1;height:18px;display:block;position:absolute}.scrollsheet-dialog:is([data-scrollsheet-handle-only],[data-scrollsheet-disable-drag]):not([data-scrollsheet-at-max],[data-scrollsheet-at-nested-boundary],[data-scrollsheet-kb]) :is(.scrollsheet-track,.scrollsheet-canvas){touch-action:none}.scrollsheet-dialog[data-scrollsheet-handle-only] .scrollsheet-panel,.scrollsheet-dialog[data-scrollsheet-disable-drag] .scrollsheet-panel{touch-action:pan-y}.scrollsheet-dialog[data-scrollsheet-handle-only]:not([data-scrollsheet-at-nested-boundary]) .scrollsheet-panel,.scrollsheet-dialog[data-scrollsheet-disable-drag]:not([data-scrollsheet-at-nested-boundary]) .scrollsheet-panel{overscroll-behavior:contain}.scrollsheet-dialog[data-scrollsheet-at-nested-boundary] .scrollsheet-track,.scrollsheet-dialog[data-scrollsheet-at-nested-boundary] .scrollsheet-canvas{touch-action:pan-y}.scrollsheet-dialog[data-scrollsheet-side=left][data-scrollsheet-at-nested-boundary] .scrollsheet-track,.scrollsheet-dialog[data-scrollsheet-side=left][data-scrollsheet-at-nested-boundary] .scrollsheet-canvas,.scrollsheet-dialog[data-scrollsheet-side=right][data-scrollsheet-at-nested-boundary] .scrollsheet-track,.scrollsheet-dialog[data-scrollsheet-side=right][data-scrollsheet-at-nested-boundary] .scrollsheet-canvas{touch-action:pan-x}:where(.scrollsheet-scrollbar){top:0;width:var(--scrollsheet-scrollbar-width,4px);box-sizing:border-box;background:var(--scrollsheet-scrollbar-color,#00000052);opacity:0;pointer-events:none;z-index:2;background-clip:padding-box;border:1px solid #ffffff38;border-radius:999px;transition:opacity .4s;position:absolute;inset-inline-end:3px}.scrollsheet-scrollbar[data-visible]{opacity:1;transition:opacity .12s}@media (prefers-reduced-motion:reduce){:where(.scrollsheet-scrollbar){transition:none}}.scrollsheet-panel:before{content:"";z-index:1;pointer-events:none;opacity:var(--scrollsheet-recede-dim,0);background:#000;position:absolute;inset:0}.scrollsheet-dialog[data-scrollsheet-behind] .scrollsheet-panel{--scrollsheet-recede-dim:.14;--scrollsheet-stack-progress:1;transform-origin:50% 0;transition:transform var(--scrollsheet-dur) var(--scrollsheet-ease);transform:translateY(8px)scale(.94)}.scrollsheet-dialog[data-scrollsheet-behind] .scrollsheet-panel:before{transition:opacity var(--scrollsheet-dur) var(--scrollsheet-ease)}.scrollsheet-dialog[data-scrollsheet-side=top][data-scrollsheet-behind] .scrollsheet-panel{transform-origin:50% 100%;transform:translateY(-8px)scale(.94)}.scrollsheet-dialog[data-scrollsheet-side=left][data-scrollsheet-behind] .scrollsheet-panel{transform-origin:100%;transform:translate(-8px)scale(.94)}.scrollsheet-dialog[data-scrollsheet-side=right][data-scrollsheet-behind] .scrollsheet-panel{transform-origin:0%;transform:translate(8px)scale(.94)}.scrollsheet-track,.scrollsheet-canvas,.scrollsheet-panel,.scrollsheet-dialog:focus-visible,.scrollsheet-track:focus-visible,.scrollsheet-canvas:focus-visible,.scrollsheet-panel:focus-visible{outline:none}[data-scrollsheet-dragging]{-webkit-user-select:none;user-select:none}[data-scrollsheet-dragging] .scrollsheet-panel{cursor:grabbing}.scrollsheet-dialog:is([data-scrollsheet-side=top],[data-scrollsheet-side=left],[data-scrollsheet-side=right]) .scrollsheet-panel{scroll-padding-bottom:var(--scrollsheet-keyboard,0px)}.scrollsheet-dialog:is([data-scrollsheet-side=top],[data-scrollsheet-side=left],[data-scrollsheet-side=right]) [data-scrollsheet-body]{padding-bottom:var(--scrollsheet-keyboard,0px)}:where([data-scrollsheet-body]){display:flow-root}.scrollsheet-panel[data-scrollsheet-fill]{flex-direction:column;display:flex}[data-scrollsheet-body][data-scrollsheet-fill]{flex-direction:column;flex:1;min-height:0;display:flex}@media (prefers-reduced-motion:reduce){.scrollsheet-dialog .scrollsheet-panel,.scrollsheet-dialog .scrollsheet-backdrop{transition:none!important}}@supports (animation-timeline:scroll()){@property --scrollsheet-progress{syntax:"<number>";inherits:true;initial-value:0}.scrollsheet-dialog[data-scrollsheet-sda]{timeline-scope:--scrollsheet-track}.scrollsheet-dialog[data-scrollsheet-sda] .scrollsheet-track{scroll-timeline:--scrollsheet-track y}.scrollsheet-dialog[data-scrollsheet-sda]:is([data-scrollsheet-side=left],[data-scrollsheet-side=right]) .scrollsheet-track{scroll-timeline:--scrollsheet-track x}.scrollsheet-dialog[data-scrollsheet-sda][data-scrollsheet-state=open] .scrollsheet-backdrop{animation:linear both scrollsheet-fade,linear both scrollsheet-progress-var;animation-timeline:--scrollsheet-track,--scrollsheet-track;animation-range:var(--scrollsheet-fade-start,0px) var(--scrollsheet-fade-end,100%), 0px var(--scrollsheet-first-detent,100%)}.scrollsheet-dialog[data-scrollsheet-sda][data-scrollsheet-state=open]:is([data-scrollsheet-side=top],[data-scrollsheet-side=left]) .scrollsheet-backdrop{animation-direction:reverse,reverse;animation-range:calc(var(--scrollsheet-max-detent,0px) - var(--scrollsheet-fade-end,0px)) calc(var(--scrollsheet-max-detent,0px) - var(--scrollsheet-fade-start,0px)), calc(var(--scrollsheet-max-detent,0px) - var(--scrollsheet-first-detent,0px)) var(--scrollsheet-max-detent,0px)}@keyframes scrollsheet-fade{0%{opacity:0}to{opacity:1}}@keyframes scrollsheet-progress-var{0%{--scrollsheet-progress:0}to{--scrollsheet-progress:1}}}.scrollsheet-fallback{z-index:2147483647;display:flex;position:fixed;inset:0}.scrollsheet-fallback[data-scrollsheet-modal=false]{pointer-events:none}.scrollsheet-fallback[data-scrollsheet-modal=false] .scrollsheet-fallback-panel{pointer-events:auto}.scrollsheet-fallback-backdrop{background:var(--scrollsheet-backdrop,#00000052);position:fixed;inset:0}:where(.scrollsheet-fallback-panel){--scrollsheet-radius:20px;background:var(--scrollsheet-bg,#fff);color:var(--scrollsheet-fg,#1d1d1f);box-shadow:var(--scrollsheet-shadow,0 -8px 32px #00000029);overscroll-behavior:contain;-webkit-overflow-scrolling:touch;outline:none;position:relative;overflow-y:auto}@media (prefers-color-scheme:dark){:where(.scrollsheet-fallback-panel){background:var(--scrollsheet-bg,#1c1c1e);color:var(--scrollsheet-fg,#f5f5f7)}}.scrollsheet-fallback[data-scrollsheet-side=bottom]{align-items:flex-end}.scrollsheet-fallback[data-scrollsheet-side=bottom] :where(.scrollsheet-fallback-panel){border-radius:var(--scrollsheet-radius) var(--scrollsheet-radius) 0 0;width:100%;max-height:85%;padding-bottom:max(var(--scrollsheet-safe-area,env(safe-area-inset-bottom,0px)), 0px)}.scrollsheet-fallback[data-scrollsheet-side=top]{align-items:flex-start}.scrollsheet-fallback[data-scrollsheet-side=top] :where(.scrollsheet-fallback-panel){border-radius:0 0 var(--scrollsheet-radius) var(--scrollsheet-radius);width:100%;max-height:85%;box-shadow:var(--scrollsheet-shadow,0 8px 32px #00000029);padding-top:max(var(--scrollsheet-safe-area-top,env(safe-area-inset-top,0px)), 0px)}.scrollsheet-fallback[data-scrollsheet-side=left]{justify-content:flex-start}.scrollsheet-fallback[data-scrollsheet-side=right]{justify-content:flex-end}.scrollsheet-fallback:is([data-scrollsheet-side=left],[data-scrollsheet-side=right]) :where(.scrollsheet-fallback-panel){max-width:85%;height:100%;padding-top:max(var(--scrollsheet-safe-area-top,env(safe-area-inset-top,0px)), 0px);padding-bottom:max(var(--scrollsheet-safe-area,env(safe-area-inset-bottom,0px)), 0px);border-radius:0}.scrollsheet-fallback[data-scrollsheet-side=left] :where(.scrollsheet-fallback-panel){box-shadow:var(--scrollsheet-shadow,8px 0 32px #00000029)}.scrollsheet-fallback[data-scrollsheet-side=right] :where(.scrollsheet-fallback-panel){box-shadow:var(--scrollsheet-shadow,-8px 0 32px #00000029)}@media (min-width:768px){.scrollsheet-fallback[data-scrollsheet-side=bottom] :where(.scrollsheet-fallback-panel),.scrollsheet-fallback[data-scrollsheet-side=top] :where(.scrollsheet-fallback-panel){max-width:var(--scrollsheet-max-inline,640px);margin-inline:auto}}.scrollsheet-fallback [data-scrollsheet-handle]{display:none}@media (forced-colors:active){:where(.scrollsheet-backdrop),:where(.scrollsheet-fallback-backdrop){forced-color-adjust:none}:where(.scrollsheet-panel),:where(.scrollsheet-fallback-panel){color:canvastext;box-shadow:none;background:canvas;border:1px solid buttonborder}.scrollsheet-dialog:is([data-scrollsheet-side=top],[data-scrollsheet-side=left],[data-scrollsheet-side=right]) :where(.scrollsheet-panel),.scrollsheet-fallback:is([data-scrollsheet-side=top],[data-scrollsheet-side=left],[data-scrollsheet-side=right]) :where(.scrollsheet-fallback-panel){box-shadow:none}:where(.scrollsheet-scrollbar){background:highlight}}.scrollsheet-dialog[data-scrollsheet-side=center] .scrollsheet-track{scroll-snap-type:none;box-sizing:border-box;padding:var(--scrollsheet-center-gutter,20px);padding-bottom:calc(var(--scrollsheet-center-gutter,20px) + var(--scrollsheet-keyboard,0px));place-items:center;display:grid;overflow:hidden}.scrollsheet-dialog[data-scrollsheet-side=center] .scrollsheet-canvas{display:contents}.scrollsheet-dialog[data-scrollsheet-side=center] .scrollsheet-canvas:after{content:none}.scrollsheet-dialog[data-scrollsheet-side=center] .scrollsheet-panel{width:auto;height:auto;max-inline-size:var(--scrollsheet-center-max-inline,min(560px, 100%));max-block-size:var(--scrollsheet-center-max-block,100%);border-radius:var(--scrollsheet-radius,16px);padding-bottom:0;position:static;inset:auto;overflow:auto}.scrollsheet-dialog[data-scrollsheet-side=center] .scrollsheet-handle{display:none}.scrollsheet-dialog[data-scrollsheet-side=center]:is([data-scrollsheet-state=pre],[data-scrollsheet-state=closing]) .scrollsheet-panel{opacity:0;transform:scale(.95)}.scrollsheet-handle:before{content:"";width:max(100%,44px);height:44px;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}:where(.scrollsheet-handle){background:var(--scrollsheet-handle,#0003);cursor:grab;width:44px;height:5px;box-shadow:0 0 0 2px transparent, 0 0 0 calc(2px + var(--scrollsheet-focus-ring-width,2px)) transparent;transition:transform .18s var(--scrollsheet-ease), background .18s ease, box-shadow .15s ease;border:none;border-radius:999px;outline:none;margin:8px auto;padding:0;display:block;position:relative}:where(.scrollsheet-handle:hover){background:var(--scrollsheet-handle-hover,#00000052)}:where(.scrollsheet-handle:active){cursor:grabbing;transform:scaleX(1.3)}@media (prefers-color-scheme:dark){:where(.scrollsheet-handle){background:var(--scrollsheet-handle,#ffffff52)}:where(.scrollsheet-handle:hover){background:var(--scrollsheet-handle-hover,#ffffff75)}}:where(.scrollsheet-handle[data-scrollsheet-handle-variant=floating]){z-index:1;margin:0;position:absolute;top:8px;left:50%;translate:-50%}:where(.scrollsheet-canvas>.scrollsheet-handle[data-scrollsheet-handle-variant=outside]){left:50%;bottom:calc(var(--scrollsheet-ib,0px) + var(--scrollsheet-keyboard,0px) + var(--scrollsheet-kb-hang,0px) + var(--scrollsheet-max-detent,0px) + 10px);background:var(--scrollsheet-handle,#fffc);margin:0;position:absolute;translate:-50%}.scrollsheet-dialog:not([data-scrollsheet-state=open]) .scrollsheet-canvas>.scrollsheet-handle[data-scrollsheet-handle-variant=outside]{opacity:0;transition:opacity .12s}.scrollsheet-dialog[data-scrollsheet-modal=false] .scrollsheet-handle[data-scrollsheet-handle-variant=outside]{pointer-events:auto;touch-action:none}:where(.scrollsheet-close){top:12px;z-index:1;background:var(--scrollsheet-close-bg,#0000000f);width:28px;height:28px;color:var(--scrollsheet-close-color,#0000008c);cursor:pointer;box-shadow:0 0 0 2px transparent, 0 0 0 calc(2px + var(--scrollsheet-focus-ring-width,2px)) transparent;border:none;border-radius:999px;outline:none;place-items:center;padding:0;transition:background .15s,color .15s,box-shadow .15s;display:grid;position:absolute;inset-inline-end:12px}.scrollsheet-close:before{content:"";width:44px;height:44px;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}:where(.scrollsheet-close:hover){background:var(--scrollsheet-close-bg-hover,#0000001a);color:var(--scrollsheet-close-color-hover,#000000bf)}@media (prefers-color-scheme:dark){:where(.scrollsheet-close){background:var(--scrollsheet-close-bg,#ffffff17);color:var(--scrollsheet-close-color,#ffffff9e)}:where(.scrollsheet-close:hover){background:var(--scrollsheet-close-bg-hover,#ffffff24);color:var(--scrollsheet-close-color-hover,#ffffffd9)}}:where(.scrollsheet-close:focus-visible),:where(.scrollsheet-handle:focus-visible){box-shadow:0 0 0 2px transparent, 0 0 0 calc(2px + var(--scrollsheet-focus-ring-width,2px)) var(--scrollsheet-focus-ring,#6366f199);outline:none}.scrollsheet-dialog[data-scrollsheet-side=left] .scrollsheet-handle:before,.scrollsheet-dialog[data-scrollsheet-side=right] .scrollsheet-handle:before{width:44px;height:max(100%,44px)}.scrollsheet-dialog[data-scrollsheet-side=left] :where(.scrollsheet-handle),.scrollsheet-dialog[data-scrollsheet-side=right] :where(.scrollsheet-handle){width:5px;height:44px;margin:auto 8px}.scrollsheet-dialog[data-scrollsheet-side=left] :where(.scrollsheet-handle:active),.scrollsheet-dialog[data-scrollsheet-side=right] :where(.scrollsheet-handle:active){transform:scaleY(1.3)}@media (forced-colors:active){:where(.scrollsheet-handle){box-shadow:none;background:buttonborder}:where(.scrollsheet-handle:focus-visible){box-shadow:none;outline:2px solid highlight}:where(.scrollsheet-close){color:buttontext;box-shadow:none;background:canvas;border:1px solid buttonborder}:where(.scrollsheet-close:focus-visible){box-shadow:none;outline:2px solid highlight}}
.scrollsheet-dialog[data-scrollsheet-side=bottom]{--scrollsheet-keyboard-lift:var(--scrollsheet-keyboard,0px)}.scrollsheet-dialog{width:100%;height:calc(100% + var(--scrollsheet-keyboard-lift,0px));--scrollsheet-ease:linear(0, 0.042 3.4%, 0.136 6.9%, 0.37 13.8%, 0.481 17.2%, 0.58 20.7%, 0.664 24.1%, 0.735 27.6%, 0.793 31%, 0.839 34.5%, 0.876 37.9%, 0.906 41.4%, 0.928 44.8%, 0.946 48.3%, 0.96 51.7%, 0.978 58.6%, 0.988 65.5%, 0.994 72.4%, 0.998 82.8%, 1);--scrollsheet-dur:475ms;background:0 0;border:none;outline:none;max-width:none;max-height:none;margin:0;padding:0;position:fixed;inset:0;overflow:clip}.scrollsheet-dialog::backdrop{background:0 0}:where(.scrollsheet-backdrop){background:var(--scrollsheet-backdrop,#00000052);opacity:0;pointer-events:none;position:fixed;inset:0}.scrollsheet-dialog[data-scrollsheet-state=opening] .scrollsheet-backdrop{opacity:min(var(--scrollsheet-dim,0), 1);transition:opacity var(--scrollsheet-dur) var(--scrollsheet-ease)}.scrollsheet-dialog[data-scrollsheet-state=open] .scrollsheet-backdrop{opacity:min(var(--scrollsheet-dim,0), 1);transition:none}.scrollsheet-dialog[data-scrollsheet-state=closing] .scrollsheet-backdrop{opacity:0;transition:opacity var(--scrollsheet-dur) var(--scrollsheet-ease)}:where(.scrollsheet-top-chrome){height:calc(env(safe-area-inset-top,0px) + 8px);inset:0 0 auto}:where(.scrollsheet-bottom-chrome){height:calc(env(safe-area-inset-bottom,0px) + 32px);inset:auto 0 0}:where(.scrollsheet-top-chrome)~:where([data-scrollsheet-backdrop]){top:calc(env(safe-area-inset-top,0px) + 8px)}:where(.scrollsheet-bottom-chrome)~:where([data-scrollsheet-backdrop]){bottom:calc(env(safe-area-inset-bottom,0px) + 32px)}.scrollsheet-dialog:is([data-scrollsheet-state=pre],[data-scrollsheet-state=opening]) :is(.scrollsheet-top-chrome,.scrollsheet-bottom-chrome){opacity:min(var(--scrollsheet-dim,0), 1);transition:none}.scrollsheet-bottom-sentinel{height:min(calc(max(env(safe-area-inset-bottom,0px), 24px) + 45vh), var(--scrollsheet-reveal,24px));pointer-events:none;position:fixed;inset:auto 0 0}.scrollsheet-track{left:0;right:0;top:var(--scrollsheet-vv-top,0px);height:calc(var(--scrollsheet-vv-height,100%) + var(--scrollsheet-keyboard-lift,0px));overscroll-behavior:contain;scroll-snap-type:y mandatory;scrollbar-width:none;position:fixed;overflow-y:scroll}.scrollsheet-track::-webkit-scrollbar{display:none}.scrollsheet-dialog:is([data-scrollsheet-side=left],[data-scrollsheet-side=right]) .scrollsheet-track{scroll-snap-type:x mandatory;overscroll-behavior-x:none;overflow:scroll hidden}.scrollsheet-dialog[data-scrollsheet-modal=false]{pointer-events:none}.scrollsheet-dialog[data-scrollsheet-modal=false] .scrollsheet-panel{pointer-events:auto;touch-action:none}.scrollsheet-canvas{height:calc(100% + var(--scrollsheet-max-detent,0px));position:relative}.scrollsheet-dialog:is([data-scrollsheet-side=left],[data-scrollsheet-side=right]) .scrollsheet-canvas{width:calc(100% + var(--scrollsheet-max-detent,0px));height:100%}.scrollsheet-canvas:after{content:"";top:calc(100% - var(--scrollsheet-keyboard,0px));--scrollsheet-od:calc(120vh + var(--scrollsheet-keyboard,0px));height:0;box-shadow:0 calc(var(--scrollsheet-od) / 2) 0 calc(var(--scrollsheet-od) / 2) var(--scrollsheet-panel-bg,transparent);pointer-events:none;position:absolute;left:0;right:0}.scrollsheet-dialog[data-scrollsheet-side=top] .scrollsheet-canvas:after{box-shadow:0 -60vh 0 60vh var(--scrollsheet-panel-bg,transparent);top:auto;bottom:100%}.scrollsheet-dialog[data-scrollsheet-side=left] .scrollsheet-canvas:after{width:0;height:auto;box-shadow:-60vw 0 0 60vw var(--scrollsheet-panel-bg,transparent);inset:0 100% 0 auto}.scrollsheet-dialog[data-scrollsheet-side=right] .scrollsheet-canvas:after{width:0;height:auto;box-shadow:60vw 0 0 60vw var(--scrollsheet-panel-bg,transparent);inset:0 auto 0 100%}.scrollsheet-dialog[data-scrollsheet-detached] .scrollsheet-canvas:after{box-shadow:none}.scrollsheet-snap{pointer-events:none;scroll-snap-align:start;width:100%;height:1px;position:absolute;left:0}.scrollsheet-dialog:is([data-scrollsheet-side=left],[data-scrollsheet-side=right]) .scrollsheet-snap{width:1px;height:100%;top:0;left:auto}.scrollsheet-panel{--scrollsheet-eg:calc(var(--scrollsheet-inset-bottom,0px) + var(--scrollsheet-keyboard,0px));bottom:var(--scrollsheet-eg);left:var(--scrollsheet-inset-x,0px);right:var(--scrollsheet-inset-x,0px);height:var(--scrollsheet-max-detent,auto);max-height:var(--scrollsheet-vv-height,none);will-change:transform;padding-bottom:max(var(--scrollsheet-safe-area,env(safe-area-inset-bottom,0px)), 0px);position:absolute;overflow:hidden;transform:translateY(100%)}.scrollsheet-dialog[data-scrollsheet-side=top] .scrollsheet-panel{bottom:auto;top:var(--scrollsheet-inset-top,0px);left:var(--scrollsheet-inset-x,0px);right:var(--scrollsheet-inset-x,0px);height:var(--scrollsheet-max-detent,auto);padding-bottom:0;padding-top:max(var(--scrollsheet-safe-area-top,env(safe-area-inset-top,0px)), 0px);transform:translateY(-100%)}.scrollsheet-dialog:is([data-scrollsheet-side=left],[data-scrollsheet-side=right]) .scrollsheet-panel{top:var(--scrollsheet-inset-y,0px);bottom:var(--scrollsheet-inset-y,0px);width:var(--scrollsheet-max-detent,auto);height:auto;padding-top:max(var(--scrollsheet-safe-area-top,env(safe-area-inset-top,0px)), 0px)}.scrollsheet-dialog[data-scrollsheet-side=left] .scrollsheet-panel{right:auto;left:var(--scrollsheet-inset-left,0px);padding-left:max(var(--scrollsheet-safe-area-left,env(safe-area-inset-left,0px)), 0px);transform:translate(-100%)}.scrollsheet-dialog[data-scrollsheet-side=right] .scrollsheet-panel{left:auto;right:var(--scrollsheet-inset-right,0px);padding-right:max(var(--scrollsheet-safe-area-right,env(safe-area-inset-right,0px)), 0px);transform:translate(100%)}.scrollsheet-dialog:is([data-scrollsheet-side=top],[data-scrollsheet-side=left]) .scrollsheet-panel{justify-content:flex-end;display:flex}.scrollsheet-dialog[data-scrollsheet-side=top] .scrollsheet-panel{flex-direction:column}.scrollsheet-dialog[data-scrollsheet-side=left] .scrollsheet-panel{flex-direction:row}.scrollsheet-dialog[data-scrollsheet-side=left] .scrollsheet-panel>*{flex:0 0 100%;min-width:0}:where(.scrollsheet-panel){scrollbar-width:none}:where(.scrollsheet-panel)::-webkit-scrollbar{display:none}:where([data-scrollsheet-nested-scroll]){scrollbar-width:none}:where([data-scrollsheet-nested-scroll])::-webkit-scrollbar{display:none}:where(.scrollsheet-dialog[data-scrollsheet-scrollbar=overlay] [data-scrollsheet-nested-scroll]){position:relative}:where(.scrollsheet-panel){--scrollsheet-radius:20px;background:var(--scrollsheet-bg,#fff);color:var(--scrollsheet-fg,#1d1d1f);border-radius:var(--scrollsheet-radius) var(--scrollsheet-radius) 0 0;box-shadow:var(--scrollsheet-shadow,0 -8px 32px #00000029)}@media (prefers-color-scheme:dark){:where(.scrollsheet-panel){background:var(--scrollsheet-bg,#1c1c1e);color:var(--scrollsheet-fg,#f5f5f7)}}.scrollsheet-dialog[data-scrollsheet-side=top] :where(.scrollsheet-panel){border-radius:0 0 var(--scrollsheet-radius) var(--scrollsheet-radius);box-shadow:var(--scrollsheet-shadow,0 8px 32px #00000029)}.scrollsheet-dialog[data-scrollsheet-side=left] :where(.scrollsheet-panel){box-shadow:var(--scrollsheet-shadow,8px 0 32px #00000029);border-radius:0}.scrollsheet-dialog[data-scrollsheet-side=right] :where(.scrollsheet-panel){box-shadow:var(--scrollsheet-shadow,-8px 0 32px #00000029);border-radius:0}@media (min-width:768px){:where(.scrollsheet-dialog[data-scrollsheet-side=bottom]) :where(.scrollsheet-panel),:where(.scrollsheet-dialog[data-scrollsheet-side=bottom]) .scrollsheet-canvas:after{width:min(var(--scrollsheet-max-inline,640px), calc(100% - 2 * var(--scrollsheet-desktop-margin,24px)));margin-inline:auto var(--scrollsheet-desktop-margin,24px)}.scrollsheet-dialog[data-scrollsheet-side=bottom] .scrollsheet-panel{--scrollsheet-inset-bottom:var(--scrollsheet-desktop-margin,24px);--scrollsheet-dg:calc(var(--scrollsheet-desktop-margin,24px) + var(--scrollsheet-inset-x,0px) + 32px)}.scrollsheet-dialog[data-scrollsheet-side=bottom]:where([data-scrollsheet-detached]) .scrollsheet-panel,.scrollsheet-dialog[data-scrollsheet-side=bottom][data-scrollsheet-state=closing]:where([data-scrollsheet-detached]) .scrollsheet-panel{transform:translate3d(calc(100% + var(--scrollsheet-dg,32px)), 0, 0)}.scrollsheet-dialog[data-scrollsheet-side=bottom]:where([data-scrollsheet-detached][data-scrollsheet-rtl]) .scrollsheet-panel,.scrollsheet-dialog[data-scrollsheet-side=bottom][data-scrollsheet-state=closing]:where([data-scrollsheet-detached][data-scrollsheet-rtl]) .scrollsheet-panel{transform:translate3d(calc(-100% - var(--scrollsheet-dg,32px)), 0, 0)}.scrollsheet-dialog[data-scrollsheet-side=top] .scrollsheet-panel{top:var(--scrollsheet-desktop-margin,24px);border-radius:var(--scrollsheet-radius)}.scrollsheet-dialog[data-scrollsheet-side=bottom] :where(.scrollsheet-handle){opacity:0;pointer-events:none;width:5px;height:44px;margin:-22px 0 0;position:absolute;top:50%;left:10px}.scrollsheet-dialog[data-scrollsheet-side=bottom] :where(.scrollsheet-handle:focus-visible){opacity:1;pointer-events:auto}.scrollsheet-dialog[data-scrollsheet-side=bottom][data-scrollsheet-has-handle] :where(.scrollsheet-body){padding-top:18px}:where(.scrollsheet-dialog[data-scrollsheet-side=top]) :where(.scrollsheet-panel),:where(.scrollsheet-dialog[data-scrollsheet-side=top]) .scrollsheet-canvas:after{max-width:var(--scrollsheet-max-inline,640px);margin-inline:auto}}.scrollsheet-dialog[data-scrollsheet-detached] :where(.scrollsheet-panel){padding-bottom:max(var(--scrollsheet-safe-area,0px), 0px);border-radius:var(--scrollsheet-radius)}.scrollsheet-dialog[data-scrollsheet-detached][data-scrollsheet-side=bottom] :where(.scrollsheet-panel){--scrollsheet-eg:calc(max(var(--scrollsheet-inset-bottom,0px), env(safe-area-inset-bottom,0px)) + var(--scrollsheet-keyboard,0px));bottom:var(--scrollsheet-eg)}.scrollsheet-dialog[data-scrollsheet-scrollbar=native] .scrollsheet-panel{scrollbar-width:auto}.scrollsheet-dialog[data-scrollsheet-scrollbar=native] .scrollsheet-panel::-webkit-scrollbar{display:block}.scrollsheet-dialog[data-scrollsheet-scrollbar=native] [data-scrollsheet-nested-scroll]{scrollbar-width:auto}.scrollsheet-dialog[data-scrollsheet-scrollbar=native] [data-scrollsheet-nested-scroll]::-webkit-scrollbar{display:block}.scrollsheet-dialog[data-scrollsheet-state=opening] .scrollsheet-panel,.scrollsheet-dialog[data-scrollsheet-state=open] .scrollsheet-panel{transform:translate(0,0)}.scrollsheet-dialog[data-scrollsheet-state=closing] .scrollsheet-panel{transform:translate3d(0, calc(100% + var(--scrollsheet-eg,0px) + 32px), 0)}.scrollsheet-dialog[data-scrollsheet-side=top][data-scrollsheet-state=closing] .scrollsheet-panel{transform:translate3d(0, calc(-100% - var(--scrollsheet-inset-top,0px) - 32px), 0)}.scrollsheet-dialog[data-scrollsheet-side=left][data-scrollsheet-state=closing] .scrollsheet-panel{transform:translate3d(calc(-100% - var(--scrollsheet-inset-left,0px) - 32px), 0, 0)}.scrollsheet-dialog[data-scrollsheet-side=right][data-scrollsheet-state=closing] .scrollsheet-panel{transform:translate3d(calc(100% + var(--scrollsheet-inset-right,0px) + 32px), 0, 0)}.scrollsheet-dialog:is([data-scrollsheet-at-max],[data-scrollsheet-kb]) .scrollsheet-panel{overflow-y:auto}.scrollsheet-dialog[data-scrollsheet-kb][data-scrollsheet-side=bottom] .scrollsheet-panel{--scrollsheet-eg:calc(var(--scrollsheet-inset-bottom,0px) + var(--scrollsheet-keyboard,0px) + var(--scrollsheet-kb-hang,0px));box-shadow:none;border-bottom-right-radius:0;border-bottom-left-radius:0}.scrollsheet-kb-fade{display:none}.scrollsheet-dialog[data-scrollsheet-kb][data-scrollsheet-side=bottom] .scrollsheet-kb-fade{left:var(--scrollsheet-inset-x,0px);right:var(--scrollsheet-inset-x,0px);bottom:calc(var(--scrollsheet-keyboard,0px) + var(--scrollsheet-kb-hang,0px));background:linear-gradient(to bottom, transparent, var(--scrollsheet-panel-bg,#fff) 75%);pointer-events:none;z-index:1;height:18px;display:block;position:absolute}.scrollsheet-dialog:is([data-scrollsheet-handle-only],[data-scrollsheet-disable-drag]):not([data-scrollsheet-at-max],[data-scrollsheet-at-nested-boundary],[data-scrollsheet-kb]) :is(.scrollsheet-track,.scrollsheet-canvas){touch-action:none}.scrollsheet-dialog[data-scrollsheet-handle-only] .scrollsheet-panel,.scrollsheet-dialog[data-scrollsheet-disable-drag] .scrollsheet-panel{touch-action:pan-y}.scrollsheet-dialog[data-scrollsheet-handle-only]:not([data-scrollsheet-at-nested-boundary]) .scrollsheet-panel,.scrollsheet-dialog[data-scrollsheet-disable-drag]:not([data-scrollsheet-at-nested-boundary]) .scrollsheet-panel{overscroll-behavior:contain}.scrollsheet-dialog[data-scrollsheet-at-nested-boundary] .scrollsheet-track,.scrollsheet-dialog[data-scrollsheet-at-nested-boundary] .scrollsheet-canvas{touch-action:pan-y}.scrollsheet-dialog[data-scrollsheet-side=left][data-scrollsheet-at-nested-boundary] .scrollsheet-track,.scrollsheet-dialog[data-scrollsheet-side=left][data-scrollsheet-at-nested-boundary] .scrollsheet-canvas,.scrollsheet-dialog[data-scrollsheet-side=right][data-scrollsheet-at-nested-boundary] .scrollsheet-track,.scrollsheet-dialog[data-scrollsheet-side=right][data-scrollsheet-at-nested-boundary] .scrollsheet-canvas{touch-action:pan-x}:where(.scrollsheet-scrollbar){top:0;width:var(--scrollsheet-scrollbar-width,4px);box-sizing:border-box;background:var(--scrollsheet-scrollbar-color,#00000052);opacity:0;pointer-events:none;z-index:2;background-clip:padding-box;border:1px solid #ffffff38;border-radius:999px;transition:opacity .4s;position:absolute;inset-inline-end:3px}.scrollsheet-scrollbar[data-visible]{opacity:1;transition:opacity .12s}@media (prefers-reduced-motion:reduce){:where(.scrollsheet-scrollbar){transition:none}}.scrollsheet-panel:before{content:"";z-index:1;pointer-events:none;opacity:var(--scrollsheet-recede-dim,0);background:#000;position:absolute;inset:0}.scrollsheet-dialog[data-scrollsheet-behind] .scrollsheet-panel{--scrollsheet-recede-dim:.14;--scrollsheet-stack-progress:1;transform-origin:50% 0;transition:transform var(--scrollsheet-dur) var(--scrollsheet-ease);transform:translateY(8px)scale(.94)}.scrollsheet-dialog[data-scrollsheet-behind] .scrollsheet-panel:before{transition:opacity var(--scrollsheet-dur) var(--scrollsheet-ease)}.scrollsheet-dialog[data-scrollsheet-side=top][data-scrollsheet-behind] .scrollsheet-panel{transform-origin:50% 100%;transform:translateY(-8px)scale(.94)}.scrollsheet-dialog[data-scrollsheet-side=left][data-scrollsheet-behind] .scrollsheet-panel{transform-origin:100%;transform:translate(-8px)scale(.94)}.scrollsheet-dialog[data-scrollsheet-side=right][data-scrollsheet-behind] .scrollsheet-panel{transform-origin:0%;transform:translate(8px)scale(.94)}.scrollsheet-track,.scrollsheet-canvas,.scrollsheet-panel,.scrollsheet-dialog:focus-visible,.scrollsheet-track:focus-visible,.scrollsheet-canvas:focus-visible,.scrollsheet-panel:focus-visible{outline:none}[data-scrollsheet-dragging]{-webkit-user-select:none;user-select:none}[data-scrollsheet-dragging] .scrollsheet-panel{cursor:grabbing}.scrollsheet-dialog:is([data-scrollsheet-side=top],[data-scrollsheet-side=left],[data-scrollsheet-side=right]) .scrollsheet-panel{scroll-padding-bottom:var(--scrollsheet-keyboard,0px)}.scrollsheet-dialog:is([data-scrollsheet-side=top],[data-scrollsheet-side=left],[data-scrollsheet-side=right]) [data-scrollsheet-body]{padding-bottom:var(--scrollsheet-keyboard,0px)}:where([data-scrollsheet-body]){display:flow-root}.scrollsheet-panel[data-scrollsheet-fill]{flex-direction:column;display:flex}[data-scrollsheet-body][data-scrollsheet-fill]{flex-direction:column;flex:1;min-height:0;display:flex}@media (prefers-reduced-motion:reduce){.scrollsheet-dialog .scrollsheet-panel,.scrollsheet-dialog .scrollsheet-backdrop{transition:none!important}}@supports (animation-timeline:scroll()){@property --scrollsheet-progress{syntax:"<number>";inherits:true;initial-value:0}.scrollsheet-dialog[data-scrollsheet-sda]{timeline-scope:--scrollsheet-track}.scrollsheet-dialog[data-scrollsheet-sda] .scrollsheet-track{scroll-timeline:--scrollsheet-track y}.scrollsheet-dialog[data-scrollsheet-sda]:is([data-scrollsheet-side=left],[data-scrollsheet-side=right]) .scrollsheet-track{scroll-timeline:--scrollsheet-track x}.scrollsheet-dialog[data-scrollsheet-sda][data-scrollsheet-state=open] .scrollsheet-backdrop{animation:linear both scrollsheet-fade,linear both scrollsheet-progress-var;animation-timeline:--scrollsheet-track,--scrollsheet-track;animation-range:var(--scrollsheet-fade-start,0px) var(--scrollsheet-fade-end,100%), 0px var(--scrollsheet-first-detent,100%)}.scrollsheet-dialog[data-scrollsheet-sda][data-scrollsheet-state=open]:is([data-scrollsheet-side=top],[data-scrollsheet-side=left]) .scrollsheet-backdrop{animation-direction:reverse,reverse;animation-range:calc(var(--scrollsheet-max-detent,0px) - var(--scrollsheet-fade-end,0px)) calc(var(--scrollsheet-max-detent,0px) - var(--scrollsheet-fade-start,0px)), calc(var(--scrollsheet-max-detent,0px) - var(--scrollsheet-first-detent,0px)) var(--scrollsheet-max-detent,0px)}@keyframes scrollsheet-fade{0%{opacity:0}to{opacity:1}}@keyframes scrollsheet-progress-var{0%{--scrollsheet-progress:0}to{--scrollsheet-progress:1}}}.scrollsheet-fallback{z-index:2147483647;display:flex;position:fixed;inset:0}.scrollsheet-fallback[data-scrollsheet-modal=false]{pointer-events:none}.scrollsheet-fallback[data-scrollsheet-modal=false] .scrollsheet-fallback-panel{pointer-events:auto}.scrollsheet-fallback-backdrop{background:var(--scrollsheet-backdrop,#00000052);position:fixed;inset:0}:where(.scrollsheet-fallback-panel){--scrollsheet-radius:20px;background:var(--scrollsheet-bg,#fff);color:var(--scrollsheet-fg,#1d1d1f);box-shadow:var(--scrollsheet-shadow,0 -8px 32px #00000029);overscroll-behavior:contain;-webkit-overflow-scrolling:touch;outline:none;position:relative;overflow-y:auto}@media (prefers-color-scheme:dark){:where(.scrollsheet-fallback-panel){background:var(--scrollsheet-bg,#1c1c1e);color:var(--scrollsheet-fg,#f5f5f7)}}.scrollsheet-fallback[data-scrollsheet-side=bottom]{align-items:flex-end}.scrollsheet-fallback[data-scrollsheet-side=bottom] :where(.scrollsheet-fallback-panel){border-radius:var(--scrollsheet-radius) var(--scrollsheet-radius) 0 0;width:100%;max-height:85%;padding-bottom:max(var(--scrollsheet-safe-area,env(safe-area-inset-bottom,0px)), 0px)}.scrollsheet-fallback[data-scrollsheet-side=top]{align-items:flex-start}.scrollsheet-fallback[data-scrollsheet-side=top] :where(.scrollsheet-fallback-panel){border-radius:0 0 var(--scrollsheet-radius) var(--scrollsheet-radius);width:100%;max-height:85%;box-shadow:var(--scrollsheet-shadow,0 8px 32px #00000029);padding-top:max(var(--scrollsheet-safe-area-top,env(safe-area-inset-top,0px)), 0px)}.scrollsheet-fallback[data-scrollsheet-side=center]{justify-content:center;align-items:center}.scrollsheet-fallback[data-scrollsheet-side=center] :where(.scrollsheet-fallback-panel){max-inline-size:var(--scrollsheet-center-max-inline,min(560px, calc(100% - 40px)));border-radius:var(--scrollsheet-radius);max-height:85%;box-shadow:var(--scrollsheet-shadow,0 8px 32px #00000029)}.scrollsheet-fallback[data-scrollsheet-side=left]{justify-content:flex-start}.scrollsheet-fallback[data-scrollsheet-side=right]{justify-content:flex-end}.scrollsheet-fallback:is([data-scrollsheet-side=left],[data-scrollsheet-side=right]) :where(.scrollsheet-fallback-panel){max-width:85%;height:100%;padding-top:max(var(--scrollsheet-safe-area-top,env(safe-area-inset-top,0px)), 0px);padding-bottom:max(var(--scrollsheet-safe-area,env(safe-area-inset-bottom,0px)), 0px);border-radius:0}.scrollsheet-fallback[data-scrollsheet-side=left] :where(.scrollsheet-fallback-panel){box-shadow:var(--scrollsheet-shadow,8px 0 32px #00000029)}.scrollsheet-fallback[data-scrollsheet-side=right] :where(.scrollsheet-fallback-panel){box-shadow:var(--scrollsheet-shadow,-8px 0 32px #00000029)}@media (min-width:768px){.scrollsheet-fallback[data-scrollsheet-side=bottom] :where(.scrollsheet-fallback-panel),.scrollsheet-fallback[data-scrollsheet-side=top] :where(.scrollsheet-fallback-panel){max-width:var(--scrollsheet-max-inline,640px);margin-inline:auto}}.scrollsheet-fallback [data-scrollsheet-handle]{display:none}@media (forced-colors:active){:where(.scrollsheet-backdrop),:where(.scrollsheet-fallback-backdrop){forced-color-adjust:none}:where(.scrollsheet-panel),:where(.scrollsheet-fallback-panel){color:canvastext;box-shadow:none;background:canvas;border:1px solid buttonborder}.scrollsheet-dialog:is([data-scrollsheet-side=top],[data-scrollsheet-side=left],[data-scrollsheet-side=right]) :where(.scrollsheet-panel),.scrollsheet-fallback:is([data-scrollsheet-side=top],[data-scrollsheet-side=left],[data-scrollsheet-side=right]) :where(.scrollsheet-fallback-panel){box-shadow:none}:where(.scrollsheet-scrollbar){background:highlight}}.scrollsheet-dialog[data-scrollsheet-side=center] .scrollsheet-track{scroll-snap-type:none;box-sizing:border-box;padding:var(--scrollsheet-center-gutter,20px);padding-bottom:calc(var(--scrollsheet-center-gutter,20px) + var(--scrollsheet-keyboard,0px));place-items:center;display:grid;overflow:hidden}.scrollsheet-dialog[data-scrollsheet-side=center] .scrollsheet-canvas{display:contents}.scrollsheet-dialog[data-scrollsheet-side=center] .scrollsheet-canvas:after{content:none}.scrollsheet-dialog[data-scrollsheet-side=center] .scrollsheet-panel{width:auto;height:auto;max-inline-size:var(--scrollsheet-center-max-inline,min(560px, 100%));max-block-size:var(--scrollsheet-center-max-block,100%);border-radius:var(--scrollsheet-radius,16px);padding-bottom:0;position:static;inset:auto;overflow:auto}.scrollsheet-dialog[data-scrollsheet-side=center] .scrollsheet-handle{display:none}.scrollsheet-dialog[data-scrollsheet-side=center]:is([data-scrollsheet-state=pre],[data-scrollsheet-state=closing]) .scrollsheet-panel{opacity:0;transform:scale(.95)}.scrollsheet-handle:before{content:"";width:max(100%,44px);height:44px;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}:where(.scrollsheet-handle){background:var(--scrollsheet-handle,#0003);cursor:grab;width:44px;height:5px;box-shadow:0 0 0 2px transparent, 0 0 0 calc(2px + var(--scrollsheet-focus-ring-width,2px)) transparent;transition:transform .18s var(--scrollsheet-ease), background .18s ease, box-shadow .15s ease;border:none;border-radius:999px;outline:none;margin:8px auto;padding:0;display:block;position:relative}:where(.scrollsheet-handle:hover){background:var(--scrollsheet-handle-hover,#00000052)}:where(.scrollsheet-handle:active){cursor:grabbing;transform:scaleX(1.3)}@media (prefers-color-scheme:dark){:where(.scrollsheet-handle){background:var(--scrollsheet-handle,#ffffff52)}:where(.scrollsheet-handle:hover){background:var(--scrollsheet-handle-hover,#ffffff75)}}:where(.scrollsheet-handle[data-scrollsheet-handle-variant=floating]){z-index:1;margin:0;position:absolute;top:8px;left:50%;translate:-50%}:where(.scrollsheet-canvas>.scrollsheet-handle[data-scrollsheet-handle-variant=outside]){left:50%;bottom:calc(var(--scrollsheet-ib,0px) + var(--scrollsheet-keyboard,0px) + var(--scrollsheet-kb-hang,0px) + var(--scrollsheet-max-detent,0px) + 10px);background:var(--scrollsheet-handle,#fffc);margin:0;position:absolute;translate:-50%}.scrollsheet-dialog:not([data-scrollsheet-state=open]) .scrollsheet-canvas>.scrollsheet-handle[data-scrollsheet-handle-variant=outside]{opacity:0;transition:opacity .12s}.scrollsheet-dialog[data-scrollsheet-modal=false] .scrollsheet-handle[data-scrollsheet-handle-variant=outside]{pointer-events:auto;touch-action:none}:where(.scrollsheet-close){top:12px;z-index:1;background:var(--scrollsheet-close-bg,#0000000f);width:28px;height:28px;color:var(--scrollsheet-close-color,#0000008c);cursor:pointer;box-shadow:0 0 0 2px transparent, 0 0 0 calc(2px + var(--scrollsheet-focus-ring-width,2px)) transparent;border:none;border-radius:999px;outline:none;place-items:center;padding:0;transition:background .15s,color .15s,box-shadow .15s;display:grid;position:absolute;inset-inline-end:12px}.scrollsheet-close:before{content:"";width:44px;height:44px;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}:where(.scrollsheet-close:hover){background:var(--scrollsheet-close-bg-hover,#0000001a);color:var(--scrollsheet-close-color-hover,#000000bf)}@media (prefers-color-scheme:dark){:where(.scrollsheet-close){background:var(--scrollsheet-close-bg,#ffffff17);color:var(--scrollsheet-close-color,#ffffff9e)}:where(.scrollsheet-close:hover){background:var(--scrollsheet-close-bg-hover,#ffffff24);color:var(--scrollsheet-close-color-hover,#ffffffd9)}}:where(.scrollsheet-close:focus-visible),:where(.scrollsheet-handle:focus-visible){box-shadow:0 0 0 2px transparent, 0 0 0 calc(2px + var(--scrollsheet-focus-ring-width,2px)) var(--scrollsheet-focus-ring,#6366f199);outline:none}.scrollsheet-dialog[data-scrollsheet-side=left] .scrollsheet-handle:before,.scrollsheet-dialog[data-scrollsheet-side=right] .scrollsheet-handle:before{width:44px;height:max(100%,44px)}.scrollsheet-dialog[data-scrollsheet-side=left] :where(.scrollsheet-handle),.scrollsheet-dialog[data-scrollsheet-side=right] :where(.scrollsheet-handle){width:5px;height:44px;margin:auto 8px}.scrollsheet-dialog[data-scrollsheet-side=left] :where(.scrollsheet-handle:active),.scrollsheet-dialog[data-scrollsheet-side=right] :where(.scrollsheet-handle:active){transform:scaleY(1.3)}@media (forced-colors:active){:where(.scrollsheet-handle){box-shadow:none;background:buttonborder}:where(.scrollsheet-handle:focus-visible){box-shadow:none;outline:2px solid highlight}:where(.scrollsheet-close){color:buttontext;box-shadow:none;background:canvas;border:1px solid buttonborder}:where(.scrollsheet-close:focus-visible){box-shadow:none;outline:2px solid highlight}}
{
"name": "scrollsheet",
"version": "1.0.0-beta.4",
"version": "1.0.0-beta.5",
"publishConfig": {

@@ -5,0 +5,0 @@ "tag": "beta"

@@ -5,3 +5,3 @@ # scrollsheet

One primitive for bottom sheets, drawers, modal dialogs, side panels, and toasts. A real `<dialog>` in the top layer. The browser's own scroll engine for gestures. Spring physics compiled to CSS `linear()`. <!--size:index.gzip:1-->17.2<!--/size--> kB gzipped, <!--size:index.brotli:1-->15.3<!--/size--> kB brotli, plus a mandatory 3.8 kB stylesheet: 22.3 kB combined. React 18+.
One primitive for bottom sheets, drawers, modal dialogs, side panels, and toasts. A real `<dialog>` in the top layer. The browser's own scroll engine for gestures. Spring physics compiled to CSS `linear()`. <!--size:index.gzip:1-->17.6<!--/size--> kB gzipped, <!--size:index.brotli:1-->15.7<!--/size--> kB brotli, plus a mandatory 3.8 kB stylesheet: 22.3 kB combined. React 18+.

@@ -80,3 +80,3 @@ ## Why another drawer

- **Motion core (experimental).** `scrollsheet/motion` is the React-free layer the sheet runs on: closed-form spring solver, interruptible WAAPI wrapper, scroll tween. 1.6 kB gzipped standalone.
- **Zero-config entry.** `scrollsheet/auto` embeds the stylesheet and injects it on first open: no CSS import needed, <!--size:auto.gzip:1-->21.3<!--/size--> kB gzip for `Sheet` against the default entry's <!--size:index.gzip:1-->17.2<!--/size-->.
- **Zero-config entry.** `scrollsheet/auto` embeds the stylesheet and injects it on first open: no CSS import needed, <!--size:auto.gzip:1-->21.8<!--/size--> kB gzip for `Sheet` against the default entry's <!--size:index.gzip:1-->17.6<!--/size-->.

@@ -83,0 +83,0 @@ Focus containment comes from the platform's `showModal()`, not a JS focus trap. On open, focus lands on the panel so mobile keyboards don't pop unasked; use native `autofocus` to override.

"use client";
import { c as Slot, d as useSheetContext, i as Content$1, n as Description, r as Title, s as Trigger, t as Close, u as Root$1 } from "./misc-DCcFJprt.mjs";
import { t as cn } from "./cn-DgpbGWy-.mjs";
import * as React from "react";
import { Fragment, jsx } from "react/jsx-runtime";
import { createPortal } from "react-dom";
//#region packages/scrollsheet/src/handle.tsx
function detentLabel(spec) {
if (spec === "full") return "Full";
if (spec === "medium") return "Half";
if (spec === "content") return "Fit content";
if (typeof spec === "number") return `${Math.round(spec * 100)}%`;
return spec ?? "";
}
const Handle$1 = React.forwardRef(function Handle({ asChild, onClick, onKeyDown, className, variant = "inside", ...props }, ref) {
const ctx = useSheetContext("Handle");
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => setMounted(true), []);
const outside = variant === "outside" && ctx.side === "bottom" && !(mounted && !ctx.canvasEl);
const variantAttr = outside ? "outside" : variant === "floating" ? "floating" : void 0;
const specs = ctx.detents;
const horizontal = ctx.side === "left" || ctx.side === "right";
const multi = specs.length >= 2;
const activeIndex = specs.findIndex((s) => Object.is(s, ctx.activeDetent));
const move = (delta) => {
if (!multi) return false;
const index = specs.findIndex((s) => Object.is(s, ctx.activeDetent));
const next = specs[index + delta];
if (next === void 0) return false;
ctx.setActiveDetent(next);
return true;
};
const sliderAria = multi ? {
role: "slider",
"aria-orientation": horizontal ? "horizontal" : "vertical",
"aria-valuemin": 0,
"aria-valuemax": specs.length - 1,
...activeIndex >= 0 ? {
"aria-valuenow": activeIndex,
"aria-valuetext": detentLabel(specs[activeIndex])
} : {}
} : {};
const button = jsx(asChild ? Slot : "button", {
...asChild ? {} : { type: "button" },
"aria-label": horizontal ? "Adjust sheet width" : "Adjust sheet height",
...sliderAria,
...props,
ref,
className: cn("scrollsheet-handle", className),
"data-scrollsheet-handle": true,
"data-scrollsheet-handle-variant": variantAttr,
onClick: (event) => {
onClick?.(event);
if (event.defaultPrevented) return;
if (!multi) return;
const index = specs.findIndex((s) => Object.is(s, ctx.activeDetent));
const next = specs[(index + 1) % specs.length];
if (next !== void 0) ctx.setActiveDetent(next);
},
onKeyDown: (event) => {
onKeyDown?.(event);
if (event.defaultPrevented) return;
const expandKey = horizontal ? "ArrowRight" : "ArrowUp";
const collapseKey = horizontal ? "ArrowLeft" : "ArrowDown";
if (event.key === expandKey) {
event.preventDefault();
move(1);
} else if (event.key === collapseKey) {
event.preventDefault();
if (!move(-1) && ctx.dismissible) ctx.setOpen(false);
} else if (multi && event.key === "Home") {
event.preventDefault();
const first = specs[0];
if (first !== void 0) ctx.setActiveDetent(first);
} else if (multi && event.key === "End") {
event.preventDefault();
const last = specs[specs.length - 1];
if (last !== void 0) ctx.setActiveDetent(last);
}
}
});
if (outside) return ctx.canvasEl ? createPortal(button, ctx.canvasEl) : null;
return button;
});
//#endregion
//#region packages/scrollsheet/src/drawer/index.tsx
const wrapperClaims = new Map();
const VAUL_CLOSE_THRESHOLD = .25;
function toDetentSpec(point) {
if (typeof point === "number") return point;
if (point === "fit-content" || point === "content") return "content";
if (/^\d+(\.\d+)?px$/.test(point)) return `${Number.parseFloat(point)}px`;
const parsed = Number.parseFloat(point);
return Number.isFinite(parsed) ? parsed : "content";
}
function resolveFadeFromIndex(snapPoints, fadeFromIndex) {
if (!snapPoints || snapPoints.length === 0) return void 0;
const point = snapPoints[fadeFromIndex ?? snapPoints.length - 1];
return point !== void 0 ? toDetentSpec(point) : void 0;
}
function resolveCloseThreshold(snapPoints, closeThreshold) {
if (snapPoints !== void 0 && snapPoints.length > 0) return void 0;
return Math.min(1, Math.max(0, 1 - (closeThreshold ?? VAUL_CLOSE_THRESHOLD)));
}
function composeOpenChange(onOpenChange, onClose) {
if (!onOpenChange && !onClose) return void 0;
return (next) => {
if (!next) onClose?.();
onOpenChange?.(next);
};
}
function Root(props) {
const { children, open, defaultOpen, onOpenChange, onClose, dismissible, snapPoints, activeSnapPoint, setActiveSnapPoint, onAnimationEnd, autoFocus, nested, direction, modal, shouldScaleBackground, setBackgroundColorOnScale, noBodyStyles, disablePreventScroll, preventScrollRestoration, repositionInputs, scrollLockTimeout, closeThreshold, fadeFromIndex, snapToSequentialPoint, handleOnly, onDrag, onRelease, container, fixed, actionsRef, backdropDismissible, escapeDismissible, keyboardExpands, onTravel, scrollbar } = props;
const detents = snapPoints && snapPoints.length > 0 ? snapPoints.map(toDetentSpec) : void 0;
const activeDetent = activeSnapPoint != null ? toDetentSpec(activeSnapPoint) : void 0;
const largestUndimmedDetent = resolveFadeFromIndex(snapPoints, fadeFromIndex);
const resolvedCloseThreshold = resolveCloseThreshold(snapPoints, closeThreshold);
const handleRelease = onRelease ? (event, willRemainOpen) => onRelease(event, willRemainOpen) : void 0;
const handleOpenChange = composeOpenChange(onOpenChange, onClose);
const handleActiveDetentChange = (detent) => {
if (!setActiveSnapPoint) return;
const match = snapPoints?.find((point) => Object.is(toDetentSpec(point), detent));
setActiveSnapPoint(match ?? detent);
};
React.useEffect(() => {
if (!shouldScaleBackground) return;
const existing = document.querySelector("[data-scrollsheet-background]");
if (existing && !wrapperClaims.has(existing)) return;
const wrapper = existing ?? document.querySelector("[data-vaul-drawer-wrapper]");
if (!wrapper) return;
const count = wrapperClaims.get(wrapper) ?? 0;
wrapperClaims.set(wrapper, count + 1);
if (count === 0) wrapper.setAttribute("data-scrollsheet-background", "");
return () => {
const remaining = (wrapperClaims.get(wrapper) ?? 1) - 1;
if (remaining <= 0) {
wrapperClaims.delete(wrapper);
wrapper.removeAttribute("data-scrollsheet-background");
} else wrapperClaims.set(wrapper, remaining);
};
}, [shouldScaleBackground]);
return jsx(DirectionContext.Provider, {
value: direction ?? "bottom",
children: jsx(Root$1, {
open,
defaultOpen,
onOpenChange: handleOpenChange,
onOpenChangeComplete: onAnimationEnd,
dismissible,
detents,
activeDetent,
onActiveDetentChange: setActiveSnapPoint ? handleActiveDetentChange : void 0,
side: direction,
modal,
backgroundEffect: shouldScaleBackground ? "scale" : "none",
largestUndimmedDetent,
handleOnly,
sequentialDetents: snapToSequentialPoint,
closeThreshold: resolvedCloseThreshold,
onRelease: handleRelease,
actionsRef,
backdropDismissible,
escapeDismissible,
keyboardExpands,
onTravel,
scrollbar,
children
})
});
}
const DirectionContext = React.createContext("bottom");
const NestedRoot = Root;
function Portal({ children, container }) {
return jsx(Fragment, { children });
}
function Overlay(_props) {
return null;
}
function hasMultipleSnapPoints(detents) {
return detents.length > 1;
}
const Content = React.forwardRef(function Content({ onPointerDownOutside, onOpenAutoFocus, onEscapeKeyDown, onCloseAutoFocus, onInteractOutside, onFocusOutside, forceMount, ...props }, ref) {
const ctx = useSheetContext("Content");
const direction = React.useContext(DirectionContext);
const snapPointsActive = hasMultipleSnapPoints(ctx.detents);
return jsx(Content$1, {
...props,
ref,
"data-vaul-drawer": "",
"data-vaul-drawer-direction": direction,
"data-vaul-snap-points": snapPointsActive ? "true" : "false"
});
});
function composeHandleClick(preventCycle, onClick) {
return (event) => {
onClick?.(event);
if (preventCycle) event.preventDefault();
};
}
const Handle = React.forwardRef(function Handle({ preventCycle, onClick, ...props }, ref) {
const ctx = useSheetContext("Handle");
return jsx(Handle$1, {
...props,
ref,
"data-vaul-handle": "",
"data-vaul-drawer-visible": ctx.open ? "true" : "false",
onClick: composeHandleClick(preventCycle, onClick)
});
});
const DrawerClose = React.forwardRef(function DrawerClose({ children, ...props }, ref) {
return jsx(Close, {
...props,
ref,
children: children ?? null
});
});
const Drawer = {
Root,
NestedRoot,
Trigger,
Portal,
Overlay,
Content,
Close: DrawerClose,
Title,
Description,
Handle
};
//#endregion
export { NestedRoot as a, Root as c, hasMultipleSnapPoints as d, resolveCloseThreshold as f, Handle as i, composeHandleClick as l, Handle$1 as m, Drawer as n, Overlay as o, resolveFadeFromIndex as p, DrawerClose as r, Portal as s, Content as t, composeOpenChange as u };
import { a as SheetTitleProps, c as SheetContentProps, g as DetentSpec, i as SheetDescriptionProps, l as SheetTriggerProps, p as SheetRootProps, r as SheetCloseProps } from "./misc-B-HikMKY.mjs";
import * as React from "react";
//#region packages/scrollsheet/src/handle.d.ts
interface SheetHandleProps extends React.ComponentProps<"button"> {
/** Render the child element instead of a `<button>`, merging props into it. */
asChild?: boolean;
/**
* Where the pill sits. `'inside'` (default) flows at the top of the
* sheet's content. `'floating'` overlays the content — absolutely
* positioned over the panel's top edge, for full-bleed content (maps,
* photos) a flow pill would push down. `'outside'` floats in the backdrop
* above the sheet's top edge (bottom sheets only; other sides render as
* `'inside'`) — it rides the canvas layer outside the panel's clip and
* drags, clicks, and keys exactly like the others.
* @default 'inside'
*/
variant?: "inside" | "floating" | "outside";
}
/**
* The grabber pill. Click cycles detents; ArrowUp/ArrowDown (or Left/Right
* for side sheets) move between them, Home/End jump to the first/last — so
* multi-detent sheets are fully keyboard-operable. With two or more detents
* it exposes itself as a slider (role, value min/max/now, value text) so
* screen readers announce the current stop, not just "button".
*
* The handle is optional. It's purely a click/keyboard affordance layered on
* top of the drag engine, which listens on the whole panel, not the handle —
* omit `<Sheet.Handle>` and the whole panel still drags and dismisses, in
* every mode (modal and non-modal) and every side (bottom/top/left/right).
* There's no boolean prop for this; omission is the API.
*/
declare const Handle$1: React.ForwardRefExoticComponent<Omit<SheetHandleProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
//#endregion
//#region packages/scrollsheet/src/drawer/index.d.ts
/** vaul's snap point shape: a 0–1 fraction, an absolute `'###px'` string, or `'fit-content'`. */
type VaulSnapPoint = number | string;
/**
* Resolves vaul's `fadeFromIndex` against `snapPoints` into a
* `largestUndimmedDetent` spec, mirroring real vaul's own default (its
* `src/index.tsx`: `fadeFromIndex = snapPoints && snapPoints.length - 1`) —
* omitted with `snapPoints` set defaults to the *topmost* snap point index
* (no dim until the last snap point), not "no undimmed range at all"
* (scrollsheet's own default when `largestUndimmedDetent` is never touched).
* No `snapPoints`, or an out-of-range explicit index, resolves to
* `undefined` (today's full-dim-range behavior) — pulled out as its own pure
* function so this index math is unit-testable independent of rendering.
*/
declare function resolveFadeFromIndex(snapPoints: readonly VaulSnapPoint[] | undefined, fadeFromIndex: number | undefined): DetentSpec | undefined;
/**
* Resolves vaul's `closeThreshold` against whether `snapPoints` is set,
* CONVERTING between opposite conventions: vaul counts the fraction dragged
* AWAY (its 0.25 default = dismiss after a quarter-height drag), scrollsheet
* counts the fraction still VISIBLE (`isBelowCloseThreshold`: dismiss when
* `revealed < firstDetent * closeThreshold`, so higher = easier). A raw
* passthrough inverts the migrated feel — vaul's 0.25 became "drag 75% to
* dismiss", harder than scrollsheet's own 0.5 default instead of easier —
* so the mapping is `1 - value`, and an omitted prop resolves to vaul's own
* `CLOSE_THRESHOLD` default of 0.25 (=> 0.75 here), never `Sheet.Root`'s
* unrelated 0.5.
* `snapPoints` set makes `closeThreshold` dead code in real vaul (its
* `onRelease` returns through the snap-points branch before ever reading
* it), matched here by always resolving to `undefined` in that case.
* Pulled out as its own pure function, mirroring `resolveFadeFromIndex`,
* so the conversion math is unit-testable independent of rendering.
*/
declare function resolveCloseThreshold(snapPoints: readonly VaulSnapPoint[] | undefined, closeThreshold: number | undefined): number | undefined;
/**
* Composes vaul's `onClose` on top of `onOpenChange`: `onClose` fires
* whenever the transition target is `false`, then `onOpenChange` always
* fires. Real vaul's `closeDrawer()` calls `onClose` from every dismiss
* path (swipe past threshold, Esc, backdrop, imperative); scrollsheet's own
* `onOpenChange` is already the single funnel every one of *its* dismiss
* paths goes through (`useControllableState`'s `onChange`, which fires
* exactly once per real open/false transition), so layering `onClose` on
* top of that wiring reproduces "every dismiss path" without a second one.
* Returns `undefined` when neither callback is given, matching the
* conditional-wrapper convention `onRelease`'s cast uses below. Pulled out
* as its own pure function so the composition is unit-testable without a
* live DOM/click event.
*/
declare function composeOpenChange(onOpenChange: ((open: boolean) => void) | undefined, onClose: (() => void) | undefined): ((open: boolean) => void) | undefined;
/**
* vaul's own props, translated — plus (via the Pick) the scrollsheet-native
* props that have no vaul counterpart and forward to `Sheet.Root`
* untranslated: `backdropDismissible` (the escape hatch vaul served with the
* Radix onPointerDownOutside/onInteractOutside preventDefault idiom),
* `escapeDismissible` (same for onEscapeKeyDown), `keyboardExpands`,
* `onTravel` (the live counterpart of vaul's ignored `onDrag`), `scrollbar`,
* and `actionsRef`. Only props with no vaul name-collision are forwarded —
* anything vaul also has (`closeThreshold`, `modal`, `dismissible`, …) keeps
* its translated vaul semantics above.
*/
interface DrawerRootProps extends Pick<SheetRootProps, "actionsRef" | "backdropDismissible" | "escapeDismissible" | "keyboardExpands" | "onTravel" | "scrollbar"> {
children?: React.ReactNode;
open?: boolean;
defaultOpen?: boolean;
onOpenChange?: (open: boolean) => void;
/**
* Fires whenever the drawer closes — every dismiss path (swipe past
* threshold, Esc, backdrop tap, imperative `close()`) funnels through the
* same `onOpenChange` wiring this is layered on top of, matching real
* vaul's `closeDrawer()`, which calls `onClose` on every one of those
* paths too.
*/
onClose?: () => void;
/** @default true */
dismissible?: boolean;
/** Fractions (0–1), `'###px'` strings, or `'fit-content'` — translated to scrollsheet `detents`. */
snapPoints?: readonly VaulSnapPoint[];
/** Optionally-controlled active snap point; `null` means "no explicit snap point". */
activeSnapPoint?: VaulSnapPoint | null;
setActiveSnapPoint?: (snapPoint: VaulSnapPoint | null) => void;
/** Fires when the open/close transition actually completes — wired to `onOpenChangeComplete`, exact rather than vaul's timer. */
onAnimationEnd?: (open: boolean) => void;
/** Ignored — the native `<dialog>` manages focus itself. */
autoFocus?: boolean;
/** Ignored — nesting is automatic: a `<Drawer.Content>` rendered inside another registers itself. */
nested?: boolean;
/** Maps to scrollsheet `side` — all four edges. */
direction?: "top" | "bottom" | "left" | "right";
/** Maps to scrollsheet `modal` — `false` renders non-modal (Popover top layer, page stays interactive). */
modal?: boolean;
/**
* Maps to `backgroundEffect="scale"`; vaul's own `[data-vaul-drawer-wrapper]`
* is picked up as the target. Left false (vaul's default), this maps to
* `'none'`, not unset — vaul never scales the page unless asked, so the
* scrollsheet default for full-height sheets must not leak in here.
*/
shouldScaleBackground?: boolean;
/**
* Maps to scrollsheet `closeThreshold` — but only when `snapPoints` is
* unset. In real vaul, `closeThreshold` is dead code once `snapPoints`
* exist (its `onRelease` returns through the snap-points branch before
* ever reading it); this compat layer matches that rather than giving the
* prop unconditional live effect. Passing both together warns once in dev
* — use the native `Sheet.Root closeThreshold` directly if you want it to
* combine with detents.
*
* Omitted (with `snapPoints` unset) resolves to vaul's own default of
* `0.25` (`CLOSE_THRESHOLD` in vaul's `src/constants.ts`) — not
* scrollsheet's own `0.5` default, which is twice the drag distance.
*/
closeThreshold?: number;
/**
* Maps to `largestUndimmedDetent`, resolved against `snapPoints` at this
* index. Omitted (with `snapPoints` set) defaults to `snapPoints.length -
* 1` — vaul's own default, meaning no dim until the topmost snap point —
* not "dim across the full range" (scrollsheet's own default when
* `largestUndimmedDetent` is never touched at all).
*/
fadeFromIndex?: number;
/** Maps to `sequentialDetents`. */
snapToSequentialPoint?: boolean;
/** Maps to `handleOnly` directly. */
handleOnly?: boolean;
/**
* Maps to `onRelease`. Typed as vaul's own `React.PointerEvent<HTMLDivElement>`
* (rather than scrollsheet's wider native-`PointerEvent` type) so a
* handler already typed against vaul's declaration still assigns cleanly —
* see the internal cast in `Root` for why this is safe at runtime.
*/
onRelease?: (event: React.PointerEvent<HTMLDivElement>, open: boolean) => void;
setBackgroundColorOnScale?: boolean;
noBodyStyles?: boolean;
disablePreventScroll?: boolean;
preventScrollRestoration?: boolean;
repositionInputs?: boolean;
scrollLockTimeout?: number;
onDrag?: (event: React.PointerEvent, percentageDragged: number) => void;
container?: HTMLElement | null;
/**
* Ignored — real vaul's `fixed` swaps its own keyboard-avoidance strategy
* (resize instead of translate); scrollsheet's `useKeyboardViewport` has
* no equivalent toggle.
*/
fixed?: boolean;
}
declare function Root(props: DrawerRootProps): React.JSX.Element;
/**
* Nested drawers just work: a `<Drawer.Content>` rendered inside another
* `<Drawer.Content>` automatically registers with the parent's stacking
* context (the parent recedes while the child is open), the way iOS stacks
* sheets. `NestedRoot` is an alias of `Root` kept for drop-in compatibility
* with vaul's API surface — there's nothing distinct for it to do.
*/
declare const NestedRoot: typeof Root;
type DrawerNestedRootProps = DrawerRootProps;
interface DrawerPortalProps {
children?: React.ReactNode;
/** Ignored — the native `<dialog>` always renders into the browser's top layer. */
container?: HTMLElement | null;
}
declare function Portal({ children, container }: DrawerPortalProps): React.JSX.Element;
type DrawerOverlayProps = React.ComponentProps<"div">;
/**
* scrollsheet draws its own backdrop (`.scrollsheet-backdrop`, themeable via
* the `--scrollsheet-backdrop` CSS variable) that tracks scroll progress, so
* there's nothing for a separate overlay element to render. This accepts
* vaul's `<Drawer.Overlay className .../>` so existing JSX doesn't crash —
* restyle the backdrop through the CSS variable instead.
*/
declare function Overlay(_props: DrawerOverlayProps): null;
/**
* Whether translated `snapPoints` produced more than one distinct detent —
* feeds Content's `data-vaul-snap-points` attribute. vaul's own
* CSS-selector convention treats a single snap point the same as none
* (scrollsheet's own single-detent default also resolves to `false` here).
* Pulled out as its own pure function, mirroring `resolveFadeFromIndex` /
* `resolveCloseThreshold`, so it's unit-testable independent of rendering
* (`<Drawer.Content>`'s actual DOM output is behind a client-only mount
* gate — see content.tsx — so SSR can't observe the attribute directly).
*/
declare function hasMultipleSnapPoints(detents: readonly DetentSpec[]): boolean;
interface DrawerContentProps extends SheetContentProps {
onPointerDownOutside?: (event: CustomEvent) => void;
onOpenAutoFocus?: (event: Event) => void;
onEscapeKeyDown?: (event: KeyboardEvent) => void;
onCloseAutoFocus?: (event: Event) => void;
onInteractOutside?: (event: CustomEvent) => void;
onFocusOutside?: (event: CustomEvent) => void;
forceMount?: boolean;
}
declare const Content: React.ForwardRefExoticComponent<Omit<DrawerContentProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
/**
* Composes the click handler Handle passes to `SheetHandle`: the caller's
* own `onClick` always fires first, then — when `preventCycle` is set —
* `event.preventDefault()`, the same signal `SheetHandle`'s own
* click-to-cycle logic already checks (`if (event.defaultPrevented) return`
* in handle.tsx) to skip advancing to the next detent. Mirrors real vaul's
* `preventCycle`, read inside its own `handleCycleSnapPoints` guard. Pulled
* out as its own pure function so the composition is unit-testable without
* a live DOM/click event.
*/
declare function composeHandleClick(preventCycle: boolean | undefined, onClick: ((event: React.MouseEvent<HTMLButtonElement>) => void) | undefined): (event: React.MouseEvent<HTMLButtonElement>) => void;
interface DrawerHandleProps extends SheetHandleProps {
/**
* Suppresses the click-to-cycle-detents behavior — the handle still
* renders, drags, and is keyboard-operable as usual, but a click no
* longer advances to the next detent. Mirrors real vaul's own
* `preventCycle`, which guards the same click path (vaul's handle has no
* keyboard cycling to suppress).
*/
preventCycle?: boolean;
}
declare const Handle: React.ForwardRefExoticComponent<Omit<DrawerHandleProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
/**
* Sheet.Close's self-closing form renders a styled ✕ default; vaul's own
* `<Drawer.Close />` renders an empty unstyled button. A migrating user
* must not get a surprise icon button from a version swap, so the compat
* Close pins children to null (defined, so the styled-default branch never
* arms) unless the caller passed some.
*/
declare const DrawerClose: React.ForwardRefExoticComponent<Omit<SheetCloseProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
/** Namespace-style access matching vaul's `<Drawer.Root>…</Drawer.Root>` shape. */
declare const Drawer: {
Root: typeof Root;
NestedRoot: typeof Root;
Trigger: React.ForwardRefExoticComponent<Omit<SheetTriggerProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
Portal: typeof Portal;
Overlay: typeof Overlay;
Content: React.ForwardRefExoticComponent<Omit<DrawerContentProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
Close: React.ForwardRefExoticComponent<Omit<SheetCloseProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
Title: React.ForwardRefExoticComponent<Omit<SheetTitleProps, "ref"> & React.RefAttributes<HTMLHeadingElement>>;
Description: React.ForwardRefExoticComponent<Omit<SheetDescriptionProps, "ref"> & React.RefAttributes<HTMLParagraphElement>>;
Handle: React.ForwardRefExoticComponent<Omit<DrawerHandleProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
};
//#endregion
export { SheetHandleProps as S, composeOpenChange as _, DrawerHandleProps as a, resolveFadeFromIndex as b, DrawerPortalProps as c, NestedRoot as d, Overlay as f, composeHandleClick as g, VaulSnapPoint as h, DrawerContentProps as i, DrawerRootProps as l, Root as m, Drawer as n, DrawerNestedRootProps as o, Portal as p, DrawerClose as r, DrawerOverlayProps as s, Content as t, Handle as u, hasMultipleSnapPoints as v, Handle$1 as x, resolveCloseThreshold as y };
import * as React from "react";
//#region packages/scrollsheet/src/internal/detents.d.ts
/**
* Detents — the stops a sheet can rest at, in the spirit of
* UISheetPresentationController's `detents`.
*
* A detent resolves to the visible height of the sheet in px.
* Accepted forms:
* - 'full' → viewport height minus top inset
* - 'medium' → 50% of viewport
* - 'content' → natural content height (measured, capped at full)
* - number in (0, 1] → fraction of viewport
* - `${number}px` → absolute pixels
*/
type DetentSpec = "full" | "medium" | "content" | number | `${number}px`;
//#endregion
//#region packages/scrollsheet/src/motion/geometry.d.ts
/**
* Per-side axis geometry.
*
* The whole engine (content.tsx) is written once against an abstract
* "revealed px" concept: 0 = fully hidden, `maxDetent` = fully revealed. The
* DOM's scroll position is the mechanism, but which raw value corresponds to
* "revealed" depends on which canvas edge the panel is pinned to:
* bottom/right sit at the canvas's *far* end, so raw scroll IS revealed px
* directly; top/left sit at the *near* end (flush with the screen when fully
* open), so raw scroll is mirrored: revealed = maxDetent - rawScroll.
* `mapScroll` is the self-inverse conversion between the two spaces.
*/
type Side = "bottom" | "top" | "left" | "right";
//#endregion
//#region packages/scrollsheet/src/context.d.ts
/**
* onTravel's third argument. Reused and mutated in place every travel frame
* (see content.tsx's updateTravel) — read it synchronously, don't retain the
* reference across frames.
*/
interface TravelInfo {
/** [0, maxDetent] — the full resolved travel range in px, this frame. */
range: readonly [number, number];
/**
* 0-1 progress toward EACH configured detent, keyed by the detent's
* resolved height in px (not its spec — a spec like 'content' isn't a
* stable identity across resizes, but its resolved height this frame is
* what a consumer interpolates against).
*/
progressAtDetents: ReadonlyMap<number, number>;
}
//#endregion
//#region packages/scrollsheet/src/root.d.ts
/** Imperative escape hatch — see `actionsRef` on `<Sheet.Root>`. */
interface SheetActions {
open(): void;
close(): void;
/**
* Moves the sheet to `detent` through the same `setActiveDetent` path a
* controlled `activeDetent` change uses, so it animates identically. Warns
* in dev (once) if `detent` isn't one of this sheet's configured
* `detents` — only the panel's *rest position* resolves to the nearest
* configured detent in that case; `activeDetent`/`onActiveDetentChange`
* (and Handle's `aria-valuenow`/`aria-valuetext`) still reflect the
* literal value passed in here, not the resolved one.
*/
snapTo(detent: DetentSpec): void;
}
interface SheetRootProps {
children?: React.ReactNode;
/** Controlled open state. */
open?: boolean;
defaultOpen?: boolean;
onOpenChange?: (open: boolean) => void;
/**
* Fires once the open/close *transition* finishes (not just the state
* flip) — `true` when the sheet has fully settled open, `false` once it's
* fully closed and unmounted. Backed by the same phase timers that drive
* `data-scrollsheet-state`/`data-scrollsheet-side` (see Content) — no
* separate timer of its own.
*/
onOpenChangeComplete?: (open: boolean) => void;
/**
* Imperative escape hatch for cases a controlled `open`/`activeDetent`
* prop is awkward for (deep links, push notifications, a descendant
* component reaching up without prop-drilling): `ref.current?.close()` /
* `.open()` / `.snapTo(detent)`. Thin wrappers over the same `setOpen`/
* `setActiveDetent` the rest of the sheet uses — not a separate code path.
*/
actionsRef?: React.Ref<SheetActions>;
/**
* The stops the sheet can rest at, UISheetPresentationController-style.
* `'content'` (natural height), `'medium'` (50%), `'full'`, a 0–1 fraction,
* or `'320px'`.
* @default ['content']
*/
detents?: readonly DetentSpec[];
/** Controlled active detent (one of `detents`). */
activeDetent?: DetentSpec;
onActiveDetentChange?: (detent: DetentSpec) => void;
/**
* Master switch: when false, swipe/drag-to-dismiss, backdrop tap and Esc
* all stop closing the sheet. `escapeDismissible`/`backdropDismissible`
* below let you turn off just one of the pointer/keyboard paths while
* leaving the others (and swipe-to-dismiss, which only this prop gates)
* alone — both default to whatever `dismissible` is.
* @default true
*/
dismissible?: boolean;
/** Whether Esc closes the sheet. @default `dismissible` */
escapeDismissible?: boolean;
/** Whether tapping the backdrop/empty track closes the sheet. @default `dismissible` */
backdropDismissible?: boolean;
/** CSP nonce for the injected style tag. */
nonce?: string;
/**
* Blend the page's `<meta name="theme-color">` toward black as the sheet
* travels, so the browser chrome (iOS Safari tints both the status bar area
* and the bottom toolbar from this tag; Android tints the status bar) dims
* along with the backdrop. Every theme-color tag is dimmed from its own
* base color, so the usual media-scoped light/dark pair works and stays
* correct even if the OS scheme flips while the sheet is open. On a
* bottom-anchored attached sheet, the strip behind the bottom toolbar takes
* the panel's own color instead (via a fixed sentinel element), so the bar
* reads as part of the sheet, not the dimmed page. No-ops if no
* tag holds a parseable color, or on platforms that ignore theme-color.
* @default false
*/
themeColorDimming?: boolean;
/**
* Fires on every frame the sheet travels: revealed px, 0-1 progress
* (against the first detent, same number `--scrollsheet-progress` uses),
* and `info` — per-detent progress plus the resolved travel range. `info`
* is reused and mutated in place every frame (this is the highest-
* frequency callback in the library); read it synchronously, don't store
* the reference. Keep this handler cheap — it runs on every travel frame.
*/
onTravel?: (revealedPx: number, progress: number, info: TravelInfo) => void;
/**
* Which edge the sheet is anchored to, or `"center"` for a centered modal
* dialog: content-sized, consumer CSS owns width, zoom+fade instead of
* travel, no detents or drag. @default 'bottom'
*/
side?: Side | "center";
/**
* When false, the page behind stays fully interactive and scrollable — no
* backdrop, no focus trap, no inert-ing the rest of the page. Renders on a
* `<div popover="manual">` (top layer, non-blocking) where supported,
* falling back to a non-modal `<dialog>` (`.show()`) otherwise.
* @default true
*/
modal?: boolean;
/**
* Applies an iOS-style card effect to the page element marked
* `data-scrollsheet-background`, driven by this sheet's own travel
* progress. `'scale'` scales/insets/rounds it; `'parallax'` only shifts it.
* No-ops if no element is marked.
*
* Left unset, a full-height bottom sheet in the mobile presentation gets
* `'scale'` — that is how the platform presents a sheet that covers the
* screen. Desktop drawers dock beside the page and never move it, and a
* detached (floating-card) sheet never claims the full screen, so neither
* defaults on. `'none'` opts out anywhere.
*/
backgroundEffect?: "scale" | "parallax" | "none";
/**
* Explicit target for backgroundEffect, instead of the default document-wide
* `[data-scrollsheet-background]` query. Pass the same ref you'd otherwise
* mark with the attribute. Skips the ownership check the implicit default
* applies (an explicit ref is unambiguous about which page element it
* means) — matches how an explicit `backgroundEffect` prop today already
* bypasses the implicit default's full-height/opener-containment check.
*/
backgroundRef?: React.RefObject<HTMLElement | null>;
/**
* Inner-content-overflow scrollbar style, for the panel's own scroll at
* max detent (or any content taller than the panel). `'overlay'`: a thin
* auto-hiding thumb (iOS/macOS-style). `'hidden'`: no thumb, no native
* scrollbar either. `'native'`: let the browser draw
* its own.
* @default 'overlay'
*/
scrollbar?: "overlay" | "hidden" | "native";
/**
* Scoped to exactly two things: the backdrop's opacity and
* `themeColorDimming`. Both stay fully transparent/undimmed while the
* sheet is at or below this detent's resolved height, then interpolate
* from there up to the next detent above it (or the tallest detent, if
* this is already the tallest). Unset (default): dims across the full
* range from closed to the first detent, today's behavior. Does *not*
* affect `onTravel`'s progress argument, `--scrollsheet-progress`,
* stacking recede, or `backgroundEffect` — those all track the sheet's
* full-range travel 1:1 regardless of this prop.
*/
largestUndimmedDetent?: DetentSpec;
/**
* Drag sessions (mouse, and touch on non-modal sheets) only start from
* `<Sheet.Handle>` — a pointerdown elsewhere on the panel no-ops. Modal
* touch (native scroll drives the gesture) is restricted via
* `touch-action` instead, except at the tallest detent, where the panel's
* own content scroll still needs it.
* @default false
*/
handleOnly?: boolean;
/**
* No drag sessions at all, handle included — detent changes are
* programmatic (`activeDetent`, `actionsRef`) or click/keyboard
* (`<Sheet.Handle>`) only. Esc and backdrop dismissal are unaffected.
* @default false
*/
disableDrag?: boolean;
/**
* A release never skips over an intermediate detent — the resolved target
* is clamped to the immediate neighbor of the detent the gesture started
* from (or, for a native-scroll settle, the last settled detent).
* @default false
*/
sequentialDetents?: boolean;
/**
* Fraction (0-1) of the first detent below which a release dismisses the
* sheet, replacing the built-in half-of-first-detent rule.
* @default 0.5
*/
closeThreshold?: number;
/**
* Bottom sheets only. When the software keyboard opens with a text field
* focused inside the sheet, promote to the tallest detent so the field
* has the whole remaining viewport to scroll within — a short peek detent
* can't show a field the keyboard would otherwise cover. Restores the
* previous detent on blur unless something else (a drag, a controlled
* change) moved the sheet in between. Gated on the keyboard actually
* appearing (a nonzero measured inset), never on focus alone, so a
* desktop click into an input doesn't expand anything. Promotion goes
* through the same `setActiveDetent` path a controlled change uses — a
* controlled sheet must apply `onActiveDetentChange` back to
* `activeDetent` or this prop has no visible effect.
* @default false
*/
keyboardExpands?: boolean;
/**
* Fires once a drag session resolves, with the real pointer event (the
* `buttons===0` recovery path may pass the triggering `pointermove`
* instead of a `pointerup`) and whether the sheet is staying open —
* `false` only when the release resolved to a dismiss.
*/
onRelease?: (event: PointerEvent, willRemainOpen: boolean) => void;
}
declare function Root({ children, open: openProp, defaultOpen, onOpenChange, onOpenChangeComplete, actionsRef, detents, activeDetent: activeDetentProp, onActiveDetentChange, dismissible, escapeDismissible, backdropDismissible, nonce, themeColorDimming, onTravel, side, modal, backgroundEffect, backgroundRef, scrollbar, largestUndimmedDetent, handleOnly, disableDrag, sequentialDetents, closeThreshold, keyboardExpands, onRelease }: SheetRootProps): React.JSX.Element;
//#endregion
//#region packages/scrollsheet/src/trigger.d.ts
interface SheetTriggerProps extends React.ComponentProps<"button"> {
/**
* Render the child element instead of a `<button>`, merging the trigger's
* props (aria, onClick) into it — for custom button components or links.
*/
asChild?: boolean;
}
declare const Trigger: React.ForwardRefExoticComponent<Omit<SheetTriggerProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
//#endregion
//#region packages/scrollsheet/src/content.d.ts
interface SheetContentProps extends React.ComponentProps<"div"> {
/** Accessible name when no <Sheet.Title> is rendered. */
"aria-label"?: string;
/**
* Stretches [data-scrollsheet-body] to fill the panel (flex column, the
* body itself flex:1/min-height:0) instead of sizing to its natural content
* height. Promotes the docs "Full-height content" recipe into the library —
* write your own inner-scroll child (flex:1, overflow-y:auto) with no CSS
* of your own required on the wrapper.
* @default false
*/
fill?: boolean;
/**
* Render the child element instead of scrollsheet's own panel `<div>`,
* merging the panel's props (role, tabIndex, ref, `data-scrollsheet-*`)
* into it — matches Radix `Dialog.Content asChild`: the child replaces the
* panel outright rather than nesting inside it. The child must be a single
* non-Fragment element; whatever it was given as its own children is
* hoisted into the body wrapper below (detent measurement and stacking
* still need a real DOM descendant there regardless of which element ends
* up as the panel).
* If `children` isn't a single non-Fragment element (missing, a string,
* multiple elements, a `<>...</>` Fragment…), `asChild` is ignored for
* that render — the default panel `<div>` is used instead, with a
* one-time dev warning.
* @default false
*/
asChild?: boolean;
}
declare const Content: React.ForwardRefExoticComponent<Omit<SheetContentProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
//#endregion
//#region packages/scrollsheet/src/misc.d.ts
interface SheetTitleProps extends React.ComponentProps<"h2"> {
/** Render the child element instead of an `<h2>`, merging props into it. */
asChild?: boolean;
}
declare const Title: React.ForwardRefExoticComponent<Omit<SheetTitleProps, "ref"> & React.RefAttributes<HTMLHeadingElement>>;
interface SheetDescriptionProps extends React.ComponentProps<"p"> {
/** Render the child element instead of a `<p>`, merging props into it. */
asChild?: boolean;
}
declare const Description: React.ForwardRefExoticComponent<Omit<SheetDescriptionProps, "ref"> & React.RefAttributes<HTMLParagraphElement>>;
interface SheetCloseProps extends React.ComponentProps<"button"> {
/** Render the child element instead of a `<button>`, merging props into it. */
asChild?: boolean;
}
declare const Close: React.ForwardRefExoticComponent<Omit<SheetCloseProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
//#endregion
export { SheetTitleProps as a, SheetContentProps as c, Root as d, SheetActions as f, DetentSpec as g, Side as h, SheetDescriptionProps as i, SheetTriggerProps as l, TravelInfo as m, Description as n, Title as o, SheetRootProps as p, SheetCloseProps as r, Content as s, Close as t, Trigger as u };

Sorry, the diff of this file is too big to display

"use client";
import * as React from "react";
//#region packages/scrollsheet/src/internal/dev-warn.ts
const warned = new Set();
function warnOnce(key, message) {
if (warned.has(key)) return;
warned.add(key);
console.warn(message);
}
//#endregion
//#region packages/scrollsheet/src/internal/env.ts
let cached = null;
function env() {
if (cached) return cached;
const hasCSS = typeof CSS !== "undefined" && typeof CSS.supports === "function";
cached = {
scrollTimeline: hasCSS && CSS.supports("animation-timeline: scroll()"),
scrollend: typeof window !== "undefined" && "onscrollend" in window,
linearEasing: hasCSS && CSS.supports("transition-timing-function", "linear(0, 1)")
};
return cached;
}
function hasDialogSupport() {
return typeof HTMLDialogElement !== "undefined" && "showModal" in HTMLDialogElement.prototype;
}
function hasPopoverSupport() {
return typeof HTMLElement !== "undefined" && "showPopover" in HTMLElement.prototype;
}
function hasClosedBySupport() {
return typeof HTMLDialogElement !== "undefined" && "closedBy" in HTMLDialogElement.prototype;
}
function hasCloseWatcherSupport() {
return typeof window !== "undefined" && "CloseWatcher" in window;
}
let warnedNoDialogSupport = false;
function warnMissingDialogSupport() {
if (warnedNoDialogSupport) return;
warnedNoDialogSupport = true;
console.warn("scrollsheet: this browser has no <dialog> support — falling back to a plain modal (no detents, drag, or animation). Content and dismissal work as normal.");
}
let warnedUndimmedDetentOutOfRange = false;
function warnLargestUndimmedDetentOutOfRange() {
if (warnedUndimmedDetentOutOfRange) return;
warnedUndimmedDetentOutOfRange = true;
console.warn("scrollsheet: largestUndimmedDetent resolved to a height above every configured detent — the backdrop and themeColorDimming will stay undimmed for nearly all of the sheet's travel. Choose a largestUndimmedDetent within the configured detents range.");
}
let warnedUnresolvableSnapToDetent = false;
function warnUnresolvableSnapToDetent() {
if (warnedUnresolvableSnapToDetent) return;
warnedUnresolvableSnapToDetent = true;
console.warn("scrollsheet: actionsRef.snapTo() was called with a detent spec that isn't in this sheet's `detents` list — the panel will rest at the nearest configured detent, but `activeDetent`/`onActiveDetentChange` (and Handle's aria-valuenow/aria-valuetext) will still reflect the literal spec you passed in, not the resolved one.");
}
let warnedContentAsChildInvalidChild = false;
function warnContentAsChildInvalidChild() {
if (warnedContentAsChildInvalidChild) return;
warnedContentAsChildInvalidChild = true;
console.warn("scrollsheet: Sheet.Content asChild expects children to be a single non-Fragment React element — falling back to the default panel <div> (asChild ignored) instead.");
}
function warnCoreStylesMissing() {
warnOnce("css", "scrollsheet: import 'scrollsheet/styles.css'");
}
function prefersReducedMotion() {
return typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
}
//#endregion
//#region packages/scrollsheet/src/internal/inject-styles.ts
function createStyleInjector(css, dataAttr) {
const injectedRoots = new WeakSet();
function injectInto(root, nonce) {
if (!css) return;
if (injectedRoots.has(root)) return;
const existing = root === document ? document.querySelector(`style[${dataAttr}]`) : root.querySelector(`style[${dataAttr}]`);
const marked = root;
if (existing || marked[Symbol.for(dataAttr)]) {
injectedRoots.add(root);
return;
}
const style = document.createElement("style");
style.setAttribute(dataAttr, "");
if (nonce) style.nonce = nonce;
style.textContent = css;
(root === document ? document.head : root).appendChild(style);
marked[Symbol.for(dataAttr)] = true;
injectedRoots.add(root);
}
function injectDocument(nonce) {
if (typeof document === "undefined") return;
injectInto(document, nonce);
}
function injectShadowRoot(root, nonce) {
if (!css) return;
if (typeof CSSStyleSheet === "undefined" || !("adoptedStyleSheets" in root)) {
injectInto(root, nonce);
return;
}
if (injectedRoots.has(root)) return;
const marker = Symbol.for(dataAttr);
const marked = root;
if (!marked[marker] && !root.querySelector(`style[${dataAttr}]`)) {
const sheet = new CSSStyleSheet();
sheet.replaceSync(css);
root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];
}
marked[marker] = true;
injectedRoots.add(root);
}
return {
injectDocument,
injectShadowRoot
};
}
//#endregion
//#region packages/scrollsheet/src/internal/use-close-watcher.ts
function useCloseWatcher({ present, nonModal, escapeDismissible, onClose }) {
const onCloseRef = React.useRef(onClose);
React.useInsertionEffect(() => {
onCloseRef.current = onClose;
});
React.useEffect(() => {
if (!present || !nonModal || !escapeDismissible) return;
if (!hasCloseWatcherSupport()) return;
if (typeof window.matchMedia !== "function" || !window.matchMedia("(hover: none) and (pointer: coarse)").matches) return;
const Ctor = window.CloseWatcher;
const watcher = new Ctor();
watcher.onclose = () => onCloseRef.current();
return () => watcher.destroy();
}, [
present,
nonModal,
escapeDismissible
]);
}
//#endregion
//#region packages/scrollsheet/src/internal/cn.ts
function cn(...classes) {
const joined = classes.filter(Boolean).join(" ");
return joined.length > 0 ? joined : void 0;
}
//#endregion
export { hasClosedBySupport as a, prefersReducedMotion as c, warnLargestUndimmedDetentOutOfRange as d, warnMissingDialogSupport as f, env as i, warnContentAsChildInvalidChild as l, warnOnce as m, useCloseWatcher as n, hasDialogSupport as o, warnUnresolvableSnapToDetent as p, createStyleInjector as r, hasPopoverSupport as s, cn as t, warnCoreStylesMissing as u };
"use client";
import { c as Slot, d as useSheetContext, i as Content$1, n as Description, r as Title, s as Trigger, t as Close, u as Root$1 } from "./misc-DMsqVKo9.mjs";
import { m as warnOnce, t as cn } from "./cn-eC4hyhVS.mjs";
import * as React from "react";
import { Fragment, jsx } from "react/jsx-runtime";
import { createPortal } from "react-dom";
//#region packages/scrollsheet/src/handle.tsx
function detentLabel(spec) {
if (spec === "full") return "Full";
if (spec === "medium") return "Half";
if (spec === "content") return "Fit content";
if (typeof spec === "number") return `${Math.round(spec * 100)}%`;
return spec ?? "";
}
const Handle$1 = React.forwardRef(function Handle({ asChild, onClick, onKeyDown, className, variant = "inside", ...props }, ref) {
const ctx = useSheetContext("Handle");
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => setMounted(true), []);
const outside = variant === "outside" && ctx.side === "bottom" && !(mounted && !ctx.canvasEl);
const variantAttr = outside ? "outside" : variant === "floating" ? "floating" : void 0;
const specs = ctx.detents;
const horizontal = ctx.side === "left" || ctx.side === "right";
const multi = specs.length >= 2;
const activeIndex = specs.findIndex((s) => Object.is(s, ctx.activeDetent));
const move = (delta) => {
if (!multi) return false;
const index = specs.findIndex((s) => Object.is(s, ctx.activeDetent));
const next = specs[index + delta];
if (next === void 0) return false;
ctx.setActiveDetent(next);
return true;
};
const sliderAria = multi ? {
role: "slider",
"aria-orientation": horizontal ? "horizontal" : "vertical",
"aria-valuemin": 0,
"aria-valuemax": specs.length - 1,
...activeIndex >= 0 ? {
"aria-valuenow": activeIndex,
"aria-valuetext": detentLabel(specs[activeIndex])
} : {}
} : {};
const button = jsx(asChild ? Slot : "button", {
...asChild ? {} : { type: "button" },
"aria-label": horizontal ? "Adjust sheet width" : "Adjust sheet height",
...sliderAria,
...props,
ref,
className: cn("scrollsheet-handle", className),
"data-scrollsheet-handle": true,
"data-scrollsheet-handle-variant": variantAttr,
onClick: (event) => {
onClick?.(event);
if (event.defaultPrevented) return;
if (!multi) return;
const index = specs.findIndex((s) => Object.is(s, ctx.activeDetent));
const next = specs[(index + 1) % specs.length];
if (next !== void 0) ctx.setActiveDetent(next);
},
onKeyDown: (event) => {
onKeyDown?.(event);
if (event.defaultPrevented) return;
const expandKey = horizontal ? "ArrowRight" : "ArrowUp";
const collapseKey = horizontal ? "ArrowLeft" : "ArrowDown";
if (event.key === expandKey) {
event.preventDefault();
move(1);
} else if (event.key === collapseKey) {
event.preventDefault();
if (!move(-1) && ctx.dismissible) ctx.setOpen(false);
} else if (multi && event.key === "Home") {
event.preventDefault();
const first = specs[0];
if (first !== void 0) ctx.setActiveDetent(first);
} else if (multi && event.key === "End") {
event.preventDefault();
const last = specs[specs.length - 1];
if (last !== void 0) ctx.setActiveDetent(last);
}
}
});
if (outside) return ctx.canvasEl ? createPortal(button, ctx.canvasEl) : null;
return button;
});
//#endregion
//#region packages/scrollsheet/src/drawer/index.tsx
const wrapperClaims = new Map();
const VAUL_CLOSE_THRESHOLD = .25;
const collectIgnored = (entries) => entries.filter(([, v]) => v !== void 0).map(([k]) => k);
function toDetentSpec(point) {
if (typeof point === "number") return point;
if (point === "fit-content" || point === "content") return "content";
if (/^\d+(\.\d+)?px$/.test(point)) return `${Number.parseFloat(point)}px`;
const parsed = Number.parseFloat(point);
return Number.isFinite(parsed) ? parsed : "content";
}
function resolveFadeFromIndex(snapPoints, fadeFromIndex) {
if (!snapPoints || snapPoints.length === 0) return void 0;
const point = snapPoints[fadeFromIndex ?? snapPoints.length - 1];
return point !== void 0 ? toDetentSpec(point) : void 0;
}
function resolveCloseThreshold(snapPoints, closeThreshold) {
if (snapPoints !== void 0 && snapPoints.length > 0) return void 0;
return Math.min(1, Math.max(0, 1 - (closeThreshold ?? VAUL_CLOSE_THRESHOLD)));
}
function composeOpenChange(onOpenChange, onClose) {
if (!onOpenChange && !onClose) return void 0;
return (next) => {
if (!next) onClose?.();
onOpenChange?.(next);
};
}
function Root(props) {
const { children, open, defaultOpen, onOpenChange, onClose, dismissible, snapPoints, activeSnapPoint, setActiveSnapPoint, onAnimationEnd, autoFocus, nested, direction, modal, shouldScaleBackground, setBackgroundColorOnScale, noBodyStyles, disablePreventScroll, preventScrollRestoration, repositionInputs, scrollLockTimeout, closeThreshold, fadeFromIndex, snapToSequentialPoint, handleOnly, onDrag, onRelease, container, fixed, actionsRef, backdropDismissible, escapeDismissible, keyboardExpands, onTravel, scrollbar } = props;
{
const ignored = collectIgnored([
["setBackgroundColorOnScale", setBackgroundColorOnScale],
["noBodyStyles", noBodyStyles],
["disablePreventScroll", disablePreventScroll],
["preventScrollRestoration", preventScrollRestoration],
["repositionInputs", repositionInputs],
["scrollLockTimeout", scrollLockTimeout],
["onDrag", onDrag],
["container", container],
["fixed", fixed],
["autoFocus", autoFocus],
["nested", nested]
]);
if (ignored.length > 0) warnOnce("root-ignored-props", `[scrollsheet Drawer] These <Drawer.Root> props from vaul have no effect in this compat layer and are ignored: ${ignored.join(", ")}. See the vaul migration notes in the README.${onDrag !== void 0 ? " onDrag: the native onTravel prop works on this same <Drawer.Root>." : ""}`);
}
const detents = snapPoints && snapPoints.length > 0 ? snapPoints.map(toDetentSpec) : void 0;
const activeDetent = activeSnapPoint != null ? toDetentSpec(activeSnapPoint) : void 0;
const largestUndimmedDetent = resolveFadeFromIndex(snapPoints, fadeFromIndex);
const resolvedCloseThreshold = resolveCloseThreshold(snapPoints, closeThreshold);
if (snapPoints !== void 0 && snapPoints.length > 0 && closeThreshold !== void 0) warnOnce("close-threshold-with-snap-points", "[scrollsheet Drawer] <Drawer.Root closeThreshold> has no effect when snapPoints is set — this matches real vaul, where closeThreshold is dead code once snapPoints exist (its onRelease returns through the snap-points branch before ever reading it). Use the native Sheet.Root `closeThreshold` (a live 0-1 fraction of the first detent, works alongside detents) if you want the upgrade.");
const handleRelease = onRelease ? (event, willRemainOpen) => onRelease(event, willRemainOpen) : void 0;
const handleOpenChange = composeOpenChange(onOpenChange, onClose);
const handleActiveDetentChange = (detent) => {
if (!setActiveSnapPoint) return;
const match = snapPoints?.find((point) => Object.is(toDetentSpec(point), detent));
setActiveSnapPoint(match ?? detent);
};
React.useEffect(() => {
if (!shouldScaleBackground) return;
const existing = document.querySelector("[data-scrollsheet-background]");
if (existing && !wrapperClaims.has(existing)) return;
const wrapper = existing ?? document.querySelector("[data-vaul-drawer-wrapper]");
if (!wrapper) return;
const count = wrapperClaims.get(wrapper) ?? 0;
wrapperClaims.set(wrapper, count + 1);
if (count === 0) wrapper.setAttribute("data-scrollsheet-background", "");
return () => {
const remaining = (wrapperClaims.get(wrapper) ?? 1) - 1;
if (remaining <= 0) {
wrapperClaims.delete(wrapper);
wrapper.removeAttribute("data-scrollsheet-background");
} else wrapperClaims.set(wrapper, remaining);
};
}, [shouldScaleBackground]);
return jsx(DirectionContext.Provider, {
value: direction ?? "bottom",
children: jsx(Root$1, {
open,
defaultOpen,
onOpenChange: handleOpenChange,
onOpenChangeComplete: onAnimationEnd,
dismissible,
detents,
activeDetent,
onActiveDetentChange: setActiveSnapPoint ? handleActiveDetentChange : void 0,
side: direction,
modal,
backgroundEffect: shouldScaleBackground ? "scale" : "none",
largestUndimmedDetent,
handleOnly,
sequentialDetents: snapToSequentialPoint,
closeThreshold: resolvedCloseThreshold,
onRelease: handleRelease,
actionsRef,
backdropDismissible,
escapeDismissible,
keyboardExpands,
onTravel,
scrollbar,
children
})
});
}
const DirectionContext = React.createContext("bottom");
const NestedRoot = Root;
function Portal({ children, container }) {
if (container !== void 0) warnOnce("portal-container", "[scrollsheet Drawer] <Drawer.Portal container> has no effect — scrollsheet's <dialog> always renders into the browser's top layer (effectively document.body), so there's no separate container to portal into.");
return jsx(Fragment, { children });
}
function Overlay(_props) {
return null;
}
function hasMultipleSnapPoints(detents) {
return detents.length > 1;
}
const Content = React.forwardRef(function Content({ onPointerDownOutside, onOpenAutoFocus, onEscapeKeyDown, onCloseAutoFocus, onInteractOutside, onFocusOutside, forceMount, ...props }, ref) {
const ctx = useSheetContext("Content");
const direction = React.useContext(DirectionContext);
const snapPointsActive = hasMultipleSnapPoints(ctx.detents);
{
const ignored = collectIgnored([
["onPointerDownOutside", onPointerDownOutside],
["onOpenAutoFocus", onOpenAutoFocus],
["onEscapeKeyDown", onEscapeKeyDown],
["onCloseAutoFocus", onCloseAutoFocus],
["onInteractOutside", onInteractOutside],
["onFocusOutside", onFocusOutside],
["forceMount", forceMount]
]);
if (ignored.length > 0) warnOnce("content-ignored-props", `[scrollsheet Drawer] These <Drawer.Content> props from vaul have no effect in this compat layer and are stripped before reaching the DOM: ${ignored.join(", ")}. See the vaul migration notes in the README.${onPointerDownOutside !== void 0 || onInteractOutside !== void 0 || onEscapeKeyDown !== void 0 ? " To block backdrop-tap dismissal, set backdropDismissible={false} on <Drawer.Root>; to block Esc, escapeDismissible={false}." : ""}`);
}
return jsx(Content$1, {
...props,
ref,
"data-vaul-drawer": "",
"data-vaul-drawer-direction": direction,
"data-vaul-snap-points": snapPointsActive ? "true" : "false"
});
});
function composeHandleClick(preventCycle, onClick) {
return (event) => {
onClick?.(event);
if (preventCycle) event.preventDefault();
};
}
const Handle = React.forwardRef(function Handle({ preventCycle, onClick, ...props }, ref) {
const ctx = useSheetContext("Handle");
return jsx(Handle$1, {
...props,
ref,
"data-vaul-handle": "",
"data-vaul-drawer-visible": ctx.open ? "true" : "false",
onClick: composeHandleClick(preventCycle, onClick)
});
});
const DrawerClose = React.forwardRef(function DrawerClose({ children, ...props }, ref) {
return jsx(Close, {
...props,
ref,
children: children ?? null
});
});
const Drawer = {
Root,
NestedRoot,
Trigger,
Portal,
Overlay,
Content,
Close: DrawerClose,
Title,
Description,
Handle
};
//#endregion
export { NestedRoot as a, Root as c, hasMultipleSnapPoints as d, resolveCloseThreshold as f, Handle as i, composeHandleClick as l, Handle$1 as m, Drawer as n, Overlay as o, resolveFadeFromIndex as p, DrawerClose as r, Portal as s, Content as t, composeOpenChange as u };

Sorry, the diff of this file is too big to display

"use client";
import { c as prefersReducedMotion, m as warnOnce, n as useCloseWatcher, r as createStyleInjector, t as cn } from "./cn-eC4hyhVS.mjs";
import * as React from "react";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
import { createPortal } from "react-dom";
//#region packages/scrollsheet/src/toast/state.ts
let toasts = [];
const listeners = new Set();
let uid = 0;
const MAX_HISTORY_SIZE = 100;
let history = [];
function trimHistory() {
let toRemove = history.length - MAX_HISTORY_SIZE;
if (toRemove <= 0) return;
const liveIds = new Set(toasts.map((t) => t.id));
history = history.filter((record) => {
if (toRemove > 0 && !liveIds.has(record.id)) {
toRemove -= 1;
return false;
}
return true;
});
}
function recordHistory(record) {
const index = history.findIndex((t) => t.id === record.id);
if (index === -1) {
history = [...history, record];
trimHistory();
return;
}
history = history.map((t, i) => i === index ? record : t);
}
function nextId() {
uid += 1;
return uid;
}
function publish() {
for (const listener of listeners) listener();
}
function subscribe(listener) {
listeners.add(listener);
return () => listeners.delete(listener);
}
function getSnapshot() {
return toasts;
}
const EMPTY_TOASTS = [];
function getServerSnapshot() {
return EMPTY_TOASTS;
}
function upsert(id, patch, fallbackType) {
const resolvedId = id ?? nextId();
const index = toasts.findIndex((t) => t.id === resolvedId);
const type = patch.type ?? fallbackType;
let record;
if (index === -1) {
record = {
dismissible: true,
...patch,
id: resolvedId,
type,
createdAt: Date.now()
};
toasts = [...toasts, record];
} else {
record = {
...toasts[index],
...patch,
id: resolvedId,
type
};
toasts = toasts.map((t, i) => i === index ? record : t);
}
recordHistory(record);
publish();
return resolvedId;
}
function dismiss(id) {
if (id === void 0) {
const swept = new Set(toasts);
for (const t of swept) {
if (!toasts.includes(t)) continue;
t.onDismiss?.(t);
}
toasts = toasts.filter((t) => !swept.has(t));
publish();
return;
}
const existing = toasts.find((t) => t.id === id);
if (!existing) return id;
existing.onDismiss?.(existing);
toasts = toasts.filter((t) => t !== existing);
publish();
return id;
}
function expire(id) {
const existing = toasts.find((t) => t.id === id);
if (!existing) return;
existing.onAutoClose?.(existing);
toasts = toasts.filter((t) => t !== existing);
publish();
}
function baseToast(message, data) {
return upsert(data?.id, {
...data,
title: message
}, "default");
}
function withType(type) {
return (message, data) => upsert(data?.id, {
...data,
title: message
}, type);
}
const toastImpl = baseToast;
toastImpl.success = withType("success");
toastImpl.error = withType("error");
toastImpl.info = withType("info");
toastImpl.warning = withType("warning");
toastImpl.loading = withType("loading");
toastImpl.message = withType("default");
toastImpl.custom = (jsx, data) => {
const id = data?.id ?? nextId();
return upsert(id, {
...data,
jsx: jsx(id)
}, "default");
};
function isHttpResponse(value) {
return typeof value === "object" && value !== null && "ok" in value && typeof value.ok === "boolean" && "status" in value && typeof value.status === "number";
}
function isPromiseExtendedResult(value) {
return typeof value === "object" && value !== null && !React.isValidElement(value);
}
function applyPromiseSettlement(id, resolved, fallbackTitle, description, type) {
if (isPromiseExtendedResult(resolved)) {
const { message, ...rest } = resolved;
upsert(id, {
description,
...rest,
title: message ?? fallbackTitle,
type
}, type);
return;
}
upsert(id, {
title: resolved ?? fallbackTitle,
description,
type
}, type);
}
function promiseImpl(promiseOrFn, data) {
const { loading, success, error, finally: onFinally, description, ...rest } = data;
const id = upsert(data.id, {
...rest,
description: typeof description === "function" ? void 0 : description,
title: loading,
type: "loading"
}, "loading");
const settle = typeof promiseOrFn === "function" ? promiseOrFn() : promiseOrFn;
let settled;
const chain = settle.then(async (result) => {
if (isHttpResponse(result) && !result.ok) {
settled = {
ok: false,
reason: result
};
const statusMessage = `HTTP error! status: ${result.status}`;
const resolvedDescription = typeof description === "function" ? await description(statusMessage) : description;
const resolved = typeof error === "function" ? await error(statusMessage) : error;
applyPromiseSettlement(id, resolved, "Error", resolvedDescription, "error");
return;
}
if (result instanceof Error) {
settled = {
ok: false,
reason: result
};
const resolvedDescription = typeof description === "function" ? await description(result) : description;
const resolved = typeof error === "function" ? await error(result) : error;
applyPromiseSettlement(id, resolved, "Error", resolvedDescription, "error");
return;
}
settled = {
ok: true,
value: result
};
const resolvedDescription = typeof description === "function" ? await description(result) : description;
const resolved = typeof success === "function" ? await success(result) : success;
applyPromiseSettlement(id, resolved, "Success", resolvedDescription, "success");
}).catch(async (err) => {
settled = {
ok: false,
reason: err
};
const resolvedDescription = typeof description === "function" ? await description(err) : description;
const resolved = typeof error === "function" ? await error(err) : error;
applyPromiseSettlement(id, resolved, "Error", resolvedDescription, "error");
}).finally(() => onFinally?.());
const unwrap = () => chain.then(() => {
if (!settled) throw new Error("scrollsheet toast.promise: chain settled with no outcome");
if (settled.ok) return settled.value;
throw settled.reason;
});
return Object.assign(id, { unwrap });
}
toastImpl.promise = promiseImpl;
toastImpl.dismiss = dismiss;
toastImpl.getToasts = () => toasts;
toastImpl.getHistory = () => history;
const toast = toastImpl;
function useSonner() {
return { toasts: React.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) };
}
const useToasts = useSonner;
function selectToasterToasts(all, toasterId) {
return all.filter((t) => toasterId === void 0 ? t.toasterId === void 0 : t.toasterId === toasterId);
}
function selectPositionToasts(all, position, defaultPosition) {
return all.filter((t) => t.position === void 0 ? position === defaultPosition : t.position === position);
}
function selectToastWindow(toasts, visibleToasts) {
const newestFirst = [...toasts].reverse();
return {
visible: newestFirst.slice(0, visibleToasts),
hidden: newestFirst.slice(visibleToasts)
};
}
//#endregion
//#region packages/scrollsheet/src/toast/toast-styles.ts
const { injectDocument, injectShadowRoot } = createStyleInjector(`.scrollsheet-toast{border-radius:var(--scrollsheet-toast-radius,14px);color:#18181b;background:#fff;border:1px solid #00000014;align-items:flex-start;gap:10px;padding:16px;font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif;font-size:13px;display:flex;position:relative;box-shadow:0 4px 12px #0000001a}.scrollsheet-toast[data-scrollsheet-custom]{box-shadow:none;background:0 0;border:none;padding:0}.scrollsheet-toast-icon{color:#fff;background:#6b7280;border-radius:50%;flex:none;place-items:center;width:18px;height:18px;margin-top:1px;font-size:11px;line-height:1;display:grid}.scrollsheet-toast-icon[data-type=success]{background:#22c55e}.scrollsheet-toast-icon[data-type=error]{background:#ef4444}.scrollsheet-toast-icon[data-type=warning]{background:#f59e0b}.scrollsheet-toast-icon[data-type=info]{background:#3b82f6}.scrollsheet-toast-icon[data-type=loading]{background:0 0}.scrollsheet-toast-spinner{border:2px solid #00000026;border-top-color:#0000008c;border-radius:50%;width:14px;height:14px;animation:.6s linear infinite scrollsheet-toast-spin}@keyframes scrollsheet-toast-spin{to{transform:rotate(360deg)}}@media (prefers-reduced-motion:reduce){.scrollsheet-toast-spinner{animation-duration:1.6s}}.scrollsheet-toast-body{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.scrollsheet-toast-title{font-size:13px;font-weight:500;line-height:1.35}.scrollsheet-toast-description{color:#0009;font-size:13px;line-height:1.4}.scrollsheet-toast-actions{flex:none;align-items:center;gap:6px;display:flex}.scrollsheet-toast-action,.scrollsheet-toast-cancel{cursor:pointer;border:none;border-radius:4px;height:24px;padding:0 8px;font-size:12px;font-weight:500}.scrollsheet-toast-action{color:#fff;background:#18181b}.scrollsheet-toast-cancel{color:#18181b;background:#0000000f}.scrollsheet-toast-close{color:#00000080;cursor:pointer;background:0 0;border:none;border-radius:50%;flex:none;place-items:center;width:20px;height:20px;padding:0;font-size:14px;line-height:1;display:grid}.scrollsheet-toast-close:hover{background:#0000000f}[data-scrollsheet-toaster]{z-index:2147483647;width:min(356px, calc(100vw - 2 * var(--scrollsheet-toast-offset,var(--sonner-offset,24px))));outline:none;margin:0;padding:0;list-style:none;transition:transform .4s;position:fixed}[data-scrollsheet-toaster][data-x-position=right]{right:var(--scrollsheet-toast-offset,var(--sonner-offset,24px))}[data-scrollsheet-toaster][data-x-position=left]{left:var(--scrollsheet-toast-offset,var(--sonner-offset,24px))}[data-scrollsheet-toaster][data-x-position=center]{left:50%;transform:translate(-50%)}[data-scrollsheet-toaster][data-y-position=top]{top:var(--scrollsheet-toast-offset,var(--sonner-offset,24px))}[data-scrollsheet-toaster][data-y-position=bottom]{bottom:var(--scrollsheet-toast-offset,var(--sonner-offset,24px))}.scrollsheet-toast{--scrollsheet-toast-lift:-1;--scrollsheet-toast-y:translateY(100%);opacity:0;transform:var(--scrollsheet-toast-y);touch-action:none;box-sizing:border-box;overflow-wrap:anywhere;transition:transform .25s,opacity .25s,height .25s;position:absolute;left:0;right:0}.scrollsheet-toast[data-y-position=top]{--scrollsheet-toast-lift:1;--scrollsheet-toast-y:translateY(-100%);top:0}.scrollsheet-toast[data-y-position=bottom]{--scrollsheet-toast-lift:-1;--scrollsheet-toast-y:translateY(100%);bottom:0}.scrollsheet-toast[data-mounted]{--scrollsheet-toast-y:translateY(0);opacity:1}.scrollsheet-toast[data-expanded=false][data-front=false]{--scrollsheet-toast-y:translateY(calc(var(--scrollsheet-toast-lift) * var(--scrollsheet-toast-gap,var(--sonner-gap,14px)) * var(--scrollsheet-toast-toasts-before,var(--sonner-toasts-before,0)))) scale(calc(1 - var(--scrollsheet-toast-toasts-before,var(--sonner-toasts-before,0)) * .05));height:var(--scrollsheet-toast-front-height,var(--sonner-front-height,auto));overflow:hidden}.scrollsheet-toast[data-expanded=false][data-front=false]>*{opacity:0}.scrollsheet-toast[data-mounted][data-expanded=true]{--scrollsheet-toast-y:translateY(calc(var(--scrollsheet-toast-lift) * var(--scrollsheet-toast-stack-offset,var(--sonner-stack-offset,0px))));height:var(--scrollsheet-toast-initial-height,var(--sonner-initial-height,auto))}.scrollsheet-toast[data-visible=false]{opacity:0;pointer-events:none}.scrollsheet-toast[data-removed]{pointer-events:none}.scrollsheet-toast[data-removed][data-front=true]{--scrollsheet-toast-y:translateY(calc(var(--scrollsheet-toast-lift) * -100%));opacity:0}.scrollsheet-toast[data-removed][data-front=false][data-expanded=true]{--scrollsheet-toast-y:translateY(calc(var(--scrollsheet-toast-lift) * var(--scrollsheet-toast-stack-offset,var(--sonner-stack-offset,0px)) + var(--scrollsheet-toast-lift) * -100%));opacity:0}.scrollsheet-toast[data-removed][data-front=false][data-expanded=false]{--scrollsheet-toast-y:translateY(40%);opacity:0}.scrollsheet-toast[data-swiping=true]{transform:var(--scrollsheet-toast-y) translateY(var(--scrollsheet-toast-swipe-y,var(--sonner-swipe-y,0px))) translateX(var(--scrollsheet-toast-swipe-x,var(--sonner-swipe-x,0px)));transition:none}.scrollsheet-toast[data-swiped=true]{-webkit-user-select:none;user-select:none}.scrollsheet-toast[data-swipe-out=true]{animation:.2s ease-out forwards scrollsheet-toast-swipe-out}.scrollsheet-toast[data-swipe-direction=left]{--scrollsheet-toast-swipe-out-x:-1}.scrollsheet-toast[data-swipe-direction=right]{--scrollsheet-toast-swipe-out-x:1}.scrollsheet-toast[data-swipe-direction=up]{--scrollsheet-toast-swipe-out-y:-1}.scrollsheet-toast[data-swipe-direction=down]{--scrollsheet-toast-swipe-out-y:1}@keyframes scrollsheet-toast-swipe-out{0%{transform:var(--scrollsheet-toast-y) translateY(var(--scrollsheet-toast-swipe-y,var(--sonner-swipe-y,0px))) translateX(var(--scrollsheet-toast-swipe-x,var(--sonner-swipe-x,0px)));opacity:1}to{transform:var(--scrollsheet-toast-y) translateY(calc(var(--scrollsheet-toast-swipe-y,var(--sonner-swipe-y,0px)) + var(--scrollsheet-toast-swipe-out-y,var(--sonner-swipe-out-y,0)) * 100%)) translateX(calc(var(--scrollsheet-toast-swipe-x,var(--sonner-swipe-x,0px)) + var(--scrollsheet-toast-swipe-out-x,var(--sonner-swipe-out-x,0)) * 100%));opacity:0}}@media (prefers-reduced-motion:reduce){[data-scrollsheet-toaster],.scrollsheet-toast{transition:none!important;animation:none!important}}@media (max-width:600px){[data-scrollsheet-toaster]{right:var(--scrollsheet-toast-mobile-offset,var(--sonner-mobile-offset,16px));left:var(--scrollsheet-toast-mobile-offset,var(--sonner-mobile-offset,16px));width:100%}[data-scrollsheet-toaster] .scrollsheet-toast{width:calc(100% - var(--scrollsheet-toast-mobile-offset,var(--sonner-mobile-offset,16px)) * 2);left:0;right:0}[data-scrollsheet-toaster][data-x-position=left]{left:var(--scrollsheet-toast-mobile-offset,var(--sonner-mobile-offset,16px))}[data-scrollsheet-toaster][data-y-position=bottom]{bottom:var(--scrollsheet-toast-mobile-offset,var(--sonner-mobile-offset,16px))}[data-scrollsheet-toaster][data-y-position=top]{top:var(--scrollsheet-toast-mobile-offset,var(--sonner-mobile-offset,16px))}[data-scrollsheet-toaster][data-x-position=center]{left:var(--scrollsheet-toast-mobile-offset,var(--sonner-mobile-offset,16px));right:var(--scrollsheet-toast-mobile-offset,var(--sonner-mobile-offset,16px));transform:none}}`, "data-sonner-toast-styles");
function injectToastStyles(nonce) {
injectDocument(nonce);
}
function injectToastStylesInto(root, nonce) {
injectShadowRoot(root, nonce);
}
//#endregion
//#region packages/scrollsheet/src/toast/toaster.tsx
function resolveVisibleToasts(count) {
if (count === void 0) return 3;
return Math.max(1, Math.floor(count));
}
function useMountedFlag() {
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => {
if (prefersReducedMotion()) {
setMounted(true);
return;
}
let raf2 = 0;
const raf1 = requestAnimationFrame(() => {
raf2 = requestAnimationFrame(() => setMounted(true));
});
return () => {
cancelAnimationFrame(raf1);
cancelAnimationFrame(raf2);
};
}, []);
return mounted;
}
function useIsDocumentHidden() {
const [hidden, setHidden] = React.useState(() => typeof document !== "undefined" && document.hidden);
React.useEffect(() => {
const onVisibilityChange = () => setHidden(document.hidden);
document.addEventListener("visibilitychange", onVisibilityChange);
return () => document.removeEventListener("visibilitychange", onVisibilityChange);
}, []);
return hidden;
}
const EMPTY_RECORDS = [];
const EMPTY_MAP = new Map();
function toIdMap(records) {
return new Map(records.map((r) => [r.id, r]));
}
function sameIds(a, b) {
if (a.size !== b.size) return false;
for (const id of a.keys()) if (!b.has(id)) return false;
return true;
}
function useToastExit(live, queued = EMPTY_RECORDS, exitMs = 220) {
const [exiting, setExiting] = React.useState(() => EMPTY_MAP);
const [prevLive, setPrevLive] = React.useState(() => toIdMap(live));
const timersRef = React.useRef(new Map());
const liveIds = new Set(live.map((r) => r.id));
let renderExiting = exiting;
if (!sameIds(prevLive, liveIds)) {
const queuedIds = new Set(queued.map((r) => r.id));
const justRemoved = [];
for (const [id, record] of prevLive) {
if (liveIds.has(id)) continue;
if (queuedIds.has(id)) continue;
justRemoved.push(record);
}
setPrevLive(toIdMap(live));
if (justRemoved.length > 0) {
const next = new Map(exiting);
for (const record of justRemoved) next.set(record.id, record);
renderExiting = next;
setExiting(next);
}
}
React.useEffect(() => {
const reduced = prefersReducedMotion();
for (const [id] of exiting) {
if (timersRef.current.has(id)) continue;
const timer = setTimeout(() => {
timersRef.current.delete(id);
setExiting((prev) => {
if (!prev.has(id)) return prev;
const next = new Map(prev);
next.delete(id);
return next;
});
}, reduced ? 0 : exitMs);
timersRef.current.set(id, timer);
}
for (const [id, timer] of timersRef.current) if (!exiting.has(id)) {
clearTimeout(timer);
timersRef.current.delete(id);
}
}, [exiting, exitMs]);
React.useEffect(() => {
const timers = timersRef.current;
return () => {
for (const timer of timers.values()) clearTimeout(timer);
timers.clear();
};
}, []);
const rows = live.map((record) => ({
record,
exiting: false
}));
for (const [id, record] of renderExiting) if (!liveIds.has(id)) rows.push({
record,
exiting: true
});
return rows;
}
//#endregion
//#region packages/scrollsheet/src/toast/shell/shell-selectors.ts
function splitPosition(position) {
const [y, x] = position.split("-");
return {
y,
x
};
}
function computePossiblePositions(defaultPosition, toasts) {
const seen = new Set([defaultPosition]);
for (const t of toasts) if (t.position) seen.add(t.position);
return [...seen];
}
function computeRowOffsets(newestFirst, heights, gap) {
let cumulativeHeight = 0;
return newestFirst.map((record, index) => {
const stackOffset = index * gap + cumulativeHeight;
cumulativeHeight += heights.get(record.id) ?? 0;
return {
id: record.id,
index,
toastsBefore: index,
stackOffset
};
});
}
function findNewestDismissible(toasts) {
for (let i = toasts.length - 1; i >= 0; i -= 1) {
const t = toasts[i];
if (t && t.dismissible !== false) return t;
}
}
const SWIPE_VELOCITY_THRESHOLD = .11;
function getDefaultSwipeDirections(position) {
const { y, x } = splitPosition(position);
const directions = [y];
if (x === "left" || x === "right") directions.push(x);
return directions;
}
function lockSwipeAxis(dx, dy) {
if (Math.abs(dx) <= 1 && Math.abs(dy) <= 1) return null;
return Math.abs(dx) > Math.abs(dy) ? "x" : "y";
}
function dampenSwipeDelta(delta) {
const dampened = delta * (1 / (1.5 + Math.abs(delta) / 20));
return Math.abs(dampened) < Math.abs(delta) ? dampened : delta;
}
function computeSwipeAxisAmount(axis, delta, directions) {
const negativeDir = axis === "x" ? "left" : "top";
const positiveDir = axis === "x" ? "right" : "bottom";
if (!(directions.includes(negativeDir) || directions.includes(positiveDir))) return 0;
return directions.includes(negativeDir) && delta < 0 || directions.includes(positiveDir) && delta > 0 ? delta : dampenSwipeDelta(delta);
}
function isSwipeReleaseAllowed(axis, amount, directions) {
if (axis === "x") return directions.includes(amount > 0 ? "right" : "left");
return directions.includes(amount > 0 ? "bottom" : "top");
}
function shouldDismissOnSwipeRelease(amount, velocity, thresholdPx = 45, velocityThreshold = SWIPE_VELOCITY_THRESHOLD) {
return Math.abs(amount) >= thresholdPx || velocity > velocityThreshold;
}
function resolveSwipeOutDirection(axis, amount) {
if (axis === "x") return amount > 0 ? "right" : "left";
return amount > 0 ? "down" : "up";
}
//#endregion
//#region packages/scrollsheet/src/toast/shell/use-toast-swipe.ts
const ZERO_AMOUNT = {
x: 0,
y: 0
};
function useToastSwipe(elRef, enabled, directions, onSwipeDismiss) {
const draggingRef = React.useRef(false);
const axisRef = React.useRef(null);
const startRef = React.useRef(ZERO_AMOUNT);
const amountRef = React.useRef(ZERO_AMOUNT);
const dragStartRef = React.useRef(0);
const onSwipeDismissRef = React.useRef(onSwipeDismiss);
onSwipeDismissRef.current = onSwipeDismiss;
const onPointerDown = React.useCallback((event) => {
if (!enabled || event.button !== 0) return;
if (event.target.tagName === "BUTTON") return;
const el = elRef.current;
if (!el) return;
draggingRef.current = true;
axisRef.current = null;
amountRef.current = ZERO_AMOUNT;
startRef.current = {
x: event.clientX,
y: event.clientY
};
dragStartRef.current = Date.now();
el.setPointerCapture(event.pointerId);
el.setAttribute("data-swiping", "true");
}, [enabled, elRef]);
const onPointerMove = React.useCallback((event) => {
if (!draggingRef.current) return;
const el = elRef.current;
if (!el) return;
if ((window.getSelection?.()?.toString().length ?? 0) > 0) return;
const start = startRef.current;
const dx = event.clientX - start.x;
const dy = event.clientY - start.y;
if (axisRef.current === null) axisRef.current = lockSwipeAxis(dx, dy);
const axis = axisRef.current;
if (axis === null) return;
const resolved = computeSwipeAxisAmount(axis, axis === "x" ? dx : dy, directions);
const amount = axis === "x" ? {
x: resolved,
y: 0
} : {
x: 0,
y: resolved
};
amountRef.current = amount;
if (resolved !== 0) el.setAttribute("data-swiped", "true");
el.style.setProperty("--scrollsheet-toast-swipe-x", `${amount.x}px`);
el.style.setProperty("--scrollsheet-toast-swipe-y", `${amount.y}px`);
}, [elRef, directions]);
const release = React.useCallback(() => {
if (!draggingRef.current) return;
draggingRef.current = false;
const el = elRef.current;
const axis = axisRef.current;
axisRef.current = null;
if (!el) return;
if (axis === null) {
el.setAttribute("data-swiping", "false");
return;
}
const amount = axis === "x" ? amountRef.current.x : amountRef.current.y;
const elapsed = Math.max(1, Date.now() - dragStartRef.current);
const velocity = Math.abs(amount) / elapsed;
if (isSwipeReleaseAllowed(axis, amount, directions) && shouldDismissOnSwipeRelease(amount, velocity)) {
const direction = resolveSwipeOutDirection(axis, amount);
el.setAttribute("data-swipe-direction", direction);
if (prefersReducedMotion()) {
el.setAttribute("data-swipe-out", "true");
onSwipeDismissRef.current();
return;
}
const onAnimEnd = () => {
el.removeEventListener("animationend", onAnimEnd);
onSwipeDismissRef.current();
};
el.addEventListener("animationend", onAnimEnd);
el.setAttribute("data-swipe-out", "true");
return;
}
el.setAttribute("data-swiping", "false");
el.setAttribute("data-swiped", "false");
el.style.setProperty("--scrollsheet-toast-swipe-x", "0px");
el.style.setProperty("--scrollsheet-toast-swipe-y", "0px");
}, [elRef, directions]);
return {
onPointerDown,
onPointerMove,
onPointerUp: React.useCallback((event) => {
if (elRef.current?.hasPointerCapture(event.pointerId)) elRef.current.releasePointerCapture(event.pointerId);
release();
}, [elRef, release]),
onPointerCancel: React.useCallback(() => release(), [release])
};
}
//#endregion
//#region packages/scrollsheet/src/toast/shell/toast-row.tsx
function ToastIcon({ type, icons, spinnerClassName }) {
if (type === "loading") {
if (icons?.loading) return jsx(Fragment, { children: icons.loading });
return jsx("span", { className: spinnerClassName });
}
switch (type) {
case "success": return jsx(Fragment, { children: icons?.success ?? "✓" });
case "error": return jsx(Fragment, { children: icons?.error ?? "✕" });
case "warning": return jsx(Fragment, { children: icons?.warning ?? "!" });
case "info": return jsx(Fragment, { children: icons?.info ?? "i" });
default: return null;
}
}
function ToastRow({ record, index, total, toastsBefore, stackOffset, height, frontHeight, visible, expanded, removed, yPosition, xPosition, closeButton, directions, onDismiss, observe, toasterClassNames, icons, toasterCloseButtonAriaLabel, toasterStyle, sonnerCompat }) {
const role = record.type === "error" ? "alert" : "status";
const mounted = useMountedFlag();
const elRef = React.useRef(null);
const dismissible = record.dismissible !== false;
const isFront = index === 0;
const setRefs = React.useCallback((el) => {
elRef.current = el;
observe(record.id, el);
}, [observe, record.id]);
const swipeHandlers = useToastSwipe(elRef, !removed && dismissible && record.type !== "loading", directions, () => onDismiss(record));
React.useEffect(() => {
const el = elRef.current;
if (el) el.inert = removed;
}, [removed]);
const style = {
"--scrollsheet-toast-toasts-before": toastsBefore,
"--scrollsheet-toast-stack-offset": `${stackOffset}px`,
"--scrollsheet-toast-front-height": frontHeight !== void 0 ? `${frontHeight}px` : "0px",
"--scrollsheet-toast-initial-height": height !== void 0 ? `${height}px` : "auto",
zIndex: Math.max(0, total - index),
...toasterStyle,
...record.style
};
const legacy = (name) => sonnerCompat ? name : void 0;
const rootClassName = cn("scrollsheet-toast", legacy("sonner-toast"), record.className, toasterClassNames?.toast, record.classNames?.toast, toasterClassNames?.default, toasterClassNames?.[record.type], record.classNames?.[record.type]);
const motionAttrs = {
"data-mounted": mounted ? "" : void 0,
"data-removed": removed ? "" : void 0,
"data-visible": visible ? "true" : "false",
"data-front": isFront ? "true" : "false",
"data-expanded": expanded ? "true" : "false",
"data-y-position": yPosition,
"data-x-position": xPosition,
"data-index": index,
"data-dismissible": dismissible ? "true" : "false",
"data-swiping": "false",
"data-swiped": "false",
"aria-hidden": removed ? "true" : void 0
};
if (record.jsx !== void 0) return jsx("div", {
ref: setRefs,
className: rootClassName,
"data-scrollsheet-toast": "",
"data-sonner-toast": sonnerCompat ? "" : void 0,
"data-scrollsheet-custom": "",
"data-sonner-custom": sonnerCompat ? "" : void 0,
"data-testid": record.testId,
role,
style,
...motionAttrs,
...swipeHandlers,
children: record.jsx
});
const showCloseButton = closeButton && dismissible && record.type !== "loading";
const hasActions = Boolean(record.action || record.cancel || showCloseButton);
const closeButtonAriaLabel = record.closeButtonAriaLabel ?? toasterCloseButtonAriaLabel ?? "Close toast";
return jsxs("div", {
ref: setRefs,
className: rootClassName,
"data-scrollsheet-toast": "",
"data-sonner-toast": sonnerCompat ? "" : void 0,
"data-type": record.type,
"data-testid": record.testId,
role,
style,
...motionAttrs,
...swipeHandlers,
children: [
jsx("span", {
className: cn("scrollsheet-toast-icon", legacy("sonner-toast-icon"), toasterClassNames?.icon, record.classNames?.icon),
"data-type": record.type,
"aria-hidden": "true",
children: record.icon ?? jsx(ToastIcon, {
type: record.type,
icons,
spinnerClassName: cn("scrollsheet-toast-spinner", legacy("sonner-toast-spinner"), toasterClassNames?.loader, record.classNames?.loader)
})
}),
jsxs("div", {
className: cn("scrollsheet-toast-body", legacy("sonner-toast-body"), toasterClassNames?.content, record.classNames?.content),
children: [record.title !== void 0 && jsx("div", {
className: cn("scrollsheet-toast-title", legacy("sonner-toast-title"), toasterClassNames?.title, record.classNames?.title),
children: record.title
}), record.description !== void 0 && jsx("div", {
className: cn("scrollsheet-toast-description", legacy("sonner-toast-description"), toasterClassNames?.description, record.classNames?.description),
children: record.description
})]
}),
hasActions && jsxs("div", {
className: cn("scrollsheet-toast-actions", legacy("sonner-toast-actions")),
children: [
record.cancel && jsx("button", {
type: "button",
className: cn("scrollsheet-toast-cancel", legacy("sonner-toast-cancel"), toasterClassNames?.cancelButton, record.classNames?.cancelButton),
onClick: (event) => {
if (!dismissible) return;
record.cancel?.onClick(event);
onDismiss(record);
},
children: record.cancel.label
}),
record.action && jsx("button", {
type: "button",
className: cn("scrollsheet-toast-action", legacy("sonner-toast-action"), toasterClassNames?.actionButton, record.classNames?.actionButton),
onClick: (event) => {
record.action?.onClick(event);
if (event.defaultPrevented) return;
onDismiss(record);
},
children: record.action.label
}),
showCloseButton && jsx("button", {
type: "button",
className: cn("scrollsheet-toast-close", legacy("sonner-toast-close"), toasterClassNames?.closeButton, record.classNames?.closeButton),
"aria-label": closeButtonAriaLabel,
onClick: () => onDismiss(record),
children: icons?.close ?? "×"
})
]
})
]
});
}
//#endregion
//#region packages/scrollsheet/src/toast/shell/use-toast-heights.ts
function useToastHeights() {
const [heights, setHeights] = React.useState(() => new Map());
const observersRef = React.useRef(new Map());
const setHeight = React.useCallback((id, height) => {
setHeights((prev) => prev.get(id) === height ? prev : new Map(prev).set(id, height));
}, []);
const forget = React.useCallback((id) => {
observersRef.current.get(id)?.disconnect();
observersRef.current.delete(id);
setHeights((prev) => {
if (!prev.has(id)) return prev;
const next = new Map(prev);
next.delete(id);
return next;
});
}, []);
const observe = React.useCallback((id, el) => {
const observers = observersRef.current;
observers.get(id)?.disconnect();
observers.delete(id);
if (!el || typeof ResizeObserver === "undefined") return;
const ro = new ResizeObserver(() => setHeight(id, el.getBoundingClientRect().height));
ro.observe(el);
observers.set(id, ro);
setHeight(id, el.getBoundingClientRect().height);
}, [setHeight]);
React.useEffect(() => {
const observers = observersRef.current;
return () => {
for (const ro of observers.values()) ro.disconnect();
observers.clear();
};
}, []);
return {
heights,
observe,
forget
};
}
//#endregion
//#region packages/scrollsheet/src/toast/shell/toaster-shell.tsx
const DEFAULT_HOTKEY = ["altKey", "KeyT"];
function PositionGroup({ position, defaultPosition, toasts, visibleToasts, gap, expanded, closeButton, swipeDirections, toastOptions, icons, className, baseStyle, sonnerCompat, registerListEl, dismissRow, onHoverEnter, onHoverLeave, onInteractingChange }) {
const directions = React.useMemo(() => swipeDirections ?? getDefaultSwipeDirections(position), [swipeDirections, position]);
const positionToasts = React.useMemo(() => selectPositionToasts(toasts, position, defaultPosition), [
toasts,
position,
defaultPosition
]);
const newestFirst = React.useMemo(() => [...positionToasts].reverse(), [positionToasts]);
const window_ = React.useMemo(() => selectToastWindow(positionToasts, visibleToasts), [positionToasts, visibleToasts]);
const visibleIds = React.useMemo(() => new Set(window_.visible.map((t) => t.id)), [window_]);
const { heights, observe, forget } = useToastHeights();
const exitRows = useToastExit(newestFirst);
const exitingIds = React.useMemo(() => new Set(exitRows.filter((r) => r.exiting).map((r) => r.record.id)), [exitRows]);
React.useEffect(() => {
for (const id of exitingIds) forget(id);
}, [exitingIds, forget]);
const offsets = React.useMemo(() => computeRowOffsets(newestFirst, heights, gap), [
newestFirst,
heights,
gap
]);
const offsetById = React.useMemo(() => new Map(offsets.map((o) => [o.id, o])), [offsets]);
const lastOffsetRef = React.useRef(new Map());
for (const o of offsets) lastOffsetRef.current.set(o.id, o);
if (lastOffsetRef.current.size > offsets.length) {
const keep = new Set([...offsets.map((o) => o.id), ...exitingIds]);
for (const id of [...lastOffsetRef.current.keys()]) if (!keep.has(id)) lastOffsetRef.current.delete(id);
}
const frontHeight = heights.get(newestFirst[0]?.id ?? "");
const { y, x } = splitPosition(position);
const listRef = React.useCallback((el) => registerListEl(position, el), [registerListEl, position]);
if (exitRows.length === 0) return null;
return jsx("ol", {
ref: listRef,
"data-scrollsheet-toaster": "",
"data-sonner-toaster": sonnerCompat ? "" : void 0,
"data-scrollsheet-theme": "light",
"data-sonner-theme": sonnerCompat ? "light" : void 0,
"data-y-position": y,
"data-x-position": x,
tabIndex: -1,
className,
style: {
...baseStyle,
"--scrollsheet-toast-gap": `${gap}px`,
"--scrollsheet-toast-front-height": frontHeight !== void 0 ? `${frontHeight}px` : void 0
},
onMouseEnter: onHoverEnter,
onMouseMove: onHoverEnter,
onMouseLeave: onHoverLeave,
onPointerDown: (event) => {
if (event.target.dataset.dismissible === "false") return;
onInteractingChange(true);
},
onPointerUp: () => onInteractingChange(false),
children: exitRows.map(({ record, exiting }) => {
const offset = offsetById.get(record.id) ?? lastOffsetRef.current.get(record.id);
const index = offset?.index ?? newestFirst.length;
return jsx(ToastRow, {
record,
index,
total: newestFirst.length,
toastsBefore: offset?.toastsBefore ?? index,
stackOffset: offset?.stackOffset ?? 0,
height: heights.get(record.id),
frontHeight,
visible: visibleIds.has(record.id),
expanded,
removed: exiting,
yPosition: y,
xPosition: x,
closeButton,
directions,
onDismiss: dismissRow,
observe,
toasterClassNames: toastOptions?.classNames,
sonnerCompat,
icons,
toasterCloseButtonAriaLabel: toastOptions?.closeButtonAriaLabel,
toasterStyle: toastOptions?.style
}, record.id);
})
});
}
function resolveShellPosition(position) {
return position ?? "bottom-right";
}
function ToasterShell({ id, toasterId, position, theme, richColors, expand: forceExpand, visibleToasts: visibleToastsProp, closeButton = false, duration: durationProp, gap = 14, offset, swipeDirections, toastOptions, icons, className, style, containerAriaLabel = "Notifications", hotkey = DEFAULT_HOTKEY, nonce, sonnerCompat = true }) {
const resolvedId = id ?? toasterId;
if (toasterId !== void 0) warnOnce("toaster-id-deprecated", "[scrollsheet Toaster] Toaster's \"toasterId\" prop is deprecated — use \"id\" instead.");
const { toasts: allToasts } = useSonner();
const toasts = React.useMemo(() => selectToasterToasts(allToasts, resolvedId), [allToasts, resolvedId]);
const defaultPosition = resolveShellPosition(position);
const possiblePositions = React.useMemo(() => computePossiblePositions(defaultPosition, toasts), [defaultPosition, toasts]);
const visibleToasts = resolveVisibleToasts(visibleToastsProp);
const defaultDuration = durationProp ?? toastOptions?.duration ?? 4e3;
if (theme !== void 0 && theme !== "light") warnOnce("shell-theme", `[scrollsheet Toaster] theme="${theme}" isn't implemented yet — v1 always renders the static light card real Sonner ships by default.`);
if (richColors) warnOnce("shell-rich-colors", "[scrollsheet Toaster] richColors isn't implemented yet — toasts render with their default icon color only.");
React.useEffect(() => {
injectToastStyles(nonce);
}, [nonce]);
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => setMounted(true), []);
const [hovered, setHovered] = React.useState(false);
const [hotkeyExpanded, setHotkeyExpanded] = React.useState(false);
const [interacting, setInteracting] = React.useState(false);
const expanded = Boolean(forceExpand) || hovered || hotkeyExpanded;
const isDocumentHidden = useIsDocumentHidden();
const handleHoverEnter = React.useCallback(() => setHovered(true), []);
const handleHoverLeave = React.useCallback(() => {
if (!interacting) setHovered(false);
}, [interacting]);
const dismissRow = React.useCallback((record) => {
toast.dismiss(record.id);
}, []);
const listElsRef = React.useRef(new Map());
const registerListEl = React.useCallback((pos, el) => {
if (el) listElsRef.current.set(pos, el);
else listElsRef.current.delete(pos);
}, []);
React.useEffect(() => {
if (hotkey.length === 0) return;
const handleKeyDown = (event) => {
if (hotkey.every((key) => event[key] || event.code === key)) {
setHotkeyExpanded(true);
const [firstList] = listElsRef.current.values();
firstList?.focus({ preventScroll: true });
}
if (event.code !== "Escape") return;
const active = document.activeElement;
const focusedEntry = [...listElsRef.current.entries()].find(([, el]) => active === el || el.contains(active));
if (!focusedEntry) return;
if (hotkeyExpanded) {
setHotkeyExpanded(false);
return;
}
const [focusedPosition] = focusedEntry;
const positionToasts = selectPositionToasts(toasts, focusedPosition, defaultPosition);
const front = positionToasts[positionToasts.length - 1];
if (front) dismissRow(front);
};
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, [
hotkey,
hotkeyExpanded,
toasts,
defaultPosition,
dismissRow
]);
const timersRef = React.useRef(new Map());
React.useEffect(() => {
const timers = timersRef.current;
const liveIds = new Set(toasts.map((t) => t.id));
for (const [id, entry] of timers) {
if (liveIds.has(id)) continue;
if (entry.timer) clearTimeout(entry.timer);
timers.delete(id);
}
const paused = expanded || interacting || isDocumentHidden;
for (const t of toasts) {
if (t.type === "loading") {
const stale = timers.get(t.id);
if (stale) {
if (stale.timer) clearTimeout(stale.timer);
timers.delete(t.id);
}
continue;
}
const ms = t.duration ?? toastOptions?.duration ?? defaultDuration;
const existing = timers.get(t.id);
if (!existing || existing.record !== t) {
if (existing?.timer) clearTimeout(existing.timer);
const entry = {
timer: null,
startedAt: 0,
remaining: ms,
record: t
};
timers.set(t.id, entry);
if (Number.isFinite(ms) && !paused) {
entry.startedAt = Date.now();
entry.timer = setTimeout(() => {
timersRef.current.delete(t.id);
expire(t.id);
}, ms);
}
continue;
}
if (paused) {
if (existing.timer) {
clearTimeout(existing.timer);
const elapsed = Date.now() - existing.startedAt;
existing.remaining = Math.max(0, existing.remaining - elapsed);
existing.timer = null;
}
continue;
}
if (existing.timer || !Number.isFinite(existing.remaining)) continue;
existing.startedAt = Date.now();
existing.timer = setTimeout(() => {
timersRef.current.delete(t.id);
expire(t.id);
}, existing.remaining);
}
}, [
toasts,
expanded,
interacting,
isDocumentHidden,
defaultDuration,
toastOptions?.duration
]);
React.useEffect(() => {
const timers = timersRef.current;
return () => {
for (const entry of timers.values()) if (entry.timer) clearTimeout(entry.timer);
timers.clear();
};
}, []);
useCloseWatcher({
present: toasts.length > 0,
nonModal: true,
escapeDismissible: true,
onClose: () => {
const target = findNewestDismissible(toasts);
if (target) dismissRow(target);
}
});
if (!mounted || typeof document === "undefined") return null;
const offsetValue = offset !== void 0 ? typeof offset === "number" ? `${offset}px` : offset : void 0;
const baseStyle = {
...style,
"--scrollsheet-toast-offset": offsetValue
};
return createPortal(jsx("section", {
"aria-label": containerAriaLabel,
tabIndex: -1,
"aria-live": "polite",
"aria-relevant": "additions text",
"aria-atomic": "false",
suppressHydrationWarning: true,
"data-react-aria-top-layer": "",
children: possiblePositions.map((groupPosition) => jsx(PositionGroup, {
position: groupPosition,
defaultPosition,
toasts,
visibleToasts,
gap,
expanded,
closeButton,
swipeDirections,
toastOptions,
icons,
className,
baseStyle,
sonnerCompat,
registerListEl,
dismissRow,
onHoverEnter: handleHoverEnter,
onHoverLeave: handleHoverLeave,
onInteractingChange: setInteracting
}, groupPosition))
}), document.body);
}
//#endregion
export { useSonner as a, toast as i, resolveVisibleToasts as n, useToasts as o, injectToastStylesInto as r, ToasterShell as t };
"use client";
import * as React from "react";
//#region packages/scrollsheet/src/internal/dev-warn.ts
const warned = new Set();
function warnOnce(key, message) {
if (warned.has(key)) return;
warned.add(key);
console.warn(message);
}
//#endregion
//#region packages/scrollsheet/src/internal/env.ts
let cached = null;
function env() {
if (cached) return cached;
const hasCSS = typeof CSS !== "undefined" && typeof CSS.supports === "function";
cached = {
scrollTimeline: hasCSS && CSS.supports("animation-timeline: scroll()"),
scrollend: typeof window !== "undefined" && "onscrollend" in window,
linearEasing: hasCSS && CSS.supports("transition-timing-function", "linear(0, 1)")
};
return cached;
}
function hasDialogSupport() {
return typeof HTMLDialogElement !== "undefined" && "showModal" in HTMLDialogElement.prototype;
}
function hasPopoverSupport() {
return typeof HTMLElement !== "undefined" && "showPopover" in HTMLElement.prototype;
}
function hasClosedBySupport() {
return typeof HTMLDialogElement !== "undefined" && "closedBy" in HTMLDialogElement.prototype;
}
function hasCloseWatcherSupport() {
return typeof window !== "undefined" && "CloseWatcher" in window;
}
let warnedNoDialogSupport = false;
function warnMissingDialogSupport() {
if (warnedNoDialogSupport) return;
warnedNoDialogSupport = true;
console.warn("scrollsheet: this browser has no <dialog> support — falling back to a plain modal (no detents, drag, or animation). Content and dismissal work as normal.");
}
let warnedUndimmedDetentOutOfRange = false;
function warnLargestUndimmedDetentOutOfRange() {
if (warnedUndimmedDetentOutOfRange) return;
warnedUndimmedDetentOutOfRange = true;
console.warn("scrollsheet: largestUndimmedDetent resolved to a height above every configured detent — the backdrop and themeColorDimming will stay undimmed for nearly all of the sheet's travel. Choose a largestUndimmedDetent within the configured detents range.");
}
let warnedUnresolvableSnapToDetent = false;
function warnUnresolvableSnapToDetent() {
if (warnedUnresolvableSnapToDetent) return;
warnedUnresolvableSnapToDetent = true;
console.warn("scrollsheet: actionsRef.snapTo() was called with a detent spec that isn't in this sheet's `detents` list — the panel will rest at the nearest configured detent, but `activeDetent`/`onActiveDetentChange` (and Handle's aria-valuenow/aria-valuetext) will still reflect the literal spec you passed in, not the resolved one.");
}
let warnedContentAsChildInvalidChild = false;
function warnContentAsChildInvalidChild() {
if (warnedContentAsChildInvalidChild) return;
warnedContentAsChildInvalidChild = true;
console.warn("scrollsheet: Sheet.Content asChild expects children to be a single non-Fragment React element — falling back to the default panel <div> (asChild ignored) instead.");
}
function warnCoreStylesMissing() {
warnOnce("css", "scrollsheet: import 'scrollsheet/styles.css'");
}
function prefersReducedMotion() {
return typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
}
//#endregion
//#region packages/scrollsheet/src/internal/inject-styles.ts
function createStyleInjector(css, dataAttr) {
const injectedRoots = new WeakSet();
function injectInto(root, nonce) {
if (!css) return;
if (injectedRoots.has(root)) return;
const existing = root === document ? document.querySelector(`style[${dataAttr}]`) : root.querySelector(`style[${dataAttr}]`);
const marked = root;
if (existing || marked[Symbol.for(dataAttr)]) {
injectedRoots.add(root);
return;
}
const style = document.createElement("style");
style.setAttribute(dataAttr, "");
if (nonce) style.nonce = nonce;
style.textContent = css;
(root === document ? document.head : root).appendChild(style);
marked[Symbol.for(dataAttr)] = true;
injectedRoots.add(root);
}
function injectDocument(nonce) {
if (typeof document === "undefined") return;
injectInto(document, nonce);
}
function injectShadowRoot(root, nonce) {
if (!css) return;
if (typeof CSSStyleSheet === "undefined" || !("adoptedStyleSheets" in root)) {
injectInto(root, nonce);
return;
}
if (injectedRoots.has(root)) return;
const marker = Symbol.for(dataAttr);
const marked = root;
if (!marked[marker] && !root.querySelector(`style[${dataAttr}]`)) {
const sheet = new CSSStyleSheet();
sheet.replaceSync(css);
root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];
}
marked[marker] = true;
injectedRoots.add(root);
}
return {
injectDocument,
injectShadowRoot
};
}
//#endregion
//#region packages/scrollsheet/src/internal/use-close-watcher.ts
function useCloseWatcher({ present, nonModal, escapeDismissible, onClose }) {
const onCloseRef = React.useRef(onClose);
React.useInsertionEffect(() => {
onCloseRef.current = onClose;
});
React.useEffect(() => {
if (!present || !nonModal || !escapeDismissible) return;
if (!hasCloseWatcherSupport()) return;
if (typeof window.matchMedia !== "function" || !window.matchMedia("(hover: none) and (pointer: coarse)").matches) return;
const Ctor = window.CloseWatcher;
const watcher = new Ctor();
watcher.onclose = () => onCloseRef.current();
return () => watcher.destroy();
}, [
present,
nonModal,
escapeDismissible
]);
}
//#endregion
//#region packages/scrollsheet/src/internal/cn.ts
function cn(...classes) {
const joined = classes.filter(Boolean).join(" ");
return joined.length > 0 ? joined : void 0;
}
//#endregion
export { hasClosedBySupport as a, prefersReducedMotion as c, warnLargestUndimmedDetentOutOfRange as d, warnMissingDialogSupport as f, env as i, warnContentAsChildInvalidChild as l, warnOnce as m, useCloseWatcher as n, hasDialogSupport as o, warnUnresolvableSnapToDetent as p, createStyleInjector as r, hasPopoverSupport as s, cn as t, warnCoreStylesMissing as u };
"use client";
import { i as Content$1, l as Root$1, n as Description, o as Trigger, r as Title, s as Slot, t as Close, u as useSheetContext } from "./misc-BSxJRAef.mjs";
import { m as warnOnce, t as cn } from "./cn-eC4hyhVS.mjs";
import * as React from "react";
import { Fragment, jsx } from "react/jsx-runtime";
import { createPortal } from "react-dom";
//#region packages/scrollsheet/src/handle.tsx
function detentLabel(spec) {
if (spec === "full") return "Full";
if (spec === "medium") return "Half";
if (spec === "content") return "Fit content";
if (typeof spec === "number") return `${Math.round(spec * 100)}%`;
return spec ?? "";
}
const Handle$1 = React.forwardRef(function Handle({ asChild, onClick, onKeyDown, className, variant = "inside", ...props }, ref) {
const ctx = useSheetContext("Handle");
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => setMounted(true), []);
const outside = variant === "outside" && ctx.side === "bottom" && !(mounted && !ctx.canvasEl);
const variantAttr = outside ? "outside" : variant === "floating" ? "floating" : void 0;
const specs = ctx.detents;
const horizontal = ctx.side === "left" || ctx.side === "right";
const multi = specs.length >= 2;
const activeIndex = specs.findIndex((s) => Object.is(s, ctx.activeDetent));
const move = (delta) => {
if (!multi) return false;
const index = specs.findIndex((s) => Object.is(s, ctx.activeDetent));
const next = specs[index + delta];
if (next === void 0) return false;
ctx.setActiveDetent(next);
return true;
};
const sliderAria = multi ? {
role: "slider",
"aria-orientation": horizontal ? "horizontal" : "vertical",
"aria-valuemin": 0,
"aria-valuemax": specs.length - 1,
...activeIndex >= 0 ? {
"aria-valuenow": activeIndex,
"aria-valuetext": detentLabel(specs[activeIndex])
} : {}
} : {};
const button = jsx(asChild ? Slot : "button", {
...asChild ? {} : { type: "button" },
"aria-label": horizontal ? "Adjust sheet width" : "Adjust sheet height",
...sliderAria,
...props,
ref,
className: cn("scrollsheet-handle", className),
"data-scrollsheet-handle": true,
"data-scrollsheet-handle-variant": variantAttr,
onClick: (event) => {
onClick?.(event);
if (event.defaultPrevented) return;
if (!multi) return;
const index = specs.findIndex((s) => Object.is(s, ctx.activeDetent));
const next = specs[(index + 1) % specs.length];
if (next !== void 0) ctx.setActiveDetent(next);
},
onKeyDown: (event) => {
onKeyDown?.(event);
if (event.defaultPrevented) return;
const expandKey = horizontal ? "ArrowRight" : "ArrowUp";
const collapseKey = horizontal ? "ArrowLeft" : "ArrowDown";
if (event.key === expandKey) {
event.preventDefault();
move(1);
} else if (event.key === collapseKey) {
event.preventDefault();
if (!move(-1) && ctx.dismissible) ctx.setOpen(false);
} else if (multi && event.key === "Home") {
event.preventDefault();
const first = specs[0];
if (first !== void 0) ctx.setActiveDetent(first);
} else if (multi && event.key === "End") {
event.preventDefault();
const last = specs[specs.length - 1];
if (last !== void 0) ctx.setActiveDetent(last);
}
}
});
if (outside) return ctx.canvasEl ? createPortal(button, ctx.canvasEl) : null;
return button;
});
//#endregion
//#region packages/scrollsheet/src/drawer/index.tsx
const wrapperClaims = new Map();
const VAUL_CLOSE_THRESHOLD = .25;
const collectIgnored = (entries) => entries.filter(([, v]) => v !== void 0).map(([k]) => k);
function toDetentSpec(point) {
if (typeof point === "number") return point;
if (point === "fit-content" || point === "content") return "content";
if (/^\d+(\.\d+)?px$/.test(point)) return `${Number.parseFloat(point)}px`;
const parsed = Number.parseFloat(point);
return Number.isFinite(parsed) ? parsed : "content";
}
function resolveFadeFromIndex(snapPoints, fadeFromIndex) {
if (!snapPoints || snapPoints.length === 0) return void 0;
const point = snapPoints[fadeFromIndex ?? snapPoints.length - 1];
return point !== void 0 ? toDetentSpec(point) : void 0;
}
function resolveCloseThreshold(snapPoints, closeThreshold) {
if (snapPoints !== void 0 && snapPoints.length > 0) return void 0;
return Math.min(1, Math.max(0, 1 - (closeThreshold ?? VAUL_CLOSE_THRESHOLD)));
}
function composeOpenChange(onOpenChange, onClose) {
if (!onOpenChange && !onClose) return void 0;
return (next) => {
if (!next) onClose?.();
onOpenChange?.(next);
};
}
function Root(props) {
const { children, open, defaultOpen, onOpenChange, onClose, dismissible, snapPoints, activeSnapPoint, setActiveSnapPoint, onAnimationEnd, autoFocus, nested, direction, modal, shouldScaleBackground, setBackgroundColorOnScale, noBodyStyles, disablePreventScroll, preventScrollRestoration, repositionInputs, scrollLockTimeout, closeThreshold, fadeFromIndex, snapToSequentialPoint, handleOnly, onDrag, onRelease, container, fixed, actionsRef, backdropDismissible, escapeDismissible, keyboardExpands, onTravel, scrollbar } = props;
{
const ignored = collectIgnored([
["setBackgroundColorOnScale", setBackgroundColorOnScale],
["noBodyStyles", noBodyStyles],
["disablePreventScroll", disablePreventScroll],
["preventScrollRestoration", preventScrollRestoration],
["repositionInputs", repositionInputs],
["scrollLockTimeout", scrollLockTimeout],
["onDrag", onDrag],
["container", container],
["fixed", fixed],
["autoFocus", autoFocus],
["nested", nested]
]);
if (ignored.length > 0) warnOnce("root-ignored-props", `[scrollsheet Drawer] These <Drawer.Root> props from vaul have no effect in this compat layer and are ignored: ${ignored.join(", ")}. See the vaul migration notes in the README.${onDrag !== void 0 ? " onDrag: the native onTravel prop works on this same <Drawer.Root>." : ""}`);
}
const detents = snapPoints && snapPoints.length > 0 ? snapPoints.map(toDetentSpec) : void 0;
const activeDetent = activeSnapPoint != null ? toDetentSpec(activeSnapPoint) : void 0;
const largestUndimmedDetent = resolveFadeFromIndex(snapPoints, fadeFromIndex);
const resolvedCloseThreshold = resolveCloseThreshold(snapPoints, closeThreshold);
if (snapPoints !== void 0 && snapPoints.length > 0 && closeThreshold !== void 0) warnOnce("close-threshold-with-snap-points", "[scrollsheet Drawer] <Drawer.Root closeThreshold> has no effect when snapPoints is set — this matches real vaul, where closeThreshold is dead code once snapPoints exist (its onRelease returns through the snap-points branch before ever reading it). Use the native Sheet.Root `closeThreshold` (a live 0-1 fraction of the first detent, works alongside detents) if you want the upgrade.");
const handleRelease = onRelease ? (event, willRemainOpen) => onRelease(event, willRemainOpen) : void 0;
const handleOpenChange = composeOpenChange(onOpenChange, onClose);
const handleActiveDetentChange = (detent) => {
if (!setActiveSnapPoint) return;
const match = snapPoints?.find((point) => Object.is(toDetentSpec(point), detent));
setActiveSnapPoint(match ?? detent);
};
React.useEffect(() => {
if (!shouldScaleBackground) return;
const existing = document.querySelector("[data-scrollsheet-background]");
if (existing && !wrapperClaims.has(existing)) return;
const wrapper = existing ?? document.querySelector("[data-vaul-drawer-wrapper]");
if (!wrapper) return;
const count = wrapperClaims.get(wrapper) ?? 0;
wrapperClaims.set(wrapper, count + 1);
if (count === 0) wrapper.setAttribute("data-scrollsheet-background", "");
return () => {
const remaining = (wrapperClaims.get(wrapper) ?? 1) - 1;
if (remaining <= 0) {
wrapperClaims.delete(wrapper);
wrapper.removeAttribute("data-scrollsheet-background");
} else wrapperClaims.set(wrapper, remaining);
};
}, [shouldScaleBackground]);
return jsx(DirectionContext.Provider, {
value: direction ?? "bottom",
children: jsx(Root$1, {
open,
defaultOpen,
onOpenChange: handleOpenChange,
onOpenChangeComplete: onAnimationEnd,
dismissible,
detents,
activeDetent,
onActiveDetentChange: setActiveSnapPoint ? handleActiveDetentChange : void 0,
side: direction,
modal,
backgroundEffect: shouldScaleBackground ? "scale" : "none",
largestUndimmedDetent,
handleOnly,
sequentialDetents: snapToSequentialPoint,
closeThreshold: resolvedCloseThreshold,
onRelease: handleRelease,
actionsRef,
backdropDismissible,
escapeDismissible,
keyboardExpands,
onTravel,
scrollbar,
children
})
});
}
const DirectionContext = React.createContext("bottom");
const NestedRoot = Root;
function Portal({ children, container }) {
if (container !== void 0) warnOnce("portal-container", "[scrollsheet Drawer] <Drawer.Portal container> has no effect — scrollsheet's <dialog> always renders into the browser's top layer (effectively document.body), so there's no separate container to portal into.");
return jsx(Fragment, { children });
}
function Overlay(_props) {
return null;
}
function hasMultipleSnapPoints(detents) {
return detents.length > 1;
}
const Content = React.forwardRef(function Content({ onPointerDownOutside, onOpenAutoFocus, onEscapeKeyDown, onCloseAutoFocus, onInteractOutside, onFocusOutside, forceMount, ...props }, ref) {
const ctx = useSheetContext("Content");
const direction = React.useContext(DirectionContext);
const snapPointsActive = hasMultipleSnapPoints(ctx.detents);
{
const ignored = collectIgnored([
["onPointerDownOutside", onPointerDownOutside],
["onOpenAutoFocus", onOpenAutoFocus],
["onEscapeKeyDown", onEscapeKeyDown],
["onCloseAutoFocus", onCloseAutoFocus],
["onInteractOutside", onInteractOutside],
["onFocusOutside", onFocusOutside],
["forceMount", forceMount]
]);
if (ignored.length > 0) warnOnce("content-ignored-props", `[scrollsheet Drawer] These <Drawer.Content> props from vaul have no effect in this compat layer and are stripped before reaching the DOM: ${ignored.join(", ")}. See the vaul migration notes in the README.${onPointerDownOutside !== void 0 || onInteractOutside !== void 0 || onEscapeKeyDown !== void 0 ? " To block backdrop-tap dismissal, set backdropDismissible={false} on <Drawer.Root>; to block Esc, escapeDismissible={false}." : ""}`);
}
return jsx(Content$1, {
...props,
ref,
"data-vaul-drawer": "",
"data-vaul-drawer-direction": direction,
"data-vaul-snap-points": snapPointsActive ? "true" : "false"
});
});
function composeHandleClick(preventCycle, onClick) {
return (event) => {
onClick?.(event);
if (preventCycle) event.preventDefault();
};
}
const Handle = React.forwardRef(function Handle({ preventCycle, onClick, ...props }, ref) {
const ctx = useSheetContext("Handle");
return jsx(Handle$1, {
...props,
ref,
"data-vaul-handle": "",
"data-vaul-drawer-visible": ctx.open ? "true" : "false",
onClick: composeHandleClick(preventCycle, onClick)
});
});
const DrawerClose = React.forwardRef(function DrawerClose({ children, ...props }, ref) {
return jsx(Close, {
...props,
ref,
children: children ?? null
});
});
const Drawer = {
Root,
NestedRoot,
Trigger,
Portal,
Overlay,
Content,
Close: DrawerClose,
Title,
Description,
Handle
};
//#endregion
export { NestedRoot as a, Root as c, hasMultipleSnapPoints as d, resolveCloseThreshold as f, Handle as i, composeHandleClick as l, Handle$1 as m, Drawer as n, Overlay as o, resolveFadeFromIndex as p, DrawerClose as r, Portal as s, Content as t, composeOpenChange as u };

Sorry, the diff of this file is too big to display

"use client";
import { c as prefersReducedMotion, m as warnOnce, n as useCloseWatcher, r as createStyleInjector, t as cn } from "./cn-eC4hyhVS.mjs";
import * as React from "react";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
import { createPortal } from "react-dom";
//#region packages/scrollsheet/src/toast/state.ts
let toasts = [];
const listeners = new Set();
let uid = 0;
const MAX_HISTORY_SIZE = 100;
let history = [];
function trimHistory() {
let toRemove = history.length - MAX_HISTORY_SIZE;
if (toRemove <= 0) return;
const liveIds = new Set(toasts.map((t) => t.id));
history = history.filter((record) => {
if (toRemove > 0 && !liveIds.has(record.id)) {
toRemove -= 1;
return false;
}
return true;
});
}
function recordHistory(record) {
const index = history.findIndex((t) => t.id === record.id);
if (index === -1) {
history = [...history, record];
trimHistory();
return;
}
history = history.map((t, i) => i === index ? record : t);
}
function nextId() {
uid += 1;
return uid;
}
function publish() {
for (const listener of listeners) listener();
}
function subscribe(listener) {
listeners.add(listener);
return () => listeners.delete(listener);
}
function getSnapshot() {
return toasts;
}
const EMPTY_TOASTS = [];
function getServerSnapshot() {
return EMPTY_TOASTS;
}
function upsert(id, patch, fallbackType) {
const resolvedId = id ?? nextId();
const index = toasts.findIndex((t) => t.id === resolvedId);
const type = patch.type ?? fallbackType;
let record;
if (index === -1) {
record = {
dismissible: true,
...patch,
id: resolvedId,
type,
createdAt: Date.now()
};
toasts = [...toasts, record];
} else {
record = {
...toasts[index],
...patch,
id: resolvedId,
type
};
toasts = toasts.map((t, i) => i === index ? record : t);
}
recordHistory(record);
publish();
return resolvedId;
}
function dismiss(id) {
if (id === void 0) {
const swept = new Set(toasts);
for (const t of swept) {
if (!toasts.includes(t)) continue;
t.onDismiss?.(t);
}
toasts = toasts.filter((t) => !swept.has(t));
publish();
return;
}
const existing = toasts.find((t) => t.id === id);
if (!existing) return id;
existing.onDismiss?.(existing);
toasts = toasts.filter((t) => t !== existing);
publish();
return id;
}
function expire(id) {
const existing = toasts.find((t) => t.id === id);
if (!existing) return;
existing.onAutoClose?.(existing);
toasts = toasts.filter((t) => t !== existing);
publish();
}
function baseToast(message, data) {
return upsert(data?.id, {
...data,
title: message
}, "default");
}
function withType(type) {
return (message, data) => upsert(data?.id, {
...data,
title: message
}, type);
}
const toastImpl = baseToast;
toastImpl.success = withType("success");
toastImpl.error = withType("error");
toastImpl.info = withType("info");
toastImpl.warning = withType("warning");
toastImpl.loading = withType("loading");
toastImpl.message = withType("default");
toastImpl.custom = (jsx, data) => {
const id = data?.id ?? nextId();
return upsert(id, {
...data,
jsx: jsx(id)
}, "default");
};
function isHttpResponse(value) {
return typeof value === "object" && value !== null && "ok" in value && typeof value.ok === "boolean" && "status" in value && typeof value.status === "number";
}
function isPromiseExtendedResult(value) {
return typeof value === "object" && value !== null && !React.isValidElement(value);
}
function applyPromiseSettlement(id, resolved, fallbackTitle, description, type) {
if (isPromiseExtendedResult(resolved)) {
const { message, ...rest } = resolved;
upsert(id, {
description,
...rest,
title: message ?? fallbackTitle,
type
}, type);
return;
}
upsert(id, {
title: resolved ?? fallbackTitle,
description,
type
}, type);
}
function promiseImpl(promiseOrFn, data) {
const { loading, success, error, finally: onFinally, description, ...rest } = data;
const id = upsert(data.id, {
...rest,
description: typeof description === "function" ? void 0 : description,
title: loading,
type: "loading"
}, "loading");
const settle = typeof promiseOrFn === "function" ? promiseOrFn() : promiseOrFn;
let settled;
const chain = settle.then(async (result) => {
if (isHttpResponse(result) && !result.ok) {
settled = {
ok: false,
reason: result
};
const statusMessage = `HTTP error! status: ${result.status}`;
const resolvedDescription = typeof description === "function" ? await description(statusMessage) : description;
const resolved = typeof error === "function" ? await error(statusMessage) : error;
applyPromiseSettlement(id, resolved, "Error", resolvedDescription, "error");
return;
}
if (result instanceof Error) {
settled = {
ok: false,
reason: result
};
const resolvedDescription = typeof description === "function" ? await description(result) : description;
const resolved = typeof error === "function" ? await error(result) : error;
applyPromiseSettlement(id, resolved, "Error", resolvedDescription, "error");
return;
}
settled = {
ok: true,
value: result
};
const resolvedDescription = typeof description === "function" ? await description(result) : description;
const resolved = typeof success === "function" ? await success(result) : success;
applyPromiseSettlement(id, resolved, "Success", resolvedDescription, "success");
}).catch(async (err) => {
settled = {
ok: false,
reason: err
};
const resolvedDescription = typeof description === "function" ? await description(err) : description;
const resolved = typeof error === "function" ? await error(err) : error;
applyPromiseSettlement(id, resolved, "Error", resolvedDescription, "error");
}).finally(() => onFinally?.());
const unwrap = () => chain.then(() => {
if (!settled) throw new Error("scrollsheet toast.promise: chain settled with no outcome");
if (settled.ok) return settled.value;
throw settled.reason;
});
return Object.assign(id, { unwrap });
}
toastImpl.promise = promiseImpl;
toastImpl.dismiss = dismiss;
toastImpl.getToasts = () => toasts;
toastImpl.getHistory = () => history;
const toast = toastImpl;
function useSonner() {
return { toasts: React.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) };
}
const useToasts = useSonner;
function selectToasterToasts(all, toasterId) {
return all.filter((t) => toasterId === void 0 ? t.toasterId === void 0 : t.toasterId === toasterId);
}
function selectPositionToasts(all, position, defaultPosition) {
return all.filter((t) => t.position === void 0 ? position === defaultPosition : t.position === position);
}
function selectToastWindow(toasts, visibleToasts) {
const newestFirst = [...toasts].reverse();
return {
visible: newestFirst.slice(0, visibleToasts),
hidden: newestFirst.slice(visibleToasts)
};
}
//#endregion
//#region packages/scrollsheet/src/toast/toast-styles.ts
const { injectDocument, injectShadowRoot } = createStyleInjector("", "data-sonner-toast-styles");
function injectToastStyles(nonce) {
injectDocument(nonce);
}
function injectToastStylesInto(root, nonce) {
injectShadowRoot(root, nonce);
}
//#endregion
//#region packages/scrollsheet/src/toast/toaster.tsx
function resolveVisibleToasts(count) {
if (count === void 0) return 3;
return Math.max(1, Math.floor(count));
}
function useMountedFlag() {
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => {
if (prefersReducedMotion()) {
setMounted(true);
return;
}
let raf2 = 0;
const raf1 = requestAnimationFrame(() => {
raf2 = requestAnimationFrame(() => setMounted(true));
});
return () => {
cancelAnimationFrame(raf1);
cancelAnimationFrame(raf2);
};
}, []);
return mounted;
}
function useIsDocumentHidden() {
const [hidden, setHidden] = React.useState(() => typeof document !== "undefined" && document.hidden);
React.useEffect(() => {
const onVisibilityChange = () => setHidden(document.hidden);
document.addEventListener("visibilitychange", onVisibilityChange);
return () => document.removeEventListener("visibilitychange", onVisibilityChange);
}, []);
return hidden;
}
const EMPTY_RECORDS = [];
const EMPTY_MAP = new Map();
function toIdMap(records) {
return new Map(records.map((r) => [r.id, r]));
}
function sameIds(a, b) {
if (a.size !== b.size) return false;
for (const id of a.keys()) if (!b.has(id)) return false;
return true;
}
function useToastExit(live, queued = EMPTY_RECORDS, exitMs = 220) {
const [exiting, setExiting] = React.useState(() => EMPTY_MAP);
const [prevLive, setPrevLive] = React.useState(() => toIdMap(live));
const timersRef = React.useRef(new Map());
const liveIds = new Set(live.map((r) => r.id));
let renderExiting = exiting;
if (!sameIds(prevLive, liveIds)) {
const queuedIds = new Set(queued.map((r) => r.id));
const justRemoved = [];
for (const [id, record] of prevLive) {
if (liveIds.has(id)) continue;
if (queuedIds.has(id)) continue;
justRemoved.push(record);
}
setPrevLive(toIdMap(live));
if (justRemoved.length > 0) {
const next = new Map(exiting);
for (const record of justRemoved) next.set(record.id, record);
renderExiting = next;
setExiting(next);
}
}
React.useEffect(() => {
const reduced = prefersReducedMotion();
for (const [id] of exiting) {
if (timersRef.current.has(id)) continue;
const timer = setTimeout(() => {
timersRef.current.delete(id);
setExiting((prev) => {
if (!prev.has(id)) return prev;
const next = new Map(prev);
next.delete(id);
return next;
});
}, reduced ? 0 : exitMs);
timersRef.current.set(id, timer);
}
for (const [id, timer] of timersRef.current) if (!exiting.has(id)) {
clearTimeout(timer);
timersRef.current.delete(id);
}
}, [exiting, exitMs]);
React.useEffect(() => {
const timers = timersRef.current;
return () => {
for (const timer of timers.values()) clearTimeout(timer);
timers.clear();
};
}, []);
const rows = live.map((record) => ({
record,
exiting: false
}));
for (const [id, record] of renderExiting) if (!liveIds.has(id)) rows.push({
record,
exiting: true
});
return rows;
}
//#endregion
//#region packages/scrollsheet/src/toast/shell/shell-selectors.ts
function splitPosition(position) {
const [y, x] = position.split("-");
return {
y,
x
};
}
function computePossiblePositions(defaultPosition, toasts) {
const seen = new Set([defaultPosition]);
for (const t of toasts) if (t.position) seen.add(t.position);
return [...seen];
}
function computeRowOffsets(newestFirst, heights, gap) {
let cumulativeHeight = 0;
return newestFirst.map((record, index) => {
const stackOffset = index * gap + cumulativeHeight;
cumulativeHeight += heights.get(record.id) ?? 0;
return {
id: record.id,
index,
toastsBefore: index,
stackOffset
};
});
}
function findNewestDismissible(toasts) {
for (let i = toasts.length - 1; i >= 0; i -= 1) {
const t = toasts[i];
if (t && t.dismissible !== false) return t;
}
}
const SWIPE_VELOCITY_THRESHOLD = .11;
function getDefaultSwipeDirections(position) {
const { y, x } = splitPosition(position);
const directions = [y];
if (x === "left" || x === "right") directions.push(x);
return directions;
}
function lockSwipeAxis(dx, dy) {
if (Math.abs(dx) <= 1 && Math.abs(dy) <= 1) return null;
return Math.abs(dx) > Math.abs(dy) ? "x" : "y";
}
function dampenSwipeDelta(delta) {
const dampened = delta * (1 / (1.5 + Math.abs(delta) / 20));
return Math.abs(dampened) < Math.abs(delta) ? dampened : delta;
}
function computeSwipeAxisAmount(axis, delta, directions) {
const negativeDir = axis === "x" ? "left" : "top";
const positiveDir = axis === "x" ? "right" : "bottom";
if (!(directions.includes(negativeDir) || directions.includes(positiveDir))) return 0;
return directions.includes(negativeDir) && delta < 0 || directions.includes(positiveDir) && delta > 0 ? delta : dampenSwipeDelta(delta);
}
function isSwipeReleaseAllowed(axis, amount, directions) {
if (axis === "x") return directions.includes(amount > 0 ? "right" : "left");
return directions.includes(amount > 0 ? "bottom" : "top");
}
function shouldDismissOnSwipeRelease(amount, velocity, thresholdPx = 45, velocityThreshold = SWIPE_VELOCITY_THRESHOLD) {
return Math.abs(amount) >= thresholdPx || velocity > velocityThreshold;
}
function resolveSwipeOutDirection(axis, amount) {
if (axis === "x") return amount > 0 ? "right" : "left";
return amount > 0 ? "down" : "up";
}
//#endregion
//#region packages/scrollsheet/src/toast/shell/use-toast-swipe.ts
const ZERO_AMOUNT = {
x: 0,
y: 0
};
function useToastSwipe(elRef, enabled, directions, onSwipeDismiss) {
const draggingRef = React.useRef(false);
const axisRef = React.useRef(null);
const startRef = React.useRef(ZERO_AMOUNT);
const amountRef = React.useRef(ZERO_AMOUNT);
const dragStartRef = React.useRef(0);
const onSwipeDismissRef = React.useRef(onSwipeDismiss);
onSwipeDismissRef.current = onSwipeDismiss;
const onPointerDown = React.useCallback((event) => {
if (!enabled || event.button !== 0) return;
if (event.target.tagName === "BUTTON") return;
const el = elRef.current;
if (!el) return;
draggingRef.current = true;
axisRef.current = null;
amountRef.current = ZERO_AMOUNT;
startRef.current = {
x: event.clientX,
y: event.clientY
};
dragStartRef.current = Date.now();
el.setPointerCapture(event.pointerId);
el.setAttribute("data-swiping", "true");
}, [enabled, elRef]);
const onPointerMove = React.useCallback((event) => {
if (!draggingRef.current) return;
const el = elRef.current;
if (!el) return;
if ((window.getSelection?.()?.toString().length ?? 0) > 0) return;
const start = startRef.current;
const dx = event.clientX - start.x;
const dy = event.clientY - start.y;
if (axisRef.current === null) axisRef.current = lockSwipeAxis(dx, dy);
const axis = axisRef.current;
if (axis === null) return;
const resolved = computeSwipeAxisAmount(axis, axis === "x" ? dx : dy, directions);
const amount = axis === "x" ? {
x: resolved,
y: 0
} : {
x: 0,
y: resolved
};
amountRef.current = amount;
if (resolved !== 0) el.setAttribute("data-swiped", "true");
el.style.setProperty("--scrollsheet-toast-swipe-x", `${amount.x}px`);
el.style.setProperty("--scrollsheet-toast-swipe-y", `${amount.y}px`);
}, [elRef, directions]);
const release = React.useCallback(() => {
if (!draggingRef.current) return;
draggingRef.current = false;
const el = elRef.current;
const axis = axisRef.current;
axisRef.current = null;
if (!el) return;
if (axis === null) {
el.setAttribute("data-swiping", "false");
return;
}
const amount = axis === "x" ? amountRef.current.x : amountRef.current.y;
const elapsed = Math.max(1, Date.now() - dragStartRef.current);
const velocity = Math.abs(amount) / elapsed;
if (isSwipeReleaseAllowed(axis, amount, directions) && shouldDismissOnSwipeRelease(amount, velocity)) {
const direction = resolveSwipeOutDirection(axis, amount);
el.setAttribute("data-swipe-direction", direction);
if (prefersReducedMotion()) {
el.setAttribute("data-swipe-out", "true");
onSwipeDismissRef.current();
return;
}
const onAnimEnd = () => {
el.removeEventListener("animationend", onAnimEnd);
onSwipeDismissRef.current();
};
el.addEventListener("animationend", onAnimEnd);
el.setAttribute("data-swipe-out", "true");
return;
}
el.setAttribute("data-swiping", "false");
el.setAttribute("data-swiped", "false");
el.style.setProperty("--scrollsheet-toast-swipe-x", "0px");
el.style.setProperty("--scrollsheet-toast-swipe-y", "0px");
}, [elRef, directions]);
return {
onPointerDown,
onPointerMove,
onPointerUp: React.useCallback((event) => {
if (elRef.current?.hasPointerCapture(event.pointerId)) elRef.current.releasePointerCapture(event.pointerId);
release();
}, [elRef, release]),
onPointerCancel: React.useCallback(() => release(), [release])
};
}
//#endregion
//#region packages/scrollsheet/src/toast/shell/toast-row.tsx
function ToastIcon({ type, icons, spinnerClassName }) {
if (type === "loading") {
if (icons?.loading) return jsx(Fragment, { children: icons.loading });
return jsx("span", { className: spinnerClassName });
}
switch (type) {
case "success": return jsx(Fragment, { children: icons?.success ?? "✓" });
case "error": return jsx(Fragment, { children: icons?.error ?? "✕" });
case "warning": return jsx(Fragment, { children: icons?.warning ?? "!" });
case "info": return jsx(Fragment, { children: icons?.info ?? "i" });
default: return null;
}
}
function ToastRow({ record, index, total, toastsBefore, stackOffset, height, frontHeight, visible, expanded, removed, yPosition, xPosition, closeButton, directions, onDismiss, observe, toasterClassNames, icons, toasterCloseButtonAriaLabel, toasterStyle, sonnerCompat }) {
const role = record.type === "error" ? "alert" : "status";
const mounted = useMountedFlag();
const elRef = React.useRef(null);
const dismissible = record.dismissible !== false;
const isFront = index === 0;
const setRefs = React.useCallback((el) => {
elRef.current = el;
observe(record.id, el);
}, [observe, record.id]);
const swipeHandlers = useToastSwipe(elRef, !removed && dismissible && record.type !== "loading", directions, () => onDismiss(record));
React.useEffect(() => {
const el = elRef.current;
if (el) el.inert = removed;
}, [removed]);
const style = {
"--scrollsheet-toast-toasts-before": toastsBefore,
"--scrollsheet-toast-stack-offset": `${stackOffset}px`,
"--scrollsheet-toast-front-height": frontHeight !== void 0 ? `${frontHeight}px` : "0px",
"--scrollsheet-toast-initial-height": height !== void 0 ? `${height}px` : "auto",
zIndex: Math.max(0, total - index),
...toasterStyle,
...record.style
};
const legacy = (name) => sonnerCompat ? name : void 0;
const rootClassName = cn("scrollsheet-toast", legacy("sonner-toast"), record.className, toasterClassNames?.toast, record.classNames?.toast, toasterClassNames?.default, toasterClassNames?.[record.type], record.classNames?.[record.type]);
const motionAttrs = {
"data-mounted": mounted ? "" : void 0,
"data-removed": removed ? "" : void 0,
"data-visible": visible ? "true" : "false",
"data-front": isFront ? "true" : "false",
"data-expanded": expanded ? "true" : "false",
"data-y-position": yPosition,
"data-x-position": xPosition,
"data-index": index,
"data-dismissible": dismissible ? "true" : "false",
"data-swiping": "false",
"data-swiped": "false",
"aria-hidden": removed ? "true" : void 0
};
if (record.jsx !== void 0) return jsx("div", {
ref: setRefs,
className: rootClassName,
"data-scrollsheet-toast": "",
"data-sonner-toast": sonnerCompat ? "" : void 0,
"data-scrollsheet-custom": "",
"data-sonner-custom": sonnerCompat ? "" : void 0,
"data-testid": record.testId,
role,
style,
...motionAttrs,
...swipeHandlers,
children: record.jsx
});
const showCloseButton = closeButton && dismissible && record.type !== "loading";
const hasActions = Boolean(record.action || record.cancel || showCloseButton);
const closeButtonAriaLabel = record.closeButtonAriaLabel ?? toasterCloseButtonAriaLabel ?? "Close toast";
return jsxs("div", {
ref: setRefs,
className: rootClassName,
"data-scrollsheet-toast": "",
"data-sonner-toast": sonnerCompat ? "" : void 0,
"data-type": record.type,
"data-testid": record.testId,
role,
style,
...motionAttrs,
...swipeHandlers,
children: [
jsx("span", {
className: cn("scrollsheet-toast-icon", legacy("sonner-toast-icon"), toasterClassNames?.icon, record.classNames?.icon),
"data-type": record.type,
"aria-hidden": "true",
children: record.icon ?? jsx(ToastIcon, {
type: record.type,
icons,
spinnerClassName: cn("scrollsheet-toast-spinner", legacy("sonner-toast-spinner"), toasterClassNames?.loader, record.classNames?.loader)
})
}),
jsxs("div", {
className: cn("scrollsheet-toast-body", legacy("sonner-toast-body"), toasterClassNames?.content, record.classNames?.content),
children: [record.title !== void 0 && jsx("div", {
className: cn("scrollsheet-toast-title", legacy("sonner-toast-title"), toasterClassNames?.title, record.classNames?.title),
children: record.title
}), record.description !== void 0 && jsx("div", {
className: cn("scrollsheet-toast-description", legacy("sonner-toast-description"), toasterClassNames?.description, record.classNames?.description),
children: record.description
})]
}),
hasActions && jsxs("div", {
className: cn("scrollsheet-toast-actions", legacy("sonner-toast-actions")),
children: [
record.cancel && jsx("button", {
type: "button",
className: cn("scrollsheet-toast-cancel", legacy("sonner-toast-cancel"), toasterClassNames?.cancelButton, record.classNames?.cancelButton),
onClick: (event) => {
if (!dismissible) return;
record.cancel?.onClick(event);
onDismiss(record);
},
children: record.cancel.label
}),
record.action && jsx("button", {
type: "button",
className: cn("scrollsheet-toast-action", legacy("sonner-toast-action"), toasterClassNames?.actionButton, record.classNames?.actionButton),
onClick: (event) => {
record.action?.onClick(event);
if (event.defaultPrevented) return;
onDismiss(record);
},
children: record.action.label
}),
showCloseButton && jsx("button", {
type: "button",
className: cn("scrollsheet-toast-close", legacy("sonner-toast-close"), toasterClassNames?.closeButton, record.classNames?.closeButton),
"aria-label": closeButtonAriaLabel,
onClick: () => onDismiss(record),
children: icons?.close ?? "×"
})
]
})
]
});
}
//#endregion
//#region packages/scrollsheet/src/toast/shell/use-toast-heights.ts
function useToastHeights() {
const [heights, setHeights] = React.useState(() => new Map());
const observersRef = React.useRef(new Map());
const setHeight = React.useCallback((id, height) => {
setHeights((prev) => prev.get(id) === height ? prev : new Map(prev).set(id, height));
}, []);
const forget = React.useCallback((id) => {
observersRef.current.get(id)?.disconnect();
observersRef.current.delete(id);
setHeights((prev) => {
if (!prev.has(id)) return prev;
const next = new Map(prev);
next.delete(id);
return next;
});
}, []);
const observe = React.useCallback((id, el) => {
const observers = observersRef.current;
observers.get(id)?.disconnect();
observers.delete(id);
if (!el || typeof ResizeObserver === "undefined") return;
const ro = new ResizeObserver(() => setHeight(id, el.getBoundingClientRect().height));
ro.observe(el);
observers.set(id, ro);
setHeight(id, el.getBoundingClientRect().height);
}, [setHeight]);
React.useEffect(() => {
const observers = observersRef.current;
return () => {
for (const ro of observers.values()) ro.disconnect();
observers.clear();
};
}, []);
return {
heights,
observe,
forget
};
}
//#endregion
//#region packages/scrollsheet/src/toast/shell/toaster-shell.tsx
const DEFAULT_HOTKEY = ["altKey", "KeyT"];
function PositionGroup({ position, defaultPosition, toasts, visibleToasts, gap, expanded, closeButton, swipeDirections, toastOptions, icons, className, baseStyle, sonnerCompat, registerListEl, dismissRow, onHoverEnter, onHoverLeave, onInteractingChange }) {
const directions = React.useMemo(() => swipeDirections ?? getDefaultSwipeDirections(position), [swipeDirections, position]);
const positionToasts = React.useMemo(() => selectPositionToasts(toasts, position, defaultPosition), [
toasts,
position,
defaultPosition
]);
const newestFirst = React.useMemo(() => [...positionToasts].reverse(), [positionToasts]);
const window_ = React.useMemo(() => selectToastWindow(positionToasts, visibleToasts), [positionToasts, visibleToasts]);
const visibleIds = React.useMemo(() => new Set(window_.visible.map((t) => t.id)), [window_]);
const { heights, observe, forget } = useToastHeights();
const exitRows = useToastExit(newestFirst);
const exitingIds = React.useMemo(() => new Set(exitRows.filter((r) => r.exiting).map((r) => r.record.id)), [exitRows]);
React.useEffect(() => {
for (const id of exitingIds) forget(id);
}, [exitingIds, forget]);
const offsets = React.useMemo(() => computeRowOffsets(newestFirst, heights, gap), [
newestFirst,
heights,
gap
]);
const offsetById = React.useMemo(() => new Map(offsets.map((o) => [o.id, o])), [offsets]);
const lastOffsetRef = React.useRef(new Map());
for (const o of offsets) lastOffsetRef.current.set(o.id, o);
if (lastOffsetRef.current.size > offsets.length) {
const keep = new Set([...offsets.map((o) => o.id), ...exitingIds]);
for (const id of [...lastOffsetRef.current.keys()]) if (!keep.has(id)) lastOffsetRef.current.delete(id);
}
const frontHeight = heights.get(newestFirst[0]?.id ?? "");
const { y, x } = splitPosition(position);
const listRef = React.useCallback((el) => registerListEl(position, el), [registerListEl, position]);
if (exitRows.length === 0) return null;
return jsx("ol", {
ref: listRef,
"data-scrollsheet-toaster": "",
"data-sonner-toaster": sonnerCompat ? "" : void 0,
"data-scrollsheet-theme": "light",
"data-sonner-theme": sonnerCompat ? "light" : void 0,
"data-y-position": y,
"data-x-position": x,
tabIndex: -1,
className,
style: {
...baseStyle,
"--scrollsheet-toast-gap": `${gap}px`,
"--scrollsheet-toast-front-height": frontHeight !== void 0 ? `${frontHeight}px` : void 0
},
onMouseEnter: onHoverEnter,
onMouseMove: onHoverEnter,
onMouseLeave: onHoverLeave,
onPointerDown: (event) => {
if (event.target.dataset.dismissible === "false") return;
onInteractingChange(true);
},
onPointerUp: () => onInteractingChange(false),
children: exitRows.map(({ record, exiting }) => {
const offset = offsetById.get(record.id) ?? lastOffsetRef.current.get(record.id);
const index = offset?.index ?? newestFirst.length;
return jsx(ToastRow, {
record,
index,
total: newestFirst.length,
toastsBefore: offset?.toastsBefore ?? index,
stackOffset: offset?.stackOffset ?? 0,
height: heights.get(record.id),
frontHeight,
visible: visibleIds.has(record.id),
expanded,
removed: exiting,
yPosition: y,
xPosition: x,
closeButton,
directions,
onDismiss: dismissRow,
observe,
toasterClassNames: toastOptions?.classNames,
sonnerCompat,
icons,
toasterCloseButtonAriaLabel: toastOptions?.closeButtonAriaLabel,
toasterStyle: toastOptions?.style
}, record.id);
})
});
}
function resolveShellPosition(position) {
return position ?? "bottom-right";
}
function ToasterShell({ id, toasterId, position, theme, richColors, expand: forceExpand, visibleToasts: visibleToastsProp, closeButton = false, duration: durationProp, gap = 14, offset, swipeDirections, toastOptions, icons, className, style, containerAriaLabel = "Notifications", hotkey = DEFAULT_HOTKEY, nonce, sonnerCompat = true }) {
const resolvedId = id ?? toasterId;
if (toasterId !== void 0) warnOnce("toaster-id-deprecated", "[scrollsheet Toaster] Toaster's \"toasterId\" prop is deprecated — use \"id\" instead.");
const { toasts: allToasts } = useSonner();
const toasts = React.useMemo(() => selectToasterToasts(allToasts, resolvedId), [allToasts, resolvedId]);
const defaultPosition = resolveShellPosition(position);
const possiblePositions = React.useMemo(() => computePossiblePositions(defaultPosition, toasts), [defaultPosition, toasts]);
const visibleToasts = resolveVisibleToasts(visibleToastsProp);
const defaultDuration = durationProp ?? toastOptions?.duration ?? 4e3;
if (theme !== void 0 && theme !== "light") warnOnce("shell-theme", `[scrollsheet Toaster] theme="${theme}" isn't implemented yet — v1 always renders the static light card real Sonner ships by default.`);
if (richColors) warnOnce("shell-rich-colors", "[scrollsheet Toaster] richColors isn't implemented yet — toasts render with their default icon color only.");
React.useEffect(() => {
injectToastStyles(nonce);
}, [nonce]);
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => setMounted(true), []);
const [hovered, setHovered] = React.useState(false);
const [hotkeyExpanded, setHotkeyExpanded] = React.useState(false);
const [interacting, setInteracting] = React.useState(false);
const expanded = Boolean(forceExpand) || hovered || hotkeyExpanded;
const isDocumentHidden = useIsDocumentHidden();
const handleHoverEnter = React.useCallback(() => setHovered(true), []);
const handleHoverLeave = React.useCallback(() => {
if (!interacting) setHovered(false);
}, [interacting]);
const dismissRow = React.useCallback((record) => {
toast.dismiss(record.id);
}, []);
const listElsRef = React.useRef(new Map());
const registerListEl = React.useCallback((pos, el) => {
if (el) listElsRef.current.set(pos, el);
else listElsRef.current.delete(pos);
}, []);
React.useEffect(() => {
if (hotkey.length === 0) return;
const handleKeyDown = (event) => {
if (hotkey.every((key) => event[key] || event.code === key)) {
setHotkeyExpanded(true);
const [firstList] = listElsRef.current.values();
firstList?.focus({ preventScroll: true });
}
if (event.code !== "Escape") return;
const active = document.activeElement;
const focusedEntry = [...listElsRef.current.entries()].find(([, el]) => active === el || el.contains(active));
if (!focusedEntry) return;
if (hotkeyExpanded) {
setHotkeyExpanded(false);
return;
}
const [focusedPosition] = focusedEntry;
const positionToasts = selectPositionToasts(toasts, focusedPosition, defaultPosition);
const front = positionToasts[positionToasts.length - 1];
if (front) dismissRow(front);
};
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, [
hotkey,
hotkeyExpanded,
toasts,
defaultPosition,
dismissRow
]);
const timersRef = React.useRef(new Map());
React.useEffect(() => {
const timers = timersRef.current;
const liveIds = new Set(toasts.map((t) => t.id));
for (const [id, entry] of timers) {
if (liveIds.has(id)) continue;
if (entry.timer) clearTimeout(entry.timer);
timers.delete(id);
}
const paused = expanded || interacting || isDocumentHidden;
for (const t of toasts) {
if (t.type === "loading") {
const stale = timers.get(t.id);
if (stale) {
if (stale.timer) clearTimeout(stale.timer);
timers.delete(t.id);
}
continue;
}
const ms = t.duration ?? toastOptions?.duration ?? defaultDuration;
const existing = timers.get(t.id);
if (!existing || existing.record !== t) {
if (existing?.timer) clearTimeout(existing.timer);
const entry = {
timer: null,
startedAt: 0,
remaining: ms,
record: t
};
timers.set(t.id, entry);
if (Number.isFinite(ms) && !paused) {
entry.startedAt = Date.now();
entry.timer = setTimeout(() => {
timersRef.current.delete(t.id);
expire(t.id);
}, ms);
}
continue;
}
if (paused) {
if (existing.timer) {
clearTimeout(existing.timer);
const elapsed = Date.now() - existing.startedAt;
existing.remaining = Math.max(0, existing.remaining - elapsed);
existing.timer = null;
}
continue;
}
if (existing.timer || !Number.isFinite(existing.remaining)) continue;
existing.startedAt = Date.now();
existing.timer = setTimeout(() => {
timersRef.current.delete(t.id);
expire(t.id);
}, existing.remaining);
}
}, [
toasts,
expanded,
interacting,
isDocumentHidden,
defaultDuration,
toastOptions?.duration
]);
React.useEffect(() => {
const timers = timersRef.current;
return () => {
for (const entry of timers.values()) if (entry.timer) clearTimeout(entry.timer);
timers.clear();
};
}, []);
useCloseWatcher({
present: toasts.length > 0,
nonModal: true,
escapeDismissible: true,
onClose: () => {
const target = findNewestDismissible(toasts);
if (target) dismissRow(target);
}
});
if (!mounted || typeof document === "undefined") return null;
const offsetValue = offset !== void 0 ? typeof offset === "number" ? `${offset}px` : offset : void 0;
const baseStyle = {
...style,
"--scrollsheet-toast-offset": offsetValue
};
return createPortal(jsx("section", {
"aria-label": containerAriaLabel,
tabIndex: -1,
"aria-live": "polite",
"aria-relevant": "additions text",
"aria-atomic": "false",
suppressHydrationWarning: true,
"data-react-aria-top-layer": "",
children: possiblePositions.map((groupPosition) => jsx(PositionGroup, {
position: groupPosition,
defaultPosition,
toasts,
visibleToasts,
gap,
expanded,
closeButton,
swipeDirections,
toastOptions,
icons,
className,
baseStyle,
sonnerCompat,
registerListEl,
dismissRow,
onHoverEnter: handleHoverEnter,
onHoverLeave: handleHoverLeave,
onInteractingChange: setInteracting
}, groupPosition))
}), document.body);
}
//#endregion
export { useSonner as a, toast as i, resolveVisibleToasts as n, useToasts as o, injectToastStylesInto as r, ToasterShell as t };
"use client";
import { i as Content$1, l as Root$1, n as Description, o as Trigger, r as Title, s as Slot, t as Close, u as useSheetContext } from "./misc-D0efWKP-.mjs";
import { t as cn } from "./cn-DgpbGWy-.mjs";
import * as React from "react";
import { Fragment, jsx } from "react/jsx-runtime";
import { createPortal } from "react-dom";
//#region packages/scrollsheet/src/handle.tsx
function detentLabel(spec) {
if (spec === "full") return "Full";
if (spec === "medium") return "Half";
if (spec === "content") return "Fit content";
if (typeof spec === "number") return `${Math.round(spec * 100)}%`;
return spec ?? "";
}
const Handle$1 = React.forwardRef(function Handle({ asChild, onClick, onKeyDown, className, variant = "inside", ...props }, ref) {
const ctx = useSheetContext("Handle");
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => setMounted(true), []);
const outside = variant === "outside" && ctx.side === "bottom" && !(mounted && !ctx.canvasEl);
const variantAttr = outside ? "outside" : variant === "floating" ? "floating" : void 0;
const specs = ctx.detents;
const horizontal = ctx.side === "left" || ctx.side === "right";
const multi = specs.length >= 2;
const activeIndex = specs.findIndex((s) => Object.is(s, ctx.activeDetent));
const move = (delta) => {
if (!multi) return false;
const index = specs.findIndex((s) => Object.is(s, ctx.activeDetent));
const next = specs[index + delta];
if (next === void 0) return false;
ctx.setActiveDetent(next);
return true;
};
const sliderAria = multi ? {
role: "slider",
"aria-orientation": horizontal ? "horizontal" : "vertical",
"aria-valuemin": 0,
"aria-valuemax": specs.length - 1,
...activeIndex >= 0 ? {
"aria-valuenow": activeIndex,
"aria-valuetext": detentLabel(specs[activeIndex])
} : {}
} : {};
const button = jsx(asChild ? Slot : "button", {
...asChild ? {} : { type: "button" },
"aria-label": horizontal ? "Adjust sheet width" : "Adjust sheet height",
...sliderAria,
...props,
ref,
className: cn("scrollsheet-handle", className),
"data-scrollsheet-handle": true,
"data-scrollsheet-handle-variant": variantAttr,
onClick: (event) => {
onClick?.(event);
if (event.defaultPrevented) return;
if (!multi) return;
const index = specs.findIndex((s) => Object.is(s, ctx.activeDetent));
const next = specs[(index + 1) % specs.length];
if (next !== void 0) ctx.setActiveDetent(next);
},
onKeyDown: (event) => {
onKeyDown?.(event);
if (event.defaultPrevented) return;
const expandKey = horizontal ? "ArrowRight" : "ArrowUp";
const collapseKey = horizontal ? "ArrowLeft" : "ArrowDown";
if (event.key === expandKey) {
event.preventDefault();
move(1);
} else if (event.key === collapseKey) {
event.preventDefault();
if (!move(-1) && ctx.dismissible) ctx.setOpen(false);
} else if (multi && event.key === "Home") {
event.preventDefault();
const first = specs[0];
if (first !== void 0) ctx.setActiveDetent(first);
} else if (multi && event.key === "End") {
event.preventDefault();
const last = specs[specs.length - 1];
if (last !== void 0) ctx.setActiveDetent(last);
}
}
});
if (outside) return ctx.canvasEl ? createPortal(button, ctx.canvasEl) : null;
return button;
});
//#endregion
//#region packages/scrollsheet/src/drawer/index.tsx
const wrapperClaims = new Map();
const VAUL_CLOSE_THRESHOLD = .25;
function toDetentSpec(point) {
if (typeof point === "number") return point;
if (point === "fit-content" || point === "content") return "content";
if (/^\d+(\.\d+)?px$/.test(point)) return `${Number.parseFloat(point)}px`;
const parsed = Number.parseFloat(point);
return Number.isFinite(parsed) ? parsed : "content";
}
function resolveFadeFromIndex(snapPoints, fadeFromIndex) {
if (!snapPoints || snapPoints.length === 0) return void 0;
const point = snapPoints[fadeFromIndex ?? snapPoints.length - 1];
return point !== void 0 ? toDetentSpec(point) : void 0;
}
function resolveCloseThreshold(snapPoints, closeThreshold) {
if (snapPoints !== void 0 && snapPoints.length > 0) return void 0;
return Math.min(1, Math.max(0, 1 - (closeThreshold ?? VAUL_CLOSE_THRESHOLD)));
}
function composeOpenChange(onOpenChange, onClose) {
if (!onOpenChange && !onClose) return void 0;
return (next) => {
if (!next) onClose?.();
onOpenChange?.(next);
};
}
function Root(props) {
const { children, open, defaultOpen, onOpenChange, onClose, dismissible, snapPoints, activeSnapPoint, setActiveSnapPoint, onAnimationEnd, autoFocus, nested, direction, modal, shouldScaleBackground, setBackgroundColorOnScale, noBodyStyles, disablePreventScroll, preventScrollRestoration, repositionInputs, scrollLockTimeout, closeThreshold, fadeFromIndex, snapToSequentialPoint, handleOnly, onDrag, onRelease, container, fixed, actionsRef, backdropDismissible, escapeDismissible, keyboardExpands, onTravel, scrollbar } = props;
const detents = snapPoints && snapPoints.length > 0 ? snapPoints.map(toDetentSpec) : void 0;
const activeDetent = activeSnapPoint != null ? toDetentSpec(activeSnapPoint) : void 0;
const largestUndimmedDetent = resolveFadeFromIndex(snapPoints, fadeFromIndex);
const resolvedCloseThreshold = resolveCloseThreshold(snapPoints, closeThreshold);
const handleRelease = onRelease ? (event, willRemainOpen) => onRelease(event, willRemainOpen) : void 0;
const handleOpenChange = composeOpenChange(onOpenChange, onClose);
const handleActiveDetentChange = (detent) => {
if (!setActiveSnapPoint) return;
const match = snapPoints?.find((point) => Object.is(toDetentSpec(point), detent));
setActiveSnapPoint(match ?? detent);
};
React.useEffect(() => {
if (!shouldScaleBackground) return;
const existing = document.querySelector("[data-scrollsheet-background]");
if (existing && !wrapperClaims.has(existing)) return;
const wrapper = existing ?? document.querySelector("[data-vaul-drawer-wrapper]");
if (!wrapper) return;
const count = wrapperClaims.get(wrapper) ?? 0;
wrapperClaims.set(wrapper, count + 1);
if (count === 0) wrapper.setAttribute("data-scrollsheet-background", "");
return () => {
const remaining = (wrapperClaims.get(wrapper) ?? 1) - 1;
if (remaining <= 0) {
wrapperClaims.delete(wrapper);
wrapper.removeAttribute("data-scrollsheet-background");
} else wrapperClaims.set(wrapper, remaining);
};
}, [shouldScaleBackground]);
return jsx(DirectionContext.Provider, {
value: direction ?? "bottom",
children: jsx(Root$1, {
open,
defaultOpen,
onOpenChange: handleOpenChange,
onOpenChangeComplete: onAnimationEnd,
dismissible,
detents,
activeDetent,
onActiveDetentChange: setActiveSnapPoint ? handleActiveDetentChange : void 0,
side: direction,
modal,
backgroundEffect: shouldScaleBackground ? "scale" : "none",
largestUndimmedDetent,
handleOnly,
sequentialDetents: snapToSequentialPoint,
closeThreshold: resolvedCloseThreshold,
onRelease: handleRelease,
actionsRef,
backdropDismissible,
escapeDismissible,
keyboardExpands,
onTravel,
scrollbar,
children
})
});
}
const DirectionContext = React.createContext("bottom");
const NestedRoot = Root;
function Portal({ children, container }) {
return jsx(Fragment, { children });
}
function Overlay(_props) {
return null;
}
function hasMultipleSnapPoints(detents) {
return detents.length > 1;
}
const Content = React.forwardRef(function Content({ onPointerDownOutside, onOpenAutoFocus, onEscapeKeyDown, onCloseAutoFocus, onInteractOutside, onFocusOutside, forceMount, ...props }, ref) {
const ctx = useSheetContext("Content");
const direction = React.useContext(DirectionContext);
const snapPointsActive = hasMultipleSnapPoints(ctx.detents);
return jsx(Content$1, {
...props,
ref,
"data-vaul-drawer": "",
"data-vaul-drawer-direction": direction,
"data-vaul-snap-points": snapPointsActive ? "true" : "false"
});
});
function composeHandleClick(preventCycle, onClick) {
return (event) => {
onClick?.(event);
if (preventCycle) event.preventDefault();
};
}
const Handle = React.forwardRef(function Handle({ preventCycle, onClick, ...props }, ref) {
const ctx = useSheetContext("Handle");
return jsx(Handle$1, {
...props,
ref,
"data-vaul-handle": "",
"data-vaul-drawer-visible": ctx.open ? "true" : "false",
onClick: composeHandleClick(preventCycle, onClick)
});
});
const DrawerClose = React.forwardRef(function DrawerClose({ children, ...props }, ref) {
return jsx(Close, {
...props,
ref,
children: children ?? null
});
});
const Drawer = {
Root,
NestedRoot,
Trigger,
Portal,
Overlay,
Content,
Close: DrawerClose,
Title,
Description,
Handle
};
//#endregion
export { NestedRoot as a, Root as c, hasMultipleSnapPoints as d, resolveCloseThreshold as f, Handle as i, composeHandleClick as l, Handle$1 as m, Drawer as n, Overlay as o, resolveFadeFromIndex as p, DrawerClose as r, Portal as s, Content as t, composeOpenChange as u };
import { a as SheetTitleProps, c as SheetContentProps, h as DetentSpec, i as SheetDescriptionProps, l as SheetTriggerProps, p as SheetRootProps, r as SheetCloseProps } from "./misc-DD6-LNnA.mjs";
import * as React from "react";
//#region packages/scrollsheet/src/handle.d.ts
interface SheetHandleProps extends React.ComponentProps<"button"> {
/** Render the child element instead of a `<button>`, merging props into it. */
asChild?: boolean;
/**
* Where the pill sits. `'inside'` (default) flows at the top of the
* sheet's content. `'floating'` overlays the content — absolutely
* positioned over the panel's top edge, for full-bleed content (maps,
* photos) a flow pill would push down. `'outside'` floats in the backdrop
* above the sheet's top edge (bottom sheets only; other sides render as
* `'inside'`) — it rides the canvas layer outside the panel's clip and
* drags, clicks, and keys exactly like the others.
* @default 'inside'
*/
variant?: "inside" | "floating" | "outside";
}
/**
* The grabber pill. Click cycles detents; ArrowUp/ArrowDown (or Left/Right
* for side sheets) move between them, Home/End jump to the first/last — so
* multi-detent sheets are fully keyboard-operable. With two or more detents
* it exposes itself as a slider (role, value min/max/now, value text) so
* screen readers announce the current stop, not just "button".
*
* The handle is optional. It's purely a click/keyboard affordance layered on
* top of the drag engine, which listens on the whole panel, not the handle —
* omit `<Sheet.Handle>` and the whole panel still drags and dismisses, in
* every mode (modal and non-modal) and every side (bottom/top/left/right).
* There's no boolean prop for this; omission is the API.
*/
declare const Handle$1: React.ForwardRefExoticComponent<Omit<SheetHandleProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
//#endregion
//#region packages/scrollsheet/src/drawer/index.d.ts
/** vaul's snap point shape: a 0–1 fraction, an absolute `'###px'` string, or `'fit-content'`. */
type VaulSnapPoint = number | string;
/**
* Resolves vaul's `fadeFromIndex` against `snapPoints` into a
* `largestUndimmedDetent` spec, mirroring real vaul's own default (its
* `src/index.tsx`: `fadeFromIndex = snapPoints && snapPoints.length - 1`) —
* omitted with `snapPoints` set defaults to the *topmost* snap point index
* (no dim until the last snap point), not "no undimmed range at all"
* (scrollsheet's own default when `largestUndimmedDetent` is never touched).
* No `snapPoints`, or an out-of-range explicit index, resolves to
* `undefined` (today's full-dim-range behavior) — pulled out as its own pure
* function so this index math is unit-testable independent of rendering.
*/
declare function resolveFadeFromIndex(snapPoints: readonly VaulSnapPoint[] | undefined, fadeFromIndex: number | undefined): DetentSpec | undefined;
/**
* Resolves vaul's `closeThreshold` against whether `snapPoints` is set,
* CONVERTING between opposite conventions: vaul counts the fraction dragged
* AWAY (its 0.25 default = dismiss after a quarter-height drag), scrollsheet
* counts the fraction still VISIBLE (`isBelowCloseThreshold`: dismiss when
* `revealed < firstDetent * closeThreshold`, so higher = easier). A raw
* passthrough inverts the migrated feel — vaul's 0.25 became "drag 75% to
* dismiss", harder than scrollsheet's own 0.5 default instead of easier —
* so the mapping is `1 - value`, and an omitted prop resolves to vaul's own
* `CLOSE_THRESHOLD` default of 0.25 (=> 0.75 here), never `Sheet.Root`'s
* unrelated 0.5.
* `snapPoints` set makes `closeThreshold` dead code in real vaul (its
* `onRelease` returns through the snap-points branch before ever reading
* it), matched here by always resolving to `undefined` in that case.
* Pulled out as its own pure function, mirroring `resolveFadeFromIndex`,
* so the conversion math is unit-testable independent of rendering.
*/
declare function resolveCloseThreshold(snapPoints: readonly VaulSnapPoint[] | undefined, closeThreshold: number | undefined): number | undefined;
/**
* Composes vaul's `onClose` on top of `onOpenChange`: `onClose` fires
* whenever the transition target is `false`, then `onOpenChange` always
* fires. Real vaul's `closeDrawer()` calls `onClose` from every dismiss
* path (swipe past threshold, Esc, backdrop, imperative); scrollsheet's own
* `onOpenChange` is already the single funnel every one of *its* dismiss
* paths goes through (`useControllableState`'s `onChange`, which fires
* exactly once per real open/false transition), so layering `onClose` on
* top of that wiring reproduces "every dismiss path" without a second one.
* Returns `undefined` when neither callback is given, matching the
* conditional-wrapper convention `onRelease`'s cast uses below. Pulled out
* as its own pure function so the composition is unit-testable without a
* live DOM/click event.
*/
declare function composeOpenChange(onOpenChange: ((open: boolean) => void) | undefined, onClose: (() => void) | undefined): ((open: boolean) => void) | undefined;
/**
* vaul's own props, translated — plus (via the Pick) the scrollsheet-native
* props that have no vaul counterpart and forward to `Sheet.Root`
* untranslated: `backdropDismissible` (the escape hatch vaul served with the
* Radix onPointerDownOutside/onInteractOutside preventDefault idiom),
* `escapeDismissible` (same for onEscapeKeyDown), `keyboardExpands`,
* `onTravel` (the live counterpart of vaul's ignored `onDrag`), `scrollbar`,
* and `actionsRef`. Only props with no vaul name-collision are forwarded —
* anything vaul also has (`closeThreshold`, `modal`, `dismissible`, …) keeps
* its translated vaul semantics above.
*/
interface DrawerRootProps extends Pick<SheetRootProps, "actionsRef" | "backdropDismissible" | "escapeDismissible" | "keyboardExpands" | "onTravel" | "scrollbar"> {
children?: React.ReactNode;
open?: boolean;
defaultOpen?: boolean;
onOpenChange?: (open: boolean) => void;
/**
* Fires whenever the drawer closes — every dismiss path (swipe past
* threshold, Esc, backdrop tap, imperative `close()`) funnels through the
* same `onOpenChange` wiring this is layered on top of, matching real
* vaul's `closeDrawer()`, which calls `onClose` on every one of those
* paths too.
*/
onClose?: () => void;
/** @default true */
dismissible?: boolean;
/** Fractions (0–1), `'###px'` strings, or `'fit-content'` — translated to scrollsheet `detents`. */
snapPoints?: readonly VaulSnapPoint[];
/** Optionally-controlled active snap point; `null` means "no explicit snap point". */
activeSnapPoint?: VaulSnapPoint | null;
setActiveSnapPoint?: (snapPoint: VaulSnapPoint | null) => void;
/** Fires when the open/close transition actually completes — wired to `onOpenChangeComplete`, exact rather than vaul's timer. */
onAnimationEnd?: (open: boolean) => void;
/** Ignored — the native `<dialog>` manages focus itself. */
autoFocus?: boolean;
/** Ignored — nesting is automatic: a `<Drawer.Content>` rendered inside another registers itself. */
nested?: boolean;
/** Maps to scrollsheet `side` — all four edges. */
direction?: "top" | "bottom" | "left" | "right";
/** Maps to scrollsheet `modal` — `false` renders non-modal (Popover top layer, page stays interactive). */
modal?: boolean;
/**
* Maps to `backgroundEffect="scale"`; vaul's own `[data-vaul-drawer-wrapper]`
* is picked up as the target. Left false (vaul's default), this maps to
* `'none'`, not unset — vaul never scales the page unless asked, so the
* scrollsheet default for full-height sheets must not leak in here.
*/
shouldScaleBackground?: boolean;
/**
* Maps to scrollsheet `closeThreshold` — but only when `snapPoints` is
* unset. In real vaul, `closeThreshold` is dead code once `snapPoints`
* exist (its `onRelease` returns through the snap-points branch before
* ever reading it); this compat layer matches that rather than giving the
* prop unconditional live effect. Passing both together warns once in dev
* — use the native `Sheet.Root closeThreshold` directly if you want it to
* combine with detents.
*
* Omitted (with `snapPoints` unset) resolves to vaul's own default of
* `0.25` (`CLOSE_THRESHOLD` in vaul's `src/constants.ts`) — not
* scrollsheet's own `0.5` default, which is twice the drag distance.
*/
closeThreshold?: number;
/**
* Maps to `largestUndimmedDetent`, resolved against `snapPoints` at this
* index. Omitted (with `snapPoints` set) defaults to `snapPoints.length -
* 1` — vaul's own default, meaning no dim until the topmost snap point —
* not "dim across the full range" (scrollsheet's own default when
* `largestUndimmedDetent` is never touched at all).
*/
fadeFromIndex?: number;
/** Maps to `sequentialDetents`. */
snapToSequentialPoint?: boolean;
/** Maps to `handleOnly` directly. */
handleOnly?: boolean;
/**
* Maps to `onRelease`. Typed as vaul's own `React.PointerEvent<HTMLDivElement>`
* (rather than scrollsheet's wider native-`PointerEvent` type) so a
* handler already typed against vaul's declaration still assigns cleanly —
* see the internal cast in `Root` for why this is safe at runtime.
*/
onRelease?: (event: React.PointerEvent<HTMLDivElement>, open: boolean) => void;
setBackgroundColorOnScale?: boolean;
noBodyStyles?: boolean;
disablePreventScroll?: boolean;
preventScrollRestoration?: boolean;
repositionInputs?: boolean;
scrollLockTimeout?: number;
onDrag?: (event: React.PointerEvent, percentageDragged: number) => void;
container?: HTMLElement | null;
/**
* Ignored — real vaul's `fixed` swaps its own keyboard-avoidance strategy
* (resize instead of translate); scrollsheet's `useKeyboardViewport` has
* no equivalent toggle.
*/
fixed?: boolean;
}
declare function Root(props: DrawerRootProps): React.JSX.Element;
/**
* Nested drawers just work: a `<Drawer.Content>` rendered inside another
* `<Drawer.Content>` automatically registers with the parent's stacking
* context (the parent recedes while the child is open), the way iOS stacks
* sheets. `NestedRoot` is an alias of `Root` kept for drop-in compatibility
* with vaul's API surface — there's nothing distinct for it to do.
*/
declare const NestedRoot: typeof Root;
type DrawerNestedRootProps = DrawerRootProps;
interface DrawerPortalProps {
children?: React.ReactNode;
/** Ignored — the native `<dialog>` always renders into the browser's top layer. */
container?: HTMLElement | null;
}
declare function Portal({ children, container }: DrawerPortalProps): React.JSX.Element;
type DrawerOverlayProps = React.ComponentProps<"div">;
/**
* scrollsheet draws its own backdrop (`.scrollsheet-backdrop`, themeable via
* the `--scrollsheet-backdrop` CSS variable) that tracks scroll progress, so
* there's nothing for a separate overlay element to render. This accepts
* vaul's `<Drawer.Overlay className .../>` so existing JSX doesn't crash —
* restyle the backdrop through the CSS variable instead.
*/
declare function Overlay(_props: DrawerOverlayProps): null;
/**
* Whether translated `snapPoints` produced more than one distinct detent —
* feeds Content's `data-vaul-snap-points` attribute. vaul's own
* CSS-selector convention treats a single snap point the same as none
* (scrollsheet's own single-detent default also resolves to `false` here).
* Pulled out as its own pure function, mirroring `resolveFadeFromIndex` /
* `resolveCloseThreshold`, so it's unit-testable independent of rendering
* (`<Drawer.Content>`'s actual DOM output is behind a client-only mount
* gate — see content.tsx — so SSR can't observe the attribute directly).
*/
declare function hasMultipleSnapPoints(detents: readonly DetentSpec[]): boolean;
interface DrawerContentProps extends SheetContentProps {
onPointerDownOutside?: (event: CustomEvent) => void;
onOpenAutoFocus?: (event: Event) => void;
onEscapeKeyDown?: (event: KeyboardEvent) => void;
onCloseAutoFocus?: (event: Event) => void;
onInteractOutside?: (event: CustomEvent) => void;
onFocusOutside?: (event: CustomEvent) => void;
forceMount?: boolean;
}
declare const Content: React.ForwardRefExoticComponent<Omit<DrawerContentProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
/**
* Composes the click handler Handle passes to `SheetHandle`: the caller's
* own `onClick` always fires first, then — when `preventCycle` is set —
* `event.preventDefault()`, the same signal `SheetHandle`'s own
* click-to-cycle logic already checks (`if (event.defaultPrevented) return`
* in handle.tsx) to skip advancing to the next detent. Mirrors real vaul's
* `preventCycle`, read inside its own `handleCycleSnapPoints` guard. Pulled
* out as its own pure function so the composition is unit-testable without
* a live DOM/click event.
*/
declare function composeHandleClick(preventCycle: boolean | undefined, onClick: ((event: React.MouseEvent<HTMLButtonElement>) => void) | undefined): (event: React.MouseEvent<HTMLButtonElement>) => void;
interface DrawerHandleProps extends SheetHandleProps {
/**
* Suppresses the click-to-cycle-detents behavior — the handle still
* renders, drags, and is keyboard-operable as usual, but a click no
* longer advances to the next detent. Mirrors real vaul's own
* `preventCycle`, which guards the same click path (vaul's handle has no
* keyboard cycling to suppress).
*/
preventCycle?: boolean;
}
declare const Handle: React.ForwardRefExoticComponent<Omit<DrawerHandleProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
/**
* Sheet.Close's self-closing form renders a styled ✕ default; vaul's own
* `<Drawer.Close />` renders an empty unstyled button. A migrating user
* must not get a surprise icon button from a version swap, so the compat
* Close pins children to null (defined, so the styled-default branch never
* arms) unless the caller passed some.
*/
declare const DrawerClose: React.ForwardRefExoticComponent<Omit<SheetCloseProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
/** Namespace-style access matching vaul's `<Drawer.Root>…</Drawer.Root>` shape. */
declare const Drawer: {
Root: typeof Root;
NestedRoot: typeof Root;
Trigger: React.ForwardRefExoticComponent<Omit<SheetTriggerProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
Portal: typeof Portal;
Overlay: typeof Overlay;
Content: React.ForwardRefExoticComponent<Omit<DrawerContentProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
Close: React.ForwardRefExoticComponent<Omit<SheetCloseProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
Title: React.ForwardRefExoticComponent<Omit<SheetTitleProps, "ref"> & React.RefAttributes<HTMLHeadingElement>>;
Description: React.ForwardRefExoticComponent<Omit<SheetDescriptionProps, "ref"> & React.RefAttributes<HTMLParagraphElement>>;
Handle: React.ForwardRefExoticComponent<Omit<DrawerHandleProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
};
//#endregion
export { SheetHandleProps as S, composeOpenChange as _, DrawerHandleProps as a, resolveFadeFromIndex as b, DrawerPortalProps as c, NestedRoot as d, Overlay as f, composeHandleClick as g, VaulSnapPoint as h, DrawerContentProps as i, DrawerRootProps as l, Root as m, Drawer as n, DrawerNestedRootProps as o, Portal as p, DrawerClose as r, DrawerOverlayProps as s, Content as t, Handle as u, hasMultipleSnapPoints as v, Handle$1 as x, resolveCloseThreshold as y };

Sorry, the diff of this file is too big to display

import { t as Side } from "./geometry-C78uYrYJ.mjs";
import * as React from "react";
//#region packages/scrollsheet/src/internal/detents.d.ts
/**
* Detents — the stops a sheet can rest at, in the spirit of
* UISheetPresentationController's `detents`.
*
* A detent resolves to the visible height of the sheet in px.
* Accepted forms:
* - 'full' → viewport height minus top inset
* - 'medium' → 50% of viewport
* - 'content' → natural content height (measured, capped at full)
* - number in (0, 1] → fraction of viewport
* - `${number}px` → absolute pixels
*/
type DetentSpec = "full" | "medium" | "content" | number | `${number}px`;
//#endregion
//#region packages/scrollsheet/src/context.d.ts
/**
* onTravel's third argument. Reused and mutated in place every travel frame
* (see content.tsx's updateTravel) — read it synchronously, don't retain the
* reference across frames.
*/
interface TravelInfo {
/** [0, maxDetent] — the full resolved travel range in px, this frame. */
range: readonly [number, number];
/**
* 0-1 progress toward EACH configured detent, keyed by the detent's
* resolved height in px (not its spec — a spec like 'content' isn't a
* stable identity across resizes, but its resolved height this frame is
* what a consumer interpolates against).
*/
progressAtDetents: ReadonlyMap<number, number>;
}
//#endregion
//#region packages/scrollsheet/src/root.d.ts
/** Imperative escape hatch — see `actionsRef` on `<Sheet.Root>`. */
interface SheetActions {
open(): void;
close(): void;
/**
* Moves the sheet to `detent` through the same `setActiveDetent` path a
* controlled `activeDetent` change uses, so it animates identically. Warns
* in dev (once) if `detent` isn't one of this sheet's configured
* `detents` — only the panel's *rest position* resolves to the nearest
* configured detent in that case; `activeDetent`/`onActiveDetentChange`
* (and Handle's `aria-valuenow`/`aria-valuetext`) still reflect the
* literal value passed in here, not the resolved one.
*/
snapTo(detent: DetentSpec): void;
}
interface SheetRootProps {
children?: React.ReactNode;
/** Controlled open state. */
open?: boolean;
defaultOpen?: boolean;
onOpenChange?: (open: boolean) => void;
/**
* Fires once the open/close *transition* finishes (not just the state
* flip) — `true` when the sheet has fully settled open, `false` once it's
* fully closed and unmounted. Backed by the same phase timers that drive
* `data-scrollsheet-state`/`data-scrollsheet-side` (see Content) — no
* separate timer of its own.
*/
onOpenChangeComplete?: (open: boolean) => void;
/**
* Imperative escape hatch for cases a controlled `open`/`activeDetent`
* prop is awkward for (deep links, push notifications, a descendant
* component reaching up without prop-drilling): `ref.current?.close()` /
* `.open()` / `.snapTo(detent)`. Thin wrappers over the same `setOpen`/
* `setActiveDetent` the rest of the sheet uses — not a separate code path.
*/
actionsRef?: React.Ref<SheetActions>;
/**
* The stops the sheet can rest at, UISheetPresentationController-style.
* `'content'` (natural height), `'medium'` (50%), `'full'`, a 0–1 fraction,
* or `'320px'`.
* @default ['content']
*/
detents?: readonly DetentSpec[];
/** Controlled active detent (one of `detents`). */
activeDetent?: DetentSpec;
onActiveDetentChange?: (detent: DetentSpec) => void;
/**
* Master switch: when false, swipe/drag-to-dismiss, backdrop tap and Esc
* all stop closing the sheet. `escapeDismissible`/`backdropDismissible`
* below let you turn off just one of the pointer/keyboard paths while
* leaving the others (and swipe-to-dismiss, which only this prop gates)
* alone — both default to whatever `dismissible` is.
* @default true
*/
dismissible?: boolean;
/** Whether Esc closes the sheet. @default `dismissible` */
escapeDismissible?: boolean;
/** Whether tapping the backdrop/empty track closes the sheet. @default `dismissible` */
backdropDismissible?: boolean;
/** CSP nonce for the injected style tag. */
nonce?: string;
/**
* Blend the page's `<meta name="theme-color">` toward black as the sheet
* travels, so the browser chrome (iOS Safari tints both the status bar area
* and the bottom toolbar from this tag; Android tints the status bar) dims
* along with the backdrop. Every theme-color tag is dimmed from its own
* base color, so the usual media-scoped light/dark pair works and stays
* correct even if the OS scheme flips while the sheet is open. On a
* bottom-anchored attached sheet, the strip behind the bottom toolbar takes
* the panel's own color instead (via a fixed sentinel element), so the bar
* reads as part of the sheet, not the dimmed page. No-ops if no
* tag holds a parseable color, or on platforms that ignore theme-color.
* @default false
*/
themeColorDimming?: boolean;
/**
* Fires on every frame the sheet travels: revealed px, 0-1 progress
* (against the first detent, same number `--scrollsheet-progress` uses),
* and `info` — per-detent progress plus the resolved travel range. `info`
* is reused and mutated in place every frame (this is the highest-
* frequency callback in the library); read it synchronously, don't store
* the reference. Keep this handler cheap — it runs on every travel frame.
*/
onTravel?: (revealedPx: number, progress: number, info: TravelInfo) => void;
/**
* Which edge the sheet is anchored to, or `"center"` for a centered modal
* dialog: content-sized, consumer CSS owns width, zoom+fade instead of
* travel, no detents or drag. @default 'bottom'
*/
side?: Side | "center";
/**
* When false, the page behind stays fully interactive and scrollable — no
* backdrop, no focus trap, no inert-ing the rest of the page. Renders on a
* `<div popover="manual">` (top layer, non-blocking) where supported,
* falling back to a non-modal `<dialog>` (`.show()`) otherwise.
* @default true
*/
modal?: boolean;
/**
* Applies an iOS-style card effect to the page element marked
* `data-scrollsheet-background`, driven by this sheet's own travel
* progress. `'scale'` scales/insets/rounds it; `'parallax'` only shifts it.
* No-ops if no element is marked.
*
* Left unset, a full-height bottom sheet in the mobile presentation gets
* `'scale'` — that is how the platform presents a sheet that covers the
* screen. Desktop drawers dock beside the page and never move it, and a
* detached (floating-card) sheet never claims the full screen, so neither
* defaults on. `'none'` opts out anywhere.
*/
backgroundEffect?: "scale" | "parallax" | "none";
/**
* Explicit target for backgroundEffect, instead of the default document-wide
* `[data-scrollsheet-background]` query. Pass the same ref you'd otherwise
* mark with the attribute. Skips the ownership check the implicit default
* applies (an explicit ref is unambiguous about which page element it
* means) — matches how an explicit `backgroundEffect` prop today already
* bypasses the implicit default's full-height/opener-containment check.
*/
backgroundRef?: React.RefObject<HTMLElement | null>;
/**
* Inner-content-overflow scrollbar style, for the panel's own scroll at
* max detent (or any content taller than the panel). `'overlay'`: a thin
* auto-hiding thumb (iOS/macOS-style). `'hidden'`: no thumb, no native
* scrollbar either. `'native'`: let the browser draw
* its own.
* @default 'overlay'
*/
scrollbar?: "overlay" | "hidden" | "native";
/**
* Scoped to exactly two things: the backdrop's opacity and
* `themeColorDimming`. Both stay fully transparent/undimmed while the
* sheet is at or below this detent's resolved height, then interpolate
* from there up to the next detent above it (or the tallest detent, if
* this is already the tallest). Unset (default): dims across the full
* range from closed to the first detent, today's behavior. Does *not*
* affect `onTravel`'s progress argument, `--scrollsheet-progress`,
* stacking recede, or `backgroundEffect` — those all track the sheet's
* full-range travel 1:1 regardless of this prop.
*/
largestUndimmedDetent?: DetentSpec;
/**
* Drag sessions (mouse, and touch on non-modal sheets) only start from
* `<Sheet.Handle>` — a pointerdown elsewhere on the panel no-ops. Modal
* touch (native scroll drives the gesture) is restricted via
* `touch-action` instead, except at the tallest detent, where the panel's
* own content scroll still needs it.
* @default false
*/
handleOnly?: boolean;
/**
* No drag sessions at all, handle included — detent changes are
* programmatic (`activeDetent`, `actionsRef`) or click/keyboard
* (`<Sheet.Handle>`) only. Esc and backdrop dismissal are unaffected.
* @default false
*/
disableDrag?: boolean;
/**
* A release never skips over an intermediate detent — the resolved target
* is clamped to the immediate neighbor of the detent the gesture started
* from (or, for a native-scroll settle, the last settled detent).
* @default false
*/
sequentialDetents?: boolean;
/**
* Fraction (0-1) of the first detent below which a release dismisses the
* sheet, replacing the built-in half-of-first-detent rule.
* @default 0.5
*/
closeThreshold?: number;
/**
* Bottom sheets only. When the software keyboard opens with a text field
* focused inside the sheet, promote to the tallest detent so the field
* has the whole remaining viewport to scroll within — a short peek detent
* can't show a field the keyboard would otherwise cover. Restores the
* previous detent on blur unless something else (a drag, a controlled
* change) moved the sheet in between. Gated on the keyboard actually
* appearing (a nonzero measured inset), never on focus alone, so a
* desktop click into an input doesn't expand anything. Promotion goes
* through the same `setActiveDetent` path a controlled change uses — a
* controlled sheet must apply `onActiveDetentChange` back to
* `activeDetent` or this prop has no visible effect.
* @default false
*/
keyboardExpands?: boolean;
/**
* Fires once a drag session resolves, with the real pointer event (the
* `buttons===0` recovery path may pass the triggering `pointermove`
* instead of a `pointerup`) and whether the sheet is staying open —
* `false` only when the release resolved to a dismiss.
*/
onRelease?: (event: PointerEvent, willRemainOpen: boolean) => void;
}
declare function Root({ children, open: openProp, defaultOpen, onOpenChange, onOpenChangeComplete, actionsRef, detents, activeDetent: activeDetentProp, onActiveDetentChange, dismissible, escapeDismissible, backdropDismissible, nonce, themeColorDimming, onTravel, side, modal, backgroundEffect, backgroundRef, scrollbar, largestUndimmedDetent, handleOnly, disableDrag, sequentialDetents, closeThreshold, keyboardExpands, onRelease }: SheetRootProps): React.JSX.Element;
//#endregion
//#region packages/scrollsheet/src/trigger.d.ts
interface SheetTriggerProps extends React.ComponentProps<"button"> {
/**
* Render the child element instead of a `<button>`, merging the trigger's
* props (aria, onClick) into it — for custom button components or links.
*/
asChild?: boolean;
}
declare const Trigger: React.ForwardRefExoticComponent<Omit<SheetTriggerProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
//#endregion
//#region packages/scrollsheet/src/content.d.ts
interface SheetContentProps extends React.ComponentProps<"div"> {
/** Accessible name when no <Sheet.Title> is rendered. */
"aria-label"?: string;
/**
* Stretches [data-scrollsheet-body] to fill the panel (flex column, the
* body itself flex:1/min-height:0) instead of sizing to its natural content
* height. Promotes the docs "Full-height content" recipe into the library —
* write your own inner-scroll child (flex:1, overflow-y:auto) with no CSS
* of your own required on the wrapper.
* @default false
*/
fill?: boolean;
/**
* Render the child element instead of scrollsheet's own panel `<div>`,
* merging the panel's props (role, tabIndex, ref, `data-scrollsheet-*`)
* into it — matches Radix `Dialog.Content asChild`: the child replaces the
* panel outright rather than nesting inside it. The child must be a single
* non-Fragment element; whatever it was given as its own children is
* hoisted into the body wrapper below (detent measurement and stacking
* still need a real DOM descendant there regardless of which element ends
* up as the panel).
* If `children` isn't a single non-Fragment element (missing, a string,
* multiple elements, a `<>...</>` Fragment…), `asChild` is ignored for
* that render — the default panel `<div>` is used instead, with a
* one-time dev warning.
* @default false
*/
asChild?: boolean;
}
declare const Content: React.ForwardRefExoticComponent<Omit<SheetContentProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
//#endregion
//#region packages/scrollsheet/src/misc.d.ts
interface SheetTitleProps extends React.ComponentProps<"h2"> {
/** Render the child element instead of an `<h2>`, merging props into it. */
asChild?: boolean;
}
declare const Title: React.ForwardRefExoticComponent<Omit<SheetTitleProps, "ref"> & React.RefAttributes<HTMLHeadingElement>>;
interface SheetDescriptionProps extends React.ComponentProps<"p"> {
/** Render the child element instead of a `<p>`, merging props into it. */
asChild?: boolean;
}
declare const Description: React.ForwardRefExoticComponent<Omit<SheetDescriptionProps, "ref"> & React.RefAttributes<HTMLParagraphElement>>;
interface SheetCloseProps extends React.ComponentProps<"button"> {
/** Render the child element instead of a `<button>`, merging props into it. */
asChild?: boolean;
}
declare const Close: React.ForwardRefExoticComponent<Omit<SheetCloseProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
//#endregion
export { SheetTitleProps as a, SheetContentProps as c, Root as d, SheetActions as f, DetentSpec as h, SheetDescriptionProps as i, SheetTriggerProps as l, TravelInfo as m, Description as n, Title as o, SheetRootProps as p, SheetCloseProps as r, Content as s, Close as t, Trigger as u };