@fluentui/react-positioning 9.1.2 → 9.2.0
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.json +56 -1
- package/CHANGELOG.md +18 -2
- package/dist/index.d.ts +9 -16
- package/lib/constants.js +5 -0
- package/lib/constants.js.map +1 -0
- package/lib/middleware/coverTarget.js +40 -0
- package/lib/middleware/coverTarget.js.map +1 -0
- package/lib/middleware/flip.js +19 -0
- package/lib/middleware/flip.js.map +1 -0
- package/lib/middleware/index.js +7 -0
- package/lib/middleware/index.js.map +1 -0
- package/lib/middleware/intersecting.js +21 -0
- package/lib/middleware/intersecting.js.map +1 -0
- package/lib/middleware/maxSize.js +43 -0
- package/lib/middleware/maxSize.js.map +1 -0
- package/lib/middleware/offset.js +11 -0
- package/lib/middleware/offset.js.map +1 -0
- package/lib/middleware/shift.js +30 -0
- package/lib/middleware/shift.js.map +1 -0
- package/lib/usePositioning.js +234 -336
- package/lib/usePositioning.js.map +1 -1
- package/lib/utils/debounce.js +22 -0
- package/lib/utils/debounce.js.map +1 -0
- package/lib/utils/fromFloatingUIPlacement.js +43 -0
- package/lib/utils/fromFloatingUIPlacement.js.map +1 -0
- package/lib/utils/getBoundary.js +5 -1
- package/lib/utils/getBoundary.js.map +1 -1
- package/lib/utils/getFloatingUIOffset.js +36 -0
- package/lib/utils/getFloatingUIOffset.js.map +1 -0
- package/lib/utils/getScrollParent.js +6 -0
- package/lib/utils/getScrollParent.js.map +1 -1
- package/lib/utils/hasAutoFocusFilter.js +28 -0
- package/lib/utils/hasAutoFocusFilter.js.map +1 -0
- package/lib/utils/index.js +6 -2
- package/lib/utils/index.js.map +1 -1
- package/lib/utils/mergeArrowOffset.js +1 -1
- package/lib/utils/mergeArrowOffset.js.map +1 -1
- package/lib/utils/parseFloatingUIPlacement.js +14 -0
- package/lib/utils/parseFloatingUIPlacement.js.map +1 -0
- package/lib/utils/toFloatingUIPlacement.js +40 -0
- package/lib/utils/toFloatingUIPlacement.js.map +1 -0
- package/lib/utils/toggleScrollListener.js +24 -0
- package/lib/utils/toggleScrollListener.js.map +1 -0
- package/lib-commonjs/constants.js +11 -0
- package/lib-commonjs/constants.js.map +1 -0
- package/lib-commonjs/middleware/coverTarget.js +50 -0
- package/lib-commonjs/middleware/coverTarget.js.map +1 -0
- package/lib-commonjs/middleware/flip.js +30 -0
- package/lib-commonjs/middleware/flip.js.map +1 -0
- package/lib-commonjs/middleware/index.js +20 -0
- package/lib-commonjs/middleware/index.js.map +1 -0
- package/lib-commonjs/middleware/intersecting.js +31 -0
- package/lib-commonjs/middleware/intersecting.js.map +1 -0
- package/lib-commonjs/middleware/maxSize.js +54 -0
- package/lib-commonjs/middleware/maxSize.js.map +1 -0
- package/lib-commonjs/middleware/offset.js +22 -0
- package/lib-commonjs/middleware/offset.js.map +1 -0
- package/lib-commonjs/middleware/shift.js +41 -0
- package/lib-commonjs/middleware/shift.js.map +1 -0
- package/lib-commonjs/usePositioning.js +236 -337
- package/lib-commonjs/usePositioning.js.map +1 -1
- package/lib-commonjs/utils/debounce.js +31 -0
- package/lib-commonjs/utils/debounce.js.map +1 -0
- package/lib-commonjs/utils/fromFloatingUIPlacement.js +52 -0
- package/lib-commonjs/utils/fromFloatingUIPlacement.js.map +1 -0
- package/lib-commonjs/utils/getBoundary.js +5 -1
- package/lib-commonjs/utils/getBoundary.js.map +1 -1
- package/lib-commonjs/utils/getFloatingUIOffset.js +46 -0
- package/lib-commonjs/utils/getFloatingUIOffset.js.map +1 -0
- package/lib-commonjs/utils/getScrollParent.js +10 -1
- package/lib-commonjs/utils/getScrollParent.js.map +1 -1
- package/lib-commonjs/utils/hasAutoFocusFilter.js +37 -0
- package/lib-commonjs/utils/hasAutoFocusFilter.js.map +1 -0
- package/lib-commonjs/utils/index.js +10 -2
- package/lib-commonjs/utils/index.js.map +1 -1
- package/lib-commonjs/utils/mergeArrowOffset.js +1 -1
- package/lib-commonjs/utils/mergeArrowOffset.js.map +1 -1
- package/lib-commonjs/utils/parseFloatingUIPlacement.js +23 -0
- package/lib-commonjs/utils/parseFloatingUIPlacement.js.map +1 -0
- package/lib-commonjs/utils/toFloatingUIPlacement.js +49 -0
- package/lib-commonjs/utils/toFloatingUIPlacement.js.map +1 -0
- package/lib-commonjs/utils/toggleScrollListener.js +34 -0
- package/lib-commonjs/utils/toggleScrollListener.js.map +1 -0
- package/package.json +7 -7
- package/dist/tsdoc-metadata.json +0 -11
- package/lib/isIntersectingModifier.js +0 -26
- package/lib/isIntersectingModifier.js.map +0 -1
- package/lib/utils/fromPopperPlacement.js +0 -40
- package/lib/utils/fromPopperPlacement.js.map +0 -1
- package/lib/utils/getPopperOffset.js +0 -44
- package/lib/utils/getPopperOffset.js.map +0 -1
- package/lib/utils/parsePopperPlacement.js +0 -14
- package/lib/utils/parsePopperPlacement.js.map +0 -1
- package/lib/utils/positioningHelper.js +0 -49
- package/lib/utils/positioningHelper.js.map +0 -1
- package/lib-commonjs/isIntersectingModifier.js +0 -34
- package/lib-commonjs/isIntersectingModifier.js.map +0 -1
- package/lib-commonjs/utils/fromPopperPlacement.js +0 -50
- package/lib-commonjs/utils/fromPopperPlacement.js.map +0 -1
- package/lib-commonjs/utils/getPopperOffset.js +0 -54
- package/lib-commonjs/utils/getPopperOffset.js.map +0 -1
- package/lib-commonjs/utils/parsePopperPlacement.js +0 -23
- package/lib-commonjs/utils/parsePopperPlacement.js.map +0 -1
- package/lib-commonjs/utils/positioningHelper.js +0 -61
- package/lib-commonjs/utils/positioningHelper.js.map +0 -1
package/lib/usePositioning.js
CHANGED
@@ -1,337 +1,90 @@
|
|
1
|
-
import {
|
1
|
+
import { computePosition, hide as hideMiddleware, arrow as arrowMiddleware } from '@floating-ui/dom';
|
2
2
|
import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts';
|
3
|
-
import
|
3
|
+
import { canUseDOM, useIsomorphicLayoutEffect } from '@fluentui/react-utilities';
|
4
|
+
import { useEventCallback } from '@fluentui/react-utilities';
|
4
5
|
import * as React from 'react';
|
5
|
-
import {
|
6
|
-
import {
|
7
|
-
import {
|
8
|
-
// Dev utils to detect if nodes have "autoFocus" props.
|
9
|
-
//
|
10
|
-
|
6
|
+
import { useCallbackRef, toFloatingUIPlacement, toggleScrollListener, hasAutofocusFilter, debounce, hasScrollParent } from './utils';
|
7
|
+
import { shift as shiftMiddleware, flip as flipMiddleware, coverTarget as coverTargetMiddleware, maxSize as maxSizeMiddleware, offset as offsetMiddleware, intersecting as intersectingMiddleware } from './middleware';
|
8
|
+
import { DATA_POSITIONING_ESCAPED, DATA_POSITIONING_INTERSECTING, DATA_POSITIONING_HIDDEN, DATA_POSITIONING_PLACEMENT } from './constants';
|
11
9
|
/**
|
12
|
-
*
|
13
|
-
* in React DOM and will not pass "autoFocus" to an actual HTML.
|
14
|
-
*/
|
15
|
-
|
16
|
-
function hasAutofocusProp(node) {
|
17
|
-
var _a; // https://github.com/facebook/react/blob/848bb2426e44606e0a55dfe44c7b3ece33772485/packages/react-dom/src/client/ReactDOMHostConfig.js#L157-L166
|
18
|
-
|
19
|
-
|
20
|
-
const isAutoFocusableElement = node.nodeName === 'BUTTON' || node.nodeName === 'INPUT' || node.nodeName === 'SELECT' || node.nodeName === 'TEXTAREA';
|
21
|
-
|
22
|
-
if (isAutoFocusableElement) {
|
23
|
-
return !!((_a = getReactFiberFromNode(node)) === null || _a === void 0 ? void 0 : _a.pendingProps.autoFocus);
|
24
|
-
}
|
25
|
-
|
26
|
-
return false;
|
27
|
-
}
|
28
|
-
|
29
|
-
function hasAutofocusFilter(node) {
|
30
|
-
return hasAutofocusProp(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
|
31
|
-
}
|
32
|
-
/**
|
33
|
-
* Provides a callback to resolve Popper options, it's stable and should be used as a dependency to trigger updates
|
34
|
-
* of Popper options.
|
35
|
-
*
|
36
|
-
* A callback is used there intentionally as some of Popper.js modifiers require DOM nodes (targer, container, arrow)
|
37
|
-
* that can't be resolved properly during an initial rendering.
|
10
|
+
* @internal
|
38
11
|
*/
|
39
12
|
|
40
|
-
|
41
|
-
function usePopperOptions(options, popperOriginalPositionRef) {
|
13
|
+
export function usePositioning(options) {
|
42
14
|
const {
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
flipBoundary,
|
48
|
-
offset,
|
49
|
-
overflowBoundary,
|
50
|
-
pinned,
|
51
|
-
position,
|
52
|
-
positionFixed,
|
53
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
54
|
-
unstable_disableTether
|
15
|
+
targetDocument
|
16
|
+
} = useFluent();
|
17
|
+
const {
|
18
|
+
enabled = true
|
55
19
|
} = options;
|
56
|
-
const
|
57
|
-
const
|
58
|
-
const strategy = positionFixed ? 'fixed' : 'absolute';
|
59
|
-
const offsetModifier = React.useMemo(() => offset ? {
|
60
|
-
name: 'offset',
|
61
|
-
options: {
|
62
|
-
offset: isRtl ? applyRtlToOffset(getPopperOffset(offset)) : getPopperOffset(offset)
|
63
|
-
}
|
64
|
-
} : null, [offset, isRtl]);
|
65
|
-
return React.useCallback((target, container, arrow) => {
|
20
|
+
const resolvePositioningOptions = usePositioningOptions(options);
|
21
|
+
const forceUpdate = useEventCallback(() => {
|
66
22
|
var _a;
|
67
23
|
|
68
|
-
const
|
69
|
-
const hasScrollableElement = scrollParentElement ? scrollParentElement !== ((_a = scrollParentElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.body) : false;
|
70
|
-
const modifiers = [isIntersectingModifier,
|
71
|
-
/**
|
72
|
-
* We are setting the position to `fixed` in the first effect to prevent scroll jumps in case of the content
|
73
|
-
* with managed focus. Modifier sets the position to `fixed` before all other modifier effects. Another part of
|
74
|
-
* a patch modifies ".forceUpdate()" directly after a Popper will be created.
|
75
|
-
*/
|
76
|
-
{
|
77
|
-
name: 'positionStyleFix',
|
78
|
-
enabled: true,
|
79
|
-
phase: 'afterWrite',
|
80
|
-
effect: ({
|
81
|
-
state,
|
82
|
-
instance
|
83
|
-
}) => {
|
84
|
-
// ".isFirstRun" is a part of our patch, on a first evaluation it will "undefined"
|
85
|
-
// should be disabled for subsequent runs as it breaks positioning for them
|
86
|
-
if (instance.isFirstRun !== false) {
|
87
|
-
popperOriginalPositionRef.current = state.elements.popper.style.position;
|
88
|
-
state.elements.popper.style.position = 'fixed';
|
89
|
-
}
|
24
|
+
const target = (_a = overrideTargetRef.current) !== null && _a !== void 0 ? _a : targetRef.current;
|
90
25
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
}, {
|
95
|
-
name: 'flip',
|
96
|
-
options: {
|
97
|
-
flipVariations: true
|
98
|
-
}
|
99
|
-
},
|
100
|
-
/**
|
101
|
-
* pinned disables the flip modifier by setting flip.enabled to false; this
|
102
|
-
* disables automatic repositioning of the popper box; it will always be placed according to
|
103
|
-
* the values of `align` and `position` props, regardless of the size of the component, the
|
104
|
-
* reference element or the viewport.
|
105
|
-
*/
|
106
|
-
pinned && {
|
107
|
-
name: 'flip',
|
108
|
-
enabled: false
|
109
|
-
},
|
110
|
-
/**
|
111
|
-
* When the popper box is placed in the context of a scrollable element, we need to set
|
112
|
-
* preventOverflow.escapeWithReference to true and flip.boundariesElement to 'scrollParent'
|
113
|
-
* (default is 'viewport') so that the popper box will stick with the targetRef when we
|
114
|
-
* scroll targetRef out of the viewport.
|
115
|
-
*/
|
116
|
-
hasScrollableElement && {
|
117
|
-
name: 'flip',
|
118
|
-
options: {
|
119
|
-
boundary: 'clippingParents'
|
120
|
-
}
|
121
|
-
}, hasScrollableElement && {
|
122
|
-
name: 'preventOverflow',
|
123
|
-
options: {
|
124
|
-
boundary: 'clippingParents'
|
125
|
-
}
|
126
|
-
}, offsetModifier,
|
127
|
-
/**
|
128
|
-
* This modifier is necessary to retain behaviour from popper v1 where untethered poppers are allowed by
|
129
|
-
* default. i.e. popper is still rendered fully in the viewport even if anchor element is no longer in the
|
130
|
-
* viewport.
|
131
|
-
*/
|
132
|
-
unstable_disableTether && {
|
133
|
-
name: 'preventOverflow',
|
134
|
-
options: {
|
135
|
-
altAxis: unstable_disableTether === 'all',
|
136
|
-
tether: false
|
137
|
-
}
|
138
|
-
}, flipBoundary && {
|
139
|
-
name: 'flip',
|
140
|
-
options: {
|
141
|
-
altBoundary: true,
|
142
|
-
boundary: getBoundary(container, flipBoundary)
|
143
|
-
}
|
144
|
-
}, overflowBoundary && {
|
145
|
-
name: 'preventOverflow',
|
146
|
-
options: {
|
147
|
-
altBoundary: true,
|
148
|
-
boundary: getBoundary(container, overflowBoundary)
|
149
|
-
}
|
150
|
-
}, {
|
151
|
-
// Similar code as popper-maxsize-modifier: https://github.com/atomiks/popper.js/blob/master/src/modifiers/maxSize.js
|
152
|
-
// popper-maxsize-modifier only calculates the max sizes.
|
153
|
-
// This modifier can apply max sizes always, or apply the max sizes only when overflow is detected
|
154
|
-
name: 'applyMaxSize',
|
155
|
-
enabled: !!autoSize,
|
156
|
-
phase: 'beforeWrite',
|
157
|
-
requiresIfExists: ['offset', 'preventOverflow', 'flip'],
|
158
|
-
options: {
|
159
|
-
altBoundary: true,
|
160
|
-
boundary: getBoundary(container, overflowBoundary)
|
161
|
-
},
|
162
|
-
|
163
|
-
fn({
|
164
|
-
state,
|
165
|
-
options: modifierOptions
|
166
|
-
}) {
|
167
|
-
const overflow = PopperJs.detectOverflow(state, modifierOptions);
|
168
|
-
const {
|
169
|
-
x,
|
170
|
-
y
|
171
|
-
} = state.modifiersData.preventOverflow || {
|
172
|
-
x: 0,
|
173
|
-
y: 0
|
174
|
-
};
|
175
|
-
const {
|
176
|
-
width,
|
177
|
-
height
|
178
|
-
} = state.rects.popper;
|
179
|
-
const basePlacement = parsePopperPlacement(state.placement).basePlacement;
|
180
|
-
const widthProp = basePlacement === 'left' ? 'left' : 'right';
|
181
|
-
const heightProp = basePlacement === 'top' ? 'top' : 'bottom';
|
182
|
-
const applyMaxWidth = autoSize === 'always' || autoSize === 'width-always' || overflow[widthProp] > 0 && (autoSize === true || autoSize === 'width');
|
183
|
-
const applyMaxHeight = autoSize === 'always' || autoSize === 'height-always' || overflow[heightProp] > 0 && (autoSize === true || autoSize === 'height');
|
184
|
-
|
185
|
-
if (applyMaxWidth) {
|
186
|
-
state.styles.popper.maxWidth = `${width - overflow[widthProp] - x}px`;
|
187
|
-
}
|
188
|
-
|
189
|
-
if (applyMaxHeight) {
|
190
|
-
state.styles.popper.maxHeight = `${height - overflow[heightProp] - y}px`;
|
191
|
-
}
|
192
|
-
}
|
193
|
-
|
194
|
-
},
|
195
|
-
/**
|
196
|
-
* This modifier is necessary in order to render the pointer. Refs are resolved in effects, so it can't be
|
197
|
-
* placed under computed modifiers. Deep merge is not required as this modifier has only these properties.
|
198
|
-
*/
|
199
|
-
{
|
200
|
-
name: 'arrow',
|
201
|
-
enabled: !!arrow,
|
202
|
-
options: {
|
203
|
-
element: arrow,
|
204
|
-
padding: arrowPadding
|
205
|
-
}
|
206
|
-
},
|
207
|
-
/**
|
208
|
-
* Modifies popper offsets to cover the reference rect, but still keep edge alignment
|
209
|
-
*/
|
210
|
-
{
|
211
|
-
name: 'coverTarget',
|
212
|
-
enabled: !!coverTarget,
|
213
|
-
phase: 'main',
|
214
|
-
requiresIfExists: ['offset', 'preventOverflow', 'flip'],
|
215
|
-
|
216
|
-
fn({
|
217
|
-
state
|
218
|
-
}) {
|
219
|
-
const basePlacement = parsePopperPlacement(state.placement).basePlacement;
|
220
|
-
|
221
|
-
switch (basePlacement) {
|
222
|
-
case 'bottom':
|
223
|
-
state.modifiersData.popperOffsets.y -= state.rects.reference.height;
|
224
|
-
break;
|
225
|
-
|
226
|
-
case 'top':
|
227
|
-
state.modifiersData.popperOffsets.y += state.rects.reference.height;
|
228
|
-
break;
|
229
|
-
|
230
|
-
case 'left':
|
231
|
-
state.modifiersData.popperOffsets.x += state.rects.reference.width;
|
232
|
-
break;
|
233
|
-
|
234
|
-
case 'right':
|
235
|
-
state.modifiersData.popperOffsets.x -= state.rects.reference.width;
|
236
|
-
break;
|
237
|
-
}
|
238
|
-
}
|
239
|
-
|
240
|
-
}].filter(Boolean); // filter boolean conditional spreading values
|
26
|
+
if (!canUseDOM || !enabled || !target || !containerRef.current) {
|
27
|
+
return;
|
28
|
+
}
|
241
29
|
|
242
|
-
const
|
243
|
-
modifiers,
|
30
|
+
const {
|
244
31
|
placement,
|
32
|
+
middleware,
|
245
33
|
strategy
|
246
|
-
};
|
247
|
-
|
248
|
-
}, [arrowPadding, autoSize, coverTarget, flipBoundary, offsetModifier, overflowBoundary, placement, strategy, unstable_disableTether, pinned, // These can be skipped from deps as they will not ever change
|
249
|
-
popperOriginalPositionRef]);
|
250
|
-
}
|
251
|
-
/**
|
252
|
-
* @internal
|
253
|
-
* Exposes Popper positioning API via React hook. Contains few important differences between an official "react-popper"
|
254
|
-
* package:
|
255
|
-
* - style attributes are applied directly on DOM to avoid re-renders
|
256
|
-
* - refs changes and resolution is handled properly without re-renders
|
257
|
-
* - contains a specific to React fix related to initial positioning when containers have components with managed focus
|
258
|
-
* to avoid focus jumps
|
259
|
-
*/
|
34
|
+
} = resolvePositioningOptions(target, containerRef.current, arrowRef.current); // Container is always initialized with `position: fixed` to avoid scroll jumps
|
35
|
+
// Before computing the positioned coordinates, revert the container to the deisred positioning strategy
|
260
36
|
|
37
|
+
Object.assign(containerRef.current.style, {
|
38
|
+
position: strategy
|
39
|
+
});
|
40
|
+
computePosition(target, containerRef.current, {
|
41
|
+
placement,
|
42
|
+
middleware,
|
43
|
+
strategy
|
44
|
+
}).then(({
|
45
|
+
x,
|
46
|
+
y,
|
47
|
+
middlewareData,
|
48
|
+
placement: computedPlacement
|
49
|
+
}) => {
|
50
|
+
var _a;
|
261
51
|
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
52
|
+
writeArrowUpdates({
|
53
|
+
arrow: arrowRef.current,
|
54
|
+
middlewareData
|
55
|
+
});
|
56
|
+
writeContainerUpdates({
|
57
|
+
container: containerRef.current,
|
58
|
+
middlewareData,
|
59
|
+
placement: computedPlacement,
|
60
|
+
coordinates: {
|
61
|
+
x,
|
62
|
+
y
|
63
|
+
},
|
64
|
+
lowPPI: (((_a = targetDocument === null || targetDocument === void 0 ? void 0 : targetDocument.defaultView) === null || _a === void 0 ? void 0 : _a.devicePixelRatio) || 1) <= 1,
|
65
|
+
strategy
|
66
|
+
});
|
67
|
+
}).catch(err => {
|
68
|
+
// https://github.com/floating-ui/floating-ui/issues/1845
|
69
|
+
// FIXME for node > 14
|
70
|
+
// node 15 introduces promise rejection which means that any components
|
71
|
+
// tests need to be `it('', async () => {})` otherwise there can be race conditions with
|
72
|
+
// JSDOM being torn down before this promise is resolved so globals like `window` and `document` don't exist
|
73
|
+
// Unless all tests that ever use `usePositioning` are turned into async tests, any logging during testing
|
74
|
+
// will actually be counter productive
|
75
|
+
if (process.env.NODE_ENV === 'development') {
|
76
|
+
// eslint-disable-next-line no-console
|
77
|
+
console.error('[usePositioning]: Failed to calculate position', err);
|
281
78
|
}
|
282
|
-
}
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
const originalForceUpdate = popperInstance.forceUpdate;
|
290
|
-
popperInstance.isFirstRun = true;
|
291
|
-
|
292
|
-
popperInstance.forceUpdate = () => {
|
293
|
-
if (popperInstance === null || popperInstance === void 0 ? void 0 : popperInstance.isFirstRun) {
|
294
|
-
popperInstance.state.elements.popper.style.position = popperOriginalPositionRef.current;
|
295
|
-
popperInstance.isFirstRun = false;
|
296
|
-
}
|
297
|
-
|
298
|
-
originalForceUpdate();
|
299
|
-
};
|
300
|
-
}
|
301
|
-
|
302
|
-
popperInstanceRef.current = popperInstance;
|
303
|
-
}); // Refs are managed by useCallbackRef() to handle ref updates scenarios.
|
304
|
-
//
|
305
|
-
// The first scenario are updates for a targetRef, we can handle it properly only via callback refs, but it causes
|
306
|
-
// re-renders (we would like to avoid them).
|
307
|
-
//
|
308
|
-
// The second problem is related to refs resolution on React's layer: refs are resolved in the same order as effects
|
309
|
-
// that causes an issue when you have a container inside a target, for example: a menu in ChatMessage.
|
310
|
-
//
|
311
|
-
// function ChatMessage(props) {
|
312
|
-
// <div className="message" ref={targetRef}> // 3) ref will be resolved only now, but it's too late already
|
313
|
-
// <Popper target={targetRef}> // 2) create a popper instance
|
314
|
-
// <div className="menu" /> // 1) capture ref from this element
|
315
|
-
// </Popper>
|
316
|
-
// </div>
|
317
|
-
// }
|
318
|
-
//
|
319
|
-
// Check a demo on CodeSandbox: https://codesandbox.io/s/popper-refs-emy60.
|
320
|
-
//
|
321
|
-
// This again can be solved with callback refs. It's not a huge issue as with hooks we are moving popper's creation
|
322
|
-
// to ChatMessage itself, however, without `useCallback()` refs it's still a Pandora box.
|
323
|
-
|
324
|
-
const targetRef = useCallbackRef(null, handlePopperUpdate, true);
|
325
|
-
const containerRef = useCallbackRef(null, handlePopperUpdate, true);
|
326
|
-
const arrowRef = useCallbackRef(null, handlePopperUpdate, true); // Stores external target from options.target or setTarget
|
327
|
-
|
328
|
-
const overrideTargetRef = useCallbackRef(null, handlePopperUpdate, true);
|
79
|
+
});
|
80
|
+
});
|
81
|
+
const updatePosition = React.useState(() => debounce(forceUpdate))[0];
|
82
|
+
const targetRef = useTargetRef(updatePosition);
|
83
|
+
const overrideTargetRef = useTargetRef(updatePosition);
|
84
|
+
const containerRef = useContainerRef(updatePosition, enabled);
|
85
|
+
const arrowRef = useArrowRef(updatePosition);
|
329
86
|
React.useImperativeHandle(options.positioningRef, () => ({
|
330
|
-
updatePosition
|
331
|
-
var _a;
|
332
|
-
|
333
|
-
(_a = popperInstanceRef.current) === null || _a === void 0 ? void 0 : _a.update();
|
334
|
-
},
|
87
|
+
updatePosition,
|
335
88
|
setTarget: target => {
|
336
89
|
if (options.target && process.env.NODE_ENV !== 'production') {
|
337
90
|
const err = new Error(); // eslint-disable-next-line no-console
|
@@ -345,34 +98,31 @@ export function usePositioning(options = {}) {
|
|
345
98
|
}
|
346
99
|
}), // Missing deps:
|
347
100
|
// options.target - only used for a runtime warning
|
348
|
-
//
|
101
|
+
// overrideTargetRef - Stable between renders
|
102
|
+
// updatePosition - Stable between renders
|
349
103
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
350
104
|
[]);
|
351
105
|
useIsomorphicLayoutEffect(() => {
|
352
106
|
if (options.target) {
|
353
107
|
overrideTargetRef.current = options.target;
|
354
108
|
}
|
355
|
-
}, [options.target, overrideTargetRef]);
|
109
|
+
}, [options.target, overrideTargetRef, containerRef]);
|
356
110
|
useIsomorphicLayoutEffect(() => {
|
357
|
-
|
358
|
-
|
359
|
-
var _a;
|
111
|
+
updatePosition();
|
112
|
+
}, [enabled, resolvePositioningOptions, updatePosition]); // Add window resize and scroll listeners to update position
|
360
113
|
|
361
|
-
(_a = popperInstanceRef.current) === null || _a === void 0 ? void 0 : _a.destroy();
|
362
|
-
popperInstanceRef.current = null;
|
363
|
-
};
|
364
|
-
}, [handlePopperUpdate, options.enabled]);
|
365
114
|
useIsomorphicLayoutEffect(() => {
|
366
|
-
|
367
|
-
|
368
|
-
if (
|
369
|
-
|
115
|
+
const win = targetDocument === null || targetDocument === void 0 ? void 0 : targetDocument.defaultView;
|
116
|
+
|
117
|
+
if (win) {
|
118
|
+
win.addEventListener('resize', updatePosition);
|
119
|
+
win.addEventListener('scroll', updatePosition);
|
120
|
+
return () => {
|
121
|
+
win.removeEventListener('resize', updatePosition);
|
122
|
+
win.removeEventListener('scroll', updatePosition);
|
123
|
+
};
|
370
124
|
}
|
371
|
-
},
|
372
|
-
// isFirstMount - Should never change after mount
|
373
|
-
// arrowRef, containerRef, targetRef - Stable between renders
|
374
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
375
|
-
[resolvePopperOptions]);
|
125
|
+
}, [updatePosition, targetDocument]);
|
376
126
|
|
377
127
|
if (process.env.NODE_ENV !== 'production') {
|
378
128
|
// This checked should run only in development mode
|
@@ -386,7 +136,7 @@ export function usePositioning(options = {}) {
|
|
386
136
|
acceptNode: hasAutofocusFilter
|
387
137
|
});
|
388
138
|
|
389
|
-
while (treeWalker
|
139
|
+
while (treeWalker.nextNode()) {
|
390
140
|
const node = treeWalker.currentNode; // eslint-disable-next-line no-console
|
391
141
|
|
392
142
|
console.warn('<Popper>:', node); // eslint-disable-next-line no-console
|
@@ -406,4 +156,152 @@ export function usePositioning(options = {}) {
|
|
406
156
|
arrowRef
|
407
157
|
};
|
408
158
|
}
|
159
|
+
|
160
|
+
function usePositioningOptions(options) {
|
161
|
+
const {
|
162
|
+
align,
|
163
|
+
arrowPadding,
|
164
|
+
autoSize,
|
165
|
+
coverTarget,
|
166
|
+
flipBoundary,
|
167
|
+
offset,
|
168
|
+
overflowBoundary,
|
169
|
+
pinned,
|
170
|
+
position,
|
171
|
+
unstable_disableTether: disableTether,
|
172
|
+
positionFixed
|
173
|
+
} = options;
|
174
|
+
const {
|
175
|
+
dir
|
176
|
+
} = useFluent();
|
177
|
+
const isRtl = dir === 'rtl';
|
178
|
+
const strategy = positionFixed ? 'fixed' : 'absolute';
|
179
|
+
return React.useCallback((target, container, arrow) => {
|
180
|
+
const hasScrollableElement = hasScrollParent(container);
|
181
|
+
const placement = toFloatingUIPlacement(align, position, isRtl);
|
182
|
+
const middleware = [offset && offsetMiddleware(offset), coverTarget && coverTargetMiddleware(), !pinned && flipMiddleware({
|
183
|
+
container,
|
184
|
+
flipBoundary,
|
185
|
+
hasScrollableElement
|
186
|
+
}), shiftMiddleware({
|
187
|
+
container,
|
188
|
+
hasScrollableElement,
|
189
|
+
overflowBoundary,
|
190
|
+
disableTether
|
191
|
+
}), autoSize && maxSizeMiddleware(autoSize), intersectingMiddleware(), arrow && arrowMiddleware({
|
192
|
+
element: arrow,
|
193
|
+
padding: arrowPadding
|
194
|
+
}), hideMiddleware({
|
195
|
+
strategy: 'referenceHidden'
|
196
|
+
}), hideMiddleware({
|
197
|
+
strategy: 'escaped'
|
198
|
+
})].filter(Boolean);
|
199
|
+
return {
|
200
|
+
placement,
|
201
|
+
middleware,
|
202
|
+
strategy
|
203
|
+
};
|
204
|
+
}, [align, arrowPadding, autoSize, coverTarget, disableTether, flipBoundary, isRtl, offset, overflowBoundary, pinned, position, strategy]);
|
205
|
+
}
|
206
|
+
|
207
|
+
function useContainerRef(updatePosition, enabled) {
|
208
|
+
return useCallbackRef(null, (container, prevContainer) => {
|
209
|
+
if (container && enabled) {
|
210
|
+
// When the container is first resolved, set position `fixed` to avoid scroll jumps.
|
211
|
+
// Without this scroll jumps can occur when the element is rendered initially and receives focus
|
212
|
+
Object.assign(container.style, {
|
213
|
+
position: 'fixed',
|
214
|
+
left: 0,
|
215
|
+
top: 0,
|
216
|
+
margin: 0
|
217
|
+
});
|
218
|
+
}
|
219
|
+
|
220
|
+
toggleScrollListener(container, prevContainer, updatePosition);
|
221
|
+
updatePosition();
|
222
|
+
});
|
223
|
+
}
|
224
|
+
|
225
|
+
function useTargetRef(updatePosition) {
|
226
|
+
return useCallbackRef(null, (target, prevTarget) => {
|
227
|
+
toggleScrollListener(target, prevTarget, updatePosition);
|
228
|
+
updatePosition();
|
229
|
+
});
|
230
|
+
}
|
231
|
+
|
232
|
+
function useArrowRef(updatePosition) {
|
233
|
+
return useCallbackRef(null, updatePosition);
|
234
|
+
}
|
235
|
+
/**
|
236
|
+
* Writes all DOM element updates after position is computed
|
237
|
+
*/
|
238
|
+
|
239
|
+
|
240
|
+
function writeContainerUpdates(options) {
|
241
|
+
var _a, _b;
|
242
|
+
|
243
|
+
const {
|
244
|
+
container,
|
245
|
+
placement,
|
246
|
+
middlewareData,
|
247
|
+
strategy,
|
248
|
+
lowPPI,
|
249
|
+
coordinates: {
|
250
|
+
x,
|
251
|
+
y
|
252
|
+
}
|
253
|
+
} = options;
|
254
|
+
|
255
|
+
if (!container) {
|
256
|
+
return;
|
257
|
+
}
|
258
|
+
|
259
|
+
container.setAttribute(DATA_POSITIONING_PLACEMENT, placement);
|
260
|
+
container.removeAttribute(DATA_POSITIONING_INTERSECTING);
|
261
|
+
|
262
|
+
if (middlewareData.intersectionObserver.intersecting) {
|
263
|
+
container.setAttribute(DATA_POSITIONING_INTERSECTING, '');
|
264
|
+
}
|
265
|
+
|
266
|
+
container.removeAttribute(DATA_POSITIONING_ESCAPED);
|
267
|
+
|
268
|
+
if ((_a = middlewareData.hide) === null || _a === void 0 ? void 0 : _a.escaped) {
|
269
|
+
container.setAttribute(DATA_POSITIONING_ESCAPED, '');
|
270
|
+
}
|
271
|
+
|
272
|
+
container.removeAttribute(DATA_POSITIONING_HIDDEN);
|
273
|
+
|
274
|
+
if ((_b = middlewareData.hide) === null || _b === void 0 ? void 0 : _b.referenceHidden) {
|
275
|
+
container.setAttribute(DATA_POSITIONING_HIDDEN, '');
|
276
|
+
}
|
277
|
+
|
278
|
+
Object.assign(container.style, {
|
279
|
+
transform: lowPPI ? `translate(${x}px, ${y}px)` : `translate3d(${x}px, ${y}px, 0)`,
|
280
|
+
position: strategy
|
281
|
+
});
|
282
|
+
}
|
283
|
+
/**
|
284
|
+
* Writes all DOM element updates after position is computed
|
285
|
+
*/
|
286
|
+
|
287
|
+
|
288
|
+
function writeArrowUpdates(options) {
|
289
|
+
const {
|
290
|
+
arrow,
|
291
|
+
middlewareData
|
292
|
+
} = options;
|
293
|
+
|
294
|
+
if (!middlewareData.arrow || !arrow) {
|
295
|
+
return;
|
296
|
+
}
|
297
|
+
|
298
|
+
const {
|
299
|
+
x: arrowX,
|
300
|
+
y: arrowY
|
301
|
+
} = middlewareData.arrow;
|
302
|
+
Object.assign(arrow.style, {
|
303
|
+
left: `${arrowX}px`,
|
304
|
+
top: `${arrowY}px`
|
305
|
+
});
|
306
|
+
}
|
409
307
|
//# sourceMappingURL=usePositioning.js.map
|