@cdx-ui/utils 0.1.7 → 0.2.1
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 +9 -7
- package/lib/commonjs/form-control/index.js +6 -0
- package/lib/commonjs/form-control/index.js.map +1 -1
- package/lib/commonjs/form-control/useFormControl.js +39 -1
- package/lib/commonjs/form-control/useFormControl.js.map +1 -1
- package/lib/module/form-control/index.js +1 -1
- package/lib/module/form-control/index.js.map +1 -1
- package/lib/module/form-control/useFormControl.js +38 -1
- package/lib/module/form-control/useFormControl.js.map +1 -1
- package/lib/typescript/form-control/index.d.ts +1 -1
- package/lib/typescript/form-control/index.d.ts.map +1 -1
- package/lib/typescript/form-control/useFormControl.d.ts +21 -0
- package/lib/typescript/form-control/useFormControl.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/form-control/index.ts +2 -0
- package/src/form-control/useFormControl.test.tsx +50 -0
- package/src/form-control/useFormControl.tsx +58 -1
package/README.md
CHANGED
|
@@ -46,13 +46,14 @@ Used internally by `@cdx-ui/components` to propagate variant props (size, color,
|
|
|
46
46
|
|
|
47
47
|
Low-level form-field state used by the `Field` and `Input` primitives.
|
|
48
48
|
|
|
49
|
-
| Export
|
|
50
|
-
|
|
|
51
|
-
| `useFormControlRoot(props)`
|
|
52
|
-
| `useFormControl(props)`
|
|
53
|
-
| `useFormControlContext()`
|
|
54
|
-
| `useReportFormControlLabelFocus(isActive)`
|
|
55
|
-
| `
|
|
49
|
+
| Export | Description |
|
|
50
|
+
| --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
51
|
+
| `useFormControlRoot(props)` | Creates the root state object for a form field (id generation, tracks `hasFeedbackText`/`hasHelpText` for describedby assembly). |
|
|
52
|
+
| `useFormControl(props)` | Merges component-level props with inherited form-control context. Assembles `aria-describedby` from feedback/help text ids. |
|
|
53
|
+
| `useFormControlContext()` | Reads form-control state from the nearest provider. |
|
|
54
|
+
| `useReportFormControlLabelFocus(isActive)` | Reports focus/active state to the parent `FormControlContext` — used by `Input`/`Select.Trigger` to trigger `isFocused` styling on the associated label. |
|
|
55
|
+
| `useReportFormControlValidation(isInvalid, errorMessage)` | Reports a self-validating control's invalid state and message to the parent `Field` — used by `DatePicker` so `Field.Error` / `Field.Helper` anywhere in the field reflect it. |
|
|
56
|
+
| `FormControlContext` | The React context object for form-control state propagation. |
|
|
56
57
|
|
|
57
58
|
## Exported types
|
|
58
59
|
|
|
@@ -66,6 +67,7 @@ Low-level form-field state used by the `Field` and `Input` primitives.
|
|
|
66
67
|
| `FormControlRootProps` | form-control |
|
|
67
68
|
| `FormControlRootReturn` | form-control |
|
|
68
69
|
| `FormControlState` | form-control |
|
|
70
|
+
| `FormControlValidation` | form-control |
|
|
69
71
|
| `StyleContextMap` | style |
|
|
70
72
|
|
|
71
73
|
## Testing
|
|
@@ -33,5 +33,11 @@ Object.defineProperty(exports, "useReportFormControlLabelFocus", {
|
|
|
33
33
|
return _useFormControl.useReportFormControlLabelFocus;
|
|
34
34
|
}
|
|
35
35
|
});
|
|
36
|
+
Object.defineProperty(exports, "useReportFormControlValidation", {
|
|
37
|
+
enumerable: true,
|
|
38
|
+
get: function () {
|
|
39
|
+
return _useFormControl.useReportFormControlValidation;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
36
42
|
var _useFormControl = require("./useFormControl");
|
|
37
43
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_useFormControl","require"],"sourceRoot":"../../../src","sources":["form-control/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"names":["_useFormControl","require"],"sourceRoot":"../../../src","sources":["form-control/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,IAAAA,eAAA,GAAAC,OAAA","ignoreList":[]}
|
|
@@ -8,6 +8,7 @@ exports.useFormControl = useFormControl;
|
|
|
8
8
|
exports.useFormControlContext = void 0;
|
|
9
9
|
exports.useFormControlRoot = useFormControlRoot;
|
|
10
10
|
exports.useReportFormControlLabelFocus = useReportFormControlLabelFocus;
|
|
11
|
+
exports.useReportFormControlValidation = useReportFormControlValidation;
|
|
11
12
|
var _react = _interopRequireDefault(require("react"));
|
|
12
13
|
var _reactNative = require("react-native");
|
|
13
14
|
var _accessibilityUtils = require("../common/accessibilityUtils");
|
|
@@ -22,6 +23,8 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
|
|
|
22
23
|
* minimal avoids a typed lie when a non-input bridge is registered.
|
|
23
24
|
*/
|
|
24
25
|
|
|
26
|
+
/** Validation a child control reports to its enclosing `Field` (see `useReportFormControlValidation`). */
|
|
27
|
+
|
|
25
28
|
const FormControlContext = exports.FormControlContext = /*#__PURE__*/_react.default.createContext({});
|
|
26
29
|
|
|
27
30
|
/** Props for the FormControl root; known fields plus passthrough to the styled root box. */
|
|
@@ -58,6 +61,7 @@ function useFormControlRoot(props) {
|
|
|
58
61
|
*/
|
|
59
62
|
const [hasHelpText, setHasHelpText] = _react.default.useState(false);
|
|
60
63
|
const [isLabelFocused, setIsLabelFocused] = _react.default.useState(false);
|
|
64
|
+
const [controlValidation, setControlValidation] = _react.default.useState(undefined);
|
|
61
65
|
const inputRef = _react.default.useRef(null);
|
|
62
66
|
const focusInput = _react.default.useCallback(() => {
|
|
63
67
|
const node = inputRef.current;
|
|
@@ -67,9 +71,11 @@ function useFormControlRoot(props) {
|
|
|
67
71
|
}, []);
|
|
68
72
|
const context = {
|
|
69
73
|
isRequired: Boolean(isRequired),
|
|
70
|
-
isInvalid: Boolean(isInvalid),
|
|
74
|
+
isInvalid: Boolean(isInvalid) || Boolean(controlValidation?.isInvalid),
|
|
71
75
|
isReadOnly: Boolean(isReadOnly),
|
|
72
76
|
isDisabled: Boolean(isDisabled),
|
|
77
|
+
errorMessage: controlValidation?.errorMessage,
|
|
78
|
+
setControlValidation,
|
|
73
79
|
hasFeedbackText,
|
|
74
80
|
setHasFeedbackText,
|
|
75
81
|
hasHelpText,
|
|
@@ -109,6 +115,38 @@ function useReportFormControlLabelFocus(isActive) {
|
|
|
109
115
|
}, [isActive, setIsLabelFocused]);
|
|
110
116
|
}
|
|
111
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Reports a control's own validation (e.g. `DatePicker` bounds) to the enclosing `Field`, so the
|
|
120
|
+
* field root, `Field.Helper`, and `Field.Error` reflect it wherever they sit inside the field.
|
|
121
|
+
* Runs in a layout effect so the invalid state and message reach the field before paint.
|
|
122
|
+
*
|
|
123
|
+
* @returns `false` outside a `Field` (nothing to report to) — the caller must provide the state itself.
|
|
124
|
+
*/
|
|
125
|
+
function useReportFormControlValidation(isInvalid, errorMessage) {
|
|
126
|
+
const {
|
|
127
|
+
setControlValidation
|
|
128
|
+
} = useFormControlContext();
|
|
129
|
+
_react.default.useLayoutEffect(() => {
|
|
130
|
+
if (typeof setControlValidation !== 'function') {
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
setControlValidation(prev => prev?.isInvalid === isInvalid && prev.errorMessage === errorMessage ? prev : {
|
|
134
|
+
isInvalid,
|
|
135
|
+
errorMessage
|
|
136
|
+
});
|
|
137
|
+
return undefined;
|
|
138
|
+
}, [errorMessage, isInvalid, setControlValidation]);
|
|
139
|
+
_react.default.useLayoutEffect(() => {
|
|
140
|
+
if (typeof setControlValidation !== 'function') {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
return () => {
|
|
144
|
+
setControlValidation(undefined);
|
|
145
|
+
};
|
|
146
|
+
}, [setControlValidation]);
|
|
147
|
+
return typeof setControlValidation === 'function';
|
|
148
|
+
}
|
|
149
|
+
|
|
112
150
|
/**
|
|
113
151
|
* Hook that merges local field props with the nearest form field context
|
|
114
152
|
* and returns normalised HTML / ARIA attributes for input elements.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_react","_interopRequireDefault","require","_reactNative","_accessibilityUtils","e","__esModule","default","FormControlContext","exports","React","createContext","useFormControlRoot","props","id","idProp","name","isRequired","isInvalid","isDisabled","isReadOnly","htmlProps","reactId","useId","labelId","feedbackId","helpTextId","hasFeedbackText","setHasFeedbackText","useState","hasHelpText","setHasHelpText","isLabelFocused","setIsLabelFocused","inputRef","useRef","focusInput","useCallback","node","current","focus","context","Boolean","Platform","OS","useReportFormControlLabelFocus","isActive","useFormControlContext","useLayoutEffect","
|
|
1
|
+
{"version":3,"names":["_react","_interopRequireDefault","require","_reactNative","_accessibilityUtils","e","__esModule","default","FormControlContext","exports","React","createContext","useFormControlRoot","props","id","idProp","name","isRequired","isInvalid","isDisabled","isReadOnly","htmlProps","reactId","useId","labelId","feedbackId","helpTextId","hasFeedbackText","setHasFeedbackText","useState","hasHelpText","setHasHelpText","isLabelFocused","setIsLabelFocused","controlValidation","setControlValidation","undefined","inputRef","useRef","focusInput","useCallback","node","current","focus","context","Boolean","errorMessage","Platform","OS","useReportFormControlLabelFocus","isActive","useFormControlContext","useLayoutEffect","useReportFormControlValidation","prev","useFormControl","field","describedBy","push","ariaDescribedBy","join","cleanProps","disabled","readOnly","required","ariaAttr","useContext"],"sourceRoot":"../../../src","sources":["form-control/useFormControl.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
export { FormControlContext, useFormControl, useFormControlContext, useFormControlRoot, useReportFormControlLabelFocus } from './useFormControl';
|
|
3
|
+
export { FormControlContext, useFormControl, useFormControlContext, useFormControlRoot, useReportFormControlLabelFocus, useReportFormControlValidation } from './useFormControl';
|
|
4
4
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["FormControlContext","useFormControl","useFormControlContext","useFormControlRoot","useReportFormControlLabelFocus"],"sourceRoot":"../../../src","sources":["form-control/index.ts"],"mappings":";;AAAA,
|
|
1
|
+
{"version":3,"names":["FormControlContext","useFormControl","useFormControlContext","useFormControlRoot","useReportFormControlLabelFocus","useReportFormControlValidation"],"sourceRoot":"../../../src","sources":["form-control/index.ts"],"mappings":";;AAAA,SAQEA,kBAAkB,EAClBC,cAAc,EACdC,qBAAqB,EACrBC,kBAAkB,EAClBC,8BAA8B,EAC9BC,8BAA8B,QACzB,kBAAkB","ignoreList":[]}
|
|
@@ -14,6 +14,8 @@ import { ariaAttr } from '../common/accessibilityUtils';
|
|
|
14
14
|
* minimal avoids a typed lie when a non-input bridge is registered.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
/** Validation a child control reports to its enclosing `Field` (see `useReportFormControlValidation`). */
|
|
18
|
+
|
|
17
19
|
export const FormControlContext = /*#__PURE__*/React.createContext({});
|
|
18
20
|
|
|
19
21
|
/** Props for the FormControl root; known fields plus passthrough to the styled root box. */
|
|
@@ -50,6 +52,7 @@ export function useFormControlRoot(props) {
|
|
|
50
52
|
*/
|
|
51
53
|
const [hasHelpText, setHasHelpText] = React.useState(false);
|
|
52
54
|
const [isLabelFocused, setIsLabelFocused] = React.useState(false);
|
|
55
|
+
const [controlValidation, setControlValidation] = React.useState(undefined);
|
|
53
56
|
const inputRef = React.useRef(null);
|
|
54
57
|
const focusInput = React.useCallback(() => {
|
|
55
58
|
const node = inputRef.current;
|
|
@@ -59,9 +62,11 @@ export function useFormControlRoot(props) {
|
|
|
59
62
|
}, []);
|
|
60
63
|
const context = {
|
|
61
64
|
isRequired: Boolean(isRequired),
|
|
62
|
-
isInvalid: Boolean(isInvalid),
|
|
65
|
+
isInvalid: Boolean(isInvalid) || Boolean(controlValidation?.isInvalid),
|
|
63
66
|
isReadOnly: Boolean(isReadOnly),
|
|
64
67
|
isDisabled: Boolean(isDisabled),
|
|
68
|
+
errorMessage: controlValidation?.errorMessage,
|
|
69
|
+
setControlValidation,
|
|
65
70
|
hasFeedbackText,
|
|
66
71
|
setHasFeedbackText,
|
|
67
72
|
hasHelpText,
|
|
@@ -101,6 +106,38 @@ export function useReportFormControlLabelFocus(isActive) {
|
|
|
101
106
|
}, [isActive, setIsLabelFocused]);
|
|
102
107
|
}
|
|
103
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Reports a control's own validation (e.g. `DatePicker` bounds) to the enclosing `Field`, so the
|
|
111
|
+
* field root, `Field.Helper`, and `Field.Error` reflect it wherever they sit inside the field.
|
|
112
|
+
* Runs in a layout effect so the invalid state and message reach the field before paint.
|
|
113
|
+
*
|
|
114
|
+
* @returns `false` outside a `Field` (nothing to report to) — the caller must provide the state itself.
|
|
115
|
+
*/
|
|
116
|
+
export function useReportFormControlValidation(isInvalid, errorMessage) {
|
|
117
|
+
const {
|
|
118
|
+
setControlValidation
|
|
119
|
+
} = useFormControlContext();
|
|
120
|
+
React.useLayoutEffect(() => {
|
|
121
|
+
if (typeof setControlValidation !== 'function') {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
setControlValidation(prev => prev?.isInvalid === isInvalid && prev.errorMessage === errorMessage ? prev : {
|
|
125
|
+
isInvalid,
|
|
126
|
+
errorMessage
|
|
127
|
+
});
|
|
128
|
+
return undefined;
|
|
129
|
+
}, [errorMessage, isInvalid, setControlValidation]);
|
|
130
|
+
React.useLayoutEffect(() => {
|
|
131
|
+
if (typeof setControlValidation !== 'function') {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
return () => {
|
|
135
|
+
setControlValidation(undefined);
|
|
136
|
+
};
|
|
137
|
+
}, [setControlValidation]);
|
|
138
|
+
return typeof setControlValidation === 'function';
|
|
139
|
+
}
|
|
140
|
+
|
|
104
141
|
/**
|
|
105
142
|
* Hook that merges local field props with the nearest form field context
|
|
106
143
|
* and returns normalised HTML / ARIA attributes for input elements.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["React","Platform","ariaAttr","FormControlContext","createContext","useFormControlRoot","props","id","idProp","name","isRequired","isInvalid","isDisabled","isReadOnly","htmlProps","reactId","useId","labelId","feedbackId","helpTextId","hasFeedbackText","setHasFeedbackText","useState","hasHelpText","setHasHelpText","isLabelFocused","setIsLabelFocused","inputRef","useRef","focusInput","useCallback","node","current","focus","context","Boolean","OS","useReportFormControlLabelFocus","isActive","useFormControlContext","useLayoutEffect","
|
|
1
|
+
{"version":3,"names":["React","Platform","ariaAttr","FormControlContext","createContext","useFormControlRoot","props","id","idProp","name","isRequired","isInvalid","isDisabled","isReadOnly","htmlProps","reactId","useId","labelId","feedbackId","helpTextId","hasFeedbackText","setHasFeedbackText","useState","hasHelpText","setHasHelpText","isLabelFocused","setIsLabelFocused","controlValidation","setControlValidation","undefined","inputRef","useRef","focusInput","useCallback","node","current","focus","context","Boolean","errorMessage","OS","useReportFormControlLabelFocus","isActive","useFormControlContext","useLayoutEffect","useReportFormControlValidation","prev","useFormControl","field","describedBy","push","ariaDescribedBy","join","cleanProps","disabled","readOnly","required","useContext"],"sourceRoot":"../../../src","sources":["form-control/useFormControl.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { type Focusable, type FormControlContextValue, type FormControlFieldProps, type FormControlRootProps, type FormControlRootReturn, type FormControlState, FormControlContext, useFormControl, useFormControlContext, useFormControlRoot, useReportFormControlLabelFocus, } from './useFormControl';
|
|
1
|
+
export { type Focusable, type FormControlContextValue, type FormControlFieldProps, type FormControlRootProps, type FormControlRootReturn, type FormControlState, type FormControlValidation, FormControlContext, useFormControl, useFormControlContext, useFormControlRoot, useReportFormControlLabelFocus, useReportFormControlValidation, } from './useFormControl';
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/form-control/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,SAAS,EACd,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,kBAAkB,EAClB,cAAc,EACd,qBAAqB,EACrB,kBAAkB,EAClB,8BAA8B,GAC/B,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/form-control/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,SAAS,EACd,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,kBAAkB,EAClB,cAAc,EACd,qBAAqB,EACrB,kBAAkB,EAClB,8BAA8B,EAC9B,8BAA8B,GAC/B,MAAM,kBAAkB,CAAC"}
|
|
@@ -18,7 +18,17 @@ export interface FormControlState {
|
|
|
18
18
|
isDisabled: boolean;
|
|
19
19
|
isReadOnly: boolean;
|
|
20
20
|
}
|
|
21
|
+
/** Validation a child control reports to its enclosing `Field` (see `useReportFormControlValidation`). */
|
|
22
|
+
export interface FormControlValidation {
|
|
23
|
+
isInvalid: boolean;
|
|
24
|
+
/** Message `Field.Error` renders when it has no children of its own. */
|
|
25
|
+
errorMessage?: string;
|
|
26
|
+
}
|
|
21
27
|
export interface FormControlContextValue extends FormControlState {
|
|
28
|
+
/** Message reported by the field's control; `Field.Error` falls back to it. */
|
|
29
|
+
errorMessage?: string;
|
|
30
|
+
/** Lets a control that validates itself report into the field. Set by the field root only. */
|
|
31
|
+
setControlValidation?: React.Dispatch<React.SetStateAction<FormControlValidation | undefined>>;
|
|
22
32
|
hasFeedbackText: boolean;
|
|
23
33
|
hasHelpText: boolean;
|
|
24
34
|
setHasFeedbackText: React.Dispatch<React.SetStateAction<boolean>>;
|
|
@@ -54,9 +64,12 @@ export type FormControlRootProps = Partial<FormControlState> & {
|
|
|
54
64
|
} & HTMLProps;
|
|
55
65
|
export interface FormControlRootReturn {
|
|
56
66
|
isRequired: boolean;
|
|
67
|
+
/** `isInvalid` prop OR-ed with the validation a child control reported. */
|
|
57
68
|
isInvalid: boolean;
|
|
58
69
|
isReadOnly: boolean;
|
|
59
70
|
isDisabled: boolean;
|
|
71
|
+
errorMessage?: string;
|
|
72
|
+
setControlValidation: React.Dispatch<React.SetStateAction<FormControlValidation | undefined>>;
|
|
60
73
|
hasFeedbackText: boolean;
|
|
61
74
|
setHasFeedbackText: React.Dispatch<React.SetStateAction<boolean>>;
|
|
62
75
|
hasHelpText: boolean;
|
|
@@ -82,6 +95,14 @@ export declare function useFormControlRoot(props: FormControlRootProps): FormCon
|
|
|
82
95
|
* No-ops outside a `Field` (no `setIsLabelFocused` on context).
|
|
83
96
|
*/
|
|
84
97
|
export declare function useReportFormControlLabelFocus(isActive: boolean): void;
|
|
98
|
+
/**
|
|
99
|
+
* Reports a control's own validation (e.g. `DatePicker` bounds) to the enclosing `Field`, so the
|
|
100
|
+
* field root, `Field.Helper`, and `Field.Error` reflect it wherever they sit inside the field.
|
|
101
|
+
* Runs in a layout effect so the invalid state and message reach the field before paint.
|
|
102
|
+
*
|
|
103
|
+
* @returns `false` outside a `Field` (nothing to report to) — the caller must provide the state itself.
|
|
104
|
+
*/
|
|
105
|
+
export declare function useReportFormControlValidation(isInvalid: boolean, errorMessage: string | undefined): boolean;
|
|
85
106
|
/**
|
|
86
107
|
* Hook that merges local field props with the nearest form field context
|
|
87
108
|
* and returns normalised HTML / ARIA attributes for input elements.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFormControl.d.ts","sourceRoot":"","sources":["../../../src/form-control/useFormControl.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEzC;;;;;;;;GAQG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,IAAI,IAAI,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,uBAAwB,SAAQ,gBAAgB;IAC/D,eAAe,EAAE,OAAO,CAAC;IACzB,WAAW,EAAE,OAAO,CAAC;IACrB,kBAAkB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAClE,cAAc,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,SAAS,CAAC;IACrB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IAC7C,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,0GAA0G;IAC1G,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,iBAAiB,CAAC,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;CACnE;AAED,eAAO,MAAM,kBAAkB,iDAA4D,CAAC;AAE5F,MAAM,WAAW,qBAAsB,SAAQ,OAAO,CAAC,gBAAgB,CAAC;IACtE,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,4FAA4F;AAC5F,MAAM,MAAM,oBAAoB,GAAG,OAAO,CAAC,gBAAgB,CAAC,GAAG;IAC7D,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,SAAS,CAAC;AAEd,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,eAAe,EAAE,OAAO,CAAC;IACzB,kBAAkB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAClE,WAAW,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,SAAS,CAAC;IACrB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IAC7C,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,cAAc,EAAE,OAAO,CAAC;IACxB,iBAAiB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;CAClE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,oBAAoB,GAAG,qBAAqB,
|
|
1
|
+
{"version":3,"file":"useFormControl.d.ts","sourceRoot":"","sources":["../../../src/form-control/useFormControl.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -5,9 +5,11 @@ export {
|
|
|
5
5
|
type FormControlRootProps,
|
|
6
6
|
type FormControlRootReturn,
|
|
7
7
|
type FormControlState,
|
|
8
|
+
type FormControlValidation,
|
|
8
9
|
FormControlContext,
|
|
9
10
|
useFormControl,
|
|
10
11
|
useFormControlContext,
|
|
11
12
|
useFormControlRoot,
|
|
12
13
|
useReportFormControlLabelFocus,
|
|
14
|
+
useReportFormControlValidation,
|
|
13
15
|
} from './useFormControl';
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
useFormControlContext,
|
|
8
8
|
useFormControlRoot,
|
|
9
9
|
useReportFormControlLabelFocus,
|
|
10
|
+
useReportFormControlValidation,
|
|
10
11
|
} from './useFormControl';
|
|
11
12
|
|
|
12
13
|
function FormControlTestProvider({
|
|
@@ -212,3 +213,52 @@ describe('useReportFormControlLabelFocus', () => {
|
|
|
212
213
|
}).not.toThrow();
|
|
213
214
|
});
|
|
214
215
|
});
|
|
216
|
+
|
|
217
|
+
describe('useReportFormControlValidation', () => {
|
|
218
|
+
it('ORs the reported state into the field and exposes the message', () => {
|
|
219
|
+
const { result, rerender, unmount } = renderHook(
|
|
220
|
+
({ invalid, message }: { invalid: boolean; message?: string }) => {
|
|
221
|
+
const reported = useReportFormControlValidation(invalid, message);
|
|
222
|
+
const { isInvalid, errorMessage } = useFormControlContext();
|
|
223
|
+
return { reported, isInvalid, errorMessage };
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
wrapper: ({ children }) => <FormControlTestProvider>{children}</FormControlTestProvider>,
|
|
227
|
+
initialProps: { invalid: true, message: 'Too early' } as {
|
|
228
|
+
invalid: boolean;
|
|
229
|
+
message?: string;
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
expect(result.current).toEqual({ reported: true, isInvalid: true, errorMessage: 'Too early' });
|
|
235
|
+
|
|
236
|
+
rerender({ invalid: false, message: undefined });
|
|
237
|
+
expect(result.current).toEqual({ reported: true, isInvalid: false, errorMessage: undefined });
|
|
238
|
+
|
|
239
|
+
unmount();
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('keeps the field invalid when the root prop is set', () => {
|
|
243
|
+
const { result } = renderHook(
|
|
244
|
+
() => {
|
|
245
|
+
useReportFormControlValidation(false, undefined);
|
|
246
|
+
return useFormControlContext().isInvalid;
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
wrapper: ({ children }) => (
|
|
250
|
+
<FormControlTestProvider rootProps={{ id: 'email', isInvalid: true }}>
|
|
251
|
+
{children}
|
|
252
|
+
</FormControlTestProvider>
|
|
253
|
+
),
|
|
254
|
+
},
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
expect(result.current).toBe(true);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('returns false outside a field', () => {
|
|
261
|
+
const { result } = renderHook(() => useReportFormControlValidation(true, 'Too early'));
|
|
262
|
+
expect(result.current).toBe(false);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
@@ -24,7 +24,18 @@ export interface FormControlState {
|
|
|
24
24
|
isReadOnly: boolean;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/** Validation a child control reports to its enclosing `Field` (see `useReportFormControlValidation`). */
|
|
28
|
+
export interface FormControlValidation {
|
|
29
|
+
isInvalid: boolean;
|
|
30
|
+
/** Message `Field.Error` renders when it has no children of its own. */
|
|
31
|
+
errorMessage?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
27
34
|
export interface FormControlContextValue extends FormControlState {
|
|
35
|
+
/** Message reported by the field's control; `Field.Error` falls back to it. */
|
|
36
|
+
errorMessage?: string;
|
|
37
|
+
/** Lets a control that validates itself report into the field. Set by the field root only. */
|
|
38
|
+
setControlValidation?: React.Dispatch<React.SetStateAction<FormControlValidation | undefined>>;
|
|
28
39
|
hasFeedbackText: boolean;
|
|
29
40
|
hasHelpText: boolean;
|
|
30
41
|
setHasFeedbackText: React.Dispatch<React.SetStateAction<boolean>>;
|
|
@@ -64,9 +75,12 @@ export type FormControlRootProps = Partial<FormControlState> & {
|
|
|
64
75
|
|
|
65
76
|
export interface FormControlRootReturn {
|
|
66
77
|
isRequired: boolean;
|
|
78
|
+
/** `isInvalid` prop OR-ed with the validation a child control reported. */
|
|
67
79
|
isInvalid: boolean;
|
|
68
80
|
isReadOnly: boolean;
|
|
69
81
|
isDisabled: boolean;
|
|
82
|
+
errorMessage?: string;
|
|
83
|
+
setControlValidation: React.Dispatch<React.SetStateAction<FormControlValidation | undefined>>;
|
|
70
84
|
hasFeedbackText: boolean;
|
|
71
85
|
setHasFeedbackText: React.Dispatch<React.SetStateAction<boolean>>;
|
|
72
86
|
hasHelpText: boolean;
|
|
@@ -111,6 +125,10 @@ export function useFormControlRoot(props: FormControlRootProps): FormControlRoot
|
|
|
111
125
|
|
|
112
126
|
const [isLabelFocused, setIsLabelFocused] = React.useState(false);
|
|
113
127
|
|
|
128
|
+
const [controlValidation, setControlValidation] = React.useState<
|
|
129
|
+
FormControlValidation | undefined
|
|
130
|
+
>(undefined);
|
|
131
|
+
|
|
114
132
|
const inputRef = React.useRef<Focusable | null>(null);
|
|
115
133
|
|
|
116
134
|
const focusInput = React.useCallback(() => {
|
|
@@ -122,9 +140,11 @@ export function useFormControlRoot(props: FormControlRootProps): FormControlRoot
|
|
|
122
140
|
|
|
123
141
|
const context: FormControlRootReturn = {
|
|
124
142
|
isRequired: Boolean(isRequired),
|
|
125
|
-
isInvalid: Boolean(isInvalid),
|
|
143
|
+
isInvalid: Boolean(isInvalid) || Boolean(controlValidation?.isInvalid),
|
|
126
144
|
isReadOnly: Boolean(isReadOnly),
|
|
127
145
|
isDisabled: Boolean(isDisabled),
|
|
146
|
+
errorMessage: controlValidation?.errorMessage,
|
|
147
|
+
setControlValidation,
|
|
128
148
|
hasFeedbackText,
|
|
129
149
|
setHasFeedbackText,
|
|
130
150
|
hasHelpText,
|
|
@@ -161,6 +181,43 @@ export function useReportFormControlLabelFocus(isActive: boolean) {
|
|
|
161
181
|
}, [isActive, setIsLabelFocused]);
|
|
162
182
|
}
|
|
163
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Reports a control's own validation (e.g. `DatePicker` bounds) to the enclosing `Field`, so the
|
|
186
|
+
* field root, `Field.Helper`, and `Field.Error` reflect it wherever they sit inside the field.
|
|
187
|
+
* Runs in a layout effect so the invalid state and message reach the field before paint.
|
|
188
|
+
*
|
|
189
|
+
* @returns `false` outside a `Field` (nothing to report to) — the caller must provide the state itself.
|
|
190
|
+
*/
|
|
191
|
+
export function useReportFormControlValidation(
|
|
192
|
+
isInvalid: boolean,
|
|
193
|
+
errorMessage: string | undefined,
|
|
194
|
+
): boolean {
|
|
195
|
+
const { setControlValidation } = useFormControlContext();
|
|
196
|
+
|
|
197
|
+
React.useLayoutEffect(() => {
|
|
198
|
+
if (typeof setControlValidation !== 'function') {
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
setControlValidation((prev) =>
|
|
202
|
+
prev?.isInvalid === isInvalid && prev.errorMessage === errorMessage
|
|
203
|
+
? prev
|
|
204
|
+
: { isInvalid, errorMessage },
|
|
205
|
+
);
|
|
206
|
+
return undefined;
|
|
207
|
+
}, [errorMessage, isInvalid, setControlValidation]);
|
|
208
|
+
|
|
209
|
+
React.useLayoutEffect(() => {
|
|
210
|
+
if (typeof setControlValidation !== 'function') {
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
return () => {
|
|
214
|
+
setControlValidation(undefined);
|
|
215
|
+
};
|
|
216
|
+
}, [setControlValidation]);
|
|
217
|
+
|
|
218
|
+
return typeof setControlValidation === 'function';
|
|
219
|
+
}
|
|
220
|
+
|
|
164
221
|
/**
|
|
165
222
|
* Hook that merges local field props with the nearest form field context
|
|
166
223
|
* and returns normalised HTML / ARIA attributes for input elements.
|