diff --git a/apps/website/src/routes/docs/headless/tooltip/examples/onChange.tsx b/apps/website/src/routes/docs/headless/tooltip/examples/onChange.tsx new file mode 100644 index 000000000..a701eb20a --- /dev/null +++ b/apps/website/src/routes/docs/headless/tooltip/examples/onChange.tsx @@ -0,0 +1,19 @@ +import { component$, useSignal } from '@builder.io/qwik'; +import { Tooltip } from '@qwik-ui/headless'; + +export default component$(() => { + const tooltipState = useSignal<'open' | 'closed'>('closed'); + + return ( + <> + (tooltipState.value = e)} flip> + Hover or Focus me + + + Tooltip content here + + + The tooltip is {tooltipState.value} + + ); +}); diff --git a/apps/website/src/routes/docs/headless/tooltip/index.mdx b/apps/website/src/routes/docs/headless/tooltip/index.mdx index 0bf2b191f..ce01194eb 100644 --- a/apps/website/src/routes/docs/headless/tooltip/index.mdx +++ b/apps/website/src/routes/docs/headless/tooltip/index.mdx @@ -199,6 +199,13 @@ CSS from the example: +## Events + +The tooltip contains a `onOpenChange$` event that runs when the tooltip opens or closes. +This can be used to trigger additional actions when the tooltip is opened or closed. + + + ## Additional References Qwik UI aims to be in line with the standard whenever possible. Our goal is to empower Qwik developers to create amazing experiences for their users. @@ -255,6 +262,12 @@ To read more about tooltips you can check it out on: type: 'selector', description: 'Style the element when the tooltip is open.', }, + { + name: 'onOpenChange$', + type: 'QRL', + description: 'QRL handler that runs when the tooltip opens or closes.', + info: 'QRL<(state: "open" | "closed") => void>', + }, ]} /> diff --git a/packages/kit-headless/src/components/tooltip/tooltip-context.ts b/packages/kit-headless/src/components/tooltip/tooltip-context.ts index 160aa1d75..fcef105ab 100644 --- a/packages/kit-headless/src/components/tooltip/tooltip-context.ts +++ b/packages/kit-headless/src/components/tooltip/tooltip-context.ts @@ -1,4 +1,4 @@ -import { createContextId, Signal } from '@builder.io/qwik'; +import { createContextId, QRL, Signal } from '@builder.io/qwik'; export const TooltipContextId = createContextId('Tooltip'); @@ -11,6 +11,8 @@ export type TooltipContext = { triggerRef: Signal; state: Signal; + + onOpenChange$: QRL<(state: 'open' | 'closed') => void>; }; export type TriggerDataState = 'closing' | 'closed' | 'opening' | 'open'; diff --git a/packages/kit-headless/src/components/tooltip/tooltip-panel.tsx b/packages/kit-headless/src/components/tooltip/tooltip-panel.tsx index dbbc01d54..898e02c8c 100644 --- a/packages/kit-headless/src/components/tooltip/tooltip-panel.tsx +++ b/packages/kit-headless/src/components/tooltip/tooltip-panel.tsx @@ -11,7 +11,12 @@ export const HTooltipPanel = component$((props: HTooltipPanelProps) => { const context = useContext(TooltipContextId); return ( - + context.onOpenChange$(e.newState)} + id={context.localId} + > ); diff --git a/packages/kit-headless/src/components/tooltip/tooltip-root.tsx b/packages/kit-headless/src/components/tooltip/tooltip-root.tsx index b3affacce..c3b74809b 100644 --- a/packages/kit-headless/src/components/tooltip/tooltip-root.tsx +++ b/packages/kit-headless/src/components/tooltip/tooltip-root.tsx @@ -7,6 +7,7 @@ import { useContextProvider, useId, useSignal, + $, } from '@builder.io/qwik'; import { FloatingProps, HPopoverRoot } from '../popover/popover-root'; import { TooltipContext, TooltipContextId, TriggerDataState } from './tooltip-context'; @@ -27,7 +28,7 @@ export type TooltipRootProps = { * QRL handler that runs when the tooltip opens or closes. * @param open The new state of the tooltip. */ - onOpenChange$?: QRL<(state: TriggerDataState) => void>; + onOpenChange$?: QRL<(state: 'open' | 'closed') => void>; /** * A value that determines how long before the tooltip will @@ -58,7 +59,7 @@ export const HTooltipRoot = component$((props: TooltipProps) => { gutter, delayDuration = 0, flip, - onOpenChange$: _, + onOpenChange$, ...rest } = props; @@ -74,6 +75,7 @@ export const HTooltipRoot = component$((props: TooltipProps) => { triggerRef, delayDuration, state: tooltipState, + onOpenChange$: $((e) => onOpenChange$?.(e)), }; useContextProvider(TooltipContextId, context); diff --git a/packages/kit-headless/src/components/tooltip/tooltip.driver.ts b/packages/kit-headless/src/components/tooltip/tooltip.driver.ts index 4c753a3d2..705cdbabd 100644 --- a/packages/kit-headless/src/components/tooltip/tooltip.driver.ts +++ b/packages/kit-headless/src/components/tooltip/tooltip.driver.ts @@ -51,6 +51,10 @@ export function createTooltipDriver(rootLocator: T) { return getTrigger().all(); }; + const getOnChangeVerificationText = (state: 'open' | 'closed') => { + return rootLocator.getByText(`The tooltip is ${state}`); + }; + const getProgrammaticButtonTrigger = () => { return rootLocator.locator('button'); }; @@ -65,5 +69,6 @@ export function createTooltipDriver(rootLocator: T) { openTooltip, getProgrammaticButtonTrigger, getTooltipByTextContent, + getOnChangeVerificationText, }; } diff --git a/packages/kit-headless/src/components/tooltip/tooltip.test.ts b/packages/kit-headless/src/components/tooltip/tooltip.test.ts index b7b49dc95..5c10c1b0e 100644 --- a/packages/kit-headless/src/components/tooltip/tooltip.test.ts +++ b/packages/kit-headless/src/components/tooltip/tooltip.test.ts @@ -221,3 +221,20 @@ test.describe('Tooltip Animations', () => { await expect(tooltip).toBeHidden(); }); }); + +test.describe('Tooltip Events', () => { + test(`GIVEN a tooltip with opOpenChange configured + WHEN hovering over the trigger + THEN the text should say "The tooltip is open"`, async ({ page }) => { + const { driver: d } = await setup(page, 'onChange'); + const tooltip = d.getTooltip(); + const trigger = d.getTrigger(); + + expect(d.getOnChangeVerificationText('closed')).toBeVisible(); + + await trigger.hover(); + await expect(tooltip).toBeVisible(); + + expect(d.getOnChangeVerificationText('open')).toBeVisible(); + }); +});