@botiverse/raft-ui 0.95.4 → 0.96.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/CHANGELOG.md +126 -98
- package/dist/components/combobox/combobox.mjs +1 -1
- package/dist/components/combobox-select/combobox-select.mjs +1 -1
- package/dist/components/context-menu/context-menu.mjs +1 -1
- package/dist/components/dialog/dialog-sheet-context.mjs +4 -2
- package/dist/components/dialog/dialog-sheet.mjs +9 -1
- package/dist/components/dialog/dialog.mjs +22 -10
- package/dist/components/drawer/drawer.d.mts +1 -1
- package/dist/components/drawer/drawer.mjs +17 -4
- package/dist/components/dropdown-menu/dropdown-menu.mjs +1 -1
- package/dist/components/lightbox/lightbox.d.mts +1 -1
- package/dist/components/lightbox/lightbox.mjs +11 -9
- package/dist/components/notification-center/notification-center.mjs +1 -1
- package/dist/components/popover/popover.mjs +1 -1
- package/dist/components/preview-card/preview-card.mjs +1 -1
- package/dist/components/select/select.mjs +1 -1
- package/dist/components/tooltip/tooltip.mjs +1 -1
- package/dist/lib/frame-insets.mjs +56 -12
- package/dist/lib/overlay-depth.d.mts +5 -0
- package/dist/lib/overlay-depth.mjs +30 -11
- package/dist/lib/popup-boundary.d.mts +24 -0
- package/dist/lib/popup-boundary.mjs +256 -0
- package/dist/lint/no-raw-z-index.mjs +4 -4
- package/dist/lint/prefer-scroll-area.d.mts +60 -19
- package/dist/lint/prefer-scroll-area.mjs +393 -38
- package/dist/wip-exports.d.mts +5 -4
- package/dist/wip-exports.mjs +4 -3
- package/dist/wip.d.mts +2 -1
- package/package.json +1 -1
- package/skills/raft-ui-guide/SKILL.md +3 -1
- package/skills/raft-ui-guide/rules/layout.md +1 -0
|
@@ -289,7 +289,7 @@ function ComboboxSeparator({ className, ...props }) {
|
|
|
289
289
|
});
|
|
290
290
|
}
|
|
291
291
|
function ComboboxFramePositioner({ collisionBoundary, collisionPadding, container, ...props }) {
|
|
292
|
-
const collision = usePopupCollision(collisionPadding, collisionBoundary, 8, container);
|
|
292
|
+
const collision = usePopupCollision(collisionPadding, collisionBoundary, 8, container, void 0, props.anchor);
|
|
293
293
|
return /* @__PURE__ */ jsx(Combobox.Positioner, {
|
|
294
294
|
...collision,
|
|
295
295
|
...props
|
|
@@ -68,7 +68,7 @@ const PHONE_LIST_COLLISION = {
|
|
|
68
68
|
fallbackAxisSide: "none"
|
|
69
69
|
};
|
|
70
70
|
function ComboboxSelectFramePositioner({ collisionBoundary, collisionPadding, container, ...props }) {
|
|
71
|
-
const collision = usePopupCollision(collisionPadding, collisionBoundary, 8, container);
|
|
71
|
+
const collision = usePopupCollision(collisionPadding, collisionBoundary, 8, container, void 0, props.anchor);
|
|
72
72
|
return /* @__PURE__ */ jsx(Combobox.Positioner, {
|
|
73
73
|
...collision,
|
|
74
74
|
...props
|
|
@@ -85,7 +85,7 @@ function ContextMenuPortal(props) {
|
|
|
85
85
|
});
|
|
86
86
|
}
|
|
87
87
|
function ContextMenuFramePositioner({ collisionBoundary, collisionPadding, container, ...props }) {
|
|
88
|
-
const collision = usePopupCollision(collisionPadding, collisionBoundary, 8, container);
|
|
88
|
+
const collision = usePopupCollision(collisionPadding, collisionBoundary, 8, container, void 0, props.anchor);
|
|
89
89
|
return /* @__PURE__ */ jsx(ContextMenu.Positioner, {
|
|
90
90
|
...collision,
|
|
91
91
|
...props
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PopupSheetReset } from "../../lib/popup-boundary.mjs";
|
|
1
2
|
import { tv } from "../../lib/tv.mjs";
|
|
2
3
|
import { createContext, createElement, useContext } from "react";
|
|
3
4
|
// src/components/dialog/dialog-sheet-context.ts
|
|
@@ -25,10 +26,11 @@ function useInDialogSheetHead() {
|
|
|
25
26
|
/**
|
|
26
27
|
* Every overlay surface starts outside any sheet: a confirm, a dialog or a
|
|
27
28
|
* drawer opened from inside a sheet keeps its own head, footer and commit
|
|
28
|
-
* button instead of taking the sheet's
|
|
29
|
+
* button instead of taking the sheet's, and its popups aren't kept inside the
|
|
30
|
+
* sheet below it (PopupSheetContext).
|
|
29
31
|
*/
|
|
30
32
|
function DialogSheetReset({ children }) {
|
|
31
|
-
return createElement(DialogSheetSurfaceContext.Provider, { value: false }, createElement(DialogSheetContext.Provider, { value: NO_SHEET }, createElement(DialogSheetHeadContext.Provider, { value: false }, children)));
|
|
33
|
+
return createElement(DialogSheetSurfaceContext.Provider, { value: false }, createElement(DialogSheetContext.Provider, { value: NO_SHEET }, createElement(DialogSheetHeadContext.Provider, { value: false }, createElement(PopupSheetReset, null, children))));
|
|
32
34
|
}
|
|
33
35
|
/**
|
|
34
36
|
* A dialog drawn as a phone sheet (design: dialog.md "On a phone"). The
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { cn } from "../../cn.mjs";
|
|
2
|
+
import { PopupSheetContext } from "../../lib/popup-boundary.mjs";
|
|
2
3
|
import { useMobileViewport } from "../../lib/use-mobile-viewport.mjs";
|
|
3
4
|
import { useSoftKeyboard } from "../../lib/use-soft-keyboard.mjs";
|
|
4
5
|
import { Button } from "../button/button.mjs";
|
|
@@ -40,6 +41,10 @@ function DialogSheetContent({ className, overlay, placement, style, ref, childre
|
|
|
40
41
|
setPopup(element);
|
|
41
42
|
assignRef(ref, element);
|
|
42
43
|
}, [ref]);
|
|
44
|
+
const popupSheet = useMemo(() => phone && popup ? {
|
|
45
|
+
head: () => popup.querySelector("[data-slot=\"dialog-header\"]"),
|
|
46
|
+
surface: () => popup
|
|
47
|
+
} : void 0, [phone, popup]);
|
|
43
48
|
const sheet = dialogSheet();
|
|
44
49
|
const sheetOverlay = useMemo(() => overlay === false ? false : {
|
|
45
50
|
...typeof overlay === "object" ? overlay : null,
|
|
@@ -65,7 +70,10 @@ function DialogSheetContent({ className, overlay, placement, style, ref, childre
|
|
|
65
70
|
"data-sheet": phone ? "" : void 0,
|
|
66
71
|
children: /* @__PURE__ */ jsx(DialogSheetContext.Provider, {
|
|
67
72
|
value: state,
|
|
68
|
-
children
|
|
73
|
+
children: /* @__PURE__ */ jsx(PopupSheetContext.Provider, {
|
|
74
|
+
value: popupSheet,
|
|
75
|
+
children
|
|
76
|
+
})
|
|
69
77
|
})
|
|
70
78
|
})
|
|
71
79
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { cn } from "../../cn.mjs";
|
|
2
2
|
import { barButtonDesktop, headEdgePull } from "../../lib/head-edges.mjs";
|
|
3
3
|
import { decisionWidth } from "../../lib/decision-width.mjs";
|
|
4
|
+
import { PopupSheetReset } from "../../lib/popup-boundary.mjs";
|
|
4
5
|
import { ScrollArea } from "../scroll-area/scroll-area.mjs";
|
|
5
6
|
import { stackedActions, useStackedActions } from "../../lib/stacked-actions.mjs";
|
|
6
7
|
import { tv } from "../../lib/tv.mjs";
|
|
@@ -23,6 +24,7 @@ import { createPortal } from "react-dom";
|
|
|
23
24
|
const dialog = tv({
|
|
24
25
|
slots: {
|
|
25
26
|
overlay: ["fixed inset-0 z-dialog bg-layer-backdrop"],
|
|
27
|
+
portal: [],
|
|
26
28
|
content: [
|
|
27
29
|
"fixed top-1/2 left-1/2 z-dialog flex w-[calc(100%-2rem)] -translate-1/2 flex-col",
|
|
28
30
|
"max-h-[calc(100dvh-2rem)] outline-none",
|
|
@@ -84,9 +86,13 @@ const dialog = tv({
|
|
|
84
86
|
},
|
|
85
87
|
1: {
|
|
86
88
|
overlay: ["z-dialog-layer"],
|
|
87
|
-
content: [
|
|
89
|
+
content: []
|
|
88
90
|
}
|
|
89
91
|
},
|
|
92
|
+
lifted: {
|
|
93
|
+
false: {},
|
|
94
|
+
true: { portal: ["relative isolate z-dialog-layer"] }
|
|
95
|
+
},
|
|
90
96
|
nested: {
|
|
91
97
|
false: {},
|
|
92
98
|
true: { overlay: ["bg-layer-backdrop-nested"] }
|
|
@@ -141,19 +147,25 @@ function DialogContent({ className, children, portalProps, overlay, size, layer,
|
|
|
141
147
|
const sheetSurface = useContext(DialogSheetSurfaceContext);
|
|
142
148
|
const [keyboard, setKeyboard] = useState(null);
|
|
143
149
|
const raised = !sheetSurface && keyboard != null;
|
|
144
|
-
const
|
|
150
|
+
const lifted = layer === 1;
|
|
151
|
+
const { content, portal } = dialog({
|
|
145
152
|
size,
|
|
146
|
-
layer,
|
|
147
153
|
placement,
|
|
154
|
+
lifted,
|
|
148
155
|
keyboard: raised || void 0
|
|
149
156
|
});
|
|
150
|
-
const
|
|
157
|
+
const { layer: overlayLayer, ...overlayRest } = typeof overlay === "object" ? overlay : {};
|
|
158
|
+
const overlayProps = lifted ? overlayRest : {
|
|
159
|
+
layer: overlayLayer,
|
|
160
|
+
...overlayRest
|
|
161
|
+
};
|
|
162
|
+
const portalClassName = portalProps?.className;
|
|
163
|
+
const liftedPortalClassName = typeof portalClassName === "function" ? (state) => portal({ className: portalClassName(state) }) : portal({ className: portalClassName });
|
|
151
164
|
return /* @__PURE__ */ jsxs(DialogPortal, {
|
|
152
165
|
...portalProps,
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}), /* @__PURE__ */ jsxs(Dialog.Popup, {
|
|
166
|
+
"data-layer": lifted ? 1 : void 0,
|
|
167
|
+
className: lifted ? liftedPortalClassName : portalClassName,
|
|
168
|
+
children: [overlay === false ? null : /* @__PURE__ */ jsx(DialogOverlay, { ...overlayProps }), /* @__PURE__ */ jsxs(Dialog.Popup, {
|
|
157
169
|
"data-slot": "dialog-content",
|
|
158
170
|
"data-placement": top ? "top" : void 0,
|
|
159
171
|
"data-soft-keyboard": raised ? "" : void 0,
|
|
@@ -255,9 +267,9 @@ function DialogHeader({ className, bar = false, children, ...props }) {
|
|
|
255
267
|
"data-sheet": "",
|
|
256
268
|
className: dialog({ bar: false }).header({ className: cn(head.header(), className) }),
|
|
257
269
|
...props,
|
|
258
|
-
children: [/* @__PURE__ */
|
|
270
|
+
children: [/* @__PURE__ */ jsx(DialogSheetHeadContext.Provider, {
|
|
259
271
|
value: true,
|
|
260
|
-
children: [backs.length > 0 ? backs : closes, rest]
|
|
272
|
+
children: /* @__PURE__ */ jsxs(PopupSheetReset, { children: [backs.length > 0 ? backs : closes, rest] })
|
|
261
273
|
}), /* @__PURE__ */ jsx("div", {
|
|
262
274
|
ref: setCommitSlot,
|
|
263
275
|
"data-slot": "dialog-commit-slot",
|
|
@@ -18,7 +18,7 @@ export declare function DrawerContent({ className, children, portalProps, ...pro
|
|
|
18
18
|
export type DrawerCloseProps = StyledProps<Drawer.Close.Props> & Pick<ButtonProps, "variant" | "size">;
|
|
19
19
|
export declare function DrawerClose({ className, variant, size, children, render, "aria-label": ariaLabel, ...props }: DrawerCloseProps): import("react").JSX.Element;
|
|
20
20
|
export type DrawerHeaderProps = useRender.ComponentProps<"div">;
|
|
21
|
-
export declare function DrawerHeader({ className, render, ...props }: DrawerHeaderProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
21
|
+
export declare function DrawerHeader({ className, render, children, ...props }: DrawerHeaderProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
22
22
|
export type DrawerFooterProps = useRender.ComponentProps<"div">;
|
|
23
23
|
export declare function DrawerFooter({ className, render, ...props }: DrawerFooterProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
24
24
|
export type DrawerBodyProps = useRender.ComponentProps<"div">;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { cn } from "../../cn.mjs";
|
|
2
|
+
import { PopupSheetContext, PopupSheetReset } from "../../lib/popup-boundary.mjs";
|
|
2
3
|
import { useThemeScopeProps } from "../../lib/theme/theme-scope.mjs";
|
|
3
4
|
import { Button } from "../button/button.mjs";
|
|
4
5
|
import { DialogSheetReset } from "../dialog/dialog-sheet-context.mjs";
|
|
5
6
|
import { drawer } from "./drawer.recipe.mjs";
|
|
6
7
|
import { useParentOverlayKind, withOverlayLayer } from "../../lib/overlay-depth.mjs";
|
|
7
8
|
import { X } from "lucide-react";
|
|
8
|
-
import { createContext, useContext, useMemo } from "react";
|
|
9
|
+
import { createContext, useContext, useMemo, useState } from "react";
|
|
9
10
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
11
|
import { mergeProps } from "@base-ui/react/merge-props";
|
|
11
12
|
import { useRender } from "@base-ui/react/use-render";
|
|
@@ -89,6 +90,11 @@ function DrawerContent({ className, children, portalProps, ...props }) {
|
|
|
89
90
|
axis: swipeAxis,
|
|
90
91
|
snapPoints: hasSnapPoints
|
|
91
92
|
});
|
|
93
|
+
const [surface, setSurface] = useState(null);
|
|
94
|
+
const popupSheet = useMemo(() => surface ? {
|
|
95
|
+
head: () => surface.querySelector("[data-slot=\"drawer-header\"]"),
|
|
96
|
+
surface: () => surface
|
|
97
|
+
} : void 0, [surface]);
|
|
92
98
|
return /* @__PURE__ */ jsxs(DrawerPortal, {
|
|
93
99
|
...portalProps,
|
|
94
100
|
children: [modal === true ? /* @__PURE__ */ jsx(DrawerOverlay, {}) : null, /* @__PURE__ */ jsx(Drawer.Viewport, {
|
|
@@ -99,9 +105,13 @@ function DrawerContent({ className, children, portalProps, ...props }) {
|
|
|
99
105
|
className: popup({ className }),
|
|
100
106
|
...props,
|
|
101
107
|
children: [hasSnapPoints || swipeDirection === "down" ? /* @__PURE__ */ jsx(DrawerSwipeHandle, {}) : null, /* @__PURE__ */ jsx(Drawer.Content, {
|
|
108
|
+
ref: setSurface,
|
|
102
109
|
"data-slot": "drawer-content",
|
|
103
110
|
className: content(),
|
|
104
|
-
children: /* @__PURE__ */ jsx(DialogSheetReset, { children
|
|
111
|
+
children: /* @__PURE__ */ jsx(DialogSheetReset, { children: /* @__PURE__ */ jsx(PopupSheetContext.Provider, {
|
|
112
|
+
value: popupSheet,
|
|
113
|
+
children
|
|
114
|
+
}) })
|
|
105
115
|
})]
|
|
106
116
|
})
|
|
107
117
|
})]
|
|
@@ -130,14 +140,17 @@ function DrawerClose({ className, variant, size, children, render, "aria-label":
|
|
|
130
140
|
children: children ?? /* @__PURE__ */ jsx(X, { className: closeIcon() })
|
|
131
141
|
});
|
|
132
142
|
}
|
|
133
|
-
function DrawerHeader({ className, render, ...props }) {
|
|
143
|
+
function DrawerHeader({ className, render, children, ...props }) {
|
|
134
144
|
const { hasSnapPoints, swipeAxis, swipeDirection } = useDrawerConfig();
|
|
135
145
|
const { header } = drawer({
|
|
136
146
|
side: swipeDirection,
|
|
137
147
|
axis: swipeAxis,
|
|
138
148
|
snapPoints: hasSnapPoints
|
|
139
149
|
});
|
|
140
|
-
const defaultProps = {
|
|
150
|
+
const defaultProps = {
|
|
151
|
+
className: header({ className }),
|
|
152
|
+
children: /* @__PURE__ */ jsx(PopupSheetReset, { children })
|
|
153
|
+
};
|
|
141
154
|
return useRender({
|
|
142
155
|
defaultTagName: "div",
|
|
143
156
|
render,
|
|
@@ -101,7 +101,7 @@ function DropdownMenuPopup({ className, ...props }) {
|
|
|
101
101
|
});
|
|
102
102
|
}
|
|
103
103
|
function DropdownMenuFramePositioner({ collisionBoundary, collisionPadding, container, ...props }) {
|
|
104
|
-
const collision = usePopupCollision(collisionPadding, collisionBoundary, 5, container);
|
|
104
|
+
const collision = usePopupCollision(collisionPadding, collisionBoundary, 5, container, void 0, props.anchor);
|
|
105
105
|
return /* @__PURE__ */ jsx(Menu.Positioner, {
|
|
106
106
|
...collision,
|
|
107
107
|
...props
|
|
@@ -61,7 +61,7 @@ export type LightboxContentProps = StyledProps<Dialog.Popup.Props>;
|
|
|
61
61
|
* A lightbox opened from state passes `finalFocus={openerRef}` so focus goes
|
|
62
62
|
* back to what opened it.
|
|
63
63
|
*/
|
|
64
|
-
export declare function LightboxContent({ className, initialFocus, finalFocus, onKeyDown, ref, ...props }: LightboxContentProps): import("react").JSX.Element;
|
|
64
|
+
export declare function LightboxContent({ className, initialFocus, finalFocus, onKeyDown, ref, children, ...props }: LightboxContentProps): import("react").JSX.Element;
|
|
65
65
|
export type LightboxHeaderProps = useRender.ComponentProps<"div">;
|
|
66
66
|
export declare function LightboxHeader({ render, className, ...props }: LightboxHeaderProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
67
67
|
export type LightboxToolbarProps = useRender.ComponentProps<"div">;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { cn } from "../../cn.mjs";
|
|
2
2
|
import { headEdgePull } from "../../lib/head-edges.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { OverlayLayer, useParentOverlayKind } from "../../lib/overlay-depth.mjs";
|
|
4
|
+
import { PopupSheetReset } from "../../lib/popup-boundary.mjs";
|
|
4
5
|
import { tv } from "../../lib/tv.mjs";
|
|
5
6
|
import { useMergedRef } from "../../lib/use-merged-ref.mjs";
|
|
6
7
|
import { useThemeScopeProps } from "../../lib/theme/theme-scope.mjs";
|
|
@@ -25,9 +26,9 @@ const lightbox = tv({
|
|
|
25
26
|
header: [
|
|
26
27
|
"pointer-events-auto shrink-0",
|
|
27
28
|
"flex items-center gap-3 pb-3",
|
|
28
|
-
"pt-[calc(env(safe-area-inset-top,0px)
|
|
29
|
-
"pl-[calc(env(safe-area-inset-left,0px)
|
|
30
|
-
"pr-[calc(env(safe-area-inset-right,0px)
|
|
29
|
+
"pt-[calc(env(safe-area-inset-top,0px)+(--spacing(3)))]",
|
|
30
|
+
"pl-[calc(env(safe-area-inset-left,0px)+(--spacing(4)))]",
|
|
31
|
+
"pr-[calc(env(safe-area-inset-right,0px)+(--spacing(4)))]",
|
|
31
32
|
"bg-transparent shadow-none",
|
|
32
33
|
headEdgePull
|
|
33
34
|
],
|
|
@@ -71,7 +72,7 @@ const lightbox = tv({
|
|
|
71
72
|
"focus-visible:outline-transparent focus-visible:shadow-focus",
|
|
72
73
|
"[&_svg]:size-5.5"
|
|
73
74
|
],
|
|
74
|
-
infoDock: ["pointer-events-none absolute
|
|
75
|
+
infoDock: ["pointer-events-none absolute inset-x-6 bottom-20 z-raised flex justify-center"],
|
|
75
76
|
infoBar: [
|
|
76
77
|
"pointer-events-auto flex items-center",
|
|
77
78
|
"w-[clamp(144px,20vw,224px)] max-w-[calc(100vw-48px)] min-w-36",
|
|
@@ -87,7 +88,7 @@ const lightbox = tv({
|
|
|
87
88
|
"*:data-[slot=button]:after:inset-x-[-3px] *:data-[slot=button]:first:after:left-[-9.5px] *:data-[slot=button]:last:after:right-[-9.5px]",
|
|
88
89
|
"pointer-coarse:*:data-[slot=button]:after:inset-y-[-9.5px]"
|
|
89
90
|
],
|
|
90
|
-
thumbnailStrip: ["pointer-events-none absolute
|
|
91
|
+
thumbnailStrip: ["pointer-events-none absolute inset-x-0 bottom-4 z-raised", "flex items-center justify-center gap-1.5"],
|
|
91
92
|
thumbnail: [
|
|
92
93
|
"group/lightbox-thumb pointer-events-auto relative size-12 overflow-hidden",
|
|
93
94
|
"transition-[border-color,opacity,box-shadow] duration-150",
|
|
@@ -127,7 +128,7 @@ function Lightbox({ closeOnModW = true, defaultOpen = false, modal = true, onOpe
|
|
|
127
128
|
onOpenChange: handleOpenChange,
|
|
128
129
|
open,
|
|
129
130
|
...props,
|
|
130
|
-
children: [modWClose, /* @__PURE__ */ jsx(
|
|
131
|
+
children: [modWClose, /* @__PURE__ */ jsx(OverlayLayer, {
|
|
131
132
|
kind: "dialog",
|
|
132
133
|
modal: true,
|
|
133
134
|
children: /* @__PURE__ */ jsx(LightboxModalContext.Provider, {
|
|
@@ -281,7 +282,7 @@ function routeScrollKey(event) {
|
|
|
281
282
|
* A lightbox opened from state passes `finalFocus={openerRef}` so focus goes
|
|
282
283
|
* back to what opened it.
|
|
283
284
|
*/
|
|
284
|
-
function LightboxContent({ className, initialFocus, finalFocus, onKeyDown, ref, ...props }) {
|
|
285
|
+
function LightboxContent({ className, initialFocus, finalFocus, onKeyDown, ref, children, ...props }) {
|
|
285
286
|
const documentMode = useContext(LightboxModalContext) === "trap-focus";
|
|
286
287
|
const nested = useParentOverlayKind() != null;
|
|
287
288
|
const { backdrop, content } = lightbox({ nested });
|
|
@@ -307,7 +308,8 @@ function LightboxContent({ className, initialFocus, finalFocus, onKeyDown, ref,
|
|
|
307
308
|
finalFocus: finalFocus ?? documentMode,
|
|
308
309
|
className: content({ className }),
|
|
309
310
|
onKeyDown: handleKeyDown,
|
|
310
|
-
...props
|
|
311
|
+
...props,
|
|
312
|
+
children: /* @__PURE__ */ jsx(PopupSheetReset, { children })
|
|
311
313
|
})] });
|
|
312
314
|
}
|
|
313
315
|
function LightboxHeader({ render, className, ...props }) {
|
|
@@ -137,7 +137,7 @@ function NotificationCenterPortal(props) {
|
|
|
137
137
|
});
|
|
138
138
|
}
|
|
139
139
|
function NotificationCenterFramePositioner({ collisionBoundary, collisionPadding, container, defaultCollisionPadding, ...props }) {
|
|
140
|
-
const collision = usePopupCollision(collisionPadding, collisionBoundary, defaultCollisionPadding, container);
|
|
140
|
+
const collision = usePopupCollision(collisionPadding, collisionBoundary, defaultCollisionPadding, container, void 0, props.anchor);
|
|
141
141
|
return /* @__PURE__ */ jsx(Popover.Positioner, {
|
|
142
142
|
...collision,
|
|
143
143
|
...props
|
|
@@ -71,7 +71,7 @@ function PopoverPopup({ className, ...props }) {
|
|
|
71
71
|
});
|
|
72
72
|
}
|
|
73
73
|
function PopoverFramePositioner({ collisionBoundary, collisionPadding, container, ...props }) {
|
|
74
|
-
const collision = usePopupCollision(collisionPadding, collisionBoundary, 5, container);
|
|
74
|
+
const collision = usePopupCollision(collisionPadding, collisionBoundary, 5, container, void 0, props.anchor);
|
|
75
75
|
return /* @__PURE__ */ jsx(Popover.Positioner, {
|
|
76
76
|
...collision,
|
|
77
77
|
...props
|
|
@@ -70,7 +70,7 @@ function PreviewCardPopup({ className, ...props }) {
|
|
|
70
70
|
});
|
|
71
71
|
}
|
|
72
72
|
function PreviewCardFramePositioner({ collisionBoundary, collisionPadding, container, ...props }) {
|
|
73
|
-
const collision = usePopupCollision(collisionPadding, collisionBoundary, 5, container);
|
|
73
|
+
const collision = usePopupCollision(collisionPadding, collisionBoundary, 5, container, void 0, props.anchor);
|
|
74
74
|
return /* @__PURE__ */ jsx(PreviewCard.Positioner, {
|
|
75
75
|
...collision,
|
|
76
76
|
...props
|
|
@@ -344,7 +344,7 @@ function SelectPopup({ className, ...props }) {
|
|
|
344
344
|
});
|
|
345
345
|
}
|
|
346
346
|
function SelectFramePositioner({ collisionBoundary, collisionPadding, container, ...props }) {
|
|
347
|
-
const collision = usePopupCollision(collisionPadding, collisionBoundary, 8, container);
|
|
347
|
+
const collision = usePopupCollision(collisionPadding, collisionBoundary, 8, container, void 0, props.anchor);
|
|
348
348
|
return /* @__PURE__ */ jsx(Select.Positioner, {
|
|
349
349
|
...collision,
|
|
350
350
|
...props
|
|
@@ -169,7 +169,7 @@ const CONTENT_COLLISION_AVOIDANCE = {
|
|
|
169
169
|
* off center, by up to half the left and right insets.
|
|
170
170
|
*/
|
|
171
171
|
function TooltipFramePositioner({ collisionBoundary, collisionPadding, container, ...props }) {
|
|
172
|
-
const collision = usePopupCollision(void 0, collisionBoundary, collisionPadding ?? 5, container, 0);
|
|
172
|
+
const collision = usePopupCollision(void 0, collisionBoundary, collisionPadding ?? 5, container, 0, props.anchor);
|
|
173
173
|
return /* @__PURE__ */ jsx(Tooltip.Positioner, {
|
|
174
174
|
...collision,
|
|
175
175
|
...props
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sheetPopupBoundary, usePopupSheet, visualViewportShift } from "./popup-boundary.mjs";
|
|
2
|
+
import { useLayoutEffect, useMemo, useState } from "react";
|
|
2
3
|
// src/lib/frame-insets.ts
|
|
3
4
|
const NO_FRAME_INSETS = Object.freeze({
|
|
4
5
|
top: 0,
|
|
@@ -127,15 +128,20 @@ function portalsToBody(container) {
|
|
|
127
128
|
return element == null || element === document.body || element === document.documentElement;
|
|
128
129
|
}
|
|
129
130
|
/**
|
|
130
|
-
* The window inset by the frame insets, as a collision boundary rect. Its
|
|
131
|
-
*
|
|
132
|
-
*
|
|
131
|
+
* The window inset by the frame insets, as a collision boundary rect. Its
|
|
132
|
+
* fields are read each time Base UI places the popup (it copies them), so it
|
|
133
|
+
* follows the window when it resizes while the popup is open, and WebKit's
|
|
134
|
+
* visual viewport when it pans (visualViewportShift).
|
|
133
135
|
*/
|
|
134
136
|
function frameCollisionBoundary(insets) {
|
|
135
137
|
const root = () => document.documentElement;
|
|
136
138
|
return {
|
|
137
|
-
x
|
|
138
|
-
|
|
139
|
+
get x() {
|
|
140
|
+
return insets.left + visualViewportShift().x;
|
|
141
|
+
},
|
|
142
|
+
get y() {
|
|
143
|
+
return insets.top + visualViewportShift().y;
|
|
144
|
+
},
|
|
139
145
|
get width() {
|
|
140
146
|
return Math.max(0, root().clientWidth - insets.left - insets.right);
|
|
141
147
|
},
|
|
@@ -191,16 +197,54 @@ function frameCollision(padding, boundary, insets, gap, container) {
|
|
|
191
197
|
* inset, and `gap` (8px) inside each inset, the insets as the boundary
|
|
192
198
|
* (frameCollision). A `collisionPadding` the caller passes wins as it is, and
|
|
193
199
|
* a `collisionBoundary` the caller passes is kept. Call it from a part inside
|
|
194
|
-
* the popup's portal, which mounts each time the popup opens
|
|
200
|
+
* the popup's portal, which mounts each time the popup opens, and spread the
|
|
201
|
+
* result on the positioner.
|
|
202
|
+
*
|
|
203
|
+
* In a sheet (a phone's DialogSheetContent, a Drawer, an app's own under
|
|
204
|
+
* SheetPopupBoundary) a popup with no `collisionBoundary` of its own takes the
|
|
205
|
+
* sheet's: the area under its head, decided for this popup each time it is
|
|
206
|
+
* placed (sheetPopupBoundary), whatever its portal container. Pass the
|
|
207
|
+
* positioner's own `anchor` as `anchor`: the sheet then decides by it, not by
|
|
208
|
+
* the trigger it would look up in the page. Outside a sheet nothing changes,
|
|
209
|
+
* and no `ref` is returned.
|
|
195
210
|
*/
|
|
196
|
-
function usePopupCollision(collisionPadding, collisionBoundary, defaultPadding, container, gap = 8) {
|
|
211
|
+
function usePopupCollision(collisionPadding, collisionBoundary, defaultPadding, container, gap = 8, anchor) {
|
|
197
212
|
const insets = useFrameInsets(container);
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
213
|
+
const sheet = usePopupSheet();
|
|
214
|
+
const [positioner] = useState(() => ({ current: null }));
|
|
215
|
+
const [latest] = useState(() => {
|
|
216
|
+
let current = anchor;
|
|
217
|
+
return {
|
|
218
|
+
read: () => current,
|
|
219
|
+
set: (next) => {
|
|
220
|
+
current = next;
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
});
|
|
224
|
+
useLayoutEffect(() => {
|
|
225
|
+
latest.set(anchor);
|
|
226
|
+
}, [latest, anchor]);
|
|
227
|
+
const sheetBoundary = useMemo(() => sheet && collisionBoundary === void 0 ? sheetPopupBoundary(sheet, () => positioner.current, latest.read) : void 0, [
|
|
228
|
+
sheet,
|
|
229
|
+
collisionBoundary,
|
|
230
|
+
positioner,
|
|
231
|
+
latest
|
|
232
|
+
]);
|
|
233
|
+
const boundary = collisionBoundary ?? sheetBoundary;
|
|
234
|
+
return useMemo(() => {
|
|
235
|
+
const collision = collisionPadding === void 0 ? frameCollision(defaultPadding, boundary, insets, gap, container) : {
|
|
236
|
+
collisionPadding,
|
|
237
|
+
collisionBoundary: boundary
|
|
238
|
+
};
|
|
239
|
+
return sheetBoundary ? {
|
|
240
|
+
...collision,
|
|
241
|
+
ref: positioner
|
|
242
|
+
} : collision;
|
|
201
243
|
}, [
|
|
202
244
|
collisionPadding,
|
|
203
|
-
|
|
245
|
+
boundary,
|
|
246
|
+
sheetBoundary,
|
|
247
|
+
positioner,
|
|
204
248
|
defaultPadding,
|
|
205
249
|
insets,
|
|
206
250
|
gap,
|
|
@@ -24,6 +24,11 @@ export type OverlayLayerProviderProps = {
|
|
|
24
24
|
* `useParentOverlayKind` inside it to pick its own scrim. Modal overlays only:
|
|
25
25
|
* a non-modal one passes `modal={false}`, adds nothing, and must not call
|
|
26
26
|
* `useParentOverlayKind` (it would read the layer below as its own).
|
|
27
|
+
*
|
|
28
|
+
* It is a surface of its own, modal or not, so what is inside starts outside
|
|
29
|
+
* any sheet around it: a popup opened in it isn't held inside a sheet it was
|
|
30
|
+
* opened from (dialog.md, "Popups in a sheet"). A sheet of the app's own puts
|
|
31
|
+
* its `SheetPopupBoundary` inside this provider.
|
|
27
32
|
*/
|
|
28
33
|
export declare function OverlayLayerProvider({ kind, modal, children }: OverlayLayerProviderProps): import("react").JSX.Element;
|
|
29
34
|
/**
|
|
@@ -1,8 +1,27 @@
|
|
|
1
|
+
import { PopupSheetReset } from "./popup-boundary.mjs";
|
|
1
2
|
import { createContext, useContext, useMemo } from "react";
|
|
2
3
|
import { jsx } from "react/jsx-runtime";
|
|
3
4
|
// src/lib/overlay-depth.tsx
|
|
4
5
|
const OverlayStackContext = createContext([]);
|
|
5
6
|
/**
|
|
7
|
+
* Adds a modal surface to the stack for everything under it. raft-ui's own
|
|
8
|
+
* roots use this one: a root also holds its trigger, which is still in the
|
|
9
|
+
* surface it is written in, so it must not start outside that sheet as
|
|
10
|
+
* `OverlayLayerProvider` does.
|
|
11
|
+
*/
|
|
12
|
+
function OverlayLayer({ kind, modal, children }) {
|
|
13
|
+
const stack = useContext(OverlayStackContext);
|
|
14
|
+
const value = useMemo(() => modal ? [...stack, kind] : stack, [
|
|
15
|
+
stack,
|
|
16
|
+
kind,
|
|
17
|
+
modal
|
|
18
|
+
]);
|
|
19
|
+
return /* @__PURE__ */ jsx(OverlayStackContext.Provider, {
|
|
20
|
+
value,
|
|
21
|
+
children
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
6
25
|
* Tells everything under it that a modal surface is open, so a raft-ui
|
|
7
26
|
* Dialog, AlertDialog or Drawer opened from inside draws the lighter nested
|
|
8
27
|
* scrim (design/foundations/elevation.md#stacked-overlays). raft-ui's own
|
|
@@ -11,17 +30,17 @@ const OverlayStackContext = createContext([]);
|
|
|
11
30
|
* `useParentOverlayKind` inside it to pick its own scrim. Modal overlays only:
|
|
12
31
|
* a non-modal one passes `modal={false}`, adds nothing, and must not call
|
|
13
32
|
* `useParentOverlayKind` (it would read the layer below as its own).
|
|
33
|
+
*
|
|
34
|
+
* It is a surface of its own, modal or not, so what is inside starts outside
|
|
35
|
+
* any sheet around it: a popup opened in it isn't held inside a sheet it was
|
|
36
|
+
* opened from (dialog.md, "Popups in a sheet"). A sheet of the app's own puts
|
|
37
|
+
* its `SheetPopupBoundary` inside this provider.
|
|
14
38
|
*/
|
|
15
39
|
function OverlayLayerProvider({ kind, modal, children }) {
|
|
16
|
-
|
|
17
|
-
const value = useMemo(() => modal ? [...stack, kind] : stack, [
|
|
18
|
-
stack,
|
|
40
|
+
return /* @__PURE__ */ jsx(OverlayLayer, {
|
|
19
41
|
kind,
|
|
20
|
-
modal
|
|
21
|
-
|
|
22
|
-
return /* @__PURE__ */ jsx(OverlayStackContext.Provider, {
|
|
23
|
-
value,
|
|
24
|
-
children
|
|
42
|
+
modal,
|
|
43
|
+
children: /* @__PURE__ */ jsx(PopupSheetReset, { children })
|
|
25
44
|
});
|
|
26
45
|
}
|
|
27
46
|
/**
|
|
@@ -44,15 +63,15 @@ function useParentOverlayKind() {
|
|
|
44
63
|
* children be a render function (`({ payload }) => …`), so keep that shape.
|
|
45
64
|
*/
|
|
46
65
|
function withOverlayLayer(kind, modal, children) {
|
|
47
|
-
if (typeof children === "function") return (...args) => /* @__PURE__ */ jsx(
|
|
66
|
+
if (typeof children === "function") return (...args) => /* @__PURE__ */ jsx(OverlayLayer, {
|
|
48
67
|
kind,
|
|
49
68
|
modal,
|
|
50
69
|
children: children(...args)
|
|
51
70
|
});
|
|
52
|
-
return /* @__PURE__ */ jsx(
|
|
71
|
+
return /* @__PURE__ */ jsx(OverlayLayer, {
|
|
53
72
|
kind,
|
|
54
73
|
modal,
|
|
55
74
|
children
|
|
56
75
|
});
|
|
57
76
|
}
|
|
58
|
-
export { OverlayLayerProvider, useParentOverlayKind, withOverlayLayer };
|
|
77
|
+
export { OverlayLayer, OverlayLayerProvider, useParentOverlayKind, withOverlayLayer };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ReactNode, RefObject } from "react";
|
|
2
|
+
export type SheetPopupBoundaryProps = {
|
|
3
|
+
/** The sheet's head: popups stay under its bottom edge. */
|
|
4
|
+
head: RefObject<Element | null>;
|
|
5
|
+
/**
|
|
6
|
+
* The sheet's surface: popups stay above its bottom edge.
|
|
7
|
+
* @default The head's parent element.
|
|
8
|
+
*/
|
|
9
|
+
surface?: RefObject<Element | null>;
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* For a sheet an app draws itself (its own surface, a head that stays put and
|
|
14
|
+
* a body that scrolls): every raft-ui popup opened under it (Select, Combobox,
|
|
15
|
+
* ComboboxSelect, DropdownMenu, ContextMenu, Popover, PreviewCard, Tooltip,
|
|
16
|
+
* NotificationCenter) flips and sizes inside the sheet, under its head
|
|
17
|
+
* (sheetPopupBoundary says when it doesn't). Wrap the sheet's body in it, or
|
|
18
|
+
* the head and the body: a popup anchored in the head is left alone either
|
|
19
|
+
* way. Put it inside the sheet's own `OverlayLayerProvider`, which starts
|
|
20
|
+
* without one. raft-ui's own sheets, `DialogSheetContent` on a phone and
|
|
21
|
+
* `Drawer`, do this by themselves, and a `collisionBoundary` passed to a popup
|
|
22
|
+
* still wins. It renders no element.
|
|
23
|
+
*/
|
|
24
|
+
export declare function SheetPopupBoundary({ head, surface, children }: SheetPopupBoundaryProps): import("react").JSX.Element;
|