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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions packages/dataviews/src/components/dataform-controls/group.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* WordPress dependencies
*/
import {
BaseControl,
__experimentalVStack as VStack,
} from '@wordpress/components';

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

function PropertiesToFields< Item >( {
data,
field,
onChange,
hideLabelFromVision,
validity,
}: DataFormControlProps< Item > ) {
const { properties } = field;
return (
<VStack spacing={ 4 }>
{ Object.entries( properties ).map( ( [ propKey, propField ] ) => {
if ( ! propField.Edit ) {
return null;
}

return (
<propField.Edit
key={ propKey }
data={ data }
field={ propField }
onChange={ onChange }
hideLabelFromVision={ hideLabelFromVision }
validity={ validity }
/>
);
} ) }
</VStack>
);
}

/**
* Group field control.
*
* Groups related fields visually without requiring nested data.
* Unlike ObjectControl, this passes data directly to properties
* (properties use their own IDs to access root-level data).
*/
export default function GroupControl< Item >( {
data,
field,
onChange,
hideLabelFromVision,
validity,
}: DataFormControlProps< Item > ) {
const { properties } = field;

if (
! properties ||
typeof properties !== 'object' ||
Object.keys( properties ).length === 0
) {
return null;
}

// KEY DIFFERENCE from ObjectControl:
// Pass `data` directly (not field.getValue result).
// Properties use their own IDs to access root-level data.
return hideLabelFromVision ? (
<PropertiesToFields
data={ data }
field={ field }
onChange={ onChange }
hideLabelFromVision={ hideLabelFromVision }
validity={ validity }
/>
) : (
<fieldset className="dataviews-controls__group">
<BaseControl.VisualLabel as="legend">
{ field.label }
</BaseControl.VisualLabel>
<PropertiesToFields
data={ data }
field={ field }
onChange={ onChange }
hideLabelFromVision={ hideLabelFromVision }
validity={ validity }
/>
</fieldset>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import textarea from './textarea';
import toggleGroup from './toggle-group';
import array from './array';
import color from './color';
import object from './object';
import group from './group';
import password from './password';
import hasElements from '../../field-types/utils/has-elements';

Expand All @@ -37,10 +39,12 @@ const FORM_CONTROLS: FormControls = {
datetime,
date,
email,
group,
telephone,
url,
integer,
number,
object,
password,
radio,
select,
Expand Down
103 changes: 103 additions & 0 deletions packages/dataviews/src/components/dataform-controls/object.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* WordPress dependencies
*/
import {
BaseControl,
VisuallyHidden,
__experimentalVStack as VStack,
} from '@wordpress/components';
import { useCallback } from '@wordpress/element';

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

function PropertiesToFields< Item >( {
data,
field,
onChange,
hideLabelFromVision,
validity,
}: DataFormControlProps< Item > ) {
const { properties } = field;
return (
<VStack spacing={ 4 }>
{ Object.entries( properties ).map( ( [ propKey, propField ] ) => {
if ( ! propField.Edit ) {
return null;
}

return (
<propField.Edit
key={ propKey }
data={ data }
field={ propField }
onChange={ onChange }
hideLabelFromVision={ hideLabelFromVision }
validity={ validity }
/>
);
} ) }
</VStack>
);
}

/**
* Object field control.
*
* Auto-generates a form with controls for each property in `properties`.
*/
export default function ObjectControl< Item >( {
data,
field,
onChange,
hideLabelFromVision,
validity,
}: DataFormControlProps< Item > ) {
const { properties } = field;

const currentValue = field.getValue( { item: data } ) ?? {};
const handlePropertyChange = useCallback(
( updates: DeepPartial< Item > ) => {
onChange(
field.setValue( {
item: data,
value: { ...currentValue, ...updates },
} )
);
},
[ onChange ]
);

if (
! properties ||
typeof properties !== 'object' ||
Object.keys( properties ).length === 0
) {
return null;
}

return hideLabelFromVision ? (
<PropertiesToFields
data={ currentValue }
field={ field }
onChange={ handlePropertyChange }
hideLabelFromVision={ hideLabelFromVision }
validity={ validity }
/>
) : (
<fieldset className="dataviews-controls__object">
<BaseControl.VisualLabel as="legend">
{ field.label }
</BaseControl.VisualLabel>
<PropertiesToFields
data={ currentValue }
field={ field }
onChange={ handlePropertyChange }
hideLabelFromVision={ hideLabelFromVision }
validity={ validity }
/>
</fieldset>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,35 @@ function ModalContent< Item >( {
[ field ]
);

const fieldsAsFieldType: Field< Item >[] = fields.map( ( f ) => ( {
...f,
Edit: f.Edit === null ? undefined : f.Edit,
isValid: {
required: f.isValid.required?.constraint,
elements: f.isValid.elements?.constraint,
min: f.isValid.min?.constraint,
max: f.isValid.max?.constraint,
pattern: f.isValid.pattern?.constraint,
minLength: f.isValid.minLength?.constraint,
maxLength: f.isValid.maxLength?.constraint,
},
} ) );
function denormalizeFields< T >(
normalizedFields: NormalizedField< T >[]
): Field< T >[] {
return normalizedFields.map( ( f ) => ( {
...f,
properties: f.properties
? Object.fromEntries(
Object.entries( f.properties ).map(
( [ key, property ] ) => [
key,
denormalizeFields( [ property ] )[ 0 ],
]
)
)
: {},
Edit: f.Edit === null ? undefined : f.Edit,
isValid: {
required: f.isValid.required?.constraint,
elements: f.isValid.elements?.constraint,
min: f.isValid.min?.constraint,
max: f.isValid.max?.constraint,
pattern: f.isValid.pattern?.constraint,
minLength: f.isValid.minLength?.constraint,
maxLength: f.isValid.maxLength?.constraint,
},
} ) ) as Field< T >[];
}

const fieldsAsFieldType: Field< Item >[] = denormalizeFields( fields );
const { validity } = useFormValidity( modalData, fieldsAsFieldType, form );

const onApply = () => {
Expand Down
44 changes: 44 additions & 0 deletions packages/dataviews/src/field-types/group.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Internal dependencies
*/
import type { NormalizedField } from '../types';
import type { FieldType } from '../types/private';
import render from './utils/render-default';

/**
* Format group value for display by joining property display values.
*/
function getValueFormatted< Item >( {
item,
field,
}: {
item: Item;
field: NormalizedField< Item >;
} ): string {
const { properties } = field;

if ( ! properties || Object.keys( properties ).length === 0 ) {
return '';
}

return Object.values( properties )
.map( ( propField ) =>
propField.getValueFormatted( { item, field: propField } )
)
.filter( Boolean )
.join( ', ' );
}

export default {
type: 'group',
render,
Edit: 'group',
sort: () => 0,
enableSorting: false,
enableGlobalSearch: false,
defaultOperators: [],
validOperators: [],
format: {},
getValueFormatted,
validate: {},
} satisfies FieldType< any >;
14 changes: 14 additions & 0 deletions packages/dataviews/src/field-types/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import { default as password } from './password';
import { default as telephone } from './telephone';
import { default as color } from './color';
import { default as url } from './url';
import { default as object } from './object';
import { default as group } from './group';
import { default as noType } from './no-type';
import getIsValid from './utils/get-is-valid';
import getFormat from './utils/get-format';
Expand All @@ -51,6 +53,8 @@ function getFieldTypeByName< Item >( type?: FieldTypeName ): FieldType< Item > {
telephone,
color,
url,
object,
group,
].find( ( fieldType ) => fieldType?.type === type );

if ( !! found ) {
Expand Down Expand Up @@ -114,6 +118,16 @@ export default function normalizeFields< Item >(
format: getFormat( field, fieldType ),
getValueFormatted:
field.getValueFormatted ?? fieldType.getValueFormatted,
properties: field.properties
? Object.fromEntries(
Object.entries( field.properties ).map(
( [ key, property ] ) => [
key,
normalizeFields( [ property ] )[ 0 ],
]
)
)
: {},
};
} );
}
Loading
Loading