@fluentui/react-positioning 0.0.0-nightly-20220714-0418.1 → 0.0.0-nightly-20221007-1237.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.
Files changed (121) hide show
  1. package/CHANGELOG.json +122 -10
  2. package/CHANGELOG.md +53 -8
  3. package/dist/index.d.ts +9 -16
  4. package/lib/constants.js +5 -0
  5. package/lib/constants.js.map +1 -0
  6. package/lib/createArrowStyles.js.map +1 -1
  7. package/lib/createVirtualElementFromClick.js.map +1 -1
  8. package/lib/index.js.map +1 -1
  9. package/lib/middleware/coverTarget.js +40 -0
  10. package/lib/middleware/coverTarget.js.map +1 -0
  11. package/lib/middleware/flip.js +19 -0
  12. package/lib/middleware/flip.js.map +1 -0
  13. package/lib/middleware/index.js +7 -0
  14. package/lib/middleware/index.js.map +1 -0
  15. package/lib/middleware/intersecting.js +21 -0
  16. package/lib/middleware/intersecting.js.map +1 -0
  17. package/lib/middleware/maxSize.js +43 -0
  18. package/lib/middleware/maxSize.js.map +1 -0
  19. package/lib/middleware/offset.js +11 -0
  20. package/lib/middleware/offset.js.map +1 -0
  21. package/lib/middleware/shift.js +30 -0
  22. package/lib/middleware/shift.js.map +1 -0
  23. package/lib/types.js.map +1 -1
  24. package/lib/usePositioning.js +234 -336
  25. package/lib/usePositioning.js.map +1 -1
  26. package/lib/usePositioningMouseTarget.js.map +1 -1
  27. package/lib/utils/debounce.js +22 -0
  28. package/lib/utils/debounce.js.map +1 -0
  29. package/lib/utils/fromFloatingUIPlacement.js +43 -0
  30. package/lib/utils/fromFloatingUIPlacement.js.map +1 -0
  31. package/lib/utils/getBoundary.js +5 -1
  32. package/lib/utils/getBoundary.js.map +1 -1
  33. package/lib/utils/getFloatingUIOffset.js +36 -0
  34. package/lib/utils/getFloatingUIOffset.js.map +1 -0
  35. package/lib/utils/getReactFiberFromNode.js.map +1 -1
  36. package/lib/utils/getScrollParent.js +6 -0
  37. package/lib/utils/getScrollParent.js.map +1 -1
  38. package/lib/utils/hasAutoFocusFilter.js +28 -0
  39. package/lib/utils/hasAutoFocusFilter.js.map +1 -0
  40. package/lib/utils/index.js +6 -2
  41. package/lib/utils/index.js.map +1 -1
  42. package/lib/utils/mergeArrowOffset.js +1 -1
  43. package/lib/utils/mergeArrowOffset.js.map +1 -1
  44. package/lib/utils/parseFloatingUIPlacement.js +14 -0
  45. package/lib/utils/parseFloatingUIPlacement.js.map +1 -0
  46. package/lib/utils/resolvePositioningShorthand.js.map +1 -1
  47. package/lib/utils/toFloatingUIPlacement.js +40 -0
  48. package/lib/utils/toFloatingUIPlacement.js.map +1 -0
  49. package/lib/utils/toggleScrollListener.js +24 -0
  50. package/lib/utils/toggleScrollListener.js.map +1 -0
  51. package/lib/utils/useCallbackRef.js.map +1 -1
  52. package/lib-commonjs/constants.js +11 -0
  53. package/lib-commonjs/constants.js.map +1 -0
  54. package/lib-commonjs/createArrowStyles.js.map +1 -1
  55. package/lib-commonjs/createVirtualElementFromClick.js.map +1 -1
  56. package/lib-commonjs/index.js.map +1 -1
  57. package/lib-commonjs/middleware/coverTarget.js +50 -0
  58. package/lib-commonjs/middleware/coverTarget.js.map +1 -0
  59. package/lib-commonjs/middleware/flip.js +30 -0
  60. package/lib-commonjs/middleware/flip.js.map +1 -0
  61. package/lib-commonjs/middleware/index.js +20 -0
  62. package/lib-commonjs/middleware/index.js.map +1 -0
  63. package/lib-commonjs/middleware/intersecting.js +31 -0
  64. package/lib-commonjs/middleware/intersecting.js.map +1 -0
  65. package/lib-commonjs/middleware/maxSize.js +54 -0
  66. package/lib-commonjs/middleware/maxSize.js.map +1 -0
  67. package/lib-commonjs/middleware/offset.js +22 -0
  68. package/lib-commonjs/middleware/offset.js.map +1 -0
  69. package/lib-commonjs/middleware/shift.js +41 -0
  70. package/lib-commonjs/middleware/shift.js.map +1 -0
  71. package/lib-commonjs/types.js.map +1 -1
  72. package/lib-commonjs/usePositioning.js +236 -337
  73. package/lib-commonjs/usePositioning.js.map +1 -1
  74. package/lib-commonjs/usePositioningMouseTarget.js.map +1 -1
  75. package/lib-commonjs/utils/debounce.js +31 -0
  76. package/lib-commonjs/utils/debounce.js.map +1 -0
  77. package/lib-commonjs/utils/fromFloatingUIPlacement.js +52 -0
  78. package/lib-commonjs/utils/fromFloatingUIPlacement.js.map +1 -0
  79. package/lib-commonjs/utils/getBoundary.js +5 -1
  80. package/lib-commonjs/utils/getBoundary.js.map +1 -1
  81. package/lib-commonjs/utils/getFloatingUIOffset.js +46 -0
  82. package/lib-commonjs/utils/getFloatingUIOffset.js.map +1 -0
  83. package/lib-commonjs/utils/getReactFiberFromNode.js.map +1 -1
  84. package/lib-commonjs/utils/getScrollParent.js +10 -1
  85. package/lib-commonjs/utils/getScrollParent.js.map +1 -1
  86. package/lib-commonjs/utils/hasAutoFocusFilter.js +37 -0
  87. package/lib-commonjs/utils/hasAutoFocusFilter.js.map +1 -0
  88. package/lib-commonjs/utils/index.js +10 -2
  89. package/lib-commonjs/utils/index.js.map +1 -1
  90. package/lib-commonjs/utils/mergeArrowOffset.js +1 -1
  91. package/lib-commonjs/utils/mergeArrowOffset.js.map +1 -1
  92. package/lib-commonjs/utils/parseFloatingUIPlacement.js +23 -0
  93. package/lib-commonjs/utils/parseFloatingUIPlacement.js.map +1 -0
  94. package/lib-commonjs/utils/resolvePositioningShorthand.js.map +1 -1
  95. package/lib-commonjs/utils/toFloatingUIPlacement.js +49 -0
  96. package/lib-commonjs/utils/toFloatingUIPlacement.js.map +1 -0
  97. package/lib-commonjs/utils/toggleScrollListener.js +34 -0
  98. package/lib-commonjs/utils/toggleScrollListener.js.map +1 -0
  99. package/lib-commonjs/utils/useCallbackRef.js.map +1 -1
  100. package/package.json +8 -12
  101. package/dist/tsdoc-metadata.json +0 -11
  102. package/lib/isIntersectingModifier.js +0 -26
  103. package/lib/isIntersectingModifier.js.map +0 -1
  104. package/lib/utils/fromPopperPlacement.js +0 -40
  105. package/lib/utils/fromPopperPlacement.js.map +0 -1
  106. package/lib/utils/getPopperOffset.js +0 -44
  107. package/lib/utils/getPopperOffset.js.map +0 -1
  108. package/lib/utils/parsePopperPlacement.js +0 -14
  109. package/lib/utils/parsePopperPlacement.js.map +0 -1
  110. package/lib/utils/positioningHelper.js +0 -49
  111. package/lib/utils/positioningHelper.js.map +0 -1
  112. package/lib-commonjs/isIntersectingModifier.js +0 -34
  113. package/lib-commonjs/isIntersectingModifier.js.map +0 -1
  114. package/lib-commonjs/utils/fromPopperPlacement.js +0 -50
  115. package/lib-commonjs/utils/fromPopperPlacement.js.map +0 -1
  116. package/lib-commonjs/utils/getPopperOffset.js +0 -54
  117. package/lib-commonjs/utils/getPopperOffset.js.map +0 -1
  118. package/lib-commonjs/utils/parsePopperPlacement.js +0 -23
  119. package/lib-commonjs/utils/parsePopperPlacement.js.map +0 -1
  120. package/lib-commonjs/utils/positioningHelper.js +0 -61
  121. package/lib-commonjs/utils/positioningHelper.js.map +0 -1
@@ -1,337 +1,90 @@
1
- import { useEventCallback, useIsomorphicLayoutEffect, useFirstMount, canUseDOM } from '@fluentui/react-utilities';
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 * as PopperJs from '@popperjs/core';
3
+ import { canUseDOM, useIsomorphicLayoutEffect } from '@fluentui/react-utilities';
4
+ import { useEventCallback } from '@fluentui/react-utilities';
4
5
  import * as React from 'react';
5
- import { isIntersectingModifier } from './isIntersectingModifier';
6
- import { getScrollParent, applyRtlToOffset, getPlacement, getReactFiberFromNode, getBoundary, useCallbackRef, parsePopperPlacement } from './utils/index';
7
- import { getPopperOffset } from './utils/getPopperOffset'; //
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
- * Detects if a passed HTML node has "autoFocus" prop on a React's fiber. Is needed as React handles autofocus behavior
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
- align,
44
- arrowPadding,
45
- autoSize,
46
- coverTarget,
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 isRtl = useFluent().dir === 'rtl';
57
- const placement = getPlacement(align, position, isRtl);
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 scrollParentElement = getScrollParent(container);
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
- return () => undefined;
92
- },
93
- requires: []
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 popperOptions = {
243
- modifiers,
30
+ const {
244
31
  placement,
32
+ middleware,
245
33
  strategy
246
- };
247
- return popperOptions;
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
- export function usePositioning(options = {}) {
263
- const {
264
- enabled = true
265
- } = options;
266
- const isFirstMount = useFirstMount();
267
- const popperOriginalPositionRef = React.useRef('absolute');
268
- const resolvePopperOptions = usePopperOptions(options, popperOriginalPositionRef);
269
- const popperInstanceRef = React.useRef(null);
270
- const handlePopperUpdate = useEventCallback(() => {
271
- var _a, _b;
272
-
273
- (_a = popperInstanceRef.current) === null || _a === void 0 ? void 0 : _a.destroy();
274
- popperInstanceRef.current = null;
275
- const target = (_b = overrideTargetRef.current) !== null && _b !== void 0 ? _b : targetRef.current;
276
- let popperInstance = null;
277
-
278
- if (canUseDOM() && enabled) {
279
- if (target && containerRef.current) {
280
- popperInstance = PopperJs.createPopper(target, containerRef.current, resolvePopperOptions(target, containerRef.current, arrowRef.current));
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
- if (popperInstance) {
285
- /**
286
- * The patch updates `.forceUpdate()` Popper function which restores the original position before the first
287
- * forceUpdate() call. See also "positionStyleFix" modifier in usePopperOptions().
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
- // targetRef - Stable between renders
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
- handlePopperUpdate();
358
- return () => {
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
- var _a, _b;
367
-
368
- if (!isFirstMount) {
369
- (_a = popperInstanceRef.current) === null || _a === void 0 ? void 0 : _a.setOptions(resolvePopperOptions((_b = overrideTargetRef.current) !== null && _b !== void 0 ? _b : targetRef.current, containerRef.current, arrowRef.current));
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
- }, // Missing deps:
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 === null || treeWalker === void 0 ? void 0 : treeWalker.nextNode()) {
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