package.lib.modifiers.computeStyles.js.flow Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of core Show documentation
Show all versions of core Show documentation
Tooltip and Popover Positioning Engine
// @flow
import type {
PositioningStrategy,
Offsets,
Modifier,
ModifierArguments,
Rect,
Window,
} from '../types';
import {
type BasePlacement,
type Variation,
top,
left,
right,
bottom,
end,
} from '../enums';
import getOffsetParent from '../dom-utils/getOffsetParent';
import getWindow from '../dom-utils/getWindow';
import getDocumentElement from '../dom-utils/getDocumentElement';
import getComputedStyle from '../dom-utils/getComputedStyle';
import getBasePlacement from '../utils/getBasePlacement';
import getVariation from '../utils/getVariation';
import { round } from '../utils/math';
// eslint-disable-next-line import/no-unused-modules
export type RoundOffsets = (
offsets: $Shape<{ x: number, y: number, centerOffset: number }>
) => Offsets;
// eslint-disable-next-line import/no-unused-modules
export type Options = {
gpuAcceleration: boolean,
adaptive: boolean,
roundOffsets?: boolean | RoundOffsets,
};
const unsetSides = {
top: 'auto',
right: 'auto',
bottom: 'auto',
left: 'auto',
};
// Round the offsets to the nearest suitable subpixel based on the DPR.
// Zooming can change the DPR, but it seems to report a value that will
// cleanly divide the values into the appropriate subpixels.
function roundOffsetsByDPR({ x, y }): Offsets {
const win: Window = window;
const dpr = win.devicePixelRatio || 1;
return {
x: round(x * dpr) / dpr || 0,
y: round(y * dpr) / dpr || 0,
};
}
export function mapToStyles({
popper,
popperRect,
placement,
variation,
offsets,
position,
gpuAcceleration,
adaptive,
roundOffsets,
isFixed,
}: {
popper: HTMLElement,
popperRect: Rect,
placement: BasePlacement,
variation: ?Variation,
offsets: $Shape<{ x: number, y: number, centerOffset: number }>,
position: PositioningStrategy,
gpuAcceleration: boolean,
adaptive: boolean,
roundOffsets: boolean | RoundOffsets,
isFixed: boolean,
}) {
let { x = 0, y = 0 } = offsets;
({ x, y } =
typeof roundOffsets === 'function'
? roundOffsets({ x, y })
: { x, y });
const hasX = offsets.hasOwnProperty('x');
const hasY = offsets.hasOwnProperty('y');
let sideX: string = left;
let sideY: string = top;
const win: Window = window;
if (adaptive) {
let offsetParent = getOffsetParent(popper);
let heightProp = 'clientHeight';
let widthProp = 'clientWidth';
if (offsetParent === getWindow(popper)) {
offsetParent = getDocumentElement(popper);
if (
getComputedStyle(offsetParent).position !== 'static' &&
position === 'absolute'
) {
heightProp = 'scrollHeight';
widthProp = 'scrollWidth';
}
}
// $FlowFixMe[incompatible-cast]: force type refinement, we compare offsetParent with window above, but Flow doesn't detect it
offsetParent = (offsetParent: Element);
if (
placement === top ||
((placement === left || placement === right) && variation === end)
) {
sideY = bottom;
const offsetY =
isFixed && offsetParent === win && win.visualViewport
? win.visualViewport.height
: // $FlowFixMe[prop-missing]
offsetParent[heightProp];
y -= offsetY - popperRect.height;
y *= gpuAcceleration ? 1 : -1;
}
if (
placement === left ||
((placement === top || placement === bottom) && variation === end)
) {
sideX = right;
const offsetX =
isFixed && offsetParent === win && win.visualViewport
? win.visualViewport.width
: // $FlowFixMe[prop-missing]
offsetParent[widthProp];
x -= offsetX - popperRect.width;
x *= gpuAcceleration ? 1 : -1;
}
}
const commonStyles = {
position,
...(adaptive && unsetSides),
};
({ x, y } =
roundOffsets === true
? roundOffsetsByDPR({ x, y })
: { x, y });
if (gpuAcceleration) {
return {
...commonStyles,
[sideY]: hasY ? '0' : '',
[sideX]: hasX ? '0' : '',
// Layer acceleration can disable subpixel rendering which causes slightly
// blurry text on low PPI displays, so we want to use 2D transforms
// instead
transform:
(win.devicePixelRatio || 1) <= 1
? `translate(${x}px, ${y}px)`
: `translate3d(${x}px, ${y}px, 0)`,
};
}
return {
...commonStyles,
[sideY]: hasY ? `${y}px` : '',
[sideX]: hasX ? `${x}px` : '',
transform: '',
};
}
function computeStyles({ state, options }: ModifierArguments) {
const {
gpuAcceleration = true,
adaptive = true,
// defaults to use builtin `roundOffsetsByDPR`
roundOffsets = true,
} = options;
if (false) {
const transitionProperty =
getComputedStyle(state.elements.popper).transitionProperty || '';
if (
adaptive &&
['transform', 'top', 'right', 'bottom', 'left'].some(
(property) => transitionProperty.indexOf(property) >= 0
)
) {
console.warn(
[
'Popper: Detected CSS transitions on at least one of the following',
'CSS properties: "transform", "top", "right", "bottom", "left".',
'\n\n',
'Disable the "computeStyles" modifier\'s `adaptive` option to allow',
'for smooth transitions, or remove these properties from the CSS',
'transition declaration on the popper element if only transitioning',
'opacity or background-color for example.',
'\n\n',
'We recommend using the popper element as a wrapper around an inner',
'element that can have any CSS property transitioned for animations.',
].join(' ')
);
}
}
const commonStyles = {
placement: getBasePlacement(state.placement),
variation: getVariation(state.placement),
popper: state.elements.popper,
popperRect: state.rects.popper,
gpuAcceleration,
isFixed: state.options.strategy === 'fixed',
};
if (state.modifiersData.popperOffsets != null) {
state.styles.popper = {
...state.styles.popper,
...mapToStyles({
...commonStyles,
offsets: state.modifiersData.popperOffsets,
position: state.options.strategy,
adaptive,
roundOffsets,
}),
};
}
if (state.modifiersData.arrow != null) {
state.styles.arrow = {
...state.styles.arrow,
...mapToStyles({
...commonStyles,
offsets: state.modifiersData.arrow,
position: 'absolute',
adaptive: false,
roundOffsets,
}),
};
}
state.attributes.popper = {
...state.attributes.popper,
'data-popper-placement': state.placement,
};
}
// eslint-disable-next-line import/no-unused-modules
export type ComputeStylesModifier = Modifier<'computeStyles', Options>;
export default ({
name: 'computeStyles',
enabled: true,
phase: 'beforeWrite',
fn: computeStyles,
data: {},
}: ComputeStylesModifier);