diff --git a/.eslintrc.js b/.eslintrc.js index 490c542f9d4565..53ae4eadd7433e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -249,6 +249,24 @@ module.exports = { ], }, }, + { + files: [ + 'packages/*/src/**/*.[tj]s?(x)', + 'storybook/stories/**/*.[tj]s?(x)', + ], + excludedFiles: [ '**/*.native.js' ], + rules: { + 'no-restricted-syntax': [ + 'error', + { + selector: + 'JSXOpeningElement[name.name="Button"]:not(:has(JSXAttribute[name.name="__experimentalIsFocusable"])) JSXAttribute[name.name="disabled"]', + message: + '`disabled` used without the `__experimentalIsFocusable` prop. Disabling a control without maintaining focusability can cause accessibility issues, by hiding their presence from screen reader users, or preventing focus from returning to a trigger element. (Ignore this error if you truly mean to disable.)', + }, + ], + }, + }, { files: [ // Components package. diff --git a/packages/block-directory/src/plugins/get-install-missing/install-button.js b/packages/block-directory/src/plugins/get-install-missing/install-button.js index 2dc01184bdeb4a..075fed360c14c8 100644 --- a/packages/block-directory/src/plugins/get-install-missing/install-button.js +++ b/packages/block-directory/src/plugins/get-install-missing/install-button.js @@ -42,6 +42,7 @@ export default function InstallButton( { attributes, block, clientId } ) { } } ) } + __experimentalIsFocusable disabled={ isInstallingBlock } isBusy={ isInstallingBlock } variant="primary" diff --git a/packages/block-editor/src/components/button-block-appender/index.js b/packages/block-editor/src/components/button-block-appender/index.js index 974f48e61bc287..cd1289c897824c 100644 --- a/packages/block-editor/src/components/button-block-appender/index.js +++ b/packages/block-editor/src/components/button-block-appender/index.js @@ -60,6 +60,8 @@ function ButtonBlockAppender( onClick={ onToggle } aria-haspopup={ isToggleButton ? 'true' : undefined } aria-expanded={ isToggleButton ? isOpen : undefined } + // Disable reason: There shouldn't be a case where this button is disabled but not visually hidden. + // eslint-disable-next-line no-restricted-syntax disabled={ disabled } label={ label } > diff --git a/packages/block-editor/src/components/link-control/link-preview.js b/packages/block-editor/src/components/link-control/link-preview.js index 867b69356eb9d9..fb4b3658e2a4f1 100644 --- a/packages/block-editor/src/components/link-control/link-preview.js +++ b/packages/block-editor/src/components/link-control/link-preview.js @@ -149,6 +149,7 @@ export default function LinkPreview( { isEmptyURL || showIconLabels ? '' : ': ' + value.url ) } ref={ ref } + __experimentalIsFocusable disabled={ isEmptyURL } size="compact" /> diff --git a/packages/block-library/src/gallery/v1/gallery-image.js b/packages/block-library/src/gallery/v1/gallery-image.js index 368d5da55c4ac9..5384944b2335d9 100644 --- a/packages/block-library/src/gallery/v1/gallery-image.js +++ b/packages/block-library/src/gallery/v1/gallery-image.js @@ -222,6 +222,8 @@ class GalleryImage extends Component { onClick={ isFirstItem ? undefined : onMoveBackward } label={ __( 'Move image backward' ) } aria-disabled={ isFirstItem } + // Disable reason: Truly disable when image is not selected. + // eslint-disable-next-line no-restricted-syntax disabled={ ! isSelected } /> diff --git a/packages/dataviews/src/item-actions.tsx b/packages/dataviews/src/item-actions.tsx index 8a4fcf1b19f8d8..90ae74b5f74ea6 100644 --- a/packages/dataviews/src/item-actions.tsx +++ b/packages/dataviews/src/item-actions.tsx @@ -254,6 +254,7 @@ function CompactItemActions< Item extends AnyItem >( { size="compact" icon={ moreVertical } label={ __( 'Actions' ) } + __experimentalIsFocusable disabled={ ! actions.length } className="dataviews-all-actions-button" /> diff --git a/packages/dataviews/src/view-list.tsx b/packages/dataviews/src/view-list.tsx index 9468d6aa2d0585..0721a9b5d8ffe7 100644 --- a/packages/dataviews/src/view-list.tsx +++ b/packages/dataviews/src/view-list.tsx @@ -255,6 +255,7 @@ function ListItem< Item extends AnyItem >( { size="compact" icon={ moreVertical } label={ __( 'Actions' ) } + __experimentalIsFocusable disabled={ ! actions.length } onKeyDown={ ( event: { key: string; diff --git a/packages/edit-post/src/components/preferences-modal/enable-custom-fields.js b/packages/edit-post/src/components/preferences-modal/enable-custom-fields.js index e3ba4a15684200..e655a7300c37ee 100644 --- a/packages/edit-post/src/components/preferences-modal/enable-custom-fields.js +++ b/packages/edit-post/src/components/preferences-modal/enable-custom-fields.js @@ -42,6 +42,7 @@ export function CustomFieldsConfirmation( { willEnable } ) { className="edit-post-preferences-modal__custom-fields-confirmation-button" variant="secondary" isBusy={ isReloading } + __experimentalIsFocusable disabled={ isReloading } onClick={ () => { setIsReloading( true ); diff --git a/packages/edit-site/src/components/global-styles-sidebar/index.js b/packages/edit-site/src/components/global-styles-sidebar/index.js index 436762d6bcf94f..f57cc8c417f410 100644 --- a/packages/edit-site/src/components/global-styles-sidebar/index.js +++ b/packages/edit-site/src/components/global-styles-sidebar/index.js @@ -152,6 +152,7 @@ export default function GlobalStylesSidebar() { isPressed={ isStyleBookOpened || isRevisionsStyleBookOpened } + __experimentalIsFocusable disabled={ shouldClearCanvasContainerView } onClick={ toggleStyleBook } size="compact" @@ -162,6 +163,7 @@ export default function GlobalStylesSidebar() { label={ __( 'Revisions' ) } icon={ backup } onClick={ toggleRevisions } + __experimentalIsFocusable disabled={ ! hasRevisions } isPressed={ isRevisionsOpened || isRevisionsStyleBookOpened diff --git a/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js b/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js index 9c4280f2b1eb51..7f4f9896344b4f 100644 --- a/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js +++ b/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js @@ -163,6 +163,7 @@ function RevisionsButtons( { >