From 9f2211c894fc33d99e92f504e0befcec02381d11 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Tue, 3 Dec 2024 15:44:58 +0100 Subject: [PATCH 01/14] Initial bulk editing changes --- packages/dataviews/src/constants.ts | 3 + packages/dataviews/src/normalize-fields.ts | 7 ++ packages/dataviews/src/types.ts | 8 +- .../src/components/post-edit/index.js | 92 ++++++++----------- 4 files changed, 56 insertions(+), 54 deletions(-) diff --git a/packages/dataviews/src/constants.ts b/packages/dataviews/src/constants.ts index 5ae94c7eb4a13..fa5a9cd179de9 100644 --- a/packages/dataviews/src/constants.ts +++ b/packages/dataviews/src/constants.ts @@ -68,3 +68,6 @@ export const sortIcons = { export const LAYOUT_TABLE = 'table'; export const LAYOUT_GRID = 'grid'; export const LAYOUT_LIST = 'list'; + +// Dataform mixed value. +export const MIXED_VALUE = Symbol.for( 'DATAFORM_MIXED_VALUE' ); diff --git a/packages/dataviews/src/normalize-fields.ts b/packages/dataviews/src/normalize-fields.ts index 2ed87cbe11222..c32fcf81e9ae7 100644 --- a/packages/dataviews/src/normalize-fields.ts +++ b/packages/dataviews/src/normalize-fields.ts @@ -64,6 +64,12 @@ export function normalizeFields< Item >( ); }; + let supportsBulkEditing = true; + // If custom Edit component is passed in we default to false for bulk edit support. + if ( typeof field.Edit === 'function' || field.supportsBulkEditing ) { + supportsBulkEditing = field.supportsBulkEditing ?? false; + } + const render = field.render || ( field.elements ? renderFromElements : getValue ); @@ -76,6 +82,7 @@ export function normalizeFields< Item >( sort, isValid, Edit, + supportsBulkEditing, enableHiding: field.enableHiding ?? true, enableSorting: field.enableSorting ?? true, }; diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts index 96fd4a8cd01af..98d4ae3a4da28 100644 --- a/packages/dataviews/src/types.ts +++ b/packages/dataviews/src/types.ts @@ -158,6 +158,11 @@ export type Field< Item > = { * Defaults to `item[ field.id ]`. */ getValue?: ( args: { item: Item } ) => any; + + /** + * Whether the field supports bulk editing. + */ + supportsBulkEditing?: boolean; }; export type NormalizedField< Item > = Field< Item > & { @@ -539,7 +544,7 @@ export type Form = { }; export interface DataFormProps< Item > { - data: Item; + data: Item | Item[]; fields: Field< Item >[]; form: Form; onChange: ( value: Record< string, any > ) => void; @@ -550,4 +555,5 @@ export interface FieldLayoutProps< Item > { field: FormField; onChange: ( value: any ) => void; hideLabelFromVision?: boolean; + isBulkEditing?: boolean; } diff --git a/packages/edit-site/src/components/post-edit/index.js b/packages/edit-site/src/components/post-edit/index.js index 97484c89ed670..3cd85049887b4 100644 --- a/packages/edit-site/src/components/post-edit/index.js +++ b/packages/edit-site/src/components/post-edit/index.js @@ -11,7 +11,7 @@ import { DataForm } from '@wordpress/dataviews'; import { useDispatch, useSelect } from '@wordpress/data'; import { store as coreDataStore } from '@wordpress/core-data'; import { __experimentalVStack as VStack } from '@wordpress/components'; -import { useState, useMemo, useEffect } from '@wordpress/element'; +import { useMemo } from '@wordpress/element'; import { privateApis as editorPrivateApis } from '@wordpress/editor'; /** @@ -24,17 +24,35 @@ import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; const { usePostFields, PostCardPanel } = unlock( editorPrivateApis ); -const fieldsWithBulkEditSupport = [ - 'title', - 'status', - 'date', - 'author', - 'comment_status', -]; +const DATAFORM_CONFIG = { + type: 'panel', + fields: [ + { + id: 'featured_media', + layout: 'regular', + }, + { + id: 'status', + label: __( 'Status & Visibility' ), + children: [ 'status', 'password' ], + }, + 'author', + 'date', + 'slug', + 'parent', + 'comment_status', + { + label: __( 'Template' ), + labelPosition: 'side', + id: 'template', + layout: 'regular', + }, + ], +}; function PostEditForm( { postType, postId } ) { const ids = useMemo( () => postId.split( ',' ), [ postId ] ); - const { record, hasFinishedResolution } = useSelect( + const { record, hasFinishedResolution, records } = useSelect( ( select ) => { const args = [ 'postType', postType, ids[ 0 ] ]; @@ -50,11 +68,20 @@ function PostEditForm( { postType, postId } ) { 'getEditedEntityRecord', args ), + records: + ids.length > 1 + ? ids.map( ( id ) => + select( coreDataStore ).getEditedEntityRecord( + 'postType', + postType, + id + ) + ) + : null, }; }, [ postType, ids ] ); - const [ multiEdits, setMultiEdits ] = useState( {} ); const { editEntityRecord } = useDispatch( coreDataStore ); const { fields: _fields } = usePostFields( { postType } ); const fields = useMemo( @@ -73,38 +100,6 @@ function PostEditForm( { postType, postId } ) { [ _fields ] ); - const form = useMemo( - () => ( { - type: 'panel', - fields: [ - { - id: 'featured_media', - layout: 'regular', - }, - { - id: 'status', - label: __( 'Status & Visibility' ), - children: [ 'status', 'password' ], - }, - 'author', - 'date', - 'slug', - 'parent', - 'comment_status', - { - label: __( 'Template' ), - labelPosition: 'side', - id: 'template', - layout: 'regular', - }, - ].filter( - ( field ) => - ids.length === 1 || - fieldsWithBulkEditSupport.includes( field ) - ), - } ), - [ ids ] - ); const onChange = ( edits ) => { for ( const id of ids ) { if ( @@ -123,17 +118,8 @@ function PostEditForm( { postType, postId } ) { edits.password = ''; } editEntityRecord( 'postType', postType, id, edits ); - if ( ids.length > 1 ) { - setMultiEdits( ( prev ) => ( { - ...prev, - ...edits, - } ) ); - } } }; - useEffect( () => { - setMultiEdits( {} ); - }, [ ids ] ); const { ExperimentalBlockEditorProvider } = unlock( blockEditorPrivateApis @@ -166,9 +152,9 @@ function PostEditForm( { postType, postId } ) { { hasFinishedResolution && ( ) } From 6072d55beed012d7db41d8eb768b7b032a1d6fbf Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Wed, 4 Dec 2024 10:46:07 +0100 Subject: [PATCH 02/14] Update fields to allow for array of form items --- .../dataform/stories/index.story.tsx | 10 ++--- .../src/dataform-controls/datetime.tsx | 5 +-- .../src/dataform-controls/integer.tsx | 5 +-- .../dataviews/src/dataform-controls/radio.tsx | 3 +- .../src/dataform-controls/select.tsx | 5 +-- .../dataviews/src/dataform-controls/text.tsx | 3 +- .../dataforms-layouts/data-form-layout.tsx | 38 +++++++++++++++++-- .../src/dataforms-layouts/panel/index.tsx | 29 +++++++++++++- .../src/dataforms-layouts/regular/index.tsx | 25 ++++++++++++ packages/dataviews/src/types.ts | 5 ++- .../src/components/post-edit/index.js | 6 ++- .../featured-image/featured-image-edit.tsx | 4 +- .../fields/src/fields/parent/parent-edit.tsx | 1 + packages/fields/src/fields/password/edit.tsx | 22 ++++------- packages/fields/src/fields/slug/slug-edit.tsx | 1 + .../src/fields/template/template-edit.tsx | 1 + 16 files changed, 118 insertions(+), 45 deletions(-) diff --git a/packages/dataviews/src/components/dataform/stories/index.story.tsx b/packages/dataviews/src/components/dataform/stories/index.story.tsx index ecad2af43fb84..8ee86444293ff 100644 --- a/packages/dataviews/src/components/dataform/stories/index.story.tsx +++ b/packages/dataviews/src/components/dataform/stories/index.story.tsx @@ -107,16 +107,14 @@ const fields = [ id: 'sticky', label: 'Sticky', type: 'integer', - Edit: ( { field, onChange, data, hideLabelFromVision } ) => { - const { id, getValue } = field; + Edit: ( { field, onChange, value, hideLabelFromVision } ) => { + const { id } = field; return ( - onChange( { [ id ]: ! getValue( { item: data } ) } ) - } + checked={ value } + onChange={ () => onChange( { [ id ]: ! value } ) } /> ); }, diff --git a/packages/dataviews/src/dataform-controls/datetime.tsx b/packages/dataviews/src/dataform-controls/datetime.tsx index b31c5c60bb9c6..53dc2b8187f4a 100644 --- a/packages/dataviews/src/dataform-controls/datetime.tsx +++ b/packages/dataviews/src/dataform-controls/datetime.tsx @@ -10,13 +10,12 @@ import { useCallback } from '@wordpress/element'; import type { DataFormControlProps } from '../types'; export default function DateTime< Item >( { - data, field, onChange, hideLabelFromVision, + value, }: DataFormControlProps< Item > ) { const { id, label } = field; - const value = field.getValue( { item: data } ); const onChangeControl = useCallback( ( newValue: string | null ) => onChange( { [ id ]: newValue } ), @@ -34,7 +33,7 @@ export default function DateTime< Item >( { { label } ) } diff --git a/packages/dataviews/src/dataform-controls/integer.tsx b/packages/dataviews/src/dataform-controls/integer.tsx index f70a90ffe1523..d40bb7dd098bd 100644 --- a/packages/dataviews/src/dataform-controls/integer.tsx +++ b/packages/dataviews/src/dataform-controls/integer.tsx @@ -10,13 +10,12 @@ import { useCallback } from '@wordpress/element'; import type { DataFormControlProps } from '../types'; export default function Integer< Item >( { - data, field, onChange, hideLabelFromVision, + value, }: DataFormControlProps< Item > ) { const { id, label, description } = field; - const value = field.getValue( { item: data } ) ?? ''; const onChangeControl = useCallback( ( newValue: string | undefined ) => onChange( { @@ -29,7 +28,7 @@ export default function Integer< Item >( { ( { - data, field, onChange, hideLabelFromVision, + value, }: DataFormControlProps< Item > ) { const { id, label } = field; - const value = field.getValue( { item: data } ); const onChangeControl = useCallback( ( newValue: string ) => diff --git a/packages/dataviews/src/dataform-controls/select.tsx b/packages/dataviews/src/dataform-controls/select.tsx index 2b3bd9373fc15..eca0323db8f5d 100644 --- a/packages/dataviews/src/dataform-controls/select.tsx +++ b/packages/dataviews/src/dataform-controls/select.tsx @@ -11,13 +11,12 @@ import { __ } from '@wordpress/i18n'; import type { DataFormControlProps } from '../types'; export default function Select< Item >( { - data, field, onChange, hideLabelFromVision, + value, }: DataFormControlProps< Item > ) { const { id, label } = field; - const value = field.getValue( { item: data } ) ?? ''; const onChangeControl = useCallback( ( newValue: any ) => onChange( { @@ -41,7 +40,7 @@ export default function Select< Item >( { return ( ( { - data, field, onChange, hideLabelFromVision, + value, }: DataFormControlProps< Item > ) { const { id, label, placeholder } = field; - const value = field.getValue( { item: data } ); const onChangeControl = useCallback( ( newValue: string ) => diff --git a/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx b/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx index 08cc47f569eaf..f11b430805390 100644 --- a/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx +++ b/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx @@ -7,24 +7,43 @@ import { useContext, useMemo } from '@wordpress/element'; /** * Internal dependencies */ -import type { Form, FormField, SimpleFormField } from '../types'; +import type { + CombinedFormField, + Form, + FormField, + NormalizedField, + SimpleFormField, +} from '../types'; import { getFormFieldLayout } from './index'; import DataFormContext from '../components/dataform-context'; import { isCombinedField } from './is-combined-field'; import normalizeFormFields from '../normalize-form-fields'; +function doesCombinedFieldSupportBulkEdits< Item >( + combinedField: CombinedFormField, + fieldDefinitions: NormalizedField< Item >[] +): boolean { + return combinedField.children.some( ( child ) => { + const fieldId = typeof child === 'string' ? child : child.id; + + return fieldDefinitions.find( + ( fieldDefinition ) => fieldDefinition.id === fieldId + )?.supportsBulkEditing; + } ); +} + export function DataFormLayout< Item >( { data, form, onChange, children, }: { - data: Item; + data: Item | Item[]; form: Form; onChange: ( value: any ) => void; children?: ( FieldLayout: ( props: { - data: Item; + data: Item | Item[]; field: FormField; onChange: ( value: any ) => void; hideLabelFromVision?: boolean; @@ -69,6 +88,19 @@ export function DataFormLayout< Item >( { return null; } + if ( + Array.isArray( data ) && + ( ( isCombinedField( formField ) && + ! doesCombinedFieldSupportBulkEdits( + formField, + fieldDefinitions + ) ) || + ( fieldDefinition && + ! fieldDefinition.supportsBulkEditing ) ) + ) { + return null; + } + if ( children ) { return children( FieldLayout, formField ); } diff --git a/packages/dataviews/src/dataforms-layouts/panel/index.tsx b/packages/dataviews/src/dataforms-layouts/panel/index.tsx index 269b2bb418a85..bb2058080115e 100644 --- a/packages/dataviews/src/dataforms-layouts/panel/index.tsx +++ b/packages/dataviews/src/dataforms-layouts/panel/index.tsx @@ -26,6 +26,7 @@ import type { import DataFormContext from '../../components/dataform-context'; import { DataFormLayout } from '../data-form-layout'; import { isCombinedField } from '../is-combined-field'; +import { MIXED_VALUE } from '../../constants'; function DropdownHeader( { title, @@ -70,7 +71,7 @@ function PanelDropdown< Item >( { fieldDefinition: NormalizedField< Item >; popoverAnchor: HTMLElement | null; labelPosition: 'side' | 'top' | 'none'; - data: Item; + data: Item | Item[]; onChange: ( value: any ) => void; field: FormField; } ) { @@ -98,6 +99,22 @@ function PanelDropdown< Item >( { }; }, [ field ] ); + const fieldValue = useMemo( () => { + if ( Array.isArray( data ) ) { + const [ firstRecord, ...remainingRecords ] = data; + const firstValue = fieldDefinition.getValue( { + item: firstRecord, + } ); + const intersects = remainingRecords.every( ( item ) => { + return fieldDefinition.getValue( { item } ) === firstValue; + } ); + return intersects ? firstValue : MIXED_VALUE; + } + return fieldDefinition.getValue( { + item: data, + } ); + }, [ data, fieldDefinition ] ); + // Memoize popoverProps to avoid returning a new object every time. const popoverProps = useMemo( () => ( { @@ -111,6 +128,8 @@ function PanelDropdown< Item >( { [ popoverAnchor ] ); + const showMixedValue = Array.isArray( data ) && fieldValue === MIXED_VALUE; + return ( ( { ) } onClick={ onToggle } > - + { showMixedValue ? ( + __( 'Mixed' ) + ) : ( + + ) } ) } renderContent={ ( { onClose } ) => ( diff --git a/packages/dataviews/src/dataforms-layouts/regular/index.tsx b/packages/dataviews/src/dataforms-layouts/regular/index.tsx index a3d90b807b5cd..a497b2070bbcf 100644 --- a/packages/dataviews/src/dataforms-layouts/regular/index.tsx +++ b/packages/dataviews/src/dataforms-layouts/regular/index.tsx @@ -16,6 +16,7 @@ import type { Form, FieldLayoutProps } from '../../types'; import DataFormContext from '../../components/dataform-context'; import { DataFormLayout } from '../data-form-layout'; import { isCombinedField } from '../is-combined-field'; +import { MIXED_VALUE } from '../../constants'; function Header( { title }: { title: string } ) { return ( @@ -59,6 +60,28 @@ export default function FormRegularField< Item >( { }; }, [ field ] ); + const fieldValue = useMemo( () => { + const fieldDefinition = fields.find( + ( fieldDef ) => fieldDef.id === field.id + ); + if ( ! fieldDefinition ) { + return undefined; + } + if ( Array.isArray( data ) ) { + const [ firstRecord, ...remainingRecords ] = data; + const firstValue = fieldDefinition.getValue( { + item: firstRecord, + } ); + const intersects = remainingRecords.every( ( item ) => { + return fieldDefinition.getValue( { item } ) === firstValue; + } ); + return intersects ? firstValue : MIXED_VALUE; + } + return fieldDefinition.getValue( { + item: data, + } ); + }, [ data, fields, field.id ] ); + if ( isCombinedField( field ) ) { return ( <> @@ -95,6 +118,7 @@ export default function FormRegularField< Item >( { field={ fieldDefinition } onChange={ onChange } hideLabelFromVision + value={ fieldValue } /> @@ -105,6 +129,7 @@ export default function FormRegularField< Item >( {
= Field< Item >[]; export type Data< Item > = Item[]; export type DataFormControlProps< Item > = { - data: Item; + data: Item | Item[]; field: NormalizedField< Item >; onChange: ( value: Record< string, any > ) => void; hideLabelFromVision?: boolean; + value: any; }; export type DataViewRenderFieldProps< Item > = { @@ -551,7 +552,7 @@ export interface DataFormProps< Item > { } export interface FieldLayoutProps< Item > { - data: Item; + data: Item | Item[]; field: FormField; onChange: ( value: any ) => void; hideLabelFromVision?: boolean; diff --git a/packages/edit-site/src/components/post-edit/index.js b/packages/edit-site/src/components/post-edit/index.js index 3cd85049887b4..5e90fa5a929ec 100644 --- a/packages/edit-site/src/components/post-edit/index.js +++ b/packages/edit-site/src/components/post-edit/index.js @@ -102,10 +102,12 @@ function PostEditForm( { postType, postId } ) { const onChange = ( edits ) => { for ( const id of ids ) { + const editedRecord = + ids.length > 1 ? records.find( ( r ) => r.id === +id ) : record; if ( edits.status && edits.status !== 'future' && - record?.status === 'future' && + editedRecord?.status === 'future' && new Date( record.date ) > new Date() ) { edits.date = null; @@ -113,7 +115,7 @@ function PostEditForm( { postType, postId } ) { if ( edits.status && edits.status === 'private' && - record.password + editedRecord.password ) { edits.password = ''; } diff --git a/packages/fields/src/fields/featured-image/featured-image-edit.tsx b/packages/fields/src/fields/featured-image/featured-image-edit.tsx index ee51e5c60f13e..07c1deed2dd19 100644 --- a/packages/fields/src/fields/featured-image/featured-image-edit.tsx +++ b/packages/fields/src/fields/featured-image/featured-image-edit.tsx @@ -17,14 +17,12 @@ import { __ } from '@wordpress/i18n'; import type { BasePost } from '../../types'; export const FeaturedImageEdit = ( { - data, field, onChange, + value, }: DataFormControlProps< BasePost > ) => { const { id } = field; - const value = field.getValue( { item: data } ); - const media = useSelect( ( select ) => { const { getEntityRecord } = select( coreStore ); diff --git a/packages/fields/src/fields/parent/parent-edit.tsx b/packages/fields/src/fields/parent/parent-edit.tsx index 21cdbee7a365a..4179cdb7e5fc5 100644 --- a/packages/fields/src/fields/parent/parent-edit.tsx +++ b/packages/fields/src/fields/parent/parent-edit.tsx @@ -287,6 +287,7 @@ export const ParentEdit = ( { onChange, }: DataFormControlProps< BasePost > ) => { const { id } = field; + data = Array.isArray( data ) ? data[ 0 ] : data; const homeUrl = useSelect( ( select ) => { // @ts-expect-error getEntityRecord is not typed with unstableBase as argument. diff --git a/packages/fields/src/fields/password/edit.tsx b/packages/fields/src/fields/password/edit.tsx index 75226c1f93f15..3c63be5a2d596 100644 --- a/packages/fields/src/fields/password/edit.tsx +++ b/packages/fields/src/fields/password/edit.tsx @@ -15,18 +15,12 @@ import { __ } from '@wordpress/i18n'; */ import type { BasePost } from '../../types'; -function PasswordEdit( { - data, - onChange, - field, -}: DataFormControlProps< BasePost > ) { - const [ showPassword, setShowPassword ] = useState( - !! field.getValue( { item: data } ) - ); +function PasswordEdit( { onChange, value }: DataFormControlProps< BasePost > ) { + const [ showPassword, setShowPassword ] = useState( !! value ); - const handleTogglePassword = ( value: boolean ) => { - setShowPassword( value ); - if ( ! value ) { + const handleTogglePassword = ( newValue: boolean ) => { + setShowPassword( newValue ); + if ( ! newValue ) { onChange( { password: '' } ); } }; @@ -48,12 +42,12 @@ function PasswordEdit( {
+ onChange={ ( newValue ) => onChange( { - password: value, + password: newValue, } ) } - value={ field.getValue( { item: data } ) || '' } + value={ value || '' } placeholder={ __( 'Use a secure password' ) } type="text" __next40pxDefaultSize diff --git a/packages/fields/src/fields/slug/slug-edit.tsx b/packages/fields/src/fields/slug/slug-edit.tsx index aad6610550069..535b7ea1476bb 100644 --- a/packages/fields/src/fields/slug/slug-edit.tsx +++ b/packages/fields/src/fields/slug/slug-edit.tsx @@ -29,6 +29,7 @@ const SlugEdit = ( { data, }: DataFormControlProps< BasePost > ) => { const { id } = field; + data = Array.isArray( data ) ? data[ 0 ] : data; const slug = field.getValue( { item: data } ) || getSlug( data ); const permalinkTemplate = data.permalink_template || ''; diff --git a/packages/fields/src/fields/template/template-edit.tsx b/packages/fields/src/fields/template/template-edit.tsx index c17364568a457..56304008f2338 100644 --- a/packages/fields/src/fields/template/template-edit.tsx +++ b/packages/fields/src/fields/template/template-edit.tsx @@ -34,6 +34,7 @@ export const TemplateEdit = ( { onChange, }: DataFormControlProps< BasePost > ) => { const { id } = field; + data = Array.isArray( data ) ? data[ 0 ] : data; const postType = data.type; const postId = typeof data.id === 'number' ? data.id : parseInt( data.id, 10 ); From 572c16a092217d1cb7ac8fba3bc6b9324aa07aad Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Wed, 4 Dec 2024 14:31:17 +0100 Subject: [PATCH 03/14] Add type to disable bulk editing --- packages/dataviews/src/types.ts | 13 +++++++++---- packages/fields/src/fields/parent/index.ts | 2 +- packages/fields/src/fields/parent/parent-edit.tsx | 3 +-- packages/fields/src/fields/slug/index.ts | 2 +- packages/fields/src/fields/slug/slug-edit.tsx | 3 +-- packages/fields/src/fields/template/index.ts | 2 +- .../fields/src/fields/template/template-edit.tsx | 3 +-- 7 files changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts index 2fec464c6accb..6bade32d711e6 100644 --- a/packages/dataviews/src/types.ts +++ b/packages/dataviews/src/types.ts @@ -71,7 +71,7 @@ export type FieldTypeDefinition< Item > = { /** * A dataview field for a specific property of a data type. */ -export type Field< Item > = { +export type Field< Item, SupportsBulkEditing extends boolean = true > = { /** * Type of the fields. */ @@ -111,7 +111,9 @@ export type Field< Item > = { /** * Callback used to render an edit control for the field. */ - Edit?: ComponentType< DataFormControlProps< Item > > | string; + Edit?: + | ComponentType< DataFormControlProps< Item, SupportsBulkEditing > > + | string; /** * Callback used to sort the field. @@ -184,8 +186,11 @@ export type Fields< Item > = Field< Item >[]; export type Data< Item > = Item[]; -export type DataFormControlProps< Item > = { - data: Item | Item[]; +export type DataFormControlProps< + Item, + SupportsBulkEditing extends boolean = true, +> = { + data: SupportsBulkEditing extends true ? Item | Item[] : Item; field: NormalizedField< Item >; onChange: ( value: Record< string, any > ) => void; hideLabelFromVision?: boolean; diff --git a/packages/fields/src/fields/parent/index.ts b/packages/fields/src/fields/parent/index.ts index f974608710bf7..84749b08bd180 100644 --- a/packages/fields/src/fields/parent/index.ts +++ b/packages/fields/src/fields/parent/index.ts @@ -11,7 +11,7 @@ import type { BasePost } from '../../types'; import { ParentEdit } from './parent-edit'; import { ParentView } from './parent-view'; -const parentField: Field< BasePost > = { +const parentField: Field< BasePost, false > = { id: 'parent', type: 'text', label: __( 'Parent' ), diff --git a/packages/fields/src/fields/parent/parent-edit.tsx b/packages/fields/src/fields/parent/parent-edit.tsx index 4179cdb7e5fc5..da8cb6591590a 100644 --- a/packages/fields/src/fields/parent/parent-edit.tsx +++ b/packages/fields/src/fields/parent/parent-edit.tsx @@ -285,9 +285,8 @@ export const ParentEdit = ( { data, field, onChange, -}: DataFormControlProps< BasePost > ) => { +}: DataFormControlProps< BasePost, false > ) => { const { id } = field; - data = Array.isArray( data ) ? data[ 0 ] : data; const homeUrl = useSelect( ( select ) => { // @ts-expect-error getEntityRecord is not typed with unstableBase as argument. diff --git a/packages/fields/src/fields/slug/index.ts b/packages/fields/src/fields/slug/index.ts index 7311b1d10a66f..bfb72655c67ab 100644 --- a/packages/fields/src/fields/slug/index.ts +++ b/packages/fields/src/fields/slug/index.ts @@ -11,7 +11,7 @@ import type { BasePost } from '../../types'; import SlugEdit from './slug-edit'; import SlugView from './slug-view'; -const slugField: Field< BasePost > = { +const slugField: Field< BasePost, false > = { id: 'slug', type: 'text', label: __( 'Slug' ), diff --git a/packages/fields/src/fields/slug/slug-edit.tsx b/packages/fields/src/fields/slug/slug-edit.tsx index 535b7ea1476bb..002e20495bdb4 100644 --- a/packages/fields/src/fields/slug/slug-edit.tsx +++ b/packages/fields/src/fields/slug/slug-edit.tsx @@ -27,9 +27,8 @@ const SlugEdit = ( { field, onChange, data, -}: DataFormControlProps< BasePost > ) => { +}: DataFormControlProps< BasePost, false > ) => { const { id } = field; - data = Array.isArray( data ) ? data[ 0 ] : data; const slug = field.getValue( { item: data } ) || getSlug( data ); const permalinkTemplate = data.permalink_template || ''; diff --git a/packages/fields/src/fields/template/index.ts b/packages/fields/src/fields/template/index.ts index c419adedb537d..925b9b8ca8fb1 100644 --- a/packages/fields/src/fields/template/index.ts +++ b/packages/fields/src/fields/template/index.ts @@ -10,7 +10,7 @@ import { __ } from '@wordpress/i18n'; import type { BasePost } from '../../types'; import { TemplateEdit } from './template-edit'; -const templateField: Field< BasePost > = { +const templateField: Field< BasePost, false > = { id: 'template', type: 'text', label: __( 'Template' ), diff --git a/packages/fields/src/fields/template/template-edit.tsx b/packages/fields/src/fields/template/template-edit.tsx index 56304008f2338..c94e3be5e958b 100644 --- a/packages/fields/src/fields/template/template-edit.tsx +++ b/packages/fields/src/fields/template/template-edit.tsx @@ -32,9 +32,8 @@ export const TemplateEdit = ( { data, field, onChange, -}: DataFormControlProps< BasePost > ) => { +}: DataFormControlProps< BasePost, false > ) => { const { id } = field; - data = Array.isArray( data ) ? data[ 0 ] : data; const postType = data.type; const postId = typeof data.id === 'number' ? data.id : parseInt( data.id, 10 ); From 55b0cdc90e7d44e67207915ec256bee611ce2cae Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Tue, 10 Dec 2024 10:38:36 +0100 Subject: [PATCH 04/14] Add WithBulkEditing type --- .../src/dataform-controls/datetime.tsx | 4 ++-- .../dataviews/src/dataform-controls/integer.tsx | 4 ++-- .../dataviews/src/dataform-controls/radio.tsx | 4 ++-- .../dataviews/src/dataform-controls/select.tsx | 4 ++-- .../dataviews/src/dataform-controls/text.tsx | 4 ++-- packages/dataviews/src/types.ts | 17 ++++++++--------- packages/fields/src/fields/parent/index.ts | 2 +- .../fields/src/fields/parent/parent-edit.tsx | 2 +- packages/fields/src/fields/password/edit.tsx | 10 ++++++++-- packages/fields/src/fields/slug/index.ts | 2 +- packages/fields/src/fields/slug/slug-edit.tsx | 2 +- packages/fields/src/fields/template/index.ts | 2 +- .../src/fields/template/template-edit.tsx | 2 +- 13 files changed, 32 insertions(+), 27 deletions(-) diff --git a/packages/dataviews/src/dataform-controls/datetime.tsx b/packages/dataviews/src/dataform-controls/datetime.tsx index 53dc2b8187f4a..f50bc835dfe6e 100644 --- a/packages/dataviews/src/dataform-controls/datetime.tsx +++ b/packages/dataviews/src/dataform-controls/datetime.tsx @@ -7,14 +7,14 @@ import { useCallback } from '@wordpress/element'; /** * Internal dependencies */ -import type { DataFormControlProps } from '../types'; +import type { DataFormControlProps, WithBulkEditing } from '../types'; export default function DateTime< Item >( { field, onChange, hideLabelFromVision, value, -}: DataFormControlProps< Item > ) { +}: DataFormControlProps< Item > & WithBulkEditing< Item > ) { const { id, label } = field; const onChangeControl = useCallback( diff --git a/packages/dataviews/src/dataform-controls/integer.tsx b/packages/dataviews/src/dataform-controls/integer.tsx index d40bb7dd098bd..aaa85fdb23064 100644 --- a/packages/dataviews/src/dataform-controls/integer.tsx +++ b/packages/dataviews/src/dataform-controls/integer.tsx @@ -7,14 +7,14 @@ import { useCallback } from '@wordpress/element'; /** * Internal dependencies */ -import type { DataFormControlProps } from '../types'; +import type { DataFormControlProps, WithBulkEditing } from '../types'; export default function Integer< Item >( { field, onChange, hideLabelFromVision, value, -}: DataFormControlProps< Item > ) { +}: DataFormControlProps< Item > & WithBulkEditing< Item > ) { const { id, label, description } = field; const onChangeControl = useCallback( ( newValue: string | undefined ) => diff --git a/packages/dataviews/src/dataform-controls/radio.tsx b/packages/dataviews/src/dataform-controls/radio.tsx index b5aea66d7672a..4f965a2558ac4 100644 --- a/packages/dataviews/src/dataform-controls/radio.tsx +++ b/packages/dataviews/src/dataform-controls/radio.tsx @@ -7,14 +7,14 @@ import { useCallback } from '@wordpress/element'; /** * Internal dependencies */ -import type { DataFormControlProps } from '../types'; +import type { DataFormControlProps, WithBulkEditing } from '../types'; export default function Radio< Item >( { field, onChange, hideLabelFromVision, value, -}: DataFormControlProps< Item > ) { +}: DataFormControlProps< Item > & WithBulkEditing< Item > ) { const { id, label } = field; const onChangeControl = useCallback( diff --git a/packages/dataviews/src/dataform-controls/select.tsx b/packages/dataviews/src/dataform-controls/select.tsx index eca0323db8f5d..ddb7179ed357a 100644 --- a/packages/dataviews/src/dataform-controls/select.tsx +++ b/packages/dataviews/src/dataform-controls/select.tsx @@ -8,14 +8,14 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import type { DataFormControlProps } from '../types'; +import type { DataFormControlProps, WithBulkEditing } from '../types'; export default function Select< Item >( { field, onChange, hideLabelFromVision, value, -}: DataFormControlProps< Item > ) { +}: DataFormControlProps< Item > & WithBulkEditing< Item > ) { const { id, label } = field; const onChangeControl = useCallback( ( newValue: any ) => diff --git a/packages/dataviews/src/dataform-controls/text.tsx b/packages/dataviews/src/dataform-controls/text.tsx index 638aeab484c9c..00fc603870de6 100644 --- a/packages/dataviews/src/dataform-controls/text.tsx +++ b/packages/dataviews/src/dataform-controls/text.tsx @@ -7,14 +7,14 @@ import { useCallback } from '@wordpress/element'; /** * Internal dependencies */ -import type { DataFormControlProps } from '../types'; +import type { DataFormControlProps, WithBulkEditing } from '../types'; export default function Text< Item >( { field, onChange, hideLabelFromVision, value, -}: DataFormControlProps< Item > ) { +}: DataFormControlProps< Item > & WithBulkEditing< Item > ) { const { id, label, placeholder } = field; const onChangeControl = useCallback( diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts index 6bade32d711e6..43afb21512a61 100644 --- a/packages/dataviews/src/types.ts +++ b/packages/dataviews/src/types.ts @@ -71,7 +71,7 @@ export type FieldTypeDefinition< Item > = { /** * A dataview field for a specific property of a data type. */ -export type Field< Item, SupportsBulkEditing extends boolean = true > = { +export type Field< Item > = { /** * Type of the fields. */ @@ -111,9 +111,7 @@ export type Field< Item, SupportsBulkEditing extends boolean = true > = { /** * Callback used to render an edit control for the field. */ - Edit?: - | ComponentType< DataFormControlProps< Item, SupportsBulkEditing > > - | string; + Edit?: ComponentType< DataFormControlProps< Item > > | string; /** * Callback used to sort the field. @@ -186,17 +184,18 @@ export type Fields< Item > = Field< Item >[]; export type Data< Item > = Item[]; -export type DataFormControlProps< - Item, - SupportsBulkEditing extends boolean = true, -> = { - data: SupportsBulkEditing extends true ? Item | Item[] : Item; +export type DataFormControlProps< Item > = { + data: Item; field: NormalizedField< Item >; onChange: ( value: Record< string, any > ) => void; hideLabelFromVision?: boolean; value: any; }; +export type WithBulkEditing< Item > = { + data: Item | Item[]; +}; + export type DataViewRenderFieldProps< Item > = { item: Item; }; diff --git a/packages/fields/src/fields/parent/index.ts b/packages/fields/src/fields/parent/index.ts index 84749b08bd180..f974608710bf7 100644 --- a/packages/fields/src/fields/parent/index.ts +++ b/packages/fields/src/fields/parent/index.ts @@ -11,7 +11,7 @@ import type { BasePost } from '../../types'; import { ParentEdit } from './parent-edit'; import { ParentView } from './parent-view'; -const parentField: Field< BasePost, false > = { +const parentField: Field< BasePost > = { id: 'parent', type: 'text', label: __( 'Parent' ), diff --git a/packages/fields/src/fields/parent/parent-edit.tsx b/packages/fields/src/fields/parent/parent-edit.tsx index da8cb6591590a..21cdbee7a365a 100644 --- a/packages/fields/src/fields/parent/parent-edit.tsx +++ b/packages/fields/src/fields/parent/parent-edit.tsx @@ -285,7 +285,7 @@ export const ParentEdit = ( { data, field, onChange, -}: DataFormControlProps< BasePost, false > ) => { +}: DataFormControlProps< BasePost > ) => { const { id } = field; const homeUrl = useSelect( ( select ) => { diff --git a/packages/fields/src/fields/password/edit.tsx b/packages/fields/src/fields/password/edit.tsx index 3c63be5a2d596..d6e92e8f5991d 100644 --- a/packages/fields/src/fields/password/edit.tsx +++ b/packages/fields/src/fields/password/edit.tsx @@ -6,7 +6,10 @@ import { __experimentalVStack as VStack, TextControl, } from '@wordpress/components'; -import type { DataFormControlProps } from '@wordpress/dataviews'; +import type { + DataFormControlProps, + WithBulkEditing, +} from '@wordpress/dataviews'; import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; @@ -15,7 +18,10 @@ import { __ } from '@wordpress/i18n'; */ import type { BasePost } from '../../types'; -function PasswordEdit( { onChange, value }: DataFormControlProps< BasePost > ) { +function PasswordEdit( { + onChange, + value, +}: DataFormControlProps< BasePost > & WithBulkEditing< BasePost > ) { const [ showPassword, setShowPassword ] = useState( !! value ); const handleTogglePassword = ( newValue: boolean ) => { diff --git a/packages/fields/src/fields/slug/index.ts b/packages/fields/src/fields/slug/index.ts index bfb72655c67ab..7311b1d10a66f 100644 --- a/packages/fields/src/fields/slug/index.ts +++ b/packages/fields/src/fields/slug/index.ts @@ -11,7 +11,7 @@ import type { BasePost } from '../../types'; import SlugEdit from './slug-edit'; import SlugView from './slug-view'; -const slugField: Field< BasePost, false > = { +const slugField: Field< BasePost > = { id: 'slug', type: 'text', label: __( 'Slug' ), diff --git a/packages/fields/src/fields/slug/slug-edit.tsx b/packages/fields/src/fields/slug/slug-edit.tsx index 002e20495bdb4..aad6610550069 100644 --- a/packages/fields/src/fields/slug/slug-edit.tsx +++ b/packages/fields/src/fields/slug/slug-edit.tsx @@ -27,7 +27,7 @@ const SlugEdit = ( { field, onChange, data, -}: DataFormControlProps< BasePost, false > ) => { +}: DataFormControlProps< BasePost > ) => { const { id } = field; const slug = field.getValue( { item: data } ) || getSlug( data ); diff --git a/packages/fields/src/fields/template/index.ts b/packages/fields/src/fields/template/index.ts index 925b9b8ca8fb1..c419adedb537d 100644 --- a/packages/fields/src/fields/template/index.ts +++ b/packages/fields/src/fields/template/index.ts @@ -10,7 +10,7 @@ import { __ } from '@wordpress/i18n'; import type { BasePost } from '../../types'; import { TemplateEdit } from './template-edit'; -const templateField: Field< BasePost, false > = { +const templateField: Field< BasePost > = { id: 'template', type: 'text', label: __( 'Template' ), diff --git a/packages/fields/src/fields/template/template-edit.tsx b/packages/fields/src/fields/template/template-edit.tsx index c94e3be5e958b..c17364568a457 100644 --- a/packages/fields/src/fields/template/template-edit.tsx +++ b/packages/fields/src/fields/template/template-edit.tsx @@ -32,7 +32,7 @@ export const TemplateEdit = ( { data, field, onChange, -}: DataFormControlProps< BasePost, false > ) => { +}: DataFormControlProps< BasePost > ) => { const { id } = field; const postType = data.type; const postId = From 5095f591511628a863032c91a66f353b18d1c691 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Tue, 10 Dec 2024 11:23:02 +0100 Subject: [PATCH 05/14] Change bulk editing to opt in --- packages/dataviews/src/field-types/datetime.tsx | 1 + packages/dataviews/src/field-types/index.tsx | 1 + packages/dataviews/src/field-types/integer.tsx | 1 + packages/dataviews/src/field-types/text.tsx | 1 + packages/dataviews/src/normalize-fields.ts | 9 ++++----- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/dataviews/src/field-types/datetime.tsx b/packages/dataviews/src/field-types/datetime.tsx index aa97fc86c318c..6f1c004e83e1d 100644 --- a/packages/dataviews/src/field-types/datetime.tsx +++ b/packages/dataviews/src/field-types/datetime.tsx @@ -25,4 +25,5 @@ export default { sort, isValid, Edit: 'datetime', + supportsBulkEditing: true, }; diff --git a/packages/dataviews/src/field-types/index.tsx b/packages/dataviews/src/field-types/index.tsx index eb9dada479c6b..16ea89769479e 100644 --- a/packages/dataviews/src/field-types/index.tsx +++ b/packages/dataviews/src/field-types/index.tsx @@ -46,5 +46,6 @@ export default function getFieldTypeDefinition( type?: FieldType ) { return true; }, Edit: () => null, + supportsBulkEditing: false, }; } diff --git a/packages/dataviews/src/field-types/integer.tsx b/packages/dataviews/src/field-types/integer.tsx index f57c8e382db81..e06c9c75f4a0b 100644 --- a/packages/dataviews/src/field-types/integer.tsx +++ b/packages/dataviews/src/field-types/integer.tsx @@ -31,4 +31,5 @@ export default { sort, isValid, Edit: 'integer', + supportsBulkEditing: true, }; diff --git a/packages/dataviews/src/field-types/text.tsx b/packages/dataviews/src/field-types/text.tsx index 76ff699d0848c..531fe47ac6f59 100644 --- a/packages/dataviews/src/field-types/text.tsx +++ b/packages/dataviews/src/field-types/text.tsx @@ -24,4 +24,5 @@ export default { sort, isValid, Edit: 'text', + supportsBulkEditing: true, }; diff --git a/packages/dataviews/src/normalize-fields.ts b/packages/dataviews/src/normalize-fields.ts index c32fcf81e9ae7..8ac1905fd9ce0 100644 --- a/packages/dataviews/src/normalize-fields.ts +++ b/packages/dataviews/src/normalize-fields.ts @@ -64,11 +64,10 @@ export function normalizeFields< Item >( ); }; - let supportsBulkEditing = true; - // If custom Edit component is passed in we default to false for bulk edit support. - if ( typeof field.Edit === 'function' || field.supportsBulkEditing ) { - supportsBulkEditing = field.supportsBulkEditing ?? false; - } + const supportsBulkEditing = + field.supportsBulkEditing || + ( typeof field.Edit !== 'function' && + fieldTypeDefinition.supportsBulkEditing ); const render = field.render || ( field.elements ? renderFromElements : getValue ); From 459c82308ce8980f8a2d83f8574ff40b8c61ab91 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Tue, 10 Dec 2024 13:13:15 +0100 Subject: [PATCH 06/14] Use DataFormControlPropsWithBulkEditing type instead to make type checking more robust --- .../src/components/dataform/stories/index.story.tsx | 1 + packages/dataviews/src/dataform-controls/datetime.tsx | 4 ++-- packages/dataviews/src/dataform-controls/integer.tsx | 6 +++--- packages/dataviews/src/dataform-controls/radio.tsx | 6 +++--- packages/dataviews/src/dataform-controls/select.tsx | 6 +++--- packages/dataviews/src/dataform-controls/text.tsx | 6 +++--- packages/dataviews/src/types.ts | 10 +++++++--- packages/fields/src/fields/password/edit.tsx | 10 ++-------- 8 files changed, 24 insertions(+), 25 deletions(-) diff --git a/packages/dataviews/src/components/dataform/stories/index.story.tsx b/packages/dataviews/src/components/dataform/stories/index.story.tsx index 8ee86444293ff..dd8ee42e9656d 100644 --- a/packages/dataviews/src/components/dataform/stories/index.story.tsx +++ b/packages/dataviews/src/components/dataform/stories/index.story.tsx @@ -19,6 +19,7 @@ type SamplePost = { date: string; birthdate: string; password?: string; + sticky?: boolean; }; const meta = { diff --git a/packages/dataviews/src/dataform-controls/datetime.tsx b/packages/dataviews/src/dataform-controls/datetime.tsx index f50bc835dfe6e..5ff7060989827 100644 --- a/packages/dataviews/src/dataform-controls/datetime.tsx +++ b/packages/dataviews/src/dataform-controls/datetime.tsx @@ -7,14 +7,14 @@ import { useCallback } from '@wordpress/element'; /** * Internal dependencies */ -import type { DataFormControlProps, WithBulkEditing } from '../types'; +import type { DataFormControlPropsWithBulkEditing } from '../types'; export default function DateTime< Item >( { field, onChange, hideLabelFromVision, value, -}: DataFormControlProps< Item > & WithBulkEditing< Item > ) { +}: DataFormControlPropsWithBulkEditing< Item, string > ) { const { id, label } = field; const onChangeControl = useCallback( diff --git a/packages/dataviews/src/dataform-controls/integer.tsx b/packages/dataviews/src/dataform-controls/integer.tsx index aaa85fdb23064..92541a4d158ba 100644 --- a/packages/dataviews/src/dataform-controls/integer.tsx +++ b/packages/dataviews/src/dataform-controls/integer.tsx @@ -7,14 +7,14 @@ import { useCallback } from '@wordpress/element'; /** * Internal dependencies */ -import type { DataFormControlProps, WithBulkEditing } from '../types'; +import type { DataFormControlPropsWithBulkEditing } from '../types'; export default function Integer< Item >( { field, onChange, hideLabelFromVision, value, -}: DataFormControlProps< Item > & WithBulkEditing< Item > ) { +}: DataFormControlPropsWithBulkEditing< Item, number > ) { const { id, label, description } = field; const onChangeControl = useCallback( ( newValue: string | undefined ) => @@ -28,7 +28,7 @@ export default function Integer< Item >( { ( { field, onChange, hideLabelFromVision, value, -}: DataFormControlProps< Item > & WithBulkEditing< Item > ) { +}: DataFormControlPropsWithBulkEditing< Item, string > ) { const { id, label } = field; const onChangeControl = useCallback( @@ -31,7 +31,7 @@ export default function Radio< Item >( { label={ label } onChange={ onChangeControl } options={ field.elements } - selected={ value } + selected={ typeof value === 'symbol' ? '' : value } hideLabelFromVision={ hideLabelFromVision } /> ); diff --git a/packages/dataviews/src/dataform-controls/select.tsx b/packages/dataviews/src/dataform-controls/select.tsx index ddb7179ed357a..084a0884c628f 100644 --- a/packages/dataviews/src/dataform-controls/select.tsx +++ b/packages/dataviews/src/dataform-controls/select.tsx @@ -8,14 +8,14 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import type { DataFormControlProps, WithBulkEditing } from '../types'; +import type { DataFormControlPropsWithBulkEditing } from '../types'; export default function Select< Item >( { field, onChange, hideLabelFromVision, value, -}: DataFormControlProps< Item > & WithBulkEditing< Item > ) { +}: DataFormControlPropsWithBulkEditing< Item, string | number > ) { const { id, label } = field; const onChangeControl = useCallback( ( newValue: any ) => @@ -40,7 +40,7 @@ export default function Select< Item >( { return ( ( { field, onChange, hideLabelFromVision, value, -}: DataFormControlProps< Item > & WithBulkEditing< Item > ) { +}: DataFormControlPropsWithBulkEditing< Item, string > ) { const { id, label, placeholder } = field; const onChangeControl = useCallback( @@ -29,7 +29,7 @@ export default function Text< Item >( { = Field< Item >[]; export type Data< Item > = Item[]; -export type DataFormControlProps< Item > = { +export type DataFormControlProps< Item, ValueType = any > = { data: Item; field: NormalizedField< Item >; onChange: ( value: Record< string, any > ) => void; hideLabelFromVision?: boolean; - value: any; + value?: ValueType; }; -export type WithBulkEditing< Item > = { +export type DataFormControlPropsWithBulkEditing< Item, ValueType = any > = Omit< + DataFormControlProps< Item, ValueType >, + 'data' | 'value' +> & { data: Item | Item[]; + value?: ValueType | symbol; }; export type DataViewRenderFieldProps< Item > = { diff --git a/packages/fields/src/fields/password/edit.tsx b/packages/fields/src/fields/password/edit.tsx index d6e92e8f5991d..3c63be5a2d596 100644 --- a/packages/fields/src/fields/password/edit.tsx +++ b/packages/fields/src/fields/password/edit.tsx @@ -6,10 +6,7 @@ import { __experimentalVStack as VStack, TextControl, } from '@wordpress/components'; -import type { - DataFormControlProps, - WithBulkEditing, -} from '@wordpress/dataviews'; +import type { DataFormControlProps } from '@wordpress/dataviews'; import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; @@ -18,10 +15,7 @@ import { __ } from '@wordpress/i18n'; */ import type { BasePost } from '../../types'; -function PasswordEdit( { - onChange, - value, -}: DataFormControlProps< BasePost > & WithBulkEditing< BasePost > ) { +function PasswordEdit( { onChange, value }: DataFormControlProps< BasePost > ) { const [ showPassword, setShowPassword ] = useState( !! value ); const handleTogglePassword = ( newValue: boolean ) => { From 39ba9f6b93594643ffafbcd172a1a6ecda634351 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Tue, 10 Dec 2024 13:16:08 +0100 Subject: [PATCH 07/14] Fix status field when switching to future --- packages/edit-site/src/components/post-edit/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edit-site/src/components/post-edit/index.js b/packages/edit-site/src/components/post-edit/index.js index 5e90fa5a929ec..03bbb58c5ac0f 100644 --- a/packages/edit-site/src/components/post-edit/index.js +++ b/packages/edit-site/src/components/post-edit/index.js @@ -108,7 +108,7 @@ function PostEditForm( { postType, postId } ) { edits.status && edits.status !== 'future' && editedRecord?.status === 'future' && - new Date( record.date ) > new Date() + new Date( editedRecord.date ) > new Date() ) { edits.date = null; } From 30387171440b865616bde0b4a3610aba8855e3e4 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Tue, 10 Dec 2024 14:59:46 +0100 Subject: [PATCH 08/14] Default bulk editing support to true --- .../dataform/stories/index.story.tsx | 9 +++++-- .../src/dataform-controls/datetime.tsx | 4 +-- .../src/dataform-controls/integer.tsx | 4 +-- .../dataviews/src/dataform-controls/radio.tsx | 4 +-- .../src/dataform-controls/select.tsx | 4 +-- .../dataviews/src/dataform-controls/text.tsx | 4 +-- .../dataviews/src/field-types/datetime.tsx | 1 - packages/dataviews/src/field-types/index.tsx | 1 - .../dataviews/src/field-types/integer.tsx | 1 - packages/dataviews/src/field-types/text.tsx | 1 - packages/dataviews/src/normalize-fields.ts | 7 +---- packages/dataviews/src/types.ts | 26 ++++++++++++------- .../featured-image/featured-image-edit.tsx | 6 +++-- .../fields/src/fields/parent/parent-edit.tsx | 16 +++++++----- packages/fields/src/fields/password/edit.tsx | 11 +++++--- packages/fields/src/fields/slug/index.ts | 1 + packages/fields/src/fields/slug/slug-edit.tsx | 4 +-- packages/fields/src/fields/template/index.ts | 1 + .../src/fields/template/template-edit.tsx | 11 ++++---- 19 files changed, 65 insertions(+), 51 deletions(-) diff --git a/packages/dataviews/src/components/dataform/stories/index.story.tsx b/packages/dataviews/src/components/dataform/stories/index.story.tsx index dd8ee42e9656d..a4717e79cf715 100644 --- a/packages/dataviews/src/components/dataform/stories/index.story.tsx +++ b/packages/dataviews/src/components/dataform/stories/index.story.tsx @@ -8,7 +8,7 @@ import { ToggleControl } from '@wordpress/components'; * Internal dependencies */ import DataForm from '../index'; -import type { Field, Form } from '../../../types'; +import type { DataFormControlProps, Field, Form } from '../../../types'; type SamplePost = { title: string; @@ -108,7 +108,12 @@ const fields = [ id: 'sticky', label: 'Sticky', type: 'integer', - Edit: ( { field, onChange, value, hideLabelFromVision } ) => { + Edit: ( { + field, + onChange, + value, + hideLabelFromVision, + }: DataFormControlProps< SamplePost > ) => { const { id } = field; return ( ( { field, onChange, hideLabelFromVision, value, -}: DataFormControlPropsWithBulkEditing< Item, string > ) { +}: DataFormControlProps< Item, string > ) { const { id, label } = field; const onChangeControl = useCallback( diff --git a/packages/dataviews/src/dataform-controls/integer.tsx b/packages/dataviews/src/dataform-controls/integer.tsx index 92541a4d158ba..d4c544d2d4969 100644 --- a/packages/dataviews/src/dataform-controls/integer.tsx +++ b/packages/dataviews/src/dataform-controls/integer.tsx @@ -7,14 +7,14 @@ import { useCallback } from '@wordpress/element'; /** * Internal dependencies */ -import type { DataFormControlPropsWithBulkEditing } from '../types'; +import type { DataFormControlProps } from '../types'; export default function Integer< Item >( { field, onChange, hideLabelFromVision, value, -}: DataFormControlPropsWithBulkEditing< Item, number > ) { +}: DataFormControlProps< Item, number > ) { const { id, label, description } = field; const onChangeControl = useCallback( ( newValue: string | undefined ) => diff --git a/packages/dataviews/src/dataform-controls/radio.tsx b/packages/dataviews/src/dataform-controls/radio.tsx index 215ab6bca291c..434e18a83f60b 100644 --- a/packages/dataviews/src/dataform-controls/radio.tsx +++ b/packages/dataviews/src/dataform-controls/radio.tsx @@ -7,14 +7,14 @@ import { useCallback } from '@wordpress/element'; /** * Internal dependencies */ -import type { DataFormControlPropsWithBulkEditing } from '../types'; +import type { DataFormControlProps } from '../types'; export default function Radio< Item >( { field, onChange, hideLabelFromVision, value, -}: DataFormControlPropsWithBulkEditing< Item, string > ) { +}: DataFormControlProps< Item, string > ) { const { id, label } = field; const onChangeControl = useCallback( diff --git a/packages/dataviews/src/dataform-controls/select.tsx b/packages/dataviews/src/dataform-controls/select.tsx index 084a0884c628f..d50625116a810 100644 --- a/packages/dataviews/src/dataform-controls/select.tsx +++ b/packages/dataviews/src/dataform-controls/select.tsx @@ -8,14 +8,14 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import type { DataFormControlPropsWithBulkEditing } from '../types'; +import type { DataFormControlProps } from '../types'; export default function Select< Item >( { field, onChange, hideLabelFromVision, value, -}: DataFormControlPropsWithBulkEditing< Item, string | number > ) { +}: DataFormControlProps< Item, string | number > ) { const { id, label } = field; const onChangeControl = useCallback( ( newValue: any ) => diff --git a/packages/dataviews/src/dataform-controls/text.tsx b/packages/dataviews/src/dataform-controls/text.tsx index edcb37ee5b198..7bc0704d56816 100644 --- a/packages/dataviews/src/dataform-controls/text.tsx +++ b/packages/dataviews/src/dataform-controls/text.tsx @@ -7,14 +7,14 @@ import { useCallback } from '@wordpress/element'; /** * Internal dependencies */ -import type { DataFormControlPropsWithBulkEditing } from '../types'; +import type { DataFormControlProps } from '../types'; export default function Text< Item >( { field, onChange, hideLabelFromVision, value, -}: DataFormControlPropsWithBulkEditing< Item, string > ) { +}: DataFormControlProps< Item, string > ) { const { id, label, placeholder } = field; const onChangeControl = useCallback( diff --git a/packages/dataviews/src/field-types/datetime.tsx b/packages/dataviews/src/field-types/datetime.tsx index 6f1c004e83e1d..aa97fc86c318c 100644 --- a/packages/dataviews/src/field-types/datetime.tsx +++ b/packages/dataviews/src/field-types/datetime.tsx @@ -25,5 +25,4 @@ export default { sort, isValid, Edit: 'datetime', - supportsBulkEditing: true, }; diff --git a/packages/dataviews/src/field-types/index.tsx b/packages/dataviews/src/field-types/index.tsx index 16ea89769479e..eb9dada479c6b 100644 --- a/packages/dataviews/src/field-types/index.tsx +++ b/packages/dataviews/src/field-types/index.tsx @@ -46,6 +46,5 @@ export default function getFieldTypeDefinition( type?: FieldType ) { return true; }, Edit: () => null, - supportsBulkEditing: false, }; } diff --git a/packages/dataviews/src/field-types/integer.tsx b/packages/dataviews/src/field-types/integer.tsx index e06c9c75f4a0b..f57c8e382db81 100644 --- a/packages/dataviews/src/field-types/integer.tsx +++ b/packages/dataviews/src/field-types/integer.tsx @@ -31,5 +31,4 @@ export default { sort, isValid, Edit: 'integer', - supportsBulkEditing: true, }; diff --git a/packages/dataviews/src/field-types/text.tsx b/packages/dataviews/src/field-types/text.tsx index 531fe47ac6f59..76ff699d0848c 100644 --- a/packages/dataviews/src/field-types/text.tsx +++ b/packages/dataviews/src/field-types/text.tsx @@ -24,5 +24,4 @@ export default { sort, isValid, Edit: 'text', - supportsBulkEditing: true, }; diff --git a/packages/dataviews/src/normalize-fields.ts b/packages/dataviews/src/normalize-fields.ts index 8ac1905fd9ce0..cac965390a218 100644 --- a/packages/dataviews/src/normalize-fields.ts +++ b/packages/dataviews/src/normalize-fields.ts @@ -64,11 +64,6 @@ export function normalizeFields< Item >( ); }; - const supportsBulkEditing = - field.supportsBulkEditing || - ( typeof field.Edit !== 'function' && - fieldTypeDefinition.supportsBulkEditing ); - const render = field.render || ( field.elements ? renderFromElements : getValue ); @@ -81,7 +76,7 @@ export function normalizeFields< Item >( sort, isValid, Edit, - supportsBulkEditing, + supportsBulkEditing: field.supportsBulkEditing ?? true, enableHiding: field.enableHiding ?? true, enableSorting: field.enableSorting ?? true, }; diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts index 115d8bfb39e9b..358bf49d4cb7e 100644 --- a/packages/dataviews/src/types.ts +++ b/packages/dataviews/src/types.ts @@ -111,7 +111,10 @@ export type Field< Item > = { /** * Callback used to render an edit control for the field. */ - Edit?: ComponentType< DataFormControlProps< Item > > | string; + Edit?: + | ComponentType< DataFormControlProps< Item > > + | ComponentType< DataFormControlPropsWithoutBulkEditing< Item > > + | string; /** * Callback used to sort the field. @@ -170,11 +173,14 @@ export type NormalizedField< Item > = Field< Item > & { header: string | ReactElement; getValue: ( args: { item: Item } ) => any; render: ComponentType< DataViewRenderFieldProps< Item > >; - Edit: ComponentType< DataFormControlProps< Item > >; + Edit: + | ComponentType< DataFormControlProps< Item > > + | ComponentType< DataFormControlPropsWithoutBulkEditing< Item > >; sort: ( a: Item, b: Item, direction: SortDirection ) => number; isValid: ( item: Item, context?: ValidationContext ) => boolean; enableHiding: boolean; enableSorting: boolean; + supportsBulkEditing: boolean; }; /** @@ -185,19 +191,19 @@ export type Fields< Item > = Field< Item >[]; export type Data< Item > = Item[]; export type DataFormControlProps< Item, ValueType = any > = { - data: Item; + data: Item | Item[]; field: NormalizedField< Item >; onChange: ( value: Record< string, any > ) => void; hideLabelFromVision?: boolean; - value?: ValueType; + value?: ValueType | symbol; }; -export type DataFormControlPropsWithBulkEditing< Item, ValueType = any > = Omit< - DataFormControlProps< Item, ValueType >, - 'data' | 'value' -> & { - data: Item | Item[]; - value?: ValueType | symbol; +export type DataFormControlPropsWithoutBulkEditing< + Item, + ValueType = any, +> = Omit< DataFormControlProps< Item, ValueType >, 'data' | 'value' > & { + data: Item; + value?: ValueType; }; export type DataViewRenderFieldProps< Item > = { diff --git a/packages/fields/src/fields/featured-image/featured-image-edit.tsx b/packages/fields/src/fields/featured-image/featured-image-edit.tsx index 07c1deed2dd19..1a99fab07741d 100644 --- a/packages/fields/src/fields/featured-image/featured-image-edit.tsx +++ b/packages/fields/src/fields/featured-image/featured-image-edit.tsx @@ -20,13 +20,15 @@ export const FeaturedImageEdit = ( { field, onChange, value, -}: DataFormControlProps< BasePost > ) => { +}: DataFormControlProps< BasePost, number > ) => { const { id } = field; const media = useSelect( ( select ) => { const { getEntityRecord } = select( coreStore ); - return getEntityRecord( 'root', 'media', value ); + return typeof value !== 'symbol' + ? getEntityRecord( 'root', 'media', value ) + : undefined; }, [ value ] ); diff --git a/packages/fields/src/fields/parent/parent-edit.tsx b/packages/fields/src/fields/parent/parent-edit.tsx index 21cdbee7a365a..d5d1852c2d2bd 100644 --- a/packages/fields/src/fields/parent/parent-edit.tsx +++ b/packages/fields/src/fields/parent/parent-edit.tsx @@ -109,16 +109,18 @@ export const getItemPriority = ( name: string, searchValue: string ) => { export function PageAttributesParent( { data, + value, onChangeControl, }: { - data: BasePost; + data: BasePost | BasePost[]; + value?: number | symbol; onChangeControl: ( newValue: number ) => void; } ) { const [ fieldValue, setFieldValue ] = useState< null | string >( null ); - const pageId = data.parent; - const postId = data.id; - const postTypeSlug = data.type; + const pageId = typeof value === 'symbol' ? undefined : value; + const postId = Array.isArray( data ) ? data[ 0 ].id : data.id; + const postTypeSlug = Array.isArray( data ) ? data[ 0 ].type : data.type; const { parentPostTitle, pageItems, isHierarchical } = useSelect( ( select ) => { @@ -272,7 +274,7 @@ export function PageAttributesParent( { value={ pageId?.toString() } options={ parentOptions } onFilterValueChange={ debounce( - ( value: unknown ) => handleKeydown( value as string ), + ( newValue: unknown ) => handleKeydown( newValue as string ), 300 ) } onChange={ handleChange } @@ -285,7 +287,8 @@ export const ParentEdit = ( { data, field, onChange, -}: DataFormControlProps< BasePost > ) => { + value, +}: DataFormControlProps< BasePost, number > ) => { const { id } = field; const homeUrl = useSelect( ( select ) => { @@ -339,6 +342,7 @@ export const ParentEdit = ( { ) }

diff --git a/packages/fields/src/fields/password/edit.tsx b/packages/fields/src/fields/password/edit.tsx index 3c63be5a2d596..094d36a0d0362 100644 --- a/packages/fields/src/fields/password/edit.tsx +++ b/packages/fields/src/fields/password/edit.tsx @@ -15,8 +15,13 @@ import { __ } from '@wordpress/i18n'; */ import type { BasePost } from '../../types'; -function PasswordEdit( { onChange, value }: DataFormControlProps< BasePost > ) { - const [ showPassword, setShowPassword ] = useState( !! value ); +function PasswordEdit( { + onChange, + value, +}: DataFormControlProps< BasePost, string > ) { + const [ showPassword, setShowPassword ] = useState( + typeof value !== 'symbol' && !! value + ); const handleTogglePassword = ( newValue: boolean ) => { setShowPassword( newValue ); @@ -47,7 +52,7 @@ function PasswordEdit( { onChange, value }: DataFormControlProps< BasePost > ) { password: newValue, } ) } - value={ value || '' } + value={ typeof value === 'symbol' ? '' : value ?? '' } placeholder={ __( 'Use a secure password' ) } type="text" __next40pxDefaultSize diff --git a/packages/fields/src/fields/slug/index.ts b/packages/fields/src/fields/slug/index.ts index 7311b1d10a66f..3ac2dc14e306b 100644 --- a/packages/fields/src/fields/slug/index.ts +++ b/packages/fields/src/fields/slug/index.ts @@ -17,6 +17,7 @@ const slugField: Field< BasePost > = { label: __( 'Slug' ), Edit: SlugEdit, render: SlugView, + supportsBulkEditing: false, }; /** diff --git a/packages/fields/src/fields/slug/slug-edit.tsx b/packages/fields/src/fields/slug/slug-edit.tsx index aad6610550069..3b23d086cb223 100644 --- a/packages/fields/src/fields/slug/slug-edit.tsx +++ b/packages/fields/src/fields/slug/slug-edit.tsx @@ -14,7 +14,7 @@ import { useDispatch } from '@wordpress/data'; import { useCallback, useEffect, useRef } from '@wordpress/element'; import { store as noticesStore } from '@wordpress/notices'; import { safeDecodeURIComponent } from '@wordpress/url'; -import type { DataFormControlProps } from '@wordpress/dataviews'; +import type { DataFormControlPropsWithoutBulkEditing } from '@wordpress/dataviews'; import { __ } from '@wordpress/i18n'; /** @@ -27,7 +27,7 @@ const SlugEdit = ( { field, onChange, data, -}: DataFormControlProps< BasePost > ) => { +}: DataFormControlPropsWithoutBulkEditing< BasePost > ) => { const { id } = field; const slug = field.getValue( { item: data } ) || getSlug( data ); diff --git a/packages/fields/src/fields/template/index.ts b/packages/fields/src/fields/template/index.ts index c419adedb537d..825c17b40dd06 100644 --- a/packages/fields/src/fields/template/index.ts +++ b/packages/fields/src/fields/template/index.ts @@ -16,6 +16,7 @@ const templateField: Field< BasePost > = { label: __( 'Template' ), Edit: TemplateEdit, enableSorting: false, + supportsBulkEditing: false, }; /** diff --git a/packages/fields/src/fields/template/template-edit.tsx b/packages/fields/src/fields/template/template-edit.tsx index c17364568a457..ea9bdf559e160 100644 --- a/packages/fields/src/fields/template/template-edit.tsx +++ b/packages/fields/src/fields/template/template-edit.tsx @@ -6,7 +6,7 @@ import { useCallback, useMemo, useState } from '@wordpress/element'; import { parse } from '@wordpress/blocks'; import type { WpTemplate } from '@wordpress/core-data'; import { store as coreStore } from '@wordpress/core-data'; -import type { DataFormControlProps } from '@wordpress/dataviews'; +import type { DataFormControlPropsWithoutBulkEditing } from '@wordpress/dataviews'; /** * Internal dependencies @@ -32,7 +32,8 @@ export const TemplateEdit = ( { data, field, onChange, -}: DataFormControlProps< BasePost > ) => { + value, +}: DataFormControlPropsWithoutBulkEditing< BasePost, string > ) => { const { id } = field; const postType = data.type; const postId = @@ -67,13 +68,13 @@ export const TemplateEdit = ( { ? allTemplates.filter( ( template ) => template.is_custom && - template.slug !== data.template && + template.slug !== value && !! template.content.raw // Skip empty templates. ) : [], }; }, - [ data.template, postId, postType ] + [ value, postId, postType ] ); const templatesAsPatterns = useMemo( @@ -89,8 +90,6 @@ export const TemplateEdit = ( { const shownTemplates = useAsyncList( templatesAsPatterns ); - const value = field.getValue( { item: data } ); - const currentTemplate = useSelect( ( select ) => { const foundTemplate = templates?.find( From afffeed264bf5714164d4035d60753902b97e6b2 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Tue, 10 Dec 2024 15:53:35 +0100 Subject: [PATCH 09/14] Fix conditionals within template field --- .../src/fields/template/template-edit.tsx | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/fields/src/fields/template/template-edit.tsx b/packages/fields/src/fields/template/template-edit.tsx index ea9bdf559e160..a9fb052edffcb 100644 --- a/packages/fields/src/fields/template/template-edit.tsx +++ b/packages/fields/src/fields/template/template-edit.tsx @@ -56,9 +56,9 @@ export const TemplateEdit = ( { select( coreStore ) ); - const isPostsPage = getPostsPageId() === +postId; + const isPostsPage = +getPostsPageId() === postId; const isFrontPage = - postType === 'page' && getHomePage()?.postId === +postId; + postType === 'page' && +getHomePage()?.postId === postId; const allowSwitchingTemplate = ! isPostsPage && ! isFrontPage; @@ -150,6 +150,10 @@ export const TemplateEdit = ( { variant="tertiary" size="compact" onClick={ onToggle } + accessibleWhenDisabled + disabled={ + availableTemplates.length === 0 && value === '' + } > { currentTemplate ? getItemTitle( currentTemplate ) @@ -158,14 +162,16 @@ export const TemplateEdit = ( { ) } renderContent={ ( { onToggle } ) => ( - { - setShowModal( true ); - onToggle(); - } } - > - { __( 'Swap template' ) } - + { availableTemplates.length > 0 && ( + { + setShowModal( true ); + onToggle(); + } } + > + { __( 'Swap template' ) } + + ) } { // The default template in a post is indicated by an empty string value !== '' && ( From 48cb5006ec6f354e67c01aaffeb41dd4b1237824 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Tue, 10 Dec 2024 16:35:17 +0100 Subject: [PATCH 10/14] All template usage for bulk editing --- packages/fields/src/fields/template/index.ts | 1 - .../src/fields/template/template-edit.tsx | 43 ++++++++++++------- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/packages/fields/src/fields/template/index.ts b/packages/fields/src/fields/template/index.ts index 825c17b40dd06..c419adedb537d 100644 --- a/packages/fields/src/fields/template/index.ts +++ b/packages/fields/src/fields/template/index.ts @@ -16,7 +16,6 @@ const templateField: Field< BasePost > = { label: __( 'Template' ), Edit: TemplateEdit, enableSorting: false, - supportsBulkEditing: false, }; /** diff --git a/packages/fields/src/fields/template/template-edit.tsx b/packages/fields/src/fields/template/template-edit.tsx index a9fb052edffcb..4df0ab62f4d42 100644 --- a/packages/fields/src/fields/template/template-edit.tsx +++ b/packages/fields/src/fields/template/template-edit.tsx @@ -6,7 +6,7 @@ import { useCallback, useMemo, useState } from '@wordpress/element'; import { parse } from '@wordpress/blocks'; import type { WpTemplate } from '@wordpress/core-data'; import { store as coreStore } from '@wordpress/core-data'; -import type { DataFormControlPropsWithoutBulkEditing } from '@wordpress/dataviews'; +import type { DataFormControlProps } from '@wordpress/dataviews'; /** * Internal dependencies @@ -33,12 +33,10 @@ export const TemplateEdit = ( { field, onChange, value, -}: DataFormControlPropsWithoutBulkEditing< BasePost, string > ) => { +}: DataFormControlProps< BasePost, string > ) => { const { id } = field; - const postType = data.type; - const postId = - typeof data.id === 'number' ? data.id : parseInt( data.id, 10 ); - const slug = data.slug; + const postType = Array.isArray( data ) ? data[ 0 ].type : data.type; + const slug = Array.isArray( data ) ? '' : data.slug; const { availableTemplates, templates } = useSelect( ( select ) => { @@ -56,11 +54,18 @@ export const TemplateEdit = ( { select( coreStore ) ); - const isPostsPage = +getPostsPageId() === postId; - const isFrontPage = - postType === 'page' && +getHomePage()?.postId === postId; + const posts = Array.isArray( data ) ? data : [ data ]; - const allowSwitchingTemplate = ! isPostsPage && ! isFrontPage; + const allowSwitchingTemplate = posts.every( ( post ) => { + const postId = + typeof post.id === 'number' + ? post.id + : parseInt( post.id, 10 ); + const isPostsPage = +getPostsPageId() === postId; + const isFrontPage = + postType === 'page' && +getHomePage()?.postId === postId; + return ! isPostsPage && ! isFrontPage; + } ); return { templates: allTemplates, @@ -74,7 +79,7 @@ export const TemplateEdit = ( { : [], }; }, - [ value, postId, postType ] + [ value, postType, data ] ); const templatesAsPatterns = useMemo( @@ -140,6 +145,13 @@ export const TemplateEdit = ( { [ id, onChange ] ); + let buttonLabel = ''; + if ( typeof value === 'symbol' ) { + buttonLabel = __( 'Mixed' ); + } else if ( currentTemplate ) { + buttonLabel = getItemTitle( currentTemplate ); + } + return (
- { currentTemplate - ? getItemTitle( currentTemplate ) - : '' } + { buttonLabel } ) } renderContent={ ( { onToggle } ) => ( @@ -174,7 +185,7 @@ export const TemplateEdit = ( { ) } { // The default template in a post is indicated by an empty string - value !== '' && ( + value !== '' && typeof value !== 'symbol' && ( { onChangeControl( '' ); From bb9b2a6719f8804fc5a498ab34bb76a825fd2dbe Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Thu, 12 Dec 2024 16:39:05 +0100 Subject: [PATCH 11/14] Move overlapping logic to useFieldValue hook --- .../src/dataforms-layouts/panel/index.tsx | 17 +-------- .../src/dataforms-layouts/regular/index.tsx | 24 +----------- .../src/dataforms-layouts/use-field-value.ts | 38 +++++++++++++++++++ 3 files changed, 42 insertions(+), 37 deletions(-) create mode 100644 packages/dataviews/src/dataforms-layouts/use-field-value.ts diff --git a/packages/dataviews/src/dataforms-layouts/panel/index.tsx b/packages/dataviews/src/dataforms-layouts/panel/index.tsx index bb2058080115e..72fcf6e0a0387 100644 --- a/packages/dataviews/src/dataforms-layouts/panel/index.tsx +++ b/packages/dataviews/src/dataforms-layouts/panel/index.tsx @@ -27,6 +27,7 @@ import DataFormContext from '../../components/dataform-context'; import { DataFormLayout } from '../data-form-layout'; import { isCombinedField } from '../is-combined-field'; import { MIXED_VALUE } from '../../constants'; +import useFieldValue from '../use-field-value'; function DropdownHeader( { title, @@ -99,21 +100,7 @@ function PanelDropdown< Item >( { }; }, [ field ] ); - const fieldValue = useMemo( () => { - if ( Array.isArray( data ) ) { - const [ firstRecord, ...remainingRecords ] = data; - const firstValue = fieldDefinition.getValue( { - item: firstRecord, - } ); - const intersects = remainingRecords.every( ( item ) => { - return fieldDefinition.getValue( { item } ) === firstValue; - } ); - return intersects ? firstValue : MIXED_VALUE; - } - return fieldDefinition.getValue( { - item: data, - } ); - }, [ data, fieldDefinition ] ); + const fieldValue = useFieldValue( data, field.id ); // Memoize popoverProps to avoid returning a new object every time. const popoverProps = useMemo( diff --git a/packages/dataviews/src/dataforms-layouts/regular/index.tsx b/packages/dataviews/src/dataforms-layouts/regular/index.tsx index a497b2070bbcf..ef81f2fb15292 100644 --- a/packages/dataviews/src/dataforms-layouts/regular/index.tsx +++ b/packages/dataviews/src/dataforms-layouts/regular/index.tsx @@ -16,7 +16,7 @@ import type { Form, FieldLayoutProps } from '../../types'; import DataFormContext from '../../components/dataform-context'; import { DataFormLayout } from '../data-form-layout'; import { isCombinedField } from '../is-combined-field'; -import { MIXED_VALUE } from '../../constants'; +import useFieldValue from '../use-field-value'; function Header( { title }: { title: string } ) { return ( @@ -60,27 +60,7 @@ export default function FormRegularField< Item >( { }; }, [ field ] ); - const fieldValue = useMemo( () => { - const fieldDefinition = fields.find( - ( fieldDef ) => fieldDef.id === field.id - ); - if ( ! fieldDefinition ) { - return undefined; - } - if ( Array.isArray( data ) ) { - const [ firstRecord, ...remainingRecords ] = data; - const firstValue = fieldDefinition.getValue( { - item: firstRecord, - } ); - const intersects = remainingRecords.every( ( item ) => { - return fieldDefinition.getValue( { item } ) === firstValue; - } ); - return intersects ? firstValue : MIXED_VALUE; - } - return fieldDefinition.getValue( { - item: data, - } ); - }, [ data, fields, field.id ] ); + const fieldValue = useFieldValue( data, field.id ); if ( isCombinedField( field ) ) { return ( diff --git a/packages/dataviews/src/dataforms-layouts/use-field-value.ts b/packages/dataviews/src/dataforms-layouts/use-field-value.ts new file mode 100644 index 0000000000000..6803437ea4651 --- /dev/null +++ b/packages/dataviews/src/dataforms-layouts/use-field-value.ts @@ -0,0 +1,38 @@ +/** + * WordPress dependencies + */ +import { useContext, useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import DataFormContext from '../components/dataform-context'; +import { MIXED_VALUE } from '../constants'; + +export default function useFieldValue< Item >( + data: Item | Item[], + fieldId: string +) { + const { fields } = useContext( DataFormContext ); + return useMemo( () => { + const fieldDefinition = fields.find( + ( fieldDef ) => fieldDef.id === fieldId + ); + if ( ! fieldDefinition ) { + return undefined; + } + if ( Array.isArray( data ) ) { + const [ firstRecord, ...remainingRecords ] = data; + const firstValue = fieldDefinition.getValue( { + item: firstRecord, + } ); + const intersects = remainingRecords.every( ( item ) => { + return fieldDefinition.getValue( { item } ) === firstValue; + } ); + return intersects ? firstValue : MIXED_VALUE; + } + return fieldDefinition.getValue( { + item: data, + } ); + }, [ data, fields, fieldId ] ); +} From 6c629e6a926ad9f2d8eba0c2f3564550850cf945 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Thu, 12 Dec 2024 16:53:32 +0100 Subject: [PATCH 12/14] Change naming of bulkEditing to unique --- .../dataforms-layouts/data-form-layout.tsx | 7 +++---- packages/dataviews/src/normalize-fields.ts | 2 +- packages/dataviews/src/types.ts | 19 +++++++++---------- packages/fields/src/fields/slug/index.ts | 2 +- packages/fields/src/fields/slug/slug-edit.tsx | 4 ++-- 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx b/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx index f11b430805390..ac70becf5c5dc 100644 --- a/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx +++ b/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx @@ -26,9 +26,9 @@ function doesCombinedFieldSupportBulkEdits< Item >( return combinedField.children.some( ( child ) => { const fieldId = typeof child === 'string' ? child : child.id; - return fieldDefinitions.find( + return ! fieldDefinitions.find( ( fieldDefinition ) => fieldDefinition.id === fieldId - )?.supportsBulkEditing; + )?.unique; } ); } @@ -95,8 +95,7 @@ export function DataFormLayout< Item >( { formField, fieldDefinitions ) ) || - ( fieldDefinition && - ! fieldDefinition.supportsBulkEditing ) ) + ( fieldDefinition && fieldDefinition.unique ) ) ) { return null; } diff --git a/packages/dataviews/src/normalize-fields.ts b/packages/dataviews/src/normalize-fields.ts index cac965390a218..c9f0f7ea8ba83 100644 --- a/packages/dataviews/src/normalize-fields.ts +++ b/packages/dataviews/src/normalize-fields.ts @@ -76,7 +76,7 @@ export function normalizeFields< Item >( sort, isValid, Edit, - supportsBulkEditing: field.supportsBulkEditing ?? true, + unique: field.unique ?? false, enableHiding: field.enableHiding ?? true, enableSorting: field.enableSorting ?? true, }; diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts index 358bf49d4cb7e..780fe71dc7c30 100644 --- a/packages/dataviews/src/types.ts +++ b/packages/dataviews/src/types.ts @@ -113,7 +113,7 @@ export type Field< Item > = { */ Edit?: | ComponentType< DataFormControlProps< Item > > - | ComponentType< DataFormControlPropsWithoutBulkEditing< Item > > + | ComponentType< DataFormUniqueControlProps< Item > > | string; /** @@ -163,9 +163,9 @@ export type Field< Item > = { getValue?: ( args: { item: Item } ) => any; /** - * Whether the field supports bulk editing. + * Whether the field supports editing multiple items. */ - supportsBulkEditing?: boolean; + unique?: boolean; }; export type NormalizedField< Item > = Field< Item > & { @@ -175,12 +175,12 @@ export type NormalizedField< Item > = Field< Item > & { render: ComponentType< DataViewRenderFieldProps< Item > >; Edit: | ComponentType< DataFormControlProps< Item > > - | ComponentType< DataFormControlPropsWithoutBulkEditing< Item > >; + | ComponentType< DataFormUniqueControlProps< Item > >; sort: ( a: Item, b: Item, direction: SortDirection ) => number; isValid: ( item: Item, context?: ValidationContext ) => boolean; enableHiding: boolean; enableSorting: boolean; - supportsBulkEditing: boolean; + unique: boolean; }; /** @@ -198,10 +198,10 @@ export type DataFormControlProps< Item, ValueType = any > = { value?: ValueType | symbol; }; -export type DataFormControlPropsWithoutBulkEditing< - Item, - ValueType = any, -> = Omit< DataFormControlProps< Item, ValueType >, 'data' | 'value' > & { +export type DataFormUniqueControlProps< Item, ValueType = any > = Omit< + DataFormControlProps< Item, ValueType >, + 'data' | 'value' +> & { data: Item; value?: ValueType; }; @@ -570,5 +570,4 @@ export interface FieldLayoutProps< Item > { field: FormField; onChange: ( value: any ) => void; hideLabelFromVision?: boolean; - isBulkEditing?: boolean; } diff --git a/packages/fields/src/fields/slug/index.ts b/packages/fields/src/fields/slug/index.ts index 3ac2dc14e306b..e457578971ef9 100644 --- a/packages/fields/src/fields/slug/index.ts +++ b/packages/fields/src/fields/slug/index.ts @@ -17,7 +17,7 @@ const slugField: Field< BasePost > = { label: __( 'Slug' ), Edit: SlugEdit, render: SlugView, - supportsBulkEditing: false, + unique: true, }; /** diff --git a/packages/fields/src/fields/slug/slug-edit.tsx b/packages/fields/src/fields/slug/slug-edit.tsx index 3b23d086cb223..e206a83b49adf 100644 --- a/packages/fields/src/fields/slug/slug-edit.tsx +++ b/packages/fields/src/fields/slug/slug-edit.tsx @@ -14,7 +14,7 @@ import { useDispatch } from '@wordpress/data'; import { useCallback, useEffect, useRef } from '@wordpress/element'; import { store as noticesStore } from '@wordpress/notices'; import { safeDecodeURIComponent } from '@wordpress/url'; -import type { DataFormControlPropsWithoutBulkEditing } from '@wordpress/dataviews'; +import type { DataFormUniqueControlProps } from '@wordpress/dataviews'; import { __ } from '@wordpress/i18n'; /** @@ -27,7 +27,7 @@ const SlugEdit = ( { field, onChange, data, -}: DataFormControlPropsWithoutBulkEditing< BasePost > ) => { +}: DataFormUniqueControlProps< BasePost, string > ) => { const { id } = field; const slug = field.getValue( { item: data } ) || getSlug( data ); From b9ca4814a364228bb786921186636b9b31a28aea Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Thu, 12 Dec 2024 17:12:45 +0100 Subject: [PATCH 13/14] Update the loading state to account for bulk edits --- packages/edit-site/src/components/post-edit/index.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/edit-site/src/components/post-edit/index.js b/packages/edit-site/src/components/post-edit/index.js index 03bbb58c5ac0f..c0c7d9a2306a6 100644 --- a/packages/edit-site/src/components/post-edit/index.js +++ b/packages/edit-site/src/components/post-edit/index.js @@ -64,9 +64,12 @@ function PostEditForm( { postType, postId } ) { return { record: ids.length === 1 ? getEditedEntityRecord( ...args ) : null, - hasFinishedResolution: hasFinished( - 'getEditedEntityRecord', - args + hasFinishedResolution: ids.every( ( id ) => + hasFinished( 'getEditedEntityRecord', [ + 'postType', + postType, + id, + ] ) ), records: ids.length > 1 From 022ef5973100c7f4bd58cd44eaaf677965eab812 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Fri, 13 Dec 2024 13:35:55 +0100 Subject: [PATCH 14/14] Set postId to undefined if editing multiple records --- packages/fields/src/fields/parent/parent-edit.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/fields/src/fields/parent/parent-edit.tsx b/packages/fields/src/fields/parent/parent-edit.tsx index d5d1852c2d2bd..85a62657ee4e4 100644 --- a/packages/fields/src/fields/parent/parent-edit.tsx +++ b/packages/fields/src/fields/parent/parent-edit.tsx @@ -119,7 +119,7 @@ export function PageAttributesParent( { const [ fieldValue, setFieldValue ] = useState< null | string >( null ); const pageId = typeof value === 'symbol' ? undefined : value; - const postId = Array.isArray( data ) ? data[ 0 ].id : data.id; + const postId = Array.isArray( data ) ? undefined : data.id; const postTypeSlug = Array.isArray( data ) ? data[ 0 ].type : data.type; const { parentPostTitle, pageItems, isHierarchical } = useSelect(