@workday/canvas-kit-react
Version:
The parent module that contains all Workday Canvas Kit React components
118 lines (106 loc) • 3.22 kB
text/typescript
import {
Placement as PopperJSPlacement,
State,
detectOverflow,
SideObject,
Modifier,
ModifierArguments,
} from '@popperjs/core';
export type Placement = PopperJSPlacement; // Use template literals to make documentation list them out
// We must use our own copy because the commonjs version of Popper doesn't export enums
// https://github.com/floating-ui/floating-ui/issues/1325
const placements: Placement[] = [
'top',
'top-start',
'top-end',
'bottom',
'bottom-start',
'bottom-end',
'right',
'right-start',
'right-end',
'left',
'left-start',
'left-end',
'auto',
'auto-start',
'auto-end'
];
const getOppositePlacement = (popperPlacement: Placement): Placement => {
const [first, second] = popperPlacement.split('-');
let oppositePlacement: Placement;
switch (first) {
case 'top':
oppositePlacement = 'bottom';
break;
case 'bottom':
oppositePlacement = 'top';
break;
case 'left':
oppositePlacement = 'right';
break;
case 'right':
oppositePlacement = 'left';
break;
default:
oppositePlacement = 'auto';
}
if (second) {
oppositePlacement =
placements.find(placement => placement.includes(`${oppositePlacement}-${second}`)) ??
oppositePlacement;
}
return oppositePlacement;
};
const choseAvailablePlacement = (
placements: Placement[],
state: State,
preferredPlacement: Placement
): Placement => {
if (placements.length === 0 || placements[0].split('-')[0] === 'auto') {
return preferredPlacement;
}
const {reference, popper} = state.rects;
const overflow = detectOverflow({...state, placement: placements[0]});
const direction = /left|right/.test(placements[0].split('-')[0]) ? 'horizontal' : 'vertical';
const isOverflowed =
(overflow.top > 0 || overflow.bottom > 0 || overflow.left > 0 || overflow.right > 0) &&
(direction === 'horizontal'
? popper.height / 2 - overflow.top < reference.height ||
popper.height / 2 - overflow.bottom < reference.height
: popper.width - overflow.left < reference.width ||
popper.width - overflow.right < reference.width);
const key = placements[0].split('-')[0] as keyof SideObject;
if (!isOverflowed && overflow[key] <= 0) {
return placements[0];
} else {
return choseAvailablePlacement(placements.slice(1), state, preferredPlacement);
}
};
export type Options = {
fallbackPlacements: Placement[];
};
const name = 'fallbackModifier';
export const fallbackPlacementsModifier: Modifier<typeof name, Options> = {
name: name,
enabled: true,
phase: 'main',
data: {
_skip: false,
},
fn({state, options}: ModifierArguments<Options>) {
const preferredPlacement = state.options.placement;
const {fallbackPlacements} = options;
const placements = [
preferredPlacement,
getOppositePlacement(preferredPlacement),
...(fallbackPlacements || []),
];
const placementOverride = choseAvailablePlacement(placements, state, preferredPlacement);
if (state.placement !== placementOverride) {
state.modifiersData[name]._skip = true;
state.placement = placementOverride;
state.reset = true;
}
},
};