@petrarca/sonnet-forms 0.5.0 → 0.7.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/README.md CHANGED
@@ -1,276 +1,101 @@
1
1
  # @petrarca/sonnet-forms
2
2
 
3
- Schema-driven form renderer and field components for the Petrarca Sonnet
4
- component library.
3
+ Schema-driven forms for the Petrarca Sonnet component library. One component,
4
+ `JsonSchemaFormRenderer`, takes a JSON Schema and a data object and produces
5
+ labelled, validated controls.
5
6
 
6
- ## What's included
7
+ ## What it provides
7
8
 
8
- **JsonSchemaFormRenderer** — Renders a complete form from a JSON Schema with
9
- automatic widget resolution, validation, nested objects, arrays, and custom
10
- UI schema overrides.
9
+ **The renderer.** `$ref` resolution, `if`/`then`/`else` and `allOf`/`anyOf`
10
+ flattening against the current values, schema defaults, nested objects and
11
+ arrays, buffered or live editing, and a change set on save.
11
12
 
12
- **Form field components** — Standalone fields with label, description, error,
13
- and compact mode: `FormInput`, `FormTextarea`, `FormSelect`, `FormMultiSelect`,
14
- `FormCheckbox`, `FormTagsInput`, `FormNumberInput`, `FormQuantityInput`.
13
+ **A widget registry.** Sixteen built-in controls covering text, numbers,
14
+ quantities, enums, checkboxes, tags, arrays, objects, entity references and
15
+ embedded JSON, plus your own controls keyed by `x-ui-widget`. A parallel
16
+ template registry makes the structure around a field -- the array-item row, the
17
+ object container, the group chrome -- replaceable without touching the widgets.
15
18
 
16
- **Widget system** — Extensible widget registry mapping schema types/formats to
17
- UI controls. Built-in widgets for text, number, select, checkbox, textarea,
18
- tags, quantity, arrays, objects, entity references, and JSON editing.
19
+ **Layout.** Rows, tabs, steps and sections, declared as `x-ui-*` hints on the
20
+ schema or in a UI schema passed beside it.
19
21
 
20
- ---
22
+ **Standalone field components.** `FormInput`, `FormTextarea`, `FormSelect`,
23
+ `FormMultiSelect`, `FormCheckbox`, `FormNumberInput`, `FormQuantityInput` and
24
+ `FormTagsInput`, for labelled inputs outside a schema-driven form.
25
+
26
+ **Change tracking.** `onUpdate` reports what changed, including inside arrays,
27
+ so a caller can send a targeted update rather than the whole object.
21
28
 
22
29
  ## Install
23
30
 
24
31
  ```bash
25
- pnpm add @petrarca/sonnet-forms @petrarca/sonnet-ui @petrarca/sonnet-core
32
+ pnpm add @petrarca/sonnet-forms
26
33
  ```
27
34
 
28
- Peer dependencies: `react >=19`, `react-dom >=19`, `tailwindcss`.
35
+ `@petrarca/sonnet-core` and `@petrarca/sonnet-ui` are regular dependencies and
36
+ arrive with it.
29
37
 
30
- ---
38
+ | Peer | Range | Needed for |
39
+ |---|---|---|
40
+ | `react`, `react-dom` | `>=19` | Everything |
41
+ | `tailwindcss` | `>=3` | The classes every component emits |
31
42
 
32
- ## Basic usage
43
+ The output assumes `sonnet-ui`'s stylesheet and Tailwind preset are already
44
+ wired up. Do that first -- see
45
+ [sonnet-ui: getting started](../sonnet-ui/docs/usage/getting-started.md).
33
46
 
34
- ### JsonSchemaFormRenderer
47
+ ## Usage
35
48
 
36
49
  ```tsx
37
50
  import { JsonSchemaFormRenderer } from "@petrarca/sonnet-forms";
51
+ import type { JsonSchema } from "@petrarca/sonnet-core/schema";
38
52
 
39
- const schema = {
53
+ const WIDGET_SCHEMA: JsonSchema = {
54
+ title: "Widget",
40
55
  type: "object",
56
+ required: ["name"],
41
57
  properties: {
42
- name: { type: "string", title: "Name" },
43
- email: { type: "string", format: "email", title: "Email" },
44
- age: { type: "integer", title: "Age" },
58
+ name: { type: "string", title: "Name" },
59
+ status: {
60
+ type: "string",
61
+ title: "Status",
62
+ enum: ["draft", "active", "retired"],
63
+ default: "draft",
64
+ },
65
+ notes: { type: "string", title: "Notes", "x-ui-widget": "textarea" },
45
66
  },
46
- required: ["name", "email"],
47
67
  };
48
68
 
49
69
  <JsonSchemaFormRenderer
50
- schema={schema}
51
- data={formData}
52
- onUpdate={(data, changedFields) => save(data)}
53
- onCancel={handleClose}
54
- showActions
55
- saveLabel="Save"
56
- />
57
- ```
58
-
59
- ### Key props
60
-
61
- | Prop | Type | Default | Purpose |
62
- |---|---|---|---|
63
- | `schema` | `JsonSchema` | required | JSON Schema describing the form |
64
- | `data` | `Record<string, unknown>` | required | Current form values |
65
- | `onUpdate` | `(data, changedFields, diff) => void` | — | Called on save |
66
- | `onCancel` | `() => void` | — | Called on cancel |
67
- | `onChange` | `(data) => void` | — | Called on every field change (use instead of `onUpdate` when managing your own buttons) |
68
- | `showActions` | `boolean` | `false` | Render built-in Save / Cancel buttons inside the form |
69
- | `showCancel` | `boolean` | `true` | Show the Cancel button (requires `showActions`) |
70
- | `saveLabel` | `string` | `"Save"` | Save button label |
71
- | `cancelLabel` | `string` | `"Cancel"` | Cancel button label |
72
- | `showExtraProperties` | `boolean` | `false` | Preserve form keys not in the schema. Set `true` for open-ended data (e.g. graph node properties) |
73
- | `uiSchema` | `Record<string, UISchema>` | — | Per-field UI overrides |
74
- | `widgets` | `WidgetRegistry` | `DEFAULT_WIDGETS` | Custom widget map |
75
- | `deferStateUpdates` | `boolean` | `false` | Update state on blur instead of on change |
76
-
77
- ---
78
-
79
- ## Schema construction
80
-
81
- The recommended pattern is Zod + `toJsonSchema()` with `.meta()` for UI hints:
82
-
83
- ```ts
84
- import { z } from "zod";
85
- import { toJsonSchema } from "@petrarca/sonnet-core/schema";
86
-
87
- const UserSchema = z.object({
88
- name: z.string().meta({
89
- "x-ui-title": "Full name",
90
- "x-ui-description": "As it appears on official documents.",
91
- }),
92
- role: z.enum(["admin", "user"]).meta({
93
- "x-ui-title": "Role",
94
- "x-ui-options": { enumNames: ["Administrator", "Standard user"] },
95
- }),
96
- bio: z.string().optional().meta({
97
- "x-ui-title": "Bio",
98
- "x-ui-widget": "textarea",
99
- }),
100
- });
101
-
102
- // Convert once, store as a module-level constant:
103
- const USER_FORM_SCHEMA = toJsonSchema(UserSchema);
104
- ```
105
-
106
- Or write the JSON Schema directly for dynamic / server-driven forms.
107
-
108
- ---
109
-
110
- ## Schema UI annotations
111
-
112
- Annotate individual fields with `x-ui-*` to control rendering:
113
-
114
- | Annotation | Purpose |
115
- |---|---|
116
- | `"x-ui-title"` | Override the field label (`false` to hide) |
117
- | `"x-ui-description"` | Override the field description |
118
- | `"x-ui-widget"` | Force a specific widget: `"textarea"`, `"tags"`, `"entity-select"`, or a custom key |
119
- | `"x-ui-options"` | Widget-specific options (see widget docs) |
120
- | `"x-ui-order"` | Array of field names to control rendering order in an object |
121
- | `"x-ui-layout"` | Array layout config: `{ direction: "horizontal", columns, gap, compact }` |
122
-
123
- ---
124
-
125
- ## onChange mode (external buttons)
126
-
127
- When you want to drive your own Submit button outside the form, omit `showActions`
128
- and use `onChange` instead of `onUpdate`:
129
-
130
- ```tsx
131
- const [formData, setFormData] = useState({});
132
-
133
- <JsonSchemaFormRenderer
134
- schema={schema}
135
- data={formData}
136
- onChange={setFormData}
137
- // no showActions — caller owns the buttons
138
- />
139
-
140
- <Button onClick={() => submit(formData)}>Submit</Button>
141
- ```
142
-
143
- ---
144
-
145
- ## UISchema
146
-
147
- Override rendering per-field without modifying the schema:
148
-
149
- ```tsx
150
- <JsonSchemaFormRenderer
151
- schema={schema}
152
- data={data}
153
- uiSchema={{
154
- name: { "x-ui-title": "Display name" }, // override label
155
- internal_id: { "x-ui-title": false }, // hide label entirely
156
- notes: { "x-ui-widget": "textarea" }, // force widget
157
- }}
158
- onUpdate={onUpdate}
70
+ schema={WIDGET_SCHEMA}
71
+ data={widget}
159
72
  showActions
160
- />
161
- ```
162
-
163
- ---
164
-
165
- ## Custom widgets
166
-
167
- Extend `DEFAULT_WIDGETS` with your own widget components:
168
-
169
- ```ts
170
- import { DEFAULT_WIDGETS, type WidgetRegistry, type WidgetProps } from "@petrarca/sonnet-forms";
171
-
172
- function MyColorWidget({ value, onChange, schema }: WidgetProps) {
173
- return (
174
- <input
175
- type="color"
176
- value={String(value ?? "#000000")}
177
- onChange={(e) => onChange(e.target.value)}
178
- />
179
- );
180
- }
181
-
182
- export const MY_WIDGETS: WidgetRegistry = {
183
- ...DEFAULT_WIDGETS,
184
- "color-picker": MyColorWidget, // key matches "x-ui-widget": "color-picker" in schema
185
- };
186
-
187
- // Usage:
188
- <JsonSchemaFormRenderer schema={schema} data={data} widgets={MY_WIDGETS} onUpdate={onUpdate} showActions />
189
- ```
190
-
191
- `WidgetProps` includes `formData` (the full current form state) — use this to
192
- build dependent widgets that react to sibling field values.
193
-
194
- ---
195
-
196
- ## Dynamic schema (edit vs. create)
197
-
198
- To hide fields that are immutable after creation, delete them from a cloned
199
- schema. Also exclude them from `data` — if a key exists in `data` but not
200
- the schema, the form engine treats it as a dirty field immediately:
201
-
202
- ```ts
203
- const schema = useMemo(() => {
204
- if (!isEditing) return BASE_SCHEMA;
205
- const s = structuredClone(BASE_SCHEMA);
206
- delete s.properties?.app_key; // read-only after creation
207
- return s;
208
- }, [isEditing]);
209
-
210
- const data = isEditing
211
- ? { name: entity.name } // omit app_key from data too
212
- : {};
73
+ saveLabel="Save changes"
74
+ onUpdate={(data, changedFields) => save(changedFields)}
75
+ onCancel={close}
76
+ showExtraProperties={false}
77
+ />;
213
78
  ```
214
79
 
215
- ---
216
-
217
- ## Standalone field components
218
-
219
- Use field components directly outside the renderer — in sidebars, toolbars,
220
- or any custom form layout:
221
-
222
- ```tsx
223
- import {
224
- FormInput, FormTextarea, FormSelect,
225
- FormCheckbox, FormMultiSelect, FormTagsInput,
226
- } from "@petrarca/sonnet-forms";
227
-
228
- <FormInput
229
- label="Name"
230
- description="Your full name"
231
- value={name}
232
- onChange={(e) => setName(e.currentTarget.value)}
233
- error={nameError}
234
- />
235
-
236
- <FormSelect
237
- label="Role"
238
- options={[{ value: "admin", label: "Admin" }, { value: "user", label: "User" }]}
239
- value={role}
240
- onChange={setRole}
241
- clearable={false}
242
- />
243
-
244
- <FormCheckbox
245
- label="Active"
246
- checked={active}
247
- onChange={(checked) => setActive(checked === true)}
248
- />
249
-
250
- <FormTextarea
251
- label="Notes"
252
- value={notes}
253
- onChange={(e) => setNotes(e.currentTarget.value)}
254
- autosize
255
- minRows={3}
256
- rightSection={<ClearButton />}
257
- rightSectionWidth={40}
258
- />
259
- ```
260
-
261
- **Common props on all field components:**
262
-
263
- | Prop | Purpose |
264
- |---|---|
265
- | `label` | Field label. Pass `false` to suppress. |
266
- | `description` | Help text below the field |
267
- | `error` | Error message (also sets error styling) |
268
- | `compact` | Tighter vertical spacing |
269
- | `disabled` | Disables the field |
270
- | `wrapperClassName` | Class on the outer wrapper div |
271
-
272
- ---
80
+ Hold the schema as a module-level constant; rebuilding it on every render
81
+ re-resolves references and conditionals. `showExtraProperties` defaults to
82
+ `true` and shows a card listing data keys absent from the schema -- most forms
83
+ want it off. The twenty-four props, the three edit modes and what validation
84
+ actually enforces are in [getting started](docs/usage/getting-started.md).
85
+
86
+ ## Documentation
87
+
88
+ - [Usage guides](docs/usage/index.md) -- getting started, the
89
+ [`x-ui-*` vocabulary](docs/usage/ui-schema.md),
90
+ [layout](docs/usage/layout.md), [widgets](docs/usage/widgets.md),
91
+ [templates](docs/usage/templates.md),
92
+ [field components](docs/usage/field-components.md) and
93
+ [change tracking](docs/usage/change-tracking.md).
94
+ - [Design docs](docs/design/index.md) -- form layout, nested fields and the
95
+ change-tracking model.
96
+ - [Workspace documentation](../../docs/README.md) -- how the packages layer and
97
+ the shared schema extension contract.
273
98
 
274
99
  ## License
275
100
 
276
- See [LICENSE.md](../../LICENSE.md).
101
+ Apache 2.0. See [LICENSE.md](../../LICENSE.md).