70 lines
2.3 KiB
TypeScript
70 lines
2.3 KiB
TypeScript
import { createStore } from 'zustand/vanilla'
|
|
|
|
export type BaseField = {
|
|
name: string
|
|
label: string
|
|
required?: boolean
|
|
pattern?: string
|
|
maxLength?: number
|
|
}
|
|
|
|
export type TextField = BaseField & { type: 'text' | 'email' | 'tel' | 'date' | 'textarea' }
|
|
export type CheckboxField = BaseField & { type: 'checkbox' }
|
|
export type RadioField = BaseField & { type: 'radio'; options: string[] }
|
|
export type SelectField = BaseField & { type: 'select'; options: string[] }
|
|
|
|
export type FormField = TextField | CheckboxField | RadioField | SelectField
|
|
|
|
export type FormFieldInput = FormField
|
|
|
|
export type FormState = {
|
|
fields: FormField[]
|
|
actionUrl: string
|
|
spam: { honeypotFieldName: string; minSubmitSeconds: number }
|
|
addField: (f: FormFieldInput) => void
|
|
moveField: (from: number, to: number) => void
|
|
removeField: (index: number) => void
|
|
updateField: (index: number, patch: Partial<FormField>) => void
|
|
setActionUrl: (url: string) => void
|
|
setSpam: (s: { honeypotFieldName?: string; minSubmitSeconds?: number }) => void
|
|
}
|
|
|
|
export function createFormStore() {
|
|
return createStore<FormState>((set) => ({
|
|
fields: [],
|
|
actionUrl: '',
|
|
spam: { honeypotFieldName: '_hp', minSubmitSeconds: 2 },
|
|
addField: (f) => set((st) => ({ fields: [...st.fields, f] })),
|
|
moveField: (from, to) =>
|
|
set((st) => {
|
|
const arr = [...st.fields]
|
|
if (from < 0 || from >= arr.length || to < 0 || to >= arr.length) return st
|
|
const [spliced] = arr.splice(from, 1)
|
|
arr.splice(to, 0, spliced)
|
|
return { fields: arr }
|
|
}),
|
|
removeField: (index) =>
|
|
set((st) => {
|
|
if (index < 0 || index >= st.fields.length) return st
|
|
const arr = st.fields.slice()
|
|
arr.splice(index, 1)
|
|
return { fields: arr }
|
|
}),
|
|
updateField: (index, patch) =>
|
|
set((st) => {
|
|
if (index < 0 || index >= st.fields.length) return st
|
|
const next = st.fields.slice()
|
|
next[index] = { ...next[index], ...patch } as FormField
|
|
return { fields: next }
|
|
}),
|
|
setActionUrl: (url) => set({ actionUrl: url }),
|
|
setSpam: (s) =>
|
|
set((st) => ({
|
|
spam: {
|
|
honeypotFieldName: s.honeypotFieldName ?? st.spam.honeypotFieldName,
|
|
minSubmitSeconds: s.minSubmitSeconds ?? st.spam.minSubmitSeconds,
|
|
},
|
|
})),
|
|
}))
|
|
}
|