Welcome to mirror list, hosted at ThFree Co, Russian Federation.

preventOverflow.js.flow « modifiers « lib « package « popperjs - github.com/gohugoio/hugo-mod-jslibs-dist.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: d503cb9a17f30d7fb39bf2bf301653427beacce6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
// @flow
import { top, left, right, bottom, start } from '../enums';
import type { Placement, Boundary, RootBoundary } from '../enums';
import type { Rect, ModifierArguments, Modifier, Padding } from '../types';
import getBasePlacement from '../utils/getBasePlacement';
import getMainAxisFromPlacement from '../utils/getMainAxisFromPlacement';
import getAltAxis from '../utils/getAltAxis';
import { within, withinMaxClamp } from '../utils/within';
import getLayoutRect from '../dom-utils/getLayoutRect';
import getOffsetParent from '../dom-utils/getOffsetParent';
import detectOverflow from '../utils/detectOverflow';
import getVariation from '../utils/getVariation';
import getFreshSideObject from '../utils/getFreshSideObject';
import { min as mathMin, max as mathMax } from '../utils/math';

type TetherOffset =
  | (({
      popper: Rect,
      reference: Rect,
      placement: Placement,
    }) => number | { mainAxis: number, altAxis: number })
  | number
  | { mainAxis: number, altAxis: number };

// eslint-disable-next-line import/no-unused-modules
export type Options = {
  /* Prevents boundaries overflow on the main axis */
  mainAxis: boolean,
  /* Prevents boundaries overflow on the alternate axis */
  altAxis: boolean,
  /* The area to check the popper is overflowing in */
  boundary: Boundary,
  /* If the popper is not overflowing the main area, fallback to this one */
  rootBoundary: RootBoundary,
  /* Use the reference's "clippingParents" boundary context */
  altBoundary: boolean,
  /**
   * Allows the popper to overflow from its boundaries to keep it near its
   * reference element
   */
  tether: boolean,
  /* Offsets when the `tether` option should activate */
  tetherOffset: TetherOffset,
  /* Sets a padding to the provided boundary */
  padding: Padding,
};

function preventOverflow({ state, options, name }: ModifierArguments<Options>) {
  const {
    mainAxis: checkMainAxis = true,
    altAxis: checkAltAxis = false,
    boundary,
    rootBoundary,
    altBoundary,
    padding,
    tether = true,
    tetherOffset = 0,
  } = options;

  const overflow = detectOverflow(state, {
    boundary,
    rootBoundary,
    padding,
    altBoundary,
  });
  const basePlacement = getBasePlacement(state.placement);
  const variation = getVariation(state.placement);
  const isBasePlacement = !variation;
  const mainAxis = getMainAxisFromPlacement(basePlacement);
  const altAxis = getAltAxis(mainAxis);
  const popperOffsets = state.modifiersData.popperOffsets;
  const referenceRect = state.rects.reference;
  const popperRect = state.rects.popper;
  const tetherOffsetValue =
    typeof tetherOffset === 'function'
      ? tetherOffset({
          ...state.rects,
          placement: state.placement,
        })
      : tetherOffset;
  const normalizedTetherOffsetValue =
    typeof tetherOffsetValue === 'number'
      ? { mainAxis: tetherOffsetValue, altAxis: tetherOffsetValue }
      : { mainAxis: 0, altAxis: 0, ...tetherOffsetValue };
  const offsetModifierState = state.modifiersData.offset
    ? state.modifiersData.offset[state.placement]
    : null;

  const data = { x: 0, y: 0 };

  if (!popperOffsets) {
    return;
  }

  if (checkMainAxis) {
    const mainSide = mainAxis === 'y' ? top : left;
    const altSide = mainAxis === 'y' ? bottom : right;
    const len = mainAxis === 'y' ? 'height' : 'width';
    const offset = popperOffsets[mainAxis];

    const min = offset + overflow[mainSide];
    const max = offset - overflow[altSide];

    const additive = tether ? -popperRect[len] / 2 : 0;

    const minLen = variation === start ? referenceRect[len] : popperRect[len];
    const maxLen = variation === start ? -popperRect[len] : -referenceRect[len];

    // We need to include the arrow in the calculation so the arrow doesn't go
    // outside the reference bounds
    const arrowElement = state.elements.arrow;
    const arrowRect =
      tether && arrowElement
        ? getLayoutRect(arrowElement)
        : { width: 0, height: 0 };
    const arrowPaddingObject = state.modifiersData['arrow#persistent']
      ? state.modifiersData['arrow#persistent'].padding
      : getFreshSideObject();
    const arrowPaddingMin = arrowPaddingObject[mainSide];
    const arrowPaddingMax = arrowPaddingObject[altSide];

    // If the reference length is smaller than the arrow length, we don't want
    // to include its full size in the calculation. If the reference is small
    // and near the edge of a boundary, the popper can overflow even if the
    // reference is not overflowing as well (e.g. virtual elements with no
    // width or height)
    const arrowLen = within(0, referenceRect[len], arrowRect[len]);

    const minOffset = isBasePlacement
      ? referenceRect[len] / 2 -
        additive -
        arrowLen -
        arrowPaddingMin -
        normalizedTetherOffsetValue.mainAxis
      : minLen -
        arrowLen -
        arrowPaddingMin -
        normalizedTetherOffsetValue.mainAxis;
    const maxOffset = isBasePlacement
      ? -referenceRect[len] / 2 +
        additive +
        arrowLen +
        arrowPaddingMax +
        normalizedTetherOffsetValue.mainAxis
      : maxLen +
        arrowLen +
        arrowPaddingMax +
        normalizedTetherOffsetValue.mainAxis;

    const arrowOffsetParent =
      state.elements.arrow && getOffsetParent(state.elements.arrow);
    const clientOffset = arrowOffsetParent
      ? mainAxis === 'y'
        ? arrowOffsetParent.clientTop || 0
        : arrowOffsetParent.clientLeft || 0
      : 0;

    const offsetModifierValue = offsetModifierState?.[mainAxis] ?? 0;
    const tetherMin = offset + minOffset - offsetModifierValue - clientOffset;
    const tetherMax = offset + maxOffset - offsetModifierValue;

    const preventedOffset = within(
      tether ? mathMin(min, tetherMin) : min,
      offset,
      tether ? mathMax(max, tetherMax) : max
    );

    popperOffsets[mainAxis] = preventedOffset;
    data[mainAxis] = preventedOffset - offset;
  }

  if (checkAltAxis) {
    const mainSide = mainAxis === 'x' ? top : left;
    const altSide = mainAxis === 'x' ? bottom : right;
    const offset = popperOffsets[altAxis];

    const len = altAxis === 'y' ? 'height' : 'width';

    const min = offset + overflow[mainSide];
    const max = offset - overflow[altSide];

    const isOriginSide = [top, left].indexOf(basePlacement) !== -1;

    const offsetModifierValue = offsetModifierState?.[altAxis] ?? 0;
    const tetherMin = isOriginSide
      ? min
      : offset -
        referenceRect[len] -
        popperRect[len] -
        offsetModifierValue +
        normalizedTetherOffsetValue.altAxis;
    const tetherMax = isOriginSide
      ? offset +
        referenceRect[len] +
        popperRect[len] -
        offsetModifierValue -
        normalizedTetherOffsetValue.altAxis
      : max;

    const preventedOffset =
      tether && isOriginSide
        ? withinMaxClamp(tetherMin, offset, tetherMax)
        : within(tether ? tetherMin : min, offset, tether ? tetherMax : max);

    popperOffsets[altAxis] = preventedOffset;
    data[altAxis] = preventedOffset - offset;
  }

  state.modifiersData[name] = data;
}

// eslint-disable-next-line import/no-unused-modules
export type PreventOverflowModifier = Modifier<'preventOverflow', Options>;
export default ({
  name: 'preventOverflow',
  enabled: true,
  phase: 'main',
  fn: preventOverflow,
  requiresIfExists: ['offset'],
}: PreventOverflowModifier);