Skip to content
Open
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
26 changes: 26 additions & 0 deletions src/js/_enqueues/admin/widgets.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,32 @@ window.wpWidgets = {
sidebars = $('div.widgets-sortables'),
isRTL = !! ( 'undefined' !== typeof isRtl && isRtl );

// Keep the single-widget screen link for keyboard and screen reader users only.
$( '#widgets-left, #widgets-right' ).find( '.widget-control-edit' )
.removeClass( 'hide-if-js' )
.addClass( 'screen-reader-text' );

// Point the link at the widget's current sidebar and position, which change without a reload.
$( '#widgets-left, #widgets-right' ).on( 'focusin', '.widget-control-edit', function() {
var link = $( this ),
widget = link.closest( 'div.widget' ),
sidebar = widget.closest( 'div.widgets-sortables' ),
url = new URL( link.prop( 'href' ) );

if ( sidebar.length ) {
url.searchParams.set( 'editwidget', widget.find( 'input.widget-id' ).val() );
url.searchParams.set( 'sidebar', sidebar.attr( 'id' ) );
url.searchParams.set( 'key', sidebar.children( '.widget' ).index( widget ) );
url.searchParams.delete( 'addnew' );
url.searchParams.delete( 'num' );
url.searchParams.delete( 'base' );
} else if ( url.searchParams.has( 'num' ) ) {
url.searchParams.set( 'num', widget.find( 'input.multi_number' ).val() );
}

link.attr( 'href', url.toString() );
} );

// Handle the widgets containers in the right column.
$( '#widgets-right .sidebar-name' )
/*
Expand Down
29 changes: 29 additions & 0 deletions src/wp-admin/css/widgets.css
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,35 @@ div#widgets-right .closed .widgets-sortables {
border-color: #dcdcde;
}

.widget-title-action .widget-control-edit.screen-reader-text:focus {
clip-path: none;
width: auto;
height: auto;
margin: 0;
padding: 0 15px;
border-left: 1px solid #dcdcde;
top: 0;
right: 100%;
white-space: nowrap;
z-index: 1;

/* Restores the link focus style removed by .screen-reader-text:focus. */
box-shadow: 0 0 0 var(--wp-admin-border-width-focus, 1.5px) var(--wp-admin-theme-color, #3858e9);

/* Only visible in Windows High Contrast mode */
outline: 2px solid transparent;
}

.widget-title-action .widget-control-edit.screen-reader-text:focus .edit,
.widget-title-action .widget-control-edit.screen-reader-text:focus .add {
position: static;
clip-path: none;
width: auto;
height: auto;
margin: 0;
overflow: visible;
}

#available-widgets .widget-control-edit .edit,
#available-widgets .widget-action .edit,
#widgets-left .inactive-sidebar .widget-control-edit .add,
Expand Down
104 changes: 104 additions & 0 deletions tests/e2e/specs/widgets-classic.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* WordPress dependencies
*/
import { test, expect } from '@wordpress/e2e-test-utils-playwright';

test.describe( 'Classic widgets screen', () => {
test.beforeAll( async ( { requestUtils } ) => {
const pluginsMap = await requestUtils.getPluginsMap();
if ( ! pluginsMap[ 'classic-widgets' ] ) {
await requestUtils.rest( {
method: 'POST',
path: 'wp/v2/plugins?slug=classic-widgets',
} );
await requestUtils.getPluginsMap( /* forceRefetch */ true );
}
await requestUtils.activatePlugin( 'classic-widgets' );
await requestUtils.deleteAllWidgets();
const created = await requestUtils.rest( {
method: 'POST',
path: 'wp/v2/widgets',
data: {
id_base: 'search',
sidebar: 'sidebar-1',
instance: { raw: {} },
},
} );
await requestUtils.rest( {
method: 'POST',
path: 'wp/v2/sidebars/sidebar-1',
data: { widgets: [ created.id ] },
} );
} );

test.afterAll( async ( { requestUtils } ) => {
await requestUtils.deleteAllWidgets();
await requestUtils.deactivatePlugin( 'classic-widgets' );
} );

for ( const [ column, label ] of [
[ '#widgets-right', '.edit' ],
[ '#available-widgets', '.add' ],
] ) {
test( `${ column } link is only shown on focus`, async ( {
admin,
page,
} ) => {
await admin.visitAdminPage( 'widgets.php' );

const link = page
.locator( `${ column } .widget-control-edit` )
.first();

await expect( link ).toHaveClass( /screen-reader-text/ );
await expect( link ).toHaveAttribute( 'href', /editwidget=/ );
// boundingBox ignores clip-path, so check the clipping itself.
await expect( link ).toHaveCSS( 'clip-path', 'inset(50%)' );

await link.focus();

await expect( link ).toHaveCSS( 'clip-path', 'none' );
await expect( link ).not.toHaveCSS( 'box-shadow', 'none' );
expect( ( await link.boundingBox() ).width ).toBeGreaterThan( 20 );
expect(
( await link.locator( label ).boundingBox() ).width
).toBeGreaterThan( 10 );
} );
}

test( 'links follow a widget added without a reload', async ( {
admin,
page,
} ) => {
await admin.visitAdminPage( 'widgets.php' );
await page
.locator( '#available-widgets .widget-top' )
.filter( { hasText: 'Archives' } )
.click();
await page.locator( '.widgets-chooser-add' ).click();

const added = page.locator( '#widgets-right .widget' ).last();
await expect( added.locator( 'input.widget-id' ) ).toHaveValue(
/^archives-\d+$/
);
const addedId = await added.locator( 'input.widget-id' ).inputValue();

const addedLink = added.locator( '.widget-control-edit' );
await addedLink.focus();
await expect( addedLink ).toHaveAttribute(
'href',
new RegExp( `editwidget=${ addedId }&sidebar=sidebar-1&key=1$` )
);

const paletteWidget = page
.locator( '#available-widgets .widget' )
.filter( { hasText: 'Archives' } );
const nextNumber = await paletteWidget
.locator( 'input.multi_number' )
.inputValue();
await paletteWidget.locator( '.widget-control-edit' ).focus();
await expect(
paletteWidget.locator( '.widget-control-edit' )
).toHaveAttribute( 'href', new RegExp( `num=${ nextNumber }&` ) );
} );
} );
Loading