@douyinfe/semi-foundation 2.102.0 → 2.104.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/lib/cjs/modal/modalContentFoundation.js +21 -0
- package/lib/cjs/progress/progress.css +48 -0
- package/lib/cjs/progress/progress.scss +60 -1
- package/lib/cjs/radio/radio.css +5 -0
- package/lib/cjs/radio/radio.scss +9 -0
- package/lib/cjs/resizable/group/index.d.ts +9 -0
- package/lib/cjs/resizable/group/index.js +18 -0
- package/lib/cjs/timePicker/utils/index.js +2 -1
- package/lib/cjs/upload/foundation.d.ts +27 -0
- package/lib/cjs/upload/foundation.js +75 -33
- package/lib/es/modal/modalContentFoundation.js +21 -0
- package/lib/es/progress/progress.css +48 -0
- package/lib/es/progress/progress.scss +60 -1
- package/lib/es/radio/radio.css +5 -0
- package/lib/es/radio/radio.scss +9 -0
- package/lib/es/resizable/group/index.d.ts +9 -0
- package/lib/es/resizable/group/index.js +18 -0
- package/lib/es/timePicker/utils/index.js +2 -1
- package/lib/es/upload/foundation.d.ts +27 -0
- package/lib/es/upload/foundation.js +75 -33
- package/modal/modalContentFoundation.ts +22 -0
- package/package.json +4 -4
- package/progress/progress.scss +60 -1
- package/radio/radio.scss +9 -0
- package/resizable/group/index.ts +19 -0
- package/timePicker/utils/index.ts +2 -1
- package/upload/foundation.ts +81 -27
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
.semi-progress-horizontal .semi-progress-track {
|
|
21
21
|
height: 100%;
|
|
22
22
|
width: 100%;
|
|
23
|
+
overflow: hidden;
|
|
23
24
|
}
|
|
24
25
|
.semi-progress-horizontal .semi-progress-track-inner {
|
|
25
26
|
height: 100%;
|
|
@@ -28,6 +29,11 @@
|
|
|
28
29
|
transition: width 0.3s;
|
|
29
30
|
transition-timing-function: cubic-bezier(0.62, 0.05, 0.36, 0.95);
|
|
30
31
|
}
|
|
32
|
+
.semi-progress-horizontal .semi-progress-track-inner-indeterminate {
|
|
33
|
+
width: 35%;
|
|
34
|
+
transition: none;
|
|
35
|
+
animation: semi-progress-indeterminate-slide 1.6s ease-in-out infinite;
|
|
36
|
+
}
|
|
31
37
|
.semi-progress-horizontal .semi-progress-line-text {
|
|
32
38
|
min-width: 45px;
|
|
33
39
|
font-weight: 600;
|
|
@@ -48,6 +54,7 @@
|
|
|
48
54
|
.semi-progress-vertical .semi-progress-track {
|
|
49
55
|
height: 100%;
|
|
50
56
|
width: 100%;
|
|
57
|
+
overflow: hidden;
|
|
51
58
|
}
|
|
52
59
|
.semi-progress-vertical .semi-progress-track-inner {
|
|
53
60
|
background-color: var(--semi-color-success);
|
|
@@ -56,6 +63,12 @@
|
|
|
56
63
|
transition: height 0.3s;
|
|
57
64
|
transition-timing-function: cubic-bezier(0.62, 0.05, 0.36, 0.95);
|
|
58
65
|
}
|
|
66
|
+
.semi-progress-vertical .semi-progress-track-inner-indeterminate {
|
|
67
|
+
height: 35%;
|
|
68
|
+
width: 100%;
|
|
69
|
+
transition: none;
|
|
70
|
+
animation: semi-progress-indeterminate-slide-vertical 1.6s ease-in-out infinite;
|
|
71
|
+
}
|
|
59
72
|
.semi-progress-vertical .semi-progress-line-text {
|
|
60
73
|
font-weight: 600;
|
|
61
74
|
margin-top: 8px;
|
|
@@ -77,6 +90,11 @@
|
|
|
77
90
|
transform-origin: 50% 50%;
|
|
78
91
|
stroke: var(--semi-color-success);
|
|
79
92
|
}
|
|
93
|
+
.semi-progress-circle-ring-inner-indeterminate {
|
|
94
|
+
transform: rotate(0deg);
|
|
95
|
+
transform-origin: 50% 50%;
|
|
96
|
+
animation: semi-progress-indeterminate-rotate 1.2s linear infinite;
|
|
97
|
+
}
|
|
80
98
|
.semi-progress-circle-text {
|
|
81
99
|
position: absolute;
|
|
82
100
|
top: 50%;
|
|
@@ -88,6 +106,36 @@
|
|
|
88
106
|
color: var(--semi-color-mode-minor-text);
|
|
89
107
|
}
|
|
90
108
|
|
|
109
|
+
@keyframes semi-progress-indeterminate-slide {
|
|
110
|
+
0% {
|
|
111
|
+
transform: translateX(-120%);
|
|
112
|
+
}
|
|
113
|
+
55% {
|
|
114
|
+
transform: translateX(300%);
|
|
115
|
+
}
|
|
116
|
+
100% {
|
|
117
|
+
transform: translateX(300%);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
@keyframes semi-progress-indeterminate-slide-vertical {
|
|
121
|
+
0% {
|
|
122
|
+
transform: translateY(-120%);
|
|
123
|
+
}
|
|
124
|
+
55% {
|
|
125
|
+
transform: translateY(300%);
|
|
126
|
+
}
|
|
127
|
+
100% {
|
|
128
|
+
transform: translateY(300%);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
@keyframes semi-progress-indeterminate-rotate {
|
|
132
|
+
from {
|
|
133
|
+
transform: rotate(0deg);
|
|
134
|
+
}
|
|
135
|
+
to {
|
|
136
|
+
transform: rotate(360deg);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
91
139
|
.semi-rtl .semi-progress,
|
|
92
140
|
.semi-portal-rtl .semi-progress {
|
|
93
141
|
direction: rtl;
|
|
@@ -23,6 +23,7 @@ $module: #{$prefix}-progress;
|
|
|
23
23
|
.#{$module}-track {
|
|
24
24
|
height: 100%;
|
|
25
25
|
width: 100%;
|
|
26
|
+
overflow: hidden;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
.#{$module}-track-inner {
|
|
@@ -33,6 +34,12 @@ $module: #{$prefix}-progress;
|
|
|
33
34
|
transition-timing-function: $motion-progress-transition_timing_function;
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
.#{$module}-track-inner-indeterminate {
|
|
38
|
+
width: 35%;
|
|
39
|
+
transition: none;
|
|
40
|
+
animation: semi-progress-indeterminate-slide 1.6s ease-in-out infinite;
|
|
41
|
+
}
|
|
42
|
+
|
|
36
43
|
.#{$module}-line-text {
|
|
37
44
|
min-width: $width-progress_line_text;
|
|
38
45
|
font-weight: $font-progress_line_text-fontWeight;
|
|
@@ -57,6 +64,7 @@ $module: #{$prefix}-progress;
|
|
|
57
64
|
.#{$module}-track {
|
|
58
65
|
height: 100%;
|
|
59
66
|
width: 100%;
|
|
67
|
+
overflow: hidden;
|
|
60
68
|
}
|
|
61
69
|
|
|
62
70
|
.#{$module}-track-inner {
|
|
@@ -67,6 +75,13 @@ $module: #{$prefix}-progress;
|
|
|
67
75
|
transition-timing-function: $motion-progress-transition_timing_function;
|
|
68
76
|
}
|
|
69
77
|
|
|
78
|
+
.#{$module}-track-inner-indeterminate {
|
|
79
|
+
height: 35%;
|
|
80
|
+
width: 100%;
|
|
81
|
+
transition: none;
|
|
82
|
+
animation: semi-progress-indeterminate-slide-vertical 1.6s ease-in-out infinite;
|
|
83
|
+
}
|
|
84
|
+
|
|
70
85
|
.#{$module}-line-text {
|
|
71
86
|
font-weight: $font-progress_line_text-fontWeight;
|
|
72
87
|
margin-top: $spacing-progress_vertical_line_text-marginTop;
|
|
@@ -95,6 +110,12 @@ $module: #{$prefix}-progress;
|
|
|
95
110
|
stroke: $color-progress_track_inner-bg;
|
|
96
111
|
}
|
|
97
112
|
|
|
113
|
+
&-ring-inner-indeterminate {
|
|
114
|
+
transform: rotate(0deg);
|
|
115
|
+
transform-origin: 50% 50%;
|
|
116
|
+
animation: semi-progress-indeterminate-rotate 1.2s linear infinite;
|
|
117
|
+
}
|
|
118
|
+
|
|
98
119
|
&-text {
|
|
99
120
|
position: absolute;
|
|
100
121
|
top: 50%;
|
|
@@ -108,4 +129,42 @@ $module: #{$prefix}-progress;
|
|
|
108
129
|
}
|
|
109
130
|
}
|
|
110
131
|
|
|
111
|
-
@
|
|
132
|
+
@keyframes semi-progress-indeterminate-slide {
|
|
133
|
+
0% {
|
|
134
|
+
transform: translateX(-120%);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
55% {
|
|
138
|
+
transform: translateX(300%);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
100% {
|
|
142
|
+
transform: translateX(300%);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@keyframes semi-progress-indeterminate-slide-vertical {
|
|
147
|
+
0% {
|
|
148
|
+
transform: translateY(-120%);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
55% {
|
|
152
|
+
transform: translateY(300%);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
100% {
|
|
156
|
+
transform: translateY(300%);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
@keyframes semi-progress-indeterminate-rotate {
|
|
161
|
+
from {
|
|
162
|
+
transform: rotate(0deg);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
to {
|
|
166
|
+
transform: rotate(360deg);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
@import './rtl.scss';
|
package/lib/es/radio/radio.css
CHANGED
package/lib/es/radio/radio.scss
CHANGED
|
@@ -236,6 +236,15 @@ $inner-width: $width-icon-medium;
|
|
|
236
236
|
width: 100%;
|
|
237
237
|
height: 100%;
|
|
238
238
|
font-size: 14px;
|
|
239
|
+
|
|
240
|
+
// #3350: 显式设置 svg 尺寸并 block 化,替代 svg 自带的 width/height="1em"。
|
|
241
|
+
// Safari 对以 em 指定尺寸的内联 SVG 存在渲染偏差,会导致 checked 状态
|
|
242
|
+
// 的白点视觉偏移;100% 让 svg 与图标容器严格同尺寸,无偏移空间。
|
|
243
|
+
svg {
|
|
244
|
+
display: block;
|
|
245
|
+
width: 100%;
|
|
246
|
+
height: 100%;
|
|
247
|
+
}
|
|
239
248
|
}
|
|
240
249
|
}
|
|
241
250
|
}
|
|
@@ -41,7 +41,16 @@ export declare class ResizeGroupFoundation<P = Record<string, any>, S = Record<s
|
|
|
41
41
|
totalMinus: number;
|
|
42
42
|
itemPercentMap: Map<number, number>;
|
|
43
43
|
type?: ResizeEventType;
|
|
44
|
+
sizeInitialized: boolean;
|
|
44
45
|
init(): void;
|
|
46
|
+
/**
|
|
47
|
+
* When the group is initially mounted inside a display:none container, its own size
|
|
48
|
+
* and the handler sizes are all 0, so item sizes are computed with a `- 0px` handler
|
|
49
|
+
* offset and never corrected once the container becomes visible. Recalculate the layout
|
|
50
|
+
* the first time the group becomes measurable. Once it has a valid size, later resizes
|
|
51
|
+
* must NOT re-run initSpace, otherwise the user's manual drag results would be reset. #3336
|
|
52
|
+
*/
|
|
53
|
+
handleGroupResize: () => void;
|
|
45
54
|
get window(): Window | null;
|
|
46
55
|
registerEvents: () => void;
|
|
47
56
|
unregisterEvents: () => void;
|
|
@@ -23,6 +23,21 @@ export class ResizeItemFoundation extends BaseFoundation {
|
|
|
23
23
|
export class ResizeGroupFoundation extends BaseFoundation {
|
|
24
24
|
constructor(adapter) {
|
|
25
25
|
super(Object.assign({}, adapter));
|
|
26
|
+
// 首次 initSpace 时 group 是否可测量(非 display:none)。
|
|
27
|
+
// Whether the group was measurable (not inside display:none) on the first initSpace run. #3336
|
|
28
|
+
this.sizeInitialized = false;
|
|
29
|
+
/**
|
|
30
|
+
* When the group is initially mounted inside a display:none container, its own size
|
|
31
|
+
* and the handler sizes are all 0, so item sizes are computed with a `- 0px` handler
|
|
32
|
+
* offset and never corrected once the container becomes visible. Recalculate the layout
|
|
33
|
+
* the first time the group becomes measurable. Once it has a valid size, later resizes
|
|
34
|
+
* must NOT re-run initSpace, otherwise the user's manual drag results would be reset. #3336
|
|
35
|
+
*/
|
|
36
|
+
this.handleGroupResize = () => {
|
|
37
|
+
if (!this.sizeInitialized && this.groupSize > 0) {
|
|
38
|
+
this.initSpace();
|
|
39
|
+
}
|
|
40
|
+
};
|
|
26
41
|
this.registerEvents = () => {
|
|
27
42
|
this._adapter.registerEvents(this.type);
|
|
28
43
|
};
|
|
@@ -186,6 +201,9 @@ export class ResizeGroupFoundation extends BaseFoundation {
|
|
|
186
201
|
// calculate accurate space for group item
|
|
187
202
|
let handlerSizes = new Array(this._adapter.getHandlerCount()).fill(0);
|
|
188
203
|
let parentSize = this.groupSize;
|
|
204
|
+
// Mark whether the group is measurable now; if not (e.g. inside display:none),
|
|
205
|
+
// handleGroupResize will re-run initSpace once it becomes visible. #3336
|
|
206
|
+
this.sizeInitialized = parentSize > 0;
|
|
189
207
|
this.totalMinus = 0;
|
|
190
208
|
for (let i = 0; i < this._adapter.getHandlerCount(); i++) {
|
|
191
209
|
let handlerSize = direction === 'horizontal' ? this._adapter.getHandler(i).offsetWidth : this._adapter.getHandler(i).offsetHeight;
|
|
@@ -111,7 +111,8 @@ export const isTimeFormatLike = (time, formatToken) => {
|
|
|
111
111
|
const hmsReg = /[H|m|s]{1,2}/;
|
|
112
112
|
const formatSplitted = formatToken.split(formatNotSupportChReg); // => ['HH', 'mm'];
|
|
113
113
|
const timeSeparator = formatToken.replace(formatSupportChReg, ''); // => :
|
|
114
|
-
const
|
|
114
|
+
const escapedTimeSeparator = timeSeparator.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); // escape regExp special characters to avoid invalid regExp, e.g. format="HH\\mm"
|
|
115
|
+
const timeReg = new RegExp(`[${escapedTimeSeparator}]`, 'g'); // => /[:]/g
|
|
115
116
|
const timeSplitted = time.split(timeReg); // => ['12', '0]
|
|
116
117
|
if (formatSplitted.length !== timeSplitted.length) {
|
|
117
118
|
isLike = false;
|
|
@@ -94,6 +94,33 @@ declare class UploadFoundation<P = Record<string, any>, S = Record<string, any>>
|
|
|
94
94
|
* When paste event is successfully handled, we ignore the subsequent keydown event.
|
|
95
95
|
*/
|
|
96
96
|
_pasteHandled: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Working copy of fileList used during a synchronous startUpload batch.
|
|
99
|
+
* Do NOT rely on React state fileList here: setState is async (batched in React 18),
|
|
100
|
+
* so reading getState('fileList') between iterations of a sync loop returns a stale
|
|
101
|
+
* snapshot and earlier removals get overwritten by later ones (see #3335).
|
|
102
|
+
* Seeded at the start of startUpload and cleared when the sync loop finishes (see
|
|
103
|
+
* try/finally in startUpload); async (promise) results fall back to _latestFileList
|
|
104
|
+
* then React state.
|
|
105
|
+
*/
|
|
106
|
+
_batchFileList: Array<BaseFileItem> | null;
|
|
107
|
+
/**
|
|
108
|
+
* The most recent fileList produced by handleBeforeUploadResultInObject.
|
|
109
|
+
* Unlike React state (which is flushed asynchronously under React 18 automatic
|
|
110
|
+
* batching), this is updated synchronously, so a later async beforeUpload result
|
|
111
|
+
* composes with earlier sync removals of the same batch instead of reading a stale
|
|
112
|
+
* snapshot (mixed sync + async beforeUpload results, see #3335).
|
|
113
|
+
*/
|
|
114
|
+
_latestFileList: Array<BaseFileItem> | null;
|
|
115
|
+
/**
|
|
116
|
+
* Unify all fileList commits: keep _latestFileList in sync synchronously so async
|
|
117
|
+
* beforeUpload results compose with recent mutations even before React flushes
|
|
118
|
+
* setState (React 18 automatic batching). (#3335)
|
|
119
|
+
*/
|
|
120
|
+
updateFileListInternal(newFileList: Array<BaseFileItem>, callback?: () => void): void;
|
|
121
|
+
/** Keep the synchronous snapshot aligned with an externally controlled fileList. */
|
|
122
|
+
syncLatestFileList(fileList: Array<BaseFileItem>): void;
|
|
123
|
+
getLatestFileList(): Array<BaseFileItem>;
|
|
97
124
|
constructor(adapter: UploadAdapter<P, S>);
|
|
98
125
|
init(): void;
|
|
99
126
|
/**
|
|
@@ -41,6 +41,25 @@ const {
|
|
|
41
41
|
TRIGGER_AUTO
|
|
42
42
|
} = strings;
|
|
43
43
|
class UploadFoundation extends BaseFoundation {
|
|
44
|
+
/**
|
|
45
|
+
* Unify all fileList commits: keep _latestFileList in sync synchronously so async
|
|
46
|
+
* beforeUpload results compose with recent mutations even before React flushes
|
|
47
|
+
* setState (React 18 automatic batching). (#3335)
|
|
48
|
+
*/
|
|
49
|
+
updateFileListInternal(newFileList, callback) {
|
|
50
|
+
if (this._batchFileList) {
|
|
51
|
+
this._batchFileList = newFileList;
|
|
52
|
+
}
|
|
53
|
+
this._latestFileList = newFileList;
|
|
54
|
+
this._adapter.updateFileList(newFileList, callback);
|
|
55
|
+
}
|
|
56
|
+
/** Keep the synchronous snapshot aligned with an externally controlled fileList. */
|
|
57
|
+
syncLatestFileList(fileList) {
|
|
58
|
+
this._latestFileList = fileList.slice();
|
|
59
|
+
}
|
|
60
|
+
getLatestFileList() {
|
|
61
|
+
return this._batchFileList || this._latestFileList || this.getState('fileList');
|
|
62
|
+
}
|
|
44
63
|
constructor(adapter) {
|
|
45
64
|
super(Object.assign({}, adapter));
|
|
46
65
|
this.destroyState = false;
|
|
@@ -55,10 +74,29 @@ class UploadFoundation extends BaseFoundation {
|
|
|
55
74
|
* When paste event is successfully handled, we ignore the subsequent keydown event.
|
|
56
75
|
*/
|
|
57
76
|
this._pasteHandled = false;
|
|
77
|
+
/**
|
|
78
|
+
* Working copy of fileList used during a synchronous startUpload batch.
|
|
79
|
+
* Do NOT rely on React state fileList here: setState is async (batched in React 18),
|
|
80
|
+
* so reading getState('fileList') between iterations of a sync loop returns a stale
|
|
81
|
+
* snapshot and earlier removals get overwritten by later ones (see #3335).
|
|
82
|
+
* Seeded at the start of startUpload and cleared when the sync loop finishes (see
|
|
83
|
+
* try/finally in startUpload); async (promise) results fall back to _latestFileList
|
|
84
|
+
* then React state.
|
|
85
|
+
*/
|
|
86
|
+
this._batchFileList = null;
|
|
87
|
+
/**
|
|
88
|
+
* The most recent fileList produced by handleBeforeUploadResultInObject.
|
|
89
|
+
* Unlike React state (which is flushed asynchronously under React 18 automatic
|
|
90
|
+
* batching), this is updated synchronously, so a later async beforeUpload result
|
|
91
|
+
* composes with earlier sync removals of the same batch instead of reading a stale
|
|
92
|
+
* snapshot (mixed sync + async beforeUpload results, see #3335).
|
|
93
|
+
*/
|
|
94
|
+
this._latestFileList = null;
|
|
58
95
|
}
|
|
59
96
|
init() {
|
|
60
97
|
// make sure state reset, otherwise may cause upload abort in React StrictMode, like https://github.com/DouyinFE/semi-design/pull/843
|
|
61
98
|
this.destroyState = false;
|
|
99
|
+
this.syncLatestFileList(this.getState('fileList'));
|
|
62
100
|
// In controlled mode, parent may keep and pass back fileList with blob url.
|
|
63
101
|
// Sync them into internal map so later remove/clear can revoke correctly.
|
|
64
102
|
this._syncLocalUrlsFromFileList();
|
|
@@ -108,6 +146,8 @@ class UploadFoundation extends BaseFoundation {
|
|
|
108
146
|
if (!disabled) {
|
|
109
147
|
this.unbindPastingHandler();
|
|
110
148
|
}
|
|
149
|
+
this._batchFileList = null;
|
|
150
|
+
this._latestFileList = null;
|
|
111
151
|
this.destroyState = true;
|
|
112
152
|
}
|
|
113
153
|
/**
|
|
@@ -324,7 +364,7 @@ class UploadFoundation extends BaseFoundation {
|
|
|
324
364
|
currentFile: newFileItem,
|
|
325
365
|
fileList: newFileList
|
|
326
366
|
});
|
|
327
|
-
this.
|
|
367
|
+
this.updateFileListInternal(newFileList, () => {
|
|
328
368
|
this._adapter.resetReplaceInput();
|
|
329
369
|
if (!newFileItem._sizeInvalid) {
|
|
330
370
|
this.upload(newFileItem);
|
|
@@ -368,7 +408,7 @@ class UploadFoundation extends BaseFoundation {
|
|
|
368
408
|
fileList: currentFiles,
|
|
369
409
|
currentFile: currentFiles[0]
|
|
370
410
|
});
|
|
371
|
-
this.
|
|
411
|
+
this.updateFileListInternal(currentFiles, () => {
|
|
372
412
|
if (uploadTrigger === TRIGGER_AUTO) {
|
|
373
413
|
this.startUpload(currentFiles);
|
|
374
414
|
}
|
|
@@ -392,7 +432,7 @@ class UploadFoundation extends BaseFoundation {
|
|
|
392
432
|
});
|
|
393
433
|
}
|
|
394
434
|
});
|
|
395
|
-
this.
|
|
435
|
+
this.updateFileListInternal(fileList, () => {
|
|
396
436
|
if (uploadTrigger === TRIGGER_AUTO) {
|
|
397
437
|
this.startUpload(currentFiles);
|
|
398
438
|
}
|
|
@@ -476,7 +516,7 @@ class UploadFoundation extends BaseFoundation {
|
|
|
476
516
|
fileList: newFileList,
|
|
477
517
|
currentFile: null
|
|
478
518
|
});
|
|
479
|
-
this.
|
|
519
|
+
this.updateFileListInternal(newFileList, () => {
|
|
480
520
|
if (uploadTrigger === TRIGGER_AUTO) {
|
|
481
521
|
this.startUpload(fileItemList);
|
|
482
522
|
}
|
|
@@ -485,15 +525,24 @@ class UploadFoundation extends BaseFoundation {
|
|
|
485
525
|
/* istanbul ignore next */
|
|
486
526
|
manualUpload() {
|
|
487
527
|
// find the list of files that have not been uploaded
|
|
488
|
-
const waitToUploadFileList = this.
|
|
528
|
+
const waitToUploadFileList = this.getLatestFileList().filter(item => item.status === FILE_STATUS_WAIT_UPLOAD);
|
|
489
529
|
this.startUpload(waitToUploadFileList);
|
|
490
530
|
}
|
|
491
531
|
startUpload(fileList) {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
532
|
+
// Seed a working copy so multiple synchronous beforeUpload results
|
|
533
|
+
// (e.g. several files returning autoRemove) compose correctly instead of each
|
|
534
|
+
// reading a stale React state snapshot and overwriting earlier removals. (#3335)
|
|
535
|
+
// try/finally guarantees the working copy is cleared even if beforeUpload throws.
|
|
536
|
+
this._batchFileList = this.getLatestFileList().slice();
|
|
537
|
+
try {
|
|
538
|
+
fileList.forEach(file => {
|
|
539
|
+
if (!file._sizeInvalid) {
|
|
540
|
+
this.upload(file);
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
} finally {
|
|
544
|
+
this._batchFileList = null;
|
|
545
|
+
}
|
|
497
546
|
}
|
|
498
547
|
upload(file) {
|
|
499
548
|
const {
|
|
@@ -504,9 +553,7 @@ class UploadFoundation extends BaseFoundation {
|
|
|
504
553
|
return;
|
|
505
554
|
}
|
|
506
555
|
if (typeof beforeUpload === 'function') {
|
|
507
|
-
const
|
|
508
|
-
fileList
|
|
509
|
-
} = this.getStates();
|
|
556
|
+
const fileList = this.getLatestFileList();
|
|
510
557
|
const buResult = this._adapter.notifyBeforeUpload({
|
|
511
558
|
file,
|
|
512
559
|
fileList
|
|
@@ -571,7 +618,11 @@ class UploadFoundation extends BaseFoundation {
|
|
|
571
618
|
validateMessage,
|
|
572
619
|
fileInstance
|
|
573
620
|
} = buResult;
|
|
574
|
-
|
|
621
|
+
// During a synchronous startUpload batch, read from the working copy; for async
|
|
622
|
+
// (promise) results read from the most recent synchronous snapshot, falling back
|
|
623
|
+
// to React state. This keeps mixed sync + async beforeUpload results composing
|
|
624
|
+
// correctly under React 18 automatic batching. (#3335)
|
|
625
|
+
let newFileList = this.getLatestFileList().slice();
|
|
575
626
|
if (autoRemove) {
|
|
576
627
|
this._releaseFileUrl(file.uid);
|
|
577
628
|
newFileList = newFileList.filter(item => item.uid !== file.uid);
|
|
@@ -593,7 +644,7 @@ class UploadFoundation extends BaseFoundation {
|
|
|
593
644
|
}
|
|
594
645
|
newFileList[index].shouldUpload = shouldUpload;
|
|
595
646
|
}
|
|
596
|
-
this.
|
|
647
|
+
this.updateFileListInternal(newFileList);
|
|
597
648
|
this._adapter.notifyChange({
|
|
598
649
|
fileList: newFileList,
|
|
599
650
|
currentFile: file
|
|
@@ -707,10 +758,7 @@ class UploadFoundation extends BaseFoundation {
|
|
|
707
758
|
e,
|
|
708
759
|
fileInstance
|
|
709
760
|
} = _ref2;
|
|
710
|
-
const
|
|
711
|
-
fileList
|
|
712
|
-
} = this.getStates();
|
|
713
|
-
const newFileList = fileList.slice();
|
|
761
|
+
const newFileList = this.getLatestFileList().slice();
|
|
714
762
|
let percent = 0;
|
|
715
763
|
if (e.total > 0) {
|
|
716
764
|
percent = Number((e.loaded / e.total * 100 * numbers.PROGRESS_COEFFICIENT).toFixed(0)) || 0;
|
|
@@ -722,7 +770,7 @@ class UploadFoundation extends BaseFoundation {
|
|
|
722
770
|
newFileList[index].percent = percent;
|
|
723
771
|
newFileList[index].status = FILE_STATUS_UPLOADING;
|
|
724
772
|
this._adapter.notifyProgress(percent, fileInstance, newFileList);
|
|
725
|
-
this.
|
|
773
|
+
this.updateFileListInternal(newFileList);
|
|
726
774
|
this._adapter.notifyChange({
|
|
727
775
|
fileList: newFileList,
|
|
728
776
|
currentFile: newFileList[index]
|
|
@@ -734,9 +782,7 @@ class UploadFoundation extends BaseFoundation {
|
|
|
734
782
|
xhr,
|
|
735
783
|
fileInstance
|
|
736
784
|
} = _ref3;
|
|
737
|
-
const
|
|
738
|
-
fileList
|
|
739
|
-
} = this.getStates();
|
|
785
|
+
const fileList = this.getLatestFileList();
|
|
740
786
|
const index = this._getFileIndex(fileInstance, fileList);
|
|
741
787
|
if (index < 0) {
|
|
742
788
|
return;
|
|
@@ -764,9 +810,7 @@ class UploadFoundation extends BaseFoundation {
|
|
|
764
810
|
xhr,
|
|
765
811
|
response
|
|
766
812
|
} = _ref4;
|
|
767
|
-
const
|
|
768
|
-
fileList
|
|
769
|
-
} = this.getStates();
|
|
813
|
+
const fileList = this.getLatestFileList();
|
|
770
814
|
let body = null;
|
|
771
815
|
const index = this._getFileIndex(fileInstance, fileList);
|
|
772
816
|
if (index < 0) {
|
|
@@ -817,7 +861,7 @@ class UploadFoundation extends BaseFoundation {
|
|
|
817
861
|
fileList: newFileList,
|
|
818
862
|
currentFile: newFileList[index]
|
|
819
863
|
});
|
|
820
|
-
this.
|
|
864
|
+
this.updateFileListInternal(newFileList);
|
|
821
865
|
}
|
|
822
866
|
_getFileIndex(file, fileList) {
|
|
823
867
|
return fileList.findIndex(item => item.uid === file.uid);
|
|
@@ -845,7 +889,7 @@ class UploadFoundation extends BaseFoundation {
|
|
|
845
889
|
newFileList.splice(index, 1);
|
|
846
890
|
this._releaseFileUrl(file.uid);
|
|
847
891
|
this._adapter.notifyRemove(file.fileInstance, newFileList, file);
|
|
848
|
-
this.
|
|
892
|
+
this.updateFileListInternal(newFileList);
|
|
849
893
|
this._adapter.notifyChange({
|
|
850
894
|
fileList: newFileList,
|
|
851
895
|
currentFile: file
|
|
@@ -858,9 +902,7 @@ class UploadFoundation extends BaseFoundation {
|
|
|
858
902
|
xhr,
|
|
859
903
|
fileInstance
|
|
860
904
|
} = _ref5;
|
|
861
|
-
const
|
|
862
|
-
fileList
|
|
863
|
-
} = this.getStates();
|
|
905
|
+
const fileList = this.getLatestFileList();
|
|
864
906
|
const index = this._getFileIndex(fileInstance, fileList);
|
|
865
907
|
if (index < 0) {
|
|
866
908
|
return;
|
|
@@ -878,7 +920,7 @@ class UploadFoundation extends BaseFoundation {
|
|
|
878
920
|
newFileList[index].response = error;
|
|
879
921
|
newFileList[index].event = e;
|
|
880
922
|
this._adapter.notifyError(error, fileInstance, newFileList, xhr);
|
|
881
|
-
this.
|
|
923
|
+
this.updateFileListInternal(newFileList);
|
|
882
924
|
this._adapter.notifyChange({
|
|
883
925
|
currentFile: newFileList[index],
|
|
884
926
|
fileList: newFileList
|
|
@@ -899,7 +941,7 @@ class UploadFoundation extends BaseFoundation {
|
|
|
899
941
|
return;
|
|
900
942
|
}
|
|
901
943
|
this._releaseAllFileUrls();
|
|
902
|
-
this.
|
|
944
|
+
this.updateFileListInternal([]);
|
|
903
945
|
this._adapter.notifyClear();
|
|
904
946
|
this._adapter.notifyChange({
|
|
905
947
|
fileList: []
|
|
@@ -30,6 +30,15 @@ export interface ModalContentAdapter extends DefaultAdapter<ModalContentProps, M
|
|
|
30
30
|
prevFocusElementReFocus: () => void
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Stack of `handleKeyDown` handlers for modals that currently listen on
|
|
35
|
+
* `document`. Every ModalContent registers its own document-level keydown
|
|
36
|
+
* listener, so `stopPropagation()` cannot stop sibling listeners on the same
|
|
37
|
+
* node — one ESC used to close every open dialog. Only the most recently
|
|
38
|
+
* mounted (top-most) listener should respond.
|
|
39
|
+
*/
|
|
40
|
+
const escListenerStack: Array<(e: any) => void> = [];
|
|
41
|
+
|
|
33
42
|
export default class ModalContentFoundation extends BaseFoundation<ModalContentAdapter> {
|
|
34
43
|
|
|
35
44
|
constructor(adapter: ModalContentAdapter) {
|
|
@@ -53,6 +62,12 @@ export default class ModalContentFoundation extends BaseFoundation<ModalContentA
|
|
|
53
62
|
handleKeyDown = (e: any) => {
|
|
54
63
|
const { closeOnEsc } = this.getProps();
|
|
55
64
|
if (closeOnEsc && e.keyCode === KeyCode.ESC) {
|
|
65
|
+
// Multiple open modals each registered a document-level keydown
|
|
66
|
+
// listener; only the top-most (most recently mounted) one may
|
|
67
|
+
// close, so a single ESC closes dialogs one at a time.
|
|
68
|
+
if (escListenerStack[escListenerStack.length - 1] !== this.handleKeyDown) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
56
71
|
e.stopPropagation();
|
|
57
72
|
this.close(e);
|
|
58
73
|
return;
|
|
@@ -61,10 +76,17 @@ export default class ModalContentFoundation extends BaseFoundation<ModalContentA
|
|
|
61
76
|
|
|
62
77
|
handleKeyDownEventListenerMount() {
|
|
63
78
|
this._adapter.addKeyDownEventListener();
|
|
79
|
+
if (this.getProps().closeOnEsc) {
|
|
80
|
+
escListenerStack.push(this.handleKeyDown);
|
|
81
|
+
}
|
|
64
82
|
}
|
|
65
83
|
|
|
66
84
|
handleKeyDownEventListenerUnmount() {
|
|
67
85
|
this._adapter.removeKeyDownEventListener();
|
|
86
|
+
const index = escListenerStack.indexOf(this.handleKeyDown);
|
|
87
|
+
if (index !== -1) {
|
|
88
|
+
escListenerStack.splice(index, 1);
|
|
89
|
+
}
|
|
68
90
|
}
|
|
69
91
|
|
|
70
92
|
getMouseState() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@douyinfe/semi-foundation",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.104.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"clean": "rimraf lib",
|
|
@@ -14225,8 +14225,8 @@
|
|
|
14225
14225
|
}
|
|
14226
14226
|
},
|
|
14227
14227
|
"dependencies": {
|
|
14228
|
-
"@douyinfe/semi-animation": "2.
|
|
14229
|
-
"@douyinfe/semi-json-viewer-core": "2.
|
|
14228
|
+
"@douyinfe/semi-animation": "2.104.0",
|
|
14229
|
+
"@douyinfe/semi-json-viewer-core": "2.104.0",
|
|
14230
14230
|
"@mdx-js/mdx": "^3.0.1",
|
|
14231
14231
|
"async-validator": "^3.5.0",
|
|
14232
14232
|
"classnames": "^2.2.6",
|
|
@@ -14247,7 +14247,7 @@
|
|
|
14247
14247
|
"*.scss",
|
|
14248
14248
|
"*.css"
|
|
14249
14249
|
],
|
|
14250
|
-
"gitHead": "
|
|
14250
|
+
"gitHead": "c69b9e013ab7b45cc617ed32f1ed548e96058bcd",
|
|
14251
14251
|
"devDependencies": {
|
|
14252
14252
|
"@babel/plugin-transform-runtime": "^7.15.8",
|
|
14253
14253
|
"@babel/preset-env": "^7.15.8",
|