@douyinfe/semi-foundation 2.101.1 → 2.103.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.
@@ -19,6 +19,7 @@ $module: #{$prefix}-button;
19
19
  padding-right: $spacing-button_default-paddingRight;
20
20
  padding-top: $spacing-button_default-paddingTop;
21
21
  padding-bottom: $spacing-button_default-paddingTop;
22
+ font-family: inherit;
22
23
  font-size: $font-button-fontSize;
23
24
  line-height: $font-button-lineHeight;
24
25
  font-weight: $font-button-fontWeight;
@@ -25,6 +25,10 @@ export default class DragMoveFoundation<P = Record<string, any>, S = Record<stri
25
25
  yMin: number;
26
26
  startOffsetX: number;
27
27
  startOffsetY: number;
28
+ startClientX: number;
29
+ startClientY: number;
30
+ startLeft: number;
31
+ startTop: number;
28
32
 
29
33
  get constrainer() {
30
34
  return this._adapter.getConstrainer();
@@ -44,11 +48,15 @@ export default class DragMoveFoundation<P = Record<string, any>, S = Record<stri
44
48
  throw new Error('drag element must be a valid element');
45
49
  }
46
50
  this.element = element;
47
- this.element.style.position = 'absolute';
51
+ this.updatePositionStrategy();
48
52
  this.handler.style.cursor = 'move';
49
53
  this._registerStartEvent();
50
54
  }
51
55
 
56
+ updatePositionStrategy = () => {
57
+ this.element.style.position = this.getProp('positionStrategy') === 'relative' ? 'relative' : 'absolute';
58
+ }
59
+
52
60
  _registerStartEvent = () => {
53
61
  this.handler.addEventListener('mousedown', this.onMouseDown);
54
62
  this.handler.addEventListener('touchstart', this.onTouchStart);
@@ -95,6 +103,18 @@ export default class DragMoveFoundation<P = Record<string, any>, S = Record<stri
95
103
  _calcMoveRange() {
96
104
  // Calculate the range within which an element can move
97
105
  if (this.constrainer) {
106
+ if (this.getProp('positionStrategy') === 'relative') {
107
+ const elementRect = this.element.getBoundingClientRect();
108
+ const constrainerRect = this.constrainer.getBoundingClientRect();
109
+ const style = window.getComputedStyle(this.element);
110
+ const currentLeft = parseFloat(style.left) || 0;
111
+ const currentTop = parseFloat(style.top) || 0;
112
+ this.xMin = currentLeft + constrainerRect.left - elementRect.left;
113
+ this.xMax = currentLeft + constrainerRect.right - elementRect.right;
114
+ this.yMin = currentTop + constrainerRect.top - elementRect.top;
115
+ this.yMax = currentTop + constrainerRect.bottom - elementRect.bottom;
116
+ return;
117
+ }
98
118
  let node = this.element.offsetParent as HTMLElement;
99
119
  let startX = 0;
100
120
  let startY = 0;
@@ -126,6 +146,14 @@ export default class DragMoveFoundation<P = Record<string, any>, S = Record<stri
126
146
  }
127
147
 
128
148
  _calcOffset = (e: Touch | MouseEvent) => {
149
+ if (this.getProp('positionStrategy') === 'relative') {
150
+ const style = window.getComputedStyle(this.element);
151
+ this.startClientX = e.clientX;
152
+ this.startClientY = e.clientY;
153
+ this.startLeft = parseFloat(style.left) || 0;
154
+ this.startTop = parseFloat(style.top) || 0;
155
+ return;
156
+ }
129
157
  this.startOffsetX = e.clientX - this.element.offsetLeft;
130
158
  this.startOffsetY = e.clientY - this.element.offsetTop;
131
159
  }
@@ -161,8 +189,13 @@ export default class DragMoveFoundation<P = Record<string, any>, S = Record<stri
161
189
 
162
190
  _changePos = (e: Touch | MouseEvent) => {
163
191
  const { customMove } = this.getProps();
164
- let newLeft = e.clientX - this.startOffsetX;
165
- let newTop = e.clientY - this.startOffsetY;
192
+ const useRelativePosition = this.getProp('positionStrategy') === 'relative';
193
+ let newLeft = useRelativePosition ?
194
+ this.startLeft + e.clientX - this.startClientX :
195
+ e.clientX - this.startOffsetX;
196
+ let newTop = useRelativePosition ?
197
+ this.startTop + e.clientY - this.startClientY :
198
+ e.clientY - this.startOffsetY;
166
199
  if (this.constrainer) {
167
200
  newLeft = clampValueInRange(newLeft, this.xMin, this.xMax);
168
201
  newTop = clampValueInRange(newTop, this.yMin, this.yMax);
@@ -45,6 +45,7 @@
45
45
  padding-right: 12px;
46
46
  padding-top: 6px;
47
47
  padding-bottom: 6px;
48
+ font-family: inherit;
48
49
  font-size: 14px;
49
50
  line-height: 20px;
50
51
  font-weight: 600;
@@ -19,6 +19,7 @@ $module: #{$prefix}-button;
19
19
  padding-right: $spacing-button_default-paddingRight;
20
20
  padding-top: $spacing-button_default-paddingTop;
21
21
  padding-bottom: $spacing-button_default-paddingTop;
22
+ font-family: inherit;
22
23
  font-size: $font-button-fontSize;
23
24
  line-height: $font-button-lineHeight;
24
25
  font-weight: $font-button-fontWeight;
@@ -20,10 +20,15 @@ export default class DragMoveFoundation<P = Record<string, any>, S = Record<stri
20
20
  yMin: number;
21
21
  startOffsetX: number;
22
22
  startOffsetY: number;
23
+ startClientX: number;
24
+ startClientY: number;
25
+ startLeft: number;
26
+ startTop: number;
23
27
  get constrainer(): HTMLElement;
24
28
  get handler(): HTMLElement;
25
29
  constructor(adapter: DragMoveAdapter<P, S>);
26
30
  init(): void;
31
+ updatePositionStrategy: () => void;
27
32
  _registerStartEvent: () => void;
28
33
  _unRegisterStartEvent: () => void;
29
34
  destroy(): void;
@@ -19,6 +19,9 @@ class DragMoveFoundation extends _foundation.default {
19
19
  }
20
20
  constructor(adapter) {
21
21
  super(Object.assign({}, adapter));
22
+ this.updatePositionStrategy = () => {
23
+ this.element.style.position = this.getProp('positionStrategy') === 'relative' ? 'relative' : 'absolute';
24
+ };
22
25
  this._registerStartEvent = () => {
23
26
  this.handler.addEventListener('mousedown', this.onMouseDown);
24
27
  this.handler.addEventListener('touchstart', this.onTouchStart);
@@ -46,6 +49,14 @@ class DragMoveFoundation extends _foundation.default {
46
49
  document.removeEventListener('touchcancel', this._onTouchCancel);
47
50
  };
48
51
  this._calcOffset = e => {
52
+ if (this.getProp('positionStrategy') === 'relative') {
53
+ const style = window.getComputedStyle(this.element);
54
+ this.startClientX = e.clientX;
55
+ this.startClientY = e.clientY;
56
+ this.startLeft = parseFloat(style.left) || 0;
57
+ this.startTop = parseFloat(style.top) || 0;
58
+ return;
59
+ }
49
60
  this.startOffsetX = e.clientX - this.element.offsetLeft;
50
61
  this.startOffsetY = e.clientY - this.element.offsetTop;
51
62
  };
@@ -79,8 +90,9 @@ class DragMoveFoundation extends _foundation.default {
79
90
  const {
80
91
  customMove
81
92
  } = this.getProps();
82
- let newLeft = e.clientX - this.startOffsetX;
83
- let newTop = e.clientY - this.startOffsetY;
93
+ const useRelativePosition = this.getProp('positionStrategy') === 'relative';
94
+ let newLeft = useRelativePosition ? this.startLeft + e.clientX - this.startClientX : e.clientX - this.startOffsetX;
95
+ let newTop = useRelativePosition ? this.startTop + e.clientY - this.startClientY : e.clientY - this.startOffsetY;
84
96
  if (this.constrainer) {
85
97
  newLeft = clampValueInRange(newLeft, this.xMin, this.xMax);
86
98
  newTop = clampValueInRange(newTop, this.yMin, this.yMax);
@@ -122,7 +134,7 @@ class DragMoveFoundation extends _foundation.default {
122
134
  throw new Error('drag element must be a valid element');
123
135
  }
124
136
  this.element = element;
125
- this.element.style.position = 'absolute';
137
+ this.updatePositionStrategy();
126
138
  this.handler.style.cursor = 'move';
127
139
  this._registerStartEvent();
128
140
  }
@@ -137,6 +149,18 @@ class DragMoveFoundation extends _foundation.default {
137
149
  _calcMoveRange() {
138
150
  // Calculate the range within which an element can move
139
151
  if (this.constrainer) {
152
+ if (this.getProp('positionStrategy') === 'relative') {
153
+ const elementRect = this.element.getBoundingClientRect();
154
+ const constrainerRect = this.constrainer.getBoundingClientRect();
155
+ const style = window.getComputedStyle(this.element);
156
+ const currentLeft = parseFloat(style.left) || 0;
157
+ const currentTop = parseFloat(style.top) || 0;
158
+ this.xMin = currentLeft + constrainerRect.left - elementRect.left;
159
+ this.xMax = currentLeft + constrainerRect.right - elementRect.right;
160
+ this.yMin = currentTop + constrainerRect.top - elementRect.top;
161
+ this.yMax = currentTop + constrainerRect.bottom - elementRect.bottom;
162
+ return;
163
+ }
140
164
  let node = this.element.offsetParent;
141
165
  let startX = 0;
142
166
  let startY = 0;
@@ -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
- @import './rtl.scss';
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';
@@ -440,7 +440,10 @@ class TreeFoundation extends _foundation.default {
440
440
  this._adapter.cacheFlattenNodes(motionType === 'hide' && this._isAnimated());
441
441
  if (!this._isExpandControlled()) {
442
442
  const flattenNodes = (0, _treeUtil.flattenTreeData)(treeData, expandedKeys, keyMaps, isSearching && showFilteredOnly && filteredShownKeys);
443
- const motionKeys = this._isAnimated() ? (0, _treeUtil.getMotionKeys)(eventKey, expandedKeys, keyEntities) : [];
443
+ let motionKeys = this._isAnimated() ? (0, _treeUtil.getMotionKeys)(eventKey, expandedKeys, keyEntities) : [];
444
+ if (isSearching && showFilteredOnly) {
445
+ motionKeys = motionKeys.filter(key => filteredShownKeys.has(key));
446
+ }
444
447
  const newState = {
445
448
  [expandedStateKey]: expandedKeys,
446
449
  flattenNodes,
@@ -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._adapter.updateFileList(newFileList, () => {
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._adapter.updateFileList(currentFiles, () => {
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._adapter.updateFileList(fileList, () => {
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._adapter.updateFileList(newFileList, () => {
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.getState('fileList').filter(item => item.status === FILE_STATUS_WAIT_UPLOAD);
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
- fileList.forEach(file => {
500
- if (!file._sizeInvalid) {
501
- this.upload(file);
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
- let newFileList = this.getState('fileList').slice();
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._adapter.updateFileList(newFileList);
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._adapter.updateFileList(newFileList);
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._adapter.updateFileList(newFileList);
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._adapter.updateFileList(newFileList);
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._adapter.updateFileList(newFileList);
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._adapter.updateFileList([]);
951
+ this.updateFileListInternal([]);
910
952
  this._adapter.notifyClear();
911
953
  this._adapter.notifyChange({
912
954
  fileList: []
@@ -45,6 +45,7 @@
45
45
  padding-right: 12px;
46
46
  padding-top: 6px;
47
47
  padding-bottom: 6px;
48
+ font-family: inherit;
48
49
  font-size: 14px;
49
50
  line-height: 20px;
50
51
  font-weight: 600;
@@ -19,6 +19,7 @@ $module: #{$prefix}-button;
19
19
  padding-right: $spacing-button_default-paddingRight;
20
20
  padding-top: $spacing-button_default-paddingTop;
21
21
  padding-bottom: $spacing-button_default-paddingTop;
22
+ font-family: inherit;
22
23
  font-size: $font-button-fontSize;
23
24
  line-height: $font-button-lineHeight;
24
25
  font-weight: $font-button-fontWeight;