-
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
Allow multi-select on iOS Safari/touch devices #63671
Changes from 42 commits
2c8d911
3a8f2c5
6f33c01
bc2921d
e7ff262
3d17e73
82bfa7a
7b8c1b1
1f0acba
d86647d
9258ed3
fa6c2a5
c430a10
781d969
9e19646
acadb15
3b1a381
a0ee724
af90de1
2f45b13
5b96b59
a6dfaa0
94cd7b5
62a779f
c372ba7
c4ec944
8ca2b67
5cb8aad
74217b2
ad43235
e887e04
62cc866
0f921d0
dc46520
e1c423c
e42e62d
e1e8324
b73cc53
9c0dc0e
55d09d3
4c2ec17
8579290
2ab2f51
e620fcd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { useRefEffect } from '@wordpress/compose'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { getSelectionRoot } from './utils'; | ||
|
||
/** | ||
* Whenever content editable is enabled on writing flow, it will have focus, so | ||
* we need to dispatch some events to the root of the selection to ensure | ||
* compatibility with rich text. In the future, perhaps the rich text event | ||
* handlers should be attached to the window instead. | ||
* | ||
* Alternatively, we could try to find a way to always maintain rich text focus. | ||
*/ | ||
export default function useEventRedirect() { | ||
return useRefEffect( ( node ) => { | ||
function onInput( event ) { | ||
if ( event.target !== node || event.__isRedirected ) { | ||
return; | ||
} | ||
|
||
const { ownerDocument } = node; | ||
const { defaultView } = ownerDocument; | ||
const prototype = Object.getPrototypeOf( event ); | ||
const constructorName = prototype.constructor.name; | ||
const Constructor = defaultView[ constructorName ]; | ||
const root = getSelectionRoot( ownerDocument ); | ||
|
||
if ( ! root ) { | ||
return; | ||
} | ||
|
||
const init = {}; | ||
|
||
for ( const key in event ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just spread the object here? |
||
init[ key ] = event[ key ]; | ||
} | ||
|
||
init.bubbles = false; | ||
|
||
const newEvent = new Constructor( event.type, init ); | ||
newEvent.__isRedirected = true; | ||
const cancelled = ! root.dispatchEvent( newEvent ); | ||
|
||
if ( cancelled ) { | ||
event.preventDefault(); | ||
} | ||
} | ||
|
||
const events = [ | ||
'beforeinput', | ||
'input', | ||
'compositionstart', | ||
'compositionend', | ||
'compositionupdate', | ||
'keydown', | ||
]; | ||
|
||
events.forEach( ( eventType ) => { | ||
node.addEventListener( eventType, onInput ); | ||
} ); | ||
|
||
return () => { | ||
events.forEach( ( eventType ) => { | ||
node.removeEventListener( eventType, onInput ); | ||
} ); | ||
}; | ||
}, [] ); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,7 @@ import { | |
* Internal dependencies | ||
*/ | ||
import { store as blockEditorStore } from '../../store'; | ||
import { getSelectionRoot } from './utils'; | ||
|
||
/** | ||
* Handles input for selections across blocks. | ||
|
@@ -49,7 +50,24 @@ export default function useInput() { | |
// DOM. This will cause React errors (and the DOM should only be | ||
// altered in a controlled fashion). | ||
if ( node.contentEditable === 'true' ) { | ||
event.preventDefault(); | ||
const selection = node.ownerDocument.defaultView.getSelection(); | ||
const range = selection.rangeCount | ||
? selection.getRangeAt( 0 ) | ||
: null; | ||
const root = getSelectionRoot( node.ownerDocument ); | ||
|
||
// If selection is contained within a nested editable, allow | ||
// input. We need to ensure that selection is maintained. | ||
if ( root ) { | ||
node.contentEditable = false; | ||
root.focus(); | ||
selection.removeAllRanges(); | ||
if ( range ) { | ||
selection.addRange( range ); | ||
} | ||
} else { | ||
event.preventDefault(); | ||
} | ||
} | ||
} | ||
|
||
|
@@ -59,6 +77,23 @@ export default function useInput() { | |
} | ||
|
||
if ( ! hasMultiSelection() ) { | ||
const { ownerDocument } = node; | ||
if ( node === ownerDocument.activeElement ) { | ||
if ( event.key === 'End' || event.key === 'Home' ) { | ||
const selectionRoot = getSelectionRoot( ownerDocument ); | ||
const selection = | ||
ownerDocument.defaultView.getSelection(); | ||
selection.selectAllChildren( selectionRoot ); | ||
const method = | ||
event.key === 'End' | ||
? 'collapseToEnd' | ||
: 'collapseToStart'; | ||
selection[ method ](); | ||
event.preventDefault(); | ||
return; | ||
} | ||
} | ||
|
||
if ( event.keyCode === ENTER ) { | ||
if ( event.shiftKey || __unstableIsFullySelected() ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not from here, but curious how There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this is Enter behaviour for partial selection. So select two paragraphs partially and press Enter, this handles split for them. |
||
return; | ||
|
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.
When this
event.__isRedirected
istrue
?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.
See 20 lines below :)