@fluentui/react-positioning 0.0.0-nightly-20220715-0418.1 → 0.0.0-nightly-20221007-1528.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +80 -10
- package/CHANGELOG.md +33 -8
- package/dist/index.d.ts +9 -16
- package/lib/constants.js +5 -0
- package/lib/constants.js.map +1 -0
- package/lib/createArrowStyles.js.map +1 -1
- package/lib/createVirtualElementFromClick.js.map +1 -1
- package/lib/index.js.map +1 -1
- 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/types.js.map +1 -1
- package/lib/usePositioning.js +234 -336
- package/lib/usePositioning.js.map +1 -1
- package/lib/usePositioningMouseTarget.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/getReactFiberFromNode.js.map +1 -1
- 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/resolvePositioningShorthand.js.map +1 -1
- 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/utils/useCallbackRef.js.map +1 -1
- package/lib-commonjs/constants.js +11 -0
- package/lib-commonjs/constants.js.map +1 -0
- package/lib-commonjs/createArrowStyles.js.map +1 -1
- package/lib-commonjs/createVirtualElementFromClick.js.map +1 -1
- package/lib-commonjs/index.js.map +1 -1
- 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/types.js.map +1 -1
- package/lib-commonjs/usePositioning.js +236 -337
- package/lib-commonjs/usePositioning.js.map +1 -1
- package/lib-commonjs/usePositioningMouseTarget.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/getReactFiberFromNode.js.map +1 -1
- 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/resolvePositioningShorthand.js.map +1 -1
- 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/lib-commonjs/utils/useCallbackRef.js.map +1 -1
- package/package.json +8 -12
- 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
|