@@ -4,23 +4,26 @@ import type * as t from '@/types';
44import { AddItemButton , TrashButton } from '@/components/shared' ;
55import { useLocalize } from '@/hooks' ;
66
7- const VALUE_TYPES : t . KVValueType [ ] = [ 'string' , 'number' , 'boolean' ] ;
7+ const DEFAULT_TYPES : t . KVValueType [ ] = [ 'string' , 'number' , 'boolean' ] ;
88const TYPE_LABELS : Record < t . KVValueType , string > = {
99 string : 'abc' ,
1010 number : '123' ,
1111 boolean : 'T/F' ,
12+ json : '{ }' ,
1213} ;
1314
1415export function KeyValueField ( {
1516 id,
1617 pairs,
1718 onChange,
1819 disabled,
20+ valueTypes,
1921 keyPlaceholder,
2022 valuePlaceholder,
2123 'aria-label' : ariaLabel ,
2224} : t . KeyValueFieldProps ) {
2325 const localize = useLocalize ( ) ;
26+ const availableTypes = valueTypes ?? DEFAULT_TYPES ;
2427 const listRef = useRef < HTMLDivElement > ( null ) ;
2528 const focusLastKeyRef = useRef ( false ) ;
2629
@@ -34,7 +37,7 @@ export function KeyValueField({
3437 } ) ;
3538
3639 const handleAdd = ( ) => {
37- onChange ( [ ...pairs , { key : '' , value : '' , valueType : 'string' } ] ) ;
40+ onChange ( [ ...pairs , { key : '' , value : '' , valueType : availableTypes [ 0 ] } ] ) ;
3841 focusLastKeyRef . current = true ;
3942 } ;
4043 const handleRemove = ( index : number ) => onChange ( pairs . filter ( ( _ , i ) => i !== index ) ) ;
@@ -49,11 +52,56 @@ export function KeyValueField({
4952 let coerced = pair . value ;
5053 if ( newType === 'boolean' ) {
5154 coerced = pair . value === 'true' || pair . value === '1' ? 'true' : 'false' ;
55+ } else if ( newType === 'json' && pair . valueType !== 'json' ) {
56+ coerced = pair . value || '{}' ;
5257 }
5358 next [ index ] = { ...pair , value : coerced , valueType : newType } ;
5459 onChange ( next ) ;
5560 } ;
5661
62+ const renderValueInput = ( vType : t . KVValueType , pair : t . KeyValuePair , index : number ) => {
63+ const valueLabel = `${ localize ( 'com_ui_value' ) } ${ index + 1 } ` ;
64+ if ( vType === 'boolean' ) {
65+ return (
66+ < div className = "select-field-a11y flex-2" >
67+ < Select
68+ value = { pair . value === 'true' ? 'true' : 'false' }
69+ onSelect = { ( v ) => handleChange ( index , 'value' , v ) }
70+ disabled = { disabled }
71+ aria-label = { valueLabel }
72+ >
73+ < Select . Item value = "true" > true</ Select . Item >
74+ < Select . Item value = "false" > false</ Select . Item >
75+ </ Select >
76+ </ div >
77+ ) ;
78+ }
79+ if ( vType === 'json' ) {
80+ return (
81+ < textarea
82+ value = { pair . value }
83+ onChange = { ( e ) => handleChange ( index , 'value' , e . target . value ) }
84+ placeholder = '{"key": "value"}'
85+ disabled = { disabled }
86+ aria-label = { valueLabel }
87+ rows = { 2 }
88+ className = "config-input flex-2 resize-y font-mono text-xs"
89+ />
90+ ) ;
91+ }
92+ return (
93+ < input
94+ type = { vType === 'number' ? 'number' : 'text' }
95+ value = { pair . value }
96+ onChange = { ( e ) => handleChange ( index , 'value' , e . target . value ) }
97+ placeholder = { valuePlaceholder ?? localize ( 'com_ui_value' ) }
98+ disabled = { disabled }
99+ aria-label = { valueLabel }
100+ className = "config-input flex-2"
101+ />
102+ ) ;
103+ } ;
104+
57105 return (
58106 < div
59107 ref = { listRef }
@@ -75,37 +123,15 @@ export function KeyValueField({
75123 aria-label = { `${ localize ( 'com_ui_key' ) } ${ index + 1 } ` }
76124 className = "config-input max-w-37.5 flex-1"
77125 />
78- { vType === 'boolean' ? (
79- < div className = "select-field-a11y flex-2" >
80- < Select
81- value = { pair . value === 'true' ? 'true' : 'false' }
82- onSelect = { ( v ) => handleChange ( index , 'value' , v ) }
83- disabled = { disabled }
84- aria-label = { `${ localize ( 'com_ui_value' ) } ${ index + 1 } ` }
85- >
86- < Select . Item value = "true" > true</ Select . Item >
87- < Select . Item value = "false" > false</ Select . Item >
88- </ Select >
89- </ div >
90- ) : (
91- < input
92- type = { vType === 'number' ? 'number' : 'text' }
93- value = { pair . value }
94- onChange = { ( e ) => handleChange ( index , 'value' , e . target . value ) }
95- placeholder = { valuePlaceholder ?? localize ( 'com_ui_value' ) }
96- disabled = { disabled }
97- aria-label = { `${ localize ( 'com_ui_value' ) } ${ index + 1 } ` }
98- className = "config-input flex-2"
99- />
100- ) }
101- { ! disabled && (
126+ { renderValueInput ( vType , pair , index ) }
127+ { ! disabled && availableTypes . length > 1 && (
102128 < div className = "select-field-a11y w-20 shrink-0" >
103129 < Select
104130 value = { vType }
105131 onSelect = { ( v ) => handleTypeChange ( index , v as t . KVValueType ) }
106132 aria-label = { `${ localize ( 'com_config_field_type' ) } ${ index + 1 } ` }
107133 >
108- { VALUE_TYPES . map ( ( vt ) => (
134+ { availableTypes . map ( ( vt ) => (
109135 < Select . Item key = { vt } value = { vt } >
110136 { TYPE_LABELS [ vt ] }
111137 </ Select . Item >
0 commit comments