浏览代码

Merge branch 'release'

pointhalo 2 年之前
父节点
当前提交
5ec463bbca
共有 98 个文件被更改,包括 1957 次插入428 次删除
  1. 7 6
      content/input/taginput/index-en-US.md
  2. 7 5
      content/input/taginput/index.md
  3. 5 1
      content/input/transfer/index-en-US.md
  4. 5 1
      content/input/transfer/index.md
  5. 48 27
      content/other/locale/index-en-US.md
  6. 49 27
      content/other/locale/index.md
  7. 1 0
      content/show/tooltip/index-en-US.md
  8. 1 0
      content/show/tooltip/index.md
  9. 30 0
      content/start/changelog/index-en-US.md
  10. 32 0
      content/start/changelog/index.md
  11. 1 1
      cypress/integration/anchor.spec.js
  12. 3 3
      cypress/integration/tabs.spec.js
  13. 9 0
      cypress/integration/tagInput.spec.js
  14. 26 1
      cypress/integration/tooltip.spec.js
  15. 1 0
      gatsby-config.js
  16. 1 1
      lerna.json
  17. 1 1
      package.json
  18. 2 2
      packages/semi-animation-react/package.json
  19. 1 1
      packages/semi-animation-styled/package.json
  20. 1 1
      packages/semi-animation/package.json
  21. 1 1
      packages/semi-eslint-plugin/package.json
  22. 2 2
      packages/semi-foundation/anchor/animation.scss
  23. 2 2
      packages/semi-foundation/autoComplete/animation.scss
  24. 2 2
      packages/semi-foundation/breadcrumb/animation.scss
  25. 26 26
      packages/semi-foundation/button/animation.scss
  26. 2 2
      packages/semi-foundation/carousel/animation.scss
  27. 2 2
      packages/semi-foundation/cascader/animation.scss
  28. 4 4
      packages/semi-foundation/checkbox/animation.scss
  29. 1 1
      packages/semi-foundation/checkbox/variables.scss
  30. 2 2
      packages/semi-foundation/collapsible/animation.scss
  31. 2 2
      packages/semi-foundation/datePicker/animation.scss
  32. 1 1
      packages/semi-foundation/dropdown/animation.scss
  33. 16 9
      packages/semi-foundation/dropdown/foundation.ts
  34. 1 12
      packages/semi-foundation/dropdown/menuFoundation.ts
  35. 3 4
      packages/semi-foundation/form/form.scss
  36. 8 4
      packages/semi-foundation/form/interface.ts
  37. 0 1
      packages/semi-foundation/form/rtl.scss
  38. 38 35
      packages/semi-foundation/form/variables.scss
  39. 6 6
      packages/semi-foundation/input/animation.scss
  40. 4 4
      packages/semi-foundation/inputNumber/animation.scss
  41. 2 2
      packages/semi-foundation/navigation/animation.scss
  42. 1 1
      packages/semi-foundation/package.json
  43. 4 4
      packages/semi-foundation/pagination/animation.scss
  44. 4 4
      packages/semi-foundation/radio/animation.scss
  45. 2 2
      packages/semi-foundation/rating/animation.scss
  46. 1 1
      packages/semi-foundation/scrollList/animation.scss
  47. 6 6
      packages/semi-foundation/select/animation.scss
  48. 2 2
      packages/semi-foundation/slider/animation.scss
  49. 6 6
      packages/semi-foundation/steps/animation.scss
  50. 1 1
      packages/semi-foundation/switch/animation.scss
  51. 1 1
      packages/semi-foundation/table/animation.scss
  52. 8 8
      packages/semi-foundation/tabs/animation.scss
  53. 43 2
      packages/semi-foundation/tabs/tabs.scss
  54. 20 0
      packages/semi-foundation/tabs/variables.scss
  55. 4 4
      packages/semi-foundation/tagInput/animation.scss
  56. 4 4
      packages/semi-foundation/timePicker/utils/animation.scss
  57. 1 0
      packages/semi-foundation/tooltip/constants.ts
  58. 322 78
      packages/semi-foundation/tooltip/foundation.ts
  59. 2 2
      packages/semi-foundation/transfer/animation.scss
  60. 3 3
      packages/semi-foundation/transfer/foundation.ts
  61. 2 2
      packages/semi-foundation/tree/animation.scss
  62. 2 2
      packages/semi-icons/package.json
  63. 1 1
      packages/semi-illustrations/package.json
  64. 2 2
      packages/semi-next/package.json
  65. 1 1
      packages/semi-scss-compile/package.json
  66. 1 1
      packages/semi-theme-default/package.json
  67. 1 0
      packages/semi-theme-default/scss/animation.scss
  68. 0 1
      packages/semi-ui/datePicker/yearAndMonth.tsx
  69. 1 7
      packages/semi-ui/dropdown/dropdownMenu.tsx
  70. 5 0
      packages/semi-ui/dropdown/index.tsx
  71. 6 2
      packages/semi-ui/form/_story/FormApi/setValuesDemo.jsx
  72. 8 4
      packages/semi-ui/form/_story/FormSubmit/index.tsx
  73. 11 3
      packages/semi-ui/form/_story/form.stories.tsx
  74. 7 7
      packages/semi-ui/form/baseForm.tsx
  75. 2 1
      packages/semi-ui/form/errorMessage.tsx
  76. 3 2
      packages/semi-ui/form/hooks/useFormState.tsx
  77. 16 18
      packages/semi-ui/form/interface.ts
  78. 6 6
      packages/semi-ui/package.json
  79. 1 1
      packages/semi-ui/select/index.tsx
  80. 1 1
      packages/semi-ui/table/_story/v2/stickyHeader/index.scss
  81. 23 8
      packages/semi-ui/tagInput/_story/tagInput.stories.jsx
  82. 7 6
      packages/semi-ui/tagInput/index.tsx
  83. 1 0
      packages/semi-ui/tooltip/__test__/tooltip.test.js
  84. 159 0
      packages/semi-ui/tooltip/_story/AutoAdjustOverflow/bottom2Other.jsx
  85. 52 0
      packages/semi-ui/tooltip/_story/AutoAdjustOverflow/common.jsx
  86. 11 0
      packages/semi-ui/tooltip/_story/AutoAdjustOverflow/index.jsx
  87. 160 0
      packages/semi-ui/tooltip/_story/AutoAdjustOverflow/left2Other.jsx
  88. 163 0
      packages/semi-ui/tooltip/_story/AutoAdjustOverflow/right2Other.jsx
  89. 158 0
      packages/semi-ui/tooltip/_story/AutoAdjustOverflow/top2Other.jsx
  90. 2 2
      packages/semi-ui/tooltip/_story/InTable/index.jsx
  91. 1 0
      packages/semi-ui/tooltip/_story/story.scss
  92. 319 2
      packages/semi-ui/tooltip/_story/tooltip.stories.jsx
  93. 9 2
      packages/semi-ui/tooltip/index.tsx
  94. 7 1
      packages/semi-ui/transfer/index.tsx
  95. 1 1
      packages/semi-webpack/package.json
  96. 10 7
      src/sitePages/newHome/components/pro/pro.jsx
  97. 2 2
      src/sitePages/newHome/components/pro/pro.module.scss
  98. 4 4
      yarn.lock

+ 7 - 6
content/input/taginput/index-en-US.md

@@ -75,7 +75,6 @@ import { TagInput } from '@douyinfe/semi-ui';
 );
 ```
 
-
 ### Disabled
 
 ```jsx live=true
@@ -355,11 +354,12 @@ class TagInputDemo extends React.Component {
 
 ### Custom TagInput rendering
 
-You can use `renderTagItem` to customize tag rendering.
+You can use `renderTagItem` to customize tag rendering. `renderTagItem(value: string, index: number, onClose: function ) => React.ReactNode` The third parameter `onClose` is available since version 2.23.0.
 
 ```jsx live=true
 import React from 'react';
 import { TagInput, Avatar } from '@douyinfe/semi-ui';
+import { IconClose } from '@douyinfe/semi-ui-icons';
 
 class CustomRender extends React.Component {
     constructor(props) {
@@ -376,7 +376,7 @@ class CustomRender extends React.Component {
         this.mapList = new Map(this.list.map( item => [item.name, item]));
     }
 
-    renderTagItem(value, index) {
+    renderTagItem(value, index, onClose) {
         const data = this.mapList.get(value);
         return (
             <div 
@@ -391,6 +391,7 @@ class CustomRender extends React.Component {
                 <span style={{ marginLeft: 8 }}>
                     {`${value}@semi.com`}
                 </span>
+                <IconClose onClick={onClose} />
             </div>
         );
     }
@@ -401,12 +402,12 @@ class CustomRender extends React.Component {
             <TagInput 
                 value={value} 
                 onChange={value=>this.setState({ value })}
-                renderTagItem={(value, index)=>this.renderTagItem(value, index)}
+                renderTagItem={(value, index, onClose) => this.renderTagItem(value, index, onClose)}
             />
         );
     }
 }
-``` 
+```
 
 ### Drag to sort
 
@@ -449,7 +450,7 @@ import { TagInput } from '@douyinfe/semi-ui';
 |placeholder   |Content to be appear by default                  |string                                                           | -         |1.19.0|
 |prefix        |Prefix                                           |ReactNode                                                        |-          |1.19.0|
 | preventScroll | Indicates whether the browser should scroll the document to display the newly focused element, acting on the focus method inside the component, excluding the component passed in by the user | boolean |  |  |
-|renderTagItem |Customize the rendering of items                 |(value: string, index: number) => React.ReactNode                | -        |1.19.0|
+|renderTagItem |Customize the rendering of items, The parameter onClose is available in version 2.23.0  |<ApiType detail='(value: string, index: number, onClose: function) => React.ReactNode'>(params) => React.ReactNode</ApiType> | -        |1.19.0|
 |separator     |Customize the separator                          |string\|string[]                                                 |,          |1.19.0,string[] is supported since 1.29.0|
 |showClear     |Whether to show the clear button                 |boolean                                                          |false      |1.19.0|
 |size          |Size, one of `small`、`large`、`default`          |string                                                           |`default` |1.19.0|

+ 7 - 5
content/input/taginput/index.md

@@ -355,11 +355,12 @@ class TagInputDemo extends React.Component {
 
 ### 自定义标签渲染
 
-可以使用 `renderTagItem` 自定义标签渲染。
+可以使用 `renderTagItem` 自定义标签渲染。 `renderTagItem(value: string, index: number, onClose: function ) => React.ReactNode` 第三个参数 `onClose` 于 2.23.0 版本开始提供。
 
 ```jsx live=true
 import React from 'react';
 import { TagInput, Avatar } from '@douyinfe/semi-ui';
+import { IconClose } from '@douyinfe/semi-ui-icons';
 
 class CustomRender extends React.Component {
     constructor(props) {
@@ -376,7 +377,7 @@ class CustomRender extends React.Component {
         this.mapList = new Map(this.list.map( item => [item.name, item]));
     }
 
-    renderTagItem(value, index) {
+    renderTagItem(value, index, onClose) {
         const data = this.mapList.get(value);
         return (
             <div 
@@ -391,6 +392,7 @@ class CustomRender extends React.Component {
                 <span style={{ marginLeft: 8 }}>
                     {`${value}@semi.com`}
                 </span>
+                <IconClose onClick={onClose} />
             </div>
         );
     }
@@ -401,12 +403,12 @@ class CustomRender extends React.Component {
             <TagInput 
                 value={value} 
                 onChange={value => this.setState({ value })}
-                renderTagItem={(value, index) => this.renderTagItem(value, index)}
+                renderTagItem={(value, index, onClose) => this.renderTagItem(value, index, onClose)}
             />
         );
     }
 }
-``` 
+```
 
 ### 拖拽排序
 
@@ -449,7 +451,7 @@ import { TagInput } from '@douyinfe/semi-ui';
 |placeholder  |占位默认值                                         |string                         | -         |1.19.0|
 |prefix       |前缀标签                                           |ReactNode                      |-          |1.19.0|
 | preventScroll | 指示浏览器是否应滚动文档以显示新聚焦的元素,作用于组件内的 focus 方法 | boolean |  |  |
-|renderTagItem|自定义标签渲染                                      |(value: string, index: number) => React.ReactNode | -        |1.19.0|
+|renderTagItem|自定义标签渲染, 参数 onClose 于版本2.23.0版本提供     | <ApiType detail='(value: string, index: number, onClose: function) => React.ReactNode'>(params) => React.ReactNode</ApiType> | -  |1.19.0|
 |separator    |设置批量输入时的分隔符                               |string\|string[]                         |,    |1.19.0, string[]是从1.29.0开始支持|
 |showClear    |是否支持一键删除所有标签和输入内容                     |boolean                        |false      |1.19.0|
 |size         |设置输入框尺寸,可选: `small`、`large`、`default`     |string                          |`default` |1.19.0|

+ 5 - 1
content/input/transfer/index-en-US.md

@@ -925,7 +925,7 @@ import { Transfer } from '@douyinfe/semi-ui';
 | draggable | Whether to enable drag sorting | boolean | false | |
 | emptyContent | Custom empty state prompt text, search is the text displayed when there are no search results, left is the text when there is no source data on the left, and right is the prompt text when no data is checked | {left: ReactNode; right: ReactNode; search: ReactNode;} | | |
 | filter | Custom filter logic, when false, the search box is not displayed | boolean \| (input:string, item: Item) => boolean | true | |
-| inputProps | Can be used to customize the search box Input, the configurable properties refer to the Input component | [InputProps](/en-US/input/input#Input) | | |
+| inputProps | Can be used to customize the search box Input, the configurable properties refer to the Input component, the value and onChange parameters will be used inside Transfer, users should not use them. If you want to search through external data, you can call the search method of Transfer | [InputProps](/en-US/input/input#Input) | | |
 | loading | Whether the left option is being loaded | boolean |-| |
 | onChange | The callback that is triggered when the selected value changes, and the callback is also triggered after the drag sort changes | (values: Array<string\|number>, items: Array<Item\>) => void | | |
 | onDeselect | Callback when unchecking | (item: Item) => void | | |
@@ -969,6 +969,10 @@ TreeItem inherits all the properties of Item
 | -------- | -------------- | ---------------- | ------- |
 | children | Children Items | array<TreeItem\> |         |
 
+### Method
+
+- search(value: string): You can call this method through ref to search, and the search value will be set to Input.
+
 ## Design Tokens
 <DesignToken/>
 

+ 5 - 1
content/input/transfer/index.md

@@ -927,7 +927,7 @@ import { Transfer } from '@douyinfe/semi-ui';
 | draggable | 是否开启拖拽排序 | boolean | false |  |
 | emptyContent | 自定义空状态的提示文本,search 为无搜索结果时展示的文本,left 为左侧无源数据时的文本,right 为无勾选数据时的提示文本 | {left: ReactNode; right: ReactNode; search: ReactNode;} |  |  |
 | filter | 自定义筛选逻辑, 当为 false 时,不展示搜索框 | boolean \| (input:string, item: Item) => boolean | true |  |
-| inputProps | 可用于自定义搜索框 Input,可配置属性参考 Input 组件 | [InputProps](/zh-CN/input/input#API%20%E5%8F%82%E8%80%83) |  |  |
+| inputProps | 可用于自定义搜索框 Input,可配置属性参考 Input 组件,其中 value 和 onChange 参数在 Transfer 内部会被使用,用户请勿使用,如需通过外部数据进行搜索,可调用 Transfer 的 search 方法 | [InputProps](/zh-CN/input/input#API%20%E5%8F%82%E8%80%83) |  |  |
 | loading | 是否正在加载左侧选项 | boolean | - |  |
 | onChange | 选中值发生变化时触发的回调, 拖拽排序变化后也会触发该回调 | (values: Array<string\|number>, items: Array<Item\>) => void |  |  |
 | onDeselect | 取消勾选时的回调 | (item: Item) => void | |  |
@@ -971,6 +971,10 @@ TreeItem 继承 Item 的所有属性
 | -------- | ------ | ---------------- | ------ |
 | children | 子元素 | Array<TreeItem\> |        |
 
+### Method
+
+- search(value: string):可通过 ref 调用该方法进行搜索,该搜索值会被置给 Input。
+
 ## 设计变量
 <DesignToken/>
 

+ 48 - 27
content/other/locale/index-en-US.md

@@ -97,52 +97,73 @@ class I18nDemo extends React.Component {
 
 ### Custom Internationalization Component
 
+When your custom component also wants to consume the localeCode in the Semi LocaleProvider Context or read the i18n text localeData of a specific component, you can use LocaleConsumer to get it
+
 ```jsx live=true dir="column" noInline=true
 import React from 'react';
 import zh_CN from '@douyinfe/semi-ui/lib/es/locale/source/zh_CN';
 import en_GB from '@douyinfe/semi-ui/lib/es/locale/source/en_GB';
+import ko_KR from '@douyinfe/semi-ui/lib/es/locale/source/ko_KR';
 import { LocaleProvider, LocaleConsumer } from '@douyinfe/semi-ui';
 
-class I18nCustomDemo extends React.Component {
-    constructor(props) {
-        super(props);
-    }
+
+class GetLocaleFromSemi extends React.Component {
     render() {
-        return (
-            <>
-                <LocaleProvider locale={zh_CN}>
-                    <CustomComponent />
-                </LocaleProvider>
-                <LocaleProvider locale={en_GB}>
-                    <CustomComponent />
-                </LocaleProvider>
-            </>
-        );
+        return <LocaleConsumer componentName="TimePicker">
+            {
+                (localeData, localeCode, dateFnsLocale) => (
+                    <div>{localeCode} : {localeData.begin}</div>
+                )
+            }
+        </LocaleConsumer>;
     }
 }
 
-const CUSTOM_TEXT_MAP = {
-    'zh-CN': '你好',
-    'en-GB': 'hello'
-};
-
-class CustomComponent extends React.Component {
-    constructor(props) {
-        super(props);
-    }
+class ExtractComponent extends React.Component {
     render() {
-        return <LocaleConsumer componentName="code">
+        return <LocaleConsumer componentName="ComponentA">
             {
-                (locale) => (
-                    CUSTOM_TEXT_MAP[locale || 'zh-CN']
+                (localeData, localeCode, dateFnsLocale) => (
+                    <div>{localeData.customKey}</div>
                 )
             }
         </LocaleConsumer>;
     }
 }
 
-render(CustomComponent);
 
+class I18nCustomDemo extends React.Component {
+    render() {
+        const new_zh_CN = { ...zh_CN, ComponentA: { customKey: 'semi' } };
+        const new_ko_KR = { ...ko_KR, ComponentA: { customKey: 'design' } };
+        const new_en_GB = { ...en_GB, ComponentA: { customKey: 'dsm' } };
+
+        return (
+            <>
+                <LocaleProvider locale={new_zh_CN}>
+                    <GetLocaleFromSemi />
+                </LocaleProvider>
+                <LocaleProvider locale={new_ko_KR}>
+                    <GetLocaleFromSemi />
+                </LocaleProvider>
+                <LocaleProvider locale={new_en_GB}>
+                    <GetLocaleFromSemi />
+                </LocaleProvider>
+                <LocaleProvider locale={new_zh_CN}>
+                    <ExtractComponent />
+                </LocaleProvider>
+                <LocaleProvider locale={new_ko_KR}>
+                    <ExtractComponent />
+                </LocaleProvider>
+                <LocaleProvider locale={new_en_GB}>
+                    <ExtractComponent />
+                </LocaleProvider>
+            </>
+        );
+    }
+}
+
+render(I18nCustomDemo);
 ```
 
 ### Components that support multilingualism

+ 49 - 27
content/other/locale/index.md

@@ -96,51 +96,73 @@ class I18nDemo extends React.Component {
 
 ### 自定义国际化组件
 
+当你的自定义组件,也希望消费 Semi LocaleProvider Context 中的 localeCode 或者读取具体某个组件的 i18n 文本 localeData时,你可以使用 LocaleConsumer 进行获取;
+
 ```jsx live=true dir="column" noInline=true
 import React from 'react';
 import zh_CN from '@douyinfe/semi-ui/lib/es/locale/source/zh_CN';
 import en_GB from '@douyinfe/semi-ui/lib/es/locale/source/en_GB';
+import ko_KR from '@douyinfe/semi-ui/lib/es/locale/source/ko_KR';
 import { LocaleProvider, LocaleConsumer } from '@douyinfe/semi-ui';
 
-class I18nCustomDemo extends React.Component {
-    constructor(props) {
-        super(props);
-    }
+
+class GetLocaleFromSemi extends React.Component {
     render() {
-        return (
-            <>
-                <LocaleProvider locale={zh_CN}>
-                    <CustomComponent />
-                </LocaleProvider>
-                <LocaleProvider locale={en_GB}>
-                    <CustomComponent />
-                </LocaleProvider>
-            </>
-        );
+        return <LocaleConsumer componentName="TimePicker">
+            {
+                (localeData, localeCode, dateFnsLocale) => (
+                    <div>{localeCode} : {localeData.begin}</div>
+                )
+            }
+        </LocaleConsumer>;
     }
 }
 
-const CUSTOM_TEXT_MAP = {
-    'zh-CN': '你好',
-    'en-GB': 'hello'
-};
-
-class CustomComponent extends React.Component {
-    constructor(props) {
-        super(props);
-    }
+class ExtractComponent extends React.Component {
     render() {
-        return <LocaleConsumer componentName="code">
+        return <LocaleConsumer componentName="ComponentA">
             {
-                (locale) => (
-                    CUSTOM_TEXT_MAP[locale || 'zh-CN']
+                (localeData, localeCode, dateFnsLocale) => (
+                    <div>{localeData.customKey}</div>
                 )
             }
         </LocaleConsumer>;
     }
 }
 
-render(CustomComponent);
+
+class I18nCustomDemo extends React.Component {
+    render() {
+        const new_zh_CN = { ...zh_CN, ComponentA: { customKey: 'semi' } };
+        const new_ko_KR = { ...ko_KR, ComponentA: { customKey: 'design' } };
+        const new_en_GB = { ...en_GB, ComponentA: { customKey: 'dsm' } };
+
+        return (
+            <>
+                <LocaleProvider locale={new_zh_CN}>
+                    <GetLocaleFromSemi />
+                </LocaleProvider>
+                <LocaleProvider locale={new_ko_KR}>
+                    <GetLocaleFromSemi />
+                </LocaleProvider>
+                <LocaleProvider locale={new_en_GB}>
+                    <GetLocaleFromSemi />
+                </LocaleProvider>
+                <LocaleProvider locale={new_zh_CN}>
+                    <ExtractComponent />
+                </LocaleProvider>
+                <LocaleProvider locale={new_ko_KR}>
+                    <ExtractComponent />
+                </LocaleProvider>
+                <LocaleProvider locale={new_en_GB}>
+                    <ExtractComponent />
+                </LocaleProvider>
+            </>
+        );
+    }
+}
+
+render(I18nCustomDemo);
 ```
 
 ### 支持多语言的组件

+ 1 - 0
content/show/tooltip/index-en-US.md

@@ -408,6 +408,7 @@ import { Popconfirm, Tooltip, Button } from '@douyinfe/semi-ui';
 | clickToHide | Whether to automatically close the elastic layer when clicking on the floating layer and any element inside | boolean | false | **0.24.0** |
 | disableFocusListener | When trigger is `hover`, does not respond to the keyboard focus popup event, see details at [issue#977](https://github.com/DouyinFE/semi-design/issues/977) | boolean | false | **2.17.0** |
 | getPopupContainer | Specifies the parent DOM, and the bullet layer will be rendered to the DOM | () => HTMLElement | () => document.body |
+| margin | Calculate the added redundancy value when overflowing, see [issue#549](https://github.com/DouyinFE/semi-design/issues/549) | number | { marginLeft: number; marginTop: number; marginRight: number; marginBottom: number } | 0 |  **2.23.0**|
 | mouseEnterDelay | After the mouse is moved in, the display delay time, in milliseconds (only effective when the trigger is hover/focus) | number | 50 |  |
 | mouseLeaveDelay | The time for the delay to disappear after the mouse is moved out, in milliseconds (only effective when the trigger is hover/focus), and is not less than mouseEnterDelay | number | 50 |  |
 | motion | Whether to show the pop-up motion | boolean | true |  |

+ 1 - 0
content/show/tooltip/index.md

@@ -441,6 +441,7 @@ function Demo() {
 | clickToHide | 点击弹出层及内部任一元素时是否自动关闭弹层 | boolean | false | **0.24.0** |
 | disableFocusListener | trigger为`hover`时,不响应键盘聚焦弹出浮层事件,详见[issue#977](https://github.com/DouyinFE/semi-design/issues/977) | boolean | false | **2.17.0** |
 | getPopupContainer | 指定父级 DOM,弹层将会渲染至该 DOM 中,自定义需要设置 `position: relative` | function():HTMLElement | () => document.body |  |
+| margin | 计算溢出时的增加的冗余值,详见[issue#549](https://github.com/DouyinFE/semi-design/issues/549) | number | { marginLeft: number; marginTop: number; marginRight: number; marginBottom: number } | 0 |  **2.23.0**|
 | mouseEnterDelay | 鼠标移入后,延迟显示的时间,单位毫秒(仅当 trigger 为 hover/focus 时生效) | number | 50 |  |
 | mouseLeaveDelay | 鼠标移出后,延迟消失的时间,单位毫秒(仅当 trigger 为 hove/focus 时生效),不小于 mouseEnterDelay | number | 50 |  |
 | motion | 是否展示弹出层动画 | boolean | true |  |

+ 30 - 0
content/start/changelog/index-en-US.md

@@ -15,6 +15,36 @@ Version:Major.Minor.Patch (follow the **Semver** specification)
 -   **Patch version**: Only include bug fix, the release time is not limited
 
 ---
+
+#### 🎉 2.23.1 (2022-11-11)
+- 【Fix】
+    - Fixed the problem that Transfer in Popover caused Popover to close unexpectedly when dragging [#1226](https://github.com/DouyinFE/semi-design/issues/1226)
+    - Fixed the issue that the Transfer/ TagInput in the pop-up layer disappeared when the dragged item was dragged  [#1149](https://github.com/DouyinFE/semi-design/issues/1149)
+    - Correct the translation error of the Table pager when it is displayed in Vietnamese (vi_VN) [@MrFatMeow](https://github.com/MrFatMeow) [#1252](https://github.com/DouyinFE/semi-design/pull/1252)
+    - fix the case that the Select and Tooltip components did not process the incoming NaN [@edc-hui](https://github.com/edc-hui)[#763](https://github.com/DouyinFE/semi-design/issues/763) 
+- 【Style】
+    - Updated Form component Design Token, `$spacing-form_label_small-paddingTop` is corrected to `$spacing-form_switch_rating_marginY`, and the useless `$spacing-form_label-paddingRight` is removed; Correct the more accurate description [#1258](https://github.com/DouyinFE/semi-design/pull/1258)
+    - Updated Tabs component Design Token, adding the Token related to the collapsed arrow button, allowing to customize the style of the arrow buttons in Tabs separately [#1251](https://github.com/DouyinFE/semi-design/pull/1251)
+
+#### 🎉 2.23.0-beta.1 (2022-11-08)
+- 【Feat】
+    - Tooltip adds a margin parameter to calculate the increased redundancy value when overflowing, and autoAdjustOverflow provides a more intelligent position adjustment strategy when it is blocked
+    - added IconConnectionPoint1、IconConnectionPoint2、 IconCalendarStroked、IconConfigStroked 、IconIssueStroked 、IconStoryStroked 、IconVersionStroked and other icons.
+- 【Style】
+    - The right icon in the Cascader menu item increases the left margin
+
+#### 🎉 2.23.0-beta.0 (2022-11-07)
+- 【Fix】
+  - fix the inconsistency between the jump link hotspot and the onSelect hotspot when the Navigation item has a parameter link
+  - Corrected Saturday, Sunday translated text of DatePicker when displayed in Turkish (tr_TR) [@habibokumus](https://github.com/habibokumus)
+  - fix the scroll bar position not as expected when Table has fixed columns.
+- 【Feat】
+  - Add the onClose parameter to the renderTagItem API of TagInput to support deleting tags  [#1219 ](https://github.com/DouyinFE/semi-design/issues/1219)
+  - Transfer provides a search method to allow users to manually trigger searches
+- 【Chore】
+  - Update Form interface, add generic pass to define values type [@Hokori23](https://github.com/Hokori23)
+  - Add test case for Image component  [#1216 ](https://github.com/DouyinFE/semi-design/issues/1216)
+
 #### 🎉 2.22.3 (2022-11-02)
 - 【Docs】
     - Added accessibility (A11y) support to Semi site

+ 32 - 0
content/start/changelog/index.md

@@ -15,6 +15,38 @@ Semi 版本号遵循 **Semver** 规范(主版本号-次版本号-修订版本
 
 
 ---
+
+
+#### 🎉 2.23.1 (2022-11-11)
+- 【Fix】
+    - 修复 Popover 中的 Transfer 在拖拽时导致 Popover 意外关闭问题  [#1226](https://github.com/DouyinFE/semi-design/issues/1226)
+    - 修复 弹出层中的 Transfer/ TagInput 在拖拽时被拖拽项消失问题  [#1149](https://github.com/DouyinFE/semi-design/issues/1149)
+    - 更正 Table 分页器 在越南语 (vi_VN) 展示时翻译错误问题 [#1252](https://github.com/DouyinFE/semi-design/pull/1252) [@MrFatMeow](https://github.com/MrFatMeow) 
+    - 修复 Select和Tooltip组件未处理props.value / props.rePosKey 传入NaN的情况 [#763](https://github.com/DouyinFE/semi-design/issues/763)  [@edc-hui](https://github.com/edc-hui)
+    - 修复 SideSheet 中挂载 children 时机延迟的问题(影响版本 v2.22.beta.0 - v2.23.beta.0) [#1255](https://github.com/DouyinFE/semi-design/pull/1255)
+- 【Style】
+    - Form 组件 Design Token更新,`$spacing-form_label_small-paddingTop` 修正为 `$spacing-form_switch_rating_marginY`,去掉无实际作用的 `$spacing-form_label-paddingRight`;更正更准确的中文描述 [#1258](https://github.com/DouyinFE/semi-design/pull/1258)
+    - Tabs 组件 Design Token更新,增加折叠箭头按钮相关的 Token,允许单独对 Tabs中的箭头按钮定制样式 [#1251](https://github.com/DouyinFE/semi-design/pull/1251)
+
+#### 🎉 2.23.0-beta.1 (2022-11-08)
+- 【Feat】
+    - Tooltip 新增 margin 参数,计算溢出时的增加的冗余值,autoAdjustOverflow 提供更智能的位置调整策略
+    - 新增 IconConnectionPoint1、IconConnectionPoint2、 IconCalendarStroked、IconConfigStroked 、IconIssueStroked 、IconStoryStroked 、IconVersionStroked 等 icon
+- 【Style】
+    - Cascader 的菜单项中右侧图标增加左侧外边距
+
+#### 🎉 2.23.0-beta.0 (2022-11-07)
+- 【Fix】
+  - 修复 Navigation item 在有参数 link 情况下跳转链接热区与 onSelect 热区不一致问题
+  - 更正 DatePicker 在土耳其语(tr_TR)展示时周六、周日翻译的文本 [@habibokumus](https://github.com/habibokumus)
+  - 修复 Table 在固定列情况下,滚动条位置不符合预期问题。
+- 【Feat】
+  - TagInput 的 renderTagItem API 增加 onClose 参数支持删除标签  [#1219](https://github.com/DouyinFE/semi-design/issues/1219)
+  - Transfer 提供 search 方法支持用户手动触发搜索
+- 【Chore】
+  - Form interface 更新,Form标签增加泛型传入,指定 Values类型 [@Hokori23](https://github.com/Hokori23)
+  - 增加 Image 组件的测试用例  [#1216](https://github.com/DouyinFE/semi-design/issues/1216)
+  
 #### 🎉 2.22.3 (2022-11-02)
 - 【Docs】
     - 站点新增无障碍(A11y)支持

+ 1 - 1
cypress/integration/anchor.spec.js

@@ -19,7 +19,7 @@ describe('anchor', () => {
         cy.get('.semi-anchor-link-title-active').contains('doc1');
         cy.wait(500);
         cy.get('#box').scrollTo('top');
-        cy.get('h1').contains('whatever').click();
+        cy.get('h1').contains('whatever').click({ force: true });
     });
 
     it('click', () => {

+ 3 - 3
cypress/integration/tabs.spec.js

@@ -25,10 +25,10 @@ describe('tabs', () => {
 
     it('collapse', () => {
         cy.visit('http://127.0.0.1:6006/iframe.html?id=tabs--collapse-tabs&args=&viewMode=story');
-        cy.viewport(800, 800);
+        cy.viewport(800, 1600);
         cy.get('.semi-tabs-content').eq(0).contains('Content of card tab 0');
-        cy.get('.semi-button').eq(1).trigger('mouseover');
-        cy.get('.semi-dropdown').contains('Tab-6').click();
+        cy.get('.semi-button').eq(1).trigger('mouseover', { force: true });
+        cy.get('.semi-dropdown').contains('Tab-6').click({ force: true });
         cy.get('.semi-tabs-content').eq(0).contains('Content of card tab 6');
 
         // Tab-10 visible

+ 9 - 0
cypress/integration/tagInput.spec.js

@@ -0,0 +1,9 @@
+describe('tag', () => {
+    it('tagInput with renderTagItem', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=taginput--render-tag-item&args=&viewMode=story');
+
+        // focus and esc
+        cy.get('.semi-icon-close').click();
+        cy.get('.semi-icon-close').should('not.exist');
+    });
+});

+ 26 - 1
cypress/integration/tooltip.spec.js

@@ -31,7 +31,7 @@ describe('tooltip', () => {
     it('position with over autoAdjustOverflow', () => {
         const viewportWidth = 600;
         const viewportHeight = 400;
-        const overList = ['leftTopOver', 'rightTopOver', 'rightBottomOver', 'leftBottomOver',];
+        const overList = ['leftTopOver', 'rightTopOver', 'rightBottomOver', 'leftBottomOver'];
 
         cy.visit('http://127.0.0.1:6006/iframe.html?id=tooltip--left-top-over-demo&args=&viewMode=story');
         cy.viewport(viewportWidth, viewportHeight);
@@ -120,4 +120,29 @@ describe('tooltip', () => {
             cy.get('[x-placement="'+ topAndLeft[i] +'"]').should('have.length', 1);
         }
     });
+
+    it('test position in tblr, fine tune to other directions', () => {
+        const viewportWidth = 1000;
+        const viewportHeight = 800;
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=tooltip--adjust-pos-if-need-tblr&args=&viewMode=story');
+        cy.viewport(viewportWidth, viewportHeight);
+        cy.get('div .semi-tag').eq(0).click({ force: true });
+        const posRow = ['top', 'bottom', 'left', 'right'];
+        const posList1 = ['topLeft', 'topRight', 'bottomLeft', 'bottomRight'];
+        const posList2 = ['leftTop', 'leftBottom', 'rightTop', 'rightBottom'];
+
+        for (let i = 0; i < posList1.length; i++) {
+            cy.get('div .semi-tag').contains(posRow[0] + ' to ' + posList1[i]).click({ force: true });
+            cy.get('[x-placement="' + posList1[i] + '"]').should('have.length', 1);
+            cy.get('div .semi-tag').contains(posRow[1] + ' to ' + posList1[i]).click({ force: true });
+            cy.get('[x-placement="' + posList1[i] + '"]').should('have.length', 1);
+        }
+
+        for (let i = 0; i < posList2.length; i++) {
+            cy.get('div .semi-tag').contains(posRow[2] + ' to ' + posList2[i]).click({ force: true });
+            cy.get('[x-placement="' + posList2[i] + '"]').should('have.length', 1);
+            cy.get('div .semi-tag').contains(posRow[3] + ' to ' + posList2[i]).click({ force: true });
+            cy.get('[x-placement="' + posList2[i] + '"]').should('have.length', 1);
+        }
+    });
 });

+ 1 - 0
gatsby-config.js

@@ -1,6 +1,7 @@
 const path = require('path');
 module.exports = {
     pathPrefix: '/',
+    assetPrefix: (process.env['CDN_OUTER_CN'] || process.env['CDN_INNER_CN']) ? `https://${(process.env['CDN_OUTER_CN'] || process.env['CDN_INNER_CN'])}/${process.env['CDN_PATH_PREFIX']}`: "",
     siteMetadata: {
         title: 'Gatsby Default Starter',
         description: 'Create a consistent, good-looking, easy-to-use, and efficient user experience with a user-centric, content-first, and human-friendly design system',

+ 1 - 1
lerna.json

@@ -1,5 +1,5 @@
 {
     "useWorkspaces": true,
     "npmClient": "yarn",
-    "version": "2.22.3"
+    "version": "2.23.0"
 }

+ 1 - 1
package.json

@@ -44,7 +44,7 @@
     "dependencies": {
         "@douyinfe/semi-site-banner": "^0.1.0",
         "@douyinfe/semi-site-doc-style": "0.0.1",
-        "@douyinfe/semi-site-header": "^0.0.13",
+        "@douyinfe/semi-site-header": "^0.0.18",
         "@douyinfe/semi-site-markdown-blocks": "^0.0.9",
         "@mdx-js/react": "^1.6.22",
         "@svgr/core": "^5.5.0",

+ 2 - 2
packages/semi-animation-react/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-animation-react",
-  "version": "2.22.3",
+  "version": "2.23.0",
   "description": "motion library for semi-ui-react",
   "keywords": [
     "motion",
@@ -26,7 +26,7 @@
   },
   "dependencies": {
     "@douyinfe/semi-animation": "2.12.0",
-    "@douyinfe/semi-animation-styled": "2.22.3",
+    "@douyinfe/semi-animation-styled": "2.23.0",
     "classnames": "^2.2.6"
   },
   "devDependencies": {

+ 1 - 1
packages/semi-animation-styled/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-animation-styled",
-  "version": "2.22.3",
+  "version": "2.23.0",
   "description": "semi styled animation",
   "keywords": [
     "semi",

+ 1 - 1
packages/semi-animation/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-animation",
-  "version": "2.22.3",
+  "version": "2.23.0",
   "description": "animation base library for semi-ui",
   "keywords": [
     "animation",

+ 1 - 1
packages/semi-eslint-plugin/package.json

@@ -1,6 +1,6 @@
 {
   "name": "eslint-plugin-semi-design",
-  "version": "2.22.3",
+  "version": "2.23.0",
   "description": "semi ui eslint plugin",
   "keywords": [
     "semi",

+ 2 - 2
packages/semi-foundation/anchor/animation.scss

@@ -1,6 +1,6 @@
-$transition_duration-anchor_title-text: var(--semi-transition_duration-faster);//锚点标题文字-文字-动画持续时间
+$transition_duration-anchor_title-text: var(--semi-transition_duration-none);//锚点标题文字-文字-动画持续时间
 $transition_function-anchor_title-text: var(--semi-transition_function-easeIn);//锚点标题文字-文字-过渡曲线
-$transition_delay-anchor_title-text: var(--semi-transition_delay-fastest);//锚点标题文字-文字-延迟时间
+$transition_delay-anchor_title-text: var(--semi-transition_delay-none);//锚点标题文字-文字-延迟时间
 
 //transform token
 $transform_scale-anchor_title-text: var(--semi-transform_scale-none);//锚点-放大

+ 2 - 2
packages/semi-foundation/autoComplete/animation.scss

@@ -1,3 +1,3 @@
-$transition_duration-autoComplete_option-bg: var(--semi-transition_duration-faster);//选择器-选项-动画持续时间
+$transition_duration-autoComplete_option-bg: var(--semi-transition_duration-none);//选择器-选项-动画持续时间
 $transition_function-autoComplete_option-bg: var(--semi-transition_function-easeIn);//选择器-选项-过渡曲线
-$transition_delay-autoComplete_option-bg: var(--semi-transition_delay-fastest);//选择器-选项-延迟时间
+$transition_delay-autoComplete_option-bg: var(--semi-transition_delay-none);//选择器-选项-延迟时间

+ 2 - 2
packages/semi-foundation/breadcrumb/animation.scss

@@ -1,6 +1,6 @@
-$transition_duration-breadcrumb_link-text: var(--semi-transition_duration-faster);//面包屑文字-文字-动画持续时间
+$transition_duration-breadcrumb_link-text: var(--semi-transition_duration-none);//面包屑文字-文字-动画持续时间
 $transition_function-breadcrumb_link-text: var(--semi-transition_function-easeIn);//面包屑文字-文字-过渡曲线
-$transition_delay-breadcrumb_link-text: var(--semi-transition_delay-fastest);//面包屑文字-文字-延迟时间
+$transition_delay-breadcrumb_link-text: var(--semi-transition_delay-none);//面包屑文字-文字-延迟时间
 
 //transform token
 $transform_scale-breadcrumb_link-text: var(--semi-transform_scale-none);//面包屑文字-放大

+ 26 - 26
packages/semi-foundation/button/animation.scss

@@ -1,60 +1,60 @@
-$transition_duration-button_primary-bg: var(--semi-transition_duration-faster);//主要按钮-背景色-动画持续时间
+$transition_duration-button_primary-bg: var(--semi-transition_duration-none);//主要按钮-背景色-动画持续时间
 $transition_function-button_primary-bg: var(--semi-transition_function-easeIn);//主要按钮-背景色-过渡曲线
-$transition_delay-button_primary-bg: var(--semi-transition_delay-fastest);//主要按钮-背景色-延迟时间
+$transition_delay-button_primary-bg: var(--semi-transition_delay-none);//主要按钮-背景色-延迟时间
 
 
-$transition_duration-button_secondary-bg: var(--semi-transition_duration-faster);//次要按钮-背景色-动画持续时间
+$transition_duration-button_secondary-bg: var(--semi-transition_duration-none);//次要按钮-背景色-动画持续时间
 $transition_function-button_secondary-bg: var(--semi-transition_function-easeIn);//次要按钮-背景色-过渡曲线
-$transition_delay-button_secondary-bg: var(--semi-transition_delay-fastest);//次要按钮-背景色-延迟时间
+$transition_delay-button_secondary-bg: var(--semi-transition_delay-none);//次要按钮-背景色-延迟时间
 
 
-$transition_duration-button_tertiary-bg: var(--semi-transition_duration-faster);//三级按钮-背景色-动画持续时间
+$transition_duration-button_tertiary-bg: var(--semi-transition_duration-none);//三级按钮-背景色-动画持续时间
 $transition_function-button_tertiary-bg: var(--semi-transition_function-easeIn);//三级按钮-背景色-过渡曲线
-$transition_delay-button_tertiary-bg: var(--semi-transition_delay-fastest);//三级按钮-背景色-延迟时间
+$transition_delay-button_tertiary-bg: var(--semi-transition_delay-none);//三级按钮-背景色-延迟时间
 
 
-$transition_duration-button_light-bg: var(--semi-transition_duration-faster);//浅色按钮-背景色-动画持续时间
+$transition_duration-button_light-bg: var(--semi-transition_duration-none);//浅色按钮-背景色-动画持续时间
 $transition_function-button_light-bg: var(--semi-transition_function-easeIn);//浅色按钮-背景色-过渡曲线
-$transition_delay-button_light-bg: var(--semi-transition_delay-fastest);//浅色按钮-背景色-延迟时间
+$transition_delay-button_light-bg: var(--semi-transition_delay-none);//浅色按钮-背景色-延迟时间
 
 
-$transition_duration-button_warning-bg: var(--semi-transition_duration-faster);//警告按钮-背景色-动画持续时间
+$transition_duration-button_warning-bg: var(--semi-transition_duration-none);//警告按钮-背景色-动画持续时间
 $transition_function-button_warning-bg: var(--semi-transition_function-easeIn);//警告按钮-背景色-过渡曲线
-$transition_delay-button_warning-bg: var(--semi-transition_delay-fastest);//警告按钮-背景色-延迟时间
+$transition_delay-button_warning-bg: var(--semi-transition_delay-none);//警告按钮-背景色-延迟时间
 
 
-$transition_duration-button_danger-bg: var(--semi-transition_duration-faster);//危险按钮-背景色-动画持续时间
+$transition_duration-button_danger-bg: var(--semi-transition_duration-none);//危险按钮-背景色-动画持续时间
 $transition_function-button_danger-bg: var(--semi-transition_function-easeIn);//危险按钮-背景色-过渡曲线
-$transition_delay-button_danger-bg: var(--semi-transition_delay-fastest);//危险按钮-背景色-延迟时间
+$transition_delay-button_danger-bg: var(--semi-transition_delay-none);//危险按钮-背景色-延迟时间
 
 
-$transition_duration-button_borderless-bg: var(--semi-transition_duration-faster);//无边框按钮-背景色-动画持续时间
+$transition_duration-button_borderless-bg: var(--semi-transition_duration-none);//无边框按钮-背景色-动画持续时间
 $transition_function-button_borderless-bg: var(--semi-transition_function-easeIn);//无边框按钮-背景色-过渡曲线
-$transition_delay-button_borderless-bg: var(--semi-transition_delay-fastest);//无边框按钮-背景色-延迟时间
+$transition_delay-button_borderless-bg: var(--semi-transition_delay-none);//无边框按钮-背景色-延迟时间
 
-$transition_duration-button_primary-border: var(--semi-transition_duration-faster);//主要按钮-边框-动画持续时间
+$transition_duration-button_primary-border: var(--semi-transition_duration-none);//主要按钮-边框-动画持续时间
 $transition_function-button_primary-border: var(--semi-transition_function-easeIn);//主要按钮-边框-过渡曲线
-$transition_delay-button_primary-border: var(--semi-transition_delay-fastest);//主要按钮-边框-延迟时间
+$transition_delay-button_primary-border: var(--semi-transition_delay-none);//主要按钮-边框-延迟时间
 
-$transition_duration-button_secondary-border: var(--semi-transition_duration-faster);//次要按钮-边框-动画持续时间
+$transition_duration-button_secondary-border: var(--semi-transition_duration-none);//次要按钮-边框-动画持续时间
 $transition_function-button_secondary-border: var(--semi-transition_function-easeIn);//次要按钮-边框-过渡曲线
-$transition_delay-button_secondary-border: var(--semi-transition_delay-fastest);//次要按钮-边框-延迟时间
+$transition_delay-button_secondary-border: var(--semi-transition_delay-none);//次要按钮-边框-延迟时间
 
-$transition_duration-button_tertiary-border: var(--semi-transition_duration-faster);//三级按钮-边框-动画持续时间
+$transition_duration-button_tertiary-border: var(--semi-transition_duration-none);//三级按钮-边框-动画持续时间
 $transition_function-button_tertiary-border: var(--semi-transition_function-easeIn);//三级按钮-边框-过渡曲线
-$transition_delay-button_tertiary-border: var(--semi-transition_delay-fastest);//三级按钮-边框-延迟时间
+$transition_delay-button_tertiary-border: var(--semi-transition_delay-none);//三级按钮-边框-延迟时间
 
-$transition_duration-button_light-border: var(--semi-transition_duration-faster);//浅色按钮-边框-动画持续时间
+$transition_duration-button_light-border: var(--semi-transition_duration-none);//浅色按钮-边框-动画持续时间
 $transition_function-button_light-border: var(--semi-transition_function-easeIn);//浅色按钮-边框-过渡曲线
-$transition_delay-button_light-border: var(--semi-transition_delay-fastest);//浅色按钮-边框-延迟时间
+$transition_delay-button_light-border: var(--semi-transition_delay-none);//浅色按钮-边框-延迟时间
 
-$transition_duration-button_warning-border: var(--semi-transition_duration-faster);//警告按钮-边框-动画持续时间
+$transition_duration-button_warning-border: var(--semi-transition_duration-none);//警告按钮-边框-动画持续时间
 $transition_function-button_warning-border: var(--semi-transition_function-easeIn);//警告按钮-边框-过渡曲线
-$transition_delay-button_warning-border: var(--semi-transition_delay-fastest);//警告按钮-边框-延迟时间
+$transition_delay-button_warning-border: var(--semi-transition_delay-none);//警告按钮-边框-延迟时间
 
-$transition_duration-button_danger-border: var(--semi-transition_duration-faster);//危险按钮-边框-动画持续时间
+$transition_duration-button_danger-border: var(--semi-transition_duration-none);//危险按钮-边框-动画持续时间
 $transition_function-button_danger-border: var(--semi-transition_function-easeIn);//危险按钮-边框-过渡曲线
-$transition_delay-button_danger-border: var(--semi-transition_delay-fastest);//危险按钮-边框-延迟时间
+$transition_delay-button_danger-border: var(--semi-transition_delay-none);//危险按钮-边框-延迟时间
 
 //transform token
 

+ 2 - 2
packages/semi-foundation/carousel/animation.scss

@@ -1,7 +1,7 @@
-$transition_duration_carousel_indicator-bg: var(--semi-transition_delay-fastest); // 指示器-背景色-动画持续时间
+$transition_duration_carousel_indicator-bg: var(--semi-transition_delay-none); // 指示器-背景色-动画持续时间
 $transition_function_carousel_indicator-bg: var(--semi-transition_function-easeOut); // 指示器-背景色-过渡曲线
 $transition_delay_carousel_indicator-bg: var(--semi-transition_delay-none); // 指示器-背景色-延迟时间
 
-$transition_duration_carousel_arrow-bg: var(--semi-transition_delay-fastest); // 箭头-背景色-动画持续时间
+$transition_duration_carousel_arrow-bg: var(--semi-transition_delay-none); // 箭头-背景色-动画持续时间
 $transition_funciton_carousel_arrow-bg: var(--semi-transition_function-easeOut); // 箭头-背景色-过渡曲线
 $transition_delay_carousel_arrow-bg: var(--semi-transition_delay-none); // 箭头-背景色-延迟时间

+ 2 - 2
packages/semi-foundation/cascader/animation.scss

@@ -1,4 +1,4 @@
-$transition_duration-cascader_option-bg: var(--semi-transition_duration-faster);//级联选项-背景色-动画持续时间
+$transition_duration-cascader_option-bg: var(--semi-transition_duration-none);//级联选项-背景色-动画持续时间
 $transition_function-cascader_option-bg: var(--semi-transition_function-easeIn);//级联选项-背景色-过渡曲线
-$transition_delay-cascader_option-bg: var(--semi-transition_delay-fastest);//级联选项-背景色-延迟时间
+$transition_delay-cascader_option-bg: var(--semi-transition_delay-none);//级联选项-背景色-延迟时间
 

+ 4 - 4
packages/semi-foundation/checkbox/animation.scss

@@ -1,10 +1,10 @@
-$transition_duration-checkbox-bg: var(--semi-transition_duration-faster);//复选框-背景色-动画持续时间
+$transition_duration-checkbox-bg: var(--semi-transition_duration-none);//复选框-背景色-动画持续时间
 $transition_function-checkbox-bg: var(--semi-transition_function-easeIn);//复选框-背景色-过渡曲线
-$transition_delay-checkbox-bg: var(--semi-transition_delay-fastest);//复选框-背景色-延迟时间
+$transition_delay-checkbox-bg: var(--semi-transition_delay-none);//复选框-背景色-延迟时间
 
-$transition_duration-checkbox-border: var(--semi-transition_duration-faster);//复选框-边框-动画持续时间
+$transition_duration-checkbox-border: var(--semi-transition_duration-none);//复选框-边框-动画持续时间
 $transition_function-checkbox-border: var(--semi-transition_function-easeIn);//复选框-边框-过渡曲线
-$transition_delay-checkbox-border: var(--semi-transition_delay-fastest);//复选框-边框-延迟时间
+$transition_delay-checkbox-border: var(--semi-transition_delay-none);//复选框-边框-延迟时间
 
 // transform token
 $transform_scale-checkbox: var(--semi-transform_scale-none);//复选框-放大

+ 1 - 1
packages/semi-foundation/checkbox/variables.scss

@@ -52,7 +52,7 @@ $spacing-checkbox_extra-marginTop: $spacing-extra-tight; // extra 副标题顶
 $spacing-checkbox_cardType-paddingX: $spacing-base; // 卡片类型复选框的水平内间距
 $spacing-checkbox_cardType-paddingY: $spacing-base-tight; // 卡片类型复选框的垂直内间距
 $spacing-checkbox_cardType_inner-marginRight: $spacing-tight; // 卡片类型复选框 inner 的右外边距
-$spacing-checkbox_card_group_vertical-marginBottom: $spacing-base; // 卡片类型复选框的垂直向下外边
+$spacing-checkbox_card_group_vertical-marginBottom: $spacing-base; // 卡片样式复选框的下间
 
 $color-checkbox_extra-text-default: var(--semi-color-text-2); // extra 副标题文字颜色
 

+ 2 - 2
packages/semi-foundation/collapsible/animation.scss

@@ -1,10 +1,10 @@
 $transition_duration-collapsible-height: 250ms;//折叠-高度-动画持续时间
 $transition_function-collapsible-height: cubic-bezier(0.25,0.1,0.25,1);//折叠-高度-过渡曲线
-$transition_delay-collapsible-height: var(--semi-transition_delay-fastest);//折叠-高度-延迟时间
+$transition_delay-collapsible-height: var(--semi-transition_delay-none);//折叠-高度-延迟时间
 
 $transition_duration-collapsible-opacity: 250ms;//折叠-透明度-动画持续时间
 $transition_function-collapsible-opacity: var(--semi-transition_function-easeIn);//折叠-透明度-过渡曲线
-$transition_delay-collapsible-opacity: var(--semi-transition_delay-fastest);//折叠-透明度-延迟时间
+$transition_delay-collapsible-opacity: var(--semi-transition_delay-none);//折叠-透明度-延迟时间
 
 
 

+ 2 - 2
packages/semi-foundation/datePicker/animation.scss

@@ -1,4 +1,4 @@
-$transition_duration-datepicker_date-bg: var(--semi-transition_duration-faster);//时间选择器-格子背景色-动画持续时间
+$transition_duration-datepicker_date-bg: var(--semi-transition_duration-none);//时间选择器-格子背景色-动画持续时间
 $transition_function-datepicker_date-bg: var(--semi-transition_function-easeIn);//时间选择器-格子背景色-过渡曲线
-$transition_delay-datepicker_date-bg: var(--semi-transition_delay-fastest);//时间选择器-格子背景色-延迟时间
+$transition_delay-datepicker_date-bg: var(--semi-transition_delay-none);//时间选择器-格子背景色-延迟时间
 

+ 1 - 1
packages/semi-foundation/dropdown/animation.scss

@@ -1,3 +1,3 @@
-$transition_duration-dropdown_item-bg: var(--semi-transition_duration-fastest); // 下拉菜单项-背景颜色-动画持续时间
+$transition_duration-dropdown_item-bg: var(--semi-transition_duration-none); // 下拉菜单项-背景颜色-动画持续时间
 $transition_function-dropdown_item-bg: var(--semi-transition_function-easeOut); // 下拉菜单项-背景颜色-过渡曲线
 $transition_delay-dropdown_item-bg: 0ms; // 下拉菜单项-背景颜色-延迟时间

+ 16 - 9
packages/semi-foundation/dropdown/foundation.ts

@@ -3,33 +3,40 @@ import { handlePrevent, setFocusToFirstItem, setFocusToLastItem } from '../utils
 
 export interface DropdownAdapter extends Partial<DefaultAdapter> {
     setPopVisible(visible: boolean): void;
-    notifyVisibleChange(visible: boolean): void
+    notifyVisibleChange(visible: boolean): void;
+    getPopupId(): string
 }
 
 export default class DropdownFoundation extends BaseFoundation<DropdownAdapter> {
     handleVisibleChange(visible: boolean) {
         this._adapter.setPopVisible(visible);
         this._adapter.notifyVisibleChange(visible);
+
+        const { trigger } = this.getProps();
+        if (visible && trigger === "click"){
+            const popupId = this._adapter.getPopupId();
+            this.setFocusToFirstMenuItem(popupId);
+        }
     }
 
-    getMenuItemNodes(target: any): HTMLElement[] {
-        const id = target.attributes['data-popupid'].value;
+    getMenuItemNodes(id: string): HTMLElement[] {
         const menuWrapper = document.getElementById(id);
         // if has dropdown item, the item must wrapped by li
         return menuWrapper ? Array.from(menuWrapper.getElementsByTagName('li')).filter(item => item.ariaDisabled === "false") : null;
     }
 
-    setFocusToFirstMenuItem(target: any): void {
-        const menuItemNodes = this.getMenuItemNodes(target);
+    setFocusToFirstMenuItem(id: string): void {
+        const menuItemNodes = this.getMenuItemNodes(id);
         menuItemNodes && setFocusToFirstItem(menuItemNodes);
     }
 
-    setFocusToLastMenuItem(target: any): void {
-        const menuItemNodes = this.getMenuItemNodes(target);
+    setFocusToLastMenuItem(id: string): void {
+        const menuItemNodes = this.getMenuItemNodes(id);
         menuItemNodes && setFocusToLastItem(menuItemNodes);
     }
 
     handleKeyDown(event: any): void {
+        const id = event.target?.attributes['data-popupid']?.value;
         switch (event.key) {
             case ' ':
             case 'Enter':
@@ -38,11 +45,11 @@ export default class DropdownFoundation extends BaseFoundation<DropdownAdapter>
                 // handlePrevent(event);
                 break;
             case 'ArrowDown':
-                this.setFocusToFirstMenuItem(event.target);
+                this.setFocusToFirstMenuItem(id);
                 handlePrevent(event);
                 break;
             case 'ArrowUp':
-                this.setFocusToLastMenuItem(event.target);
+                this.setFocusToLastMenuItem(id);
                 handlePrevent(event);
                 break;
             default:

+ 1 - 12
packages/semi-foundation/dropdown/menuFoundation.ts

@@ -1,22 +1,11 @@
 
 import BaseFoundation, { DefaultAdapter } from '../base/foundation';
-import { handlePrevent, isPrintableCharacter, findIndexByCharacter, getAncestorNodeByRole, getMenuButton, setFocusToFirstItem, setFocusToItem, setFocusToNextMenuitem, setFocusToPreviousMenuItem } from '../utils/a11y';
-
+import { handlePrevent, isPrintableCharacter, findIndexByCharacter, getAncestorNodeByRole, getMenuButton, setFocusToItem, setFocusToNextMenuitem, setFocusToPreviousMenuItem } from '../utils/a11y';
 
 export default class DropdownMenuFoundation extends BaseFoundation<Partial<DefaultAdapter>> {
     menuItemNodes: HTMLElement[] = null;
     firstChars: string[] = [];
 
-    // if trigger is click, auto focus to the first menu item
-    autoFocus(ulElement: any): void {
-        const trigger = this._adapter.getContext('trigger');
-        if (trigger === 'click'){
-            // find all non-disabled li under this menu and set focus to the first menu
-            this.menuItemNodes = [...ulElement.getElementsByTagName('li')].filter(item => item.ariaDisabled !== "true");
-            setFocusToFirstItem(this.menuItemNodes);
-        }
-    }
-
     handleEscape(menu: Element): void {
         const trigger = this._adapter.getContext('trigger');
         if (trigger === 'custom'){

+ 3 - 4
packages/semi-foundation/form/form.scss

@@ -88,9 +88,7 @@ $rating: #{$prefix}-rating;
     }
 
     &-field-label {
-        // padding-left: $spacing-base;
         box-sizing: border-box;
-        padding-right: $spacing-form_label-paddingRight;
         font-weight: $font-form_label-fontWeight;
         color: $color-form_label-text-default;
         margin-bottom: $spacing-form_label-marginBottom;
@@ -146,6 +144,7 @@ $rating: #{$prefix}-rating;
         @include font-size-regular;
         display: flex;
         align-items: center;
+        // TODO help text margin token?
         margin-top: $spacing-form_message-marginTop;
         .#{$prefix}-icon-alert_triangle {
             color: $color-form_alertIcon-icon-default;
@@ -204,8 +203,8 @@ $rating: #{$prefix}-rating;
         .#{$switch},
         .#{$rating} {
             vertical-align: middle;
-            margin-top: $spacing-form_label_small-paddingTop;
-            margin-bottom: $spacing-form_label_small-paddingTop;
+            margin-top: $spacing-form_switch_rating_marginY;
+            margin-bottom: $spacing-form_switch_rating_marginY;
         }
     }
 

+ 8 - 4
packages/semi-foundation/form/interface.ts

@@ -9,24 +9,28 @@ export type FieldValidateTriggerType = BasicTriggerType | Array<BasicTriggerType
 
 export type CommonFieldError = boolean | string | Array<any> | undefined | unknown;
 
-export interface BaseFormAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
+export type BasicFieldError = Array<any>;
+
+export interface BaseFormAdapter<P = Record<string, any>, S = Record<string, any>, Values extends object = any> extends DefaultAdapter<P, S> {
     cloneDeep: (val: any, ...rest: any[]) => any;
     notifySubmit: (values: any) => void;
-    notifySubmitFail: (errors: Record<string, any>, values: any) => void;
+    notifySubmitFail: (errors: Record<keyof Values, BasicFieldError>, values: Partial<Values>) => void;
     forceUpdate: (callback?: () => void) => void;
     notifyChange: (formState: FormState) => void;
     notifyValueChange: (values: any, changedValues: any) => void;
     notifyReset: () => void;
-    getInitValues: () => Record<string, any>;
+    getInitValues: () => Partial<Values>;
     getFormProps: (keys: undefined | string | Array<string>) => any;
     getAllErrorDOM: () => NodeList;
     getFieldDOM: (field: string) => Node;
     initFormId: () => void
 }
 
+export type AllErrors<T> = T extends Record<string, any> ? { [K in keyof T]?: string } : Record<string, any>;
+
 export interface FormState<T extends Record<string, any> = any> {
     values?: T extends Record<string, any> ? T : Record<string, any>;
-    errors?: T extends Record<string, any> ? { [K in keyof T]?: string } : Record<string, any>;
+    errors?: AllErrors<T>;
     touched?: T extends Record<string, any> ? { [K in keyof T]?: boolean } : Record<string, any>
 }
 export interface setValuesConfig {

+ 0 - 1
packages/semi-foundation/form/rtl.scss

@@ -33,7 +33,6 @@ $rating: #{$prefix}-rating;
     
         &-field-label {
             padding-right: 0;
-            padding-left: $spacing-form_label-paddingRight;
     
             &-with-extra {
                 .#{$field}-label-extra {

+ 38 - 35
packages/semi-foundation/form/variables.scss

@@ -1,45 +1,48 @@
-$spacing-form_label-paddingTop: ($height-control-default - 20px) * 0.5; // 水平布局表单标题顶部内边距
-$spacing-form_label_small-paddingTop: ($height-control-default - 24px) * 0.5; // 水平布局 小尺寸表单标题顶部内边距
-
-$spacing-form_field_horizontal-paddingRight: $spacing-base; // 水平布局表单标题右侧内边距
+// form field
+$spacing-form_field_horizontal-paddingRight: $spacing-base; // 水平布局表单项右侧内边距
 $spacing-form_field_group_horizontal-paddingRight: $spacing-base; // 水平布局表单组标题右侧内边距
-
-$spacing-form_field_vertical-paddingTop: $spacing-base-tight; // 垂直布局表单顶部内边距
-$spacing-form_field_vertical-paddingBottom: $spacing-base-tight; // 垂直布局表单底部内边距
-
+$spacing-form_field_vertical-paddingTop: $spacing-base-tight; // 表单项顶部内边距(垂直布局)
+$spacing-form_field_vertical-paddingBottom: $spacing-base-tight; // 表单项底部内边距(垂直布局)
 $spacing-form_field_group_vertical-paddingTop: $spacing-base-tight; // 垂直布局表单组顶部内边距
 $spacing-form_field_group_vertical-paddingBottom: $spacing-base-tight; // 垂直布局表单组底部内边距
-$spacing-form_section-marginTop: $spacing-super-loose - $spacing-base-tight; // 表单分组顶部内边距
-$spacing-form_section_text-paddingBottom: $spacing-tight; // 表单分组标题底部内边距
-$spacing-form_section_text-marginBottom: $spacing-extra-tight; // 表单分组标题底部外边距
 
-$spacing-form_label-paddingRight: $spacing-base; // 表单标题右侧内边距
-$spacing-form_label-marginBottom: $spacing-extra-tight; // 表单标题底部外边距
-$spacing-form_label_extra-marginLeft: $spacing-extra-tight; // 表单标题图标左侧内边距
-$spacing-form_label_required-marginLeft: $spacing-extra-tight; // 表单标题必填标志左侧内边距
-$spacing-form_label_posLeft-marginRight: 0; // 水平布局表单标题右侧外边距
-$spacing-form_label_posLeft-marginBottom: 0; // 水平布局表单标题底部外边距
-$spacing-form_label_posTop-paddingTop: $spacing-extra-tight; // 垂直布局表单标题顶部内边距
-$spacing-form_label_posTop-paddingBottom: $spacing-extra-tight; // 垂直布局表单标题底部内边距
-$spacing-form_label_extra_posMid-marginTop: $spacing-extra-tight; // 表单额外信息辅助文字顶部外边距 - 居中
-$spacing-form_label_extra_posMid-marginBottom: $spacing-extra-tight; // 表单额外信息辅助文字底部外边距 - 居中
-$spacing-form_label_extra_posBottom-marginTop: $spacing-extra-tight; // 表单额外信息辅助文字顶部外边距 - 底部
-$spacing-form_statusIcon-marginRight: $spacing-extra-tight;  // 表单校验图标右侧外边距
+// form label
+$spacing-form_label-paddingTop: ($height-control-default - 20px) * 0.5; // 表单项标签顶部内边距(水平布局)
+$spacing-form_label-marginBottom: $spacing-extra-tight; // 表单项标签底部外边距
+$spacing-form_label_extra-marginLeft: $spacing-extra-tight; // 表单项标签额外信息左侧边距
+$spacing-form_label_required-marginLeft: $spacing-extra-tight; // 表单项标签必填标志左侧边距
+$spacing-form_label_posLeft-marginRight: 0; // 表单项左侧标签右侧外边距
+$spacing-form_label_posLeft-marginBottom: 0; // 表单项左侧标签底部外边距
+$spacing-form_label_posTop-paddingTop: $spacing-extra-tight; // 表单项顶部标签顶部边距
+$spacing-form_label_posTop-paddingBottom: $spacing-extra-tight; // 表单项顶部标签底部边距
+$spacing-form_label_extra_posMid-marginTop: $spacing-extra-tight; // 表单项标签 图标/可选标记 顶部外边距
+$spacing-form_label_extra_posMid-marginBottom: $spacing-extra-tight; // 表单项标签 图标/可选标记 底部外边距 - 居中
+$spacing-form_label_extra_posBottom-marginTop: $spacing-extra-tight; // 表单项标签 图标/可选标记 顶部外边距 - 底部
+
+// form switch rating需要额外margin以对齐高度 32px
+$spacing-form_switch_rating_marginY: ($height-control-default - 24px) * 0.5; // Switch / Rating 表单项顶部内边距(水平布局)
 
-$spacing-form_message-marginTop: $spacing-extra-tight; // 表单校验信息顶部外边距
+$color-form_requiredMark_disabled-text-default: var(--semi-color-danger);  // 禁用表单项必填标记颜色
+$color-form_label_disabled-text-default: var(--semi-color-disabled-text); // 禁用表单项标签文字颜色
+$font-form_label-fontWeight: $font-weight-bold; // 表单项标签字重
 
-$font-form_label-fontWeight: $font-weight-bold; // 表单标题字重
+$color-form_label-text-default: var(--semi-color-text-0); // 表单项标签文字颜色
+$color-form_label_optional-text-default: var(--semi-color-tertiary); // 表单项标签可选标记颜色
+$color-form_label_extra-text-default: var(--semi-color-tertiary); // 表单项标签图标颜色
+$color-form_requiredMark-text-default: var(--semi-color-danger); // 必填标记颜色
 $font-form_requiredMark-fontWeight: $font-weight-bold; // 表单必填标识字重
 
-$color-form_label-text-default: var(--semi-color-text-0); // 表单标题文字颜色
-$color-form_label_optional-text-default: var(--semi-color-tertiary); // 表单可选标记文字颜色
-$color-form_label_extra-text-default: var(--semi-color-tertiary); // 表单额外信息辅助文字颜色
+// form errormessage
+$color-form_message_error-text-default: var(--semi-color-danger); // 错误提示颜色
+$color-form_alertIcon-icon-default: var(--semi-color-warning); // 警告图标颜色
+$spacing-form_statusIcon-marginRight: $spacing-extra-tight;  // 表单校验状态图标右侧外边距
+$spacing-form_message-marginTop: $spacing-extra-tight; // 表单错误信息、辅助文字顶部外边距
 
-$color-form_label_disabled-text-default: var(--semi-color-disabled-text); // 禁用表单项标题文字颜色
-$color-form_requiredMark-text-default: var(--semi-color-danger); // 必填标记颜色
-$color-form_requiredMark_disabled-text-default: var(--semi-color-danger);  // 禁用表单项必填标记颜色
-$color-form_alertIcon-icon-default: var(--semi-color-warning); // 警告表单项图标颜色
-$color-form_message_error-text-default: var(--semi-color-danger); // 错误表单项图标颜色
+// form section
 $color-form_section-text-default: var(--semi-color-text-0); // 表单分组标题文字颜色
-$color-form_section-border-default: var(--semi-color-border); // 表单分组描边颜色
-$width-form_section-border: $border-thickness-control; // 表单分组描边宽度
+$color-form_section-border-default: var(--semi-color-border); // 表单分组标题底部描边颜色
+$width-form_section-border: $border-thickness-control; // 表单分组标题底部描边宽度
+$spacing-form_section-marginTop: $spacing-super-loose - $spacing-base-tight; // 表单分组顶部内边距
+$spacing-form_section_text-paddingBottom: $spacing-tight; // 表单分组标题底部内边距
+$spacing-form_section_text-marginBottom: $spacing-extra-tight; // 表单分组标题底部外边距
+

+ 6 - 6
packages/semi-foundation/input/animation.scss

@@ -1,14 +1,14 @@
-$transition_duration-input-bg: var(--semi-transition_duration-faster);//输入框-背景色-动画持续时间
+$transition_duration-input-bg: var(--semi-transition_duration-none);//输入框-背景色-动画持续时间
 $transition_function-input-bg: var(--semi-transition_function-easeIn);//输入框-背景色-过渡曲线
-$transition_delay-input-bg: var(--semi-transition_delay-fastest);//输入框-背景色-延迟时间
+$transition_delay-input-bg: var(--semi-transition_delay-none);//输入框-背景色-延迟时间
 
-$transition_duration-input-border: var(--semi-transition_duration-faster);//输入框-边框-动画持续时间
+$transition_duration-input-border: var(--semi-transition_duration-none);//输入框-边框-动画持续时间
 $transition_function-input-border: var(--semi-transition_function-easeIn);//输入框-边框-过渡曲线
-$transition_delay-input-border: var(--semi-transition_delay-fastest);//输入框-边框-延迟时间
+$transition_delay-input-border: var(--semi-transition_delay-none);//输入框-边框-延迟时间
 
-$transition_duration-input-text: var(--semi-transition_duration-faster);//输入框-文字或图标-动画持续时间
+$transition_duration-input-text: var(--semi-transition_duration-none);//输入框-文字或图标-动画持续时间
 $transition_function-input-text: var(--semi-transition_function-easeIn);//输入框-文字或图标-过渡曲线
-$transition_delay-input-text: var(--semi-transition_delay-fastest);//输入框-文字或图标-延迟时间
+$transition_delay-input-text: var(--semi-transition_delay-none);//输入框-文字或图标-延迟时间
 
 // transform token
 $transform_scale-input: var(--semi-transform_scale-none);//输入框-变大

+ 4 - 4
packages/semi-foundation/inputNumber/animation.scss

@@ -1,10 +1,10 @@
-$transition_duration-inputNumber-bg: var(--semi-transition_duration-faster);//数字输入框-背景色-动画持续时间
+$transition_duration-inputNumber-bg: var(--semi-transition_duration-none);//数字输入框-背景色-动画持续时间
 $transition_function-inputNumber-bg: var(--semi-transition_function-easeIn);//数字输入框-背景色-过渡曲线
-$transition_delay-inputNumber-bg: var(--semi-transition_delay-fastest);//数字输入框-背景色-延迟时间
+$transition_delay-inputNumber-bg: var(--semi-transition_delay-none);//数字输入框-背景色-延迟时间
 
-$transition_duration-inputNumber-border: var(--semi-transition_duration-faster);//数字输入框-边框-动画持续时间
+$transition_duration-inputNumber-border: var(--semi-transition_duration-none);//数字输入框-边框-动画持续时间
 $transition_function-inputNumber-border: var(--semi-transition_function-easeIn);//数字输入框-边框-过渡曲线
-$transition_delay-inputNumber-border: var(--semi-transition_delay-fastest);//数字输入框-边框-延迟时间
+$transition_delay-inputNumber-border: var(--semi-transition_delay-none);//数字输入框-边框-延迟时间
 
 // transform token
 $transform_scale-inputNumber: var(--semi-transform_scale-none);//数字输入框-变大

+ 2 - 2
packages/semi-foundation/navigation/animation.scss

@@ -1,4 +1,4 @@
-$transition_duration-navigation_itemL1-bg: var(--semi-transition_duration-faster);//导航一级菜单-背景色-动画持续时间
+$transition_duration-navigation_itemL1-bg: var(--semi-transition_duration-none);//导航一级菜单-背景色-动画持续时间
 $transition_function-navigation_itemL1-bg: var(--semi-transition_function-easeIn);//导航一级菜单-背景色-过渡曲线
-$transition_delay-navigation_itemL1-bg: var(--semi-transition_delay-fastest);//导航一级菜单-背景色-延迟时间
+$transition_delay-navigation_itemL1-bg: var(--semi-transition_delay-none);//导航一级菜单-背景色-延迟时间
 

+ 1 - 1
packages/semi-foundation/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-foundation",
-    "version": "2.22.3",
+    "version": "2.23.0",
     "description": "",
     "scripts": {
         "build:lib": "node ./scripts/compileLib.js",

+ 4 - 4
packages/semi-foundation/pagination/animation.scss

@@ -1,10 +1,10 @@
-$transition_duration-pagination_item-bg: var(--semi-transition_duration-faster);//翻页器页码-背景色-动画持续时间
+$transition_duration-pagination_item-bg: var(--semi-transition_duration-none);//翻页器页码-背景色-动画持续时间
 $transition_function-pagination_item-bg: var(--semi-transition_function-easeIn);//翻页器页码-背景色-过渡曲线
-$transition_delay-pagination_item-bg: var(--semi-transition_delay-fastest);//翻页器页码-背景色-延迟时间
+$transition_delay-pagination_item-bg: var(--semi-transition_delay-none);//翻页器页码-背景色-延迟时间
 
-$transition_duration-pagination_item-text: var(--semi-transition_duration-faster);//翻页器页码文本-背景色-动画持续时间
+$transition_duration-pagination_item-text: var(--semi-transition_duration-none);//翻页器页码文本-背景色-动画持续时间
 $transition_function-pagination_item-text: var(--semi-transition_function-easeIn);//翻页器页码文本-背景色-过渡曲线
-$transition_delay-pagination_item-text: var(--semi-transition_delay-fastest);//翻页器页码文本-背景色-延迟时间
+$transition_delay-pagination_item-text: var(--semi-transition_delay-none);//翻页器页码文本-背景色-延迟时间
 
 //transform token
 

+ 4 - 4
packages/semi-foundation/radio/animation.scss

@@ -1,10 +1,10 @@
-$transition_duration-radio-bg: var(--semi-transition_duration-faster);//单选框-背景色-动画持续时间
+$transition_duration-radio-bg: var(--semi-transition_duration-none);//单选框-背景色-动画持续时间
 $transition_function-radio-bg: var(--semi-transition_function-easeIn);//单选框-背景色-过渡曲线
-$transition_delay-radio-bg: var(--semi-transition_delay-fastest);//单选框-背景色-延迟时间
+$transition_delay-radio-bg: var(--semi-transition_delay-none);//单选框-背景色-延迟时间
 
-$transition_duration-radio-border: var(--semi-transition_duration-faster);//单选框-边框-动画持续时间
+$transition_duration-radio-border: var(--semi-transition_duration-none);//单选框-边框-动画持续时间
 $transition_function-radio-border: var(--semi-transition_function-easeIn);//单选框-边框-过渡曲线
-$transition_delay-radio-border: var(--semi-transition_delay-fastest);//单选框-边框-延迟时间
+$transition_delay-radio-border: var(--semi-transition_delay-none);//单选框-边框-延迟时间
 
 // transform token
 $transform_scale-radio: var(--semi-transform_scale-none);//单选框-变大

+ 2 - 2
packages/semi-foundation/rating/animation.scss

@@ -1,6 +1,6 @@
-$transition_duration-rating-color: var(--semi-transition_duration-faster);//评分-背景色-动画持续时间
+$transition_duration-rating-color: var(--semi-transition_duration-none);//评分-背景色-动画持续时间
 $transition_function-rating-color: var(--semi-transition_function-easeIn);//评分-背景色-过渡曲线
-$transition_delay-rating-color: var(--semi-transition_delay-fastest);//评分-背景色-延迟时间
+$transition_delay-rating-color: var(--semi-transition_delay-none);//评分-背景色-延迟时间
 
 // transform token
 $transform_scale-rating: var(--semi-transform_scale-none);//评分-变大

+ 1 - 1
packages/semi-foundation/scrollList/animation.scss

@@ -1,3 +1,3 @@
-$transition_duration-scrollList_selected_item-bg: var(--semi-transition_duration-fastest); // 滚动列表选中选项-背景颜色-动画持续时间
+$transition_duration-scrollList_selected_item-bg: var(--semi-transition_duration-none); // 滚动列表选中选项-背景颜色-动画持续时间
 $transition_function-scrollList_selected_item-bg: var(--semi-transition_function-easeOut); // 滚动列表选中选项-背景颜色-过渡曲线
 $transition_delay-scrollList_selected_item-bg: 0ms; // 滚动列表选中选项-背景颜色-延迟时间

+ 6 - 6
packages/semi-foundation/select/animation.scss

@@ -1,14 +1,14 @@
-$transition_duration-select-bg: var(--semi-transition_duration-faster);//选择器-背景色-动画持续时间
+$transition_duration-select-bg: var(--semi-transition_duration-none);//选择器-背景色-动画持续时间
 $transition_function-select-bg: var(--semi-transition_function-easeIn);//选择器-背景色-过渡曲线
-$transition_delay-select-bg: var(--semi-transition_delay-fastest);//选择器-背景色-延迟时间
+$transition_delay-select-bg: var(--semi-transition_delay-none);//选择器-背景色-延迟时间
 
-$transition_duration-select-border: var(--semi-transition_duration-faster);//选择器-边框-动画持续时间
+$transition_duration-select-border: var(--semi-transition_duration-none);//选择器-边框-动画持续时间
 $transition_function-select-border: var(--semi-transition_function-easeIn);//选择器-边框-过渡曲线
-$transition_delay-select-border: var(--semi-transition_delay-fastest);//选择器-边框-延迟时间
+$transition_delay-select-border: var(--semi-transition_delay-none);//选择器-边框-延迟时间
 
-$transition_duration-select_option-bg: var(--semi-transition_duration-faster);//选择器-选项-动画持续时间
+$transition_duration-select_option-bg: var(--semi-transition_duration-none);//选择器-选项-动画持续时间
 $transition_function-select_option-bg: var(--semi-transition_function-easeIn);//选择器-选项-过渡曲线
-$transition_delay-select_option-bg: var(--semi-transition_delay-fastest);//选择器-选项-延迟时间
+$transition_delay-select_option-bg: var(--semi-transition_delay-none);//选择器-选项-延迟时间
 
 // transform token
 $transform_scale-select: var(--semi-transform_scale-none);//选择框-变大

+ 2 - 2
packages/semi-foundation/slider/animation.scss

@@ -1,6 +1,6 @@
-$transition_duration-slider_handle-bg: var(--semi-transition_duration-faster);//滑动条圆形按钮-背景色-动画持续时间
+$transition_duration-slider_handle-bg: var(--semi-transition_duration-none);//滑动条圆形按钮-背景色-动画持续时间
 $transition_function-slider_handle-bg: var(--semi-transition_function-easeIn);//滑动条圆形按钮-背景色-过渡曲线
-$transition_delay-slider_handle-bg: var(--semi-transition_delay-fastest);//滑动条圆形按钮-背景色-延迟时间
+$transition_delay-slider_handle-bg: var(--semi-transition_delay-none);//滑动条圆形按钮-背景色-延迟时间
 
 //transform token
 

+ 6 - 6
packages/semi-foundation/steps/animation.scss

@@ -1,14 +1,14 @@
-$transition_duration-steps_item_title-text: var(--semi-transition_duration-faster);//步骤条标题文字-背景色-动画持续时间
+$transition_duration-steps_item_title-text: var(--semi-transition_duration-none);//步骤条标题文字-背景色-动画持续时间
 $transition_function-steps_item_title-text: var(--semi-transition_function-easeIn);//步骤条标题文字-背景色-过渡曲线
-$transition_delay-steps_item_title-text: var(--semi-transition_delay-fastest);//步骤条标题文字-背景色-延迟时间
+$transition_delay-steps_item_title-text: var(--semi-transition_delay-none);//步骤条标题文字-背景色-延迟时间
 
-$transition_duration-steps_item_title-icon: var(--semi-transition_duration-faster);//步骤条标题文字-背景色-动画持续时间
+$transition_duration-steps_item_title-icon: var(--semi-transition_duration-none);//步骤条标题文字-背景色-动画持续时间
 $transition_function-steps_item_title-icon: var(--semi-transition_function-easeIn);//步骤条标题文字-背景色-过渡曲线
-$transition_delay-steps_item_title-icon: var(--semi-transition_delay-fastest);//步骤条标题文字-背景色-延迟时间
+$transition_delay-steps_item_title-icon: var(--semi-transition_delay-none);//步骤条标题文字-背景色-延迟时间
 
-$transition_duration-steps_item_backgroundColor: var(--semi-transition_duration-faster);//步骤条标题文字-背景色-动画持续时间
+$transition_duration-steps_item_backgroundColor: var(--semi-transition_duration-none);//步骤条标题文字-背景色-动画持续时间
 $transition_function-steps_item_backgroundColor: var(--semi-transition_function-easeIn);//步骤条标题文字-背景色-过渡曲线
-$transition_delay-steps_item_backgroundColor: var(--semi-transition_delay-fastest);//步骤条标题文字-背景色-延迟时间
+$transition_delay-steps_item_backgroundColor: var(--semi-transition_delay-none);//步骤条标题文字-背景色-延迟时间
 
 
 

+ 1 - 1
packages/semi-foundation/switch/animation.scss

@@ -1,4 +1,4 @@
 $transition_duration-switch-bg: 200ms;//开关-背景色-动画持续时间
 $transition_function-switch-bg: var(--semi-transition_function-easeIn);//开关-背景色-过渡曲线
-$transition_delay-switch-bg: var(--semi-transition_delay-fastest);//开关-背景色-延迟时间
+$transition_delay-switch-bg: var(--semi-transition_delay-none);//开关-背景色-延迟时间
 

+ 1 - 1
packages/semi-foundation/table/animation.scss

@@ -1,3 +1,3 @@
-$transition_duration-table_body-bg: var(--semi-transition_duration-faster); // 表格-背景颜色-动画持续时间
+$transition_duration-table_body-bg: var(--semi-transition_duration-none); // 表格-背景颜色-动画持续时间
 $transition_function-table_body-bg: var(--semi-transition_function-easeOut); // 表格-背景颜色-过渡曲线
 $transition_delay-table_body-bg: 0ms; // 表格-背景颜色-延迟时间

+ 8 - 8
packages/semi-foundation/tabs/animation.scss

@@ -1,20 +1,20 @@
-$transition_duration-tabs_tab_line-border: var(--semi-transition_duration-faster); //线条式标签页标示线-边框-动画持续时间
+$transition_duration-tabs_tab_line-border: var(--semi-transition_duration-none); //线条式标签页标示线-边框-动画持续时间
 $transition_function-tabs_tab_line-border: var(--semi-transition_function-easeIn); //线条式标签页标示线-边框-过渡曲线
-$transition_delay-tabs_tab_line-border: var(--semi-transition_delay-fastest); //线条式标签页标示线-边框-延迟时间
+$transition_delay-tabs_tab_line-border: var(--semi-transition_delay-none); //线条式标签页标示线-边框-延迟时间
 
-$transition_duration-tabs_tab_line-text: var(--semi-transition_duration-faster); //线条式标签页-文字-动画持续时间
+$transition_duration-tabs_tab_line-text: var(--semi-transition_duration-none); //线条式标签页-文字-动画持续时间
 $transition_function-tabs_tab_line-text: var(--semi-transition_function-easeIn); //线条式标签页-文字-过渡曲线
-$transition_delay-tabs_tab_line-text: var(--semi-transition_delay-fastest); //线条式标签页-文字-延迟时间
+$transition_delay-tabs_tab_line-text: var(--semi-transition_delay-none); //线条式标签页-文字-延迟时间
 
 
-$transition_duration-tabs_tab_button-bg: var(--semi-transition_duration-faster); //按钮式标签页-背景色-动画持续时间
+$transition_duration-tabs_tab_button-bg: var(--semi-transition_duration-none); //按钮式标签页-背景色-动画持续时间
 $transition_function-tabs_tab_button-bg: var(--semi-transition_function-easeIn); //按钮式标签页-背景色-过渡曲线
-$transition_delay-tabs_tab_button-bg: var(--semi-transition_delay-fastest); //按钮式标签页-背景色-延迟时间
+$transition_delay-tabs_tab_button-bg: var(--semi-transition_delay-none); //按钮式标签页-背景色-延迟时间
 
 
-$transition_duration-tabs_tab_card-bg: var(--semi-transition_duration-faster); //卡片式标签页文字-背景色-动画持续时间
+$transition_duration-tabs_tab_card-bg: var(--semi-transition_duration-none); //卡片式标签页文字-背景色-动画持续时间
 $transition_function-tabs_tab_card-bg: var(--semi-transition_function-easeIn); //卡片式标签页文字-背景色-过渡曲线
-$transition_delay-tabs_tab_card-bg: var(--semi-transition_delay-fastest); //卡片式标签页文字-背景色-延迟时间
+$transition_delay-tabs_tab_card-bg: var(--semi-transition_delay-none); //卡片式标签页文字-背景色-延迟时间
 
 //transform token
 

+ 43 - 2
packages/semi-foundation/tabs/tabs.scss

@@ -134,14 +134,55 @@ $module: #{$prefix}-tabs;
                     outline-offset: $width-tabs-outline-offset;
                 }
             }
+            & > .#{$prefix}-button-disabled{
+                color: $color-tabs_tab-pane_arrow_disabled-text-default;
+                background-color: $color-tabs_tab-pane_arrow_disabled-bg-default;
+                &:hover{
+                    color: $color-tabs_tab-pane_arrow_disabled-text-hover;
+                    background-color: $color-tabs_tab-pane_arrow_disabled-bg-hover;
+                }
+            }
         }
 
         .#{$module}-bar-arrow-start {
             margin-right: $spacing-tabs_overflow_icon-marginRight;
+            & > .#{$prefix}-button{
+                color: $color-tabs_tab-pane_arrow-text-default;
+                padding: $spacing-tabs_tab-pane_arrow;
+                border: $width-tabs_tab-pane_arrow-border solid $color-tabs_tab-pane_arrow-border-default;
+                background-color: $color-tabs_tab-pane_arrow-bg-default;
+                &:hover{
+                    background-color: var(--semi-color-fill-0);
+                    color: $color-tabs_tab-pane_arrow-text-hover;
+                    border-color: $color-tabs_tab-pane_arrow-border-hover;
+                }
+                &:active{
+                    background-color: var(--semi-color-fill-1);
+                    color: $color-tabs_tab-pane_arrow-text-active;
+                    border-color: $color-tabs_tab-pane_arrow-border-active;
+                }
+            }
+
         }
 
         .#{$module}-bar-arrow-end {
             margin-left: $spacing-tabs_overflow_icon-marginLeft;
+            & > .#{$prefix}-button{
+                color: $color-tabs_tab-pane_arrow-text-default;
+                padding: $spacing-tabs_tab-pane_arrow;
+                border: $width-tabs_tab-pane_arrow-border solid $color-tabs_tab-pane_arrow-border-default;
+                background-color: $color-tabs_tab-pane_arrow-bg-default;
+                &:hover{
+                    background-color: var(--semi-color-fill-0);
+                    color: $color-tabs_tab-pane_arrow-text-hover;
+                    border-color: $color-tabs_tab-pane_arrow-border-hover;
+                }
+                &:active{
+                    background-color: var(--semi-color-fill-1);
+                    color: $color-tabs_tab-pane_arrow-text-active;
+                    border-color: $color-tabs_tab-pane_arrow-border-active;
+                }
+            }
         }
     }
 
@@ -160,13 +201,13 @@ $module: #{$prefix}-tabs;
     &-bar-line {
         &.#{$module}-bar-top {
             border-bottom: $width-tabs_bar_line-border solid $color-tabs_tab_line_default-border-default;
-            transition: color $transition_duration-tabs_tab_line-text $transition_function-tabs_tab_line-text $transition_delay-tabs_tab_line-text;//线条式tabs的 color的transition
+            transition: color $transition_duration-tabs_tab_line-text $transition_function-tabs_tab_line-text $transition_delay-tabs_tab_line-text; //线条式tabs的 color的transition
             transform:scale($transform_scale-tabs_tab_line-item);
 
             .#{$module}-tab {
                 padding: $spacing-tabs_bar_line_tab-paddingTop $spacing-tabs_bar_line_tab-paddingRight $spacing-tabs_bar_line_tab-paddingBottom $spacing-tabs_bar_line_tab-paddingLeft;
                 transition: border-bottom-color $transition_duration-tabs_tab_line-border $transition_function-tabs_tab_line-border $transition_delay-tabs_tab_line-border,
-                color $transition_duration-tabs_tab_line-text $transition_function-tabs_tab_line-text $transition_delay-tabs_tab_line-text;//线条式tabs的border-color 的 transition
+                color $transition_duration-tabs_tab_line-text $transition_function-tabs_tab_line-text $transition_delay-tabs_tab_line-text; //线条式tabs的border-color 的 transition
                 &:nth-of-type(1) {
                     padding-left: 0;
                 }

+ 20 - 0
packages/semi-foundation/tabs/variables.scss

@@ -48,6 +48,23 @@ $color-tabs_tab-outline-focus: var(--semi-color-primary-light-active); // 页签
 
 $color-tabs_tab-pane-text-default: var(--semi-color-text-0); // 标签页内容文本颜色 - 默认
 
+$color-tabs_tab-pane_arrow-text-default: var(--semi-color-primary); // 滚动折叠箭头颜色 - 默认
+$color-tabs_tab-pane_arrow-border-default: transparent; // 滚动折叠箭头边框颜色 - 默认
+$color-tabs_tab-pane_arrow-bg-default:transparent; // 滚动折叠箭头背景色 - 默认
+
+$color-tabs_tab-pane_arrow-text-hover: var(--semi-color-primary); // 滚动折叠箭头颜色 - 悬浮
+$color-tabs_tab-pane_arrow-border-hover: transparent; // 滚动折叠箭头边框颜色 - 悬浮
+$color-tabs_tab-pane_arrow-bg-hover: var(--semi-color-fill-0); // 滚动折叠箭头背景色 - 悬浮
+
+$color-tabs_tab-pane_arrow-text-active: var(--semi-color-primary); // 滚动折叠箭头颜色 - 按下
+$color-tabs_tab-pane_arrow-border-active: transparent; // 滚动折叠箭头边框颜色 - 按下
+$color-tabs_tab-pane_arrow-bg-active: var(--semi-color-fill-1); // 滚动折叠箭头背景色 - 按下
+
+$color-tabs_tab-pane_arrow_disabled-bg-default: transparent;
+$color-tabs_tab-pane_arrow_disabled-bg-hover:  transparent;
+$color-tabs_tab-pane_arrow_disabled-text-default: var(--semi-color-disabled-text);
+$color-tabs_tab-pane_arrow_disabled-text-hover:  var(--semi-color-disabled-text);
+
 $font-tabs_tab-fontWeight: $font-weight-regular; // 页签文本字重 - 默认
 $font-tabs_tab_active-fontWeight: $font-weight-bold; // 页签文本字重 - 选中
 
@@ -60,6 +77,7 @@ $width-tabs_bar_card-border: $border-thickness-control; // 卡片式页签底部
 $width-tabs-outline: 2px; // 聚焦轮廓宽度
 $width-tabs-outline-offset: -2px; // 聚焦轮廓偏移宽度
 $width-tabs_bar_line-outline-offset: -1px; // 线条式页签聚焦轮廓偏移宽度
+$width-tabs_tab-pane_arrow-border:0px; // 滚动折叠箭头边框宽度
 
 $height-tabs_bar_extra_large: 50px; // 大尺寸页签高度
 $font-tabs_bar_extra_large-lineHeight: $height-tabs_bar_extra_large; // 大尺寸页签文字行高
@@ -67,6 +85,7 @@ $font-tabs_bar_extra_large-lineHeight: $height-tabs_bar_extra_large; // 大尺
 $height-tabs_bar_extra_small: 36px; // 小尺寸页签高度
 $font-tabs_bar_extra_small-lineHeight: $height-tabs_bar_extra_small; // 小尺寸页签文字行高
 
+$spacing-tabs_tab-pane_arrow: 8px; //滚动折叠箭头内边距
 $spacing-tabs_bar_extra-paddingY: 0px; // 附加操作垂直内边距
 $spacing-tabs_bar_extra-paddingX: 5px; // 附加操作水平内边距
 $spacing-tabs_tab_icon-marginRight: $spacing-tight; // 附加操作垂直内边距
@@ -119,3 +138,4 @@ $spacing-tabs_bar_button_tab-paddingX: $spacing-base-tight; // 按钮式页签
 $radius-tabs_tab_card: var(--semi-border-radius-small) var(--semi-border-radius-small) 0 0; // 卡片式页签四向圆角
 $radius-tabs_tab_card_left: var(--semi-border-radius-small) 0 0 var(--semi-border-radius-small); // 垂直卡片式页签四向圆角
 $radius-tabs_tab_button: var(--semi-border-radius-small); // 按钮式页签圆角
+

+ 4 - 4
packages/semi-foundation/tagInput/animation.scss

@@ -1,7 +1,7 @@
-$transition_duration-tagInput-bg: var(--semi-transition_duration-faster);//标签输入框-背景色-动画持续时间
+$transition_duration-tagInput-bg: var(--semi-transition_duration-none);//标签输入框-背景色-动画持续时间
 $transition_function-tagInput-bg: var(--semi-transition_function-easeIn);//标签输入框-背景色-过渡曲线
-$transition_delay-tagInput-bg: var(--semi-transition_delay-fastest);//标签输入框-背景色-延迟时间
+$transition_delay-tagInput-bg: var(--semi-transition_delay-none);//标签输入框-背景色-延迟时间
 
-$transition_duration-tagInput-border: var(--semi-transition_duration-faster);//标签输入框-边框-动画持续时间
+$transition_duration-tagInput-border: var(--semi-transition_duration-none);//标签输入框-边框-动画持续时间
 $transition_function-tagInput-border: var(--semi-transition_function-easeIn);//标签输入框-边框-过渡曲线
-$transition_delay-tagInput-border: var(--semi-transition_delay-fastest);//标签输入框-边框-延迟时间
+$transition_delay-tagInput-border: var(--semi-transition_delay-none);//标签输入框-边框-延迟时间

+ 4 - 4
packages/semi-foundation/timePicker/utils/animation.scss

@@ -1,7 +1,7 @@
-$transition_duration-timePicker-bg: var(--semi-transition_duration-faster);//标签输入框-背景色-动画持续时间
+$transition_duration-timePicker-bg: var(--semi-transition_duration-none);//标签输入框-背景色-动画持续时间
 $transition_function-tagInput-bg: var(--semi-transition_function-easeIn);//标签输入框-背景色-过渡曲线
-$transition_delay-tagInput-bg: var(--semi-transition_delay-fastest);//标签输入框-背景色-延迟时间
+$transition_delay-tagInput-bg: var(--semi-transition_delay-none);//标签输入框-背景色-延迟时间
 
-$transition_duration-tagInput-border: var(--semi-transition_duration-faster);//标签输入框-边框-动画持续时间
+$transition_duration-tagInput-border: var(--semi-transition_duration-none);//标签输入框-边框-动画持续时间
 $transition_function-tagInput-border: var(--semi-transition_function-easeIn);//标签输入框-边框-过渡曲线
-$transition_delay-tagInput-border: var(--semi-transition_delay-fastest);//标签输入框-边框-延迟时间
+$transition_delay-tagInput-border: var(--semi-transition_delay-none);//标签输入框-边框-延迟时间

+ 1 - 0
packages/semi-foundation/tooltip/constants.ts

@@ -39,6 +39,7 @@ const numbers = {
     MOUSE_ENTER_DELAY: 50,
     MOUSE_LEAVE_DELAY: 50,
     SPACING: 8, // Values are consistent with spacing-tight in scss
+    MARGIN: 0,
 } as const;
 
 export { cssClasses, strings, numbers };

+ 322 - 78
packages/semi-foundation/tooltip/foundation.ts

@@ -133,11 +133,26 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
     unBindResizeEvent() {
         this._adapter.unregisterResizeHandler(this.onResize);
     }
-
+  
     removePortal = () => {
         this._adapter.removePortal();
     }
 
+    _adjustPos(position = '', isVertical = false, adjustType = 'reverse', concatPos?: any) {
+        switch (adjustType) {
+            case 'reverse':
+                return this._reversePos(position, isVertical);
+            case 'expand':
+                // only happens when position is top/bottom/left/right
+                return this._expandPos(position, concatPos);
+            case 'reduce':
+                // only happens when position other than top/bottom/left/right
+                return this._reducePos(position);
+            default:
+                return this._reversePos(position, isVertical);
+        }
+    }
+
     _reversePos(position = '', isVertical = false) {
         if (isVertical) {
             if (REGS.TOP.test(position)) {
@@ -153,6 +168,16 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
         return position;
     }
 
+    _expandPos(position = '', concatPos: string) {
+        return position.concat(concatPos);
+    }
+
+    _reducePos(position = '') {
+        // if cur position consists of two directions, remove the last position
+        const found = ['Top', 'Bottom', 'Left', 'Right'].find(pos => position.endsWith(pos));
+        return found ? position.replace(found, ''): position;
+    }
+
     clearDelayTimer() {
         if (this._timer) {
             clearTimeout(this._timer);
@@ -269,6 +294,10 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
         const content = this.getProp('content');
         const trigger = this.getProp('trigger');
         const clickTriggerToHide = this.getProp('clickTriggerToHide');
+        const { visible } = this.getStates();
+        if (visible) {
+            return ;
+        }
 
         this.clearDelayTimer();
 
@@ -285,9 +314,7 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
             this._togglePortalVisible(true);
         });
 
-        const position = this.calcPosition(null, null, null, false);
-
-        this._adapter.insertPortal(content, position);
+        this._adapter.insertPortal(content, { left: -9999, top: -9999 }); // offscreen rendering
 
         if (trigger === 'custom') {
             // eslint-disable-next-line
@@ -358,14 +385,15 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
         return null;
     }
 
-    calcPosStyle(triggerRect: DOMRect, wrapperRect: DOMRect, containerRect: PopupContainerDOMRect, position?: Position, spacing?: number) {
-        triggerRect = (isEmpty(triggerRect) ? triggerRect : this._adapter.getTriggerBounding()) || { ...defaultRect as any };
-        containerRect = (isEmpty(containerRect) ? containerRect : this._adapter.getPopupContainerRect()) || {
+    calcPosStyle(props: {triggerRect: DOMRect; wrapperRect: DOMRect; containerRect: PopupContainerDOMRect; position?: Position; spacing?: number; isOverFlow?: [boolean, boolean]}) {
+        const { spacing, isOverFlow } = props;
+        const triggerRect = (isEmpty(props.triggerRect) ? props.triggerRect : this._adapter.getTriggerBounding()) || { ...defaultRect as any };
+        const containerRect = (isEmpty(props.containerRect) ? props.containerRect : this._adapter.getPopupContainerRect()) || {
             ...defaultRect,
         };
-        wrapperRect = (isEmpty(wrapperRect) ? wrapperRect : this._adapter.getWrapperBounding()) || { ...defaultRect as any };
+        const wrapperRect = (isEmpty(props.wrapperRect) ? props.wrapperRect : this._adapter.getWrapperBounding()) || { ...defaultRect as any };
         // eslint-disable-next-line
-        position = position != null ? position : this.getProp('position');
+        const position = props.position != null ? props.position : this.getProp('position');
         // eslint-disable-next-line
         const SPACING = spacing != null ? spacing : this.getProp('spacing');
         const { arrowPointAtCenter, showArrow, arrowBounding } = this.getProps();
@@ -389,67 +417,104 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
         const offsetXWithArrow = positionOffsetX + horizontalArrowWidth / 2;
         const offsetYWithArrow = positionOffsetY + verticalArrowHeight / 2;
 
+        const heightDifference = wrapperRect.height - containerRect.height;
+        const widthDifference = wrapperRect.width - containerRect.width;
+
+        const offsetHeight = heightDifference > 0 ? heightDifference : 0;
+        const offsetWidth = widthDifference > 0 ? widthDifference : 0;
+        const isHeightOverFlow = isOverFlow && isOverFlow[0];
+        const isWidthOverFlow = isOverFlow && isOverFlow[1];
+
+        const isTriggerNearLeft = middleX - containerRect.left < containerRect.right - middleX;
+        const isTriggerNearTop = middleY - containerRect.top < containerRect.bottom - middleY;
+
+
         switch (position) {
             case 'top':
-                left = middleX;
-                top = triggerRect.top - SPACING;
+                // left = middleX;
+                // top = triggerRect.top - SPACING;
+                left = isWidthOverFlow ? (isTriggerNearLeft ? containerRect.left + wrapperRect.width / 2 : containerRect.right - wrapperRect.width / 2 + offsetWidth): middleX;
+                top = isHeightOverFlow ? containerRect.bottom + offsetHeight : triggerRect.top - SPACING;
                 translateX = -0.5;
                 translateY = -1;
                 break;
             case 'topLeft':
-                left = pointAtCenter ? middleX - offsetXWithArrow : triggerRect.left;
-                top = triggerRect.top - SPACING;
+                // left = pointAtCenter ? middleX - offsetXWithArrow : triggerRect.left;
+                // top = triggerRect.top - SPACING;
+                left = isWidthOverFlow ? containerRect.left : (pointAtCenter ? middleX - offsetXWithArrow : triggerRect.left);
+                top = isHeightOverFlow ? containerRect.bottom + offsetHeight : triggerRect.top - SPACING;
                 translateY = -1;
                 break;
             case 'topRight':
-                left = pointAtCenter ? middleX + offsetXWithArrow : triggerRect.right;
-                top = triggerRect.top - SPACING;
+                // left = pointAtCenter ? middleX + offsetXWithArrow : triggerRect.right;
+                // top = triggerRect.top - SPACING;
+                left = isWidthOverFlow ? containerRect.right + offsetWidth : (pointAtCenter ? middleX + offsetXWithArrow : triggerRect.right);
+                top = isHeightOverFlow ? containerRect.bottom + offsetHeight : triggerRect.top - SPACING;
                 translateY = -1;
                 translateX = -1;
                 break;
             case 'left':
-                left = triggerRect.left - SPACING;
-                top = middleY;
+                // left = triggerRect.left - SPACING;
+                // top = middleY;
+                // left = isWidthOverFlow? containerRect.right - SPACING : triggerRect.left - SPACING;
+                left = isWidthOverFlow ? containerRect.right + offsetWidth - SPACING + offsetXWithArrow : triggerRect.left - SPACING;
+                top = isHeightOverFlow ? (isTriggerNearTop ? containerRect.top + wrapperRect.height / 2 : containerRect.bottom - wrapperRect.height / 2 + offsetHeight): middleY;
                 translateX = -1;
                 translateY = -0.5;
                 break;
             case 'leftTop':
-                left = triggerRect.left - SPACING;
-                top = pointAtCenter ? middleY - offsetYWithArrow : triggerRect.top;
+                // left = triggerRect.left - SPACING;
+                // top = pointAtCenter ? middleY - offsetYWithArrow : triggerRect.top;
+                left = isWidthOverFlow ? containerRect.right + offsetWidth - SPACING + offsetXWithArrow : triggerRect.left - SPACING;
+                top = isHeightOverFlow ? containerRect.top : (pointAtCenter ? middleY - offsetYWithArrow : triggerRect.top);
                 translateX = -1;
                 break;
             case 'leftBottom':
-                left = triggerRect.left - SPACING;
-                top = pointAtCenter ? middleY + offsetYWithArrow : triggerRect.bottom;
+                // left = triggerRect.left - SPACING;
+                // top = pointAtCenter ? middleY + offsetYWithArrow : triggerRect.bottom;
+                left = isWidthOverFlow ? containerRect.right + offsetWidth - SPACING + offsetXWithArrow: triggerRect.left - SPACING;
+                top = isHeightOverFlow ? containerRect.bottom + offsetHeight: (pointAtCenter ? middleY + offsetYWithArrow : triggerRect.bottom);
                 translateX = -1;
                 translateY = -1;
                 break;
             case 'bottom':
-                left = middleX;
-                top = triggerRect.top + triggerRect.height + SPACING;
+                // left = middleX;
+                // top = triggerRect.top + triggerRect.height + SPACING;
+                left = isWidthOverFlow ? (isTriggerNearLeft ? containerRect.left + wrapperRect.width / 2 : containerRect.right - wrapperRect.width / 2 + offsetWidth): middleX;
+                top = isHeightOverFlow ? containerRect.top + offsetYWithArrow - SPACING: triggerRect.top + triggerRect.height + SPACING;
                 translateX = -0.5;
                 break;
             case 'bottomLeft':
-                left = pointAtCenter ? middleX - offsetXWithArrow : triggerRect.left;
-                top = triggerRect.bottom + SPACING;
+                // left = pointAtCenter ? middleX - offsetXWithArrow : triggerRect.left;
+                // top = triggerRect.bottom + SPACING;
+                left = isWidthOverFlow ? containerRect.left : (pointAtCenter ? middleX - offsetXWithArrow : triggerRect.left);
+                top = isHeightOverFlow ? containerRect.top + offsetYWithArrow - SPACING : triggerRect.top + triggerRect.height + SPACING;
                 break;
             case 'bottomRight':
-                left = pointAtCenter ? middleX + offsetXWithArrow : triggerRect.right;
-                top = triggerRect.bottom + SPACING;
+                // left = pointAtCenter ? middleX + offsetXWithArrow : triggerRect.right;
+                // top = triggerRect.bottom + SPACING;
+                left = isWidthOverFlow ? containerRect.right + offsetWidth : (pointAtCenter ? middleX + offsetXWithArrow : triggerRect.right);
+                top = isHeightOverFlow ? containerRect.top + offsetYWithArrow - SPACING : triggerRect.top + triggerRect.height + SPACING;
                 translateX = -1;
                 break;
             case 'right':
-                left = triggerRect.right + SPACING;
-                top = middleY;
+                // left = triggerRect.right + SPACING;
+                // top = middleY;
+                left = isWidthOverFlow ? containerRect.left - SPACING + offsetXWithArrow : triggerRect.right + SPACING;
+                top = isHeightOverFlow ? (isTriggerNearTop ? containerRect.top + wrapperRect.height / 2 : containerRect.bottom - wrapperRect.height / 2 + offsetHeight) : middleY;
                 translateY = -0.5;
                 break;
             case 'rightTop':
-                left = triggerRect.right + SPACING;
-                top = pointAtCenter ? middleY - offsetYWithArrow : triggerRect.top;
+                // left = triggerRect.right + SPACING;
+                // top = pointAtCenter ? middleY - offsetYWithArrow : triggerRect.top;
+                left = isWidthOverFlow ? containerRect.left - SPACING + offsetXWithArrow : triggerRect.right + SPACING;
+                top = isHeightOverFlow ? containerRect.top : (pointAtCenter ? middleY - offsetYWithArrow : triggerRect.top);
                 break;
             case 'rightBottom':
-                left = triggerRect.right + SPACING;
-                top = pointAtCenter ? middleY + offsetYWithArrow : triggerRect.bottom;
+                // left = triggerRect.right + SPACING;
+                // top = pointAtCenter ? middleY + offsetYWithArrow : triggerRect.bottom;
+                left = isWidthOverFlow ? containerRect.left - SPACING + offsetXWithArrow : triggerRect.right + SPACING;
+                top = isHeightOverFlow ? containerRect.bottom + offsetHeight : (pointAtCenter ? middleY + offsetYWithArrow : triggerRect.bottom);
                 translateY = -1;
                 break;
             case 'leftTopOver':
@@ -574,18 +639,18 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
 
         // console.log('containerRect: ', containerRect, 'triggerRect: ', triggerRect, 'wrapperRect: ', wrapperRect);
 
-        let style = this.calcPosStyle(triggerRect, wrapperRect, containerRect);
+        let style = this.calcPosStyle({ triggerRect, wrapperRect, containerRect });
 
         let position = this.getProp('position');
 
         if (this.getProp('autoAdjustOverflow')) {
             // console.log('style: ', style, '\ntriggerRect: ', triggerRect, '\nwrapperRect: ', wrapperRect);
-            const adjustedPos = this.adjustPosIfNeed(position, style, triggerRect, wrapperRect, containerRect);
+            const { position: adjustedPos, isHeightOverFlow, isWidthOverFlow } = this.adjustPosIfNeed(position, style, triggerRect, wrapperRect, containerRect);
 
-            if (position !== adjustedPos) {
+            if (position !== adjustedPos || isHeightOverFlow || isWidthOverFlow) {
                 position = adjustedPos;
 
-                style = this.calcPosStyle(triggerRect, wrapperRect, containerRect, position);
+                style = this.calcPosStyle({ triggerRect, wrapperRect, containerRect, position, spacing: null, isOverFlow: [ isHeightOverFlow, isWidthOverFlow ] });
             }
         }
 
@@ -598,17 +663,66 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
     };
 
     isLR(position = '') {
-        return position.indexOf('left') === 0 || position.indexOf('right') === 0;
+        return position.includes('left') || position.includes('right');
     }
 
     isTB(position = '') {
-        return position.indexOf('top') === 0 || position.indexOf('bottom') === 0;
+        return position.includes('top') || position.includes('bottom');
+    }
+
+    isReverse(rowSpace: number, reverseSpace: number, size: number) {
+        // 原空间不足,反向空间足够
+        // Insufficient original space, enough reverse space
+        return rowSpace < size && reverseSpace > size;
+    }
+
+    isOverFlow(rowSpace: number, reverseSpace: number, size: number){
+        // 原空间且反向空间都不足
+        // The original space and the reverse space are not enough
+        return rowSpace < size && reverseSpace < size;
+    }
+
+    isHalfOverFlow(posSpace: number, negSpace: number, size: number){
+        // 正半空间或者负半空间不足,即表示有遮挡,需要偏移
+        // Insufficient positive half space or negative half space means that there is occlusion and needs to be offset
+        return posSpace < size || negSpace < size;
+    }
+
+    isHalfAllEnough(posSpace: number, negSpace: number, size: number){
+        // 正半空间和负半空间都足够,即表示可以从 topLeft/topRight 变成 top
+        // Both positive and negative half-spaces are sufficient, which means you can change from topLeft/topRight to top
+        return posSpace >= size || negSpace >= size;
+    }
+
+    getReverse(viewOverFlow: boolean, containerOverFlow: boolean, shouldReverseView: boolean, shouldReverseContainer: boolean) {
+        /**
+         * 基于视口和容器一起判断,以下几种情况允许从原方向转到反方向,以判断是否应该由top->bottom为例子
+         *
+         * 1. 视口上下空间不足 且 容器上空间❌下空间✅
+         * 2. 视口上空间❌下空间✅ 且 容器上下空间不足
+         * 3. 视口上空间❌下空间✅ 且 容器上空间❌下空间✅
+         * 
+         * Based on the judgment of the viewport and the container, the following situations are allowed to turn from the original direction to the opposite direction
+         * to judge whether it should be top->bottom as an example
+         * 1. There is insufficient space above and below the viewport and the space above the container ❌ the space below ✅
+         * 2. The space above the viewport ❌ the space below ✅ and the space above and below the container is insufficient
+         * 3. Viewport upper space ❌ lower space✅ and container upper space ❌ lower space✅
+         */
+        return (viewOverFlow && shouldReverseContainer) || (shouldReverseView && containerOverFlow) || (shouldReverseView && shouldReverseContainer);
     }
 
     // place the dom correctly
     adjustPosIfNeed(position: Position | string, style: Record<string, any>, triggerRect: DOMRect, wrapperRect: DOMRect, containerRect: PopupContainerDOMRect) {
         const { innerWidth, innerHeight } = window;
-        const { spacing } = this.getProps();
+        const { spacing, margin } = this.getProps();
+
+        const marginLeft = typeof margin === 'number' ? margin : margin.marginLeft;
+        const marginTop = typeof margin === 'number' ? margin : margin.marginTop;
+        const marginRight = typeof margin === 'number' ? margin : margin.marginRight;
+        const marginBottom = typeof margin === 'number' ? margin : margin.marginBottom;
+
+        let isHeightOverFlow = false;
+        let isWidthOverFlow = false;
 
         if (wrapperRect.width > 0 && wrapperRect.height > 0) {
             // let clientLeft = left + translateX * wrapperRect.width - containerRect.scrollLeft;
@@ -638,144 +752,274 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
 
             // The wrapperR ect.top|bottom equivalent cannot be directly used here for comparison, which is easy to cause jitter
 
-            const shouldReverseTop = clientTop < wrapperRect.height + spacing && restClientBottom > wrapperRect.height + spacing;
-            const shouldReverseLeft = clientLeft < wrapperRect.width + spacing && restClientRight > wrapperRect.width + spacing;
-            const shouldReverseBottom = restClientBottom < wrapperRect.height + spacing && clientTop > wrapperRect.height + spacing;
-            const shouldReverseRight = restClientRight < wrapperRect.width + spacing && clientLeft > wrapperRect.width + spacing;
+            // 基于视口的微调判断
+            // Fine-tuning judgment based on viewport
+            const shouldViewReverseTop = clientTop - marginTop < wrapperRect.height + spacing && restClientBottom - marginBottom > wrapperRect.height + spacing;
+            const shouldViewReverseLeft = clientLeft - marginLeft < wrapperRect.width + spacing && restClientRight - marginRight > wrapperRect.width + spacing;
+            const shouldViewReverseBottom = restClientBottom - marginBottom < wrapperRect.height + spacing && clientTop - marginTop > wrapperRect.height + spacing;
+            const shouldViewReverseRight = restClientRight - marginRight < wrapperRect.width + spacing && clientLeft - marginLeft > wrapperRect.width + spacing;
+            const shouldViewReverseTopOver = restClientTop - marginBottom< wrapperRect.height + spacing && clientBottom - marginTop> wrapperRect.height + spacing;
+            const shouldViewReverseBottomOver = clientBottom - marginTop < wrapperRect.height + spacing && restClientTop - marginBottom > wrapperRect.height + spacing;
+
+            const shouldViewReverseTopSide = restClientTop < wrapperRect.height && clientBottom > wrapperRect.height;
+            const shouldViewReverseBottomSide = clientBottom < wrapperRect.height && restClientTop > wrapperRect.height;
+            const shouldViewReverseLeftSide = restClientLeft < wrapperRect.width && clientRight > wrapperRect.width;
+            const shouldViewReverseRightSide = clientRight < wrapperRect.width && restClientLeft > wrapperRect.width;
+
             const shouldReverseTopOver = restClientTop < wrapperRect.height + spacing && clientBottom > wrapperRect.height + spacing;
             const shouldReverseBottomOver = clientBottom < wrapperRect.height + spacing && restClientTop > wrapperRect.height + spacing;
 
-            const shouldReverseTopSide = restClientTop < wrapperRect.height && clientBottom > wrapperRect.height;
-            const shouldReverseBottomSide = clientBottom < wrapperRect.height && restClientTop > wrapperRect.height;
-            const shouldReverseLeftSide = restClientLeft < wrapperRect.width && clientRight > wrapperRect.width;
-            const shouldReverseRightSide = clientRight < wrapperRect.width && restClientLeft > wrapperRect.width;
-
             const shouldReverseLeftOver = restClientLeft < wrapperRect.width && clientRight > wrapperRect.width;
             const shouldReverseRightOver = clientRight < wrapperRect.width && restClientLeft > wrapperRect.width;
 
+            // 基于容器的微调判断
+            // Fine-tuning judgment based on container
+            const clientTopInContainer = clientTop - containerRect.top;
+            const clientLeftInContainer = clientLeft - containerRect.left;
+            const clientBottomInContainer = clientTopInContainer + triggerRect.height;
+            const clientRightInContainer = clientLeftInContainer + triggerRect.width;
+
+            const restClientBottomInContainer = containerRect.bottom - clientBottom;
+            const restClientRightInContainer = containerRect.right - clientRight;
+            const restClientTopInContainer = restClientBottomInContainer + triggerRect.height;
+            const restClientLeftInContainer = restClientRightInContainer + triggerRect.width;
+
+            // 当原空间不足,反向空间足够时,可以反向。
+            // When the original space is insufficient and the reverse space is sufficient, the reverse can be performed.
+            const shouldContainerReverseTop = this.isReverse(clientTopInContainer - marginTop, restClientBottomInContainer - marginBottom, wrapperRect.height + spacing);
+            const shouldContainerReverseLeft = this.isReverse(clientLeftInContainer - marginLeft, restClientRightInContainer - marginRight, wrapperRect.width + spacing);
+            const shouldContainerReverseBottom = this.isReverse(restClientBottomInContainer - marginBottom, clientTopInContainer - marginTop, wrapperRect.height + spacing);
+            const shouldContainerReverseRight = this.isReverse(restClientRightInContainer - marginRight, clientLeftInContainer - marginLeft, wrapperRect.width + spacing);
+            const shouldContainerReverseTopOver = this.isReverse(restClientTopInContainer - marginBottom, clientBottomInContainer - marginTop, wrapperRect.height + spacing);
+            const shouldContainerReverseBottomOver = this.isReverse(clientBottomInContainer - marginTop, restClientTopInContainer - marginBottom, wrapperRect.height + spacing);
+
+            const shouldContainerReverseTopSide = this.isReverse(restClientTopInContainer, clientBottomInContainer, wrapperRect.height);
+            const shouldContainerReverseBottomSide = this.isReverse(clientBottomInContainer, restClientTopInContainer, wrapperRect.height);
+            const shouldContainerReverseLeftSide = this.isReverse(restClientLeftInContainer, clientRightInContainer, wrapperRect.width);
+            const shouldContainerReverseRightSide = this.isReverse(clientRightInContainer, restClientLeftInContainer, wrapperRect.width);
+
+            const halfHeight = triggerRect.height / 2;
+            const halfWidth = triggerRect.width / 2;
+
+            // 视口, 原空间与反向空间是否都不足判断
+            // Viewport, whether the original space and the reverse space are insufficient to judge
+            const isViewYOverFlow = this.isOverFlow(clientTop - marginTop, restClientBottom - marginBottom, wrapperRect.height + spacing);
+            const isViewXOverFlow = this.isOverFlow(clientLeft - marginLeft, restClientRight - marginRight, wrapperRect.width + spacing);
+            const isViewYOverFlowSide = this.isOverFlow(clientBottom - marginTop, restClientTop - marginBottom, wrapperRect.height + spacing);
+            const isViewXOverFlowSide = this.isOverFlow(clientRight - marginLeft, restClientLeft - marginRight, wrapperRect.width + spacing);
+            const isViewYOverFlowSideHalf = this.isHalfOverFlow(clientBottom - halfHeight, restClientTop - halfHeight, wrapperRect.height / 2);
+            const isViewXOverFlowSideHalf = this.isHalfOverFlow(clientRight - halfWidth, restClientLeft - halfWidth, wrapperRect.width / 2);
+            const isViewYEnoughSideHalf = this.isHalfAllEnough(clientBottom - halfHeight, restClientTop - halfHeight, wrapperRect.height / 2);
+            const isViewXEnoughSideHalf = this.isHalfAllEnough(clientRight - halfWidth, restClientLeft - halfWidth, wrapperRect.width / 2);
+
+            // 容器, 原空间与反向空间是否都不足判断
+            // container, whether the original space and the reverse space are insufficient to judge
+            const isContainerYOverFlow = this.isOverFlow(clientTopInContainer - marginTop, restClientBottomInContainer - marginBottom, wrapperRect.height + spacing);
+            const isContainerXOverFlow = this.isOverFlow(clientLeftInContainer - marginLeft, restClientRightInContainer - marginRight, wrapperRect.width + spacing);
+            const isContainerYOverFlowSide = this.isOverFlow(clientBottomInContainer - marginTop, restClientTopInContainer - marginBottom, wrapperRect.height + spacing);
+            const isContainerXOverFlowSide = this.isOverFlow(clientRightInContainer - marginLeft, restClientLeftInContainer - marginRight, wrapperRect.width + spacing);
+            const isContainerYOverFlowSideHalf = this.isHalfOverFlow(clientBottomInContainer - halfHeight, restClientTopInContainer - halfHeight, wrapperRect.height / 2);
+            const isContainerXOverFlowSideHalf = this.isHalfOverFlow(clientRightInContainer - halfWidth, restClientLeftInContainer - halfWidth, wrapperRect.width / 2);
+            const isContainerYEnoughSideHalf = this.isHalfAllEnough(clientBottomInContainer - halfHeight, restClientTopInContainer - halfHeight, wrapperRect.height / 2);
+            const isContainerXEnoughSideHalf = this.isHalfAllEnough(clientRightInContainer - halfWidth, restClientLeftInContainer - halfWidth, wrapperRect.width / 2);
+
+            // 综合 viewport + container 判断微调,即视口 + 容器都放置不行时才能考虑位置调整
+            // Comprehensive viewport + container judgment fine-tuning, that is, the position adjustment can only be considered when the viewport + container cannot be placed.
+            const shouldReverseTop = this.getReverse(isViewYOverFlow, isContainerYOverFlow, shouldViewReverseTop, shouldContainerReverseTop);
+            const shouldReverseLeft = this.getReverse(isViewXOverFlow, isContainerXOverFlow, shouldViewReverseLeft, shouldContainerReverseLeft);
+            const shouldReverseBottom = this.getReverse(isViewYOverFlow, isContainerYOverFlow, shouldViewReverseBottom, shouldContainerReverseBottom);
+            const shouldReverseRight = this.getReverse(isViewXOverFlow, isContainerXOverFlow, shouldViewReverseRight, shouldContainerReverseRight);
+
+            // const shouldReverseTopOver = this.getReverse(isViewYOverFlowSide, isContainerYOverFlowSide, shouldViewReverseTopOver, shouldContainerReverseTopOver);
+            // const shouldReverseBottomOver = this.getReverse(isViewYOverFlowSide, isContainerYOverFlowSide, shouldViewReverseBottomOver, shouldContainerReverseBottomOver);
+
+            const shouldReverseTopSide = this.getReverse(isViewYOverFlowSide, isContainerYOverFlowSide, shouldViewReverseTopSide, shouldContainerReverseTopSide);
+            const shouldReverseBottomSide = this.getReverse(isViewYOverFlowSide, isContainerYOverFlowSide, shouldViewReverseBottomSide, shouldContainerReverseBottomSide);
+            const shouldReverseLeftSide = this.getReverse(isViewXOverFlowSide, isContainerXOverFlowSide, shouldViewReverseLeftSide, shouldContainerReverseLeftSide);
+            const shouldReverseRightSide = this.getReverse(isViewXOverFlowSide, isContainerXOverFlowSide, shouldViewReverseRightSide, shouldContainerReverseRightSide);
+
+            const isYOverFlowSideHalf = isViewYOverFlowSideHalf && isContainerYOverFlowSideHalf;
+            const isXOverFlowSideHalf = isViewXOverFlowSideHalf && isContainerXOverFlowSideHalf;
+
             switch (position) {
                 case 'top':
                     if (shouldReverseTop) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true);
+                    }
+                    if (isXOverFlowSideHalf && (shouldReverseLeftSide || shouldReverseRightSide)) {
+                        position = this._adjustPos(position, true, 'expand', shouldReverseLeftSide ? 'Right' : 'Left');
                     }
                     break;
                 case 'topLeft':
                     if (shouldReverseTop) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true);
                     }
                     if (shouldReverseLeftSide && widthIsBigger) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position, true);
+                    }
+                    if (isWidthOverFlow && (isViewXEnoughSideHalf || isContainerXEnoughSideHalf)) {
+                        position = this._adjustPos(position, true, 'reduce');
                     }
                     break;
                 case 'topRight':
                     if (shouldReverseTop) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true);
                     }
                     if (shouldReverseRightSide && widthIsBigger) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position);
+                    }
+                    if (isWidthOverFlow && (isViewXEnoughSideHalf || isContainerXEnoughSideHalf)) {
+                        position = this._adjustPos(position, true, 'reduce');
                     }
                     break;
                 case 'left':
                     if (shouldReverseLeft) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position);
+                    }
+                    if (isYOverFlowSideHalf && (shouldReverseTopSide || shouldReverseBottomSide)) {
+                        position = this._adjustPos(position, false, 'expand', shouldReverseTopSide ? 'Bottom' : 'Top');
                     }
                     break;
                 case 'leftTop':
                     if (shouldReverseLeft) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position);
                     }
                     if (shouldReverseTopSide && heightIsBigger) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true); 
+                    }
+                    if (isHeightOverFlow && (isViewYEnoughSideHalf || isContainerYEnoughSideHalf)) {
+                        position = this._adjustPos(position, false, 'reduce');
                     }
                     break;
                 case 'leftBottom':
                     if (shouldReverseLeft) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position);
                     }
                     if (shouldReverseBottomSide && heightIsBigger) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true);
+                    }
+                    if (isHeightOverFlow && (isViewYEnoughSideHalf || isContainerYEnoughSideHalf)) {
+                        position = this._adjustPos(position, false, 'reduce');
                     }
                     break;
                 case 'bottom':
                     if (shouldReverseBottom) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true);
+                    }
+                    if (isXOverFlowSideHalf && (shouldReverseLeftSide || shouldReverseRightSide)) {
+                        position = this._adjustPos(position, true, 'expand', shouldReverseLeftSide ? 'Right' : 'Left');
                     }
                     break;
                 case 'bottomLeft':
                     if (shouldReverseBottom) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true);
                     }
                     if (shouldReverseLeftSide && widthIsBigger) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position);
+                    }
+                    if (isWidthOverFlow && (isViewXEnoughSideHalf || isContainerXEnoughSideHalf)) {
+                        position = this._adjustPos(position, true, 'reduce');
                     }
                     break;
                 case 'bottomRight':
                     if (shouldReverseBottom) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true);
                     }
                     if (shouldReverseRightSide && widthIsBigger) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position);
+                    }
+                    if (isWidthOverFlow && (isViewXEnoughSideHalf || isContainerXEnoughSideHalf)) {
+                        position = this._adjustPos(position, true, 'reduce');
                     }
                     break;
                 case 'right':
                     if (shouldReverseRight) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position);
+                    }
+                    if (isYOverFlowSideHalf && (shouldReverseTopSide || shouldReverseBottomSide)) {
+                        position = this._adjustPos(position, false, 'expand', shouldReverseTopSide ? 'Bottom' : 'Top');
                     }
                     break;
                 case 'rightTop':
                     if (shouldReverseRight) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position);
                     }
                     if (shouldReverseTopSide && heightIsBigger) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true);
+                    }
+                    if (isHeightOverFlow && (isViewYEnoughSideHalf || isContainerYEnoughSideHalf)) {
+                        position = this._adjustPos(position, false, 'reduce');
                     }
                     break;
                 case 'rightBottom':
                     if (shouldReverseRight) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position);
                     }
                     if (shouldReverseBottomSide && heightIsBigger) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true);
+                    }
+                    if (isHeightOverFlow && (isViewYEnoughSideHalf || isContainerYEnoughSideHalf)) {
+                        position = this._adjustPos(position, false, 'reduce');
                     }
                     break;
                 case 'leftTopOver':
                     if (shouldReverseTopOver) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true);
                     }
                     if (shouldReverseLeftOver) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position);
                     }
                     break;
                 case 'leftBottomOver':
                     if (shouldReverseBottomOver) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true);
                     }
                     if (shouldReverseLeftOver) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position);
                     }
                     break;
                 case 'rightTopOver':
                     if (shouldReverseTopOver) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true);
                     }
                     if (shouldReverseRightOver) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position);
                     }
                     break;
                 case 'rightBottomOver':
                     if (shouldReverseBottomOver) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true);
                     }
                     if (shouldReverseRightOver) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position);
                     }
                     break;
                 default:
                     break;
             }
+
+            // 判断溢出 Judgment overflow
+            // 上下方向 top and bottom
+            if (this.isTB(position)){
+                isHeightOverFlow = isViewYOverFlow && isContainerYOverFlow;
+                if (position === 'top' || position === 'bottom') {
+                    isWidthOverFlow = isViewXOverFlowSideHalf && isContainerXOverFlowSideHalf;
+                } else {
+                    isWidthOverFlow = isViewXOverFlowSide && isContainerXOverFlowSide;
+                }
+            }
+            // 左右方向 left and right
+            if (this.isLR(position)){
+                isWidthOverFlow = isViewXOverFlow && isContainerXOverFlow;
+                if (position === 'left' || position === 'right') {
+                    isHeightOverFlow = isViewYOverFlowSideHalf && isContainerYOverFlowSideHalf;
+                } else {
+                    isHeightOverFlow = isViewYOverFlowSide && isContainerYOverFlowSide;
+                }
+            }
         }
 
-        return position;
+        return { position, isHeightOverFlow, isWidthOverFlow };
     }
 
     delayHide = () => {

+ 2 - 2
packages/semi-foundation/transfer/animation.scss

@@ -1,4 +1,4 @@
-$transition_duration_transfer_item-bg: var(--semi-transition_duration-faster);//穿梭框条目-背景色-动画持续时间
+$transition_duration_transfer_item-bg: var(--semi-transition_duration-none);//穿梭框条目-背景色-动画持续时间
 $transition_function_transfer_item-bg: var(--semi-transition_function-easeIn);//穿梭框条目-背景色-过渡曲线
-$transition_delay_transfer_item-bg: var(--semi-transition_delay-fastest);//穿梭框条目-背景色-延迟时间
+$transition_delay_transfer_item-bg: var(--semi-transition_delay-none);//穿梭框条目-背景色-延迟时间
 

+ 3 - 3
packages/semi-foundation/transfer/foundation.ts

@@ -61,13 +61,13 @@ export default class TransferFoundation<P = Record<string, any>, S = Record<stri
         return path.map((p: any) => p.label).join(' > ');
     }
 
-    handleInputChange(inputVal: string) {
+    handleInputChange(inputVal: string, notify: boolean) {
         const { data } = this.getStates();
         const { filter, type } = this.getProps();
         if (type === strings.TYPE_TREE_TO_LIST) {
             const searchResult = new Set(data.map((item: BasicResolvedDataItem) => item.key)) as Set<number | string>;
             this._adapter.searchTree(inputVal);
-            this._adapter.notifySearch(inputVal);
+            notify && this._adapter.notifySearch(inputVal);
             this._adapter.updateInput(inputVal);
             this._adapter.updateSearchResult(searchResult);
             return;
@@ -77,7 +77,7 @@ export default class TransferFoundation<P = Record<string, any>, S = Record<stri
             (item: BasicResolvedDataItem) => typeof item.label === 'string' && item.label.includes(inputVal);
         const searchData = data.filter(filterFunc);
         const searchResult = new Set(searchData.map((item: BasicResolvedDataItem) => item.key)) as Set<number | string>;
-        this._adapter.notifySearch(inputVal);
+        notify && this._adapter.notifySearch(inputVal);
         this._adapter.updateInput(inputVal);
         this._adapter.updateSearchResult(searchResult);
     }

+ 2 - 2
packages/semi-foundation/tree/animation.scss

@@ -1,6 +1,6 @@
-$transition_duration-tree_option-bg: var(--semi-transition_duration-faster);//树选项-背景色-动画持续时间
+$transition_duration-tree_option-bg: var(--semi-transition_duration-none);//树选项-背景色-动画持续时间
 $transition_function-tree_option-bg: var(--semi-transition_function-easeIn);//树选项-背景色-过渡曲线
-$transition_delay-tree_option-bg: var(--semi-transition_delay-fastest);//树选项-背景色-延迟时间
+$transition_delay-tree_option-bg: var(--semi-transition_delay-none);//树选项-背景色-延迟时间
 
 //transform token
 

+ 2 - 2
packages/semi-icons/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-icons",
-  "version": "2.22.3",
+  "version": "2.23.0",
   "description": "semi icons",
   "keywords": [
     "semi",
@@ -35,7 +35,7 @@
   "devDependencies": {
     "@babel/preset-env": "^7.15.8",
     "@babel/preset-react": "^7.14.5",
-    "@douyinfe/semi-webpack-plugin": "2.22.3",
+    "@douyinfe/semi-webpack-plugin": "2.23.0",
     "babel-loader": "^8.2.2",
     "css-loader": "4.3.0",
     "del": "^6.0.0",

+ 1 - 1
packages/semi-illustrations/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-illustrations",
-  "version": "2.22.3",
+  "version": "2.23.0",
   "description": "semi illustrations",
   "keywords": [
     "semi",

+ 2 - 2
packages/semi-next/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-next",
-    "version": "2.22.3",
+    "version": "2.23.0",
     "description": "Plugin that support Semi Design in Next.js",
     "author": "伍浩威 <[email protected]>",
     "homepage": "",
@@ -23,7 +23,7 @@
         "typescript": "^4"
     },
     "dependencies": {
-        "@douyinfe/semi-webpack-plugin": "2.22.3"
+        "@douyinfe/semi-webpack-plugin": "2.23.0"
     },
     "gitHead": "eb34a4f25f002bb4cbcfa51f3df93bed868c831a"
 }

+ 1 - 1
packages/semi-scss-compile/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-scss-compile",
-  "version": "2.22.3",
+  "version": "2.23.0",
   "description": "compile semi scss to css",
   "author": "[email protected]",
   "license": "MIT",

+ 1 - 1
packages/semi-theme-default/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-theme-default",
-    "version": "2.22.3",
+    "version": "2.23.0",
     "description": "semi-theme-default",
     "keywords": [
         "semi-theme",

+ 1 - 0
packages/semi-theme-default/scss/animation.scss

@@ -8,6 +8,7 @@ body{
   --semi-transition_duration-fast:0ms;
   --semi-transition_duration-faster:0ms;
   --semi-transition_duration-fastest:0ms;
+  --semi-transition_duration-none:0ms;
 
   --semi-transition_function-linear:linear;
   --semi-transition_function-ease:ease;

+ 0 - 1
packages/semi-ui/datePicker/yearAndMonth.tsx

@@ -170,7 +170,6 @@ class YearAndMonth extends BaseComponent<YearAndMonthProps, YearAndMonthState> {
     renderColMonth() {
         const { months, currentMonth, currentYear } = this.state;
         const { locale, localeCode, monthCycled, disabledDate, yearAndMonthOpts } = this.props;
-        console.log('yearAndMonthOpts', yearAndMonthOpts);
         let transform = (val: string) => val;
         const currentDate = setYear(Date.now(), currentYear);
         if (localeCode === 'zh-CN' || localeCode === 'zh-TW') {

+ 1 - 7
packages/semi-ui/dropdown/dropdownMenu.tsx

@@ -19,12 +19,10 @@ class DropdownMenu extends BaseComponent<DropdownMenuProps> {
 
     static contextType = DropdownContext;
 
-    menuRef: React.RefObject<HTMLUListElement>
 
     constructor(props: DropdownMenuProps) {
         super(props);
 
-        this.menuRef = React.createRef();
         this.foundation = new Foundation(this.adapter);
     }
 
@@ -34,14 +32,10 @@ class DropdownMenu extends BaseComponent<DropdownMenuProps> {
         };
     }
 
-    componentDidMount() {
-        this.foundation.autoFocus(this.menuRef.current);
-    }
-
     render() {
         const { children, className, style, ...rest } = this.props;
         return (
-            <ul role="menu" aria-orientation="vertical" ref={this.menuRef} {...rest} className={classnames(`${prefixCls}-menu`, className)} style={style} onKeyDown={e => this.foundation.onMenuKeydown(e)}>
+            <ul role="menu" aria-orientation="vertical" {...rest} className={classnames(`${prefixCls}-menu`, className)} style={style} onKeyDown={e => this.foundation.onMenuKeydown(e)}>
                 {children}
             </ul>
         );

+ 5 - 0
packages/semi-ui/dropdown/index.tsx

@@ -111,6 +111,8 @@ class Dropdown extends BaseComponent<DropdownProps, DropdownState> {
         onEscKeyDown: noop,
     };
 
+    tooltipRef: React.RefObject<Tooltip>
+
     constructor(props: DropdownProps) {
         super(props);
 
@@ -119,6 +121,7 @@ class Dropdown extends BaseComponent<DropdownProps, DropdownState> {
         };
 
         this.foundation = new Foundation(this.adapter);
+        this.tooltipRef = React.createRef();
     }
 
     context: DropdownContextType;
@@ -128,6 +131,7 @@ class Dropdown extends BaseComponent<DropdownProps, DropdownState> {
             ...super.adapter,
             setPopVisible: (popVisible: boolean) => this.setState({ popVisible }),
             notifyVisibleChange: (visible: boolean) => this.props.onVisibleChange(visible),
+            getPopupId: () => this.tooltipRef.current.getPopupId()
         };
     }
 
@@ -237,6 +241,7 @@ class Dropdown extends BaseComponent<DropdownProps, DropdownState> {
                 onVisibleChange={this.handleVisibleChange}
                 showArrow={false}
                 returnFocusOnClose={true}
+                ref={this.tooltipRef}
                 {...attr}
             >
                 {React.isValidElement(children) ?

+ 6 - 2
packages/semi-ui/form/_story/FormApi/setValuesDemo.jsx

@@ -39,7 +39,7 @@ class SetValuesDemo extends React.Component {
     render() {
         const { flag } = this.state;
         return (
-            <Form getFormApi={this.getFormApi} onChange={v => console.log(v)} onSubmit={v => console.log(v)}>
+            <Form getFormApi={this.getFormApi} onChange={v => console.log('onChange', v)} onSubmit={v => console.log('onSubmit', v)}>
                 {({ formState }) => (
                     <>
                         <Input field="name" initValue=""></Input>
@@ -90,7 +90,11 @@ class SetValuesWithArrayField extends React.Component {
     render() {
         const { flag } = this.state;
         return (
-            <Form getFormApi={this.getFormApi} onValueChange={v => console.log(v)} onSubmit={v => console.log(v)}>
+            <Form
+                getFormApi={this.getFormApi}
+                onValueChange={(values, changedValue) => console.log('onValueChange', { values, changedValue })}
+                onSubmit={v => console.log(v)}
+            >
                 {({ formState }) => (
                     <>
                         <ArrayField field='effects' initValue={[]}>

+ 8 - 4
packages/semi-ui/form/_story/FormSubmit/index.tsx

@@ -17,7 +17,10 @@ const App = () => {
     const { Option } = Form.Select;
 
     return (
-        <Form onSubmit={() => { console.log('submit');} }>
+        <Form
+            onSubmit={() => console.log('submit')}
+            onSubmitFail={(errors, values) => console.log(errors, values)}
+        >
             <Form.Select field="Role" label='角色' style={{ width: 176 }}>
                 <Option value="admin">管理员</Option>
                 <Option value="user">普通用户</Option>
@@ -30,13 +33,14 @@ const App = () => {
             }} />
             <Form.Input
                 field='Password'
-                label={{ 
+                label={{
                     text: '密码',
-                    extra: <Tooltip content='详情'><IconHelpCircle style={{ color: '--semi-color-text-1' }}/></Tooltip> 
+                    extra: <Tooltip content='详情'><IconHelpCircle style={{ color: '--semi-color-text-1' }} /></Tooltip>
                 }}
+                rules={[{ message: '密码长度至少为6位', validator: (_, value) => value?.length >= 6 }]}
                 style={{ width: 176 }}
             />
-            <Button>提交</Button>
+            <Button htmlType="submit">提交</Button>
         </Form>
     );
 };

+ 11 - 3
packages/semi-ui/form/_story/form.stories.tsx

@@ -150,7 +150,11 @@ interface FData {
     test: boolean;
     test2: boolean;
     test3: string;
-    // [x: string]: unknown;
+    test4: {
+        event: string,
+    },
+    testK: boolean;
+    // [x: string]: any;
 }
 class Demo extends React.Component<IProps, IState> {
     constructor(props:any) {
@@ -166,8 +170,12 @@ class Demo extends React.Component<IProps, IState> {
       const { visible } = this.state;
       return (
         <>
-          <Form getFormApi={this.getFormApi}>
-
+          <Form<FData>
+            getFormApi={this.getFormApi}
+            onSubmit={values => console.log(values.test2)}
+            onChange={formState => formState.values.test}
+            validateFields={values => ({ test4: 'test4 empty', test2: '' }) }
+        >
           </Form>
         </>
       );

+ 7 - 7
packages/semi-ui/form/baseForm.tsx

@@ -48,7 +48,7 @@ const prefix = cssClasses.PREFIX;
 interface BaseFormState {
     formId: string
 }
-class Form extends BaseComponent<BaseFormProps, BaseFormState> {
+class Form<Values extends Record<string, any> = any> extends BaseComponent<BaseFormProps<Values>, BaseFormState> {
     static propTypes = {
         'aria-label': PropTypes.string,
         onSubmit: PropTypes.func,
@@ -117,9 +117,9 @@ class Form extends BaseComponent<BaseFormProps, BaseFormState> {
     static Label = Label;
     static Section = Section;
 
-    formApi: FormApi;
+    formApi: FormApi<Values>;
 
-    constructor(props: BaseFormProps) {
+    constructor(props: BaseFormProps<Values>) {
         super(props);
         this.state = {
             formId: '',
@@ -153,14 +153,14 @@ class Form extends BaseComponent<BaseFormProps, BaseFormState> {
         this.foundation.destroy();
     }
 
-    get adapter(): BaseFormAdapter<BaseFormProps, BaseFormState> {
+    get adapter(): BaseFormAdapter<BaseFormProps<Values>, BaseFormState, Values> {
         return {
             ...super.adapter,
             cloneDeep,
-            notifySubmit: (values: any) => {
+            notifySubmit: (values: Values) => {
                 this.props.onSubmit(values);
             },
-            notifySubmitFail: (errors: ErrorMsg, values: any) => {
+            notifySubmitFail: (errors, values) => {
                 this.props.onSubmitFail(errors, values);
             },
             forceUpdate: (callback?: () => void) => {
@@ -169,7 +169,7 @@ class Form extends BaseComponent<BaseFormProps, BaseFormState> {
             notifyChange: (formState: FormState) => {
                 this.props.onChange(formState);
             },
-            notifyValueChange: (values: any, changedValues: any) => {
+            notifyValueChange: (values: Values, changedValues: Partial<Values>) => {
                 this.props.onValueChange(values, changedValues);
             },
             notifyReset: () => {

+ 2 - 1
packages/semi-ui/form/errorMessage.tsx

@@ -4,10 +4,11 @@ import classNames from 'classnames';
 import PropTypes from 'prop-types';
 import { cssClasses } from '@douyinfe/semi-foundation/form/constants';
 import { IconAlertTriangle, IconAlertCircle } from '@douyinfe/semi-icons';
+import type { BasicFieldError } from '@douyinfe/semi-foundation/form/interface';
 
 const prefix = cssClasses.PREFIX;
 
-export type ReactFieldError = Array<any> | React.ReactNode;
+export type ReactFieldError = BasicFieldError | React.ReactNode;
 
 export interface ErrorMessageProps {
     error?: ReactFieldError;

+ 3 - 2
packages/semi-ui/form/hooks/useFormState.tsx

@@ -1,8 +1,9 @@
+import { FormState } from '@douyinfe/semi-foundation/form/interface';
 import React, { useContext } from 'react';
 import { FormStateContext } from '../context';
 
-function useFormState() {
-    const formState = useContext(FormStateContext);
+function useFormState<T extends Record<string, any> = any>() {
+    const formState = useContext<FormState<T>>(FormStateContext);
     return formState;
 }
 

+ 16 - 18
packages/semi-ui/form/interface.ts

@@ -3,19 +3,19 @@
 import * as React from 'react';
 import { Subtract } from 'utility-types';
 import type { RuleItem } from 'async-validator';
-import { Options as scrollIntoViewOptions } from 'scroll-into-view-if-needed';
+import type { Options as scrollIntoViewOptions } from 'scroll-into-view-if-needed';
 
-import { BaseFormApi as FormApi, FormState, WithFieldOption } from '@douyinfe/semi-foundation/form/interface';
-import { SelectProps } from '../select/index';
+import type { BaseFormApi as FormApi, FormState, WithFieldOption, AllErrors } from '@douyinfe/semi-foundation/form/interface';
+import type { SelectProps } from '../select/index';
 import Option from '../select/option';
 import OptGroup from '../select/optionGroup';
-import { CheckboxProps } from '../checkbox/index';
-import { RadioProps } from '../radio/index';
+import type { CheckboxProps } from '../checkbox/index';
+import type { RadioProps } from '../radio/index';
 
 import { ErrorMessageProps, ReactFieldError as FieldError } from './errorMessage';
-import { LabelProps } from './label';
+import type { LabelProps } from './label';
 
-export { FormState, FormApi, WithFieldOption, RuleItem };
+export type { FormState, FormApi, WithFieldOption, RuleItem };
 
 export type CommonFieldProps = {
     /** Field is required (except Form. Checkbox within the Group, Form. Radio) */
@@ -98,23 +98,21 @@ export interface FormFCChild<K extends Record<string, any> = any> {
     formApi: FormApi<K>
 }
 
-interface setValuesConfig {
-    isOverride: boolean
-}
 
-export interface BaseFormProps extends Omit<React.FormHTMLAttributes<HTMLFormElement>, 'children' | 'onChange'> {
+
+export interface BaseFormProps <Values extends Record<string, any> = any> extends Omit<React.FormHTMLAttributes<HTMLFormElement>, 'children' | 'onChange' | 'onSubmit' | 'onReset'> {
     'aria-label'?: React.AriaAttributes['aria-label'];
-    onSubmit?: (values: Record<string, any>) => void;
-    onSubmitFail?: (errors: Record<string, FieldError>, values: any) => void;
+    onSubmit?: (values: Values) => void;
+    onSubmitFail?: (errors: Record<keyof Values, FieldError>, values: Partial<Values>) => void;
     onReset?: () => void;
-    onValueChange?: (values: Record<string, any>, changedValue: Record<string, any>) => void;
-    onChange?: (formState: FormState) => void;
-    validateFields?: (values: Record<string, any>) => string | Record<string, any>;
+    onValueChange?: (values: Values, changedValue: Partial<Values>) => void;
+    onChange?: (formState: FormState<Values>) => void;
+    validateFields?: (values: Values) => string | Partial<AllErrors<Values>>;
     /** Use this if you want to populate the form with initial values. */
-    initValues?: Record<string, any>;
+    initValues?: Values;
     id?: string;
     /** getFormApi will be call once when Form mounted, u can save formApi reference in your component  */
-    getFormApi?: (formApi: FormApi) => void;
+    getFormApi?: (formApi: FormApi<Values>) => void;
     style?: React.CSSProperties;
     className?: string;
     layout?: 'horizontal' | 'vertical';

+ 6 - 6
packages/semi-ui/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-ui",
-    "version": "2.22.3",
+    "version": "2.23.0",
     "description": "",
     "main": "lib/cjs/index.js",
     "module": "lib/es/index.js",
@@ -18,11 +18,11 @@
     ],
     "dependencies": {
         "@douyinfe/semi-animation": "2.12.0",
-        "@douyinfe/semi-animation-react": "2.22.3",
-        "@douyinfe/semi-foundation": "2.22.3",
-        "@douyinfe/semi-icons": "2.22.3",
+        "@douyinfe/semi-animation-react": "2.23.0",
+        "@douyinfe/semi-foundation": "2.23.0",
+        "@douyinfe/semi-icons": "2.23.0",
         "@douyinfe/semi-illustrations": "2.15.0",
-        "@douyinfe/semi-theme-default": "2.22.3",
+        "@douyinfe/semi-theme-default": "2.23.0",
         "async-validator": "^3.5.0",
         "classnames": "^2.2.6",
         "copy-text-to-clipboard": "^2.1.1",
@@ -75,7 +75,7 @@
         "@babel/plugin-transform-runtime": "^7.15.8",
         "@babel/preset-env": "^7.15.8",
         "@babel/preset-react": "^7.14.5",
-        "@douyinfe/semi-scss-compile": "2.22.3",
+        "@douyinfe/semi-scss-compile": "2.23.0",
         "@storybook/addon-knobs": "^6.3.1",
         "@types/lodash": "^4.14.176",
         "@types/react": ">=16.0.0",

+ 1 - 1
packages/semi-ui/select/index.tsx

@@ -612,7 +612,7 @@ class Select extends BaseComponent<SelectProps, SelectState> {
         }
 
         // Add isOptionChanged: There may be cases where the value is unchanged, but the optionList is updated. At this time, the label corresponding to the value may change, and the selected item needs to be updated
-        if (prevProps.value !== this.props.value || isOptionsChanged) {
+        if (!isEqual(this.props.value, prevProps.value) || isOptionsChanged) {
             if ('value' in this.props) {
                 this.foundation.handleValueChange(this.props.value as any);
             } else {

+ 1 - 1
packages/semi-ui/table/_story/v2/stickyHeader/index.scss

@@ -1,3 +1,3 @@
 body {
-    height: 150vh;
+    // height: 150vh;
 }

+ 23 - 8
packages/semi-ui/tagInput/_story/tagInput.stories.jsx

@@ -343,6 +343,7 @@ class CustomRender extends React.Component {
   constructor() {
     super();
     this.state = {
+      draggable: false,
       list: [
         {
           name: 'semi',
@@ -354,7 +355,7 @@ class CustomRender extends React.Component {
     };
   }
 
-  renderTagItem(node, index) {
+  renderTagItem(node, index, onClose) {
     return (
       <div
         key={index}
@@ -375,6 +376,7 @@ class CustomRender extends React.Component {
         >
           {node.email}
         </span>
+        <IconClose onClick={onClose} />
       </div>
     );
   }
@@ -392,15 +394,28 @@ class CustomRender extends React.Component {
     });
   }
 
+  onSwitchChange(value) {
+    this.setState({
+      draggable: value
+    });
+  }
+
   render() {
-    const { list } = this.state;
+    const { list, draggable } = this.state;
     return (
-      <TagInput
-        style={style}
-        value={list}
-        onChange={value => this.handleChange(value)}
-        renderTagItem={(node, index) => this.renderTagItem(node, index)}
-      />
+      <>
+        <div style={{ display: 'flex', alignItems: 'center', marginBottom: 20 }}>
+          <span>是否可拖拽:</span>
+          <Switch checked={draggable} onChange={(value) => this.onSwitchChange(value)} />
+        </div>
+        <TagInput
+          draggable={draggable}
+          style={style}
+          value={list}
+          onChange={value => this.handleChange(value)}
+          renderTagItem={(node, index, onClose) => this.renderTagItem(node, index, onClose)}
+        />
+      </>
     );
   }
 }

+ 7 - 6
packages/semi-ui/tagInput/index.tsx

@@ -68,7 +68,7 @@ export interface TagInputProps {
     insetLabel?: React.ReactNode;
     insetLabelId?: string;
     prefix?: React.ReactNode;
-    renderTagItem?: (value: string, index: number) => React.ReactNode;
+    renderTagItem?: (value: string, index: number, onClose: () => void) => React.ReactNode;
     separator?: string | string[] | null;
     showClear?: boolean;
     size?: Size;
@@ -416,11 +416,14 @@ class TagInput extends BaseComponent<TagInputProps, TagInputState> {
         const DragHandle = SortableHandle(() => <IconHandle className={`${prefixCls}-drag-handler`}></IconHandle>);
         return tagsArray.map((value, index) => {
             const elementKey = showIconHandler ? value : `${index}${value}`;
+            const onClose = () => {
+                !disabled && this.handleTagClose(index);
+            };
             if (isFunction(renderTagItem)) {
                 return showIconHandler? (<div className={itemWrapperCls} key={elementKey}>
                     <DragHandle />
-                    {renderTagItem(value, index)}
-                </div>) : renderTagItem(value, index);
+                    {renderTagItem(value, index, onClose)}
+                </div>) : renderTagItem(value, index, onClose);
             } else {
                 return (
                     <Tag
@@ -428,9 +431,7 @@ class TagInput extends BaseComponent<TagInputProps, TagInputState> {
                         color="white"
                         size={size === 'small' ? 'small' : 'large'}
                         type="light"
-                        onClose={() => {
-                            !disabled && this.handleTagClose(index);
-                        }}
+                        onClose={onClose}
                         closable={!disabled}
                         key={elementKey}
                         visible

+ 1 - 0
packages/semi-ui/tooltip/__test__/tooltip.test.js

@@ -57,6 +57,7 @@ describe(`Tooltip`, () => {
         expect(document.querySelectorAll(`.${BASE_CLASS_PREFIX}-tooltip-wrapper`).length).toBe(1);
 
         // scroll
+        await sleep(100);
         const deltaY = 50;
         const scrollContainer = demo.find(`#${scrollContainerId}`);
         const oldTop = window.getComputedStyle(tooltipOuter).top;

+ 159 - 0
packages/semi-ui/tooltip/_story/AutoAdjustOverflow/bottom2Other.jsx

@@ -0,0 +1,159 @@
+import React, { PureComponent, useState } from 'react';
+import { Tooltip, Button, Popover } from '@douyinfe/semi-ui';
+import { PopupContent, Trigger } from './common';
+
+
+const Bottom2Top = () => {
+    const [pos, setPos] = useState('top');
+    return (<div>
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    bottom: 0,
+                    left: 100
+                }}
+            >
+                pos: {pos}
+            </Trigger>
+        </Tooltip>
+    </div>);
+};
+
+const Bottom2TopLeft = () => {
+    const [pos, setPos] = useState('top');
+    return (<div>
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    bottom: 0
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+const Bottom2TopRight = () => {
+    const [pos, setPos] = useState('top');
+    return (<div
+        style={{
+        }}
+    >
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    right: 40,
+                    bottom: 0
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+const Bottom2BottomLeft = () => {
+    const [pos, setPos] = useState('top');
+    return (<div
+        style={{
+        }}
+    >
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    left: 40
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+const Bottom2BottomRight = () => {
+    const [pos, setPos] = useState('top');
+    return (<div
+        style={{
+        }}
+    >
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    right: 40
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+export { Bottom2Top, Bottom2TopLeft, Bottom2TopRight, Bottom2BottomLeft, Bottom2BottomRight };

+ 52 - 0
packages/semi-ui/tooltip/_story/AutoAdjustOverflow/common.jsx

@@ -0,0 +1,52 @@
+import React, { PureComponent, useState } from 'react';
+import { Tooltip, Button, Popover } from '@douyinfe/semi-ui';
+
+
+const commonTriggerStyle = {
+    border: '1px solid var(--semi-color-primary)',
+    justifyContent: 'center',
+    alignItems: 'center',
+    display: 'inline-flex',
+    color: 'var(--semi-color-primary)',
+};
+
+const commonContentStyle = {
+    justifyContent: 'center',
+    alignItems: 'center',
+    backgroundColor: 'rgba(var(--semi-light-blue-5), 1)',
+};
+
+
+class Trigger extends PureComponent {
+    render() {
+        const { w = 100, h = 100, style, children, ...rest } = this.props;
+        return (
+            <div
+                {...rest}
+                style={{
+                    width: w,
+                    height: h,
+                    ...commonTriggerStyle,
+                    ...style,
+                }}>
+                Trigger w:{w}
+                <br/>
+                {children}
+            </div>
+        );
+    }
+}
+
+const PopupContent = ({ w = 100, h = 40, style, ...rest }) => (
+    <div style={{
+        width: w,
+        height: h,
+        ...commonContentStyle,
+        ...style,
+    }}
+    >
+        Popup Content width {w}
+    </div>
+);
+
+export { PopupContent, Trigger };

+ 11 - 0
packages/semi-ui/tooltip/_story/AutoAdjustOverflow/index.jsx

@@ -0,0 +1,11 @@
+import { Right2Left, Right2LeftTop, Right2LeftBottom, Right2RightTop, Right2RightBottom } from './right2Other';
+import { Left2Right, Left2RightTop, Left2RightBottom, Left2LeftTop, Left2LeftBottom } from './left2Other';
+import { Top2Bottom, Top2BottomLeft, Top2BottomRight, Top2TopLeft, Top2TopRight } from './top2Other';
+import { Bottom2Top, Bottom2TopLeft, Bottom2TopRight, Bottom2BottomLeft, Bottom2BottomRight } from './bottom2Other';
+
+export {
+    Left2Right, Left2RightTop, Left2RightBottom, Left2LeftTop, Left2LeftBottom,
+    Right2Left, Right2LeftTop, Right2LeftBottom, Right2RightTop, Right2RightBottom,
+    Top2Bottom, Top2BottomLeft, Top2BottomRight, Top2TopLeft, Top2TopRight,
+    Bottom2Top, Bottom2TopLeft, Bottom2TopRight, Bottom2BottomLeft, Bottom2BottomRight
+};

+ 160 - 0
packages/semi-ui/tooltip/_story/AutoAdjustOverflow/left2Other.jsx

@@ -0,0 +1,160 @@
+import React, { PureComponent, useState } from 'react';
+import { Tooltip, Button, Popover } from '@douyinfe/semi-ui';
+import { PopupContent, Trigger } from './common';
+
+const Left2Right = () => {
+    const [pos, setPos] = useState('left');
+    return (<div>
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    top: 70
+                }}
+            >
+                pos: {pos}
+            </Trigger>
+        </Tooltip>
+    </div>);
+};
+
+const Left2RightTop = () => {
+    const [pos, setPos] = useState('left');
+    return (<div>
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                // style={{
+                //     position: 'absolute',
+                //     right: 0,
+                //     top: 0
+                // }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+const Left2RightBottom = () => {
+    const [pos, setPos] = useState('left');
+    return (<div
+        style={{
+        }}
+    >
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    // right: 0,
+                    bottom: 0
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+const Left2LeftTop = () => {
+    const [pos, setPos] = useState('left');
+    return (<div
+        style={{
+        }}
+    >
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    right: 0,
+                    top: 50
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+const Left2LeftBottom = () => {
+    const [pos, setPos] = useState('left');
+    return (<div
+        style={{
+        }}
+    >
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    right: 0,
+                    bottom: 0
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+export { Left2Right, Left2RightTop, Left2RightBottom, Left2LeftTop, Left2LeftBottom };

+ 163 - 0
packages/semi-ui/tooltip/_story/AutoAdjustOverflow/right2Other.jsx

@@ -0,0 +1,163 @@
+import React, { PureComponent, useState } from 'react';
+import { Tooltip, Button, Popover } from '@douyinfe/semi-ui';
+
+import { PopupContent, Trigger } from './common';
+
+// ❌
+const Right2Left = () => {
+    const [pos, setPos] = useState('right');
+    return (<div>
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    right: 0,
+                    bottom: 300
+                }}
+            >
+                pos: {pos}
+            </Trigger>
+        </Tooltip>
+    </div>);
+};
+
+// ✅
+const Right2LeftTop = () => {
+    const [pos, setPos] = useState('right');
+    return (<div>
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    right: 0,
+                    top: 0
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+// ❌
+const Right2LeftBottom = () => {
+    const [pos, setPos] = useState('right');
+    return (<div
+        style={{
+        }}
+    >
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    right: 0,
+                    bottom: 0
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+const Right2RightTop = () => {
+    const [pos, setPos] = useState('right');
+    return (<div
+        style={{
+        }}
+    >
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    top: 40,
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+const Right2RightBottom = () => {
+    const [pos, setPos] = useState('right');
+    return (<div
+        style={{
+        }}
+    >
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    bottom: 0
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+export { Right2Left, Right2LeftTop, Right2LeftBottom, Right2RightTop, Right2RightBottom };

+ 158 - 0
packages/semi-ui/tooltip/_story/AutoAdjustOverflow/top2Other.jsx

@@ -0,0 +1,158 @@
+import React, { PureComponent, useState } from 'react';
+import { Tooltip, Button, Popover } from '@douyinfe/semi-ui';
+import { PopupContent, Trigger } from './common';
+
+
+const Top2Bottom = () => {
+    const [pos, setPos] = useState('top');
+    return (<div>
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    left: 300,
+                }}
+            >
+                pos: {pos}
+            </Trigger>
+        </Tooltip>
+    </div>);
+};
+
+const Top2BottomLeft = () => {
+    const [pos, setPos] = useState('top');
+    return (<div>
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    left: 40,
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+const Top2BottomRight = () => {
+    const [pos, setPos] = useState('top');
+    return (<div
+        style={{
+        }}
+    >
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    right: 40,
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+const Top2TopLeft = () => {
+    const [pos, setPos] = useState('top');
+    return (<div
+        style={{
+        }}
+    >
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    bottom: 0,
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+const Top2TopRight = () => {
+    const [pos, setPos] = useState('top');
+    return (<div
+        style={{
+        }}
+    >
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    top: 224,
+                    right: 0
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+export { Top2Bottom, Top2BottomLeft, Top2BottomRight, Top2TopLeft, Top2TopRight };

+ 2 - 2
packages/semi-ui/tooltip/_story/InTable/index.jsx

@@ -15,13 +15,13 @@ export default function InTableDemo(props = {}) {
                 return (
                     <div id="dropdown-wrap" style={{ position: 'relative' }}>
                         <Dropdown
-                            getPopupContainer={getPopupContainer}
+                            // getPopupContainer={getPopupContainer}
                             position="rightTop"
                             trigger="click"
                             render={
                                 <Dropdown.Menu>
                                     <Dropdown
-                                        getPopupContainer={getPopupContainer}
+                                        // getPopupContainer={getPopupContainer}
                                         position="rightTop"
                                         render={
                                             <Dropdown.Menu>

+ 1 - 0
packages/semi-ui/tooltip/_story/story.scss

@@ -1,5 +1,6 @@
 body.sb-show-main {
     position: relative;
+    // width: 200px;
 }
 
 .demo {

+ 319 - 2
packages/semi-ui/tooltip/_story/tooltip.stories.jsx

@@ -1,4 +1,4 @@
-import React, { useState, useMemo } from 'react';
+import React, { useState, useMemo, PureComponent } from 'react';
 import Tooltip from '../index';
 import './story.scss';
 import {
@@ -14,6 +14,8 @@ import {
   Space,
   Popover,
   Input,
+  RadioGroup,
+  SideSheet
 } from '@douyinfe/semi-ui';
 
 import InTableDemo from './InTable';
@@ -24,6 +26,13 @@ import ArrowPointAtCenter from './ArrowPointAtCenter';
 import CustomContainer from './CustomContainer';
 import ContainerPosition from './ContainerPosition';
 import { IconList, IconSidebar, IconEdit } from '@douyinfe/semi-icons';
+import {  
+  Right2Left, Right2LeftTop, Right2LeftBottom, Right2RightTop, Right2RightBottom,
+  Left2Right, Left2RightTop, Left2RightBottom, Left2LeftTop, Left2LeftBottom,
+  Top2Bottom, Top2BottomLeft, Top2BottomRight, Top2TopLeft, Top2TopRight,
+  Bottom2Top, Bottom2TopLeft, Bottom2TopRight, Bottom2BottomLeft, Bottom2BottomRight,
+ } from './AutoAdjustOverflow';
+
 
 export default {
   title: 'Tooltip',
@@ -1094,4 +1103,312 @@ export const TransitionDemo = () => {
     <Button onClick={() => setKey(Math.random())}>reset Demo</Button>
   </>
   )
-}
+}
+
+export const AdjustPosIfNeedTBLR = () => {
+  
+  const content = <article>
+    Hi ByteDancer, this is a tooltip.
+    <br /> We have 2 lines.
+  </article>
+
+  const contentHigh = <article>
+    Hi ByteDancer, this is a tooltip.
+    <br /> We have 2 lines.
+    <br /> We have 2 lines.
+  </article>
+
+  return (
+    <div style={{ paddingLeft: 0, width: 800, height: '100%' }}>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={content}
+        position={'top'}
+      >
+        <Tag style={{ position: 'absolute', left: 20, top: 40 }}>top to bottomLeft</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={content}
+        position={'top'}
+      >
+        <Tag style={{ position: 'absolute', right: 20, top: 40 }}>top to bottomRight</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={content}
+        position={'top'}
+      >
+        <Tag style={{ position: 'absolute', left: 20, top: 70 }}>top to topLeft</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={content}
+        position={'top'}
+      >
+        <Tag style={{ position: 'absolute', right: 20, top: 70 }}>top to topRight</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={content}
+        position={'bottom'}
+      >
+        <Tag data-cy={'bottom'} style={{ position: 'absolute', left: 20, bottom: 70 }}>bottom to bottomLeft</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={content}
+        position={'bottom'}
+      >
+        <Tag data-cy={'bottom'} style={{ position: 'absolute', right: 20, bottom: 70 }}>bottom to bottomRight</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={content}
+        position={'bottom'}
+      >
+        <Tag data-cy={'bottom'} style={{ position: 'absolute', left: 20, bottom: 40 }}>bottom to topLeft</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={content}
+        position={'bottom'}
+      >
+        <Tag data-cy={'bottom'} style={{ position: 'absolute', right: 20, bottom: 40 }}>bottom to topRight</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={contentHigh}
+        position={'left'}
+      >
+        <Tag style={{ position: 'absolute', left: 300, top: 20 }}>left to leftTop</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={contentHigh}
+        position={'left'}
+      >
+        <Tag style={{ position: 'absolute', left: 300, bottom: 20 }}>left to leftBottom</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={contentHigh}
+        position={'left'}
+      >
+        <Tag style={{ position: 'absolute', left: 180, top: 20 }}>left to rightTop</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={contentHigh}
+        position={'left'}
+      >
+        <Tag style={{ position: 'absolute', left: 180, bottom: 20 }}>left to rightBottom</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={contentHigh}
+        position={'right'}
+      >
+        <Tag style={{ position: 'absolute', right: 300, top: 20 }}>right to rightTop</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={contentHigh}
+        position={'right'}
+      >
+        <Tag style={{ position: 'absolute', right: 300, bottom: 20 }}>right to rightBottom</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={contentHigh}
+        position={'right'}
+      >
+        <Tag style={{ position: 'absolute', right: 180, top: 20 }}>right to leftTop</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={contentHigh}
+        position={'right'}
+      >
+        <Tag style={{ position: 'absolute', right: 180, bottom: 20 }}>right to leftBottom</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={contentHigh}
+        position={'rightTop'}
+      >
+        <Tag style={{ position: 'absolute', right: 180, bottom: 50 }}>right to leftBottom</Tag>
+      </Tooltip>
+    </div>
+  );
+}
+
+export const marginDemo = () => {
+  const [visible, setVisible] = useState(false);
+  const change = () => {
+    setVisible(!visible);
+  };
+  return (
+    <>
+      <Button onClick={change}>Open SideSheet</Button>
+      <SideSheet title="滑动侧边栏" visible={visible} onCancel={change}>
+        <div style={{ height: '800px', overflow: 'scroll' }}>
+          <div
+            id='test'
+            style={{
+              height: '880px',
+              display: 'flex',
+              flexDirection: 'column-reverse',
+              position: 'relative'
+            }}
+          >
+            <Tooltip
+              getPopupContainer={() => document.querySelector('#test')}
+              content='cecece'
+              position='bottom'
+              margin={{ marginTop: 0, marginLeft: 0, marginBottom: 36, marginRight: 0 }}
+            >
+              <div style={{ marginBottom: 20 }}>
+                test
+              </div>
+            </Tooltip>
+          </div>
+        </div>
+        <footer style={{
+          position: 'sticky',
+          bottom: 0,
+          height: 36,
+          border: '1px solid pink'
+        }}>
+          i am footer
+        </footer>
+      </SideSheet>
+    </>
+  );
+};
+
+export const SmartPosAdjustDemo = () => {
+  const [pos, setPosition] = useState('top');
+  const onChange = (e) => {
+    setPosition(e.target.value);
+  };
+  return (
+    <div style={{ width: 800, height: 800 }}>
+      <Popover 
+        position={pos}
+        showArrow={true}
+        content={
+          <div style={{ minWidth: 900, height: 900, backgroundColor: 'lightblue' }}>
+            <article>
+              <p>hi semi! hi semi! hi semi!hi semi! hi semi</p>
+              <p>hi semi! hi semi! hi semi!hi semi! hi semi</p>
+              <p>hi semi! hi semi! hi semi!</p>
+            </article>
+          </div>
+        }
+      >
+        <Tag style={{ marginLeft: 450, marginTop: 550 }}>悬停此处</Tag>
+      </Popover>
+      <div style={{ marginLeft: 250, width: 300 }}>
+        <RadioGroup onChange={onChange} value={pos} aria-label="position" name="position">
+          <Radio value={'topLeft'}>TL </Radio>
+          <Radio value={'top'}>top </Radio>
+          <Radio value={'topRight'}>TR </Radio>
+          <Radio value={'bottomLeft'}>BL</Radio>
+          <Radio value={'bottom'}>Bottom</Radio>
+          <Radio value={'bottomRight'}>BR</Radio>
+          <Radio value={'leftTop'}>LT</Radio>
+          <Radio value={'left'}>Left</Radio>
+          <Radio value={'leftBottom'}>LB</Radio>
+          <Radio value={'rightTop'}>RT</Radio>
+          <Radio value={'right'}>Right</Radio>
+          <Radio value={'rightBottom'}>RB</Radio>
+        </RadioGroup>
+      </div>
+    </div>
+  )
+}
+
+
+// right -> other
+export const AutoRight2LeftDemo = () => <Right2Left />;
+AutoRight2LeftDemo.storyName = `✅ auto : right -> left`;
+
+export const AutoRight2LeftBottomDemo = () => <Right2LeftBottom />;
+AutoRight2LeftBottomDemo.storyName = `✅ auto : right -> leftBottom`;
+
+export const AutoRight2LeftTopDemo = () => <Right2LeftTop />;
+AutoRight2LeftTopDemo.storyName = `✅ auto : riht -> leftTop`;
+
+export const AutoRight2RightBottomDemo = () => <Right2RightBottom />;
+AutoRight2RightBottomDemo.storyName = `✅ auto : right -> rightBottom`;
+
+export const AutoRight2RightTopDemo = () => <Right2RightTop />;
+AutoRight2RightTopDemo.storyName = `✅ auto : riht -> rightTop`;
+
+
+// left -> other
+export const Left2RightDemo = () => <Left2Right />;
+Left2RightDemo.storyName = `✅ auto : left -> right`;
+
+export const Left2LeftBottomDemo = () => <Left2LeftBottom />;
+Left2LeftBottomDemo.storyName = `✅ auto : left -> leftBottom`;
+
+export const Left2LeftTopDemo = () => <Left2LeftTop />;
+Left2LeftTopDemo.storyName = `✅ auto : left -> leftTop`;
+
+export const Left2RightBottomDemo = () => <Left2RightBottom />;
+Left2RightBottomDemo.storyName = `✅ auto : left -> rightBottom`;
+
+export const Left2RightTopDemo = () => <Left2RightTop />;
+Left2RightTopDemo.storyName = `✅ auto : left -> rightTop`;
+
+// top -> other
+export const Top2BottomDemo = () => <Top2Bottom />;
+Top2BottomDemo.storyName = `✅ auto : top -> bottom`;
+
+export const Top2BottomLeftDemo = () => <Top2BottomLeft />;
+Top2BottomLeftDemo.storyName = `✅ auto : top -> bottomLeft`;
+
+export const Top2BottomRightDemo = () => <Top2BottomRight />;
+Top2BottomRightDemo.storyName = `✅ auto : top -> bottomRight`;
+
+export const Top2TopLeftDemo = () => <Top2TopLeft />;
+Top2TopLeftDemo.storyName = `✅ auto : top -> topLeft`;
+
+export const Top2TopRightDemo = () => <Top2TopRight />;
+Top2TopRightDemo.storyName = `✅ auto : top -> topRight`;
+
+
+// bottom -> other
+export const Bottom2TopDemo = () => <Bottom2Top />;
+Bottom2TopDemo.storyName = `✅ auto : bottom -> top`;
+
+export const Bottom2TopLeftDemo = () => <Bottom2TopLeft />;
+Bottom2TopLeftDemo.storyName = `✅ auto : bottom -> topLeft`;
+
+export const Bottom2TopRightDemo = () => <Bottom2TopRight />;
+Bottom2TopRightDemo.storyName = `✅ auto : bottom -> topRight`;
+
+export const Bottom2BottomLeftDemo = () => <Bottom2BottomLeft />;
+Bottom2BottomLeftDemo.storyName = `✅ auto : bottom -> bottomLeft`;
+
+export const Bottom2BottomRightDemo = () => <Bottom2BottomRight />;
+Bottom2BottomRightDemo.storyName = `✅ auto : bottom -> bottomRight`;

+ 9 - 2
packages/semi-ui/tooltip/index.tsx

@@ -3,7 +3,7 @@ import React, { isValidElement, cloneElement } from 'react';
 import ReactDOM from 'react-dom';
 import classNames from 'classnames';
 import PropTypes from 'prop-types';
-import { throttle, noop, get, omit, each, isEmpty, isFunction } from 'lodash';
+import { throttle, noop, get, omit, each, isEmpty, isFunction, isEqual } from 'lodash';
 
 import { BASE_CLASS_PREFIX } from '@douyinfe/semi-foundation/base/constants';
 import warning from '@douyinfe/semi-foundation/utils/warning';
@@ -58,6 +58,7 @@ export interface TooltipProps extends BaseProps {
     onVisibleChange?: (visible: boolean) => void;
     onClickOutSide?: (e: React.MouseEvent) => void;
     spacing?: number;
+    margin?: number | { marginLeft: number; marginTop: number; marginRight: number; marginBottom: number };
     showArrow?: boolean | React.ReactNode;
     zIndex?: number;
     rePosKey?: string | number;
@@ -126,6 +127,7 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
         onVisibleChange: PropTypes.func,
         onClickOutSide: PropTypes.func,
         spacing: PropTypes.number,
+        margin: PropTypes.oneOfType([PropTypes.number, PropTypes.object]),
         showArrow: PropTypes.oneOfType([PropTypes.bool, PropTypes.node]),
         zIndex: PropTypes.number,
         rePosKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
@@ -156,6 +158,7 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
         onVisibleChange: noop,
         onClickOutSide: noop,
         spacing: numbers.SPACING,
+        margin: numbers.MARGIN,
         showArrow: true,
         wrapWhenSpecial: true,
         zIndex: numbers.DEFAULT_Z_INDEX,
@@ -495,7 +498,7 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
         if (prevProps.visible !== this.props.visible) {
             this.props.visible ? this.foundation.delayShow() : this.foundation.delayHide();
         }
-        if (prevProps.rePosKey !== this.props.rePosKey) {
+        if (!isEqual(prevProps.rePosKey, this.props.rePosKey)) {
             this.rePosition();
         }
     }
@@ -649,6 +652,10 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
         return mergedEvents;
     };
 
+    getPopupId = () => {
+        return this.state.id;
+    }
+
     render() {
         const { isInsert, triggerEventSet, visible, id } = this.state;
         const { wrapWhenSpecial, role, trigger } = this.props;

+ 7 - 1
packages/semi-ui/transfer/index.tsx

@@ -298,7 +298,13 @@ class Transfer extends BaseComponent<TransferProps, TransferState> {
     }
 
     onInputChange(value: string) {
-        this.foundation.handleInputChange(value);
+        this.foundation.handleInputChange(value, true);
+    }
+
+    search(value: string) {
+        // The search method is used to provide the user with a manually triggered search
+        // Since the method is manually called by the user, setting the second parameter to false does not trigger the onSearch callback to notify the user
+        this.foundation.handleInputChange(value, false);
     }
 
     onSelectOrRemove(item: ResolvedDataItem) {

+ 1 - 1
packages/semi-webpack/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-webpack-plugin",
-    "version": "2.22.3",
+    "version": "2.23.0",
     "description": "",
     "author": "伍浩威 <[email protected]>",
     "homepage": "",

+ 10 - 7
src/sitePages/newHome/components/pro/pro.jsx

@@ -8,12 +8,15 @@ import Component from './d2cApplication.jsx';
 
 function BetaTag({ style }) {
     return (
-        <Tag style={{ 
-            color: 'var(--semi-color-white)', 
-            background: 'rgba(var(--semi-violet-4), 1)', 
-            fontWeight: 600,
-            ...style
-        }}>
+        <Tag
+            className='semi-always-dark'
+            style={{ 
+                color: 'var(--semi-color-white)', 
+                background: 'rgba(var(--semi-violet-4), 1)', 
+                fontWeight: 600,
+                ...style
+            }}
+        >
             {_t('BETA', {}, 'BETA')}
         </Tag>
     );
@@ -28,7 +31,7 @@ function Pro(props) {
     return (
         <div {...props} className={`${styles.macBookPro2}`}>
             <div className={styles.frame14294}>
-                <div className={`${styles.frame4151} semi-always-dark`}>
+                <div className={`${styles.frame4151}`}>
                     <Badge count={<BetaTag style={{ right: -14, top: 4 }} />}>
                         <p className={styles.semiPro} data-locale={"en-US"}>{_t('home.pro.title')}</p>
                     </Badge>

+ 2 - 2
src/sitePages/newHome/components/pro/pro.module.scss

@@ -26,7 +26,7 @@
     // width: 321px;
     font-size: 32px;
     font-family: 'Inter';
-    color: var(--semi-color-white);
+    color: #FFF;
     line-height: 44px;
     font-weight: 700;
     letter-spacing: -1.28px;
@@ -53,7 +53,7 @@
 .text_4c571d3f {
     font-size: 18px;
     font-family: 'PingFang SC';
-    color: var(--semi-color-white);
+    color: #FFF;
     line-height: 32px;
     font-weight: 400;
 }

+ 4 - 4
yarn.lock

@@ -1532,10 +1532,10 @@
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-site-doc-style/-/semi-site-doc-style-0.0.1.tgz#c3c803014218ec00441dac32db9a875f6222ed0b"
   integrity sha512-y7Jc1i9q/O2idfaqckSJvghpt4AboQJgZ4iTEK8UMqjQkyWmb5I/NRzVWjOP9S0LEbJNs76OKfZil7DwsOmY/A==
 
-"@douyinfe/semi-site-header@^0.0.13":
-  version "0.0.13"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-site-header/-/semi-site-header-0.0.13.tgz#2e44fcd59b3112b9e6678334346b35dc2c852e4d"
-  integrity sha512-HYLlYXcS/e3TjQ/etRbYEP7LGyiqPY2fnBzKG4gWlAZ0vq4D6qCmhuliZ2XUKwjXyP19Sx1XqQluowPNXE+EwA==
+"@douyinfe/semi-site-header@^0.0.18":
+  version "0.0.18"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-site-header/-/semi-site-header-0.0.18.tgz#f4fe1b9ab02768a99eaa752ea3eb4b56a8085e5e"
+  integrity sha512-ttBMbiQQrkZvhBTz8ZQM4cFosR9IAMR9neRXTmteeaEDyw+xrvGUvnIjf6cDiXTKzQYinfzN13XmH3Ei9inuZw==
   dependencies:
     "@douyinfe/semi-icons" "^2.0.0"
     "@douyinfe/semi-ui" "^2.0.0"