-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Block List: Move deselect behavior to block component #3743
Conversation
Was previously used in an implementation where managing relationship of selected blocks between parent and children, refactored in #3743 instead
The e2e failures appear to be inconsistent, and I wasn't able to reproduce the failing behavior in manual testing. Will continue to investigate... |
const { relatedTarget } = event; | ||
const isOutside = ! relatedTarget || ! relatedTarget.closest( [ | ||
'.editor-header', | ||
'.editor-sidebar', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This component is reusable and these two selectors are not guaranteed to exist (example P2). Should we use a prop for these or a setting in the context?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This component is reusable and these two selectors are not guaranteed to exist (example P2). Should we use a prop for these or a setting in the context?
I agree, and noted in Implementation Notes that this was not ideal, but slightly improved from the previous implementation. I tried an alternate approach in 00fcfca, passing an onDeselectBlock
callback from the visual editor to control deselection via event.preventDefault
. What are your thoughts on this approach?
2bd32f8
to
00fcfca
Compare
Codecov Report
@@ Coverage Diff @@
## master #3743 +/- ##
==========================================
- Coverage 37.72% 37.63% -0.09%
==========================================
Files 279 279
Lines 6738 6730 -8
Branches 1226 1225 -1
==========================================
- Hits 2542 2533 -9
- Misses 3535 3536 +1
Partials 661 661
Continue to review full report at Codecov.
|
ownProps.onDeselect( event, ...args ); | ||
} | ||
|
||
if ( ! event || ! event.isDefaultPrevented() ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clever
The extra prop is a bit annoying but it is still better than what we have. Also, I'm seeing a difference between multi and mono selection. When I click the white space around the editor it deselects the block but if it's a multiselection, it doesn't work, should we aligh the behavior? Also, I'm seeing a small issue:
|
Yeah, I see why this would be an issue, since focus has already left the block at this point, the focus outside handler would never be called again. Thinking click-outside could be a better handler here, except we'd probably want to limit it to be applied only on the selected block (otherwise we could have hundreds of handlers firing on every click for long posts). |
I spent some time on this earlier in the week, and it's a very difficult problem to solve. Testing click outside of the selected block works mostly well, except:
The most promising direction would be to migrate inspector and toolbar slots to behave with React event bubbling so focus stays within a block while interacting with these controls. But then this has a few issues as well:
|
00fcfca
to
cfd7e96
Compare
I made another attempt to iterate on this, and made some decent progress with click detection. I've included my original summary below, but the e2e test failures led me to discover some blocking issues:
I'm going to take a break from this for now. My hunch is that maybe we need a document-wide Original revisions summary: The general idea is that while a block is selected, we detect mouse clicks on the document to deselect the block, with the following caveats:
I think the most promising option here is: Stop propagation in the block's Since we don't need this behavior until nested blocks are implemented, I have omitted it from the changes here. (An alternative option I had considered is testing whether the closest block wrapper where the click occurs is the same as the current block wrapper. The issue here is that |
Another iteration today in ce2f722, binding focus events to try deselecting block when focus moves elsewhere in the page. This mostly works okay, but there's an issue with opening the block secondary menu causing the block to become deselected. I have a feeling that since we rely on event propagation to cancel the deselection, there may be some |
Related: https://discuss.reactjs.org/t/ordering-of-native-and-react-events/829 The current implementation doesn't work well specifically here: gutenberg/editor/components/block-list/block.js Lines 220 to 224 in ce2f722
...because when opening the block settings menu, focus transitions within the popover content, which is not in the same DOM hierarchy as the rendered DOM node of the block itself, so it doesn't capture the focus event to cancel the deselect. Normally we'd be able to handle this simply binding diff --git a/editor/components/block-list/block.js b/editor/components/block-list/block.js
index 2bc51321..e46664ec 100644
--- a/editor/components/block-list/block.js
+++ b/editor/components/block-list/block.js
@@ -216,12 +216,6 @@ export class BlockListBlock extends Component {
const bindFn = isListening ? 'addEventListener' : 'removeEventListener';
document[ bindFn ]( 'focus', this.triggerDeselect, true );
document[ bindFn ]( 'deselect', this.debouncedDeselect );
-
- // Bind to the DOM reference of the rendered wrapper node, since React
- // synthetic event binding and focus normalization conflicts with our
- // own document event binding and therefore canceling occurs before the
- // deselect event is triggered (out of expected order).
- this.wrapperNode[ bindFn ]( 'focusin', this.cancelDeselect );
}
setAttributes( attributes ) {
@@ -471,6 +465,7 @@ export class BlockListBlock extends Component {
data-type={ block.name }
onTouchStart={ this.onTouchStart }
onClick={ this.onClick }
+ onFocus={ this.cancelDeselect }
{ ...wrapperProps }
>
<BlockDropZone index={ order } /> But the problem appears to be that React's normalization of events is causing the cancel to be triggered before our own |
ce2f722
to
88fc5b2
Compare
See also: facebook/react#285 The implementation here finally seems mostly solid. I was able to move back to relying on the synthetic events. Binding focus events to That all being said, the e2e tests are failing and I'm having a hard time debugging them, as repeating the steps from the failing test manually works well for me in my own browser. Will debug some more in the morning. |
isBlurring instance variable was never used
Shouldn't be the case that we forbid access to bubbling from toolbar click. Necessary for block deselect canceling.
This allows clicks that are technically within but not on content of a block to trigger the click outside logic, since if pointer events are disabled, the click occurs on the element behind the block (the editor canvas)
This reverts commit cfd7e96.
Previously reverse of expected: We want the _inside_ case to prevent default behavior.
onDeselect is fired in other cases, e.g. escape press. Only handle deselect via focus
More reliable when using virtual event bubbling (e.g. portals), but as workaround to noted issue of document-level event binding, need to stop propagation. See: facebook/react#285
This reverts commit 153fcdc.
This reverts commit c22cc94.
Captures sooner than React synthetic events, so canceling occurs in expected order
88fc5b2
to
37f4d77
Compare
I no longer intend to pursue this approach for now, as it's proven to be quite problematic, and the current behavior that clearing is an effect of the action of clicking on the editor canvas is more agreeable than at initial consideration. That said, there are still issues to resolve with deselection, namely #2322, which should be addressed separately. |
Closes #3712
This pull request seeks to improve block deselect behavior. The previous implementation had the visual editor test for click events on the canvas to infer a deselection. This was necessary because there are many cases where focus leaves a block, but the block should remain selected (e.g. clicking toolbar buttons, or inspector controls). However, the previous solution was not ideal because (a) it introduces an implicit dependency between the visual editor and block focus, (b) it prioritized click behavior and while tab focus changes worked, it was only because multiple singular blocks could not be selected at the same time, and (c) it conflicts with efforts to introduce multiple editor scopes (block nesting).
Implementation notes:
I see this as a dramatic simplification to VisualEditor, but still anticipate that improvements could be made. Specifically, the block component shouldn't need to have any awareness of editor headers or sidebars, as the proposed implementation includes. Two alternatives I see here include:
Testing instructions:
Verify that there are no regressions in the behavior of selecting blocks, particularly: