Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit c81f259

Browse filesBrowse files
authored
feat(flip): "alignment" string value for crossAxis option (#3308)
1 parent 4f41867 commit c81f259
Copy full SHA for c81f259

File tree

Expand file treeCollapse file tree

8 files changed

+105
-28
lines changed
Filter options
Expand file treeCollapse file tree

8 files changed

+105
-28
lines changed

‎.changeset/eight-mugs-teach.md

Copy file name to clipboard
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@floating-ui/core": patch
3+
---
4+
5+
feat(flip): add `"alignment"` string value for `crossAxis` option. This value determines if cross axis overflow checking is restricted to the `alignment` of the placement only. This prevents `fallbackPlacements`/`fallbackAxisSideDirection` from too eagerly changing to the perpendicular side (thereby preferring `shift()` if overflow is detected along the cross axis, even if `shift()` is placed after `flip()` in the middleware array).

‎.github/workflows/update-snapshots.yml

Copy file name to clipboardExpand all lines: .github/workflows/update-snapshots.yml
+16-8Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
11
name: Update Visual Snapshots
22
on: [workflow_dispatch]
3-
env:
4-
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 # Skip downloading during yarn install
5-
PLAYWRIGHT_BROWSERS_PATH: 0 # Places binaries to node_modules/@playwright/test
63

74
jobs:
85
functional-tests:
9-
name: Functional
106
runs-on: ubuntu-latest
117
steps:
128
- uses: actions/checkout@v4
13-
- uses: ./.github/actions/setup
14-
- run: npx playwright install --with-deps chromium
15-
- run: pnpm run playwright:update
16-
- uses: EndBug/add-and-commit@v9
9+
10+
- name: Setup & install dependencies
11+
uses: ./.github/actions/setup
12+
env:
13+
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
14+
15+
- name: Install Playwright browsers
16+
run: pnpm exec playwright install --with-deps chromium
17+
env:
18+
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 0
19+
20+
- name: Update visual snapshots
21+
run: pnpm run playwright:update
22+
23+
- name: Commit updated snapshots
24+
uses: EndBug/add-and-commit@v9
1725
with:
1826
add: '.'
1927
author_name: 'GitHub Actions'

‎packages/core/src/middleware/flip.ts

Copy file name to clipboardExpand all lines: packages/core/src/middleware/flip.ts
+22-11Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@ export interface FlipOptions extends DetectOverflowOptions {
2323
/**
2424
* The axis that runs along the alignment of the floating element. Determines
2525
* whether overflow along this axis is checked to perform a flip.
26+
* - `true`: Whether to check cross axis overflow for both side and alignment flipping.
27+
* - `false`: Whether to disable all cross axis overflow checking.
28+
* - `'alignment'`: Whether to check cross axis overflow for alignment flipping only.
2629
* @default true
2730
*/
28-
crossAxis?: boolean;
31+
crossAxis?: boolean | 'alignment';
2932
/**
3033
* Placements to try sequentially if the preferred `placement` does not fit.
3134
* @default [oppositePlacement] (computed)
@@ -137,16 +140,24 @@ export const flip = (
137140
const nextPlacement = placements[nextIndex];
138141

139142
if (nextPlacement) {
140-
// Try next placement and re-run the lifecycle.
141-
return {
142-
data: {
143-
index: nextIndex,
144-
overflows: overflowsData,
145-
},
146-
reset: {
147-
placement: nextPlacement,
148-
},
149-
};
143+
const ignoreCrossAxisOverflow =
144+
checkCrossAxis === 'alignment'
145+
? initialSideAxis !== getSideAxis(nextPlacement)
146+
: false;
147+
const hasInitialMainAxisOverflow = overflowsData[0]?.overflows[0] > 0;
148+
149+
if (!ignoreCrossAxisOverflow || hasInitialMainAxisOverflow) {
150+
// Try next placement and re-run the lifecycle.
151+
return {
152+
data: {
153+
index: nextIndex,
154+
overflows: overflowsData,
155+
},
156+
reset: {
157+
placement: nextPlacement,
158+
},
159+
};
160+
}
150161
}
151162

152163
// First, find the candidates that fit on the mainAxis side of overflow,

‎packages/dom/test/functional/flip.test.ts

Copy file name to clipboardExpand all lines: packages/dom/test/functional/flip.test.ts
+28Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,3 +171,31 @@ test('falls back to only checking mainAxis overflow first', async ({page}) => {
171171
`fallback-shift-main-axis.png`,
172172
);
173173
});
174+
175+
test('when crossAxis: "alignment" and fallbackAxisSideDirection: "end", does not flip to the perpendicular side if preferred side has no mainAxis overflow', async ({
176+
page,
177+
}) => {
178+
await page.goto('http://localhost:1234/flip');
179+
await click(page, `[data-testid="placement-left"]`);
180+
await click(page, `[data-testid="crossAxis-alignment"]`);
181+
await click(page, `[data-testid="shift-true"]`);
182+
await click(page, `[data-testid="fallbackAxisSideDirection-end"]`);
183+
184+
await scroll(page, {x: 400, y: 600});
185+
186+
expect(await page.locator('.container').screenshot()).toMatchSnapshot(
187+
`cross-axis-alignment-fallback-axis-side-left.png`,
188+
);
189+
190+
await scroll(page, {x: 400, y: 350});
191+
192+
expect(await page.locator('.container').screenshot()).toMatchSnapshot(
193+
`cross-axis-alignment-fallback-axis-side-remains-left.png`,
194+
);
195+
196+
await scroll(page, {x: 480, y: 600});
197+
198+
expect(await page.locator('.container').screenshot()).toMatchSnapshot(
199+
`cross-axis-alignment-fallback-axis-side-chooses-bottom.png`,
200+
);
201+
});

‎packages/dom/test/visual/spec/Flip.tsx

Copy file name to clipboardExpand all lines: packages/dom/test/visual/spec/Flip.tsx
+34-9Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,29 @@ const FALLBACK_STRATEGIES: Array<FlipOptions['fallbackStrategy']> = [
1616
export function Flip() {
1717
const [placement, setPlacement] = useState<Placement>('bottom');
1818
const [mainAxis, setMainAxis] = useState(true);
19-
const [crossAxis, setCrossAxis] = useState(true);
19+
const [crossAxis, setCrossAxis] = useState<FlipOptions['crossAxis']>(true);
2020
const [fallbackPlacements, setFallbackPlacements] = useState<Placement[]>();
2121
const [fallbackStrategy, setFallbackStrategy] =
2222
useState<FlipOptions['fallbackStrategy']>('bestFit');
2323
const [flipAlignment, setFlipAlignment] = useState(true);
2424
const [addShift, setAddShift] = useState(false);
25+
const [fallbackAxisSideDirection, setFallbackAxisSideDirection] =
26+
useState<FlipOptions['fallbackAxisSideDirection']>('none');
27+
2528
const {x, y, strategy, update, refs} = useFloating({
2629
placement,
2730
whileElementsMounted: autoUpdate,
2831
middleware: [
2932
flip({
3033
mainAxis,
3134
crossAxis,
32-
fallbackPlacements: addShift ? ['bottom'] : fallbackPlacements,
35+
fallbackPlacements:
36+
addShift && fallbackAxisSideDirection === 'none'
37+
? ['bottom']
38+
: fallbackPlacements,
3339
fallbackStrategy,
3440
flipAlignment,
41+
fallbackAxisSideDirection: 'end',
3542
}),
3643
addShift && shift(),
3744
],
@@ -62,7 +69,8 @@ export function Flip() {
6269
top: y ?? 0,
6370
left: x ?? 0,
6471
...(addShift && {
65-
width: 400,
72+
width: fallbackAxisSideDirection === 'none' ? 400 : 200,
73+
height: fallbackAxisSideDirection === 'none' ? undefined : 50,
6674
}),
6775
}}
6876
>
@@ -103,14 +111,14 @@ export function Flip() {
103111

104112
<h2>crossAxis</h2>
105113
<Controls>
106-
{BOOLS.map((bool) => (
114+
{([...BOOLS, 'alignment'] as const).map((value) => (
107115
<button
108-
key={String(bool)}
109-
data-testid={`crossAxis-${bool}`}
110-
onClick={() => setCrossAxis(bool)}
111-
style={{backgroundColor: crossAxis === bool ? 'black' : ''}}
116+
key={String(value)}
117+
data-testid={`crossAxis-${value}`}
118+
onClick={() => setCrossAxis(value)}
119+
style={{backgroundColor: crossAxis === value ? 'black' : ''}}
112120
>
113-
{String(bool)}
121+
{String(value)}
114122
</button>
115123
))}
116124
</Controls>
@@ -203,6 +211,23 @@ export function Flip() {
203211
</button>
204212
))}
205213
</Controls>
214+
215+
<h2>fallbackAxisSideDirection</h2>
216+
<Controls>
217+
{(['start', 'end', 'none'] as const).map((value) => (
218+
<button
219+
key={value}
220+
data-testid={`fallbackAxisSideDirection-${value}`}
221+
onClick={() => setFallbackAxisSideDirection(value)}
222+
style={{
223+
backgroundColor:
224+
value === fallbackAxisSideDirection ? 'black' : '',
225+
}}
226+
>
227+
{String(value)}
228+
</button>
229+
))}
230+
</Controls>
206231
</>
207232
);
208233
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.