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

DOM: Allow copying text from non-text input elements #40192

Merged
merged 5 commits into from
May 6, 2022

Conversation

mcsf
Copy link
Contributor

@mcsf mcsf commented Apr 8, 2022

What?

Fixes #32148.

This pull request guarantees that a user is able to copy the contents of any <input> elements inside blocks using their browser's native actions (Ctrl-C, Command-C).

Captura de ecrã 2022-04-08, às 16 10 29

Why?

This pull request fixes a bug in which certain types of input were not copiable — specifically, any "non-text" input, such as number or even email. Typically, when this bug occurred, the block editor would ignore the browser's native copying behaviour, thus resulting in the copying of the entire block — instead of just the selected content in the input element.

How?

The selection state of non-text input elements is opaque to Gutenberg, per the HTML spec. Unaware of this
nuance, inputFieldHasUncollapsedSelection( element ) would incorrectly report that some non-text input element had no text selected when in fact it did. This caused the block editor to take over copy events to copy whole blocks, preventing the user from copying what they had actually selected inside the input element in question.

The trade-off in this commit is to always assume that there could be an uncollapsed selection in an active element if that element's selection state is opaque.

Caveat

The trade-off means that, if the user places the caret inside a non-text input element but has no text selected (according to the jargon, this means that the selection is collapsed), then pressing the copy shortcut will no longer let the block editor copy the whole block. Text inputs (input="text" or "url") and rich-text elements should not be affected by this.

Testing Instructions

For specifics, see instructions in #32148.

  • Fixed: Ensure that the contents of non-text inputs can be copied using the native keyboard shortcut. Try this using <TextControl type="number" inside a block, as well as type="email", type="time".
  • Unchanged: Ensure that the contents of text inputs can be copied using the native keyboard shortcut. Try this using <TextControl type="text" inside a block, as well as type="url".
  • Unchanged: Ensure that an entire block can be copied with the native keyboard shortcut when the caret is placed inside a text input with no selection.
  • Unchanged: Ensure that copying is otherwise unaffected: the copying of rich-text content, of whole blocks, of multiple blocks, etc.
Attached: The diff I applied to the Paragraph block for my own testing
diff --git a/packages/block-library/src/paragraph/edit.js b/packages/block-library/src/paragraph/edit.js
index c6a2988a3d..8795d868f8 100644
--- a/packages/block-library/src/paragraph/edit.js
+++ b/packages/block-library/src/paragraph/edit.js
@@ -7,10 +7,12 @@ import classnames from 'classnames';
  * WordPress dependencies
  */
 import { __, _x, isRTL } from '@wordpress/i18n';
+import { useState } from '@wordpress/element';
 import {
 	ToolbarDropdownMenu,
 	ToggleControl,
 	__experimentalToolsPanelItem as ToolsPanelItem,
+	TextControl,
 } from '@wordpress/components';
 import {
 	AlignmentControl,
@@ -56,6 +58,11 @@ function ParagraphBlock( {
 } ) {
 	const { align, content, direction, dropCap, placeholder } = attributes;
 	const isDropCapFeatureEnabled = useSetting( 'typography.dropCap' );
+
+	const [ fooText, setFooText ] = useState( 'hello' );
+	const [ fooEmail, setFooEmail ] = useState( '[email protected]' );
+	const [ fooNumber, setFooNumber ] = useState( 42 );
+
 	const blockProps = useBlockProps( {
 		className: classnames( {
 			'has-drop-cap': dropCap,
@@ -149,6 +156,21 @@ function ParagraphBlock( {
 				__unstableEmbedURLOnPaste
 				__unstableAllowPrefixTransformations
 			/>
+			<TextControl
+				type="text"
+				value={ fooText }
+				onChange={ ( v ) => ( console.log( v ), setFooText( v ) ) }
+			/>
+			<TextControl
+				type="email"
+				value={ fooEmail }
+				onChange={ ( v ) => ( console.log( v ), setFooEmail( v ) ) }
+			/>
+			<TextControl
+				type="time"
+				value={ fooNumber }
+				onChange={ ( v ) => ( console.log( v ), setFooNumber( v ) ) }
+			/>
 		
 	);
 }

@mcsf mcsf added [Type] Bug An existing feature does not function as intended [Package] DOM /packages/dom labels Apr 8, 2022
@mcsf mcsf requested a review from ellatrix as a code owner April 8, 2022 15:36
@@ -26,6 +26,7 @@ export default function isTextField( node ) {
'reset',
'submit',
'number',
'email',
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Could any component by negatively affected by this spec correction?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also, I really dislike isTextField's exclusion approach. Since, in fact, most input types are not text fields, we might be better off with a stricter inclusion approach, i.e. [ 'text', ... ].includes( type ).

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think anything would be negatively affected by this.. And I also think a reversed approach would be okay, but we would really need to make sure we have all the needed types 😄

@github-actions
Copy link

github-actions bot commented Apr 8, 2022

Size Change: +14 B (0%)

Total Size: 1.23 MB

Filename Size Change
build/dom/index.min.js 4.59 kB +14 B (0%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 993 B
build/admin-manifest/index.min.js 1.24 kB
build/annotations/index.min.js 2.77 kB
build/api-fetch/index.min.js 2.27 kB
build/autop/index.min.js 2.15 kB
build/blob/index.min.js 487 B
build/block-directory/index.min.js 6.51 kB
build/block-directory/style-rtl.css 1.01 kB
build/block-directory/style.css 1.01 kB
build/block-editor/default-editor-styles-rtl.css 378 B
build/block-editor/default-editor-styles.css 378 B
build/block-editor/index.min.js 150 kB
build/block-editor/style-rtl.css 15.2 kB
build/block-editor/style.css 15.2 kB
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 111 B
build/block-library/blocks/audio/style.css 111 B
build/block-library/blocks/audio/theme-rtl.css 125 B
build/block-library/blocks/audio/theme.css 125 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 445 B
build/block-library/blocks/button/editor.css 445 B
build/block-library/blocks/button/style-rtl.css 560 B
build/block-library/blocks/button/style.css 560 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-template/style-rtl.css 127 B
build/block-library/blocks/comment-template/style.css 127 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-query-loop/editor-rtl.css 95 B
build/block-library/blocks/comments-query-loop/editor.css 95 B
build/block-library/blocks/cover/editor-rtl.css 546 B
build/block-library/blocks/cover/editor.css 547 B
build/block-library/blocks/cover/style-rtl.css 1.56 kB
build/block-library/blocks/cover/style.css 1.56 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 417 B
build/block-library/blocks/embed/style.css 417 B
build/block-library/blocks/embed/theme-rtl.css 124 B
build/block-library/blocks/embed/theme.css 124 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 255 B
build/block-library/blocks/file/style.css 255 B
build/block-library/blocks/file/view.min.js 353 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 961 B
build/block-library/blocks/gallery/editor.css 964 B
build/block-library/blocks/gallery/style-rtl.css 1.51 kB
build/block-library/blocks/gallery/style.css 1.51 kB
build/block-library/blocks/gallery/theme-rtl.css 122 B
build/block-library/blocks/gallery/theme.css 122 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 332 B
build/block-library/blocks/html/editor.css 333 B
build/block-library/blocks/image/editor-rtl.css 731 B
build/block-library/blocks/image/editor.css 730 B
build/block-library/blocks/image/style-rtl.css 529 B
build/block-library/blocks/image/style.css 535 B
build/block-library/blocks/image/theme-rtl.css 124 B
build/block-library/blocks/image/theme.css 124 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 447 B
build/block-library/blocks/latest-posts/style.css 446 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 708 B
build/block-library/blocks/navigation-link/editor.css 706 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 299 B
build/block-library/blocks/navigation-submenu/editor.css 299 B
build/block-library/blocks/navigation-submenu/view.min.js 375 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.93 kB
build/block-library/blocks/navigation/style.css 1.92 kB
build/block-library/blocks/navigation/view-modal.min.js 2.65 kB
build/block-library/blocks/navigation/view.min.js 395 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/style-rtl.css 446 B
build/block-library/blocks/post-comments-form/style.css 446 B
build/block-library/blocks/post-comments/style-rtl.css 521 B
build/block-library/blocks/post-comments/style.css 521 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 721 B
build/block-library/blocks/post-featured-image/editor.css 721 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 323 B
build/block-library/blocks/post-template/style.css 323 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 369 B
build/block-library/blocks/query/editor.css 369 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 397 B
build/block-library/blocks/search/style.css 398 B
build/block-library/blocks/search/theme-rtl.css 64 B
build/block-library/blocks/search/theme.css 64 B
build/block-library/blocks/separator/editor-rtl.css 140 B
build/block-library/blocks/separator/editor.css 140 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 474 B
build/block-library/blocks/shortcode/editor.css 474 B
build/block-library/blocks/site-logo/editor-rtl.css 759 B
build/block-library/blocks/site-logo/editor.css 759 B
build/block-library/blocks/site-logo/style-rtl.css 181 B
build/block-library/blocks/site-logo/style.css 181 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 332 B
build/block-library/blocks/spacer/editor.css 332 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 504 B
build/block-library/blocks/table/editor.css 504 B
build/block-library/blocks/table/style-rtl.css 625 B
build/block-library/blocks/table/style.css 625 B
build/block-library/blocks/table/theme-rtl.css 188 B
build/block-library/blocks/table/theme.css 188 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 571 B
build/block-library/blocks/video/editor.css 572 B
build/block-library/blocks/video/style-rtl.css 173 B
build/block-library/blocks/video/style.css 173 B
build/block-library/blocks/video/theme-rtl.css 124 B
build/block-library/blocks/video/theme.css 124 B
build/block-library/common-rtl.css 993 B
build/block-library/common.css 990 B
build/block-library/editor-rtl.css 10.2 kB
build/block-library/editor.css 10.2 kB
build/block-library/index.min.js 175 kB
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 689 B
build/block-library/theme.css 694 B
build/block-serialization-default-parser/index.min.js 1.12 kB
build/block-serialization-spec-parser/index.min.js 2.83 kB
build/blocks/index.min.js 47 kB
build/components/index.min.js 223 kB
build/components/style-rtl.css 15 kB
build/components/style.css 15 kB
build/compose/index.min.js 11.2 kB
build/core-data/index.min.js 14.5 kB
build/customize-widgets/index.min.js 11 kB
build/customize-widgets/style-rtl.css 1.39 kB
build/customize-widgets/style.css 1.39 kB
build/data-controls/index.min.js 663 B
build/data/index.min.js 8.66 kB
build/date/index.min.js 32 kB
build/deprecated/index.min.js 518 B
build/dom-ready/index.min.js 336 B
build/edit-navigation/index.min.js 15.8 kB
build/edit-navigation/style-rtl.css 4.04 kB
build/edit-navigation/style.css 4.05 kB
build/edit-post/classic-rtl.css 546 B
build/edit-post/classic.css 547 B
build/edit-post/index.min.js 30.1 kB
build/edit-post/style-rtl.css 7.18 kB
build/edit-post/style.css 7.18 kB
build/edit-site/index.min.js 47.4 kB
build/edit-site/style-rtl.css 8.02 kB
build/edit-site/style.css 8.01 kB
build/edit-widgets/index.min.js 16.3 kB
build/edit-widgets/style-rtl.css 4.4 kB
build/edit-widgets/style.css 4.39 kB
build/editor/index.min.js 38.5 kB
build/editor/style-rtl.css 3.71 kB
build/editor/style.css 3.71 kB
build/element/index.min.js 4.29 kB
build/escape-html/index.min.js 548 B
build/format-library/index.min.js 6.62 kB
build/format-library/style-rtl.css 571 B
build/format-library/style.css 571 B
build/hooks/index.min.js 1.66 kB
build/html-entities/index.min.js 454 B
build/i18n/index.min.js 3.79 kB
build/is-shallow-equal/index.min.js 535 B
build/keyboard-shortcuts/index.min.js 1.83 kB
build/keycodes/index.min.js 1.41 kB
build/list-reusable-blocks/index.min.js 1.75 kB
build/list-reusable-blocks/style-rtl.css 838 B
build/list-reusable-blocks/style.css 838 B
build/media-utils/index.min.js 2.94 kB
build/notices/index.min.js 957 B
build/nux/index.min.js 2.12 kB
build/nux/style-rtl.css 751 B
build/nux/style.css 749 B
build/plugins/index.min.js 1.98 kB
build/preferences/index.min.js 1.2 kB
build/primitives/index.min.js 949 B
build/priority-queue/index.min.js 611 B
build/react-i18n/index.min.js 704 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.69 kB
build/reusable-blocks/index.min.js 2.24 kB
build/reusable-blocks/style-rtl.css 256 B
build/reusable-blocks/style.css 256 B
build/rich-text/index.min.js 11.2 kB
build/server-side-render/index.min.js 1.61 kB
build/shortcode/index.min.js 1.52 kB
build/token-list/index.min.js 668 B
build/url/index.min.js 1.99 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 280 B
build/widgets/index.min.js 7.21 kB
build/widgets/style-rtl.css 1.16 kB
build/widgets/style.css 1.16 kB
build/wordcount/index.min.js 1.07 kB

compressed-size-action

@@ -18,9 +18,25 @@ import isNumberInput from './is-number-input';
* @return {boolean} Whether the input/textareaa element has some "selection".
*/
export default function inputFieldHasUncollapsedSelection( element ) {
if ( ! isTextField( element ) && ! isNumberInput( element ) ) {
if ( ! isTextField( element ) && ! isHTMLInputElement( element ) ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

How about creating a new util here(instead of isHTMLInputElement) that wraps the checks for the specific inputs we want to let browsers handle it. I think these would be number, email and time? In that case we would add an extra same check below, if there is no selection but this is true.

Now if we have any other inputs like button, etc.. would return true and I don't think we need to do this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure I understood your question; could you show me?

Copy link
Contributor

Choose a reason for hiding this comment

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

In several places in the code base we use the isNumberInput for the exact same reason of this PR: trying also include input[type="number"] for some selection related actions/calculations. What I suggest is to make a new util that will include isNumberInput and similar checks as if we had isEmailInput and isTimeInput. Makes sense?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, I understand the point now. What's interesting is that, the more I look at this PR and at existing usage of isNumberInput, the more I'm confused. :) I'll comment in more detail in the general thread.

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.

Thanks for the PR Miguel!

I think this is the right direction, but we need to make sure we update if needed related code to this. For example in documentHasSelection util we perform similar checks and we should probably take into account the added email,time.

Also it's interesting that while we had code for checking number type, since it has no selectionStart/End we would always return false 😄

@mcsf
Copy link
Contributor Author

mcsf commented Apr 18, 2022

Part of what makes these components tricky is that browsers implement these interfaces differently. My notes in the following paragraph are sort of a recap, but I find it helpful even just for myself to explain it again:

The spec states that selectionStart and selectionEnd only apply to input types text, search, url, tel, password, and it states that, for any other types, the attributes selectionStart and selectionEnd should be null. Safari goes against both prescriptions: it extends selection attributes to input types number and email, while any attempt to read these attributes in other input types throws a type error.

Here's a quick codesandbox script to try on different browsers. Red indicates input types deemed "opaque" — i.e., that don't support selectionStart, whether because it is null or because it throws an error. Green indicates a successful selection read. Notice that, in Safari, towards the bottom, types number and email show up in green, whereas in Firefox and Chrome only the first five types are green (following the spec).

I've been going back and forth on a question: since Safari is arguably being "useful" by supporting selection APIs in a wider range of input types, should we let users benefit from this via browser-dependent behaviour, or should we prioritise behaviour that is consistent across browsers and up to spec?

@mcsf
Copy link
Contributor Author

mcsf commented Apr 18, 2022

In several places in the code base we use the isNumberInput for the exact same reason of this PR: trying also include input[type="number"] for some selection related actions/calculations. What I suggest is to make a new util that will include isNumberInput and similar checks as if we had isEmailInput and isTimeInput.

My other question, prompted by Nik's comment, is around isNumberInput. Luckily, it is only used twice in Gutenberg — in documentHasSelection and inputFieldHasUncollapsedSelection — but I wondered why number inputs receive special treatment in those situations. Reading the PR that introduced it, #21457, it seems that number inputs were special-cased simply because it was the only non-text input type where the issue was felt at the time. (As a side note, there is a fundamental bug in that function due to !! node.valueAsNumber returning false if the value is the number 0.)

This leads me to think that there is no reason to continue to treat numbers differently. Ultimately, all these selection-checking functions serve one purpose: to determine what to do when the user uses general keyboard shortcuts to copy, cut, and paste content.

Below are two tables describing what I think is the current intended behaviour in Gutenberg. For simplicity, ignore contentEditable elements.

When pasting:

focus \ action paste
text input paste into field (= defer to default behaviour)
opaque input paste into field (= defer to default behaviour)
elsewhere in a block replace whole block with clipboard content (= prevent default, intervene)

Pasting is easier, because it doesn't care whether there is a collapsed or uncollapsed selection in the active element. Also note that, ideally, opaque inputs should behave just like text ones — browser permitting.

Copying and cutting is more complicated, due to the desire to distinguish collapsed and uncollapsed selections. In bold I highlight what this PR was initially aiming to fix (i.e. conform with the intended behaviour). In italic I highlight what this PR was intentionally dismissing as a trade-off.

When copying or cutting:

focus \ action copy cut
text input (uncollapsed) copy input value cut input value
opaque input (uncollapsed) copy input value cut input value
text input (collapsed) copy whole block cut whole block
opaque input (collapsed) copy whole block cut whole block
elsewhere in a block copy whole block copy whole block

Solution one

Based on these tables, I'd like to drop usage of isNumberInput in our selection functions (although we can't remove the function, as it is public) and just operate based on whether the input is "opaque". This is a continuation of what this PR is already doing. The open question here is whether we should strictly adhere to the spec or whether we should go with what each browser allows.

Solution two

Although solution 1 works well for me, I'm also tempted to simplify this drastically, removing the collapsed/uncollapsed distinction, which in turn eliminates the need to inspect the input:

focus \ action copy cut
input copy input value (browser default) cut input value (browser default)
(contentEditable) (handled by RichText) (handled by RichText)
elsewhere in a block copy whole block (WritingFlow) cut whole block (WritingFlow)

Thoughts? Preferences? /cc @ntsekouras, @youknowriad

@mcsf mcsf force-pushed the fix/copy-from-non-text-inputs branch 2 times, most recently from 72e2a0b to b19886a Compare April 19, 2022 11:17
@youknowriad
Copy link
Contributor

youknowriad commented Apr 19, 2022

@mcsf I don't feel like I understand all the implications but the reasoning makes sense and I'm always for simplifications. So I'm onboard with solution 2.

The selection state of non-text input elements (e.g. `number`, but even
`email`) is opaque to Gutenberg, per the HTML spec [1]. Unaware of this
nuance, `inputFieldHasUncollapsedSelection` would incorrectly report
that some non-text input element had no text selected when in fact it
did. This caused the block editor to take over `copy` events to copy
whole blocks, preventing the user from copying what they had actually
selected inside the input element in question.

The workaround in this commit is to always assume that there could be an
uncollapsed selection in an active element if that element's selection
state is opaque.

[1]: https://html.spec.whatwg.org/multipage/input.html#do-not-apply
... as oppposed to just text inputs (isTextField) and number inputs
(isNumberInput).
@mcsf mcsf force-pushed the fix/copy-from-non-text-inputs branch from b19886a to b301855 Compare April 19, 2022 16:44
@ntsekouras
Copy link
Contributor

(As a side note, there is a fundamental bug in that function due to !! node.valueAsNumber returning false if the value is the number 0.)

That's true 😄 . We should have a follow up on that one.

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.

The approach makes sense to me and code looks good. Thanks Miguel!

@mcsf
Copy link
Contributor Author

mcsf commented May 6, 2022

@mcsf I don't feel like I understand all the implications but the reasoning makes sense and I'm always for simplifications. So I'm onboard with solution 2.

For the record, I paired with Nik today and we looked at both solutions, and we decided in favour of the first one. I had overestimated the simplification benefits of Solution 2, and I'd rather keep the special treatment of uncollapsed selections if that means that there's a higher likelihood that the editor will do what the user wanted.

Going to merge. :) Thanks, everyone.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Package] DOM /packages/dom [Type] Bug An existing feature does not function as intended
Projects
None yet
3 participants