@douyinfe/semi-foundation 2.94.1 → 2.95.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { format, isValid, isSameSecond, isEqual as isDateEqual, isDate } from 'date-fns';
1
+ import { format, isValid, isSameSecond, isEqual as isDateEqual, isDate, startOfDay } from 'date-fns';
2
2
  import { get, isObject, isString, isEqual, isFunction } from 'lodash';
3
3
 
4
4
  import BaseFoundation, { DefaultAdapter } from '../base/foundation';
@@ -1228,9 +1228,18 @@ export default class DatePickerFoundation extends BaseFoundation<DatePickerAdapt
1228
1228
  let isSomeDateDisabled = false;
1229
1229
  for (const date of value) {
1230
1230
  // skip check if date is null
1231
- if (!isNullOrUndefined(date) && this.disabledDisposeDate(date, disabledOptions)) {
1232
- isSomeDateDisabled = true;
1233
- break;
1231
+ if (!isNullOrUndefined(date)) {
1232
+ /**
1233
+ * When type is dateTimeRange, disabledDate should only consider the date part, not the time part.
1234
+ * This ensures that when user adjusts time, the date disable check is not affected by the time portion.
1235
+ * For example, if the user selects start date X and end date X+7 (exactly 7 days), then adjusts
1236
+ * the end time to 10:00, the end date should still be valid, not be incorrectly judged as disabled.
1237
+ */
1238
+ const dateToCheck = this._isRangeType() && isValid(date) ? startOfDay(date) : date;
1239
+ if (this.disabledDisposeDate(dateToCheck, disabledOptions)) {
1240
+ isSomeDateDisabled = true;
1241
+ break;
1242
+ }
1234
1243
  }
1235
1244
  }
1236
1245
  return isSomeDateDisabled;
@@ -59,7 +59,15 @@ class InputFoundation extends BaseFoundation<InputAdapter> {
59
59
  }
60
60
 
61
61
  handleChange(value: any, e: any) {
62
+ const { composition } = this._adapter.getProps();
62
63
  let nextValue = value;
64
+ // When composition is enabled and in IME composing, only update internal state without triggering onChange
65
+ // We still need to update internal state for both controlled and uncontrolled components,
66
+ // so that the input can correctly display the composing text
67
+ if (composition && this.compositionEnter) {
68
+ this._adapter.setValue(nextValue);
69
+ return;
70
+ }
63
71
  if (!this.compositionEnter) {
64
72
  nextValue = this.getNextValue(nextValue);
65
73
  }
@@ -101,9 +109,16 @@ class InputFoundation extends BaseFoundation<InputAdapter> {
101
109
  }
102
110
 
103
111
  handleCompositionEnd = (e: any) => {
112
+ const { composition } = this._adapter.getProps();
104
113
  const value = e.target.value;
105
114
  this.compositionEnter = false;
106
- this._adapter.notifyCompositionEnd(e);
115
+ this._adapter.notifyCompositionEnd(e);
116
+ // When composition is enabled, trigger onChange after IME composition ends
117
+ if (composition) {
118
+ const nextValue = this.getNextValue(value);
119
+ this.changeInput(nextValue, e);
120
+ return;
121
+ }
107
122
  const { getValueLength, maxLength, minLength } = this.getProps();
108
123
  if (!isFunction(getValueLength)) {
109
124
  return;
@@ -63,8 +63,15 @@ export default class TextAreaFoundation extends BaseFoundation<TextAreaAdapter>
63
63
  }
64
64
 
65
65
  handleChange(value: string, e: any) {
66
- const { maxLength, minLength, getValueLength } = this._adapter.getProps();
66
+ const { composition } = this._adapter.getProps();
67
67
  let nextValue = value;
68
+ // When composition is enabled and in IME composing, only update internal state without triggering onChange
69
+ // We still need to update internal state for both controlled and uncontrolled components,
70
+ // so that the textarea can correctly display the composing text
71
+ if (composition && this.compositionEnter) {
72
+ this._adapter.setValue(nextValue);
73
+ return;
74
+ }
68
75
  if (!this.compositionEnter) {
69
76
  nextValue = this.getNextValue(nextValue);
70
77
  }
@@ -100,20 +107,27 @@ export default class TextAreaFoundation extends BaseFoundation<TextAreaAdapter>
100
107
  }
101
108
 
102
109
  handleCompositionEnd = (e: any) => {
110
+ const { composition } = this._adapter.getProps();
111
+ const value = e.target.value;
103
112
  this.compositionEnter = false;
104
- this._adapter.notifyCompositionEnd(e);
113
+ this._adapter.notifyCompositionEnd(e);
114
+ // When composition is enabled, trigger onChange after IME composition ends
115
+ if (composition) {
116
+ const nextValue = this.getNextValue(value);
117
+ this._changeValue(nextValue, e);
118
+ return;
119
+ }
105
120
  const { getValueLength, maxLength, minLength } = this.getProps();
106
121
  if (!isFunction(getValueLength)) {
107
122
  return;
108
123
  }
109
- const value = e.target.value;
110
124
  if (maxLength) {
111
125
  const nextValue = this.handleVisibleMaxLength(value);
112
126
  nextValue !== value && this._changeValue(nextValue, e);
113
127
  }
114
128
  if (minLength) {
115
129
  this.handleVisibleMinLength(value);
116
- }
130
+ }
117
131
  }
118
132
 
119
133
  handleCompositionUpdate = (e) => {
@@ -405,7 +405,22 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
405
405
 
406
406
  const propsValue = this._isControlledComponent('value') ? value : defaultValue;
407
407
 
408
- const tmpNumber = this.doParse(this._isCurrency() ? propsValue : toString(propsValue), false, true, true);
408
+ // Align init logic with controlled update logic in semi-ui:
409
+ // - When propsValue is a number, we should apply formatter first (doFormat)
410
+ // so that parser/formatter pairs like percentage (value=1 => display=100)
411
+ // work correctly on first mount.
412
+ // - When propsValue is a string, keep original behavior.
413
+ let valueStr: any = propsValue;
414
+ // NOTE: Currency mode should keep the original behavior (number stays number) because
415
+ // parseInternationalCurrency expects locale-formatted strings and relies on symbols
416
+ // computed during init().
417
+ if (typeof propsValue === 'number' && !this._isCurrency()) {
418
+ valueStr = this.doFormat(propsValue);
419
+ } else if (!this._isCurrency()) {
420
+ valueStr = toString(propsValue);
421
+ }
422
+
423
+ const tmpNumber = this.doParse(valueStr, false, true, true);
409
424
 
410
425
  let number = null;
411
426
  if (typeof tmpNumber === 'number' && !isNaN(tmpNumber)) {
@@ -593,7 +608,8 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
593
608
  const parser = this.getProp('parser');
594
609
 
595
610
  if (typeof parser === 'function') {
596
- value = parser(value);
611
+ const parsedValue = parser(value) as unknown;
612
+ value = typeof parsedValue === 'number' ? toString(parsedValue) : parsedValue as string;
597
613
  }
598
614
 
599
615
  if (needCheckPrec && typeof value === 'string') {
@@ -648,7 +664,8 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
648
664
  }
649
665
 
650
666
  if (typeof parser === 'function') {
651
- value = parser(value);
667
+ const parsedValue = parser(value) as unknown;
668
+ value = typeof parsedValue === 'number' ? toString(parsedValue) : parsedValue as string;
652
669
  }
653
670
  if (needAdjustPrec) {
654
671
  value = this._adjustPrec(value);
@@ -1046,9 +1046,18 @@ class DatePickerFoundation extends _foundation.default {
1046
1046
  let isSomeDateDisabled = false;
1047
1047
  for (const date of value) {
1048
1048
  // skip check if date is null
1049
- if (!(0, _isNullOrUndefined.default)(date) && this.disabledDisposeDate(date, disabledOptions)) {
1050
- isSomeDateDisabled = true;
1051
- break;
1049
+ if (!(0, _isNullOrUndefined.default)(date)) {
1050
+ /**
1051
+ * When type is dateTimeRange, disabledDate should only consider the date part, not the time part.
1052
+ * This ensures that when user adjusts time, the date disable check is not affected by the time portion.
1053
+ * For example, if the user selects start date X and end date X+7 (exactly 7 days), then adjusts
1054
+ * the end time to 10:00, the end date should still be valid, not be incorrectly judged as disabled.
1055
+ */
1056
+ const dateToCheck = this._isRangeType() && (0, _dateFns.isValid)(date) ? (0, _dateFns.startOfDay)(date) : date;
1057
+ if (this.disabledDisposeDate(dateToCheck, disabledOptions)) {
1058
+ isSomeDateDisabled = true;
1059
+ break;
1060
+ }
1052
1061
  }
1053
1062
  }
1054
1063
  return isSomeDateDisabled;
@@ -61,9 +61,18 @@ class InputFoundation extends _foundation.default {
61
61
  this._adapter.notifyCompositionStart(e);
62
62
  };
63
63
  this.handleCompositionEnd = e => {
64
+ const {
65
+ composition
66
+ } = this._adapter.getProps();
64
67
  const value = e.target.value;
65
68
  this.compositionEnter = false;
66
69
  this._adapter.notifyCompositionEnd(e);
70
+ // When composition is enabled, trigger onChange after IME composition ends
71
+ if (composition) {
72
+ const nextValue = this.getNextValue(value);
73
+ this.changeInput(nextValue, e);
74
+ return;
75
+ }
67
76
  const {
68
77
  getValueLength,
69
78
  maxLength,
@@ -95,7 +104,17 @@ class InputFoundation extends _foundation.default {
95
104
  this._adapter.setValue(value);
96
105
  }
97
106
  handleChange(value, e) {
107
+ const {
108
+ composition
109
+ } = this._adapter.getProps();
98
110
  let nextValue = value;
111
+ // When composition is enabled and in IME composing, only update internal state without triggering onChange
112
+ // We still need to update internal state for both controlled and uncontrolled components,
113
+ // so that the input can correctly display the composing text
114
+ if (composition && this.compositionEnter) {
115
+ this._adapter.setValue(nextValue);
116
+ return;
117
+ }
99
118
  if (!this.compositionEnter) {
100
119
  nextValue = this.getNextValue(nextValue);
101
120
  }
@@ -59,8 +59,18 @@ class TextAreaFoundation extends _foundation.default {
59
59
  this._adapter.notifyCompositionStart(e);
60
60
  };
61
61
  this.handleCompositionEnd = e => {
62
+ const {
63
+ composition
64
+ } = this._adapter.getProps();
65
+ const value = e.target.value;
62
66
  this.compositionEnter = false;
63
67
  this._adapter.notifyCompositionEnd(e);
68
+ // When composition is enabled, trigger onChange after IME composition ends
69
+ if (composition) {
70
+ const nextValue = this.getNextValue(value);
71
+ this._changeValue(nextValue, e);
72
+ return;
73
+ }
64
74
  const {
65
75
  getValueLength,
66
76
  maxLength,
@@ -69,7 +79,6 @@ class TextAreaFoundation extends _foundation.default {
69
79
  if (!(0, _isFunction2.default)(getValueLength)) {
70
80
  return;
71
81
  }
72
- const value = e.target.value;
73
82
  if (maxLength) {
74
83
  const nextValue = this.handleVisibleMaxLength(value);
75
84
  nextValue !== value && this._changeValue(nextValue, e);
@@ -110,11 +119,16 @@ class TextAreaFoundation extends _foundation.default {
110
119
  }
111
120
  handleChange(value, e) {
112
121
  const {
113
- maxLength,
114
- minLength,
115
- getValueLength
122
+ composition
116
123
  } = this._adapter.getProps();
117
124
  let nextValue = value;
125
+ // When composition is enabled and in IME composing, only update internal state without triggering onChange
126
+ // We still need to update internal state for both controlled and uncontrolled components,
127
+ // so that the textarea can correctly display the composing text
128
+ if (composition && this.compositionEnter) {
129
+ this._adapter.setValue(nextValue);
130
+ return;
131
+ }
118
132
  if (!this.compositionEnter) {
119
133
  nextValue = this.getNextValue(nextValue);
120
134
  }
@@ -346,7 +346,21 @@ class InputNumberFoundation extends _foundation.default {
346
346
  value
347
347
  } = this.getProps();
348
348
  const propsValue = this._isControlledComponent('value') ? value : defaultValue;
349
- const tmpNumber = this.doParse(this._isCurrency() ? propsValue : (0, _toString2.default)(propsValue), false, true, true);
349
+ // Align init logic with controlled update logic in semi-ui:
350
+ // - When propsValue is a number, we should apply formatter first (doFormat)
351
+ // so that parser/formatter pairs like percentage (value=1 => display=100)
352
+ // work correctly on first mount.
353
+ // - When propsValue is a string, keep original behavior.
354
+ let valueStr = propsValue;
355
+ // NOTE: Currency mode should keep the original behavior (number stays number) because
356
+ // parseInternationalCurrency expects locale-formatted strings and relies on symbols
357
+ // computed during init().
358
+ if (typeof propsValue === 'number' && !this._isCurrency()) {
359
+ valueStr = this.doFormat(propsValue);
360
+ } else if (!this._isCurrency()) {
361
+ valueStr = (0, _toString2.default)(propsValue);
362
+ }
363
+ const tmpNumber = this.doParse(valueStr, false, true, true);
350
364
  let number = null;
351
365
  if (typeof tmpNumber === 'number' && !isNaN(tmpNumber)) {
352
366
  number = tmpNumber;
@@ -524,7 +538,8 @@ class InputNumberFoundation extends _foundation.default {
524
538
  }
525
539
  const parser = this.getProp('parser');
526
540
  if (typeof parser === 'function') {
527
- value = parser(value);
541
+ const parsedValue = parser(value);
542
+ value = typeof parsedValue === 'number' ? (0, _toString2.default)(parsedValue) : parsedValue;
528
543
  }
529
544
  if (needCheckPrec && typeof value === 'string') {
530
545
  const zeroIsValid = value.indexOf('.') === -1 || value.indexOf('.') > -1 && (value === '0' || value.lastIndexOf('0') < value.length - 1);
@@ -568,7 +583,8 @@ class InputNumberFoundation extends _foundation.default {
568
583
  value = this.parseInternationalCurrency(value);
569
584
  }
570
585
  if (typeof parser === 'function') {
571
- value = parser(value);
586
+ const parsedValue = parser(value);
587
+ value = typeof parsedValue === 'number' ? (0, _toString2.default)(parsedValue) : parsedValue;
572
588
  }
573
589
  if (needAdjustPrec) {
574
590
  value = this._adjustPrec(value);
@@ -7,6 +7,7 @@ export interface BasicDataItem {
7
7
  value?: string | number;
8
8
  disabled?: boolean;
9
9
  style?: any;
10
+ fullPath?: Array<BasicDataItem>;
10
11
  className?: string;
11
12
  }
12
13
  export type DataItemMap = Map<number | string, BasicDataItem>;
@@ -36,6 +37,17 @@ export default class TransferFoundation<P = Record<string, any>, S = Record<stri
36
37
  _generateGroupedData(dataSource: any[]): any[];
37
38
  _generateTreeData(dataSource: any[]): any[];
38
39
  _generatePath(item: BasicResolvedDataItem): any;
40
+ _getFullPath(item: BasicResolvedDataItem): {
41
+ [x: string]: any;
42
+ key: string | number;
43
+ label?: any;
44
+ value?: string | number;
45
+ disabled?: boolean;
46
+ style?: any;
47
+ fullPath?: BasicDataItem[];
48
+ className?: string;
49
+ }[];
50
+ _injectFullPath(item: BasicResolvedDataItem): BasicResolvedDataItem;
39
51
  handleInputChange(inputVal: string, notify: boolean): void;
40
52
  handleAll(wantAllChecked: boolean): void;
41
53
  handleClear(): void;
@@ -26,6 +26,25 @@ class TransferFoundation extends _foundation.default {
26
26
  } = item;
27
27
  return path.map(p => p.label).join(' > ');
28
28
  }
29
+ _getFullPath(item) {
30
+ const {
31
+ type,
32
+ showPath
33
+ } = this.getProps();
34
+ if (type !== _constants.strings.TYPE_TREE_TO_LIST || showPath !== true || !Array.isArray(item.path)) {
35
+ return undefined;
36
+ }
37
+ return item.path.map(pathItem => Object.assign({}, pathItem));
38
+ }
39
+ _injectFullPath(item) {
40
+ const fullPath = this._getFullPath(item);
41
+ if (!fullPath) {
42
+ return item;
43
+ }
44
+ return Object.assign(Object.assign({}, item), {
45
+ fullPath
46
+ });
47
+ }
29
48
  handleInputChange(inputVal, notify) {
30
49
  const {
31
50
  data
@@ -128,15 +147,16 @@ class TransferFoundation extends _foundation.default {
128
147
  disabled
129
148
  } = this.getProps();
130
149
  const selectedItems = this._adapter.getSelected();
150
+ const changedItem = this._injectFullPath(item);
131
151
  if (disabled || item.disabled) {
132
152
  return;
133
153
  }
134
154
  if (selectedItems.has(item.key)) {
135
155
  selectedItems.delete(item.key);
136
- this._adapter.notifyDeselect(item);
156
+ this._adapter.notifyDeselect(changedItem);
137
157
  } else {
138
158
  selectedItems.set(item.key, item);
139
- this._adapter.notifySelect(item);
159
+ this._adapter.notifySelect(changedItem);
140
160
  }
141
161
  if (!this._isControlledComponent()) {
142
162
  this._adapter.updateSelected(selectedItems);
@@ -182,7 +202,8 @@ class TransferFoundation extends _foundation.default {
182
202
  const items = [];
183
203
  const values = [];
184
204
  for (const item of selectedItems) {
185
- const obj = type === _constants.strings.TYPE_GROUP_LIST ? (0, _omit2.default)(item[1], '_parent') : item[1];
205
+ let obj = type === _constants.strings.TYPE_GROUP_LIST ? (0, _omit2.default)(item[1], '_parent') : item[1];
206
+ obj = this._injectFullPath(obj);
186
207
  items.push(obj);
187
208
  values.push(obj.value);
188
209
  }
@@ -44,11 +44,11 @@ export interface AfterUploadResult {
44
44
  }
45
45
  export interface UploadAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
46
46
  notifyFileSelect: (files: Array<CustomFile>) => void;
47
- notifyError: (error: XhrError, fileInstance: File, fileList: Array<BaseFileItem>, xhr: XMLHttpRequest) => void;
48
- notifySuccess: (body: any, fileInstance: File, newFileList: Array<BaseFileItem>) => void;
49
- notifyProgress: (percent: number, fileInstance: File, newFileList: Array<BaseFileItem>) => void;
50
- notifyRemove: (file: File, newFileList: Array<BaseFileItem>, fileItem: BaseFileItem) => void;
51
- notifySizeError: (file: File, fileList: Array<BaseFileItem>) => void;
47
+ notifyError: (error: XhrError, fileInstance: CustomFile, fileList: Array<BaseFileItem>, xhr: XMLHttpRequest) => void;
48
+ notifySuccess: (body: any, fileInstance: CustomFile, newFileList: Array<BaseFileItem>) => void;
49
+ notifyProgress: (percent: number, fileInstance: CustomFile, newFileList: Array<BaseFileItem>) => void;
50
+ notifyRemove: (file: CustomFile, newFileList: Array<BaseFileItem>, fileItem: BaseFileItem) => void;
51
+ notifySizeError: (file: CustomFile, fileList: Array<BaseFileItem>) => void;
52
52
  notifyExceed: (files: Array<File>) => void;
53
53
  updateFileList: (newFileList: Array<BaseFileItem>, callback?: () => void) => void;
54
54
  notifyBeforeUpload: ({ file, fileList }: {
@@ -3,7 +3,7 @@ import _isEqual from "lodash/isEqual";
3
3
  import _isString from "lodash/isString";
4
4
  import _isObject from "lodash/isObject";
5
5
  import _get from "lodash/get";
6
- import { format, isValid, isSameSecond, isEqual as isDateEqual, isDate } from 'date-fns';
6
+ import { format, isValid, isSameSecond, isEqual as isDateEqual, isDate, startOfDay } from 'date-fns';
7
7
  import BaseFoundation from '../base/foundation';
8
8
  import { isValidDate, isTimestamp } from './_utils/index';
9
9
  import isNullOrUndefined from '../utils/isNullOrUndefined';
@@ -1039,9 +1039,18 @@ export default class DatePickerFoundation extends BaseFoundation {
1039
1039
  let isSomeDateDisabled = false;
1040
1040
  for (const date of value) {
1041
1041
  // skip check if date is null
1042
- if (!isNullOrUndefined(date) && this.disabledDisposeDate(date, disabledOptions)) {
1043
- isSomeDateDisabled = true;
1044
- break;
1042
+ if (!isNullOrUndefined(date)) {
1043
+ /**
1044
+ * When type is dateTimeRange, disabledDate should only consider the date part, not the time part.
1045
+ * This ensures that when user adjusts time, the date disable check is not affected by the time portion.
1046
+ * For example, if the user selects start date X and end date X+7 (exactly 7 days), then adjusts
1047
+ * the end time to 10:00, the end date should still be valid, not be incorrectly judged as disabled.
1048
+ */
1049
+ const dateToCheck = this._isRangeType() && isValid(date) ? startOfDay(date) : date;
1050
+ if (this.disabledDisposeDate(dateToCheck, disabledOptions)) {
1051
+ isSomeDateDisabled = true;
1052
+ break;
1053
+ }
1045
1054
  }
1046
1055
  }
1047
1056
  return isSomeDateDisabled;
@@ -54,9 +54,18 @@ class InputFoundation extends BaseFoundation {
54
54
  this._adapter.notifyCompositionStart(e);
55
55
  };
56
56
  this.handleCompositionEnd = e => {
57
+ const {
58
+ composition
59
+ } = this._adapter.getProps();
57
60
  const value = e.target.value;
58
61
  this.compositionEnter = false;
59
62
  this._adapter.notifyCompositionEnd(e);
63
+ // When composition is enabled, trigger onChange after IME composition ends
64
+ if (composition) {
65
+ const nextValue = this.getNextValue(value);
66
+ this.changeInput(nextValue, e);
67
+ return;
68
+ }
60
69
  const {
61
70
  getValueLength,
62
71
  maxLength,
@@ -88,7 +97,17 @@ class InputFoundation extends BaseFoundation {
88
97
  this._adapter.setValue(value);
89
98
  }
90
99
  handleChange(value, e) {
100
+ const {
101
+ composition
102
+ } = this._adapter.getProps();
91
103
  let nextValue = value;
104
+ // When composition is enabled and in IME composing, only update internal state without triggering onChange
105
+ // We still need to update internal state for both controlled and uncontrolled components,
106
+ // so that the input can correctly display the composing text
107
+ if (composition && this.compositionEnter) {
108
+ this._adapter.setValue(nextValue);
109
+ return;
110
+ }
92
111
  if (!this.compositionEnter) {
93
112
  nextValue = this.getNextValue(nextValue);
94
113
  }
@@ -52,8 +52,18 @@ export default class TextAreaFoundation extends BaseFoundation {
52
52
  this._adapter.notifyCompositionStart(e);
53
53
  };
54
54
  this.handleCompositionEnd = e => {
55
+ const {
56
+ composition
57
+ } = this._adapter.getProps();
58
+ const value = e.target.value;
55
59
  this.compositionEnter = false;
56
60
  this._adapter.notifyCompositionEnd(e);
61
+ // When composition is enabled, trigger onChange after IME composition ends
62
+ if (composition) {
63
+ const nextValue = this.getNextValue(value);
64
+ this._changeValue(nextValue, e);
65
+ return;
66
+ }
57
67
  const {
58
68
  getValueLength,
59
69
  maxLength,
@@ -62,7 +72,6 @@ export default class TextAreaFoundation extends BaseFoundation {
62
72
  if (!_isFunction(getValueLength)) {
63
73
  return;
64
74
  }
65
- const value = e.target.value;
66
75
  if (maxLength) {
67
76
  const nextValue = this.handleVisibleMaxLength(value);
68
77
  nextValue !== value && this._changeValue(nextValue, e);
@@ -103,11 +112,16 @@ export default class TextAreaFoundation extends BaseFoundation {
103
112
  }
104
113
  handleChange(value, e) {
105
114
  const {
106
- maxLength,
107
- minLength,
108
- getValueLength
115
+ composition
109
116
  } = this._adapter.getProps();
110
117
  let nextValue = value;
118
+ // When composition is enabled and in IME composing, only update internal state without triggering onChange
119
+ // We still need to update internal state for both controlled and uncontrolled components,
120
+ // so that the textarea can correctly display the composing text
121
+ if (composition && this.compositionEnter) {
122
+ this._adapter.setValue(nextValue);
123
+ return;
124
+ }
111
125
  if (!this.compositionEnter) {
112
126
  nextValue = this.getNextValue(nextValue);
113
127
  }
@@ -339,7 +339,21 @@ class InputNumberFoundation extends BaseFoundation {
339
339
  value
340
340
  } = this.getProps();
341
341
  const propsValue = this._isControlledComponent('value') ? value : defaultValue;
342
- const tmpNumber = this.doParse(this._isCurrency() ? propsValue : _toString(propsValue), false, true, true);
342
+ // Align init logic with controlled update logic in semi-ui:
343
+ // - When propsValue is a number, we should apply formatter first (doFormat)
344
+ // so that parser/formatter pairs like percentage (value=1 => display=100)
345
+ // work correctly on first mount.
346
+ // - When propsValue is a string, keep original behavior.
347
+ let valueStr = propsValue;
348
+ // NOTE: Currency mode should keep the original behavior (number stays number) because
349
+ // parseInternationalCurrency expects locale-formatted strings and relies on symbols
350
+ // computed during init().
351
+ if (typeof propsValue === 'number' && !this._isCurrency()) {
352
+ valueStr = this.doFormat(propsValue);
353
+ } else if (!this._isCurrency()) {
354
+ valueStr = _toString(propsValue);
355
+ }
356
+ const tmpNumber = this.doParse(valueStr, false, true, true);
343
357
  let number = null;
344
358
  if (typeof tmpNumber === 'number' && !isNaN(tmpNumber)) {
345
359
  number = tmpNumber;
@@ -517,7 +531,8 @@ class InputNumberFoundation extends BaseFoundation {
517
531
  }
518
532
  const parser = this.getProp('parser');
519
533
  if (typeof parser === 'function') {
520
- value = parser(value);
534
+ const parsedValue = parser(value);
535
+ value = typeof parsedValue === 'number' ? _toString(parsedValue) : parsedValue;
521
536
  }
522
537
  if (needCheckPrec && typeof value === 'string') {
523
538
  const zeroIsValid = value.indexOf('.') === -1 || value.indexOf('.') > -1 && (value === '0' || value.lastIndexOf('0') < value.length - 1);
@@ -561,7 +576,8 @@ class InputNumberFoundation extends BaseFoundation {
561
576
  value = this.parseInternationalCurrency(value);
562
577
  }
563
578
  if (typeof parser === 'function') {
564
- value = parser(value);
579
+ const parsedValue = parser(value);
580
+ value = typeof parsedValue === 'number' ? _toString(parsedValue) : parsedValue;
565
581
  }
566
582
  if (needAdjustPrec) {
567
583
  value = this._adjustPrec(value);
@@ -7,6 +7,7 @@ export interface BasicDataItem {
7
7
  value?: string | number;
8
8
  disabled?: boolean;
9
9
  style?: any;
10
+ fullPath?: Array<BasicDataItem>;
10
11
  className?: string;
11
12
  }
12
13
  export type DataItemMap = Map<number | string, BasicDataItem>;
@@ -36,6 +37,17 @@ export default class TransferFoundation<P = Record<string, any>, S = Record<stri
36
37
  _generateGroupedData(dataSource: any[]): any[];
37
38
  _generateTreeData(dataSource: any[]): any[];
38
39
  _generatePath(item: BasicResolvedDataItem): any;
40
+ _getFullPath(item: BasicResolvedDataItem): {
41
+ [x: string]: any;
42
+ key: string | number;
43
+ label?: any;
44
+ value?: string | number;
45
+ disabled?: boolean;
46
+ style?: any;
47
+ fullPath?: BasicDataItem[];
48
+ className?: string;
49
+ }[];
50
+ _injectFullPath(item: BasicResolvedDataItem): BasicResolvedDataItem;
39
51
  handleInputChange(inputVal: string, notify: boolean): void;
40
52
  handleAll(wantAllChecked: boolean): void;
41
53
  handleClear(): void;
@@ -19,6 +19,25 @@ export default class TransferFoundation extends BaseFoundation {
19
19
  } = item;
20
20
  return path.map(p => p.label).join(' > ');
21
21
  }
22
+ _getFullPath(item) {
23
+ const {
24
+ type,
25
+ showPath
26
+ } = this.getProps();
27
+ if (type !== strings.TYPE_TREE_TO_LIST || showPath !== true || !Array.isArray(item.path)) {
28
+ return undefined;
29
+ }
30
+ return item.path.map(pathItem => Object.assign({}, pathItem));
31
+ }
32
+ _injectFullPath(item) {
33
+ const fullPath = this._getFullPath(item);
34
+ if (!fullPath) {
35
+ return item;
36
+ }
37
+ return Object.assign(Object.assign({}, item), {
38
+ fullPath
39
+ });
40
+ }
22
41
  handleInputChange(inputVal, notify) {
23
42
  const {
24
43
  data
@@ -121,15 +140,16 @@ export default class TransferFoundation extends BaseFoundation {
121
140
  disabled
122
141
  } = this.getProps();
123
142
  const selectedItems = this._adapter.getSelected();
143
+ const changedItem = this._injectFullPath(item);
124
144
  if (disabled || item.disabled) {
125
145
  return;
126
146
  }
127
147
  if (selectedItems.has(item.key)) {
128
148
  selectedItems.delete(item.key);
129
- this._adapter.notifyDeselect(item);
149
+ this._adapter.notifyDeselect(changedItem);
130
150
  } else {
131
151
  selectedItems.set(item.key, item);
132
- this._adapter.notifySelect(item);
152
+ this._adapter.notifySelect(changedItem);
133
153
  }
134
154
  if (!this._isControlledComponent()) {
135
155
  this._adapter.updateSelected(selectedItems);
@@ -175,7 +195,8 @@ export default class TransferFoundation extends BaseFoundation {
175
195
  const items = [];
176
196
  const values = [];
177
197
  for (const item of selectedItems) {
178
- const obj = type === strings.TYPE_GROUP_LIST ? _omit(item[1], '_parent') : item[1];
198
+ let obj = type === strings.TYPE_GROUP_LIST ? _omit(item[1], '_parent') : item[1];
199
+ obj = this._injectFullPath(obj);
179
200
  items.push(obj);
180
201
  values.push(obj.value);
181
202
  }
@@ -44,11 +44,11 @@ export interface AfterUploadResult {
44
44
  }
45
45
  export interface UploadAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
46
46
  notifyFileSelect: (files: Array<CustomFile>) => void;
47
- notifyError: (error: XhrError, fileInstance: File, fileList: Array<BaseFileItem>, xhr: XMLHttpRequest) => void;
48
- notifySuccess: (body: any, fileInstance: File, newFileList: Array<BaseFileItem>) => void;
49
- notifyProgress: (percent: number, fileInstance: File, newFileList: Array<BaseFileItem>) => void;
50
- notifyRemove: (file: File, newFileList: Array<BaseFileItem>, fileItem: BaseFileItem) => void;
51
- notifySizeError: (file: File, fileList: Array<BaseFileItem>) => void;
47
+ notifyError: (error: XhrError, fileInstance: CustomFile, fileList: Array<BaseFileItem>, xhr: XMLHttpRequest) => void;
48
+ notifySuccess: (body: any, fileInstance: CustomFile, newFileList: Array<BaseFileItem>) => void;
49
+ notifyProgress: (percent: number, fileInstance: CustomFile, newFileList: Array<BaseFileItem>) => void;
50
+ notifyRemove: (file: CustomFile, newFileList: Array<BaseFileItem>, fileItem: BaseFileItem) => void;
51
+ notifySizeError: (file: CustomFile, fileList: Array<BaseFileItem>) => void;
52
52
  notifyExceed: (files: Array<File>) => void;
53
53
  updateFileList: (newFileList: Array<BaseFileItem>, callback?: () => void) => void;
54
54
  notifyBeforeUpload: ({ file, fileList }: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@douyinfe/semi-foundation",
3
- "version": "2.94.1",
3
+ "version": "2.95.1",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "clean": "rimraf lib",
@@ -14210,8 +14210,8 @@
14210
14210
  }
14211
14211
  },
14212
14212
  "dependencies": {
14213
- "@douyinfe/semi-animation": "2.94.1",
14214
- "@douyinfe/semi-json-viewer-core": "2.94.1",
14213
+ "@douyinfe/semi-animation": "2.95.1",
14214
+ "@douyinfe/semi-json-viewer-core": "2.95.1",
14215
14215
  "@mdx-js/mdx": "^3.0.1",
14216
14216
  "async-validator": "^3.5.0",
14217
14217
  "classnames": "^2.2.6",
@@ -14232,7 +14232,7 @@
14232
14232
  "*.scss",
14233
14233
  "*.css"
14234
14234
  ],
14235
- "gitHead": "9d324c236709af72bc2b18166bcc738b3d7f9d91",
14235
+ "gitHead": "2a3f86116f503105a95f32045127b82a4f03c08d",
14236
14236
  "devDependencies": {
14237
14237
  "@babel/plugin-transform-runtime": "^7.15.8",
14238
14238
  "@babel/preset-env": "^7.15.8",
@@ -13,6 +13,7 @@ export interface BasicDataItem {
13
13
  value?: string | number;
14
14
  disabled?: boolean;
15
15
  style?: any;
16
+ fullPath?: Array<BasicDataItem>;
16
17
  className?: string
17
18
  }
18
19
 
@@ -60,6 +61,29 @@ export default class TransferFoundation<P = Record<string, any>, S = Record<stri
60
61
  return path.map((p: any) => p.label).join(' > ');
61
62
  }
62
63
 
64
+ _getFullPath(item: BasicResolvedDataItem) {
65
+ const { type, showPath } = this.getProps() as { type?: string; showPath?: boolean };
66
+
67
+ if (type !== strings.TYPE_TREE_TO_LIST || showPath !== true || !Array.isArray(item.path)) {
68
+ return undefined;
69
+ }
70
+
71
+ return item.path.map((pathItem: BasicDataItem) => ({ ...pathItem }));
72
+ }
73
+
74
+ _injectFullPath(item: BasicResolvedDataItem) {
75
+ const fullPath = this._getFullPath(item);
76
+
77
+ if (!fullPath) {
78
+ return item;
79
+ }
80
+
81
+ return {
82
+ ...item,
83
+ fullPath,
84
+ };
85
+ }
86
+
63
87
  handleInputChange(inputVal: string, notify: boolean) {
64
88
  const { data } = this.getStates();
65
89
  const { filter, type } = this.getProps();
@@ -148,15 +172,16 @@ export default class TransferFoundation<P = Record<string, any>, S = Record<stri
148
172
  handleSelectOrRemove(item: BasicResolvedDataItem) {
149
173
  const { disabled } = this.getProps();
150
174
  const selectedItems = this._adapter.getSelected();
175
+ const changedItem = this._injectFullPath(item);
151
176
  if (disabled || item.disabled) {
152
177
  return;
153
178
  }
154
179
  if (selectedItems.has(item.key)) {
155
180
  selectedItems.delete(item.key);
156
- this._adapter.notifyDeselect(item);
181
+ this._adapter.notifyDeselect(changedItem);
157
182
  } else {
158
183
  selectedItems.set(item.key, item);
159
- this._adapter.notifySelect(item);
184
+ this._adapter.notifySelect(changedItem);
160
185
  }
161
186
  if (!this._isControlledComponent()) {
162
187
  this._adapter.updateSelected(selectedItems);
@@ -201,7 +226,9 @@ export default class TransferFoundation<P = Record<string, any>, S = Record<stri
201
226
  const items = [];
202
227
  const values = [];
203
228
  for (const item of selectedItems) {
204
- const obj = (type === strings.TYPE_GROUP_LIST ? omit(item[1], '_parent') : item[1]) as BasicDataItem;
229
+ let obj = (type === strings.TYPE_GROUP_LIST ? omit(item[1], '_parent') : item[1]) as BasicResolvedDataItem;
230
+ obj = this._injectFullPath(obj);
231
+
205
232
  items.push(obj);
206
233
  values.push(obj.value);
207
234
  }
@@ -66,11 +66,11 @@ export interface AfterUploadResult {
66
66
 
67
67
  export interface UploadAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
68
68
  notifyFileSelect: (files: Array<CustomFile>) => void;
69
- notifyError: (error: XhrError, fileInstance: File, fileList: Array<BaseFileItem>, xhr: XMLHttpRequest) => void;
70
- notifySuccess: (body: any, fileInstance: File, newFileList: Array<BaseFileItem>) => void;
71
- notifyProgress: (percent: number, fileInstance: File, newFileList: Array<BaseFileItem>) => void;
72
- notifyRemove: (file: File, newFileList: Array<BaseFileItem>, fileItem: BaseFileItem) => void;
73
- notifySizeError: (file: File, fileList: Array<BaseFileItem>) => void;
69
+ notifyError: (error: XhrError, fileInstance: CustomFile, fileList: Array<BaseFileItem>, xhr: XMLHttpRequest) => void;
70
+ notifySuccess: (body: any, fileInstance: CustomFile, newFileList: Array<BaseFileItem>) => void;
71
+ notifyProgress: (percent: number, fileInstance: CustomFile, newFileList: Array<BaseFileItem>) => void;
72
+ notifyRemove: (file: CustomFile, newFileList: Array<BaseFileItem>, fileItem: BaseFileItem) => void;
73
+ notifySizeError: (file: CustomFile, fileList: Array<BaseFileItem>) => void;
74
74
  notifyExceed: (files: Array<File>) => void;
75
75
  updateFileList: (newFileList: Array<BaseFileItem>, callback?: () => void) => void;
76
76
  notifyBeforeUpload: ({ file, fileList }: { file: BaseFileItem; fileList: Array<BaseFileItem> }) => boolean | BeforeUploadObjectResult | Promise<BeforeUploadObjectResult>;