Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/dataviews/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Features

- Introduce a new `array` DataForm Edit control that supports multi-selection. [#71136](https://github.com/WordPress/gutenberg/pull/71136)
- Add `enableMoving` option to the `table` layout to allow or disallow column moving left and right. [#71120](https://github.com/WordPress/gutenberg/pull/71120)

## 6.0.0 (2025-08-07)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type SamplePost = {
password?: string;
filesize?: number;
dimensions?: string;
tags?: string[];
};

const meta = {
Expand Down Expand Up @@ -143,6 +144,20 @@ const fields = [
type: 'text' as const,
readOnly: true,
},
{
id: 'tags',
label: 'Tags',
type: 'array' as const,
placeholder: 'Enter comma-separated tags',
description: 'Add tags separated by commas (e.g., "tag1, tag2, tag3")',
elements: [
{ value: 'astronomy', label: 'Astronomy' },
{ value: 'book-review', label: 'Book review' },
{ value: 'event', label: 'Event' },
{ value: 'photography', label: 'Photography' },
{ value: 'travel', label: 'Travel' },
],
},
] as Field< SamplePost >[];

export const Default = ( {
Expand All @@ -165,6 +180,7 @@ export const Default = ( {
can_comment: false,
filesize: 1024,
dimensions: '1920x1080',
tags: [ 'photography' ],
} );

const form = useMemo(
Expand All @@ -185,6 +201,7 @@ export const Default = ( {
'can_comment',
'filesize',
'dimensions',
'tags',
],
} ),
[ type, labelPosition ]
Expand Down Expand Up @@ -222,6 +239,7 @@ const CombinedFieldsComponent = ( {
birthdate: '1950-02-23T12:00:00',
filesize: 1024,
dimensions: '1920x1080',
tags: [ 'photography' ],
} );

const form = useMemo(
Expand All @@ -239,6 +257,7 @@ const CombinedFieldsComponent = ( {
'author',
'filesize',
'dimensions',
'tags',
],
} ),
[ type, labelPosition ]
Expand Down
85 changes: 85 additions & 0 deletions packages/dataviews/src/dataform-controls/array.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* WordPress dependencies
*/
import { FormTokenField } from '@wordpress/components';
import { useCallback, useMemo } from '@wordpress/element';

/**
* Internal dependencies
*/
import type { DataFormControlProps } from '../types';

export default function ArrayControl< Item >( {
data,
field,
onChange,
hideLabelFromVision,
}: DataFormControlProps< Item > ) {
const { id, label, placeholder, elements } = field;
const value = field.getValue( { item: data } );

const findElementByValue = useCallback(
( suggestionValue: string ) => {
return elements?.find(
( suggestion ) => suggestion.value === suggestionValue
);
},
[ elements ]
);

const findElementByLabel = useCallback(
( suggestionLabel: string ) => {
return elements?.find(
( suggestion ) => suggestion.label === suggestionLabel
);
},
[ elements ]
);

// Ensure value is an array
const arrayValue = useMemo(
() =>
Array.isArray( value )
? value.map( ( token ) => {
const tokenLabel = findElementByValue( token )?.label;
return tokenLabel || token;
} )
: [],
[ value, findElementByValue ]
);

const onChangeControl = useCallback(
( tokens: ( string | { value: string } )[] ) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to understand when the returned tokens can be an array of objects instead of strings. Unless I'm missing some context, I can't reproduce; every individual token is always a string. Can you confirm this? If so, can we simplify this to only handle strings?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is because the onChange prop from FormTokenField expects that. I haven't been able to reproduce this too, as it's always strings, but changing that will raise a TypeScript error 😞

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I missed that. It's all good, thanks for the context.

// Convert TokenItem objects to strings
const stringTokens = tokens.map( ( token ) => {
if ( typeof token !== 'string' ) {
return token.value;
}

const tokenByLabel = findElementByLabel( token );

return tokenByLabel?.value || token;
} );

onChange( {
[ id ]: stringTokens,
} );
},
[ id, onChange, findElementByLabel ]
);

return (
<FormTokenField
label={ hideLabelFromVision ? undefined : label }
value={ arrayValue }
onChange={ onChangeControl }
placeholder={ placeholder }
suggestions={
elements?.map( ( suggestion ) => suggestion.label ) ?? []
}
__experimentalExpandOnFocus={ elements && elements.length > 0 }
__next40pxDefaultSize
__nextHasNoMarginBottom
/>
);
}
4 changes: 3 additions & 1 deletion packages/dataviews/src/dataform-controls/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ import select from './select';
import text from './text';
import toggleGroup from './toggle-group';
import boolean from './boolean';
import array from './array';

interface FormControls {
[ key: string ]: ComponentType< DataFormControlProps< any > >;
}

const FORM_CONTROLS: FormControls = {
array,
boolean,
checkbox,
datetime,
Expand All @@ -51,7 +53,7 @@ export function getControl< Item >(
return getControlByType( field.Edit );
}

if ( field.elements ) {
Copy link
Member

@oandregal oandregal Aug 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is okay for now, it's how it's already working. I think a better approach would be to actually remove this from here and make every field definition absorb this (handle how they want to use or not use the elements information). Leaving this as feedback in case you are available to follow up on this in a separate PR, as it involves testing/making sure every field type works well for editing and filtering.

if ( field.elements && field.type !== 'array' ) {
return getControlByType( 'select' );
}

Expand Down
2 changes: 1 addition & 1 deletion packages/dataviews/src/field-types/array.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const arrayFieldType: FieldTypeDefinition< any > = {
return null;
},
},
Edit: null, // Not implemented yet
Edit: 'array', // Use array control
render,
enableSorting: true,
filterBy: {
Expand Down
Loading