Skip to content

fix(react-positioning): improve safe zone hit testing - #36708

Open
JM (Yakubko) wants to merge 3 commits into
microsoft:masterfrom
Yakubko:fix/react-positioning-safe-zone-area
Open

JM (Yakubko) wants to merge 3 commits into
microsoft:masterfrom
Yakubko:fix/react-positioning-safe-zone-area

Conversation

@Yakubko

@Yakubko JM (Yakubko) commented Sep 8, 2026

Copy link
Copy Markdown

Previous Behavior

useSafeZoneArea rendered four overlapping SVG triangles around the positioned container. Their geometry was stored in React state, causing the component to rerender repeatedly during pointer movement.

The generated safe zone could also cover more area than necessary because it included every container edge instead of only the edges facing the pointer.

useSafeZoneArea is used by the Fluent UI React Menu and can also be used with Popover to preserve the open state while the pointer travels from the trigger to the positioned surface.

New Behavior

  • Draws a single safe-zone polygon using only the container edges facing the pointer.
  • Updates SVG dimensions and paths imperatively, avoiding React rerenders during pointer movement.
  • Clips only the target element from the safe zone.
  • Keeps the SVG mounted to allow geometry updates without remounting.
  • Improves safe-zone behavior for consumers such as Menu and Popover.
  • Adds focused tests for all pointer directions.
  • Updates the debug story to make the behavior easier to inspect.

Browser Performance Comparison

Captured using the same scenario, browser version, recording duration, and pointer movement.

Before After
Screenshot 2026-09-07 at 13 55 11 Screenshot 2026-09-07 at 13 53 47
Metric Before After
React renders during pointer movement Every RAF 0
Scripting time 79ms 12ms
Rendering time 24ms 11ms

Testing

  • yarn nx run react-positioning:test
    • 24 test suites passed
    • 207 tests passed
    • 4 snapshots passed
  • yarn nx run react-positioning:e2e
    • 7 Cypress tests passed
  • react-positioning lint, type-check, and format checks passed.
  • react-positioning-stories lint, type-check, and format checks passed.

Change File

Includes a patch change file for @fluentui/react-positioning.

Reduce the safe zone to the container edges facing the pointer and update SVG geometry imperatively to avoid rerenders during pointer movement.

Add focused geometry coverage, update the debug story, and include a patch change file.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The implementation and tests are sound; only a non-blocking reversed metric in the PR description needs correction.

Pull request overview

Improves safe-zone hit testing while eliminating React rerenders during pointer movement.

Changes:

  • Replaces overlapping triangles with one directional polygon.
  • Updates SVG geometry imperatively and clips only the target.
  • Adds directional geometry tests and refreshes the debug story.

Merge confidence: 100/100. One non-blocking PR-description metric appears reversed.

File summaries
File Description
UseSafeZoneAreaDefault.stories.tsx Makes debug mode configurable.
SafeZoneArea.tsx Implements imperative polygon updates.
SafeZoneArea.styles.ts Renames polygon styles.
getSafeZonePoints.ts Computes facing container edges.
getSafeZonePoints.test.ts Covers eight pointer directions.
computeOutsideClipPath.ts Clips only the target rectangle.
computeOutsideClipPath.test.ts Updates clipping expectations.
SafeZoneArea.test.tsx.snap Updates rendered SVG snapshots.
Change file Records the positioning bug fix.
Review details
  • Files reviewed: 9/9 changed files
  • Comments generated: 1
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

📊 Bundle size report

Package & Exports Baseline (minified/GZIP) PR Change
react-charts
AreaChart
406.902 kB
127.183 kB
406.696 kB
127.075 kB
-206 B
-108 B
react-charts
DeclarativeChart
758.446 kB
222.689 kB
758.238 kB
222.542 kB
-208 B
-147 B
react-charts
DonutChart
317.733 kB
97.977 kB
317.527 kB
97.852 kB
-206 B
-125 B
react-charts
FunnelChart
309.133 kB
94.775 kB
308.927 kB
94.653 kB
-206 B
-122 B
react-charts
GanttChart
390.032 kB
121.663 kB
389.826 kB
121.555 kB
-206 B
-108 B
react-charts
GaugeChart
317.083 kB
97.402 kB
316.877 kB
97.283 kB
-206 B
-119 B
react-charts
GroupedVerticalBarChart
398.115 kB
124.454 kB
397.909 kB
124.34 kB
-206 B
-114 B
react-charts
HeatMapChart
392.172 kB
122.702 kB
391.966 kB
122.596 kB
-206 B
-106 B
react-charts
HorizontalBarChart
297.409 kB
90.473 kB
297.203 kB
90.374 kB
-206 B
-99 B
react-charts
Legends
236.596 kB
71.053 kB
236.39 kB
70.934 kB
-206 B
-119 B
react-charts
LineChart
418.251 kB
130.314 kB
418.045 kB
130.188 kB
-206 B
-126 B
react-charts
PolarChart
345.588 kB
108.005 kB
345.382 kB
107.889 kB
-206 B
-116 B
react-charts
ScatterChart
397.575 kB
124.369 kB
397.369 kB
124.244 kB
-206 B
-125 B
react-charts
VerticalBarChart
434.566 kB
129.339 kB
434.36 kB
129.238 kB
-206 B
-101 B
react-charts
VerticalStackedBarChart
404.13 kB
125.845 kB
403.924 kB
125.738 kB
-206 B
-107 B
react-components
react-components: all base hooks
217.893 kB
68.338 kB
217.687 kB
68.237 kB
-206 B
-101 B
react-components
react-components: Accordion, Button, FluentProvider, Image, Menu, Popover
227.136 kB
68.41 kB
226.93 kB
68.283 kB
-206 B
-127 B
react-components
react-components: entire library
1.283 MB
322.086 kB
1.283 MB
322.004 kB
-206 B
-82 B
react-menu
Menu (including children components)
160.788 kB
51.11 kB
160.582 kB
50.991 kB
-206 B
-119 B
react-menu
Menu (including selectable components)
163.954 kB
51.739 kB
163.748 kB
51.619 kB
-206 B
-120 B
react-positioning
useSafeZoneArea
13.398 kB
5.365 kB
13.198 kB
5.294 kB
-200 B
-71 B
Unchanged fixtures
Package & Exports Size (minified/GZIP)
react-avatar
Avatar
48.256 kB
15.276 kB
react-avatar
AvatarGroup
17.269 kB
6.89 kB
react-avatar
AvatarGroupItem
61.24 kB
19.139 kB
react-breadcrumb
@fluentui/react-breadcrumb - package
103.667 kB
29.079 kB
react-charts
HorizontalBarChartWithAxis
63 B
83 B
react-charts
SankeyChart
213.797 kB
68.722 kB
react-charts
Sparkline
80.888 kB
26.864 kB
react-checkbox
Checkbox
30.781 kB
10.914 kB
react-combobox
Combobox (including child components)
139.318 kB
45.067 kB
react-combobox
Dropdown (including child components)
139.083 kB
44.818 kB
react-components
react-components: Button, FluentProvider & webLightTheme
67.471 kB
19.465 kB
react-components
react-components: FluentProvider & webLightTheme
40.694 kB
13.549 kB
react-datepicker-compat
DatePicker Compat
217.447 kB
64.407 kB
react-dialog
Dialog (including children components)
90.984 kB
28.254 kB
react-field
Field
22.311 kB
8.312 kB
react-headless-components-preview
react-headless-components-preview: entire library
241.736 kB
68.14 kB
react-headless-components-preview
@fluentui/react-headless-components-preview/tag-picker
54.012 kB
17.756 kB
react-headless-components-preview
@fluentui/react-headless-components-preview/teaching-popover
36.073 kB
12.006 kB
react-input
Input
26.24 kB
8.643 kB
react-list
List
75.545 kB
23.408 kB
react-list
ListItem
99.06 kB
30.273 kB
react-overflow
hooks only
10.203 kB
3.694 kB
react-persona
Persona
55.209 kB
17.22 kB
react-popover
Popover
126.6 kB
40.873 kB
react-portal-compat
PortalCompatProvider
5.341 kB
2.146 kB
react-positioning
usePositioning
28.985 kB
10.198 kB
react-progress
ProgressBar
20.217 kB
7.836 kB
react-radio
Radio
28.166 kB
9.157 kB
react-radio
RadioGroup
13.931 kB
5.613 kB
react-select
Select
26.109 kB
9.403 kB
react-slider
Slider
33.405 kB
11.51 kB
react-spinbutton
SpinButton
33.661 kB
11.013 kB
react-swatch-picker
@fluentui/react-swatch-picker - package
93.186 kB
27.608 kB
react-switch
Switch
33.402 kB
10.566 kB
react-table
DataGrid
147.809 kB
43.977 kB
react-table
Table (Primitives only)
38.042 kB
12.69 kB
react-table
Table as DataGrid
119.68 kB
33.755 kB
react-table
Table (Selection only)
66.557 kB
18.957 kB
react-table
Table (Sort only)
65.22 kB
18.58 kB
react-tag-picker
@fluentui/react-tag-picker - package
174.763 kB
54.56 kB
react-tags
InteractionTag
13.629 kB
5.386 kB
react-tags
Tag
29.542 kB
9.338 kB
react-tags
TagGroup
70.806 kB
21.811 kB
react-teaching-popover
TeachingPopover
101.77 kB
32.204 kB
react-textarea
Textarea
24.606 kB
8.912 kB
react-timepicker-compat
TimePicker
142.037 kB
46.44 kB
react-tooltip
Tooltip
54.69 kB
19.443 kB
react-tree
FlatTree
136.693 kB
40.821 kB
react-tree
PersonaFlatTree
138.556 kB
41.361 kB
react-tree
PersonaTree
134.637 kB
40.143 kB
react-tree
Tree
132.78 kB
39.638 kB
🤖 This report was generated against a5b8341b24ed78137d2eeb4e7514b01071c6d471

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

Pull request demo site: URL

@@ -0,0 +1,7 @@
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🕵🏾‍♀️ visual changes to review in the Visual Change Report

vr-tests-react-components/Positioning 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Positioning.Positioning end.chromium.png 149 Changed
vr-tests-react-components/Positioning.Positioning end.updated 2 times.chromium.png 618 Changed
vr-tests-react-components/ProgressBar converged 3 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - High Contrast.default.chromium.png 159 Changed
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - Dark Mode.default.chromium.png 81 Changed
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness.default.chromium.png 27 Changed
vr-tests-react-components/TagPicker 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/TagPicker.disabled - Dark Mode.disabled input hover.chromium.png 658 Changed
vr-tests-react-components/TagPicker.disabled.disabled input hover.chromium.png 677 Changed

There were 2 duplicate changes discarded. Check the build logs for more information.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants