@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.
@@ -711,7 +711,10 @@ export default class TreeFoundation extends BaseFoundation<TreeAdapter, BasicTre
711
711
  keyMaps,
712
712
  isSearching && showFilteredOnly && filteredShownKeys
713
713
  );
714
- const motionKeys = this._isAnimated() ? getMotionKeys(eventKey, expandedKeys, keyEntities) : [];
714
+ let motionKeys = this._isAnimated() ? getMotionKeys(eventKey, expandedKeys, keyEntities) : [];
715
+ if (isSearching && showFilteredOnly) {
716
+ motionKeys = motionKeys.filter(key => filteredShownKeys.has(key));
717
+ }
715
718
  const newState = {
716
719
  [expandedStateKey]: expandedKeys,
717
720
  flattenNodes,
@@ -110,6 +110,45 @@ class UploadFoundation<P = Record<string, any>, S = Record<string, any>> extends
110
110
  * When paste event is successfully handled, we ignore the subsequent keydown event.
111
111
  */
112
112
  _pasteHandled: boolean = false;
113
+ /**
114
+ * Working copy of fileList used during a synchronous startUpload batch.
115
+ * Do NOT rely on React state fileList here: setState is async (batched in React 18),
116
+ * so reading getState('fileList') between iterations of a sync loop returns a stale
117
+ * snapshot and earlier removals get overwritten by later ones (see #3335).
118
+ * Seeded at the start of startUpload and cleared when the sync loop finishes (see
119
+ * try/finally in startUpload); async (promise) results fall back to _latestFileList
120
+ * then React state.
121
+ */
122
+ _batchFileList: Array<BaseFileItem> | null = null;
123
+ /**
124
+ * The most recent fileList produced by handleBeforeUploadResultInObject.
125
+ * Unlike React state (which is flushed asynchronously under React 18 automatic
126
+ * batching), this is updated synchronously, so a later async beforeUpload result
127
+ * composes with earlier sync removals of the same batch instead of reading a stale
128
+ * snapshot (mixed sync + async beforeUpload results, see #3335).
129
+ */
130
+ _latestFileList: Array<BaseFileItem> | null = null;
131
+ /**
132
+ * Unify all fileList commits: keep _latestFileList in sync synchronously so async
133
+ * beforeUpload results compose with recent mutations even before React flushes
134
+ * setState (React 18 automatic batching). (#3335)
135
+ */
136
+ updateFileListInternal(newFileList: Array<BaseFileItem>, callback?: () => void): void {
137
+ if (this._batchFileList) {
138
+ this._batchFileList = newFileList;
139
+ }
140
+ this._latestFileList = newFileList;
141
+ this._adapter.updateFileList(newFileList, callback);
142
+ }
143
+
144
+ /** Keep the synchronous snapshot aligned with an externally controlled fileList. */
145
+ syncLatestFileList(fileList: Array<BaseFileItem>): void {
146
+ this._latestFileList = fileList.slice();
147
+ }
148
+
149
+ getLatestFileList(): Array<BaseFileItem> {
150
+ return this._batchFileList || this._latestFileList || this.getState('fileList');
151
+ }
113
152
  constructor(adapter: UploadAdapter<P, S>) {
114
153
  super({ ...adapter });
115
154
  }
@@ -117,6 +156,7 @@ class UploadFoundation<P = Record<string, any>, S = Record<string, any>> extends
117
156
  init(): void {
118
157
  // make sure state reset, otherwise may cause upload abort in React StrictMode, like https://github.com/DouyinFE/semi-design/pull/843
119
158
  this.destroyState = false;
159
+ this.syncLatestFileList(this.getState('fileList'));
120
160
  // In controlled mode, parent may keep and pass back fileList with blob url.
121
161
  // Sync them into internal map so later remove/clear can revoke correctly.
122
162
  this._syncLocalUrlsFromFileList();
@@ -160,6 +200,8 @@ class UploadFoundation<P = Record<string, any>, S = Record<string, any>> extends
160
200
  if (!disabled) {
161
201
  this.unbindPastingHandler();
162
202
  }
203
+ this._batchFileList = null;
204
+ this._latestFileList = null;
163
205
  this.destroyState = true;
164
206
  }
165
207
 
@@ -363,7 +405,7 @@ class UploadFoundation<P = Record<string, any>, S = Record<string, any>> extends
363
405
  }
364
406
  newFileList.splice(replaceIdx, 1, newFileItem);
365
407
  this._adapter.notifyChange({ currentFile: newFileItem, fileList: newFileList });
366
- this._adapter.updateFileList(newFileList, () => {
408
+ this.updateFileListInternal(newFileList, () => {
367
409
  this._adapter.resetReplaceInput();
368
410
  if (!newFileItem._sizeInvalid) {
369
411
  this.upload(newFileItem);
@@ -405,7 +447,7 @@ class UploadFoundation<P = Record<string, any>, S = Record<string, any>> extends
405
447
  const { uploadTrigger } = this.getProps();
406
448
  const currentFiles = files.map(item => this.buildFileItem(item, uploadTrigger));
407
449
  this._adapter.notifyChange({ fileList: currentFiles, currentFile: currentFiles[0] });
408
- this._adapter.updateFileList(currentFiles, () => {
450
+ this.updateFileListInternal(currentFiles, () => {
409
451
  if (uploadTrigger === TRIGGER_AUTO) {
410
452
  this.startUpload(currentFiles);
411
453
  }
@@ -425,7 +467,7 @@ class UploadFoundation<P = Record<string, any>, S = Record<string, any>> extends
425
467
  this._adapter.notifyChange({ fileList, currentFile: file });
426
468
  }
427
469
  });
428
- this._adapter.updateFileList(fileList, () => {
470
+ this.updateFileListInternal(fileList, () => {
429
471
  if (uploadTrigger === TRIGGER_AUTO) {
430
472
  this.startUpload(currentFiles);
431
473
  }
@@ -506,7 +548,7 @@ class UploadFoundation<P = Record<string, any>, S = Record<string, any>> extends
506
548
 
507
549
  this._adapter.notifyFileSelect(currentFileList);
508
550
  this._adapter.notifyChange({ fileList: newFileList, currentFile: null });
509
- this._adapter.updateFileList(newFileList, () => {
551
+ this.updateFileListInternal(newFileList, () => {
510
552
  if (uploadTrigger === TRIGGER_AUTO) {
511
553
  this.startUpload(fileItemList);
512
554
  }
@@ -516,16 +558,25 @@ class UploadFoundation<P = Record<string, any>, S = Record<string, any>> extends
516
558
  /* istanbul ignore next */
517
559
  manualUpload(): void {
518
560
  // find the list of files that have not been uploaded
519
- const waitToUploadFileList = this.getState('fileList').filter((item: BaseFileItem) => item.status === FILE_STATUS_WAIT_UPLOAD);
561
+ const waitToUploadFileList = this.getLatestFileList().filter((item: BaseFileItem) => item.status === FILE_STATUS_WAIT_UPLOAD);
520
562
  this.startUpload(waitToUploadFileList);
521
563
  }
522
564
 
523
565
  startUpload(fileList: Array<BaseFileItem>): void {
524
- fileList.forEach(file => {
525
- if (!file._sizeInvalid) {
526
- this.upload(file);
527
- }
528
- });
566
+ // Seed a working copy so multiple synchronous beforeUpload results
567
+ // (e.g. several files returning autoRemove) compose correctly instead of each
568
+ // reading a stale React state snapshot and overwriting earlier removals. (#3335)
569
+ // try/finally guarantees the working copy is cleared even if beforeUpload throws.
570
+ this._batchFileList = this.getLatestFileList().slice();
571
+ try {
572
+ fileList.forEach(file => {
573
+ if (!file._sizeInvalid) {
574
+ this.upload(file);
575
+ }
576
+ });
577
+ } finally {
578
+ this._batchFileList = null;
579
+ }
529
580
  }
530
581
 
531
582
  upload(file: BaseFileItem): void {
@@ -535,7 +586,7 @@ class UploadFoundation<P = Record<string, any>, S = Record<string, any>> extends
535
586
  return;
536
587
  }
537
588
  if (typeof beforeUpload === 'function') {
538
- const { fileList } = this.getStates();
589
+ const fileList = this.getLatestFileList();
539
590
  const buResult = this._adapter.notifyBeforeUpload({ file, fileList });
540
591
  switch (true) {
541
592
  // sync validate - boolean
@@ -584,7 +635,11 @@ class UploadFoundation<P = Record<string, any>, S = Record<string, any>> extends
584
635
  // handle beforeUpload result when it's an object
585
636
  handleBeforeUploadResultInObject(buResult: Partial<BeforeUploadObjectResult>, file: BaseFileItem): void {
586
637
  const { shouldUpload, status, autoRemove, validateMessage, fileInstance } = buResult;
587
- let newFileList: Array<BaseFileItem> = this.getState('fileList').slice();
638
+ // During a synchronous startUpload batch, read from the working copy; for async
639
+ // (promise) results read from the most recent synchronous snapshot, falling back
640
+ // to React state. This keeps mixed sync + async beforeUpload results composing
641
+ // correctly under React 18 automatic batching. (#3335)
642
+ let newFileList: Array<BaseFileItem> = this.getLatestFileList().slice();
588
643
  if (autoRemove) {
589
644
  this._releaseFileUrl(file.uid);
590
645
  newFileList = newFileList.filter(item => item.uid !== file.uid);
@@ -607,7 +662,7 @@ class UploadFoundation<P = Record<string, any>, S = Record<string, any>> extends
607
662
  newFileList[index].shouldUpload = shouldUpload;
608
663
  }
609
664
 
610
- this._adapter.updateFileList(newFileList);
665
+ this.updateFileListInternal(newFileList);
611
666
  this._adapter.notifyChange({ fileList: newFileList, currentFile: file });
612
667
 
613
668
  if (shouldUpload) {
@@ -703,8 +758,7 @@ class UploadFoundation<P = Record<string, any>, S = Record<string, any>> extends
703
758
  }
704
759
 
705
760
  handleProgress({ e, fileInstance }: { e?: ProgressEvent; fileInstance: File }): void {
706
- const { fileList } = this.getStates();
707
- const newFileList = fileList.slice();
761
+ const newFileList = this.getLatestFileList().slice();
708
762
  let percent = 0;
709
763
  if (e.total > 0) {
710
764
  percent = Number(((e.loaded / e.total) * 100 * numbers.PROGRESS_COEFFICIENT).toFixed(0)) || 0;
@@ -714,15 +768,15 @@ class UploadFoundation<P = Record<string, any>, S = Record<string, any>> extends
714
768
  return;
715
769
  }
716
770
  newFileList[index].percent = percent;
717
- newFileList[index].status = FILE_STATUS_UPLOADING;
771
+ newFileList[index].status = FILE_STATUS_UPLOADING as FileItemStatus;
718
772
 
719
773
  this._adapter.notifyProgress(percent, fileInstance, newFileList);
720
- this._adapter.updateFileList(newFileList);
774
+ this.updateFileListInternal(newFileList);
721
775
  this._adapter.notifyChange({ fileList: newFileList, currentFile: newFileList[index] });
722
776
  }
723
777
 
724
778
  handleOnLoad({ e, xhr, fileInstance }: { e?: ProgressEvent; xhr: XMLHttpRequest; fileInstance: File }): void {
725
- const { fileList } = this.getStates();
779
+ const fileList = this.getLatestFileList();
726
780
  const index = this._getFileIndex(fileInstance, fileList);
727
781
  if (index < 0) {
728
782
  return;
@@ -735,7 +789,7 @@ class UploadFoundation<P = Record<string, any>, S = Record<string, any>> extends
735
789
  }
736
790
 
737
791
  handleSuccess({ e, fileInstance, isCustomRequest = false, xhr, response }: { e?: ProgressEvent; fileInstance: CustomFile; isCustomRequest?: boolean; xhr?: XMLHttpRequest; response?: any }): void {
738
- const { fileList } = this.getStates();
792
+ const fileList = this.getLatestFileList();
739
793
  let body: any = null;
740
794
  const index = this._getFileIndex(fileInstance, fileList);
741
795
  if (index < 0) {
@@ -750,7 +804,7 @@ class UploadFoundation<P = Record<string, any>, S = Record<string, any>> extends
750
804
  const newFileList = fileList.slice();
751
805
  const { afterUpload } = this.getProps();
752
806
 
753
- newFileList[index].status = FILE_STATUS_SUCCESS;
807
+ newFileList[index].status = FILE_STATUS_SUCCESS as FileItemStatus;
754
808
  newFileList[index].percent = 100;
755
809
  this._adapter.notifyProgress(100, fileInstance, newFileList);
756
810
  newFileList[index].response = body;
@@ -763,7 +817,7 @@ class UploadFoundation<P = Record<string, any>, S = Record<string, any>> extends
763
817
  file: newFileList[index],
764
818
  fileList: newFileList,
765
819
  }) || {};
766
- status ? (newFileList[index].status = status) : null;
820
+ status ? (newFileList[index].status = status as FileItemStatus) : null;
767
821
  validateMessage ? (newFileList[index].validateMessage = validateMessage) : null;
768
822
  name ? (newFileList[index].name = name) : null;
769
823
  if (url) {
@@ -778,7 +832,7 @@ class UploadFoundation<P = Record<string, any>, S = Record<string, any>> extends
778
832
  }
779
833
  this._adapter.notifySuccess(body, fileInstance, newFileList);
780
834
  this._adapter.notifyChange({ fileList: newFileList, currentFile: newFileList[index] });
781
- this._adapter.updateFileList(newFileList);
835
+ this.updateFileListInternal(newFileList);
782
836
  }
783
837
 
784
838
  _getFileIndex(file: CustomFile | BaseFileItem, fileList: Array<BaseFileItem>): number {
@@ -808,13 +862,13 @@ class UploadFoundation<P = Record<string, any>, S = Record<string, any>> extends
808
862
  this._releaseFileUrl(file.uid);
809
863
 
810
864
  this._adapter.notifyRemove(file.fileInstance, newFileList, file);
811
- this._adapter.updateFileList(newFileList);
865
+ this.updateFileListInternal(newFileList);
812
866
  this._adapter.notifyChange({ fileList: newFileList, currentFile: file });
813
867
  });
814
868
  }
815
869
 
816
870
  handleError({ e, xhr, fileInstance }: { e?: ProgressEvent;xhr: XMLHttpRequest;fileInstance: CustomFile }): void {
817
- const { fileList } = this.getStates();
871
+ const fileList = this.getLatestFileList();
818
872
  const index = this._getFileIndex(fileInstance, fileList);
819
873
  if (index < 0) {
820
874
  return;
@@ -823,12 +877,12 @@ class UploadFoundation<P = Record<string, any>, S = Record<string, any>> extends
823
877
  const newFileList = fileList.slice();
824
878
  const error = this.getError({ action, xhr, fileName: fileInstance.name });
825
879
 
826
- newFileList[index].status = FILE_STATUS_UPLOAD_FAIL;
880
+ newFileList[index].status = FILE_STATUS_UPLOAD_FAIL as FileItemStatus;
827
881
  newFileList[index].response = error;
828
882
  newFileList[index].event = e;
829
883
 
830
884
  this._adapter.notifyError(error, fileInstance, newFileList, xhr);
831
- this._adapter.updateFileList(newFileList);
885
+ this.updateFileListInternal(newFileList);
832
886
  this._adapter.notifyChange({ currentFile: newFileList[index], fileList: newFileList });
833
887
  }
834
888
 
@@ -844,7 +898,7 @@ class UploadFoundation<P = Record<string, any>, S = Record<string, any>> extends
844
898
  return;
845
899
  }
846
900
  this._releaseAllFileUrls();
847
- this._adapter.updateFileList([]);
901
+ this.updateFileListInternal([]);
848
902
  this._adapter.notifyClear();
849
903
  this._adapter.notifyChange({ fileList: [] } as any);
850
904
  }).catch(error => {