@meshmakers/octo-ui 3.5.430 → 3.5.450
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/entity-forms/README.md
CHANGED
|
@@ -219,6 +219,48 @@ providers: [{ provide: ENTITY_FORM_FALLBACK_FORMS, useValue: MY_FALLBACK_FORMS }
|
|
|
219
219
|
This is the per-type counterpart of the built-in `form-default` safety net; the host is
|
|
220
220
|
responsible for keeping its copies in step with the seed.
|
|
221
221
|
|
|
222
|
+
## Cross-field validation and field marks (AB#7422)
|
|
223
|
+
|
|
224
|
+
Per-field rules (`required`, `min`, `max`, `pattern`) cannot express a rule over several fields such as
|
|
225
|
+
"request must not exceed limit". A host registers a validator for the CK types it knows; the library has
|
|
226
|
+
no domain knowledge:
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
const requestWithinLimit: EntityFormCrossFieldValidator = {
|
|
230
|
+
ckTypeIds: ['System.UI/BaseType'], // concrete type or any ancestor, case-insensitive
|
|
231
|
+
validate: ({ values }) => Number(values['request']) > Number(values['limit'])
|
|
232
|
+
? { request: 'Request exceeds limit', limit: 'Request exceeds limit' } // field key or attribute name, any casing
|
|
233
|
+
: null,
|
|
234
|
+
};
|
|
235
|
+
// route / root: providers: [provideEntityFormCrossFieldValidators(requestWithinLimit)]
|
|
236
|
+
// or per form: <mm-entity-form [crossFieldValidators]="[requestWithinLimit]" ...>
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
- `values` holds the current values of the **enabled** fields (hidden by `VisibleWhen`, read-only and `view`
|
|
240
|
+
mode fields are absent), keyed by field key = camelCase attribute name. The validator is re-run on every value
|
|
241
|
+
change and when the form is built, so a stored invalid combination is marked on open.
|
|
242
|
+
- Each named field becomes invalid (`control.errors.crossField`), its message is shown under the field **at once**
|
|
243
|
+
(also on a field the user has not touched, e.g. the partner field), and `isValid()` is `false` until the validator
|
|
244
|
+
answers `null` or `{}`. A host message replaces the generic text of the field's own rules (min, max, pattern, ...) so a validator
|
|
245
|
+
can word the format error itself; `required` still comes first. The first message per field wins when several validators answer.
|
|
246
|
+
- A form matches by `ResolvedEntityForm.rtCkTypeId` and `ckTypeAncestors` (new, from the CK metadata), so a rule on a
|
|
247
|
+
base type also covers derived types and tenant forms of derived types.
|
|
248
|
+
- A throwing validator is logged and ignored; it never blocks the form.
|
|
249
|
+
|
|
250
|
+
**`fieldErrors` input** (`mm-entity-form`): `Record<fieldKeyOrAttributeName, text>` marks a field with a host text, for
|
|
251
|
+
example the field named by the last failed deployment. A mark is informational: it does not affect validity or
|
|
252
|
+
saving and disappears as soon as the user edits that field.
|
|
253
|
+
|
|
254
|
+
**Field hints** (`fieldHints` input, AB#7423): `(context) => Record<fieldKeyOrAttributeName, { text, actionLabel?, actionId? }>`,
|
|
255
|
+
called with the current values (same context as a cross-field validator) on every change. The hint is shown under the field
|
|
256
|
+
(warning colour, role `note`, linked with `aria-describedby`); with `actionLabel` it carries one link that emits
|
|
257
|
+
`fieldHintAction` `{ key, actionId }` — the host decides what it does (e.g. `focusField('valuesYaml')`, which focuses a
|
|
258
|
+
field by key or attribute name and expands its collapsed section). Hints are informational: no validity, no change set.
|
|
259
|
+
|
|
260
|
+
**Accessibility**: the error text (else the help text) of a field is linked to its input with `aria-describedby`
|
|
261
|
+
(`mm-ef-error-<key>` / `mm-ef-help-<key>`), an invalid input gets `aria-invalid="true"`, and `markAllAsTouched()` moves
|
|
262
|
+
the focus to the first invalid field (expanding a collapsed section; `focusFirstInvalid()` does the same on its own).
|
|
263
|
+
|
|
222
264
|
## Action confirmation (`ENTITY_FORM_ACTION_CONFIRMATION`)
|
|
223
265
|
|
|
224
266
|
Optional `(request: { action: 'delete', ckTypeId, count, description }) => Promise<boolean>`,
|