@antelopejs/interface-dms 0.7.1 → 0.7.2

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.
Files changed (56) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +70 -68
  3. package/dist/auth/db/models/sessions.model.d.ts +1 -0
  4. package/dist/auth/db/models/userExternalIdentities.model.d.ts +1 -0
  5. package/dist/auth/db/models/users.model.d.ts +1 -0
  6. package/dist/base/block-types.d.ts +1 -0
  7. package/dist/base/block-types.js +1 -0
  8. package/dist/base/block-types.js.map +1 -1
  9. package/dist/base/live-toggle.d.ts +2 -2
  10. package/dist/base/period-selector.d.ts +2 -2
  11. package/dist/base/table-view/schema.d.ts +4 -4
  12. package/dist/db/internal/exportJobs.model.d.ts +1 -0
  13. package/dist/db/internal/inviteResolutions.model.d.ts +1 -0
  14. package/dist/db/internal/tenantLifecycle.model.d.ts +1 -0
  15. package/dist/db/models/roles.model.d.ts +1 -0
  16. package/dist/db/models/tenantMembers.model.d.ts +1 -0
  17. package/dist/db/models/tenants.model.d.ts +1 -0
  18. package/dist/db/models/user_invites.model.d.ts +1 -0
  19. package/docs/.navigation.yml +2 -0
  20. package/docs/1.core/.navigation.yml +2 -0
  21. package/docs/1.core/1.introduction.md +169 -0
  22. package/docs/1.core/10.layout-banners.md +133 -0
  23. package/docs/1.core/2.pages-and-modules.md +647 -0
  24. package/docs/1.core/3.components.md +287 -0
  25. package/docs/1.core/4.permissions.md +279 -0
  26. package/docs/1.core/5.tenancy.md +356 -0
  27. package/docs/1.core/6.invites.md +239 -0
  28. package/docs/1.core/7.hooks.md +354 -0
  29. package/docs/1.core/8.realtime.md +147 -0
  30. package/docs/1.core/9.quick-actions.md +115 -0
  31. package/docs/2.auth/.navigation.yml +2 -0
  32. package/docs/2.auth/1.introduction.md +75 -0
  33. package/docs/2.auth/2.authentication.md +222 -0
  34. package/docs/2.auth/3.tokens-and-sessions.md +352 -0
  35. package/docs/2.auth/4.users.md +256 -0
  36. package/docs/2.auth/5.external-identities.md +122 -0
  37. package/docs/3.base/.navigation.yml +2 -0
  38. package/docs/3.base/1.introduction.md +202 -0
  39. package/docs/3.base/10.display-blocks.md +136 -0
  40. package/docs/3.base/2.types.md +402 -0
  41. package/docs/3.base/3.forms.md +349 -0
  42. package/docs/3.base/4.tables.md +608 -0
  43. package/docs/3.base/5.charts-and-widgets.md +466 -0
  44. package/docs/3.base/6.layout.md +431 -0
  45. package/docs/3.base/8.data-types.md +361 -0
  46. package/docs/3.base/9.helpers.md +414 -0
  47. package/docs/4.notifications/.navigation.yml +2 -0
  48. package/docs/4.notifications/1.introduction.md +79 -0
  49. package/docs/4.notifications/2.categories-and-subjects.md +212 -0
  50. package/docs/4.notifications/3.sending.md +283 -0
  51. package/docs/5.html-render/.navigation.yml +2 -0
  52. package/docs/5.html-render/1.introduction.md +372 -0
  53. package/docs/5.html-render/2.built-in-emails.md +107 -0
  54. package/docs/index.md +100 -0
  55. package/docs/tsconfig.json +11 -0
  56. package/package.json +24 -18
@@ -0,0 +1,349 @@
1
+ ---
2
+ title: "Forms"
3
+ description: "The Form builder, its fields and field groups, sections, validation and the save modes."
4
+ ---
5
+
6
+ # Forms
7
+
8
+ ## Overview
9
+
10
+ `Form` builds a form component from a list of fields. Each field names a [data type](/docs/interfaces/base/data-types), and the data type supplies both the input component the frontend renders and the Zod schema the value is validated against. The builder does three things when you call it: it serializes every field, it compiles the field list into a single Zod object schema, and it attaches the JSON Schema form of that schema to the component options — so the description shipped to the frontend already carries its own validation contract.
11
+
12
+ Everything on this page is exported from the `form` module, reachable either from the package root or from its own subpath:
13
+
14
+ ```typescript
15
+ import { Form } from "@antelopejs/interface-dms/base";
16
+ ```
17
+
18
+ Or import directly from the form subpath:
19
+
20
+ ```typescript
21
+ import { Form, FormComponents } from "@antelopejs/interface-dms/base/form";
22
+ ```
23
+
24
+ `Form` returns a `FormBuilder`, which is a `ComponentBuilder<FormPropsSerialized>` with one extra readonly property, `fields`, carrying the original unserialized `FormFieldOrGroup[]`. That property is what lets `formSchema` derive a schema straight from a builder.
25
+
26
+ ## Declare a Form
27
+
28
+ Assign the builder to a static field of a page class. The form fetches its initial values from `fetchUrl` and posts back to `submitUrl`.
29
+
30
+ ```typescript
31
+ import {
32
+ PageController,
33
+ pagesCategory,
34
+ RegisterPage,
35
+ } from "@antelopejs/interface-dms/page";
36
+ import { Form } from "@antelopejs/interface-dms/base";
37
+ import { DefaultDataTypes } from "@antelopejs/interface-dms/base/data-types";
38
+ import { DefaultLayout } from "@antelopejs/interface-dms/base/layouts";
39
+ import { HttpMethod } from "@antelopejs/interface-dms/base/types";
40
+
41
+ const notesForm = Form({
42
+ title: "Notes",
43
+ fields: [
44
+ {
45
+ id: "message",
46
+ label: "Message",
47
+ description: "Stored in the playground `notes` table.",
48
+ type: new DefaultDataTypes.StringType({
49
+ placeholder: "Hello from the playground",
50
+ maxLength: 500,
51
+ }),
52
+ required: true,
53
+ },
54
+ ],
55
+ fetchUrl: "/playground/notes",
56
+ submitUrl: "/playground/notes",
57
+ submitUrlMethod: HttpMethod.post,
58
+ });
59
+
60
+ @RegisterPage()
61
+ export class NotesPage extends PageController(
62
+ "notes",
63
+ { displayName: "Notes", icon: "i-ph-note", category: pagesCategory },
64
+ DefaultLayout(),
65
+ ) {
66
+ static content = notesForm;
67
+ }
68
+ ```
69
+
70
+ ### FormProps
71
+
72
+ `FormProps` extends [`BaseComponentProps`](/docs/interfaces/base/types).
73
+
74
+ | Option | Type | Description |
75
+ | --- | --- | --- |
76
+ | `fields` | `FormFieldOrGroup[]` | The fields and field groups of the form. Beside `sections`, they come first, outside any section. |
77
+ | `sections` | `FormSection[]` | The fields split into titled cards, `{ id, label, description?, icon?, navGroup?, fields }` (`navGroup`: the heading its entry sits under in the navigation, side list or chips, shared by consecutive sections naming the same one), on one page and saved together. |
78
+ | `sectionNav` | `FormSectionNav` | `"side"` (a list beside the sections following the scroll, chips on a narrow form), `"jump"` (chips above them) or `"none"`. Defaults to `"side"` from three sections, `"none"` below. |
79
+ | `title` | `string` | Form title. Also becomes the component's display name; defaults to `"Form"` when absent. |
80
+ | `description` | `string` | Form description. |
81
+ | `fetchUrl` | `string` | URL the form loads its initial values from. |
82
+ | `fetchUrlMethod` | `HttpMethod` | Method for `fetchUrl`. |
83
+ | `submitUrl` | `string` | URL the form submits to. |
84
+ | `submitUrlMethod` | `HttpMethod` | Method for `submitUrl`. |
85
+ | `submitLabel` | `string` | Label of the submit button, as an i18n key or a literal. Defaults to the generic "Save changes" label. |
86
+ | `successMessage` | `string` | Message shown after a successful submit. |
87
+ | `errorMessage` | `string` | Message shown after a failed submit. |
88
+ | `fieldsOrientation` | `"horizontal" \| "vertical"` | Layout direction of the field list. |
89
+ | `redirectOnSuccess` | `string` | Path to navigate to after a successful submit. Accepts `{{params.X}}` and `{{query.X}}` tokens like `submitUrl`, plus `{{response.X}}` which resolves against the JSON body of the submit response. |
90
+ | `submitDefaults` | `Record<string, unknown>` | Values merged into the submit payload even when the field is absent from the form. String values accept `{{params.X}}` and `{{query.X}}` tokens resolved against the form's route at submit time; entries whose tokens do not resolve are dropped. |
91
+ | `saveMode` | `FormSaveMode` | How the form offers to save. `"bar"` (default): a sticky bar shown while there are unsaved changes, naming them, with Discard and Save; on a page it floats over the form, taking no space while hidden. `"footer"`: buttons in the form's footer. `"none"`: no buttons. `"instant"`: each change saves on its own, the changed field alone sent to `submitUrl` (so the endpoint must accept a partial body), a pick at once and a text once typing pauses; a refused save is put back to the last confirmed value, with a Retry; an `"action"` form ignores it. In a drawer or modal, `"bar"` and `"footer"` both use the container's footer. |
92
+ | `kind` | `FormKind` | What the form edits. `"record"` (default): keeps its values once saved, and offers Cancel while clean only when it has somewhere to go back to (the drawer or modal it sits in, or `backTo` on a page). `"action"`: a send, invite or run form; its `submitLabel` always shows in its footer, ready, so a pre-filled form sends its defaults as they are (a form with no field to fill in included); Reset joins it once a value changes (in a drawer or a modal, Cancel before that), and it empties after a successful submit (each field back to its `defaultValue`, or what `fetchUrl` loaded). |
93
+ | `backTo` | `string` | Where Cancel leads a `"record"` form on a page; without it the form offers no Cancel there. `TableView` sets it on its form pages, to the list. |
94
+ | `labelKey` | `string` | Field of the loaded record that names it: on a page, the breadcrumb ends with its value once the form has loaded. `TableView` sets it on its edit and details pages from its own `labelKey`. Also in the page editor's Form schema, as an advanced option. |
95
+ | `requiresRecord` | `boolean` | The form is about the record `fetchUrl` loads: while a route value that URL names is missing (a page reached without its `?id=`), it shows "No entry selected" instead of its fields, and cannot be saved. `ResourceForm` sets it in `edit` and `view`. |
96
+
97
+ A form on a page that sets neither `saveMode` nor `kind` saves with the save bar, as a record: declare `kind: "action"` on a form that sends something new each time. `FORM_SAVE_MODES` and `FORM_KINDS` list the accepted values.
98
+
99
+ An `"action"` form resets after a successful submit, a `"record"` keeps its values; `labelKey` names the field labelling the record.
100
+
101
+ `FormPropsSerialized` is `FormProps` with `fields` replaced by `FormFieldOrGroupSerialized[]` — every entry of the form, its own then each section's (`formEntries`) — `sections` by `FormSectionSerialized[]` (each section with the `fieldIds` of the entries it holds), and one added property, `schema`, holding the JSON Schema produced from the compiled Zod object. A required boolean field whose input is a checkbox validates as `z.literal(true)` (`isAcceptanceField`).
102
+
103
+ ## Fields
104
+
105
+ A `FormField` describes one input.
106
+
107
+ | Option | Type | Description |
108
+ | --- | --- | --- |
109
+ | `id` | `string` | Field key in the payload. Required. |
110
+ | `type` | `DataType` | Data type instance. Supplies the input component and the validation schema. Required. |
111
+ | `label` | `string` | Field label. |
112
+ | `description` | `string` | Help text. |
113
+ | `inputComponent` | `ComponentInfoSerialized` | Overrides the input component the data type would provide. |
114
+ | `disabled` | `boolean` | Render the input non-editable. |
115
+ | `required` | `boolean` | Whether a value must be supplied. Drives the schema, see below. |
116
+ | `defaultValue` | `DefaultValue` | Initial value. |
117
+ | `localized` | `boolean` | The value is a per-locale record rather than a single value. |
118
+
119
+ `FormFieldSerialized` replaces `type` with the data type's registered id string and `inputComponent` with a mandatory `component: ComponentInfoSerialized` — the resolved input component, taken from `inputComponent` when set and from `type.inputComponent()` otherwise.
120
+
121
+ ## Group Fields
122
+
123
+ A `FieldGroup` nests fields under a common heading. It is an alternative member of the `fields` array, not a wrapper around it.
124
+
125
+ ```typescript
126
+ import type { FieldGroup } from "@antelopejs/interface-dms/base/form";
127
+ import { DefaultDataTypes } from "@antelopejs/interface-dms/base/data-types";
128
+
129
+ const contact: FieldGroup = {
130
+ id: "contact",
131
+ label: "Contact",
132
+ orientation: "horizontal",
133
+ order: 1,
134
+ fields: [
135
+ { id: "email", label: "Email", type: new DefaultDataTypes.EmailType() },
136
+ { id: "phone", label: "Phone", type: new DefaultDataTypes.PhoneType() },
137
+ ],
138
+ };
139
+ ```
140
+
141
+ | Option | Type | Description |
142
+ | --- | --- | --- |
143
+ | `id` | `string` | Group identifier. Required. |
144
+ | `fields` | `FormField[]` | Fields in the group. Required. |
145
+ | `label` | `string` | Group heading. |
146
+ | `description` | `string` | Group description. |
147
+ | `orientation` | `"horizontal" \| "vertical"` | Layout direction inside the group. |
148
+ | `order` | `number` | Ordering hint among the top-level entries. |
149
+
150
+ `FieldGroupSerialized` is the same shape with `fields: FormFieldSerialized[]`.
151
+
152
+ `FormFieldOrGroup` is `FormField | FieldGroup`, and `FormFieldOrGroupSerialized` is `FormFieldSerialized | FieldGroupSerialized`. Two guards discriminate them, both by checking for an array-valued `fields` property:
153
+
154
+ ```typescript
155
+ import {
156
+ isFieldGroup,
157
+ isFieldGroupSerialized,
158
+ } from "@antelopejs/interface-dms/base/form";
159
+
160
+ for (const item of formProps.fields) {
161
+ if (isFieldGroup(item)) {
162
+ // item is FieldGroup
163
+ }
164
+ }
165
+ ```
166
+
167
+ ## Validate a Submission
168
+
169
+ The schema the form ships to the frontend is also available on the backend, so the route that receives the submission validates against exactly the same rules.
170
+
171
+ ```typescript
172
+ import { Controller, HTTPResult, JSONBody, Post } from "@antelopejs/interface-api";
173
+ import { formSchema } from "@antelopejs/interface-dms/base";
174
+
175
+ const notesFormSchema = formSchema(notesForm);
176
+
177
+ export class NotesController extends Controller("/playground/notes") {
178
+ @Post("")
179
+ async create(@JSONBody() body: unknown) {
180
+ const parsed = notesFormSchema.safeParse(body);
181
+ if (!parsed.success) {
182
+ return new HTTPResult(400, { error: "Invalid form payload" });
183
+ }
184
+ // parsed.data is validated
185
+ }
186
+ }
187
+ ```
188
+
189
+ ### formSchema
190
+
191
+ ```typescript
192
+ function formSchema(
193
+ source: FormSchemaSource,
194
+ ): z.ZodObject<Record<string, z.ZodTypeAny>>;
195
+ ```
196
+
197
+ `FormSchemaSource` is `FormFieldOrGroup[] | FormProps | FormBuilder`. `formSchema` accepts any of the three: an array is compiled directly, a `FormBuilder` and a `FormProps` object are compiled from their `fields`. Anything else throws `Error("formSchema: unsupported source")`.
198
+
199
+ ### buildFormSchema
200
+
201
+ ```typescript
202
+ function buildFormSchema(
203
+ fields: FormFieldOrGroup[],
204
+ ): z.ZodObject<Record<string, z.ZodTypeAny>>;
205
+ ```
206
+
207
+ `buildFormSchema` is the field-list form of the same operation and is what `Form` itself calls. It walks the list, flattens groups so a grouped field lands in the object under its own `id`, and adapts every field's base schema. The resulting object is flat: grouping affects layout, not the payload shape.
208
+
209
+ ### adaptFieldValidationSchema
210
+
211
+ ```typescript
212
+ function adaptFieldValidationSchema(
213
+ baseSchema: z.ZodTypeAny,
214
+ options: { localized?: boolean; required?: boolean },
215
+ ): z.ZodTypeAny;
216
+ ```
217
+
218
+ `adaptFieldValidationSchema` turns the schema a data type returns from `getValidation()` into the schema for one particular field:
219
+
220
+ - With `localized: true` the schema is wrapped in `z.record(z.string(), schema)`, so the value becomes a map of locale to value.
221
+ - When `required` is falsy the schema becomes `.nullable().optional()`.
222
+ - When `required` is true and the schema is a `ZodString` or a `ZodArray`, a `.min(1)` is added so an empty string or empty array does not satisfy the requirement.
223
+
224
+ It is exported because the same adaptation is applied outside forms — the `Column` decorator uses it to register a field validator on a data controller.
225
+
226
+ ### serializeFormFields
227
+
228
+ ```typescript
229
+ function serializeFormFields(
230
+ fields: FormFieldOrGroup[],
231
+ ): FormFieldOrGroupSerialized[];
232
+ ```
233
+
234
+ `serializeFormFields` produces the serialized field list: for each field it resolves the input component (`inputComponent` if given, otherwise `type.inputComponent()`) and replaces the data type instance with its registered id, falling back to the string `"unknown"` when the type is not registered. Groups keep their metadata and have their `fields` serialized in place.
235
+
236
+ ## Input Components
237
+
238
+ `FormComponents` is a namespace of factory functions, each returning a `ComponentInfoSerialized` for one input widget. Data types call them to produce their default input; you call them directly when a field needs a widget other than its type's default, via `FormField.inputComponent` or `ColumnOptions.inputComponent`.
239
+
240
+ ```typescript
241
+ import { FormComponents } from "@antelopejs/interface-dms/base/form";
242
+
243
+ const priority = FormComponents.InputSelect({
244
+ items: [
245
+ { label: "Low", value: "low" },
246
+ { label: "High", value: "high", icon: "i-ph-warning" },
247
+ ],
248
+ placeholder: "Pick a priority",
249
+ });
250
+ ```
251
+
252
+ | Function | Options interface |
253
+ | --- | --- |
254
+ | `InputCheckbox()` | — |
255
+ | `InputText(options?)` | `InputTextOptions` |
256
+ | `InputTextarea(options?)` | `TextareaOptions` |
257
+ | `InputEmail(options?)` | `InputEmailOptions` |
258
+ | `InputColor(options?)` | `InputColorOptions` |
259
+ | `InputPassword(options?)` | `InputPasswordOptions` |
260
+ | `InputNumber(options?)` | `InputNumberOptions` |
261
+ | `InputPercentage(options?)` | `InputNumberOptions` |
262
+ | `InputTime(options?)` | `InputTimeOptions` |
263
+ | `InputPhone(options?)` | `InputPhoneOptions` |
264
+ | `InputSlider(options?)` | `SliderOptions` |
265
+ | `InputSwitch()` | — |
266
+ | `InputRadioGroup(options)` | `RadioGroupOptions` |
267
+ | `InputSelect(options)` | `SelectOptions` |
268
+ | `InputRelation(options)` | `RelationOptions` |
269
+ | `InputCascader(options)` | `CascaderRelationOptions` |
270
+ | `InputAddress(options?)` | `AddressOptions` |
271
+ | `Calendar(options?)` | `CalendarOptions` |
272
+ | `DatePicker(options?)` | `CalendarOptions` |
273
+ | `DatePickerRange(options?)` | `DatePickerRangeOptions` |
274
+ | `RichText(options?)` | `RichTextOptions` |
275
+ | `File(options?)` | `FileOptions` |
276
+ | `Image(options?)` | `ImageOptions` |
277
+
278
+ The option interfaces live in the same namespace. The ones with more than a placeholder:
279
+
280
+ | Interface | Fields |
281
+ | --- | --- |
282
+ | `SelectOption` | `label: string`, `value: string \| number`, `disabled?`, `icon?`, `iconColor?`, `textColor?` |
283
+ | `SelectOptions` | `items: SelectOption[]`, `placeholder?`, `multiple?`, `deselectable?` |
284
+ | `RadioGroupOptions` | `items: SelectOption[]`, `orientation?: AxeOrientation` |
285
+ | `InputTextOptions` | `placeholder?`, `maxLength?`, `minLength?`, `showCount?` |
286
+ | `TextareaOptions` | `placeholder?`, `rows?`, `maxLength?`, `showCount?` |
287
+ | `InputNumberOptions` | `min?`, `max?`, `step?`, `placeholder?`, `suffix?`, `clamp?` (`InputNumber` only: `false` keeps a value outside `min` / `max` as typed and reports it under the field instead of clamping it on blur) |
288
+ | `InputTimeOptions` | `min?`, `max?`, `placeholder?` |
289
+ | `InputPhoneOptions` | `placeholder?`, `requiredPrefix?` |
290
+ | `InputPasswordOptions` | `placeholder?`, `minLength?`, `confirmPassword?`, `confirmPlaceholder?` |
291
+ | `SliderOptions` | `min?`, `max?`, `step?` |
292
+ | `CalendarOptions` | `range?`, `multiple?`, `minDate?`, `maxDate?` |
293
+ | `DatePickerRangeOptions` | `minDate?`, `maxDate?` |
294
+ | `RelationOptions` | `searchUrl: string`, `placeholder?`, `multiple?`, `deselectable?`, `keyMapping?: { label?, value?, avatar?, disabled? }`, `addForm?: ComponentInfoSerialized<FormPropsSerialized>`, `addPermissionIds?: string[]`, `addFormTexts?: { displayName?, description? }` |
295
+ | `CascaderRelationKeyMapping` | `label: string`, `value: string`, `parent: string`, `disabled?` |
296
+ | `CascaderRelationOptions` | `searchUrl: string`, `keyMapping: CascaderRelationKeyMapping`, `placeholder?`, `multiple?`, `deselectable?`, `maxDepth?`, `leafOnly?`, `fallback?` |
297
+ | `AddressOptions` | `placeholder?` (per address component), `autocomplete?: DefaultDataTypes.AddressAutocomplete` |
298
+ | `FileOptions` | `multiple?`, `constraints?: { maxSize?, allowedMimetypes? }`, `path?`, `storage?` |
299
+ | `ImageOptions` | `FileOptions` plus `max?` and `resize?: ImageResizeOptions` |
300
+ | `ImageResizeOptions` | `maxWidth: number`, `maxHeight: number`, `fit?: "cover" \| "contain"` |
301
+ | `InputEmailOptions`, `InputColorOptions`, `RichTextOptions` | `placeholder?` |
302
+
303
+ `ImageResizeOptions` downscales client-side before upload. `contain` (the default) scales down while preserving the source ratio; `cover` crops the image centred to the `maxWidth`/`maxHeight` ratio first, then scales down. An image already within bounds is uploaded untouched. This is a UX-level bound, not a server-enforced guarantee — keep `constraints.maxSize` for the latter.
304
+
305
+ ## Events and Functions
306
+
307
+ `FormEvents` names the events a form emits, `FormFunctions` the functions it exposes to watch actions.
308
+
309
+ | Namespace member | Value |
310
+ | --- | --- |
311
+ | `FormEvents.SUBMIT` | `"DmsComponent.Form.Submit"` |
312
+ | `FormEvents.SUBMIT_SUCCESS` | `"DmsComponent.Form.SubmitSuccess"` |
313
+ | `FormEvents.SUBMIT_ERROR` | `"DmsComponent.Form.SubmitError"` |
314
+ | `FormEvents.FIELD_CHANGE` | `"DmsComponent.Form.FieldChange"` |
315
+ | `FormEvents.RESET` | `"DmsComponent.Form.Reset"` |
316
+ | `FormFunctions.SET_FIELD_DISABLED` | `"DmsComponent.Form.SetFieldDisabled"` |
317
+ | `FormFunctions.SET_FIELD_HIDDEN` | `"DmsComponent.Form.SetFieldHidden"` |
318
+ | `FormFunctions.SET_FIELD_REQUIRED` | `"DmsComponent.Form.SetFieldRequired"` |
319
+
320
+ Both functions register their parameter shape in `WatchFunctionParamMap`, so `.watch()` type-checks the `params` object:
321
+
322
+ | Function | Params |
323
+ | --- | --- |
324
+ | `SET_FIELD_DISABLED` | `{ targetField: string; setDisabled: boolean }` |
325
+ | `SET_FIELD_HIDDEN` | `{ targetField: string; setHidden: boolean }` |
326
+ | `SET_FIELD_REQUIRED` | `{ targetField: string; setRequired: boolean }` |
327
+
328
+ ```typescript
329
+ import { Form, FormEvents, FormFunctions } from "@antelopejs/interface-dms/base/form";
330
+ import { DefaultDataTypes } from "@antelopejs/interface-dms/base/data-types";
331
+
332
+ const form = Form({
333
+ fields: [
334
+ { id: "isCompany", label: "Company", type: new DefaultDataTypes.BooleanType() },
335
+ { id: "vatNumber", label: "VAT number", type: new DefaultDataTypes.StringType() },
336
+ ],
337
+ submitUrl: "/api/customers/new",
338
+ }).watch(FormEvents.FIELD_CHANGE, FormFunctions.SET_FIELD_HIDDEN, {
339
+ params: { targetField: "vatNumber", setHidden: true },
340
+ });
341
+ ```
342
+
343
+ `FIELD_CHANGE` carries `{ fieldId, value }` and fires once per field with the values the form starts from (its defaults, or the record it loaded), then on each change: the watch above, which names no condition, hides `vatNumber` as soon as the form opens. Add a `value` condition to `onParam` for a watch that applies only when the field holds that value, whether it opened with it or the user picked it.
344
+
345
+ `.watch()` records the watch on the component itself; `.watchOn(childId, …)` records it on a child declared with `.child()`. Both come from `ComponentBuilder` in `@antelopejs/interface-dms/component`.
346
+
347
+ ## Next Steps
348
+
349
+ - [Tables](/docs/interfaces/base/tables) - Turn a data controller into a table view with columns, row actions and guards.