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.
- package/AutoComplete/styles/index.css +3 -0
- package/CHANGELOG.md +27 -0
- package/Cascader/styles/index.css +3 -0
- package/CheckPicker/styles/index.css +3 -0
- package/CheckTree/styles/index.css +3 -0
- package/CheckTreePicker/styles/index.css +3 -0
- package/DatePicker/styles/index.css +3 -0
- package/DateRangePicker/styles/index.css +3 -0
- package/InputPicker/styles/index.css +3 -0
- package/MultiCascadeTree/styles/index.css +3 -0
- package/MultiCascader/styles/index.css +3 -0
- package/Pagination/styles/index.css +3 -0
- package/SelectPicker/styles/index.css +3 -0
- package/TagInput/styles/index.css +3 -0
- package/TagPicker/styles/index.css +3 -0
- package/TimePicker/styles/index.css +3 -0
- package/TimeRangePicker/styles/index.css +3 -0
- package/Timeline/styles/index.css +11 -0
- package/Timeline/styles/index.scss +13 -0
- package/Tree/styles/index.css +3 -0
- package/TreePicker/styles/index.css +3 -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 +4 -1
- package/cjs/toaster/toaster.js +38 -3
- package/dist/rsuite-no-reset.css +17 -0
- package/dist/rsuite-no-reset.min.css +1 -1
- package/dist/rsuite.css +17 -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 +4 -1
- package/esm/toaster/toaster.js +38 -3
- package/internals/Picker/styles/index.scss +3 -0
- package/package.json +1 -1
|
@@ -2223,6 +2223,9 @@
|
|
|
2223
2223
|
align-items:center;
|
|
2224
2224
|
}
|
|
2225
2225
|
.rs-picker-toggle-indicator .rs-picker-clean{
|
|
2226
|
+
display:inline-flex;
|
|
2227
|
+
align-items:center;
|
|
2228
|
+
justify-content:center;
|
|
2226
2229
|
color:var(--rs-text-secondary);
|
|
2227
2230
|
transition:0.2s color linear;
|
|
2228
2231
|
cursor:pointer;
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
## [6.2.1](https://github.com/rsuite/rsuite/compare/v6.2.0...v6.2.1) (2026-06-12)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **picker:** hide caret when clear button is shown ([#4581](https://github.com/rsuite/rsuite/issues/4581)) ([d52842b](https://github.com/rsuite/rsuite/commit/d52842b0c6444bc6765595899e3f9917de52af7a))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# [6.2.0](https://github.com/rsuite/rsuite/compare/v6.1.3...v6.2.0) (2026-06-12)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **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)
|
|
16
|
+
* **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))
|
|
17
|
+
* **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)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* **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))
|
|
23
|
+
* **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))
|
|
24
|
+
* **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)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
1
28
|
## [6.1.3](https://github.com/rsuite/rsuite/compare/v6.1.2...v6.1.3) (2026-04-22)
|
|
2
29
|
|
|
3
30
|
|
|
@@ -2384,6 +2384,9 @@
|
|
|
2384
2384
|
align-items:center;
|
|
2385
2385
|
}
|
|
2386
2386
|
.rs-picker-toggle-indicator .rs-picker-clean{
|
|
2387
|
+
display:inline-flex;
|
|
2388
|
+
align-items:center;
|
|
2389
|
+
justify-content:center;
|
|
2387
2390
|
color:var(--rs-text-secondary);
|
|
2388
2391
|
transition:0.2s color linear;
|
|
2389
2392
|
cursor:pointer;
|
|
@@ -2411,6 +2411,9 @@ label:hover .rs-checkbox-control .rs-checkbox-inner::before{
|
|
|
2411
2411
|
align-items:center;
|
|
2412
2412
|
}
|
|
2413
2413
|
.rs-picker-toggle-indicator .rs-picker-clean{
|
|
2414
|
+
display:inline-flex;
|
|
2415
|
+
align-items:center;
|
|
2416
|
+
justify-content:center;
|
|
2414
2417
|
color:var(--rs-text-secondary);
|
|
2415
2418
|
transition:0.2s color linear;
|
|
2416
2419
|
cursor:pointer;
|
|
@@ -2242,6 +2242,9 @@
|
|
|
2242
2242
|
align-items:center;
|
|
2243
2243
|
}
|
|
2244
2244
|
.rs-picker-toggle-indicator .rs-picker-clean{
|
|
2245
|
+
display:inline-flex;
|
|
2246
|
+
align-items:center;
|
|
2247
|
+
justify-content:center;
|
|
2245
2248
|
color:var(--rs-text-secondary);
|
|
2246
2249
|
transition:0.2s color linear;
|
|
2247
2250
|
cursor:pointer;
|
|
@@ -2243,6 +2243,9 @@
|
|
|
2243
2243
|
align-items:center;
|
|
2244
2244
|
}
|
|
2245
2245
|
.rs-picker-toggle-indicator .rs-picker-clean{
|
|
2246
|
+
display:inline-flex;
|
|
2247
|
+
align-items:center;
|
|
2248
|
+
justify-content:center;
|
|
2246
2249
|
color:var(--rs-text-secondary);
|
|
2247
2250
|
transition:0.2s color linear;
|
|
2248
2251
|
cursor:pointer;
|
|
@@ -2275,6 +2275,9 @@
|
|
|
2275
2275
|
align-items:center;
|
|
2276
2276
|
}
|
|
2277
2277
|
.rs-picker-toggle-indicator .rs-picker-clean{
|
|
2278
|
+
display:inline-flex;
|
|
2279
|
+
align-items:center;
|
|
2280
|
+
justify-content:center;
|
|
2278
2281
|
color:var(--rs-text-secondary);
|
|
2279
2282
|
transition:0.2s color linear;
|
|
2280
2283
|
cursor:pointer;
|
|
@@ -2277,6 +2277,9 @@
|
|
|
2277
2277
|
align-items:center;
|
|
2278
2278
|
}
|
|
2279
2279
|
.rs-picker-toggle-indicator .rs-picker-clean{
|
|
2280
|
+
display:inline-flex;
|
|
2281
|
+
align-items:center;
|
|
2282
|
+
justify-content:center;
|
|
2280
2283
|
color:var(--rs-text-secondary);
|
|
2281
2284
|
transition:0.2s color linear;
|
|
2282
2285
|
cursor:pointer;
|
|
@@ -2244,6 +2244,9 @@
|
|
|
2244
2244
|
align-items:center;
|
|
2245
2245
|
}
|
|
2246
2246
|
.rs-picker-toggle-indicator .rs-picker-clean{
|
|
2247
|
+
display:inline-flex;
|
|
2248
|
+
align-items:center;
|
|
2249
|
+
justify-content:center;
|
|
2247
2250
|
color:var(--rs-text-secondary);
|
|
2248
2251
|
transition:0.2s color linear;
|
|
2249
2252
|
cursor:pointer;
|
|
@@ -2564,6 +2564,9 @@ label:hover .rs-checkbox-control .rs-checkbox-inner::before{
|
|
|
2564
2564
|
align-items:center;
|
|
2565
2565
|
}
|
|
2566
2566
|
.rs-picker-toggle-indicator .rs-picker-clean{
|
|
2567
|
+
display:inline-flex;
|
|
2568
|
+
align-items:center;
|
|
2569
|
+
justify-content:center;
|
|
2567
2570
|
color:var(--rs-text-secondary);
|
|
2568
2571
|
transition:0.2s color linear;
|
|
2569
2572
|
cursor:pointer;
|
|
@@ -2564,6 +2564,9 @@ label:hover .rs-checkbox-control .rs-checkbox-inner::before{
|
|
|
2564
2564
|
align-items:center;
|
|
2565
2565
|
}
|
|
2566
2566
|
.rs-picker-toggle-indicator .rs-picker-clean{
|
|
2567
|
+
display:inline-flex;
|
|
2568
|
+
align-items:center;
|
|
2569
|
+
justify-content:center;
|
|
2567
2570
|
color:var(--rs-text-secondary);
|
|
2568
2571
|
transition:0.2s color linear;
|
|
2569
2572
|
cursor:pointer;
|
|
@@ -2309,6 +2309,9 @@
|
|
|
2309
2309
|
align-items:center;
|
|
2310
2310
|
}
|
|
2311
2311
|
.rs-picker-toggle-indicator .rs-picker-clean{
|
|
2312
|
+
display:inline-flex;
|
|
2313
|
+
align-items:center;
|
|
2314
|
+
justify-content:center;
|
|
2312
2315
|
color:var(--rs-text-secondary);
|
|
2313
2316
|
transition:0.2s color linear;
|
|
2314
2317
|
cursor:pointer;
|
|
@@ -2244,6 +2244,9 @@
|
|
|
2244
2244
|
align-items:center;
|
|
2245
2245
|
}
|
|
2246
2246
|
.rs-picker-toggle-indicator .rs-picker-clean{
|
|
2247
|
+
display:inline-flex;
|
|
2248
|
+
align-items:center;
|
|
2249
|
+
justify-content:center;
|
|
2247
2250
|
color:var(--rs-text-secondary);
|
|
2248
2251
|
transition:0.2s color linear;
|
|
2249
2252
|
cursor:pointer;
|
|
@@ -2256,6 +2256,9 @@
|
|
|
2256
2256
|
align-items:center;
|
|
2257
2257
|
}
|
|
2258
2258
|
.rs-picker-toggle-indicator .rs-picker-clean{
|
|
2259
|
+
display:inline-flex;
|
|
2260
|
+
align-items:center;
|
|
2261
|
+
justify-content:center;
|
|
2259
2262
|
color:var(--rs-text-secondary);
|
|
2260
2263
|
transition:0.2s color linear;
|
|
2261
2264
|
cursor:pointer;
|
|
@@ -2256,6 +2256,9 @@
|
|
|
2256
2256
|
align-items:center;
|
|
2257
2257
|
}
|
|
2258
2258
|
.rs-picker-toggle-indicator .rs-picker-clean{
|
|
2259
|
+
display:inline-flex;
|
|
2260
|
+
align-items:center;
|
|
2261
|
+
justify-content:center;
|
|
2259
2262
|
color:var(--rs-text-secondary);
|
|
2260
2263
|
transition:0.2s color linear;
|
|
2261
2264
|
cursor:pointer;
|
|
@@ -2275,6 +2275,9 @@
|
|
|
2275
2275
|
align-items:center;
|
|
2276
2276
|
}
|
|
2277
2277
|
.rs-picker-toggle-indicator .rs-picker-clean{
|
|
2278
|
+
display:inline-flex;
|
|
2279
|
+
align-items:center;
|
|
2280
|
+
justify-content:center;
|
|
2278
2281
|
color:var(--rs-text-secondary);
|
|
2279
2282
|
transition:0.2s color linear;
|
|
2280
2283
|
cursor:pointer;
|
|
@@ -2277,6 +2277,9 @@
|
|
|
2277
2277
|
align-items:center;
|
|
2278
2278
|
}
|
|
2279
2279
|
.rs-picker-toggle-indicator .rs-picker-clean{
|
|
2280
|
+
display:inline-flex;
|
|
2281
|
+
align-items:center;
|
|
2282
|
+
justify-content:center;
|
|
2280
2283
|
color:var(--rs-text-secondary);
|
|
2281
2284
|
transition:0.2s color linear;
|
|
2282
2285
|
cursor:pointer;
|
|
@@ -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
|
}
|
package/Tree/styles/index.css
CHANGED
|
@@ -2242,6 +2242,9 @@
|
|
|
2242
2242
|
align-items:center;
|
|
2243
2243
|
}
|
|
2244
2244
|
.rs-picker-toggle-indicator .rs-picker-clean{
|
|
2245
|
+
display:inline-flex;
|
|
2246
|
+
align-items:center;
|
|
2247
|
+
justify-content:center;
|
|
2245
2248
|
color:var(--rs-text-secondary);
|
|
2246
2249
|
transition:0.2s color linear;
|
|
2247
2250
|
cursor:pointer;
|
|
@@ -2242,6 +2242,9 @@
|
|
|
2242
2242
|
align-items:center;
|
|
2243
2243
|
}
|
|
2244
2244
|
.rs-picker-toggle-indicator .rs-picker-clean{
|
|
2245
|
+
display:inline-flex;
|
|
2246
|
+
align-items:center;
|
|
2247
|
+
justify-content:center;
|
|
2245
2248
|
color:var(--rs-text-secondary);
|
|
2246
2249
|
transition:0.2s color linear;
|
|
2247
2250
|
cursor:pointer;
|
|
@@ -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 = {
|