@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
|
@@ -7,6 +7,14 @@ exports.default = void 0;
|
|
|
7
7
|
var _foundation = _interopRequireDefault(require("../base/foundation"));
|
|
8
8
|
var _keyCode = _interopRequireDefault(require("../utils/keyCode"));
|
|
9
9
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
+
/**
|
|
11
|
+
* Stack of `handleKeyDown` handlers for modals that currently listen on
|
|
12
|
+
* `document`. Every ModalContent registers its own document-level keydown
|
|
13
|
+
* listener, so `stopPropagation()` cannot stop sibling listeners on the same
|
|
14
|
+
* node — one ESC used to close every open dialog. Only the most recently
|
|
15
|
+
* mounted (top-most) listener should respond.
|
|
16
|
+
*/
|
|
17
|
+
const escListenerStack = [];
|
|
10
18
|
class ModalContentFoundation extends _foundation.default {
|
|
11
19
|
constructor(adapter) {
|
|
12
20
|
super(Object.assign(Object.assign({}, ModalContentFoundation.defaultAdapter), adapter));
|
|
@@ -15,6 +23,12 @@ class ModalContentFoundation extends _foundation.default {
|
|
|
15
23
|
closeOnEsc
|
|
16
24
|
} = this.getProps();
|
|
17
25
|
if (closeOnEsc && e.keyCode === _keyCode.default.ESC) {
|
|
26
|
+
// Multiple open modals each registered a document-level keydown
|
|
27
|
+
// listener; only the top-most (most recently mounted) one may
|
|
28
|
+
// close, so a single ESC closes dialogs one at a time.
|
|
29
|
+
if (escListenerStack[escListenerStack.length - 1] !== this.handleKeyDown) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
18
32
|
e.stopPropagation();
|
|
19
33
|
this.close(e);
|
|
20
34
|
return;
|
|
@@ -34,9 +48,16 @@ class ModalContentFoundation extends _foundation.default {
|
|
|
34
48
|
}
|
|
35
49
|
handleKeyDownEventListenerMount() {
|
|
36
50
|
this._adapter.addKeyDownEventListener();
|
|
51
|
+
if (this.getProps().closeOnEsc) {
|
|
52
|
+
escListenerStack.push(this.handleKeyDown);
|
|
53
|
+
}
|
|
37
54
|
}
|
|
38
55
|
handleKeyDownEventListenerUnmount() {
|
|
39
56
|
this._adapter.removeKeyDownEventListener();
|
|
57
|
+
const index = escListenerStack.indexOf(this.handleKeyDown);
|
|
58
|
+
if (index !== -1) {
|
|
59
|
+
escListenerStack.splice(index, 1);
|
|
60
|
+
}
|
|
40
61
|
}
|
|
41
62
|
getMouseState() {
|
|
42
63
|
this._adapter.getMouseState();
|
|
@@ -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/cjs/radio/radio.css
CHANGED
package/lib/cjs/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;
|
|
@@ -31,6 +31,21 @@ exports.ResizeItemFoundation = ResizeItemFoundation;
|
|
|
31
31
|
class ResizeGroupFoundation extends _foundation.default {
|
|
32
32
|
constructor(adapter) {
|
|
33
33
|
super(Object.assign({}, adapter));
|
|
34
|
+
// 首次 initSpace 时 group 是否可测量(非 display:none)。
|
|
35
|
+
// Whether the group was measurable (not inside display:none) on the first initSpace run. #3336
|
|
36
|
+
this.sizeInitialized = false;
|
|
37
|
+
/**
|
|
38
|
+
* When the group is initially mounted inside a display:none container, its own size
|
|
39
|
+
* and the handler sizes are all 0, so item sizes are computed with a `- 0px` handler
|
|
40
|
+
* offset and never corrected once the container becomes visible. Recalculate the layout
|
|
41
|
+
* the first time the group becomes measurable. Once it has a valid size, later resizes
|
|
42
|
+
* must NOT re-run initSpace, otherwise the user's manual drag results would be reset. #3336
|
|
43
|
+
*/
|
|
44
|
+
this.handleGroupResize = () => {
|
|
45
|
+
if (!this.sizeInitialized && this.groupSize > 0) {
|
|
46
|
+
this.initSpace();
|
|
47
|
+
}
|
|
48
|
+
};
|
|
34
49
|
this.registerEvents = () => {
|
|
35
50
|
this._adapter.registerEvents(this.type);
|
|
36
51
|
};
|
|
@@ -194,6 +209,9 @@ class ResizeGroupFoundation extends _foundation.default {
|
|
|
194
209
|
// calculate accurate space for group item
|
|
195
210
|
let handlerSizes = new Array(this._adapter.getHandlerCount()).fill(0);
|
|
196
211
|
let parentSize = this.groupSize;
|
|
212
|
+
// Mark whether the group is measurable now; if not (e.g. inside display:none),
|
|
213
|
+
// handleGroupResize will re-run initSpace once it becomes visible. #3336
|
|
214
|
+
this.sizeInitialized = parentSize > 0;
|
|
197
215
|
this.totalMinus = 0;
|
|
198
216
|
for (let i = 0; i < this._adapter.getHandlerCount(); i++) {
|
|
199
217
|
let handlerSize = direction === 'horizontal' ? this._adapter.getHandler(i).offsetWidth : this._adapter.getHandler(i).offsetHeight;
|
|
@@ -125,7 +125,8 @@ const isTimeFormatLike = (time, formatToken) => {
|
|
|
125
125
|
const hmsReg = /[H|m|s]{1,2}/;
|
|
126
126
|
const formatSplitted = formatToken.split(formatNotSupportChReg); // => ['HH', 'mm'];
|
|
127
127
|
const timeSeparator = formatToken.replace(formatSupportChReg, ''); // => :
|
|
128
|
-
const
|
|
128
|
+
const escapedTimeSeparator = timeSeparator.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); // escape regExp special characters to avoid invalid regExp, e.g. format="HH\\mm"
|
|
129
|
+
const timeReg = new RegExp(`[${escapedTimeSeparator}]`, 'g'); // => /[:]/g
|
|
129
130
|
const timeSplitted = time.split(timeReg); // => ['12', '0]
|
|
130
131
|
if (formatSplitted.length !== timeSplitted.length) {
|
|
131
132
|
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
|
/**
|
|
@@ -48,6 +48,25 @@ const {
|
|
|
48
48
|
TRIGGER_AUTO
|
|
49
49
|
} = _constants.strings;
|
|
50
50
|
class UploadFoundation extends _foundation.default {
|
|
51
|
+
/**
|
|
52
|
+
* Unify all fileList commits: keep _latestFileList in sync synchronously so async
|
|
53
|
+
* beforeUpload results compose with recent mutations even before React flushes
|
|
54
|
+
* setState (React 18 automatic batching). (#3335)
|
|
55
|
+
*/
|
|
56
|
+
updateFileListInternal(newFileList, callback) {
|
|
57
|
+
if (this._batchFileList) {
|
|
58
|
+
this._batchFileList = newFileList;
|
|
59
|
+
}
|
|
60
|
+
this._latestFileList = newFileList;
|
|
61
|
+
this._adapter.updateFileList(newFileList, callback);
|
|
62
|
+
}
|
|
63
|
+
/** Keep the synchronous snapshot aligned with an externally controlled fileList. */
|
|
64
|
+
syncLatestFileList(fileList) {
|
|
65
|
+
this._latestFileList = fileList.slice();
|
|
66
|
+
}
|
|
67
|
+
getLatestFileList() {
|
|
68
|
+
return this._batchFileList || this._latestFileList || this.getState('fileList');
|
|
69
|
+
}
|
|
51
70
|
constructor(adapter) {
|
|
52
71
|
super(Object.assign({}, adapter));
|
|
53
72
|
this.destroyState = false;
|
|
@@ -62,10 +81,29 @@ class UploadFoundation extends _foundation.default {
|
|
|
62
81
|
* When paste event is successfully handled, we ignore the subsequent keydown event.
|
|
63
82
|
*/
|
|
64
83
|
this._pasteHandled = false;
|
|
84
|
+
/**
|
|
85
|
+
* Working copy of fileList used during a synchronous startUpload batch.
|
|
86
|
+
* Do NOT rely on React state fileList here: setState is async (batched in React 18),
|
|
87
|
+
* so reading getState('fileList') between iterations of a sync loop returns a stale
|
|
88
|
+
* snapshot and earlier removals get overwritten by later ones (see #3335).
|
|
89
|
+
* Seeded at the start of startUpload and cleared when the sync loop finishes (see
|
|
90
|
+
* try/finally in startUpload); async (promise) results fall back to _latestFileList
|
|
91
|
+
* then React state.
|
|
92
|
+
*/
|
|
93
|
+
this._batchFileList = null;
|
|
94
|
+
/**
|
|
95
|
+
* The most recent fileList produced by handleBeforeUploadResultInObject.
|
|
96
|
+
* Unlike React state (which is flushed asynchronously under React 18 automatic
|
|
97
|
+
* batching), this is updated synchronously, so a later async beforeUpload result
|
|
98
|
+
* composes with earlier sync removals of the same batch instead of reading a stale
|
|
99
|
+
* snapshot (mixed sync + async beforeUpload results, see #3335).
|
|
100
|
+
*/
|
|
101
|
+
this._latestFileList = null;
|
|
65
102
|
}
|
|
66
103
|
init() {
|
|
67
104
|
// make sure state reset, otherwise may cause upload abort in React StrictMode, like https://github.com/DouyinFE/semi-design/pull/843
|
|
68
105
|
this.destroyState = false;
|
|
106
|
+
this.syncLatestFileList(this.getState('fileList'));
|
|
69
107
|
// In controlled mode, parent may keep and pass back fileList with blob url.
|
|
70
108
|
// Sync them into internal map so later remove/clear can revoke correctly.
|
|
71
109
|
this._syncLocalUrlsFromFileList();
|
|
@@ -115,6 +153,8 @@ class UploadFoundation extends _foundation.default {
|
|
|
115
153
|
if (!disabled) {
|
|
116
154
|
this.unbindPastingHandler();
|
|
117
155
|
}
|
|
156
|
+
this._batchFileList = null;
|
|
157
|
+
this._latestFileList = null;
|
|
118
158
|
this.destroyState = true;
|
|
119
159
|
}
|
|
120
160
|
/**
|
|
@@ -331,7 +371,7 @@ class UploadFoundation extends _foundation.default {
|
|
|
331
371
|
currentFile: newFileItem,
|
|
332
372
|
fileList: newFileList
|
|
333
373
|
});
|
|
334
|
-
this.
|
|
374
|
+
this.updateFileListInternal(newFileList, () => {
|
|
335
375
|
this._adapter.resetReplaceInput();
|
|
336
376
|
if (!newFileItem._sizeInvalid) {
|
|
337
377
|
this.upload(newFileItem);
|
|
@@ -375,7 +415,7 @@ class UploadFoundation extends _foundation.default {
|
|
|
375
415
|
fileList: currentFiles,
|
|
376
416
|
currentFile: currentFiles[0]
|
|
377
417
|
});
|
|
378
|
-
this.
|
|
418
|
+
this.updateFileListInternal(currentFiles, () => {
|
|
379
419
|
if (uploadTrigger === TRIGGER_AUTO) {
|
|
380
420
|
this.startUpload(currentFiles);
|
|
381
421
|
}
|
|
@@ -399,7 +439,7 @@ class UploadFoundation extends _foundation.default {
|
|
|
399
439
|
});
|
|
400
440
|
}
|
|
401
441
|
});
|
|
402
|
-
this.
|
|
442
|
+
this.updateFileListInternal(fileList, () => {
|
|
403
443
|
if (uploadTrigger === TRIGGER_AUTO) {
|
|
404
444
|
this.startUpload(currentFiles);
|
|
405
445
|
}
|
|
@@ -483,7 +523,7 @@ class UploadFoundation extends _foundation.default {
|
|
|
483
523
|
fileList: newFileList,
|
|
484
524
|
currentFile: null
|
|
485
525
|
});
|
|
486
|
-
this.
|
|
526
|
+
this.updateFileListInternal(newFileList, () => {
|
|
487
527
|
if (uploadTrigger === TRIGGER_AUTO) {
|
|
488
528
|
this.startUpload(fileItemList);
|
|
489
529
|
}
|
|
@@ -492,15 +532,24 @@ class UploadFoundation extends _foundation.default {
|
|
|
492
532
|
/* istanbul ignore next */
|
|
493
533
|
manualUpload() {
|
|
494
534
|
// find the list of files that have not been uploaded
|
|
495
|
-
const waitToUploadFileList = this.
|
|
535
|
+
const waitToUploadFileList = this.getLatestFileList().filter(item => item.status === FILE_STATUS_WAIT_UPLOAD);
|
|
496
536
|
this.startUpload(waitToUploadFileList);
|
|
497
537
|
}
|
|
498
538
|
startUpload(fileList) {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
539
|
+
// Seed a working copy so multiple synchronous beforeUpload results
|
|
540
|
+
// (e.g. several files returning autoRemove) compose correctly instead of each
|
|
541
|
+
// reading a stale React state snapshot and overwriting earlier removals. (#3335)
|
|
542
|
+
// try/finally guarantees the working copy is cleared even if beforeUpload throws.
|
|
543
|
+
this._batchFileList = this.getLatestFileList().slice();
|
|
544
|
+
try {
|
|
545
|
+
fileList.forEach(file => {
|
|
546
|
+
if (!file._sizeInvalid) {
|
|
547
|
+
this.upload(file);
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
} finally {
|
|
551
|
+
this._batchFileList = null;
|
|
552
|
+
}
|
|
504
553
|
}
|
|
505
554
|
upload(file) {
|
|
506
555
|
const {
|
|
@@ -511,9 +560,7 @@ class UploadFoundation extends _foundation.default {
|
|
|
511
560
|
return;
|
|
512
561
|
}
|
|
513
562
|
if (typeof beforeUpload === 'function') {
|
|
514
|
-
const
|
|
515
|
-
fileList
|
|
516
|
-
} = this.getStates();
|
|
563
|
+
const fileList = this.getLatestFileList();
|
|
517
564
|
const buResult = this._adapter.notifyBeforeUpload({
|
|
518
565
|
file,
|
|
519
566
|
fileList
|
|
@@ -578,7 +625,11 @@ class UploadFoundation extends _foundation.default {
|
|
|
578
625
|
validateMessage,
|
|
579
626
|
fileInstance
|
|
580
627
|
} = buResult;
|
|
581
|
-
|
|
628
|
+
// During a synchronous startUpload batch, read from the working copy; for async
|
|
629
|
+
// (promise) results read from the most recent synchronous snapshot, falling back
|
|
630
|
+
// to React state. This keeps mixed sync + async beforeUpload results composing
|
|
631
|
+
// correctly under React 18 automatic batching. (#3335)
|
|
632
|
+
let newFileList = this.getLatestFileList().slice();
|
|
582
633
|
if (autoRemove) {
|
|
583
634
|
this._releaseFileUrl(file.uid);
|
|
584
635
|
newFileList = newFileList.filter(item => item.uid !== file.uid);
|
|
@@ -600,7 +651,7 @@ class UploadFoundation extends _foundation.default {
|
|
|
600
651
|
}
|
|
601
652
|
newFileList[index].shouldUpload = shouldUpload;
|
|
602
653
|
}
|
|
603
|
-
this.
|
|
654
|
+
this.updateFileListInternal(newFileList);
|
|
604
655
|
this._adapter.notifyChange({
|
|
605
656
|
fileList: newFileList,
|
|
606
657
|
currentFile: file
|
|
@@ -714,10 +765,7 @@ class UploadFoundation extends _foundation.default {
|
|
|
714
765
|
e,
|
|
715
766
|
fileInstance
|
|
716
767
|
} = _ref2;
|
|
717
|
-
const
|
|
718
|
-
fileList
|
|
719
|
-
} = this.getStates();
|
|
720
|
-
const newFileList = fileList.slice();
|
|
768
|
+
const newFileList = this.getLatestFileList().slice();
|
|
721
769
|
let percent = 0;
|
|
722
770
|
if (e.total > 0) {
|
|
723
771
|
percent = Number((e.loaded / e.total * 100 * _constants.numbers.PROGRESS_COEFFICIENT).toFixed(0)) || 0;
|
|
@@ -729,7 +777,7 @@ class UploadFoundation extends _foundation.default {
|
|
|
729
777
|
newFileList[index].percent = percent;
|
|
730
778
|
newFileList[index].status = FILE_STATUS_UPLOADING;
|
|
731
779
|
this._adapter.notifyProgress(percent, fileInstance, newFileList);
|
|
732
|
-
this.
|
|
780
|
+
this.updateFileListInternal(newFileList);
|
|
733
781
|
this._adapter.notifyChange({
|
|
734
782
|
fileList: newFileList,
|
|
735
783
|
currentFile: newFileList[index]
|
|
@@ -741,9 +789,7 @@ class UploadFoundation extends _foundation.default {
|
|
|
741
789
|
xhr,
|
|
742
790
|
fileInstance
|
|
743
791
|
} = _ref3;
|
|
744
|
-
const
|
|
745
|
-
fileList
|
|
746
|
-
} = this.getStates();
|
|
792
|
+
const fileList = this.getLatestFileList();
|
|
747
793
|
const index = this._getFileIndex(fileInstance, fileList);
|
|
748
794
|
if (index < 0) {
|
|
749
795
|
return;
|
|
@@ -771,9 +817,7 @@ class UploadFoundation extends _foundation.default {
|
|
|
771
817
|
xhr,
|
|
772
818
|
response
|
|
773
819
|
} = _ref4;
|
|
774
|
-
const
|
|
775
|
-
fileList
|
|
776
|
-
} = this.getStates();
|
|
820
|
+
const fileList = this.getLatestFileList();
|
|
777
821
|
let body = null;
|
|
778
822
|
const index = this._getFileIndex(fileInstance, fileList);
|
|
779
823
|
if (index < 0) {
|
|
@@ -824,7 +868,7 @@ class UploadFoundation extends _foundation.default {
|
|
|
824
868
|
fileList: newFileList,
|
|
825
869
|
currentFile: newFileList[index]
|
|
826
870
|
});
|
|
827
|
-
this.
|
|
871
|
+
this.updateFileListInternal(newFileList);
|
|
828
872
|
}
|
|
829
873
|
_getFileIndex(file, fileList) {
|
|
830
874
|
return fileList.findIndex(item => item.uid === file.uid);
|
|
@@ -852,7 +896,7 @@ class UploadFoundation extends _foundation.default {
|
|
|
852
896
|
newFileList.splice(index, 1);
|
|
853
897
|
this._releaseFileUrl(file.uid);
|
|
854
898
|
this._adapter.notifyRemove(file.fileInstance, newFileList, file);
|
|
855
|
-
this.
|
|
899
|
+
this.updateFileListInternal(newFileList);
|
|
856
900
|
this._adapter.notifyChange({
|
|
857
901
|
fileList: newFileList,
|
|
858
902
|
currentFile: file
|
|
@@ -865,9 +909,7 @@ class UploadFoundation extends _foundation.default {
|
|
|
865
909
|
xhr,
|
|
866
910
|
fileInstance
|
|
867
911
|
} = _ref5;
|
|
868
|
-
const
|
|
869
|
-
fileList
|
|
870
|
-
} = this.getStates();
|
|
912
|
+
const fileList = this.getLatestFileList();
|
|
871
913
|
const index = this._getFileIndex(fileInstance, fileList);
|
|
872
914
|
if (index < 0) {
|
|
873
915
|
return;
|
|
@@ -885,7 +927,7 @@ class UploadFoundation extends _foundation.default {
|
|
|
885
927
|
newFileList[index].response = error;
|
|
886
928
|
newFileList[index].event = e;
|
|
887
929
|
this._adapter.notifyError(error, fileInstance, newFileList, xhr);
|
|
888
|
-
this.
|
|
930
|
+
this.updateFileListInternal(newFileList);
|
|
889
931
|
this._adapter.notifyChange({
|
|
890
932
|
currentFile: newFileList[index],
|
|
891
933
|
fileList: newFileList
|
|
@@ -906,7 +948,7 @@ class UploadFoundation extends _foundation.default {
|
|
|
906
948
|
return;
|
|
907
949
|
}
|
|
908
950
|
this._releaseAllFileUrls();
|
|
909
|
-
this.
|
|
951
|
+
this.updateFileListInternal([]);
|
|
910
952
|
this._adapter.notifyClear();
|
|
911
953
|
this._adapter.notifyChange({
|
|
912
954
|
fileList: []
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import BaseFoundation from '../base/foundation';
|
|
2
2
|
import KeyCode from '../utils/keyCode';
|
|
3
|
+
/**
|
|
4
|
+
* Stack of `handleKeyDown` handlers for modals that currently listen on
|
|
5
|
+
* `document`. Every ModalContent registers its own document-level keydown
|
|
6
|
+
* listener, so `stopPropagation()` cannot stop sibling listeners on the same
|
|
7
|
+
* node — one ESC used to close every open dialog. Only the most recently
|
|
8
|
+
* mounted (top-most) listener should respond.
|
|
9
|
+
*/
|
|
10
|
+
const escListenerStack = [];
|
|
3
11
|
export default class ModalContentFoundation extends BaseFoundation {
|
|
4
12
|
constructor(adapter) {
|
|
5
13
|
super(Object.assign(Object.assign({}, ModalContentFoundation.defaultAdapter), adapter));
|
|
@@ -8,6 +16,12 @@ export default class ModalContentFoundation extends BaseFoundation {
|
|
|
8
16
|
closeOnEsc
|
|
9
17
|
} = this.getProps();
|
|
10
18
|
if (closeOnEsc && e.keyCode === KeyCode.ESC) {
|
|
19
|
+
// Multiple open modals each registered a document-level keydown
|
|
20
|
+
// listener; only the top-most (most recently mounted) one may
|
|
21
|
+
// close, so a single ESC closes dialogs one at a time.
|
|
22
|
+
if (escListenerStack[escListenerStack.length - 1] !== this.handleKeyDown) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
11
25
|
e.stopPropagation();
|
|
12
26
|
this.close(e);
|
|
13
27
|
return;
|
|
@@ -27,9 +41,16 @@ export default class ModalContentFoundation extends BaseFoundation {
|
|
|
27
41
|
}
|
|
28
42
|
handleKeyDownEventListenerMount() {
|
|
29
43
|
this._adapter.addKeyDownEventListener();
|
|
44
|
+
if (this.getProps().closeOnEsc) {
|
|
45
|
+
escListenerStack.push(this.handleKeyDown);
|
|
46
|
+
}
|
|
30
47
|
}
|
|
31
48
|
handleKeyDownEventListenerUnmount() {
|
|
32
49
|
this._adapter.removeKeyDownEventListener();
|
|
50
|
+
const index = escListenerStack.indexOf(this.handleKeyDown);
|
|
51
|
+
if (index !== -1) {
|
|
52
|
+
escListenerStack.splice(index, 1);
|
|
53
|
+
}
|
|
33
54
|
}
|
|
34
55
|
getMouseState() {
|
|
35
56
|
this._adapter.getMouseState();
|