rsuite 6.1.3 → 6.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.
Files changed (66) hide show
  1. package/AutoComplete/styles/index.css +3 -0
  2. package/CHANGELOG.md +27 -0
  3. package/Cascader/styles/index.css +3 -0
  4. package/CheckPicker/styles/index.css +3 -0
  5. package/CheckTree/styles/index.css +3 -0
  6. package/CheckTreePicker/styles/index.css +3 -0
  7. package/DatePicker/styles/index.css +3 -0
  8. package/DateRangePicker/styles/index.css +3 -0
  9. package/InputPicker/styles/index.css +3 -0
  10. package/MultiCascadeTree/styles/index.css +3 -0
  11. package/MultiCascader/styles/index.css +3 -0
  12. package/Pagination/styles/index.css +3 -0
  13. package/SelectPicker/styles/index.css +3 -0
  14. package/TagInput/styles/index.css +3 -0
  15. package/TagPicker/styles/index.css +3 -0
  16. package/TimePicker/styles/index.css +3 -0
  17. package/TimeRangePicker/styles/index.css +3 -0
  18. package/Timeline/styles/index.css +11 -0
  19. package/Timeline/styles/index.scss +13 -0
  20. package/Tree/styles/index.css +3 -0
  21. package/TreePicker/styles/index.css +3 -0
  22. package/Uploader/styles/index.css +3 -0
  23. package/Uploader/styles/index.scss +3 -0
  24. package/cjs/AutoComplete/AutoComplete.d.ts +2 -0
  25. package/cjs/AutoComplete/AutoComplete.js +3 -1
  26. package/cjs/CheckTree/utils.js +2 -1
  27. package/cjs/Form/Form.d.ts +37 -0
  28. package/cjs/Form/Form.js +16 -1
  29. package/cjs/Form/hooks/useFormValidate.d.ts +2 -0
  30. package/cjs/Form/hooks/useFormValidate.js +117 -1
  31. package/cjs/Form/index.d.ts +1 -0
  32. package/cjs/Form/resolvers.d.ts +59 -0
  33. package/cjs/Form/resolvers.js +4 -0
  34. package/cjs/Timeline/Timeline.d.ts +5 -0
  35. package/cjs/Timeline/Timeline.js +13 -6
  36. package/cjs/Tree/hooks/useFlattenTree.js +5 -8
  37. package/cjs/Uploader/Uploader.d.ts +2 -0
  38. package/cjs/Uploader/Uploader.js +48 -2
  39. package/cjs/internals/Picker/PickerIndicator.js +4 -1
  40. package/cjs/toaster/toaster.js +38 -3
  41. package/dist/rsuite-no-reset.css +17 -0
  42. package/dist/rsuite-no-reset.min.css +1 -1
  43. package/dist/rsuite.css +17 -0
  44. package/dist/rsuite.js +9 -9
  45. package/dist/rsuite.min.css +1 -1
  46. package/dist/rsuite.min.js +1 -1
  47. package/dist/rsuite.min.js.map +1 -1
  48. package/esm/AutoComplete/AutoComplete.d.ts +2 -0
  49. package/esm/AutoComplete/AutoComplete.js +3 -1
  50. package/esm/CheckTree/utils.js +2 -1
  51. package/esm/Form/Form.d.ts +37 -0
  52. package/esm/Form/Form.js +16 -1
  53. package/esm/Form/hooks/useFormValidate.d.ts +2 -0
  54. package/esm/Form/hooks/useFormValidate.js +117 -1
  55. package/esm/Form/index.d.ts +1 -0
  56. package/esm/Form/resolvers.d.ts +59 -0
  57. package/esm/Form/resolvers.js +2 -0
  58. package/esm/Timeline/Timeline.d.ts +5 -0
  59. package/esm/Timeline/Timeline.js +13 -6
  60. package/esm/Tree/hooks/useFlattenTree.js +5 -8
  61. package/esm/Uploader/Uploader.d.ts +2 -0
  62. package/esm/Uploader/Uploader.js +48 -2
  63. package/esm/internals/Picker/PickerIndicator.js +4 -1
  64. package/esm/toaster/toaster.js +38 -3
  65. package/internals/Picker/styles/index.scss +3 -0
  66. package/package.json +1 -1
@@ -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 */
@@ -132,6 +132,7 @@ const Uploader = (0, _utils.forwardRef)((props, ref) => {
132
132
  onError,
133
133
  onProgress,
134
134
  onReupload,
135
+ onCompletion,
135
136
  ...rest
136
137
  } = propsWithDefaults;
137
138
  const {
@@ -143,6 +144,11 @@ const Uploader = (0, _utils.forwardRef)((props, ref) => {
143
144
  const rootRef = (0, _react.useRef)(null);
144
145
  const xhrs = (0, _react.useRef)({});
145
146
  const trigger = (0, _react.useRef)(null);
147
+ const uploadingCount = (0, _react.useRef)(0);
148
+ const uploadResults = (0, _react.useRef)({
149
+ completed: [],
150
+ failed: []
151
+ });
146
152
  const [fileList, dispatch] = useFileList(fileListProp || defaultFileList);
147
153
  (0, _react.useEffect)(() => {
148
154
  if (typeof fileListProp !== 'undefined') {
@@ -182,7 +188,16 @@ const Uploader = (0, _utils.forwardRef)((props, ref) => {
182
188
  };
183
189
  updateFileStatus(nextFile);
184
190
  onSuccess?.(response, nextFile, event, xhr);
185
- }, [onSuccess, updateFileStatus]);
191
+ uploadingCount.current--;
192
+ uploadResults.current.completed.push(nextFile);
193
+ if (uploadingCount.current === 0) {
194
+ onCompletion?.(uploadResults.current.completed, uploadResults.current.failed);
195
+ uploadResults.current = {
196
+ completed: [],
197
+ failed: []
198
+ };
199
+ }
200
+ }, [onCompletion, onSuccess, updateFileStatus]);
186
201
 
187
202
  /**
188
203
  * Callback for file upload error.
@@ -198,7 +213,16 @@ const Uploader = (0, _utils.forwardRef)((props, ref) => {
198
213
  };
199
214
  updateFileStatus(nextFile);
200
215
  onError?.(status, nextFile, event, xhr);
201
- }, [onError, updateFileStatus]);
216
+ uploadingCount.current--;
217
+ uploadResults.current.failed.push(nextFile);
218
+ if (uploadingCount.current === 0) {
219
+ onCompletion?.(uploadResults.current.completed, uploadResults.current.failed);
220
+ uploadResults.current = {
221
+ completed: [],
222
+ failed: []
223
+ };
224
+ }
225
+ }, [onCompletion, onError, updateFileStatus]);
202
226
 
203
227
  /**
204
228
  * Callback for file upload progress update.
@@ -252,9 +276,19 @@ const Uploader = (0, _utils.forwardRef)((props, ref) => {
252
276
  fileList.current.forEach(file => {
253
277
  const checkState = shouldUpload?.(file);
254
278
  if (checkState instanceof Promise) {
279
+ uploadingCount.current++;
255
280
  checkState.then(res => {
256
281
  if (res) {
257
282
  handleUploadFile(file);
283
+ } else {
284
+ uploadingCount.current--;
285
+ if (uploadingCount.current === 0) {
286
+ onCompletion?.(uploadResults.current.completed, uploadResults.current.failed);
287
+ uploadResults.current = {
288
+ completed: [],
289
+ failed: []
290
+ };
291
+ }
258
292
  }
259
293
  });
260
294
  return;
@@ -262,6 +296,7 @@ const Uploader = (0, _utils.forwardRef)((props, ref) => {
262
296
  return;
263
297
  }
264
298
  if (file.status === 'inited') {
299
+ uploadingCount.current++;
265
300
  handleUploadFile(file);
266
301
  }
267
302
  });
@@ -308,6 +343,15 @@ const Uploader = (0, _utils.forwardRef)((props, ref) => {
308
343
  const nextFileList = fileList.current.filter(f => f.fileKey !== fileKey);
309
344
  if (xhrs.current?.[file.fileKey]?.readyState !== 4) {
310
345
  xhrs.current[file.fileKey]?.abort();
346
+ uploadingCount.current--;
347
+ uploadResults.current.failed.push(file);
348
+ if (uploadingCount.current === 0) {
349
+ onCompletion?.(uploadResults.current.completed, uploadResults.current.failed);
350
+ uploadResults.current = {
351
+ completed: [],
352
+ failed: []
353
+ };
354
+ }
311
355
  }
312
356
  dispatch({
313
357
  type: 'remove',
@@ -318,6 +362,7 @@ const Uploader = (0, _utils.forwardRef)((props, ref) => {
318
362
  cleanInputValue();
319
363
  });
320
364
  const handleReupload = (0, _hooks.useEventCallback)(file => {
365
+ uploadingCount.current++;
321
366
  autoUpload && handleUploadFile(file);
322
367
  onReupload?.(file);
323
368
  });
@@ -325,6 +370,7 @@ const Uploader = (0, _utils.forwardRef)((props, ref) => {
325
370
  // public API
326
371
  const start = (0, _react.useCallback)(file => {
327
372
  if (file) {
373
+ uploadingCount.current++;
328
374
  handleUploadFile(file);
329
375
  return;
330
376
  }
@@ -53,8 +53,11 @@ const PickerIndicator = ({
53
53
  });
54
54
  };
55
55
  const props = Component === _InputGroup.default.Addon ? {
56
+ className: prefix('toggle-indicator'),
56
57
  disabled
57
- } : undefined;
58
+ } : Component === _react.default.Fragment ? undefined : {
59
+ className: prefix('toggle-indicator')
60
+ };
58
61
  return /*#__PURE__*/_react.default.createElement(Component, props, addon());
59
62
  };
60
63
  var _default = exports.default = PickerIndicator;
@@ -5,9 +5,17 @@ exports.__esModule = true;
5
5
  exports.default = void 0;
6
6
  var _ToastContainer = _interopRequireWildcard(require("./ToastContainer"));
7
7
  var _symbols = require("../internals/symbols");
8
+ var _utils = require("../internals/utils");
8
9
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
9
10
  const containers = new Map();
10
11
 
12
+ /**
13
+ * Track in-progress container creation promises keyed by `${containerId}_${placement}`.
14
+ * This prevents duplicate containers from being created when `push` is called multiple
15
+ * times synchronously (e.g. inside a loop) before the first container has mounted.
16
+ */
17
+ const pendingContainerPromises = new Map();
18
+
11
19
  /**
12
20
  * Create a container instance.
13
21
  * @param placement
@@ -15,7 +23,9 @@ const containers = new Map();
15
23
  */
16
24
  async function createContainer(placement, props) {
17
25
  const [container, containerId] = await _ToastContainer.default.getInstance(props);
18
- containers.set(`${containerId}_${placement}`, container);
26
+ const key = `${containerId}_${placement}`;
27
+ containers.set(key, container);
28
+ pendingContainerPromises.delete(key);
19
29
  return container;
20
30
  }
21
31
 
@@ -35,12 +45,37 @@ toaster.push = (message, options = {}) => {
35
45
  ...restOptions
36
46
  } = options;
37
47
  const containerElement = typeof container === 'function' ? container() : container;
38
- const containerElementId = containerElement ? containerElement[_symbols.RSUITE_TOASTER_ID] : null;
39
- if (containerElementId) {
48
+ if (containerElement) {
49
+ // Pre-assign the container ID so subsequent synchronous calls can find it
50
+ // before the async container creation has completed.
51
+ if (!containerElement[_symbols.RSUITE_TOASTER_ID]) {
52
+ containerElement[_symbols.RSUITE_TOASTER_ID] = (0, _utils.guid)();
53
+ }
54
+ const containerElementId = containerElement[_symbols.RSUITE_TOASTER_ID];
55
+ const key = `${containerElementId}_${placement}`;
40
56
  const existedContainer = getContainer(containerElementId, placement);
41
57
  if (existedContainer) {
42
58
  return existedContainer.current?.push(message, restOptions);
43
59
  }
60
+
61
+ // A container creation for this placement may already be in progress (e.g. when `push`
62
+ // is called multiple times synchronously in a loop). Reuse that promise instead of
63
+ // creating a second container.
64
+ const pendingPromise = pendingContainerPromises.get(key);
65
+ if (pendingPromise) {
66
+ return pendingPromise.then(ref => ref.current?.push(message, restOptions));
67
+ }
68
+ const newOptions = {
69
+ ...options,
70
+ container: containerElement,
71
+ placement
72
+ };
73
+ const containerPromise = createContainer(placement, newOptions);
74
+
75
+ // Register the pending promise before any async work begins so that subsequent
76
+ // synchronous `push` calls for the same placement chain onto it.
77
+ pendingContainerPromises.set(key, containerPromise);
78
+ return containerPromise.then(ref => ref.current?.push(message, restOptions));
44
79
  }
45
80
  const newOptions = {
46
81
  ...options,
@@ -3480,6 +3480,9 @@ tbody.rs-anim-collapse.rs-anim-in{
3480
3480
  align-items:center;
3481
3481
  }
3482
3482
  .rs-picker-toggle-indicator .rs-picker-clean{
3483
+ display:inline-flex;
3484
+ align-items:center;
3485
+ justify-content:center;
3483
3486
  color:var(--rs-text-secondary);
3484
3487
  transition:0.2s color linear;
3485
3488
  cursor:pointer;
@@ -14642,6 +14645,17 @@ blockquote.rs-text{
14642
14645
  height:auto;
14643
14646
  min-height:var(--rs-time-line-tail-min-height);
14644
14647
  }
14648
+ .rs-timeline-reverse.rs-timeline-endless .rs-timeline-item:first-child .rs-timeline-item-tail{
14649
+ top:0;
14650
+ bottom:0;
14651
+ height:auto;
14652
+ }
14653
+
14654
+ .rs-timeline-reverse.rs-timeline-endless .rs-timeline-item:last-child .rs-timeline-item-tail{
14655
+ height:calc(var(--rs-time-line-dot-center-gap) + var(--rs-time-line-dot-side-length));
14656
+ min-height:0;
14657
+ }
14658
+
14645
14659
  .rs-timeline-item:only-child .rs-timeline-item-tail{
14646
14660
  display:none;
14647
14661
  }
@@ -15351,6 +15365,7 @@ blockquote.rs-text{
15351
15365
  text-overflow:ellipsis;
15352
15366
  white-space:nowrap;
15353
15367
  flex:1 1 auto;
15368
+ min-width:0;
15354
15369
  }
15355
15370
  .rs-uploader[data-list-type=text] .rs-uploader-file-item-size{
15356
15371
  flex:0 0 auto;
@@ -15397,6 +15412,7 @@ blockquote.rs-text{
15397
15412
  .rs-uploader[data-list-type=picture]{
15398
15413
  display:inline-flex;
15399
15414
  flex-direction:row;
15415
+ flex-wrap:wrap;
15400
15416
  gap:var(--rs-uploader-item-spacing);
15401
15417
  }
15402
15418
  .rs-uploader[data-list-type=picture] .rs-uploader-trigger-btn{
@@ -15431,6 +15447,7 @@ blockquote.rs-text{
15431
15447
  }
15432
15448
  .rs-uploader[data-list-type=picture] .rs-uploader-file-items{
15433
15449
  display:inline-flex;
15450
+ flex-wrap:wrap;
15434
15451
  gap:var(--rs-uploader-item-spacing);
15435
15452
  }
15436
15453
  .rs-uploader[data-list-type=picture] .rs-uploader-file-item{