/** * `` — THE bottom sheet: a bottom-anchored panel whose * visible height snaps between detents, follows the finger when dragged, * rides above the keyboard, and (optionally) dims what's behind it and * drag-dismisses. No route required — place it in your own layout. * * Successor to lynx-navigation's inline `` with the route * sheet's modal features folded in; built on this package's shared * engine/pan/backdrop (`engine.ts`, `drag.ts`, `Backdrop.tsx`). * * ## How it grows without a layout reflow * The panel is a fixed-height container (as tall as the top detent) * anchored at the bottom; a `translateY` (a TRANSFORM, safe to drive from * the main thread every frame — unlike `height`) slides it down so only * the bottom `reveal` px show. Content is laid out top-aligned once and * rides up as the sheet grows — put the part that should stay pinned to * the visible top (e.g. a text input) FIRST. * * ## Pinning to the visible BOTTOM edge * Because the panel is slid down, its own bottom edge is off-screen at * every rest below the top detent — `position: absolute; bottom: 0` pins * to a place nobody can see. Pass `pinnedBottomRef` instead: the sheet * binds that element to the inverse of the slide, so it sits last in flow * yet paints flush with the bottom of the revealed slice, every frame. * Keep the body above it at full panel height, so a drag never opens a * gap between the content and the pinned row. * * ## Detents * `detents` are `DetentSpec`s (px, `{fraction}`, `{keyboard}`) resolved * live against screen height, safe-area insets, the remembered keyboard * height, and `topOffset` — see `resolveDetents`. The lowest resolved * detent is the floor. Geometry is re-resolved every render, never * snapshotted (#743): a composer floor that grows an attachment row * re-seats the parked sheet automatically. * * ## Modes * - Persistent (default): the sheet never goes below its floor — a * composer accessory. `open` toggles floor ↔ `openDetentIndex`. * - `dismissible`: releases projecting below half the floor settle at * reveal 0 and emit `dismiss`; the CONSUMER then flips `open`/unmounts. * `open: false` parks at 0 (hidden) instead of the floor. Combine with * `backdrop` for the modal tray the route sheet used to be needed for. * * ## Drag modes (mount-constant) * - `'handle'` (default): the pan attaches to the `handle` slot only — a * raw `` body keeps scrolling untouched (`lynx-list` does not * adopt the ScrollDragHost protocol yet). * - `'surface'`: the whole panel drags, arbitrating against an adopted * inner `@sigx/lynx` `` (this component provides the * ScrollDragHost) — iOS-style: drag collapses the sheet until it's at * max, then content scrolls, with the one-way handoff back. * - `'grabber'`: only the top chrome strip drags; body never does. * - `'none'`: no gesture. * * ## Backdrop & stacking * Lynx has no z-index/portal — stacking is document order. The backdrop * dims this component's positioned ancestor, so for a full-screen dim * render the sheet as the LAST child of a full-surface positioned * container. While the sheet is parked at its floor (or dismissed) the * backdrop is `display: none` and intercepts nothing. */ import { type Define, type MainThread, type MainThreadRef, type SharedValue } from '@sigx/lynx'; import { type DetentSpec } from './detents.js'; export interface BackdropOptions { /** Fully-open dim opacity. Default 0.4. */ maxOpacity?: number; /** Tap on the dim dismisses (dismissible sheets only). Default true. */ pressToDismiss?: boolean; /** * Intrinsic tag to render the dim as, instead of `'view'` — pass * `TOUCH_GUARD_TAG` from `@sigx/lynx-gestures` (`'sigx-touch-guard'`) * so the dim's native view consumes the platform touch stream and an * Android EditText underneath can't grab focus (#787). A plain string * so lynx-sheet stays pure JS; the tag requires `sigx prebuild`. */ guardTag?: string; } export type BottomSheetDragMode = 'surface' | 'handle' | 'grabber' | 'none'; export type BottomSheetProps = /** Resting heights — see `DetentSpec`. Lowest resolved detent = floor. */ Define.Prop<'detents', readonly DetentSpec[], true> /** Open ⇒ move to `openDetentIndex`; closed ⇒ floor (or 0 when dismissible). */ & Define.Prop<'open', boolean, false> /** * Animate the `open`/close move. Default `false` — JUMP instantly, so * some *other* motion (e.g. a soft keyboard sliding away) reveals the * already-painted sheet and the sheet animates nothing (the WhatsApp * dip-free reveal). User drags always animate their release snap. */ & Define.Prop<'animate', boolean, false> /** Which detent `open` targets. Default: index 1 when there is more than one, else 0. */ & Define.Prop<'openDetentIndex', number, false> /** * On open, snap to the CURRENT lifted position (`max(reveal, floor + * liftSV)`) instead of the `openDetentIndex` detent — the live keyboard * height is captured on the main thread the instant it opens, so when * the keyboard's lift animates to 0 the content does NOT move. The * captured value also becomes the low snap target for drags. Requires * `liftSV`; no-op otherwise. */ & Define.Prop<'openToLift', boolean, false> /** Gate the drag gesture (e.g. false while the keyboard owns the space). Default true. */ & Define.Prop<'dragEnabled', boolean, false> /** * External lift (px) under the collapsed reveal — pass a keyboard lift * SharedValue (`useKeyboardLiftSV()`) so the sheet rides above the * keyboard. Effective reveal is `max(reveal, floor + liftSV)`. Note: a * sheet cannot visually dismiss under an open keyboard (the lift wins * the max) — dismissible overlay sheets shouldn't pass this. */ & Define.Prop<'liftSV', SharedValue, false> /** * Drag-to-dismiss: a release projecting below half the floor settles * at reveal 0 and emits `dismiss`. The sheet only PARKS — the consumer * flips `open`/unmounts it. Default false (persistent floor). */ & Define.Prop<'dismissible', boolean, false> /** Dim behind the sheet — `true` or per-option object. Default off. */ & Define.Prop<'backdrop', boolean | BackdropOptions, false> /** Gesture attachment shape — MOUNT-CONSTANT (worklets register at setup). */ & Define.Prop<'dragMode', BottomSheetDragMode, false> /** * Px reserved above the fully-open sheet (top inset + a header it must * never slide under). Caps every resolved detent. */ & Define.Prop<'topOffset', number, false> /** * Px the sheet's BOTTOM edge sits above the true screen bottom — e.g. * `insets.bottom` when an ancestor `` * pads the gesture bar. The sheet's top is `bottomEdge - reveal`, so * without this the `topOffset` cap is measured from the wrong anchor * and the fully-open sheet slides under the header by exactly this * amount. Also anchors the surface-drag grabber-zone geometry. */ & Define.Prop<'bottomOffset', number, false> /** Receives the combined reveal SharedValue once, at setup (bind siblings to it). */ & Define.Prop<'onReveal', (sv: SharedValue) => void, false> /** * Element to pin to the sheet's VISIBLE bottom edge — a WhatsApp-style * emoji category bar, a sticky action row. * * The panel is laid out as tall as the top detent and slid DOWN by * `panelHeight - combined`, so its own bottom edge is off-screen at * every rest below the top detent: `position: absolute; bottom: 0` * pins to a place nobody can see. The sheet instead binds this element * to that same slide with `factor: -1`, cancelling it out — the * element keeps its normal place in flow (put it last) and PAINTS * flush with the bottom of the revealed slice, on the main thread, * every frame of a drag or keyboard lift. * * MOUNT-CONSTANT (the binding registers at setup). The element must * keep an identity-stable inline style — a re-emitted `SET_STYLE` * clobbers the main-thread transform until the next reveal change — * and must not carry another `translateY` binding of its own * (transform outputs concatenate). */ & Define.Prop<'pinnedBottomRef', MainThreadRef, false> /** * Fires on the BG thread when a drag settles. The payload indexes the * snap CANDIDATES — the resolved detents normally, `[floor, rest, top]` * under `openToLift` — so index 0 = floor and the last index = top in * both cases. Only the latest release emits (superseded settles bail). */ & Define.Event<'snap', number> /** * The sheet's settled reveal in px — its VISIBLE height — emitted on * the BG thread whenever it changes (mount, `open` toggle, drag * settle, dismiss). `onSnap` says *which* detent; this says *how * tall*, so content can size itself to the slice the sheet actually * shows without re-deriving detent math (the composer's emoji panel * sizes its grid from this — a body sized from the top detent instead * hangs its tail below the screen edge, #811). * * Under `openToLift` the open value is the BG approximation of the * captured lift (the `openDetentIndex` detent); the two agree once a * keyboard height has been observed. */ & Define.Event<'rest', number> /** The sheet settled dismissed (drag or backdrop tap). Consumer closes it. */ & Define.Event<'dismiss', void> /** Any tap on the enabled backdrop (fires whether or not it dismisses). */ & Define.Event<'backdropTap', void> /** * Height (px) of the always-drags chrome strip at the sheet's top edge * — the zone that claims the drag in `dragMode 'surface'` and the ONLY * zone that drags in `'grabber'` (#711: WhatsApp-style sheets drag by * a whole ~64px input row, not just a pill). Meaningless for * `'handle'`/`'none'`. MOUNT-CONSTANT (worklet capture). Default 28. */ & Define.Prop<'grabberPx', number, false> & Define.Prop<'class', string, false> & Define.Prop<'style', Record, false> /** Sheet body — laid out top-aligned in the fixed-height box. */ & Define.Slot<'default'> /** Drag-handle region (a pill, a whole input row); rendered above `default`. */ & Define.Slot<'handle'>; export declare const BottomSheet: import("@sigx/runtime-core").ComponentFactory import("@sigx/runtime-core").JSXElement | import("@sigx/runtime-core").JSXElement[] | null) | undefined; } & { handle?: (() => import("@sigx/runtime-core").JSXElement | import("@sigx/runtime-core").JSXElement[] | null) | undefined; }>; //# sourceMappingURL=BottomSheet.d.ts.map