From deb750ad21869b871549c4d9162887ef8d901d7a Mon Sep 17 00:00:00 2001 From: cexbrayat Date: Fri, 22 May 2026 15:35:50 +0200 Subject: [PATCH] docs: clarify signal forms limit metadata keys Explain that MIN and MAX are selection keys which point to the type-specific limit metadata keys, such as MIN_NUMBER, MIN_DATE, MAX_NUMBER, and MAX_DATE. Also list minDate() and maxDate() alongside min() and max() in the Signal Forms metadata docs, so the validator tables match the actual metadata model. --- .../guide/forms/signals/field-metadata.md | 40 +++++++++++-------- .../content/guide/forms/signals/form-logic.md | 20 +++++----- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/adev/src/content/guide/forms/signals/field-metadata.md b/adev/src/content/guide/forms/signals/field-metadata.md index d2f5d7092f75..a35a117a281a 100644 --- a/adev/src/content/guide/forms/signals/field-metadata.md +++ b/adev/src/content/guide/forms/signals/field-metadata.md @@ -40,14 +40,16 @@ Calling `required(path.username)` contributes a value to the `REQUIRED` metadata Several built-in constraint validators follow this pattern: -| Validator | Metadata key | Type | `FieldState` getter | -| ------------- | ------------ | --------------------- | ------------------- | -| `required()` | `REQUIRED` | `boolean` | `required` | -| `min()` | `MIN` | `number \| undefined` | `min` | -| `max()` | `MAX` | `number \| undefined` | `max` | -| `minLength()` | `MIN_LENGTH` | `number \| undefined` | `minLength` | -| `maxLength()` | `MAX_LENGTH` | `number \| undefined` | `maxLength` | -| `pattern()` | `PATTERN` | `RegExp[]` | `pattern` | +| Validator | Metadata key | Type | `FieldState` getter | +| ------------- | -------------------------- | --------------------- | ------------------- | +| `required()` | `REQUIRED` | `boolean` | `required` | +| `min()` | `MIN` selects `MIN_NUMBER` | `number \| undefined` | `min` | +| `max()` | `MAX` selects `MAX_NUMBER` | `number \| undefined` | `max` | +| `minDate()` | `MIN` selects `MIN_DATE` | `Date \| undefined` | `min` | +| `maxDate()` | `MAX` selects `MAX_DATE` | `Date \| undefined` | `max` | +| `minLength()` | `MIN_LENGTH` | `number \| undefined` | `minLength` | +| `maxLength()` | `MAX_LENGTH` | `number \| undefined` | `maxLength` | +| `pattern()` | `PATTERN` | `RegExp[]` | `pattern` | Non-constraint validators like `email()` and `validate()` do not contribute to metadata. They run their check and surface a validation error, but they do not publish a reactive value for templates to read. @@ -217,16 +219,20 @@ While `MetadataReducer.min()` and `MetadataReducer.max()` are reducers, you may The built-in constraint keys pick their reducers based on what "strictest" means for the constraint, which is often the opposite of what the key's name suggests: -| Key | Reducer | Reasoning | -| ------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------- | -| `REQUIRED` | `or()` | If any `required()` rule evaluates to `true`, the field is required. | -| `MIN` | `max()` | A minimum-value constraint is strictest when largest. If one rule requires `>= 5` and another `>= 10`, the effective minimum is `10`. | -| `MAX` | `min()` | A maximum-value constraint is strictest when smallest. If one rule caps at `100` and another at `50`, the effective maximum is `50`. | -| `MIN_LENGTH` | `max()` | Same logic as `MIN`: the longest required length wins. | -| `MAX_LENGTH` | `min()` | Same logic as `MAX`: the shortest allowed length wins. | -| `PATTERN` | `list()` | Each `pattern()` call contributes a regex; the value must match all of them. | +| Key | Reducer | Reasoning | +| ------------ | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------- | +| `REQUIRED` | `or()` | If any `required()` rule evaluates to `true`, the field is required. | +| `MIN_NUMBER` | `max()` | A minimum-number constraint is strictest when largest. If one rule requires `>= 5` and another `>= 10`, the effective minimum is `10`. | +| `MIN_DATE` | `max()` | Same logic as `MIN_NUMBER`: the latest required date wins. | +| `MAX_NUMBER` | `min()` | A maximum-number constraint is strictest when smallest. If one rule caps at `100` and another at `50`, the effective maximum is `50`. | +| `MAX_DATE` | `min()` | Same logic as `MAX_NUMBER`: the earliest allowed date wins. | +| `MIN_LENGTH` | `max()` | Same logic as `MIN_NUMBER`: the longest required length wins. | +| `MAX_LENGTH` | `min()` | Same logic as `MAX_NUMBER`: the shortest allowed length wins. | +| `PATTERN` | `list()` | Each `pattern()` call contributes a regex; the value must match all of them. | -This pairing of "strictest wins" is why calling `min(path.age, 18)` and `min(path.age, 21)` in two composed schemas works correctly. Each call registers its own validator that enforces its specific bound (so a value below either bound fails validation). Separately, each call contributes to the public `MIN` key, and `state.metadata(MIN)!()` reports the aggregate (`21`) so UI and custom controls can read the effective minimum. +`MIN` and `MAX` are selection keys. They point to the concrete key that matches the field's value type, such as `MIN_NUMBER` for `min()` and `MIN_DATE` for `minDate()`. This is why `field().min()` and `field().max()` work for both numeric and date fields. + +This pairing of "strictest wins" is why calling `min(path.age, 18)` and `min(path.age, 21)` in two composed schemas works correctly. Each call registers its own validator that enforces its specific bound (so a value below either bound fails validation). Separately, each call contributes to the `MIN_NUMBER` key, and `state.min!()` reports the aggregate (`21`) so UI and custom controls can read the effective minimum. ### Writing a custom reducer diff --git a/adev/src/content/guide/forms/signals/form-logic.md b/adev/src/content/guide/forms/signals/form-logic.md index d7a13a5013de..f02ffe2a2802 100644 --- a/adev/src/content/guide/forms/signals/form-logic.md +++ b/adev/src/content/guide/forms/signals/form-logic.md @@ -458,16 +458,16 @@ Don't use debouncing if: Metadata attaches reactive data to a field. Validation rules use this system internally, and you can publish your own keys for application-specific information like help text, configuration, or computed display values. -Signal Forms provides six pre-defined metadata keys that built-in validators populate automatically: - -| Key | Populated by | Read via | -| ------------ | ------------- | --------------------- | -| `REQUIRED` | `required()` | `field().required()` | -| `MIN` | `min()` | `field().min()` | -| `MAX` | `max()` | `field().max()` | -| `MIN_LENGTH` | `minLength()` | `field().minLength()` | -| `MAX_LENGTH` | `maxLength()` | `field().maxLength()` | -| `PATTERN` | `pattern()` | `field().pattern()` | +Signal Forms provides pre-defined metadata keys that built-in validators populate automatically: + +| Key | Populated by | Read via | +| ------------ | -------------------- | --------------------- | +| `REQUIRED` | `required()` | `field().required()` | +| `MIN` | `min()`, `minDate()` | `field().min()` | +| `MAX` | `max()`, `maxDate()` | `field().max()` | +| `MIN_LENGTH` | `minLength()` | `field().minLength()` | +| `MAX_LENGTH` | `maxLength()` | `field().maxLength()` | +| `PATTERN` | `pattern()` | `field().pattern()` | The `[formField]` directive automatically binds five of these (`REQUIRED`, `MIN`, `MAX`, `MIN_LENGTH`, and `MAX_LENGTH`) to the corresponding HTML attribute on a native form control. `PATTERN` is the exception, because Signal Forms supports multiple patterns per field but the HTML `pattern` attribute accepts only a single regular expression.