diff --git a/packages/fluentui/CHANGELOG.md b/packages/fluentui/CHANGELOG.md index b218d6cc0f68d4..e61e97326c12dc 100644 --- a/packages/fluentui/CHANGELOG.md +++ b/packages/fluentui/CHANGELOG.md @@ -31,6 +31,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Features - Add new Popup prop `closeOnScroll` to close popup when scroll happens outside of the popover element @yuanboxue-amber ([#21453](https://github.com/microsoft/fluentui/pull/21453)) +- Add new `isIntersectingModifier` modifier to `usePopper` @layershifter ([#21829](https://github.com/microsoft/fluentui/pull/21829)) ## [v0.60.1](https://github.com/microsoft/fluentui/tree/@fluentui/react-northstar_v0.60.1) (2022-01-17) diff --git a/packages/fluentui/docs/src/examples/components/Popup/Visual/PopperExampleVisibilityModifiers.steps.ts b/packages/fluentui/docs/src/examples/components/Popup/Visual/PopperExampleVisibilityModifiers.steps.ts new file mode 100644 index 00000000000000..65e3d7783250f2 --- /dev/null +++ b/packages/fluentui/docs/src/examples/components/Popup/Visual/PopperExampleVisibilityModifiers.steps.ts @@ -0,0 +1,18 @@ +import { ScreenerTestsConfig } from '@fluentui/scripts/screener'; + +const config: ScreenerTestsConfig = { + steps: [ + builder => + builder + .click('#message-1') + .snapshot('Opened a popup on second message') + .executeScript('document.querySelector("#scrollable-area").scrollTop = 50') + .snapshot('has "[data-popper-is-intersecting]" when the popover intersects boundaries') + .executeScript('document.querySelector("#scrollable-area").scrollTop = 80') + .snapshot(`has "[data-popper-escaped]" when the popper escapes the reference element's boundary`) + .executeScript('document.querySelector("#scrollable-area").scrollTop = 150') + .snapshot('has "[data-popper-reference-hidden]" when the reference is hidden'), + ], +}; + +export default config; diff --git a/packages/fluentui/docs/src/examples/components/Popup/Visual/PopperExampleVisibilityModifiers.tsx b/packages/fluentui/docs/src/examples/components/Popup/Visual/PopperExampleVisibilityModifiers.tsx new file mode 100644 index 00000000000000..cdb43e810fd1ad --- /dev/null +++ b/packages/fluentui/docs/src/examples/components/Popup/Visual/PopperExampleVisibilityModifiers.tsx @@ -0,0 +1,74 @@ +import { Popup, popupContentSlotClassNames } from '@fluentui/react-northstar'; +import * as _ from 'lodash'; +import * as React from 'react'; + +const PopperExampleVisibilityModifiers = () => ( + <> +

+ This visual test asserts that visual styles are applied based on popper element's state: +

+ +
+ {_.times(20).map(i => ( + +
+

message: {i}

+
+
+ ))} +
+ +); + +export default PopperExampleVisibilityModifiers; diff --git a/packages/fluentui/docs/src/examples/components/Popup/Visual/index.tsx b/packages/fluentui/docs/src/examples/components/Popup/Visual/index.tsx index af1a7d84245323..de1dd2d975cf92 100644 --- a/packages/fluentui/docs/src/examples/components/Popup/Visual/index.tsx +++ b/packages/fluentui/docs/src/examples/components/Popup/Visual/index.tsx @@ -9,6 +9,7 @@ const Visual = () => ( + ); diff --git a/packages/fluentui/react-northstar/src/utils/positioner/isIntersectingModifier.ts b/packages/fluentui/react-northstar/src/utils/positioner/isIntersectingModifier.ts new file mode 100644 index 00000000000000..9f0fcc09938844 --- /dev/null +++ b/packages/fluentui/react-northstar/src/utils/positioner/isIntersectingModifier.ts @@ -0,0 +1,27 @@ +import { detectOverflow, Modifier } from '@popperjs/core'; + +export const isIntersectingModifier: IsIntersectingModifier = { + name: 'is-intersecting-modifier', + enabled: true, + phase: 'main', + requires: ['preventOverflow'], + fn: ({ state, name }) => { + const popperRect = state.rects.popper; + const popperAltOverflow = detectOverflow(state, { altBoundary: true }); + + const isIntersectingTop = popperAltOverflow.top < popperRect.height && popperAltOverflow.top > 0; + const isIntersectingBottom = popperAltOverflow.bottom < popperRect.height && popperAltOverflow.bottom > 0; + + const isIntersecting = isIntersectingTop || isIntersectingBottom; + + state.modifiersData[name] = { + isIntersecting, + }; + state.attributes.popper = { + ...state.attributes.popper, + 'data-popper-is-intersecting': isIntersecting, + }; + }, +}; + +type IsIntersectingModifier = Modifier<'is-intersecting-modifier', never>; diff --git a/packages/fluentui/react-northstar/src/utils/positioner/usePopper.ts b/packages/fluentui/react-northstar/src/utils/positioner/usePopper.ts index ebe8f5627f454a..f0cefa89bf267d 100644 --- a/packages/fluentui/react-northstar/src/utils/positioner/usePopper.ts +++ b/packages/fluentui/react-northstar/src/utils/positioner/usePopper.ts @@ -12,6 +12,7 @@ import { getReactFiberFromNode } from '../getReactFiberFromNode'; import { isBrowser } from '../isBrowser'; import { getBoundary } from './getBoundary'; import { getScrollParent } from './getScrollParent'; +import { isIntersectingModifier } from './isIntersectingModifier'; import { applyRtlToOffset, getPlacement } from './positioningHelper'; import { PopperInstance, PopperOptions } from './types'; @@ -102,6 +103,8 @@ function usePopperOptions(options: PopperOptions, popperOriginalPositionRef: Rea : false; const modifiers: PopperJs.Options['modifiers'] = [ + isIntersectingModifier, + /** * We are setting the position to `fixed` in the first effect to prevent scroll jumps in case of the content * with managed focus. Modifier sets the position to `fixed` before all other modifier effects. Another part of