diff --git a/docs/reference-guides/interactivity-api/api-reference.md b/docs/reference-guides/interactivity-api/api-reference.md
index a898e437b40de..acfba8c48af66 100644
--- a/docs/reference-guides/interactivity-api/api-reference.md
+++ b/docs/reference-guides/interactivity-api/api-reference.md
@@ -220,10 +220,36 @@ The `wp-class` directive is executed:
- When the element is created
- Each time there's a change on any of the properties of the `state` or `context` involved in getting the final value of the directive (inside the callback or the expression passed as reference)
-When `wp-class` directive references a callback to get its final boolean value, the callback receives the class name: `className`.
-
The boolean value received by the directive is used to toggle (add when `true` or remove when `false`) the associated class name from the `class` attribute.
+It's important to note that when using the `wp-class` directive, it's recommended to use kebab-case for class names instead of camelCase. This is because HTML attributes are not case-sensitive, and HTML will treat `data-wp-class--isDark` the same as `data-wp-class--isdark` or `DATA-WP-CLASS--ISDARK`.
+
+So, for example, use the class name `is-dark` instead of `isDark` and `data-wp-class--is-dark` instead of `data-wp-class--isDark`:
+
+```html
+
+
+
+
+
+
+
+
+
+```
+
+```css
+/* Recommended */
+.is-dark {
+ /* ... */
+}
+
+/* Not recommended */
+.isDark {
+ /* ... */
+}
+```
+
### `wp-style`
This directive adds or removes inline style to an HTML element, depending on its value. It follows the syntax `data-wp-style--css-property`.
@@ -256,8 +282,6 @@ The `wp-style` directive is executed:
- When the element is created
- Each time there's a change on any of the properties of the `state` or `context` involved in getting the final value of the directive (inside the callback or the expression passed as reference)
-When `wp-style` directive references a callback to get its final value, the callback receives the class style property: `css-property`.
-
The value received by the directive is used to add or remove the style attribute with the associated CSS property:
- If the value is `false`, the style attribute is removed: ``
diff --git a/packages/components/src/tabs/styles.ts b/packages/components/src/tabs/styles.ts
index ce575c9f31779..83304a030330b 100644
--- a/packages/components/src/tabs/styles.ts
+++ b/packages/components/src/tabs/styles.ts
@@ -36,22 +36,20 @@ export const TabListWrapper = styled.div`
outline-offset: -1px;
}
&:not( [aria-orientation='vertical'] )::after {
- left: var( --indicator-left );
bottom: 0;
+ left: var( --indicator-left );
width: var( --indicator-width );
height: 0;
border-bottom: var( --wp-admin-border-width-focus ) solid
${ COLORS.theme.accent };
}
&[aria-orientation='vertical']::after {
- /* Temporarily hidden, context: https://github.com/WordPress/gutenberg/pull/60560#issuecomment-2126670072 */
- opacity: 0;
-
- right: 0;
+ z-index: -1;
+ left: 0;
+ width: 100%;
top: var( --indicator-top );
height: var( --indicator-height );
- border-right: var( --wp-admin-border-width-focus ) solid
- ${ COLORS.theme.accent };
+ background-color: ${ COLORS.theme.gray[ 100 ] };
}
`;
diff --git a/packages/dataviews/src/bulk-actions-toolbar.tsx b/packages/dataviews/src/bulk-actions-toolbar.tsx
index 50a1386aadec0..07edaa6cd394c 100644
--- a/packages/dataviews/src/bulk-actions-toolbar.tsx
+++ b/packages/dataviews/src/bulk-actions-toolbar.tsx
@@ -20,6 +20,7 @@ import { useRegistry } from '@wordpress/data';
import { ActionWithModal } from './item-actions';
import type { Action } from './types';
import type { ActionTriggerProps } from './item-actions';
+import type { SetSelection } from './private-types';
interface ActionButtonProps< Item > {
action: Action< Item >;
@@ -32,14 +33,14 @@ interface ToolbarContentProps< Item > {
selection: string[];
actionsToShow: Action< Item >[];
selectedItems: Item[];
- onSelectionChange: ( selection: Item[] ) => void;
+ onSelectionChange: SetSelection;
}
interface BulkActionsToolbarProps< Item > {
data: Item[];
selection: string[];
actions: Action< Item >[];
- onSelectionChange: ( selection: Item[] ) => void;
+ onSelectionChange: SetSelection;
getItemId: ( item: Item ) => string;
}
@@ -131,7 +132,7 @@ function renderToolbarContent< Item >(
selectedItems: Item[],
actionInProgress: string | null,
setActionInProgress: ( actionId: string | null ) => void,
- onSelectionChange: ( selection: Item[] ) => void
+ onSelectionChange: SetSelection
) {
return (
<>
diff --git a/packages/dataviews/src/bulk-actions.tsx b/packages/dataviews/src/bulk-actions.tsx
index 7f743bbeea1a2..adafbe5799ccb 100644
--- a/packages/dataviews/src/bulk-actions.tsx
+++ b/packages/dataviews/src/bulk-actions.tsx
@@ -15,6 +15,7 @@ import { useRegistry } from '@wordpress/data';
*/
import { unlock } from './lock-unlock';
import type { Action, ActionModal } from './types';
+import type { SetSelection } from './private-types';
const {
DropdownMenuV2: DropdownMenu,
@@ -46,7 +47,7 @@ interface BulkActionsProps< Item > {
data: Item[];
actions: Action< Item >[];
selection: string[];
- onSelectionChange: ( selection: Item[] ) => void;
+ onSelectionChange: SetSelection;
getItemId: ( item: Item ) => string;
}
@@ -248,7 +249,11 @@ export default function BulkActions< Item >( {
disabled={ areAllSelected }
hideOnClick={ false }
onClick={ () => {
- onSelectionChange( selectableItems );
+ onSelectionChange(
+ selectableItems.map( ( item ) =>
+ getItemId( item )
+ )
+ );
} }
suffix={ numberSelectableItems }
>
diff --git a/packages/dataviews/src/dataviews.tsx b/packages/dataviews/src/dataviews.tsx
index 476ed895ed529..f179ee71dcf04 100644
--- a/packages/dataviews/src/dataviews.tsx
+++ b/packages/dataviews/src/dataviews.tsx
@@ -7,7 +7,7 @@ import type { ComponentType } from 'react';
* WordPress dependencies
*/
import { __experimentalHStack as HStack } from '@wordpress/components';
-import { useMemo, useState, useCallback } from '@wordpress/element';
+import { useMemo, useState } from '@wordpress/element';
/**
* Internal dependencies
@@ -22,6 +22,7 @@ import BulkActions from './bulk-actions';
import { normalizeFields } from './normalize-fields';
import BulkActionsToolbar from './bulk-actions-toolbar';
import type { Action, Field, View, ViewBaseProps } from './types';
+import type { SetSelection, SelectionOrUpdater } from './private-types';
type ItemWithId = { id: string };
@@ -40,7 +41,7 @@ type DataViewsProps< Item > = {
};
supportedLayouts: string[];
selection?: string[];
- setSelection?: ( selection: string[] ) => void;
+ setSelection?: SetSelection;
onSelectionChange?: ( items: Item[] ) => void;
} & ( Item extends ItemWithId
? { getItemId?: ( item: Item ) => string }
@@ -83,26 +84,22 @@ export default function DataViews< Item >( {
onSelectionChange = defaultOnSelectionChange,
}: DataViewsProps< Item > ) {
const [ selectionState, setSelectionState ] = useState< string[] >( [] );
- let selection, setSelection;
- if (
- selectionProperty !== undefined &&
- setSelectionProperty !== undefined
- ) {
- selection = selectionProperty;
- setSelection = setSelectionProperty;
- } else {
- selection = selectionState;
- setSelection = setSelectionState;
- }
+ const isUncontrolled =
+ selectionProperty === undefined || setSelectionProperty === undefined;
+ const selection = isUncontrolled ? selectionState : selectionProperty;
+ const setSelection = isUncontrolled
+ ? setSelectionState
+ : setSelectionProperty;
const [ openedFilter, setOpenedFilter ] = useState< string | null >( null );
- const onSetSelection = useCallback(
- ( items: Item[] ) => {
- setSelection( items.map( ( item ) => getItemId( item ) ) );
- onSelectionChange( items );
- },
- [ setSelection, getItemId, onSelectionChange ]
- );
+ function setSelectionWithChange( value: SelectionOrUpdater ) {
+ const newValue =
+ typeof value === 'function' ? value( selection ) : value;
+ onSelectionChange(
+ data.filter( ( item ) => newValue.includes( getItemId( item ) ) )
+ );
+ return setSelection( value );
+ }
const ViewComponent = VIEW_LAYOUTS.find( ( v ) => v.type === view.type )
?.component as ComponentType< ViewBaseProps< Item > >;
@@ -149,7 +146,7 @@ export default function DataViews< Item >( {
@@ -168,7 +165,7 @@ export default function DataViews< Item >( {
getItemId={ getItemId }
isLoading={ isLoading }
onChangeView={ onChangeView }
- onSelectionChange={ onSetSelection }
+ onSelectionChange={ setSelectionWithChange }
selection={ _selection }
setOpenedFilter={ setOpenedFilter }
view={ view }
@@ -184,7 +181,7 @@ export default function DataViews< Item >( {
data={ data }
actions={ actions }
selection={ _selection }
- onSelectionChange={ onSetSelection }
+ onSelectionChange={ setSelectionWithChange }
getItemId={ getItemId }
/>
) }
diff --git a/packages/dataviews/src/private-types.tsx b/packages/dataviews/src/private-types.tsx
new file mode 100644
index 0000000000000..d2d453e63599c
--- /dev/null
+++ b/packages/dataviews/src/private-types.tsx
@@ -0,0 +1,2 @@
+export type SelectionOrUpdater = string[] | ( ( prev: string[] ) => string[] );
+export type SetSelection = ( selection: SelectionOrUpdater ) => void;
diff --git a/packages/dataviews/src/single-selection-checkbox.tsx b/packages/dataviews/src/single-selection-checkbox.tsx
index 84b359508663b..c8490a8c71c32 100644
--- a/packages/dataviews/src/single-selection-checkbox.tsx
+++ b/packages/dataviews/src/single-selection-checkbox.tsx
@@ -8,12 +8,12 @@ import { CheckboxControl } from '@wordpress/components';
* Internal dependencies
*/
import type { Field } from './types';
+import type { SetSelection } from './private-types';
interface SingleSelectionCheckboxProps< Item > {
selection: string[];
- onSelectionChange: ( selection: Item[] ) => void;
+ onSelectionChange: SetSelection;
item: Item;
- data: Item[];
getItemId: ( item: Item ) => string;
primaryField?: Field< Item >;
disabled: boolean;
@@ -23,23 +23,22 @@ export default function SingleSelectionCheckbox< Item >( {
selection,
onSelectionChange,
item,
- data,
getItemId,
primaryField,
disabled,
}: SingleSelectionCheckboxProps< Item > ) {
const id = getItemId( item );
- const isSelected = ! disabled && selection.includes( id );
+ const checked = ! disabled && selection.includes( id );
let selectionLabel;
if ( primaryField?.getValue && item ) {
// eslint-disable-next-line @wordpress/valid-sprintf
selectionLabel = sprintf(
/* translators: %s: item title. */
- isSelected ? __( 'Deselect item: %s' ) : __( 'Select item: %s' ),
+ checked ? __( 'Deselect item: %s' ) : __( 'Select item: %s' ),
primaryField.getValue( { item } )
);
} else {
- selectionLabel = isSelected
+ selectionLabel = checked
? __( 'Select a new item' )
: __( 'Deselect item' );
}
@@ -49,31 +48,17 @@ export default function SingleSelectionCheckbox< Item >( {
__nextHasNoMarginBottom
aria-label={ selectionLabel }
aria-disabled={ disabled }
- checked={ isSelected }
+ checked={ checked }
onChange={ () => {
if ( disabled ) {
return;
}
- if ( ! isSelected ) {
- onSelectionChange(
- data.filter( ( _item ) => {
- const itemId = getItemId?.( _item );
- return (
- itemId === id || selection.includes( itemId )
- );
- } )
- );
- } else {
- onSelectionChange(
- data.filter( ( _item ) => {
- const itemId = getItemId?.( _item );
- return (
- itemId !== id && selection.includes( itemId )
- );
- } )
- );
- }
+ onSelectionChange(
+ selection.includes( id )
+ ? selection.filter( ( itemId ) => id !== itemId )
+ : [ ...selection, id ]
+ );
} }
/>
);
diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts
index 76b514755056a..964523c72f8a6 100644
--- a/packages/dataviews/src/types.ts
+++ b/packages/dataviews/src/types.ts
@@ -3,6 +3,11 @@
*/
import type { ReactElement, ReactNode } from 'react';
+/**
+ * Internal dependencies
+ */
+import type { SetSelection } from './private-types';
+
export type SortDirection = 'asc' | 'desc';
/**
@@ -383,7 +388,7 @@ export interface ViewBaseProps< Item > {
getItemId: ( item: Item ) => string;
isLoading?: boolean;
onChangeView( view: View ): void;
- onSelectionChange: ( items: Item[] ) => void;
+ onSelectionChange: SetSelection;
selection: string[];
setOpenedFilter: ( fieldId: string ) => void;
view: View;
diff --git a/packages/dataviews/src/view-grid.tsx b/packages/dataviews/src/view-grid.tsx
index 8fa9d6413d851..56f856a5d82b2 100644
--- a/packages/dataviews/src/view-grid.tsx
+++ b/packages/dataviews/src/view-grid.tsx
@@ -23,11 +23,11 @@ import ItemActions from './item-actions';
import SingleSelectionCheckbox from './single-selection-checkbox';
import { useHasAPossibleBulkAction } from './bulk-actions';
import type { Action, NormalizedField, ViewGridProps } from './types';
+import type { SetSelection } from './private-types';
interface GridItemProps< Item > {
selection: string[];
- data: Item[];
- onSelectionChange: ( items: Item[] ) => void;
+ onSelectionChange: SetSelection;
getItemId: ( item: Item ) => string;
item: Item;
actions: Action< Item >[];
@@ -40,7 +40,6 @@ interface GridItemProps< Item > {
function GridItem< Item >( {
selection,
- data,
onSelectionChange,
getItemId,
item,
@@ -68,27 +67,11 @@ function GridItem< Item >( {
if ( ! hasBulkAction ) {
return;
}
- if ( ! isSelected ) {
- onSelectionChange(
- data.filter( ( _item ) => {
- const itemId = getItemId?.( _item );
- return (
- itemId === id ||
- selection.includes( itemId )
- );
- } )
- );
- } else {
- onSelectionChange(
- data.filter( ( _item ) => {
- const itemId = getItemId?.( _item );
- return (
- itemId !== id &&
- selection.includes( itemId )
- );
- } )
- );
- }
+ onSelectionChange(
+ selection.includes( id )
+ ? selection.filter( ( itemId ) => id !== itemId )
+ : [ ...selection, id ]
+ );
}
} }
>
@@ -104,7 +87,6 @@ function GridItem< Item >( {
selection={ selection }
onSelectionChange={ onSelectionChange }
getItemId={ getItemId }
- data={ data }
primaryField={ primaryField }
disabled={ ! hasBulkAction }
/>
@@ -239,7 +221,6 @@ export default function ViewGrid< Item >( {
( props: ViewListProps< Item > ) {
)
);
- const onSelect = useCallback(
- ( item: Item ) => onSelectionChange( [ item ] ),
- [ onSelectionChange ]
- );
+ const onSelect = ( item: Item ) =>
+ onSelectionChange( [ getItemId( item ) ] );
const getItemDomId = useCallback(
( item?: Item ) =>
diff --git a/packages/dataviews/src/view-table.tsx b/packages/dataviews/src/view-table.tsx
index f09b46733e1a8..fbd1400a301a1 100644
--- a/packages/dataviews/src/view-table.tsx
+++ b/packages/dataviews/src/view-table.tsx
@@ -51,6 +51,7 @@ import type {
ViewTable as ViewTableType,
ViewTableProps,
} from './types';
+import type { SetSelection } from './private-types';
const {
DropdownMenuV2: DropdownMenu,
@@ -71,7 +72,7 @@ interface HeaderMenuProps< Item > {
interface BulkSelectionCheckboxProps< Item > {
selection: string[];
- onSelectionChange: ( items: Item[] ) => void;
+ onSelectionChange: SetSelection;
data: Item[];
actions: Action< Item >[];
getItemId: ( item: Item ) => string;
@@ -86,8 +87,7 @@ interface TableRowProps< Item > {
primaryField?: NormalizedField< Item >;
selection: string[];
getItemId: ( item: Item ) => string;
- onSelectionChange: ( items: Item[] ) => void;
- data: Item[];
+ onSelectionChange: SetSelection;
}
function WithDropDownMenuSeparators( { children }: { children: ReactNode } ) {
@@ -276,7 +276,9 @@ function BulkSelectionCheckbox< Item >( {
if ( areAllSelected ) {
onSelectionChange( [] );
} else {
- onSelectionChange( selectableItems );
+ onSelectionChange(
+ selectableItems.map( ( item ) => getItemId( item ) )
+ );
}
} }
aria-label={
@@ -296,7 +298,6 @@ function TableRow< Item >( {
selection,
getItemId,
onSelectionChange,
- data,
}: TableRowProps< Item > ) {
const hasPossibleBulkAction = useHasAPossibleBulkAction( actions, item );
const isSelected = hasPossibleBulkAction && selection.includes( id );
@@ -336,27 +337,11 @@ function TableRow< Item >( {
! isTouchDevice.current &&
document.getSelection()?.type !== 'Range'
) {
- if ( ! isSelected ) {
- onSelectionChange(
- data.filter( ( _item ) => {
- const itemId = getItemId?.( _item );
- return (
- itemId === id ||
- selection.includes( itemId )
- );
- } )
- );
- } else {
- onSelectionChange(
- data.filter( ( _item ) => {
- const itemId = getItemId?.( _item );
- return (
- itemId !== id &&
- selection.includes( itemId )
- );
- } )
- );
- }
+ onSelectionChange(
+ selection.includes( id )
+ ? selection.filter( ( itemId ) => id !== itemId )
+ : [ ...selection, id ]
+ );
}
} }
>
@@ -373,7 +358,6 @@ function TableRow< Item >( {
selection={ selection }
onSelectionChange={ onSelectionChange }
getItemId={ getItemId }
- data={ data }
primaryField={ primaryField }
disabled={ ! hasPossibleBulkAction }
/>
@@ -579,7 +563,6 @@ function ViewTable< Item >( {
selection={ selection }
getItemId={ getItemId }
onSelectionChange={ onSelectionChange }
- data={ data }
/>
) ) }
diff --git a/packages/edit-post/src/style.scss b/packages/edit-post/src/style.scss
index a95c02242644b..f77c8122ca2e5 100644
--- a/packages/edit-post/src/style.scss
+++ b/packages/edit-post/src/style.scss
@@ -31,11 +31,13 @@ body.js.block-editor-page {
}
}
-// Target the editor UI excluding the visual editor contents, metaboxes and custom fields areas.
+// Target the editor UI excluding the non-iframed visual editor contents, metaboxes
+// and custom fields areas.
.editor-header,
.editor-text-editor,
.editor-sidebar,
-.editor-post-publish-panel {
+.editor-post-publish-panel,
+.edit-post-visual-editor.is-iframed {
@include reset;
}
diff --git a/packages/interactivity/src/hooks.tsx b/packages/interactivity/src/hooks.tsx
index d8aa43d2c0e5a..9af6fb00d6aba 100644
--- a/packages/interactivity/src/hooks.tsx
+++ b/packages/interactivity/src/hooks.tsx
@@ -231,7 +231,7 @@ const directivePriorities: Record< string, number > = {};
* ```js
* directive(
* 'color', // Name without prefix and suffix.
- * ( { directives: { color }, ref, evaluate } ) =>
+ * ( { directives: { color: colors }, ref, evaluate } ) =>
* colors.forEach( ( color ) => {
* if ( color.suffix = 'text' ) {
* ref.style.setProperty(
diff --git a/packages/preferences/src/components/preferences-modal-tabs/style.scss b/packages/preferences/src/components/preferences-modal-tabs/style.scss
index e20b9aa9064ed..247da29eb5d12 100644
--- a/packages/preferences/src/components/preferences-modal-tabs/style.scss
+++ b/packages/preferences/src/components/preferences-modal-tabs/style.scss
@@ -6,31 +6,6 @@ $vertical-tabs-width: 160px;
// Aligns button text instead of button box.
left: $grid-unit-20;
width: $vertical-tabs-width;
-
- &::after {
- content: none !important;
- }
-}
-
-.preferences__tabs-tab {
- border-radius: $radius-block-ui;
- font-weight: 400;
-
- &[aria-selected="true"] {
- background: $gray-100;
- box-shadow: none;
- font-weight: 500;
- }
-
- &[role="tab"]:focus:not(:disabled) {
- box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color);
- // Windows high contrast mode.
- outline: 2px solid transparent;
- }
-
- &:focus-visible::before {
- content: none;
- }
}
.preferences__tabs-tabpanel {
diff --git a/packages/project-management-automation/action.yml b/packages/project-management-automation/action.yml
index 34490f539875c..cf7f23a2174f2 100644
--- a/packages/project-management-automation/action.yml
+++ b/packages/project-management-automation/action.yml
@@ -6,5 +6,5 @@ inputs:
description: Secret GitHub API token to use for making API requests.
required: true
runs:
- using: node12
+ using: node20
main: lib/index.js
diff --git a/test/e2e/specs/site-editor/block-style-variations.spec.js b/test/e2e/specs/site-editor/block-style-variations.spec.js
index fedcd0946eac8..6d139dd755b29 100644
--- a/test/e2e/specs/site-editor/block-style-variations.spec.js
+++ b/test/e2e/specs/site-editor/block-style-variations.spec.js
@@ -149,12 +149,14 @@ test.describe( 'Block Style Variations', () => {
},
},
} );
- // The save button has been re-enabled.
+
+ // Wait for the save button to be re-enabled.
await expect(
page
.getByRole( 'region', { name: 'Editor top bar' } )
.getByRole( 'button', { name: 'Save' } )
- ).toBeEnabled();
+ ).toBeVisible();
+
// Second revision (current).
await siteEditorBlockStyleVariations.saveRevision( stylesPostId, {
blocks: {