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

Improvements to use-focus-first-element and utils (dom) #39461

Merged
merged 15 commits into from
Mar 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -7,7 +7,12 @@ import { first, last } from 'lodash';
* WordPress dependencies
*/
import { useEffect, useRef } from '@wordpress/element';
import { focus, isTextField, placeCaretAtHorizontalEdge } from '@wordpress/dom';
import {
focus,
isFormElement,
isTextField,
placeCaretAtHorizontalEdge,
} from '@wordpress/dom';
import { useSelect } from '@wordpress/data';

/**
Expand Down Expand Up @@ -52,16 +57,6 @@ function useInitialPosition( clientId ) {
);
}

function isFormElement( element ) {
const { tagName } = element;
return (
tagName === 'INPUT' ||
tagName === 'BUTTON' ||
tagName === 'SELECT' ||
tagName === 'TEXTAREA'
);
}

/**
* Transitions focus to the block or inner tabbable when the block becomes
* selected and an initial position is set.
Expand Down Expand Up @@ -107,17 +102,13 @@ export function useFocusFirstElement( clientId ) {
}

// Check to see if element is focussable before a generic caret insert.
if ( ! target.getAttribute( 'contenteditable' ) ) {
const focusElement = focus.tabbable.findNext( target );
// Make sure focusElement is valid, form field, and within the current target element.
// Ensure is not block inserter trigger, don't want to focus that in the event of the group block which doesn't contain any other focussable elements.
if ( ! ref.current.getAttribute( 'contenteditable' ) ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Out of interest, why make the change here from target to ref.current?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@talldan E2E tests caught a bug in the embed block and I confirmed it. Seems like using ref.current is more stable in this case.

It would focus the button of the embed block vs. the field to enter a URL. Odd issue but easy enough fix. Not sure if it was the IFrame or what...

const focusElement = focus.tabbable.findNext( ref.current );
// Make sure focusElement is valid, contained in the same block, and a form field.
if (
focusElement &&
isFormElement( focusElement ) &&
target.contains( focusElement ) &&
! focusElement.classList.contains(
'block-editor-button-block-appender'
)
isInsideRootBlock( ref.current, focusElement ) &&
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This looks so much cleaner to me.

isFormElement( focusElement )
) {
focusElement.focus();
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
import { focus } from '@wordpress/dom';
import { focus, isFormElement } from '@wordpress/dom';
import { TAB, ESCAPE } from '@wordpress/keycodes';
import { useSelect, useDispatch } from '@wordpress/data';
import { useRefEffect, useMergeRefs } from '@wordpress/compose';
Expand All @@ -12,16 +12,6 @@ import { useRef } from '@wordpress/element';
*/
import { store as blockEditorStore } from '../../store';

function isFormElement( element ) {
const { tagName } = element;
return (
tagName === 'INPUT' ||
tagName === 'BUTTON' ||
tagName === 'SELECT' ||
tagName === 'TEXTAREA'
);
}

export default function useTabNav() {
const container = useRef();
const focusCaptureBeforeRef = useRef();
Expand Down
3 changes: 2 additions & 1 deletion packages/block-editor/src/utils/dom.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const BLOCK_SELECTOR = '.block-editor-block-list__block';
const APPENDER_SELECTOR = '.block-list-appender';
const BLOCK_APPENDER_CLASS = '.block-editor-button-block-appender';

/**
* Returns true if two elements are contained within the same block.
Expand All @@ -25,7 +26,7 @@ export function isInSameBlock( a, b ) {
*/
export function isInsideRootBlock( blockElement, element ) {
const parentBlock = element.closest(
[ BLOCK_SELECTOR, APPENDER_SELECTOR ].join( ',' )
[ BLOCK_SELECTOR, APPENDER_SELECTOR, BLOCK_APPENDER_CLASS ].join( ',' )
);
return parentBlock === blockElement;
}
Expand Down
12 changes: 12 additions & 0 deletions packages/dom/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,18 @@ _Returns_

- `boolean`: True if entirely selected, false if not.

### isFormElement

Detects if element is a form element.

_Parameters_

- _element_ `Element`: The element to check.

_Returns_

- `boolean`: True if form element and false otherwise.

### isHorizontalEdge

Check whether the selection is horizontally at the edge of the container.
Expand Down
1 change: 1 addition & 0 deletions packages/dom/src/dom/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export { default as getRectangleFromRange } from './get-rectangle-from-range';
export { default as getScrollContainer } from './get-scroll-container';
export { default as getOffsetParent } from './get-offset-parent';
export { default as isEntirelySelected } from './is-entirely-selected';
export { default as isFormElement } from './is-form-element';
export { default as isHorizontalEdge } from './is-horizontal-edge';
export { default as isNumberInput } from './is-number-input';
export { default as isTextField } from './is-text-field';
Expand Down
20 changes: 20 additions & 0 deletions packages/dom/src/dom/is-form-element.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Internal dependencies
*/
import isInputOrTextArea from './is-input-or-text-area';

/**
*
* Detects if element is a form element.
*
* @param {Element} element The element to check.
*
* @return {boolean} True if form element and false otherwise.
*/
export default function isFormElement( element ) {
const { tagName } = element;
const checkForInputTextarea = isInputOrTextArea( element );
return (
checkForInputTextarea || tagName === 'BUTTON' || tagName === 'SELECT'
);
}