1 | // @flow
|
---|
2 | import type { State, SideObject, Padding, PositioningStrategy } from '../types';
|
---|
3 | import type { Placement, Boundary, RootBoundary, Context } from '../enums';
|
---|
4 | import getClippingRect from '../dom-utils/getClippingRect';
|
---|
5 | import getDocumentElement from '../dom-utils/getDocumentElement';
|
---|
6 | import getBoundingClientRect from '../dom-utils/getBoundingClientRect';
|
---|
7 | import computeOffsets from './computeOffsets';
|
---|
8 | import rectToClientRect from './rectToClientRect';
|
---|
9 | import {
|
---|
10 | clippingParents,
|
---|
11 | reference,
|
---|
12 | popper,
|
---|
13 | bottom,
|
---|
14 | top,
|
---|
15 | right,
|
---|
16 | basePlacements,
|
---|
17 | viewport,
|
---|
18 | } from '../enums';
|
---|
19 | import { isElement } from '../dom-utils/instanceOf';
|
---|
20 | import mergePaddingObject from './mergePaddingObject';
|
---|
21 | import expandToHashMap from './expandToHashMap';
|
---|
22 |
|
---|
23 | // eslint-disable-next-line import/no-unused-modules
|
---|
24 | export type Options = {
|
---|
25 | placement: Placement,
|
---|
26 | strategy: PositioningStrategy,
|
---|
27 | boundary: Boundary,
|
---|
28 | rootBoundary: RootBoundary,
|
---|
29 | elementContext: Context,
|
---|
30 | altBoundary: boolean,
|
---|
31 | padding: Padding,
|
---|
32 | };
|
---|
33 |
|
---|
34 | export default function detectOverflow(
|
---|
35 | state: State,
|
---|
36 | options: $Shape<Options> = {}
|
---|
37 | ): SideObject {
|
---|
38 | const {
|
---|
39 | placement = state.placement,
|
---|
40 | strategy = state.strategy,
|
---|
41 | boundary = clippingParents,
|
---|
42 | rootBoundary = viewport,
|
---|
43 | elementContext = popper,
|
---|
44 | altBoundary = false,
|
---|
45 | padding = 0,
|
---|
46 | } = options;
|
---|
47 |
|
---|
48 | const paddingObject = mergePaddingObject(
|
---|
49 | typeof padding !== 'number'
|
---|
50 | ? padding
|
---|
51 | : expandToHashMap(padding, basePlacements)
|
---|
52 | );
|
---|
53 |
|
---|
54 | const altContext = elementContext === popper ? reference : popper;
|
---|
55 |
|
---|
56 | const popperRect = state.rects.popper;
|
---|
57 | const element = state.elements[altBoundary ? altContext : elementContext];
|
---|
58 |
|
---|
59 | const clippingClientRect = getClippingRect(
|
---|
60 | isElement(element)
|
---|
61 | ? element
|
---|
62 | : element.contextElement || getDocumentElement(state.elements.popper),
|
---|
63 | boundary,
|
---|
64 | rootBoundary,
|
---|
65 | strategy
|
---|
66 | );
|
---|
67 |
|
---|
68 | const referenceClientRect = getBoundingClientRect(state.elements.reference);
|
---|
69 |
|
---|
70 | const popperOffsets = computeOffsets({
|
---|
71 | reference: referenceClientRect,
|
---|
72 | element: popperRect,
|
---|
73 | strategy: 'absolute',
|
---|
74 | placement,
|
---|
75 | });
|
---|
76 |
|
---|
77 | const popperClientRect = rectToClientRect({
|
---|
78 | ...popperRect,
|
---|
79 | ...popperOffsets,
|
---|
80 | });
|
---|
81 |
|
---|
82 | const elementClientRect =
|
---|
83 | elementContext === popper ? popperClientRect : referenceClientRect;
|
---|
84 |
|
---|
85 | // positive = overflowing the clipping rect
|
---|
86 | // 0 or negative = within the clipping rect
|
---|
87 | const overflowOffsets = {
|
---|
88 | top: clippingClientRect.top - elementClientRect.top + paddingObject.top,
|
---|
89 | bottom:
|
---|
90 | elementClientRect.bottom -
|
---|
91 | clippingClientRect.bottom +
|
---|
92 | paddingObject.bottom,
|
---|
93 | left: clippingClientRect.left - elementClientRect.left + paddingObject.left,
|
---|
94 | right:
|
---|
95 | elementClientRect.right - clippingClientRect.right + paddingObject.right,
|
---|
96 | };
|
---|
97 |
|
---|
98 | const offsetData = state.modifiersData.offset;
|
---|
99 |
|
---|
100 | // Offsets can be applied only to the popper element
|
---|
101 | if (elementContext === popper && offsetData) {
|
---|
102 | const offset = offsetData[placement];
|
---|
103 |
|
---|
104 | Object.keys(overflowOffsets).forEach((key) => {
|
---|
105 | const multiply = [right, bottom].indexOf(key) >= 0 ? 1 : -1;
|
---|
106 | const axis = [top, bottom].indexOf(key) >= 0 ? 'y' : 'x';
|
---|
107 | overflowOffsets[key] += offset[axis] * multiply;
|
---|
108 | });
|
---|
109 | }
|
---|
110 |
|
---|
111 | return overflowOffsets;
|
---|
112 | }
|
---|