diff --git a/packages/dataviews/CHANGELOG.md b/packages/dataviews/CHANGELOG.md
index 0498418d90bc43..03dae9f85c1df7 100644
--- a/packages/dataviews/CHANGELOG.md
+++ b/packages/dataviews/CHANGELOG.md
@@ -15,6 +15,7 @@
### Enhancements
+- DataViews table layout: remove row click-to-select behavior and hover styles. Selection is now only possible via checkboxes, or by ctrl/cmd clicking. [#73873](https://github.com/WordPress/gutenberg/pull/73873)
- Better labels for operators and deprecate the `isNotAll` operator. [#73671](https://github.com/WordPress/gutenberg/pull/73671)
- Field API: move validation to the field type. [#73642](https://github.com/WordPress/gutenberg/pull/73642)
- DataForm: add support for `min`/`max` and `minLength`/`maxLength` validation for relevant controls. [#73465](https://github.com/WordPress/gutenberg/pull/73465)
diff --git a/packages/dataviews/src/dataviews-layouts/table/index.tsx b/packages/dataviews/src/dataviews-layouts/table/index.tsx
index bc97a8b975f42d..1d98e2b3c95851 100644
--- a/packages/dataviews/src/dataviews-layouts/table/index.tsx
+++ b/packages/dataviews/src/dataviews-layouts/table/index.tsx
@@ -122,20 +122,12 @@ function TableRow< Item >( {
const { paginationInfo } = useContext( DataViewsContext );
const hasPossibleBulkAction = useHasAPossibleBulkAction( actions, item );
const isSelected = hasPossibleBulkAction && selection.includes( id );
- const [ isHovered, setIsHovered ] = useState( false );
const {
showTitle = true,
showMedia = true,
showDescription = true,
infiniteScrollEnabled,
} = view;
- const handleMouseEnter = () => {
- setIsHovered( true );
- };
- const handleMouseLeave = () => {
- setIsHovered( false );
- };
-
// Will be set to true if `onTouchStart` fires. This happens before
// `onClick` and can be used to exclude touchscreen devices from certain
// behaviours.
@@ -150,11 +142,8 @@ function TableRow< Item >( {
{
isTouchDeviceRef.current = true;
} }
@@ -183,29 +172,22 @@ function TableRow< Item >( {
return;
}
+ // Only handle Ctrl/Cmd+Click for multi-selection
+ const isModifierKeyPressed = isAppleOS()
+ ? event.metaKey
+ : event.ctrlKey;
+
if (
+ isModifierKeyPressed &&
! isTouchDeviceRef.current &&
document.getSelection()?.type !== 'Range'
) {
- if ( isAppleOS() ? event.metaKey : event.ctrlKey ) {
- // Handle non-consecutive selection.
- onChangeSelection(
- selection.includes( id )
- ? selection.filter(
- ( itemId ) => id !== itemId
- )
- : [ ...selection, id ]
- );
- } else {
- // Handle single selection
- onChangeSelection(
- selection.includes( id )
- ? selection.filter(
- ( itemId ) => id !== itemId
- )
- : [ id ]
- );
- }
+ // Handle non-consecutive selection with Ctrl/Cmd+Click
+ onChangeSelection(
+ selection.includes( id )
+ ? selection.filter( ( itemId ) => id !== itemId )
+ : [ ...selection, id ]
+ );
}
} }
>
diff --git a/packages/dataviews/src/dataviews-layouts/table/style.scss b/packages/dataviews/src/dataviews-layouts/table/style.scss
index 00e7ddda560fb9..a69848aa1adf2b 100644
--- a/packages/dataviews/src/dataviews-layouts/table/style.scss
+++ b/packages/dataviews/src/dataviews-layouts/table/style.scss
@@ -30,7 +30,7 @@
&.dataviews-view-table__actions-column--sticky {
position: sticky;
right: 0;
- background-color: inherit;
+ background-color: var(--wp-dataviews-color-background, $white);
}
&.dataviews-view-table__actions-column--stuck {
@@ -73,11 +73,6 @@
border-bottom: 0;
}
- &.is-hovered,
- &.is-hovered .dataviews-view-table__actions-column--sticky {
- background-color: #f8f8f8;
- }
-
.dataviews-item-actions .components-button:not(.dataviews-all-actions-button) {
opacity: 0;
}
@@ -98,38 +93,31 @@
border-top: 1px solid rgba(var(--wp-admin-theme-color--rgb), 0.12);
}
- &:hover {
- background-color: color-mix(in srgb, rgb(var(--wp-admin-theme-color--rgb)) 8%, $white);
- }
-
.dataviews-view-table__actions-column--sticky {
background-color: color-mix(in srgb, rgb(var(--wp-admin-theme-color--rgb)) 4%, $white);
}
-
- &:hover .dataviews-view-table__actions-column--sticky {
- background-color: color-mix(in srgb, rgb(var(--wp-admin-theme-color--rgb)) 8%, $white);
- }
}
}
- // Hover styles only apply when bulk actions are available
+ // Show interactive elements on focus
&.has-bulk-actions {
tr {
- // Only apply hover background to non-selected rows
- &:not(.is-selected) {
- &.is-hovered,
- &.is-hovered .dataviews-view-table__actions-column--sticky {
- background-color: #f8f8f8;
- }
- }
-
&:focus-within,
- &.is-hovered,
&:hover {
- .components-checkbox-control__input,
.dataviews-item-actions .components-button:not(.dataviews-all-actions-button) {
opacity: 1;
}
+
+ .dataviews-view-table__actions-column--sticky {
+ background-color: var(--wp-dataviews-color-background, $white);
+ }
+ }
+
+ &.is-selected,
+ &.is-selected:hover {
+ .dataviews-view-table__actions-column--sticky {
+ background-color: color-mix(in srgb, rgb(var(--wp-admin-theme-color--rgb)) 4%, $white);
+ }
}
}
}
diff --git a/packages/dataviews/src/test/dataviews.tsx b/packages/dataviews/src/test/dataviews.tsx
index aef345483b8d2a..67cd7eb1bffe6d 100644
--- a/packages/dataviews/src/test/dataviews.tsx
+++ b/packages/dataviews/src/test/dataviews.tsx
@@ -302,35 +302,6 @@ describe( 'DataViews component', () => {
expect( onClickItemCallback ).toHaveBeenCalledWith( data[ 0 ] );
} );
- it( 'accepts click for single selection', async () => {
- render(
-
- );
- const firstItemElement = screen.getByText( data[ 0 ].title );
- const thirdItemElement = screen.getByText( data[ 2 ].title );
- const user = userEvent.setup();
- await user.click( firstItemElement );
-
- // First item should be selected.
- expect(
- screen.getByRole( 'checkbox', { name: data[ 0 ].title } )
- ).toBeChecked();
- await user.click( thirdItemElement );
-
- // Third item should be selected. First item was deselected.
- expect(
- screen.getByRole( 'checkbox', { name: data[ 2 ].title } )
- ).toBeChecked();
- } );
-
it( 'accepts ctrl/cmd key and click for non-consecutive multi-selection', async () => {
render(
{
const firstItemElement = screen.getByText( data[ 0 ].title );
const thirdItemElement = screen.getByText( data[ 2 ].title );
const user = userEvent.setup();
+ await user.keyboard( '{Control>}' );
await user.click( firstItemElement );
// First item should be selected.
expect(
screen.getByRole( 'checkbox', { name: data[ 0 ].title } )
).toBeChecked();
- await user.keyboard( '{Control>}' );
await user.click( thirdItemElement );
// Both items should be selected.
@@ -445,66 +416,65 @@ describe( 'DataViews component', () => {
expect( mediaClickItemCallback ).toHaveBeenCalledWith( data[ 0 ] );
} );
- it( 'accepts click for single selection', async () => {
+ it( 'accepts checkbox click for selection', async () => {
render(
);
- const firstItemElement = screen.getByText( data[ 0 ].title );
- const thirdItemElement = screen.getByText( data[ 2 ].title );
+ const firstCheckbox = screen.getByRole( 'checkbox', {
+ name: data[ 0 ].title,
+ } );
+ const thirdCheckbox = screen.getByRole( 'checkbox', {
+ name: data[ 2 ].title,
+ } );
const user = userEvent.setup();
- await user.click( firstItemElement );
+ await user.click( firstCheckbox );
// First item should be selected.
- expect(
- screen.getByRole( 'checkbox', { name: data[ 0 ].title } )
- ).toBeChecked();
- await user.click( thirdItemElement );
+ expect( firstCheckbox ).toBeChecked();
+ await user.click( thirdCheckbox );
- // Third item should be selected. First item was deselected.
- expect(
- screen.getByRole( 'checkbox', { name: data[ 2 ].title } )
- ).toBeChecked();
+ // Both items should be selected (checkboxes toggle independently).
+ expect( firstCheckbox ).toBeChecked();
+ expect( thirdCheckbox ).toBeChecked();
} );
- it( 'accepts ctrl/cmd key and click for non-consecutive multi-selection', async () => {
+ it( 'accepts ctrl/cmd key and click for multi-selection', async () => {
render(
false }
/>
);
- const firstItemElement = screen.getByText( data[ 0 ].title );
- const thirdItemElement = screen.getByText( data[ 2 ].title );
+ // Click on the gridcell directly (not wrapped in ItemClickWrapper)
+ const firstItemCard = screen.getByRole( 'gridcell', {
+ name: /Hello World/,
+ } );
const user = userEvent.setup();
- await user.click( firstItemElement );
-
- // First item should be selected.
- expect(
- screen.getByRole( 'checkbox', { name: data[ 0 ].title } )
- ).toBeChecked();
await user.keyboard( '{Control>}' );
- await user.click( thirdItemElement );
+ await user.click( firstItemCard );
- // Both items should be selected.
+ // First item should be selected.
expect(
screen.getByRole( 'checkbox', { name: data[ 0 ].title } )
).toBeChecked();
- expect(
- screen.getByRole( 'checkbox', { name: data[ 2 ].title } )
- ).toBeChecked();
await user.keyboard( '{/Control}' );
} );