Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions UNRELEASED.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Use [the changelog guidelines](https://git.io/polaris-changelog-guidelines) to f
- Added `ariaLabelledBy` props to `Navigation` component to allow a hidden label for accessibility ([#4343](https://github.com/Shopify/polaris-react/pull/4343))
- Add `lastColumnSticky` prop to `IndexTable` to create a sticky last cell and optional sticky last heading on viewports larger than small ([#4150](https://github.com/Shopify/polaris-react/pull/4150))
- Allow promoted actions to be rendered as a menu on the `BulkAction` component ([#4266](https://github.com/Shopify/polaris-react/pull/4266))
- Added `mutationObserveConfig` prop to `Popover` and `PositionedOverlay` ([#4303](https://github.com/Shopify/polaris-react/pull/4303))

### Bug fixes

Expand Down
4 changes: 4 additions & 0 deletions src/components/Popover/Popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export interface PopoverProps {
* @default true
*/
preferInputActivator?: PopoverOverlayProps['preferInputActivator'];
/** Customize the MutationObserveInit object for more precise Popover re-rendering on `children` changes */
mutationObserveConfig?: PopoverOverlayProps['mutationObserveConfig'];
/**
* The element type to wrap the activator with
* @default 'div'
Expand Down Expand Up @@ -96,6 +98,7 @@ export const Popover: React.FunctionComponent<PopoverProps> & {
fixed,
ariaHaspopup,
preferInputActivator = true,
mutationObserveConfig,
colorScheme,
zIndexOverride,
...rest
Expand Down Expand Up @@ -181,6 +184,7 @@ export const Popover: React.FunctionComponent<PopoverProps> & {
id={id}
activator={activatorNode}
preferInputActivator={preferInputActivator}
mutationObserveConfig={mutationObserveConfig}
onClose={handleClose}
active={active}
fixed={fixed}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export interface PopoverOverlayProps {
zIndexOverride?: number;
activator: HTMLElement;
preferInputActivator?: PositionedOverlayProps['preferInputActivator'];
mutationObserveConfig?: PositionedOverlayProps['mutationObserveConfig'];
sectioned?: boolean;
fixed?: boolean;
hideOnPrint?: boolean;
Expand Down Expand Up @@ -120,6 +121,7 @@ export class PopoverOverlay extends PureComponent<PopoverOverlayProps, State> {
preferInputActivator = true,
fixed,
zIndexOverride,
mutationObserveConfig,
} = this.props;
const {transitionStatus} = this.state;
if (transitionStatus === TransitionStatus.Exited && !active) return null;
Expand Down Expand Up @@ -148,6 +150,7 @@ export class PopoverOverlay extends PureComponent<PopoverOverlayProps, State> {
onScrollOut={this.handleScrollOut}
classNames={className}
zIndexOverride={zIndexOverride}
mutationObserveConfig={mutationObserveConfig}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,27 @@ describe('<PopoverOverlay />', () => {
});
});

it('passes mutationObserveConfig to PositionedOverlay', () => {
const mockMutationObserveConfig = {
attributes: true,
};
const popoverOverlay = mountWithApp(
<PopoverOverlay
active
mutationObserveConfig={mockMutationObserveConfig}
id="PopoverOverlay-1"
activator={activator}
onClose={noop}
>
{children}
</PopoverOverlay>,
);

expect(popoverOverlay).toContainReactComponent(PositionedOverlay, {
mutationObserveConfig: mockMutationObserveConfig,
});
});

it("doesn't include a tabindex prop when autofocusTarget is 'none'", () => {
const popoverOverlay = mountWithAppProvider(
<PopoverOverlay
Expand Down
19 changes: 19 additions & 0 deletions src/components/Popover/tests/Popover.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,25 @@ describe('<Popover />', () => {
});
});

it('passes mutationObserveConfig to PopoverOverlay', () => {
const mockMutationObserveConfig = {
attributes: true,
};
const popover = mountWithApp(
<Popover
active={false}
preferredPosition="above"
activator={<div>Activator</div>}
onClose={spy}
mutationObserveConfig={mockMutationObserveConfig}
/>,
);

expect(popover).toContainReactComponent(PopoverOverlay, {
mutationObserveConfig: mockMutationObserveConfig,
});
});

it('has a div as activatorWrapper by default', () => {
const popover = mountWithApp(
<Popover
Expand Down
13 changes: 10 additions & 3 deletions src/components/PositionedOverlay/PositionedOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export interface PositionedOverlayProps {
preventInteraction?: boolean;
classNames?: string;
zIndexOverride?: number;
mutationObserveConfig?: MutationObserverInit;
render(overlayDetails: OverlayDetails): React.ReactNode;
onScrollOut?(): void;
}
Expand All @@ -57,7 +58,7 @@ interface State {
lockPosition: boolean;
}

const OBSERVER_CONFIG = {
export const DEFAULT_OBSERVER_CONFIG: PositionedOverlayProps['mutationObserveConfig'] = {
childList: true,
subtree: true,
characterData: true,
Expand Down Expand Up @@ -208,6 +209,7 @@ export class PositionedOverlay extends PureComponent<
fullWidth,
fixed,
preferInputActivator = true,
mutationObserveConfig,
} = this.props;

const preferredActivator = preferInputActivator
Expand Down Expand Up @@ -281,8 +283,13 @@ export class PositionedOverlay extends PureComponent<
},
() => {
if (!this.overlay) return;
this.observer.observe(this.overlay, OBSERVER_CONFIG);
this.observer.observe(activator, OBSERVER_CONFIG);

this.observer.observe(this.overlay, {
...DEFAULT_OBSERVER_CONFIG,
...mutationObserveConfig,
});

this.observer.observe(activator, DEFAULT_OBSERVER_CONFIG);
},
);
},
Expand Down
62 changes: 48 additions & 14 deletions src/components/PositionedOverlay/tests/PositionedOverlay.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from 'react';
import {mountWithAppProvider} from 'test-utilities/legacy';

import {EventListener} from '../../EventListener';
import {PositionedOverlay} from '../PositionedOverlay';
import {PositionedOverlay, DEFAULT_OBSERVER_CONFIG} from '../PositionedOverlay';
import * as mathModule from '../utilities/math';
import * as geometry from '../../../utilities/geometry';
import styles from '../PositionedOverlay.scss';
Expand Down Expand Up @@ -38,20 +38,54 @@ describe('<PositionedOverlay />', () => {
mutationObserverObserveSpy.mockRestore();
});

it('observers the activator', () => {
const activator = document.createElement('button');
mountWithAppProvider(
<PositionedOverlay
{...mockProps}
activator={activator}
preferredPosition="above"
/>,
);
describe('overlay', () => {
it('passes default config to', () => {
const positionedOverlay = mountWithAppProvider(
<PositionedOverlay {...mockProps} />,
);
const element = positionedOverlay.find('div').getDOMNode();

expect(mutationObserverObserveSpy).toHaveBeenCalledWith(
element,
DEFAULT_OBSERVER_CONFIG,
);
});

it('merges prop with default config', () => {
const mockMutationObserveConfig = {
subtree: false,
attributes: true,
};
const positionedOverlay = mountWithAppProvider(
<PositionedOverlay
{...mockProps}
mutationObserveConfig={mockMutationObserveConfig}
/>,
);
const element = positionedOverlay.find('div').getDOMNode();

expect(mutationObserverObserveSpy).toHaveBeenCalledWith(element, {
...DEFAULT_OBSERVER_CONFIG,
...mockMutationObserveConfig,
});
});
});

expect(mutationObserverObserveSpy).toHaveBeenCalledWith(activator, {
characterData: true,
childList: true,
subtree: true,
describe('activator', () => {
it('observers the activator', () => {
const activator = document.createElement('button');

mountWithAppProvider(
<PositionedOverlay
{...mockProps}
activator={activator}
preferredPosition="above"
/>,
);

expect(mutationObserverObserveSpy).toHaveBeenCalledWith(activator, {
...DEFAULT_OBSERVER_CONFIG,
});
});
});
});
Expand Down