-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Visual Editor: Keep track of the focused block #438
Conversation
blocks/library/image/index.js
Outdated
|
||
/* eslint-disable */ |
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.
the onClick
adds to many accessibility linting errors :(
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.
Not sure of the context, but is it worth adding a note about this to a ticket like #392?
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.
Also, we should be specifically disable only the lint rule we want to target, in case there are other lint issues in the region.
I like the idea of commenting on any disable we apply, since it holds us accountable to understanding why lint errors occur and how they're not applicable.
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.
Yep that was my intent, but this is breaking three rules :). I'll update.
598fe5b
to
cea6e0a
Compare
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 seems reasonable to me. As with anything we add, we should be conscious of the developer experience impact. I think @iseulde raises a good point at #420 (comment) . At a glance, "focused" and "selected" seem synonymous, so how can we clarify or consolidate the two? With regards to new props being passed to edit
, are there options to explore with React's hidden context feature to help avoid the need for the block implementer to consider managing focus themselves?
editor/state.js
Outdated
* @param {Object} action Dispatched action | ||
* @return {Object} Updated state | ||
*/ | ||
export function focus( state = {}, action ) { |
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.
Should this be included in undo history?
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.
Probably 👍
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.
Tried this and it has several implications, we should ignore consecutive focuses etc... I think this should be added but in its own PR.
blocks/library/image/index.js
Outdated
|
||
/* eslint-disable */ |
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.
Also, we should be specifically disable only the lint rule we want to target, in case there are other lint issues in the region.
I like the idea of commenting on any disable we apply, since it holds us accountable to understanding why lint errors occur and how they're not applicable.
blocks/library/image/index.js
Outdated
<Editable | ||
tagName="figcaption" | ||
placeholder={ wp.i18n.__( 'Write caption…' ) } | ||
value={ caption } | ||
focus={ focus && focus.editable === 'caption' ? focus : null } |
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.
Do we even need the config here since we only have one focusable element? Wondering if focusCaption
could be implemented just as:
const focusCaption = () => setFocus();
And we'd infer caption should be focused merely by a truthy incoming focus
value?
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.
I hesitated and you're probably right here.
blocks/library/quote/index.js
Outdated
@@ -7,7 +7,7 @@ import Editable from 'components/editable'; | |||
|
|||
const { parse, html, query } = hpq; | |||
|
|||
const fromValueToParagraphs = ( value ) => value.map( ( paragraph ) => `<p>${ paragraph }</p>` ).join( '' ); | |||
const fromValueToParagraphs = ( value ) => value ? value.map( ( paragraph ) => `<p>${ paragraph }</p>` ).join( '' ) : ''; |
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.
Is this fixing a bug? Do we really want to support an undefined value
? Do we need some concept of default attributes, whether that's an explicit property of a block or even just how we destructure attributes
in edit
and save
?
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.
Yes this fixes the bug of inserting an empty quote block (using the inserter) since we're assuming the attributes are all nullable.
@aduth I'm not sure how can we prevent the block author to handle the focus |
Well, at first I'd thought to use React's context to have the visual editor define child context values that the |
I'm not totally sure it's entirely a naming problem, as I don't really see much of a conceptual difference between our idea of selected and focused. Instead, it seems that "selected" is different only in that it accounts for typing status to fade out the border and controls. And if that's the case (correct me if it's not), could this be better reflected by a standalone |
In e3ce9f6 I merged the "selected" and the "focused" concepts to avoid confusion. @aduth I'm not certain we should hide the "focus" management from the block author. I think the block author should have some control on keyboard navigation and stuff like that (think moving the cursor from the content to the citation in the quote block). I think we should merge this as is and explore enhancements. |
editor/modes/visual-editor/block.js
Outdated
onKeyDown={ onDeselect } | ||
onMouseEnter={ onMouseEnter } | ||
onKeyDown={ onStartTyping } | ||
onMouseMove={ onMouseMove } |
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.
onMouseMove
is an incredibly noisy event (watch Redux DevTools while moving). Is the idea here that we want the block to regain its hover effects when moving the mouse while typing? Maybe we can at least add a condition to only trigger the callback if isTyping
is true?
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.
Exactly! yep good ideal to limit it to when we're typing.
editor/modes/visual-editor/block.js
Outdated
attributes={ block.attributes } | ||
setAttributes={ setAttributes } /> | ||
setAttributes={ setAttributes } | ||
updateFocus={ onFocus } |
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.
Even just for consistency with setAttributes
, do you think naming this setFocus
is still accurate / any better?
editor/modes/visual-editor/block.js
Outdated
@@ -122,6 +131,13 @@ export default connect( | |||
hovered: false, | |||
uid: ownProps.uid | |||
} ); | |||
}, | |||
onFocus( config = {} ) { |
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.
Should we be assigning the default config into state memory or would it be more appropriate to apply the default in mapStateToProps
?
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.
Right, handling the default value in the state is safer
editor/state.js
Outdated
}; | ||
|
||
case 'START_TYPING': | ||
if ( action.uid !== state.uid ) { |
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.
Does typing consideration need to be per-block? Seems like a global effect.
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.
Like implemented for now "START_TYPING" is per-block. It's triggered when typing inside a specific block, we can drop the uid
. I feel it should stay as is unless we catch the start typing in a higher level maybe?
Needs a rebase. |
e3ce9f6
to
5b53075
Compare
If it helps, in previous prototypes I had also a separate key for showing and hiding UI in the Redux store. |
Feedback addressed, should we merge? |
blocks/components/editable/index.js
Outdated
} else if ( this.props.value !== prevProps.value ) { | ||
} | ||
|
||
if ( this.props.focus !== prevProps.focus && this.props.focus ) { |
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.
Because of the strict comparison of objects and how the reducer is creating next state, this condition passes much more frequently than we'd probably expect. For example I see upon adding a console.log
to the inner block that it gets called twice every time I select a block.
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.
Maybe if ( this.props.focus && ! prevProps.focus )
would be better? Edit: no, looks like I probably misunderstood the intent here.
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.
I think to avoid these calls, we could check for a falsy this.props.focus
before calling the onFocus
prop for now. Once we introduce the focus position (probably a following PR) we should check if it's the same position before calling the callback.
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.
@nylen proposition is valid for now because we don't store any offset yet. I updated in 7488f23.
What is Why is it an object rather than a primitive such as a boolean (or a string to indicate that a specific field is focused)? If it's possible to make it a primitive, this would also solve #438 (comment) at the same time. |
Also, from a usability perspective, this seems to be working extremely well and addresses a bunch of issues I was having previously, so huge 👍 there |
@nylen The |
OK, thanks for explaining. How can we document this in the code? Should it be two separate properties instead? Probably the solution to #438 (comment) then is to compare each of the primitive properties inside of |
Feedback addressed, anyone to give a last 👀 before merging? |
7488f23
to
35a7e2d
Compare
35a7e2d
to
56af22c
Compare
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.
As Riad pointed out in Slack, changing focus
to primitive properties would complicate the code. We can document afterwards along with the rest of the edit
parameters. I'll make a separate issue for that.
references #420
In this PR, I'm adding the
focus
state to keep track of the focused block and any extra config that let us focus the right element. For now, I'm storing the focused block and the focused editable inside the block. To make undo/redo work correctly, we'll need to add the position inside the focused editable.closes #437