@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.
- package/dist/SheetPanel/default-header.d.ts +1 -3
- package/dist/SheetPanel/default-header.d.ts.map +1 -1
- package/dist/SheetPanel/sheet-panel-content.d.ts +1 -2
- package/dist/SheetPanel/sheet-panel-content.d.ts.map +1 -1
- package/dist/SheetPanel/sheet-panel-focus.d.ts.map +1 -1
- package/dist/SheetPanel/sheet-panel-handles.d.ts +3 -2
- package/dist/SheetPanel/sheet-panel-handles.d.ts.map +1 -1
- package/dist/SheetPanel/sheet-panel-types.d.ts +2 -0
- package/dist/SheetPanel/sheet-panel-types.d.ts.map +1 -1
- package/dist/SheetPanel/sheet-panel.d.ts.map +1 -1
- package/dist/SheetParts/sheet-back.d.ts +1 -1
- package/dist/SheetParts/sheet-back.d.ts.map +1 -1
- package/dist/SheetParts/sheet-body.d.ts +1 -1
- package/dist/SheetParts/sheet-body.d.ts.map +1 -1
- package/dist/SheetParts/sheet-close.d.ts +1 -1
- package/dist/SheetParts/sheet-close.d.ts.map +1 -1
- package/dist/SheetParts/sheet-description.d.ts +1 -1
- package/dist/SheetParts/sheet-description.d.ts.map +1 -1
- package/dist/SheetParts/sheet-footer.d.ts +1 -1
- package/dist/SheetParts/sheet-footer.d.ts.map +1 -1
- package/dist/SheetParts/sheet-handle.d.ts +1 -1
- package/dist/SheetParts/sheet-handle.d.ts.map +1 -1
- package/dist/SheetParts/sheet-header.d.ts +1 -1
- package/dist/SheetParts/sheet-header.d.ts.map +1 -1
- package/dist/SheetParts/sheet-part-element.d.ts +13 -0
- package/dist/SheetParts/sheet-part-element.d.ts.map +1 -0
- package/dist/SheetParts/sheet-part-types.d.ts +3 -2
- package/dist/SheetParts/sheet-part-types.d.ts.map +1 -1
- package/dist/SheetParts/sheet-parts.d.ts +8 -8
- package/dist/SheetParts/sheet-title.d.ts +1 -1
- package/dist/SheetParts/sheet-title.d.ts.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/index.mjs +293 -128
- package/dist/index.mjs.map +1 -1
- package/dist/render.d.ts +10 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/renderer-effects.d.ts +12 -0
- package/dist/renderer-effects.d.ts.map +1 -1
- package/dist/renderer-helpers.d.ts +2 -2
- package/dist/renderer-helpers.d.ts.map +1 -1
- package/dist/renderer.d.ts.map +1 -1
- package/dist/sheet-portal.d.ts +12 -0
- package/dist/sheet-portal.d.ts.map +1 -0
- package/dist/stacking.d.ts +6 -0
- package/dist/stacking.d.ts.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types.d.ts +30 -1
- package/dist/types.d.ts.map +1 -1
- 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 {
|
|
13
|
-
import
|
|
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
|
-
|
|
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:
|
|
923
|
+
height: BOTTOM_SHEET_HEIGHT,
|
|
830
924
|
left: 0,
|
|
831
|
-
maxHeight:
|
|
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
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
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
|
|
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
|
|
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, {
|
|
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
|
|
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: [
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
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(
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
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 = ({
|
|
2011
|
+
const SheetBack = ({ render, className, style, children }) => {
|
|
1879
2012
|
const { back, isNested } = useSheetPanel();
|
|
1880
2013
|
if (!isNested) return null;
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
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
|
|
1894
|
-
|
|
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 = ({
|
|
2061
|
+
const SheetClose = ({ render, className, style, children }) => {
|
|
1917
2062
|
const { close } = useSheetPanel();
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
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 = ({
|
|
2078
|
+
const SheetDescription = ({ render, className, style, children }) => {
|
|
1931
2079
|
const { panelId, registerDescription } = useSheetPanel();
|
|
1932
2080
|
useEffect(() => registerDescription(), [registerDescription]);
|
|
1933
|
-
return /* @__PURE__ */ jsx(
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
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 = ({
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
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 = ({
|
|
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
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
"
|
|
1965
|
-
|
|
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 = ({
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
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 = ({
|
|
2143
|
+
const SheetTitle = ({ render, className, style, children }) => {
|
|
1982
2144
|
const { panelId, registerTitle } = useSheetPanel();
|
|
1983
2145
|
useEffect(() => registerTitle(), [registerTitle]);
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
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
|