@seed-ship/mcp-ui-solid 4.1.0 → 4.2.0
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/dist/components/FormFieldRenderer.cjs +314 -264
- package/dist/components/FormFieldRenderer.cjs.map +1 -1
- package/dist/components/FormFieldRenderer.d.ts.map +1 -1
- package/dist/components/FormFieldRenderer.js +315 -265
- package/dist/components/FormFieldRenderer.js.map +1 -1
- package/dist/components/FormRenderer.cjs +74 -17
- package/dist/components/FormRenderer.cjs.map +1 -1
- package/dist/components/FormRenderer.d.ts.map +1 -1
- package/dist/components/FormRenderer.js +76 -19
- package/dist/components/FormRenderer.js.map +1 -1
- package/dist/types/index.d.ts +15 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types.d.cts +15 -0
- package/dist/types.d.ts +15 -0
- package/package.json +1 -1
- package/src/components/FormFieldRenderer.tsx +35 -4
- package/src/components/FormRenderer.tsx +74 -4
- package/src/types/index.ts +18 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Sprint 4: Form state persistence
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { Component, createSignal, For, Show, onMount, createEffect } from 'solid-js'
|
|
8
|
+
import { Component, createSignal, For, Show, onMount, createEffect, onCleanup } from 'solid-js'
|
|
9
9
|
import { FormFieldRenderer } from './FormFieldRenderer'
|
|
10
10
|
import type { UIComponent, FormComponentParams, FormFieldParams } from '../types'
|
|
11
11
|
import { useAction } from '../hooks/useAction'
|
|
@@ -56,11 +56,43 @@ export const FormRenderer: Component<FormRendererProps> = (props) => {
|
|
|
56
56
|
})
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
//
|
|
59
|
+
// Auto-submit countdown state (v4.2.0)
|
|
60
|
+
const [countdown, setCountdown] = createSignal<number | null>(null)
|
|
61
|
+
let countdownTimer: ReturnType<typeof setInterval> | null = null
|
|
62
|
+
const [userInteracted, setUserInteracted] = createSignal(false)
|
|
63
|
+
|
|
64
|
+
const cancelCountdown = () => {
|
|
65
|
+
if (countdownTimer) {
|
|
66
|
+
clearInterval(countdownTimer)
|
|
67
|
+
countdownTimer = null
|
|
68
|
+
}
|
|
69
|
+
setCountdown(null)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const handleUserInteraction = () => {
|
|
73
|
+
if (!userInteracted()) {
|
|
74
|
+
setUserInteracted(true)
|
|
75
|
+
cancelCountdown()
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
onCleanup(() => cancelCountdown())
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Check if all required fields have prefill values
|
|
83
|
+
*/
|
|
84
|
+
const allRequiredPrefilled = (): boolean => {
|
|
85
|
+
return params().fields
|
|
86
|
+
.filter((f) => f.required)
|
|
87
|
+
.every((f) => f.prefill != null)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Initialize form data with default values, applying prefill (v4.2.0)
|
|
60
91
|
const initializeForm = (clearStorage = false) => {
|
|
61
92
|
const initial: Record<string, any> = {}
|
|
62
93
|
for (const field of params().fields) {
|
|
63
|
-
|
|
94
|
+
// prefill takes priority over defaultValue
|
|
95
|
+
initial[field.name] = field.prefill ?? field.defaultValue ?? getFieldDefault(field.type)
|
|
64
96
|
}
|
|
65
97
|
setFormData(initial)
|
|
66
98
|
setErrors({})
|
|
@@ -91,7 +123,29 @@ export const FormRenderer: Component<FormRendererProps> = (props) => {
|
|
|
91
123
|
}
|
|
92
124
|
})
|
|
93
125
|
|
|
126
|
+
// Auto-submit countdown (v4.2.0)
|
|
127
|
+
createEffect(() => {
|
|
128
|
+
const delay = params().autoSubmitDelay
|
|
129
|
+
if (!delay || !allRequiredPrefilled() || userInteracted()) return
|
|
130
|
+
|
|
131
|
+
let remaining = Math.ceil(delay / 1000)
|
|
132
|
+
setCountdown(remaining)
|
|
133
|
+
|
|
134
|
+
countdownTimer = setInterval(() => {
|
|
135
|
+
remaining--
|
|
136
|
+
if (remaining <= 0) {
|
|
137
|
+
cancelCountdown()
|
|
138
|
+
// Trigger submit programmatically
|
|
139
|
+
const form = document.querySelector(`#form-${props.component.id}`) as HTMLFormElement | null
|
|
140
|
+
if (form) form.requestSubmit()
|
|
141
|
+
} else {
|
|
142
|
+
setCountdown(remaining)
|
|
143
|
+
}
|
|
144
|
+
}, 1000)
|
|
145
|
+
})
|
|
146
|
+
|
|
94
147
|
const handleFieldChange = (name: string, value: any) => {
|
|
148
|
+
handleUserInteraction()
|
|
95
149
|
setFormData((prev) => ({ ...prev, [name]: value }))
|
|
96
150
|
// Clear error on change
|
|
97
151
|
if (errors()[name]) {
|
|
@@ -179,7 +233,7 @@ export const FormRenderer: Component<FormRendererProps> = (props) => {
|
|
|
179
233
|
</h3>
|
|
180
234
|
</Show>
|
|
181
235
|
|
|
182
|
-
<form onSubmit={handleSubmit} noValidate>
|
|
236
|
+
<form id={`form-${props.component.id}`} onSubmit={handleSubmit} noValidate>
|
|
183
237
|
<div class={layoutClass()}>
|
|
184
238
|
<For each={params().fields}>
|
|
185
239
|
{(field) => (
|
|
@@ -203,6 +257,22 @@ export const FormRenderer: Component<FormRendererProps> = (props) => {
|
|
|
203
257
|
</div>
|
|
204
258
|
</Show>
|
|
205
259
|
|
|
260
|
+
{/* Auto-submit countdown (v4.2.0) */}
|
|
261
|
+
<Show when={countdown() != null}>
|
|
262
|
+
<div class="mt-4 flex items-center gap-3 p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md">
|
|
263
|
+
<span class="text-sm text-blue-700 dark:text-blue-300">
|
|
264
|
+
{params().submitLabel || 'Submit'} in {countdown()}s...
|
|
265
|
+
</span>
|
|
266
|
+
<button
|
|
267
|
+
type="button"
|
|
268
|
+
onClick={() => { cancelCountdown(); setUserInteracted(true) }}
|
|
269
|
+
class="text-sm text-blue-600 dark:text-blue-400 underline hover:text-blue-800 dark:hover:text-blue-200"
|
|
270
|
+
>
|
|
271
|
+
Cancel
|
|
272
|
+
</button>
|
|
273
|
+
</div>
|
|
274
|
+
</Show>
|
|
275
|
+
|
|
206
276
|
<div class="flex gap-2 pt-4 mt-4 border-t border-gray-200 dark:border-gray-700">
|
|
207
277
|
<button
|
|
208
278
|
type="submit"
|
package/src/types/index.ts
CHANGED
|
@@ -323,6 +323,9 @@ export interface ShowWhenCondition {
|
|
|
323
323
|
/**
|
|
324
324
|
* Form field parameters
|
|
325
325
|
*/
|
|
326
|
+
/** How a prefilled value was obtained */
|
|
327
|
+
export type PrefillSource = 'user' | 'detected' | 'inferred' | 'default'
|
|
328
|
+
|
|
326
329
|
export interface FormFieldParams {
|
|
327
330
|
name: string
|
|
328
331
|
type: FormFieldType
|
|
@@ -333,6 +336,16 @@ export interface FormFieldParams {
|
|
|
333
336
|
disabled?: boolean
|
|
334
337
|
defaultValue?: any
|
|
335
338
|
|
|
339
|
+
// Prefill — pre-populated value with source tracking (v4.2.0)
|
|
340
|
+
/** Pre-filled value. Field renders with this value instead of empty. */
|
|
341
|
+
prefill?: string | string[]
|
|
342
|
+
/** Human-readable display for prefilled value (e.g. "Rhône — déduit de Lyon") */
|
|
343
|
+
displayHint?: string
|
|
344
|
+
/** How this value was obtained. Drives visual treatment. */
|
|
345
|
+
source?: PrefillSource
|
|
346
|
+
/** If true, field is visually muted (but still editable on click/focus). */
|
|
347
|
+
muted?: boolean
|
|
348
|
+
|
|
336
349
|
// Text/textarea specific
|
|
337
350
|
minLength?: number
|
|
338
351
|
maxLength?: number
|
|
@@ -428,6 +441,11 @@ export interface FormComponentParams {
|
|
|
428
441
|
* Custom CSS class (Sprint 7)
|
|
429
442
|
*/
|
|
430
443
|
className?: string
|
|
444
|
+
/**
|
|
445
|
+
* Auto-submit countdown in ms when all required fields are prefilled (v4.2.0).
|
|
446
|
+
* Shows a countdown with cancel button. Stops if user interacts.
|
|
447
|
+
*/
|
|
448
|
+
autoSubmitDelay?: number
|
|
431
449
|
}
|
|
432
450
|
|
|
433
451
|
/**
|