Skip to content

Commit 7667713

Browse files
authored
refactor: improve callback signatures (floating-ui#1674)
1 parent 54bbb8f commit 7667713

File tree

7 files changed

+98
-68
lines changed

7 files changed

+98
-68
lines changed

packages/core/src/middleware/offset.ts

+12-21
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type {Placement, Rect, Coords, Middleware, ElementRects} from '../types';
1+
import type {Coords, Middleware, MiddlewareArguments} from '../types';
22
import {getAlignment} from '../utils/getAlignment';
33
import {getSide} from '../utils/getSide';
44
import {getMainAxisFromPlacement} from '../utils/getMainAxisFromPlacement';
@@ -25,28 +25,25 @@ type OffsetValue =
2525
*/
2626
alignmentAxis?: number | null;
2727
};
28-
type OffsetFunction = (args: {
29-
floating: Rect;
30-
reference: Rect;
31-
placement: Placement;
32-
}) => OffsetValue;
28+
type OffsetFunction = (args: MiddlewareArguments) => OffsetValue;
3329

3430
export type Options = OffsetValue | OffsetFunction;
3531

36-
export function convertValueToCoords(
37-
placement: Placement,
38-
rects: ElementRects,
39-
value: Options,
40-
rtl = false
41-
): Coords {
32+
export async function convertValueToCoords(
33+
middlewareArguments: MiddlewareArguments,
34+
value: Options
35+
): Promise<Coords> {
36+
const {placement, platform, elements} = middlewareArguments;
37+
const rtl = await platform.isRTL?.(elements.floating);
38+
4239
const side = getSide(placement);
4340
const alignment = getAlignment(placement);
4441
const isVertical = getMainAxisFromPlacement(placement) === 'x';
4542
const mainAxisMulti = ['left', 'top'].includes(side) ? -1 : 1;
4643
const crossAxisMulti = rtl && isVertical ? -1 : 1;
4744

4845
const rawValue =
49-
typeof value === 'function' ? value({...rects, placement}) : value;
46+
typeof value === 'function' ? value(middlewareArguments) : value;
5047

5148
// eslint-disable-next-line prefer-const
5249
let {mainAxis, crossAxis, alignmentAxis} =
@@ -71,14 +68,8 @@ export const offset = (value: Options = 0): Middleware => ({
7168
name: 'offset',
7269
options: value,
7370
async fn(middlewareArguments) {
74-
const {x, y, placement, rects, platform, elements} = middlewareArguments;
75-
76-
const diffCoords = convertValueToCoords(
77-
placement,
78-
rects,
79-
value,
80-
await platform.isRTL?.(elements.floating)
81-
);
71+
const {x, y} = middlewareArguments;
72+
const diffCoords = await convertValueToCoords(middlewareArguments, value);
8273

8374
return {
8475
x: x + diffCoords.x,

packages/core/src/middleware/size.ts

+13-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type {Dimensions, ElementRects, Middleware} from '../types';
1+
import type {Middleware, MiddlewareArguments} from '../types';
22
import {
33
detectOverflow,
44
Options as DetectOverflowOptions,
@@ -13,7 +13,12 @@ export interface Options {
1313
* to change its size.
1414
* @default undefined
1515
*/
16-
apply(args: Dimensions & ElementRects): void;
16+
apply(
17+
args: MiddlewareArguments & {
18+
availableWidth: number;
19+
availableHeight: number;
20+
}
21+
): void;
1722
}
1823

1924
/**
@@ -59,15 +64,15 @@ export const size = (
5964
const yMax = max(overflow.bottom, 0);
6065

6166
const dimensions = {
62-
height:
67+
availableHeight:
6368
rects.floating.height -
6469
(['left', 'right'].includes(placement)
6570
? 2 *
6671
(yMin !== 0 || yMax !== 0
6772
? yMin + yMax
6873
: max(overflow.top, overflow.bottom))
6974
: overflow[heightSide]),
70-
width:
75+
availableWidth:
7176
rects.floating.width -
7277
(['top', 'bottom'].includes(placement)
7378
? 2 *
@@ -79,7 +84,10 @@ export const size = (
7984

8085
const prevDimensions = await platform.getDimensions(elements.floating);
8186

82-
apply?.({...dimensions, ...rects});
87+
apply?.({
88+
...middlewareArguments,
89+
...dimensions,
90+
});
8391

8492
const nextDimensions = await platform.getDimensions(elements.floating);
8593

packages/dom/test/visual/spec/Offset.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ const VALUES: Array<{offset: Options; name: string}> = [
1111
{offset: -10, name: '-10'},
1212
{offset: {crossAxis: 10}, name: 'cA: 10'},
1313
{offset: {mainAxis: 5, crossAxis: -10}, name: 'mA: 5, cA: -10'},
14-
{offset: ({floating}) => -floating.height, name: '() => -f.height'},
14+
{offset: ({rects}) => -rects.floating.height, name: '() => -f.height'},
1515
{
16-
offset: ({floating}) => ({crossAxis: -floating.width / 2}),
16+
offset: ({rects}) => ({crossAxis: -rects.floating.width / 2}),
1717
name: '() => cA: -f.width/2',
1818
},
1919
{offset: {alignmentAxis: 5}, name: 'aA: 5'},

packages/dom/test/visual/spec/Size.tsx

+5-8
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
1-
import type {Dimensions, ElementRects, Placement} from '@floating-ui/core';
1+
import type {Placement} from '@floating-ui/core';
22
import {useFloating, size} from '@floating-ui/react-dom';
33
import {allPlacements} from '../utils/allPlacements';
44
import {useState, useLayoutEffect} from 'react';
55
import {Controls} from '../utils/Controls';
66
import {useScroll} from '../utils/useScroll';
7-
import {flushSync} from 'react-dom';
87

98
export function Size() {
109
const [rtl, setRtl] = useState(false);
11-
const [sizeData, setSizeData] = useState<ElementRects & Dimensions>();
1210
const [placement, setPlacement] = useState<Placement>('bottom');
1311
const {x, y, reference, floating, strategy, update, refs} = useFloating({
1412
placement,
1513
middleware: [
1614
size({
17-
apply: (data) => {
18-
flushSync(() => {
19-
setSizeData(data);
15+
apply({availableHeight, availableWidth, elements}) {
16+
Object.assign(elements.floating.style, {
17+
maxWidth: `${availableWidth}px`,
18+
maxHeight: `${availableHeight}px`,
2019
});
2120
},
2221
padding: 10,
@@ -50,8 +49,6 @@ export function Size() {
5049
position: strategy,
5150
top: y ?? '',
5251
left: x ?? '',
53-
maxHeight: sizeData?.height ?? '',
54-
maxWidth: sizeData?.width ?? '',
5552
width: 400,
5653
height: 300,
5754
}}

packages/react-dom/test/index.test.tsx

+16-11
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import {
1414
import {renderHook} from '@testing-library/react-hooks';
1515
import {render, waitFor, fireEvent} from '@testing-library/react';
1616
import {useRef, useState} from 'react';
17-
import type {Dimensions, ElementRects} from '@floating-ui/core';
1817

1918
test('`x` and `y` are initially `null`', async () => {
2019
const {result} = renderHook(() => useFloating());
@@ -25,9 +24,6 @@ test('`x` and `y` are initially `null`', async () => {
2524

2625
test('middleware is always fresh and does not cause an infinite loop', async () => {
2726
function InlineMiddleware() {
28-
const [sizeData, setSizeData] = useState<
29-
(ElementRects & Dimensions) | null
30-
>(null);
3127
const arrowRef = useRef(null);
3228
const {reference, floating} = useFloating({
3329
placement: 'right',
@@ -54,22 +50,25 @@ test('middleware is always fresh and does not cause an infinite loop', async ()
5450

5551
hide(),
5652

57-
size({apply: setSizeData}),
53+
size({
54+
apply({availableHeight, elements}) {
55+
Object.assign(elements.floating.style, {
56+
maxHeight: `${availableHeight}px`,
57+
});
58+
},
59+
}),
5860
],
5961
});
6062

6163
return (
6264
<>
6365
<div ref={reference} />
64-
<div ref={floating} style={{height: sizeData?.height ?? ''}} />
66+
<div ref={floating} />
6567
</>
6668
);
6769
}
6870

6971
function StateMiddleware() {
70-
const [sizeData, setSizeData] = useState<
71-
(ElementRects & Dimensions) | null
72-
>(null);
7372
const arrowRef = useRef(null);
7473
const [middleware, setMiddleware] = useState([
7574
offset(),
@@ -97,7 +96,13 @@ test('middleware is always fresh and does not cause an infinite loop', async ()
9796

9897
hide(),
9998

100-
size({apply: setSizeData}),
99+
size({
100+
apply({availableHeight, elements}) {
101+
Object.assign(elements.floating.style, {
102+
maxHeight: `${availableHeight}px`,
103+
});
104+
},
105+
}),
101106
]);
102107
const {x, y, reference, floating} = useFloating({
103108
placement: 'right',
@@ -107,7 +112,7 @@ test('middleware is always fresh and does not cause an infinite loop', async ()
107112
return (
108113
<>
109114
<div ref={reference} />
110-
<div ref={floating} style={{height: sizeData?.height ?? ''}} />
115+
<div ref={floating} />
111116
<button
112117
data-testid="step1"
113118
onClick={() => setMiddleware([offset(10)])}

website/pages/docs/offset.mdx

+8-4
Original file line numberDiff line numberDiff line change
@@ -123,14 +123,18 @@ values — this enables you to read the dimensions of the reference
123123
or floating elements and the current `placement{:.objectKey}`.
124124

125125
```js
126-
offset(({reference, floating, placement}) =>
127-
placement === 'bottom' ? floating.width : reference.width
126+
offset(({rects, placement}) =>
127+
placement === 'bottom'
128+
? rects.floating.width
129+
: rects.reference.width
128130
);
129131

130-
offset(({reference, floating, placement}) => ({
132+
offset(({rects, placement}) => ({
131133
mainAxis: placement === 'top' ? 5 : -5,
132134
crossAxis:
133-
placement === 'right' ? reference.width : floating.width,
135+
placement === 'right'
136+
? rects.reference.width
137+
: rects.floating.width,
134138
}));
135139
```
136140

website/pages/docs/size.mdx

+42-17
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@ import {computePosition, size} from '@floating-ui/dom';
3636
computePosition(referenceEl, floatingEl, {
3737
middleware: [
3838
size({
39-
apply({width, height, reference, floating}) {
39+
apply({availableWidth, availableHeight, elements}) {
4040
// Do things with the data, e.g.
41-
Object.assign(floatingEl.style, {
42-
maxWidth: `${width}px`,
43-
maxHeight: `${height}px`,
41+
Object.assign(elements.floating.style, {
42+
maxWidth: `${availableWidth}px`,
43+
maxHeight: `${availableHeight}px`,
4444
});
4545
},
4646
}),
@@ -57,7 +57,12 @@ These are the options you can pass to `size(){:js}`.
5757

5858
```ts
5959
interface Options extends DetectOverflowOptions {
60-
apply?: (args: Dimensions & ElementRects) => void;
60+
apply?: (
61+
args: MiddlewareArguments & {
62+
availableWidth: number;
63+
availableHeight: number;
64+
}
65+
) => void;
6166
}
6267
```
6368

@@ -72,16 +77,36 @@ lifecycle:
7277

7378
```js
7479
size({
75-
apply({width, height}) {
80+
apply({
81+
availableWidth,
82+
availableHeight,
83+
...middlewareArguments
84+
}) {
7685
// Style mutations here
7786
},
7887
});
7988
```
8089

81-
This is because the `x` and `y` coordinates become incorrect
82-
after you've changed the size, and so changes to the dimensions
83-
of the floating element need to be performed before the
84-
coordinates get assigned to it.
90+
#### availableWidth
91+
92+
Represents how wide the floating element can be before it will
93+
overflow its clipping context. You'll generally set this as the
94+
`maxWidth{:.objectKey}` CSS property.
95+
96+
#### availableHeight
97+
98+
Represents how tall the floating element can be before it will
99+
overflow its clipping context. You'll generally set this as the
100+
`maxHeight{:.objectKey}` CSS property.
101+
102+
#### ...middlewareArguments
103+
104+
See [MiddlewareArguments](/docs/middleware#middlewarearguments).
105+
106+
Many useful properties are also accessible via this callback,
107+
such as `rects{:.objectKey}` and `elements{:.objectKey}`, the
108+
latter of which is useful in the context of React where you need
109+
access to the floating element and it does not exist in scope.
85110

86111
### ...detectOverflowOptions
87112

@@ -113,7 +138,7 @@ placement is used. In this scenario, place `size(){:js}`
113138
const middleware = [
114139
flip(),
115140
size({
116-
apply({width, height}) {
141+
apply({availableWidth, availableHeight}) {
117142
// ...
118143
},
119144
}),
@@ -155,11 +180,11 @@ and are setting a minimum acceptable size, place `size(){:js}`
155180
```js
156181
const middleware = [
157182
size({
158-
apply({width, height}) {
159-
Object.assign(floatingEl.style, {
183+
apply({availableHeight, elements}) {
184+
Object.assign(elements.floating.style, {
160185
// Minimum acceptable height is 50px.
161186
// `flip` will then take over.
162-
maxHeight: `${Math.max(50, height)}px`,
187+
maxHeight: `${Math.max(50, availableHeight)}px`,
163188
});
164189
},
165190
}),
@@ -176,11 +201,11 @@ the width of the reference regardless of its contents. You can
176201
also use `size(){:js}` for this, as the `Rect{:.class}`s get
177202
passed in:
178203

179-
```js
204+
```js /rects/
180205
size({
181-
apply({reference}) {
206+
apply({rects}) {
182207
Object.assign(floatingEl.style, {
183-
width: `${reference.width}px`,
208+
width: `${rects.reference.width}px`,
184209
});
185210
},
186211
});

0 commit comments

Comments
 (0)