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: {