rsuite 6.1.3 → 6.2.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/CHANGELOG.md +18 -0
- package/Timeline/styles/index.css +11 -0
- package/Timeline/styles/index.scss +13 -0
- package/Uploader/styles/index.css +3 -0
- package/Uploader/styles/index.scss +3 -0
- package/cjs/AutoComplete/AutoComplete.d.ts +2 -0
- package/cjs/AutoComplete/AutoComplete.js +3 -1
- package/cjs/CheckTree/utils.js +2 -1
- package/cjs/Form/Form.d.ts +37 -0
- package/cjs/Form/Form.js +16 -1
- package/cjs/Form/hooks/useFormValidate.d.ts +2 -0
- package/cjs/Form/hooks/useFormValidate.js +117 -1
- package/cjs/Form/index.d.ts +1 -0
- package/cjs/Form/resolvers.d.ts +59 -0
- package/cjs/Form/resolvers.js +4 -0
- package/cjs/Timeline/Timeline.d.ts +5 -0
- package/cjs/Timeline/Timeline.js +13 -6
- package/cjs/Tree/hooks/useFlattenTree.js +5 -8
- package/cjs/Uploader/Uploader.d.ts +2 -0
- package/cjs/Uploader/Uploader.js +48 -2
- package/cjs/internals/Picker/PickerIndicator.js +13 -11
- package/cjs/toaster/toaster.js +38 -3
- package/dist/rsuite-no-reset.css +14 -0
- package/dist/rsuite-no-reset.min.css +1 -1
- package/dist/rsuite.css +14 -0
- package/dist/rsuite.js +9 -9
- package/dist/rsuite.min.css +1 -1
- package/dist/rsuite.min.js +1 -1
- package/dist/rsuite.min.js.map +1 -1
- package/esm/AutoComplete/AutoComplete.d.ts +2 -0
- package/esm/AutoComplete/AutoComplete.js +3 -1
- package/esm/CheckTree/utils.js +2 -1
- package/esm/Form/Form.d.ts +37 -0
- package/esm/Form/Form.js +16 -1
- package/esm/Form/hooks/useFormValidate.d.ts +2 -0
- package/esm/Form/hooks/useFormValidate.js +117 -1
- package/esm/Form/index.d.ts +1 -0
- package/esm/Form/resolvers.d.ts +59 -0
- package/esm/Form/resolvers.js +2 -0
- package/esm/Timeline/Timeline.d.ts +5 -0
- package/esm/Timeline/Timeline.js +13 -6
- package/esm/Tree/hooks/useFlattenTree.js +5 -8
- package/esm/Uploader/Uploader.d.ts +2 -0
- package/esm/Uploader/Uploader.js +48 -2
- package/esm/internals/Picker/PickerIndicator.js +13 -11
- package/esm/toaster/toaster.js +38 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
# [6.2.0](https://github.com/rsuite/rsuite/compare/v6.1.3...v6.2.0) (2026-06-12)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **CheckTreePicker,CheckTree:** fix infinite loop with cascade and defaultExpandAll ([#4579](https://github.com/rsuite/rsuite/issues/4579)) ([fc86644](https://github.com/rsuite/rsuite/commit/fc86644b595809c83f4aa453f7a5e07d00367522)), closes [#3973](https://github.com/rsuite/rsuite/issues/3973) [#3973](https://github.com/rsuite/rsuite/issues/3973) [#4541](https://github.com/rsuite/rsuite/issues/4541) [#4404](https://github.com/rsuite/rsuite/issues/4404) [#4424](https://github.com/rsuite/rsuite/issues/4424)
|
|
7
|
+
* **toaster:** prevent duplicate containers when push() is called synchronously in a loop ([#4571](https://github.com/rsuite/rsuite/issues/4571)) ([c507f5f](https://github.com/rsuite/rsuite/commit/c507f5f59a726dc25dce65ab37fad484563e502d))
|
|
8
|
+
* **Uploader:** prevent horizontal overflow and width increase ([#4580](https://github.com/rsuite/rsuite/issues/4580)) ([e064f4c](https://github.com/rsuite/rsuite/commit/e064f4cc27959f94d3354a9177a8ae0a9ad71460)), closes [#4536](https://github.com/rsuite/rsuite/issues/4536)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **Form:** add `resolver` prop for third-party validation library integration ([#4573](https://github.com/rsuite/rsuite/issues/4573)) ([4a9ff7b](https://github.com/rsuite/rsuite/commit/4a9ff7bef3979764fcf3b12e869ccbf5bf61c3bd))
|
|
14
|
+
* **Timeline:** add `reverse` prop to display items in reverse order ([#4569](https://github.com/rsuite/rsuite/issues/4569)) ([a0dd1f4](https://github.com/rsuite/rsuite/commit/a0dd1f4307756f283c05157cd05f8ea6f7885c36))
|
|
15
|
+
* **Uploader:** add `onCompletion` callback prop ([#4570](https://github.com/rsuite/rsuite/issues/4570)) ([7eb0748](https://github.com/rsuite/rsuite/commit/7eb0748f78d36e1f34ac940b4fac3b6c20bf1a52)), closes [#813](https://github.com/rsuite/rsuite/issues/813)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
1
19
|
## [6.1.3](https://github.com/rsuite/rsuite/compare/v6.1.2...v6.1.3) (2026-04-22)
|
|
2
20
|
|
|
3
21
|
|
|
@@ -145,6 +145,17 @@
|
|
|
145
145
|
height:auto;
|
|
146
146
|
min-height:var(--rs-time-line-tail-min-height);
|
|
147
147
|
}
|
|
148
|
+
.rs-timeline-reverse.rs-timeline-endless .rs-timeline-item:first-child .rs-timeline-item-tail{
|
|
149
|
+
top:0;
|
|
150
|
+
bottom:0;
|
|
151
|
+
height:auto;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.rs-timeline-reverse.rs-timeline-endless .rs-timeline-item:last-child .rs-timeline-item-tail{
|
|
155
|
+
height:calc(var(--rs-time-line-dot-center-gap) + var(--rs-time-line-dot-side-length));
|
|
156
|
+
min-height:0;
|
|
157
|
+
}
|
|
158
|
+
|
|
148
159
|
.rs-timeline-item:only-child .rs-timeline-item-tail{
|
|
149
160
|
display:none;
|
|
150
161
|
}
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
// --------------------------------------------------
|
|
9
9
|
|
|
10
10
|
.rs-timeline {
|
|
11
|
+
$root: &;
|
|
12
|
+
|
|
11
13
|
// CSS Variables
|
|
12
14
|
--rs-time-line-tail-min-height: 2.375rem; // 20px + 18px
|
|
13
15
|
--rs-time-line-item-content-margin: 12px;
|
|
@@ -87,6 +89,17 @@
|
|
|
87
89
|
min-height: var(--rs-time-line-tail-min-height);
|
|
88
90
|
}
|
|
89
91
|
|
|
92
|
+
@at-root #{$root}-reverse#{$root}-endless #{$root}-item:first-child #{$root}-item-tail {
|
|
93
|
+
top: 0;
|
|
94
|
+
bottom: 0;
|
|
95
|
+
height: auto;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@at-root #{$root}-reverse#{$root}-endless #{$root}-item:last-child #{$root}-item-tail {
|
|
99
|
+
height: calc(var(--rs-time-line-dot-center-gap) + var(--rs-time-line-dot-side-length));
|
|
100
|
+
min-height: 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
90
103
|
&-item:only-child &-item-tail {
|
|
91
104
|
display: none;
|
|
92
105
|
}
|
|
@@ -1272,6 +1272,7 @@
|
|
|
1272
1272
|
text-overflow:ellipsis;
|
|
1273
1273
|
white-space:nowrap;
|
|
1274
1274
|
flex:1 1 auto;
|
|
1275
|
+
min-width:0;
|
|
1275
1276
|
}
|
|
1276
1277
|
.rs-uploader[data-list-type=text] .rs-uploader-file-item-size{
|
|
1277
1278
|
flex:0 0 auto;
|
|
@@ -1318,6 +1319,7 @@
|
|
|
1318
1319
|
.rs-uploader[data-list-type=picture]{
|
|
1319
1320
|
display:inline-flex;
|
|
1320
1321
|
flex-direction:row;
|
|
1322
|
+
flex-wrap:wrap;
|
|
1321
1323
|
gap:var(--rs-uploader-item-spacing);
|
|
1322
1324
|
}
|
|
1323
1325
|
.rs-uploader[data-list-type=picture] .rs-uploader-trigger-btn{
|
|
@@ -1352,6 +1354,7 @@
|
|
|
1352
1354
|
}
|
|
1353
1355
|
.rs-uploader[data-list-type=picture] .rs-uploader-file-items{
|
|
1354
1356
|
display:inline-flex;
|
|
1357
|
+
flex-wrap:wrap;
|
|
1355
1358
|
gap:var(--rs-uploader-item-spacing);
|
|
1356
1359
|
}
|
|
1357
1360
|
.rs-uploader[data-list-type=picture] .rs-uploader-file-item{
|
|
@@ -109,6 +109,7 @@
|
|
|
109
109
|
@include utils.ellipsis-basic;
|
|
110
110
|
|
|
111
111
|
flex: 1 1 auto;
|
|
112
|
+
min-width: 0;
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
&-size {
|
|
@@ -169,6 +170,7 @@
|
|
|
169
170
|
.rs-uploader[data-list-type='picture'] {
|
|
170
171
|
display: inline-flex;
|
|
171
172
|
flex-direction: row;
|
|
173
|
+
flex-wrap: wrap;
|
|
172
174
|
gap: var(--rs-uploader-item-spacing);
|
|
173
175
|
|
|
174
176
|
.rs-uploader-trigger-btn {
|
|
@@ -213,6 +215,7 @@
|
|
|
213
215
|
|
|
214
216
|
.rs-uploader-file-items {
|
|
215
217
|
display: inline-flex;
|
|
218
|
+
flex-wrap: wrap;
|
|
216
219
|
gap: var(--rs-uploader-item-spacing);
|
|
217
220
|
}
|
|
218
221
|
|
|
@@ -30,6 +30,8 @@ export interface AutoCompleteProps<T = string> extends FormControlPickerProps<T,
|
|
|
30
30
|
onOpen?: () => void;
|
|
31
31
|
/** Called on close */
|
|
32
32
|
onClose?: () => void;
|
|
33
|
+
/** Ref to the input element */
|
|
34
|
+
inputRef?: React.Ref<HTMLInputElement>;
|
|
33
35
|
}
|
|
34
36
|
/**
|
|
35
37
|
* Autocomplete function of input field.
|
|
@@ -57,6 +57,7 @@ const AutoComplete = (0, _utils.forwardRef)((props, ref) => {
|
|
|
57
57
|
onFocus,
|
|
58
58
|
onBlur,
|
|
59
59
|
onMenuFocus,
|
|
60
|
+
inputRef,
|
|
60
61
|
...rest
|
|
61
62
|
} = propsWithDefaults;
|
|
62
63
|
const datalist = (0, _utils2.transformData)(data);
|
|
@@ -210,7 +211,8 @@ const AutoComplete = (0, _utils.forwardRef)((props, ref) => {
|
|
|
210
211
|
onBlur: handleInputBlur,
|
|
211
212
|
onFocus: handleInputFocus,
|
|
212
213
|
onChange: handleChange,
|
|
213
|
-
onKeyDown: handleKeyDownEvent
|
|
214
|
+
onKeyDown: handleKeyDownEvent,
|
|
215
|
+
inputRef: inputRef
|
|
214
216
|
})));
|
|
215
217
|
});
|
|
216
218
|
AutoComplete.displayName = 'AutoComplete';
|
package/cjs/CheckTree/utils.js
CHANGED
|
@@ -227,7 +227,8 @@ function getDisabledState(nodes, node, props) {
|
|
|
227
227
|
*/
|
|
228
228
|
function getCheckTreeDefaultValue(value, uncheckableItemValues) {
|
|
229
229
|
if (Array.isArray(value) && Array.isArray(uncheckableItemValues)) {
|
|
230
|
-
|
|
230
|
+
const filtered = value.filter(v => !uncheckableItemValues.includes(v));
|
|
231
|
+
return filtered.length === value.length ? value : filtered;
|
|
231
232
|
}
|
|
232
233
|
return value;
|
|
233
234
|
}
|
package/cjs/Form/Form.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { FormControlComponent } from '../FormControl';
|
|
|
3
3
|
import { FormInstance } from './hooks/useFormRef';
|
|
4
4
|
import { Schema } from 'schema-typed';
|
|
5
5
|
import type { WithAsProps, CheckTriggerType } from '../internals/types';
|
|
6
|
+
import type { Resolver } from './resolvers';
|
|
6
7
|
export interface FormProps<V = Record<string, any>, M = any, E = {
|
|
7
8
|
[P in keyof V]?: M;
|
|
8
9
|
}> extends WithAsProps, Omit<FormHTMLAttributes<HTMLFormElement>, 'onChange' | 'onSubmit' | 'onError' | 'onReset'> {
|
|
@@ -40,6 +41,42 @@ export interface FormProps<V = Record<string, any>, M = any, E = {
|
|
|
40
41
|
* @see https://github.com/rsuite/schema-typed
|
|
41
42
|
*/
|
|
42
43
|
model?: Schema;
|
|
44
|
+
/**
|
|
45
|
+
* A resolver function for integrating third-party validation libraries such as
|
|
46
|
+
* Yup, Zod, AJV, Joi, Valibot, etc.
|
|
47
|
+
*
|
|
48
|
+
* When provided, the `resolver` takes precedence over the `model` prop for
|
|
49
|
+
* form-level validation (`check` / `checkAsync`). Field-level inline `rule`
|
|
50
|
+
* props on `<Form.Control>` components are still respected.
|
|
51
|
+
*
|
|
52
|
+
* The resolver receives the current form values and must return (or resolve to)
|
|
53
|
+
* a `{ errors }` object where each key is a field name and each value is an
|
|
54
|
+
* error message or error object. An empty `errors` object means the form is valid.
|
|
55
|
+
*
|
|
56
|
+
* **Note:** If the resolver is asynchronous, form-level sync validation
|
|
57
|
+
* (`check()`) will return `false` and log a warning. Use `checkAsync()` or
|
|
58
|
+
* rely on the `onSubmit` callback (which always awaits the resolver).
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```tsx
|
|
62
|
+
* import * as yup from 'yup';
|
|
63
|
+
*
|
|
64
|
+
* const schema = yup.object({ name: yup.string().email().required() });
|
|
65
|
+
* const resolver = async (formValue) => {
|
|
66
|
+
* try {
|
|
67
|
+
* await schema.validate(formValue, { abortEarly: false });
|
|
68
|
+
* return { errors: {} };
|
|
69
|
+
* } catch (e) {
|
|
70
|
+
* const errors = {};
|
|
71
|
+
* e.inner.forEach(err => { if (err.path) errors[err.path] = err.message; });
|
|
72
|
+
* return { errors };
|
|
73
|
+
* }
|
|
74
|
+
* };
|
|
75
|
+
*
|
|
76
|
+
* <Form resolver={resolver} onSubmit={handleSubmit}>…</Form>
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
resolver?: Resolver<V>;
|
|
43
80
|
/**
|
|
44
81
|
* Make the form readonly
|
|
45
82
|
*/
|
package/cjs/Form/Form.js
CHANGED
|
@@ -58,6 +58,7 @@ const Form = (0, _utils.forwardRef)((props, ref) => {
|
|
|
58
58
|
fluid,
|
|
59
59
|
layout,
|
|
60
60
|
model: formModel = defaultSchema,
|
|
61
|
+
resolver,
|
|
61
62
|
readOnly,
|
|
62
63
|
plaintext,
|
|
63
64
|
children,
|
|
@@ -88,7 +89,8 @@ const Form = (0, _utils.forwardRef)((props, ref) => {
|
|
|
88
89
|
getCombinedModel,
|
|
89
90
|
onCheck,
|
|
90
91
|
onError,
|
|
91
|
-
nestedField
|
|
92
|
+
nestedField,
|
|
93
|
+
resolver
|
|
92
94
|
};
|
|
93
95
|
const {
|
|
94
96
|
formError,
|
|
@@ -104,6 +106,19 @@ const Form = (0, _utils.forwardRef)((props, ref) => {
|
|
|
104
106
|
cleanErrorForField
|
|
105
107
|
} = (0, _useFormValidate.default)(controlledFormError, formValidateProps);
|
|
106
108
|
const submit = (0, _hooks.useEventCallback)(event => {
|
|
109
|
+
if (resolver) {
|
|
110
|
+
// When a resolver is provided, always use the async validation path so that
|
|
111
|
+
// both sync and async resolvers are handled correctly.
|
|
112
|
+
checkAsync().then(({
|
|
113
|
+
hasError
|
|
114
|
+
}) => {
|
|
115
|
+
if (!hasError) {
|
|
116
|
+
onSubmit?.(formValue, event);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
107
122
|
// Check the form before submitting
|
|
108
123
|
if (check()) {
|
|
109
124
|
onSubmit?.(formValue, event);
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import type { Resolver } from '../resolvers';
|
|
1
2
|
export interface FormErrorProps {
|
|
2
3
|
formValue: any;
|
|
3
4
|
getCombinedModel: () => any;
|
|
4
5
|
onCheck?: (formError: any) => void;
|
|
5
6
|
onError?: (formError: any) => void;
|
|
6
7
|
nestedField?: boolean;
|
|
8
|
+
resolver?: Resolver;
|
|
7
9
|
}
|
|
8
10
|
export default function useFormValidate(_formError: any, props: FormErrorProps): {
|
|
9
11
|
formError: any;
|
|
@@ -15,7 +15,8 @@ function useFormValidate(_formError, props) {
|
|
|
15
15
|
getCombinedModel,
|
|
16
16
|
onCheck,
|
|
17
17
|
onError,
|
|
18
|
-
nestedField
|
|
18
|
+
nestedField,
|
|
19
|
+
resolver
|
|
19
20
|
} = props;
|
|
20
21
|
const [realFormError, setFormError] = (0, _hooks.useControlled)(_formError, {});
|
|
21
22
|
const checkOptions = {
|
|
@@ -24,12 +25,64 @@ function useFormValidate(_formError, props) {
|
|
|
24
25
|
const realFormErrorRef = (0, _react.useRef)(realFormError);
|
|
25
26
|
realFormErrorRef.current = realFormError;
|
|
26
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Returns true when an error value is considered non-empty (i.e. the field has an error).
|
|
30
|
+
*/
|
|
31
|
+
const isValidError = error => error !== undefined && error !== null && error !== '';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Merges resolver errors into the current form error state, removing entries that
|
|
35
|
+
* are no longer invalid according to the latest resolver result.
|
|
36
|
+
*/
|
|
37
|
+
const mergeResolverErrors = (current, resolverErrors) => {
|
|
38
|
+
const next = {
|
|
39
|
+
...current
|
|
40
|
+
};
|
|
41
|
+
Object.keys({
|
|
42
|
+
...current,
|
|
43
|
+
...resolverErrors
|
|
44
|
+
}).forEach(key => {
|
|
45
|
+
if (isValidError(resolverErrors[key])) {
|
|
46
|
+
next[key] = resolverErrors[key];
|
|
47
|
+
} else {
|
|
48
|
+
delete next[key];
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return next;
|
|
52
|
+
};
|
|
53
|
+
|
|
27
54
|
/**
|
|
28
55
|
* Validate the form data and return a boolean.
|
|
29
56
|
* The error message after verification is returned in the callback.
|
|
57
|
+
*
|
|
58
|
+
* When a `resolver` is provided and the resolver returns a Promise (async resolver),
|
|
59
|
+
* this method cannot resolve the result synchronously. In that case it returns `false`
|
|
60
|
+
* immediately and you should use `checkAsync()` instead.
|
|
30
61
|
* @param callback
|
|
31
62
|
*/
|
|
32
63
|
const check = (0, _hooks.useEventCallback)(callback => {
|
|
64
|
+
if (resolver) {
|
|
65
|
+
const result = resolver(formValue || {});
|
|
66
|
+
|
|
67
|
+
// Async resolver: cannot handle synchronously
|
|
68
|
+
if (result instanceof Promise) {
|
|
69
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
70
|
+
console.warn('[rsuite] The `resolver` provided to <Form> returns a Promise. ' + 'Use `checkAsync()` or rely on `onSubmit` for async validation.');
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
const {
|
|
75
|
+
errors
|
|
76
|
+
} = result;
|
|
77
|
+
const hasError = Object.keys(errors).length > 0;
|
|
78
|
+
setFormError(errors);
|
|
79
|
+
onCheck?.(errors);
|
|
80
|
+
callback?.(errors);
|
|
81
|
+
if (hasError) {
|
|
82
|
+
onError?.(errors);
|
|
83
|
+
}
|
|
84
|
+
return !hasError;
|
|
85
|
+
}
|
|
33
86
|
const formError = {};
|
|
34
87
|
let errorCount = 0;
|
|
35
88
|
const model = getCombinedModel();
|
|
@@ -64,6 +117,35 @@ function useFormValidate(_formError, props) {
|
|
|
64
117
|
return true;
|
|
65
118
|
});
|
|
66
119
|
const checkFieldForNextValue = (0, _hooks.useEventCallback)((fieldName, nextValue, callback) => {
|
|
120
|
+
if (resolver) {
|
|
121
|
+
const result = resolver(nextValue);
|
|
122
|
+
if (result instanceof Promise) {
|
|
123
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
124
|
+
console.warn('[rsuite] The `resolver` provided to <Form> returns a Promise. ' + 'Use `checkAsync()` or `checkForFieldAsync()` for async validation.');
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
const {
|
|
129
|
+
errors
|
|
130
|
+
} = result;
|
|
131
|
+
const fieldError = errors[fieldName];
|
|
132
|
+
const hasFieldError = isValidError(fieldError);
|
|
133
|
+
// Merge resolver errors with existing errors, clearing fields that now pass
|
|
134
|
+
const nextFormError = mergeResolverErrors(realFormError, errors);
|
|
135
|
+
setFormError(nextFormError);
|
|
136
|
+
onCheck?.(nextFormError);
|
|
137
|
+
const callbackResult = {
|
|
138
|
+
hasError: hasFieldError,
|
|
139
|
+
errorMessage: fieldError
|
|
140
|
+
};
|
|
141
|
+
callback?.(hasFieldError ? callbackResult : {
|
|
142
|
+
hasError: false
|
|
143
|
+
});
|
|
144
|
+
if (Object.keys(nextFormError).length > 0) {
|
|
145
|
+
onError?.(nextFormError);
|
|
146
|
+
}
|
|
147
|
+
return !hasFieldError;
|
|
148
|
+
}
|
|
67
149
|
const model = getCombinedModel();
|
|
68
150
|
const resultOfCurrentField = model.checkForField(fieldName, nextValue, checkOptions);
|
|
69
151
|
let nextFormError = {
|
|
@@ -122,6 +204,22 @@ function useFormValidate(_formError, props) {
|
|
|
122
204
|
* Check form data asynchronously and return a Promise
|
|
123
205
|
*/
|
|
124
206
|
const checkAsync = (0, _hooks.useEventCallback)(() => {
|
|
207
|
+
if (resolver) {
|
|
208
|
+
return Promise.resolve(resolver(formValue || {})).then(({
|
|
209
|
+
errors
|
|
210
|
+
}) => {
|
|
211
|
+
const hasError = Object.keys(errors).length > 0;
|
|
212
|
+
onCheck?.(errors);
|
|
213
|
+
setFormError(errors);
|
|
214
|
+
if (hasError) {
|
|
215
|
+
onError?.(errors);
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
hasError,
|
|
219
|
+
formError: errors
|
|
220
|
+
};
|
|
221
|
+
});
|
|
222
|
+
}
|
|
125
223
|
const promises = [];
|
|
126
224
|
const keys = [];
|
|
127
225
|
const model = getCombinedModel();
|
|
@@ -150,6 +248,24 @@ function useFormValidate(_formError, props) {
|
|
|
150
248
|
});
|
|
151
249
|
});
|
|
152
250
|
const checkFieldAsyncForNextValue = (0, _hooks.useEventCallback)((fieldName, nextValue) => {
|
|
251
|
+
if (resolver) {
|
|
252
|
+
return Promise.resolve(resolver(nextValue)).then(({
|
|
253
|
+
errors
|
|
254
|
+
}) => {
|
|
255
|
+
const fieldError = errors[fieldName];
|
|
256
|
+
const hasFieldError = isValidError(fieldError);
|
|
257
|
+
const nextFormError = mergeResolverErrors(realFormError, errors);
|
|
258
|
+
onCheck?.(nextFormError);
|
|
259
|
+
setFormError(nextFormError);
|
|
260
|
+
if (Object.keys(nextFormError).length > 0) {
|
|
261
|
+
onError?.(nextFormError);
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
hasError: hasFieldError,
|
|
265
|
+
errorMessage: fieldError
|
|
266
|
+
};
|
|
267
|
+
});
|
|
268
|
+
}
|
|
153
269
|
const model = getCombinedModel();
|
|
154
270
|
return model.checkForFieldAsync(fieldName, nextValue, checkOptions).then(resultOfCurrentField => {
|
|
155
271
|
let nextFormError = {
|
package/cjs/Form/index.d.ts
CHANGED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The result returned by a validation resolver.
|
|
3
|
+
*
|
|
4
|
+
* @template E - The type of the form error map. Defaults to a record of string keys to any.
|
|
5
|
+
*/
|
|
6
|
+
export interface ResolverResult<E = Record<string, any>> {
|
|
7
|
+
errors: E;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A resolver is a function that integrates third-party validation libraries
|
|
11
|
+
* (e.g. Yup, Zod, AJV, Joi, Valibot…) with the rsuite `Form` component.
|
|
12
|
+
*
|
|
13
|
+
* The resolver receives the current form values and an optional context object,
|
|
14
|
+
* and must return (or resolve to) a `ResolverResult` whose `errors` property is
|
|
15
|
+
* a plain object that maps field names to error messages / error objects.
|
|
16
|
+
*
|
|
17
|
+
* An **empty** `errors` object means the form is valid.
|
|
18
|
+
*
|
|
19
|
+
* @template V - The shape of the form values. Defaults to `Record<string, any>`.
|
|
20
|
+
* @template E - The shape of the error map. Defaults to `Record<string, any>`.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* // Yup example
|
|
25
|
+
* import * as yup from 'yup';
|
|
26
|
+
* import type { Resolver } from 'rsuite';
|
|
27
|
+
*
|
|
28
|
+
* const schema = yup.object({ name: yup.string().email('Invalid email').required() });
|
|
29
|
+
*
|
|
30
|
+
* const resolver: Resolver = async (formValue) => {
|
|
31
|
+
* try {
|
|
32
|
+
* await schema.validate(formValue, { abortEarly: false });
|
|
33
|
+
* return { errors: {} };
|
|
34
|
+
* } catch (e: any) {
|
|
35
|
+
* const errors: Record<string, string> = {};
|
|
36
|
+
* e.inner.forEach((err: yup.ValidationError) => {
|
|
37
|
+
* if (err.path) errors[err.path] = err.message;
|
|
38
|
+
* });
|
|
39
|
+
* return { errors };
|
|
40
|
+
* }
|
|
41
|
+
* };
|
|
42
|
+
*
|
|
43
|
+
* // Zod example
|
|
44
|
+
* import { z } from 'zod';
|
|
45
|
+
*
|
|
46
|
+
* const schema = z.object({ name: z.string().email('Invalid email') });
|
|
47
|
+
*
|
|
48
|
+
* const resolver: Resolver = (formValue) => {
|
|
49
|
+
* const result = schema.safeParse(formValue);
|
|
50
|
+
* if (result.success) return { errors: {} };
|
|
51
|
+
* const errors: Record<string, string> = {};
|
|
52
|
+
* result.error.issues.forEach(err => {
|
|
53
|
+
* if (err.path.length) errors[err.path[0]] = err.message;
|
|
54
|
+
* });
|
|
55
|
+
* return { errors };
|
|
56
|
+
* };
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export type Resolver<V = Record<string, any>, E = Record<string, any>> = (formValue: V, context?: any) => ResolverResult<E> | Promise<ResolverResult<E>>;
|
|
@@ -7,6 +7,11 @@ export interface TimelineProps extends BoxProps {
|
|
|
7
7
|
align?: 'left' | 'right' | 'alternate';
|
|
8
8
|
/** Timeline endless **/
|
|
9
9
|
endless?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Reverse the order of Timeline items
|
|
12
|
+
* @version 6.2.0
|
|
13
|
+
**/
|
|
14
|
+
reverse?: boolean;
|
|
10
15
|
/**
|
|
11
16
|
* Whether an item is active (with highlighted dot).
|
|
12
17
|
*
|
package/cjs/Timeline/Timeline.js
CHANGED
|
@@ -35,6 +35,7 @@ const Timeline = (0, _utils.forwardRef)((props, ref) => {
|
|
|
35
35
|
className,
|
|
36
36
|
align = 'left',
|
|
37
37
|
endless,
|
|
38
|
+
reverse,
|
|
38
39
|
isItemActive = ACTIVE_LAST,
|
|
39
40
|
...rest
|
|
40
41
|
} = propsWithDefaults;
|
|
@@ -46,17 +47,23 @@ const Timeline = (0, _utils.forwardRef)((props, ref) => {
|
|
|
46
47
|
const withTime = (0, _some.default)(_react.default.Children.toArray(children), item => item?.props?.time);
|
|
47
48
|
const classes = merge(className, withPrefix(`align-${align}`, {
|
|
48
49
|
endless,
|
|
49
|
-
'with-time': withTime
|
|
50
|
+
'with-time': withTime,
|
|
51
|
+
reverse
|
|
50
52
|
}));
|
|
53
|
+
const childrenArray = _react.default.Children.toArray(children);
|
|
54
|
+
const orderedChildren = reverse ? [...childrenArray].reverse() : childrenArray;
|
|
51
55
|
return /*#__PURE__*/_react.default.createElement(_Box.default, (0, _extends2.default)({
|
|
52
56
|
as: as,
|
|
53
57
|
ref: ref,
|
|
54
58
|
className: classes
|
|
55
|
-
}, rest),
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
}, rest), orderedChildren.map((child, domIndex) => {
|
|
60
|
+
const logicalIndex = reverse ? count - 1 - domIndex : domIndex;
|
|
61
|
+
return /*#__PURE__*/_react.default.cloneElement(child, {
|
|
62
|
+
last: logicalIndex + 1 === count,
|
|
63
|
+
INTERNAL_active: isItemActive(logicalIndex, count),
|
|
64
|
+
align
|
|
65
|
+
});
|
|
66
|
+
}));
|
|
60
67
|
}, SubcomponentsAndStaticMethods);
|
|
61
68
|
Timeline.displayName = 'Timeline';
|
|
62
69
|
var _default = exports.default = Timeline;
|
|
@@ -95,23 +95,20 @@ function useFlattenTree(data, options) {
|
|
|
95
95
|
forceUpdate();
|
|
96
96
|
}, [callback, forceUpdate, valueKey, labelKey, uncheckableItemValues, childrenKey]);
|
|
97
97
|
(0, _react.useEffect)(() => {
|
|
98
|
-
// when data is changed, should clear the flattenedNodes, avoid duplicate keys
|
|
99
98
|
flattenedNodes.current = {};
|
|
100
99
|
seenValues.current.clear();
|
|
101
100
|
flattenTreeData(data);
|
|
101
|
+
if (multiple) {
|
|
102
|
+
updateTreeNodeCheckState(value);
|
|
103
|
+
forceUpdate();
|
|
104
|
+
}
|
|
102
105
|
}, [data]);
|
|
103
106
|
(0, _react.useEffect)(() => {
|
|
104
107
|
if (multiple) {
|
|
105
108
|
updateTreeNodeCheckState(value);
|
|
106
109
|
forceUpdate();
|
|
107
110
|
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Add a dependency on data, because when loading data asynchronously through getChildren,
|
|
111
|
-
* data may change and the node status needs to be updated.
|
|
112
|
-
* @see https://github.com/rsuite/rsuite/issues/3973
|
|
113
|
-
*/
|
|
114
|
-
}, [value, data]);
|
|
111
|
+
}, [value]);
|
|
115
112
|
return flattenedNodes.current;
|
|
116
113
|
}
|
|
117
114
|
var _default = exports.default = useFlattenTree;
|
|
@@ -99,6 +99,8 @@ export interface UploaderProps extends BaseBoxProps, Omit<UploadTriggerProps, 'o
|
|
|
99
99
|
onProgress?: (percent: number, file: FileType, event: ProgressEvent, xhr: XMLHttpRequest) => void;
|
|
100
100
|
/** In the file list, click the callback function to delete a file */
|
|
101
101
|
onRemove?: (file: FileType) => void;
|
|
102
|
+
/** Callback function called when all files in the current upload batch have finished (succeeded or failed) */
|
|
103
|
+
onCompletion?: (completedFiles: FileType[], failedFiles: FileType[]) => void;
|
|
102
104
|
/** Custom render file information */
|
|
103
105
|
renderFileInfo?: (file: FileType, fileElement: React.ReactNode) => React.ReactNode;
|
|
104
106
|
/** Custom render thumbnail */
|