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.
Files changed (47) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/Timeline/styles/index.css +11 -0
  3. package/Timeline/styles/index.scss +13 -0
  4. package/Uploader/styles/index.css +3 -0
  5. package/Uploader/styles/index.scss +3 -0
  6. package/cjs/AutoComplete/AutoComplete.d.ts +2 -0
  7. package/cjs/AutoComplete/AutoComplete.js +3 -1
  8. package/cjs/CheckTree/utils.js +2 -1
  9. package/cjs/Form/Form.d.ts +37 -0
  10. package/cjs/Form/Form.js +16 -1
  11. package/cjs/Form/hooks/useFormValidate.d.ts +2 -0
  12. package/cjs/Form/hooks/useFormValidate.js +117 -1
  13. package/cjs/Form/index.d.ts +1 -0
  14. package/cjs/Form/resolvers.d.ts +59 -0
  15. package/cjs/Form/resolvers.js +4 -0
  16. package/cjs/Timeline/Timeline.d.ts +5 -0
  17. package/cjs/Timeline/Timeline.js +13 -6
  18. package/cjs/Tree/hooks/useFlattenTree.js +5 -8
  19. package/cjs/Uploader/Uploader.d.ts +2 -0
  20. package/cjs/Uploader/Uploader.js +48 -2
  21. package/cjs/internals/Picker/PickerIndicator.js +13 -11
  22. package/cjs/toaster/toaster.js +38 -3
  23. package/dist/rsuite-no-reset.css +14 -0
  24. package/dist/rsuite-no-reset.min.css +1 -1
  25. package/dist/rsuite.css +14 -0
  26. package/dist/rsuite.js +9 -9
  27. package/dist/rsuite.min.css +1 -1
  28. package/dist/rsuite.min.js +1 -1
  29. package/dist/rsuite.min.js.map +1 -1
  30. package/esm/AutoComplete/AutoComplete.d.ts +2 -0
  31. package/esm/AutoComplete/AutoComplete.js +3 -1
  32. package/esm/CheckTree/utils.js +2 -1
  33. package/esm/Form/Form.d.ts +37 -0
  34. package/esm/Form/Form.js +16 -1
  35. package/esm/Form/hooks/useFormValidate.d.ts +2 -0
  36. package/esm/Form/hooks/useFormValidate.js +117 -1
  37. package/esm/Form/index.d.ts +1 -0
  38. package/esm/Form/resolvers.d.ts +59 -0
  39. package/esm/Form/resolvers.js +2 -0
  40. package/esm/Timeline/Timeline.d.ts +5 -0
  41. package/esm/Timeline/Timeline.js +13 -6
  42. package/esm/Tree/hooks/useFlattenTree.js +5 -8
  43. package/esm/Uploader/Uploader.d.ts +2 -0
  44. package/esm/Uploader/Uploader.js +48 -2
  45. package/esm/internals/Picker/PickerIndicator.js +13 -11
  46. package/esm/toaster/toaster.js +38 -3
  47. 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';
@@ -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
- return value.filter(v => !uncheckableItemValues.includes(v));
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
  }
@@ -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 = {
@@ -1,5 +1,6 @@
1
1
  import Form from './Form';
2
2
  export type { FormProps } from './Form';
3
3
  export type { FormInstance } from './hooks/useFormRef';
4
+ export type { Resolver, ResolverResult } from './resolvers';
4
5
  export { Form };
5
6
  export default Form;
@@ -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>>;
@@ -0,0 +1,4 @@
1
+ 'use client';
2
+ "use strict";
3
+
4
+ exports.__esModule = true;
@@ -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
  *
@@ -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), _utils.rch.mapCloneElement(children, (_child, index) => ({
56
- last: index + 1 === count,
57
- INTERNAL_active: isItemActive(index, count),
58
- align
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 */