feat(react-headless-components-preview): add Popover & positioning#36006
feat(react-headless-components-preview): add Popover & positioning#36006mainframev wants to merge 9 commits intomicrosoft:masterfrom
Conversation
f26ae3e to
22f36ba
Compare
|
Pull request demo site: URL |
22f36ba to
e9fef9d
Compare
📊 Bundle size report
🤖 This report was generated against 86cf4cbebfc9517e2373ce8077674bbcaf3ce7da |
58218ca to
c32d139
Compare
…n spec-pure CSS anchor positioning
c32d139 to
4774cce
Compare
60d79ff to
30490b7
Compare
`useDialogContextValues copy.ts` was an editor artifact that slipped into the feature commit. Removing it leaves `useDialogContextValues.ts` untouched.
30490b7 to
f4a8375
Compare
…oning on spec-pure CSS anchor positioning
…oning on spec-pure CSS anchor positioning
0da03bc to
c16a425
Compare
…oning on spec-pure CSS anchor positioning
c16a425 to
e42cb0f
Compare
|
|
||
| export type PositioningReturn = { | ||
| targetRef: React.RefCallback<HTMLElement>; | ||
| containerRef: React.RefCallback<HTMLElement>; |
There was a problem hiding this comment.
should this be surfaceRef or is it for consistency with v9?
| import { computeAvailableHeight, computeAvailableWidth, resolveBoundaryPadding, resolveElementRef } from './utils'; | ||
|
|
||
| export type UseBoundaryClampOptions = { | ||
| overflowBoundary: PositioningProps['overflowBoundary']; |
There was a problem hiding this comment.
would be great to document the exported props and hooks
| function mountHook(options: PositioningProps = {}) { | ||
| const resultRef = React.createRef<{ current: PositioningReturn }>(); | ||
| const Capture = () => { | ||
| const result = usePositioning(options); |
There was a problem hiding this comment.
nit, but we can probably type-cast once if it's really needed
| const result = usePositioning(options); | |
| const result = usePositioning(options) as unknown as { current: PositioningReturn }; |
| result.current.containerRef(node); | ||
|
|
||
| expect(node.style.getPropertyValue('position-anchor')).toMatch(/^--popover-anchor-/); | ||
| expect(node.style.getPropertyValue('position-area')).toBe('block-end span-inline-end'); |
There was a problem hiding this comment.
nit, but we can use the https://github.com/testing-library/jest-dom#tohavestyle to make it a bit cleaner
| expect(node.style.getPropertyValue('position-area')).toBe('block-end span-inline-end'); | |
| expect(node).toHaveStyle({ positionArea: 'block-end span-inline-end' }); |
| const node = document.createElement('div'); | ||
| result.current.containerRef(node); | ||
|
|
||
| expect(node.style.position).toBe('absolute'); |
There was a problem hiding this comment.
nit, but can be combined to one assertion for all styles
There was a problem hiding this comment.
as discussed offline, we'll need to remove this for now
| const childProps = (child?.props ?? {}) as Record<string, unknown>; | ||
|
|
||
| const triggerChildProps = { | ||
| 'aria-expanded': `${open}` as 'true' | 'false', |
There was a problem hiding this comment.
Why don't we use the popovertarget attribute https://developer.mozilla.org/en-US/docs/Web/API/Popover_API#html_attributes?
|
|
||
| const positioning = usePositioning(resolvePositioningShorthand(props.positioning)); | ||
|
|
||
| useOnClickOutside({ |
There was a problem hiding this comment.
just to confrim, did you check if Popover API has this functionality out-of-the-box?
| disabledFocusOnIframe: !closeOnIframeFocus, | ||
| }); | ||
|
|
||
| useOnScrollOutside({ |
There was a problem hiding this comment.
the same question as above, pls double check if we can drop this in favor of Popover API
| * for the breathing-room `GAP`. Used by `useAutoSizeBoundary` to derive a | ||
| * numeric `max-height` when `overflowBoundary` is supplied. | ||
| */ | ||
| export function computeAvailableHeight( |
There was a problem hiding this comment.
lets cover these utils with unit tests, should be straightforward as they are stateless

Adds a headless Popover (Popover, PopoverTrigger, PopoverSurface) to the @fluentui/react-headless-components-preview package, positioned via the native CSS Anchor Positioning API instead of floating-ui
Feature with v9
@fluentui/react-popoveropen/defaultOpen/onOpenChangeopenOnHover+mouseLeaveDelayopenOnContext(right-click, cursor-anchored)withArrow(+ consumer-owned arrow CSS via[data-placement])trapFocus+aria-modal/role="dialog"useFocusScope)disableAutoFocuscloseOnScroll,closeOnIframeFocusinline(skip top-layer)<Portal>wrapper - DOM placement only, positioning math unchanged. Headless: skips HTML Popover API top-layer promotion, also changes the overflow boundary CSSposition-try-fallbacksflips against (viewport → nearest + scroll port / containing block)mountNode(portal target)positioning.position+positioning.alignpositioning.offset(number or{ mainAxis, crossAxis })positioning.coverTargetpositioning.fallbackPositionspositioning.autoSize(true/'width'/'height')positioning.overflowBoundarypositioning.overflowBoundaryPaddingpositioning.overflowBoundaryRectpositioning.matchTargetSize: 'width'positioning.strategy: 'absolute' | 'fixed'positioning.pinnedpositioning.target(custom anchor)positioning.positioningRef(imperativesetTarget)flipBoundaryarrowPaddingshiftToCoverTargetonPositioningEnddisableUpdateOnResizeuseTransformRelated Issue(s)