-
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
Add multi select #896
Add multi select #896
Conversation
More work to do there. Only adds a background. |
@jasmussen Feel free to style as you'd like. :) |
It's probably better to have a separate reducer for this... |
To do:
|
How will keyboard multi-selection work? |
Good question. I thing we could add several ways. A good one would be the shortcut you normally use to select paragraph by paragraph, which is |
I was expecting since I could click and drag across multiple blocks that I could shift+down from a paragraph into the next. Might be difficult to implement, since it's hard to know when shift+down occurs on the "last" line of a paragraph. |
@aduth You mean once you have a selection? If there's no selection yet, it will conflict with selecting line-by-line. |
Another thing we might want to do, in addition to drag, is a select mode, much like you have in the iOS Photos app. If it's toggled, clicking could select multiple blocks. The only awkward thing is that they'll be forced consecutive. |
Maybe @afercia has some more ideas. |
This is what I mean: Repeatedly Shift+Down, I hit the end of the text block, but never moves into multi selection, which feels disjointed from the click+drag experience: https://cloudup.com/c-8wUVJ4iIv |
Aha! Yeah, I should be able to make that work. Again here a better selection state will be helpful. :) |
If using repeatedly Shift+Down, how screen reader users would know when they've reached the end of a block? I'm not sure any kind of keyboard shortcut here would be ideal, since it will be confusing with what normally happens in a textarea. Really not sure what to do. The whole keyboard interaction for navigation and selection across blocks is a real issue, I have no great ideas so far. |
Looks good, works pretty well, nice work! I added a slightly darker border around each selected block.
Can we do this by simply styling the selection rectangle the same color as the background, when you have multi-selected? Seems like it would be good to only hide the text selection, not actually remove it. We might find it annoying if you're just trying to select text inside a block, and accidentally invoke the multi block selection thing to ony have your selection undone. In that vein it might be good to have some buffer zone or timer, so that you only actually start selecting across blocks once your mouse is, like, n pixels over the next block, and only then after half a second has passed. Something in that vein would be good to experiment with. Right now you can't deselect. Should we make it so clicking outside, just like how you deselect text, deselects blocks too? Escape should probably also deselect.
Maybe ⌘A, when focus is outside of a block, selects all blocks. But maybe you can't select just a few at all, using just the keyboard. The purpose of multi selection is to make it easy to delete multiple blocks at once, or move multiple blocks at once. You will already be able to do this using keyboard shortcuts alone — simply invoke the keyboard shortcuts for delete blocks multiple times in a row. Or tab to the block you want to delete, then delete it. Same with rearranging blocks. That is — you can accomplish the same end result using keyboard shortcuts. It's just a slightly different flow. This is similar to how layouting apps like Keynote or Sketch work. |
Thanks for adding the styling! I saw you design also had some spacing in between, is this something you'd like to see too?
I suggested hiding right? :) I'll have a look at the buffering and deselect. |
8b3e6ff
to
aa092eb
Compare
For me deselecting works by clicking on any of the blocks. |
6d881c9
to
1637cac
Compare
I've added the delete and moving buttons. Still needs some tests. Also the location of these buttons may not be ideal. |
1637cac
to
91b577c
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.
Realy awesome job here 👏🏻
I'm seeing some minor issues:
-
Removing using backspace is not working (The "regular" backspace event is being handled instead.
-
Removing using the button don't work sometimes. I'm not sure when it works and when it doesn't
-
The sticky behaviour (remove button) is not working as expected.
onSelectionStart={ () => this.onSelectionStart( uid ) } | ||
onSelectionChange={ () => this.onSelectionChange( uid ) } | ||
onSelectionEnd={ this.onSelectionEnd } | ||
selectedBlocks={ selectedBlocks } |
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 drop the selectedProps
prop and move it to the connect of VisualEditorBlock
instead?
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.
Yeah :)
editor/modes/visual-editor/block.js
Outdated
@@ -229,7 +241,26 @@ class VisualEditorBlock extends wp.element.Component { | |||
</div> | |||
</CSSTransitionGroup> | |||
} | |||
<div onKeyPress={ this.maybeStartTyping }> | |||
{ this.props.isFirstSelected && ( |
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.
Minor: Could we destructure all the this.props.*
?
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.
Yeah. I think I forgot to remove this after I saw it clashes with the selector... Any naming conventions 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'm ok keeping it to avoid conflicting with the selector. but Maybe I'd rename the selector isFirstSelectedBlock
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.
Sounds good!
switch ( action.type ) { | ||
case 'RESET_BLOCKS': | ||
return action.blocks.map( ( { uid } ) => uid ); | ||
|
||
case 'INSERT_BLOCK': | ||
case 'INSERT_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.
Curious to know why do you wrap case
between {}
. Is this a good practice I'm missing?
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.
Yeah I'd rather scope the const
declarations to one case instead of the whole reducer. You can use {}
anywhere you want to create a new block scope AFAIK.
/* ... */
{
let something = 1;
}
/* ... */
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.
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.
Gotcha, Thanks for the explanation
@youknowriad Yep, I know the sticky positioning is not working... I'm not sure how to resolve this atm. Was thinking to do try to address this in a separate PR. I'll add the backspace shortcut. |
Regarding sticky positioning... We could wrap the selected block in an extra div. This would also be far cleaner than adding the buttons to the first selected block. A big downside is that iframes would reload. The other solution is adding the buttons to the parent division, but we'll have to reposition it with JS. |
Something else that just popped in my mind: overlay the selected area with a |
This feels pretty good, especially as a first PR version of this. Click outside to deselect works great, as does the text selection trick you're doing. Great work! I can't get the mover to work. The sticky toolbar and cross-select "buffer zone" seem like perfect candidates to address separately. On Sticky — would it make sense to think of a "global toolbar" component, which can be invoked in situations like this? I could imagine us using this one for mobile improvements as well. That is, a sticky toolbar that sits where it needs to sit in the DOM in order to scroll the full length of the document, but is only used as a tray for buttons in certain situations? The pattern to emulate could be that of Google Photos, where you have one toolbar when you don't have any photos selected: ... and another toolbar replacing it, when you have photos selected: Might be nice to even copy the "8 blocks selected" pattern. This could even be the location of the mover buttons.
Not critical, and not worth doing in this PR. The truth is right now the blocks are flush next to each other, so that's why there isn't that space. However if we remove one px of vertical padding inside the blocks, and make that margin between them instead, it should address that. But because of collapsing margins and such, worth doing separately. |
I agree that we should iterate on the rest of the issues. I'd like to get it merged ASAP in this state. :) The block movers were working for me be let me double check. :) |
Huh, I broke them... |
@jasmussen Actually no, they work fine here. I'm not sure why it's not working for you. |
Sorry, it's the last commit, I see it now. Got confused between what I had checked out. :/ |
Moving seems to work now! It doesn't scroll you to the new location like moving an individual block does, though. Is this fixable? If yes, fine to do in a separate PR. |
Yeah I knew it wouldn't scroll. I'll quickly have a look how much it takes to make it work. |
Impressive! Moving works great now! 💖 |
Any technical concerns? Otherwise I'll merge in the next few hours. |
} | ||
|
||
componentDidMount() { | ||
if ( 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.
Could you explain the issues with focus you were seeing? Do we need to monitor this prop if it changes during the lifetime of the component?
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.
Could you explain the issues with focus you were seeing? Do we need to monitor this prop if it changes during the lifetime of the component?
Circling back to this. I encountered the Toolbar focus
prop when working on some unrelated refactoring and had no idea what its purpose is. I still don't, and could use some clarification as to this unanswered question.
@@ -10,11 +10,11 @@ import './style.scss'; | |||
import Button from '../button'; | |||
import Dashicon from '../dashicon'; | |||
|
|||
function IconButton( { icon, children, label, className, ...additionalProps } ) { | |||
function IconButton( { icon, children, label, className, focus, ...additionalProps } ) { |
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.
focus
would be passed to the underlying Button
without being explicitly handled here, based on the behavior of additionalProps
spread.
@@ -30,6 +30,7 @@ function Toolbar( { controls } ) { | |||
'is-active': control.isActive, | |||
} ) } | |||
aria-pressed={ control.isActive } | |||
focus={ focus && ! index } |
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 line is causing some trouble for me in tests. I am unsure as to what is happening here. Where is focus
coming from? Is this supposed to be passed into the component as a prop
? If that is the case, we will need to add focus to the function signature at the top. Toolbar( { controls, 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.
You're right. This was causing a bug too. Thanks for catching it!
See #62.