Skip to content

Commit 01136d1

Browse files
oandregalyouknowriadntsekouras
authored
Field API: simplify field normalization (#73387)
Co-authored-by: oandregal <oandregal@git.wordpress.org> Co-authored-by: youknowriad <youknowriad@git.wordpress.org> Co-authored-by: ntsekouras <ntsekouras@git.wordpress.org>
1 parent 09eeecb commit 01136d1

32 files changed

+1276
-910
lines changed

packages/dataviews/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Enhancements
66

7+
- Simplify field normalization and types. [#73387](https://github.com/WordPress/gutenberg/pull/73387)
78
- DataViews table layout: make checkboxes permanently visible when bulk actions are available. [#73245](https://github.com/WordPress/gutenberg/pull/73245)
89
- Documentation: surface better the `type` property in the documentation. [#73349](https://github.com/WordPress/gutenberg/pull/73349)
910
- DataViews: Make sticky elements (table headers, footer, actions column) inherit background colors from parent container. This allows DataViews instances to seamlessly adapt to containers with custom background colors. [#73240](https://github.com/WordPress/gutenberg/pull/73240)

packages/dataviews/src/components/dataform/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { useMemo } from '@wordpress/element';
88
*/
99
import type { DataFormProps } from '../../types';
1010
import { DataFormProvider } from '../dataform-context';
11-
import normalizeFields from '../../utils/normalize-fields';
11+
import normalizeFields from '../../field-types/utils/normalize-fields';
1212
import { DataFormLayout } from '../../dataform-layouts/data-form-layout';
1313
import normalizeForm from '../../dataform-layouts/normalize-form';
1414

packages/dataviews/src/components/dataviews-filters/filter.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@ import { useRef, createInterpolateElement } from '@wordpress/element';
2121
import { closeSmall } from '@wordpress/icons';
2222
import { dateI18n, getDate } from '@wordpress/date';
2323

24-
const ENTER = 'Enter';
25-
const SPACE = ' ';
26-
2724
/**
2825
* Internal dependencies
2926
*/
@@ -57,6 +54,7 @@ import {
5754
import type {
5855
Filter,
5956
NormalizedField,
57+
NormalizedFieldDate,
6058
NormalizedFilter,
6159
Operator,
6260
Option,
@@ -65,6 +63,9 @@ import type {
6563
import useElements from '../../hooks/use-elements';
6664
import parseDateTime from '../../field-types/utils/parse-date-time';
6765

66+
const ENTER = 'Enter';
67+
const SPACE = ' ';
68+
6869
interface FilterTextProps {
6970
activeElements: Option[];
7071
filterInView?: Filter;
@@ -503,7 +504,10 @@ export default function Filter( {
503504
try {
504505
const dateValue = parseDateTime( label );
505506
if ( dateValue !== null ) {
506-
label = dateI18n( field.format.date, getDate( label ) );
507+
label = dateI18n(
508+
( field as NormalizedFieldDate< any > ).format.date,
509+
getDate( label )
510+
);
507511
}
508512
} catch ( e ) {
509513
label = filterInView.value;

packages/dataviews/src/components/dataviews-picker/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import DataViewsViewConfig, {
2929
DataviewsViewConfigDropdown,
3030
ViewTypeMenu,
3131
} from '../dataviews-view-config';
32-
import normalizeFields from '../../utils/normalize-fields';
32+
import normalizeFields from '../../field-types/utils/normalize-fields';
3333
import type { ActionButton, Field, View, SupportedLayouts } from '../../types';
3434
import type { SelectionOrUpdater } from '../../types/private';
3535
type ItemWithId = { id: string };

packages/dataviews/src/components/dataviews/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import DataViewsViewConfig, {
3030
DataviewsViewConfigDropdown,
3131
ViewTypeMenu,
3232
} from '../dataviews-view-config';
33-
import normalizeFields from '../../utils/normalize-fields';
33+
import normalizeFields from '../../field-types/utils/normalize-fields';
3434
import type { Action, Field, View, SupportedLayouts } from '../../types';
3535
import type { SelectionOrUpdater } from '../../types/private';
3636
type ItemWithId = { id: string };

packages/dataviews/src/dataform-controls/date.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,11 @@ import { unlock } from '../lock-unlock';
4848
import type {
4949
DataFormControlProps,
5050
FieldValidity,
51+
FormatDate,
5152
NormalizedField,
5253
} from '../types';
5354
import getCustomValidity from './utils/get-custom-validity';
54-
import { weekStartsOnToNumber } from '../utils/week-starts-on';
55+
import { weekStartsOnToNumber } from '../field-types/utils/week-starts-on';
5556

5657
const { DateCalendar, DateRangeCalendar } = unlock( componentsPrivateApis );
5758

@@ -271,9 +272,13 @@ function CalendarDateControl< Item >( {
271272
null
272273
);
273274

274-
let weekStartsOn;
275+
let weekStartsOn = getSettings().l10n.startOfWeek;
275276
if ( type === 'date' ) {
276-
weekStartsOn = weekStartsOnToNumber( fieldFormat.weekStartsOn );
277+
// If the field type is date, we've already normalized the format,
278+
// and so it's safe to tell TypeScript to trust us ("as Required<Format>").
279+
weekStartsOn = weekStartsOnToNumber(
280+
( fieldFormat as Required< FormatDate > ).weekStartsOn
281+
);
277282
}
278283

279284
const fieldValue = getValue( { item: data } );
@@ -437,7 +442,11 @@ function CalendarDateRangeControl< Item >( {
437442

438443
let weekStartsOn;
439444
if ( type === 'date' ) {
440-
weekStartsOn = weekStartsOnToNumber( fieldFormat.weekStartsOn );
445+
// If the field type is date, we've already normalized the format,
446+
// and so it's safe to tell TypeScript to trust us ("as Required<Format>").
447+
weekStartsOn = weekStartsOnToNumber(
448+
( fieldFormat as Required< FormatDate > ).weekStartsOn
449+
);
441450
}
442451

443452
const onChangeCallback = useCallback(

packages/dataviews/src/dataform-controls/index.tsx

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,7 @@ import type { ComponentType } from 'react';
66
/**
77
* Internal dependencies
88
*/
9-
import type {
10-
DataFormControlProps,
11-
Field,
12-
FieldTypeDefinition,
13-
EditConfig,
14-
} from '../types';
9+
import type { DataFormControlProps, Field, EditConfig } from '../types';
1510
import checkbox from './checkbox';
1611
import datetime from './datetime';
1712
import date from './date';
@@ -29,7 +24,7 @@ import toggleGroup from './toggle-group';
2924
import array from './array';
3025
import color from './color';
3126
import password from './password';
32-
import hasElements from '../utils/has-elements';
27+
import hasElements from '../field-types/utils/has-elements';
3328

3429
interface FormControls {
3530
[ key: string ]: ComponentType< DataFormControlProps< any > >;
@@ -64,6 +59,9 @@ function isEditConfig( value: any ): value is EditConfig {
6459
function createConfiguredControl( config: EditConfig ) {
6560
const { control, ...controlConfig } = config;
6661
const BaseControlType = getControlByType( control );
62+
if ( BaseControlType === null ) {
63+
return null;
64+
}
6765

6866
return function ConfiguredControl< Item >(
6967
props: DataFormControlProps< Item >
@@ -74,8 +72,8 @@ function createConfiguredControl( config: EditConfig ) {
7472

7573
export function getControl< Item >(
7674
field: Field< Item >,
77-
fieldTypeDefinition: FieldTypeDefinition< Item >
78-
) {
75+
fallback: string | null
76+
): ComponentType< DataFormControlProps< Item > > | null {
7977
if ( typeof field.Edit === 'function' ) {
8078
return field.Edit;
8179
}
@@ -92,21 +90,17 @@ export function getControl< Item >(
9290
return getControlByType( 'select' );
9391
}
9492

95-
if ( typeof fieldTypeDefinition.Edit === 'string' ) {
96-
return getControlByType( fieldTypeDefinition.Edit );
97-
}
98-
99-
if ( isEditConfig( fieldTypeDefinition.Edit ) ) {
100-
return createConfiguredControl( fieldTypeDefinition.Edit );
93+
if ( fallback === null ) {
94+
return null;
10195
}
10296

103-
return fieldTypeDefinition.Edit;
97+
return getControlByType( fallback );
10498
}
10599

106100
export function getControlByType( type: string ) {
107101
if ( Object.keys( FORM_CONTROLS ).includes( type ) ) {
108102
return FORM_CONTROLS[ type ];
109103
}
110104

111-
throw 'Control ' + type + ' not found';
105+
return null;
112106
}

packages/dataviews/src/field-types/array.tsx

Lines changed: 74 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,45 +8,66 @@ import { __ } from '@wordpress/i18n';
88
*/
99
import type {
1010
DataViewRenderFieldProps,
11-
SortDirection,
12-
FieldTypeDefinition,
11+
Field,
1312
NormalizedField,
13+
Operator,
14+
Rules,
15+
SortDirection,
1416
} from '../types';
1517
import {
1618
OPERATOR_IS_ALL,
1719
OPERATOR_IS_ANY,
1820
OPERATOR_IS_NONE,
1921
OPERATOR_IS_NOT_ALL,
2022
} from '../constants';
21-
22-
// Sort arrays by length, then alphabetically by joined string
23-
function sort( valueA: any, valueB: any, direction: SortDirection ) {
24-
const arrA = Array.isArray( valueA ) ? valueA : [];
25-
const arrB = Array.isArray( valueB ) ? valueB : [];
26-
if ( arrA.length !== arrB.length ) {
27-
return direction === 'asc'
28-
? arrA.length - arrB.length
29-
: arrB.length - arrA.length;
30-
}
31-
32-
const joinedA = arrA.join( ',' );
33-
const joinedB = arrB.join( ',' );
34-
return direction === 'asc'
35-
? joinedA.localeCompare( joinedB )
36-
: joinedB.localeCompare( joinedA );
37-
}
23+
import { getControl } from '../dataform-controls';
24+
import hasElements from './utils/has-elements';
25+
import getValueFromId from './utils/get-value-from-id';
26+
import setValueFromId from './utils/set-value-from-id';
27+
import getFilterBy from './utils/get-filter-by';
3828

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

44-
const arrayFieldType: FieldTypeDefinition< any > = {
45-
sort,
46-
isValid: {
34+
const defaultOperators: Operator[] = [ OPERATOR_IS_ANY, OPERATOR_IS_NONE ];
35+
const validOperators: Operator[] = [
36+
OPERATOR_IS_ANY,
37+
OPERATOR_IS_NONE,
38+
OPERATOR_IS_ALL,
39+
OPERATOR_IS_NOT_ALL,
40+
];
41+
42+
export default function normalizeField< Item >(
43+
field: Field< Item >
44+
): NormalizedField< Item > {
45+
const getValue = field.getValue || getValueFromId( field.id );
46+
const setValue = field.setValue || setValueFromId( field.id );
47+
48+
const sort = ( a: any, b: any, direction: SortDirection ) => {
49+
// Sort arrays by length, then alphabetically by joined string
50+
const valueA = getValue( a );
51+
const valueB = getValue( b );
52+
const arrA = Array.isArray( valueA ) ? valueA : [];
53+
const arrB = Array.isArray( valueB ) ? valueB : [];
54+
if ( arrA.length !== arrB.length ) {
55+
return direction === 'asc'
56+
? arrA.length - arrB.length
57+
: arrB.length - arrA.length;
58+
}
59+
60+
const joinedA = arrA.join( ',' );
61+
const joinedB = arrB.join( ',' );
62+
return direction === 'asc'
63+
? joinedA.localeCompare( joinedB )
64+
: joinedB.localeCompare( joinedA );
65+
};
66+
67+
const isValid: Rules< Item > = {
4768
elements: true,
48-
custom: ( item: any, field: NormalizedField< any > ) => {
49-
const value = field.getValue( { item } );
69+
custom: ( item: any, normalizedField ) => {
70+
const value = normalizedField.getValue( { item } );
5071

5172
if (
5273
! [ undefined, '', null ].includes( value ) &&
@@ -62,19 +83,33 @@ const arrayFieldType: FieldTypeDefinition< any > = {
6283

6384
return null;
6485
},
65-
},
66-
Edit: 'array', // Use array control
67-
render,
68-
enableSorting: true,
69-
filterBy: {
70-
defaultOperators: [ OPERATOR_IS_ANY, OPERATOR_IS_NONE ],
71-
validOperators: [
72-
OPERATOR_IS_ANY,
73-
OPERATOR_IS_NONE,
74-
OPERATOR_IS_ALL,
75-
OPERATOR_IS_NOT_ALL,
76-
],
77-
},
78-
};
86+
};
7987

80-
export default arrayFieldType;
88+
return {
89+
id: field.id,
90+
type: 'array',
91+
label: field.label || field.id,
92+
header: field.header || field.label || field.id,
93+
description: field.description,
94+
placeholder: field.placeholder,
95+
getValue,
96+
setValue,
97+
elements: field.elements,
98+
getElements: field.getElements,
99+
hasElements: hasElements( field ),
100+
render: field.render ?? render,
101+
Edit: getControl( field, 'array' ),
102+
sort: field.sort ?? sort,
103+
isValid: {
104+
...isValid,
105+
...field.isValid,
106+
},
107+
isVisible: field.isVisible,
108+
enableSorting: field.enableSorting ?? true,
109+
enableGlobalSearch: field.enableGlobalSearch ?? false,
110+
enableHiding: field.enableHiding ?? true,
111+
readOnly: field.readOnly ?? false,
112+
filterBy: getFilterBy( field, defaultOperators, validOperators ),
113+
format: {},
114+
};
115+
}

0 commit comments

Comments
 (0)