From cfc93cf2caa25fd0126587c0e96cf08e0532da6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Tue, 18 Nov 2025 10:59:26 +0100 Subject: [PATCH 01/15] Implement getFormat in all FieldTypeDefinitions --- packages/dataviews/src/field-types/array.tsx | 4 +++- .../dataviews/src/field-types/boolean.tsx | 4 +++- packages/dataviews/src/field-types/color.tsx | 6 +++-- packages/dataviews/src/field-types/date.tsx | 22 +++++++++++++++++-- .../dataviews/src/field-types/datetime.tsx | 4 +++- packages/dataviews/src/field-types/email.tsx | 6 +++-- packages/dataviews/src/field-types/index.tsx | 2 ++ .../dataviews/src/field-types/integer.tsx | 6 +++-- packages/dataviews/src/field-types/media.tsx | 3 ++- packages/dataviews/src/field-types/number.tsx | 6 +++-- .../dataviews/src/field-types/password.tsx | 4 +++- .../dataviews/src/field-types/telephone.tsx | 4 +++- packages/dataviews/src/field-types/text.tsx | 4 +++- packages/dataviews/src/field-types/url.tsx | 4 +++- packages/dataviews/src/types/field-api.ts | 4 ++++ 15 files changed, 65 insertions(+), 18 deletions(-) diff --git a/packages/dataviews/src/field-types/array.tsx b/packages/dataviews/src/field-types/array.tsx index a4e78871a34681..2c596570690839 100644 --- a/packages/dataviews/src/field-types/array.tsx +++ b/packages/dataviews/src/field-types/array.tsx @@ -8,9 +8,10 @@ import { __ } from '@wordpress/i18n'; */ import type { DataViewRenderFieldProps, - SortDirection, FieldTypeDefinition, NormalizedField, + NormalizedFormat, + SortDirection, } from '../types'; import { OPERATOR_IS_ALL, @@ -65,6 +66,7 @@ const arrayFieldType: FieldTypeDefinition< any > = { }, Edit: 'array', // Use array control render, + getFormat: (): NormalizedFormat => ( {} ), enableSorting: true, filterBy: { defaultOperators: [ OPERATOR_IS_ANY, OPERATOR_IS_NONE ], diff --git a/packages/dataviews/src/field-types/boolean.tsx b/packages/dataviews/src/field-types/boolean.tsx index 6e58c059f31e2b..060b1c813b9a09 100644 --- a/packages/dataviews/src/field-types/boolean.tsx +++ b/packages/dataviews/src/field-types/boolean.tsx @@ -8,9 +8,10 @@ import { __ } from '@wordpress/i18n'; */ import type { DataViewRenderFieldProps, - SortDirection, FieldTypeDefinition, NormalizedField, + NormalizedFormat, + SortDirection, } from '../types'; import RenderFromElements from './utils/render-from-elements'; import { OPERATOR_IS, OPERATOR_IS_NOT } from '../constants'; @@ -65,6 +66,7 @@ export default { return null; }, + getFormat: (): NormalizedFormat => ( {} ), enableSorting: true, filterBy: { defaultOperators: [ OPERATOR_IS, OPERATOR_IS_NOT ], diff --git a/packages/dataviews/src/field-types/color.tsx b/packages/dataviews/src/field-types/color.tsx index a293e77f3a6280..ab612f56619493 100644 --- a/packages/dataviews/src/field-types/color.tsx +++ b/packages/dataviews/src/field-types/color.tsx @@ -13,9 +13,10 @@ import { __ } from '@wordpress/i18n'; */ import type { DataViewRenderFieldProps, - SortDirection, - NormalizedField, FieldTypeDefinition, + NormalizedField, + NormalizedFormat, + SortDirection, } from '../types'; import RenderFromElements from './utils/render-from-elements'; import { @@ -101,6 +102,7 @@ export default { ); }, + getFormat: (): NormalizedFormat => ( {} ), enableSorting: true, filterBy: { defaultOperators: [ OPERATOR_IS_ANY, OPERATOR_IS_NONE ], diff --git a/packages/dataviews/src/field-types/date.tsx b/packages/dataviews/src/field-types/date.tsx index fa23f660c4b464..3900b16d171e22 100644 --- a/packages/dataviews/src/field-types/date.tsx +++ b/packages/dataviews/src/field-types/date.tsx @@ -1,15 +1,18 @@ /** * WordPress dependencies */ -import { dateI18n, getDate } from '@wordpress/date'; +import { dateI18n, getDate, getSettings } from '@wordpress/date'; /** * Internal dependencies */ import type { DataViewRenderFieldProps, - SortDirection, + DayString, + Field, FieldTypeDefinition, + NormalizedFormat, + SortDirection, } from '../types'; import RenderFromElements from './utils/render-from-elements'; import { @@ -23,6 +26,7 @@ import { OPERATOR_OVER, OPERATOR_BETWEEN, } from '../constants'; +import { DAYS_OF_WEEK, numberToWeekStartsOn } from '../utils/week-starts-on'; function sort( a: any, b: any, direction: SortDirection ) { const timeA = new Date( a ).getTime(); @@ -60,6 +64,20 @@ export default { return dateI18n( field.format.date, getDate( value ) ); }, enableSorting: true, + getFormat: ( field: Field< any > ): NormalizedFormat => { + return { + date: + field.format?.date !== undefined && + typeof field.format.date === 'string' + ? field.format.date + : getSettings().formats.date, + weekStartsOn: + field.format?.weekStartsOn !== undefined && + DAYS_OF_WEEK.includes( field.format?.weekStartsOn as DayString ) + ? field.format.weekStartsOn + : numberToWeekStartsOn( getSettings().l10n.startOfWeek ), + }; + }, filterBy: { defaultOperators: [ OPERATOR_ON, diff --git a/packages/dataviews/src/field-types/datetime.tsx b/packages/dataviews/src/field-types/datetime.tsx index c857c38c9389d3..2dfba4edb30b75 100644 --- a/packages/dataviews/src/field-types/datetime.tsx +++ b/packages/dataviews/src/field-types/datetime.tsx @@ -3,8 +3,9 @@ */ import type { DataViewRenderFieldProps, - SortDirection, FieldTypeDefinition, + NormalizedFormat, + SortDirection, } from '../types'; import RenderFromElements from './utils/render-from-elements'; import parseDateTime from './utils/parse-date-time'; @@ -50,6 +51,7 @@ export default { return null; } }, + getFormat: (): NormalizedFormat => ( {} ), enableSorting: true, filterBy: { defaultOperators: [ diff --git a/packages/dataviews/src/field-types/email.tsx b/packages/dataviews/src/field-types/email.tsx index 8af0d443a9a24f..7f911461abfe8e 100644 --- a/packages/dataviews/src/field-types/email.tsx +++ b/packages/dataviews/src/field-types/email.tsx @@ -8,9 +8,10 @@ import { __ } from '@wordpress/i18n'; */ import type { DataViewRenderFieldProps, - SortDirection, - NormalizedField, FieldTypeDefinition, + NormalizedField, + NormalizedFormat, + SortDirection, } from '../types'; import RenderFromElements from './utils/render-from-elements'; import { @@ -61,6 +62,7 @@ export default { field.getValue( { item } ) ); }, + getFormat: (): NormalizedFormat => ( {} ), enableSorting: true, filterBy: { defaultOperators: [ OPERATOR_IS_ANY, OPERATOR_IS_NONE ], diff --git a/packages/dataviews/src/field-types/index.tsx b/packages/dataviews/src/field-types/index.tsx index 8db2da452da65a..e61b5e74124059 100644 --- a/packages/dataviews/src/field-types/index.tsx +++ b/packages/dataviews/src/field-types/index.tsx @@ -5,6 +5,7 @@ import type { DataViewRenderFieldProps, FieldType, FieldTypeDefinition, + NormalizedFormat, SortDirection, } from '../types'; import { default as email } from './email'; @@ -108,6 +109,7 @@ export default function getFieldTypeDefinition< Item >( field.getValue( { item } ) ); }, + getFormat: (): NormalizedFormat => ( {} ), enableSorting: true, filterBy: { defaultOperators: [ OPERATOR_IS, OPERATOR_IS_NOT ], diff --git a/packages/dataviews/src/field-types/integer.tsx b/packages/dataviews/src/field-types/integer.tsx index 7674cdb9d3329c..212eb3bca10651 100644 --- a/packages/dataviews/src/field-types/integer.tsx +++ b/packages/dataviews/src/field-types/integer.tsx @@ -8,9 +8,10 @@ import { __ } from '@wordpress/i18n'; */ import type { DataViewRenderFieldProps, - SortDirection, - NormalizedField, FieldTypeDefinition, + NormalizedField, + NormalizedFormat, + SortDirection, } from '../types'; import RenderFromElements from './utils/render-from-elements'; import { @@ -55,6 +56,7 @@ export default { field.getValue( { item } ) ); }, + getFormat: (): NormalizedFormat => ( {} ), enableSorting: true, filterBy: { defaultOperators: [ diff --git a/packages/dataviews/src/field-types/media.tsx b/packages/dataviews/src/field-types/media.tsx index 62fd76458a0ffa..7d4e684629a3f6 100644 --- a/packages/dataviews/src/field-types/media.tsx +++ b/packages/dataviews/src/field-types/media.tsx @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import type { FieldTypeDefinition } from '../types'; +import type { FieldTypeDefinition, NormalizedFormat } from '../types'; function sort() { return 0; @@ -15,6 +15,7 @@ export default { }, Edit: null, render: () => null, + getFormat: (): NormalizedFormat => ( {} ), enableSorting: false, filterBy: false, } satisfies FieldTypeDefinition< any >; diff --git a/packages/dataviews/src/field-types/number.tsx b/packages/dataviews/src/field-types/number.tsx index d968c6bc66a519..ad533f0bdb94d8 100644 --- a/packages/dataviews/src/field-types/number.tsx +++ b/packages/dataviews/src/field-types/number.tsx @@ -8,9 +8,10 @@ import { __ } from '@wordpress/i18n'; */ import type { DataViewRenderFieldProps, - SortDirection, - NormalizedField, FieldTypeDefinition, + NormalizedField, + NormalizedFormat, + SortDirection, } from '../types'; import { OPERATOR_IS, @@ -62,6 +63,7 @@ export default { return null; }, + getFormat: (): NormalizedFormat => ( {} ), enableSorting: true, filterBy: { defaultOperators: [ diff --git a/packages/dataviews/src/field-types/password.tsx b/packages/dataviews/src/field-types/password.tsx index 6a9d5b95273a75..6ed19430088310 100644 --- a/packages/dataviews/src/field-types/password.tsx +++ b/packages/dataviews/src/field-types/password.tsx @@ -3,8 +3,9 @@ */ import type { DataViewRenderFieldProps, - SortDirection, FieldTypeDefinition, + SortDirection, + NormalizedFormat, } from '../types'; import RenderFromElements from './utils/render-from-elements'; @@ -28,6 +29,7 @@ export default { '••••••••' ); }, + getFormat: (): NormalizedFormat => ( {} ), enableSorting: false, filterBy: false, } satisfies FieldTypeDefinition< any >; diff --git a/packages/dataviews/src/field-types/telephone.tsx b/packages/dataviews/src/field-types/telephone.tsx index 7fe98248f0ba59..9498b5a87f5681 100644 --- a/packages/dataviews/src/field-types/telephone.tsx +++ b/packages/dataviews/src/field-types/telephone.tsx @@ -3,8 +3,9 @@ */ import type { DataViewRenderFieldProps, - SortDirection, FieldTypeDefinition, + SortDirection, + NormalizedFormat, } from '../types'; import RenderFromElements from './utils/render-from-elements'; import { @@ -39,6 +40,7 @@ export default { field.getValue( { item } ) ); }, + getFormat: (): NormalizedFormat => ( {} ), enableSorting: true, filterBy: { defaultOperators: [ OPERATOR_IS_ANY, OPERATOR_IS_NONE ], diff --git a/packages/dataviews/src/field-types/text.tsx b/packages/dataviews/src/field-types/text.tsx index 5cb5944248d0c3..fd79e83730c55a 100644 --- a/packages/dataviews/src/field-types/text.tsx +++ b/packages/dataviews/src/field-types/text.tsx @@ -3,8 +3,9 @@ */ import type { DataViewRenderFieldProps, - SortDirection, FieldTypeDefinition, + NormalizedFormat, + SortDirection, } from '../types'; import RenderFromElements from './utils/render-from-elements'; import { @@ -39,6 +40,7 @@ export default { field.getValue( { item } ) ); }, + getFormat: (): NormalizedFormat => ( {} ), enableSorting: true, filterBy: { defaultOperators: [ OPERATOR_IS_ANY, OPERATOR_IS_NONE ], diff --git a/packages/dataviews/src/field-types/url.tsx b/packages/dataviews/src/field-types/url.tsx index 8d24ff96fc1f04..6fd1c50ad2987d 100644 --- a/packages/dataviews/src/field-types/url.tsx +++ b/packages/dataviews/src/field-types/url.tsx @@ -3,8 +3,9 @@ */ import type { DataViewRenderFieldProps, - SortDirection, FieldTypeDefinition, + NormalizedFormat, + SortDirection, } from '../types'; import RenderFromElements from './utils/render-from-elements'; import { @@ -39,6 +40,7 @@ export default { field.getValue( { item } ) ); }, + getFormat: (): NormalizedFormat => ( {} ), enableSorting: true, filterBy: { defaultOperators: [ OPERATOR_IS_ANY, OPERATOR_IS_NONE ], diff --git a/packages/dataviews/src/types/field-api.ts b/packages/dataviews/src/types/field-api.ts index 6a0cc90bbde59f..109b013d39208c 100644 --- a/packages/dataviews/src/types/field-api.ts +++ b/packages/dataviews/src/types/field-api.ts @@ -136,6 +136,8 @@ export type FieldTypeDefinition< Item > = { */ filterBy: FilterConfigForType | false; + getFormat: ( field: Field< Item > ) => NormalizedFormat; + /** * Whether the field is readOnly. * If `true`, the value will be rendered using the `render` callback. @@ -315,6 +317,8 @@ export type Field< Item > = { format?: FormatDate; }; +export type NormalizedFormat = Required< FormatDate > | {}; + /** * Format for date fields: * From 8e08c82696f2d2b410d5d8b55c9f045533413c2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Tue, 18 Nov 2025 11:00:31 +0100 Subject: [PATCH 02/15] Use getFormat in normalizeFields --- .../dataviews/src/utils/normalize-fields.ts | 24 +------------------ 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/packages/dataviews/src/utils/normalize-fields.ts b/packages/dataviews/src/utils/normalize-fields.ts index 42fda2e86a2e01..30e0e16fd96be3 100644 --- a/packages/dataviews/src/utils/normalize-fields.ts +++ b/packages/dataviews/src/utils/normalize-fields.ts @@ -3,17 +3,11 @@ */ import type { FunctionComponent } from 'react'; -/** - * WordPress dependencies - */ -import { getSettings } from '@wordpress/date'; - /** * Internal dependencies */ import getFieldTypeDefinition from '../field-types'; import type { - DayString, DataViewRenderFieldProps, Field, FieldTypeDefinition, @@ -27,7 +21,6 @@ import { SINGLE_SELECTION_OPERATORS, } from '../constants'; import hasElements from './has-elements'; -import { numberToWeekStartsOn, DAYS_OF_WEEK } from './week-starts-on'; const getValueFromId = ( id: string ) => @@ -225,22 +218,7 @@ export default function normalizeFields< Item >( }; if ( field.type === 'date' ) { - const format = { - date: - field.format?.date !== undefined && - typeof field.format.date === 'string' - ? field.format.date - : getSettings().formats.date, - weekStartsOn: - field.format?.weekStartsOn !== undefined && - DAYS_OF_WEEK.includes( - field.format?.weekStartsOn as DayString - ) - ? field.format.weekStartsOn - : numberToWeekStartsOn( - getSettings().l10n.startOfWeek - ), - }; + const format = fieldTypeDefinition.getFormat( field ); return { ...baseField, From 379c335af9d09220c9bc136a0ef4bf8635ab2d92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Tue, 18 Nov 2025 11:47:01 +0100 Subject: [PATCH 03/15] Tell TypeScript the format is proper --- packages/dataviews/src/types/field-api.ts | 2 +- packages/dataviews/src/utils/normalize-fields.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/dataviews/src/types/field-api.ts b/packages/dataviews/src/types/field-api.ts index 109b013d39208c..5f82475a4498f2 100644 --- a/packages/dataviews/src/types/field-api.ts +++ b/packages/dataviews/src/types/field-api.ts @@ -327,7 +327,7 @@ export type NormalizedFormat = Required< FormatDate > | {}; * * If not provided, defaults to WordPress date format settings. */ -type FormatDate = { +export type FormatDate = { date?: string; weekStartsOn?: DayString; }; diff --git a/packages/dataviews/src/utils/normalize-fields.ts b/packages/dataviews/src/utils/normalize-fields.ts index 30e0e16fd96be3..43b859ccd34628 100644 --- a/packages/dataviews/src/utils/normalize-fields.ts +++ b/packages/dataviews/src/utils/normalize-fields.ts @@ -13,6 +13,7 @@ import type { FieldTypeDefinition, NormalizedFilterByConfig, NormalizedField, + FormatDate, } from '../types'; import { getControl } from '../dataform-controls'; import { @@ -218,7 +219,9 @@ export default function normalizeFields< Item >( }; if ( field.type === 'date' ) { - const format = fieldTypeDefinition.getFormat( field ); + const format = fieldTypeDefinition.getFormat( + field + ) as Required< FormatDate >; return { ...baseField, @@ -227,6 +230,9 @@ export default function normalizeFields< Item >( }; } - return { ...baseField, type: field.type, format: {} }; + return { + ...baseField, + type: field.type, + }; } ); } From dc5f7132ca061b80e83e3aef2fc8c43efe0c6129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Wed, 19 Nov 2025 11:13:08 +0100 Subject: [PATCH 04/15] Export normalize function --- .../src/components/dataform/index.tsx | 2 +- .../src/components/dataviews-picker/index.tsx | 2 +- .../src/components/dataviews/index.tsx | 2 +- .../dataviews/src/dataform-controls/date.tsx | 2 +- .../dataviews/src/dataform-controls/index.tsx | 30 +-- packages/dataviews/src/field-types/array.tsx | 77 ++++-- .../dataviews/src/field-types/boolean.tsx | 91 ++++--- packages/dataviews/src/field-types/color.tsx | 124 +++++---- packages/dataviews/src/field-types/date.tsx | 170 ++++++++----- .../dataviews/src/field-types/datetime.tsx | 130 ++++++---- packages/dataviews/src/field-types/email.tsx | 103 +++++--- packages/dataviews/src/field-types/index.tsx | 106 +++++--- .../dataviews/src/field-types/integer.tsx | 125 +++++---- packages/dataviews/src/field-types/media.tsx | 56 ++++- packages/dataviews/src/field-types/number.tsx | 131 ++++++---- .../dataviews/src/field-types/password.tsx | 71 ++++-- .../dataviews/src/field-types/telephone.tsx | 100 +++++--- packages/dataviews/src/field-types/text.tsx | 102 +++++--- packages/dataviews/src/field-types/url.tsx | 100 +++++--- .../src/field-types/utils/get-filter-by.ts | 49 ++++ .../field-types/utils/get-value-from-id.ts | 17 ++ .../{ => field-types}/utils/has-elements.ts | 2 +- .../src/field-types/utils/normalize-fields.ts | 25 ++ .../field-types/utils/set-value-from-id.ts | 17 ++ .../{ => field-types}/utils/week-starts-on.ts | 2 +- .../dataviews/src/hooks/use-form-validity.ts | 2 +- .../dataviews/src/test/normalize-fields.ts | 2 +- .../src/utils/filter-sort-and-paginate.ts | 2 +- .../dataviews/src/utils/normalize-fields.ts | 238 ------------------ 29 files changed, 1122 insertions(+), 758 deletions(-) create mode 100644 packages/dataviews/src/field-types/utils/get-filter-by.ts create mode 100644 packages/dataviews/src/field-types/utils/get-value-from-id.ts rename packages/dataviews/src/{ => field-types}/utils/has-elements.ts (82%) create mode 100644 packages/dataviews/src/field-types/utils/normalize-fields.ts create mode 100644 packages/dataviews/src/field-types/utils/set-value-from-id.ts rename packages/dataviews/src/{ => field-types}/utils/week-starts-on.ts (93%) delete mode 100644 packages/dataviews/src/utils/normalize-fields.ts diff --git a/packages/dataviews/src/components/dataform/index.tsx b/packages/dataviews/src/components/dataform/index.tsx index f9d9e3025e730e..321af6f9302669 100644 --- a/packages/dataviews/src/components/dataform/index.tsx +++ b/packages/dataviews/src/components/dataform/index.tsx @@ -8,7 +8,7 @@ import { useMemo } from '@wordpress/element'; */ import type { DataFormProps } from '../../types'; import { DataFormProvider } from '../dataform-context'; -import normalizeFields from '../../utils/normalize-fields'; +import normalizeFields from '../../field-types/utils/normalize-fields'; import { DataFormLayout } from '../../dataform-layouts/data-form-layout'; import normalizeForm from '../../dataform-layouts/normalize-form'; diff --git a/packages/dataviews/src/components/dataviews-picker/index.tsx b/packages/dataviews/src/components/dataviews-picker/index.tsx index df831a79a9e506..a97bb40825978f 100644 --- a/packages/dataviews/src/components/dataviews-picker/index.tsx +++ b/packages/dataviews/src/components/dataviews-picker/index.tsx @@ -29,7 +29,7 @@ import DataViewsViewConfig, { DataviewsViewConfigDropdown, ViewTypeMenu, } from '../dataviews-view-config'; -import normalizeFields from '../../utils/normalize-fields'; +import normalizeFields from '../../field-types/utils/normalize-fields'; import type { ActionButton, Field, View, SupportedLayouts } from '../../types'; import type { SelectionOrUpdater } from '../../types/private'; type ItemWithId = { id: string }; diff --git a/packages/dataviews/src/components/dataviews/index.tsx b/packages/dataviews/src/components/dataviews/index.tsx index 9d5870174f9db7..07cda5d91bd9b7 100644 --- a/packages/dataviews/src/components/dataviews/index.tsx +++ b/packages/dataviews/src/components/dataviews/index.tsx @@ -30,7 +30,7 @@ import DataViewsViewConfig, { DataviewsViewConfigDropdown, ViewTypeMenu, } from '../dataviews-view-config'; -import normalizeFields from '../../utils/normalize-fields'; +import normalizeFields from '../../field-types/utils/normalize-fields'; import type { Action, Field, View, SupportedLayouts } from '../../types'; import type { SelectionOrUpdater } from '../../types/private'; type ItemWithId = { id: string }; diff --git a/packages/dataviews/src/dataform-controls/date.tsx b/packages/dataviews/src/dataform-controls/date.tsx index ae17fd3bd65869..dd97fd7ebcf8c8 100644 --- a/packages/dataviews/src/dataform-controls/date.tsx +++ b/packages/dataviews/src/dataform-controls/date.tsx @@ -51,7 +51,7 @@ import type { NormalizedField, } from '../types'; import getCustomValidity from './utils/get-custom-validity'; -import { weekStartsOnToNumber } from '../utils/week-starts-on'; +import { weekStartsOnToNumber } from '../field-types/utils/week-starts-on'; const { DateCalendar, DateRangeCalendar } = unlock( componentsPrivateApis ); diff --git a/packages/dataviews/src/dataform-controls/index.tsx b/packages/dataviews/src/dataform-controls/index.tsx index 1f351d85d592b2..b95256e310a7af 100644 --- a/packages/dataviews/src/dataform-controls/index.tsx +++ b/packages/dataviews/src/dataform-controls/index.tsx @@ -6,12 +6,7 @@ import type { ComponentType } from 'react'; /** * Internal dependencies */ -import type { - DataFormControlProps, - Field, - FieldTypeDefinition, - EditConfig, -} from '../types'; +import type { DataFormControlProps, Field, EditConfig } from '../types'; import checkbox from './checkbox'; import datetime from './datetime'; import date from './date'; @@ -29,7 +24,7 @@ import toggleGroup from './toggle-group'; import array from './array'; import color from './color'; import password from './password'; -import hasElements from '../utils/has-elements'; +import hasElements from '../field-types/utils/has-elements'; interface FormControls { [ key: string ]: ComponentType< DataFormControlProps< any > >; @@ -55,15 +50,18 @@ const FORM_CONTROLS: FormControls = { toggleGroup, }; -function isEditConfig( value: any ): value is EditConfig { +export function isEditConfig( value: any ): value is EditConfig { return ( value && typeof value === 'object' && typeof value.control === 'string' ); } -function createConfiguredControl( config: EditConfig ) { +export function createConfiguredControl( config: EditConfig ) { const { control, ...controlConfig } = config; const BaseControlType = getControlByType( control ); + if ( BaseControlType === null ) { + return null; + } return function ConfiguredControl< Item >( props: DataFormControlProps< Item > @@ -74,7 +72,7 @@ function createConfiguredControl( config: EditConfig ) { export function getControl< Item >( field: Field< Item >, - fieldTypeDefinition: FieldTypeDefinition< Item > + fallback: string | null ) { if ( typeof field.Edit === 'function' ) { return field.Edit; @@ -92,15 +90,11 @@ export function getControl< Item >( return getControlByType( 'select' ); } - if ( typeof fieldTypeDefinition.Edit === 'string' ) { - return getControlByType( fieldTypeDefinition.Edit ); - } - - if ( isEditConfig( fieldTypeDefinition.Edit ) ) { - return createConfiguredControl( fieldTypeDefinition.Edit ); + if ( fallback === null ) { + return null; } - return fieldTypeDefinition.Edit; + return getControlByType( fallback ); } export function getControlByType( type: string ) { @@ -108,5 +102,5 @@ export function getControlByType( type: string ) { return FORM_CONTROLS[ type ]; } - throw 'Control ' + type + ' not found'; + return null; } diff --git a/packages/dataviews/src/field-types/array.tsx b/packages/dataviews/src/field-types/array.tsx index 2c596570690839..9015f14360130e 100644 --- a/packages/dataviews/src/field-types/array.tsx +++ b/packages/dataviews/src/field-types/array.tsx @@ -8,9 +8,10 @@ import { __ } from '@wordpress/i18n'; */ import type { DataViewRenderFieldProps, - FieldTypeDefinition, + Field, NormalizedField, - NormalizedFormat, + Operator, + Rules, SortDirection, } from '../types'; import { @@ -19,6 +20,11 @@ import { 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'; // Sort arrays by length, then alphabetically by joined string function sort( valueA: any, valueB: any, direction: SortDirection ) { @@ -42,12 +48,24 @@ function render( { item, field }: DataViewRenderFieldProps< any > ) { return value.join( ', ' ); } -const arrayFieldType: FieldTypeDefinition< any > = { - sort, - isValid: { +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< Item > = { elements: true, - custom: ( item: any, field: NormalizedField< any > ) => { - const value = field.getValue( { item } ); + custom: ( item: any, normalizedField ) => { + const value = normalizedField.getValue( { item } ); if ( ! [ undefined, '', null ].includes( value ) && @@ -63,20 +81,33 @@ const arrayFieldType: FieldTypeDefinition< any > = { return null; }, - }, - Edit: 'array', // Use array control - render, - getFormat: (): NormalizedFormat => ( {} ), - enableSorting: true, - filterBy: { - defaultOperators: [ OPERATOR_IS_ANY, OPERATOR_IS_NONE ], - validOperators: [ - OPERATOR_IS_ANY, - OPERATOR_IS_NONE, - OPERATOR_IS_ALL, - OPERATOR_IS_NOT_ALL, - ], - }, -}; + }; -export default arrayFieldType; + 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: {}, + }; +} diff --git a/packages/dataviews/src/field-types/boolean.tsx b/packages/dataviews/src/field-types/boolean.tsx index 060b1c813b9a09..677184df2a9f9c 100644 --- a/packages/dataviews/src/field-types/boolean.tsx +++ b/packages/dataviews/src/field-types/boolean.tsx @@ -8,13 +8,19 @@ import { __ } from '@wordpress/i18n'; */ import type { DataViewRenderFieldProps, - FieldTypeDefinition, + Field, NormalizedField, - NormalizedFormat, + Operator, + Rules, SortDirection, } from '../types'; 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 sort( a: any, b: any, direction: SortDirection ) { const boolA = Boolean( a ); @@ -33,12 +39,31 @@ function sort( a: any, b: any, direction: SortDirection ) { return boolA ? -1 : 1; } -export default { - sort, - isValid: { +function render( { item, field }: DataViewRenderFieldProps< any > ) { + if ( field.hasElements ) { + return ; + } + + if ( field.getValue( { item } ) === true ) { + return __( 'True' ); + } + + if ( field.getValue( { item } ) === false ) { + return __( 'False' ); + } + + 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< Item > = { elements: true, - custom: ( item: any, field: NormalizedField< any > ) => { - const value = field.getValue( { item } ); + custom: ( item: any, normalizedField ) => { + const value = normalizedField.getValue( { item } ); if ( ! [ undefined, '', null ].includes( value ) && @@ -49,27 +74,37 @@ export default { return null; }, - }, - Edit: 'checkbox', - render: ( { item, field }: DataViewRenderFieldProps< any > ) => { - if ( field.hasElements ) { - return ; - } + }; - if ( field.getValue( { item } ) === true ) { - return __( 'True' ); - } + const defaultOperators: Operator[] = [ OPERATOR_IS, OPERATOR_IS_NOT ]; - if ( field.getValue( { item } ) === false ) { - return __( 'False' ); - } + const validOperators: Operator[] = [ OPERATOR_IS, OPERATOR_IS_NOT ]; - return null; - }, - getFormat: (): NormalizedFormat => ( {} ), - enableSorting: true, - filterBy: { - defaultOperators: [ OPERATOR_IS, OPERATOR_IS_NOT ], - validOperators: [ OPERATOR_IS, OPERATOR_IS_NOT ], - }, -} satisfies FieldTypeDefinition< any >; + 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: {}, + }; +} diff --git a/packages/dataviews/src/field-types/color.tsx b/packages/dataviews/src/field-types/color.tsx index ab612f56619493..025b7b8bf0ada5 100644 --- a/packages/dataviews/src/field-types/color.tsx +++ b/packages/dataviews/src/field-types/color.tsx @@ -13,9 +13,10 @@ import { __ } from '@wordpress/i18n'; */ import type { DataViewRenderFieldProps, - FieldTypeDefinition, + Field, NormalizedField, - NormalizedFormat, + Operator, + Rules, SortDirection, } from '../types'; import RenderFromElements from './utils/render-from-elements'; @@ -25,6 +26,11 @@ import { OPERATOR_IS_NONE, 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 sort( valueA: any, valueB: any, direction: SortDirection ) { // Convert colors to HSL for better sorting @@ -54,12 +60,44 @@ function sort( valueA: any, valueB: any, direction: SortDirection ) { return direction === 'asc' ? hslA.l - hslB.l : hslB.l - hslA.l; } -export default { - sort, - isValid: { +function render( { item, field }: DataViewRenderFieldProps< any > ) { + if ( field.hasElements ) { + return ; + } + + const value = field.getValue( { item } ); + + if ( ! value || ! colord( value ).isValid() ) { + return value; + } + + // Render color with visual preview + return ( +
+
+ { value } +
+ ); +} + +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< Item > = { elements: true, - custom: ( item: any, field: NormalizedField< any > ) => { - const value = field.getValue( { item } ); + custom: ( item: any, normalizedField ) => { + const value = normalizedField.getValue( { item } ); if ( ! [ undefined, '', null ].includes( value ) && @@ -70,42 +108,42 @@ export default { return null; }, - }, - Edit: 'color', - render: ( { item, field }: DataViewRenderFieldProps< any > ) => { - if ( field.hasElements ) { - return ; - } + }; - const value = field.getValue( { item } ); + const defaultOperators: Operator[] = [ OPERATOR_IS_ANY, OPERATOR_IS_NONE ]; - if ( ! value || ! colord( value ).isValid() ) { - return value; - } + const validOperators: Operator[] = [ + OPERATOR_IS, + OPERATOR_IS_NOT, + OPERATOR_IS_ANY, + OPERATOR_IS_NONE, + ]; - // Render color with visual preview - return ( -
-
- { value } -
- ); - }, - getFormat: (): NormalizedFormat => ( {} ), - enableSorting: true, - filterBy: { - defaultOperators: [ OPERATOR_IS_ANY, OPERATOR_IS_NONE ], - validOperators: [ OPERATOR_IS, OPERATOR_IS_NOT ], - }, -} satisfies FieldTypeDefinition< any >; + return { + id: field.id, + type: 'color', + 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, 'color' ), + 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: {}, + }; +} diff --git a/packages/dataviews/src/field-types/date.tsx b/packages/dataviews/src/field-types/date.tsx index 3900b16d171e22..89b93efd9d3828 100644 --- a/packages/dataviews/src/field-types/date.tsx +++ b/packages/dataviews/src/field-types/date.tsx @@ -10,8 +10,10 @@ import type { DataViewRenderFieldProps, DayString, Field, - FieldTypeDefinition, - NormalizedFormat, + FormatDate, + NormalizedField, + Operator, + Rules, SortDirection, } from '../types'; import RenderFromElements from './utils/render-from-elements'; @@ -26,7 +28,12 @@ import { OPERATOR_OVER, OPERATOR_BETWEEN, } from '../constants'; -import { DAYS_OF_WEEK, numberToWeekStartsOn } from '../utils/week-starts-on'; +import { DAYS_OF_WEEK, numberToWeekStartsOn } from './utils/week-starts-on'; +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 sort( a: any, b: any, direction: SortDirection ) { const timeA = new Date( a ).getTime(); @@ -35,71 +42,102 @@ function sort( a: any, b: any, direction: SortDirection ) { return direction === 'asc' ? timeA - timeB : timeB - timeA; } -export default { - sort, - Edit: 'date', - isValid: { +function getFormat( field: Field< any > ): Required< FormatDate > { + return { + date: + field.format?.date !== undefined && + typeof field.format.date === 'string' + ? field.format.date + : getSettings().formats.date, + weekStartsOn: + field.format?.weekStartsOn !== undefined && + DAYS_OF_WEEK.includes( field.format?.weekStartsOn as DayString ) + ? field.format.weekStartsOn + : numberToWeekStartsOn( getSettings().l10n.startOfWeek ), + }; +} + +function render( { item, field }: DataViewRenderFieldProps< any > ) { + if ( field.hasElements ) { + return ; + } + + const value = field.getValue( { item } ); + if ( ! value ) { + return ''; + } + + // Not all fields have format, but date fields do. + // + // At runtime, this method will never be called for non-date fields. + // However, the type system does not know this, so we need to check it. + // There's an opportunity here to improve the type system. + if ( field.type !== 'date' ) { + return ''; + } + + return dateI18n( field.format.date, getDate( value ) ); +} + +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< Item > = { elements: true, custom: () => null, - }, - render: ( { item, field }: DataViewRenderFieldProps< any > ) => { - if ( field.hasElements ) { - return ; - } + }; - const value = field.getValue( { item } ); - if ( ! value ) { - return ''; - } + const defaultOperators: Operator[] = [ + OPERATOR_ON, + OPERATOR_NOT_ON, + OPERATOR_BEFORE, + OPERATOR_AFTER, + OPERATOR_BEFORE_INC, + OPERATOR_AFTER_INC, + OPERATOR_IN_THE_PAST, + OPERATOR_OVER, + OPERATOR_BETWEEN, + ]; - // Not all fields have format, but date fields do. - // - // At runtime, this method will never be called for non-date fields. - // However, the type system does not know this, so we need to check it. - // There's an opportunity here to improve the type system. - if ( field.type !== 'date' ) { - return ''; - } + const validOperators: Operator[] = [ + OPERATOR_ON, + OPERATOR_NOT_ON, + OPERATOR_BEFORE, + OPERATOR_AFTER, + OPERATOR_BEFORE_INC, + OPERATOR_AFTER_INC, + OPERATOR_IN_THE_PAST, + OPERATOR_OVER, + OPERATOR_BETWEEN, + ]; - return dateI18n( field.format.date, getDate( value ) ); - }, - enableSorting: true, - getFormat: ( field: Field< any > ): NormalizedFormat => { - return { - date: - field.format?.date !== undefined && - typeof field.format.date === 'string' - ? field.format.date - : getSettings().formats.date, - weekStartsOn: - field.format?.weekStartsOn !== undefined && - DAYS_OF_WEEK.includes( field.format?.weekStartsOn as DayString ) - ? field.format.weekStartsOn - : numberToWeekStartsOn( getSettings().l10n.startOfWeek ), - }; - }, - filterBy: { - defaultOperators: [ - OPERATOR_ON, - OPERATOR_NOT_ON, - OPERATOR_BEFORE, - OPERATOR_AFTER, - OPERATOR_BEFORE_INC, - OPERATOR_AFTER_INC, - OPERATOR_IN_THE_PAST, - OPERATOR_OVER, - OPERATOR_BETWEEN, - ], - validOperators: [ - OPERATOR_ON, - OPERATOR_NOT_ON, - OPERATOR_BEFORE, - OPERATOR_AFTER, - OPERATOR_BEFORE_INC, - OPERATOR_AFTER_INC, - OPERATOR_IN_THE_PAST, - OPERATOR_OVER, - OPERATOR_BETWEEN, - ], - }, -} satisfies FieldTypeDefinition< any >; + return { + id: field.id, + type: 'date', + 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, 'date' ), + 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: getFormat( field ), + }; +} diff --git a/packages/dataviews/src/field-types/datetime.tsx b/packages/dataviews/src/field-types/datetime.tsx index 2dfba4edb30b75..dc7ac51c77eabd 100644 --- a/packages/dataviews/src/field-types/datetime.tsx +++ b/packages/dataviews/src/field-types/datetime.tsx @@ -3,8 +3,10 @@ */ import type { DataViewRenderFieldProps, - FieldTypeDefinition, - NormalizedFormat, + Field, + NormalizedField, + Operator, + Rules, SortDirection, } from '../types'; import RenderFromElements from './utils/render-from-elements'; @@ -19,6 +21,11 @@ import { OPERATOR_IN_THE_PAST, OPERATOR_OVER, } 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 sort( a: any, b: any, direction: SortDirection ) { const timeA = new Date( a ).getTime(); @@ -27,52 +34,81 @@ function sort( a: any, b: any, direction: SortDirection ) { return direction === 'asc' ? timeA - timeB : timeB - timeA; } -export default { - sort, - isValid: { +function render( { item, field }: DataViewRenderFieldProps< any > ) { + if ( field.elements ) { + return ; + } + + const value = field.getValue( { item } ); + if ( [ '', undefined, null ].includes( value ) ) { + return null; + } + + try { + const dateValue = parseDateTime( value ); + return dateValue?.toLocaleString(); + } catch ( error ) { + 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< Item > = { elements: true, custom: () => null, - }, - Edit: 'datetime', - render: ( { item, field }: DataViewRenderFieldProps< any > ) => { - if ( field.elements ) { - return ; - } + }; + + const defaultOperators: Operator[] = [ + OPERATOR_ON, + OPERATOR_NOT_ON, + OPERATOR_BEFORE, + OPERATOR_AFTER, + OPERATOR_BEFORE_INC, + OPERATOR_AFTER_INC, + OPERATOR_IN_THE_PAST, + OPERATOR_OVER, + ]; - const value = field.getValue( { item } ); - if ( [ '', undefined, null ].includes( value ) ) { - return null; - } + const validOperators: Operator[] = [ + OPERATOR_ON, + OPERATOR_NOT_ON, + OPERATOR_BEFORE, + OPERATOR_AFTER, + OPERATOR_BEFORE_INC, + OPERATOR_AFTER_INC, + OPERATOR_IN_THE_PAST, + OPERATOR_OVER, + ]; - try { - const dateValue = parseDateTime( value ); - return dateValue?.toLocaleString(); - } catch ( error ) { - return null; - } - }, - getFormat: (): NormalizedFormat => ( {} ), - enableSorting: true, - filterBy: { - defaultOperators: [ - OPERATOR_ON, - OPERATOR_NOT_ON, - OPERATOR_BEFORE, - OPERATOR_AFTER, - OPERATOR_BEFORE_INC, - OPERATOR_AFTER_INC, - OPERATOR_IN_THE_PAST, - OPERATOR_OVER, - ], - validOperators: [ - OPERATOR_ON, - OPERATOR_NOT_ON, - OPERATOR_BEFORE, - OPERATOR_AFTER, - OPERATOR_BEFORE_INC, - OPERATOR_AFTER_INC, - OPERATOR_IN_THE_PAST, - OPERATOR_OVER, - ], - }, -} satisfies FieldTypeDefinition< any >; + return { + id: field.id, + type: 'datetime', + 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, 'datetime' ), + 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: {}, + }; +} diff --git a/packages/dataviews/src/field-types/email.tsx b/packages/dataviews/src/field-types/email.tsx index 7f911461abfe8e..5febdb5a00e4a1 100644 --- a/packages/dataviews/src/field-types/email.tsx +++ b/packages/dataviews/src/field-types/email.tsx @@ -8,9 +8,10 @@ import { __ } from '@wordpress/i18n'; */ import type { DataViewRenderFieldProps, - FieldTypeDefinition, + Field, NormalizedField, - NormalizedFormat, + Operator, + Rules, SortDirection, } from '../types'; import RenderFromElements from './utils/render-from-elements'; @@ -25,6 +26,11 @@ import { OPERATOR_NOT_CONTAINS, OPERATOR_STARTS_WITH, } 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 sort( valueA: any, valueB: any, direction: SortDirection ) { return direction === 'asc' @@ -37,12 +43,23 @@ function sort( valueA: any, valueB: any, direction: SortDirection ) { const emailRegex = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; -export default { - sort, - isValid: { +function render( { item, field }: DataViewRenderFieldProps< any > ) { + return field.hasElements ? ( + + ) : ( + field.getValue( { item } ) + ); +} + +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< Item > = { elements: true, - custom: ( item: any, field: NormalizedField< any > ) => { - const value = field.getValue( { item } ); + custom: ( item: any, normalizedField ) => { + const value = normalizedField.getValue( { item } ); if ( ! [ undefined, '', null ].includes( value ) && @@ -53,30 +70,48 @@ export default { return null; }, - }, - Edit: 'email', - render: ( { item, field }: DataViewRenderFieldProps< any > ) => { - return field.hasElements ? ( - - ) : ( - field.getValue( { item } ) - ); - }, - getFormat: (): NormalizedFormat => ( {} ), - enableSorting: true, - filterBy: { - defaultOperators: [ OPERATOR_IS_ANY, OPERATOR_IS_NONE ], - validOperators: [ - OPERATOR_IS, - OPERATOR_IS_NOT, - OPERATOR_CONTAINS, - OPERATOR_NOT_CONTAINS, - OPERATOR_STARTS_WITH, - // Multiple selection - OPERATOR_IS_ANY, - OPERATOR_IS_NONE, - OPERATOR_IS_ALL, - OPERATOR_IS_NOT_ALL, - ], - }, -} satisfies FieldTypeDefinition< any >; + }; + + const defaultOperators: Operator[] = [ OPERATOR_IS_ANY, OPERATOR_IS_NONE ]; + + const validOperators: Operator[] = [ + OPERATOR_IS, + OPERATOR_IS_NOT, + OPERATOR_CONTAINS, + OPERATOR_NOT_CONTAINS, + OPERATOR_STARTS_WITH, + // Multiple selection + OPERATOR_IS_ANY, + OPERATOR_IS_NONE, + OPERATOR_IS_ALL, + OPERATOR_IS_NOT_ALL, + ]; + + return { + id: field.id, + type: 'email', + 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, 'email' ), + 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: {}, + }; +} diff --git a/packages/dataviews/src/field-types/index.tsx b/packages/dataviews/src/field-types/index.tsx index e61b5e74124059..e6c943f0677b23 100644 --- a/packages/dataviews/src/field-types/index.tsx +++ b/packages/dataviews/src/field-types/index.tsx @@ -3,9 +3,10 @@ */ import type { DataViewRenderFieldProps, + Field, FieldType, - FieldTypeDefinition, - NormalizedFormat, + NormalizedField, + Operator, SortDirection, } from '../types'; import { default as email } from './email'; @@ -23,6 +24,75 @@ import { default as color } from './color'; import { default as url } from './url'; import RenderFromElements from './utils/render-from-elements'; import { ALL_OPERATORS, 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 normalizeField< Item >( + field: Field< Item > +): NormalizedField< Item > { + const getValue = field.getValue || getValueFromId( field.id ); + const setValue = field.setValue || setValueFromId( field.id ); + + const isValid = { + elements: true, + custom: () => null, + }; + + const sort = ( a: any, b: any, direction: SortDirection ) => { + if ( typeof a === 'number' && typeof b === 'number' ) { + return direction === 'asc' ? a - b : b - a; + } + + return direction === 'asc' + ? a.localeCompare( b ) + : b.localeCompare( a ); + }; + + const render = ( { + item, + field: normalizedField, + }: DataViewRenderFieldProps< Item > ) => { + return normalizedField.hasElements ? ( + + ) : ( + normalizedField.getValue( { item } ) + ); + }; + + const defaultOperators: Operator[] = [ OPERATOR_IS, OPERATOR_IS_NOT ]; + const validOperators: Operator[] = ALL_OPERATORS; + + return { + id: field.id, + // type — it does not have a type + 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, null ), + 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: {}, + }; +} /** * @@ -32,7 +102,7 @@ import { ALL_OPERATORS, OPERATOR_IS, OPERATOR_IS_NOT } from '../constants'; */ export default function getFieldTypeDefinition< Item >( type?: FieldType -): FieldTypeDefinition< Item > { +): ( field: Field< Item > ) => NormalizedField< Item > { if ( 'email' === type ) { return email; } @@ -87,33 +157,5 @@ export default function getFieldTypeDefinition< Item >( // This is a fallback for fields that don't provide a type. // It can be removed when the field.type is mandatory. - return { - sort: ( a: any, b: any, direction: SortDirection ) => { - if ( typeof a === 'number' && typeof b === 'number' ) { - return direction === 'asc' ? a - b : b - a; - } - - return direction === 'asc' - ? a.localeCompare( b ) - : b.localeCompare( a ); - }, - isValid: { - elements: true, - custom: () => null, - }, - Edit: null, - render: ( { item, field }: DataViewRenderFieldProps< Item > ) => { - return field.hasElements ? ( - - ) : ( - field.getValue( { item } ) - ); - }, - getFormat: (): NormalizedFormat => ( {} ), - enableSorting: true, - filterBy: { - defaultOperators: [ OPERATOR_IS, OPERATOR_IS_NOT ], - validOperators: ALL_OPERATORS, - }, - }; + return normalizeField; } diff --git a/packages/dataviews/src/field-types/integer.tsx b/packages/dataviews/src/field-types/integer.tsx index 212eb3bca10651..b76d559ee78f95 100644 --- a/packages/dataviews/src/field-types/integer.tsx +++ b/packages/dataviews/src/field-types/integer.tsx @@ -8,9 +8,10 @@ import { __ } from '@wordpress/i18n'; */ import type { DataViewRenderFieldProps, - FieldTypeDefinition, + Field, NormalizedField, - NormalizedFormat, + Operator, + Rules, SortDirection, } from '../types'; import RenderFromElements from './utils/render-from-elements'; @@ -27,17 +28,33 @@ import { OPERATOR_IS_NOT_ALL, OPERATOR_BETWEEN, } 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 sort( a: any, b: any, direction: SortDirection ) { return direction === 'asc' ? a - b : b - a; } -export default { - sort, - isValid: { +function render( { item, field }: DataViewRenderFieldProps< any > ) { + return field.hasElements ? ( + + ) : ( + field.getValue( { item } ) + ); +} + +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< Item > = { elements: true, - custom: ( item: any, field: NormalizedField< any > ) => { - const value = field.getValue( { item } ); + custom: ( item: any, normalizedField ) => { + const value = normalizedField.getValue( { item } ); if ( ! [ undefined, '', null ].includes( value ) && ! Number.isInteger( value ) @@ -47,41 +64,59 @@ export default { return null; }, - }, - Edit: 'integer', - render: ( { item, field }: DataViewRenderFieldProps< any > ) => { - return field.hasElements ? ( - - ) : ( - field.getValue( { item } ) - ); - }, - getFormat: (): NormalizedFormat => ( {} ), - enableSorting: true, - filterBy: { - defaultOperators: [ - OPERATOR_IS, - OPERATOR_IS_NOT, - OPERATOR_LESS_THAN, - OPERATOR_GREATER_THAN, - OPERATOR_LESS_THAN_OR_EQUAL, - OPERATOR_GREATER_THAN_OR_EQUAL, - OPERATOR_BETWEEN, - ], - validOperators: [ - // Single-selection - OPERATOR_IS, - OPERATOR_IS_NOT, - OPERATOR_LESS_THAN, - OPERATOR_GREATER_THAN, - OPERATOR_LESS_THAN_OR_EQUAL, - OPERATOR_GREATER_THAN_OR_EQUAL, - OPERATOR_BETWEEN, - // Multiple-selection - OPERATOR_IS_ANY, - OPERATOR_IS_NONE, - OPERATOR_IS_ALL, - OPERATOR_IS_NOT_ALL, - ], - }, -} satisfies FieldTypeDefinition< any >; + }; + + const defaultOperators: Operator[] = [ + OPERATOR_IS, + OPERATOR_IS_NOT, + OPERATOR_LESS_THAN, + OPERATOR_GREATER_THAN, + OPERATOR_LESS_THAN_OR_EQUAL, + OPERATOR_GREATER_THAN_OR_EQUAL, + OPERATOR_BETWEEN, + ]; + + const validOperators: Operator[] = [ + // Single-selection + OPERATOR_IS, + OPERATOR_IS_NOT, + OPERATOR_LESS_THAN, + OPERATOR_GREATER_THAN, + OPERATOR_LESS_THAN_OR_EQUAL, + OPERATOR_GREATER_THAN_OR_EQUAL, + OPERATOR_BETWEEN, + // Multiple-selection + OPERATOR_IS_ANY, + OPERATOR_IS_NONE, + OPERATOR_IS_ALL, + OPERATOR_IS_NOT_ALL, + ]; + + return { + id: field.id, + type: 'integer', + 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, 'integer' ), + 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: {}, + }; +} diff --git a/packages/dataviews/src/field-types/media.tsx b/packages/dataviews/src/field-types/media.tsx index 7d4e684629a3f6..6e2ba665110189 100644 --- a/packages/dataviews/src/field-types/media.tsx +++ b/packages/dataviews/src/field-types/media.tsx @@ -1,21 +1,55 @@ /** * Internal dependencies */ -import type { FieldTypeDefinition, NormalizedFormat } from '../types'; +import type { Field, NormalizedField, Rules } from '../types'; +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'; function sort() { return 0; } -export default { - sort, - isValid: { +function render() { + 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< Item > = { elements: true, custom: () => null, - }, - Edit: null, - render: () => null, - getFormat: (): NormalizedFormat => ( {} ), - enableSorting: false, - filterBy: false, -} satisfies FieldTypeDefinition< any >; + }; + + return { + id: field.id, + type: 'media', + 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, null ), + sort: field.sort ?? sort, + isValid: { + ...isValid, + ...field.isValid, + }, + isVisible: field.isVisible, + enableSorting: field.enableSorting ?? false, + enableGlobalSearch: field.enableGlobalSearch ?? false, + enableHiding: field.enableHiding ?? true, + readOnly: field.readOnly ?? false, + filterBy: false, + format: {}, + }; +} diff --git a/packages/dataviews/src/field-types/number.tsx b/packages/dataviews/src/field-types/number.tsx index ad533f0bdb94d8..966290104ad9b6 100644 --- a/packages/dataviews/src/field-types/number.tsx +++ b/packages/dataviews/src/field-types/number.tsx @@ -8,9 +8,10 @@ import { __ } from '@wordpress/i18n'; */ import type { DataViewRenderFieldProps, - FieldTypeDefinition, + Field, NormalizedField, - NormalizedFormat, + Operator, + Rules, SortDirection, } from '../types'; import { @@ -27,6 +28,11 @@ import { OPERATOR_BETWEEN, } from '../constants'; import RenderFromElements from './utils/render-from-elements'; +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 sort( a: any, b: any, direction: SortDirection ) { return direction === 'asc' ? a - b : b - a; @@ -36,12 +42,28 @@ function isEmpty( value: unknown ): value is '' | undefined | null { return value === '' || value === undefined || value === null; } -export default { - sort, - isValid: { +function render( { item, field }: DataViewRenderFieldProps< any > ) { + if ( field.hasElements ) { + return ; + } + + const value = field.getValue( { item } ); + if ( ! [ null, undefined ].includes( value ) ) { + return Number( value ).toFixed( 2 ); + } + + 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< Item > = { elements: true, - custom: ( item: any, field: NormalizedField< any > ) => { - const value = field.getValue( { item } ); + custom: ( item: any, normalizedField ) => { + const value = normalizedField.getValue( { item } ); if ( ! isEmpty( value ) && ! Number.isFinite( value ) ) { return __( 'Value must be a number.' ); @@ -49,46 +71,59 @@ export default { return null; }, - }, - Edit: 'number', - render: ( { item, field }: DataViewRenderFieldProps< any > ) => { - if ( field.hasElements ) { - ; - } + }; - const value = field.getValue( { item } ); - if ( ! [ null, undefined ].includes( value ) ) { - return Number( value ).toFixed( 2 ); - } + const defaultOperators: Operator[] = [ + OPERATOR_IS, + OPERATOR_IS_NOT, + OPERATOR_LESS_THAN, + OPERATOR_GREATER_THAN, + OPERATOR_LESS_THAN_OR_EQUAL, + OPERATOR_GREATER_THAN_OR_EQUAL, + OPERATOR_BETWEEN, + ]; - return null; - }, - getFormat: (): NormalizedFormat => ( {} ), - enableSorting: true, - filterBy: { - defaultOperators: [ - OPERATOR_IS, - OPERATOR_IS_NOT, - OPERATOR_LESS_THAN, - OPERATOR_GREATER_THAN, - OPERATOR_LESS_THAN_OR_EQUAL, - OPERATOR_GREATER_THAN_OR_EQUAL, - OPERATOR_BETWEEN, - ], - validOperators: [ - // Single-selection - OPERATOR_IS, - OPERATOR_IS_NOT, - OPERATOR_LESS_THAN, - OPERATOR_GREATER_THAN, - OPERATOR_LESS_THAN_OR_EQUAL, - OPERATOR_GREATER_THAN_OR_EQUAL, - OPERATOR_BETWEEN, - // Multiple-selection - OPERATOR_IS_ANY, - OPERATOR_IS_NONE, - OPERATOR_IS_ALL, - OPERATOR_IS_NOT_ALL, - ], - }, -} satisfies FieldTypeDefinition< any >; + const validOperators: Operator[] = [ + // Single-selection + OPERATOR_IS, + OPERATOR_IS_NOT, + OPERATOR_LESS_THAN, + OPERATOR_GREATER_THAN, + OPERATOR_LESS_THAN_OR_EQUAL, + OPERATOR_GREATER_THAN_OR_EQUAL, + OPERATOR_BETWEEN, + // Multiple-selection + OPERATOR_IS_ANY, + OPERATOR_IS_NONE, + OPERATOR_IS_ALL, + OPERATOR_IS_NOT_ALL, + ]; + + return { + id: field.id, + type: 'number', + 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, 'number' ), + 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: {}, + }; +} diff --git a/packages/dataviews/src/field-types/password.tsx b/packages/dataviews/src/field-types/password.tsx index 6ed19430088310..ec8c5ea8dc5758 100644 --- a/packages/dataviews/src/field-types/password.tsx +++ b/packages/dataviews/src/field-types/password.tsx @@ -3,33 +3,66 @@ */ import type { DataViewRenderFieldProps, - FieldTypeDefinition, + Field, + NormalizedField, + Rules, SortDirection, - NormalizedFormat, } from '../types'; import RenderFromElements from './utils/render-from-elements'; +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'; /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ -function sort( valueA: any, valueB: any, direction: SortDirection ) { +function sort( _valueA: any, _valueB: any, _direction: SortDirection ) { // Passwords should not be sortable for security reasons return 0; } -export default { - sort, - isValid: { +function render( { item, field }: DataViewRenderFieldProps< any > ) { + return field.hasElements ? ( + + ) : ( + '••••••••' + ); +} + +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< Item > = { elements: true, custom: () => null, - }, - Edit: 'password', - render: ( { item, field }: DataViewRenderFieldProps< any > ) => { - return field.hasElements ? ( - - ) : ( - '••••••••' - ); - }, - getFormat: (): NormalizedFormat => ( {} ), - enableSorting: false, - filterBy: false, -} satisfies FieldTypeDefinition< any >; + }; + + return { + id: field.id, + type: 'password', + 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, 'password' ), + sort: field.sort ?? sort, + isValid: { + ...isValid, + ...field.isValid, + }, + isVisible: field.isVisible, + enableSorting: field.enableSorting ?? false, + enableGlobalSearch: field.enableGlobalSearch ?? false, + enableHiding: field.enableHiding ?? true, + readOnly: field.readOnly ?? false, + filterBy: false, + format: {}, + }; +} diff --git a/packages/dataviews/src/field-types/telephone.tsx b/packages/dataviews/src/field-types/telephone.tsx index 9498b5a87f5681..612638e5cbb376 100644 --- a/packages/dataviews/src/field-types/telephone.tsx +++ b/packages/dataviews/src/field-types/telephone.tsx @@ -3,9 +3,11 @@ */ import type { DataViewRenderFieldProps, - FieldTypeDefinition, + Field, + NormalizedField, + Operator, + Rules, SortDirection, - NormalizedFormat, } from '../types'; import RenderFromElements from './utils/render-from-elements'; import { @@ -19,6 +21,11 @@ import { OPERATOR_NOT_CONTAINS, OPERATOR_STARTS_WITH, } 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 sort( valueA: any, valueB: any, direction: SortDirection ) { return direction === 'asc' @@ -26,35 +33,64 @@ function sort( valueA: any, valueB: any, direction: SortDirection ) { : valueB.localeCompare( valueA ); } -export default { - sort, - isValid: { +function render( { item, field }: DataViewRenderFieldProps< any > ) { + return field.hasElements ? ( + + ) : ( + field.getValue( { item } ) + ); +} + +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< Item > = { elements: true, custom: () => null, - }, - Edit: 'telephone', - render: ( { item, field }: DataViewRenderFieldProps< any > ) => { - return field.hasElements ? ( - - ) : ( - field.getValue( { item } ) - ); - }, - getFormat: (): NormalizedFormat => ( {} ), - enableSorting: true, - filterBy: { - defaultOperators: [ OPERATOR_IS_ANY, OPERATOR_IS_NONE ], - validOperators: [ - OPERATOR_IS, - OPERATOR_IS_NOT, - OPERATOR_CONTAINS, - OPERATOR_NOT_CONTAINS, - OPERATOR_STARTS_WITH, - // Multiple selection - OPERATOR_IS_ANY, - OPERATOR_IS_NONE, - OPERATOR_IS_ALL, - OPERATOR_IS_NOT_ALL, - ], - }, -} satisfies FieldTypeDefinition< any >; + }; + + const defaultOperators: Operator[] = [ OPERATOR_IS_ANY, OPERATOR_IS_NONE ]; + + const validOperators: Operator[] = [ + OPERATOR_IS, + OPERATOR_IS_NOT, + OPERATOR_CONTAINS, + OPERATOR_NOT_CONTAINS, + OPERATOR_STARTS_WITH, + // Multiple selection + OPERATOR_IS_ANY, + OPERATOR_IS_NONE, + OPERATOR_IS_ALL, + OPERATOR_IS_NOT_ALL, + ]; + + return { + id: field.id, + type: 'telephone', + 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, 'telephone' ), + 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: {}, + }; +} diff --git a/packages/dataviews/src/field-types/text.tsx b/packages/dataviews/src/field-types/text.tsx index fd79e83730c55a..65dbcd7ec15b4b 100644 --- a/packages/dataviews/src/field-types/text.tsx +++ b/packages/dataviews/src/field-types/text.tsx @@ -3,8 +3,10 @@ */ import type { DataViewRenderFieldProps, - FieldTypeDefinition, - NormalizedFormat, + Field, + NormalizedField, + Operator, + Rules, SortDirection, } from '../types'; import RenderFromElements from './utils/render-from-elements'; @@ -19,6 +21,11 @@ import { OPERATOR_NOT_CONTAINS, OPERATOR_STARTS_WITH, } 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 sort( valueA: any, valueB: any, direction: SortDirection ) { return direction === 'asc' @@ -26,36 +33,65 @@ function sort( valueA: any, valueB: any, direction: SortDirection ) { : valueB.localeCompare( valueA ); } -export default { - sort, - isValid: { +function render( { item, field }: DataViewRenderFieldProps< any > ) { + return field.hasElements ? ( + + ) : ( + field.getValue( { item } ) + ); +} + +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< Item > = { elements: true, custom: () => null, - }, - Edit: 'text', - render: ( { item, field }: DataViewRenderFieldProps< any > ) => { - return field.hasElements ? ( - - ) : ( - field.getValue( { item } ) - ); - }, - getFormat: (): NormalizedFormat => ( {} ), - enableSorting: true, - filterBy: { - defaultOperators: [ OPERATOR_IS_ANY, OPERATOR_IS_NONE ], - validOperators: [ - // Single selection - OPERATOR_IS, - OPERATOR_IS_NOT, - OPERATOR_CONTAINS, - OPERATOR_NOT_CONTAINS, - OPERATOR_STARTS_WITH, - // Multiple selection - OPERATOR_IS_ANY, - OPERATOR_IS_NONE, - OPERATOR_IS_ALL, - OPERATOR_IS_NOT_ALL, - ], - }, -} satisfies FieldTypeDefinition< any >; + }; + + const defaultOperators: Operator[] = [ OPERATOR_IS_ANY, OPERATOR_IS_NONE ]; + + const validOperators: Operator[] = [ + // Single selection + OPERATOR_IS, + OPERATOR_IS_NOT, + OPERATOR_CONTAINS, + OPERATOR_NOT_CONTAINS, + OPERATOR_STARTS_WITH, + // Multiple selection + OPERATOR_IS_ANY, + OPERATOR_IS_NONE, + OPERATOR_IS_ALL, + OPERATOR_IS_NOT_ALL, + ]; + + return { + id: field.id, + type: 'text', + 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, 'text' ), + 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: {}, + }; +} diff --git a/packages/dataviews/src/field-types/url.tsx b/packages/dataviews/src/field-types/url.tsx index 6fd1c50ad2987d..724d7a311dd29b 100644 --- a/packages/dataviews/src/field-types/url.tsx +++ b/packages/dataviews/src/field-types/url.tsx @@ -3,8 +3,10 @@ */ import type { DataViewRenderFieldProps, - FieldTypeDefinition, - NormalizedFormat, + Field, + NormalizedField, + Operator, + Rules, SortDirection, } from '../types'; import RenderFromElements from './utils/render-from-elements'; @@ -19,6 +21,11 @@ import { OPERATOR_NOT_CONTAINS, OPERATOR_STARTS_WITH, } 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 sort( valueA: any, valueB: any, direction: SortDirection ) { return direction === 'asc' @@ -26,35 +33,64 @@ function sort( valueA: any, valueB: any, direction: SortDirection ) { : valueB.localeCompare( valueA ); } -export default { - sort, - isValid: { +function render( { item, field }: DataViewRenderFieldProps< any > ) { + return field.hasElements ? ( + + ) : ( + field.getValue( { item } ) + ); +} + +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< Item > = { elements: true, custom: () => null, - }, - Edit: 'url', - render: ( { item, field }: DataViewRenderFieldProps< any > ) => { - return field.hasElements ? ( - - ) : ( - field.getValue( { item } ) - ); - }, - getFormat: (): NormalizedFormat => ( {} ), - enableSorting: true, - filterBy: { - defaultOperators: [ OPERATOR_IS_ANY, OPERATOR_IS_NONE ], - validOperators: [ - OPERATOR_IS, - OPERATOR_IS_NOT, - OPERATOR_CONTAINS, - OPERATOR_NOT_CONTAINS, - OPERATOR_STARTS_WITH, - // Multiple selection - OPERATOR_IS_ANY, - OPERATOR_IS_NONE, - OPERATOR_IS_ALL, - OPERATOR_IS_NOT_ALL, - ], - }, -} satisfies FieldTypeDefinition< any >; + }; + + const defaultOperators: Operator[] = [ OPERATOR_IS_ANY, OPERATOR_IS_NONE ]; + + const validOperators: Operator[] = [ + OPERATOR_IS, + OPERATOR_IS_NOT, + OPERATOR_CONTAINS, + OPERATOR_NOT_CONTAINS, + OPERATOR_STARTS_WITH, + // Multiple selection + OPERATOR_IS_ANY, + OPERATOR_IS_NONE, + OPERATOR_IS_ALL, + OPERATOR_IS_NOT_ALL, + ]; + + return { + id: field.id, + type: 'url', + 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, 'url' ), + 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: {}, + }; +} diff --git a/packages/dataviews/src/field-types/utils/get-filter-by.ts b/packages/dataviews/src/field-types/utils/get-filter-by.ts new file mode 100644 index 00000000000000..a8c82c8e605eca --- /dev/null +++ b/packages/dataviews/src/field-types/utils/get-filter-by.ts @@ -0,0 +1,49 @@ +/** + * Internal dependencies + */ +import type { Field, Operator, NormalizedFilterByConfig } from '../../types'; + +function getFilterBy< Item >( + field: Field< Item >, + defaultOperators: Operator[], + validOperators: Operator[] +): NormalizedFilterByConfig | false { + if ( field.filterBy === false ) { + return false; + } + + if ( typeof field.filterBy === 'object' ) { + let operators = field.filterBy.operators; + + // Assign default values if no operator was provided. + if ( ! operators || ! Array.isArray( operators ) ) { + operators = defaultOperators; + } + + // Make sure only valid operators are included. + operators = operators.filter( ( operator ) => + validOperators.includes( operator ) + ); + + // If no operators are left at this point, + // the filters should be disabled. + if ( operators.length === 0 ) { + return false; + } + + return { + isPrimary: !! field.filterBy.isPrimary, + operators, + }; + } + + if ( defaultOperators.length === 0 ) { + return false; + } + + return { + operators: defaultOperators, + }; +} + +export default getFilterBy; diff --git a/packages/dataviews/src/field-types/utils/get-value-from-id.ts b/packages/dataviews/src/field-types/utils/get-value-from-id.ts new file mode 100644 index 00000000000000..558f42af6065e7 --- /dev/null +++ b/packages/dataviews/src/field-types/utils/get-value-from-id.ts @@ -0,0 +1,17 @@ +const getValueFromId = + ( id: string ) => + ( { item }: { item: any } ) => { + const path = id.split( '.' ); + let value = item; + for ( const segment of path ) { + if ( value.hasOwnProperty( segment ) ) { + value = value[ segment ]; + } else { + value = undefined; + } + } + + return value; + }; + +export default getValueFromId; diff --git a/packages/dataviews/src/utils/has-elements.ts b/packages/dataviews/src/field-types/utils/has-elements.ts similarity index 82% rename from packages/dataviews/src/utils/has-elements.ts rename to packages/dataviews/src/field-types/utils/has-elements.ts index 9fcee088f35a69..ae1414f649ccb8 100644 --- a/packages/dataviews/src/utils/has-elements.ts +++ b/packages/dataviews/src/field-types/utils/has-elements.ts @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import type { Field } from '../types/field-api'; +import type { Field } from '../../types/field-api'; export default function hasElements< Item >( field: Field< Item > ): boolean { return ( diff --git a/packages/dataviews/src/field-types/utils/normalize-fields.ts b/packages/dataviews/src/field-types/utils/normalize-fields.ts new file mode 100644 index 00000000000000..0f564ea6ba9458 --- /dev/null +++ b/packages/dataviews/src/field-types/utils/normalize-fields.ts @@ -0,0 +1,25 @@ +/** + * External dependencies + */ + +/** + * Internal dependencies + */ +import getFieldTypeDefinition from '..'; +import type { Field, NormalizedField } from '../../types'; + +/** + * Apply default values and normalize the fields config. + * + * @param fields Fields config. + * @return Normalized fields config. + */ +export default function normalizeFields< Item >( + fields: Field< Item >[] +): NormalizedField< Item >[] { + return fields.map( ( field ) => { + const normalize = getFieldTypeDefinition< Item >( field.type ); + + return normalize( field ); + } ); +} diff --git a/packages/dataviews/src/field-types/utils/set-value-from-id.ts b/packages/dataviews/src/field-types/utils/set-value-from-id.ts new file mode 100644 index 00000000000000..8419d0b0ea3cc7 --- /dev/null +++ b/packages/dataviews/src/field-types/utils/set-value-from-id.ts @@ -0,0 +1,17 @@ +const setValueFromId = + ( id: string ) => + ( { value }: { value: any } ) => { + const path = id.split( '.' ); + const result: any = {}; + let current = result; + + for ( const segment of path.slice( 0, -1 ) ) { + current[ segment ] = {}; + current = current[ segment ]; + } + + current[ path.at( -1 )! ] = value; + return result; + }; + +export default setValueFromId; diff --git a/packages/dataviews/src/utils/week-starts-on.ts b/packages/dataviews/src/field-types/utils/week-starts-on.ts similarity index 93% rename from packages/dataviews/src/utils/week-starts-on.ts rename to packages/dataviews/src/field-types/utils/week-starts-on.ts index 60651023751289..eb8576756dab88 100644 --- a/packages/dataviews/src/utils/week-starts-on.ts +++ b/packages/dataviews/src/field-types/utils/week-starts-on.ts @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import type { DayNumber, DayString } from '../types/field-api'; +import type { DayNumber, DayString } from '../../types/field-api'; export const DAYS_OF_WEEK: DayString[] = [ 'sunday', diff --git a/packages/dataviews/src/hooks/use-form-validity.ts b/packages/dataviews/src/hooks/use-form-validity.ts index 1167c9af763644..3c7d2a3d6fb02a 100644 --- a/packages/dataviews/src/hooks/use-form-validity.ts +++ b/packages/dataviews/src/hooks/use-form-validity.ts @@ -13,7 +13,7 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import normalizeFields from '../utils/normalize-fields'; +import normalizeFields from '../field-types/utils/normalize-fields'; import normalizeForm from '../dataform-layouts/normalize-form'; import type { Field, diff --git a/packages/dataviews/src/test/normalize-fields.ts b/packages/dataviews/src/test/normalize-fields.ts index 946abeff64cf82..ffdc0916dfd6e0 100644 --- a/packages/dataviews/src/test/normalize-fields.ts +++ b/packages/dataviews/src/test/normalize-fields.ts @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import normalizeFields from '../utils/normalize-fields'; +import normalizeFields from '../field-types/utils/normalize-fields'; import type { Field } from '../types'; describe( 'normalizeFields: default getValue', () => { diff --git a/packages/dataviews/src/utils/filter-sort-and-paginate.ts b/packages/dataviews/src/utils/filter-sort-and-paginate.ts index 5738fa331fc689..362d80ab68a4bc 100644 --- a/packages/dataviews/src/utils/filter-sort-and-paginate.ts +++ b/packages/dataviews/src/utils/filter-sort-and-paginate.ts @@ -36,7 +36,7 @@ import { OPERATOR_IN_THE_PAST, OPERATOR_OVER, } from '../constants'; -import normalizeFields from './normalize-fields'; +import normalizeFields from '../field-types/utils/normalize-fields'; import type { Field, View } from '../types'; function normalizeSearchInput( input = '' ) { diff --git a/packages/dataviews/src/utils/normalize-fields.ts b/packages/dataviews/src/utils/normalize-fields.ts deleted file mode 100644 index 43b859ccd34628..00000000000000 --- a/packages/dataviews/src/utils/normalize-fields.ts +++ /dev/null @@ -1,238 +0,0 @@ -/** - * External dependencies - */ -import type { FunctionComponent } from 'react'; - -/** - * Internal dependencies - */ -import getFieldTypeDefinition from '../field-types'; -import type { - DataViewRenderFieldProps, - Field, - FieldTypeDefinition, - NormalizedFilterByConfig, - NormalizedField, - FormatDate, -} from '../types'; -import { getControl } from '../dataform-controls'; -import { - ALL_OPERATORS, - OPERATOR_BETWEEN, - SINGLE_SELECTION_OPERATORS, -} from '../constants'; -import hasElements from './has-elements'; - -const getValueFromId = - ( id: string ) => - ( { item }: { item: any } ) => { - const path = id.split( '.' ); - let value = item; - for ( const segment of path ) { - if ( value.hasOwnProperty( segment ) ) { - value = value[ segment ]; - } else { - value = undefined; - } - } - - return value; - }; - -const setValueFromId = - ( id: string ) => - ( { value }: { value: any } ) => { - const path = id.split( '.' ); - const result: any = {}; - let current = result; - - for ( const segment of path.slice( 0, -1 ) ) { - current[ segment ] = {}; - current = current[ segment ]; - } - - current[ path.at( -1 )! ] = value; - return result; - }; - -function getFilterBy< Item >( - field: Field< Item >, - fieldTypeDefinition: FieldTypeDefinition< Item > -): NormalizedFilterByConfig | false { - if ( field.filterBy === false ) { - return false; - } - - if ( typeof field.filterBy === 'object' ) { - let operators = field.filterBy.operators; - - // Assign default values if no operator was provided. - if ( ! operators || ! Array.isArray( operators ) ) { - operators = !! fieldTypeDefinition.filterBy - ? fieldTypeDefinition.filterBy.defaultOperators - : []; - } - - // Make sure only valid operators are included. - let validOperators = ALL_OPERATORS; - if ( typeof fieldTypeDefinition.filterBy === 'object' ) { - validOperators = fieldTypeDefinition.filterBy.validOperators; - } - operators = operators.filter( ( operator ) => - validOperators.includes( operator ) - ); - - // The `between` operator is not supported when elements are provided. - if ( hasElements( field ) && operators.includes( OPERATOR_BETWEEN ) ) { - operators = operators.filter( - ( operator ) => operator !== OPERATOR_BETWEEN - ); - } - - // Do not allow mixing single & multiselection operators. - // Remove multiselection operators if any of the single selection ones is present. - const hasSingleSelectionOperator = operators.some( ( operator ) => - SINGLE_SELECTION_OPERATORS.includes( operator ) - ); - if ( hasSingleSelectionOperator ) { - operators = operators.filter( ( operator ) => - // The 'Between' operator is unique as it can be combined with single selection operators. - [ ...SINGLE_SELECTION_OPERATORS, OPERATOR_BETWEEN ].includes( - operator - ) - ); - } - - // If no operators are left at this point, - // the filters should be disabled. - if ( operators.length === 0 ) { - return false; - } - - return { - isPrimary: !! field.filterBy.isPrimary, - operators, - }; - } - - if ( fieldTypeDefinition.filterBy === false ) { - return false; - } - - let defaultOperators = fieldTypeDefinition.filterBy.defaultOperators; - // The `between` operator is not supported when elements are provided. - if ( - hasElements( field ) && - defaultOperators.includes( OPERATOR_BETWEEN ) - ) { - defaultOperators = defaultOperators.filter( - ( operator ) => operator !== OPERATOR_BETWEEN - ); - } - - return { - operators: defaultOperators, - }; -} - -/** - * Apply default values and normalize the fields config. - * - * @param fields Fields config. - * @return Normalized fields config. - */ -export default function normalizeFields< Item >( - fields: Field< Item >[] -): NormalizedField< Item >[] { - return fields.map( ( field ) => { - const fieldTypeDefinition = getFieldTypeDefinition< Item >( - field.type - ); - const getValue = field.getValue || getValueFromId( field.id ); - const setValue = field.setValue || setValueFromId( field.id ); - - const sort = - field.sort ?? - function sort( a, b, direction ) { - return fieldTypeDefinition.sort( - getValue( { item: a } ), - getValue( { item: b } ), - direction - ); - }; - - const isValid = { - ...fieldTypeDefinition.isValid, - ...field.isValid, - }; - - const Edit = getControl( field, fieldTypeDefinition ); - - const render = - field.render ?? - function render( { - item, - field: renderedField, - }: DataViewRenderFieldProps< Item > ) { - return ( - fieldTypeDefinition.render as FunctionComponent< - DataViewRenderFieldProps< Item > - > - )( { item, field: renderedField } ); - }; - - const filterBy = getFilterBy( field, fieldTypeDefinition ); - - /** - * NormalizedField is a discriminated union type: the shape of the format property - * depends on the type property. For example, for the 'date' type, the format - * contains date or weekStartsOn — which are not valid for other types. - * - * Being type and format interdependent, we need to write the code - * in a way that TypeScript is able to statically infer the types. - * That's why we have a return branch for every item in the union type. - * - * See a longer explanation with examples at - * https://github.com/WordPress/gutenberg/pull/72999#discussion_r2523145453 - */ - const { type, ...fieldWithoutType } = field; - - const baseField = { - ...fieldWithoutType, - label: field.label || field.id, - header: field.header || field.label || field.id, - getValue, - setValue, - render, - sort, - isValid, - Edit, - hasElements: hasElements( field ), - enableHiding: field.enableHiding ?? true, - enableSorting: - field.enableSorting ?? - fieldTypeDefinition.enableSorting ?? - true, - filterBy, - readOnly: field.readOnly ?? fieldTypeDefinition.readOnly ?? false, - format: {}, - }; - - if ( field.type === 'date' ) { - const format = fieldTypeDefinition.getFormat( - field - ) as Required< FormatDate >; - - return { - ...baseField, - type: 'date', - format, - }; - } - - return { - ...baseField, - type: field.type, - }; - } ); -} From 1cf3064c5837681da326f718a9972aad140d4374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Wed, 19 Nov 2025 12:03:17 +0100 Subject: [PATCH 05/15] Remove the concept of field type definition --- packages/dataviews/src/field-types/index.tsx | 2 +- .../src/field-types/utils/normalize-fields.ts | 4 +- packages/dataviews/src/types/field-api.ts | 59 ------------------- 3 files changed, 3 insertions(+), 62 deletions(-) diff --git a/packages/dataviews/src/field-types/index.tsx b/packages/dataviews/src/field-types/index.tsx index e6c943f0677b23..fada223253f5f3 100644 --- a/packages/dataviews/src/field-types/index.tsx +++ b/packages/dataviews/src/field-types/index.tsx @@ -100,7 +100,7 @@ function normalizeField< Item >( * * @return A field type definition. */ -export default function getFieldTypeDefinition< Item >( +export default function getNormalizeFieldFunction< Item >( type?: FieldType ): ( field: Field< Item > ) => NormalizedField< Item > { if ( 'email' === type ) { diff --git a/packages/dataviews/src/field-types/utils/normalize-fields.ts b/packages/dataviews/src/field-types/utils/normalize-fields.ts index 0f564ea6ba9458..0abb1f55f7332c 100644 --- a/packages/dataviews/src/field-types/utils/normalize-fields.ts +++ b/packages/dataviews/src/field-types/utils/normalize-fields.ts @@ -5,7 +5,7 @@ /** * Internal dependencies */ -import getFieldTypeDefinition from '..'; +import getNormalizeFieldFunction from '..'; import type { Field, NormalizedField } from '../../types'; /** @@ -18,7 +18,7 @@ export default function normalizeFields< Item >( fields: Field< Item >[] ): NormalizedField< Item >[] { return fields.map( ( field ) => { - const normalize = getFieldTypeDefinition< Item >( field.type ); + const normalize = getNormalizeFieldFunction< Item >( field.type ); return normalize( field ); } ); diff --git a/packages/dataviews/src/types/field-api.ts b/packages/dataviews/src/types/field-api.ts index 5f82475a4498f2..b8e5b6051cfbb3 100644 --- a/packages/dataviews/src/types/field-api.ts +++ b/packages/dataviews/src/types/field-api.ts @@ -52,18 +52,6 @@ export interface NormalizedFilterByConfig { isPrimary?: boolean; } -interface FilterConfigForType { - /** - * What operators are used by default. - */ - defaultOperators: Operator[]; - - /** - * What operators are supported by the field. - */ - validOperators: Operator[]; -} - export type Operator = | 'is' | 'isNot' @@ -103,53 +91,6 @@ export type FieldType = | 'url' | 'array'; -/** - * An abstract interface for Field based on the field type. - */ -export type FieldTypeDefinition< Item > = { - /** - * Callback used to sort the field. - */ - sort: ( a: Item, b: Item, direction: SortDirection ) => number; - - /** - * Callback used to validate the field. - */ - isValid: Rules< Item >; - - /** - * Callback used to render an edit control for the field or control name. - */ - Edit: - | ComponentType< DataFormControlProps< Item > > - | string - | EditConfig - | null; - - /** - * Callback used to render the field. - */ - render: ComponentType< DataViewRenderFieldProps< Item > >; - - /** - * The filter config for the field. - */ - filterBy: FilterConfigForType | false; - - getFormat: ( field: Field< Item > ) => NormalizedFormat; - - /** - * Whether the field is readOnly. - * If `true`, the value will be rendered using the `render` callback. - */ - readOnly?: boolean; - - /** - * Whether the field is sortable. - */ - enableSorting: boolean; -}; - export type Rules< Item > = { required?: boolean; elements?: boolean; From 92a1eef9a7d6ba252c0d6f06b82e9463b154686e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Wed, 19 Nov 2025 12:14:35 +0100 Subject: [PATCH 06/15] Simplify normalized field --- .../src/field-types/utils/get-filter-by.ts | 5 ++- packages/dataviews/src/types/field-api.ts | 37 ++----------------- 2 files changed, 7 insertions(+), 35 deletions(-) diff --git a/packages/dataviews/src/field-types/utils/get-filter-by.ts b/packages/dataviews/src/field-types/utils/get-filter-by.ts index a8c82c8e605eca..333ed12b959365 100644 --- a/packages/dataviews/src/field-types/utils/get-filter-by.ts +++ b/packages/dataviews/src/field-types/utils/get-filter-by.ts @@ -1,13 +1,13 @@ /** * Internal dependencies */ -import type { Field, Operator, NormalizedFilterByConfig } from '../../types'; +import type { Field, FilterByConfig, Operator } from '../../types'; function getFilterBy< Item >( field: Field< Item >, defaultOperators: Operator[], validOperators: Operator[] -): NormalizedFilterByConfig | false { +): Required< FilterByConfig > | false { if ( field.filterBy === false ) { return false; } @@ -42,6 +42,7 @@ function getFilterBy< Item >( } return { + isPrimary: false, operators: defaultOperators, }; } diff --git a/packages/dataviews/src/types/field-api.ts b/packages/dataviews/src/types/field-api.ts index b8e5b6051cfbb3..c887de785c2a25 100644 --- a/packages/dataviews/src/types/field-api.ts +++ b/packages/dataviews/src/types/field-api.ts @@ -37,21 +37,6 @@ export interface FilterByConfig { isPrimary?: boolean; } -export interface NormalizedFilterByConfig { - /** - * The list of operators supported by the field. - */ - operators: Operator[]; - - /** - * Whether it is a primary filter. - * - * A primary filter is always visible and is not listed in the "Add filter" component, - * except for the list layout where it behaves like a secondary filter. - */ - isPrimary?: boolean; -} - export type Operator = | 'is' | 'isNot' @@ -282,20 +267,11 @@ export type DayString = | 'friday' | 'saturday'; -type NormalizedFieldBase< Item > = Omit< Field< Item >, 'Edit' > & { - label: string; - header: string | ReactElement; - getValue: ( args: { item: Item } ) => any; - setValue: ( args: { item: Item; value: any } ) => DeepPartial< Item >; - render: ComponentType< DataViewRenderFieldProps< Item > >; +type NormalizedFieldBase< Item > = Required< Field< Item > > & { Edit: ComponentType< DataFormControlProps< Item > > | null; + filterBy: Required< FilterByConfig > | false; + format: {}; hasElements: boolean; - sort: ( a: Item, b: Item, direction: SortDirection ) => number; - isValid: Rules< Item >; - enableHiding: boolean; - enableSorting: boolean; - filterBy: NormalizedFilterByConfig | false; - readOnly: boolean; }; type NormalizedFieldDate< Item > = NormalizedFieldBase< Item > & { @@ -303,13 +279,8 @@ type NormalizedFieldDate< Item > = NormalizedFieldBase< Item > & { format: Required< FormatDate >; }; -type NormalizedFieldGeneric< Item > = NormalizedFieldBase< Item > & { - type?: Exclude< FieldType, 'date' >; - format: {}; -}; - export type NormalizedField< Item > = - | NormalizedFieldGeneric< Item > + | NormalizedFieldBase< Item > | NormalizedFieldDate< Item >; /** From 741b019f30ac81fb6bd300c8d41753b252694ecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Wed, 19 Nov 2025 12:17:04 +0100 Subject: [PATCH 07/15] Add changelog --- packages/dataviews/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/dataviews/CHANGELOG.md b/packages/dataviews/CHANGELOG.md index 710b46241230d4..4a5b6b09c4be28 100644 --- a/packages/dataviews/CHANGELOG.md +++ b/packages/dataviews/CHANGELOG.md @@ -4,6 +4,7 @@ ### Enhancements +- Simplify field normalization and types. [#73387](https://github.com/WordPress/gutenberg/pull/73387) - DataViews table layout: make checkboxes permanently visible when bulk actions are available. [#73245](https://github.com/WordPress/gutenberg/pull/73245) - Documentation: surface better the `type` property in the documentation. [#73349](https://github.com/WordPress/gutenberg/pull/73349) - 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) From 6a05ef68bfa83c96f4f1fbf262d860a00d579344 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Wed, 19 Nov 2025 13:43:12 +0100 Subject: [PATCH 08/15] NormalizedField: refactor, there are optional props --- packages/dataviews/src/dataform-controls/index.tsx | 2 +- packages/dataviews/src/types/field-api.ts | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/dataviews/src/dataform-controls/index.tsx b/packages/dataviews/src/dataform-controls/index.tsx index b95256e310a7af..0c11f7a1225843 100644 --- a/packages/dataviews/src/dataform-controls/index.tsx +++ b/packages/dataviews/src/dataform-controls/index.tsx @@ -73,7 +73,7 @@ export function createConfiguredControl( config: EditConfig ) { export function getControl< Item >( field: Field< Item >, fallback: string | null -) { +): ComponentType< DataFormControlProps< Item > > | null { if ( typeof field.Edit === 'function' ) { return field.Edit; } diff --git a/packages/dataviews/src/types/field-api.ts b/packages/dataviews/src/types/field-api.ts index c887de785c2a25..0f888af7cbb8b1 100644 --- a/packages/dataviews/src/types/field-api.ts +++ b/packages/dataviews/src/types/field-api.ts @@ -267,11 +267,21 @@ export type DayString = | 'friday' | 'saturday'; -type NormalizedFieldBase< Item > = Required< Field< Item > > & { +type NormalizedFieldBase< Item > = Omit< Field< Item >, 'Edit' > & { + label: string; + header: string | ReactElement; + getValue: ( args: { item: Item } ) => any; + setValue: ( args: { item: Item; value: any } ) => DeepPartial< Item >; + render: ComponentType< DataViewRenderFieldProps< Item > >; Edit: ComponentType< DataFormControlProps< Item > > | null; + hasElements: boolean; + sort: ( a: Item, b: Item, direction: SortDirection ) => number; + isValid: Rules< Item >; + enableHiding: boolean; + enableSorting: boolean; filterBy: Required< FilterByConfig > | false; + readOnly: boolean; format: {}; - hasElements: boolean; }; type NormalizedFieldDate< Item > = NormalizedFieldBase< Item > & { From 407ad4265b5966d17e5770d0a673235ce607c395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:11:09 +0100 Subject: [PATCH 09/15] Fix FormatDate issues --- packages/dataviews/src/dataform-controls/date.tsx | 13 +++++++++++-- packages/dataviews/src/field-types/date.tsx | 15 +++++++++------ packages/dataviews/src/types/field-api.ts | 2 +- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/dataviews/src/dataform-controls/date.tsx b/packages/dataviews/src/dataform-controls/date.tsx index dd97fd7ebcf8c8..2fba6822b7322f 100644 --- a/packages/dataviews/src/dataform-controls/date.tsx +++ b/packages/dataviews/src/dataform-controls/date.tsx @@ -48,6 +48,7 @@ import { unlock } from '../lock-unlock'; import type { DataFormControlProps, FieldValidity, + FormatDate, NormalizedField, } from '../types'; import getCustomValidity from './utils/get-custom-validity'; @@ -273,7 +274,11 @@ function CalendarDateControl< Item >( { let weekStartsOn; if ( type === 'date' ) { - weekStartsOn = weekStartsOnToNumber( fieldFormat.weekStartsOn ); + // If the field type is date, we've already normalized the format, + // and so it's safe to tell TypeScript to trust us ("as Required"). + weekStartsOn = weekStartsOnToNumber( + ( fieldFormat as Required< FormatDate > ).weekStartsOn + ); } const fieldValue = getValue( { item: data } ); @@ -437,7 +442,11 @@ function CalendarDateRangeControl< Item >( { let weekStartsOn; if ( type === 'date' ) { - weekStartsOn = weekStartsOnToNumber( fieldFormat.weekStartsOn ); + // If the field type is date, we've already normalized the format, + // and so it's safe to tell TypeScript to trust us ("as Required"). + weekStartsOn = weekStartsOnToNumber( + ( fieldFormat as Required< FormatDate > ).weekStartsOn + ); } const onChangeCallback = useCallback( diff --git a/packages/dataviews/src/field-types/date.tsx b/packages/dataviews/src/field-types/date.tsx index 89b93efd9d3828..ae6afcd46a94d4 100644 --- a/packages/dataviews/src/field-types/date.tsx +++ b/packages/dataviews/src/field-types/date.tsx @@ -67,16 +67,19 @@ function render( { item, field }: DataViewRenderFieldProps< any > ) { return ''; } - // Not all fields have format, but date fields do. + // If the field type is date, we've already normalized the format, + // and so it's safe to tell TypeScript to trust us ("as Required"). // - // At runtime, this method will never be called for non-date fields. - // However, the type system does not know this, so we need to check it. - // There's an opportunity here to improve the type system. + // There're no runtime paths where this render function is called with a non-date field, + // but TypeScript is unable to infer this, hence the type assertion. + let format: Required< FormatDate >; if ( field.type !== 'date' ) { - return ''; + format = getFormat( field as Field< any > ); + } else { + format = field.format as Required< FormatDate >; } - return dateI18n( field.format.date, getDate( value ) ); + return dateI18n( format.weekStartsOn, getDate( value ) ); } export default function normalizeField< Item >( diff --git a/packages/dataviews/src/types/field-api.ts b/packages/dataviews/src/types/field-api.ts index 0f888af7cbb8b1..31f1011eeaa454 100644 --- a/packages/dataviews/src/types/field-api.ts +++ b/packages/dataviews/src/types/field-api.ts @@ -284,7 +284,7 @@ type NormalizedFieldBase< Item > = Omit< Field< Item >, 'Edit' > & { format: {}; }; -type NormalizedFieldDate< Item > = NormalizedFieldBase< Item > & { +export type NormalizedFieldDate< Item > = NormalizedFieldBase< Item > & { type: 'date'; format: Required< FormatDate >; }; From 96b68c0db5bdabaf4a7eae002d6102150fc4948b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:17:38 +0100 Subject: [PATCH 10/15] Revert unnecessary exports --- packages/dataviews/src/dataform-controls/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/dataviews/src/dataform-controls/index.tsx b/packages/dataviews/src/dataform-controls/index.tsx index 0c11f7a1225843..a2b97ee3a8016c 100644 --- a/packages/dataviews/src/dataform-controls/index.tsx +++ b/packages/dataviews/src/dataform-controls/index.tsx @@ -50,13 +50,13 @@ const FORM_CONTROLS: FormControls = { toggleGroup, }; -export function isEditConfig( value: any ): value is EditConfig { +function isEditConfig( value: any ): value is EditConfig { return ( value && typeof value === 'object' && typeof value.control === 'string' ); } -export function createConfiguredControl( config: EditConfig ) { +function createConfiguredControl( config: EditConfig ) { const { control, ...controlConfig } = config; const BaseControlType = getControlByType( control ); if ( BaseControlType === null ) { From 93d0632c31bde47ea06c7913922341a30f68e30b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Wed, 19 Nov 2025 15:25:05 +0100 Subject: [PATCH 11/15] Fix normalizeFields test --- packages/dataviews/src/test/normalize-fields.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/dataviews/src/test/normalize-fields.ts b/packages/dataviews/src/test/normalize-fields.ts index ffdc0916dfd6e0..7b1296e00c8305 100644 --- a/packages/dataviews/src/test/normalize-fields.ts +++ b/packages/dataviews/src/test/normalize-fields.ts @@ -165,7 +165,10 @@ describe( 'normalizeFields: default getValue', () => { ]; const normalizedFields = normalizeFields( fields ); const result = normalizedFields[ 0 ].filterBy; - expect( result ).toStrictEqual( { operators: [ 'is', 'isNot' ] } ); + expect( result ).toStrictEqual( { + isPrimary: false, + operators: [ 'is', 'isNot' ], + } ); } ); it( 'returns the default field type definition if undefined for untyped field (for primary filters)', () => { const fields: Field< {} >[] = [ @@ -194,6 +197,7 @@ describe( 'normalizeFields: default getValue', () => { const normalizedFields = normalizeFields( fields ); const result = normalizedFields[ 0 ].filterBy; expect( result ).toStrictEqual( { + isPrimary: false, operators: [ 'is', 'isNot', @@ -216,6 +220,7 @@ describe( 'normalizeFields: default getValue', () => { const normalizedFields = normalizeFields( fields ); const result = normalizedFields[ 0 ].filterBy; expect( result ).toStrictEqual( { + isPrimary: false, operators: [ 'is', 'isNot', From 49d43ca89b50d0a4b39be80236f6ca5c91f885a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Wed, 19 Nov 2025 18:06:56 +0100 Subject: [PATCH 12/15] NormalizedFieldDate --- .../src/components/dataviews-filters/filter.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/dataviews/src/components/dataviews-filters/filter.tsx b/packages/dataviews/src/components/dataviews-filters/filter.tsx index bb21598b43b225..f41c50dd1e6ba1 100644 --- a/packages/dataviews/src/components/dataviews-filters/filter.tsx +++ b/packages/dataviews/src/components/dataviews-filters/filter.tsx @@ -21,9 +21,6 @@ import { useRef, createInterpolateElement } from '@wordpress/element'; import { closeSmall } from '@wordpress/icons'; import { dateI18n, getDate } from '@wordpress/date'; -const ENTER = 'Enter'; -const SPACE = ' '; - /** * Internal dependencies */ @@ -57,6 +54,7 @@ import { import type { Filter, NormalizedField, + NormalizedFieldDate, NormalizedFilter, Operator, Option, @@ -65,6 +63,9 @@ import type { import useElements from '../../hooks/use-elements'; import parseDateTime from '../../field-types/utils/parse-date-time'; +const ENTER = 'Enter'; +const SPACE = ' '; + interface FilterTextProps { activeElements: Option[]; filterInView?: Filter; @@ -503,7 +504,10 @@ export default function Filter( { try { const dateValue = parseDateTime( label ); if ( dateValue !== null ) { - label = dateI18n( field.format.date, getDate( label ) ); + label = dateI18n( + ( field as NormalizedFieldDate< any > ).format.date, + getDate( label ) + ); } } catch ( e ) { label = filterInView.value; From c336ab3d015f70704add0bccb77905ec76765766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Thu, 20 Nov 2025 10:40:42 +0100 Subject: [PATCH 13/15] Fix sorting --- packages/dataviews/src/field-types/array.tsx | 36 ++++++------ .../dataviews/src/field-types/boolean.tsx | 37 ++++++------ packages/dataviews/src/field-types/color.tsx | 57 ++++++++++--------- packages/dataviews/src/field-types/date.tsx | 16 +++--- .../dataviews/src/field-types/datetime.tsx | 16 +++--- packages/dataviews/src/field-types/email.tsx | 15 +++-- packages/dataviews/src/field-types/index.tsx | 11 ++-- .../dataviews/src/field-types/integer.tsx | 10 ++-- packages/dataviews/src/field-types/number.tsx | 10 ++-- .../dataviews/src/field-types/telephone.tsx | 14 +++-- packages/dataviews/src/field-types/text.tsx | 14 +++-- packages/dataviews/src/field-types/url.tsx | 14 +++-- .../src/stories/dataviews.fixtures.tsx | 1 + 13 files changed, 139 insertions(+), 112 deletions(-) diff --git a/packages/dataviews/src/field-types/array.tsx b/packages/dataviews/src/field-types/array.tsx index 9015f14360130e..50fc08b519a645 100644 --- a/packages/dataviews/src/field-types/array.tsx +++ b/packages/dataviews/src/field-types/array.tsx @@ -26,23 +26,6 @@ import getValueFromId from './utils/get-value-from-id'; import setValueFromId from './utils/set-value-from-id'; import getFilterBy from './utils/get-filter-by'; -// Sort arrays by length, then alphabetically by joined string -function sort( valueA: any, valueB: any, direction: SortDirection ) { - 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; - } - - const joinedA = arrA.join( ',' ); - const joinedB = arrB.join( ',' ); - return direction === 'asc' - ? joinedA.localeCompare( joinedB ) - : joinedB.localeCompare( joinedA ); -} - function render( { item, field }: DataViewRenderFieldProps< any > ) { const value = field.getValue( { item } ) || []; return value.join( ', ' ); @@ -62,6 +45,25 @@ export default function normalizeField< Item >( const getValue = field.getValue || getValueFromId( field.id ); const setValue = field.setValue || setValueFromId( field.id ); + 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; + } + + 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 ) => { diff --git a/packages/dataviews/src/field-types/boolean.tsx b/packages/dataviews/src/field-types/boolean.tsx index 677184df2a9f9c..3c1ab3059f4dd6 100644 --- a/packages/dataviews/src/field-types/boolean.tsx +++ b/packages/dataviews/src/field-types/boolean.tsx @@ -22,23 +22,6 @@ import getValueFromId from './utils/get-value-from-id'; import setValueFromId from './utils/set-value-from-id'; import getFilterBy from './utils/get-filter-by'; -function sort( a: any, b: any, direction: SortDirection ) { - const boolA = Boolean( a ); - const boolB = Boolean( b ); - - if ( boolA === boolB ) { - return 0; - } - - // In ascending order, false comes before true - if ( direction === 'asc' ) { - return boolA ? 1 : -1; - } - - // In descending order, true comes before false - return boolA ? -1 : 1; -} - function render( { item, field }: DataViewRenderFieldProps< any > ) { if ( field.hasElements ) { return ; @@ -60,6 +43,26 @@ export default function normalizeField< Item >( ): NormalizedField< Item > { const getValue = field.getValue || getValueFromId( field.id ); const setValue = field.setValue || setValueFromId( field.id ); + + 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; + } + + // In descending order, true comes before false + return boolA ? -1 : 1; + }; + const isValid: Rules< Item > = { elements: true, custom: ( item: any, normalizedField ) => { diff --git a/packages/dataviews/src/field-types/color.tsx b/packages/dataviews/src/field-types/color.tsx index 025b7b8bf0ada5..907c62f8ee0620 100644 --- a/packages/dataviews/src/field-types/color.tsx +++ b/packages/dataviews/src/field-types/color.tsx @@ -32,34 +32,6 @@ import getValueFromId from './utils/get-value-from-id'; import setValueFromId from './utils/set-value-from-id'; import getFilterBy from './utils/get-filter-by'; -function sort( valueA: any, valueB: any, direction: SortDirection ) { - // Convert colors to HSL for better sorting - const colorA = colord( valueA ); - const colorB = colord( valueB ); - - if ( ! colorA.isValid() && ! colorB.isValid() ) { - return 0; - } - if ( ! colorA.isValid() ) { - return direction === 'asc' ? 1 : -1; - } - if ( ! colorB.isValid() ) { - return direction === 'asc' ? -1 : 1; - } - - // Sort by hue, then saturation, then lightness - const hslA = colorA.toHsl(); - const hslB = colorB.toHsl(); - - if ( hslA.h !== hslB.h ) { - return direction === 'asc' ? hslA.h - hslB.h : hslB.h - hslA.h; - } - if ( hslA.s !== hslB.s ) { - return direction === 'asc' ? hslA.s - hslB.s : hslB.s - hslA.s; - } - return direction === 'asc' ? hslA.l - hslB.l : hslB.l - hslA.l; -} - function render( { item, field }: DataViewRenderFieldProps< any > ) { if ( field.hasElements ) { return ; @@ -94,6 +66,35 @@ export default function normalizeField< Item >( ): NormalizedField< Item > { const getValue = field.getValue || getValueFromId( field.id ); const setValue = field.setValue || setValueFromId( field.id ); + + const sort = ( valueA: any, valueB: any, direction: SortDirection ) => { + // Convert colors to HSL for better sorting + const colorA = colord( valueA ); + const colorB = colord( valueB ); + + if ( ! colorA.isValid() && ! colorB.isValid() ) { + return 0; + } + if ( ! colorA.isValid() ) { + return direction === 'asc' ? 1 : -1; + } + if ( ! colorB.isValid() ) { + return direction === 'asc' ? -1 : 1; + } + + // Sort by hue, then saturation, then lightness + const hslA = colorA.toHsl(); + const hslB = colorB.toHsl(); + + if ( hslA.h !== hslB.h ) { + return direction === 'asc' ? hslA.h - hslB.h : hslB.h - hslA.h; + } + if ( hslA.s !== hslB.s ) { + return direction === 'asc' ? hslA.s - hslB.s : hslB.s - hslA.s; + } + return direction === 'asc' ? hslA.l - hslB.l : hslB.l - hslA.l; + }; + const isValid: Rules< Item > = { elements: true, custom: ( item: any, normalizedField ) => { diff --git a/packages/dataviews/src/field-types/date.tsx b/packages/dataviews/src/field-types/date.tsx index ae6afcd46a94d4..c6cb5ba3ad59d4 100644 --- a/packages/dataviews/src/field-types/date.tsx +++ b/packages/dataviews/src/field-types/date.tsx @@ -35,13 +35,6 @@ import getValueFromId from './utils/get-value-from-id'; import setValueFromId from './utils/set-value-from-id'; import getFilterBy from './utils/get-filter-by'; -function sort( a: any, b: any, direction: SortDirection ) { - const timeA = new Date( a ).getTime(); - const timeB = new Date( b ).getTime(); - - return direction === 'asc' ? timeA - timeB : timeB - timeA; -} - function getFormat( field: Field< any > ): Required< FormatDate > { return { date: @@ -92,6 +85,15 @@ export default function normalizeField< Item >( custom: () => null, }; + const sort = ( a: Item, b: Item, direction: SortDirection ) => { + const valueA = getValue( { item: a } ); + const valueB = getValue( { item: b } ); + const timeA = new Date( valueA ).getTime(); + const timeB = new Date( valueB ).getTime(); + + return direction === 'asc' ? timeA - timeB : timeB - timeA; + }; + const defaultOperators: Operator[] = [ OPERATOR_ON, OPERATOR_NOT_ON, diff --git a/packages/dataviews/src/field-types/datetime.tsx b/packages/dataviews/src/field-types/datetime.tsx index dc7ac51c77eabd..bdf424c1ec57a3 100644 --- a/packages/dataviews/src/field-types/datetime.tsx +++ b/packages/dataviews/src/field-types/datetime.tsx @@ -27,13 +27,6 @@ import getValueFromId from './utils/get-value-from-id'; import setValueFromId from './utils/set-value-from-id'; import getFilterBy from './utils/get-filter-by'; -function sort( a: any, b: any, direction: SortDirection ) { - const timeA = new Date( a ).getTime(); - const timeB = new Date( b ).getTime(); - - return direction === 'asc' ? timeA - timeB : timeB - timeA; -} - function render( { item, field }: DataViewRenderFieldProps< any > ) { if ( field.elements ) { return ; @@ -62,6 +55,15 @@ export default function normalizeField< Item >( custom: () => null, }; + const sort = ( a: Item, b: Item, direction: SortDirection ) => { + const valueA = getValue( { item: a } ); + const valueB = getValue( { item: b } ); + const timeA = new Date( valueA ).getTime(); + const timeB = new Date( valueB ).getTime(); + + return direction === 'asc' ? timeA - timeB : timeB - timeA; + }; + const defaultOperators: Operator[] = [ OPERATOR_ON, OPERATOR_NOT_ON, diff --git a/packages/dataviews/src/field-types/email.tsx b/packages/dataviews/src/field-types/email.tsx index 5febdb5a00e4a1..ad4e03fa6b0f49 100644 --- a/packages/dataviews/src/field-types/email.tsx +++ b/packages/dataviews/src/field-types/email.tsx @@ -32,12 +32,6 @@ import getValueFromId from './utils/get-value-from-id'; import setValueFromId from './utils/set-value-from-id'; import getFilterBy from './utils/get-filter-by'; -function sort( valueA: any, valueB: any, direction: SortDirection ) { - return direction === 'asc' - ? valueA.localeCompare( valueB ) - : valueB.localeCompare( valueA ); -} - // Email validation regex based on HTML5 spec // https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address const emailRegex = @@ -56,6 +50,15 @@ export default function normalizeField< Item >( ): NormalizedField< Item > { const getValue = field.getValue || getValueFromId( field.id ); const setValue = field.setValue || setValueFromId( field.id ); + + const sort = ( a: any, b: any, direction: SortDirection ) => { + const valueA = getValue( { item: a } ); + const valueB = getValue( { item: b } ); + return direction === 'asc' + ? valueA.localeCompare( valueB ) + : valueB.localeCompare( valueA ); + }; + const isValid: Rules< Item > = { elements: true, custom: ( item: any, normalizedField ) => { diff --git a/packages/dataviews/src/field-types/index.tsx b/packages/dataviews/src/field-types/index.tsx index fada223253f5f3..4534a2ff3e6d6f 100644 --- a/packages/dataviews/src/field-types/index.tsx +++ b/packages/dataviews/src/field-types/index.tsx @@ -42,13 +42,16 @@ function normalizeField< Item >( }; const sort = ( a: any, b: any, direction: SortDirection ) => { - if ( typeof a === 'number' && typeof b === 'number' ) { - return direction === 'asc' ? a - b : b - a; + const valueA = getValue( { item: a } ); + const valueB = getValue( { item: b } ); + + if ( typeof valueA === 'number' && typeof valueB === 'number' ) { + return direction === 'asc' ? valueA - valueB : valueB - valueA; } return direction === 'asc' - ? a.localeCompare( b ) - : b.localeCompare( a ); + ? valueA.localeCompare( valueB ) + : valueB.localeCompare( valueA ); }; const render = ( { diff --git a/packages/dataviews/src/field-types/integer.tsx b/packages/dataviews/src/field-types/integer.tsx index b76d559ee78f95..19864a1fab7ae8 100644 --- a/packages/dataviews/src/field-types/integer.tsx +++ b/packages/dataviews/src/field-types/integer.tsx @@ -34,10 +34,6 @@ import getValueFromId from './utils/get-value-from-id'; import setValueFromId from './utils/set-value-from-id'; import getFilterBy from './utils/get-filter-by'; -function sort( a: any, b: any, direction: SortDirection ) { - return direction === 'asc' ? a - b : b - a; -} - function render( { item, field }: DataViewRenderFieldProps< any > ) { return field.hasElements ? ( @@ -66,6 +62,12 @@ export default function normalizeField< Item >( }, }; + const sort = ( a: Item, b: Item, direction: SortDirection ) => { + const valueA = getValue( { item: a } ); + const valueB = getValue( { item: b } ); + return direction === 'asc' ? valueA - valueB : valueB - valueA; + }; + const defaultOperators: Operator[] = [ OPERATOR_IS, OPERATOR_IS_NOT, diff --git a/packages/dataviews/src/field-types/number.tsx b/packages/dataviews/src/field-types/number.tsx index 966290104ad9b6..ca6d54fbeb352e 100644 --- a/packages/dataviews/src/field-types/number.tsx +++ b/packages/dataviews/src/field-types/number.tsx @@ -34,10 +34,6 @@ import getValueFromId from './utils/get-value-from-id'; import setValueFromId from './utils/set-value-from-id'; import getFilterBy from './utils/get-filter-by'; -function sort( a: any, b: any, direction: SortDirection ) { - return direction === 'asc' ? a - b : b - a; -} - function isEmpty( value: unknown ): value is '' | undefined | null { return value === '' || value === undefined || value === null; } @@ -73,6 +69,12 @@ export default function normalizeField< Item >( }, }; + const sort = ( a: Item, b: Item, direction: SortDirection ) => { + const valueA = getValue( { item: a } ); + const valueB = getValue( { item: b } ); + return direction === 'asc' ? valueA - valueB : valueB - valueA; + }; + const defaultOperators: Operator[] = [ OPERATOR_IS, OPERATOR_IS_NOT, diff --git a/packages/dataviews/src/field-types/telephone.tsx b/packages/dataviews/src/field-types/telephone.tsx index 612638e5cbb376..19d2040c820de0 100644 --- a/packages/dataviews/src/field-types/telephone.tsx +++ b/packages/dataviews/src/field-types/telephone.tsx @@ -27,12 +27,6 @@ import getValueFromId from './utils/get-value-from-id'; import setValueFromId from './utils/set-value-from-id'; import getFilterBy from './utils/get-filter-by'; -function sort( valueA: any, valueB: any, direction: SortDirection ) { - return direction === 'asc' - ? valueA.localeCompare( valueB ) - : valueB.localeCompare( valueA ); -} - function render( { item, field }: DataViewRenderFieldProps< any > ) { return field.hasElements ? ( @@ -51,6 +45,14 @@ export default function normalizeField< Item >( custom: () => null, }; + const sort = ( a: any, b: any, direction: SortDirection ) => { + const valueA = getValue( { item: a } ); + const valueB = getValue( { item: b } ); + return direction === 'asc' + ? valueA.localeCompare( valueB ) + : valueB.localeCompare( valueA ); + }; + const defaultOperators: Operator[] = [ OPERATOR_IS_ANY, OPERATOR_IS_NONE ]; const validOperators: Operator[] = [ diff --git a/packages/dataviews/src/field-types/text.tsx b/packages/dataviews/src/field-types/text.tsx index 65dbcd7ec15b4b..036863ff8ad058 100644 --- a/packages/dataviews/src/field-types/text.tsx +++ b/packages/dataviews/src/field-types/text.tsx @@ -27,12 +27,6 @@ import getValueFromId from './utils/get-value-from-id'; import setValueFromId from './utils/set-value-from-id'; import getFilterBy from './utils/get-filter-by'; -function sort( valueA: any, valueB: any, direction: SortDirection ) { - return direction === 'asc' - ? valueA.localeCompare( valueB ) - : valueB.localeCompare( valueA ); -} - function render( { item, field }: DataViewRenderFieldProps< any > ) { return field.hasElements ? ( @@ -51,6 +45,14 @@ export default function normalizeField< Item >( custom: () => null, }; + const sort = ( a: Item, b: Item, direction: SortDirection ) => { + const valueA = getValue( { item: a } ); + const valueB = getValue( { item: b } ); + return direction === 'asc' + ? valueA.localeCompare( valueB ) + : valueB.localeCompare( valueA ); + }; + const defaultOperators: Operator[] = [ OPERATOR_IS_ANY, OPERATOR_IS_NONE ]; const validOperators: Operator[] = [ diff --git a/packages/dataviews/src/field-types/url.tsx b/packages/dataviews/src/field-types/url.tsx index 724d7a311dd29b..d43dcfd885e57f 100644 --- a/packages/dataviews/src/field-types/url.tsx +++ b/packages/dataviews/src/field-types/url.tsx @@ -27,12 +27,6 @@ import getValueFromId from './utils/get-value-from-id'; import setValueFromId from './utils/set-value-from-id'; import getFilterBy from './utils/get-filter-by'; -function sort( valueA: any, valueB: any, direction: SortDirection ) { - return direction === 'asc' - ? valueA.localeCompare( valueB ) - : valueB.localeCompare( valueA ); -} - function render( { item, field }: DataViewRenderFieldProps< any > ) { return field.hasElements ? ( @@ -51,6 +45,14 @@ export default function normalizeField< Item >( custom: () => null, }; + const sort = ( a: any, b: any, direction: SortDirection ) => { + const valueA = getValue( { item: a } ); + const valueB = getValue( { item: b } ); + return direction === 'asc' + ? valueA.localeCompare( valueB ) + : valueB.localeCompare( valueA ); + }; + const defaultOperators: Operator[] = [ OPERATOR_IS_ANY, OPERATOR_IS_NONE ]; const validOperators: Operator[] = [ diff --git a/packages/dataviews/src/stories/dataviews.fixtures.tsx b/packages/dataviews/src/stories/dataviews.fixtures.tsx index 88928639d6458a..86349786fdd3dc 100644 --- a/packages/dataviews/src/stories/dataviews.fixtures.tsx +++ b/packages/dataviews/src/stories/dataviews.fixtures.tsx @@ -1425,6 +1425,7 @@ export const fields: Field< SpaceObject >[] = [ }, { label: 'Type', + // type: 'text', id: 'type', enableHiding: false, elements: [ From 2fedda20948f30f07f3a9189ea91e0893723adb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Thu, 20 Nov 2025 17:20:51 +0100 Subject: [PATCH 14/15] Remove leftover --- packages/dataviews/src/stories/dataviews.fixtures.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/dataviews/src/stories/dataviews.fixtures.tsx b/packages/dataviews/src/stories/dataviews.fixtures.tsx index 86349786fdd3dc..88928639d6458a 100644 --- a/packages/dataviews/src/stories/dataviews.fixtures.tsx +++ b/packages/dataviews/src/stories/dataviews.fixtures.tsx @@ -1425,7 +1425,6 @@ export const fields: Field< SpaceObject >[] = [ }, { label: 'Type', - // type: 'text', id: 'type', enableHiding: false, elements: [ From 893b71a932ddb7429e86d3d05bc3548ab741950a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Thu, 20 Nov 2025 17:33:12 +0100 Subject: [PATCH 15/15] Use WordPress defaults when field type is not date --- packages/dataviews/src/dataform-controls/date.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dataviews/src/dataform-controls/date.tsx b/packages/dataviews/src/dataform-controls/date.tsx index 2fba6822b7322f..64cf4139d7a1f0 100644 --- a/packages/dataviews/src/dataform-controls/date.tsx +++ b/packages/dataviews/src/dataform-controls/date.tsx @@ -272,7 +272,7 @@ function CalendarDateControl< Item >( { null ); - let weekStartsOn; + let weekStartsOn = getSettings().l10n.startOfWeek; if ( type === 'date' ) { // If the field type is date, we've already normalized the format, // and so it's safe to tell TypeScript to trust us ("as Required").