diff --git a/.changeset/three-toes-love.md b/.changeset/three-toes-love.md new file mode 100644 index 000000000..362568d96 --- /dev/null +++ b/.changeset/three-toes-love.md @@ -0,0 +1,5 @@ +--- +'@qwik-ui/headless': minor +--- + +minor: adds strategy prop to use when positioning the floating element in popover diff --git a/apps/website/src/routes/docs/headless/popover/index.mdx b/apps/website/src/routes/docs/headless/popover/index.mdx index 8242279bb..c29f312db 100644 --- a/apps/website/src/routes/docs/headless/popover/index.mdx +++ b/apps/website/src/routes/docs/headless/popover/index.mdx @@ -362,9 +362,15 @@ To read more about the popover API you can check it out on: }, { name: 'floating', - type: 'boolean | TPlacement', + type: 'boolean | Placement', description: 'Enables extra JavaScript behavior for floating elements.', }, + { + name: 'strategy', + type: 'absolute | fixed', + description: + 'The strategy to use when positioning the floating element. The default value is absolute, which suites most cases, while fixed position might be better in legacy browsers like iOS 15.4.', + }, { name: 'anchorRef', type: 'Signal', diff --git a/packages/kit-headless/src/components/popover/floating.tsx b/packages/kit-headless/src/components/popover/floating.tsx index 1fa0f917e..e6914de49 100644 --- a/packages/kit-headless/src/components/popover/floating.tsx +++ b/packages/kit-headless/src/components/popover/floating.tsx @@ -13,7 +13,7 @@ import { PropsOf, Slot, component$, useContext, useTask$ } from '@builder.io/qwi import { HPopoverPanelImpl } from './popover-panel-impl'; import { isServer } from '@builder.io/qwik/build'; -import { popoverContextId } from './popover-context'; +import { popoverContextId } from './popover-types'; export const FloatingPopover = component$((props: PropsOf<'div'>) => { const context = useContext(popoverContextId); @@ -58,6 +58,7 @@ export const FloatingPopover = component$((props: PropsOf<'div'>) => { await computePosition(anchor as ReferenceElement, popover, { placement: placement as Placement, middleware, + strategy: context.strategy, }).then(async (resolvedData) => { const { x, y } = resolvedData; diff --git a/packages/kit-headless/src/components/popover/popover-panel-arrow.tsx b/packages/kit-headless/src/components/popover/popover-panel-arrow.tsx index 117f4d453..78a4818d0 100644 --- a/packages/kit-headless/src/components/popover/popover-panel-arrow.tsx +++ b/packages/kit-headless/src/components/popover/popover-panel-arrow.tsx @@ -1,6 +1,6 @@ import { PropsOf, component$, useContext } from '@builder.io/qwik'; -import { popoverContextId } from './popover-context'; +import { popoverContextId } from './popover-types'; export const HPopoverPanelArrow = component$((props: PropsOf<'div'>) => { const context = useContext(popoverContextId); diff --git a/packages/kit-headless/src/components/popover/popover-panel-impl.tsx b/packages/kit-headless/src/components/popover/popover-panel-impl.tsx index 341763ffd..e0ff64734 100644 --- a/packages/kit-headless/src/components/popover/popover-panel-impl.tsx +++ b/packages/kit-headless/src/components/popover/popover-panel-impl.tsx @@ -13,7 +13,7 @@ import { import { isServer } from '@builder.io/qwik/build'; import { useCombinedRef } from '../../hooks/combined-refs'; -import { popoverContextId } from './popover-context'; +import { popoverContextId } from './popover-types'; import popoverStyles from './popover.css?inline'; // We don't need a provider, that way we connect all context to the root diff --git a/packages/kit-headless/src/components/popover/popover-panel.tsx b/packages/kit-headless/src/components/popover/popover-panel.tsx index 640f4a6e3..d22177eea 100644 --- a/packages/kit-headless/src/components/popover/popover-panel.tsx +++ b/packages/kit-headless/src/components/popover/popover-panel.tsx @@ -1,7 +1,7 @@ import { component$, PropsOf, Slot, useContext } from '@builder.io/qwik'; import { FloatingPopover } from './floating'; import { HPopoverPanelImpl } from './popover-panel-impl'; -import { popoverContextId } from './popover-context'; +import { popoverContextId } from './popover-types'; // TODO: improve the type so that it only includes FloatingProps when floating is true. @@ -11,7 +11,7 @@ export const HPopoverPanel = component$((props: PropsOf<'div'>) => { if (context.floating) { return ( - + ); diff --git a/packages/kit-headless/src/components/popover/popover-root.tsx b/packages/kit-headless/src/components/popover/popover-root.tsx index 12a86d0f5..7e36ef5b3 100644 --- a/packages/kit-headless/src/components/popover/popover-root.tsx +++ b/packages/kit-headless/src/components/popover/popover-root.tsx @@ -7,13 +7,19 @@ import { useId, useSignal, } from '@builder.io/qwik'; -import { popoverContextId, PopoverContext } from './popover-context'; + +import { + popoverContextId, + type Floating, + type Placement, + type PopoverContext, +} from './popover-types'; export type PopoverRootProps = { popover?: 'manual' | 'auto'; manual?: boolean; ref?: Signal; - floating?: boolean | TPlacement; + floating?: boolean | Placement; /** @deprecated Use the tooltip instead, which adheres to the WAI-ARIA design pattern. */ hover?: boolean; id?: string; @@ -21,39 +27,9 @@ export type PopoverRootProps = { 'bind:panel'?: Signal; }; -export type FloatingProps = { - ancestorScroll?: boolean; - ancestorResize?: boolean; - elementResize?: boolean; - layoutShift?: boolean; - animationFrame?: boolean; - gutter?: number; - shift?: boolean; - flip?: boolean; - size?: boolean; - hide?: 'referenceHidden' | 'escaped'; - inline?: boolean; - transform?: string; - arrow?: boolean; -}; - -export type TPlacement = - | 'top' - | 'top-start' - | 'top-end' - | 'right' - | 'right-start' - | 'right-end' - | 'bottom' - | 'bottom-start' - | 'bottom-end' - | 'left' - | 'left-start' - | 'left-end'; - export type PopoverProps = PopoverRootProps & { - floating?: boolean | TPlacement; -} & FloatingProps & + floating?: boolean | Placement; +} & Floating & PropsOf<'div'>; export const HPopoverRoot = component$((props: PopoverProps) => { @@ -74,6 +50,7 @@ export const HPopoverRoot = component$((props: PopoverProps) => { elementResize = true, animationFrame = false, transform, + strategy, ...rest } = props; @@ -109,6 +86,7 @@ export const HPopoverRoot = component$((props: PopoverProps) => { flip, shift, hide, + strategy, ancestorScroll, ancestorResize, elementResize, diff --git a/packages/kit-headless/src/components/popover/popover-trigger.tsx b/packages/kit-headless/src/components/popover/popover-trigger.tsx index d2f874888..8092252b0 100644 --- a/packages/kit-headless/src/components/popover/popover-trigger.tsx +++ b/packages/kit-headless/src/components/popover/popover-trigger.tsx @@ -1,5 +1,5 @@ import { Slot, component$, $, PropsOf, useContext } from '@builder.io/qwik'; -import { popoverContextId } from './popover-context'; +import { popoverContextId } from './popover-types'; import { usePopover } from './use-popover'; type PopoverTriggerProps = { diff --git a/packages/kit-headless/src/components/popover/popover-context.ts b/packages/kit-headless/src/components/popover/popover-types.ts similarity index 63% rename from packages/kit-headless/src/components/popover/popover-context.ts rename to packages/kit-headless/src/components/popover/popover-types.ts index 0c0ffdf91..840ea2cd5 100644 --- a/packages/kit-headless/src/components/popover/popover-context.ts +++ b/packages/kit-headless/src/components/popover/popover-types.ts @@ -1,13 +1,41 @@ -import { Signal, createContextId } from '@builder.io/qwik'; -import { TPlacement } from './popover-root'; +import { type Signal, createContextId } from '@builder.io/qwik'; -export const popoverContextId = createContextId('qui-popover'); +export type Floating = { + ancestorScroll?: boolean; + ancestorResize?: boolean; + elementResize?: boolean; + layoutShift?: boolean; + animationFrame?: boolean; + gutter?: number; + shift?: boolean; + flip?: boolean; + size?: boolean; + hide?: 'referenceHidden' | 'escaped'; + inline?: boolean; + transform?: string; + arrow?: boolean; + strategy?: 'absolute' | 'fixed'; +}; + +export type Placement = + | 'top' + | 'top-start' + | 'top-end' + | 'right' + | 'right-start' + | 'right-end' + | 'bottom' + | 'bottom-start' + | 'bottom-end' + | 'left' + | 'left-start' + | 'left-end'; -export type PopoverContext = { +export type PopoverContext = Floating & { // core state compId: string; isOpenSig: Signal; - floating?: boolean | TPlacement; + floating?: boolean | Placement; localId: string; manual?: boolean; hover?: boolean; @@ -17,19 +45,6 @@ export type PopoverContext = { panelRef?: Signal; triggerRef?: Signal; arrowRef?: Signal; - - // floating props - ancestorScroll?: boolean; - ancestorResize?: boolean; - elementResize?: boolean; - layoutShift?: boolean; - animationFrame?: boolean; - gutter?: number; - shift?: boolean; - flip?: boolean; - size?: boolean; - arrow?: boolean; - hide?: 'referenceHidden' | 'escaped'; - inline?: boolean; - transform?: string; }; + +export const popoverContextId = createContextId('qui-popover'); diff --git a/packages/kit-headless/src/components/popover/popover.css b/packages/kit-headless/src/components/popover/popover.css index 4928552f7..e622b0e7d 100644 --- a/packages/kit-headless/src/components/popover/popover.css +++ b/packages/kit-headless/src/components/popover/popover.css @@ -19,6 +19,10 @@ overflow: unset; position: absolute; } + + [data-floating='fixed'] { + position: fixed; + } } /** override the polyfill's layer, which gets dynamically imported later on. */ @@ -30,4 +34,8 @@ overflow: unset; position: absolute; } + + [data-floating='fixed'] { + position: fixed; + } } diff --git a/packages/kit-headless/src/components/tooltip/tooltip-root.tsx b/packages/kit-headless/src/components/tooltip/tooltip-root.tsx index c3b74809b..792ba502c 100644 --- a/packages/kit-headless/src/components/tooltip/tooltip-root.tsx +++ b/packages/kit-headless/src/components/tooltip/tooltip-root.tsx @@ -9,7 +9,8 @@ import { useSignal, $, } from '@builder.io/qwik'; -import { FloatingProps, HPopoverRoot } from '../popover/popover-root'; +import { type Floating } from '../popover/popover-types'; +import { HPopoverRoot } from '../popover/popover-root'; import { TooltipContext, TooltipContextId, TriggerDataState } from './tooltip-context'; /** @@ -42,7 +43,7 @@ export type TooltipRootProps = { placement?: Parameters['0']['floating']; id?: string; -} & Pick; +} & Pick; /** * TooltipProps combines TooltipRootProps and the properties of a div element.