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

Visual Editor: Keep track of the focused block #438

Merged
merged 7 commits into from
Apr 20, 2017
Merged

Conversation

youknowriad
Copy link
Contributor

@youknowriad youknowriad commented Apr 17, 2017

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

@youknowriad youknowriad added the Framework Issues related to broader framework topics, especially as it relates to javascript label Apr 17, 2017
@youknowriad youknowriad self-assigned this Apr 17, 2017
@youknowriad youknowriad requested review from aduth and ellatrix April 17, 2017 12:58

/* eslint-disable */
Copy link
Contributor Author

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 :(

Copy link
Contributor

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?

Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

@aduth aduth left a 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 ) {
Copy link
Member

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Probably 👍

Copy link
Contributor Author

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.


/* eslint-disable */
Copy link
Member

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.

<Editable
tagName="figcaption"
placeholder={ wp.i18n.__( 'Write caption…' ) }
value={ caption }
focus={ focus && focus.editable === 'caption' ? focus : null }
Copy link
Member

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?

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 hesitated and you're probably right here.

@@ -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( '' ) : '';
Copy link
Member

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?

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 this fixes the bug of inserting an empty quote block (using the inserter) since we're assuming the attributes are all nullable.

@youknowriad
Copy link
Contributor Author

@aduth I'm not sure how can we prevent the block author to handle the focus

@youknowriad
Copy link
Contributor Author

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?

I agree, and I'm really bad with naming things. Open to any suggestion.

@aduth
Copy link
Member

aduth commented Apr 17, 2017

@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 Editable component could directly access. This is how react-redux gets around the need to pass store as a prop down through intermediary components. But thinking about react-redux got me to consider whether it could interact with the store directly? The obvious problem is that Editable is supposed to not be aware of the editor, being that it's a general purpose component in the blocks directory. Per #408, I think we'll need to find a better solution that lets us treat components as editor-aware while still exposing them for consumption to external plugins.

@aduth
Copy link
Member

aduth commented Apr 17, 2017

I agree, and I'm really bad with naming things. Open to any suggestion.

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 isTyping state value updated in response to a global keyboard event?

@youknowriad
Copy link
Contributor Author

@aduth @iseulde Good point.

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.

onKeyDown={ onDeselect }
onMouseEnter={ onMouseEnter }
onKeyDown={ onStartTyping }
onMouseMove={ onMouseMove }
Copy link
Member

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?

Copy link
Contributor Author

@youknowriad youknowriad Apr 19, 2017

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.

attributes={ block.attributes }
setAttributes={ setAttributes } />
setAttributes={ setAttributes }
updateFocus={ onFocus }
Copy link
Member

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?

@@ -122,6 +131,13 @@ export default connect(
hovered: false,
uid: ownProps.uid
} );
},
onFocus( config = {} ) {
Copy link
Member

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?

Copy link
Contributor Author

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 ) {
Copy link
Member

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.

Copy link
Contributor Author

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?

@aduth
Copy link
Member

aduth commented Apr 19, 2017

Needs a rebase.

@ellatrix
Copy link
Member

ellatrix commented Apr 19, 2017

If it helps, in previous prototypes I had also a separate key for showing and hiding UI in the Redux store. SHOW_UI on mousedown touchstart focus and HIDE_UI on keydown blur. There are state checks before dispatching.

@youknowriad
Copy link
Contributor Author

Feedback addressed, should we merge?

} else if ( this.props.value !== prevProps.value ) {
}

if ( this.props.focus !== prevProps.focus && this.props.focus ) {
Copy link
Member

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.

Copy link
Member

@nylen nylen Apr 19, 2017

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.

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 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.

Copy link
Contributor Author

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.

@nylen
Copy link
Member

nylen commented Apr 19, 2017

What is this.props.focus? What are its possible valid values and what does each one mean? How can we make this more clear?

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.

@nylen
Copy link
Member

nylen commented Apr 19, 2017

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

@youknowriad
Copy link
Contributor Author

@nylen this.props.focus is a nullable object. If it's falsy, this means the current block does not have the focus. If it's not, its shape is defined partially in this PR. and the final shape could be { editable: 'citation', offset: 10 } (maybe tinymce's bookmark instead of offset, not sure yet).

The editable is optional. It's intended for blocks with multiple editables and the offset is the offset inside this editable.

@nylen
Copy link
Member

nylen commented Apr 19, 2017

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 focus.

@youknowriad
Copy link
Contributor Author

Feedback addressed, anyone to give a last 👀 before merging?

Copy link
Member

@nylen nylen left a 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.

@youknowriad youknowriad merged commit c970c16 into master Apr 20, 2017
@youknowriad youknowriad deleted the add/focus-state branch April 20, 2017 16:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Framework Issues related to broader framework topics, especially as it relates to javascript
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Cannot type in empty image caption
5 participants