@botiverse/raft-ui 0.95.3 → 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.
Files changed (35) hide show
  1. package/CHANGELOG.md +135 -98
  2. package/dist/components/app-link-card/app-link-card.d.mts +17 -1
  3. package/dist/components/app-link-card/app-link-card.mjs +70 -2
  4. package/dist/components/app-link-card/index.d.mts +2 -2
  5. package/dist/components/combobox/combobox.mjs +1 -1
  6. package/dist/components/combobox-select/combobox-select.mjs +1 -1
  7. package/dist/components/context-menu/context-menu.mjs +1 -1
  8. package/dist/components/dialog/dialog-sheet-context.mjs +4 -2
  9. package/dist/components/dialog/dialog-sheet.mjs +9 -1
  10. package/dist/components/dialog/dialog.mjs +22 -10
  11. package/dist/components/drawer/drawer.d.mts +1 -1
  12. package/dist/components/drawer/drawer.mjs +17 -4
  13. package/dist/components/dropdown-menu/dropdown-menu.mjs +1 -1
  14. package/dist/components/lightbox/lightbox.d.mts +1 -1
  15. package/dist/components/lightbox/lightbox.mjs +11 -9
  16. package/dist/components/notification-center/notification-center.mjs +1 -1
  17. package/dist/components/popover/popover.mjs +1 -1
  18. package/dist/components/preview-card/preview-card.mjs +1 -1
  19. package/dist/components/select/select.mjs +1 -1
  20. package/dist/components/tooltip/tooltip.mjs +1 -1
  21. package/dist/lib/fill-contrast.mjs +63 -0
  22. package/dist/lib/frame-insets.mjs +56 -12
  23. package/dist/lib/overlay-depth.d.mts +5 -0
  24. package/dist/lib/overlay-depth.mjs +30 -11
  25. package/dist/lib/popup-boundary.d.mts +24 -0
  26. package/dist/lib/popup-boundary.mjs +256 -0
  27. package/dist/lint/no-raw-z-index.mjs +4 -4
  28. package/dist/lint/prefer-scroll-area.d.mts +60 -19
  29. package/dist/lint/prefer-scroll-area.mjs +393 -38
  30. package/dist/wip-exports.d.mts +6 -5
  31. package/dist/wip-exports.mjs +5 -4
  32. package/dist/wip.d.mts +3 -2
  33. package/package.json +1 -1
  34. package/skills/raft-ui-guide/SKILL.md +3 -1
  35. package/skills/raft-ui-guide/rules/layout.md +1 -0
@@ -1,3 +1,4 @@
1
+ import { BadgeProps } from "../badge/badge.mjs";
1
2
  import { ComponentPropsWithRef } from "react";
2
3
  export type AppLinkCardProps = ComponentPropsWithRef<"div">;
3
4
  /**
@@ -44,8 +45,23 @@ export type AppLinkCardTitleProps = ComponentPropsWithRef<"p">;
44
45
  */
45
46
  export declare function AppLinkCardTitle({ className, ...props }: AppLinkCardTitleProps): import("react").JSX.Element;
46
47
  export type AppLinkCardMetaProps = ComponentPropsWithRef<"div">;
47
- /** One state in a neutral Badge, then muted details. Wraps when it runs out of room. */
48
+ /** One state in an `AppLinkCardBadge`, then muted details. Wraps when it runs out of room. */
48
49
  export declare function AppLinkCardMeta({ className, ...props }: AppLinkCardMetaProps): import("react").JSX.Element;
50
+ export type AppLinkCardBadgeProps = Omit<BadgeProps, "appearance"> & {
51
+ /**
52
+ * The app's colour for the badge, `#RRGGBB`. Only the fill changes: the
53
+ * type is black or white, whichever reads better on it, the same in light
54
+ * and dark mode, with a hairline ring where the fill is too close to the
55
+ * card's surface. Without it, or with any other value, and in forced
56
+ * colours, the badge is the soft Badge of its `variant`.
57
+ */
58
+ fill?: string;
59
+ };
60
+ /**
61
+ * The item's state in `AppLinkCardMeta`: a soft Badge in the app's words.
62
+ * `variant` is its tone; `fill` takes the app's colour for it.
63
+ */
64
+ export declare function AppLinkCardBadge({ fill, className, style, ...props }: AppLinkCardBadgeProps): import("react").JSX.Element;
49
65
  export type AppLinkCardActionsProps = ComponentPropsWithRef<"div">;
50
66
  /**
51
67
  * The app's buttons at the card's bottom end: at most two, the bordered one
@@ -1,10 +1,19 @@
1
+ import { fillOnSurface, oklchToRgb, parseHexColor } from "../../lib/fill-contrast.mjs";
1
2
  import { tv } from "../../lib/tv.mjs";
3
+ import { Badge } from "../badge/badge.mjs";
2
4
  import { messageCard, messageEmbed } from "../message-embed/message-embed.recipe.mjs";
3
5
  import { Skeleton } from "../skeleton/skeleton.mjs";
6
+ import { useMemo } from "react";
4
7
  import { jsx, jsxs } from "react/jsx-runtime";
5
8
  // src/components/app-link-card/app-link-card.tsx
6
9
  const appLinkCard = tv({ slots: {
7
10
  root: ["flex flex-col", "my-2 first:mt-0 last:mb-0"],
11
+ badgeFill: [
12
+ "not-forced-colors:bg-(--badge-fill) not-forced-colors:text-(--badge-fill-text)",
13
+ "not-forced-colors:inset-ring",
14
+ "[--badge-ring:var(--badge-ring-light)] dark:[--badge-ring:var(--badge-ring-dark)]",
15
+ "not-forced-colors:inset-ring-(--badge-ring)"
16
+ ],
8
17
  placeholderLine: ["flex h-lh items-center"],
9
18
  placeholderButton: ["h-8 md:pointer-fine:h-control w-28 rounded-control"]
10
19
  } });
@@ -164,7 +173,7 @@ function AppLinkCardTitle({ className, ...props }) {
164
173
  ...props
165
174
  });
166
175
  }
167
- /** One state in a neutral Badge, then muted details. Wraps when it runs out of room. */
176
+ /** One state in an `AppLinkCardBadge`, then muted details. Wraps when it runs out of room. */
168
177
  function AppLinkCardMeta({ className, ...props }) {
169
178
  const { meta } = messageCard();
170
179
  return /* @__PURE__ */ jsx("div", {
@@ -173,6 +182,65 @@ function AppLinkCardMeta({ className, ...props }) {
173
182
  ...props
174
183
  });
175
184
  }
185
+ const APP_LINK_CARD_SURFACE = {
186
+ light: [
187
+ 1,
188
+ 0,
189
+ 0
190
+ ],
191
+ dark: [
192
+ .2596,
193
+ .0037,
194
+ 106.65
195
+ ]
196
+ };
197
+ const CARD_SURFACE = {
198
+ light: oklchToRgb(...APP_LINK_CARD_SURFACE.light),
199
+ dark: oklchToRgb(...APP_LINK_CARD_SURFACE.dark)
200
+ };
201
+ const FILL_TEXT = {
202
+ black: "oklch(0 0 0)",
203
+ white: "oklch(1 0 0)"
204
+ };
205
+ /** The badge's custom properties for an app's fill, or null for no valid `#RRGGBB`. */
206
+ function badgeFillStyle(fill) {
207
+ const rgb = fill === void 0 ? null : parseHexColor(fill);
208
+ if (!rgb) return null;
209
+ const light = fillOnSurface(rgb, CARD_SURFACE.light);
210
+ const dark = fillOnSurface(rgb, CARD_SURFACE.dark);
211
+ return {
212
+ "--badge-fill": fill,
213
+ "--badge-fill-text": FILL_TEXT[light.text],
214
+ "--badge-ring-light": light.ring ? "var(--line-hairline)" : "transparent",
215
+ "--badge-ring-dark": dark.ring ? "var(--line-hairline)" : "transparent"
216
+ };
217
+ }
218
+ /**
219
+ * The item's state in `AppLinkCardMeta`: a soft Badge in the app's words.
220
+ * `variant` is its tone; `fill` takes the app's colour for it.
221
+ */
222
+ function AppLinkCardBadge({ fill, className, style, ...props }) {
223
+ const fillStyle = useMemo(() => {
224
+ const vars = badgeFillStyle(fill);
225
+ return vars && {
226
+ ...vars,
227
+ ...style
228
+ };
229
+ }, [fill, style]);
230
+ if (!fillStyle) return /* @__PURE__ */ jsx(Badge, {
231
+ appearance: "soft",
232
+ className,
233
+ style,
234
+ ...props
235
+ });
236
+ const { badgeFill } = appLinkCard();
237
+ return /* @__PURE__ */ jsx(Badge, {
238
+ appearance: "soft",
239
+ className: badgeFill({ className }),
240
+ style: fillStyle,
241
+ ...props
242
+ });
243
+ }
176
244
  /**
177
245
  * The app's buttons at the card's bottom end: at most two, the bordered one
178
246
  * last, with a More menu (⋯) to their left for the rest.
@@ -185,4 +253,4 @@ function AppLinkCardActions({ className, ...props }) {
185
253
  ...props
186
254
  });
187
255
  }
188
- export { AppLinkCard, AppLinkCardActions, AppLinkCardBody, AppLinkCardEyebrow, AppLinkCardHeader, AppLinkCardHost, AppLinkCardIcon, AppLinkCardMeta, AppLinkCardName, AppLinkCardPlaceholder, AppLinkCardTitle };
256
+ export { APP_LINK_CARD_SURFACE, AppLinkCard, AppLinkCardActions, AppLinkCardBadge, AppLinkCardBody, AppLinkCardEyebrow, AppLinkCardHeader, AppLinkCardHost, AppLinkCardIcon, AppLinkCardMeta, AppLinkCardName, AppLinkCardPlaceholder, AppLinkCardTitle };
@@ -1,2 +1,2 @@
1
- import { AppLinkCard, AppLinkCardActions, AppLinkCardActionsProps, AppLinkCardBody, AppLinkCardBodyProps, AppLinkCardEyebrow, AppLinkCardEyebrowProps, AppLinkCardHeader, AppLinkCardHeaderProps, AppLinkCardHost, AppLinkCardHostProps, AppLinkCardIcon, AppLinkCardIconProps, AppLinkCardMeta, AppLinkCardMetaProps, AppLinkCardName, AppLinkCardNameProps, AppLinkCardPlaceholder, AppLinkCardPlaceholderProps, AppLinkCardTitle, AppLinkCardTitleProps } from "./app-link-card.mjs";
2
- export { AppLinkCard, AppLinkCardActions, type AppLinkCardActionsProps, AppLinkCardBody, type AppLinkCardBodyProps, AppLinkCardEyebrow, type AppLinkCardEyebrowProps, AppLinkCardHeader, type AppLinkCardHeaderProps, AppLinkCardHost, type AppLinkCardHostProps, AppLinkCardIcon, type AppLinkCardIconProps, AppLinkCardMeta, type AppLinkCardMetaProps, AppLinkCardName, type AppLinkCardNameProps, AppLinkCardPlaceholder, type AppLinkCardPlaceholderProps, AppLinkCardTitle, type AppLinkCardTitleProps };
1
+ import { AppLinkCard, AppLinkCardActions, AppLinkCardActionsProps, AppLinkCardBadge, AppLinkCardBadgeProps, AppLinkCardBody, AppLinkCardBodyProps, AppLinkCardEyebrow, AppLinkCardEyebrowProps, AppLinkCardHeader, AppLinkCardHeaderProps, AppLinkCardHost, AppLinkCardHostProps, AppLinkCardIcon, AppLinkCardIconProps, AppLinkCardMeta, AppLinkCardMetaProps, AppLinkCardName, AppLinkCardNameProps, AppLinkCardPlaceholder, AppLinkCardPlaceholderProps, AppLinkCardTitle, AppLinkCardTitleProps } from "./app-link-card.mjs";
2
+ export { AppLinkCard, AppLinkCardActions, type AppLinkCardActionsProps, AppLinkCardBadge, type AppLinkCardBadgeProps, AppLinkCardBody, type AppLinkCardBodyProps, AppLinkCardEyebrow, type AppLinkCardEyebrowProps, AppLinkCardHeader, type AppLinkCardHeaderProps, AppLinkCardHost, type AppLinkCardHostProps, AppLinkCardIcon, type AppLinkCardIconProps, AppLinkCardMeta, type AppLinkCardMetaProps, AppLinkCardName, type AppLinkCardNameProps, AppLinkCardPlaceholder, type AppLinkCardPlaceholderProps, AppLinkCardTitle, type AppLinkCardTitleProps };
@@ -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: ["z-dialog-layer"]
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 { content } = dialog({
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 overlayProps = typeof overlay === "object" ? overlay : {};
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
- children: [overlay === false ? null : /* @__PURE__ */ jsx(DialogOverlay, {
154
- layer,
155
- ...overlayProps
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__ */ jsxs(DialogSheetHeadContext.Provider, {
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 = { className: header({ className }) };
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 { OverlayLayerProvider, useParentOverlayKind } from "../../lib/overlay-depth.mjs";
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)+--spacing(3))]",
29
- "pl-[calc(env(safe-area-inset-left,0px)+--spacing(4))]",
30
- "pr-[calc(env(safe-area-inset-right,0px)+--spacing(4))]",
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 right-6 bottom-20 left-6 z-raised flex justify-center"],
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 right-0 bottom-4 left-0 z-raised", "flex items-center justify-center gap-1.5"],
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(OverlayLayerProvider, {
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
@@ -0,0 +1,63 @@
1
+ // src/lib/fill-contrast.ts
2
+ /**
3
+ * Below this contrast against the surface, a fill gets a hairline ring.
4
+ * 1.5:1 is where a flat fill stops reading as a shape of its own: a pale
5
+ * yellow on a white card, a near-black on a dark one.
6
+ */
7
+ const FILL_RING_CONTRAST = 1.5;
8
+ /** `#RRGGBB` (either case) as sRGB channels; anything else is null. */
9
+ function parseHexColor(value) {
10
+ const match = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(value);
11
+ if (!match) return null;
12
+ return [
13
+ parseInt(match[1], 16) / 255,
14
+ parseInt(match[2], 16) / 255,
15
+ parseInt(match[3], 16) / 255
16
+ ];
17
+ }
18
+ /** An `oklch(l c h)` token value (as tokens.json stores its components) in sRGB, clamped. */
19
+ function oklchToRgb(l, c, h) {
20
+ const a = c * Math.cos(h * Math.PI / 180);
21
+ const b = c * Math.sin(h * Math.PI / 180);
22
+ const L = (l + .3963377774 * a + .2158037573 * b) ** 3;
23
+ const M = (l - .1055613458 * a - .0638541728 * b) ** 3;
24
+ const S = (l - .0894841775 * a - 1.291485548 * b) ** 3;
25
+ const [r, g, bl] = [
26
+ 4.0767416621 * L - 3.3077115913 * M + .2309699292 * S,
27
+ -1.2684380046 * L + 2.6097574011 * M - .3413193965 * S,
28
+ -.0041960863 * L - .7034186147 * M + 1.707614701 * S
29
+ ].map((x) => {
30
+ const y = Math.min(1, Math.max(0, x));
31
+ return y <= .0031308 ? 12.92 * y : 1.055 * y ** (1 / 2.4) - .055;
32
+ });
33
+ return [
34
+ r,
35
+ g,
36
+ bl
37
+ ];
38
+ }
39
+ /** WCAG 2 relative luminance. */
40
+ function relativeLuminance([r, g, b]) {
41
+ const linear = (v) => v <= .04045 ? v / 12.92 : ((v + .055) / 1.055) ** 2.4;
42
+ return .2126 * linear(r) + .7152 * linear(g) + .0722 * linear(b);
43
+ }
44
+ /** WCAG 2 contrast ratio, from 1 to 21, in either order. */
45
+ function contrastRatio(a, b) {
46
+ const la = relativeLuminance(a);
47
+ const lb = relativeLuminance(b);
48
+ return (Math.max(la, lb) + .05) / (Math.min(la, lb) + .05);
49
+ }
50
+ /**
51
+ * The type and the ring for `fill` lying on `surface`: black or white type,
52
+ * whichever has the higher contrast (one of them always reaches 4.58:1), and
53
+ * a ring when the fill's contrast with the surface is below
54
+ * `FILL_RING_CONTRAST`.
55
+ */
56
+ function fillOnSurface(fill, surface) {
57
+ const luminance = relativeLuminance(fill);
58
+ return {
59
+ text: (luminance + .05) / .05 >= 1.05 / (luminance + .05) ? "black" : "white",
60
+ ring: contrastRatio(fill, surface) < FILL_RING_CONTRAST
61
+ };
62
+ }
63
+ export { FILL_RING_CONTRAST, contrastRatio, fillOnSurface, oklchToRgb, parseHexColor, relativeLuminance };