From 65bf84b8155c5850b415ddb2308dd4a6155fc467 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 10 Jun 2026 14:12:14 -0400 Subject: [PATCH 01/16] feat(headless): add Dialog.Viewport and forward refs on all dialog parts Split the FloatingOverlay and scroll-lock out of Dialog.Backdrop into a new Dialog.Viewport part, so the backdrop is a pure dim layer and the viewport owns overlay/centering/scroll-lock. Every dialog part now uses forwardRef so a consumer ref reaches the rendered element, and the popup is marked inert to outside elements while modal. --- .../headless/src/primitives/dialog/README.md | 34 ++++---- .../src/primitives/dialog/dialog-backdrop.tsx | 79 ++++++++----------- .../src/primitives/dialog/dialog-close.tsx | 11 ++- .../primitives/dialog/dialog-description.tsx | 33 ++++---- .../src/primitives/dialog/dialog-popup.tsx | 3 +- .../src/primitives/dialog/dialog-title.tsx | 11 ++- .../src/primitives/dialog/dialog-trigger.tsx | 2 +- .../src/primitives/dialog/dialog-viewport.tsx | 46 +++++++++++ .../src/primitives/dialog/dialog.test.tsx | 21 ++++- .../headless/src/primitives/dialog/index.ts | 1 + .../headless/src/primitives/dialog/parts.ts | 1 + 11 files changed, 158 insertions(+), 84 deletions(-) create mode 100644 packages/headless/src/primitives/dialog/dialog-viewport.tsx diff --git a/packages/headless/src/primitives/dialog/README.md b/packages/headless/src/primitives/dialog/README.md index f59e19937c8..b18fbc8cf2d 100644 --- a/packages/headless/src/primitives/dialog/README.md +++ b/packages/headless/src/primitives/dialog/README.md @@ -15,13 +15,14 @@ import { Dialog } from '@/primitives/dialog'; Open Dialog - + + Confirm Action Are you sure you want to proceed? Cancel - + ; ``` @@ -51,7 +52,8 @@ const [open, setOpen] = useState(false); | `Dialog.Root` | — | Root context provider | | `Dialog.Trigger` | ` - ); -}); diff --git a/packages/ui/src/mosaic/components/button.tsx b/packages/ui/src/mosaic/components/button.tsx index 282742b8cc4..32ec885a53f 100644 --- a/packages/ui/src/mosaic/components/button.tsx +++ b/packages/ui/src/mosaic/components/button.tsx @@ -23,7 +23,7 @@ export const buttonStyles = cva(theme => ({ }, }, variants: { - color: { + intent: { primary: { backgroundColor: theme.color.primary, color: theme.color.primaryForeground, @@ -40,13 +40,13 @@ export const buttonStyles = cva(theme => ({ true: { opacity: 0.5, cursor: 'not-allowed', pointerEvents: 'none' }, }, }, - defaultVariants: { color: 'primary', size: 'md', disabled: false }, + defaultVariants: { intent: 'primary', size: 'md', disabled: false }, })); export type ButtonProps = React.ComponentPropsWithRef<'button'> & VariantProps; export const Button = React.forwardRef(function MosaicButton(props, ref) { - const { color, size, disabled, sx, children, ...rest } = props; + const { intent, size, disabled, sx, children, ...rest } = props; const theme = useMosaicTheme(); return ( From c6d1ad8c15ca9761fa66c80779bae1563dfb6d5b Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 10 Jun 2026 14:20:33 -0400 Subject: [PATCH 03/16] feat(ui): bridge headless dialog primitives into mosaic Adds a two-layer bridge for rendering @clerk/headless dialog parts styled through mosaic: - mosaic/primitives: withMosaicTheme wraps each headless part, forwarding its ref and resolving an `sx` prop (static or theme-aware) to an Emotion `css` prop. primitives/dialog maps every Dialog part through it; the bridged types are inferred, so future primitives only call withMosaicTheme. - mosaic/components/dialog: styled(primitive, cva) composes a primitive with a cva style definition, auto-stripping variant props before forwarding. - cva exposes CvaFn/Variants so styled can type against cva results. Adds @clerk/headless as a dependency of @clerk/ui. --- packages/ui/package.json | 1 + packages/ui/src/mosaic/components/dialog.tsx | 114 ++++++++++++++++++ packages/ui/src/mosaic/cva.ts | 6 +- packages/ui/src/mosaic/primitives/dialog.tsx | 27 +++++ .../src/mosaic/primitives/withMosaicTheme.tsx | 27 +++++ packages/ui/src/mosaic/styled.tsx | 50 ++++++++ packages/ui/src/mosaic/types.ts | 4 + pnpm-lock.yaml | 3 + 8 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 packages/ui/src/mosaic/components/dialog.tsx create mode 100644 packages/ui/src/mosaic/primitives/dialog.tsx create mode 100644 packages/ui/src/mosaic/primitives/withMosaicTheme.tsx create mode 100644 packages/ui/src/mosaic/styled.tsx create mode 100644 packages/ui/src/mosaic/types.ts diff --git a/packages/ui/package.json b/packages/ui/package.json index e27f25afac2..5582f7eec77 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -94,6 +94,7 @@ "type-check": "tsc --noEmit" }, "dependencies": { + "@clerk/headless": "workspace:^", "@clerk/localizations": "workspace:^", "@clerk/shared": "workspace:^", "@emotion/cache": "11.11.0", diff --git a/packages/ui/src/mosaic/components/dialog.tsx b/packages/ui/src/mosaic/components/dialog.tsx new file mode 100644 index 00000000000..388c62aaddf --- /dev/null +++ b/packages/ui/src/mosaic/components/dialog.tsx @@ -0,0 +1,114 @@ +import { cva } from '../cva'; +import { Dialog as Primitive } from '../primitives/dialog'; +import { styled } from '../styled'; + +const backdropStyles = cva({ + base: { + position: 'fixed', + inset: 0, + backgroundColor: 'color-mix(in oklab, #000, transparent 50%)', + transition: 'opacity 150ms', + '&[data-cl-starting-style], &[data-cl-ending-style]': { + opacity: 0, + }, + }, + variants: {}, +}); + +const viewportStyles = cva(theme => ({ + base: { + display: 'grid', + placeItems: 'center', + width: '100%', + height: '100%', + padding: theme.spacing(4), + }, + variants: {}, +})); + +const popupStyles = cva(theme => ({ + base: { + backgroundColor: theme.color.primaryForeground, + color: theme.color.primary, + borderRadius: theme.rounded.lg, + padding: theme.spacing(6), + minWidth: '20rem', + maxWidth: '32rem', + width: '100%', + boxShadow: '0 10px 30px rgba(0,0,0,0.18)', + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(3), + transition: 'transform 150ms ease-out, opacity 150ms ease-out', + '&[data-cl-starting-style], &[data-cl-ending-style]': { + opacity: 0, + transform: 'scale(0.98)', + }, + }, + variants: {}, +})); + +const titleStyles = cva(theme => ({ + base: { + ...theme.text('lg'), + fontWeight: 600, + margin: 0, + }, + variants: {}, +})); + +const descriptionStyles = cva(theme => ({ + base: { + ...theme.text('sm'), + margin: 0, + opacity: 0.8, + }, + variants: {}, +})); + +const closeStyles = cva(theme => ({ + base: { + alignSelf: 'flex-end', + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + paddingInline: theme.spacing(3), + paddingBlock: theme.spacing(1), + borderRadius: theme.rounded.md, + backgroundColor: 'transparent', + color: theme.color.primary, + border: `1px solid ${theme.alpha('primary', 20)}`, + ...theme.text('sm'), + cursor: 'pointer', + }, + variants: {}, +})); + +const Backdrop = styled(Primitive.Backdrop, backdropStyles); +const Viewport = styled(Primitive.Viewport, viewportStyles); +const Popup = styled(Primitive.Popup, popupStyles); +const Title = styled(Primitive.Title, titleStyles); +const Description = styled(Primitive.Description, descriptionStyles); +const Close = styled(Primitive.Close, closeStyles); + +export const Dialog: { + Root: typeof Primitive.Root; + Trigger: typeof Primitive.Trigger; + Portal: typeof Primitive.Portal; + Backdrop: typeof Backdrop; + Viewport: typeof Viewport; + Popup: typeof Popup; + Title: typeof Title; + Description: typeof Description; + Close: typeof Close; +} = { + Root: Primitive.Root, + Trigger: Primitive.Trigger, + Portal: Primitive.Portal, + Backdrop, + Viewport, + Popup, + Title, + Description, + Close, +}; diff --git a/packages/ui/src/mosaic/cva.ts b/packages/ui/src/mosaic/cva.ts index 0c95c514189..0278cb88632 100644 --- a/packages/ui/src/mosaic/cva.ts +++ b/packages/ui/src/mosaic/cva.ts @@ -23,7 +23,9 @@ export type VariantProps any> = T extends CvaFn ? VariantPropsOf & { sx?: SxProp } : never; // ─── Internal Types ─────────────────────────────────────────────────────────── -type Variants = Record>; + +/** Map of variant axis → variant value → style rule. Exposed so `styled` can type against `cva` results. */ +export type Variants = Record>; /** Converts `'true' | 'false'` string literal keys to `boolean` so callers pass real booleans. */ type UnwrapBooleanVariant = T extends 'true' | 'false' ? boolean : T; @@ -43,7 +45,7 @@ type CvaConfig = { }; /** The curried function returned by `cva`: receives variant props, returns a theme → StyleRule resolver. */ -type CvaFn = { +export type CvaFn = { (props?: VariantPropsOf & { sx?: SxProp }): (theme: MosaicTheme) => StyleRule; /** Variant definitions exposed for tooling. Not part of the public API. */ _variants: V; diff --git a/packages/ui/src/mosaic/primitives/dialog.tsx b/packages/ui/src/mosaic/primitives/dialog.tsx new file mode 100644 index 00000000000..ac2d6df82f2 --- /dev/null +++ b/packages/ui/src/mosaic/primitives/dialog.tsx @@ -0,0 +1,27 @@ +import { Dialog as HeadlessDialog } from '@clerk/headless/dialog'; +import type { DialogPortalProps, DialogProps } from '@clerk/headless/dialog'; +import type { FunctionComponent } from 'react'; + +import { withMosaicTheme } from './withMosaicTheme'; + +/** + * The headless dialog parts bridged into mosaic. Each styleable part is wrapped + * with `withMosaicTheme`, which forwards its ref and adds the `sx` prop — the + * bridged type is inferred, so there is nothing to hand-annotate per part. + * + * The structural parts (`Root`, `Portal`) render no element of their own and + * pass through unchanged; they are cast to their public component types so the + * inferred `Dialog` type stays portable (otherwise it references internal + * `@clerk/headless` declaration paths). + */ +export const Dialog = { + Root: HeadlessDialog.Root as FunctionComponent, + Trigger: withMosaicTheme(HeadlessDialog.Trigger), + Portal: HeadlessDialog.Portal as FunctionComponent, + Backdrop: withMosaicTheme(HeadlessDialog.Backdrop), + Viewport: withMosaicTheme(HeadlessDialog.Viewport), + Popup: withMosaicTheme(HeadlessDialog.Popup), + Title: withMosaicTheme(HeadlessDialog.Title), + Description: withMosaicTheme(HeadlessDialog.Description), + Close: withMosaicTheme(HeadlessDialog.Close), +}; diff --git a/packages/ui/src/mosaic/primitives/withMosaicTheme.tsx b/packages/ui/src/mosaic/primitives/withMosaicTheme.tsx new file mode 100644 index 00000000000..93afad7125c --- /dev/null +++ b/packages/ui/src/mosaic/primitives/withMosaicTheme.tsx @@ -0,0 +1,27 @@ +import React from 'react'; + +import { useMosaicTheme } from '../MosaicProvider'; +import type { Mosaic } from '../types'; + +/** + * Bridges a headless primitive into mosaic: accepts an `sx` prop (a static + * `StyleRule` or a `(theme) => StyleRule` resolver), resolves it against the + * mosaic theme, and forwards it as an Emotion `css` prop. The headless part + * turns that into a `className` and forwards it to its rendered DOM node. + */ +export const withMosaicTheme = (Component: React.FunctionComponent

): React.FunctionComponent> => { + const Wrapped = React.forwardRef((props: Mosaic, ref) => { + const { sx, ...rest } = props; + const theme = useMosaicTheme(); + const css = typeof sx === 'function' ? sx(theme) : sx; + return ( + + ); + }); + Wrapped.displayName = `Mosaic${Component.displayName || Component.name || 'Component'}`; + return Wrapped as React.FunctionComponent>; +}; diff --git a/packages/ui/src/mosaic/styled.tsx b/packages/ui/src/mosaic/styled.tsx new file mode 100644 index 00000000000..525444cceaf --- /dev/null +++ b/packages/ui/src/mosaic/styled.tsx @@ -0,0 +1,50 @@ +import React from 'react'; + +import type { CvaFn, SxProp, VariantProps, Variants } from './cva'; + +/** Props of a styled component: the wrapped component's props plus the cva's variant props (including `sx`). */ +type StyledProps, V extends Variants> = React.ComponentProps & + VariantProps>; + +/** + * Wraps a bridged mosaic primitive (e.g. `Dialog.Popup`) with a `cva` style + * definition. Returns a `forwardRef` component whose props are the wrapped + * component's props plus the cva's variant props (including `sx`). + * + * Variant keys are read from `styles._variants` and stripped from the props + * before forwarding, so variant props never leak onto the DOM — there is no + * list to keep in sync. The cva resolver is handed to the primitive as its + * `sx` prop, which `withMosaicTheme` resolves against the theme. + */ +export function styled, V extends Variants>( + Component: C, + styles: CvaFn, +): React.ForwardRefExoticComponent< + React.PropsWithoutRef> & React.RefAttributes> +> { + const variantKeys = Object.keys(styles._variants); + // Bridged primitives forward refs at runtime; widen once so JSX accepts `ref` and the cva resolver as `sx`. + const Forwarded = Component as React.ComponentType< + Record & { sx?: SxProp; ref?: React.Ref } + >; + + const Wrapped = React.forwardRef, StyledProps>(function Styled(props, ref) { + const { sx, ...rest } = props as Record; + const variantArgs: Record = { sx }; + for (const key of variantKeys) { + if (key in rest) { + variantArgs[key] = rest[key]; + delete rest[key]; + } + } + return ( + >[0])} + /> + ); + }); + Wrapped.displayName = `Mosaic(${Component.displayName || Component.name || 'Component'})`; + return Wrapped; +} diff --git a/packages/ui/src/mosaic/types.ts b/packages/ui/src/mosaic/types.ts new file mode 100644 index 00000000000..91e62f4f1a1 --- /dev/null +++ b/packages/ui/src/mosaic/types.ts @@ -0,0 +1,4 @@ +import type { SxProp } from './cva'; + +/** A component prop set augmented with mosaic's `sx` override prop. */ +export type Mosaic = T & { sx?: SxProp }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a64b447ef23..5fa067988f9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1053,6 +1053,9 @@ importers: packages/ui: dependencies: + '@clerk/headless': + specifier: workspace:^ + version: link:../headless '@clerk/localizations': specifier: workspace:^ version: link:../localizations From a9e45840cba7d781ec2631592dd2a888d4043575 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 10 Jun 2026 14:20:52 -0400 Subject: [PATCH 04/16] chore(ui): move @floating-ui/react to catalog:repo Use the repo catalog pin for @floating-ui/react instead of a hardcoded version, matching the other catalog-managed dependencies. --- packages/ui/package.json | 2 +- pnpm-lock.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index 5582f7eec77..5d5e2ee7b3f 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -99,7 +99,7 @@ "@clerk/shared": "workspace:^", "@emotion/cache": "11.11.0", "@emotion/react": "11.11.1", - "@floating-ui/react": "0.27.12", + "@floating-ui/react": "catalog:repo", "@formkit/auto-animate": "^0.8.2", "@solana/wallet-adapter-base": "catalog:module-manager", "@solana/wallet-adapter-react": "catalog:module-manager", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5fa067988f9..726da22dd60 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1069,7 +1069,7 @@ importers: specifier: 11.11.1 version: 11.11.1(@types/react@18.3.28)(react@18.3.1) '@floating-ui/react': - specifier: 0.27.12 + specifier: catalog:repo version: 0.27.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@formkit/auto-animate': specifier: ^0.8.2 From 0477fa80085d6645492224f24afa5f8284a5c6f8 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 10 Jun 2026 14:21:32 -0400 Subject: [PATCH 05/16] add changeset --- .changeset/whole-loops-find.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changeset/whole-loops-find.md diff --git a/.changeset/whole-loops-find.md b/.changeset/whole-loops-find.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/whole-loops-find.md @@ -0,0 +1,2 @@ +--- +--- From 7740983d0631d891461fd09b6889cde35e22f341 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 10 Jun 2026 14:48:48 -0400 Subject: [PATCH 06/16] refactor(headless): replace masking type assertions with validated satisfies The default-prop objects on every primitive part were cast with `as React.ComponentPropsWithRef`, which disabled all type checking on the object just to permit internal `data-cl-*` keys (absent from `@types/react` element props). floating-ui prop getters were also downcast from their honest `Record` to a shape the library never guarantees. - Add `DefaultProps` helper (`ComponentPropsWithRef` plus a `data-${string}` index signature) and validate authored props with `satisfies` instead of asserting with `as`. - Split hybrid parts into typed `ownProps` + the uncast floating-ui getter result, which `mergeProps` already accepts. - mosaic `variables.ts`: convert theme-factory `as MosaicTheme[...]` casts to `satisfies` so the helper implementations are checked against the theme contract. No runtime or public API change. --- .changeset/wise-types-satisfy.md | 2 ++ .../autocomplete/autocomplete-input.tsx | 4 ++-- .../autocomplete/autocomplete-list.tsx | 11 ++++++----- .../autocomplete/autocomplete-option.tsx | 14 +++++++++----- .../autocomplete/autocomplete-positioner.tsx | 11 ++++++----- .../src/primitives/dialog/dialog-backdrop.tsx | 4 ++-- .../src/primitives/dialog/dialog-close.tsx | 4 ++-- .../primitives/dialog/dialog-description.tsx | 4 ++-- .../src/primitives/dialog/dialog-popup.tsx | 10 +++++----- .../src/primitives/dialog/dialog-title.tsx | 4 ++-- .../src/primitives/dialog/dialog-trigger.tsx | 11 ++++++----- .../src/primitives/dialog/dialog-viewport.tsx | 4 ++-- .../headless/src/primitives/menu/menu-item.tsx | 18 +++++++++++------- .../src/primitives/menu/menu-positioner.tsx | 11 ++++++----- .../src/primitives/menu/menu-trigger.tsx | 13 +++++++------ .../primitives/popover/popover-positioner.tsx | 9 +++++---- .../src/primitives/popover/popover-trigger.tsx | 11 ++++++----- .../src/primitives/select/select-option.tsx | 17 ++++++++++------- .../primitives/select/select-positioner.tsx | 11 ++++++----- .../src/primitives/select/select-trigger.tsx | 11 ++++++----- .../primitives/tooltip/tooltip-positioner.tsx | 11 ++++++----- .../src/primitives/tooltip/tooltip-trigger.tsx | 11 ++++++----- packages/headless/src/utils/index.ts | 2 +- packages/headless/src/utils/render-element.tsx | 14 ++++++++++++++ packages/ui/src/mosaic/variables.ts | 8 ++++---- 25 files changed, 134 insertions(+), 96 deletions(-) create mode 100644 .changeset/wise-types-satisfy.md diff --git a/.changeset/wise-types-satisfy.md b/.changeset/wise-types-satisfy.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/wise-types-satisfy.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/headless/src/primitives/autocomplete/autocomplete-input.tsx b/packages/headless/src/primitives/autocomplete/autocomplete-input.tsx index 0604785a422..0a432545e3a 100644 --- a/packages/headless/src/primitives/autocomplete/autocomplete-input.tsx +++ b/packages/headless/src/primitives/autocomplete/autocomplete-input.tsx @@ -33,7 +33,7 @@ export const AutocompleteInput = React.forwardRef), + }), }; return renderElement({ diff --git a/packages/headless/src/primitives/autocomplete/autocomplete-list.tsx b/packages/headless/src/primitives/autocomplete/autocomplete-list.tsx index 27b4e952c6c..a31cf55c764 100644 --- a/packages/headless/src/primitives/autocomplete/autocomplete-list.tsx +++ b/packages/headless/src/primitives/autocomplete/autocomplete-list.tsx @@ -3,7 +3,7 @@ import { FloatingList, useMergeRefs } from '@floating-ui/react'; import React, { useEffect } from 'react'; -import { type ComponentProps, mergeProps, renderElement } from '../../utils/render-element'; +import { type ComponentProps, type DefaultProps, mergeProps, renderElement } from '../../utils/render-element'; import { useAutocompleteContext } from './autocomplete-context'; export type AutocompleteListProps = ComponentProps<'div'>; @@ -24,14 +24,15 @@ export const AutocompleteList = React.forwardRef; + const floatingProps = getFloatingProps(); const wiredId = floatingProps.id; - const defaultProps = { + const ownProps = { 'data-cl-slot': 'autocomplete-list', ref: combinedRef, - ...floatingProps, - }; + } satisfies DefaultProps<'div'>; + + const defaultProps = { ...ownProps, ...floatingProps }; const merged = mergeProps<'div'>(defaultProps, otherProps); // The wired id is owned by the primitive: a consumer-supplied id must not diff --git a/packages/headless/src/primitives/autocomplete/autocomplete-option.tsx b/packages/headless/src/primitives/autocomplete/autocomplete-option.tsx index 38ba5d05237..ddd22e1b799 100644 --- a/packages/headless/src/primitives/autocomplete/autocomplete-option.tsx +++ b/packages/headless/src/primitives/autocomplete/autocomplete-option.tsx @@ -3,7 +3,7 @@ import { useListItem, useMergeRefs } from '@floating-ui/react'; import React, { useEffect, useId } from 'react'; -import { type ComponentProps, mergeProps, renderElement } from '../../utils/render-element'; +import { type ComponentProps, type DefaultProps, mergeProps, renderElement } from '../../utils/render-element'; import { useAutocompleteContext } from './autocomplete-context'; export interface AutocompleteOptionProps extends ComponentProps<'div'> { @@ -43,21 +43,25 @@ export const AutocompleteOption = React.forwardRef; + + const defaultProps = { + ...ownProps, + ...getItemProps({ onClick() { if (!disabled) { handleSelect(value, index, displayLabel); (refs.domReference.current as HTMLElement | null)?.focus(); } }, - }) as React.ComponentPropsWithRef<'div'>), + }), }; const merged = mergeProps<'div'>(defaultProps, otherProps); diff --git a/packages/headless/src/primitives/autocomplete/autocomplete-positioner.tsx b/packages/headless/src/primitives/autocomplete/autocomplete-positioner.tsx index 4cb4cc03cfa..ccb90a6cdf2 100644 --- a/packages/headless/src/primitives/autocomplete/autocomplete-positioner.tsx +++ b/packages/headless/src/primitives/autocomplete/autocomplete-positioner.tsx @@ -3,7 +3,7 @@ import { FloatingFocusManager, FloatingList, useMergeRefs } from '@floating-ui/react'; import React from 'react'; -import { type ComponentProps, mergeProps, renderElement } from '../../utils/render-element'; +import { type ComponentProps, type DefaultProps, mergeProps, renderElement } from '../../utils/render-element'; import { useAutocompleteContext } from './autocomplete-context'; export type AutocompletePositionerProps = ComponentProps<'div'>; @@ -22,16 +22,17 @@ export const AutocompletePositioner = React.forwardRef; + const floatingProps = getFloatingProps(); const wiredId = floatingProps.id; - const defaultProps = { + const ownProps = { 'data-cl-slot': 'autocomplete-positioner', 'data-cl-side': side, ref: combinedRef, style: floatingStyles, - ...floatingProps, - }; + } satisfies DefaultProps<'div'>; + + const defaultProps = { ...ownProps, ...floatingProps }; const merged = mergeProps<'div'>(defaultProps, otherProps); // The wired id is owned by the primitive: a consumer-supplied id must not diff --git a/packages/headless/src/primitives/dialog/dialog-backdrop.tsx b/packages/headless/src/primitives/dialog/dialog-backdrop.tsx index 2c58ad5a3d3..d8ccc183d1c 100644 --- a/packages/headless/src/primitives/dialog/dialog-backdrop.tsx +++ b/packages/headless/src/primitives/dialog/dialog-backdrop.tsx @@ -2,7 +2,7 @@ import React from 'react'; -import { type ComponentProps, mergeProps, renderElement } from '../../utils'; +import { type ComponentProps, type DefaultProps, mergeProps, renderElement } from '../../utils'; import { useDialogContext } from './dialog-context'; export type DialogBackdropProps = ComponentProps<'div'>; @@ -22,7 +22,7 @@ export const DialogBackdrop = React.forwardRef; + } satisfies DefaultProps<'div'>; return renderElement({ defaultTagName: 'div', diff --git a/packages/headless/src/primitives/dialog/dialog-close.tsx b/packages/headless/src/primitives/dialog/dialog-close.tsx index 2b5594a87f6..726a42dba2a 100644 --- a/packages/headless/src/primitives/dialog/dialog-close.tsx +++ b/packages/headless/src/primitives/dialog/dialog-close.tsx @@ -2,7 +2,7 @@ import React from 'react'; -import { type ComponentProps, mergeProps, renderElement } from '../../utils'; +import { type ComponentProps, type DefaultProps, mergeProps, renderElement } from '../../utils'; import { useDialogContext } from './dialog-context'; export type DialogCloseProps = ComponentProps<'button'>; @@ -18,7 +18,7 @@ export const DialogClose = React.forwardRef onClick() { setOpen(false); }, - } as React.ComponentPropsWithRef<'button'>; + } satisfies DefaultProps<'button'>; return renderElement({ defaultTagName: 'button', diff --git a/packages/headless/src/primitives/dialog/dialog-description.tsx b/packages/headless/src/primitives/dialog/dialog-description.tsx index 8c178064105..cf998d01bea 100644 --- a/packages/headless/src/primitives/dialog/dialog-description.tsx +++ b/packages/headless/src/primitives/dialog/dialog-description.tsx @@ -2,7 +2,7 @@ import React from 'react'; -import { type ComponentProps, mergeProps, renderElement } from '../../utils'; +import { type ComponentProps, type DefaultProps, mergeProps, renderElement } from '../../utils'; import { useDialogContext } from './dialog-context'; export type DialogDescriptionProps = Omit, 'id'>; @@ -16,7 +16,7 @@ export const DialogDescription = React.forwardRef; + } satisfies DefaultProps<'p'>; return renderElement({ defaultTagName: 'p', diff --git a/packages/headless/src/primitives/dialog/dialog-popup.tsx b/packages/headless/src/primitives/dialog/dialog-popup.tsx index 225b7a823b2..4187106dad4 100644 --- a/packages/headless/src/primitives/dialog/dialog-popup.tsx +++ b/packages/headless/src/primitives/dialog/dialog-popup.tsx @@ -3,7 +3,7 @@ import { FloatingFocusManager, useMergeRefs } from '@floating-ui/react'; import React from 'react'; -import { type ComponentProps, mergeProps, renderElement } from '../../utils'; +import { type ComponentProps, type DefaultProps, mergeProps, renderElement } from '../../utils'; import { useDialogContext } from './dialog-context'; export type DialogPopupProps = ComponentProps<'div'>; @@ -23,14 +23,14 @@ export const DialogPopup = React.forwardRef(fu return null; } - const defaultProps = { + const ownProps = { 'data-cl-slot': 'dialog-popup', ref: combinedRef, 'aria-labelledby': labelId, 'aria-describedby': descriptionId, - ...(getFloatingProps() as React.ComponentPropsWithRef<'div'>), - ...transitionProps, - }; + } satisfies DefaultProps<'div'>; + + const defaultProps = { ...ownProps, ...getFloatingProps(), ...transitionProps }; return ( , 'id'>; @@ -15,7 +15,7 @@ export const DialogTitle = React.forwardRef; + } satisfies DefaultProps<'h2'>; return renderElement({ defaultTagName: 'h2', diff --git a/packages/headless/src/primitives/dialog/dialog-trigger.tsx b/packages/headless/src/primitives/dialog/dialog-trigger.tsx index 2cd5a470956..87bdd5d8f90 100644 --- a/packages/headless/src/primitives/dialog/dialog-trigger.tsx +++ b/packages/headless/src/primitives/dialog/dialog-trigger.tsx @@ -3,7 +3,7 @@ import { useMergeRefs } from '@floating-ui/react'; import React from 'react'; -import { type ComponentProps, mergeProps, renderElement } from '../../utils'; +import { type ComponentProps, type DefaultProps, mergeProps, renderElement } from '../../utils'; import { useDialogContext } from './dialog-context'; export type DialogTriggerProps = ComponentProps<'button'>; @@ -21,12 +21,13 @@ export const DialogTrigger = React.forwardRef), - }; + } satisfies DefaultProps<'button'>; + + const defaultProps = { ...ownProps, ...getReferenceProps() }; return renderElement({ defaultTagName: 'button', diff --git a/packages/headless/src/primitives/dialog/dialog-viewport.tsx b/packages/headless/src/primitives/dialog/dialog-viewport.tsx index 51723b6b6c1..2311f34ff90 100644 --- a/packages/headless/src/primitives/dialog/dialog-viewport.tsx +++ b/packages/headless/src/primitives/dialog/dialog-viewport.tsx @@ -3,7 +3,7 @@ import { FloatingOverlay } from '@floating-ui/react'; import React from 'react'; -import { type ComponentProps, mergeProps, renderElement } from '../../utils'; +import { type ComponentProps, type DefaultProps, mergeProps, renderElement } from '../../utils'; import { useDialogContext } from './dialog-context'; export interface DialogViewportProps extends ComponentProps<'div'> { @@ -26,7 +26,7 @@ export const DialogViewport = React.forwardRef; + } satisfies DefaultProps<'div'>; return ( diff --git a/packages/headless/src/primitives/menu/menu-item.tsx b/packages/headless/src/primitives/menu/menu-item.tsx index 826c90106f9..26d74829c6a 100644 --- a/packages/headless/src/primitives/menu/menu-item.tsx +++ b/packages/headless/src/primitives/menu/menu-item.tsx @@ -3,7 +3,7 @@ import { useFloatingTree, useListItem, useMergeRefs } from '@floating-ui/react'; import React from 'react'; -import { type ComponentProps, mergeProps, renderElement } from '../../utils/render-element'; +import { type ComponentProps, type DefaultProps, mergeProps, renderElement } from '../../utils/render-element'; import { useMenuContext } from './menu-context'; export interface MenuItemProps extends ComponentProps<'button'> { @@ -31,20 +31,24 @@ export const MenuItem = React.forwardRef(funct disabled: !!disabled, }; - const defaultProps = { + const ownProps = { 'data-cl-slot': 'menu-item', - type: 'button' as const, + type: 'button', ref: combinedRef, - role: 'menuitem' as const, + role: 'menuitem', tabIndex: isActive ? 0 : -1, - ...(disabled && { 'aria-disabled': true as const }), - ...(getItemProps({ + ...(disabled && { 'aria-disabled': true }), + } satisfies DefaultProps<'button'>; + + const defaultProps = { + ...ownProps, + ...getItemProps({ onClick() { if (!disabled && closeOnClick) { tree?.events.emit('click'); } }, - }) as React.ComponentPropsWithRef<'button'>), + }), }; return renderElement({ diff --git a/packages/headless/src/primitives/menu/menu-positioner.tsx b/packages/headless/src/primitives/menu/menu-positioner.tsx index 811adc047a5..db1bab027e4 100644 --- a/packages/headless/src/primitives/menu/menu-positioner.tsx +++ b/packages/headless/src/primitives/menu/menu-positioner.tsx @@ -3,7 +3,7 @@ import { FloatingFocusManager, FloatingList, useMergeRefs } from '@floating-ui/react'; import React from 'react'; -import { type ComponentProps, mergeProps, renderElement } from '../../utils/render-element'; +import { type ComponentProps, type DefaultProps, mergeProps, renderElement } from '../../utils/render-element'; import { useMenuContext } from './menu-context'; export type MenuPositionerProps = ComponentProps<'div'>; @@ -53,15 +53,16 @@ export const MenuPositioner = React.forwardRef; + }); - const defaultProps = { + const ownProps = { 'data-cl-slot': 'menu-positioner', 'data-cl-side': side, ref: combinedRef, style: floatingStyles, - ...floatingProps, - }; + } satisfies DefaultProps<'div'>; + + const defaultProps = { ...ownProps, ...floatingProps }; if (!mounted) { return null; diff --git a/packages/headless/src/primitives/menu/menu-trigger.tsx b/packages/headless/src/primitives/menu/menu-trigger.tsx index 40a501e7ad8..c639be8caac 100644 --- a/packages/headless/src/primitives/menu/menu-trigger.tsx +++ b/packages/headless/src/primitives/menu/menu-trigger.tsx @@ -3,7 +3,7 @@ import { useListItem, useMergeRefs } from '@floating-ui/react'; import React from 'react'; -import { type ComponentProps, mergeProps, renderElement } from '../../utils/render-element'; +import { type ComponentProps, type DefaultProps, mergeProps, renderElement } from '../../utils/render-element'; import { useMenuContext } from './menu-context'; export type MenuTriggerProps = ComponentProps<'button'>; @@ -30,16 +30,17 @@ export const MenuTrigger = React.forwardRef referenceProps = getReferenceProps(); } - const defaultProps = { - type: 'button' as const, + const ownProps = { + type: 'button', 'data-cl-slot': 'menu-trigger', ref: mergedRef, ...(isNested && { - role: 'menuitem' as const, + role: 'menuitem', tabIndex: parentContext?.activeIndex === item.index ? 0 : -1, }), - ...(referenceProps as React.ComponentPropsWithRef<'button'>), - }; + } satisfies DefaultProps<'button'>; + + const defaultProps = { ...ownProps, ...referenceProps }; return renderElement({ defaultTagName: 'button', diff --git a/packages/headless/src/primitives/popover/popover-positioner.tsx b/packages/headless/src/primitives/popover/popover-positioner.tsx index 7e6a8e1abae..3a07a70519f 100644 --- a/packages/headless/src/primitives/popover/popover-positioner.tsx +++ b/packages/headless/src/primitives/popover/popover-positioner.tsx @@ -3,7 +3,7 @@ import { FloatingFocusManager, useMergeRefs } from '@floating-ui/react'; import React from 'react'; -import { type ComponentProps, mergeProps, renderElement } from '../../utils/render-element'; +import { type ComponentProps, type DefaultProps, mergeProps, renderElement } from '../../utils/render-element'; import { usePopoverContext } from './popover-context'; export type PopoverPositionerProps = ComponentProps<'div'>; @@ -33,15 +33,16 @@ export const PopoverPositioner = React.forwardRef), - }; + } satisfies DefaultProps<'div'>; + + const defaultProps = { ...ownProps, ...getFloatingProps() }; const element = renderElement({ defaultTagName: 'div', diff --git a/packages/headless/src/primitives/popover/popover-trigger.tsx b/packages/headless/src/primitives/popover/popover-trigger.tsx index 8231e348bed..b4b5bc8f17c 100644 --- a/packages/headless/src/primitives/popover/popover-trigger.tsx +++ b/packages/headless/src/primitives/popover/popover-trigger.tsx @@ -3,7 +3,7 @@ import { useMergeRefs } from '@floating-ui/react'; import React from 'react'; -import { type ComponentProps, mergeProps, renderElement } from '../../utils/render-element'; +import { type ComponentProps, type DefaultProps, mergeProps, renderElement } from '../../utils/render-element'; import { usePopoverContext } from './popover-context'; export type PopoverTriggerProps = ComponentProps<'button'>; @@ -21,12 +21,13 @@ export const PopoverTrigger = React.forwardRef), - }; + } satisfies DefaultProps<'button'>; + + const defaultProps = { ...ownProps, ...getReferenceProps() }; return renderElement({ defaultTagName: 'button', diff --git a/packages/headless/src/primitives/select/select-option.tsx b/packages/headless/src/primitives/select/select-option.tsx index 42a76e29897..ce0140d323d 100644 --- a/packages/headless/src/primitives/select/select-option.tsx +++ b/packages/headless/src/primitives/select/select-option.tsx @@ -1,10 +1,9 @@ 'use client'; import { useListItem, useMergeRefs } from '@floating-ui/react'; -import type React from 'react'; import { useEffect } from 'react'; -import { type ComponentProps, mergeProps, renderElement } from '../../utils/render-element'; +import { type ComponentProps, type DefaultProps, mergeProps, renderElement } from '../../utils/render-element'; import { useSelectContext } from './select-context'; export interface SelectOptionProps extends ComponentProps<'button'> { @@ -39,21 +38,25 @@ export function SelectOption(props: SelectOptionProps) { disabled: !!disabled, }; - const defaultProps = { + const ownProps = { 'data-cl-slot': 'select-option', - type: 'button' as const, + type: 'button', ref: combinedRef, - role: 'option' as const, + role: 'option', 'aria-selected': isSelected, 'aria-disabled': disabled || undefined, tabIndex: isActive ? 0 : -1, - ...(getItemProps({ + } satisfies DefaultProps<'button'>; + + const defaultProps = { + ...ownProps, + ...getItemProps({ onClick() { if (!disabled) { handleSelect(value, index); } }, - }) as React.ComponentPropsWithRef<'button'>), + }), }; return renderElement({ diff --git a/packages/headless/src/primitives/select/select-positioner.tsx b/packages/headless/src/primitives/select/select-positioner.tsx index 16ba6fee87c..d63b4a610ac 100644 --- a/packages/headless/src/primitives/select/select-positioner.tsx +++ b/packages/headless/src/primitives/select/select-positioner.tsx @@ -3,7 +3,7 @@ import { FloatingFocusManager, FloatingList, useMergeRefs } from '@floating-ui/react'; import React from 'react'; -import { type ComponentProps, mergeProps, renderElement } from '../../utils/render-element'; +import { type ComponentProps, type DefaultProps, mergeProps, renderElement } from '../../utils/render-element'; import { useSelectContext } from './select-context'; export type SelectPositionerProps = ComponentProps<'div'>; @@ -52,15 +52,16 @@ export const SelectPositioner = React.forwardRef; + }); - const defaultProps = { + const ownProps = { 'data-cl-slot': 'select-positioner', 'data-cl-side': side, ref: combinedRef, style: floatingStyles, - ...floatingProps, - }; + } satisfies DefaultProps<'div'>; + + const defaultProps = { ...ownProps, ...floatingProps }; const merged = mergeProps<'div'>(defaultProps, otherProps); // The listbox id is owned by floating-ui's listbox role: a consumer-supplied diff --git a/packages/headless/src/primitives/select/select-trigger.tsx b/packages/headless/src/primitives/select/select-trigger.tsx index 750458c18c0..c02bccb4ab9 100644 --- a/packages/headless/src/primitives/select/select-trigger.tsx +++ b/packages/headless/src/primitives/select/select-trigger.tsx @@ -3,7 +3,7 @@ import { useMergeRefs } from '@floating-ui/react'; import React from 'react'; -import { type ComponentProps, mergeProps, renderElement } from '../../utils/render-element'; +import { type ComponentProps, type DefaultProps, mergeProps, renderElement } from '../../utils/render-element'; import { useSelectContext } from './select-context'; export type SelectTriggerProps = ComponentProps<'button'>; @@ -21,12 +21,13 @@ export const SelectTrigger = React.forwardRef), - }; + } satisfies DefaultProps<'button'>; + + const defaultProps = { ...ownProps, ...getReferenceProps() }; return renderElement({ defaultTagName: 'button', diff --git a/packages/headless/src/primitives/tooltip/tooltip-positioner.tsx b/packages/headless/src/primitives/tooltip/tooltip-positioner.tsx index 2724158a456..221ba845caf 100644 --- a/packages/headless/src/primitives/tooltip/tooltip-positioner.tsx +++ b/packages/headless/src/primitives/tooltip/tooltip-positioner.tsx @@ -3,7 +3,7 @@ import { useMergeRefs } from '@floating-ui/react'; import React from 'react'; -import { type ComponentProps, mergeProps, renderElement } from '../../utils/render-element'; +import { type ComponentProps, type DefaultProps, mergeProps, renderElement } from '../../utils/render-element'; import { useTooltipContext } from './tooltip-context'; export type TooltipPositionerProps = ComponentProps<'div'>; @@ -20,16 +20,17 @@ export const TooltipPositioner = React.forwardRef; + const floatingProps = getFloatingProps(); const wiredId = floatingProps.id; - const defaultProps = { + const ownProps = { 'data-cl-slot': 'tooltip-positioner', 'data-cl-side': side, ref: combinedRef, style: floatingStyles, - ...floatingProps, - }; + } satisfies DefaultProps<'div'>; + + const defaultProps = { ...ownProps, ...floatingProps }; const merged = mergeProps<'div'>(defaultProps, otherProps); // The wired id is owned by floating-ui: it pairs with the trigger's aria-describedby. diff --git a/packages/headless/src/primitives/tooltip/tooltip-trigger.tsx b/packages/headless/src/primitives/tooltip/tooltip-trigger.tsx index 1f54df30f71..86d3bbf40a4 100644 --- a/packages/headless/src/primitives/tooltip/tooltip-trigger.tsx +++ b/packages/headless/src/primitives/tooltip/tooltip-trigger.tsx @@ -3,7 +3,7 @@ import { useMergeRefs } from '@floating-ui/react'; import React from 'react'; -import { type ComponentProps, mergeProps, renderElement } from '../../utils/render-element'; +import { type ComponentProps, type DefaultProps, mergeProps, renderElement } from '../../utils/render-element'; import { useTooltipContext } from './tooltip-context'; export type TooltipTriggerProps = ComponentProps<'button'>; @@ -21,12 +21,13 @@ export const TooltipTrigger = React.forwardRef), - }; + } satisfies DefaultProps<'button'>; + + const defaultProps = { ...ownProps, ...getReferenceProps() }; return renderElement({ defaultTagName: 'button', diff --git a/packages/headless/src/utils/index.ts b/packages/headless/src/utils/index.ts index ebfd4caefaa..2f7cb85009c 100644 --- a/packages/headless/src/utils/index.ts +++ b/packages/headless/src/utils/index.ts @@ -1,2 +1,2 @@ export { cssVars } from './css-vars'; -export { type ComponentProps, mergeProps, type RenderProp, renderElement } from './render-element'; +export { type ComponentProps, type DefaultProps, mergeProps, type RenderProp, renderElement } from './render-element'; diff --git a/packages/headless/src/utils/render-element.tsx b/packages/headless/src/utils/render-element.tsx index 4a1e5f0ba1c..06157b4990e 100644 --- a/packages/headless/src/utils/render-element.tsx +++ b/packages/headless/src/utils/render-element.tsx @@ -17,6 +17,20 @@ export type ComponentProps = Reac render?: RenderProp; }; +/** + * The props a primitive part applies to its own rendered element. Extends the + * native props for `Tag` and additionally permits internal `data-*` attributes + * (e.g. `data-cl-slot`), which `@types/react` intentionally omits from its + * element prop types. + * + * Use with `satisfies` to type-check authored default props — this validates + * every key against the real element props while still allowing our `data-*` + * attributes, instead of laundering the whole object past the checker with an + * `as` assertion. + */ +export type DefaultProps = React.ComponentPropsWithRef & + Record<`data-${string}`, string>; + /** * Maps state keys to functions that return data-attribute objects (or null). */ diff --git a/packages/ui/src/mosaic/variables.ts b/packages/ui/src/mosaic/variables.ts index e95c1b2e292..08d2f559d83 100644 --- a/packages/ui/src/mosaic/variables.ts +++ b/packages/ui/src/mosaic/variables.ts @@ -97,17 +97,17 @@ export function resolveVariables(defaults: MosaicTokens, variables?: MosaicVaria const tokens = variables ? (merge(defaults, variables) as MosaicTokens) : defaults; return { ...tokens, - spacing: ((n: N) => `calc(${tokens.spacing} * ${n})`) as MosaicTheme['spacing'], + spacing: ((n: N) => `calc(${tokens.spacing} * ${n})`) satisfies MosaicTheme['spacing'], alpha: ((color: K, opacity: O) => - `color-mix(in oklab, ${tokens.color[color]} ${opacity}%, transparent)`) as MosaicTheme['alpha'], + `color-mix(in oklab, ${tokens.color[color]} ${opacity}%, transparent)`) satisfies MosaicTheme['alpha'], mix: (( a: A, b: B, percentage: P, - ) => `color-mix(in oklab, ${tokens.color[a]}, ${tokens.color[b]} ${percentage}%)`) as MosaicTheme['mix'], + ) => `color-mix(in oklab, ${tokens.color[a]}, ${tokens.color[b]} ${percentage}%)`) satisfies MosaicTheme['mix'], text: ((key: K) => ({ fontSize: tokens.text[key].fontSize, lineHeight: tokens.text[key].lineHeight, - })) as MosaicTheme['text'], + })) satisfies MosaicTheme['text'], }; } From 49e21c33e90f17c97833e558154c75766c3f2e59 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 10 Jun 2026 15:00:10 -0400 Subject: [PATCH 07/16] Delete .changeset/whole-loops-find.md --- .changeset/whole-loops-find.md | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .changeset/whole-loops-find.md diff --git a/.changeset/whole-loops-find.md b/.changeset/whole-loops-find.md deleted file mode 100644 index a845151cc84..00000000000 --- a/.changeset/whole-loops-find.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- From 920ce9a2dfc3e751f29bdbf43cadbfab81b1bb36 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 10 Jun 2026 15:03:11 -0400 Subject: [PATCH 08/16] docs(headless,ui): add JSDoc to dialog parts and mosaic components --- .../headless/src/primitives/dialog/dialog-backdrop.tsx | 2 ++ packages/headless/src/primitives/dialog/dialog-close.tsx | 2 ++ .../src/primitives/dialog/dialog-description.tsx | 2 ++ packages/headless/src/primitives/dialog/dialog-popup.tsx | 2 ++ packages/headless/src/primitives/dialog/dialog-title.tsx | 2 ++ .../headless/src/primitives/dialog/dialog-trigger.tsx | 2 ++ .../headless/src/primitives/dialog/dialog-viewport.tsx | 9 +++++++++ packages/ui/src/mosaic/components/button.tsx | 3 +++ packages/ui/src/mosaic/components/dialog.tsx | 1 + 9 files changed, 25 insertions(+) diff --git a/packages/headless/src/primitives/dialog/dialog-backdrop.tsx b/packages/headless/src/primitives/dialog/dialog-backdrop.tsx index d8ccc183d1c..d97263270dc 100644 --- a/packages/headless/src/primitives/dialog/dialog-backdrop.tsx +++ b/packages/headless/src/primitives/dialog/dialog-backdrop.tsx @@ -5,8 +5,10 @@ import React from 'react'; import { type ComponentProps, type DefaultProps, mergeProps, renderElement } from '../../utils'; import { useDialogContext } from './dialog-context'; +/** Props for {@link DialogBackdrop}. */ export type DialogBackdropProps = ComponentProps<'div'>; +/** Semi-transparent overlay surface rendered behind the dialog. Does not own scroll-lock or positioning — use `Dialog.Viewport` for those. */ export const DialogBackdrop = React.forwardRef( function DialogBackdrop(props, ref) { const { render, ...otherProps } = props; diff --git a/packages/headless/src/primitives/dialog/dialog-close.tsx b/packages/headless/src/primitives/dialog/dialog-close.tsx index 726a42dba2a..abfc231bab3 100644 --- a/packages/headless/src/primitives/dialog/dialog-close.tsx +++ b/packages/headless/src/primitives/dialog/dialog-close.tsx @@ -5,8 +5,10 @@ import React from 'react'; import { type ComponentProps, type DefaultProps, mergeProps, renderElement } from '../../utils'; import { useDialogContext } from './dialog-context'; +/** Props for {@link DialogClose}. */ export type DialogCloseProps = ComponentProps<'button'>; +/** Button that closes the dialog when clicked. Calls `setOpen(false)` from dialog context. */ export const DialogClose = React.forwardRef(function DialogClose(props, ref) { const { render, ...otherProps } = props; const { setOpen } = useDialogContext(); diff --git a/packages/headless/src/primitives/dialog/dialog-description.tsx b/packages/headless/src/primitives/dialog/dialog-description.tsx index cf998d01bea..6e241b84a65 100644 --- a/packages/headless/src/primitives/dialog/dialog-description.tsx +++ b/packages/headless/src/primitives/dialog/dialog-description.tsx @@ -5,8 +5,10 @@ import React from 'react'; import { type ComponentProps, type DefaultProps, mergeProps, renderElement } from '../../utils'; import { useDialogContext } from './dialog-context'; +/** Props for {@link DialogDescription}. */ export type DialogDescriptionProps = Omit, 'id'>; +/** Accessible dialog description. Wires its `id` to `aria-describedby` on `Dialog.Popup`. */ export const DialogDescription = React.forwardRef( function DialogDescription(props, ref) { const { render, ...otherProps } = props; diff --git a/packages/headless/src/primitives/dialog/dialog-popup.tsx b/packages/headless/src/primitives/dialog/dialog-popup.tsx index 4187106dad4..46644abf7b9 100644 --- a/packages/headless/src/primitives/dialog/dialog-popup.tsx +++ b/packages/headless/src/primitives/dialog/dialog-popup.tsx @@ -6,8 +6,10 @@ import React from 'react'; import { type ComponentProps, type DefaultProps, mergeProps, renderElement } from '../../utils'; import { useDialogContext } from './dialog-context'; +/** Props for {@link DialogPopup}. */ export type DialogPopupProps = ComponentProps<'div'>; +/** The dialog content container. Manages focus trapping via `FloatingFocusManager` and wires ARIA attributes from `Dialog.Title` and `Dialog.Description`. */ export const DialogPopup = React.forwardRef(function DialogPopup(props, ref) { const { render, ...otherProps } = props; const { popupRef, refs, getFloatingProps, floatingContext, modal, labelId, descriptionId, mounted, transitionProps } = diff --git a/packages/headless/src/primitives/dialog/dialog-title.tsx b/packages/headless/src/primitives/dialog/dialog-title.tsx index de9b482d0f2..7f9aef97315 100644 --- a/packages/headless/src/primitives/dialog/dialog-title.tsx +++ b/packages/headless/src/primitives/dialog/dialog-title.tsx @@ -5,8 +5,10 @@ import React from 'react'; import { type ComponentProps, type DefaultProps, mergeProps, renderElement } from '../../utils'; import { useDialogContext } from './dialog-context'; +/** Props for {@link DialogTitle}. */ export type DialogTitleProps = Omit, 'id'>; +/** Accessible dialog heading. Wires its `id` to `aria-labelledby` on `Dialog.Popup`. */ export const DialogTitle = React.forwardRef(function DialogTitle(props, ref) { const { render, ...otherProps } = props; const { labelId } = useDialogContext(); diff --git a/packages/headless/src/primitives/dialog/dialog-trigger.tsx b/packages/headless/src/primitives/dialog/dialog-trigger.tsx index 87bdd5d8f90..f97f7dbc49a 100644 --- a/packages/headless/src/primitives/dialog/dialog-trigger.tsx +++ b/packages/headless/src/primitives/dialog/dialog-trigger.tsx @@ -6,8 +6,10 @@ import React from 'react'; import { type ComponentProps, type DefaultProps, mergeProps, renderElement } from '../../utils'; import { useDialogContext } from './dialog-context'; +/** Props for {@link DialogTrigger}. */ export type DialogTriggerProps = ComponentProps<'button'>; +/** Button that opens the dialog. Wired to Floating UI's reference element for ARIA and interaction handling. */ export const DialogTrigger = React.forwardRef( function DialogTrigger(props, ref) { const { render, ...otherProps } = props; diff --git a/packages/headless/src/primitives/dialog/dialog-viewport.tsx b/packages/headless/src/primitives/dialog/dialog-viewport.tsx index 2311f34ff90..18edeff5d09 100644 --- a/packages/headless/src/primitives/dialog/dialog-viewport.tsx +++ b/packages/headless/src/primitives/dialog/dialog-viewport.tsx @@ -6,11 +6,20 @@ import React from 'react'; import { type ComponentProps, type DefaultProps, mergeProps, renderElement } from '../../utils'; import { useDialogContext } from './dialog-context'; +/** Props for {@link DialogViewport}. */ export interface DialogViewportProps extends ComponentProps<'div'> { /** When true, locks body scroll while the dialog is open. Default: true */ lockScroll?: boolean; } +/** + * Fixed centering container for dialog content. Wraps `FloatingOverlay` to + * provide scroll-locking and full-viewport positioning. Nest `Dialog.Popup` + * inside this component for standard modal behavior. + * + * Scroll-lock responsibility lives here, not on `Dialog.Backdrop`, so the + * backdrop surface can be styled independently. + */ export const DialogViewport = React.forwardRef( function DialogViewport(props, ref) { const { render, lockScroll = true, ...otherProps } = props; diff --git a/packages/ui/src/mosaic/components/button.tsx b/packages/ui/src/mosaic/components/button.tsx index 32ec885a53f..cfb8b38f862 100644 --- a/packages/ui/src/mosaic/components/button.tsx +++ b/packages/ui/src/mosaic/components/button.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { cva, type VariantProps } from '../cva'; import { useMosaicTheme } from '../MosaicProvider'; +/** CVA style definition for `Button` variants (`intent`, `size`, `disabled`). */ export const buttonStyles = cva(theme => ({ base: { display: 'inline-flex', @@ -43,8 +44,10 @@ export const buttonStyles = cva(theme => ({ defaultVariants: { intent: 'primary', size: 'md', disabled: false }, })); +/** Props for {@link Button}. */ export type ButtonProps = React.ComponentPropsWithRef<'button'> & VariantProps; +/** Styled mosaic Button component with `intent`, `size`, and `disabled` variants. */ export const Button = React.forwardRef(function MosaicButton(props, ref) { const { intent, size, disabled, sx, children, ...rest } = props; const theme = useMosaicTheme(); diff --git a/packages/ui/src/mosaic/components/dialog.tsx b/packages/ui/src/mosaic/components/dialog.tsx index 388c62aaddf..bd9e1317a0c 100644 --- a/packages/ui/src/mosaic/components/dialog.tsx +++ b/packages/ui/src/mosaic/components/dialog.tsx @@ -91,6 +91,7 @@ const Title = styled(Primitive.Title, titleStyles); const Description = styled(Primitive.Description, descriptionStyles); const Close = styled(Primitive.Close, closeStyles); +/** Styled mosaic Dialog components built on headless Dialog primitives. */ export const Dialog: { Root: typeof Primitive.Root; Trigger: typeof Primitive.Trigger; From 9c291ef564fc01a0c5fcca2eda957b4db07b47e7 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 10 Jun 2026 15:03:15 -0400 Subject: [PATCH 09/16] fix(ui): preserve generic props and correct return type in withMosaicTheme --- packages/ui/src/mosaic/primitives/withMosaicTheme.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/mosaic/primitives/withMosaicTheme.tsx b/packages/ui/src/mosaic/primitives/withMosaicTheme.tsx index 93afad7125c..9edd969f37f 100644 --- a/packages/ui/src/mosaic/primitives/withMosaicTheme.tsx +++ b/packages/ui/src/mosaic/primitives/withMosaicTheme.tsx @@ -9,19 +9,21 @@ import type { Mosaic } from '../types'; * mosaic theme, and forwards it as an Emotion `css` prop. The headless part * turns that into a `className` and forwards it to its rendered DOM node. */ -export const withMosaicTheme = (Component: React.FunctionComponent

): React.FunctionComponent> => { - const Wrapped = React.forwardRef((props: Mosaic, ref) => { +export const withMosaicTheme = ( + Component: React.FunctionComponent

, +): React.ForwardRefExoticComponent> & React.RefAttributes> => { + const Wrapped = React.forwardRef>((props, ref) => { const { sx, ...rest } = props; const theme = useMosaicTheme(); const css = typeof sx === 'function' ? sx(theme) : sx; return ( ); }); Wrapped.displayName = `Mosaic${Component.displayName || Component.name || 'Component'}`; - return Wrapped as React.FunctionComponent>; + return Wrapped; }; From aeaceb7e97df153eecda74e4c733c8ef9dd01ee0 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 10 Jun 2026 15:09:25 -0400 Subject: [PATCH 10/16] fix(headless): pass pointer events through viewport overlay when non-modal --- .../src/primitives/dialog/dialog-viewport.tsx | 8 +++-- .../src/primitives/dialog/dialog.test.tsx | 29 +++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/headless/src/primitives/dialog/dialog-viewport.tsx b/packages/headless/src/primitives/dialog/dialog-viewport.tsx index 18edeff5d09..6205116ccc8 100644 --- a/packages/headless/src/primitives/dialog/dialog-viewport.tsx +++ b/packages/headless/src/primitives/dialog/dialog-viewport.tsx @@ -23,7 +23,7 @@ export interface DialogViewportProps extends ComponentProps<'div'> { export const DialogViewport = React.forwardRef( function DialogViewport(props, ref) { const { render, lockScroll = true, ...otherProps } = props; - const { open, mounted, transitionProps } = useDialogContext(); + const { open, mounted, transitionProps, modal } = useDialogContext(); if (!mounted) { return null; @@ -35,10 +35,14 @@ export const DialogViewport = React.forwardRef; return ( - + {renderElement({ defaultTagName: 'div', render, diff --git a/packages/headless/src/primitives/dialog/dialog.test.tsx b/packages/headless/src/primitives/dialog/dialog.test.tsx index 005bb973782..29f7f7c3a7b 100644 --- a/packages/headless/src/primitives/dialog/dialog.test.tsx +++ b/packages/headless/src/primitives/dialog/dialog.test.tsx @@ -236,6 +236,35 @@ describe('Dialog', () => { // Focus should be trapped inside the dialog expect(screen.getByRole('dialog')).toBeInTheDocument(); }); + + it('non-modal dialog allows background interaction', async () => { + const onBackgroundClick = vi.fn(); + const user = userEvent.setup(); + render( + <> + + + Open dialog + + + + Dialog Title + Close + + + + , + ); + + await user.click(screen.getByRole('button', { name: 'Open dialog' })); + + const viewport = document.querySelector('[data-cl-slot="dialog-viewport"]'); + expect(viewport).toHaveStyle({ pointerEvents: 'auto' }); + expect(viewport?.parentElement).toHaveStyle({ pointerEvents: 'none' }); + + await user.click(screen.getByRole('button', { name: 'Background button' })); + expect(onBackgroundClick).toHaveBeenCalledTimes(1); + }); }); describe('focus management', () => { From 3b94b24abcb035c293f4db5f8d4451580843484c Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 10 Jun 2026 15:17:38 -0400 Subject: [PATCH 11/16] fix @clerk/headless usage in ui package --- packages/ui/package.json | 2 +- packages/ui/tsdown.config.mts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index 5d5e2ee7b3f..2afa479630c 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -94,7 +94,6 @@ "type-check": "tsc --noEmit" }, "dependencies": { - "@clerk/headless": "workspace:^", "@clerk/localizations": "workspace:^", "@clerk/shared": "workspace:^", "@emotion/cache": "11.11.0", @@ -113,6 +112,7 @@ "qrcode.react": "4.2.0" }, "devDependencies": { + "@clerk/headless": "workspace:^", "@floating-ui/react-dom": "^2.1.8", "@rsdoctor/rspack-plugin": "^0.4.13", "@rspack/cli": "catalog:rspack", diff --git a/packages/ui/tsdown.config.mts b/packages/ui/tsdown.config.mts index 1ab785fffb5..fcf7e5e089e 100644 --- a/packages/ui/tsdown.config.mts +++ b/packages/ui/tsdown.config.mts @@ -12,6 +12,7 @@ export default defineConfig(({ watch }) => { target: 'es2022', platform: 'browser', external: ['react', 'react-dom', '@clerk/localizations', '@clerk/shared'], + noExternal: ['@clerk/headless'], format: ['esm'], // ESM only fixedExtension: false, minify: false, From d1f8db1e7505e1e760ba7ac1b2ac590e0d987bed Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 10 Jun 2026 15:21:07 -0400 Subject: [PATCH 12/16] chore(repo): update lockfile after moving @clerk/headless to devDependencies in @clerk/ui --- pnpm-lock.yaml | 127 ++++++++++++++++++++++++------------------------- 1 file changed, 63 insertions(+), 64 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 726da22dd60..3d4b72e7705 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -385,7 +385,7 @@ importers: version: 9.0.2 vitest-environment-miniflare: specifier: 2.14.4 - version: 2.14.4(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@25.6.0)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(lightningcss@1.32.0)(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) + version: 2.14.4(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vitest@4.1.4) packages/chrome-extension: dependencies: @@ -1053,9 +1053,6 @@ importers: packages/ui: dependencies: - '@clerk/headless': - specifier: workspace:^ - version: link:../headless '@clerk/localizations': specifier: workspace:^ version: link:../localizations @@ -1111,6 +1108,9 @@ importers: specifier: 18.3.1 version: 18.3.1(react@18.3.1) devDependencies: + '@clerk/headless': + specifier: workspace:^ + version: link:../headless '@floating-ui/react-dom': specifier: ^2.1.8 version: 2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -2910,7 +2910,7 @@ packages: '@expo/bunyan@4.0.1': resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==} - engines: {node: '>=0.10.0'} + engines: {'0': node >=0.10.0} '@expo/cli@0.22.28': resolution: {integrity: sha512-lvt72KNitGuixYD2l3SZmRKVu2G4zJpmg5V7WfUBNpmUU5oODBw/6qmiJ6kSLAlfDozscUk+BBGknBBzxUrwrA==} @@ -22002,6 +22002,20 @@ snapshots: - vite optional: true + '@vitest/browser-playwright@4.1.4(bufferutil@4.1.0)(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(playwright@1.59.1)(utf-8-validate@5.0.10)(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))(vitest@4.1.4)': + dependencies: + '@vitest/browser': 4.1.4(bufferutil@4.1.0)(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(utf-8-validate@5.0.10)(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))(vitest@4.1.4) + '@vitest/mocker': 4.1.4(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) + playwright: 1.59.1 + tinyrainbow: 3.1.0 + vitest: 4.1.4(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.4)(@vitest/coverage-v8@3.2.4(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.17)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(lightningcss@1.32.0)(msw@2.14.2(@types/node@22.19.17)(typescript@6.0.3))(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)))(happy-dom@18.0.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + optional: true + '@vitest/browser@4.1.4(bufferutil@4.1.0)(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(utf-8-validate@5.0.10)(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))(vitest@4.1.4)': dependencies: '@blazediff/core': 1.9.1 @@ -22020,6 +22034,24 @@ snapshots: - vite optional: true + '@vitest/browser@4.1.4(bufferutil@4.1.0)(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(utf-8-validate@5.0.10)(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))(vitest@4.1.4)': + dependencies: + '@blazediff/core': 1.9.1 + '@vitest/mocker': 4.1.4(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) + '@vitest/utils': 4.1.4 + magic-string: 0.30.21 + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.1.0 + vitest: 4.1.4(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.4)(@vitest/coverage-v8@3.2.4(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.17)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(lightningcss@1.32.0)(msw@2.14.2(@types/node@22.19.17)(typescript@6.0.3))(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)))(happy-dom@18.0.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) + ws: 8.20.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + optional: true + '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.17)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(lightningcss@1.32.0)(msw@2.14.2(@types/node@22.19.17)(typescript@6.0.3))(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))': dependencies: '@ampproject/remapping': 2.3.0 @@ -22065,23 +22097,23 @@ snapshots: msw: 2.14.2(@types/node@22.19.17)(typescript@6.0.3) vite: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3) - '@vitest/mocker@3.2.4(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))': + '@vitest/mocker@4.1.4(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))': dependencies: - '@vitest/spy': 3.2.4 + '@vitest/spy': 4.1.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: msw: 2.14.2(@types/node@25.6.0)(typescript@6.0.3) vite: 6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3) - '@vitest/mocker@4.1.4(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))': + '@vitest/mocker@4.1.4(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))': dependencies: '@vitest/spy': 4.1.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: msw: 2.14.2(@types/node@25.6.0)(typescript@6.0.3) - vite: 6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3) + vite: 7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3) '@vitest/pretty-format@3.2.4': dependencies: @@ -32687,27 +32719,6 @@ snapshots: - tsx - yaml - vite-node@3.2.4(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3): - dependencies: - cac: 6.7.14 - debug: 4.4.3(supports-color@8.1.1) - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - vite-node@5.3.0(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3): dependencies: cac: 6.7.14 @@ -32879,14 +32890,14 @@ snapshots: dependencies: '@types/chrome': 0.0.114 - vitest-environment-miniflare@2.14.4(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@25.6.0)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(lightningcss@1.32.0)(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)): + vitest-environment-miniflare@2.14.4(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vitest@4.1.4): dependencies: '@miniflare/queues': 2.14.4 '@miniflare/runner-vm': 2.14.4 '@miniflare/shared': 2.14.4 '@miniflare/shared-test-environment': 2.14.4(bufferutil@4.1.0)(utf-8-validate@5.0.10) undici: 5.28.4 - vitest: 3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@25.6.0)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(lightningcss@1.32.0)(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3) + vitest: 4.1.4(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.4)(@vitest/coverage-v8@3.2.4(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.17)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(lightningcss@1.32.0)(msw@2.14.2(@types/node@22.19.17)(typescript@6.0.3))(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)))(happy-dom@18.0.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) transitivePeerDependencies: - bufferutil - utf-8-validate @@ -32936,55 +32947,43 @@ snapshots: - tsx - yaml - vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@25.6.0)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(lightningcss@1.32.0)(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3): + vitest@4.1.4(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.4)(@vitest/coverage-v8@3.2.4(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.17)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(lightningcss@1.32.0)(msw@2.14.2(@types/node@22.19.17)(typescript@6.0.3))(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)))(happy-dom@18.0.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)): dependencies: - '@types/chai': 5.2.3 - '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) - '@vitest/pretty-format': 3.2.4 - '@vitest/runner': 3.2.4 - '@vitest/snapshot': 3.2.4 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.3.3 - debug: 4.4.3(supports-color@8.1.1) + '@vitest/expect': 4.1.4 + '@vitest/mocker': 4.1.4(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) + '@vitest/pretty-format': 4.1.4 + '@vitest/runner': 4.1.4 + '@vitest/snapshot': 4.1.4 + '@vitest/spy': 4.1.4 + '@vitest/utils': 4.1.4 + es-module-lexer: 2.1.0 expect-type: 1.3.0 magic-string: 0.30.21 + obug: 2.1.1 pathe: 2.0.3 picomatch: 4.0.4 - std-env: 3.10.0 + std-env: 4.1.0 tinybench: 2.9.0 - tinyexec: 0.3.2 + tinyexec: 1.2.4 tinyglobby: 0.2.17 - tinypool: 1.1.1 - tinyrainbow: 2.0.0 + tinyrainbow: 3.1.0 vite: 6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3) - vite-node: 3.2.4(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: '@edge-runtime/vm': 5.0.0 - '@types/debug': 4.1.12 + '@opentelemetry/api': 1.9.0 '@types/node': 25.6.0 + '@vitest/browser-playwright': 4.1.4(bufferutil@4.1.0)(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(playwright@1.59.1)(utf-8-validate@5.0.10)(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))(vitest@4.1.4) + '@vitest/coverage-v8': 3.2.4(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.17)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(lightningcss@1.32.0)(msw@2.14.2(@types/node@22.19.17)(typescript@6.0.3))(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) happy-dom: 18.0.1 jsdom: 27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) transitivePeerDependencies: - - jiti - - less - - lightningcss - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - vitest@4.1.4(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.4)(@vitest/coverage-v8@3.2.4(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.17)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(lightningcss@1.32.0)(msw@2.14.2(@types/node@22.19.17)(typescript@6.0.3))(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)))(happy-dom@18.0.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)): + vitest@4.1.4(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.4)(@vitest/coverage-v8@3.2.4(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.17)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(lightningcss@1.32.0)(msw@2.14.2(@types/node@22.19.17)(typescript@6.0.3))(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)))(happy-dom@18.0.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)): dependencies: '@vitest/expect': 4.1.4 - '@vitest/mocker': 4.1.4(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) + '@vitest/mocker': 4.1.4(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) '@vitest/pretty-format': 4.1.4 '@vitest/runner': 4.1.4 '@vitest/snapshot': 4.1.4 @@ -33001,13 +33000,13 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3) + vite: 7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: '@edge-runtime/vm': 5.0.0 '@opentelemetry/api': 1.9.0 '@types/node': 25.6.0 - '@vitest/browser-playwright': 4.1.4(bufferutil@4.1.0)(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(playwright@1.59.1)(utf-8-validate@5.0.10)(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))(vitest@4.1.4) + '@vitest/browser-playwright': 4.1.4(bufferutil@4.1.0)(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(playwright@1.59.1)(utf-8-validate@5.0.10)(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))(vitest@4.1.4) '@vitest/coverage-v8': 3.2.4(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.17)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(lightningcss@1.32.0)(msw@2.14.2(@types/node@22.19.17)(typescript@6.0.3))(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) happy-dom: 18.0.1 jsdom: 27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) From 3e82b1b28166e247207348221bf7971b1a55e28a Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 10 Jun 2026 15:27:49 -0400 Subject: [PATCH 13/16] chore(repo): dedupe lockfile --- pnpm-lock.yaml | 121 +++++++++++++++++++++++++------------------------ 1 file changed, 61 insertions(+), 60 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3d4b72e7705..c239508ea4b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -385,7 +385,7 @@ importers: version: 9.0.2 vitest-environment-miniflare: specifier: 2.14.4 - version: 2.14.4(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vitest@4.1.4) + version: 2.14.4(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@25.6.0)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(lightningcss@1.32.0)(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) packages/chrome-extension: dependencies: @@ -2910,7 +2910,7 @@ packages: '@expo/bunyan@4.0.1': resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==} - engines: {'0': node >=0.10.0} + engines: {node: '>=0.10.0'} '@expo/cli@0.22.28': resolution: {integrity: sha512-lvt72KNitGuixYD2l3SZmRKVu2G4zJpmg5V7WfUBNpmUU5oODBw/6qmiJ6kSLAlfDozscUk+BBGknBBzxUrwrA==} @@ -22002,20 +22002,6 @@ snapshots: - vite optional: true - '@vitest/browser-playwright@4.1.4(bufferutil@4.1.0)(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(playwright@1.59.1)(utf-8-validate@5.0.10)(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))(vitest@4.1.4)': - dependencies: - '@vitest/browser': 4.1.4(bufferutil@4.1.0)(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(utf-8-validate@5.0.10)(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))(vitest@4.1.4) - '@vitest/mocker': 4.1.4(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) - playwright: 1.59.1 - tinyrainbow: 3.1.0 - vitest: 4.1.4(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.4)(@vitest/coverage-v8@3.2.4(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.17)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(lightningcss@1.32.0)(msw@2.14.2(@types/node@22.19.17)(typescript@6.0.3))(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)))(happy-dom@18.0.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) - transitivePeerDependencies: - - bufferutil - - msw - - utf-8-validate - - vite - optional: true - '@vitest/browser@4.1.4(bufferutil@4.1.0)(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(utf-8-validate@5.0.10)(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))(vitest@4.1.4)': dependencies: '@blazediff/core': 1.9.1 @@ -22034,24 +22020,6 @@ snapshots: - vite optional: true - '@vitest/browser@4.1.4(bufferutil@4.1.0)(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(utf-8-validate@5.0.10)(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))(vitest@4.1.4)': - dependencies: - '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.4(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) - '@vitest/utils': 4.1.4 - magic-string: 0.30.21 - pngjs: 7.0.0 - sirv: 3.0.2 - tinyrainbow: 3.1.0 - vitest: 4.1.4(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.4)(@vitest/coverage-v8@3.2.4(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.17)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(lightningcss@1.32.0)(msw@2.14.2(@types/node@22.19.17)(typescript@6.0.3))(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)))(happy-dom@18.0.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) - ws: 8.20.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) - transitivePeerDependencies: - - bufferutil - - msw - - utf-8-validate - - vite - optional: true - '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.17)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(lightningcss@1.32.0)(msw@2.14.2(@types/node@22.19.17)(typescript@6.0.3))(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))': dependencies: '@ampproject/remapping': 2.3.0 @@ -22097,23 +22065,23 @@ snapshots: msw: 2.14.2(@types/node@22.19.17)(typescript@6.0.3) vite: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3) - '@vitest/mocker@4.1.4(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))': + '@vitest/mocker@3.2.4(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))': dependencies: - '@vitest/spy': 4.1.4 + '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: msw: 2.14.2(@types/node@25.6.0)(typescript@6.0.3) vite: 6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3) - '@vitest/mocker@4.1.4(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))': + '@vitest/mocker@4.1.4(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))': dependencies: '@vitest/spy': 4.1.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: msw: 2.14.2(@types/node@25.6.0)(typescript@6.0.3) - vite: 7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3) + vite: 6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3) '@vitest/pretty-format@3.2.4': dependencies: @@ -32719,6 +32687,27 @@ snapshots: - tsx - yaml + vite-node@3.2.4(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3): + dependencies: + cac: 6.7.14 + debug: 4.4.3(supports-color@8.1.1) + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + vite-node@5.3.0(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3): dependencies: cac: 6.7.14 @@ -32890,14 +32879,14 @@ snapshots: dependencies: '@types/chrome': 0.0.114 - vitest-environment-miniflare@2.14.4(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vitest@4.1.4): + vitest-environment-miniflare@2.14.4(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@25.6.0)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(lightningcss@1.32.0)(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)): dependencies: '@miniflare/queues': 2.14.4 '@miniflare/runner-vm': 2.14.4 '@miniflare/shared': 2.14.4 '@miniflare/shared-test-environment': 2.14.4(bufferutil@4.1.0)(utf-8-validate@5.0.10) undici: 5.28.4 - vitest: 4.1.4(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.4)(@vitest/coverage-v8@3.2.4(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.17)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(lightningcss@1.32.0)(msw@2.14.2(@types/node@22.19.17)(typescript@6.0.3))(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)))(happy-dom@18.0.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) + vitest: 3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@25.6.0)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(lightningcss@1.32.0)(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3) transitivePeerDependencies: - bufferutil - utf-8-validate @@ -32947,43 +32936,55 @@ snapshots: - tsx - yaml - vitest@4.1.4(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.4)(@vitest/coverage-v8@3.2.4(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.17)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(lightningcss@1.32.0)(msw@2.14.2(@types/node@22.19.17)(typescript@6.0.3))(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)))(happy-dom@18.0.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)): + vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@25.6.0)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(lightningcss@1.32.0)(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3): dependencies: - '@vitest/expect': 4.1.4 - '@vitest/mocker': 4.1.4(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) - '@vitest/pretty-format': 4.1.4 - '@vitest/runner': 4.1.4 - '@vitest/snapshot': 4.1.4 - '@vitest/spy': 4.1.4 - '@vitest/utils': 4.1.4 - es-module-lexer: 2.1.0 + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + debug: 4.4.3(supports-color@8.1.1) expect-type: 1.3.0 magic-string: 0.30.21 - obug: 2.1.1 pathe: 2.0.3 picomatch: 4.0.4 - std-env: 4.1.0 + std-env: 3.10.0 tinybench: 2.9.0 - tinyexec: 1.2.4 + tinyexec: 0.3.2 tinyglobby: 0.2.17 - tinyrainbow: 3.1.0 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 vite: 6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3) + vite-node: 3.2.4(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: '@edge-runtime/vm': 5.0.0 - '@opentelemetry/api': 1.9.0 + '@types/debug': 4.1.12 '@types/node': 25.6.0 - '@vitest/browser-playwright': 4.1.4(bufferutil@4.1.0)(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(playwright@1.59.1)(utf-8-validate@5.0.10)(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))(vitest@4.1.4) - '@vitest/coverage-v8': 3.2.4(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.17)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(lightningcss@1.32.0)(msw@2.14.2(@types/node@22.19.17)(typescript@6.0.3))(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) happy-dom: 18.0.1 jsdom: 27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) transitivePeerDependencies: + - jiti + - less + - lightningcss - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml - vitest@4.1.4(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.4)(@vitest/coverage-v8@3.2.4(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.17)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(lightningcss@1.32.0)(msw@2.14.2(@types/node@22.19.17)(typescript@6.0.3))(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)))(happy-dom@18.0.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)): + vitest@4.1.4(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.4)(@vitest/coverage-v8@3.2.4(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.17)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(lightningcss@1.32.0)(msw@2.14.2(@types/node@22.19.17)(typescript@6.0.3))(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)))(happy-dom@18.0.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)): dependencies: '@vitest/expect': 4.1.4 - '@vitest/mocker': 4.1.4(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) + '@vitest/mocker': 4.1.4(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) '@vitest/pretty-format': 4.1.4 '@vitest/runner': 4.1.4 '@vitest/snapshot': 4.1.4 @@ -33000,13 +33001,13 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3) + vite: 6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: '@edge-runtime/vm': 5.0.0 '@opentelemetry/api': 1.9.0 '@types/node': 25.6.0 - '@vitest/browser-playwright': 4.1.4(bufferutil@4.1.0)(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(playwright@1.59.1)(utf-8-validate@5.0.10)(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))(vitest@4.1.4) + '@vitest/browser-playwright': 4.1.4(bufferutil@4.1.0)(msw@2.14.2(@types/node@25.6.0)(typescript@6.0.3))(playwright@1.59.1)(utf-8-validate@5.0.10)(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))(vitest@4.1.4) '@vitest/coverage-v8': 3.2.4(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.17)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(lightningcss@1.32.0)(msw@2.14.2(@types/node@22.19.17)(typescript@6.0.3))(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) happy-dom: 18.0.1 jsdom: 27.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) From 80de9e7226e6385b715d15f9f2657083d574eddb Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 10 Jun 2026 16:01:35 -0400 Subject: [PATCH 14/16] docs(headless): fix data-cl-* tables in Popover and Tooltip READMEs Add Popup to data-cl-open/data-cl-closed row and document the missing data-cl-starting-style/data-cl-ending-style transition attributes. --- packages/headless/src/primitives/popover/README.md | 11 ++++++----- packages/headless/src/primitives/tooltip/README.md | 11 ++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/headless/src/primitives/popover/README.md b/packages/headless/src/primitives/popover/README.md index 7f2060642fd..d72481fa719 100644 --- a/packages/headless/src/primitives/popover/README.md +++ b/packages/headless/src/primitives/popover/README.md @@ -89,11 +89,12 @@ Accepts all `FloatingArrow` props. `ref` and `context` are injected automaticall ## Data Attributes -| Attribute | Applies To | Description | -| --------------------------------- | ----------------- | ---------------------------------------- | -| `data-cl-slot` | All parts | Part identifier (e.g. `"popover-popup"`) | -| `data-cl-open` / `data-cl-closed` | Trigger | Open state | -| `data-cl-side` | Positioner, Arrow | Resolved placement side | +| Attribute | Applies To | Description | +| ------------------------------------------------- | ----------------- | --------------------------------------------------------- | +| `data-cl-slot` | All parts | Part identifier (e.g. `"popover-popup"`) | +| `data-cl-open` / `data-cl-closed` | Trigger, Popup | Open/closed state | +| `data-cl-starting-style` / `data-cl-ending-style` | Popup | Transition state — set during enter/exit animation frames | +| `data-cl-side` | Positioner, Arrow | Resolved placement side | ## Positioning diff --git a/packages/headless/src/primitives/tooltip/README.md b/packages/headless/src/primitives/tooltip/README.md index 73567689805..be3f710a2eb 100644 --- a/packages/headless/src/primitives/tooltip/README.md +++ b/packages/headless/src/primitives/tooltip/README.md @@ -93,11 +93,12 @@ Accepts all `FloatingArrow` props. `ref` and `context` are injected automaticall ## Data Attributes -| Attribute | Applies To | Description | -| --------------------------------- | ----------------- | ---------------------------------------- | -| `data-cl-slot` | All parts | Part identifier (e.g. `"tooltip-popup"`) | -| `data-cl-open` / `data-cl-closed` | Trigger | Open state | -| `data-cl-side` | Positioner, Arrow | Resolved placement side | +| Attribute | Applies To | Description | +| ------------------------------------------------- | ----------------- | --------------------------------------------------------- | +| `data-cl-slot` | All parts | Part identifier (e.g. `"tooltip-popup"`) | +| `data-cl-open` / `data-cl-closed` | Trigger, Popup | Open/closed state | +| `data-cl-starting-style` / `data-cl-ending-style` | Popup | Transition state — set during enter/exit animation frames | +| `data-cl-side` | Positioner, Arrow | Resolved placement side | ## Positioning From 5be6d135898e02486cf89df8720762815a7d9d1b Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 10 Jun 2026 16:53:40 -0400 Subject: [PATCH 15/16] fix(headless): remove dead DialogScopedContext from dialog primitives --- .../headless/src/primitives/dialog/dialog-context.ts | 1 - .../headless/src/primitives/dialog/dialog-portal.tsx | 9 ++------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/headless/src/primitives/dialog/dialog-context.ts b/packages/headless/src/primitives/dialog/dialog-context.ts index 27ae31ede2b..863d26af38e 100644 --- a/packages/headless/src/primitives/dialog/dialog-context.ts +++ b/packages/headless/src/primitives/dialog/dialog-context.ts @@ -19,7 +19,6 @@ export interface DialogContextValue { } export const DialogContext = createContext(null); -export const DialogScopedContext = createContext(false); export function useDialogContext() { const ctx = useContext(DialogContext); diff --git a/packages/headless/src/primitives/dialog/dialog-portal.tsx b/packages/headless/src/primitives/dialog/dialog-portal.tsx index 3c507354d72..4e154b4a8e5 100644 --- a/packages/headless/src/primitives/dialog/dialog-portal.tsx +++ b/packages/headless/src/primitives/dialog/dialog-portal.tsx @@ -3,7 +3,7 @@ import { FloatingPortal } from '@floating-ui/react'; import type { ReactNode } from 'react'; -import { DialogScopedContext, useDialogContext } from './dialog-context'; +import { useDialogContext } from './dialog-context'; export interface DialogPortalProps { children: ReactNode; @@ -12,15 +12,10 @@ export interface DialogPortalProps { export function DialogPortal(props: DialogPortalProps) { const { mounted } = useDialogContext(); - const isScoped = props.root != null; if (!mounted) { return null; } - return ( - - {props.children} - - ); + return {props.children}; } From f83a7a79d365856c99dd0dd9705d569d28493de9 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 10 Jun 2026 16:53:46 -0400 Subject: [PATCH 16/16] fix(ui): use minHeight in mosaic dialog viewport to prevent tall content clipping --- packages/ui/src/mosaic/components/dialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/mosaic/components/dialog.tsx b/packages/ui/src/mosaic/components/dialog.tsx index bd9e1317a0c..e81be1e51c6 100644 --- a/packages/ui/src/mosaic/components/dialog.tsx +++ b/packages/ui/src/mosaic/components/dialog.tsx @@ -20,7 +20,7 @@ const viewportStyles = cva(theme => ({ display: 'grid', placeItems: 'center', width: '100%', - height: '100%', + minHeight: '100%', padding: theme.spacing(4), }, variants: {},