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 @@ -31,6 +31,7 @@
### Breaking changes

- DataViews: rename `groupByField` to `groupBy.field` to allow control over both the field and the direction of the grouping. [#72780](https://github.com/WordPress/gutenberg/pull/72780)
- Types: FieldType is now FieldTypeName. [#73546](https://github.com/WordPress/gutenberg/pull/73546)

## 10.3.0 (2025-11-12)

Expand Down
2 changes: 1 addition & 1 deletion packages/dataviews/src/components/dataform/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useMemo } from '@wordpress/element';
*/
import type { DataFormProps } from '../../types';
import { DataFormProvider } from '../dataform-context';
import normalizeFields from '../../field-types/utils/normalize-fields';
import normalizeFields from '../../field-types';
import { DataFormLayout } from '../../dataform-layouts/data-form-layout';
import normalizeForm from '../../dataform-layouts/normalize-form';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import DataViewsViewConfig, {
DataviewsViewConfigDropdown,
ViewTypeMenu,
} from '../dataviews-view-config';
import normalizeFields from '../../field-types/utils/normalize-fields';
import normalizeFields from '../../field-types';
import type { ActionButton, Field, View, SupportedLayouts } from '../../types';
import type { SelectionOrUpdater } from '../../types/private';
type ItemWithId = { id: string };
Expand Down
2 changes: 1 addition & 1 deletion packages/dataviews/src/components/dataviews/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import DataViewsViewConfig, {
DataviewsViewConfigDropdown,
ViewTypeMenu,
} from '../dataviews-view-config';
import normalizeFields from '../../field-types/utils/normalize-fields';
import normalizeFields from '../../field-types';
import type { Action, Field, View, SupportedLayouts } from '../../types';
import type { SelectionOrUpdater } from '../../types/private';
type ItemWithId = { id: string };
Expand Down
138 changes: 50 additions & 88 deletions packages/dataviews/src/field-types/array.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,110 +6,72 @@ import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import type {
DataViewRenderFieldProps,
Field,
NormalizedField,
Operator,
Rules,
SortDirection,
} from '../types';
import type { DataViewRenderFieldProps, Rules, SortDirection } from '../types';
import type { FieldType } from '../types/private';
import {
OPERATOR_IS_ALL,
OPERATOR_IS_ANY,
OPERATOR_IS_NONE,
OPERATOR_IS_NOT_ALL,
} from '../constants';
import { getControl } from '../dataform-controls';
import hasElements from './utils/has-elements';
import getValueFromId from './utils/get-value-from-id';
import setValueFromId from './utils/set-value-from-id';
import getFilterBy from './utils/get-filter-by';

function render( { item, field }: DataViewRenderFieldProps< any > ) {
const value = field.getValue( { item } ) || [];
return value.join( ', ' );
}

const defaultOperators: Operator[] = [ OPERATOR_IS_ANY, OPERATOR_IS_NONE ];
const validOperators: Operator[] = [
OPERATOR_IS_ANY,
OPERATOR_IS_NONE,
OPERATOR_IS_ALL,
OPERATOR_IS_NOT_ALL,
];

export default function normalizeField< Item >(
field: Field< Item >
): NormalizedField< Item > {
const getValue = field.getValue || getValueFromId( field.id );
const setValue = field.setValue || setValueFromId( field.id );
const isValid: Rules< any > = {
elements: true,
custom: ( item: any, normalizedField ) => {
const value = normalizedField.getValue( { item } );

const sort = ( a: any, b: any, direction: SortDirection ) => {
// Sort arrays by length, then alphabetically by joined string
const valueA = getValue( a );
const valueB = getValue( b );
const arrA = Array.isArray( valueA ) ? valueA : [];
const arrB = Array.isArray( valueB ) ? valueB : [];
if ( arrA.length !== arrB.length ) {
return direction === 'asc'
? arrA.length - arrB.length
: arrB.length - arrA.length;
if (
! [ undefined, '', null ].includes( value ) &&
! Array.isArray( value )
) {
return __( 'Value must be an array.' );
}

const joinedA = arrA.join( ',' );
const joinedB = arrB.join( ',' );
return direction === 'asc'
? joinedA.localeCompare( joinedB )
: joinedB.localeCompare( joinedA );
};

const isValid: Rules< Item > = {
elements: true,
custom: ( item: any, normalizedField ) => {
const value = normalizedField.getValue( { item } );
// Only allow strings for now. Can be extended to other types in the future.
if ( ! value.every( ( v: any ) => typeof v === 'string' ) ) {
return __( 'Every value must be a string.' );
}

if (
! [ undefined, '', null ].includes( value ) &&
! Array.isArray( value )
) {
return __( 'Value must be an array.' );
}
return null;
},
};

// Only allow strings for now. Can be extended to other types in the future.
if ( ! value.every( ( v: any ) => typeof v === 'string' ) ) {
return __( 'Every value must be a string.' );
}
const sort = ( a: any, b: any, direction: SortDirection ) => {
// Sort arrays by length, then alphabetically by joined string
const arrA = Array.isArray( a ) ? a : [];
const arrB = Array.isArray( b ) ? b : [];
if ( arrA.length !== arrB.length ) {
return direction === 'asc'
? arrA.length - arrB.length
: arrB.length - arrA.length;
}

return null;
},
};
const joinedA = arrA.join( ',' );
const joinedB = arrB.join( ',' );
return direction === 'asc'
? joinedA.localeCompare( joinedB )
: joinedB.localeCompare( joinedA );
};

return {
id: field.id,
type: 'array',
label: field.label || field.id,
header: field.header || field.label || field.id,
description: field.description,
placeholder: field.placeholder,
getValue,
setValue,
elements: field.elements,
getElements: field.getElements,
hasElements: hasElements( field ),
render: field.render ?? render,
Edit: getControl( field, 'array' ),
sort: field.sort ?? sort,
isValid: {
...isValid,
...field.isValid,
},
isVisible: field.isVisible,
enableSorting: field.enableSorting ?? true,
enableGlobalSearch: field.enableGlobalSearch ?? false,
enableHiding: field.enableHiding ?? true,
readOnly: field.readOnly ?? false,
filterBy: getFilterBy( field, defaultOperators, validOperators ),
format: {},
};
}
export default {
type: 'array',
render,
Edit: 'array',
sort,
isValid,
enableSorting: true,
enableGlobalSearch: false,
defaultOperators: [ OPERATOR_IS_ANY, OPERATOR_IS_NONE ],
validOperators: [
OPERATOR_IS_ANY,
OPERATOR_IS_NONE,
OPERATOR_IS_ALL,
OPERATOR_IS_NOT_ALL,
],
getFormat: () => ( {} ),
} satisfies FieldType< any >;
119 changes: 40 additions & 79 deletions packages/dataviews/src/field-types/boolean.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,10 @@ import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import type {
DataViewRenderFieldProps,
Field,
NormalizedField,
Operator,
Rules,
SortDirection,
} from '../types';
import type { DataViewRenderFieldProps, Rules, SortDirection } from '../types';
import type { FieldType } from '../types/private';
import RenderFromElements from './utils/render-from-elements';
import { OPERATOR_IS, OPERATOR_IS_NOT } from '../constants';
import { getControl } from '../dataform-controls';
import hasElements from './utils/has-elements';
import getValueFromId from './utils/get-value-from-id';
import setValueFromId from './utils/set-value-from-id';
import getFilterBy from './utils/get-filter-by';

function render( { item, field }: DataViewRenderFieldProps< any > ) {
if ( field.hasElements ) {
Expand All @@ -38,76 +27,48 @@ function render( { item, field }: DataViewRenderFieldProps< any > ) {
return null;
}

export default function normalizeField< Item >(
field: Field< Item >
): NormalizedField< Item > {
const getValue = field.getValue || getValueFromId( field.id );
const setValue = field.setValue || setValueFromId( field.id );
const isValid: Rules< any > = {
elements: true,
custom: ( item: any, normalizedField ) => {
const value = normalizedField.getValue( { item } );

const sort = ( a: any, b: any, direction: SortDirection ) => {
const valueA = getValue( { item: a } );
const valueB = getValue( { item: b } );
const boolA = Boolean( valueA );
const boolB = Boolean( valueB );

if ( boolA === boolB ) {
return 0;
}

// In ascending order, false comes before true
if ( direction === 'asc' ) {
return boolA ? 1 : -1;
if (
! [ undefined, '', null ].includes( value ) &&
! [ true, false ].includes( value )
) {
return __( 'Value must be true, false, or undefined' );
}

// In descending order, true comes before false
return boolA ? -1 : 1;
};

const isValid: Rules< Item > = {
elements: true,
custom: ( item: any, normalizedField ) => {
const value = normalizedField.getValue( { item } );
return null;
},
};

if (
! [ undefined, '', null ].includes( value ) &&
! [ true, false ].includes( value )
) {
return __( 'Value must be true, false, or undefined' );
}
const sort = ( a: any, b: any, direction: SortDirection ) => {
const boolA = Boolean( a );
const boolB = Boolean( b );

return null;
},
};

const defaultOperators: Operator[] = [ OPERATOR_IS, OPERATOR_IS_NOT ];
if ( boolA === boolB ) {
return 0;
}

const validOperators: Operator[] = [ OPERATOR_IS, OPERATOR_IS_NOT ];
// In ascending order, false comes before true
if ( direction === 'asc' ) {
return boolA ? 1 : -1;
}

return {
id: field.id,
type: 'boolean',
label: field.label || field.id,
header: field.header || field.label || field.id,
description: field.description,
placeholder: field.placeholder,
getValue,
setValue,
elements: field.elements,
getElements: field.getElements,
hasElements: hasElements( field ),
render: field.render ?? render,
Edit: getControl( field, 'checkbox' ),
sort: field.sort ?? sort,
isValid: {
...isValid,
...field.isValid,
},
isVisible: field.isVisible,
enableSorting: field.enableSorting ?? true,
enableGlobalSearch: field.enableGlobalSearch ?? false,
enableHiding: field.enableHiding ?? true,
readOnly: field.readOnly ?? false,
filterBy: getFilterBy( field, defaultOperators, validOperators ),
format: {},
};
}
// In descending order, true comes before false
return boolA ? -1 : 1;
};

export default {
type: 'boolean',
render,
Edit: 'checkbox',
sort,
isValid,
enableSorting: true,
enableGlobalSearch: false,
defaultOperators: [ OPERATOR_IS, OPERATOR_IS_NOT ],
validOperators: [ OPERATOR_IS, OPERATOR_IS_NOT ],
getFormat: () => ( {} ),
} satisfies FieldType< any >;
Loading
Loading