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

WIP: Attempt to fix partial select + scroll jumping in iOS #45642

Closed
wants to merge 37 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
14916c8
Try permanentyl enabling contenteditable on writing flow
ellatrix Nov 9, 2022
f5d6ffd
Fix non element node
ellatrix Nov 9, 2022
f2ea002
Fix select all
ellatrix Nov 10, 2022
95aa756
Ensure React handlers are called
ellatrix Nov 10, 2022
50e4a19
Fix selecting by click
ellatrix Nov 14, 2022
1b05ce0
Remove focus redirect
ellatrix Nov 14, 2022
16c0fc1
Dispatch new event from closest content editable
ellatrix Nov 14, 2022
075e831
Fix paste emulate
ellatrix Nov 14, 2022
3466d7e
Redirect beforeinput
ellatrix Nov 14, 2022
fc48b90
Fix composition end test
ellatrix Nov 15, 2022
8a209d8
Fix selection on non editable
ellatrix Nov 21, 2022
a21414a
Fix label on editor
ellatrix Nov 21, 2022
a3a05b0
Fix some e2e tests
ellatrix Nov 21, 2022
860a7fc
Fix arrow key nav issue
ellatrix Nov 21, 2022
250e161
Fix clipboard emulator
ellatrix Nov 21, 2022
3600d1c
Fix native multi select ranges
ellatrix Nov 21, 2022
1a28046
Add contenteditable for block lists
ellatrix Nov 22, 2022
b6ad671
Fix focus on wrapper blocks
ellatrix Nov 22, 2022
b711bb1
Change ref order
ellatrix Nov 22, 2022
0badf74
Fix regression in event handling order
ellatrix Nov 22, 2022
55c769d
Fix focus after merge
ellatrix Nov 22, 2022
2eb68c6
Redirect events on inner blocks CE
ellatrix Nov 22, 2022
2b71a63
Attempt to fix some arrow nav issues
ellatrix Nov 22, 2022
6656d7a
Attempt to solve focus issues
ellatrix Nov 22, 2022
70ea862
Don't modify container in place caret
ellatrix Nov 22, 2022
9bb1b03
Fix input actions on single fully selected block
ellatrix Nov 22, 2022
3d2a089
Fix some playwright tests
ellatrix Nov 23, 2022
0b6fadb
...
ellatrix Nov 23, 2022
b81c1c9
🤞
ellatrix Nov 23, 2022
df9f4f4
Try click handler
ellatrix Nov 23, 2022
8ce9621
Simulate click
ellatrix Nov 23, 2022
68e3c57
Fix arrow key nav for too broad target
ellatrix Nov 23, 2022
29f9547
Fix shift click selection
ellatrix Nov 24, 2022
2791ea7
Fix a few multi selection tests
ellatrix Nov 24, 2022
6f1d507
aria label
ellatrix Nov 24, 2022
c656261
Fix Safari issue
ellatrix Nov 24, 2022
25e026c
wip
ellatrix Nov 24, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
}

// Block focus.
.block-editor-block-list__block:not([contenteditable]):focus {
.block-editor-block-list__block:focus {
outline: none;

// We're using a pseudo element to overflow placeholder borders
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) {

return {
tabIndex: 0,
contentEditable: false,
...wrapperProps,
...props,
ref: mergedRefs,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,36 @@ export function useFocusFirstElement( clientId ) {
}

const { ownerDocument } = ref.current;
const { defaultView } = ownerDocument;
const selection = defaultView.getSelection();
const { anchorNode } = selection;
const anchorElement =
anchorNode?.nodeType === anchorNode?.ELEMENT_NODE
? anchorNode
: anchorNode.parentElement;

// Do not focus the block if it already contains the active element.
if ( isInsideRootBlock( ref.current, ownerDocument.activeElement ) ) {
if (
anchorElement &&
isInsideRootBlock( ref.current, anchorElement )
) {
let editor = ref.current;

// Ensure editor has focus.
while ( editor.parentElement?.closest( '[contenteditable]' ) ) {
editor = /** @type {HTMLElement} */ (
editor.parentElement.closest( '[contenteditable]' )
);
}

editor.focus();
return;
}

if (
ownerDocument.activeElement &&
isInsideRootBlock( ref.current, ownerDocument.activeElement )
) {
return;
}

Expand All @@ -96,12 +123,22 @@ export function useFocusFirstElement( clientId ) {
textInputs[ isReverse ? textInputs.length - 1 : 0 ] || ref.current;

if ( ! isInsideRootBlock( ref.current, target ) ) {
ref.current.focus();
selection.removeAllRanges();
let editor = ref.current;

// Ensure editor has focus.
while ( editor.parentElement?.closest( '[contenteditable]' ) ) {
editor = /** @type {HTMLElement} */ (
editor.parentElement.closest( '[contenteditable]' )
);
}

editor.focus();
return;
}

// Check to see if element is focussable before a generic caret insert.
if ( ! ref.current.getAttribute( 'contenteditable' ) ) {
if ( ref.current.getAttribute( 'contenteditable' ) !== 'true' ) {
const focusElement = focus.tabbable.findNext( ref.current );
// Make sure focusElement is valid, contained in the same block, and a form field.
if (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,7 @@ export function useFocusHandler( clientId ) {
* @param {FocusEvent} event Focus event.
*/
function onFocus( event ) {
// When the whole editor is editable, let writing flow handle
// selection.
if (
node.parentElement.closest( '[contenteditable="true"]' )
) {
if ( event.shiftKey ) {
return;
}

Expand All @@ -57,10 +53,10 @@ export function useFocusHandler( clientId ) {
selectBlock( clientId );
}

node.addEventListener( 'focusin', onFocus );
node.addEventListener( 'click', onFocus );

return () => {
node.removeEventListener( 'focusin', onFocus );
node.removeEventListener( 'click', onFocus );
};
},
[ isBlockSelected, selectBlock ]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/**
* WordPress dependencies
*/
import { isTextField } from '@wordpress/dom';
import { ENTER, BACKSPACE, DELETE } from '@wordpress/keycodes';
import { useSelect, useDispatch } from '@wordpress/data';
import { useRefEffect } from '@wordpress/compose';
Expand Down Expand Up @@ -54,7 +53,10 @@ export function useEventHandlers( clientId ) {
return;
}

if ( target !== node || isTextField( target ) ) {
if (
target !== node ||
target.ownerDocument.activeElement !== node
) {
return;
}

Expand Down
87 changes: 86 additions & 1 deletion packages/block-editor/src/components/inner-blocks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import classnames from 'classnames';
/**
* WordPress dependencies
*/
import { useViewportMatch, useMergeRefs } from '@wordpress/compose';
import {
useViewportMatch,
useMergeRefs,
useRefEffect,
} from '@wordpress/compose';
import { forwardRef } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import {
Expand Down Expand Up @@ -193,6 +197,85 @@ export function useInnerBlocksProps( props = {}, options = {} ) {
} );

const ref = useMergeRefs( [
useRefEffect( ( node ) => {
function onInput( event ) {
if ( event.__unstableRedirect === true ) {
return;
}

if ( event.target !== node ) {
return;
}

const { ownerDocument } = node;
const { defaultView } = ownerDocument;
const prototype = Object.getPrototypeOf( event );
const constructorName = prototype.constructor.name;
const Constructor = window[ constructorName ];
const { anchorNode } = defaultView.getSelection();

if ( ! anchorNode ) {
return;
}

const anchorElement = (
anchorNode.nodeType === anchorNode.ELEMENT_NODE
? anchorNode
: anchorNode.parentElement
).closest( '[contenteditable]' );

if ( ! anchorElement ) {
return;
}

const init = {};

for ( const key in event ) {
init[ key ] = event[ key ];
}

const newEvent = new Constructor( event.type, init );
newEvent.__unstableRedirect = true;
const cancelled = ! anchorElement.dispatchEvent( newEvent );
event.stopImmediatePropagation();

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 );
} );
};
}, [] ),
// useRefEffect( ( node ) => {
// function onKeyDown( event ) {
// if (
// event.target === event.target.ownerDocument.activeElement
// ) {
// event.preventDefault();
// }
// }
// node.addEventListener( 'keydown', onKeyDown );
// return () => {
// node.removeEventListener( 'keydown', onKeyDown );
// };
// }, [] ),
props.ref,
__unstableDisableDropZone ? null : blockDropZoneRef,
] );
Expand All @@ -209,6 +292,8 @@ export function useInnerBlocksProps( props = {}, options = {} ) {
return {
...props,
ref,
contentEditable: true,
suppressContentEditableWarning: true,
className: classnames(
props.className,
'block-editor-block-list__layout',
Expand Down
11 changes: 11 additions & 0 deletions packages/block-editor/src/components/rich-text/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,17 @@ function RichTextWrapper(

function onFocus() {
anchorRef.current?.focus();

// if ( anchorRef.current ) {
// const parentEditable =
// anchorRef.current.parentElement?.closest( '[contenteditable]' );

// if ( parentEditable ) {
// parentEditable.focus();
// } else {
// anchorRef.current.focus();
// }
// }
}

const TagName = tagName;
Expand Down
62 changes: 58 additions & 4 deletions packages/block-editor/src/components/writing-flow/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,16 @@ export function useWritingFlow() {
( select ) => select( blockEditorStore ).hasMultiSelection(),
[]
);
const selectedClientId = useSelect(
( select ) => select( blockEditorStore ).getSelectedBlockClientId(),
[]
);

return [
before,
useMergeRefs( [
ref,
useInput(),
ref,
useDragSelection(),
useSelectionObserver(),
useClickSelection(),
Expand All @@ -45,25 +49,75 @@ export function useWritingFlow() {
useRefEffect(
( node ) => {
node.tabIndex = -1;
node.contentEditable = hasMultiSelection;
node.contentEditable = true;

if ( ! hasMultiSelection ) {
return;
}

node.classList.add( 'has-multi-selection' );
node.setAttribute(
'aria-label',
__( 'Multiple selected blocks' )
);
node.classList.add( 'has-multi-selection' );

return () => {
node.classList.remove( 'has-multi-selection' );
node.removeAttribute( 'aria-label' );
};
},
[ hasMultiSelection ]
),
useRefEffect(
( node ) => {
if ( ! selectedClientId ) return;

const { ownerDocument } = node;
const { defaultView } = ownerDocument;
const selection = defaultView.getSelection();

const blockElement = ownerDocument.getElementById(
'block-' + selectedClientId
);
const blockLabel =
blockElement?.getAttribute( 'aria-label' );

node.setAttribute( 'aria-label', blockLabel );

function onSelectionChange() {
const { anchorNode } = selection;
let innerLabel = blockLabel;

if ( anchorNode ) {
const anchorElement =
anchorNode.nodeType === anchorNode.ELEMENT_NODE
? anchorNode
: anchorNode.parentElement;
const anchorLabel =
anchorElement?.closest( '[aria-label]' );

if ( anchorLabel ) {
innerLabel =
anchorLabel.getAttribute( 'aria-label' );
}
}

node.setAttribute( 'aria-label', innerLabel );
}

ownerDocument.addEventListener(
'selectionchange',
onSelectionChange
);

return () => {
ownerDocument.removeEventListener(
'selectionchange',
onSelectionChange
);
};
},
[ selectedClientId ]
),
] ),
after,
];
Expand Down
Loading