@@ -32,15 +32,17 @@ import {
32
32
TooltipProvider ,
33
33
TooltipTrigger ,
34
34
} from "components/Tooltip/Tooltip" ;
35
+ import { useDebouncedValue } from "hooks/debounce" ;
36
+ import { useEffectEvent } from "hooks/hookPolyfills" ;
35
37
import { Info , LinkIcon , Settings , TriangleAlert } from "lucide-react" ;
36
- import { type FC , useId , useState } from "react" ;
38
+ import { type FC , useEffect , useId , useRef , useState } from "react" ;
37
39
import type { AutofillBuildParameter } from "utils/richParameters" ;
38
40
import * as Yup from "yup" ;
39
41
40
42
export interface DynamicParameterProps {
41
43
parameter : PreviewParameter ;
42
44
value ?: string ;
43
- onChange : ( value : string ) => void ;
45
+ onChange : ( value : string ) => Promise < void > ;
44
46
disabled ?: boolean ;
45
47
isPreset ?: boolean ;
46
48
autofill : boolean ;
@@ -68,13 +70,24 @@ export const DynamicParameter: FC<DynamicParameterProps> = ({
68
70
autofill = { autofill }
69
71
/>
70
72
< div className = "max-w-lg" >
71
- < ParameterField
72
- id = { id }
73
- parameter = { parameter }
74
- value = { value }
75
- onChange = { onChange }
76
- disabled = { disabled }
77
- />
73
+ { parameter . form_type === "input" ||
74
+ parameter . form_type === "textarea" ? (
75
+ < DebouncedParameterField
76
+ id = { id }
77
+ parameter = { parameter }
78
+ value = { value }
79
+ onChange = { onChange }
80
+ disabled = { disabled }
81
+ />
82
+ ) : (
83
+ < ParameterField
84
+ id = { id }
85
+ parameter = { parameter }
86
+ value = { value }
87
+ onChange = { onChange }
88
+ disabled = { disabled }
89
+ />
90
+ ) }
78
91
</ div >
79
92
{ parameter . diagnostics . length > 0 && (
80
93
< ParameterDiagnostics diagnostics = { parameter . diagnostics } />
@@ -187,15 +200,15 @@ const ParameterLabel: FC<ParameterLabelProps> = ({
187
200
) ;
188
201
} ;
189
202
190
- interface ParameterFieldProps {
203
+ interface DebouncedParameterFieldProps {
191
204
parameter : PreviewParameter ;
192
205
value ?: string ;
193
- onChange : ( value : string ) => void ;
206
+ onChange : ( value : string ) => Promise < void > ;
194
207
disabled ?: boolean ;
195
208
id : string ;
196
209
}
197
210
198
- const ParameterField : FC < ParameterFieldProps > = ( {
211
+ const DebouncedParameterField : FC < DebouncedParameterFieldProps > = ( {
199
212
parameter,
200
213
value,
201
214
onChange,
@@ -205,16 +218,96 @@ const ParameterField: FC<ParameterFieldProps> = ({
205
218
const [ localValue , setLocalValue ] = useState (
206
219
value !== undefined ? value : validValue ( parameter . value ) ,
207
220
) ;
208
- if ( value !== undefined && value !== localValue ) {
209
- setLocalValue ( value ) ;
221
+ const debouncedLocalValue = useDebouncedValue ( localValue , 500 ) ;
222
+ const onChangeEvent = useEffectEvent ( onChange ) ;
223
+ // prevDebouncedValueRef is to prevent calling the onChangeEvent on the initial render
224
+ const prevDebouncedValueRef = useRef < string | undefined > ( ) ;
225
+
226
+ useEffect ( ( ) => {
227
+ if ( prevDebouncedValueRef . current !== undefined ) {
228
+ onChangeEvent ( debouncedLocalValue ) ;
229
+ }
230
+
231
+ prevDebouncedValueRef . current = debouncedLocalValue ;
232
+ } , [ debouncedLocalValue , onChangeEvent ] ) ;
233
+
234
+ switch ( parameter . form_type ) {
235
+ case "textarea" :
236
+ return (
237
+ < Textarea
238
+ id = { id }
239
+ className = "max-w-2xl"
240
+ value = { localValue }
241
+ onChange = { ( e ) => {
242
+ setLocalValue ( e . target . value ) ;
243
+ } }
244
+ onInput = { ( e ) => {
245
+ const target = e . currentTarget ;
246
+ target . style . maxHeight = "700px" ;
247
+ target . style . height = `${ target . scrollHeight } px` ;
248
+ } }
249
+ disabled = { disabled }
250
+ placeholder = { parameter . styling ?. placeholder }
251
+ required = { parameter . required }
252
+ />
253
+ ) ;
254
+
255
+ case "input" : {
256
+ const inputType = parameter . type === "number" ? "number" : "text" ;
257
+ const inputProps : Record < string , unknown > = { } ;
258
+
259
+ if ( parameter . type === "number" ) {
260
+ const validations = parameter . validations [ 0 ] || { } ;
261
+ const { validation_min, validation_max } = validations ;
262
+
263
+ if ( validation_min !== null ) {
264
+ inputProps . min = validation_min ;
265
+ }
266
+
267
+ if ( validation_max !== null ) {
268
+ inputProps . max = validation_max ;
269
+ }
270
+ }
271
+
272
+ return (
273
+ < Input
274
+ id = { id }
275
+ type = { inputType }
276
+ value = { localValue }
277
+ onChange = { ( e ) => {
278
+ setLocalValue ( e . target . value ) ;
279
+ } }
280
+ disabled = { disabled }
281
+ required = { parameter . required }
282
+ placeholder = { parameter . styling ?. placeholder }
283
+ { ...inputProps }
284
+ />
285
+ ) ;
286
+ }
210
287
}
288
+ } ;
211
289
290
+ interface ParameterFieldProps {
291
+ parameter : PreviewParameter ;
292
+ value ?: string ;
293
+ onChange : ( value : string ) => Promise < void > ;
294
+ disabled ?: boolean ;
295
+ id : string ;
296
+ }
297
+
298
+ const ParameterField : FC < ParameterFieldProps > = ( {
299
+ parameter,
300
+ value,
301
+ onChange,
302
+ disabled,
303
+ id,
304
+ } ) => {
212
305
switch ( parameter . form_type ) {
213
306
case "dropdown" :
214
307
return (
215
308
< Select
216
309
onValueChange = { onChange }
217
- value = { localValue }
310
+ value = { value }
218
311
disabled = { disabled }
219
312
required = { parameter . required }
220
313
>
@@ -234,7 +327,15 @@ const ParameterField: FC<ParameterFieldProps> = ({
234
327
) ;
235
328
236
329
case "multi-select" : {
237
- const values = parseStringArrayValue ( localValue ?? "" ) ;
330
+ const parsedValues = parseStringArrayValue ( value ?? "" ) ;
331
+
332
+ if ( parsedValues . error ) {
333
+ return (
334
+ < p className = "text-sm text-content-destructive" >
335
+ { parsedValues . error }
336
+ </ p >
337
+ ) ;
338
+ }
238
339
239
340
// Map parameter options to MultiSelectCombobox options format
240
341
const options : Option [ ] = parameter . options . map ( ( opt ) => ( {
@@ -247,7 +348,7 @@ const ParameterField: FC<ParameterFieldProps> = ({
247
348
parameter . options . map ( ( opt ) => [ opt . value . value , opt . name ] ) ,
248
349
) ;
249
350
250
- const selectedOptions : Option [ ] = values . map ( ( val ) => {
351
+ const selectedOptions : Option [ ] = parsedValues . values . map ( ( val ) => {
251
352
return {
252
353
value : val ,
253
354
label : optionMap . get ( val ) || val ,
@@ -279,13 +380,21 @@ const ParameterField: FC<ParameterFieldProps> = ({
279
380
}
280
381
281
382
case "tag-select" : {
282
- const values = parseStringArrayValue ( localValue ?? "" ) ;
383
+ const parsedValues = parseStringArrayValue ( value ?? "" ) ;
384
+
385
+ if ( parsedValues . error ) {
386
+ return (
387
+ < p className = "text-sm text-content-destructive" >
388
+ { parsedValues . error }
389
+ </ p >
390
+ ) ;
391
+ }
283
392
284
393
return (
285
394
< TagInput
286
395
id = { id }
287
396
label = { parameter . display_name || parameter . name }
288
- values = { values }
397
+ values = { parsedValues . values }
289
398
onChange = { ( values ) => {
290
399
onChange ( JSON . stringify ( values ) ) ;
291
400
} }
@@ -297,7 +406,7 @@ const ParameterField: FC<ParameterFieldProps> = ({
297
406
return (
298
407
< Switch
299
408
id = { id }
300
- checked = { localValue === "true" }
409
+ checked = { value === "true" }
301
410
onCheckedChange = { ( checked ) => {
302
411
onChange ( checked ? "true" : "false" ) ;
303
412
} }
@@ -307,11 +416,7 @@ const ParameterField: FC<ParameterFieldProps> = ({
307
416
308
417
case "radio" :
309
418
return (
310
- < RadioGroup
311
- onValueChange = { onChange }
312
- disabled = { disabled }
313
- value = { localValue }
314
- >
419
+ < RadioGroup onValueChange = { onChange } disabled = { disabled } value = { value } >
315
420
{ parameter . options . map ( ( option ) => (
316
421
< div
317
422
key = { option . value . value }
@@ -337,7 +442,7 @@ const ParameterField: FC<ParameterFieldProps> = ({
337
442
< div className = "flex items-center space-x-2" >
338
443
< Checkbox
339
444
id = { id }
340
- checked = { localValue === "true" }
445
+ checked = { value === "true" }
341
446
onCheckedChange = { ( checked ) => {
342
447
onChange ( checked ? "true" : "false" ) ;
343
448
} }
@@ -353,9 +458,8 @@ const ParameterField: FC<ParameterFieldProps> = ({
353
458
< Slider
354
459
id = { id }
355
460
className = "mt-2"
356
- value = { [ Number ( localValue ) ] }
461
+ value = { [ Number ( value ) ] }
357
462
onValueChange = { ( [ value ] ) => {
358
- setLocalValue ( value . toString ( ) ) ;
359
463
onChange ( value . toString ( ) ) ;
360
464
} }
361
465
min = { parameter . validations [ 0 ] ?. validation_min ?? 0 }
@@ -365,79 +469,32 @@ const ParameterField: FC<ParameterFieldProps> = ({
365
469
< span className = "w-4 font-medium" > { parameter . value . value } </ span >
366
470
</ div >
367
471
) ;
368
-
369
- case "textarea" :
370
- return (
371
- < Textarea
372
- id = { id }
373
- className = "max-w-2xl"
374
- value = { localValue }
375
- onChange = { ( e ) => {
376
- setLocalValue ( e . target . value ) ;
377
- onChange ( e . target . value ) ;
378
- } }
379
- onInput = { ( e ) => {
380
- const target = e . currentTarget ;
381
- target . style . maxHeight = "700px" ;
382
- target . style . height = `${ target . scrollHeight } px` ;
383
- } }
384
- disabled = { disabled }
385
- placeholder = { parameter . styling ?. placeholder }
386
- required = { parameter . required }
387
- />
388
- ) ;
389
-
390
- case "input" : {
391
- const inputType = parameter . type === "number" ? "number" : "text" ;
392
- const inputProps : Record < string , unknown > = { } ;
393
-
394
- if ( parameter . type === "number" ) {
395
- const validations = parameter . validations [ 0 ] || { } ;
396
- const { validation_min, validation_max } = validations ;
397
-
398
- if ( validation_min !== null ) {
399
- inputProps . min = validation_min ;
400
- }
401
-
402
- if ( validation_max !== null ) {
403
- inputProps . max = validation_max ;
404
- }
405
- }
406
-
407
- return (
408
- < Input
409
- id = { id }
410
- type = { inputType }
411
- value = { localValue }
412
- onChange = { ( e ) => {
413
- setLocalValue ( e . target . value ) ;
414
- onChange ( e . target . value ) ;
415
- } }
416
- disabled = { disabled }
417
- required = { parameter . required }
418
- placeholder = { parameter . styling ?. placeholder }
419
- { ...inputProps }
420
- />
421
- ) ;
422
- }
423
472
}
424
473
} ;
425
474
426
- const parseStringArrayValue = ( value : string ) : string [ ] => {
427
- let values : string [ ] = [ ] ;
475
+ type ParsedValues = {
476
+ values : string [ ] ;
477
+ error : string ;
478
+ } ;
479
+
480
+ const parseStringArrayValue = ( value : string ) : ParsedValues => {
481
+ const parsedValues : ParsedValues = {
482
+ values : [ ] ,
483
+ error : "" ,
484
+ } ;
428
485
429
486
if ( value ) {
430
487
try {
431
488
const parsed = JSON . parse ( value ) ;
432
489
if ( Array . isArray ( parsed ) ) {
433
- values = parsed ;
490
+ parsedValues . values = parsed ;
434
491
}
435
492
} catch ( e ) {
436
- console . error ( " Error parsing parameter of type list(string)" , e ) ;
493
+ parsedValues . error = ` Error parsing parameter of type list(string), ${ e } ` ;
437
494
}
438
495
}
439
496
440
- return values ;
497
+ return parsedValues ;
441
498
} ;
442
499
443
500
interface OptionDisplayProps {
@@ -542,10 +599,6 @@ const isValidParameterOption = (
542
599
values = parsed ;
543
600
}
544
601
} catch ( e ) {
545
- console . error (
546
- "Error parsing parameter value with form_type multi-select" ,
547
- e ,
548
- ) ;
549
602
return false ;
550
603
}
551
604
0 commit comments