@trackunit/react-components 1.21.13 → 1.21.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/react-components",
3
- "version": "1.21.13",
3
+ "version": "1.21.15",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
@@ -13,10 +13,10 @@
13
13
  "@floating-ui/react": "^0.26.25",
14
14
  "string-ts": "^2.0.0",
15
15
  "tailwind-merge": "^2.0.0",
16
- "@trackunit/ui-design-tokens": "1.11.93",
17
- "@trackunit/css-class-variance-utilities": "1.11.96",
18
- "@trackunit/shared-utils": "1.13.96",
19
- "@trackunit/ui-icons": "1.11.92",
16
+ "@trackunit/ui-design-tokens": "1.11.94",
17
+ "@trackunit/css-class-variance-utilities": "1.11.97",
18
+ "@trackunit/shared-utils": "1.13.97",
19
+ "@trackunit/ui-icons": "1.11.93",
20
20
  "es-toolkit": "^1.39.10",
21
21
  "@tanstack/react-virtual": "3.13.12",
22
22
  "fflate": "^0.8.2",
@@ -0,0 +1,29 @@
1
+ import { type ReactElement } from "react";
2
+ import type { SheetProps } from "./sheet-types";
3
+ /**
4
+ * Container-scoped bottom sheet with adaptive snap levels and gesture support.
5
+ *
6
+ * Use Sheet for contextual surfaces that slide up from the bottom of a
7
+ * container -- action menus, detail panels, element settings, or filters.
8
+ * Every Sheet renders via Portal into a required `container` element.
9
+ * On small screens, consider using Sheet instead of a Popover for better
10
+ * touch UX. For app-level blocking dialogs, use Modal instead (which
11
+ * automatically renders as a Sheet on small screens).
12
+ *
13
+ * When `variant="modal"`, the sheet behaves as a dialog: it renders a
14
+ * dimming backdrop, sets `role="dialog"` and `aria-modal` on the panel,
15
+ * and traps keyboard focus via FloatingFocusManager. Pass
16
+ * `trapFocus={false}` when a parent component provides its own focus
17
+ * management (e.g. Modal in sheet mode).
18
+ *
19
+ * Snap levels are adaptive: the Sheet measures the container and creates
20
+ * fewer stops in shorter containers. Consumers navigate with directional
21
+ * methods (`snapUp`, `snapDown`, `expand`, `collapse`, `dock`) instead of
22
+ * naming specific snap points.
23
+ *
24
+ * The outermost wrapper sets `container-type: size` so cqh/cqw units
25
+ * resolve against the container's dimensions. Open/close animations use
26
+ * CSS transitions on transform; the component stays mounted during the
27
+ * close animation and unmounts after the transition completes.
28
+ */
29
+ export declare const Sheet: ({ isOpen, state, snap, onGeometryChange, onSnap, onClickHandle, onCloseGesture, floatingUi, ref, anchor, snapping, resizable, variant, trapFocus, container, dockedContent, className, "data-testid": dataTestId, onCloseComplete, entryAnimation, children, }: SheetProps) => ReactElement | null;
@@ -0,0 +1,63 @@
1
+ import type { CSSProperties } from "react";
2
+ import type { SheetAnimationState } from "./sheet-animation-reducer";
3
+ import type { SheetVariant } from "./sheet-types";
4
+ /**
5
+ * Container wrapper that creates the positioning context and container query
6
+ * context for the Sheet. Sets `container-type: size` so cqh and cqw units
7
+ * resolve against this wrapper's dimensions.
8
+ *
9
+ * Always absolutely positioned within the provided container element.
10
+ */
11
+ export declare const cvaSheetContainer: (props?: ({
12
+ docked?: boolean | null | undefined;
13
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
14
+ /**
15
+ * Sheet panel — the main content surface, always positioned at the bottom
16
+ * of the container.
17
+ *
18
+ * Height and vertical positioning are controlled via inline styles returned
19
+ * by `getSheetPanelStyle()`:
20
+ *
21
+ * - `height`: Target height for the current snap (e.g., "50cqh"), or
22
+ * `--sheet-drag-height` during drag so the sheet stays anchored at the bottom
23
+ * - `transform`: translateY for entry slide-up animation
24
+ * - `transition`: separate curves for entry (transform), close (height collapse),
25
+ * and snap changes (height)
26
+ *
27
+ * During drag, the gesture hook sets `--sheet-drag-height` and `transition: none`
28
+ * directly on the element for immediate visual feedback, then restores on release.
29
+ */
30
+ export declare const cvaSheetPanel: (props?: ({
31
+ anchor?: "center" | "start" | "end" | null | undefined;
32
+ variant?: "default" | "modal" | "floating" | null | undefined;
33
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
34
+ /**
35
+ * Scrollable area below the drag handle. Nested inside the Sheet panel (Card)
36
+ * so the Card's base `overflow-clip` clips this element's scrollbar at the
37
+ * rounded corners. The handle stays fixed above, outside the scroll context.
38
+ *
39
+ * `fillHeight` controls whether the scroll area stretches to fill available
40
+ * panel space. Enabled for level/docked modes (fixed panel height); disabled
41
+ * for fit mode so that `scrollHeight` reflects natural content height rather
42
+ * than the stretched layout — critical for correct fit-height measurement.
43
+ */
44
+ export declare const cvaSheetScrollArea: (props?: ({
45
+ fillHeight?: boolean | null | undefined;
46
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
47
+ /**
48
+ * Returns inline styles for the sheet panel element.
49
+ *
50
+ * Entry animation uses a transform slide-up (translateY 100% → 0).
51
+ * Close animation is variant-aware — see `getCloseStyle` for details.
52
+ * Snap-level changes animate height while the sheet is open.
53
+ */
54
+ export declare const getSheetPanelStyle: ({ snapHeight, isOpen, closePhase, variant, autoHeight, maxHeight, isDragging, suppressTransition, }: {
55
+ readonly snapHeight: string;
56
+ readonly isOpen: boolean;
57
+ readonly closePhase: SheetAnimationState["closePhase"];
58
+ readonly variant: SheetVariant;
59
+ readonly autoHeight?: boolean;
60
+ readonly maxHeight?: string;
61
+ readonly isDragging?: boolean;
62
+ readonly suppressTransition?: boolean;
63
+ }) => CSSProperties;
@@ -0,0 +1,64 @@
1
+ import type { MouseEventHandler, ReactElement, PointerEvent as ReactPointerEvent } from "react";
2
+ /**
3
+ * Centralized visual intensity parameters for the sheet handle line.
4
+ *
5
+ * All values control the handle bar (line) color, which mixes between
6
+ * `base` and `emphasized` colors. The mixing factor is the maximum of:
7
+ * - `--sheet-dismiss-progress` (0-1, set on the panel by gesture hook or Sheet effect)
8
+ * - `--sheet-snap-proximity` * proximityWeight (approaching a snap point)
9
+ * - `--handle-interaction` (hover / dragging / pressed states below)
10
+ *
11
+ * Interaction values are set via CSS custom property on the handle div
12
+ * (Tailwind arbitrary properties + pseudo-classes). Keep these in sync
13
+ * with the CVA class strings in `cvaSheetHandle`.
14
+ */
15
+ export declare const HANDLE_VISUALS: {
16
+ readonly line: {
17
+ readonly base: "var(--color-neutral-300)";
18
+ readonly emphasized: "var(--color-neutral-600)";
19
+ readonly interaction: {
20
+ readonly idle: 0;
21
+ readonly hover: 0.15;
22
+ readonly dragging: 0.25;
23
+ readonly pressed: 0.5;
24
+ };
25
+ readonly proximityWeight: 0.5;
26
+ };
27
+ };
28
+ /**
29
+ * Props for the SheetHandle component.
30
+ */
31
+ export type SheetHandleProps = {
32
+ /** Pointer event handler from useSheetGestures. */
33
+ readonly onPointerDown?: (e: ReactPointerEvent) => void;
34
+ /** Pointer event handler from useSheetGestures. */
35
+ readonly onPointerMove?: (e: ReactPointerEvent) => void;
36
+ /** Pointer event handler from useSheetGestures. */
37
+ readonly onPointerUp?: (e: ReactPointerEvent) => void;
38
+ /** Resets drag state when the browser cancels the pointer stream (e.g. tab switch, touch interruption). */
39
+ readonly onLostPointerCapture?: () => void;
40
+ /** Click handler for cycling snap levels and modifier-based actions. */
41
+ readonly onClick?: MouseEventHandler<HTMLDivElement>;
42
+ /** Whether the handle is currently being dragged. Controls visual state. */
43
+ readonly isDragging?: boolean;
44
+ /** Mouse enter handler (from useHover). */
45
+ readonly onMouseEnter?: () => void;
46
+ /** Mouse leave handler (from useHover). */
47
+ readonly onMouseLeave?: () => void;
48
+ /** Test ID for the handle element. */
49
+ readonly "data-testid"?: string;
50
+ };
51
+ /**
52
+ * Visual drag indicator at the top of the Sheet.
53
+ *
54
+ * Renders a single handle line that narrows into a short stub as the user
55
+ * drags toward the dismiss threshold, with color fading from neutral to dark.
56
+ * The dismiss visual is driven entirely by the `--sheet-dismiss-progress`
57
+ * CSS custom property set on the parent panel element — this component
58
+ * has no dismiss-related state or logic.
59
+ *
60
+ * Accepts pointer event handlers from useSheetGestures and forwards them
61
+ * to the handle element. The element has `touch-action: none` so pointer
62
+ * events work reliably on touch devices.
63
+ */
64
+ export declare const SheetHandle: ({ onPointerDown, onPointerMove, onPointerUp, onLostPointerCapture, onClick, isDragging, onMouseEnter, onMouseLeave, "data-testid": dataTestId, }: SheetHandleProps) => ReactElement;
@@ -0,0 +1,20 @@
1
+ import type { ReactElement } from "react";
2
+ /**
3
+ * Props for the SheetOverlay component.
4
+ */
5
+ export type SheetOverlayProps = {
6
+ /** Whether the overlay is visible. */
7
+ readonly visible: boolean;
8
+ /** Test ID for the overlay element. */
9
+ readonly "data-testid"?: string;
10
+ };
11
+ /**
12
+ * Semi-transparent backdrop overlay for the Sheet.
13
+ *
14
+ * Fades in/out using CSS transitions. When not visible, the overlay is
15
+ * transparent with pointer-events disabled so the background remains
16
+ * interactable. Dismiss is handled by Floating UI's `useDismiss`
17
+ * outside-press detection — clicks on the visible overlay bubble to the
18
+ * document where `useDismiss` triggers the close flow.
19
+ */
20
+ export declare const SheetOverlay: ({ visible, "data-testid": dataTestId }: SheetOverlayProps) => ReactElement;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Animation lifecycle reducer for the Sheet component.
3
+ * Manages mount/unmount transitions and close animation phases.
4
+ *
5
+ * Since snap state no longer resets on close, Sheet reads level/sizingMode
6
+ * directly from SheetState during close animations — no need to freeze
7
+ * display values here.
8
+ */
9
+ export type SheetAnimationState = {
10
+ readonly shouldRender: boolean;
11
+ readonly visuallyOpen: boolean;
12
+ readonly closePhase: "idle" | "collapsing" | "vanishing";
13
+ readonly prevIsOpen: boolean;
14
+ };
15
+ export declare const INITIAL_ANIMATION_STATE: SheetAnimationState;
16
+ export type SheetAnimationAction = {
17
+ readonly type: "SYNC_OPEN";
18
+ readonly isOpen: boolean;
19
+ readonly skipEntryAnimation: boolean;
20
+ } | {
21
+ readonly type: "SET_VISUALLY_OPEN";
22
+ } | {
23
+ readonly type: "UNMOUNT";
24
+ } | {
25
+ readonly type: "ENSURE_RENDER";
26
+ } | {
27
+ readonly type: "START_VANISH";
28
+ };
29
+ /** Reducer managing the Sheet component's mount/unmount animation lifecycle. */
30
+ export declare const sheetAnimationReducer: (state: SheetAnimationState, action: SheetAnimationAction) => SheetAnimationState;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Recorded pointer position for velocity calculation.
3
+ */
4
+ export type PointerSample = {
5
+ readonly y: number;
6
+ readonly timestamp: number;
7
+ };
8
+ /** Minimum velocity (px/ms) to trigger directional snap selection. */
9
+ export declare const VELOCITY_THRESHOLD = 0.5;
10
+ /** Dampening factor for rubber-band effect past snap bounds. */
11
+ export declare const RUBBER_BAND_FACTOR = 0.3;
12
+ /**
13
+ * Calculates vertical velocity from pointer position history.
14
+ * Positive = downward, negative = upward.
15
+ */
16
+ export declare const calculateVelocity: (samples: ReadonlyArray<PointerSample>) => number;
17
+ /**
18
+ * Applies rubber-band dampening when the effective height exceeds snap bounds.
19
+ *
20
+ * Within [minSnap, maxSnap] the raw drag delta passes through unchanged.
21
+ * Beyond maxSnap the excess movement is dampened by RUBBER_BAND_FACTOR.
22
+ *
23
+ * Below minSnap behaviour depends on `closable`:
24
+ * - **closable = true** — the sheet follows the finger freely (clamped at 0)
25
+ * so the user can always reach the close threshold.
26
+ * - **closable = false** — rubber-band dampening is applied (same feel as
27
+ * the upward over-stretch) because the sheet cannot be dismissed.
28
+ *
29
+ * Returns the constrained effective height in pixels.
30
+ */
31
+ export declare const computeConstrainedEffectiveHeight: (rawDelta: number, activeSnapHeight: number, minSnap: number, maxSnap: number, closable: boolean) => number;
32
+ /**
33
+ * Computes how far the sheet has progressed toward dismissal (0 = at min snap, 1 = at close threshold).
34
+ *
35
+ * A dead zone below the min snap absorbs small overdrags before the dismiss
36
+ * indicator starts. Returns 0 when close is not available.
37
+ */
38
+ export declare const computeDismissProgress: (rawEffectiveHeight: number, minSnap: number, containerHeight: number, closeThresholdFraction: number, dismissDeadZonePx: number) => number;
39
+ /**
40
+ * Computes a 0-1 proximity value indicating how close the drag position is
41
+ * to the nearest snap point in the current drag direction.
42
+ *
43
+ * 1 = directly at a snap point, 0 = further than `proximityRange` away.
44
+ */
45
+ export declare const computeSnapProximity: (constrainedHeight: number, snapHeights: ReadonlyArray<number>, direction: -1 | 0 | 1, proximityRange: number) => number;
46
+ /**
47
+ * Resolves the target snap height based on effective position and velocity.
48
+ *
49
+ * - High upward velocity -> next larger snap above the current effective height
50
+ * - High downward velocity -> next smaller snap below the current effective height
51
+ * - Low velocity -> snap to the nearest point
52
+ *
53
+ * snapHeights must be sorted ascending.
54
+ */
55
+ export declare const resolveSnapTarget: (effectiveHeight: number, snapHeights: ReadonlyArray<number>, velocity: number) => number;
@@ -0,0 +1,20 @@
1
+ import type { SheetSizingMode } from "./sheet-types";
2
+ /**
3
+ * Computes the active snap height in pixels for the current display state.
4
+ *
5
+ * Docked → measured docked height.
6
+ * Fit mode with a measured height → use that height.
7
+ * Otherwise → look up the level height from the precomputed array.
8
+ */
9
+ export declare const computeActiveSnapHeight: (sizingMode: SheetSizingMode, fitHeight: number, levelHeights: ReadonlyArray<number>, displayLevel: number, dockedHeightPx?: number) => number;
10
+ /**
11
+ * Builds the set of pixel heights the gesture system snaps to.
12
+ *
13
+ * - Snapping disabled → empty array.
14
+ * - Fit mode with measured height → `[dockedHeight?, fitHeight]`.
15
+ * - Fit mode before measurement → falls back to level-based snaps.
16
+ * - Normal mode → level heights with optional docked height prepended.
17
+ *
18
+ * The returned array is always sorted ascending.
19
+ */
20
+ export declare const computeGestureSnapHeights: (effectiveSnapping: boolean, sizingMode: SheetSizingMode, fitHeight: number, levelHeights: ReadonlyArray<number>, dockingEnabled: boolean, dockedHeightPx?: number) => ReadonlyArray<number>;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Width constraint for anchored sheets (start/end positioning).
3
+ * Uses container-relative units with a pixel cap.
4
+ */
5
+ export declare const ANCHORED_SHEET_MAX_WIDTH: "min(90cqw, 420px)";
6
+ /**
7
+ * Margin from container edges for anchored sheets.
8
+ */
9
+ export declare const ANCHORED_SHEET_MARGIN: "12px";
10
+ /**
11
+ * Transition timing for snap animations — gentle deceleration with minimal overshoot.
12
+ */
13
+ export declare const SHEET_TRANSITION_DURATION_MS: 300;
14
+ export declare const SHEET_TRANSITION_DURATION: "300ms";
15
+ export declare const SHEET_TRANSITION_EASING: "cubic-bezier(0.2, 0.0, 0.0, 1.0)";
16
+ /**
17
+ * Threshold (as a fraction of container height) below the smallest snap
18
+ * at which the sheet will close on release.
19
+ */
20
+ export declare const CLOSE_THRESHOLD_FRACTION: 0.15;
21
+ /**
22
+ * Dead zone in pixels below the smallest snap before the dismiss
23
+ * visual indicator starts showing. Prevents the cross from appearing
24
+ * too early when the user barely drags past the lowest snap.
25
+ */
26
+ export declare const DISMISS_DEAD_ZONE_PX = 20;
27
+ /**
28
+ * Pixel range within which the snap-proximity visual feedback activates.
29
+ * The handle widens and darkens as the sheet approaches a snap point.
30
+ */
31
+ export declare const SNAP_PROXIMITY_RANGE_PX = 50;
32
+ /** Fallback pixel height for the docked state. */
33
+ export declare const DOCKED_HEIGHT_PX: 64;
34
+ /**
35
+ * Top margin subtracted from the largest snap level so the sheet never
36
+ * fully covers the container (leaves a small gap at the top).
37
+ */
38
+ export declare const FULL_HEIGHT_TOP_MARGIN_PX = 40;
39
+ /**
40
+ * Computes an array of snap level heights in pixels, evenly distributed
41
+ * across the available space. When docking is enabled the distribution
42
+ * starts from the docked height rather than 0, so the first level is
43
+ * always at least `MIN_SNAP_SPACING_PX` above the dock.
44
+ *
45
+ * The resulting heights are sorted ascending (smallest first).
46
+ *
47
+ * @param containerHeight - Container height in pixels.
48
+ * @param dockingEnabled - Whether docked mode is available.
49
+ * @param dockedHeightPx - Pixel height of the docked state (measured from content).
50
+ * @returns {ReadonlyArray<number>} Array of pixel heights for each snap level.
51
+ */
52
+ export declare const computeSnapLevelHeights: (containerHeight: number, dockingEnabled: boolean, dockedHeightPx?: number) => ReadonlyArray<number>;
53
+ /**
54
+ * Converts a snap level index to a CSS height string using container-relative units.
55
+ *
56
+ * When docking is enabled the height range starts from the docked height
57
+ * so the CSS value reflects the shifted distribution.
58
+ *
59
+ * @param levelIndex - Zero-based snap level index.
60
+ * @param totalLevels - Total number of snap levels.
61
+ * @param dockingEnabled - Whether docked mode is available.
62
+ * @param dockedHeightPx - Pixel height of the docked state (measured from content).
63
+ */
64
+ export declare const getSnapLevelCssHeight: (levelIndex: number, totalLevels: number, dockingEnabled: boolean, dockedHeightPx?: number) => string;
@@ -0,0 +1,209 @@
1
+ import type { UseFloatingReturn } from "@floating-ui/react";
2
+ import type { ReactNode, Ref, RefObject } from "react";
3
+ import type { UseOverlayDismissibleProps } from "../../overlay-dismissible/types";
4
+ /**
5
+ * Horizontal positioning of the bottom sheet.
6
+ * - center: Full container width (default bottom sheet)
7
+ * - start: Anchored to inline-start (left in LTR), constrained width
8
+ * - end: Anchored to inline-end (right in LTR), constrained width
9
+ */
10
+ export type SheetAnchor = "center" | "start" | "end";
11
+ /**
12
+ * Initial size when the sheet opens.
13
+ * - fit: Open at content height (fit-content). Supports dismiss via gesture and docking.
14
+ * - collapsed: Open at the smallest non-docked snap level.
15
+ * - expanded: Open at the largest snap level.
16
+ * - docked: Open in docked mode (requires dockedContent on Sheet).
17
+ */
18
+ export type SheetDefaultSize = "fit" | "collapsed" | "expanded" | "docked";
19
+ /**
20
+ * Variant controlling the sheet's appearance, backdrop, and modal behavior.
21
+ * - default: Standard panel, no backdrop, flush to container bottom, top-only rounded corners.
22
+ * - modal: Dimming backdrop that blocks background interaction, focus-trapped dialog
23
+ * with `role="dialog"` and `aria-modal`. Flush bottom, top-only rounded corners.
24
+ * - floating: No backdrop, rounded corners on all sides, offset from the container bottom (iPad slideover style).
25
+ */
26
+ export type SheetVariant = "default" | "modal" | "floating";
27
+ /**
28
+ * Entry animation behavior when the sheet opens.
29
+ * - always: Animate every open.
30
+ * - subsequent: Skip the animation on the very first open, animate thereafter.
31
+ * - never: No entry animation; the sheet appears instantly.
32
+ */
33
+ export type SheetEntryAnimation = "always" | "subsequent" | "never";
34
+ /**
35
+ * Discriminant for how the sheet determines its height.
36
+ * Prevents impossible states (e.g. fit + docked simultaneously).
37
+ */
38
+ export type SheetSizingMode = "fit" | "level" | "docked";
39
+ /**
40
+ * Unified read-only state of the Sheet.
41
+ * Does NOT reset on close — reflects the last known position.
42
+ * Replaces both the old `SheetSizeInfo` and `SheetSnapDisplay`.
43
+ */
44
+ export type SheetState = {
45
+ readonly sizingMode: SheetSizingMode;
46
+ /** Current snap level index (0 = collapsed). Excludes docked. */
47
+ readonly level: number;
48
+ /** Total number of snap levels available (excludes docked). */
49
+ readonly totalLevels: number;
50
+ /** Only meaningful when `sizingMode === "level"`. */
51
+ readonly isExpanded: boolean;
52
+ /** Only meaningful when `sizingMode === "level"`. */
53
+ readonly isCollapsed: boolean;
54
+ /**
55
+ * Measured height of the docked content in pixels. Always reflects the
56
+ * docked size — does not change when the sheet expands. Falls back to the
57
+ * default docked height (64px) before the first measurement.
58
+ * Zero when the sheet has no dockedContent.
59
+ */
60
+ readonly dockedHeight: number;
61
+ readonly levelHeights: ReadonlyArray<number>;
62
+ readonly effectiveDockedHeight: number;
63
+ };
64
+ /**
65
+ * Measurement data reported by Sheet to the snap system.
66
+ * Sent via `onGeometryChange` whenever container or content geometry changes.
67
+ */
68
+ export type SheetGeometry = {
69
+ readonly containerHeight: number;
70
+ readonly fitHeight: number;
71
+ readonly dockedHeight: number;
72
+ };
73
+ /** Navigation commands returned by `useSheetSnap` and accepted by Sheet. */
74
+ export type SheetSnapActions = {
75
+ readonly snapUp: () => void;
76
+ readonly snapDown: () => void;
77
+ readonly expand: () => void;
78
+ readonly collapse: () => void;
79
+ readonly dock: () => void;
80
+ readonly fit: () => void;
81
+ };
82
+ /**
83
+ * Floating UI props returned by `useSheet` for Sheet's focus management
84
+ * and dismiss interaction binding. Mirrors `useModal`'s `floatingUi` shape.
85
+ */
86
+ export type SheetFloatingUiProps = {
87
+ readonly refs: UseFloatingReturn["refs"];
88
+ readonly context: UseFloatingReturn["context"];
89
+ readonly getFloatingProps: (userProps?: Record<string, unknown>) => Record<string, unknown>;
90
+ };
91
+ /** Props for the useSheet hook. */
92
+ export type UseSheetProps = UseOverlayDismissibleProps & {
93
+ /**
94
+ * Variant controlling appearance and dismiss behavior.
95
+ * Outside-press dismiss is only active when `"modal"`.
96
+ * Flows through to the Sheet component via spread.
97
+ *
98
+ * @default "default"
99
+ */
100
+ readonly variant?: SheetVariant;
101
+ /** Where the sheet opens. Default: 'fit' (opens at content height). */
102
+ readonly defaultSize?: SheetDefaultSize;
103
+ /** Called when the sheet's state changes (snap level, sizing mode, etc.). */
104
+ readonly onStateChange?: (state: SheetState) => void;
105
+ /** Called when container/fit/docked geometry changes. */
106
+ readonly onGeometryChange?: (geometry: SheetGeometry) => void;
107
+ };
108
+ /** Return value of the useSheet hook. */
109
+ export type UseSheetReturnValue = {
110
+ readonly isOpen: boolean;
111
+ readonly ref: RefObject<HTMLDivElement | null>;
112
+ /** Unified sheet state. Does NOT reset on close. */
113
+ readonly state: SheetState;
114
+ /** Resolved variant, passed through to Sheet via spread. */
115
+ readonly variant: SheetVariant;
116
+ readonly open: () => void;
117
+ readonly close: () => void;
118
+ /** Snap navigation commands. */
119
+ readonly snap: SheetSnapActions;
120
+ /**
121
+ * Floating UI props for Sheet's focus management and dismiss binding.
122
+ * Sheet uses `context` for FloatingFocusManager and merges
123
+ * `refs.setFloating` onto the panel. ESC dismiss is always active;
124
+ * outside-press is only active when `variant="modal"`.
125
+ */
126
+ readonly floatingUi: SheetFloatingUiProps;
127
+ readonly onGeometryChange: (geometry: SheetGeometry, dockingEnabled: boolean) => void;
128
+ readonly onSnap: (heightPx: number, useLevelSnap: boolean) => void;
129
+ readonly onClickHandle: () => void;
130
+ /** Gesture close handler for Sheet. Undefined when dismiss.gesture is false. */
131
+ readonly onCloseGesture: (() => void) | undefined;
132
+ };
133
+ /** Return value of the useSheetSnap hook. */
134
+ export type UseSheetSnapReturn = {
135
+ readonly state: SheetState;
136
+ readonly snap: SheetSnapActions;
137
+ readonly onGeometryChange: (geometry: SheetGeometry, dockingEnabled: boolean) => void;
138
+ readonly onSnap: (heightPx: number, useLevelSnap: boolean) => void;
139
+ readonly onClickHandle: () => void;
140
+ };
141
+ /**
142
+ * Props for the Sheet component.
143
+ *
144
+ * Decoupled from UseSheetReturnValue — Sheet only declares the props it
145
+ * actually reads. Consumers spread the hook return (`{...sheet}`) and
146
+ * TypeScript allows extra properties on spread expressions.
147
+ */
148
+ export type SheetProps = {
149
+ readonly isOpen: boolean;
150
+ /** Unified sheet state from useSheetSnap. */
151
+ readonly state: SheetState;
152
+ /** Snap navigation commands from useSheetSnap. */
153
+ readonly snap: SheetSnapActions;
154
+ readonly onGeometryChange: (geometry: SheetGeometry, dockingEnabled: boolean) => void;
155
+ readonly onSnap: (heightPx: number, useLevelSnap: boolean) => void;
156
+ readonly onClickHandle: () => void;
157
+ /** Gesture close handler. Undefined when gesture dismiss is disabled. */
158
+ readonly onCloseGesture: (() => void) | undefined;
159
+ /**
160
+ * Floating UI props for focus management and dismiss interaction binding.
161
+ * Provided by `useSheet`. When omitted (e.g. Modal in sheet mode),
162
+ * Sheet skips its own focus trap and dismiss — the parent owns those.
163
+ */
164
+ readonly floatingUi?: SheetFloatingUiProps;
165
+ readonly ref?: Ref<HTMLDivElement>;
166
+ /** Horizontal positioning of the sheet. */
167
+ readonly anchor?: SheetAnchor;
168
+ /** Enable/disable drag-to-snap between levels. Default: true. */
169
+ readonly snapping?: boolean;
170
+ /** Show the drag handle and enable all resize interactions (drag, click-to-cycle). Default: true. When false, the handle is hidden and snapping is implicitly disabled. */
171
+ readonly resizable?: boolean;
172
+ /** Variant controlling appearance, backdrop, and modal behavior. When "modal", the sheet traps focus and sets dialog ARIA attributes (unless `trapFocus` is false). Default: "default". */
173
+ readonly variant?: SheetVariant;
174
+ /**
175
+ * Whether the sheet should manage its own focus trap when `variant="modal"`.
176
+ * When true (default for modal variant), the sheet activates a
177
+ * FloatingFocusManager to trap keyboard focus inside the panel.
178
+ * Set to false when a parent component (e.g. Modal) provides its own
179
+ * focus management.
180
+ *
181
+ * Ignored when variant is not "modal".
182
+ *
183
+ * @default true
184
+ */
185
+ readonly trapFocus?: boolean;
186
+ /** The container element the sheet renders into via Portal. Required. Use a callback ref (useState setter) so mounting triggers a re-render. */
187
+ readonly container: HTMLElement | null;
188
+ /** Content rendered when in docked mode. Presence enables docking behavior. */
189
+ readonly dockedContent?: ReactNode;
190
+ /** Custom class name. */
191
+ readonly className?: string;
192
+ /** Test ID for the sheet. */
193
+ readonly "data-testid"?: string;
194
+ /** Sheet content. */
195
+ readonly children?: ReactNode;
196
+ /** Called after the close CSS transition completes (transitionend on transform). */
197
+ readonly onCloseComplete?: () => void;
198
+ /**
199
+ * Controls how the sheet animates when opening.
200
+ *
201
+ * - `"always"` — slide-up animation every time the sheet opens.
202
+ * - `"subsequent"` — skip the animation on the very first open (e.g. page
203
+ * load / refresh) but animate on every subsequent open.
204
+ * - `"never"` — no entry animation; the sheet appears instantly.
205
+ *
206
+ * Default: `"subsequent"`.
207
+ */
208
+ readonly entryAnimation?: SheetEntryAnimation;
209
+ };
@@ -0,0 +1,48 @@
1
+ import type { SheetDefaultSize, SheetSizingMode } from "./sheet-types";
2
+ export type SnapState = {
3
+ readonly currentLevel: number;
4
+ readonly sizingMode: SheetSizingMode;
5
+ readonly initialSizeApplied: boolean;
6
+ readonly prevIsOpen: boolean;
7
+ readonly totalLevels: number;
8
+ readonly dockingEnabled: boolean;
9
+ readonly levelHeights: ReadonlyArray<number>;
10
+ readonly effectiveDockedHeight: number;
11
+ readonly fitHeight: number;
12
+ };
13
+ export declare const INITIAL_SNAP_STATE: SnapState;
14
+ export type SnapAction = {
15
+ readonly type: "SYNC_OPEN";
16
+ readonly isOpen: boolean;
17
+ } | {
18
+ readonly type: "SYNC_MEASUREMENTS";
19
+ readonly measurements: {
20
+ readonly fitHeight: number;
21
+ readonly dockedHeight: number;
22
+ readonly dockingEnabled: boolean;
23
+ };
24
+ readonly levelHeights: ReadonlyArray<number>;
25
+ } | {
26
+ readonly type: "APPLY_INITIAL_SIZE";
27
+ readonly defaultSize: SheetDefaultSize;
28
+ } | {
29
+ readonly type: "SNAP_UP";
30
+ } | {
31
+ readonly type: "SNAP_DOWN";
32
+ } | {
33
+ readonly type: "EXPAND";
34
+ } | {
35
+ readonly type: "COLLAPSE";
36
+ } | {
37
+ readonly type: "DOCK";
38
+ } | {
39
+ readonly type: "FIT";
40
+ } | {
41
+ readonly type: "GESTURE_SNAP";
42
+ readonly heightPx: number;
43
+ readonly useLevelSnap: boolean;
44
+ } | {
45
+ readonly type: "CYCLE_SNAP";
46
+ };
47
+ /** Reducer managing snap-level positioning and measurement-derived state. */
48
+ export declare const snapReducer: (state: SnapState, action: SnapAction) => SnapState;
@@ -0,0 +1,26 @@
1
+ import type { SheetSizingMode } from "./sheet-types";
2
+ /**
3
+ * Measures the panel's natural content height when docked and persists it for
4
+ * the next dock transition.
5
+ *
6
+ * The stored "last measured" height is used as the animation target when
7
+ * transitioning from expanded to docked, so the sheet animates smoothly to
8
+ * the correct height. If no value has been stored yet (first dock), the
9
+ * consumer falls back to DOCKED_HEIGHT_PX — no animation is acceptable.
10
+ *
11
+ * When transitioning from expanded to docked, the panel retains its previous
12
+ * height during layout, so scrollHeight returns the expanded value. We measure
13
+ * via a hidden clone (height: auto) so we never mutate the panel — the height
14
+ * transition runs without disruption.
15
+ *
16
+ * The value is intentionally NOT reset when leaving docked mode, so re-entry
17
+ * always has the correct target for the animation.
18
+ *
19
+ * Does NOT use a ResizeObserver because the gesture system manipulates the
20
+ * element's CSS height during drag, which inflates `scrollHeight` beyond the
21
+ * true content size and would create a feedback loop.
22
+ *
23
+ * Returns 0 before the first measurement. The consumer falls back to
24
+ * DOCKED_HEIGHT_PX until a positive value is available.
25
+ */
26
+ export declare const useDockedContentHeight: (element: HTMLDivElement | null, shouldRender: boolean, sizingMode: SheetSizingMode) => number;