Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tooltip: Fix positioning by anchoring to child element #41268

Merged
merged 17 commits into from
Jul 19, 2022

Conversation

andrewserong
Copy link
Contributor

@andrewserong andrewserong commented May 24, 2022

What?

Alternative to #41176

This PR updates the ToolTip component to ensure that its position is correctly relative to the first child of the ToolTip.

Why?

Following on from #40740 where the Popover component was refactored to use the floatingUI library, it looks like the following issues were introduced:

  • Tooltips were no longer centred relative to their child component
  • Tooltips at the edge of the screen would overflow / get a horizontal scrollbar

It looks like it was also mentioned in this comment: #40740 (review)

How?

  • Add a ref to the ToolTip component that gets passed to both the ToolTip's child, and as an anchorRef, used as a reference for the Popover — this ensures that the Popover has the correct reference point for calculating the Tooltip position.
  • Set the min-width of the Tooltip to min-content instead of 0 to prevent horizontal scrollbars from appearing

Testing Instructions

  1. Test in Storybook and ensure that by default the ToolTip component is now positioned relative to its first child.
  2. In the post editor, hover over the options (vertical ellipsis) at the top right, and check that the Tooltip does not overflow / introduce a scrollbar.
  3. Hover over the Undo / Redo buttons and ensure the Tooltip is now positioned more closely to the expected position.
  4. Insert a block, and hover over the controls in the block toolbar: the tooltips should now be centered to the button.

Screenshots or screencast

Before After
image image
image
image image
image image

@andrewserong andrewserong added [Type] Bug An existing feature does not function as intended [Feature] UI Components Impacts or related to the UI component system [Package] Components /packages/components labels May 24, 2022
@andrewserong andrewserong self-assigned this May 24, 2022
@github-actions
Copy link

github-actions bot commented May 24, 2022

Size Change: +377 B (0%)

Total Size: 1.25 MB

Filename Size Change
build/block-editor/index.min.js 153 kB +184 B (0%)
build/block-editor/style-rtl.css 14.6 kB +27 B (0%)
build/block-editor/style.css 14.6 kB +27 B (0%)
build/block-library/index.min.js 183 kB +30 B (0%)
build/components/index.min.js 230 kB +60 B (0%)
build/components/style-rtl.css 14 kB +2 B (0%)
build/components/style.css 14 kB +3 B (0%)
build/edit-post/index.min.js 30.5 kB +7 B (0%)
build/edit-site/index.min.js 53.1 kB +31 B (0%)
build/format-library/index.min.js 6.75 kB +6 B (0%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 982 B
build/annotations/index.min.js 2.76 kB
build/api-fetch/index.min.js 2.26 kB
build/autop/index.min.js 2.14 kB
build/blob/index.min.js 475 B
build/block-directory/index.min.js 6.58 kB
build/block-directory/style-rtl.css 990 B
build/block-directory/style.css 991 B
build/block-editor/default-editor-styles-rtl.css 378 B
build/block-editor/default-editor-styles.css 378 B
build/block-library/blocks/archives/editor-rtl.css 61 B
build/block-library/blocks/archives/editor.css 60 B
build/block-library/blocks/archives/style-rtl.css 65 B
build/block-library/blocks/archives/style.css 65 B
build/block-library/blocks/audio/editor-rtl.css 150 B
build/block-library/blocks/audio/editor.css 150 B
build/block-library/blocks/audio/style-rtl.css 103 B
build/block-library/blocks/audio/style.css 103 B
build/block-library/blocks/audio/theme-rtl.css 110 B
build/block-library/blocks/audio/theme.css 110 B
build/block-library/blocks/avatar/editor-rtl.css 116 B
build/block-library/blocks/avatar/editor.css 116 B
build/block-library/blocks/avatar/style-rtl.css 59 B
build/block-library/blocks/avatar/style.css 59 B
build/block-library/blocks/block/editor-rtl.css 161 B
build/block-library/blocks/block/editor.css 161 B
build/block-library/blocks/button/editor-rtl.css 441 B
build/block-library/blocks/button/editor.css 441 B
build/block-library/blocks/button/style-rtl.css 543 B
build/block-library/blocks/button/style.css 543 B
build/block-library/blocks/buttons/editor-rtl.css 292 B
build/block-library/blocks/buttons/editor.css 292 B
build/block-library/blocks/buttons/style-rtl.css 275 B
build/block-library/blocks/buttons/style.css 275 B
build/block-library/blocks/calendar/style-rtl.css 207 B
build/block-library/blocks/calendar/style.css 207 B
build/block-library/blocks/categories/editor-rtl.css 84 B
build/block-library/blocks/categories/editor.css 83 B
build/block-library/blocks/categories/style-rtl.css 79 B
build/block-library/blocks/categories/style.css 79 B
build/block-library/blocks/code/style-rtl.css 103 B
build/block-library/blocks/code/style.css 103 B
build/block-library/blocks/code/theme-rtl.css 124 B
build/block-library/blocks/code/theme.css 124 B
build/block-library/blocks/columns/editor-rtl.css 108 B
build/block-library/blocks/columns/editor.css 108 B
build/block-library/blocks/columns/style-rtl.css 406 B
build/block-library/blocks/columns/style.css 406 B
build/block-library/blocks/comment-author-avatar/editor-rtl.css 125 B
build/block-library/blocks/comment-author-avatar/editor.css 125 B
build/block-library/blocks/comment-content/style-rtl.css 92 B
build/block-library/blocks/comment-content/style.css 92 B
build/block-library/blocks/comment-template/style-rtl.css 187 B
build/block-library/blocks/comment-template/style.css 185 B
build/block-library/blocks/comments-pagination-numbers/editor-rtl.css 123 B
build/block-library/blocks/comments-pagination-numbers/editor.css 121 B
build/block-library/blocks/comments-pagination/editor-rtl.css 222 B
build/block-library/blocks/comments-pagination/editor.css 209 B
build/block-library/blocks/comments-pagination/style-rtl.css 235 B
build/block-library/blocks/comments-pagination/style.css 231 B
build/block-library/blocks/comments-title/editor-rtl.css 75 B
build/block-library/blocks/comments-title/editor.css 75 B
build/block-library/blocks/comments/editor-rtl.css 95 B
build/block-library/blocks/comments/editor.css 95 B
build/block-library/blocks/cover/editor-rtl.css 615 B
build/block-library/blocks/cover/editor.css 616 B
build/block-library/blocks/cover/style-rtl.css 1.55 kB
build/block-library/blocks/cover/style.css 1.55 kB
build/block-library/blocks/embed/editor-rtl.css 293 B
build/block-library/blocks/embed/editor.css 293 B
build/block-library/blocks/embed/style-rtl.css 410 B
build/block-library/blocks/embed/style.css 410 B
build/block-library/blocks/embed/theme-rtl.css 110 B
build/block-library/blocks/embed/theme.css 110 B
build/block-library/blocks/file/editor-rtl.css 300 B
build/block-library/blocks/file/editor.css 300 B
build/block-library/blocks/file/style-rtl.css 253 B
build/block-library/blocks/file/style.css 254 B
build/block-library/blocks/file/view.min.js 346 B
build/block-library/blocks/freeform/editor-rtl.css 2.44 kB
build/block-library/blocks/freeform/editor.css 2.44 kB
build/block-library/blocks/gallery/editor-rtl.css 948 B
build/block-library/blocks/gallery/editor.css 950 B
build/block-library/blocks/gallery/style-rtl.css 1.5 kB
build/block-library/blocks/gallery/style.css 1.49 kB
build/block-library/blocks/gallery/theme-rtl.css 108 B
build/block-library/blocks/gallery/theme.css 108 B
build/block-library/blocks/group/editor-rtl.css 333 B
build/block-library/blocks/group/editor.css 333 B
build/block-library/blocks/group/style-rtl.css 57 B
build/block-library/blocks/group/style.css 57 B
build/block-library/blocks/group/theme-rtl.css 78 B
build/block-library/blocks/group/theme.css 78 B
build/block-library/blocks/heading/style-rtl.css 76 B
build/block-library/blocks/heading/style.css 76 B
build/block-library/blocks/html/editor-rtl.css 327 B
build/block-library/blocks/html/editor.css 329 B
build/block-library/blocks/image/editor-rtl.css 738 B
build/block-library/blocks/image/editor.css 737 B
build/block-library/blocks/image/style-rtl.css 524 B
build/block-library/blocks/image/style.css 530 B
build/block-library/blocks/image/theme-rtl.css 110 B
build/block-library/blocks/image/theme.css 110 B
build/block-library/blocks/latest-comments/style-rtl.css 284 B
build/block-library/blocks/latest-comments/style.css 284 B
build/block-library/blocks/latest-posts/editor-rtl.css 199 B
build/block-library/blocks/latest-posts/editor.css 198 B
build/block-library/blocks/latest-posts/style-rtl.css 463 B
build/block-library/blocks/latest-posts/style.css 462 B
build/block-library/blocks/list/style-rtl.css 88 B
build/block-library/blocks/list/style.css 88 B
build/block-library/blocks/media-text/editor-rtl.css 266 B
build/block-library/blocks/media-text/editor.css 263 B
build/block-library/blocks/media-text/style-rtl.css 493 B
build/block-library/blocks/media-text/style.css 490 B
build/block-library/blocks/more/editor-rtl.css 431 B
build/block-library/blocks/more/editor.css 431 B
build/block-library/blocks/navigation-link/editor-rtl.css 705 B
build/block-library/blocks/navigation-link/editor.css 703 B
build/block-library/blocks/navigation-link/style-rtl.css 115 B
build/block-library/blocks/navigation-link/style.css 115 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 296 B
build/block-library/blocks/navigation-submenu/editor.css 295 B
build/block-library/blocks/navigation-submenu/view.min.js 423 B
build/block-library/blocks/navigation/editor-rtl.css 2.03 kB
build/block-library/blocks/navigation/editor.css 2.04 kB
build/block-library/blocks/navigation/style-rtl.css 1.96 kB
build/block-library/blocks/navigation/style.css 1.95 kB
build/block-library/blocks/navigation/view-modal.min.js 2.78 kB
build/block-library/blocks/navigation/view.min.js 443 B
build/block-library/blocks/nextpage/editor-rtl.css 395 B
build/block-library/blocks/nextpage/editor.css 395 B
build/block-library/blocks/page-list/editor-rtl.css 363 B
build/block-library/blocks/page-list/editor.css 363 B
build/block-library/blocks/page-list/style-rtl.css 175 B
build/block-library/blocks/page-list/style.css 175 B
build/block-library/blocks/paragraph/editor-rtl.css 157 B
build/block-library/blocks/paragraph/editor.css 157 B
build/block-library/blocks/paragraph/style-rtl.css 260 B
build/block-library/blocks/paragraph/style.css 260 B
build/block-library/blocks/post-author/style-rtl.css 175 B
build/block-library/blocks/post-author/style.css 176 B
build/block-library/blocks/post-comments-form/editor-rtl.css 96 B
build/block-library/blocks/post-comments-form/editor.css 96 B
build/block-library/blocks/post-comments-form/style-rtl.css 495 B
build/block-library/blocks/post-comments-form/style.css 495 B
build/block-library/blocks/post-comments/editor-rtl.css 77 B
build/block-library/blocks/post-comments/editor.css 77 B
build/block-library/blocks/post-comments/style-rtl.css 632 B
build/block-library/blocks/post-comments/style.css 630 B
build/block-library/blocks/post-excerpt/editor-rtl.css 73 B
build/block-library/blocks/post-excerpt/editor.css 73 B
build/block-library/blocks/post-excerpt/style-rtl.css 69 B
build/block-library/blocks/post-excerpt/style.css 69 B
build/block-library/blocks/post-featured-image/editor-rtl.css 605 B
build/block-library/blocks/post-featured-image/editor.css 605 B
build/block-library/blocks/post-featured-image/style-rtl.css 153 B
build/block-library/blocks/post-featured-image/style.css 153 B
build/block-library/blocks/post-template/editor-rtl.css 99 B
build/block-library/blocks/post-template/editor.css 98 B
build/block-library/blocks/post-template/style-rtl.css 282 B
build/block-library/blocks/post-template/style.css 282 B
build/block-library/blocks/post-terms/style-rtl.css 73 B
build/block-library/blocks/post-terms/style.css 73 B
build/block-library/blocks/post-title/style-rtl.css 80 B
build/block-library/blocks/post-title/style.css 80 B
build/block-library/blocks/preformatted/style-rtl.css 103 B
build/block-library/blocks/preformatted/style.css 103 B
build/block-library/blocks/pullquote/editor-rtl.css 198 B
build/block-library/blocks/pullquote/editor.css 198 B
build/block-library/blocks/pullquote/style-rtl.css 370 B
build/block-library/blocks/pullquote/style.css 370 B
build/block-library/blocks/pullquote/theme-rtl.css 167 B
build/block-library/blocks/pullquote/theme.css 167 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/query-pagination-numbers/editor.css 121 B
build/block-library/blocks/query-pagination/editor-rtl.css 221 B
build/block-library/blocks/query-pagination/editor.css 211 B
build/block-library/blocks/query-pagination/style-rtl.css 234 B
build/block-library/blocks/query-pagination/style.css 231 B
build/block-library/blocks/query/editor-rtl.css 365 B
build/block-library/blocks/query/editor.css 364 B
build/block-library/blocks/quote/style-rtl.css 213 B
build/block-library/blocks/quote/style.css 213 B
build/block-library/blocks/quote/theme-rtl.css 223 B
build/block-library/blocks/quote/theme.css 226 B
build/block-library/blocks/read-more/style-rtl.css 132 B
build/block-library/blocks/read-more/style.css 132 B
build/block-library/blocks/rss/editor-rtl.css 202 B
build/block-library/blocks/rss/editor.css 204 B
build/block-library/blocks/rss/style-rtl.css 289 B
build/block-library/blocks/rss/style.css 288 B
build/block-library/blocks/search/editor-rtl.css 165 B
build/block-library/blocks/search/editor.css 165 B
build/block-library/blocks/search/style-rtl.css 385 B
build/block-library/blocks/search/style.css 386 B
build/block-library/blocks/search/theme-rtl.css 114 B
build/block-library/blocks/search/theme.css 114 B
build/block-library/blocks/separator/editor-rtl.css 146 B
build/block-library/blocks/separator/editor.css 146 B
build/block-library/blocks/separator/style-rtl.css 233 B
build/block-library/blocks/separator/style.css 233 B
build/block-library/blocks/separator/theme-rtl.css 194 B
build/block-library/blocks/separator/theme.css 194 B
build/block-library/blocks/shortcode/editor-rtl.css 464 B
build/block-library/blocks/shortcode/editor.css 464 B
build/block-library/blocks/site-logo/editor-rtl.css 708 B
build/block-library/blocks/site-logo/editor.css 708 B
build/block-library/blocks/site-logo/style-rtl.css 192 B
build/block-library/blocks/site-logo/style.css 192 B
build/block-library/blocks/site-tagline/editor-rtl.css 86 B
build/block-library/blocks/site-tagline/editor.css 86 B
build/block-library/blocks/site-title/editor-rtl.css 84 B
build/block-library/blocks/site-title/editor.css 84 B
build/block-library/blocks/social-link/editor-rtl.css 177 B
build/block-library/blocks/social-link/editor.css 177 B
build/block-library/blocks/social-links/editor-rtl.css 674 B
build/block-library/blocks/social-links/editor.css 673 B
build/block-library/blocks/social-links/style-rtl.css 1.37 kB
build/block-library/blocks/social-links/style.css 1.36 kB
build/block-library/blocks/spacer/editor-rtl.css 322 B
build/block-library/blocks/spacer/editor.css 322 B
build/block-library/blocks/spacer/style-rtl.css 48 B
build/block-library/blocks/spacer/style.css 48 B
build/block-library/blocks/table/editor-rtl.css 494 B
build/block-library/blocks/table/editor.css 494 B
build/block-library/blocks/table/style-rtl.css 611 B
build/block-library/blocks/table/style.css 609 B
build/block-library/blocks/table/theme-rtl.css 175 B
build/block-library/blocks/table/theme.css 175 B
build/block-library/blocks/tag-cloud/style-rtl.css 226 B
build/block-library/blocks/tag-cloud/style.css 227 B
build/block-library/blocks/template-part/editor-rtl.css 149 B
build/block-library/blocks/template-part/editor.css 149 B
build/block-library/blocks/template-part/theme-rtl.css 101 B
build/block-library/blocks/template-part/theme.css 101 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B
build/block-library/blocks/text-columns/editor.css 95 B
build/block-library/blocks/text-columns/style-rtl.css 166 B
build/block-library/blocks/text-columns/style.css 166 B
build/block-library/blocks/verse/style-rtl.css 87 B
build/block-library/blocks/verse/style.css 87 B
build/block-library/blocks/video/editor-rtl.css 561 B
build/block-library/blocks/video/editor.css 563 B
build/block-library/blocks/video/style-rtl.css 159 B
build/block-library/blocks/video/style.css 159 B
build/block-library/blocks/video/theme-rtl.css 110 B
build/block-library/blocks/video/theme.css 110 B
build/block-library/common-rtl.css 987 B
build/block-library/common.css 984 B
build/block-library/editor-rtl.css 10.2 kB
build/block-library/editor.css 10.2 kB
build/block-library/elements-rtl.css 54 B
build/block-library/elements.css 54 B
build/block-library/reset-rtl.css 478 B
build/block-library/reset.css 478 B
build/block-library/style-rtl.css 11.5 kB
build/block-library/style.css 11.5 kB
build/block-library/theme-rtl.css 695 B
build/block-library/theme.css 700 B
build/block-serialization-default-parser/index.min.js 1.11 kB
build/block-serialization-spec-parser/index.min.js 2.83 kB
build/blocks/index.min.js 47 kB
build/compose/index.min.js 11.7 kB
build/core-data/index.min.js 14.7 kB
build/customize-widgets/index.min.js 11.2 kB
build/customize-widgets/style-rtl.css 1.4 kB
build/customize-widgets/style.css 1.4 kB
build/data-controls/index.min.js 653 B
build/data/index.min.js 7.99 kB
build/date/index.min.js 32 kB
build/deprecated/index.min.js 507 B
build/dom-ready/index.min.js 324 B
build/dom/index.min.js 4.66 kB
build/edit-navigation/index.min.js 16 kB
build/edit-navigation/style-rtl.css 4.02 kB
build/edit-navigation/style.css 4.03 kB
build/edit-post/classic-rtl.css 546 B
build/edit-post/classic.css 547 B
build/edit-post/style-rtl.css 6.97 kB
build/edit-post/style.css 6.97 kB
build/edit-site/style-rtl.css 8.23 kB
build/edit-site/style.css 8.22 kB
build/edit-widgets/index.min.js 16.5 kB
build/edit-widgets/style-rtl.css 4.35 kB
build/edit-widgets/style.css 4.35 kB
build/editor/index.min.js 39.4 kB
build/editor/style-rtl.css 3.65 kB
build/editor/style.css 3.65 kB
build/element/index.min.js 4.27 kB
build/escape-html/index.min.js 537 B
build/format-library/style-rtl.css 571 B
build/format-library/style.css 571 B
build/hooks/index.min.js 1.64 kB
build/html-entities/index.min.js 448 B
build/i18n/index.min.js 3.77 kB
build/is-shallow-equal/index.min.js 527 B
build/keyboard-shortcuts/index.min.js 1.78 kB
build/keycodes/index.min.js 1.38 kB
build/list-reusable-blocks/index.min.js 1.74 kB
build/list-reusable-blocks/style-rtl.css 835 B
build/list-reusable-blocks/style.css 835 B
build/media-utils/index.min.js 2.93 kB
build/notices/index.min.js 953 B
build/nux/index.min.js 2.05 kB
build/nux/style-rtl.css 732 B
build/nux/style.css 728 B
build/plugins/index.min.js 1.94 kB
build/preferences-persistence/index.min.js 2.22 kB
build/preferences/index.min.js 1.3 kB
build/primitives/index.min.js 933 B
build/priority-queue/index.min.js 612 B
build/react-i18n/index.min.js 696 B
build/react-refresh-entry/index.min.js 8.44 kB
build/react-refresh-runtime/index.min.js 7.31 kB
build/redux-routine/index.min.js 2.68 kB
build/reusable-blocks/index.min.js 2.22 kB
build/reusable-blocks/style-rtl.css 256 B
build/reusable-blocks/style.css 256 B
build/rich-text/index.min.js 11.1 kB
build/server-side-render/index.min.js 1.61 kB
build/shortcode/index.min.js 1.53 kB
build/token-list/index.min.js 644 B
build/url/index.min.js 3.61 kB
build/vendors/react-dom.min.js 38.5 kB
build/vendors/react.min.js 4.34 kB
build/viewport/index.min.js 1.08 kB
build/warning/index.min.js 268 B
build/widgets/index.min.js 7.19 kB
build/widgets/style-rtl.css 1.16 kB
build/widgets/style.css 1.16 kB
build/wordcount/index.min.js 1.06 kB

compressed-size-action

* @param {string} position A position string such as 'bottom left'
* @return {string} A placement string such as 'bottom-start'
*/
const positionToPlacement = ( position ) => {
Copy link
Contributor Author

@andrewserong andrewserong May 24, 2022

Choose a reason for hiding this comment

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

This transformation is necessary because otherwise we end up with the following issue (tested in Storybook):

Before After
image image

I'm not too sure if I've understood the switched left / right logic in the Popover, but so far in testing this function appears to be working correctly for the ToolTip to me. Very happy for feedback, though, if anyone has ideas for how to improve this 🙂

Copy link
Contributor

Choose a reason for hiding this comment

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

This confused me as well.

It's a pity that Popover and Tooltip would have "opposite" behaviour in terms of how left and right work.

@andrewserong
Copy link
Contributor Author

I've pinged a few folks who reviewed #40740. Apologies for the liberal use of pinging / adding reviewers here — I wasn't too sure if the way I've gone about fixing this is the right approach, so just wanted to make sure y'all could see the PR just in case I missed some details about how best to work with the refactored Popover. Thanks!

@@ -194,6 +194,7 @@ const Popover = (
shift( {
crossAxis: true,
limiter: limitShift(),
padding: 4,
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this necessary, I fear it would have an impact on the block toolbar...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, I thought maybe we could use some clearance between the edge of the screen and the Tooltip, but from looking at other web apps (e.g. Gmail) it looks like it's pretty common for Tooltips to go flush up against the viewport. I've removed this line.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Update: turns out without setting padding the Tooltip jumps around a bit when it hits the edge of the viewport:

2022-05-25 17 06 53

I'll look into a fix tomorrow.

@@ -212,9 +259,12 @@ function Tooltip( props ) {
? getDisabledElement
: getRegularElement;

const placement = positionToPlacement( position );
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do you think we need this here? Is the "positionToPlacement" in the "Popover" component not working properly? Should we fix that one instead?

Copy link
Contributor

@ciampo ciampo left a comment

Choose a reason for hiding this comment

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

Left a few inline comments, focusing more on the component in isolation and less on its integration in the editor (which I'm sure other folks can also help with).

I also realised that we also have an experimental Tooltip component — it would be interesting to assess if we should look into using that "new" implementation or discard it.

* @param {string} position A position string such as 'bottom left'
* @return {string} A placement string such as 'bottom-start'
*/
const positionToPlacement = ( position ) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

This confused me as well.

It's a pity that Popover and Tooltip would have "opposite" behaviour in terms of how left and right work.

* @param {string} position A position string such as 'bottom left'
* @return {string} A placement string such as 'bottom-start'
*/
const positionToPlacement = ( position ) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we add some more unit tests to make sure that this function works properly?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, good idea, I'll add some tests tomorrow.

Comment on lines 154 to 136
const childRef = useRef( null );
const mergedChildRefs = useMergeRefs( [
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we could add some inline code to explain why we're storing both childRef and mergedChildRefs (which are being used later in code in the getElementWithPopover function)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added an inline comment to hopefully clear this one up a bit.

Copy link
Contributor

@ntsekouras ntsekouras Jul 12, 2022

Choose a reason for hiding this comment

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

We need the first child's ref now because cloneElement would previously preserve the ref, correct? Also can you explain a bit why would we need the childRef in getRegularElement? Just trying to understand it fully 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This one was the result of trial and error so I'm not 100% sure how it's working but here's my current understanding:

  • We need a stable ref to the first child to be passed to the Popover so that it can calculate the correct position. In order to pass it reliably to the Popover without causing any errors, by using an additional childRef we have the one reference that is pointing to the current child element.
  • Because we can't know whether or not the first child of the Tooltip is already using a ref for another reason, we need to attempt to merge it in with the childRef we're using for the anchorRef, this way we don't accidentally overwrite a ref used for other purposes.
  • In getRegularElement and getDisabledElement we specify the mergedRef so that both refs are attached correctly to the cloned element (any existing ref is preserved, and our additional childRef for the Popover can be used for position calculation)

Let me know if you think any changes are needed here, or if we can make it clearer what's going on in the inline comment!

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for explaining! I'm afraid I can't suggest something right now, since I tried doing what I asked above, that is pass only the first child's ref if exists to cloneElement and only pass childRef var as the anchorRef without success.. It would be great though if we were 100% sure for the solution --cc @ellatrix @diegohaz if they have any insights.

Copy link
Member

Choose a reason for hiding this comment

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

I'm having a hard time understanding what's going on here, but it's definitely good practice to create new refs for each purpose and not try to reuse refs created elsewhere. So the use of useMergeRefs seems fine here, if that's what you're asking.
Seeing Children.toArray( children )[ 0 ]?.ref makes me frown a little though. Can't you use the child variable that you have as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So the use of useMergeRefs seems fine here, if that's what you're asking.

Thanks for confirming, I think that gets at the main issue?

Seeing Children.toArray( children )[ 0 ]?.ref makes me frown a little though. Can't you use the child variable that you have as well?

This is an interesting one — I had a go at consolidating with the exiting child variable, but it results in a few subtle issues:

  • React's Children.only() function throws an error rather than returning undefined so we can't use it before the conditional to check how many children there are.
  • Because we're using a hook here, we have the call to the hook after a conditional.
  • If we switch child to use the Children.toArray( children )[ 0 ] approach, then an incorrect key gets copied when the element is duplicated, which causes other components' tests to fail (I found that the hard way in 90f5541)

So, as far as I can tell the best trade-off we have so far is this slightly awkward way of accessing the child in order to retrieve the existing ref (if it exists). I've tidied this up a tiny bit by storing the value in const existingChildRef to see if that helps with clarity.

Let me know if either of you can think of a better way to handle this, I've very rarely had to use React.Children directly so I am a little unfamiliar with this part of the API. I'm happy to keep iterating now or in follow-ups.

isOver,
position,
placement,
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we consider updating the APIs of Tooltip to match the ones of Popover? (i.e. adding the placement prop and deprecating the position prop)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's a good question, while digging into this, it seems that we need a little more smarts for the Tooltip positioning. "bottom left" means something different to "bottom-start", and in the case of the Tooltip — I think I'd be keen to retain position as it implies implicit offset logic, but potentially also expose placement. I'll write a separate longer comment with a couple of thoughts.

@andrewserong andrewserong changed the title ToolTip: Fix positioning by anchoring to child element Tooltip: Fix positioning by anchoring to child element May 25, 2022
@andrewserong
Copy link
Contributor Author

Thanks for the feedback @ciampo and @youknowriad! I've gotten a little closer to coming up with a slightly more thorough fix, with honing in some of the underlying issues. I've pushed a change in 8001942, but it needs some tidying up and for me to add some tests which I'll look into tomorrow.

Is the "positionToPlacement" in the "Popover" component not working properly? Should we fix that one instead?

I spent some time digging into this question, and I think we have some subtle issues to tease apart. The main issue appears to be the gap between the idea of the placement point and the expectations of the existing position prop. In floatingUI, we set a placement point, and then the element extends in the opposite direction from that alignment. So, in the original Popover PR, flipping left and right makes sense when the Popover is large enough to extend into the area we think of when we use left/right. The logic isn't intuitive per se, but achieves the desired result, for the most part.

However, Tooltips, being quite small, have a slightly different expectation associated with them. I think for a Tooltip, when we say "bottom left" we probably mean something closer to "center the Tooltip at the bottom left corner of this element".

The good news is that we can pass a function to offset in floatingUI, so in my latest commit, I've used this functionality so that we can better approximate the expectation of position "bottom left" — in this case, by setting alignmentAxis to negative half the width of the Tooltip, we can place the Tooltip at the desired location. As a result, it means we can achieve placement like the following:

image

This has a couple of flow on implications if it seems like an okay approach:

  • The Tooltip's position property is still useful as it implicitly switches on some centering offset logic, which means that I don't think we'd want to deprecate the property.
  • The floatingUI functionality for placement is still super useful, and gives more control over the desired position of the Popover/Tooltip, so maybe we'd want to expose this as well as the position property?
  • However, we'd then have two very similar properties.
  • I believe we still want the transformation from position to placement in the Tooltip to be unique to the tooltip, and not the Popover, as the expectations surrounding how a Tooltip will be placed are different to a larger Popover containing arbitrary content. For example, when I tried to apply the logic in this PR (which tests pretty well for me so far), the same logic (use offset with alignment axis of half the floating width) looked very wrong for components like the publishing visibility or publish dates in the sidebar inspector.

Anyway, those are my thoughts from going down a bit of a floatingUI rabbit hole today. I'll tidy up the approach tomorrow if there aren't any objections, and then get the PR ready for review again. But, do let me know if you think there's a different direction I should be pursuing 🙂

@andrewserong
Copy link
Contributor Author

andrewserong commented May 25, 2022

I also realised that we also have an experimental Tooltip component — it would be interesting to assess if we should look into using that "new" implementation or discard it.

It looks like that one uses Reakit for the Tooltip, rather than Popover. It'd probably be good to consolidate on the one approach, so I imagine if/when this PR lands, we might be able to look at a refactor? (But let me know if you think we should do it the other way around)

@ciampo
Copy link
Contributor

ciampo commented May 25, 2022

Anyway, those are my thoughts from going down a bit of a floatingUI rabbit hole today. I'll tidy up the approach tomorrow if there aren't any objections, and then get the PR ready for review again. But, do let me know if you think there's a different direction I should be pursuing 🙂

Thank you for sharing these thoughts. I think that we shouldn't have both position and placement as it would be quite confusing. i'd love it if we could have consistency in the APIs of Popover and Tooltip`, but I also understand that the expectations for the 2 components can be different.

It looks like that one uses Reakit for the Tooltip, rather than Popover. It'd probably be good to consolidate on the one approach, so I imagine if/when this PR lands, we might be able to look at a refactor? (But let me know if you think we should do it the other way around)

Makes sense — it'd be great to clean this duplication up

@andrewserong
Copy link
Contributor Author

Part of the issue in this PR (minimum width of the popover) will be resolved by #41361, if that PR looks viable. I'll put this PR on hold in lieu of that PR, then can rebase this one to see about a possibly more minimal change just for the Tooltip.

@andrewserong
Copy link
Contributor Author

The main issue to resolve with this PR has now been merged separately in #41361 (the Tooltip being cut off at the edge of the viewport). The other idea in this PR (to tweak the position of the Tooltip so that it's anchored correctly to the child element) would still be good to get in, but is lower priority, so I'm going to set this PR aside for the moment while I work on some higher priority tasks, and then will 🤞 get back to this next week.

Thanks again for the 👀, all!

@afercia
Copy link
Contributor

afercia commented Jul 4, 2022

Thanks for looking into this. Just wanted to note that this is also an accessibility issue.

Right now, on trunk, the tooltip may appear on top of some input fields, making impossible for keyboard users to actually see the value they're entering. For example. see the Margin control in the screenshot below:

  • use the keyboard to focus one of the input fields
  • the tooltip appears and partially hides the input
  • type a value or use arrow keys to change the value: it's not possible to actually see the entered value

Screenshot 2022-07-04 at 14 08 03

On this branch, the tooltip position is OK:

Screenshot 2022-07-04 at 14 22 25

@andrewserong
Copy link
Contributor Author

Thanks for taking a look (and for the reminder about this PR) @afercia 😀

I'll aim to dust this PR off and rebase it this week to see if we can progress it toward being mergable. Cheers!

@andrewserong andrewserong force-pushed the fix/tooltip-position-relative-to-anchor branch from 8001942 to 50b54ce Compare July 11, 2022 06:31
@andrewserong andrewserong force-pushed the fix/tooltip-position-relative-to-anchor branch from c69377f to b903d19 Compare July 13, 2022 04:58
@andrewserong
Copy link
Contributor Author

Thanks for taking this for a spin @ntsekouras!

I think this PR is working pretty well at the moment. My one hesitation is about the Tooltip (and Popover) position → placement calculation. I've updated the Storybook example in this PR to highlight the problem, which is that the Popover's logic which translates left to -end and right to -start works well if the Tooltip is wider than the target element, and poorly if the Tooltip is narrower than the target element.

Here's a Gif of the Storybook example with the Tooltip position set to bottom left:

2022-07-13 15 12 18

The URL for that example (if you're running npm run storybook:dev) is: http://localhost:50240/?path=/story/components-tooltip--default&knob-Delay=700&knob-Position=bottom%20left&knob-Text=More%20information

I've tried hacking around a little bit with not much luck, but I think ideally the Popover would be aware enough to switch the placement to the correct side based on the relative size of the Popover versus its ref, but I haven't been able to come up with anything that isn't super hacky just yet. It's a little tricky to come up with the right logic, too, given that the Floating UI placement concept doesn't quite match conceptually to the existing position strings 🤔

I'm curious if you (or others) think it's worth getting this PR in as-is, even though the positioning is reversed when the Tooltip is narrower than its ref, or if we should see if we can fix the position → placement logic in the Popover first?

@ntsekouras
Copy link
Contributor

I'm curious if you (or others) think it's worth getting this PR in as-is, even though the positioning is reversed when the Tooltip is narrower than its ref, or if we should see if we can fix the position → placement logic in the Popover first?

I can't see any difference regarding the width of the ref 🤔 . I can see the issue for your concern though:

However, Tooltips, being quite small, have a slightly different expectation associated with them. I think for a Tooltip, when we say "bottom left" we probably mean something closer to "center the Tooltip at the bottom left corner of this element".

Is it something more? Was the position worked like this before the Popover refactor? I don't remember..

IMO this is a big improvement from trunk and I haven't observed any regression.

const childRef = useRef( null );
const mergedChildRefs = useMergeRefs( [
childRef,
Children.toArray( children )[ 0 ]?.ref,
Copy link
Member

Choose a reason for hiding this comment

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

Can you not use child here? That would make things a bit clearer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Replied in #41268 (comment)

@andrewserong
Copy link
Contributor Author

Thanks for the continued reviews @ntsekouras and @ellatrix!

I can't see any difference regarding the width of the ref 🤔

I probably did a bad job at explaining it, it's such a subtle/nuanced issue it's difficult to describe! 😄
Currently the Popover makes no change based on the width of the ref — but I was wondering if it should in order to more accurately interpret "position" in the sense of pre-Popover-refactor to "placement" after the refactor.

Is it something more? Was the position worked like this before the Popover refactor? I don't remember..

Previously the Tooltip (and Popover) position situated the Tooltip at the desired location, rather than defining the direction in which it extends. So bottom left meant, "place the Tooltip at the bottom left position" rather than "extend the Tooltip to the bottom left direction". Tricky stuff! Particularly now that it's hard to find an example 😅

The issue technically exists prior to this PR, but is made more visible for the Tooltip now that it's anchored to an element rather than a zero width span.

It's a little subtle to work with when it comes to the Tooltip, so it might very well be something for us to dig into in follow-ups. Personally I think this PR improves things, too, so if everyone agrees, we could always land this PR to fix the immediate positioning of Tooltips issue, and continue exploring positioning/placements issues in follow-ups.

Copy link
Member

@noisysocks noisysocks left a comment

Choose a reason for hiding this comment

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

I ran into this bug while working on #42352 and can confirm that it miraculously went away after merging this branch into mine.

It's a hell of a lot better than what is in trunk. I say let's merge it sooner than later.

PS nice work @andrewserong!

@andrewserong
Copy link
Contributor Author

Thanks for reviewing @noisysocks! 🙇

I think merging sooner rather than later, and then seeing if we can tweak positioning (or code quality) in follow-ups sounds like a good way to go, too. @ntsekouras and @ellatrix let me know if there are any objections to merging, otherwise I'll merge this one tomorrow morning my time (just to give folks in other timezones a chance to weigh in, since there was a bit of discussion about the approach of accessing React children).

Copy link
Contributor

@ntsekouras ntsekouras left a comment

Choose a reason for hiding this comment

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

I think it's okay to land. Thanks for your work Andrew! 💯

@andrewserong andrewserong merged commit 49a4a01 into trunk Jul 19, 2022
@andrewserong andrewserong deleted the fix/tooltip-position-relative-to-anchor branch July 19, 2022 22:56
@github-actions github-actions bot added this to the Gutenberg 13.8 milestone Jul 19, 2022
@priethor priethor added the [Focus] Accessibility (a11y) Changes that impact accessibility and need corresponding review (e.g. markup changes). label Jul 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] UI Components Impacts or related to the UI component system [Focus] Accessibility (a11y) Changes that impact accessibility and need corresponding review (e.g. markup changes). [Package] Components /packages/components [Type] Bug An existing feature does not function as intended
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants