@seed-ship/mcp-ui-solid 2.5.3 → 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -6
- package/dist/components/ChatPrompt.cjs +16 -75
- package/dist/components/ChatPrompt.cjs.map +1 -1
- package/dist/components/ChatPrompt.d.ts.map +1 -1
- package/dist/components/ChatPrompt.js +16 -75
- package/dist/components/ChatPrompt.js.map +1 -1
- package/dist/components/FormFieldRenderer.cjs +232 -3
- package/dist/components/FormFieldRenderer.cjs.map +1 -1
- package/dist/components/FormFieldRenderer.d.ts.map +1 -1
- package/dist/components/FormFieldRenderer.js +233 -4
- package/dist/components/FormFieldRenderer.js.map +1 -1
- package/dist/types/chat-bus.d.ts +21 -1
- package/dist/types/chat-bus.d.ts.map +1 -1
- package/dist/types/index.d.ts +17 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types.d.cts +17 -1
- package/dist/types.d.ts +17 -1
- package/package.json +1 -1
- package/src/components/ChatPrompt.tsx +19 -47
- package/src/components/FormFieldRenderer.tsx +218 -3
- package/src/types/chat-bus.ts +21 -1
- package/src/types/index.ts +19 -0
- package/tsconfig.tsbuildinfo +1 -1
package/dist/types.d.cts
CHANGED
|
@@ -217,7 +217,7 @@ export interface FormFieldOption {
|
|
|
217
217
|
/**
|
|
218
218
|
* Form field type
|
|
219
219
|
*/
|
|
220
|
-
export type FormFieldType = 'text' | 'email' | 'password' | 'number' | 'date' | 'textarea' | 'select' | 'checkbox' | 'radio';
|
|
220
|
+
export type FormFieldType = 'text' | 'email' | 'password' | 'number' | 'date' | 'textarea' | 'select' | 'checkbox' | 'radio' | 'autocomplete';
|
|
221
221
|
/**
|
|
222
222
|
* Operators for conditional field display
|
|
223
223
|
*/
|
|
@@ -251,6 +251,22 @@ export interface FormFieldParams {
|
|
|
251
251
|
minDate?: string;
|
|
252
252
|
maxDate?: string;
|
|
253
253
|
options?: FormFieldOption[];
|
|
254
|
+
/** Enable multi-select (returns array of values) */
|
|
255
|
+
multiple?: boolean;
|
|
256
|
+
/** API URL for autocomplete suggestions */
|
|
257
|
+
apiUrl?: string;
|
|
258
|
+
/** Query parameter name (e.g. 'nom', 'q') */
|
|
259
|
+
searchParam?: string;
|
|
260
|
+
/** Field in API response to display */
|
|
261
|
+
labelField?: string;
|
|
262
|
+
/** Field in API response to use as value */
|
|
263
|
+
valueField?: string;
|
|
264
|
+
/** Extra query parameters */
|
|
265
|
+
extraParams?: Record<string, string>;
|
|
266
|
+
/** Min characters before triggering (default: 2) */
|
|
267
|
+
minChars?: number;
|
|
268
|
+
/** Debounce delay in ms (default: 300) */
|
|
269
|
+
debounceMs?: number;
|
|
254
270
|
checkboxLabel?: string;
|
|
255
271
|
rows?: number;
|
|
256
272
|
showWhen?: ShowWhenCondition;
|
package/dist/types.d.ts
CHANGED
|
@@ -217,7 +217,7 @@ export interface FormFieldOption {
|
|
|
217
217
|
/**
|
|
218
218
|
* Form field type
|
|
219
219
|
*/
|
|
220
|
-
export type FormFieldType = 'text' | 'email' | 'password' | 'number' | 'date' | 'textarea' | 'select' | 'checkbox' | 'radio';
|
|
220
|
+
export type FormFieldType = 'text' | 'email' | 'password' | 'number' | 'date' | 'textarea' | 'select' | 'checkbox' | 'radio' | 'autocomplete';
|
|
221
221
|
/**
|
|
222
222
|
* Operators for conditional field display
|
|
223
223
|
*/
|
|
@@ -251,6 +251,22 @@ export interface FormFieldParams {
|
|
|
251
251
|
minDate?: string;
|
|
252
252
|
maxDate?: string;
|
|
253
253
|
options?: FormFieldOption[];
|
|
254
|
+
/** Enable multi-select (returns array of values) */
|
|
255
|
+
multiple?: boolean;
|
|
256
|
+
/** API URL for autocomplete suggestions */
|
|
257
|
+
apiUrl?: string;
|
|
258
|
+
/** Query parameter name (e.g. 'nom', 'q') */
|
|
259
|
+
searchParam?: string;
|
|
260
|
+
/** Field in API response to display */
|
|
261
|
+
labelField?: string;
|
|
262
|
+
/** Field in API response to use as value */
|
|
263
|
+
valueField?: string;
|
|
264
|
+
/** Extra query parameters */
|
|
265
|
+
extraParams?: Record<string, string>;
|
|
266
|
+
/** Min characters before triggering (default: 2) */
|
|
267
|
+
minChars?: number;
|
|
268
|
+
/** Debounce delay in ms (default: 300) */
|
|
269
|
+
debounceMs?: number;
|
|
254
270
|
checkboxLabel?: string;
|
|
255
271
|
rows?: number;
|
|
256
272
|
showWhen?: ShowWhenCondition;
|
package/package.json
CHANGED
|
@@ -16,6 +16,8 @@ import type {
|
|
|
16
16
|
ConfirmPromptConfig,
|
|
17
17
|
FormPromptConfig,
|
|
18
18
|
} from '../types/chat-bus'
|
|
19
|
+
import { FormFieldRenderer } from './FormFieldRenderer'
|
|
20
|
+
import type { FormFieldParams } from '../types'
|
|
19
21
|
|
|
20
22
|
export interface ChatPromptProps {
|
|
21
23
|
/** Prompt configuration */
|
|
@@ -185,25 +187,24 @@ const ConfirmBody: Component<{
|
|
|
185
187
|
)
|
|
186
188
|
}
|
|
187
189
|
|
|
188
|
-
// ─── Form
|
|
190
|
+
// ─── Form (delegates to FormFieldRenderer for all field types) ───
|
|
189
191
|
|
|
190
192
|
const FormBody: Component<{
|
|
191
193
|
config: FormPromptConfig
|
|
192
194
|
onSubmit: (data: Record<string, unknown>, label: string) => void
|
|
193
195
|
}> = (props) => {
|
|
194
|
-
const [formData, setFormData] = createSignal<Record<string,
|
|
196
|
+
const [formData, setFormData] = createSignal<Record<string, any>>({})
|
|
195
197
|
|
|
196
|
-
const updateField = (name: string, value:
|
|
198
|
+
const updateField = (name: string, value: any) => {
|
|
197
199
|
setFormData((prev) => ({ ...prev, [name]: value }))
|
|
198
200
|
}
|
|
199
201
|
|
|
200
202
|
const handleSubmit = (e: Event) => {
|
|
201
203
|
e.preventDefault()
|
|
202
204
|
const data = formData()
|
|
203
|
-
// Build a human-readable label from the form values
|
|
204
205
|
const label = Object.entries(data)
|
|
205
|
-
.filter(([, v]) => v)
|
|
206
|
-
.map(([k, v]) => `${k}: ${v}`)
|
|
206
|
+
.filter(([, v]) => v !== undefined && v !== '' && !(Array.isArray(v) && v.length === 0))
|
|
207
|
+
.map(([k, v]) => `${k}: ${Array.isArray(v) ? v.join(', ') : v}`)
|
|
207
208
|
.join(', ')
|
|
208
209
|
props.onSubmit(data, label || 'Form submitted')
|
|
209
210
|
}
|
|
@@ -212,53 +213,24 @@ const FormBody: Component<{
|
|
|
212
213
|
const data = formData()
|
|
213
214
|
return (props.config.fields || [])
|
|
214
215
|
.filter((f) => f.required)
|
|
215
|
-
.every((f) =>
|
|
216
|
+
.every((f) => {
|
|
217
|
+
const val = data[f.name]
|
|
218
|
+
if (Array.isArray(val)) return val.length > 0
|
|
219
|
+
if (typeof val === 'boolean') return true
|
|
220
|
+
return val !== undefined && val !== ''
|
|
221
|
+
})
|
|
216
222
|
}
|
|
217
223
|
|
|
218
224
|
return (
|
|
219
225
|
<form onSubmit={handleSubmit} class="flex flex-col gap-3">
|
|
220
226
|
<For each={props.config.fields}>
|
|
221
227
|
{(field) => (
|
|
222
|
-
<
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
</label>
|
|
229
|
-
<Switch>
|
|
230
|
-
<Match when={field.type === 'textarea'}>
|
|
231
|
-
<textarea
|
|
232
|
-
value={formData()[field.name] || ''}
|
|
233
|
-
onInput={(e) => updateField(field.name, e.currentTarget.value)}
|
|
234
|
-
placeholder={field.placeholder}
|
|
235
|
-
rows={3}
|
|
236
|
-
class="w-full px-3 py-2 text-sm rounded-lg border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:border-blue-400 focus:ring-1 focus:ring-blue-400 outline-none transition-colors"
|
|
237
|
-
/>
|
|
238
|
-
</Match>
|
|
239
|
-
<Match when={field.type === 'select'}>
|
|
240
|
-
<select
|
|
241
|
-
value={formData()[field.name] || ''}
|
|
242
|
-
onChange={(e) => updateField(field.name, e.currentTarget.value)}
|
|
243
|
-
class="w-full px-3 py-2 text-sm rounded-lg border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:border-blue-400 focus:ring-1 focus:ring-blue-400 outline-none transition-colors"
|
|
244
|
-
>
|
|
245
|
-
<option value="">{field.placeholder || 'Select...'}</option>
|
|
246
|
-
<For each={field.options}>
|
|
247
|
-
{(opt) => <option value={opt.value}>{opt.label}</option>}
|
|
248
|
-
</For>
|
|
249
|
-
</select>
|
|
250
|
-
</Match>
|
|
251
|
-
<Match when={true}>
|
|
252
|
-
<input
|
|
253
|
-
type={field.type === 'number' ? 'number' : 'text'}
|
|
254
|
-
value={formData()[field.name] || ''}
|
|
255
|
-
onInput={(e) => updateField(field.name, e.currentTarget.value)}
|
|
256
|
-
placeholder={field.placeholder}
|
|
257
|
-
class="w-full px-3 py-2 text-sm rounded-lg border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:border-blue-400 focus:ring-1 focus:ring-blue-400 outline-none transition-colors"
|
|
258
|
-
/>
|
|
259
|
-
</Match>
|
|
260
|
-
</Switch>
|
|
261
|
-
</div>
|
|
228
|
+
<FormFieldRenderer
|
|
229
|
+
field={field as FormFieldParams}
|
|
230
|
+
value={formData()[field.name]}
|
|
231
|
+
onChange={(val) => updateField(field.name, val)}
|
|
232
|
+
formData={formData}
|
|
233
|
+
/>
|
|
262
234
|
)}
|
|
263
235
|
</For>
|
|
264
236
|
<div class="flex justify-end">
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Sprint 2: Conditional field visibility (showWhen)
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { Component, Show, For, Switch, Match, Accessor } from 'solid-js'
|
|
7
|
+
import { Component, Show, For, Switch, Match, Accessor, createSignal, createEffect, onCleanup } from 'solid-js'
|
|
8
8
|
import type { FormFieldParams } from '../types'
|
|
9
9
|
import { useConditionalField } from '../hooks/useConditionalField'
|
|
10
10
|
|
|
@@ -133,8 +133,8 @@ export const FormFieldRenderer: Component<FormFieldRendererProps> = (props) => {
|
|
|
133
133
|
/>
|
|
134
134
|
</Match>
|
|
135
135
|
|
|
136
|
-
{/* Select */}
|
|
137
|
-
<Match when={props.field.type === 'select'}>
|
|
136
|
+
{/* Select (single) */}
|
|
137
|
+
<Match when={props.field.type === 'select' && !props.field.multiple}>
|
|
138
138
|
<select
|
|
139
139
|
id={fieldId()}
|
|
140
140
|
name={props.field.name}
|
|
@@ -182,6 +182,28 @@ export const FormFieldRenderer: Component<FormFieldRendererProps> = (props) => {
|
|
|
182
182
|
</label>
|
|
183
183
|
</Match>
|
|
184
184
|
|
|
185
|
+
{/* Multi-Select with chips */}
|
|
186
|
+
<Match when={props.field.type === 'select' && props.field.multiple}>
|
|
187
|
+
<MultiSelectField
|
|
188
|
+
field={props.field}
|
|
189
|
+
value={props.value || []}
|
|
190
|
+
onChange={props.onChange}
|
|
191
|
+
disabled={props.disabled}
|
|
192
|
+
baseClass={baseInputClass()}
|
|
193
|
+
/>
|
|
194
|
+
</Match>
|
|
195
|
+
|
|
196
|
+
{/* Autocomplete with API fetch */}
|
|
197
|
+
<Match when={props.field.type === 'autocomplete'}>
|
|
198
|
+
<AutocompleteField
|
|
199
|
+
field={props.field}
|
|
200
|
+
value={props.value || ''}
|
|
201
|
+
onChange={props.onChange}
|
|
202
|
+
disabled={props.disabled}
|
|
203
|
+
baseClass={baseInputClass()}
|
|
204
|
+
/>
|
|
205
|
+
</Match>
|
|
206
|
+
|
|
185
207
|
{/* Radio Group */}
|
|
186
208
|
<Match when={props.field.type === 'radio'}>
|
|
187
209
|
<div
|
|
@@ -227,3 +249,196 @@ export const FormFieldRenderer: Component<FormFieldRendererProps> = (props) => {
|
|
|
227
249
|
</Show>
|
|
228
250
|
)
|
|
229
251
|
}
|
|
252
|
+
|
|
253
|
+
// ─── Multi-Select with Chips ─────────────────────────────────
|
|
254
|
+
|
|
255
|
+
const MultiSelectField: Component<{
|
|
256
|
+
field: FormFieldParams
|
|
257
|
+
value: string[]
|
|
258
|
+
onChange: (value: string[]) => void
|
|
259
|
+
disabled?: boolean
|
|
260
|
+
baseClass: string
|
|
261
|
+
}> = (props) => {
|
|
262
|
+
const [open, setOpen] = createSignal(false)
|
|
263
|
+
|
|
264
|
+
const toggle = (val: string) => {
|
|
265
|
+
const current = props.value || []
|
|
266
|
+
if (current.includes(val)) {
|
|
267
|
+
props.onChange(current.filter((v) => v !== val))
|
|
268
|
+
} else {
|
|
269
|
+
props.onChange([...current, val])
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const removeChip = (val: string) => {
|
|
274
|
+
props.onChange((props.value || []).filter((v) => v !== val))
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const getLabel = (val: string) =>
|
|
278
|
+
props.field.options?.find((o) => o.value === val)?.label || val
|
|
279
|
+
|
|
280
|
+
return (
|
|
281
|
+
<div class="relative">
|
|
282
|
+
{/* Selected chips */}
|
|
283
|
+
<Show when={props.value.length > 0}>
|
|
284
|
+
<div class="flex flex-wrap gap-1 mb-1">
|
|
285
|
+
<For each={props.value}>
|
|
286
|
+
{(val) => (
|
|
287
|
+
<span class="inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded-full">
|
|
288
|
+
{getLabel(val)}
|
|
289
|
+
<button
|
|
290
|
+
type="button"
|
|
291
|
+
onClick={() => removeChip(val)}
|
|
292
|
+
class="hover:text-blue-900 dark:hover:text-blue-100"
|
|
293
|
+
aria-label={`Remove ${getLabel(val)}`}
|
|
294
|
+
>
|
|
295
|
+
×
|
|
296
|
+
</button>
|
|
297
|
+
</span>
|
|
298
|
+
)}
|
|
299
|
+
</For>
|
|
300
|
+
</div>
|
|
301
|
+
</Show>
|
|
302
|
+
|
|
303
|
+
{/* Trigger button */}
|
|
304
|
+
<button
|
|
305
|
+
type="button"
|
|
306
|
+
onClick={() => setOpen(!open())}
|
|
307
|
+
disabled={props.disabled}
|
|
308
|
+
class={`${props.baseClass} text-left flex items-center justify-between`}
|
|
309
|
+
>
|
|
310
|
+
<span class={props.value.length ? 'text-gray-900 dark:text-white' : 'text-gray-400'}>
|
|
311
|
+
{props.value.length
|
|
312
|
+
? `${props.value.length} selected`
|
|
313
|
+
: props.field.placeholder || 'Select...'}
|
|
314
|
+
</span>
|
|
315
|
+
<svg class="w-4 h-4 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
316
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
|
317
|
+
</svg>
|
|
318
|
+
</button>
|
|
319
|
+
|
|
320
|
+
{/* Dropdown */}
|
|
321
|
+
<Show when={open()}>
|
|
322
|
+
<div class="absolute z-20 mt-1 w-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-md shadow-lg max-h-48 overflow-auto">
|
|
323
|
+
<For each={props.field.options}>
|
|
324
|
+
{(option) => (
|
|
325
|
+
<label class="flex items-center gap-2 px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer text-sm">
|
|
326
|
+
<input
|
|
327
|
+
type="checkbox"
|
|
328
|
+
checked={(props.value || []).includes(option.value)}
|
|
329
|
+
onChange={() => toggle(option.value)}
|
|
330
|
+
class="w-4 h-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600"
|
|
331
|
+
/>
|
|
332
|
+
<span class="text-gray-900 dark:text-white">{option.label}</span>
|
|
333
|
+
</label>
|
|
334
|
+
)}
|
|
335
|
+
</For>
|
|
336
|
+
</div>
|
|
337
|
+
</Show>
|
|
338
|
+
</div>
|
|
339
|
+
)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ─── Autocomplete with API fetch ─────────────────────────────
|
|
343
|
+
|
|
344
|
+
const AutocompleteField: Component<{
|
|
345
|
+
field: FormFieldParams
|
|
346
|
+
value: string
|
|
347
|
+
onChange: (value: string) => void
|
|
348
|
+
disabled?: boolean
|
|
349
|
+
baseClass: string
|
|
350
|
+
}> = (props) => {
|
|
351
|
+
const [query, setQuery] = createSignal('')
|
|
352
|
+
const [suggestions, setSuggestions] = createSignal<Array<{ label: string; value: string }>>([])
|
|
353
|
+
const [isOpen, setIsOpen] = createSignal(false)
|
|
354
|
+
const [selectedLabel, setSelectedLabel] = createSignal('')
|
|
355
|
+
let debounceTimer: ReturnType<typeof setTimeout> | null = null
|
|
356
|
+
|
|
357
|
+
const minChars = () => props.field.minChars ?? 2
|
|
358
|
+
const debounceMs = () => props.field.debounceMs ?? 300
|
|
359
|
+
|
|
360
|
+
const fetchSuggestions = async (q: string) => {
|
|
361
|
+
if (!props.field.apiUrl || !props.field.searchParam) return
|
|
362
|
+
|
|
363
|
+
try {
|
|
364
|
+
const params = new URLSearchParams({ [props.field.searchParam]: q })
|
|
365
|
+
if (props.field.extraParams) {
|
|
366
|
+
for (const [k, v] of Object.entries(props.field.extraParams)) {
|
|
367
|
+
params.set(k, v)
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
const res = await fetch(`${props.field.apiUrl}?${params}`)
|
|
371
|
+
if (!res.ok) return
|
|
372
|
+
|
|
373
|
+
const data = await res.json()
|
|
374
|
+
const items = Array.isArray(data) ? data : data.results || data.features || []
|
|
375
|
+
const labelField = props.field.labelField || 'label'
|
|
376
|
+
const valueField = props.field.valueField || 'value'
|
|
377
|
+
|
|
378
|
+
setSuggestions(items.slice(0, 10).map((item: any) => ({
|
|
379
|
+
label: item[labelField] || String(item),
|
|
380
|
+
value: String(item[valueField] || item[labelField] || item),
|
|
381
|
+
})))
|
|
382
|
+
setIsOpen(true)
|
|
383
|
+
} catch {
|
|
384
|
+
setSuggestions([])
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const handleInput = (value: string) => {
|
|
389
|
+
setQuery(value)
|
|
390
|
+
setSelectedLabel('')
|
|
391
|
+
props.onChange('')
|
|
392
|
+
|
|
393
|
+
if (debounceTimer) clearTimeout(debounceTimer)
|
|
394
|
+
if (value.length < minChars()) {
|
|
395
|
+
setSuggestions([])
|
|
396
|
+
setIsOpen(false)
|
|
397
|
+
return
|
|
398
|
+
}
|
|
399
|
+
debounceTimer = setTimeout(() => fetchSuggestions(value), debounceMs())
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const selectSuggestion = (item: { label: string; value: string }) => {
|
|
403
|
+
props.onChange(item.value)
|
|
404
|
+
setSelectedLabel(item.label)
|
|
405
|
+
setQuery(item.label)
|
|
406
|
+
setIsOpen(false)
|
|
407
|
+
setSuggestions([])
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
onCleanup(() => { if (debounceTimer) clearTimeout(debounceTimer) })
|
|
411
|
+
|
|
412
|
+
return (
|
|
413
|
+
<div class="relative">
|
|
414
|
+
<input
|
|
415
|
+
type="text"
|
|
416
|
+
value={query()}
|
|
417
|
+
onInput={(e) => handleInput(e.currentTarget.value)}
|
|
418
|
+
onFocus={() => { if (suggestions().length) setIsOpen(true) }}
|
|
419
|
+
onBlur={() => setTimeout(() => setIsOpen(false), 200)}
|
|
420
|
+
placeholder={props.field.placeholder}
|
|
421
|
+
disabled={props.disabled}
|
|
422
|
+
class={props.baseClass}
|
|
423
|
+
autocomplete="off"
|
|
424
|
+
/>
|
|
425
|
+
|
|
426
|
+
<Show when={isOpen() && suggestions().length > 0}>
|
|
427
|
+
<div class="absolute z-20 mt-1 w-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-md shadow-lg max-h-48 overflow-auto">
|
|
428
|
+
<For each={suggestions()}>
|
|
429
|
+
{(item) => (
|
|
430
|
+
<button
|
|
431
|
+
type="button"
|
|
432
|
+
onMouseDown={(e) => e.preventDefault()}
|
|
433
|
+
onClick={() => selectSuggestion(item)}
|
|
434
|
+
class="w-full text-left px-3 py-2 text-sm hover:bg-blue-50 dark:hover:bg-blue-900/20 text-gray-900 dark:text-white"
|
|
435
|
+
>
|
|
436
|
+
{item.label}
|
|
437
|
+
</button>
|
|
438
|
+
)}
|
|
439
|
+
</For>
|
|
440
|
+
</div>
|
|
441
|
+
</Show>
|
|
442
|
+
</div>
|
|
443
|
+
)
|
|
444
|
+
}
|
package/src/types/chat-bus.ts
CHANGED
|
@@ -197,10 +197,30 @@ export interface FormPromptConfig {
|
|
|
197
197
|
fields: Array<{
|
|
198
198
|
name: string
|
|
199
199
|
label: string
|
|
200
|
-
type: 'text' | 'number' | 'select' | 'textarea'
|
|
200
|
+
type: 'text' | 'number' | 'select' | 'textarea' | 'checkbox' | 'radio' | 'date' | 'email' | 'autocomplete'
|
|
201
201
|
required?: boolean
|
|
202
202
|
placeholder?: string
|
|
203
203
|
options?: Array<{ label: string; value: string }>
|
|
204
|
+
/** Enable multi-select (returns array) */
|
|
205
|
+
multiple?: boolean
|
|
206
|
+
/** Autocomplete: API URL */
|
|
207
|
+
apiUrl?: string
|
|
208
|
+
/** Autocomplete: query param name */
|
|
209
|
+
searchParam?: string
|
|
210
|
+
/** Autocomplete: field in response for display */
|
|
211
|
+
labelField?: string
|
|
212
|
+
/** Autocomplete: field in response for value */
|
|
213
|
+
valueField?: string
|
|
214
|
+
/** Autocomplete: extra query params */
|
|
215
|
+
extraParams?: Record<string, string>
|
|
216
|
+
/** Autocomplete: min chars before trigger (default: 2) */
|
|
217
|
+
minChars?: number
|
|
218
|
+
/** Autocomplete: debounce ms (default: 300) */
|
|
219
|
+
debounceMs?: number
|
|
220
|
+
/** Checkbox label text */
|
|
221
|
+
checkboxLabel?: string
|
|
222
|
+
/** Help text below field */
|
|
223
|
+
helpText?: string
|
|
204
224
|
}>
|
|
205
225
|
submitLabel?: string
|
|
206
226
|
}
|
package/src/types/index.ts
CHANGED
|
@@ -267,6 +267,7 @@ export type FormFieldType =
|
|
|
267
267
|
| 'select'
|
|
268
268
|
| 'checkbox'
|
|
269
269
|
| 'radio'
|
|
270
|
+
| 'autocomplete'
|
|
270
271
|
|
|
271
272
|
/**
|
|
272
273
|
* Operators for conditional field display
|
|
@@ -324,6 +325,24 @@ export interface FormFieldParams {
|
|
|
324
325
|
|
|
325
326
|
// Select/Radio specific
|
|
326
327
|
options?: FormFieldOption[]
|
|
328
|
+
/** Enable multi-select (returns array of values) */
|
|
329
|
+
multiple?: boolean
|
|
330
|
+
|
|
331
|
+
// Autocomplete specific
|
|
332
|
+
/** API URL for autocomplete suggestions */
|
|
333
|
+
apiUrl?: string
|
|
334
|
+
/** Query parameter name (e.g. 'nom', 'q') */
|
|
335
|
+
searchParam?: string
|
|
336
|
+
/** Field in API response to display */
|
|
337
|
+
labelField?: string
|
|
338
|
+
/** Field in API response to use as value */
|
|
339
|
+
valueField?: string
|
|
340
|
+
/** Extra query parameters */
|
|
341
|
+
extraParams?: Record<string, string>
|
|
342
|
+
/** Min characters before triggering (default: 2) */
|
|
343
|
+
minChars?: number
|
|
344
|
+
/** Debounce delay in ms (default: 300) */
|
|
345
|
+
debounceMs?: number
|
|
327
346
|
|
|
328
347
|
// Checkbox specific
|
|
329
348
|
checkboxLabel?: string
|