diff --git a/packages/eui/changelogs/upcoming/9152.md b/packages/eui/changelogs/upcoming/9152.md new file mode 100644 index 00000000000..e70949ddbf1 --- /dev/null +++ b/packages/eui/changelogs/upcoming/9152.md @@ -0,0 +1 @@ +- Added `EuiPopover` and `EuiToolTip`'s `repositionOnScroll` to `componentDefaults` diff --git a/packages/eui/cypress/support/component.tsx b/packages/eui/cypress/support/component.tsx index e30ee10b7d2..50016cd0814 100644 --- a/packages/eui/cypress/support/component.tsx +++ b/packages/eui/cypress/support/component.tsx @@ -24,6 +24,7 @@ import './copy/select_and_copy'; import './setup/mount'; import './setup/realMount'; import './css/cssVar'; +import './helpers/wait_for_position_to_settle'; // @see https://github.com/quasarframework/quasar/issues/2233#issuecomment-492975745 // @see also https://github.com/cypress-io/cypress/issues/20341 diff --git a/packages/eui/cypress/support/helpers/wait_for_position_to_settle.ts b/packages/eui/cypress/support/helpers/wait_for_position_to_settle.ts new file mode 100644 index 00000000000..b5593dd6f2e --- /dev/null +++ b/packages/eui/cypress/support/helpers/wait_for_position_to_settle.ts @@ -0,0 +1,66 @@ +/** + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/// + +/** + * A recursive helper function that polls an element's position. + * + * @param subject The Cypress subject (the DOM element). + * @param stabilityCount The number of times the position has been stable. + * @param retries The number of remaining retries. + * @param prevRect The element's rect from the previous check. + */ +export function waitForPositionToSettle( + subject: JQuery, + stabilityCount = 0, + retries = 40, // 40 * 50ms = 2s timeout + prevRect?: DOMRect +): Cypress.Chainable> { + const STABILITY_THRESHOLD = 3; // require 3 consecutive stable checks + + if (retries < 0) { + throw new Error('Position did not settle in time after 2s'); + } + + const currentRect = subject[0].getBoundingClientRect(); + + let nextStabilityCount = stabilityCount; + if ( + prevRect && + currentRect.top === prevRect.top && + currentRect.left === prevRect.left + ) { + nextStabilityCount++; + } else { + // Position changed, reset counter + nextStabilityCount = 0; + } + + if (nextStabilityCount >= STABILITY_THRESHOLD) { + cy.log('Position settled'); + return cy.wrap(subject); + } + + return cy.wait(50, { log: false }).then(() => { + return waitForPositionToSettle( + subject, + nextStabilityCount, + retries - 1, + currentRect + ); + }); +} + +Cypress.Commands.add( + 'waitForPositionToSettle', + { prevSubject: 'element' }, + (subject: JQuery) => { + return waitForPositionToSettle(subject); + } +); diff --git a/packages/eui/cypress/support/index.d.ts b/packages/eui/cypress/support/index.d.ts index f049bc0227b..54f3624eb9b 100644 --- a/packages/eui/cypress/support/index.d.ts +++ b/packages/eui/cypress/support/index.d.ts @@ -62,6 +62,12 @@ declare global { * Params: variableName - the name of the CSS variable (e.g. '--euiColorPrimary') */ cssVar(variableName: string): Chainable; + + /** + * Waits for an element's position to remain stable for a few consecutive checks. + * This is useful for ensuring that repositioning logic has completed after an event like a scroll. + */ + waitForPositionToSettle(): Chainable>; } } } diff --git a/packages/eui/src/components/popover/popover.spec.tsx b/packages/eui/src/components/popover/popover.spec.tsx index 82405b56b2a..5b3ae088278 100644 --- a/packages/eui/src/components/popover/popover.spec.tsx +++ b/packages/eui/src/components/popover/popover.spec.tsx @@ -20,6 +20,7 @@ import React, { import { EuiButton, EuiConfirmModal } from '../../components'; import { EuiPopover, EuiPopoverProps } from './popover'; +import { testRepositionOnScroll } from '../../test/cypress/test_reposition_on_scroll'; const PopoverToggle: FC<{ onClick: MouseEventHandler }> = ({ onClick, @@ -215,6 +216,69 @@ describe('EuiPopover', () => { }); }); + describe('repositionOnScroll', () => { + const renderPopover = (props: { repositionOnScroll?: boolean }) => ( + Test popover + ); + + const config = { + renderComponent: renderPopover, + componentName: 'EuiPopover' as const, + triggerSelector: '[data-test-subj="togglePopover"]', + panelSelector: '[data-test-subj="popoverPanel"]', + }; + + describe('is repositioned', () => { + it('when `repositionOnScroll=true`', () => { + testRepositionOnScroll({ + ...config, + shouldReposition: true, + propValue: true, + }); + }); + + it('when `componentDefaults` has `repositionOnScroll=true`', () => { + testRepositionOnScroll({ + ...config, + shouldReposition: true, + componentDefaultValue: true, + }); + }); + + it('when `repositionOnScroll=true` even if `componentDefaults` has `repositionOnScroll=false`', () => { + testRepositionOnScroll({ + ...config, + shouldReposition: true, + propValue: true, + componentDefaultValue: false, + }); + }); + }); + + describe('is not repositioned', () => { + it('when `repositionOnScroll=false` (default value)', () => { + testRepositionOnScroll({ ...config, shouldReposition: false }); + }); + + it('when `componentDefaults` has `repositionOnScroll=false`', () => { + testRepositionOnScroll({ + ...config, + shouldReposition: false, + componentDefaultValue: false, + }); + }); + + it('when `repositionOnScroll=false` even if `componentDefaults` has `repositionOnScroll=true`', () => { + testRepositionOnScroll({ + ...config, + shouldReposition: false, + propValue: false, + componentDefaultValue: true, + }); + }); + }); + }); + describe('repositionToCrossAxis', () => { beforeEach(() => { // Set a forced viewport with not enough room to render the popover vertically diff --git a/packages/eui/src/components/popover/popover.tsx b/packages/eui/src/components/popover/popover.tsx index 5443fd44cb5..e178f3dcabc 100644 --- a/packages/eui/src/components/popover/popover.tsx +++ b/packages/eui/src/components/popover/popover.tsx @@ -15,6 +15,7 @@ import React, { Ref, RefCallback, PropsWithChildren, + ContextType, } from 'react'; import classNames from 'classnames'; import { focusable } from 'tabbable'; @@ -42,6 +43,10 @@ import { getElementZIndex, EuiPopoverPosition, } from '../../services/popover'; +import { + createRepositionOnScroll, + type CreateRepositionOnScrollReturnType, +} from '../../services/popover/reposition_on_scroll'; import { EuiI18n } from '../i18n'; import { EuiOutsideClickDetector } from '../outside_click_detector'; @@ -50,6 +55,7 @@ import { euiPopoverStyles } from './popover.styles'; import { EuiPopoverPanel } from './popover_panel'; import { EuiPopoverPanelProps } from './popover_panel/_popover_panel'; import { EuiPaddingSize } from '../../global_styling'; +import { EuiComponentDefaultsContext } from '../provider/component_defaults'; export const popoverAnchorPosition = [ 'upCenter', @@ -284,6 +290,10 @@ type PropsWithDefaults = Props & { }; export class EuiPopover extends Component { + static contextType = EuiComponentDefaultsContext; + declare context: ContextType; + private repositionOnScroll: CreateRepositionOnScrollReturnType; + static defaultProps: Partial = { isOpen: false, ownFocus: true, @@ -319,7 +329,7 @@ export class EuiPopover extends Component { return null; } - private respositionTimeout: number | undefined; + private repositionTimeout: number | undefined; private strandedFocusTimeout: number | undefined; private closingTransitionTimeout: number | undefined; private closingTransitionAnimationFrame: number | undefined; @@ -343,6 +353,12 @@ export class EuiPopover extends Component { openPosition: null, // once a stable position has been found, keep the contents on that side isOpenStable: false, // wait for any initial opening transitions to finish before marking as stable }; + + this.repositionOnScroll = createRepositionOnScroll(() => ({ + repositionOnScroll: this.props.repositionOnScroll, + componentDefaults: this.context.EuiPopover, + repositionFn: this.positionPopoverFixed, + })); } closePopover = () => { @@ -428,8 +444,8 @@ export class EuiPopover extends Component { { durationMatch: 0, delayMatch: 0 } ); - clearTimeout(this.respositionTimeout); - this.respositionTimeout = window.setTimeout(() => { + clearTimeout(this.repositionTimeout); + this.repositionTimeout = window.setTimeout(() => { this.setState({ isOpenStable: true }, () => { this.positionPopoverFixed(); }); @@ -445,9 +461,7 @@ export class EuiPopover extends Component { }); } - if (this.props.repositionOnScroll) { - window.addEventListener('scroll', this.positionPopoverFixed, true); - } + this.repositionOnScroll.subscribe(); } componentDidUpdate(prevProps: Props) { @@ -468,13 +482,7 @@ export class EuiPopover extends Component { } // update scroll listener - if (prevProps.repositionOnScroll !== this.props.repositionOnScroll) { - if (this.props.repositionOnScroll) { - window.addEventListener('scroll', this.positionPopoverFixed, true); - } else { - window.removeEventListener('scroll', this.positionPopoverFixed, true); - } - } + this.repositionOnScroll.update(); // The popover is being closed. if (prevProps.isOpen && !this.props.isOpen) { @@ -489,8 +497,8 @@ export class EuiPopover extends Component { } componentWillUnmount() { - window.removeEventListener('scroll', this.positionPopoverFixed, true); - clearTimeout(this.respositionTimeout); + this.repositionOnScroll.cleanup(); + clearTimeout(this.repositionTimeout); clearTimeout(this.strandedFocusTimeout); clearTimeout(this.closingTransitionTimeout); cancelAnimationFrame(this.closingTransitionAnimationFrame!); diff --git a/packages/eui/src/components/provider/component_defaults/component_defaults.tsx b/packages/eui/src/components/provider/component_defaults/component_defaults.tsx index 85e7a60e1c5..02df975d16d 100644 --- a/packages/eui/src/components/provider/component_defaults/component_defaults.tsx +++ b/packages/eui/src/components/provider/component_defaults/component_defaults.tsx @@ -18,6 +18,8 @@ import type { EuiPortalProps } from '../../portal'; import type { EuiFocusTrapProps } from '../../focus_trap'; import type { EuiTablePaginationProps, EuiTableProps } from '../../table'; import type { EuiFlyoutProps } from '../../flyout'; +import type { EuiPopoverProps } from '../../popover'; +import type { EuiToolTipProps } from '../../tool_tip'; export type EuiComponentDefaults = { /** @@ -53,6 +55,16 @@ export type EuiComponentDefaults = { EuiFlyoutProps, 'includeSelectorInFocusTrap' | 'includeFixedHeadersInFocusTrap' >; + /** + * Provide a global configuration for `EuiPopover`s. + * Defaults will be inherited by every `EuiPopover`. + */ + EuiPopover?: Pick; + /** + * Provide a global configuration for `EuiToolTip`s. + * Defaults will be inherited by every `EuiToolTip`. + */ + EuiToolTip?: Pick; }; // Declaring as a static const for reference integrity/reducing rerenders diff --git a/packages/eui/src/components/tool_tip/tool_tip.spec.tsx b/packages/eui/src/components/tool_tip/tool_tip.spec.tsx index 36605fd0f14..694584376c9 100644 --- a/packages/eui/src/components/tool_tip/tool_tip.spec.tsx +++ b/packages/eui/src/components/tool_tip/tool_tip.spec.tsx @@ -17,6 +17,7 @@ import { EuiFlyout } from '../flyout'; import { EuiModal } from '../modal'; import { EuiPopover } from '../popover'; import { EuiToolTip } from './tool_tip'; +import { testRepositionOnScroll } from '../../test/cypress/test_reposition_on_scroll'; describe('EuiToolTip', () => { it('shows the tooltip on hover and hides it on mouseout', () => { @@ -216,4 +217,69 @@ describe('EuiToolTip', () => { cy.get('[data-test-subj="popover"]').should('not.exist'); }); }); + + describe('repositionOnScroll', () => { + const renderTooltip = (props: { repositionOnScroll?: boolean }) => ( + + Fixed Button + + ); + + const config = { + renderComponent: renderTooltip, + componentName: 'EuiToolTip' as const, + triggerSelector: '[data-test-subj="fixed-trigger"]', + panelSelector: '[data-test-subj="tooltip"]', + }; + + describe('is repositioned', () => { + it('when `repositionOnScroll=true`', () => { + testRepositionOnScroll({ + ...config, + shouldReposition: true, + propValue: true, + }); + }); + + it('when `componentDefaults` has `repositionOnScroll=true`', () => { + testRepositionOnScroll({ + ...config, + shouldReposition: true, + componentDefaultValue: true, + }); + }); + + it('when `repositionOnScroll=true` even if `componentDefaults` has `repositionOnScroll=false`', () => { + testRepositionOnScroll({ + ...config, + shouldReposition: true, + propValue: true, + componentDefaultValue: false, + }); + }); + }); + + describe('is not repositioned', () => { + it('when `repositionOnScroll=false` (default value)', () => { + testRepositionOnScroll({ ...config, shouldReposition: false }); + }); + + it('when `componentDefaults` has `repositionOnScroll=false`', () => { + testRepositionOnScroll({ + ...config, + shouldReposition: false, + componentDefaultValue: false, + }); + }); + + it('when `repositionOnScroll=false` even if `componentDefaults` has `repositionOnScroll=true`', () => { + testRepositionOnScroll({ + ...config, + shouldReposition: false, + propValue: false, + componentDefaultValue: true, + }); + }); + }); + }); }); diff --git a/packages/eui/src/components/tool_tip/tool_tip.test.tsx b/packages/eui/src/components/tool_tip/tool_tip.test.tsx index 8a7484be835..9b401fa5ead 100644 --- a/packages/eui/src/components/tool_tip/tool_tip.test.tsx +++ b/packages/eui/src/components/tool_tip/tool_tip.test.tsx @@ -111,31 +111,6 @@ describe('EuiToolTip', () => { expect(container).toMatchSnapshot(); }); - test('repositionOnScroll', () => { - const addEventSpy = jest.spyOn(window, 'addEventListener'); - const removeEventSpy = jest.spyOn(window, 'removeEventListener'); - const repositionFn = expect.any(Function); - - const { rerender, unmount } = render( - - - - ); - expect(addEventSpy).not.toHaveBeenCalledWith('scroll', expect.anything()); - - // Should add a scroll event listener on mount and on update - rerender( - - - - ); - expect(addEventSpy).toHaveBeenCalledWith('scroll', repositionFn, true); - - // Should remove the scroll event listener on unmount - unmount(); - expect(removeEventSpy).toHaveBeenCalledWith('scroll', repositionFn, true); - }); - describe('aria-describedby', () => { it('by default, sets an `aria-describedby` on the anchor when the tooltip is visible', async () => { const { getByTestSubject } = render( diff --git a/packages/eui/src/components/tool_tip/tool_tip.tsx b/packages/eui/src/components/tool_tip/tool_tip.tsx index 6b5f406acba..6655d9f368d 100644 --- a/packages/eui/src/components/tool_tip/tool_tip.tsx +++ b/packages/eui/src/components/tool_tip/tool_tip.tsx @@ -8,6 +8,7 @@ import React, { Component, + ContextType, ReactElement, ReactNode, MouseEvent as ReactMouseEvent, @@ -17,10 +18,15 @@ import classNames from 'classnames'; import { CommonProps } from '../common'; import { findPopoverPosition, htmlIdGenerator, keys } from '../../services'; +import { + createRepositionOnScroll, + type CreateRepositionOnScrollReturnType, +} from '../../services/popover/reposition_on_scroll'; import { type EuiPopoverPosition } from '../../services/popover'; import { enqueueStateChange } from '../../services/react'; import { EuiResizeObserver } from '../observer/resize_observer'; import { EuiPortal } from '../portal'; +import { EuiComponentDefaultsContext } from '../provider/component_defaults'; import { EuiToolTipPopover, ToolTipPositions } from './tool_tip_popover'; import { EuiToolTipAnchor } from './tool_tip_anchor'; @@ -139,19 +145,32 @@ interface State { } export class EuiToolTip extends Component { + static contextType = EuiComponentDefaultsContext; + declare context: ContextType; + private repositionOnScroll: CreateRepositionOnScrollReturnType; + _isMounted = false; anchor: null | HTMLElement = null; popover: null | HTMLElement = null; private timeoutId?: ReturnType; - state: State = { - visible: false, - hasFocus: false, - calculatedPosition: this.props.position, - toolTipStyles: DEFAULT_TOOLTIP_STYLES, - arrowStyles: undefined, - id: this.props.id || htmlIdGenerator()(), - }; + constructor(props: EuiToolTipProps) { + super(props); + this.state = { + visible: false, + hasFocus: false, + calculatedPosition: this.props.position, + toolTipStyles: DEFAULT_TOOLTIP_STYLES, + arrowStyles: undefined, + id: this.props.id || htmlIdGenerator()(), + }; + + this.repositionOnScroll = createRepositionOnScroll(() => ({ + repositionOnScroll: this.props.repositionOnScroll, + componentDefaults: this.context.EuiToolTip, + repositionFn: this.positionToolTip, + })); + } static defaultProps: Partial = { position: 'top', @@ -168,15 +187,13 @@ export class EuiToolTip extends Component { componentDidMount() { this._isMounted = true; - if (this.props.repositionOnScroll) { - window.addEventListener('scroll', this.positionToolTip, true); - } + this.repositionOnScroll.subscribe(); } componentWillUnmount() { this.clearAnimationTimeout(); this._isMounted = false; - window.removeEventListener('scroll', this.positionToolTip, true); + this.repositionOnScroll.cleanup(); } componentDidUpdate(prevProps: EuiToolTipProps, prevState: State) { @@ -185,13 +202,7 @@ export class EuiToolTip extends Component { } // update scroll listener - if (prevProps.repositionOnScroll !== this.props.repositionOnScroll) { - if (this.props.repositionOnScroll) { - window.addEventListener('scroll', this.positionToolTip, true); - } else { - window.removeEventListener('scroll', this.positionToolTip, true); - } - } + this.repositionOnScroll.update(); } testAnchor = () => { diff --git a/packages/eui/src/services/popover/reposition_on_scroll.ts b/packages/eui/src/services/popover/reposition_on_scroll.ts new file mode 100644 index 00000000000..99a4bea3c0f --- /dev/null +++ b/packages/eui/src/services/popover/reposition_on_scroll.ts @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +type SupportedComponentDefaults = { + repositionOnScroll?: boolean; +}; + +type RepositionOnScrollArgs = { + /** + * The component's `repositionOnScroll` prop. + */ + repositionOnScroll?: boolean; + /** + * The function to be called on scroll to reposition the component. + */ + repositionFn: () => void; + /** + * The component's defaults context. + */ + componentDefaults?: T; +}; + +/** + * Returns the value of the `repositionOnScroll` from the props, component's defaults context + * or default to `false`. + * + * @param args The arguments for `getRepositionOnScroll`. See {@link RepositionOnScrollArgs}. + * @returns The value of the `repositionOnScroll`. + */ +export const getRepositionOnScroll = ( + args: RepositionOnScrollArgs +): boolean => { + const { repositionOnScroll, componentDefaults } = args; + + if (repositionOnScroll !== undefined) return repositionOnScroll; + + if ( + componentDefaults && + 'repositionOnScroll' in componentDefaults && + typeof componentDefaults.repositionOnScroll === 'boolean' + ) { + const contextValue = componentDefaults.repositionOnScroll; + + return contextValue ?? false; + } + + return false; +}; + +export type CreateRepositionOnScrollReturnType = { + subscribe: () => void; + update: () => void; + cleanup: () => void; +}; + +/** + * Creates a manager for handling `repositionOnScroll` logic in overlay components. + * This utility abstracts the adding, updating, and removing of window scroll event listeners. + * + * @param getArgs A function that returns the arguments for `getRepositionOnScroll`. See {@link RepositionOnScrollArgs}. + * @returns An object with `subscribe`, `update`, and `cleanup` methods to manage the scroll listener. + */ +export const createRepositionOnScroll = ( + getArgs: () => RepositionOnScrollArgs +): CreateRepositionOnScrollReturnType => { + let lastResolvedRepositionOnScroll: boolean | undefined; + + const subscribe = () => { + const repositionOnScroll = getRepositionOnScroll(getArgs()); + + lastResolvedRepositionOnScroll = repositionOnScroll; + if (repositionOnScroll) { + window.addEventListener('scroll', getArgs().repositionFn, true); + } + }; + + const update = () => { + const repositionOnScroll = getRepositionOnScroll(getArgs()); + + if (lastResolvedRepositionOnScroll !== repositionOnScroll) { + if (repositionOnScroll) { + window.addEventListener('scroll', getArgs().repositionFn, true); + } else { + window.removeEventListener('scroll', getArgs().repositionFn, true); + } + lastResolvedRepositionOnScroll = repositionOnScroll; + } + }; + + const cleanup = () => { + window.removeEventListener('scroll', getArgs().repositionFn, true); + }; + + return { subscribe, update, cleanup }; +}; diff --git a/packages/eui/src/test/cypress/test_reposition_on_scroll.tsx b/packages/eui/src/test/cypress/test_reposition_on_scroll.tsx new file mode 100644 index 00000000000..25a783635ca --- /dev/null +++ b/packages/eui/src/test/cypress/test_reposition_on_scroll.tsx @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/// +/// +/// + +import React from 'react'; + +type ComponentName = 'EuiToolTip' | 'EuiPopover'; + +interface RepositionTestConfig { + shouldReposition: boolean; + propValue?: boolean; + componentDefaultValue?: boolean; + componentName: ComponentName; + triggerSelector: string; + panelSelector: string; + renderComponent: (props: { + repositionOnScroll?: boolean; + }) => React.ReactElement; +} + +export const testRepositionOnScroll = ({ + shouldReposition, + propValue, + componentDefaultValue, + componentName, + triggerSelector, + panelSelector, + renderComponent, +}: RepositionTestConfig) => { + const repositionProps = { + repositionOnScroll: typeof propValue === 'boolean' ? propValue : undefined, + }; + + const providerProps = + typeof componentDefaultValue === 'boolean' + ? { + providerProps: { + componentDefaults: { + [componentName]: { repositionOnScroll: componentDefaultValue }, + }, + }, + } + : undefined; + + cy.mount( + <> +
+
+ {renderComponent(repositionProps)} +
+ , + providerProps + ); + + cy.get(triggerSelector).click(); + + // Wait for positioning to finish + cy.get(panelSelector).as('panel').should('exist').waitForPositionToSettle(); + + cy.get('@panel').then(($panel) => { + const initialRect = $panel[0].getBoundingClientRect(); + cy.scrollTo(0, 500); + + // Wait for re-positioning to finish + cy.get('@panel') + .waitForPositionToSettle() + .then(($repositionedPanel) => { + const finalRect = $repositionedPanel[0].getBoundingClientRect(); + if (shouldReposition) { + expect(finalRect.top).to.equal(initialRect.top); + } else { + expect(finalRect.top).to.not.equal(initialRect.top); + } + }); + }); +}; diff --git a/packages/website/docs/getting-started/setup.mdx b/packages/website/docs/getting-started/setup.mdx index 6100230524a..9a0e2ca590c 100644 --- a/packages/website/docs/getting-started/setup.mdx +++ b/packages/website/docs/getting-started/setup.mdx @@ -321,6 +321,9 @@ While all props can be individually customized via props, some components can ha EuiTablePagination: { itemsPerPage: 20, }, EuiFocusTrap: { crossFrame: true }, EuiPortal: { insert }, + EuiFlyout: { includeSelectorInFocusTrap: ['.custom-selector', '[data-custom-selector]'], includeFixedHeadersInFocusTrap: false }, + EuiPopover: { repositionOnScroll: true }, + EuiToolTip: { repositionOnScroll: true }, }} > diff --git a/packages/website/docs/utilities/provider.mdx b/packages/website/docs/utilities/provider.mdx index a3a6a8b0cb5..3ed2d2f8946 100644 --- a/packages/website/docs/utilities/provider.mdx +++ b/packages/website/docs/utilities/provider.mdx @@ -106,7 +106,9 @@ All EUI components ship with a set of baseline defaults that can usually be conf EuiTablePagination: { itemsPerPage: 20, }, EuiFocusTrap: { crossFrame: true }, EuiPortal: { insert }, - EuiFlyout: { includeSelectorInFocusTrap: ['.custom-selector', '[data-custom-selector]'], includeFixedHeadersInFocusTrap: false } + EuiFlyout: { includeSelectorInFocusTrap: ['.custom-selector', '[data-custom-selector]'], includeFixedHeadersInFocusTrap: false }, + EuiPopover: { repositionOnScroll: true }, + EuiToolTip: { repositionOnScroll: true }, }} >