@ahrowe/ui 0.52.0 → 0.53.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/cjs-types/common/otpInput/otpInput.types.d.ts +1 -1
  3. package/dist/cjs-types/index.d.ts +1 -1
  4. package/dist/cjs-types/services/formValidation/index.d.ts +1 -1
  5. package/dist/cjs-types/services/formValidation/useFormValidatorGroup.d.ts +1 -1
  6. package/dist/esm/common/datePicker/datePicker.mjs +1 -1
  7. package/dist/esm/common/datePicker/datePicker.mjs.map +1 -1
  8. package/dist/esm/common/dropdown/dropdown.mjs +1 -1
  9. package/dist/esm/common/dropdown/dropdown.mjs.map +1 -1
  10. package/dist/esm/common/iconPicker/iconPicker.mjs +1 -1
  11. package/dist/esm/common/iconPicker/iconPicker.mjs.map +1 -1
  12. package/dist/esm/common/input/input.mjs +1 -1
  13. package/dist/esm/common/input/input.mjs.map +1 -1
  14. package/dist/esm/common/inputDropdown/inputDropdown.mjs +1 -1
  15. package/dist/esm/common/inputDropdown/inputDropdown.mjs.map +1 -1
  16. package/dist/esm/common/multiSelect/multiSelect.mjs +1 -1
  17. package/dist/esm/common/multiSelect/multiSelect.mjs.map +1 -1
  18. package/dist/esm/common/numberInput/numberInput.mjs +1 -1
  19. package/dist/esm/common/numberInput/numberInput.mjs.map +1 -1
  20. package/dist/esm/common/otpInput/otpInput.mjs +1 -1
  21. package/dist/esm/common/otpInput/otpInput.mjs.map +1 -1
  22. package/dist/esm/common/otpInput/otpInput.types.mjs.map +1 -1
  23. package/dist/esm/common/radioGroup/radioGroup.mjs +1 -1
  24. package/dist/esm/common/radioGroup/radioGroup.mjs.map +1 -1
  25. package/dist/esm/common/timeInput/timeInput.mjs +1 -1
  26. package/dist/esm/common/timeInput/timeInput.mjs.map +1 -1
  27. package/dist/esm/services/formValidation/useFormValidatorGroup.mjs +1 -1
  28. package/dist/esm/services/formValidation/useFormValidatorGroup.mjs.map +1 -1
  29. package/dist/esm/services/formValidation/validatableComponent.mjs +1 -1
  30. package/dist/esm/services/formValidation/validatableComponent.mjs.map +1 -1
  31. package/dist/index.cjs +3 -3
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/types/common/otpInput/otpInput.types.d.ts +1 -1
  34. package/dist/types/index.d.ts +1 -1
  35. package/dist/types/services/formValidation/index.d.ts +1 -1
  36. package/dist/types/services/formValidation/useFormValidatorGroup.d.ts +1 -1
  37. package/docs/FormValidator.md +23 -1
  38. package/docs/FormValidatorGroup.md +5 -2
  39. package/docs/InputDropdown.md +1 -1
  40. package/docs/MultiSelect.md +1 -1
  41. package/docs/OtpInput.md +1 -1
  42. package/docs/RadioGroup.md +1 -1
  43. package/package.json +2 -2
@@ -34,7 +34,7 @@ export interface OtpInputProps extends Omit<HtmlProps, 'onChange'> {
34
34
  label?: string;
35
35
  isValid?: boolean;
36
36
  /** Shown in an error Tooltip on focus/hover once `isValid` is `false` (or, with a
37
- * `formValidator`, once it's touched and has an error). Persists until the value changes
37
+ * `formValidator`, once its `shouldShowError` is true). Persists until the value changes
38
38
  * `isValid`/`formValidator`'s error state itself — see `clearOnError` to auto-dismiss it. */
39
39
  errorMessage?: string;
40
40
  isRequired?: boolean;
@@ -170,6 +170,6 @@ export { default as Wizard } from './common/wizard/index.js';
170
170
  export * from './common/wizard/index.js';
171
171
  export * from './common/hooks/index.js';
172
172
  export { FormValidator, FormValidatorGroup, Validators, ValidatableComponent, useFormValidator, useFormValidatorGroup, } from './services/formValidation/index.js';
173
- export type { ValidationError, ValidatorFunction, ErrorParser, FormValidatorGroupOptions, InferGroupValues, MergedValues, } from './services/formValidation/index.js';
173
+ export type { ValidationError, ValidatorFunction, ErrorParser, FormValidatorGroupOptions, InferGroupValues, MergedValues, ShowErrorsMode, FormValidatorOptions, } from './services/formValidation/index.js';
174
174
  export { mapLocale, setLocale, getLocale } from './services/localization.js';
175
175
  export { isNumber, isDate, isDay, isISODate, isEmail, isBic, isURL, hasEightPasswordChars, hasOnePasswordLetter, hasOnePasswordDigit, isPassword, } from '@ahrowe/form-validation';
@@ -2,4 +2,4 @@ export { default as FormValidatorGroup } from './FormValidatorGroupWithForm.js';
2
2
  export { default as ValidatableComponent } from './validatableComponent.js';
3
3
  export { useFormValidatorGroup } from './useFormValidatorGroup.js';
4
4
  export { FormValidator, Validators, useFormValidator } from '@ahrowe/form-validation';
5
- export type { ValidationError, ValidatorFunction, ErrorParser, FormValidatorGroupOptions, InferGroupValues, MergedValues, } from '@ahrowe/form-validation';
5
+ export type { ValidationError, ValidatorFunction, ErrorParser, FormValidatorGroupOptions, InferGroupValues, MergedValues, ShowErrorsMode, FormValidatorOptions, } from '@ahrowe/form-validation';
@@ -7,4 +7,4 @@ import { default as FormValidatorGroupWithForm } from './FormValidatorGroupWithF
7
7
  * (useFormValidatorGroupInstance) so this and the pure hook share one
8
8
  * implementation of "subscribe a function component to a group".
9
9
  */
10
- export declare function useFormValidatorGroup<TGroup extends FormGroup = FormGroup, TInitial = InferGroupValues<TGroup>>(groupDef: TGroup, { initialValues, onSubmit, onValidate, errorParser, onSubmitError }?: FormValidatorGroupOptions<TGroup, TInitial>): FormValidatorGroupWithForm<TGroup, TInitial>;
10
+ export declare function useFormValidatorGroup<TGroup extends FormGroup = FormGroup, TInitial = InferGroupValues<TGroup>>(groupDef: TGroup, { onSubmit, onValidate, ...options }?: FormValidatorGroupOptions<TGroup, TInitial>): FormValidatorGroupWithForm<TGroup, TInitial>;
@@ -5,7 +5,7 @@
5
5
  **Keywords:** form field validation, field validator
6
6
 
7
7
  **Import:** `import { FormValidator, Validators, useFormValidator } from '@ahrowe/ui'`
8
- **Types:** `import type { ValidatorFunction, ValidationError } from '@ahrowe/ui'`
8
+ **Types:** `import type { ValidatorFunction, ValidationError, ShowErrorsMode, FormValidatorOptions } from '@ahrowe/ui'`
9
9
 
10
10
  ## Basic usage
11
11
 
@@ -39,6 +39,26 @@ nameValidator.dirty // value has changed since creation
39
39
  >
40
40
  > To pre-fill fields when **editing an existing entity**, don't seed values here — pass them as `initialValues` on a `FormValidatorGroup` (see [FormValidatorGroup.md](FormValidatorGroup.md)). That keeps the empty value as the validator's true default so `reset()` works correctly.
41
41
 
42
+ ## When a component shows the error
43
+
44
+ A form component marks the field red, and shows the message on hover or focus, while
45
+ `shouldShowError` is true: the field has an error, and its `showErrors` mode allows it. The mode is
46
+ the constructor's third argument:
47
+
48
+ | `showErrors` | The error shows |
49
+ |--------------|-----------------|
50
+ | `'touched'` (default) | once the field is `touched`: left (blurred) by the user, a picker's panel closed, or marked by the group's `validate()` on submit |
51
+ | `'dirty'` | once the field is `dirty` (changed by the user) or `touched`. Suits password rules; an email field would complain after the first letter |
52
+ | `'always'` | from the start. For an edit form loaded with data that is already invalid |
53
+
54
+ ```tsx
55
+ const password = new FormValidator('', [Validators.password()], { showErrors: 'dirty' });
56
+ ```
57
+
58
+ Once shown, an error follows every edit and disappears as soon as the value is valid. A
59
+ `FormValidatorGroup` takes `showErrors` too, as the default for every field that does not set its
60
+ own (FormValidatorGroup.md).
61
+
42
62
  ## Re-rendering a function component when the value changes
43
63
 
44
64
  A `FormValidator` lives outside React state, so changing it does **not** by itself re-render a function component. When you read `validator.value` / `validator.hasError()` directly in render (rather than only handing the instance to a form component), subscribe with `useFormValidator` so the component re-renders on every change:
@@ -82,6 +102,8 @@ const agreedValidator = new FormValidator<boolean>(false, [Validators.required()
82
102
  | `validate` | `() => void` | Clear errors and run every validator against the current value. |
83
103
  | `touched` | `boolean` (get/set) | User has entered and left the field. |
84
104
  | `dirty` | `boolean` (get/set) | Value changed since creation. |
105
+ | `showErrors` | `ShowErrorsMode \| undefined` (get/set) | When errors show: `'touched'`, `'dirty'` or `'always'`. `undefined` falls back to the group's, then `'touched'`. |
106
+ | `shouldShowError` | `boolean` (get) | Has an error that `showErrors` says to show now. What every form component reads. |
85
107
  | `errors` | `ValidationError[]` (get) | All current errors (`{ id, message }`). |
86
108
  | `hasError` | `(id?: string) => boolean` | Any error when called with no arg; a specific error by `id` otherwise. |
87
109
  | `getCurrentErrorMessage` | `() => string \| null` | First error message, or `null`. |
@@ -69,7 +69,8 @@ const editForm = useFormValidatorGroup(fields, { initialValues: user, onSubmit:
69
69
  3. On the **first** submit, runs `validate()` (validates every field + marks them touched); on later submits, runs `onValidate` only.
70
70
  4. If any field error or a custom validation error exists → calls `onSubmitError` with it, increments `submitCount`, and stops (does **not** call `onSubmit`).
71
71
  5. Otherwise calls `onSubmit(values, group)`. If it throws, the error is passed to `parseError(err, true)` to surface field-level or generic errors.
72
- 6. Sets `isSubmitting` false.
72
+ 6. After a successful `onSubmit`, clears dirty/touched on every field.
73
+ 7. Sets `isSubmitting` false.
73
74
 
74
75
  By default `onSubmitError` shows the first error as an error **toast** (see `Toast`). Override it via `options.onSubmitError`.
75
76
 
@@ -84,6 +85,8 @@ interface FormValidatorGroupOptions {
84
85
  onValidate?: (group) => ValidationError | null | void; // extra cross-field validation
85
86
  errorParser?: (err: unknown) => ValidationError[]; // turn an API error into field errors
86
87
  onSubmitError?: (errors: ValidationError[]) => void; // default: error toast of the first message
88
+ showErrors?: 'touched' | 'dirty' | 'always'; // default for every field that does not set
89
+ // its own; see FormValidator.md
87
90
  }
88
91
  ```
89
92
 
@@ -115,7 +118,7 @@ useFormValidatorGroup(
115
118
  | `setValidator` | `(name, validator) => this` | Swap a field's `FormValidator` instance. |
116
119
  | `reset` | `(overrides?) => void` | Restore fields to their post-creation values (or `overrides`), clear dirty/touched, fire `onReset`. |
117
120
  | `resetDirtyAndTouched` | `() => void` | Clear dirty/touched on all fields without changing values. |
118
- | `parseError` | `(err, isInternal?) => void` | Run `errorParser` and push field errors onto matching validators; otherwise a generic error. |
121
+ | `parseError` | `(err, isInternal?) => void` | Run `errorParser`, push field errors onto matching validators and mark those touched; otherwise a generic error. |
119
122
  | `getFormError` | `() => ValidationError \| null` | First error across all fields, or `null`. |
120
123
  | `isSubmitting` | `boolean` (get) | True while `submit` is running. |
121
124
  | `isDirty` | `boolean` (get) | Any field changed since creation. |
@@ -64,7 +64,7 @@ interface InputDropdownItem {
64
64
  | `placeholder` | `string` | |
65
65
  | `readOnly` | `boolean` | Make the field read-only — typing is blocked, the list won't open, and keyboard navigation/selection is disabled. Default `false` |
66
66
  | `labels` | `InputDropdownLabels` | Text the component renders of its own, see **Labels** below |
67
- | `formValidator` | `FormValidator` | Connects to validation — shows the error state/message once the field has been touched (blurred), and the required mark (`*`) when the validator has a `required` validator |
67
+ | `formValidator` | `FormValidator` | Connects to validation — shows the error state/message once the validator's `shouldShowError` is true (by default once the field has been blurred, see FormValidator.md), and the required mark (`*`) when the validator has a `required` validator |
68
68
  | `error` | `string` | Manual error message (used when no `formValidator` is given) |
69
69
  | `isRequired` | `boolean` | Shows the required mark (`*`) next to the label. Also inferred automatically when `formValidator` has a `required` validator. Default `false` |
70
70
 
@@ -49,7 +49,7 @@ import { Presentation } from '@ahrowe/ui';
49
49
 
50
50
  **The label floats like `Input`'s.** It rests vertically centred over the control and rises into a notch cut out of the border once the field is focused, open, or has a selection, using the same `fieldset`/`legend` construction and the same `--input-notch-height` geometry as `Input`. A `MultiSelect` sitting next to an `Input` in a form animates with it rather than against it. Set `alwaysFloatLabel` to pin it up, which is what you want when a `placeholder` should always be readable: with a label present the placeholder is otherwise faded in on a delay matching the label's animation, so the two never sit on top of each other. Without a label there is nothing to wait for and it fades in at once.
51
51
 
52
- **Errors:** with a `formValidator` the field stays quiet until it has been touched (the first time the panel closes), then shows the validator's message while the field is hovered or focused, and drops it as soon as the validator is happy again. Without a validator, `isValid` and `errorMessage` are plain props and nothing clears them for you: derive them from your own state, as in `isValid={picked.length > 0}`.
52
+ **Errors:** with a `formValidator` the field stays quiet until the validator's `showErrors` lets the error through (by default once touched: the first time the panel closes, see FormValidator.md), then shows the validator's message while the field is hovered or focused, and drops it as soon as the validator is happy again. Without a validator, `isValid` and `errorMessage` are plain props and nothing clears them for you: derive them from your own state, as in `isValid={picked.length > 0}`.
53
53
 
54
54
  **Height:** a MultiSelect with one row of chips is exactly as tall as an `Input` or `Dropdown` beside it, so a form row lines up. It grows only when the chips wrap onto a second row. The chips render at `0.8em` to fit: a standalone `Chip` is nearly as tall as the whole field, because its remove button carries `ActionIcon`'s padding around a `1em` icon.
55
55
 
package/docs/OtpInput.md CHANGED
@@ -60,7 +60,7 @@ const codeValidator = new FormValidator('', [Validators.required(), Validators.m
60
60
 
61
61
  **Interaction:** typing a character fills the focused box and jumps to the next one; on the last box, it blurs instead. Backspace on a filled box just clears it; on an already-empty box it clears and focuses the previous one. Left/Right arrow keys move focus without changing values. Pasting (or an SMS autofill suggestion landing in one box) distributes the pasted text across boxes starting from wherever it lands. Focusing a box selects its content, so typing immediately overwrites it rather than requiring a manual clear first.
62
62
 
63
- **Error display:** like `Input`/`RadioGroup`, the boxes get a red border once errored, and the message itself appears in an error `Tooltip` on focus or hover rather than sitting inline under the boxes all the time. With a `formValidator`, this only shows once the field has been *touched* (focus has left the group) **and** has an error — so a required-field error doesn't flash before the user has even had a chance to type. With manual `isValid`/`errorMessage`, there's no touched-gating (the consumer already controls when to set `isValid={false}`, e.g. only after a failed verification call). Either way, the error persists across renders until whatever's driving it — `isValid`, or the `formValidator`'s own error state — actually changes; `OtpInput` never silently clears it on its own. See `clearOnError` below for the one built-in exception.
63
+ **Error display:** like `Input`/`RadioGroup`, the boxes get a red border once errored, and the message itself appears in an error `Tooltip` on focus or hover rather than sitting inline under the boxes all the time. With a `formValidator`, this shows once the validator's `shouldShowError` is true: by default once the field has been *touched* (focus has left the group) **and** has an error, so a required-field error doesn't flash before the user has even had a chance to type (`showErrors`, see FormValidator.md). With manual `isValid`/`errorMessage`, there's no touched-gating (the consumer already controls when to set `isValid={false}`, e.g. only after a failed verification call). Either way, the error persists across renders until whatever's driving it — `isValid`, or the `formValidator`'s own error state — actually changes; `OtpInput` never silently clears it on its own. See `clearOnError` below for the one built-in exception.
64
64
 
65
65
  **`onComplete`:** fires once per genuinely new completion — i.e. the code going from fewer than `length` characters to exactly `length`. Editing individual boxes of an already-complete code (retyping a digit that was wrong) does **not** refire it on every keystroke, since the code never drops below `length` in between; it fires again only after the code is cleared (manually, or via `clearOnError` below) and refilled.
66
66
 
@@ -71,7 +71,7 @@ interface RadioOption {
71
71
  | `errorMessage` | `string` | Manual error message (when not using a `formValidator`) |
72
72
  | `isValid` | `boolean` | Manual valid state (default `true`) |
73
73
 
74
- **Validation:** like `Input`, an errored group shows an error `Tooltip` and turns its unselected circles red. With a `formValidator` the error surfaces only after the field is *touched* (focus leaves the group), then on focus or hover.
74
+ **Validation:** like `Input`, an errored group shows an error `Tooltip` and turns its unselected circles red. With a `formValidator` the error surfaces once the validator's `showErrors` allows it (by default after the field is *touched*: focus leaves the group, see FormValidator.md), then on focus or hover.
75
75
 
76
76
  **Global defaults:** adopts `ConfigProvider` — e.g. `defaultProps={{ RadioGroup: { orientation: RadioGroupOrientation.Horizontal } }}`. See [ConfigProvider.md](ConfigProvider.md).
77
77
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ahrowe/ui",
3
- "version": "0.52.0",
3
+ "version": "0.53.0",
4
4
  "description": "A React UI component library with theming, CSS Modules, and TypeScript support",
5
5
  "keywords": [
6
6
  "agent",
@@ -106,7 +106,7 @@
106
106
  "prepare": "husky"
107
107
  },
108
108
  "dependencies": {
109
- "@ahrowe/form-validation": "^0.3.0",
109
+ "@ahrowe/form-validation": "^0.4.0",
110
110
  "@dnd-kit/core": "^6.3.1",
111
111
  "@dnd-kit/sortable": "^10.0.0",
112
112
  "@dnd-kit/utilities": "^3.2.2",