@patternmode/stacksheet 1.3.8 → 2.0.0

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.
Files changed (49) hide show
  1. package/dist/SheetPanel/default-header.d.ts +1 -3
  2. package/dist/SheetPanel/default-header.d.ts.map +1 -1
  3. package/dist/SheetPanel/sheet-panel-content.d.ts +1 -2
  4. package/dist/SheetPanel/sheet-panel-content.d.ts.map +1 -1
  5. package/dist/SheetPanel/sheet-panel-focus.d.ts.map +1 -1
  6. package/dist/SheetPanel/sheet-panel-handles.d.ts +3 -2
  7. package/dist/SheetPanel/sheet-panel-handles.d.ts.map +1 -1
  8. package/dist/SheetPanel/sheet-panel-types.d.ts +2 -0
  9. package/dist/SheetPanel/sheet-panel-types.d.ts.map +1 -1
  10. package/dist/SheetPanel/sheet-panel.d.ts.map +1 -1
  11. package/dist/SheetParts/sheet-back.d.ts +1 -1
  12. package/dist/SheetParts/sheet-back.d.ts.map +1 -1
  13. package/dist/SheetParts/sheet-body.d.ts +1 -1
  14. package/dist/SheetParts/sheet-body.d.ts.map +1 -1
  15. package/dist/SheetParts/sheet-close.d.ts +1 -1
  16. package/dist/SheetParts/sheet-close.d.ts.map +1 -1
  17. package/dist/SheetParts/sheet-description.d.ts +1 -1
  18. package/dist/SheetParts/sheet-description.d.ts.map +1 -1
  19. package/dist/SheetParts/sheet-footer.d.ts +1 -1
  20. package/dist/SheetParts/sheet-footer.d.ts.map +1 -1
  21. package/dist/SheetParts/sheet-handle.d.ts +1 -1
  22. package/dist/SheetParts/sheet-handle.d.ts.map +1 -1
  23. package/dist/SheetParts/sheet-header.d.ts +1 -1
  24. package/dist/SheetParts/sheet-header.d.ts.map +1 -1
  25. package/dist/SheetParts/sheet-part-element.d.ts +13 -0
  26. package/dist/SheetParts/sheet-part-element.d.ts.map +1 -0
  27. package/dist/SheetParts/sheet-part-types.d.ts +3 -2
  28. package/dist/SheetParts/sheet-part-types.d.ts.map +1 -1
  29. package/dist/SheetParts/sheet-parts.d.ts +8 -8
  30. package/dist/SheetParts/sheet-title.d.ts +1 -1
  31. package/dist/SheetParts/sheet-title.d.ts.map +1 -1
  32. package/dist/config.d.ts.map +1 -1
  33. package/dist/index.mjs +293 -128
  34. package/dist/index.mjs.map +1 -1
  35. package/dist/render.d.ts +10 -0
  36. package/dist/render.d.ts.map +1 -0
  37. package/dist/renderer-effects.d.ts +12 -0
  38. package/dist/renderer-effects.d.ts.map +1 -1
  39. package/dist/renderer-helpers.d.ts +2 -2
  40. package/dist/renderer-helpers.d.ts.map +1 -1
  41. package/dist/renderer.d.ts.map +1 -1
  42. package/dist/sheet-portal.d.ts +12 -0
  43. package/dist/sheet-portal.d.ts.map +1 -0
  44. package/dist/stacking.d.ts +6 -0
  45. package/dist/stacking.d.ts.map +1 -1
  46. package/dist/styles.css +1 -1
  47. package/dist/types.d.ts +30 -1
  48. package/dist/types.d.ts.map +1 -1
  49. package/package.json +4 -6
package/dist/index.mjs CHANGED
@@ -1,5 +1,4 @@
1
1
  "use client";
2
- import { Portal } from "@radix-ui/react-portal";
3
2
  import { createContext, use, useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
4
3
  import { createStore, useStore } from "zustand";
5
4
  import { useShallow } from "zustand/react/shallow";
@@ -9,8 +8,10 @@ import { RemoveScroll } from "react-remove-scroll";
9
8
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
9
  import { FocusTrap } from "focus-trap-react";
11
10
  import { joinClassNames } from "@patternmode/system";
12
- import { Slot } from "@radix-ui/react-slot";
13
- import { Root, Scrollbar, Thumb, Viewport } from "@radix-ui/react-scroll-area";
11
+ import { createPortal } from "react-dom";
12
+ import "@base-ui/react/merge-props";
13
+ import { useRender } from "@base-ui/react/use-render";
14
+ import { ScrollArea } from "@base-ui/react/scroll-area";
14
15
  //#region src/springs.ts
15
16
  /** Strips the shared token's `type: "spring"` down to Stacksheet's numeric SpringConfig. */
16
17
  const toSpringConfig = (spring) => ({
@@ -56,10 +57,12 @@ const DEFAULT_CONFIG = {
56
57
  closeThreshold: .25,
57
58
  dismissible: true,
58
59
  drag: true,
60
+ handle: "inside",
59
61
  lockScroll: true,
60
62
  maxDepth: Number.POSITIVE_INFINITY,
61
63
  maxWidth: "90vw",
62
64
  modal: true,
65
+ repositionInputs: true,
63
66
  scaleBackgroundAmount: .97,
64
67
  shouldScaleBackground: false,
65
68
  showOverlay: true,
@@ -169,6 +172,79 @@ const useViewportHeight = (active) => {
169
172
  }, []);
170
173
  return active ? height ?? 0 : 0;
171
174
  };
175
+ /** Input types that don't summon the on-screen keyboard. */
176
+ const NON_TEXT_INPUT_TYPES = new Set([
177
+ "button",
178
+ "checkbox",
179
+ "color",
180
+ "file",
181
+ "hidden",
182
+ "image",
183
+ "radio",
184
+ "range",
185
+ "reset",
186
+ "submit"
187
+ ]);
188
+ /** True for elements whose focus raises the on-screen keyboard. */
189
+ const isEditableElement = (el) => {
190
+ if (!(el instanceof HTMLElement)) return false;
191
+ if (el.isContentEditable) return true;
192
+ if (el instanceof HTMLTextAreaElement) return true;
193
+ if (el instanceof HTMLInputElement) return !NON_TEXT_INPUT_TYPES.has(el.type);
194
+ return false;
195
+ };
196
+ /**
197
+ * Height (px) the on-screen keyboard occupies while a field inside `containerRef`
198
+ * is focused, else `0`. Derived from the gap between the layout viewport and the
199
+ * (keyboard-shrunk, possibly panned) visual viewport.
200
+ *
201
+ * Focus-gated to fields *inside the container* so unrelated `visualViewport`
202
+ * changes — Android URL-bar collapse, or typing into the page behind a
203
+ * non-modal sheet — don't move the sheet, and zero while pinch-zoomed since a
204
+ * shrunk visual viewport then isn't a keyboard. rAF-throttled because iOS
205
+ * fires many resize events over the keyboard's open animation.
206
+ */
207
+ const useKeyboardInset = (active, containerRef) => {
208
+ const [inset, setInset] = useState(0);
209
+ useEffect(() => {
210
+ const canListen = active && typeof window !== "undefined";
211
+ const container = containerRef.current;
212
+ const viewport = canListen ? window.visualViewport : void 0;
213
+ let frame = 0;
214
+ let scheduled = false;
215
+ const measure = () => {
216
+ const el = document.activeElement;
217
+ const focused = isEditableElement(el) && container !== null && container.contains(el);
218
+ const zoomed = (viewport?.scale ?? 1) > 1;
219
+ const visibleHeight = viewport?.height ?? window.innerHeight;
220
+ const gap = window.innerHeight - (viewport?.offsetTop ?? 0) - visibleHeight;
221
+ setInset(focused && !zoomed ? Math.max(0, gap) : 0);
222
+ };
223
+ const schedule = () => {
224
+ if (scheduled) return;
225
+ scheduled = true;
226
+ frame = window.requestAnimationFrame(() => {
227
+ scheduled = false;
228
+ measure();
229
+ });
230
+ };
231
+ if (canListen) {
232
+ container?.addEventListener("focusin", schedule);
233
+ container?.addEventListener("focusout", schedule);
234
+ viewport?.addEventListener("resize", schedule, { passive: true });
235
+ viewport?.addEventListener("scroll", schedule, { passive: true });
236
+ schedule();
237
+ }
238
+ return () => {
239
+ if (frame !== 0) window.cancelAnimationFrame(frame);
240
+ container?.removeEventListener("focusin", schedule);
241
+ container?.removeEventListener("focusout", schedule);
242
+ viewport?.removeEventListener("resize", schedule);
243
+ viewport?.removeEventListener("scroll", schedule);
244
+ };
245
+ }, [active, containerRef]);
246
+ return active ? inset : 0;
247
+ };
172
248
  const BODY_SCALE_TRANSITION = "transform 500ms cubic-bezier(0.32, 0.72, 0, 1), border-radius 500ms cubic-bezier(0.32, 0.72, 0, 1)";
173
249
  /** Fallback delay (transition duration + margin) if `transitionend` never fires. */
174
250
  const BODY_SCALE_RESET_FALLBACK_MS = 600;
@@ -319,14 +395,12 @@ const getSnapOffset = (snapIndex, snapHeights, panelHeight) => {
319
395
  //#region src/renderer-helpers.ts
320
396
  const EMPTY_CLASSNAMES = {
321
397
  backdrop: "",
322
- header: "",
323
398
  panel: ""
324
399
  };
325
400
  const resolveClassNames = (cn) => {
326
401
  if (!cn) return EMPTY_CLASSNAMES;
327
402
  return {
328
403
  backdrop: cn.backdrop ?? "",
329
- header: cn.header ?? "",
330
404
  panel: cn.panel ?? ""
331
405
  };
332
406
  };
@@ -358,8 +432,21 @@ const VISUAL_TWEEN = {
358
432
  const SHADOW_SM = "0px 1px 3px 0px rgba(0,0,0,0.06), 0px 6px 12px 0px rgba(0,0,0,0.06)";
359
433
  const SHADOW_LG = "0px 8px 24px 0px rgba(0,0,0,0.06), 0px 24px 48px 0px rgba(0,0,0,0.04), 0px 48px 96px 0px rgba(0,0,0,0.03)";
360
434
  const getShadow = (isNested) => isNested ? SHADOW_SM : SHADOW_LG;
361
- const buildPanelStyle = (panelStyles, isTop, hasPanelClass, isDragging) => ({
435
+ /**
436
+ * Clear the on-screen keyboard. Plain sheets stay anchored at bottom: 0 and
437
+ * pad their content up instead — the panel surface extends under the keyboard
438
+ * (and iOS Safari's floating URL-pill chrome), so no backdrop gap ever shows
439
+ * between sheet and keyboard, and measurement error hides behind the keyboard.
440
+ * Snap sheets are transform-anchored, so they lift via `bottom` (not the
441
+ * Motion `y` transform) and keep their viewport-driven sizing untouched.
442
+ */
443
+ const getKeyboardClearance = (keyboardInset, padForKeyboard) => {
444
+ if (keyboardInset <= 0) return {};
445
+ return padForKeyboard ? { paddingBottom: keyboardInset } : { bottom: keyboardInset };
446
+ };
447
+ const buildPanelStyle = (panelStyles, isTop, hasPanelClass, isDragging, keyboardInset, padForKeyboard) => ({
362
448
  ...panelStyles,
449
+ ...getKeyboardClearance(keyboardInset, padForKeyboard),
363
450
  pointerEvents: isTop ? "auto" : "none",
364
451
  ...isTop ? {} : { contain: "layout style paint" },
365
452
  ...isDragging ? { transition: "none" } : {},
@@ -742,6 +829,12 @@ const useSheetPanel = () => {
742
829
  //#endregion
743
830
  //#region src/stacking.ts
744
831
  /**
832
+ * Resting height of a bottom sheet. `dvh` tracks the dynamic viewport on iOS
833
+ * Safari (accounts for browser chrome). Shared so the keyboard-inset `calc()`
834
+ * in `buildPanelStyle` can't drift from the panel's base height.
835
+ */
836
+ const BOTTOM_SHEET_HEIGHT = "85dvh";
837
+ /**
745
838
  * Compute visual transforms for a panel at a given depth.
746
839
  * depth=0 is the top (foreground) panel.
747
840
  * Panels beyond renderThreshold are clamped to the edge position and faded out.
@@ -818,6 +911,7 @@ const getPanelStyles = (side, config, index) => {
818
911
  const base = {
819
912
  display: "flex",
820
913
  flexDirection: "column",
914
+ outline: "none",
821
915
  position: "fixed",
822
916
  transformOrigin: getTransformOrigin(side),
823
917
  willChange: "transform",
@@ -826,9 +920,9 @@ const getPanelStyles = (side, config, index) => {
826
920
  if (side === "bottom") return {
827
921
  ...base,
828
922
  bottom: 0,
829
- height: "85dvh",
923
+ height: BOTTOM_SHEET_HEIGHT,
830
924
  left: 0,
831
- maxHeight: "85dvh",
925
+ maxHeight: BOTTOM_SHEET_HEIGHT,
832
926
  right: 0
833
927
  };
834
928
  const sideStyles = side === "right" ? {
@@ -876,34 +970,25 @@ const XIcon = () => /* @__PURE__ */ jsx("svg", {
876
970
  });
877
971
  //#endregion
878
972
  //#region src/SheetPanel/default-header.tsx
879
- const DefaultHeader = ({ isNested, onBack, onClose, className }) => /* @__PURE__ */ jsxs("div", {
880
- className: `flex shrink-0 items-center justify-between px-4 pt-4 pb-2 ${className ?? ""}`,
881
- children: [/* @__PURE__ */ jsx("div", {
882
- className: "flex items-center gap-2",
883
- children: isNested && /* @__PURE__ */ jsx("button", {
884
- "aria-label": "Back",
885
- className: "flex min-h-11 min-w-11 shrink-0 cursor-pointer items-center justify-center rounded-full border-none bg-black/5 p-0 text-inherit opacity-70 transition-opacity duration-150 hover:opacity-100",
886
- onClick: onBack,
887
- type: "button",
888
- children: /* @__PURE__ */ jsx(ArrowLeftIcon, {})
889
- })
890
- }), /* @__PURE__ */ jsx("button", {
891
- "aria-label": "Close",
892
- className: "flex min-h-11 min-w-11 shrink-0 cursor-pointer items-center justify-center rounded-full border-none bg-black/5 p-0 text-inherit opacity-70 transition-opacity duration-150 hover:opacity-100",
893
- onClick: onClose,
894
- type: "button",
895
- children: /* @__PURE__ */ jsx(XIcon, {})
896
- })]
897
- });
973
+ const DefaultHeader = ({ isNested, onBack, onClose }) => /* @__PURE__ */ jsxs(Fragment, { children: [isNested && /* @__PURE__ */ jsx("button", {
974
+ "aria-label": "Back",
975
+ className: "absolute left-2 top-2 z-20 flex min-h-11 min-w-11 cursor-pointer items-center justify-center rounded-md border-none bg-transparent p-0 text-inherit opacity-50 transition-opacity duration-150 hover:opacity-100",
976
+ onClick: onBack,
977
+ type: "button",
978
+ children: /* @__PURE__ */ jsx(ArrowLeftIcon, {})
979
+ }), /* @__PURE__ */ jsx("button", {
980
+ "aria-label": "Close",
981
+ className: "absolute right-2 top-2 z-20 flex min-h-11 min-w-11 cursor-pointer items-center justify-center rounded-md border-none bg-transparent p-0 text-inherit opacity-50 transition-opacity duration-150 hover:opacity-100",
982
+ onClick: onClose,
983
+ type: "button",
984
+ children: /* @__PURE__ */ jsx(XIcon, {})
985
+ })] });
898
986
  //#endregion
899
987
  //#region src/SheetPanel/sheet-panel-content.tsx
900
- const PanelInnerContent = ({ isComposable, shouldRender, Content, data, renderHeader, headerProps, headerClassName }) => {
988
+ const PanelInnerContent = ({ isComposable, shouldRender, Content, data, renderHeader, headerProps }) => {
901
989
  if (isComposable) return shouldRender && Content !== void 0 ? /* @__PURE__ */ jsx(Content, { ...data }) : null;
902
990
  const customHeader = renderHeader !== void 0 && renderHeader !== false ? renderHeader : void 0;
903
- return /* @__PURE__ */ jsxs(Fragment, { children: [customHeader === void 0 ? /* @__PURE__ */ jsx(DefaultHeader, {
904
- ...headerProps,
905
- className: headerClassName
906
- }) : customHeader(headerProps), shouldRender && Content !== void 0 && /* @__PURE__ */ jsx("div", {
991
+ return /* @__PURE__ */ jsxs(Fragment, { children: [customHeader === void 0 ? /* @__PURE__ */ jsx(DefaultHeader, { ...headerProps }) : customHeader(headerProps), shouldRender && Content !== void 0 && /* @__PURE__ */ jsx("div", {
907
992
  className: "min-h-0 flex-1 overflow-y-auto overscroll-contain",
908
993
  "data-stacksheet-no-drag": "",
909
994
  children: /* @__PURE__ */ jsx(Content, { ...data })
@@ -913,6 +998,10 @@ PanelInnerContent.displayName = "PanelInnerContent";
913
998
  //#endregion
914
999
  //#region src/SheetPanel/sheet-panel-focus.tsx
915
1000
  const LAYERED_MODAL_SELECTORS = [
1001
+ "[role=\"dialog\"][data-open]",
1002
+ "[role=\"alertdialog\"][data-open]",
1003
+ "[data-base-ui-focus-guard]",
1004
+ "[data-slot=\"tag-selector-content\"]",
916
1005
  "[role=\"dialog\"][data-state=\"open\"]",
917
1006
  "[role=\"alertdialog\"][data-state=\"open\"]",
918
1007
  "[data-radix-popper-content-wrapper]",
@@ -955,9 +1044,9 @@ const ModalFocusTrap = ({ enabled, active, fallbackRef, children }) => {
955
1044
  };
956
1045
  //#endregion
957
1046
  //#region src/SheetPanel/sheet-panel-handles.tsx
958
- const BottomHandle = ({ onDismiss }) => /* @__PURE__ */ jsx("button", {
1047
+ const BottomHandle = ({ onDismiss, position = "inside" }) => /* @__PURE__ */ jsx("button", {
959
1048
  "aria-label": "Dismiss",
960
- className: "absolute inset-x-0 top-0 z-10 flex w-full cursor-grab touch-none items-center justify-center border-none bg-transparent pt-2.5 pb-2",
1049
+ className: joinClassNames("absolute inset-x-0 z-10 flex w-full cursor-grab touch-none items-center justify-center border-none bg-transparent text-inherit", position === "outside" ? "bottom-full pt-1 pb-2" : "top-0 pt-2.5 pb-2"),
961
1050
  "data-stacksheet-handle": "",
962
1051
  onClick: onDismiss,
963
1052
  type: "button",
@@ -1036,7 +1125,10 @@ const getOptionalSideHandle = ({ isHovered, onDismiss, show, side }) => show ? /
1036
1125
  onDismiss,
1037
1126
  side
1038
1127
  }) : null;
1039
- const getOptionalBottomHandle = (show, onDismiss) => show ? /* @__PURE__ */ jsx(BottomHandle, { onDismiss }) : null;
1128
+ const getOptionalBottomHandle = (show, onDismiss, position) => show ? /* @__PURE__ */ jsx(BottomHandle, {
1129
+ onDismiss,
1130
+ position
1131
+ }) : null;
1040
1132
  const completeOpeningAnimation = (hasEnteredRef, isTop, onOpenCompleteRef) => {
1041
1133
  if (isTop && !hasEnteredRef.current) {
1042
1134
  hasEnteredRef.current = true;
@@ -1118,7 +1210,7 @@ const useSheetPanelContext = ({ close, isNested, isTop, pop, side }, panelId) =>
1118
1210
  };
1119
1211
  };
1120
1212
  const useSheetPanelModel = (props) => {
1121
- const { item, index, depth, isTop, isNested, side, config, classNames, pop, close, snapHeights, activeSnapIndex, layout, renderHeader, slideFrom, slideTarget, spring, stackSpring } = props;
1213
+ const { item, index, depth, isTop, isNested, side, config, classNames, pop, close, snapHeights, keyboardInset, activeSnapIndex, layout, renderHeader, slideFrom, slideTarget, spring, stackSpring } = props;
1122
1214
  const panelRef = useRef(null);
1123
1215
  const [isHovered, setIsHovered] = useState(false);
1124
1216
  const measuredHeight = usePanelHeight(panelRef, snapHeights.length > 0);
@@ -1132,7 +1224,8 @@ const useSheetPanelModel = (props) => {
1132
1224
  const isComposable = resolvePanelLayout(layout, renderHeader) === "composable";
1133
1225
  const hasPanelClass = classNames.panel !== "";
1134
1226
  const dragOffset = getDragTransform(side, dragState.offset);
1135
- const panelStyle = buildPanelStyle(panelStyles, isTop, hasPanelClass, dragState.isDragging);
1227
+ const activeKeyboardInset = isTop && side === "bottom" ? keyboardInset : 0;
1228
+ const panelStyle = buildPanelStyle(panelStyles, isTop, hasPanelClass, dragState.isDragging, activeKeyboardInset, snapHeights.length === 0);
1136
1229
  const headerProps = getHeaderProps({
1137
1230
  close,
1138
1231
  isNested,
@@ -1155,7 +1248,7 @@ const useSheetPanelModel = (props) => {
1155
1248
  const animateTarget = buildAnimateTarget(slideTarget, getStackOffset(side, transform.offset), dragOffset, transform, animatedRadius, transition, snapYOffset, isTop);
1156
1249
  const initialRadius = getInitialRadius(side);
1157
1250
  const showSideHandle = isTop && side !== "bottom";
1158
- const showBottomHandle = isTop && side === "bottom";
1251
+ const showBottomHandle = isTop && side === "bottom" && !isComposable;
1159
1252
  const dismiss = isNested ? pop : close;
1160
1253
  const sideHandle = getOptionalSideHandle({
1161
1254
  isHovered,
@@ -1166,7 +1259,8 @@ const useSheetPanelModel = (props) => {
1166
1259
  return {
1167
1260
  animateTarget,
1168
1261
  ariaProps,
1169
- bottomHandle: getOptionalBottomHandle(showBottomHandle, dismiss),
1262
+ bottomHandle: getOptionalBottomHandle(showBottomHandle, dismiss, config.handle),
1263
+ bottomHandleOutside: showBottomHandle && config.handle === "outside",
1170
1264
  handleAnimationComplete,
1171
1265
  headerProps,
1172
1266
  hoverProps: getPanelHoverProps(showSideHandle, setIsHovered),
@@ -1182,7 +1276,7 @@ const useSheetPanelModel = (props) => {
1182
1276
  };
1183
1277
  const SheetPanel = (props) => {
1184
1278
  const { item, isTop, config, classNames, Content, shouldRender, renderHeader, prefersReducedMotion } = props;
1185
- const { animateTarget, ariaProps, bottomHandle, handleAnimationComplete, headerProps, hoverProps, inactivePanelProps, initialRadius, isComposable, panelContext, panelRef, panelStyle, resolvedSlideFrom, sideHandle } = useSheetPanelModel(props);
1279
+ const { animateTarget, ariaProps, bottomHandle, bottomHandleOutside, handleAnimationComplete, headerProps, hoverProps, inactivePanelProps, initialRadius, isComposable, panelContext, panelRef, panelStyle, resolvedSlideFrom, sideHandle } = useSheetPanelModel(props);
1186
1280
  const panelContent = /* @__PURE__ */ jsxs(m.div, {
1187
1281
  animate: animateTarget,
1188
1282
  className: classNames.panel || void 0,
@@ -1210,18 +1304,21 @@ const SheetPanel = (props) => {
1210
1304
  ...hoverProps,
1211
1305
  ...inactivePanelProps,
1212
1306
  ...ariaProps,
1213
- children: [sideHandle, /* @__PURE__ */ jsxs("div", {
1214
- className: "relative flex min-h-0 flex-1 flex-col overflow-hidden rounded-[inherit]",
1215
- children: [bottomHandle, /* @__PURE__ */ jsx(PanelInnerContent, {
1216
- Content,
1217
- data: item.data,
1218
- headerClassName: classNames.header || void 0,
1219
- headerProps,
1220
- isComposable,
1221
- renderHeader,
1222
- shouldRender
1223
- })]
1224
- })]
1307
+ children: [
1308
+ sideHandle,
1309
+ bottomHandleOutside ? bottomHandle : null,
1310
+ /* @__PURE__ */ jsxs("div", {
1311
+ className: "relative flex min-h-0 flex-1 flex-col overflow-hidden rounded-[inherit]",
1312
+ children: [bottomHandleOutside ? null : bottomHandle, /* @__PURE__ */ jsx(PanelInnerContent, {
1313
+ Content,
1314
+ data: item.data,
1315
+ headerProps,
1316
+ isComposable,
1317
+ renderHeader,
1318
+ shouldRender
1319
+ })]
1320
+ })
1321
+ ]
1225
1322
  }, item.id);
1226
1323
  return /* @__PURE__ */ jsx(SheetPanelContext.Provider, {
1227
1324
  value: panelContext,
@@ -1308,6 +1405,13 @@ const useSnapState = (config, isOpen, side, stack) => {
1308
1405
  snapHeights
1309
1406
  };
1310
1407
  };
1408
+ const usePanelKeyboardInset = (config, isOpen, side) => {
1409
+ const panelWrapperRef = useRef(null);
1410
+ return {
1411
+ keyboardInset: useKeyboardInset(isOpen && side === "bottom" && config.repositionInputs, panelWrapperRef),
1412
+ panelWrapperRef
1413
+ };
1414
+ };
1311
1415
  const useCloseControls = (rawClose, rawPop) => {
1312
1416
  const closeReasonRef = useRef("programmatic");
1313
1417
  const closeWith = (reason) => {
@@ -1422,6 +1526,7 @@ const SheetRenderer = ({ store, config, sheets, componentMap, classNames: classN
1422
1526
  const classNames = resolveClassNames(classNamesProp);
1423
1527
  const { activeSnapIndex, handleSnap, snapHeights } = useSnapState(config, isOpen, side, stack);
1424
1528
  const { close, closeReasonRef, closeWith, pop, popWith } = useCloseControls(rawClose, rawPop);
1529
+ const { panelWrapperRef, keyboardInset } = usePanelKeyboardInset(config, isOpen, side);
1425
1530
  useBodyScale(config, isOpen, prefersReducedMotion);
1426
1531
  useFocusRestore(isOpen);
1427
1532
  useDismissalEffects({
@@ -1467,6 +1572,7 @@ const SheetRenderer = ({ store, config, sheets, componentMap, classNames: classN
1467
1572
  }), /* @__PURE__ */ jsx(RemoveScroll, {
1468
1573
  enabled: shouldLockScroll,
1469
1574
  forwardProps: true,
1575
+ ref: panelWrapperRef,
1470
1576
  children: /* @__PURE__ */ jsx("div", {
1471
1577
  className: "pointer-events-none fixed inset-0 overflow-hidden",
1472
1578
  style: { zIndex: config.zIndex + 1 },
@@ -1488,6 +1594,7 @@ const SheetRenderer = ({ store, config, sheets, componentMap, classNames: classN
1488
1594
  isNested,
1489
1595
  isTop,
1490
1596
  item,
1597
+ keyboardInset,
1491
1598
  layout,
1492
1599
  onSnap: handleSnap,
1493
1600
  pop,
@@ -1510,6 +1617,23 @@ const SheetRenderer = ({ store, config, sheets, componentMap, classNames: classN
1510
1617
  });
1511
1618
  };
1512
1619
  //#endregion
1620
+ //#region src/sheet-portal.tsx
1621
+ /**
1622
+ * Minimal client-only portal into `document.body`. Replaces
1623
+ * `@radix-ui/react-portal` — Base UI ships no standalone portal primitive, and
1624
+ * the sheet renderer only ever runs on the client. Mounting is deferred to an
1625
+ * effect so server render and first client render agree (no hydration
1626
+ * mismatch).
1627
+ */
1628
+ const SheetPortal = ({ children }) => {
1629
+ const [mounted, setMounted] = useState(false);
1630
+ useEffect(() => {
1631
+ setMounted(true);
1632
+ }, []);
1633
+ if (!mounted || typeof document === "undefined") return null;
1634
+ return createPortal(children, document.body);
1635
+ };
1636
+ //#endregion
1513
1637
  //#region src/Store/store-args.ts
1514
1638
  const isRecord = (value) => typeof value === "object" && value !== null;
1515
1639
  const toRecord = (value) => isRecord(value) ? value : {};
@@ -1831,18 +1955,15 @@ const createStacksheet = (config) => {
1831
1955
  };
1832
1956
  const StacksheetProvider = ({ sheets, children, classNames, layout, renderHeader }) => /* @__PURE__ */ jsxs(StoreContext.Provider, {
1833
1957
  value: providerValue,
1834
- children: [children, /* @__PURE__ */ jsx(Portal, {
1835
- asChild: false,
1836
- children: /* @__PURE__ */ jsx(SheetRenderer, {
1837
- classNames,
1838
- componentMap,
1839
- config: resolved,
1840
- layout,
1841
- renderHeader,
1842
- sheets,
1843
- store
1844
- })
1845
- })]
1958
+ children: [children, /* @__PURE__ */ jsx(SheetPortal, { children: /* @__PURE__ */ jsx(SheetRenderer, {
1959
+ classNames,
1960
+ componentMap,
1961
+ config: resolved,
1962
+ layout,
1963
+ renderHeader,
1964
+ sheets,
1965
+ store
1966
+ }) })]
1846
1967
  });
1847
1968
  const useSheet = () => {
1848
1969
  const { store: s } = useStoreContext();
@@ -1874,119 +1995,163 @@ const createStacksheet = (config) => {
1874
1995
  };
1875
1996
  };
1876
1997
  //#endregion
1998
+ //#region src/SheetParts/sheet-part-element.tsx
1999
+ /**
2000
+ * Renders a sheet part through `render` (or its default tag) via Base UI's
2001
+ * `useRender`. Centralising the hook here lets each part early-return before
2002
+ * constructing the element without tripping the Rules of Hooks.
2003
+ */
2004
+ const SheetPartElement = ({ defaultTagName, props, render }) => useRender({
2005
+ defaultTagName,
2006
+ props,
2007
+ render
2008
+ });
2009
+ //#endregion
1877
2010
  //#region src/SheetParts/sheet-back.tsx
1878
- const SheetBack = ({ asChild, className, style, children }) => {
2011
+ const SheetBack = ({ render, className, style, children }) => {
1879
2012
  const { back, isNested } = useSheetPanel();
1880
2013
  if (!isNested) return null;
1881
- const isAsChild = asChild === true;
1882
- return /* @__PURE__ */ jsx(isAsChild ? Slot : "button", {
1883
- "aria-label": children === void 0 || children === null ? "Back" : void 0,
1884
- className: joinClassNames(isAsChild ? void 0 : "flex min-h-11 min-w-11 shrink-0 cursor-pointer items-center justify-center rounded-md border-none bg-transparent p-0 text-inherit opacity-60 transition-opacity duration-150 hover:opacity-100", className),
1885
- onClick: back,
1886
- style,
1887
- type: isAsChild ? void 0 : "button",
1888
- children: children ?? /* @__PURE__ */ jsx(ArrowLeftIcon, {})
2014
+ return /* @__PURE__ */ jsx(SheetPartElement, {
2015
+ defaultTagName: "button",
2016
+ props: {
2017
+ "aria-label": children === void 0 || children === null ? "Back" : void 0,
2018
+ children: children ?? /* @__PURE__ */ jsx(ArrowLeftIcon, {}),
2019
+ className: joinClassNames(render ? void 0 : "flex min-h-11 min-w-11 shrink-0 cursor-pointer items-center justify-center rounded-md border-none bg-transparent p-0 text-inherit opacity-60 transition-opacity duration-150 hover:opacity-100", className),
2020
+ onClick: back,
2021
+ style,
2022
+ type: render ? void 0 : "button"
2023
+ },
2024
+ render
1889
2025
  });
1890
2026
  };
1891
2027
  //#endregion
1892
2028
  //#region src/SheetParts/sheet-body.tsx
1893
- const SheetBody = ({ asChild, className, style, children }) => {
1894
- if (asChild === true) return /* @__PURE__ */ jsx(Slot, {
2029
+ const SheetBodyRendered = ({ render, className, style, children }) => useRender({
2030
+ props: {
2031
+ children,
1895
2032
  className: joinClassNames("relative min-h-0 flex-1", className),
1896
2033
  "data-stacksheet-no-drag": "",
2034
+ style
2035
+ },
2036
+ render
2037
+ });
2038
+ const SheetBody = ({ render, className, style, children }) => {
2039
+ if (render !== void 0) return /* @__PURE__ */ jsx(SheetBodyRendered, {
2040
+ className,
2041
+ render,
1897
2042
  style,
1898
2043
  children
1899
2044
  });
1900
- return /* @__PURE__ */ jsxs(Root, {
2045
+ return /* @__PURE__ */ jsxs(ScrollArea.Root, {
1901
2046
  className: joinClassNames("relative flex min-h-0 flex-1 flex-col overflow-hidden", className),
1902
2047
  "data-stacksheet-no-drag": "",
1903
2048
  style,
1904
- children: [/* @__PURE__ */ jsx(Viewport, {
2049
+ children: [/* @__PURE__ */ jsx(ScrollArea.Viewport, {
1905
2050
  className: "min-h-0 w-full flex-1 overscroll-contain",
1906
2051
  children
1907
- }), /* @__PURE__ */ jsx(Scrollbar, {
1908
- className: "flex w-2 touch-none select-none p-0.5",
2052
+ }), /* @__PURE__ */ jsx(ScrollArea.Scrollbar, {
2053
+ className: "flex w-2 touch-none select-none p-0.5 opacity-0 transition-opacity data-[hovering]:opacity-100 data-[scrolling]:opacity-100",
1909
2054
  orientation: "vertical",
1910
- children: /* @__PURE__ */ jsx(Thumb, { className: "relative flex-1 rounded bg-current/15" })
2055
+ children: /* @__PURE__ */ jsx(ScrollArea.Thumb, { className: "relative flex-1 rounded bg-current/15" })
1911
2056
  })]
1912
2057
  });
1913
2058
  };
1914
2059
  //#endregion
1915
2060
  //#region src/SheetParts/sheet-close.tsx
1916
- const SheetClose = ({ asChild, className, style, children }) => {
2061
+ const SheetClose = ({ render, className, style, children }) => {
1917
2062
  const { close } = useSheetPanel();
1918
- const isAsChild = asChild === true;
1919
- return /* @__PURE__ */ jsx(isAsChild ? Slot : "button", {
1920
- "aria-label": children === void 0 || children === null ? "Close" : void 0,
1921
- className: joinClassNames(isAsChild ? void 0 : "flex min-h-11 min-w-11 shrink-0 cursor-pointer items-center justify-center rounded-md border-none bg-transparent p-0 text-inherit opacity-60 transition-opacity duration-150 hover:opacity-100", className),
1922
- onClick: close,
1923
- style,
1924
- type: isAsChild ? void 0 : "button",
1925
- children: children ?? /* @__PURE__ */ jsx(XIcon, {})
2063
+ return /* @__PURE__ */ jsx(SheetPartElement, {
2064
+ defaultTagName: "button",
2065
+ props: {
2066
+ "aria-label": children === void 0 || children === null ? "Close" : void 0,
2067
+ children: children ?? /* @__PURE__ */ jsx(XIcon, {}),
2068
+ className: joinClassNames(render ? void 0 : "absolute right-2 top-2 z-20 flex min-h-11 min-w-11 cursor-pointer items-center justify-center rounded-md border-none bg-transparent p-0 text-inherit opacity-50 transition-opacity duration-150 hover:opacity-100", className),
2069
+ onClick: close,
2070
+ style,
2071
+ type: render ? void 0 : "button"
2072
+ },
2073
+ render
1926
2074
  });
1927
2075
  };
1928
2076
  //#endregion
1929
2077
  //#region src/SheetParts/sheet-description.tsx
1930
- const SheetDescription = ({ asChild, className, style, children }) => {
2078
+ const SheetDescription = ({ render, className, style, children }) => {
1931
2079
  const { panelId, registerDescription } = useSheetPanel();
1932
2080
  useEffect(() => registerDescription(), [registerDescription]);
1933
- return /* @__PURE__ */ jsx(asChild === true ? Slot : "p", {
1934
- className,
1935
- id: `${panelId}-desc`,
1936
- style,
1937
- children
2081
+ return /* @__PURE__ */ jsx(SheetPartElement, {
2082
+ defaultTagName: "p",
2083
+ props: {
2084
+ children,
2085
+ className,
2086
+ id: `${panelId}-desc`,
2087
+ style
2088
+ },
2089
+ render
1938
2090
  });
1939
2091
  };
1940
2092
  //#endregion
1941
2093
  //#region src/SheetParts/sheet-footer.tsx
1942
- const SheetFooter = ({ asChild, className, style, children }) => {
1943
- const isAsChild = asChild === true;
1944
- return /* @__PURE__ */ jsx(isAsChild ? Slot : "footer", {
1945
- className: joinClassNames(isAsChild ? "shrink-0" : "flex shrink-0 items-center gap-2 border-t px-6 py-3", className),
1946
- style,
1947
- children
2094
+ const SheetFooter = ({ render, className, style, children }) => {
2095
+ return /* @__PURE__ */ jsx(SheetPartElement, {
2096
+ defaultTagName: "footer",
2097
+ props: {
2098
+ children,
2099
+ className: joinClassNames(render ? "shrink-0" : "flex shrink-0 items-center gap-2 border-t px-6 py-3", className),
2100
+ style
2101
+ },
2102
+ render
1948
2103
  });
1949
2104
  };
1950
2105
  //#endregion
1951
2106
  //#region src/SheetParts/sheet-handle.tsx
1952
- const SheetHandle = ({ asChild, className, style, children }) => {
1953
- const { close, back, isNested } = useSheetPanel();
2107
+ const SheetHandle = ({ render, className, style, children }) => {
2108
+ const { close, back, isNested, side } = useSheetPanel();
2109
+ if (side !== "bottom") return null;
1954
2110
  const dismiss = isNested ? back : close;
1955
- const isAsChild = asChild === true;
1956
- return /* @__PURE__ */ jsx(isAsChild ? Slot : "button", {
1957
- "aria-label": "Dismiss",
1958
- className: joinClassNames(isAsChild ? void 0 : "flex shrink-0 cursor-grab touch-none items-center justify-center border-none bg-transparent pt-4 pb-1 text-inherit", className),
1959
- "data-stacksheet-handle": "",
1960
- onClick: dismiss,
1961
- style,
1962
- type: isAsChild ? void 0 : "button",
1963
- children: children ?? /* @__PURE__ */ jsx("div", {
1964
- "aria-hidden": "true",
1965
- className: "h-1 w-9 rounded-sm bg-current/25"
1966
- })
2111
+ return /* @__PURE__ */ jsx(SheetPartElement, {
2112
+ defaultTagName: "button",
2113
+ props: {
2114
+ "aria-label": "Dismiss",
2115
+ children: children ?? /* @__PURE__ */ jsx("div", {
2116
+ "aria-hidden": "true",
2117
+ className: "h-1 w-9 rounded-sm bg-current/25"
2118
+ }),
2119
+ className: joinClassNames(render ? void 0 : "flex shrink-0 cursor-grab touch-none items-center justify-center border-none bg-transparent pt-4 pb-1 text-inherit", className),
2120
+ "data-stacksheet-handle": "",
2121
+ onClick: dismiss,
2122
+ style,
2123
+ type: render ? void 0 : "button"
2124
+ },
2125
+ render
1967
2126
  });
1968
2127
  };
1969
2128
  //#endregion
1970
2129
  //#region src/SheetParts/sheet-header.tsx
1971
- const SheetHeader = ({ asChild, className, style, children }) => {
1972
- const isAsChild = asChild === true;
1973
- return /* @__PURE__ */ jsx(isAsChild ? Slot : "header", {
1974
- className: joinClassNames(isAsChild ? "shrink-0" : "flex h-14 shrink-0 items-center justify-between border-b px-6", className),
1975
- style,
1976
- children
2130
+ const SheetHeader = ({ render, className, style, children }) => {
2131
+ return /* @__PURE__ */ jsx(SheetPartElement, {
2132
+ defaultTagName: "header",
2133
+ props: {
2134
+ children,
2135
+ className: joinClassNames(render ? "shrink-0" : "flex shrink-0 items-center justify-between gap-3", className),
2136
+ style
2137
+ },
2138
+ render
1977
2139
  });
1978
2140
  };
1979
2141
  //#endregion
1980
2142
  //#region src/SheetParts/sheet-title.tsx
1981
- const SheetTitle = ({ asChild, className, style, children }) => {
2143
+ const SheetTitle = ({ render, className, style, children }) => {
1982
2144
  const { panelId, registerTitle } = useSheetPanel();
1983
2145
  useEffect(() => registerTitle(), [registerTitle]);
1984
- const isAsChild = asChild === true;
1985
- return /* @__PURE__ */ jsx(isAsChild ? Slot : "h2", {
1986
- className: joinClassNames(isAsChild ? void 0 : "font-semibold text-sm", className),
1987
- id: `${panelId}-title`,
1988
- style,
1989
- children
2146
+ return /* @__PURE__ */ jsx(SheetPartElement, {
2147
+ defaultTagName: "h2",
2148
+ props: {
2149
+ children,
2150
+ className: joinClassNames(render ? void 0 : "font-semibold text-sm", className),
2151
+ id: `${panelId}-title`,
2152
+ style
2153
+ },
2154
+ render
1990
2155
  });
1991
2156
  };
1992
2157
  //#endregion