From dad0c81535ab821685008ebc489136c296ab3afb Mon Sep 17 00:00:00 2001 From: Vrishabhsk Date: Mon, 29 Dec 2025 16:18:59 +0400 Subject: [PATCH 1/7] Update types --- packages/dataviews/src/types/field-api.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/dataviews/src/types/field-api.ts b/packages/dataviews/src/types/field-api.ts index a65c9253a4499c..d7477e8540bd9b 100644 --- a/packages/dataviews/src/types/field-api.ts +++ b/packages/dataviews/src/types/field-api.ts @@ -129,6 +129,10 @@ export type EditConfigTextarea = { * Number of rows for the textarea. */ rows?: number; + /** + * Whether the control is disabled. + */ + disabled?: boolean; }; /** @@ -144,13 +148,25 @@ export type EditConfigText = { * Suffix component to display after the input. */ suffix?: React.ComponentType; + /** + * Whether the control is disabled. + */ + disabled?: boolean; }; /** - * Edit configuration for other control types (excluding 'text' and 'textarea'). + * Edit configuration for other control types. + * Includes all available dataform control types beyond text and textarea. */ export type EditConfigGeneric = { - control: Exclude< FieldTypeName, 'text' | 'textarea' >; + control: + | FieldTypeName + | 'checkbox' + | 'radio' + | 'select' + | 'toggle' + | 'toggleGroup'; + disabled?: boolean; }; /** @@ -417,6 +433,7 @@ export type DataFormControlProps< Item > = { prefix?: React.ComponentType; suffix?: React.ComponentType; rows?: number; + disabled?: boolean; }; }; From c083633c6ed7fc3d5af5faacca9b80598b019e01 Mon Sep 17 00:00:00 2001 From: Vrishabhsk Date: Mon, 29 Dec 2025 16:21:31 +0400 Subject: [PATCH 2/7] Update dataform controls --- .../dataviews/src/dataform-controls/array.tsx | 3 + .../src/dataform-controls/checkbox.tsx | 3 + .../dataviews/src/dataform-controls/color.tsx | 10 ++- .../dataviews/src/dataform-controls/date.tsx | 65 ++++++++++++------- .../src/dataform-controls/datetime.tsx | 32 +++++---- .../dataviews/src/dataform-controls/email.tsx | 3 + .../src/dataform-controls/integer.tsx | 4 +- .../src/dataform-controls/number.tsx | 8 ++- .../src/dataform-controls/password.tsx | 3 + .../dataviews/src/dataform-controls/radio.tsx | 3 + .../src/dataform-controls/select.tsx | 3 + .../src/dataform-controls/telephone.tsx | 3 + .../dataviews/src/dataform-controls/text.tsx | 3 +- .../src/dataform-controls/textarea.tsx | 3 +- .../src/dataform-controls/toggle-group.tsx | 3 + .../src/dataform-controls/toggle.tsx | 3 + .../dataviews/src/dataform-controls/url.tsx | 3 + .../utils/relative-date-control.tsx | 4 ++ .../utils/validated-input.tsx | 6 ++ .../utils/validated-number.tsx | 3 + 20 files changed, 130 insertions(+), 38 deletions(-) diff --git a/packages/dataviews/src/dataform-controls/array.tsx b/packages/dataviews/src/dataform-controls/array.tsx index 3c8bc9dc8e4b3c..e17a3eb63788ba 100644 --- a/packages/dataviews/src/dataform-controls/array.tsx +++ b/packages/dataviews/src/dataform-controls/array.tsx @@ -20,9 +20,11 @@ export default function ArrayControl< Item >( { onChange, hideLabelFromVision, validity, + config, }: DataFormControlProps< Item > ) { const { label, placeholder, getValue, setValue, isValid } = field; const value = getValue( { item: data } ); + const { disabled = false } = config || {}; const { elements, isLoading } = useElements( { elements: field.elements, @@ -71,6 +73,7 @@ export default function ArrayControl< Item >( { onChange={ onChangeControl } placeholder={ placeholder } suggestions={ elements?.map( ( element ) => element.value ) } + disabled={ disabled } __experimentalValidateInput={ ( token: string ) => { // If elements validation is required, check if token is valid if ( field.isValid?.elements && elements ) { diff --git a/packages/dataviews/src/dataform-controls/checkbox.tsx b/packages/dataviews/src/dataform-controls/checkbox.tsx index 8f5cf436b494d5..ff41971b2bf264 100644 --- a/packages/dataviews/src/dataform-controls/checkbox.tsx +++ b/packages/dataviews/src/dataform-controls/checkbox.tsx @@ -19,8 +19,10 @@ export default function Checkbox< Item >( { data, hideLabelFromVision, validity, + config, }: DataFormControlProps< Item > ) { const { getValue, setValue, label, description, isValid } = field; + const { disabled = false } = config || {}; const onChangeControl = useCallback( () => { onChange( @@ -37,6 +39,7 @@ export default function Checkbox< Item >( { help={ description } checked={ getValue( { item: data } ) } onChange={ onChangeControl } + disabled={ disabled } /> ); } diff --git a/packages/dataviews/src/dataform-controls/color.tsx b/packages/dataviews/src/dataform-controls/color.tsx index ba48bdbaf85129..97a6ce2ac58398 100644 --- a/packages/dataviews/src/dataform-controls/color.tsx +++ b/packages/dataviews/src/dataform-controls/color.tsx @@ -25,9 +25,11 @@ const { ValidatedInputControl, Picker } = unlock( privateApis ); const ColorPicker = ( { color, onColorChange, + disabled, }: { color: string; onColorChange: ( colorObject: any ) => void; + disabled?: boolean; } ) => { const validColor = color && colord( color ).isValid() ? color : '#ffffff'; @@ -38,13 +40,14 @@ const ColorPicker = ( { @@ -609,6 +622,7 @@ function CalendarDateRangeControl< Item >( { handleManualDateChange( 'from', newValue ) } required={ !! field.isValid?.required } + disabled={ disabled } /> ( { handleManualDateChange( 'to', newValue ) } required={ !! field.isValid?.required } + disabled={ disabled } /> - + { ! disabled && ( + + ) } @@ -646,6 +663,7 @@ export default function DateControl< Item >( { hideLabelFromVision, operator, validity, + config, }: DataFormControlProps< Item > ) { if ( operator === OPERATOR_IN_THE_PAST || operator === OPERATOR_OVER ) { return ( @@ -656,6 +674,7 @@ export default function DateControl< Item >( { onChange={ onChange } hideLabelFromVision={ hideLabelFromVision } operator={ operator } + config={ config } /> ); } @@ -668,6 +687,7 @@ export default function DateControl< Item >( { onChange={ onChange } hideLabelFromVision={ hideLabelFromVision } validity={ validity } + config={ config } /> ); } @@ -679,6 +699,7 @@ export default function DateControl< Item >( { onChange={ onChange } hideLabelFromVision={ hideLabelFromVision } validity={ validity } + config={ config } /> ); } diff --git a/packages/dataviews/src/dataform-controls/datetime.tsx b/packages/dataviews/src/dataform-controls/datetime.tsx index 74723ecd9f1d80..4edc83b5eb0848 100644 --- a/packages/dataviews/src/dataform-controls/datetime.tsx +++ b/packages/dataviews/src/dataform-controls/datetime.tsx @@ -44,8 +44,10 @@ function CalendarDateTimeControl< Item >( { onChange, hideLabelFromVision, validity, + config, }: DataFormControlProps< Item > ) { const { id, label, description, setValue, getValue, isValid } = field; + const { disabled = false } = config || {}; const fieldValue = getValue( { item: data } ); const value = typeof fieldValue === 'string' ? fieldValue : undefined; @@ -167,17 +169,21 @@ function CalendarDateTimeControl< Item >( { > { /* Calendar widget */ } - + { ! disabled && ( + + ) } { /* Manual datetime input */ } ( { : '' } onChange={ handleManualDateTimeChange } + disabled={ disabled } /> @@ -208,6 +215,7 @@ export default function DateTime< Item >( { hideLabelFromVision, operator, validity, + config, }: DataFormControlProps< Item > ) { if ( operator === OPERATOR_IN_THE_PAST || operator === OPERATOR_OVER ) { return ( @@ -218,6 +226,7 @@ export default function DateTime< Item >( { onChange={ onChange } hideLabelFromVision={ hideLabelFromVision } operator={ operator } + config={ config } /> ); } @@ -229,6 +238,7 @@ export default function DateTime< Item >( { onChange={ onChange } hideLabelFromVision={ hideLabelFromVision } validity={ validity } + config={ config } /> ); } diff --git a/packages/dataviews/src/dataform-controls/email.tsx b/packages/dataviews/src/dataform-controls/email.tsx index 7c9a86f0b17d3c..403ed35f4f37f6 100644 --- a/packages/dataviews/src/dataform-controls/email.tsx +++ b/packages/dataviews/src/dataform-controls/email.tsx @@ -19,7 +19,9 @@ export default function Email< Item >( { onChange, hideLabelFromVision, validity, + config, }: DataFormControlProps< Item > ) { + const { disabled = false } = config || {}; return ( ( { onChange, hideLabelFromVision, validity, + disabled, type: 'email', prefix: ( diff --git a/packages/dataviews/src/dataform-controls/integer.tsx b/packages/dataviews/src/dataform-controls/integer.tsx index 8106a0c6ea5cb2..f4877ff0ec56a5 100644 --- a/packages/dataviews/src/dataform-controls/integer.tsx +++ b/packages/dataviews/src/dataform-controls/integer.tsx @@ -5,5 +5,7 @@ import type { DataFormControlProps } from '../types'; import ValidatedNumber from './utils/validated-number'; export default function Number< Item >( props: DataFormControlProps< Item > ) { - return ; + return ( + + ); } diff --git a/packages/dataviews/src/dataform-controls/number.tsx b/packages/dataviews/src/dataform-controls/number.tsx index 000debb16c32e7..13f3c0afaeb6fb 100644 --- a/packages/dataviews/src/dataform-controls/number.tsx +++ b/packages/dataviews/src/dataform-controls/number.tsx @@ -6,5 +6,11 @@ import ValidatedNumber from './utils/validated-number'; export default function Number< Item >( props: DataFormControlProps< Item > ) { const decimals = ( props.field.format as FormatNumber )?.decimals ?? 2; - return ; + return ( + + ); } diff --git a/packages/dataviews/src/dataform-controls/password.tsx b/packages/dataviews/src/dataform-controls/password.tsx index f31079ef7efe2b..47c1fc9be8efa6 100644 --- a/packages/dataviews/src/dataform-controls/password.tsx +++ b/packages/dataviews/src/dataform-controls/password.tsx @@ -20,8 +20,10 @@ export default function Password< Item >( { onChange, hideLabelFromVision, validity, + config, }: DataFormControlProps< Item > ) { const [ isVisible, setIsVisible ] = useState( false ); + const { disabled = false } = config || {}; const toggleVisibility = useCallback( () => { setIsVisible( ( prev ) => ! prev ); @@ -35,6 +37,7 @@ export default function Password< Item >( { onChange, hideLabelFromVision, validity, + disabled, type: isVisible ? 'text' : 'password', suffix: ( diff --git a/packages/dataviews/src/dataform-controls/radio.tsx b/packages/dataviews/src/dataform-controls/radio.tsx index 909851894b715f..1bade77f667321 100644 --- a/packages/dataviews/src/dataform-controls/radio.tsx +++ b/packages/dataviews/src/dataform-controls/radio.tsx @@ -20,8 +20,10 @@ export default function Radio< Item >( { onChange, hideLabelFromVision, validity, + config, }: DataFormControlProps< Item > ) { const { label, description, getValue, setValue, isValid } = field; + const { disabled = false } = config || {}; const { elements, isLoading } = useElements( { elements: field.elements, getElements: field.getElements, @@ -48,6 +50,7 @@ export default function Radio< Item >( { options={ elements } selected={ value } hideLabelFromVision={ hideLabelFromVision } + disabled={ disabled } /> ); } diff --git a/packages/dataviews/src/dataform-controls/select.tsx b/packages/dataviews/src/dataform-controls/select.tsx index 055cb0162516a8..f569c1c7488b0f 100644 --- a/packages/dataviews/src/dataform-controls/select.tsx +++ b/packages/dataviews/src/dataform-controls/select.tsx @@ -20,8 +20,10 @@ export default function Select< Item >( { onChange, hideLabelFromVision, validity, + config, }: DataFormControlProps< Item > ) { const { type, label, description, getValue, setValue, isValid } = field; + const { disabled = false } = config || {}; const isMultiple = type === 'array'; const value = getValue( { item: data } ) ?? ( isMultiple ? [] : '' ); @@ -54,6 +56,7 @@ export default function Select< Item >( { __nextHasNoMarginBottom hideLabelFromVision={ hideLabelFromVision } multiple={ isMultiple } + disabled={ disabled } /> ); } diff --git a/packages/dataviews/src/dataform-controls/telephone.tsx b/packages/dataviews/src/dataform-controls/telephone.tsx index 749f183631c830..13c73ef7f6f930 100644 --- a/packages/dataviews/src/dataform-controls/telephone.tsx +++ b/packages/dataviews/src/dataform-controls/telephone.tsx @@ -19,7 +19,9 @@ export default function Telephone< Item >( { onChange, hideLabelFromVision, validity, + config, }: DataFormControlProps< Item > ) { + const { disabled = false } = config || {}; return ( ( { onChange, hideLabelFromVision, validity, + disabled, type: 'tel', prefix: ( diff --git a/packages/dataviews/src/dataform-controls/text.tsx b/packages/dataviews/src/dataform-controls/text.tsx index 4115a1f125c6e3..56bd6d52a2b92f 100644 --- a/packages/dataviews/src/dataform-controls/text.tsx +++ b/packages/dataviews/src/dataform-controls/text.tsx @@ -17,7 +17,7 @@ export default function Text< Item >( { config, validity, }: DataFormControlProps< Item > ) { - const { prefix, suffix } = config || {}; + const { prefix, suffix, disabled = false } = config || {}; return ( ( { onChange, hideLabelFromVision, validity, + disabled, prefix: prefix ? createElement( prefix ) : undefined, suffix: suffix ? createElement( suffix ) : undefined, } } diff --git a/packages/dataviews/src/dataform-controls/textarea.tsx b/packages/dataviews/src/dataform-controls/textarea.tsx index 21b6d614d98afd..b1489a026e7195 100644 --- a/packages/dataviews/src/dataform-controls/textarea.tsx +++ b/packages/dataviews/src/dataform-controls/textarea.tsx @@ -21,7 +21,7 @@ export default function Textarea< Item >( { config, validity, }: DataFormControlProps< Item > ) { - const { rows = 4 } = config || {}; + const { rows = 4, disabled = false } = config || {}; const { label, placeholder, description, setValue, isValid } = field; const value = field.getValue( { item: data } ); @@ -41,6 +41,7 @@ export default function Textarea< Item >( { help={ description } onChange={ onChangeControl } rows={ rows } + disabled={ disabled } minLength={ isValid.minLength ? isValid.minLength.constraint : undefined } diff --git a/packages/dataviews/src/dataform-controls/toggle-group.tsx b/packages/dataviews/src/dataform-controls/toggle-group.tsx index e6390943984d68..d62ef61b78b3cd 100644 --- a/packages/dataviews/src/dataform-controls/toggle-group.tsx +++ b/packages/dataviews/src/dataform-controls/toggle-group.tsx @@ -24,8 +24,10 @@ export default function ToggleGroup< Item >( { onChange, hideLabelFromVision, validity, + config, }: DataFormControlProps< Item > ) { const { getValue, setValue, isValid } = field; + const { disabled = false } = config || {}; const value = getValue( { item: data } ); const onChangeControl = useCallback( @@ -65,6 +67,7 @@ export default function ToggleGroup< Item >( { key={ el.value } label={ el.label } value={ el.value } + disabled={ disabled } /> ) ) } diff --git a/packages/dataviews/src/dataform-controls/toggle.tsx b/packages/dataviews/src/dataform-controls/toggle.tsx index 4cfa65515693c1..74b1c1aa17a80f 100644 --- a/packages/dataviews/src/dataform-controls/toggle.tsx +++ b/packages/dataviews/src/dataform-controls/toggle.tsx @@ -19,8 +19,10 @@ export default function Toggle< Item >( { data, hideLabelFromVision, validity, + config, }: DataFormControlProps< Item > ) { const { label, description, getValue, setValue, isValid } = field; + const { disabled = false } = config || {}; const onChangeControl = useCallback( () => { onChange( @@ -38,6 +40,7 @@ export default function Toggle< Item >( { help={ description } checked={ getValue( { item: data } ) } onChange={ onChangeControl } + disabled={ disabled } /> ); } diff --git a/packages/dataviews/src/dataform-controls/url.tsx b/packages/dataviews/src/dataform-controls/url.tsx index d8ab624ebeb489..cda4ba01ee9821 100644 --- a/packages/dataviews/src/dataform-controls/url.tsx +++ b/packages/dataviews/src/dataform-controls/url.tsx @@ -19,7 +19,9 @@ export default function Url< Item >( { onChange, hideLabelFromVision, validity, + config, }: DataFormControlProps< Item > ) { + const { disabled = false } = config || {}; return ( ( { onChange, hideLabelFromVision, validity, + disabled, type: 'url', prefix: ( diff --git a/packages/dataviews/src/dataform-controls/utils/relative-date-control.tsx b/packages/dataviews/src/dataform-controls/utils/relative-date-control.tsx index f2aa74c623b0d0..964d57a309537f 100644 --- a/packages/dataviews/src/dataform-controls/utils/relative-date-control.tsx +++ b/packages/dataviews/src/dataform-controls/utils/relative-date-control.tsx @@ -50,6 +50,7 @@ export default function RelativeDateControl< Item >( { onChange, hideLabelFromVision, operator, + config, }: DataFormControlProps< Item > & { className: string; } ) { @@ -59,6 +60,7 @@ export default function RelativeDateControl< Item >( { ]; const { id, label, getValue, setValue } = field; + const { disabled = false } = config || {}; const fieldValue = getValue( { item: data } ); const { value: relValue = '', unit = options[ 0 ].value } = fieldValue && typeof fieldValue === 'object' ? fieldValue : {}; @@ -102,6 +104,7 @@ export default function RelativeDateControl< Item >( { step={ 1 } value={ relValue } onChange={ onChangeValue } + disabled={ disabled } /> ( { options={ options } onChange={ onChangeUnit } hideLabelFromVision + disabled={ disabled } /> diff --git a/packages/dataviews/src/dataform-controls/utils/validated-input.tsx b/packages/dataviews/src/dataform-controls/utils/validated-input.tsx index 160d7be35d9234..48fbf68aafe949 100644 --- a/packages/dataviews/src/dataform-controls/utils/validated-input.tsx +++ b/packages/dataviews/src/dataform-controls/utils/validated-input.tsx @@ -27,6 +27,10 @@ export type DataFormValidatedTextControlProps< Item > = * Optional suffix element to display after the input. */ suffix?: React.ReactElement; + /** + * Whether the control is disabled. + */ + disabled?: boolean; }; export default function ValidatedText< Item >( { @@ -38,6 +42,7 @@ export default function ValidatedText< Item >( { prefix, suffix, validity, + disabled, }: DataFormValidatedTextControlProps< Item > ) { const { label, placeholder, description, getValue, setValue, isValid } = field; @@ -67,6 +72,7 @@ export default function ValidatedText< Item >( { type={ type } prefix={ prefix } suffix={ suffix } + disabled={ disabled } pattern={ isValid.pattern ? isValid.pattern.constraint : undefined } minLength={ isValid.minLength ? isValid.minLength.constraint : undefined diff --git a/packages/dataviews/src/dataform-controls/utils/validated-number.tsx b/packages/dataviews/src/dataform-controls/utils/validated-number.tsx index 6f291cb9e493ad..2deb0486c7b3c2 100644 --- a/packages/dataviews/src/dataform-controls/utils/validated-number.tsx +++ b/packages/dataviews/src/dataform-controls/utils/validated-number.tsx @@ -100,10 +100,12 @@ export default function ValidatedNumber< Item >( { operator, decimals, validity, + config, }: DataFormValidatedNumberControlProps< Item > ) { const step = Math.pow( 10, Math.abs( decimals ) * -1 ); const { label, description, getValue, setValue, isValid } = field; const value = getValue( { item: data } ) ?? ''; + const { disabled = false } = config || {}; const onChangeControl = useCallback( ( newValue: string | undefined ) => { @@ -168,6 +170,7 @@ export default function ValidatedNumber< Item >( { step={ step } min={ isValid.min ? isValid.min.constraint : undefined } max={ isValid.max ? isValid.max.constraint : undefined } + disabled={ disabled } /> ); } From 0aad89e99a15fc3f3eff2b27f820d01667dd710e Mon Sep 17 00:00:00 2001 From: Vrishabhsk Date: Mon, 29 Dec 2025 16:23:10 +0400 Subject: [PATCH 3/7] Update dataform storybook --- .../dataviews/src/stories/dataform.story.tsx | 282 +++++++++++++++++- 1 file changed, 281 insertions(+), 1 deletion(-) diff --git a/packages/dataviews/src/stories/dataform.story.tsx b/packages/dataviews/src/stories/dataform.story.tsx index 748327d0f570bf..b5b42b45429c61 100644 --- a/packages/dataviews/src/stories/dataform.story.tsx +++ b/packages/dataviews/src/stories/dataform.story.tsx @@ -65,6 +65,10 @@ const fields: Field< SamplePost >[] = [ id: 'title', label: 'Title', type: 'text', + Edit: { + control: 'text', + disabled: true, + }, }, { id: 'order', @@ -163,6 +167,10 @@ const fields: Field< SamplePost >[] = [ id: 'tags', label: 'Tags', type: 'array', + Edit: { + control: 'array', + disabled: true, + }, placeholder: 'Enter comma-separated tags', description: 'Add tags separated by commas (e.g., "tag1, tag2, tag3")', elements: [ @@ -289,7 +297,7 @@ const LayoutRegularComponent = ( { labelPosition: 'default' | 'top' | 'side' | 'none'; } ) => { const [ post, setPost ] = useState( { - title: 'Hello, World!', + title: 'Test', order: 2, author: 1, status: 'draft', @@ -2456,3 +2464,275 @@ const DataAdapterComponent = () => { export const DataAdapter = { render: DataAdapterComponent, }; + +const DisabledFieldsComponent = () => { + type DisabledDemoItem = { + textField: string; + textareaField: string; + selectField: string; + radioField: string; + toggleField: boolean; + toggleGroupField: string; + checkboxField: boolean; + integerField: number; + numberField: number; + emailField: string; + passwordField: string; + urlField: string; + telephoneField: string; + colorField: string; + arrayField: string[]; + dateField: string; + dateTimeField: string; + }; + + const [ data, setData ] = useState< DisabledDemoItem >( { + textField: 'This is a disabled text field', + textareaField: 'This is a disabled textarea', + selectField: 'option2', + radioField: 'radio2', + toggleField: true, + toggleGroupField: 'toggle2', + checkboxField: true, + integerField: 42, + numberField: 3.14, + emailField: 'disabled@example.com', + passwordField: 'secret123', + urlField: 'https://example.com', + telephoneField: '+1234567890', + colorField: '#ff6600', + arrayField: [ 'tag1', 'tag2' ], + dateField: '2024-01-15', + dateTimeField: '2024-01-15T10:00:00', + } ); + + const disabledFields: Field< DisabledDemoItem >[] = [ + { + id: 'textField', + label: 'Disabled Text Field', + type: 'text', + Edit: { + control: 'text', + disabled: true, + }, + }, + { + id: 'textareaField', + label: 'Disabled Textarea', + type: 'text', + Edit: { + control: 'textarea', + disabled: true, + rows: 3, + }, + }, + { + id: 'selectField', + label: 'Disabled Select', + type: 'text', + Edit: { + control: 'select', + disabled: true, + }, + elements: [ + { value: 'option1', label: 'Option 1' }, + { value: 'option2', label: 'Option 2' }, + { value: 'option3', label: 'Option 3' }, + ], + }, + { + id: 'radioField', + label: 'Disabled Radio', + type: 'text', + Edit: { + control: 'radio', + disabled: true, + }, + elements: [ + { value: 'radio1', label: 'Radio 1' }, + { value: 'radio2', label: 'Radio 2' }, + { value: 'radio3', label: 'Radio 3' }, + ], + }, + { + id: 'toggleField', + label: 'Disabled Toggle', + type: 'boolean', + Edit: { + control: 'toggle', + disabled: true, + }, + }, + { + id: 'toggleGroupField', + label: 'Disabled Toggle Group', + type: 'text', + Edit: { + control: 'toggleGroup', + disabled: true, + }, + elements: [ + { value: 'toggle1', label: 'Toggle 1' }, + { value: 'toggle2', label: 'Toggle 2' }, + { value: 'toggle3', label: 'Toggle 3' }, + ], + }, + { + id: 'checkboxField', + label: 'Disabled Checkbox', + type: 'boolean', + Edit: { + control: 'checkbox', + disabled: true, + }, + }, + { + id: 'integerField', + label: 'Disabled Integer', + type: 'integer', + Edit: { + control: 'integer', + disabled: true, + }, + }, + { + id: 'numberField', + label: 'Disabled Number', + type: 'number', + Edit: { + control: 'number', + disabled: true, + }, + }, + { + id: 'emailField', + label: 'Disabled Email', + type: 'email', + Edit: { + control: 'email', + disabled: true, + }, + }, + { + id: 'passwordField', + label: 'Disabled Password', + type: 'password', + Edit: { + control: 'password', + disabled: true, + }, + }, + { + id: 'urlField', + label: 'Disabled URL', + type: 'url', + Edit: { + control: 'url', + disabled: true, + }, + }, + { + id: 'telephoneField', + label: 'Disabled Telephone', + type: 'telephone', + Edit: { + control: 'telephone', + disabled: true, + }, + }, + { + id: 'colorField', + label: 'Disabled Color', + type: 'color', + Edit: { + control: 'color', + disabled: true, + }, + }, + { + id: 'arrayField', + label: 'Disabled Array', + type: 'array', + Edit: { + control: 'array', + disabled: true, + }, + elements: [ + { value: 'tag1', label: 'Tag 1' }, + { value: 'tag2', label: 'Tag 2' }, + { value: 'tag3', label: 'Tag 3' }, + { value: 'tag4', label: 'Tag 4' }, + ], + }, + { + id: 'dateField', + label: 'Disabled Date', + type: 'date', + Edit: { + control: 'date', + disabled: true, + }, + }, + { + id: 'dateTimeField', + label: 'Disabled DateTime', + type: 'datetime', + Edit: { + control: 'datetime', + disabled: true, + }, + }, + ]; + + const form: Form = useMemo( + () => ( { + layout: { type: 'regular', labelPosition: 'top' }, + fields: [ + 'textField', + 'textareaField', + 'selectField', + 'radioField', + 'toggleField', + 'toggleGroupField', + 'checkboxField', + 'integerField', + 'numberField', + 'emailField', + 'passwordField', + 'urlField', + 'telephoneField', + 'colorField', + 'arrayField', + 'dateField', + 'dateTimeField', + ], + } ), + [] + ); + + return ( + <> +

Disabled Fields Demo

+

+ This story demonstrates the `disabled` config option for all + dataform controls. All fields below are disabled and cannot be + edited. +

+ + data={ data } + fields={ disabledFields } + form={ form } + onChange={ ( edits ) => + setData( ( prev ) => ( { + ...prev, + ...edits, + } ) ) + } + /> + + ); +}; + +export const DisabledFields = { + render: DisabledFieldsComponent, +}; From bb10b21f4f2dcf4760b009e94fd9ecd8a3ccb101 Mon Sep 17 00:00:00 2001 From: Vrishabhsk Date: Wed, 7 Jan 2026 17:59:06 +0400 Subject: [PATCH 4/7] Add disabled control for direct manipulation --- .../src/dataform/stories/disabled.tsx | 280 ------------------ .../src/dataform/stories/index.story.tsx | 12 +- .../src/dataform/stories/layout-regular.tsx | 45 ++- 3 files changed, 51 insertions(+), 286 deletions(-) delete mode 100644 packages/dataviews/src/dataform/stories/disabled.tsx diff --git a/packages/dataviews/src/dataform/stories/disabled.tsx b/packages/dataviews/src/dataform/stories/disabled.tsx deleted file mode 100644 index 3b83e14ddf06c8..00000000000000 --- a/packages/dataviews/src/dataform/stories/disabled.tsx +++ /dev/null @@ -1,280 +0,0 @@ -/** - * WordPress dependencies - */ -import { useMemo, useState } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import DataForm from '../index'; -import type { Field, Form } from '../../types'; - -type DisabledDemoItem = { - textField: string; - textareaField: string; - selectField: string; - radioField: string; - toggleField: boolean; - toggleGroupField: string; - checkboxField: boolean; - integerField: number; - numberField: number; - emailField: string; - passwordField: string; - urlField: string; - telephoneField: string; - colorField: string; - arrayField: string[]; - dateField: string; - dateTimeField: string; -}; - -const DisabledFieldsComponent = () => { - const [ data, setData ] = useState< DisabledDemoItem >( { - textField: 'This is a disabled text field', - textareaField: 'This is a disabled textarea', - selectField: 'option2', - radioField: 'radio2', - toggleField: true, - toggleGroupField: 'toggle2', - checkboxField: true, - integerField: 42, - numberField: 3.14, - emailField: 'disabled@example.com', - passwordField: 'secret123', - urlField: 'https://example.com', - telephoneField: '+1234567890', - colorField: '#ff6600', - arrayField: [ 'tag1', 'tag2' ], - dateField: '2024-01-15', - dateTimeField: '2024-01-15T10:00:00', - } ); - - const disabledFields: Field< DisabledDemoItem >[] = [ - { - id: 'textField', - label: 'Disabled Text Field', - type: 'text', - Edit: { - control: 'text', - disabled: true, - }, - }, - { - id: 'textareaField', - label: 'Disabled Textarea', - type: 'text', - Edit: { - control: 'textarea', - disabled: true, - rows: 3, - }, - }, - { - id: 'selectField', - label: 'Disabled Select', - type: 'text', - Edit: { - control: 'select', - disabled: true, - }, - elements: [ - { value: 'option1', label: 'Option 1' }, - { value: 'option2', label: 'Option 2' }, - { value: 'option3', label: 'Option 3' }, - ], - }, - { - id: 'radioField', - label: 'Disabled Radio', - type: 'text', - Edit: { - control: 'radio', - disabled: true, - }, - elements: [ - { value: 'radio1', label: 'Radio 1' }, - { value: 'radio2', label: 'Radio 2' }, - { value: 'radio3', label: 'Radio 3' }, - ], - }, - { - id: 'toggleField', - label: 'Disabled Toggle', - type: 'boolean', - Edit: { - control: 'toggle', - disabled: true, - }, - }, - { - id: 'toggleGroupField', - label: 'Disabled Toggle Group', - type: 'text', - Edit: { - control: 'toggleGroup', - disabled: true, - }, - elements: [ - { value: 'toggle1', label: 'Toggle 1' }, - { value: 'toggle2', label: 'Toggle 2' }, - { value: 'toggle3', label: 'Toggle 3' }, - ], - }, - { - id: 'checkboxField', - label: 'Disabled Checkbox', - type: 'boolean', - Edit: { - control: 'checkbox', - disabled: true, - }, - }, - { - id: 'integerField', - label: 'Disabled Integer', - type: 'integer', - Edit: { - control: 'integer', - disabled: true, - }, - }, - { - id: 'numberField', - label: 'Disabled Number', - type: 'number', - Edit: { - control: 'number', - disabled: true, - }, - }, - { - id: 'emailField', - label: 'Disabled Email', - type: 'email', - Edit: { - control: 'email', - disabled: true, - }, - }, - { - id: 'passwordField', - label: 'Disabled Password', - type: 'password', - Edit: { - control: 'password', - disabled: true, - }, - }, - { - id: 'urlField', - label: 'Disabled URL', - type: 'url', - Edit: { - control: 'url', - disabled: true, - }, - }, - { - id: 'telephoneField', - label: 'Disabled Telephone', - type: 'telephone', - Edit: { - control: 'telephone', - disabled: true, - }, - }, - { - id: 'colorField', - label: 'Disabled Color', - type: 'color', - Edit: { - control: 'color', - disabled: true, - }, - }, - { - id: 'arrayField', - label: 'Disabled Array', - type: 'array', - Edit: { - control: 'array', - disabled: true, - }, - elements: [ - { value: 'tag1', label: 'Tag 1' }, - { value: 'tag2', label: 'Tag 2' }, - { value: 'tag3', label: 'Tag 3' }, - { value: 'tag4', label: 'Tag 4' }, - ], - }, - { - id: 'dateField', - label: 'Disabled Date', - type: 'date', - Edit: { - control: 'date', - disabled: true, - }, - }, - { - id: 'dateTimeField', - label: 'Disabled DateTime', - type: 'datetime', - Edit: { - control: 'datetime', - disabled: true, - }, - }, - ]; - - const form: Form = useMemo( - () => ( { - layout: { type: 'regular', labelPosition: 'top' }, - fields: [ - 'textField', - 'textareaField', - 'selectField', - 'radioField', - 'toggleField', - 'toggleGroupField', - 'checkboxField', - 'integerField', - 'numberField', - 'emailField', - 'passwordField', - 'urlField', - 'telephoneField', - 'colorField', - 'arrayField', - 'dateField', - 'dateTimeField', - ], - } ), - [] - ); - - return ( - <> -

Disabled Fields Demo

-

- This story demonstrates the `disabled` config option for all - dataform controls. All fields below are disabled and cannot be - edited. -

- - data={ data } - fields={ disabledFields } - form={ form } - onChange={ ( edits ) => - setData( ( prev ) => ( { - ...prev, - ...edits, - } ) ) - } - /> - - ); -}; - -export default DisabledFieldsComponent; diff --git a/packages/dataviews/src/dataform/stories/index.story.tsx b/packages/dataviews/src/dataform/stories/index.story.tsx index e639c6e5900fd1..94b9a08f35c7d0 100644 --- a/packages/dataviews/src/dataform/stories/index.story.tsx +++ b/packages/dataviews/src/dataform/stories/index.story.tsx @@ -11,7 +11,6 @@ import LayoutPanelComponent from './layout-panel'; import DataAdapterComponent from './data-adapter'; import ValidationComponent from './validation'; import VisibilityComponent from './visibility'; -import DisabledFieldsComponent from './disabled'; const meta = { title: 'DataViews/DataForm', @@ -74,6 +73,13 @@ export const LayoutRegular = { description: 'Chooses the label position.', options: [ 'default', 'top', 'side', 'none' ], }, + disabled: { + control: { type: 'boolean' }, + description: 'Disable all fields in the form.', + }, + }, + args: { + disabled: false, }, }; @@ -147,7 +153,3 @@ export const Visibility = { export const DataAdapter = { render: DataAdapterComponent, }; - -export const DisabledFields = { - render: DisabledFieldsComponent, -}; diff --git a/packages/dataviews/src/dataform/stories/layout-regular.tsx b/packages/dataviews/src/dataform/stories/layout-regular.tsx index 4c6a1b8a646f40..d5baeaae8e2098 100644 --- a/packages/dataviews/src/dataform/stories/layout-regular.tsx +++ b/packages/dataviews/src/dataform/stories/layout-regular.tsx @@ -312,8 +312,10 @@ const getLayoutFromStoryArgs = ( { const LayoutRegularComponent = ( { labelPosition, + disabled = false, }: { labelPosition: 'default' | 'top' | 'side' | 'none'; + disabled?: boolean; } ) => { const [ post, setPost ] = useState( { title: 'Hello, World!', @@ -332,6 +334,47 @@ const LayoutRegularComponent = ( { description: 'This is a sample description.', } ); + // Create modified fields with disabled config. + const modifiedFields: Field< SamplePost >[] = useMemo( () => { + if ( ! disabled ) { + return fields; + } + + return fields.map( ( field ) => { + // Add disabled config to the field + const editConfig = field.Edit; + let newEdit: any; + + if ( typeof editConfig === 'string' ) { + newEdit = { + control: editConfig as any, + disabled: true, + }; + } else if ( + typeof editConfig === 'object' && + editConfig !== null + ) { + newEdit = { + ...editConfig, + disabled: true, + }; + } else { + // No Edit config - infer control type based on field properties + // Fields with elements default to select, otherwise use the field type + const controlType = field.elements ? 'select' : field.type; + newEdit = { + control: controlType as any, + disabled: true, + }; + } + + return { + ...field, + Edit: newEdit, + } as Field< SamplePost >; + } ); + }, [ disabled ] ); + const form: Form = useMemo( () => ( { layout: getLayoutFromStoryArgs( { @@ -363,7 +406,7 @@ const LayoutRegularComponent = ( { return ( data={ post } - fields={ fields } + fields={ modifiedFields } form={ form } onChange={ ( edits ) => setPost( ( prev ) => ( { From 6fb11f19858b27f6b6ab0d05b99c8403257f40c1 Mon Sep 17 00:00:00 2001 From: Vrishabhsk Date: Wed, 7 Jan 2026 17:59:36 +0400 Subject: [PATCH 5/7] Prevent filters from ever being disabled --- .../components/dataviews-filters/input-widget.tsx | 15 +++++++++++++++ .../src/field-types/stories/index.story.tsx | 4 ++++ 2 files changed, 19 insertions(+) diff --git a/packages/dataviews/src/components/dataviews-filters/input-widget.tsx b/packages/dataviews/src/components/dataviews-filters/input-widget.tsx index 65ed2a4685c880..5f16f809242dea 100644 --- a/packages/dataviews/src/components/dataviews-filters/input-widget.tsx +++ b/packages/dataviews/src/components/dataviews-filters/input-widget.tsx @@ -20,6 +20,7 @@ import type { NormalizedRules, } from '../../types'; import { getCurrentValue } from './utils'; +import { getControlByType } from '../dataform-controls'; interface UserInputWidgetProps { view: View; @@ -60,8 +61,22 @@ export default function InputWidget( { const field = useMemo( () => { const currentField = fields.find( ( f ) => f.id === filter.field ); if ( currentField ) { + // For filters, we need to ensure the Edit control is never disabled. + // We use the base control type based on the field type or elements. + let FilterEdit = currentField.Edit; + + // If field has elements and isn't an array, use select control + // Otherwise use the control for the field type + if ( currentField.hasElements && currentField.type !== 'array' ) { + FilterEdit = getControlByType( 'select' ); + } else if ( currentField.type ) { + FilterEdit = + getControlByType( currentField.type ) || currentField.Edit; + } + return { ...currentField, + Edit: FilterEdit, // Deactivate validation for filters. isValid: {} satisfies NormalizedRules< any >, // Configure getValue/setValue as if Item was a plain object. diff --git a/packages/dataviews/src/field-types/stories/index.story.tsx b/packages/dataviews/src/field-types/stories/index.story.tsx index 1bb36f820cf0fd..5d9c88e6804c5d 100644 --- a/packages/dataviews/src/field-types/stories/index.story.tsx +++ b/packages/dataviews/src/field-types/stories/index.story.tsx @@ -174,6 +174,10 @@ const fields: Field< DataType >[] = [ type: 'text', label: 'Text', description: 'Help for text.', + Edit: { + control: 'text', + disabled: true, + }, }, { id: 'textWithElements', From f80dbf29e518b04fadcc123906dcdbd27a8f50bb Mon Sep 17 00:00:00 2001 From: Vrishabhsk Date: Wed, 7 Jan 2026 18:39:29 +0400 Subject: [PATCH 6/7] Fix duplicate import --- packages/block-editor/src/hooks/utils.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/block-editor/src/hooks/utils.js b/packages/block-editor/src/hooks/utils.js index 4aa9070fb29c72..464642b5bf7554 100644 --- a/packages/block-editor/src/hooks/utils.js +++ b/packages/block-editor/src/hooks/utils.js @@ -12,11 +12,6 @@ import { useDispatch, useRegistry } from '@wordpress/data'; import { createHigherOrderComponent } from '@wordpress/compose'; import { addFilter } from '@wordpress/hooks'; -/** - * External dependencies - */ -import clsx from 'clsx'; - /** * Internal dependencies */ From 49772ce4a4b281d80223622fbf6de3f0ed214374 Mon Sep 17 00:00:00 2001 From: Vrishabhsk Date: Thu, 15 Jan 2026 19:09:32 +0400 Subject: [PATCH 7/7] Implement sustainable logic --- .../dataviews-filters/input-widget.tsx | 17 +++------ packages/dataviews/src/field-types/index.tsx | 35 +++++++++++++++++++ packages/dataviews/src/types/field-api.ts | 1 + 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/packages/dataviews/src/components/dataviews-filters/input-widget.tsx b/packages/dataviews/src/components/dataviews-filters/input-widget.tsx index 5f16f809242dea..03157d0113ac80 100644 --- a/packages/dataviews/src/components/dataviews-filters/input-widget.tsx +++ b/packages/dataviews/src/components/dataviews-filters/input-widget.tsx @@ -20,7 +20,6 @@ import type { NormalizedRules, } from '../../types'; import { getCurrentValue } from './utils'; -import { getControlByType } from '../dataform-controls'; interface UserInputWidgetProps { view: View; @@ -61,18 +60,10 @@ export default function InputWidget( { const field = useMemo( () => { const currentField = fields.find( ( f ) => f.id === filter.field ); if ( currentField ) { - // For filters, we need to ensure the Edit control is never disabled. - // We use the base control type based on the field type or elements. - let FilterEdit = currentField.Edit; - - // If field has elements and isn't an array, use select control - // Otherwise use the control for the field type - if ( currentField.hasElements && currentField.type !== 'array' ) { - FilterEdit = getControlByType( 'select' ); - } else if ( currentField.type ) { - FilterEdit = - getControlByType( currentField.type ) || currentField.Edit; - } + // In filter mode we reuse the field edit control, but without allowing + // EditConfig.disabled to make the filter UI non-interactive. + // NormalizedField.filter is created at normalization time for that purpose. + const FilterEdit = currentField.filter ?? currentField.Edit; return { ...currentField, diff --git a/packages/dataviews/src/field-types/index.tsx b/packages/dataviews/src/field-types/index.tsx index f967ccf036e35f..81c79a5a932f38 100644 --- a/packages/dataviews/src/field-types/index.tsx +++ b/packages/dataviews/src/field-types/index.tsx @@ -4,6 +4,7 @@ import type { Field, FieldTypeName, + EditConfig, NormalizedField, SortDirection, } from '../types'; @@ -62,6 +63,39 @@ function getFieldTypeByName< Item >( type?: FieldTypeName ): FieldType< Item > { return noType; } +function isEditConfig( value: unknown ): value is EditConfig { + return ( + !! value && + typeof value === 'object' && + typeof ( value as EditConfig ).control === 'string' + ); +} + +function getFilterControl< Item >( + field: Field< Item >, + fallback: string | null +) { + if ( ! isEditConfig( field.Edit ) ) { + return getControl( field, fallback ); + } + + // Filters should never be disabled via EditConfig. + // Remove the disabled flag before creating the configured control component. + // Note: getControl() will turn EditConfig into a component that closes over config. + // If we don't strip it here, we can't override it later in filter UI. + const filterConfig = { ...field.Edit } as EditConfig & { + disabled?: boolean; + }; + delete filterConfig.disabled; + return getControl( + { + ...field, + Edit: filterConfig as EditConfig, + }, + fallback + ); +} + /** * Apply default values and normalize the fields config. * @@ -101,6 +135,7 @@ export default function normalizeFields< Item >( type: fieldType.type, render: field.render ?? fieldType.render, Edit: getControl( field, fieldType.Edit ), + filter: getFilterControl( field, fieldType.Edit ), sort, enableSorting: field.enableSorting ?? fieldType.enableSorting, enableGlobalSearch: diff --git a/packages/dataviews/src/types/field-api.ts b/packages/dataviews/src/types/field-api.ts index 0b9909aa764d27..1a10b6fe649ddb 100644 --- a/packages/dataviews/src/types/field-api.ts +++ b/packages/dataviews/src/types/field-api.ts @@ -366,6 +366,7 @@ export type NormalizedField< Item > = Omit< setValue: ( args: { item: Item; value: any } ) => DeepPartial< Item >; render: ComponentType< DataViewRenderFieldProps< Item > >; Edit: ComponentType< DataFormControlProps< Item > > | null; + filter: ComponentType< DataFormControlProps< Item > > | null; hasElements: boolean; sort: ( a: Item, b: Item, direction: SortDirection ) => number; isValid: NormalizedRules< Item >;