@douyinfe/semi-foundation 2.96.0 → 2.97.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.
Files changed (81) hide show
  1. package/cascader/foundation.ts +74 -19
  2. package/datePicker/datePicker.scss +100 -5
  3. package/form/foundation.ts +7 -3
  4. package/form/utils.ts +7 -2
  5. package/image/previewImageFoundation.ts +34 -3
  6. package/image/previewInnerFoundation.ts +15 -4
  7. package/input/textarea.scss +35 -0
  8. package/lib/cjs/cascader/foundation.d.ts +12 -0
  9. package/lib/cjs/cascader/foundation.js +68 -23
  10. package/lib/cjs/datePicker/datePicker.css +67 -5
  11. package/lib/cjs/datePicker/datePicker.scss +100 -5
  12. package/lib/cjs/form/foundation.d.ts +1 -1
  13. package/lib/cjs/form/foundation.js +6 -6
  14. package/lib/cjs/form/utils.js +5 -2
  15. package/lib/cjs/image/previewImageFoundation.d.ts +4 -0
  16. package/lib/cjs/image/previewImageFoundation.js +33 -2
  17. package/lib/cjs/image/previewInnerFoundation.d.ts +1 -0
  18. package/lib/cjs/image/previewInnerFoundation.js +17 -4
  19. package/lib/cjs/input/textarea.css +17 -0
  20. package/lib/cjs/input/textarea.scss +35 -0
  21. package/lib/cjs/navigation/navigation.css +2 -1
  22. package/lib/cjs/navigation/navigation.scss +1 -0
  23. package/lib/cjs/navigation/variables.scss +1 -1
  24. package/lib/cjs/overflowList/foundation.d.ts +1 -0
  25. package/lib/cjs/overflowList/foundation.js +51 -1
  26. package/lib/cjs/select/foundation.d.ts +1 -1
  27. package/lib/cjs/select/foundation.js +28 -2
  28. package/lib/cjs/switch/switch.css +1 -0
  29. package/lib/cjs/switch/switch.scss +1 -0
  30. package/lib/cjs/switch/variables.scss +2 -1
  31. package/lib/cjs/table/foundation.js +2 -1
  32. package/lib/cjs/tag/tag.css +26 -0
  33. package/lib/cjs/tag/tag.scss +33 -0
  34. package/lib/cjs/tagInput/tagInput.css +17 -0
  35. package/lib/cjs/tagInput/tagInput.scss +18 -0
  36. package/lib/cjs/timePicker/constants.d.ts +1 -0
  37. package/lib/cjs/timePicker/foundation.d.ts +7 -1
  38. package/lib/cjs/timePicker/foundation.js +62 -11
  39. package/lib/es/cascader/foundation.d.ts +12 -0
  40. package/lib/es/cascader/foundation.js +68 -23
  41. package/lib/es/datePicker/datePicker.css +67 -5
  42. package/lib/es/datePicker/datePicker.scss +100 -5
  43. package/lib/es/form/foundation.d.ts +1 -1
  44. package/lib/es/form/foundation.js +6 -6
  45. package/lib/es/form/utils.js +5 -2
  46. package/lib/es/image/previewImageFoundation.d.ts +4 -0
  47. package/lib/es/image/previewImageFoundation.js +33 -2
  48. package/lib/es/image/previewInnerFoundation.d.ts +1 -0
  49. package/lib/es/image/previewInnerFoundation.js +17 -4
  50. package/lib/es/input/textarea.css +17 -0
  51. package/lib/es/input/textarea.scss +35 -0
  52. package/lib/es/navigation/navigation.css +2 -1
  53. package/lib/es/navigation/navigation.scss +1 -0
  54. package/lib/es/navigation/variables.scss +1 -1
  55. package/lib/es/overflowList/foundation.d.ts +1 -0
  56. package/lib/es/overflowList/foundation.js +51 -1
  57. package/lib/es/select/foundation.d.ts +1 -1
  58. package/lib/es/select/foundation.js +28 -2
  59. package/lib/es/switch/switch.css +1 -0
  60. package/lib/es/switch/switch.scss +1 -0
  61. package/lib/es/switch/variables.scss +2 -1
  62. package/lib/es/table/foundation.js +2 -1
  63. package/lib/es/tag/tag.css +26 -0
  64. package/lib/es/tag/tag.scss +33 -0
  65. package/lib/es/tagInput/tagInput.css +17 -0
  66. package/lib/es/tagInput/tagInput.scss +18 -0
  67. package/lib/es/timePicker/constants.d.ts +1 -0
  68. package/lib/es/timePicker/foundation.d.ts +7 -1
  69. package/lib/es/timePicker/foundation.js +62 -11
  70. package/navigation/navigation.scss +1 -0
  71. package/navigation/variables.scss +1 -1
  72. package/overflowList/foundation.ts +48 -2
  73. package/package.json +4 -4
  74. package/select/foundation.ts +27 -2
  75. package/switch/switch.scss +1 -0
  76. package/switch/variables.scss +2 -1
  77. package/table/foundation.ts +2 -1
  78. package/tag/tag.scss +33 -0
  79. package/tagInput/tagInput.scss +18 -0
  80. package/timePicker/constants.ts +2 -0
  81. package/timePicker/foundation.ts +62 -10
@@ -27,11 +27,21 @@ class OverflowListFoundation extends BaseFoundation {
27
27
  } = this.getProps();
28
28
  const {
29
29
  visibleState,
30
- overflow
30
+ overflow,
31
+ scrollOverflow
31
32
  } = this.getStates();
32
33
  if (!this.isScrollMode()) {
33
34
  return overflow;
34
35
  }
36
+ // Scroll mode relies on IntersectionObserver to compute visibility.
37
+ // During recalculation (e.g. items changed, or before observer fires),
38
+ // keep last computed overflow to avoid UI flicker (tabs arrows/menu state).
39
+ if (!visibleState || visibleState.size === 0) {
40
+ if (Array.isArray(scrollOverflow) && scrollOverflow.length === 2) {
41
+ return scrollOverflow;
42
+ }
43
+ return [[], []];
44
+ }
35
45
  const visibleStateArr = items.map(_ref => {
36
46
  let {
37
47
  key
@@ -40,11 +50,49 @@ class OverflowListFoundation extends BaseFoundation {
40
50
  });
41
51
  const visibleStart = visibleStateArr.indexOf(true);
42
52
  const visibleEnd = visibleStateArr.lastIndexOf(true);
53
+ // If no item is visible (e.g. initial layout not ready or list out of viewport),
54
+ // treat it as "unknown" and keep last computed overflow to avoid wrong enabling.
55
+ if (visibleStart < 0 || visibleEnd < 0) {
56
+ if (Array.isArray(scrollOverflow) && scrollOverflow.length === 2) {
57
+ return scrollOverflow;
58
+ }
59
+ return [[], []];
60
+ }
43
61
  const overflowList = [];
44
62
  overflowList[0] = visibleStart >= 0 ? items.slice(0, visibleStart) : [];
45
63
  overflowList[1] = visibleEnd >= 0 ? items.slice(visibleEnd + 1, items.length) : items.slice();
46
64
  return overflowList;
47
65
  }
66
+ _syncScrollOverflowCache(nextVisibleState) {
67
+ if (!this.isScrollMode()) {
68
+ return;
69
+ }
70
+ const {
71
+ items
72
+ } = this.getProps();
73
+ const visibleStateArr = items.map(_ref2 => {
74
+ let {
75
+ key
76
+ } = _ref2;
77
+ return Boolean(nextVisibleState.get(key));
78
+ });
79
+ const visibleStart = visibleStateArr.indexOf(true);
80
+ const visibleEnd = visibleStateArr.lastIndexOf(true);
81
+ // No visible items means the result is not reliable; keep cache and stay "calculating"
82
+ if (visibleStart < 0 || visibleEnd < 0) {
83
+ this._adapter.updateStates({
84
+ isScrollOverflowCalculating: true
85
+ });
86
+ return;
87
+ }
88
+ const overflowList = [];
89
+ overflowList[0] = visibleStart >= 0 ? items.slice(0, visibleStart) : [];
90
+ overflowList[1] = visibleEnd >= 0 ? items.slice(visibleEnd + 1, items.length) : items.slice();
91
+ this._adapter.updateStates({
92
+ scrollOverflow: overflowList,
93
+ isScrollOverflowCalculating: false
94
+ });
95
+ }
48
96
  handleIntersect(entries) {
49
97
  const visibleState = copy(this.getState('visibleState'));
50
98
  const res = {};
@@ -73,6 +121,8 @@ class OverflowListFoundation extends BaseFoundation {
73
121
  }
74
122
  this.previousY = currentY;
75
123
  this._adapter.updateVisibleState(visibleState);
124
+ // Keep scroll overflow cache in sync for stable UI
125
+ this._syncScrollOverflowCache(visibleState);
76
126
  this._adapter.notifyIntersect(res);
77
127
  }
78
128
  handleCollapseOverflow() {
@@ -54,7 +54,7 @@ export default class SelectFoundation extends BaseFoundation<SelectAdapter> {
54
54
  constructor(adapter: SelectAdapter);
55
55
  _keydownHandler: (...arg: any[]) => void | null;
56
56
  init(): void;
57
- focus(optionsForOpen?: BasicOptionProps[]): void;
57
+ focus(optionsForOpen?: BasicOptionProps[], openDropdown?: boolean): void;
58
58
  _focusTrigger(): void;
59
59
  destroy(): void;
60
60
  _setDropdownWidth(): void;
@@ -37,15 +37,40 @@ export default class SelectFoundation extends BaseFoundation {
37
37
  this.focus(originalOptions);
38
38
  }
39
39
  }
40
- focus(optionsForOpen) {
40
+ focus(optionsForOpen, openDropdown) {
41
41
  const isFilterable = this._isFilterable();
42
42
  const isMultiple = this._isMultiple();
43
43
  const {
44
44
  isOpen
45
45
  } = this.getStates();
46
+ const {
47
+ searchPosition
48
+ } = this.getProps();
49
+ // Default to true if not specified (backward compatibility)
50
+ const shouldOpenDropdown = openDropdown !== false;
46
51
  this._adapter.updateFocusState(true);
47
52
  this._adapter.setIsFocusInContainer(false);
48
53
  if (isFilterable) {
54
+ /**
55
+ * When openDropdown is false, we only want to "refocus" the Select
56
+ * without changing dropdown visibility.
57
+ *
58
+ * NOTE: For searchPosition='dropdown', the search input is rendered in dropdown,
59
+ * so we should NOT toggle trigger input(showInput) here, otherwise it may affect
60
+ * tabIndex / focusability unexpectedly.
61
+ */
62
+ if (!shouldOpenDropdown) {
63
+ if (searchPosition === strings.SEARCH_POSITION_TRIGGER) {
64
+ if (isMultiple) {
65
+ this.focusInput();
66
+ } else {
67
+ this.toggle2SearchInput(true);
68
+ }
69
+ } else {
70
+ this._focusTrigger();
71
+ }
72
+ return;
73
+ }
49
74
  if (isMultiple) {
50
75
  // when filter and multiple, focus input and open dropdown
51
76
  this.focusInput();
@@ -1083,7 +1108,8 @@ export default class SelectFoundation extends BaseFoundation {
1083
1108
  this.clearInput(e);
1084
1109
  }
1085
1110
  // after click showClear button, the select need to be focused
1086
- this.focus();
1111
+ // but don't open dropdown to avoid focus state confusion
1112
+ this.focus(undefined, false);
1087
1113
  this.clearSelected();
1088
1114
  // prevent this click open dropdown
1089
1115
  e.stopPropagation();
@@ -33,6 +33,7 @@
33
33
  }
34
34
  .semi-switch-checked .semi-switch-knob {
35
35
  transform: translateX(18px);
36
+ background-color: rgba(var(--semi-white), 1);
36
37
  }
37
38
  .semi-switch-checked:active .semi-switch-knob {
38
39
  transform: translateX(12px);
@@ -43,6 +43,7 @@ $module: #{$prefix}-switch;
43
43
 
44
44
  .#{$module}-knob {
45
45
  transform: translateX($spacing-switch_checked-translateX);
46
+ background-color: $color-switch_knob-bg-checked;
46
47
  }
47
48
 
48
49
  &:active {
@@ -30,7 +30,8 @@ $color-switch_disabled-border-default: var(--semi-color-border); // 禁用态开
30
30
  $color-switch_disabled-bg-hover: transparent; // 禁用态开关背景色 - 悬浮
31
31
  $color-switch_checked_disabled-bg-default: var(--semi-color-success-disabled); // 禁用开启态开关背景颜色
32
32
  $color-switch_checked_disabled-border-default: transparent; // 禁用开启态开关描边颜色
33
- $color-switch_knob-bg-default: rgba(var(--semi-white), 1); // 开关滑块背景颜色
33
+ $color-switch_knob-bg-default: rgba(var(--semi-white), 1); // 开关滑块背景颜色 - 关闭态
34
+ $color-switch_knob-bg-checked: rgba(var(--semi-white), 1); // 开关滑块背景颜色 - 开启态
34
35
  $color-switch_knob-border-default: var(--semi-color-border); // 开关滑块描边颜色
35
36
  $color-switch_checked-text-default: var(--semi-color-white); // 开启态开关文案颜色
36
37
  $color-switch_unchecked-text-default: var(--semi-color-text-2); // 关闭态开关文案颜色
@@ -881,7 +881,8 @@ class TableFoundation extends BaseFoundation {
881
881
  let e = arguments.length > 1 ? arguments[1] : undefined;
882
882
  let check = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
883
883
  var _a, _b, _c, _d;
884
- this.stopPropagation(e);
884
+ /* Do not call stopPropagation here, otherwise the click event registered via onHeaderCell
885
+ will be blocked when the click hot area is the whole title (#1861). */
885
886
  /* if mouse down to the resizable handle, do not trigger the sorting,fix #2802
886
887
  The target of the click event may be different from the target of the mousedown,
887
888
  e.g: Press the mouse, move to another node and then release it,
@@ -505,6 +505,32 @@
505
505
  color: var(--semi-color-text-0);
506
506
  }
507
507
 
508
+ .semi-tag-split {
509
+ display: inline-flex;
510
+ align-items: center;
511
+ }
512
+ .semi-tag-split .semi-tag {
513
+ border-radius: 0;
514
+ margin-right: 1px;
515
+ }
516
+ .semi-tag-split .semi-tag-first {
517
+ border-top-left-radius: var(--semi-border-radius-small);
518
+ border-bottom-left-radius: var(--semi-border-radius-small);
519
+ }
520
+ .semi-tag-split .semi-tag-last {
521
+ border-top-right-radius: var(--semi-border-radius-small);
522
+ border-bottom-right-radius: var(--semi-border-radius-small);
523
+ margin-right: unset;
524
+ }
525
+ .semi-tag-split .semi-tag-circle.semi-tag-first {
526
+ border-top-left-radius: var(--semi-border-radius-full);
527
+ border-bottom-left-radius: var(--semi-border-radius-full);
528
+ }
529
+ .semi-tag-split .semi-tag-circle.semi-tag-last {
530
+ border-top-right-radius: var(--semi-border-radius-full);
531
+ border-bottom-right-radius: var(--semi-border-radius-full);
532
+ }
533
+
508
534
  .semi-rtl .semi-tag,
509
535
  .semi-portal-rtl .semi-tag {
510
536
  direction: rtl;
@@ -329,4 +329,37 @@ $types: "ghost", "solid", "light";
329
329
  color: $color-tag_avatar-text-default;
330
330
  }
331
331
 
332
+ .#{$module}-split {
333
+ display: inline-flex;
334
+ align-items: center;
335
+
336
+ .#{$module} {
337
+ border-radius: 0;
338
+ margin-right: 1px;
339
+
340
+ &-first {
341
+ border-top-left-radius: $radius-tag;
342
+ border-bottom-left-radius: $radius-tag;
343
+ }
344
+
345
+ &-last {
346
+ border-top-right-radius: $radius-tag;
347
+ border-bottom-right-radius: $radius-tag;
348
+ margin-right: unset;
349
+ }
350
+
351
+ &-circle {
352
+ &.#{$module}-first {
353
+ border-top-left-radius: $radius-tag_circle;
354
+ border-bottom-left-radius: $radius-tag_circle;
355
+ }
356
+
357
+ &.#{$module}-last {
358
+ border-top-right-radius: $radius-tag_circle;
359
+ border-bottom-right-radius: $radius-tag_circle;
360
+ }
361
+ }
362
+ }
363
+ }
364
+
332
365
  @import './rtl.scss';
@@ -112,6 +112,8 @@
112
112
  padding-left: 4px;
113
113
  padding-right: 4px;
114
114
  overflow: hidden;
115
+ position: relative;
116
+ /* hidden mirror used to measure input text width */
115
117
  }
116
118
  .semi-tagInput-wrapper-tag {
117
119
  margin-right: 4px;
@@ -154,6 +156,8 @@
154
156
  .semi-tagInput-wrapper .semi-tagInput-wrapper-input {
155
157
  flex-grow: 1;
156
158
  width: min-content;
159
+ min-width: 2px;
160
+ max-width: 100%;
157
161
  border: none;
158
162
  outline: none;
159
163
  background-color: transparent;
@@ -195,6 +199,19 @@
195
199
  height: 24px;
196
200
  line-height: 24px;
197
201
  }
202
+ .semi-tagInput-wrapper-inputMirror {
203
+ position: absolute;
204
+ top: 0;
205
+ left: 0;
206
+ visibility: hidden;
207
+ pointer-events: none;
208
+ height: 0;
209
+ overflow: hidden;
210
+ white-space: pre;
211
+ font-size: 14px;
212
+ font-weight: 400;
213
+ font-family: inherit;
214
+ }
198
215
  .semi-tagInput-clearBtn {
199
216
  display: flex;
200
217
  justify-content: center;
@@ -137,6 +137,7 @@ $module: #{$prefix}-tagInput;
137
137
  padding-left: $spacing-extra-tight;
138
138
  padding-right: $spacing-extra-tight;
139
139
  overflow: hidden;
140
+ position: relative;
140
141
 
141
142
  &-tag {
142
143
  margin-right: $spacing-extra-tight;
@@ -190,6 +191,8 @@ $module: #{$prefix}-tagInput;
190
191
  & &-input {
191
192
  flex-grow: 1;
192
193
  width: min-content;
194
+ min-width: 2px;
195
+ max-width: 100%;
193
196
  // min-width: 38px;
194
197
  border: none;
195
198
  outline: none;
@@ -240,6 +243,21 @@ $module: #{$prefix}-tagInput;
240
243
  }
241
244
  }
242
245
  }
246
+
247
+ /* hidden mirror used to measure input text width */
248
+ &-inputMirror {
249
+ position: absolute;
250
+ top: 0;
251
+ left: 0;
252
+ visibility: hidden;
253
+ pointer-events: none;
254
+ height: 0;
255
+ overflow: hidden;
256
+ white-space: pre;
257
+ font-size: $font-size-regular;
258
+ font-weight: $font-weight-regular;
259
+ font-family: inherit;
260
+ }
243
261
  }
244
262
 
245
263
  &-clearBtn {
@@ -1,3 +1,4 @@
1
+ export type PanelType = 'left' | 'right';
1
2
  declare const cssClasses: {
2
3
  PREFIX: string;
3
4
  RANGE_PICKER: string;
@@ -1,3 +1,4 @@
1
+ import type { PanelType } from './constants';
1
2
  import BaseFoundation, { DefaultAdapter } from '../base/foundation';
2
3
  export type Position = 'top' | 'topLeft' | 'topRight' | 'left' | 'leftTop' | 'leftBottom' | 'right' | 'rightTop' | 'rightBottom' | 'bottom' | 'bottomLeft' | 'bottomRight' | 'leftTopOver' | 'rightTopOver';
3
4
  export interface TimePickerAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
@@ -16,11 +17,16 @@ declare class TimePickerFoundation<P = Record<string, any>, S = Record<string, a
16
17
  constructor(adapter: TimePickerAdapter<P, S>);
17
18
  init(): void;
18
19
  getPosition(): Position;
20
+ getDisabledTimeFns(panelType: PanelType, dates: Date[]): {
21
+ disabledHours: any;
22
+ disabledMinutes: any;
23
+ disabledSeconds: any;
24
+ };
19
25
  isDisabledHMS({ hours, minutes, seconds }: {
20
26
  hours: number;
21
27
  minutes: number;
22
28
  seconds: number;
23
- }): boolean;
29
+ }, panelType?: PanelType, dates?: Date[]): boolean;
24
30
  isValidTimeZone(timeZone: string | number): boolean;
25
31
  getDefaultFormatIfNeed(): string;
26
32
  /**
@@ -26,17 +26,44 @@ class TimePickerFoundation extends BaseFoundation {
26
26
  const rtlDirection = direction === 'rtl' ? 'bottomRight' : '';
27
27
  return position || rtlDirection || strings.DEFAULT_POSITION[type];
28
28
  }
29
+ getDisabledTimeFns(panelType, dates) {
30
+ const {
31
+ disabledHours,
32
+ disabledMinutes,
33
+ disabledSeconds,
34
+ disabledTime
35
+ } = this.getProps();
36
+ // disabledTime is range-only: only invoke it when the picker is
37
+ // actually a range picker. In single mode panelType has no meaning, so
38
+ // we fall back to the top-level disabledHours / disabledMinutes /
39
+ // disabledSeconds without invoking disabledTime.
40
+ if (typeof disabledTime === 'function' && this._adapter.isRangePicker()) {
41
+ const disabledObj = disabledTime(dates, panelType) || {};
42
+ return {
43
+ disabledHours: disabledObj.disabledHours || disabledHours,
44
+ disabledMinutes: disabledObj.disabledMinutes || disabledMinutes,
45
+ disabledSeconds: disabledObj.disabledSeconds || disabledSeconds
46
+ };
47
+ }
48
+ return {
49
+ disabledHours,
50
+ disabledMinutes,
51
+ disabledSeconds
52
+ };
53
+ }
29
54
  isDisabledHMS(_ref) {
30
55
  let {
31
56
  hours,
32
57
  minutes,
33
58
  seconds
34
59
  } = _ref;
60
+ let panelType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'left';
61
+ let dates = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
35
62
  const {
36
63
  disabledHours,
37
64
  disabledMinutes,
38
65
  disabledSeconds
39
- } = this.getProps();
66
+ } = this.getDisabledTimeFns(panelType, dates);
40
67
  const hDis = !isNullOrUndefined(hours) && hourIsDisabled(disabledHours, hours);
41
68
  const mDis = !isNullOrUndefined(hours) && !isNullOrUndefined(minutes) && minuteIsDisabled(disabledMinutes, hours, minutes);
42
69
  const sDis = !isNullOrUndefined(hours) && !isNullOrUndefined(minutes) && !isNullOrUndefined(seconds) && secondIsDisabled(disabledSeconds, hours, minutes, seconds);
@@ -158,11 +185,23 @@ class TimePickerFoundation extends BaseFoundation {
158
185
  if (this.isValidTimeZone(timeZone)) {
159
186
  dates = dates.map(date => utcToZonedTime(this.isValidTimeZone(__prevTimeZone) ? zonedTimeToUtc(date, __prevTimeZone) : date, timeZone));
160
187
  }
161
- invalid = dates.some(d => this.isDisabledHMS({
162
- hours: d.getHours(),
163
- minutes: d.getMinutes(),
164
- seconds: d.getSeconds()
165
- }));
188
+ if (this._adapter.isRangePicker()) {
189
+ invalid = dates.some((d, idx) => {
190
+ const panelType = idx === 1 ? 'right' : 'left';
191
+ return this.isDisabledHMS({
192
+ hours: d.getHours(),
193
+ minutes: d.getMinutes(),
194
+ seconds: d.getSeconds()
195
+ }, panelType, dates);
196
+ });
197
+ } else {
198
+ const d = dates[0];
199
+ invalid = d ? this.isDisabledHMS({
200
+ hours: d.getHours(),
201
+ minutes: d.getMinutes(),
202
+ seconds: d.getSeconds()
203
+ }, 'left', dates) : false;
204
+ }
166
205
  }
167
206
  const inputValue = this.formatValue(dates);
168
207
  this.setState({
@@ -258,11 +297,23 @@ class TimePickerFoundation extends BaseFoundation {
258
297
  let dates = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
259
298
  let invalid = dates.some(d => isNaN(Number(d)));
260
299
  if (!invalid) {
261
- invalid = dates.some(d => this.isDisabledHMS({
262
- hours: d.getHours(),
263
- minutes: d.getMinutes(),
264
- seconds: d.getSeconds()
265
- }));
300
+ if (this._adapter.isRangePicker()) {
301
+ invalid = dates.some((d, idx) => {
302
+ const panelType = idx === 1 ? 'right' : 'left';
303
+ return this.isDisabledHMS({
304
+ hours: d.getHours(),
305
+ minutes: d.getMinutes(),
306
+ seconds: d.getSeconds()
307
+ }, panelType, dates);
308
+ });
309
+ } else {
310
+ const d = dates[0];
311
+ invalid = d ? this.isDisabledHMS({
312
+ hours: d.getHours(),
313
+ minutes: d.getMinutes(),
314
+ seconds: d.getSeconds()
315
+ }, 'left', dates) : false;
316
+ }
266
317
  }
267
318
  return invalid;
268
319
  }
@@ -569,6 +569,7 @@ $module: #{$prefix}-navigation;
569
569
  & > .#{$prefix}-button {
570
570
  padding-left: $spacing-navigation_footer_collapse_btn_inner-paddingX;
571
571
  padding-right: $spacing-navigation_footer_collapse_btn_inner-paddingX;
572
+ color: $color-navigation_footer_icon-default;
572
573
  }
573
574
  }
574
575
 
@@ -65,7 +65,7 @@ $spacing-navigation_sub_item_left_toggle_marginRight:$spacing-tight; // 顶部
65
65
  $color-navigation-bg-default: var(--semi-color-nav-bg); // 导航栏背景色
66
66
  $color-navigation_border-default: var(--semi-color-border); // 导航栏分割线色
67
67
  $color-navigation_header-text-default: var(--semi-color-text-0); // 导航栏 header 文字颜色
68
- $color-navigation_footer_icon-default: var(--semi-color-text-2); // 导航栏 footer 图标颜色
68
+ $color-navigation_footer_icon-default: var(--semi-color-text-1); // 导航栏 footer 图标颜色
69
69
  $color-navigation_itemL1-bg-default: transparent; // 导航栏一级菜单项背景色
70
70
  $color-navigation_itemL1-text-default: var(--semi-color-text-0); // 导航栏一级菜单项文字颜色
71
71
  $color-navigation_itemL1_icon-default: var(--semi-color-text-2); // 导航栏一级菜单项图标颜色
@@ -28,22 +28,66 @@ class OverflowListFoundation extends BaseFoundation<OverflowListAdapter> {
28
28
 
29
29
  getOverflowItem(): Array<Array<Record<string, any>>> {
30
30
  const { items } = this.getProps();
31
- const { visibleState, overflow } = this.getStates();
31
+ const { visibleState, overflow, scrollOverflow } = this.getStates();
32
32
  if (!this.isScrollMode()) {
33
33
  return overflow;
34
34
  }
35
35
 
36
+ // Scroll mode relies on IntersectionObserver to compute visibility.
37
+ // During recalculation (e.g. items changed, or before observer fires),
38
+ // keep last computed overflow to avoid UI flicker (tabs arrows/menu state).
39
+ if (!visibleState || visibleState.size === 0) {
40
+ if (Array.isArray(scrollOverflow) && scrollOverflow.length === 2) {
41
+ return scrollOverflow;
42
+ }
43
+ return [[], []];
44
+ }
36
45
 
37
46
  const visibleStateArr = items.map(({ key }: { key: string }) => Boolean(visibleState.get(key)));
38
47
  const visibleStart = visibleStateArr.indexOf(true);
39
48
  const visibleEnd = visibleStateArr.lastIndexOf(true);
40
49
 
50
+ // If no item is visible (e.g. initial layout not ready or list out of viewport),
51
+ // treat it as "unknown" and keep last computed overflow to avoid wrong enabling.
52
+ if (visibleStart < 0 || visibleEnd < 0) {
53
+ if (Array.isArray(scrollOverflow) && scrollOverflow.length === 2) {
54
+ return scrollOverflow;
55
+ }
56
+ return [[], []];
57
+ }
58
+
41
59
  const overflowList = [];
42
60
  overflowList[0] = visibleStart >= 0 ? items.slice(0, visibleStart) : [];
43
61
  overflowList[1] = visibleEnd >= 0 ? items.slice(visibleEnd + 1, items.length) : items.slice();
44
62
  return overflowList;
45
63
  }
46
64
 
65
+ private _syncScrollOverflowCache(nextVisibleState: Map<string, boolean>): void {
66
+ if (!this.isScrollMode()) {
67
+ return;
68
+ }
69
+ const { items } = this.getProps();
70
+ const visibleStateArr = items.map(({ key }: { key: string }) => Boolean(nextVisibleState.get(key)));
71
+ const visibleStart = visibleStateArr.indexOf(true);
72
+ const visibleEnd = visibleStateArr.lastIndexOf(true);
73
+
74
+ // No visible items means the result is not reliable; keep cache and stay "calculating"
75
+ if (visibleStart < 0 || visibleEnd < 0) {
76
+ this._adapter.updateStates({
77
+ isScrollOverflowCalculating: true,
78
+ });
79
+ return;
80
+ }
81
+
82
+ const overflowList: Array<Array<Record<string, any>>> = [];
83
+ overflowList[0] = visibleStart >= 0 ? items.slice(0, visibleStart) : [];
84
+ overflowList[1] = visibleEnd >= 0 ? items.slice(visibleEnd + 1, items.length) : items.slice();
85
+ this._adapter.updateStates({
86
+ scrollOverflow: overflowList,
87
+ isScrollOverflowCalculating: false,
88
+ });
89
+ }
90
+
47
91
  handleIntersect(entries: Array<IntersectionObserverEntry>): void {
48
92
  const visibleState = copy(this.getState('visibleState'));
49
93
 
@@ -73,6 +117,8 @@ class OverflowListFoundation extends BaseFoundation<OverflowListAdapter> {
73
117
  }
74
118
  this.previousY = currentY;
75
119
  this._adapter.updateVisibleState(visibleState);
120
+ // Keep scroll overflow cache in sync for stable UI
121
+ this._syncScrollOverflowCache(visibleState);
76
122
  this._adapter.notifyIntersect(res);
77
123
  }
78
124
 
@@ -126,4 +172,4 @@ class OverflowListFoundation extends BaseFoundation<OverflowListAdapter> {
126
172
 
127
173
  }
128
174
 
129
- export default OverflowListFoundation;
175
+ export default OverflowListFoundation;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@douyinfe/semi-foundation",
3
- "version": "2.96.0",
3
+ "version": "2.97.0",
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.96.0",
14214
- "@douyinfe/semi-json-viewer-core": "2.96.0",
14213
+ "@douyinfe/semi-animation": "2.97.0",
14214
+ "@douyinfe/semi-json-viewer-core": "2.97.0",
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": "25977da675cef6d8dd70e92531b2441030991952",
14235
+ "gitHead": "fefc4b3e8ea823bc99f68bfee02f3f8cc314726c",
14236
14236
  "devDependencies": {
14237
14237
  "@babel/plugin-transform-runtime": "^7.15.8",
14238
14238
  "@babel/preset-env": "^7.15.8",
@@ -85,15 +85,39 @@ export default class SelectFoundation extends BaseFoundation<SelectAdapter> {
85
85
  }
86
86
  }
87
87
 
88
- focus(optionsForOpen?: BasicOptionProps[]) {
88
+ focus(optionsForOpen?: BasicOptionProps[], openDropdown?: boolean) {
89
89
  const isFilterable = this._isFilterable();
90
90
  const isMultiple = this._isMultiple();
91
91
  const { isOpen } = this.getStates();
92
+ const { searchPosition } = this.getProps();
93
+ // Default to true if not specified (backward compatibility)
94
+ const shouldOpenDropdown = openDropdown !== false;
92
95
 
93
96
  this._adapter.updateFocusState(true);
94
97
  this._adapter.setIsFocusInContainer(false);
95
98
 
96
99
  if (isFilterable) {
100
+ /**
101
+ * When openDropdown is false, we only want to "refocus" the Select
102
+ * without changing dropdown visibility.
103
+ *
104
+ * NOTE: For searchPosition='dropdown', the search input is rendered in dropdown,
105
+ * so we should NOT toggle trigger input(showInput) here, otherwise it may affect
106
+ * tabIndex / focusability unexpectedly.
107
+ */
108
+ if (!shouldOpenDropdown) {
109
+ if (searchPosition === strings.SEARCH_POSITION_TRIGGER) {
110
+ if (isMultiple) {
111
+ this.focusInput();
112
+ } else {
113
+ this.toggle2SearchInput(true);
114
+ }
115
+ } else {
116
+ this._focusTrigger();
117
+ }
118
+ return;
119
+ }
120
+
97
121
  if (isMultiple) {
98
122
  // when filter and multiple, focus input and open dropdown
99
123
  this.focusInput();
@@ -1077,7 +1101,8 @@ export default class SelectFoundation extends BaseFoundation<SelectAdapter> {
1077
1101
  this.clearInput(e);
1078
1102
  }
1079
1103
  // after click showClear button, the select need to be focused
1080
- this.focus();
1104
+ // but don't open dropdown to avoid focus state confusion
1105
+ this.focus(undefined, false);
1081
1106
  this.clearSelected();
1082
1107
  // prevent this click open dropdown
1083
1108
  e.stopPropagation();
@@ -43,6 +43,7 @@ $module: #{$prefix}-switch;
43
43
 
44
44
  .#{$module}-knob {
45
45
  transform: translateX($spacing-switch_checked-translateX);
46
+ background-color: $color-switch_knob-bg-checked;
46
47
  }
47
48
 
48
49
  &:active {
@@ -30,7 +30,8 @@ $color-switch_disabled-border-default: var(--semi-color-border); // 禁用态开
30
30
  $color-switch_disabled-bg-hover: transparent; // 禁用态开关背景色 - 悬浮
31
31
  $color-switch_checked_disabled-bg-default: var(--semi-color-success-disabled); // 禁用开启态开关背景颜色
32
32
  $color-switch_checked_disabled-border-default: transparent; // 禁用开启态开关描边颜色
33
- $color-switch_knob-bg-default: rgba(var(--semi-white), 1); // 开关滑块背景颜色
33
+ $color-switch_knob-bg-default: rgba(var(--semi-white), 1); // 开关滑块背景颜色 - 关闭态
34
+ $color-switch_knob-bg-checked: rgba(var(--semi-white), 1); // 开关滑块背景颜色 - 开启态
34
35
  $color-switch_knob-border-default: var(--semi-color-border); // 开关滑块描边颜色
35
36
  $color-switch_checked-text-default: var(--semi-color-white); // 开启态开关文案颜色
36
37
  $color-switch_unchecked-text-default: var(--semi-color-text-2); // 关闭态开关文案颜色