@stonecrop/aform 0.13.12 → 0.14.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,6 +1,6 @@
1
1
  # @stonecrop/aform
2
2
 
3
- Schema-driven form components for the Stonecrop framework. Renders a `ResolvedField[]` array (produced by `registry.resolveSchema()`) into a form, wiring field values to a `data` object via `v-model:data`.
3
+ Schema-driven form components for the Stonecrop framework. Renders a `ResolvedField[]` array into a form, wiring field values to a `data` object via `v-model:data`. The array usually comes from `registry.resolveSchema()`, but may also be hand-authored for view chrome that has no backing doctype — see [Authoring space vs rendering space](#authoring-space-vs-rendering-space).
4
4
 
5
5
  ## Components
6
6
 
@@ -8,15 +8,19 @@ Schema-driven form components for the Stonecrop framework. Renders a `ResolvedFi
8
8
  |---|---|
9
9
  | `AForm` | Root form renderer — iterates schema, renders child components, handles nested forms |
10
10
  | `ACheckbox` | Boolean toggle |
11
- | `AComboBox` | Editable combo box with option list |
12
11
  | `ADate` | Date text input |
13
12
  | `ADatePicker` | Date picker with calendar UI |
13
+ | `ADateTime` | Combined date and time input |
14
+ | `ADateRange` | Date-range input (start and end) |
15
+ | `ADateSelection` | Wrapper combining a date picker and time input |
16
+ | `ADuration` | Duration input |
14
17
  | `ADropdown` | Single-select dropdown for string enum fields |
15
18
  | `AFieldset` | Collapsible grouping container for other fields |
16
19
  | `AFileAttach` | File upload and attachment |
17
20
  | `AFormLink` | Linked document selector with search dropdown and navigation arrow |
18
21
  | `ANumericInput` | Numeric input with type-specific formatting |
19
22
  | `ATextInput` | Single-line text input |
23
+ | `ATextarea` | Multi-line text input |
20
24
 
21
25
  ## Installation
22
26
 
@@ -32,12 +36,58 @@ This registers all components globally. They can also be imported individually.
32
36
 
33
37
  ## AForm
34
38
 
39
+ ### Authoring space vs rendering space
40
+
41
+ Stonecrop has two field shapes, and AForm consumes only the second:
42
+
43
+ | | Type | Produced by | Table columns live under |
44
+ |---|---|---|---|
45
+ | **Authoring space** | `DoctypeField[]` (`@stonecrop/schema`) | hand-authored doctype JSON, the docbuilder, the GraphQL converter | `columns` |
46
+ | **Rendering space** | `ResolvedField[]` (this package) | `registry.resolveSchema()` | `schema` |
47
+
48
+ `resolveSchema()` renames a table's `columns` to `schema` because `schema` is the ATable prop that runs
49
+ `schemaToColumns()`; ATable's own `columns` prop means already-converted `TableColumn[]`. Passing an
50
+ authoring-space field straight to AForm therefore does **not** render a table.
51
+
52
+ `kind` is required and is the only thing AForm dispatches on — it does not infer a field's type from its
53
+ structure. Every path into rendering space sets it: Zod's preprocess, `Doctype.fromObject`, and the
54
+ registry. When hand-authoring, declare it yourself and use `satisfies` to stay checked:
55
+
56
+ ```typescript
57
+ import type { ResolvedField, ResolvedTable } from '@stonecrop/aform/types'
58
+
59
+ const schema: ResolvedField[] = [
60
+ {
61
+ kind: 'table',
62
+ fieldname: 'line_items',
63
+ component: 'ATable',
64
+ schema: [{ fieldname: 'item_code', label: 'SKU', component: 'ATextInput' }],
65
+ config: { view: 'list' },
66
+ } satisfies ResolvedTable,
67
+ ]
68
+ ```
69
+
70
+ Each column needs a `component`: `schemaToColumns()` drops entries without one, since absence is what
71
+ marks a non-scalar entry.
72
+
73
+ ### Table rows come from the data model
74
+
75
+ A table's rows are never part of its schema. AForm reads them from `dataModel[fieldname]`, so a `rows`
76
+ key on the schema field is ignored, and a table whose `fieldname` has no matching data key renders empty:
77
+
78
+ ```typescript
79
+ // schema declares fieldname: 'line_items' → rows are read from here
80
+ const data = ref({ line_items: [{ item_code: 'LAPTOP-PRO-15', quantity: 2 }] })
81
+ ```
82
+
83
+ For a table nested in a fieldset, the rows nest the same way — `data[fieldsetFieldname][tableFieldname]`.
84
+
35
85
  ### Field width
36
86
 
37
87
  Set `width` on any schema field to control its share of the form row. The value is any valid CSS size and is applied as `flex-basis` + `width` directly on the field's flex item:
38
88
 
39
89
  ```json
40
- { "fieldname": "notes", "fieldtype": "Text", "component": "ATextInput", "label": "Notes", "width": "100%" }
90
+ { "fieldname": "notes", "component": "ATextInput", "label": "Notes", "width": "100%" }
41
91
  ```
42
92
 
43
93
  | Value | Effect |
@@ -52,7 +102,7 @@ Fields without `width` continue to share space equally (`flex-grow: 1; min-width
52
102
 
53
103
  ## AFormLink
54
104
 
55
- A form input for selecting and navigating to linked documents (`fieldtype: 'Link'`). Combines a searchable text input, an optional dropdown of results, and a navigation arrow button.
105
+ A form input for selecting and navigating to linked documents (fields carrying a `doctype` marker). Combines a searchable text input, an optional dropdown of results, and a navigation arrow button.
56
106
 
57
107
  ### Value shape
58
108
 
@@ -142,21 +192,21 @@ If no navigator is provided, the arrow button is still rendered but navigation c
142
192
 
143
193
  ### Via resolveSchema
144
194
 
145
- For `fieldtype: 'Link'` fields with no matching `links` declaration, `Registry.resolveSchema()` automatically assigns `component: 'AFormLink'` and sets `doctype` from `field.options`. No manual wiring required:
195
+ For fields carrying a `doctype` marker with no matching `links` declaration and no `component`, `Registry.resolveSchema()` automatically assigns `component: 'AFormLink'`. No manual wiring required:
146
196
 
147
197
  ```typescript
148
198
  const config: DoctypeConfig = {
149
- slug: 'sales-order',
199
+ name: 'Sales Order',
150
200
  fields: [
151
- { fieldname: 'order_number', fieldtype: 'Data', component: 'ATextInput', label: 'Order Number' },
152
- { fieldname: 'territory', fieldtype: 'Link', options: 'territory', label: 'Territory' },
201
+ { fieldname: 'order_number', component: 'ATextInput', label: 'Order Number' },
202
+ { fieldname: 'territory', doctype: 'territory', label: 'Territory' },
153
203
  // no 'links' entry for territory
154
204
  ],
155
205
  }
156
206
 
157
207
  registry.addDoctype(Doctype.fromObject(config))
158
208
  const resolved = registry.resolveSchema(registry.registry['sales-order'])
159
- // resolved[1] === { fieldname: 'territory', component: 'AFormLink', doctype: 'territory', label: 'Territory' }
209
+ // resolved[1] === { kind: 'field', fieldname: 'territory', component: 'AFormLink', doctype: 'territory', label: 'Territory' }
160
210
 
161
211
  // Pass to AForm as normal — the territory field renders as AFormLink automatically
162
212
  ```
package/dist/aform.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import ACheckbox from './components/form/ACheckbox.vue';
2
- import AComboBox from './components/form/AComboBox.vue';
3
2
  import ADate from './components/form/ADate.vue';
4
3
  import ADatePicker from './components/form/ADatePicker.vue';
5
4
  import ADateRange from './components/form/ADateRange.vue';
@@ -13,6 +12,7 @@ import AForm from './components/AForm.vue';
13
12
  import AFormLink from './components/form/AFormLink.vue';
14
13
  import ANumericInput from './components/form/ANumericInput.vue';
15
14
  import type { App } from 'vue';
15
+ import ATextarea from './components/form/ATextarea.vue';
16
16
  import ATextInput from './components/form/ATextInput.vue';
17
17
  import type { ColumnSchema } from '@stonecrop/schema';
18
18
  import type { FieldValidation } from '@stonecrop/schema';
@@ -23,8 +23,6 @@ import type { ValueField } from '@stonecrop/schema';
23
23
 
24
24
  export { ACheckbox }
25
25
 
26
- export { AComboBox }
27
-
28
26
  export { ADate }
29
27
 
30
28
  export { ADatePicker }
@@ -71,6 +69,8 @@ export declare interface AFormLinkValue {
71
69
 
72
70
  export { ANumericInput }
73
71
 
72
+ export { ATextarea }
73
+
74
74
  export { ATextInput }
75
75
 
76
76
  /**
@@ -124,6 +124,13 @@ export declare type ComponentProps = {
124
124
  errorMessage: string;
125
125
  [key: string]: any;
126
126
  };
127
+ /**
128
+ * Inline validation error messages to display on this field. Fed by the host
129
+ * (e.g. mapped from the core validation store) — the renderer stays dumb and just
130
+ * shows what it is given. Takes precedence over the static `validation.errorMessage`.
131
+ * @public
132
+ */
133
+ errors?: string[];
127
134
  };
128
135
 
129
136
  /**
@@ -154,9 +161,13 @@ export { InteractionMode }
154
161
  export { Login }
155
162
 
156
163
  /**
157
- * The discriminated union of all resolved field types — what AForm consumes
158
- * after `resolveSchema()` has transformed the authoring `DoctypeField[]`.
164
+ * The discriminated union of all resolved field types — what AForm consumes,
165
+ * usually after `resolveSchema()` has transformed the authoring `DoctypeField[]`,
166
+ * but also valid hand-authored for view chrome with no backing doctype.
159
167
  * Narrowed by `kind`: `'field'` | `'link'` | `'table'` | `'fieldset'`.
168
+ *
169
+ * `kind` is required — AForm dispatches on it alone and does not infer a field's
170
+ * type from its structure.
160
171
  * @public
161
172
  */
162
173
  export declare type ResolvedField = ResolvedScalar | ResolvedLink | ResolvedTable | ResolvedFieldset;
@@ -225,6 +236,14 @@ export declare type ResolvedScalar = Omit<ValueField, 'cardinality'> & {
225
236
  * A resolved table — either from a Link with `noneOrMany`/`atLeastOne` cardinality,
226
237
  * or from an inline TableField. ATable receives columns via `:schema` (ColumnSchema[])
227
238
  * and row data via `:rows` from formData at render time.
239
+ *
240
+ * Note the key rename: an authoring `TableField` declares its columns under `columns`;
241
+ * `resolveSchema` moves them to `schema` here, because `schema` is the ATable prop that
242
+ * runs `schemaToColumns()` (ATable's own `columns` prop means already-converted
243
+ * `TableColumn[]`). A hand-authored table must therefore use `schema`, not `columns`.
244
+ *
245
+ * Rows are never part of the schema. AForm sources them from the data model at
246
+ * `dataModel[fieldname]`, so a `rows` key placed on this object is ignored.
228
247
  * @public
229
248
  */
230
249
  export declare interface ResolvedTable {