npm i @sjsf/form@next @sjsf/ajv8-validator@next ajv@8 @sjsf/basic-theme@next
yarn add @sjsf/form@next @sjsf/ajv8-validator@next ajv@8 @sjsf/basic-theme@next
pnpm add @sjsf/form@next @sjsf/ajv8-validator@next ajv@8 @sjsf/basic-theme@next
bun add @sjsf/form@next @sjsf/ajv8-validator@next ajv@8 @sjsf/basic-theme@next
Usage
<script lang="ts"> import { createForm, BasicForm } from "@sjsf/form";
import { schema, uiSchema, initialValue, type Data, withFile, } from "../data"; import * as defaults from "./defaults";
const form = createForm({ ...defaults, // required due to several forms on the page idPrefix: "basic", initialValue, schema, uiSchema, onSubmit: ({ name }: Data) => window.alert(`Hello, ${name}`), });</script>
<BasicForm {form} />
<pre>{JSON.stringify(form.value, withFile, 2)}</pre>
import { formatFileSize } from "@sjsf/form/validators/file-size";import type { Schema, UiSchemaRoot } from "@sjsf/form";import type { FromSchema } from "json-schema-to-ts";
export const schema = { title: "User Registration", description: "Simple user registration form", type: "object", required: ["name", "email", "age"], properties: { name: { type: "string", title: "Full Name", minLength: 2, maxLength: 50, }, email: { type: "string", title: "Email", format: "email", }, age: { type: "integer", title: "Age", minimum: 13, maximum: 120, }, country: { type: "string", title: "Country", enum: ["US", "CA", "UK", "DE", "FR"], }, experience: { type: "string", title: "Work Experience", enum: ["beginner", "intermediate", "advanced"], }, skills: { type: "array", title: "Skills", items: { type: "string", enum: ["HTML", "CSS", "JS/TS", "Svelte"], }, uniqueItems: true, minItems: 4 }, bio: { type: "string", title: "About You", maxLength: 200, }, startDate: { type: "string", title: "Available Start Date", format: "date", }, resume: { title: "Upload Resume", }, },} as const satisfies Schema;
export type Data = FromSchema<typeof schema>;
export const uiSchema: UiSchemaRoot = { "ui:options": { layouts: { "object-properties": { style: "display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem;", }, }, translations: { submit: "Register", }, }, name: { "ui:options": { text: { autocomplete: "name", }, flowbite3Text: { autocomplete: "name", }, }, }, country: { "ui:components": { stringField: "enumField", }, "ui:options": { enumNames: [ "United States", "Canada", "United Kingdom", "Germany", "France", ], }, }, skills: { "ui:components": { arrayField: "multiEnumField", }, "ui:options": { layouts: { "field-content": { style: "display: flex; flex-wrap: wrap; gap: 0.5rem;", }, }, }, }, bio: { "ui:components": { textWidget: "textareaWidget", }, "ui:options": { layouts: { "object-property": { style: "grid-row: span 2;", }, }, textarea: { rows: 5, }, flowbite3Textarea: { rows: 5, }, }, }, experience: { "ui:components": { stringField: "enumField", selectWidget: "radioWidget", }, "ui:options": { layouts: { "field-content": { style: "display: flex; flex-wrap: wrap; gap: 0.5rem;", }, }, shadcn4RadioGroup: { style: "grid-auto-flow: column; grid-auto-columns: max-content;", }, enumNames: ["0-2 years", "3-7 years", "8+ years"], }, }, startDate: { "ui:components": { textWidget: "datePickerWidget", }, }, resume: { "ui:components": { unknownField: "nativeFileFieldWrapper", }, },};
export const initialValue: Data = { name: "Sarah Johnson", email: "sarah.johnson@invalid", age: 28, country: "CA", skills: ["HTML", "CSS", "JS/TS", "Svelte"], experience: "intermediate", startDate: new Date().toLocaleDateString("en-CA"), bio: "Bio",};
export function withFile(_: string, value: any) { if (value instanceof File) { return `File(${value.name}, ${formatFileSize(value.size)})`; } return value;}
import { cast } from "@sjsf/form/lib/component";import { extendByRecord } from "@sjsf/form/lib/resolver";import type { ComponentDefinition, FieldCommonProps } from "@sjsf/form";
export { translation } from "@sjsf/form/translations/en";
export { resolver } from "@sjsf/form/resolvers/basic";import "@sjsf/form/fields/extra-fields/enum-include";import "@sjsf/form/fields/extra-fields/multi-enum-include";import NativeFileField from "@sjsf/form/fields/extra-fields/native-file.svelte";
import { theme as baseTheme } from "@sjsf/basic-theme";import "@sjsf/basic-theme/extra-widgets/textarea-include";import "@sjsf/basic-theme/extra-widgets/checkboxes-include";import "@sjsf/basic-theme/extra-widgets/radio-include";import "@sjsf/basic-theme/extra-widgets/file-include";import "@sjsf/basic-theme/extra-widgets/date-picker-include";
declare module "@sjsf/form" { interface ComponentProps { nativeFileFieldWrapper: FieldCommonProps<unknown>; } interface ComponentBindings { nativeFileFieldWrapper: "value"; }}
const nativeFileFieldWrapper = cast(NativeFileField, { value: { transform(props) { const v = props.value; if (v !== undefined && !(v instanceof File)) { throw new Error( `expected "File" or "undefined" value, but got ${typeof v}` ); } return v; }, },}) satisfies ComponentDefinition<"unknownField">;
export const theme = extendByRecord(baseTheme, { nativeFileFieldWrapper,});
export { createFormMerger as createMerger } from "@sjsf/form/mergers/modern";
import type { ValidatorFactoryOptions } from "@sjsf/form";import { addFormComponents, createFormValidator } from "@sjsf/ajv8-validator";import addFormats from "ajv-formats";
export const createValidator = (options: ValidatorFactoryOptions) => createFormValidator({ ...options, ajvPlugins: (ajv) => addFormComponents(addFormats(ajv)), });