浏览代码

Merge branch release into main

pointhalo 2 年之前
父节点
当前提交
6624f4d0c9
共有 86 个文件被更改,包括 1683 次插入388 次删除
  1. 60 25
      content/input/cascader/index-en-US.md
  2. 57 27
      content/input/cascader/index.md
  3. 61 4
      content/input/datepicker/index-en-US.md
  4. 62 4
      content/input/datepicker/index.md
  5. 3 2
      content/input/select/index-en-US.md
  6. 4 3
      content/input/select/index.md
  7. 43 21
      content/input/treeselect/index-en-US.md
  8. 44 24
      content/input/treeselect/index.md
  9. 12 1
      content/start/changelog/index-en-US.md
  10. 12 0
      content/start/changelog/index.md
  11. 74 0
      cypress/integration/datePicker.spec.js
  12. 16 0
      cypress/integration/treeSelect.spec.js
  13. 1 1
      lerna.json
  14. 1 1
      packages/semi-animation-react/package.json
  15. 1 1
      packages/semi-animation-styled/package.json
  16. 1 1
      packages/semi-animation/package.json
  17. 1 1
      packages/semi-eslint-plugin/package.json
  18. 8 1
      packages/semi-foundation/cascader/foundation.ts
  19. 1 0
      packages/semi-foundation/datePicker/_utils/getDefaultFormatToken.ts
  20. 1 0
      packages/semi-foundation/datePicker/_utils/getInsetInputFormatToken.ts
  21. 1 0
      packages/semi-foundation/datePicker/_utils/getInsetInputValueFromInsetInputStr.ts
  22. 1 1
      packages/semi-foundation/datePicker/constants.ts
  23. 33 6
      packages/semi-foundation/datePicker/datePicker.scss
  24. 132 74
      packages/semi-foundation/datePicker/foundation.ts
  25. 9 3
      packages/semi-foundation/datePicker/inputFoundation.ts
  26. 6 4
      packages/semi-foundation/datePicker/variables.scss
  27. 65 18
      packages/semi-foundation/datePicker/yearAndMonthFoundation.ts
  28. 1 0
      packages/semi-foundation/modal/modal.scss
  29. 1 0
      packages/semi-foundation/modal/variables.scss
  30. 1 1
      packages/semi-foundation/package.json
  31. 15 0
      packages/semi-foundation/sideSheet/sideSheet.scss
  32. 6 0
      packages/semi-foundation/sideSheet/variables.scss
  33. 6 3
      packages/semi-foundation/treeSelect/foundation.ts
  34. 1 1
      packages/semi-icons/package.json
  35. 1 1
      packages/semi-illustrations/package.json
  36. 1 1
      packages/semi-next/package.json
  37. 1 1
      packages/semi-scss-compile/package.json
  38. 1 1
      packages/semi-theme-default/package.json
  39. 106 2
      packages/semi-ui/cascader/_story/cascader.stories.jsx
  40. 7 0
      packages/semi-ui/cascader/index.tsx
  41. 122 1
      packages/semi-ui/datePicker/_story/datePicker.stories.jsx
  42. 24 0
      packages/semi-ui/datePicker/_story/v2/FeatOnClickOutside.tsx
  43. 39 0
      packages/semi-ui/datePicker/_story/v2/FeatRefClass.tsx
  44. 64 0
      packages/semi-ui/datePicker/_story/v2/FeatRefFocus.tsx
  45. 31 0
      packages/semi-ui/datePicker/_story/v2/FeatRefOpen.tsx
  46. 46 0
      packages/semi-ui/datePicker/_story/v2/FixNeedConfirmInTabs.tsx
  47. 5 0
      packages/semi-ui/datePicker/_story/v2/index.js
  48. 16 6
      packages/semi-ui/datePicker/dateInput.tsx
  49. 97 22
      packages/semi-ui/datePicker/datePicker.tsx
  50. 1 0
      packages/semi-ui/datePicker/index.tsx
  51. 5 3
      packages/semi-ui/datePicker/monthsGrid.tsx
  52. 100 43
      packages/semi-ui/datePicker/yearAndMonth.tsx
  53. 2 1
      packages/semi-ui/locale/interface.ts
  54. 1 0
      packages/semi-ui/locale/source/ar.ts
  55. 1 0
      packages/semi-ui/locale/source/de.ts
  56. 1 0
      packages/semi-ui/locale/source/en_GB.ts
  57. 1 0
      packages/semi-ui/locale/source/en_US.ts
  58. 1 0
      packages/semi-ui/locale/source/es.ts
  59. 1 0
      packages/semi-ui/locale/source/fr.ts
  60. 1 0
      packages/semi-ui/locale/source/id_ID.ts
  61. 1 0
      packages/semi-ui/locale/source/it.ts
  62. 1 0
      packages/semi-ui/locale/source/ja_JP.ts
  63. 1 0
      packages/semi-ui/locale/source/ko_KR.ts
  64. 1 0
      packages/semi-ui/locale/source/ms_MY.ts
  65. 1 0
      packages/semi-ui/locale/source/nl_NL.ts
  66. 1 0
      packages/semi-ui/locale/source/pl_PL.ts
  67. 1 0
      packages/semi-ui/locale/source/pt_BR.ts
  68. 1 0
      packages/semi-ui/locale/source/ro.ts
  69. 1 0
      packages/semi-ui/locale/source/ru_RU.ts
  70. 1 0
      packages/semi-ui/locale/source/sv_SE.ts
  71. 1 0
      packages/semi-ui/locale/source/th_TH.ts
  72. 1 0
      packages/semi-ui/locale/source/tr_TR.ts
  73. 1 0
      packages/semi-ui/locale/source/vi_VN.ts
  74. 1 0
      packages/semi-ui/locale/source/zh_CN.ts
  75. 1 0
      packages/semi-ui/locale/source/zh_TW.ts
  76. 40 42
      packages/semi-ui/modal/Modal.tsx
  77. 7 7
      packages/semi-ui/package.json
  78. 23 2
      packages/semi-ui/select/index.tsx
  79. 8 5
      packages/semi-ui/sideSheet/SideSheetContent.tsx
  80. 2 2
      packages/semi-ui/sideSheet/index.tsx
  81. 3 1
      packages/semi-ui/space/index.tsx
  82. 1 0
      packages/semi-ui/table/_story/v2/fixedResizableWithForm.tsx
  83. 147 3
      packages/semi-ui/treeSelect/_story/treeSelect.stories.jsx
  84. 4 0
      packages/semi-ui/treeSelect/index.tsx
  85. 1 1
      packages/semi-webpack/package.json
  86. 15 15
      yarn.lock

+ 60 - 25
content/input/cascader/index-en-US.md

@@ -1620,24 +1620,28 @@ interface TriggerRenderProps {
      * The function used to update the value of the input box. You
      *  should call this function when the value of the Input component
      *  customized by triggerRender is updated to synchronize the
-     *  state with Cascader
+     *  state with Cascader, you need to set the filterTreeNode parameter
+     *  to non-false when use it, support since v2.32.0
      */
-    onChange: (inputValue: string) => void;
+    onSearch: (inputValue: string) => void;
     /* Function to clear the value */
     onClear: () => void;
     /* Placeholder of Cascader */
     placeholder?: string;
+    /* Used to delete a single item, the input parameter is value , 
+     * support since v2.32.0
+     */
+    onRemove: (value) => void
 }
 ```
 
 ```jsx live=true
 import React, { useState, useCallback, useMemo } from 'react';
-import { Cascader, Button } from '@douyinfe/semi-ui';
+import { Cascader, Button, Tag, TagInput } from '@douyinfe/semi-ui';
 import { IconClose, IconChevronDown } from '@douyinfe/semi-icons';
 
 
 function Demo() {
-    const [value, setValue] = useState([]);
     const treeData = useMemo(() => [
         {
             label: 'Asia',
@@ -1674,34 +1678,65 @@ function Demo() {
             ],
         }
     ], []);
-    const onChange = useCallback((val) => {
-        setValue(val);
-    }, []);
-    const onClear = useCallback(e => {
-        e && e.stopPropagation();
-        setValue([]);
-    }, []);
 
-    const closeIcon = useMemo(() => {
-        return value && value.length ? <IconClose onClick={onClear} /> : <IconChevronDown />;
-    }, [value]);
+    const closeIcon = useCallback((value, onClear) => {
+        return value ? <IconClose onClick={onClear} /> : <IconChevronDown />;
+    }, []);
 
-    const triggerRender = ({ value: innerStateValue, placeholder, ...rest }) => {
+    const triggerRenderSingle = ({ value, placeholder, onClear, ...rest }) => {
         return (
-            <Button theme={'light'} icon={closeIcon} iconPosition={'right'}>
-                {value && value.length ? value.join('/') : placeholder}
+            <Button theme={'light'} icon={closeIcon(value, onClear)} iconPosition={'right'}>
+                {value && value.length > 0 ? getLabelFromValue(value) : placeholder}
             </Button>
         );
     };
 
+    const getLabelFromValue = useCallback((value) => {
+        const valueArr = value.split('-').map(item => Number(item));
+        let resultData = treeData;
+        valueArr.forEach((item, index) => {
+            resultData = index === 0 ? resultData[item] : resultData.children[item];
+        });
+        return resultData.label;
+    }, [treeData]);
+
+    const triggerRenderMultiple = useCallback((props) => {
+        const { value, onSearch, onRemove } = props;
+        const onCloseTag = (value, e, tagKey) => {
+            onRemove(tagKey);
+        };
+
+        const renderTagItem = (value) => {
+            const label = getLabelFromValue(value);
+            return <Tag tagKey={value} key={value} closable onClose={onCloseTag} style={{ marginLeft: 2 }}>{label}</Tag>;
+        };
+        
+        return (
+            <TagInput
+                value={Array.from(value)}
+                onInputChange={onSearch}
+                renderTagItem={renderTagItem}
+            />
+        );
+    }, []);
+
     return (
-        <Cascader
-            onChange={onChange}
-            value={value}
-            treeData={treeData}
-            placeholder='Custom Trigger'
-            triggerRender={triggerRender}
-        />
+        <>
+            <Cascader
+                treeData={treeData}
+                placeholder='Custom Trigger'
+                triggerRender={triggerRenderSingle}
+            />
+            <br />
+            <Cascader
+                triggerRender={triggerRenderMultiple}
+                multiple
+                filterTreeNode
+                treeData={treeData}
+                style={{ width: 300 }}
+                placeholder='Custom Trigger'
+            />
+        </>
     );
 }
 ```
@@ -1760,7 +1795,7 @@ function Demo() {
 | topSlot | top slot | ReactNode | - |  1.27.0 |
 | treeData | Render data. Refer to [CascaderData](#CascaderData)  for detailed formatting. | CascaderData[] |  []  | - |
 | treeNodeFilterProp | When searching, the input item filters the corresponding CascaderData property. | string | `label`   | - |
-| triggerRender | Method to create a custom trigger  | (triggerRenderData: object) => ReactNode | - | 0.34.0 |
+| triggerRender | Method to create a custom trigger  | (props: TriggerRenderProps) => ReactNode | - | 0.34.0 |
 | value | Selected value (controlled mode) | string\|number\|CascaderData\|(string\|number\|CascaderData)[][]  | - | -  |
 | validateStatus |The validation status of the trigger only affects the display style. Optional: default、error、warning | string | `default` | - |
 | zIndex | zIndex for dropdown menu | number | 1030 | - |

+ 57 - 27
content/input/cascader/index.md

@@ -1603,23 +1603,24 @@ interface TriggerRenderProps {
     /**
      * 用于更新 input 框值的函数,当你在 triggerRender 自定义的
      * Input 组件值更新时,你应该调用该函数,用于向 Cascader 内部
-     * 同步状态
+     * 同步状态, 使用时需要设置 filterTreeNode 参数非 false
      */
-    onChange: (inputValue: string) => void;
+    onSearch: (inputValue: string) => void;
     /* 用于清空值的函数 */
     onClear: () => void;
     /* Placeholder */
     placeholder?: string;
+    /* 用于删除单个 item , 入参为 value */
+    onRemove: (value) => void
 }
 ```
 
 ```jsx live=true
 import React, { useState, useCallback, useMemo } from 'react';
-import { Cascader, Button } from '@douyinfe/semi-ui';
+import { Cascader, Button, Tag, TagInput } from '@douyinfe/semi-ui';
 import { IconClose, IconChevronDown } from '@douyinfe/semi-icons';
 
 function Demo() {
-    const [value, setValue] = useState([]);
     const treeData = useMemo(() => [
         {
             label: '浙江省',
@@ -1660,36 +1661,65 @@ function Demo() {
             ],
         }
     ], []);
-    const onChange = useCallback((val) => {
-        setValue(val);
-    }, []);
-    const onClear = useCallback(e => {
-        e && e.stopPropagation();
-        setValue([]);
-    }, []);
 
-    const closeIcon = useMemo(() => {
-        return value && value.length ? <IconClose onClick={onClear} /> : <IconChevronDown />;
-    }, [value]);
+    const closeIcon = useCallback((value, onClear) => {
+        return value ? <IconClose onClick={onClear} /> : <IconChevronDown />;
+    }, []);
 
-    const triggerRender = ({ value: innerStateValue, placeholder, ...rest }) => {
-        console.log(value);
-        console.log(rest);
+    const triggerRenderSingle = ({ value, placeholder, onClear, ...rest }) => {
         return (
-            <Button theme={'light'} icon={closeIcon} iconPosition={'right'}>
-                {value && value.length ? value.join('/') : placeholder}
+            <Button theme={'light'} icon={closeIcon(value, onClear)} iconPosition={'right'}>
+                {value && value.length > 0 ? getLabelFromValue(value) : placeholder}
             </Button>
         );
     };
 
+    const getLabelFromValue = useCallback((value) => {
+        const valueArr = value.split('-').map(item => Number(item));
+        let resultData = treeData;
+        valueArr.forEach((item, index) => {
+            resultData = index === 0 ? resultData[item] : resultData.children[item];
+        });
+        return resultData.label;
+    }, [treeData]);
+
+    const triggerRenderMultiple = useCallback((props) => {
+        const { value, onSearch, onRemove } = props;
+        const onCloseTag = (value, e, tagKey) => {
+            onRemove(tagKey);
+        };
+
+        const renderTagItem = (value) => {
+            const label = getLabelFromValue(value);
+            return <Tag tagKey={value} key={value} closable onClose={onCloseTag} style={{ marginLeft: 2 }}>{label}</Tag>;
+        };
+        
+        return (
+            <TagInput
+                value={Array.from(value)}
+                onInputChange={onSearch}
+                renderTagItem={renderTagItem}
+            />
+        );
+    }, []);
+
     return (
-        <Cascader
-            onChange={onChange}
-            value={value}
-            treeData={treeData}
-            placeholder='Custom Trigger'
-            triggerRender={triggerRender}
-        />
+        <>
+            <Cascader
+                treeData={treeData}
+                placeholder='Custom Trigger'
+                triggerRender={triggerRenderSingle}
+            />
+            <br />
+            <Cascader
+                triggerRender={triggerRenderMultiple}
+                multiple
+                filterTreeNode
+                treeData={treeData}
+                style={{ width: 300 }}
+                placeholder='Custom Trigger'
+            />
+        </>
     );
 }
 ```
@@ -1748,7 +1778,7 @@ function Demo() {
 | topSlot              | 顶部插槽                                                                                                                                  | ReactNode                                                                                 | -                              | 1.27.0 |
 | treeData             | 展示数据,具体属性参考 [CascaderData](#CascaderData)                                                                                       | CascaderData[]                                                                            | []                             | -      |
 | treeNodeFilterProp   | 搜索时输入项过滤对应的 CascaderData 属性                                                                                                  | string                                                                                    | `label`                        | -      |
-| triggerRender        | 自定义触发器渲染方法                                                                                                                      | (triggerRenderData: object) => ReactNode                                                  | -                              | 0.34.0 |
+| triggerRender        | 自定义触发器渲染方法                                                                                                                      | (props: TriggerRenderProps) => ReactNode                                                  | -                              | 0.34.0 |
 | validateStatus       | trigger 的校验状态,仅影响展示样式。可选: default、error、warning                                                                             | string                                                                                    | `default`                      | -      |
 | value                | (受控)选中的条目                                                                                                                          | string\|number\|CascaderData\|(string\|number\|CascaderData)[]                            | -                              | -      |
 | zIndex               | 下拉菜单的 zIndex                                                                                                                         | number                                                                                    | 1030                           | -      |

+ 61 - 4
content/input/datepicker/index-en-US.md

@@ -175,6 +175,9 @@ function Demo() {
             <DatePicker type="month" placeholder="please input month" insetInput style={{ width: 140 }} />
             <br />
             <br />
+            <DatePicker type="monthRange" placeholder="please input month Range" insetInput style={{ width: 200 }} />
+            <br />
+            <br />
             <DatePicker type="dateTime" format="yyyy-MM-dd HH:mm" insetInput />
         </div>
     );
@@ -299,6 +302,19 @@ class App extends React.Component {
 }
 ```
 
+### Year and Month Range Selection
+
+**version:** >= 2.32.0
+
+Set `type` to `monthRange` to select the year and month range, small size and quick panel are not supported yet.
+
+```jsx live=true
+import React from 'react';
+import { DatePicker } from '@douyinfe/semi-ui';
+
+() => <DatePicker type="monthRange" style={{ width: 200 }} />;
+```
+
 ### Confirm Date and Time Selection
 
 **Version: > = 0.18.0**
@@ -895,7 +911,7 @@ function Demo() {
 | multiple           | Whether you can choose multiple, only type = "date" is supported                                                                                                                          | boolean                                                                                                                                                                                                   | false                                                                                 |                           |
 | needConfirm        | Do you need to "confirm selection", only `type= "dateTime"\| "dateTimeRange"` works.       | boolean                                                                                                                                                                                                   |                                                                                       | **0.18.0**                |
 | open               | Controlled properties displayed or hidden by panels                                                                                                                                       | boolean                                                                                                                                                                                                   |                                                                                       |                           |
-| placeholder        | Input box prompts text                                                                                                                                                                    | string                                                                                                                                                                                                    | 'Select date'                                                                         |                           |
+| placeholder        | Input box prompts text                                                                                                                                                                    | string\|string[]                                                                                                                                                                                                    | 'Select date'                                                                         |                           |
 | position           | Floating layer position, optional value with [Popover #API Reference · position](/en-US/show/popover#API%20Reference)                                                                 | string                                                                                                                                                                                                    | 'bottomLeft'                                                                          |                           |
 | prefix             | Prefix content                                                                                                                                                                            | string\|ReactNode                                                                                                                                                                                         |                                                                                       |                           |
 | presets            | Date Time Shortcut     |  <ApiType detail='Array< { start: BaseValueType, end :BaseValueType, text: string } \| () => { start:B aseValueType, end: BaseValueType, text: string }>'>Array</ApiType>                                  | []                                                                                    |                           |
@@ -912,20 +928,61 @@ function Demo() {
 | timePickerOpts     | For other parameters that can be transparently passed to the time selector, see [TimePicker·API Reference](/en-US/input/timepicker#API%20Reference)                                    |                                                                                                                                                                                                           | object                                                                                | **1.1.0**                 |
 | topSlot            | Render the top extra area                                                                                 | ReactNode                                                                                                                                                                                                 |                                                | **1.22.0**                   |
 | triggerRender      | Custom trigger rendering method                                                                                                                                                           | (TriggerRenderProps) => ReactNode                                                                                                                                                                    |                                                                                       | **0.34.0**                |
-| type               | Type, optional value: "date", "dateRange", "dateTime", "dateTimeRange", "month"                                                                                                           | string                                                                                                                                                                                                    | 'date'                                                                                |  |
+| type               | Type, optional value: "date", "dateRange", "dateTime", "dateTimeRange", "month", "monthRange"                                                                                                           | string                                                                                                                                                                                                    | 'date'                                                                                |  |
 | value              | Controlled value                                                                                                                                                                          | ValueType                                                                                                                                                     |                                                                                       |                           |
 | weekStartsOn       | Take the day of the week as the first day of the week, 0 for Sunday, 1 for Monday, and so on.                                                                                             | number                                                                                                                                                                                                    | 0                                                                                     |                           |
-| onBlur             | Callback when focus is lost                                                                                                                                                               | (event) => void                                                                                                                                                                                     | () => {}                                                                              | **1.0.0**                 |
+| onBlur | Callback when focus is lost. It is not recommended to use this API in range selection | (event) => void | () => {} | **1.0.0** |
 | onCancel           | Cancel the callback when selected, enter the reference as the value of the last confirmed selection, only `type` equals "dateTime"or "dateTimeRange" and `needConfirm` equals true        | <ApiType detail='(date: DateType, dateStr: StringType) => void'>(date, dateString) => void</ApiType>                                                              |                                                                                       | **0.18.0**                |
 | onChange           | A callback when the value changes |   <ApiType detail='(date: DateType, dateString: StringType) => void'>(date, dateString) => void</ApiType>       |                                                                                       |                           |
 | onClear            | A callback when click the clear button                                                                                                                                                    | (event) => void                                                                                                                                                                                     | () => {}                                                                              | **1.16.0**           |
+| onClickOutSide    | When the pop-up layer is in a display state, click the non-popup layer and trigger callback | () => void | () => {} | **2.31.0** |
 | onConfirm          | Confirm the callback at the time of selection, enter the reference as the value of the current selection, only `type` equals "dateTime" or "dateTimeRange" and `needConfirm` equals true  |  <ApiType detail='(date: DateType, dateStr: StringType) => void'>(date, dateString) => void</ApiType>|                                                                                       | **0.18.0**                |
-| onFocus            | Callback when focus is obtained                                                                                                                                                           | (event) => void                                                                                                                                                                                     | () => {}                                                                              | **1.0.0**                 |
+| onFocus | Callback when focus is obtained. It is not recommended to use this API in range selection  | (event) => void | () => {} | **1.0.0** |
 | onOpenChange       | Callback when popup open or close                                                                                                                                 | (isOpen) => void                                                                                                                                                                                 |                                                                                       |                           |
 | onPanelChange      | Callback when the year or date of the panel is switched|  <ApiType detail='(date: DateType \| DateType[], dateStr: StringType \| StringType[])=>void'>(date, dateStr) => void</ApiType>  |  |**1.28.0**|
 | onPresetClick      | Callback when click preset button                                                                          | <ApiType detail='(item: Object, e: Event) => void'>(item, e) => void</ApiType>       |   **1.24.0**                           |
 | yearAndMonthOpts | Other parameters that can be transparently passed to the year-month selector, see details in [ScrollList#API](/zh-CN/show/scrolllist#ScrollItem)|  | object | **2.22.0** |
 
+## Methods
+
+| Methods | Description                                       | Version |
+|---------|---------------------------------------------------|---------|
+| open    | The dropdown can be manually opened when calling  | 2.31.0  |
+| close   | The dropdown can be manually closed when calling  | 2.31.0  |
+| focus   | The input box can be manually focused when called | 2.31.0  |
+| blur    | The input box can be manually blurred when called | 2.31.0  |
+
+```jsx live=true
+import React, { useRef } from 'react';
+import { DatePicker, Space, Button } from '@douyinfe/semi-ui';
+import { BaseDatePicker } from '@douyinfe/semi-ui/lib/es/datePicker';
+
+function Demo() {
+    const ref = useRef();
+    // Typescript
+    // const ref = useRef<BaseDatePicker>();
+    // Why not import the DatePicker exported by the entry? -> The entry component is a forwardRef component, and the ref is transparently passed to this component
+
+    const handleClickOutside = () => {
+        console.log('click outside');
+    };
+
+    return (
+        <Space vertical align={'start'}>
+            <Space>
+                <Button onClick={() => ref.current.open()}>open</Button>
+                <Button onClick={() => ref.current.close()}>close</Button>
+                <Button onClick={() => ref.current.focus()}>focus</Button>
+                <Button onClick={() => ref.current.blur()}>blur</Button>
+            </Space>
+            <div>
+                <DatePicker type="dateTime" ref={ref} onClickOutSide={handleClickOutside} />
+            </div>
+        </Space>
+    );
+}
+```
+
 
 ## Interface Define
 

+ 62 - 4
content/input/datepicker/index.md

@@ -172,6 +172,9 @@ function Demo() {
             <DatePicker type="month" placeholder="请选择年月" insetInput style={{ width: 140 }} />
             <br />
             <br />
+            <DatePicker type="monthRange" placeholder="请选择年月范围" insetInput style={{ width: 200 }} />
+            <br />
+            <br />
             <DatePicker type="date" position="bottomLeft" insetInput />
             <br />
             <br />
@@ -288,6 +291,19 @@ import { DatePicker } from '@douyinfe/semi-ui';
 () => <DatePicker defaultValue={new Date()} type="month" style={{ width: 140 }} />;
 ```
 
+### 年月范围选择
+
+**版本:** >= 2.32.0
+
+将 `type` 设定为 `monthRange`,可以进行年月范围选择。暂不支持小尺寸与快捷面板。
+
+```jsx live=true
+import React from 'react';
+import { DatePicker } from '@douyinfe/semi-ui';
+
+() => <DatePicker type="monthRange" style={{ width: 200 }} />;
+```
+
 ### 确认日期时间选择
 
 **版本:** >= 0.18.0
@@ -860,7 +876,7 @@ function Demo() {
 | multiple | 是否可以选择多个,仅支持 type="date" | boolean | false |  |
 | needConfirm | 是否需要“确认选择”,仅 type="dateTime"\|"dateTimeRange" 时有效 | boolean |  | **0.18.0** |
 | open | 面板显示或隐藏的受控属性 | boolean |  |  |
-| placeholder | 输入框提示文字 | string | 'Select date' |  |
+| placeholder | 输入框提示文字 | string\|string[] | 'Select date' |  |
 | position | 浮层位置,可选值同[Popover#API 参考·position 参数](/zh-CN/show/popover#API参考) | string | 'bottomLeft' |  |
 | prefix | 前缀内容 | string\|ReactNode |  |  |
 | presets | 日期时间快捷方式 |  <ApiType detail='Array< { start: BaseValueType, end :BaseValueType, text: string } \| () => { start:B aseValueType, end: BaseValueType, text: string }>'>Array</ApiType> | [] |  |
@@ -879,23 +895,65 @@ function Demo() {
 | timePickerOpts | 其他可以透传给时间选择器的参数,详见 [TimePicker·API 参考](/zh-CN/input/timepicker#API_参考) |  | object | **1.1.0** |
 | topSlot | 渲染顶部额外区域 | ReactNode |  | **1.22.0** |
 | triggerRender | 自定义触发器渲染方法,第一个参数是个 Object,详情看下方类型定义 | (props) => ReactNode| | **0.34.0** |
-| type | 类型,可选值:"date", "dateRange", "dateTime", "dateTimeRange", "month" | string | 'date' |  |
+| type | 类型,可选值:"date", "dateRange", "dateTime", "dateTimeRange", "month", "monthRange" | string | 'date' |  |
 | validateStatus | 校验状态,可选值 default、error、warning,默认 default。仅影响展示样式 | string |  |  |
 | value | 受控的值 | ValueType |  |  |
 | weekStartsOn | 以周几作为每周第一天,0 代表周日,1 代表周一,以此类推 | number | 0 |  |
 | zIndex | 弹出面板的 zIndex | number | 1030 |  |
-| onBlur | 失去焦点时的回调 | (e: event) => void | () => {} | **1.0.0** |
+| onBlur | 失去焦点时的回调,范围选择时不推荐使用 | (e: event) => void | () => {} | **1.0.0** |
 | onCancel | 取消选择时的回调,入参为上次确认选择的值,仅 type="dateTime"\|"dateTimeRange" 且 needConfirm=true 时有效。<br/>0.x版本入参顺序与新版有所不同 | <ApiType detail='(date: DateType, dateStr: StringType) => void'>(date, dateString) => void</ApiType> |  | **0.18.0** |
 | onChange | 值变化时的回调。<br/>0.x版本入参顺序与新版有所不同 | <ApiType detail='(date: DateType, dateString: StringType) => void'>(date, dateString) => void</ApiType> |  |  |
 | onChangeWithDateFirst | 0.x 中 onChange(string, Date), 1.0 后(Date, string)。此开关设为 false 时,入参顺序将与 0.x 版本保持一致 | boolean | true | **1.0.0** |
 | onClear | 点击 clear 按钮时触发 | (e: event) => void | () => {} | **1.16.0** |
+| onClickOutSide | 当弹出层处于展示状态,点击非弹出层、触发器的回调 | () => void | () => {} | **2.31.0** |
 | onConfirm | 确认选择时的回调,入参为当前选择的值,仅 type="dateTime"\|"dateTimeRange" 且 needConfirm=true 时有效。<br/>0.x版本入参顺序与新版有所不同 | <ApiType detail='(date: DateType, dateStr: StringType) => void'>(date, dateString) => void</ApiType>|  | **0.18.0** |
-| onFocus | 获得焦点时的回调 | (e: event) => void | () => {} | **1.0.0** |
+| onFocus | 获得焦点时的回调,范围选择时不推荐使用 | (e: event) => void | () => {} | **1.0.0** |
 | onOpenChange | 面板显示或隐藏状态切换的回调 | <ApiType detail='(isOpen: boolean) => void'>(isOpen) => void</ApiType> |  |  |
 | onPanelChange | 切换面板的年份或者日期时的回调 | <ApiType detail='(date: DateType \| DateType[], dateStr: StringType \| StringType[])=>void'>(date, dateStr) => void</ApiType> | function | **1.28.0** |
 | onPresetClick | 点击快捷选择按钮的回调 | <ApiType detail='(item: Object, e: Event) => void'>(item, e) => void</ApiType> | () => {}  | **1.24.0** |
 | yearAndMonthOpts | 其他可以透传给年月选择器的参数,详见 [ScrollList#API](/zh-CN/show/scrolllist#ScrollItem)|  | object | **2.20.0** |
 
+## Methods
+
+| 方法  | 说明                       | 类型                                             | 版本   |
+|-------|--------------------------|--------------------------------------------------|--------|
+| open  | 调用时可以手动展开下拉列表 | () => void                                       | 2.31.0 |
+| close | 调用时可以手动关闭下拉列表 | () => void                                       | 2.31.0 |
+| focus | 调用时可以手动聚焦输入框   | (focusType?: 'rangeStart' \| 'rangeEnd') => void | 2.31.0 |
+| blur  | 调用时可以手动失焦输入框   | () => void                                       | 2.31.0 |
+
+```jsx live=true
+import React, { useRef } from 'react';
+import { DatePicker, Space, Button } from '@douyinfe/semi-ui';
+import { BaseDatePicker } from '@douyinfe/semi-ui/lib/es/datePicker';
+
+function Demo() {
+    const ref = useRef();
+    // Typescript 写法
+    // const ref = useRef<BaseDatePicker>();
+    // 为什么不引用入口导出的 DatePicker?-> 入口组件是个 forwardRef 组件,ref 透传到了这个组件上
+
+
+    const handleClickOutside = () => {
+        console.log('click outside');
+    };
+
+    return (
+        <Space vertical align={'start'}>
+            <Space>
+                <Button onClick={() => ref.current.open()}>open</Button>
+                <Button onClick={() => ref.current.close()}>close</Button>
+                <Button onClick={() => ref.current.focus()}>focus</Button>
+                <Button onClick={() => ref.current.blur()}>blur</Button>
+            </Space>
+            <div>
+                <DatePicker type="dateTime" ref={ref} onClickOutSide={handleClickOutside} />
+            </div>
+        </Space>
+    );
+}
+```
+
 ## 类型定义
 
 ```typescript

+ 3 - 2
content/input/select/index-en-US.md

@@ -1062,11 +1062,12 @@ If the default layout style of the selection box does not meet your needs, you c
 The parameters of triggerRender are as follows
 
 ```typescript
-interface triggerRenderProps {
+interface TriggerRenderProps {
   value: array<object> // All currently selected options
   inputValue: string; // The input value of the current input box
-  onChange: (inputValue: string) => void; // The function used to update the value of the input box. You should call this function when the value of the Input component you customize in triggerRender is updated to synchronize the state to the Select internal
+  onSearch: (inputValue: string) => void; // The function used to update the value of the input box. You should call this function when the value of the Input component you customize in triggerRender is updated to synchronize the state to the Select internal. props.filter needs to be true, support after v2.32
   onClear: () => void; // Function to clear the value
+  onRemove: (option: object) => void; // support after v2.32
   disabled: boolean; // Whether to disable Select
   placeholder: string; // Select placeholder
   componentProps: //All props passed to Select by users

+ 4 - 3
content/input/select/index.md

@@ -1126,14 +1126,15 @@ class VirtualizeDemo extends React.Component {
 triggerRender 入参如下
 
 ```typescript
-interface triggerRenderProps {
+interface TriggerRenderProps {
   value: array<object> // 当前所有已选中的options
   inputValue: string; // 当前input框的输入值
-  onChange: (inputValue: string) => void; // 用于更新 input框值的函数,当你在triggerRender自定义的Input组件值更新时你应该调用该函数,用于向Select内部同步状态
+  onSearch: (inputValue: string) => void; // 用于更新 input框值的函数,当你在triggerRender自定义的Input组件值更新时你应该调用该函数,用于向Select内部同步状态。注意 filter 需同时设为true, v2.32 提供
+  onRemove: (option: object) => void; // 用于移除单个已选项,option至少需带有 label、value 两项,v2.32提供
   onClear: () => void; // 用于清空值的函数
   disabled: boolean; // 是否禁用Select
   placeholder: string; // Select的placeholder
-  componentProps: // 所有用户传给Select的props
+  componentProps: object // 所有用户传给Select的props
 }
 ```
 

+ 43 - 21
content/input/treeselect/index-en-US.md

@@ -1217,13 +1217,24 @@ interface triggerRenderProps {
     inputValue: string;             // value of the input box
     onClear: e => void;             // onClear function
     placeholder: string;            // placeholder
+    /* The function called when deleting a single item, 
+     *   with the key of the item as an input parameter, 
+     *  supported from version v2.32.0
+     */
+    onRemove: key => void;
+    /* It is used to start the search when the value of the Input box is updated. 
+     * When you update the value of the Input component customized by triggerRender, 
+     * you should call this function to synchronize the state with the TreeSelect 
+     * internally. you need to set the filterTreeNode parameter to non-false when use it,
+     * and set searchPosition to 'trigger'. It is supported from v2.32.0
+     */
+    onSearch: inputValue => void;   
 }
 ```
 
 ```jsx live=true
 import React, { useState, useCallback, useMemo } from 'react';
-import { TreeSelect, Button } from '@douyinfe/semi-ui';
-import { IconClose, IconChevronDown } from '@douyinfe/semi-icons';
+import { TreeSelect, Button, Tag, TagInput } from '@douyinfe/semi-ui';
 
 function Demo() {
     const [value, setValue] = useState([]);
@@ -1258,31 +1269,41 @@ function Demo() {
             key: '1',
         }
     ], []);
-    const onChange = useCallback((val) => {
-        setValue(val);
-    }, []);
-    const onClear = useCallback(e => {
-        e && e.stopPropagation();
-        setValue([]);
+    
+    const onValueChange = useCallback((value) => {
+        console.log('onChange', value);
+    });
+
+    const renderTrigger = useCallback((props) => {
+        const { value, onSearch, onRemove, inputValue } = props;
+        const tagInputValue = value.map(item => item.key);
+        const renderTagInMultiple = (key) => {
+            const label = value.find(item => item.key === key).label;
+            const onCloseTag = (value, e, tagKey) => {
+                onRemove(tagKey);
+            };
+            return <Tag style={{ marginLeft: 2 }} tagKey={key} key={key} onClose={onCloseTag} closable>{label}</Tag>;
+        };
+        return (
+            <TagInput
+                inputValue={inputValue}
+                value={tagInputValue}
+                onInputChange={onSearch}
+                renderTagItem={renderTagInMultiple}
+            />
+        );
     }, []);
 
-    const closeIcon = useMemo(() => {
-        return value && value.length ? <IconClose onClick={onClear} /> : <IconChevronDown />;
-    }, [value]);
-
     return (
         <TreeSelect
-            onChange={onChange}
-            style={{ width: 300 }}
-            value={value}
+            triggerRender={renderTrigger}
+            filterTreeNode
+            searchPosition="trigger"
             multiple
             treeData={treeData}
             placeholder='Custom Trigger'
-            triggerRender={({ placeholder }) => (
-                <Button theme={'light'} icon={closeIcon} iconPosition={'right'}>
-                    {value && value.length ? value.join(', ') : placeholder}
-                </Button>
-            )}
+            onChange={onValueChange}
+            style={{ width: 300 }}
         />
     );
 }
@@ -1386,6 +1407,7 @@ function Demo() {
 | className                | Class name                                                                          | string                                                            | -           | -       |
 | clearIcon    | Can be used to customize the clear button, valid when showClear is true                       | ReactNode                |       | 2.25.0    |
 | clickToHide  | Whether to close the drop-down layer automatically when selecting, only works in single-selection mode  | boolean    | true | 1.5.0      |
+| clickTriggerToHide  | When the panel is open, whether to close the panel after clicking the Trigger  | boolean    | true | 2.32.0      |
 | defaultExpandAll    | Set whether to expand all nodes during initialization. And if the data (`treeData`) changes, this api cannot affect the expansion of the node. If you need this, you can use `expandAll`    | boolean                     | false   | 0.32.0 |
 | defaultExpandedKeys | Keys of default expanded nodes. Direct child nodes will be displayed. | string\[] | - | 0.32.0 |
 | defaultOpen | Toggle whether to open dropdown menu by default | boolean | false | 0.32.0 |
@@ -1434,7 +1456,7 @@ function Demo() {
 | treeData                 | Data for treeNodes                                                                  | TreeNodeData[]                                                  | \[]         | -       |
 | treeNodeFilterProp       | Property in a `TreeNodeData` used to search                                             | string                                                            | `label`     | -       |
 | treeNodeLabelProp        | Property in a `TreeNodeData` used to display                                            | string                                                            | `label`     | -       |
-| triggerRender | Method to create a custom trigger  | (TriggerProps) => ReactNode | - | 0.34.0 |
+| triggerRender | Method to create a custom trigger  | (props: TriggerRenderProps) => ReactNode | - | 0.34.0 |
 | validateStatus | Validate status,one of `warning`、`error`、 `default`, only affects the background color of the component | string | - | 0.32.0 |
 | value                    | Value data of current item, used when TreeSelect is a controlled component     | <ApiType detail='string \| number \| TreeNodeData \| (string \| number \| TreeNodeData)[]'>ValueType</ApiType>    | -           | -       |
 | virtualize | Efficiently rendering large lists, refer to Tree - VirtualizeObj. Motion is disabled when tree is rendered as virtualized list. | object | - | 0.32.0 |

+ 44 - 24
content/input/treeselect/index.md

@@ -1178,24 +1178,33 @@ import { TreeSelect } from '@douyinfe/semi-ui';
 triggerRender 入参如下:
 
 ```typescript
-interface triggerRenderProps {
+interface TriggerRenderProps {
     componentProps: TreeSelectProps;// 所有用户传给 TreeSelect 的 props
     disabled: boolean;              // 是否禁用 TreeSelect
     value: TreeNodeData[];              // 已选中的 node 的数据
     inputValue: string;             // 当前 input 框的输入值
     onClear: e => void;             // 用于清空值的函数
     placeholder: string;            // placeholder
+    /* 删除单个 item 时调用的函数,以 item 的 key 作为入参, 
+     * 从 v2.32.0 版本开始支持 
+    */
+    onRemove: key => void;          
+    /**
+     * 用于在 Input 框值更新时候启动搜索,当你在 triggerRender 自定义的
+     * Input 组件值更新时,你应该调用该函数,用于向 TreeSelect 内部
+     * 同步状态, 使用同时需要设置 filterTreeNode 参数非 false, 
+     * searchPosition 为 'trigger'
+     * 从 v2.32.0 版本开始支持
+    */
+    onSearch: inputValue => void;   
 }
 ```
 
-
 ```jsx live=true
 import React, { useState, useCallback, useMemo } from 'react';
-import { TreeSelect, Button } from '@douyinfe/semi-ui';
-import { IconClose, IconChevronDown } from '@douyinfe/semi-icons';
+import { TreeSelect, Button, Tag, TagInput } from '@douyinfe/semi-ui';
 
 function Demo() {
-    const [value, setValue] = useState([]);
     const treeData = useMemo(() => [
         {
             label: '亚洲',
@@ -1227,31 +1236,41 @@ function Demo() {
             key: '1',
         }
     ], []);
-    const onChange = useCallback((val) => {
-        setValue(val);
-    }, []);
-    const onClear = useCallback(e => {
-        e && e.stopPropagation();
-        setValue([]);
-    }, []);
 
-    const closeIcon = useMemo(() => {
-        return value && value.length ? <IconClose onClick={onClear} /> : <IconChevronDown />;
-    }, [value]);
+    const onValueChange = useCallback((value) => {
+        console.log('onChange', value);
+    });
+
+    const renderTrigger = useCallback((props) => {
+        const { value, onSearch, onRemove, inputValue } = props;
+        const tagInputValue = value.map(item => item.key);
+        const renderTagInMultiple = (key) => {
+            const label = value.find(item => item.key === key).label;
+            const onCloseTag = (value, e, tagKey) => {
+                onRemove(tagKey);
+            };
+            return <Tag style={{ marginLeft: 2 }} tagKey={key} key={key} onClose={onCloseTag} closable>{label}</Tag>;
+        };
+        return (
+            <TagInput
+                inputValue={inputValue}
+                value={tagInputValue}
+                onInputChange={onSearch}
+                renderTagItem={renderTagInMultiple}
+            />
+        );
+    }, []);
 
     return (
         <TreeSelect
-            onChange={onChange}
-            style={{ width: 300 }}
-            value={value}
+            triggerRender={renderTrigger}
+            filterTreeNode
+            searchPosition="trigger"
             multiple
             treeData={treeData}
             placeholder='Custom Trigger'
-            triggerRender={({ placeholder }) => (
-                <Button theme={'light'} icon={closeIcon} iconPosition={'right'}>
-                    {value && value.length ? value.join(',') : placeholder}
-                </Button>
-            )}
+            onChange={onValueChange}
+            style={{ width: 300 }}
         />
     );
 }
@@ -1369,6 +1388,7 @@ function Demo() {
 | className | 选择框的 `className` 属性 | string | - | - |
 | clearIcon | 可用于自定义清除按钮, showClear为true时有效 | ReactNode | |2.25.0  |
 | clickToHide  | 选择后是否自动关闭下拉弹层,仅单选模式有效  | boolean    | true | 1.5.0      |
+| clickTriggerToHide  | 面板打开状态下,点击 Trigger 后是否关闭面板  | boolean    | true | 2.32.0      |
 | defaultExpandAll | 设置在初始化时是否展开所有节点。而如果后续数据(`treeData`)发生改变,这个 api 是无法影响节点的展开情况的,如果有这个需要可以使用 `expandAll` | boolean | false | 0.32.0 |
 | defaultExpandedKeys | 默认展开的节点,显示其直接子级 | string\[] | - | 0.32.0 |
 | defaultOpen | 默认展开下拉菜单 | boolean | false | 0.32.0 |
@@ -1417,7 +1437,7 @@ function Demo() {
 | treeData | `treeNodes` 数据,如果设置则不需要手动构造 `TreeNode` 节点(`key` 值在整个树范围内唯一) | TreeNodeData[] | \[] | - |
 | treeNodeFilterProp | 搜索时输入项过滤对应的 `TreeNodeData` 属性 | string | `label` | - |
 | treeNodeLabelProp | 作为显示的 `prop` 设置 | string | `label` | - |
-| triggerRender | 自定义触发器渲染方法  | ({ placeholder: string }) => ReactNode | - | 0.34.0 |
+| triggerRender | 自定义触发器渲染方法  | (props: TriggerRenderProps) => ReactNode | - | 0.34.0 |
 | validateStatus | 校验结果,可选 `warning`、`error`、 `default`(只影响样式背景色) | string | - | 0.32.0 |
 | value | 当前选中的节点的value值,传入该值时将作为受控组件 | <ApiType detail='string \| number \| TreeNodeData \| (string \| number \| TreeNodeData)[]'>ValueType</ApiType>| - | - |
 | virtualize | 列表虚拟化,用于大量树节点的情况,由 height, width, itemSize 组成,参考 Tree - Virtualize Object。开启后将关闭动画效果。 | object | - | 0.32.0 |

+ 12 - 1
content/start/changelog/index-en-US.md

@@ -16,10 +16,21 @@ Version:Major.Minor.Patch (follow the **Semver** specification)
 
 ---
 
+#### 🎉 2.32.0-beta.0 (2023-03-28)
+- 【Design Token】
+  - Modal provides `$spacing-modal_content_fullscreen-top` to control the top height of the full screen, Sidesheet adds `$color-sideSheet_header-borderBottom` and `$width-sideSheet_header-borderBottom` to add a dividing line under the header, `$width-sideSheet_size-small` `$width-sideSheet_size-medium` `$width-sideSheet_size-large` controls the default expanded width
+- 【Feat】
+  - DatePicker added type monthRange
+  - The TriggerRender API parameters of TreeSelect、 Cascader and Select support onSearch and onRemove respectively to support custom triggers to start searching and delete a single selected item
+  - TreeSelect has added a clickTriggerToHide parameter to support setting whether to click the Trigger part to trigger the panel to close when the panel is open
+  - DatePicker support open, close, focus, blur methods and onClickOutside callback  [#566](https://github.com/DouyinFE/semi-design/issues/566)
+- 【Fix】
+  - fix the problem that the style of the shortcut selection panel for DatePicker type month is not as expected
+  - Change the initial setting of the state in the constructor in Switch to avoid unexpected animations when used in other components
+
 #### 🎉 2.31.3 (2023-03-31)
 - 【Fix】
     - Fix DatePicker timeZone conversion problem when date-fns-tz version >= 1.3.8  [#1522](https://github.com/DouyinFE/semi-design/issues/1522)
-
 #### 🎉 2.31.2 (2023-03-24)
 - 【Fix】
     - Fix the problem of using resizable Table and form at the same time in the dev environment to report an error  [#1506](https://github.com/DouyinFE/semi-design/issues/1506)

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

@@ -13,6 +13,18 @@ Semi 版本号遵循 **Semver** 规范(主版本号-次版本号-修订版本
 -   修订版本号(patch):仅会进行 bugfix,发布时间不限
 -   不同版本间的详细关系,可查阅 [FAQ](/zh-CN/start/faq)
 
+#### 🎉 2.32.0-beta.0 (2023-03-28)
+- 【Design Token】
+  - Modal 提供 `$spacing-modal_content_fullscreen-top` 用于控制全屏时顶部高度, Sidesheet 新增 `$color-sideSheet_header-borderBottom` `$width-sideSheet_header-borderBottom` 用于在 header 下添加分割线, `$width-sideSheet_size-small` `$width-sideSheet_size-medium` `$width-sideSheet_size-large` 控制默认展开宽度
+- 【Feat】
+  - DatePicker 新增 type monthRange
+  - TreeSelect 、 Cascader、Select 的 TriggerRender API 参数支持 onSearch 和 onRemove 分别用于支持自定义 trigger 启动搜索,删除单个已选项
+  - TreeSelect 新增 clickTriggerToHide 参数支持设置在面板打开状态下,点击 Trigger 部分是否触发面板关闭
+  - DatePicker 支持 open、close、focus、blur 方法和 onClickOutside 回调  [#566](https://github.com/DouyinFE/semi-design/issues/566)
+- 【Fix】
+  - 修复 DatePicker type month 开启上下方位快捷选择面板样式不符合预期问题
+  - 更改 Switch 中 state 在 constructor 中的初始设置,避免在其他组件中使用时候出现不符合预期的动画
+
 
 #### 🎉 2.31.3 (2023-03-31)
 - 【Fix】

+ 74 - 0
cypress/integration/datePicker.spec.js

@@ -145,6 +145,17 @@ describe('DatePicker', () => {
         cy.get('[data-cy=month] .semi-input').should("have.value", "2021-11");
     });
 
+    it('insetInput + monthRange', () => {
+        cy.visit('http://localhost:6006/iframe.html?id=datepicker--month-range-picker&args=&viewMode=story');
+        cy.get('[data-cy=monthRange] .semi-input').click();
+        cy.get('.semi-popover .semi-input-wrapper-focus');
+        cy.get('.semi-popover .semi-input').should("have.value", "2023年03月 到 2023年04月");
+        cy.get('.semi-popover .semi-input-wrapper-focus').clear();
+        cy.get('.semi-scrolllist').eq(1).contains('2025').click();
+        cy.get('.semi-scrolllist .semi-scrolllist-item').eq(3).contains(6).click();
+        cy.get('[data-cy=monthRange] .semi-input').should("have.value", "2023年03月 到 2025年06月");
+    });
+
     it('insetInput + dateTime', () => {
         cy.visit('http://localhost:6006/iframe.html?id=datepicker--inset-input-e-2-e&args=&viewMode=story');   
         cy.get('[data-cy=dateTime] .semi-input').click();
@@ -665,4 +676,67 @@ describe('DatePicker', () => {
         cy.get('.semi-datepicker-month-grid-left .semi-datepicker-day').contains('20').click();
         cy.get('.semi-datepicker-navigation-month').contains("2019年 8月");
     });
+
+    it('test clickOutSide', () => {
+        cy.visit('http://localhost:6006/iframe.html?id=datepicker--feat-on-click-outside&args=&viewMode=story', {
+            onBeforeLoad(win) {
+                cy.stub(win.console, 'log').as('consoleLog');
+            },
+        });
+        cy.get('.semi-input').eq(0).click();
+        cy.get('.semi-datepicker-footer').click();
+        cy.get('body').click();
+        cy.get('@consoleLog').should('be.calledOnce');
+    });
+
+    it('test open & close', () => {
+        cy.visit('http://localhost:6006/iframe.html?id=datepicker--feat-ref-open&args=&viewMode=story');
+        cy.get('button').contains('open').eq(0).click();
+        cy.get('.semi-popover .semi-datepicker');
+        cy.get('button').contains('close').eq(0).click();
+        cy.get('.semi-popover .semi-datepicker').should('not.exist');
+    });
+
+    it('test needConfirm + close', () => {
+        cy.visit('http://localhost:6006/iframe.html?id=datepicker--fix-need-confirm-in-tabs&args=&viewMode=story');
+        cy.get('.semi-input').eq(0).click();
+        cy.get('.semi-popover .semi-datepicker');
+        cy.get('.semi-tabs-tab').contains('快速起步').click();
+        cy.get('.semi-popover .semi-datepicker').should('not.exist');
+    });
+
+    it('test focus + blur + date type', () => {
+        cy.visit('http://localhost:6006/iframe.html?id=datepicker--feat-ref-focus&args=&viewMode=story');
+        cy.get('[data-cy=single] .semi-button').contains('focus').eq(0).click();
+        cy.get('[data-cy=single] .semi-input-wrapper-focus');
+        cy.get('[data-cy=single] input').should('be.focused');
+        cy.get('[data-cy=single] .semi-button').contains('blur').eq(0).click();
+        cy.get('[data-cy=single] .semi-input-wrapper-focus').should('not.exist');
+        cy.get('[data-cy=single] input').should('not.be.focused');
+    });
+
+    it('test focus + blur + dateRange type', () => {
+        cy.visit('http://localhost:6006/iframe.html?id=datepicker--feat-ref-focus&args=&viewMode=story');
+        cy.get('[data-cy=range] .semi-button').contains('focus default').click();
+        cy.get('[data-cy=range] .semi-datepicker-range-input-wrapper-start .semi-input-wrapper-focus');
+        cy.get('[data-cy=range] .semi-datepicker-range-input-wrapper-start input').should('be.focused');
+        cy.get('[data-cy=range] .semi-button').contains('focus end').click();
+        cy.get('[data-cy=range] .semi-datepicker-range-input-wrapper-end .semi-input-wrapper-focus');
+        cy.get('[data-cy=range] .semi-datepicker-range-input-wrapper-end input').should('be.focused');
+        cy.get('[data-cy=range] .semi-button').contains('blur').eq(0).click();
+        cy.get('[data-cy=range] .semi-input-wrapper-focus').should('not.exist');
+        cy.get('[data-cy=range] input').eq(0).should('not.be.focused');
+        cy.get('[data-cy=range] input').eq(1).should('not.be.focused');
+    });
+
+    it('test focus + blur + inset type', () => {
+        cy.visit('http://localhost:6006/iframe.html?id=datepicker--feat-ref-focus&args=&viewMode=story');
+        cy.get('[data-cy=inset] .semi-button').contains('focus start + open').click();
+        cy.get('.semi-datepicker-inset-input-wrapper input').eq(0).should('be.focused');
+        cy.get('[data-cy=inset] .semi-button').contains('focus end + open').click({ force: true });
+        cy.get('.semi-datepicker-inset-input-wrapper input').eq(1).should('be.focused');
+        cy.get('[data-cy=inset] .semi-button').contains('blur + close').eq(0).click({ force: true });
+        cy.get('[data-cy=inset] input').eq(0).should('not.be.focused');
+        cy.get('[data-cy=inset] input').eq(1).should('not.be.focused');
+    });
 });

+ 16 - 0
cypress/integration/treeSelect.spec.js

@@ -92,5 +92,21 @@ describe('treeSelect', () => {
         cy.get('.semi-tree-option').eq(0).click();
         cy.get('.semi-tree-select-selection-TriggerSearchItem').eq(0).should('contain.text', '亚洲');
     });
+
+    it('treeSelect clickTriggerToHide', () => {
+        // 测试 clickTriggerToHide API 是否符合预期
+        // 未设置 clickTriggerToHide, 默认为 true, 面板打开状态下再次点击会关闭
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=treeselect--click-trigger-to-hide');
+        cy.get('.semi-tree-select').eq(0).click();
+        cy.get('.semi-tree-select-popover').should('exist');
+        cy.get('.semi-tree-select').eq(0).click();
+        cy.get('.semi-tree-select-popover').should('not.exist');
+
+        // clickTriggerToHide 设置为 false, 面板打开的状态下再次点击不会关闭
+        cy.get('.semi-tree-select').eq(1).click();
+        cy.get('.semi-tree-select-popover').should('exist');
+        cy.get('.semi-tree-select').eq(1).click();
+        cy.get('.semi-tree-select-popover').should('exist');
+    });
 });
 

+ 1 - 1
lerna.json

@@ -1,5 +1,5 @@
 {
     "useWorkspaces": true,
     "npmClient": "yarn",
-    "version": "2.31.4"
+    "version": "2.32.0-beta.0"
 }

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-animation-react",
-    "version": "2.31.4",
+    "version": "2.32.0-beta.0",
     "description": "motion library for semi-ui-react",
     "keywords": [
         "motion",

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-animation-styled",
-    "version": "2.31.4",
+    "version": "2.32.0-beta.0",
     "description": "semi styled animation",
     "keywords": [
         "semi",

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-animation",
-    "version": "2.31.4",
+    "version": "2.32.0-beta.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.31.4",
+    "version": "2.32.0-beta.0",
     "description": "semi ui eslint plugin",
     "keywords": [
         "semi",

+ 8 - 1
packages/semi-foundation/cascader/foundation.ts

@@ -91,9 +91,16 @@ export interface BasicTriggerRenderProps {
      * should call this function when the value of the Input component
      * customized by triggerRender is updated to synchronize the state
      * with Cascader. */
+    onSearch: (inputValue: string) => void;
+    /* This function is the same as onSearch (supported since v2.32.0), 
+     * because this function was used before, and to align with TreeSelect, 
+     * use onSearch instead of onChange is more suitable, 
+     * onChange needs to be deleted in the next Major
+    */
     onChange: (inputValue: string) => void;
     /* Function to clear the value */
-    onClear: (e: any) => void
+    onClear: (e: any) => void;
+    onRemove: (key: string) => void
 }
 
 export interface BasicScrollPanelProps {

+ 1 - 0
packages/semi-foundation/datePicker/_utils/getDefaultFormatToken.ts

@@ -6,6 +6,7 @@ const defaultFormatTokens = {
     dateRange: strings.FORMAT_FULL_DATE,
     dateTimeRange: strings.FORMAT_DATE_TIME,
     month: strings.FORMAT_YEAR_MONTH,
+    monthRange: strings.FORMAT_YEAR_MONTH,
 } as const;
 
 const getDefaultFormatToken = (type: string) => defaultFormatTokens;

+ 1 - 0
packages/semi-foundation/datePicker/_utils/getInsetInputFormatToken.ts

@@ -31,6 +31,7 @@ export default function getInsetInputFormatToken(options: { format: string; type
             break;
         case 'date':
         case 'month':
+        case 'monthRange':
         case 'dateRange':
         default:
             const dateResult = dateReg.exec(format);

+ 1 - 0
packages/semi-foundation/datePicker/_utils/getInsetInputValueFromInsetInputStr.ts

@@ -34,6 +34,7 @@ export default function getInsetInputValueFromInsetInputStr(options: { inputValu
     switch (type) {
         case 'date':
         case 'month':
+        case 'monthRange':
             insetInputValue.monthLeft.dateInput = inputValue;
             break;
         case 'dateRange':

+ 1 - 1
packages/semi-foundation/datePicker/constants.ts

@@ -46,7 +46,7 @@ const strings = {
     DEFAULT_SEPARATOR_MULTIPLE: ',',
     DEFAULT_SEPARATOR_RANGE: ' ~ ',
     SIZE_SET: ['small', 'default', 'large'],
-    TYPE_SET: ['date', 'dateRange', 'year', 'month', 'dateTime', 'dateTimeRange'],
+    TYPE_SET: ['date', 'dateRange', 'year', 'month', 'monthRange', 'dateTime', 'dateTimeRange'],
     PRESET_POSITION_SET: ['left', 'right', 'top', 'bottom'],
     DENSITY_SET: ['default', 'compact'],
     PANEL_TYPE_LEFT: 'left',

+ 33 - 6
packages/semi-foundation/datePicker/datePicker.scss

@@ -169,11 +169,23 @@ $module-list: #{$prefix}-scrolllist;
     }
 
     // 年月选择器
-
     &-panel-yam {
         // add left or right preset panel to panel yam, max-width will be bigger
         max-width: $width-datepicker_monthPanel_max + $width-datepicker_presetPanel_left_and_right;
 
+        &[x-type="monthRange"] {
+            max-width: $width-datepicker_monthRangePanel_max + $width-datepicker_presetPanel_left_and_right;
+        }
+
+        .#{$module}-yearmonth-body {
+            display: flex;
+
+            .#{$prefix}-scrolllist:nth-child(2) {
+                border-left: 1px solid var(--semi-color-border);
+
+            }
+        }
+
         .#{$prefix}-scrolllist {
             box-shadow: none;
             height: $height-datepicker_panel_yam_scrolllist;
@@ -196,13 +208,14 @@ $module-list: #{$prefix}-scrolllist;
                 padding: 0;
                 overflow: hidden;
 
-                .#{$prefix}-scrolllist-item-wheel {
+                .#{$prefix}-scrolllist-item-wheel:not(#neverExistElement) {
+                    // equal to #{$prefix}-scrolllist-item-wheel, add [:not] selector only to increase selector priority
                     border: none;
                 }
 
-                .#{$prefix}-scrolllist-item {
-                    border: none;
-                }
+                // .#{$prefix}-scrolllist-item {
+                //     border: none;
+                // }
             }
         }
     }
@@ -832,6 +845,9 @@ $module-list: #{$prefix}-scrolllist;
             &[x-type=month] {
                 width: $width-datepicker_insetInput_month_type_wrapper;
             }
+            &[x-type=monthRange] {
+                width: $width-datepicker_insetInput_month_range_type_wrapper;
+            }
 
             .#{$prefix}-input-wrapper {
                 flex: 1;
@@ -869,7 +885,9 @@ $module-list: #{$prefix}-scrolllist;
                 border-color: $color-datepicker_range_trigger-border-active ;
             }
 
-
+            .#{$module}-monthRange-input {
+                background-color: transparent;
+            }
 
             &-wrapper {
                 box-sizing: border-box;
@@ -1108,6 +1126,15 @@ $module-list: #{$prefix}-scrolllist;
             padding: $spacing-datepicker_yam_panel_header_compact-padding;
         }
 
+        .#{$module}-yearmonth-body {
+            display: flex;
+
+            .#{$prefix}-scrolllist:nth-child(2){
+                border: 1px solid var(--semi-color-border);
+
+            }
+        }
+
         .#{$prefix}-scrolllist {
             @include font-size-small;
             line-height: $lineHeight-datepicker_compact;

+ 132 - 74
packages/semi-foundation/datePicker/foundation.ts

@@ -110,13 +110,11 @@ export interface EventHandlerProps {
     onPanelChange?: OnPanelChangeType;
     onConfirm?: OnConfirmType;
     // properties below need overwrite
-    // onBlur?: React.MouseEventHandler<HTMLInputElement>;
     onBlur?: (e: any) => void;
-    // onClear?: React.MouseEventHandler<HTMLDivElement>;
     onClear?: (e: any) => void;
-    // onFocus?: React.MouseEventHandler<HTMLInputElement>;
     onFocus?: (e: any, rangType: RangeType) => void;
-    onPresetClick?: OnPresetClickType
+    onPresetClick?: OnPresetClickType;
+    onClickOutSide?: () => void
 }
 
 export interface DatePickerFoundationProps extends ElementProps, RenderProps, EventHandlerProps {
@@ -191,7 +189,7 @@ export interface DatePickerFoundationState {
 export { Type, DateInputFoundationProps };
 
 export interface DatePickerAdapter extends DefaultAdapter<DatePickerFoundationProps, DatePickerFoundationState> {
-    togglePanel: (panelShow: boolean) => void;
+    togglePanel: (panelShow: boolean, cb?: () => void) => void;
     registerClickOutSide: () => void;
     unregisterClickOutSide: () => void;
     notifyBlur: DatePickerFoundationProps['onBlur'];
@@ -213,7 +211,10 @@ export interface DatePickerAdapter extends DefaultAdapter<DatePickerFoundationPr
     isEventTarget: (e: any) => boolean;
     updateInsetInputValue: (insetInputValue: InsetInputValue) => void;
     setInsetInputFocus: () => void;
-    setTriggerDisabled: (disabled: boolean) => void
+    setTriggerDisabled: (disabled: boolean) => void;
+    setInputFocus: () => void;
+    setInputBlur: () => void;
+    setRangeInputBlur: () => void
 }
  
 
@@ -224,6 +225,7 @@ export interface DatePickerAdapter extends DefaultAdapter<DatePickerFoundationPr
  */
 export default class DatePickerFoundation extends BaseFoundation<DatePickerAdapter> {
 
+    clickConfirmButton: boolean;
     constructor(adapter: DatePickerAdapter) {
         super({ ...adapter });
     }
@@ -245,8 +247,7 @@ export default class DatePickerFoundation extends BaseFoundation<DatePickerAdapt
         const result = this.parseWithTimezone(_value, timeZone, prevTimeZone);
         this._adapter.updatePrevTimezone(prevTimeZone);
         // reset input value when value update
-        this._adapter.updateInputValue(null);
-        this._adapter.updateInsetInputValue(null);
+        this.clearInputValue();
         this._adapter.updateValue(result);
         this.resetCachedSelectedValue(result);
         this.initRangeInputFocus(result);
@@ -332,7 +333,7 @@ export default class DatePickerFoundation extends BaseFoundation<DatePickerAdapt
 
     destroy() {
         // Ensure that event listeners will be uninstalled and users may not trigger closePanel
-        // this._adapter.togglePanel(false);
+        this._adapter.togglePanel(false);
         this._adapter.unregisterClickOutSide();
     }
 
@@ -349,45 +350,46 @@ export default class DatePickerFoundation extends BaseFoundation<DatePickerAdapt
     openPanel() {
         if (!this.getProp('disabled')) {
             if (!this._isControlledComponent('open')) {
-                this._adapter.togglePanel(true);
-                this._adapter.registerClickOutSide();
+                this.open();
             }
             this._adapter.notifyOpenChange(true);
         }
     }
 
     /**
+     * @deprecated
      * do these side effects when type is dateRange or dateTimeRange
      *   1. trigger input blur, if input value is invalid, set input value and state value to previous status
      *   2. set cachedSelectedValue using given dates(in needConfirm mode)
      *      - directly closePanel without click confirm will set cachedSelectedValue to state value
      *      - select one date(which means that the selection value is incomplete) and click confirm also set cachedSelectedValue to state value
      */
-    rangeTypeSideEffectsWhenClosePanel(inputValue: string, willUpdateDates: Date[]) {
-        if (this._isRangeType()) {
-            this._adapter.setRangeInputFocus(false);
-            /**
-             * inputValue is string when it is not disabled or can't parsed
-             * when inputValue is null, picker value will back to last selected value
-             */
-            this.handleInputBlur(inputValue);
-            this.resetCachedSelectedValue(willUpdateDates);
-        }
-    }
+    // rangeTypeSideEffectsWhenClosePanel(inputValue: string, willUpdateDates: Date[]) {
+    //     if (this._isRangeType()) {
+    //         this._adapter.setRangeInputFocus(false);
+    //         /**
+    //          * inputValue is string when it is not disabled or can't parsed
+    //          * when inputValue is null, picker value will back to last selected value
+    //          */
+    //         this.handleInputBlur(inputValue);
+    //         this.resetCachedSelectedValue(willUpdateDates);
+    //     }
+    // }
 
     /**
+     * @deprecated
      * clear input value when selected date is not confirmed
      */
-    needConfirmSideEffectsWhenClosePanel(willUpdateDates: Date[] | null | undefined) {
-        if (this._adapter.needConfirm() && !this._isRangeType()) {
-            /**
-             * if `null` input element will show `cachedSelectedValue` formatted value(format in DateInput render)
-             * if `` input element will show `` directly
-             */
-            this._adapter.updateInputValue(null);
-            this.resetCachedSelectedValue(willUpdateDates);
-        }
-    }
+    // needConfirmSideEffectsWhenClosePanel(willUpdateDates: Date[] | null | undefined) {
+    //     if (this._adapter.needConfirm() && !this._isRangeType()) {
+    //         /**
+    //          * if `null` input element will show `cachedSelectedValue` formatted value(format in DateInput render)
+    //          * if `` input element will show `` directly
+    //          */
+    //         this._adapter.updateInputValue(null);
+    //         this.resetCachedSelectedValue(willUpdateDates);
+    //     }
+    // }
 
     /**
      * clear inset input value when close panel
@@ -426,17 +428,92 @@ export default class DatePickerFoundation extends BaseFoundation<DatePickerAdapt
         const { value } = this._adapter.getStates();
         const willUpdateDates = isNullOrUndefined(dates) ? value : dates;
         if (!this._isControlledComponent('open')) {
-            this._adapter.togglePanel(false);
-            this._adapter.unregisterClickOutSide();
+            this.close();
+        } else {
+            this.resetInnerSelectedStates(willUpdateDates);
         }
-        // range type picker, closing panel requires the following side effects
-        this.rangeTypeSideEffectsWhenClosePanel(inputValue, willUpdateDates as Date[]);
-        this.needConfirmSideEffectsWhenClosePanel(willUpdateDates as Date[]);
-        this.clearInsetInputValue();
         this._adapter.notifyOpenChange(false);
+    }
+
+    open() {
+        this._adapter.togglePanel(true);
+        this._adapter.registerClickOutSide();
+    }
+
+    close() {
+        this._adapter.togglePanel(false, () => this.resetInnerSelectedStates());
+        this._adapter.unregisterClickOutSide();
+    }
+
+    focus(focusType?: Exclude<RangeType, false>) {
+        if (this._isRangeType()) {
+            const rangeInputFocus = focusType ?? 'rangeStart';
+            this._adapter.setRangeInputFocus(rangeInputFocus);
+        } else {
+            this._adapter.setInputFocus();
+        }
+    }
+
+    blur() {
+        if (this._isRangeType()) {
+            this._adapter.setRangeInputBlur();
+        } else {
+            this._adapter.setInputBlur();
+        }
+    }
+
+    /**
+     * reset cachedSelectedValue, inputValue when close panel
+     */
+    resetInnerSelectedStates(willUpdateDates?: Date[]) {
+        const { value } = this._adapter.getStates();
+        const needResetCachedSelectedValue = !this.isCachedSelectedValueValid(willUpdateDates) || this._adapter.needConfirm() && !this.clickConfirmButton;
+        if (needResetCachedSelectedValue) {
+            this.resetCachedSelectedValue(value);
+        }
+        this.resetFocus();
+        this.clearInputValue();
+        this.clickConfirmButton = false;
+    }
+
+    resetFocus(e?: any) {
+        this._adapter.setRangeInputFocus(false);
         this._adapter.notifyBlur(e);
     }
 
+    /**
+     * cachedSelectedValue can be `(Date|null)[]` or `null`
+     */
+    isCachedSelectedValueValid(dates: Date[]) {
+        const cachedSelectedValue = dates || this._adapter.getState('cachedSelectedValue');
+        const { type } = this._adapter.getProps();
+        let isValid = true;
+        switch (true) {
+            case type === 'dateRange':
+            case type === 'dateTimeRange':
+                if (!this._isRangeValueComplete(cachedSelectedValue)) {
+                    isValid = false;
+                }
+                break;
+            default:
+                const value = cachedSelectedValue?.filter(item => item);
+                if (!(Array.isArray(value) && value.length)) {
+                    isValid = false;
+                }
+                break;
+        }
+        return isValid;
+    }
+
+    /**
+     * 将输入框内容置空
+     */
+    clearInputValue() {
+        this._adapter.updateInputValue(null);
+        this._adapter.updateInsetInputValue(null);
+    }
+
+
     /**
      * clear range input focus when open is controlled
      * fixed github 1375
@@ -524,38 +601,8 @@ export default class DatePickerFoundation extends BaseFoundation<DatePickerAdapt
      * @param {String} input
      * @param {Event} e
      */
+    // eslint-disable-next-line @typescript-eslint/no-empty-function
     handleInputBlur(input = '', e?: any) {
-        const parsedResult = input ?
-            this._isMultiple() ?
-                this.parseMultipleInput(input, ',', true) :
-                this.parseInput(input) :
-            [];
-
-        const stateValue = this.getState('value');
-
-        // console.log(input, parsedResult);
-
-        if (parsedResult && parsedResult.length) {
-            this._updateValueAndInput(parsedResult, input === '');
-        } else if (input === '') {
-            // if clear input, set input to `''`
-            this._updateValueAndInput('' as any, true, '');
-        } else {
-            this._updateValueAndInput(stateValue);
-        }
-
-        /**
-         * 当不是范围类型且不需要确认时,使用 stateValue 重置 cachedSelectedValue
-         * 这样做的目的是,在输入非法值时,使用上次选中的值作为已选值
-         * needConfirm 或者 range type 时,我们在 close panel 时调用 resetCachedSelectedValue,这里不用重复调用
-         * 
-         * Use stateValue to reset cachedSelectedValue when it is not a range type and does not require confirmation
-         * The purpose of this is to use the last selected value as the selected value when an invalid value is entered
-         * When needConfirm or range type, we call resetCachedSelectedValue when close panel, no need to call repeatedly here
-         */
-        if (!this._adapter.needConfirm() && !this._isRangeType()) {
-            this.resetCachedSelectedValue(stateValue);
-        }
     }
 
     /**
@@ -671,6 +718,7 @@ export default class DatePickerFoundation extends BaseFoundation<DatePickerAdapt
                     break;
                 case 'dateRange':
                 case 'dateTimeRange':
+                case 'monthRange':
                     const separator = rangeSeparator;
                     const values = input.split(separator);
                     parsedResult =
@@ -864,6 +912,7 @@ export default class DatePickerFoundation extends BaseFoundation<DatePickerAdapt
 
                 case 'dateRange':
                 case 'dateTimeRange':
+                case 'monthRange':
                     const startIsTruthy = !isNullOrUndefined(dates[0]);
                     const endIsTruthy = !isNullOrUndefined(dates[1]);
                     if (startIsTruthy && endIsTruthy) {
@@ -903,6 +952,7 @@ export default class DatePickerFoundation extends BaseFoundation<DatePickerAdapt
                     break;
                 case 'dateRange':
                 case 'dateTimeRange':
+                case 'monthRange':
                     for (let i = 0; i < dates.length; i += 2) {
                         strs.push(this.formatDates(dates.slice(i, i + 2), customFormat));
                     }
@@ -1012,22 +1062,29 @@ export default class DatePickerFoundation extends BaseFoundation<DatePickerAdapt
     }
 
     /**
-     * when changing the year and month through the panel when the type is year or month
+     * when changing the year and month through the panel when the type is year or month or monthRange
      * @param {*} item
      */
-    handleYMSelectedChange(item: { currentMonth?: number; currentYear?: number } = {}) {
+    handleYMSelectedChange(item: { currentMonth?: { left: number; right: number }; currentYear?: { left: number; right: number } } = {}) {
         // console.log(item);
         const { currentMonth, currentYear } = item;
+        const { type } = this.getProps();
 
-        if (typeof currentMonth === 'number' && typeof currentYear === 'number') {
-            // Strings with only dates (e.g. "1970-01-01") will be treated as UTC instead of local time #1460
-            const date = new Date(currentYear, currentMonth - 1);
+        if (type === 'month') {
+            const date = new Date(currentYear['left'], currentMonth['left'] - 1);
 
             this.handleSelectedChange([date]);
+        } else {
+            const dateLeft = new Date(currentYear['left'], currentMonth['left'] - 1);
+            const dateRight = new Date(currentYear['right'], currentMonth['right'] - 1);
+
+            this.handleSelectedChange([dateLeft, dateRight]);
+
         }
     }
 
     handleConfirm() {
+        this.clickConfirmButton = true;
         const { cachedSelectedValue, value } = this._adapter.getStates();
         const isRangeValueComplete = this._isRangeValueComplete(cachedSelectedValue);
         const newValue = isRangeValueComplete ? cachedSelectedValue : value;
@@ -1127,6 +1184,7 @@ export default class DatePickerFoundation extends BaseFoundation<DatePickerAdapt
                 break;
             case 'dateRange':
             case 'dateTimeRange':
+            case 'monthRange':
                 notifyValue = _value.map(v => v && this.localeFormat(v, formatToken));
                 notifyDate = [..._value];
                 break;

+ 9 - 3
packages/semi-foundation/datePicker/inputFoundation.ts

@@ -17,7 +17,7 @@ const KEY_CODE_ENTER = 'Enter';
 const KEY_CODE_TAB = 'Tab';
 
 
-export type Type = 'date' | 'dateRange' | 'year' | 'month' | 'dateTime' | 'dateTimeRange';
+export type Type = 'date' | 'dateRange' | 'year' | 'month' | 'dateTime' | 'dateTimeRange' | 'monthRange';
 export type RangeType = 'rangeStart' | 'rangeEnd';
 export type PanelType = 'left' | 'right';
 
@@ -180,6 +180,9 @@ export default class InputFoundation extends BaseFoundation<DateInputAdapter> {
             case 'month':
                 text = formatDateValues(value, formatToken, undefined, dateFnsLocale);
                 break;
+            case 'monthRange':
+                text = formatDateValues(value, formatToken, { groupSize: 2, groupInnerSeparator: rangeSeparator }, dateFnsLocale);
+                break;
             default:
                 break;
         }
@@ -244,7 +247,7 @@ export default class InputFoundation extends BaseFoundation<DateInputAdapter> {
      * Otherwise the default format will be used as placeholder
      */
     getInsetInputPlaceholder() {
-        const { type, format } = this._adapter.getProps();
+        const { type, format, rangeSeparator } = this._adapter.getProps();
         const insetInputFormat = getInsetInputFormatToken({ type, format });
         let datePlaceholder, timePlaceholder;
 
@@ -257,6 +260,8 @@ export default class InputFoundation extends BaseFoundation<DateInputAdapter> {
             case 'dateTime':
             case 'dateTimeRange':
                 [datePlaceholder, timePlaceholder] = insetInputFormat.split(' ');
+            case 'monthRange':
+                datePlaceholder = insetInputFormat + rangeSeparator + insetInputFormat;
                 break;
         }
 
@@ -271,7 +276,7 @@ export default class InputFoundation extends BaseFoundation<DateInputAdapter> {
      * 
      * Parse out insetInputValue from current date value or inputValue
      */
-    getInsetInputValue({ value, insetInputValue } : { value: BaseValueType[]; insetInputValue: InsetInputValue }) {
+    getInsetInputValue({ value, insetInputValue }: { value: BaseValueType[]; insetInputValue: InsetInputValue }) {
         const { type, rangeSeparator, format } = this._adapter.getProps();
 
         let inputValueStr = '';
@@ -302,6 +307,7 @@ export default class InputFoundation extends BaseFoundation<DateInputAdapter> {
         switch (type) {
             case 'date':
             case 'month':
+            case 'monthRange':
                 inputValue = insetInputValue.monthLeft.dateInput;
                 break;
             case 'dateRange':

+ 6 - 4
packages/semi-foundation/datePicker/variables.scss

@@ -9,6 +9,7 @@ $height-datepicker_timeType_tpk: calc(100% - 54px); // 时间面板高度
 $height-datepicker_panel_yam_scrolllist:  266px; // 时间滚动内容高度
 
 $width-datepicker_monthPanel_max: 284px; // 年月选择器最大宽度
+$width-datepicker_monthRangePanel_max: 384px; // 年月选择器最大宽度
 $height-datepicker_timepicker_header_min: 24px; // 年月选择 header 最小高度
 $width-datepicker_navigation_button_min: 32px; // header 按钮最小宽度
 $height-datepicker_yamShowing_min: 378px; // 日期时间选择器菜单最小高度
@@ -30,7 +31,8 @@ $height-datepicker_timeType_insetInput_yam: 100%; // 时间面板高度 - 内嵌
 $height-datepicker_timeType_insetInput_tpk: 100%; // 时间面板高度 - 内嵌输入框
 $width-datepicker_insetInput_date_type_wrapper: 284px; // 日期类型内嵌输入框宽度
 $width-datepicker_insetInput_date_range_type_wrapper: $width-datepicker_insetInput_date_type_wrapper * 2; // 范围选择内嵌输入框宽度
-$width-datepicker_insetInput_month_type_wrapper: 204px; // 月份类型内嵌输入框宽度
+$width-datepicker_insetInput_month_type_wrapper: 165px; // 月份类型内嵌输入框宽度
+$width-datepicker_insetInput_month_range_type_wrapper: 331px; // 年月范围类型内嵌输入框宽度
 $height-datepicker_insetInput_separator: 32px;
 $height-datepicker_month_grid_yearType_insetInput: 317px;
 $height-datepicker_month_grid_timeType_insetInput: 317px;
@@ -222,7 +224,7 @@ $transition-datepicker_range_input: background-color .16s ease-in-out;
 $width-datepicker_presetPanel_left_and_right_content: $width-datepicker_presetPanel_left_and_right - $spacing-datepicker_quick_control_content-paddingX * 2; // 左右方位快捷选择面板,内容宽度
 $width-datepicker_presetPanel_top_and_bottom_content_date: $width-datepicker_day * 7 + $spacing-datepicker_month-padding * 2 - $spacing-datepicker_quick_control_top_and_bottom_content-paddingX * 2; // date/dateTime下, 上下方位快捷选择面板内容宽度, 默认(284 - 40)px
 $width-datepicker_presetPanel_top_and_bottom_content_range: ($width-datepicker_day * 7 + $spacing-datepicker_month-padding * 2) * 2 - $spacing-datepicker_quick_control_top_and_bottom_content-paddingX * 2; // dateRange/dateTimeRange下, 上下方位快捷选择内容面板宽度,默认528px
-$width-datepicker_presetPanel_top_and_bottom_content_month: 194px - $spacing-datepicker_quick_control_top_and_bottom_content-paddingX * 2; // month下,上下方位快捷选择内容面板宽度, 默认154px
+$width-datepicker_presetPanel_top_and_bottom_content_month: 165px - $spacing-datepicker_quick_control_top_and_bottom_content-paddingX * 2; // month下,上下方位快捷选择内容面板宽度, 默认154px
 
 $height-datepicker_month_max: $width-datepicker_day * 7 + 1px; // 年月面板最大高度, 最多6 + 1行,再加上一个border宽度, 默认253px
 $height-datepicker_month_max_compact: $width-datepicker_day_compact * 7 + $spacing-datepicker_weeks_compact-paddingTop + $spacing-datepicker_weeks_compact-padding + $spacing-datepicker_weekday_compact-paddingBottom; // 年月面板最大高度, 最多6 + 1行,再加上padding,默认220px
@@ -233,7 +235,7 @@ $height-datepicker_presetPanel_left_and_right_except_content: 20px + $spacing-da
 // compact
 $width-datepicker_presetPanel_top_and_bottom_content_date_compact: $width-datepicker_day_compact * 7 + $spacing-datepicker_weeks_compact-padding * 2 - $spacing-datepicker_quick_control_top_and_bottom_content_compact-paddingX * 2; // date/dateTime下, 上下方位快捷选择面板内容宽度, 默认(216 - 20)px
 $width-datepicker_presetPanel_top_and_bottom_content_range_compact: ($width-datepicker_day_compact * 7 + $spacing-datepicker_weeks_compact-padding * 2) * 2 - $spacing-datepicker_quick_control_top_and_bottom_content_compact-paddingX * 2; // dateRange/dateTimeRange下, 上下方位快捷选择内容面板宽度,默认412px
-$width-datepicker_presetPanel_top_and_bottom_content_month_compact: 194px - $spacing-datepicker_quick_control_top_and_bottom_content_compact-paddingX  * 2; // month下,上下方位快捷选择内容面板宽度, 默认174px
+$width-datepicker_presetPanel_top_and_bottom_content_month_compact: 165px - $spacing-datepicker_quick_control_top_and_bottom_content_compact-paddingX  * 2; // month下,上下方位快捷选择内容面板宽度, 默认174px
 
 $height-datepicker_date_panel_compact: $height-datepicker_month_max_compact + $width-datepicker_nav_compact + $spacing-datepicker_nav_compact-padding; // compact,date/dateRange,面板渲染最大高度,默认256px
 $height-datepicker_date_time_panel_compact: $height-datepicker_date_panel_compact + $height-datepicker_switch_compact; // compact,dateTime/dateTImeRange,面板渲染最大高度,默认288px
@@ -242,7 +244,7 @@ $height-datepicker_presetPanel_left_and_right_except_content_compact: 20px + $sp
 $width-datepicker_presetPanel_left_and_right_two_col_button: ($width-datepicker_presetPanel_left_and_right_content - $spacing-datepicker_quick_control_item-margin) * 0.5; // 左右方位快捷选择面板,固定两列,按钮宽度
 $width-datepicker_presetPanel_top_and_bottom_three_col_button: ($width-datepicker_presetPanel_top_and_bottom_content_date - $spacing-datepicker_quick_control_item-margin * 2) * 0.333; // 上下方位快捷选择面板,固定三列,按钮宽度
 $width-datepicker_presetPanel_top_and_bottom_five_col_button: ($width-datepicker_presetPanel_top_and_bottom_content_range - $spacing-datepicker_quick_control_item-margin * 4) * 0.2; // 上下方位快捷选择面板,固定五列,按钮宽度
-$width-datepicker_presetPanel_top_and_bottom_two_col_button:($width-datepicker_presetPanel_top_and_bottom_content_month - $spacing-datepicker_quick_control_item-margin) * 0.5; // 上下方位快捷选择面板,固定两列,按钮宽度
+$width-datepicker_presetPanel_top_and_bottom_two_col_button: ($width-datepicker_presetPanel_top_and_bottom_content_month - $spacing-datepicker_quick_control_item-margin) * 0.5; // 上下方位快捷选择面板,固定两列,按钮宽度
 
 // compact
 $width-datepicker_presetPanel_top_and_bottom_three_col_button_compact: ($width-datepicker_presetPanel_top_and_bottom_content_date_compact - $spacing-datepicker_quick_control_item-margin * 2) * 0.333; // 上下方位快捷选择面板,固定三列,按钮宽度

+ 65 - 18
packages/semi-foundation/datePicker/yearAndMonthFoundation.ts

@@ -1,11 +1,17 @@
 import { setMonth, setYear } from 'date-fns';
 import BaseFoundation, { DefaultAdapter } from '../base/foundation';
 import { PresetPosition } from './foundation';
+import { ArrayElement } from '../utils/type';
+import { strings } from './constants';
+import { PanelType } from './monthsGridFoundation';
+import { cloneDeep } from 'lodash';
+
+type Type = ArrayElement<typeof strings.TYPE_SET>;
 
 export interface YearAndMonthFoundationProps {
-    currentYear?: number;
-    currentMonth?: number;
-    onSelect?: (obj: { currentMonth: number; currentYear: number }) => void;
+    currentYear: { left: number; right: number };
+    currentMonth: { left: number; right: number };
+    onSelect?: (obj: { currentMonth: { left: number; right: number }; currentYear: { left: number; right: number } }) => void;
     onBackToMain?: () => void;
     locale?: any;
     localeCode?: string;
@@ -17,20 +23,23 @@ export interface YearAndMonthFoundationProps {
     presetPosition?: PresetPosition;
     renderQuickControls?: any;
     renderDateInput?: any;
+    type?: Type;
     yearAndMonthOpts?: any
 }
 
 export interface YearAndMonthFoundationState {
     years: Array<{ value: number; year: number }>;
     months: Array<{ value: number; month: number }>;
-    currentYear: number;
-    currentMonth: number
+    currentYear: { left: number; right: number };
+    currentMonth: { left: number; right: number }
 }
 export interface YearAndMonthAdapter extends DefaultAdapter<YearAndMonthFoundationProps, YearAndMonthFoundationState> {
-    setCurrentYear: (currentYear: number, cb?: () => void) => void;
-    setCurrentMonth: (currentMonth: number) => void;
-    notifySelectYear: (year: number) => void;
-    notifySelectMonth: (month: number) => void;
+    setCurrentYear: (currentYear: { left: number; right: number }, cb?: () => void) => void;
+    setCurrentMonth: (currentMonth: { left: number; right: number }) => void;
+    setCurrentYearAndMonth: (currentYear: { left: number; right: number }, currentMonth: { left: number; right: number }) => void;
+    notifySelectYear: (year: { left: number; right: number }) => void;
+    notifySelectMonth: (month: { left: number; right: number }) => void;
+    notifySelectYearAndMonth: (year: { left: number; right: number }, month: { left: number; right: number }) => void;
     notifyBackToMain: () => void
 }
 
@@ -60,14 +69,47 @@ export default class YearAndMonthFoundation extends BaseFoundation<YearAndMonthA
     // eslint-disable-next-line @typescript-eslint/no-empty-function
     destroy() {}
 
-    selectYear(item: YearScrollItem) {
-        const year = item.value;
-        this._adapter.setCurrentYear(year, () => this.autoSelectMonth(item));
+    selectYear(item: YearScrollItem, panelType?: PanelType) {
+        // const year = item.value;
+        const { currentYear, currentMonth } = this.getStates();
+        const { type } = this.getProps();
+        const left = strings.PANEL_TYPE_LEFT;
+        const right = strings.PANEL_TYPE_RIGHT;
+
+        const year = cloneDeep(currentYear);
+        year[panelType] = item.value;
+
+        // make sure the right panel time is always less than the left panel time
+        if (type === 'monthRange') {
+            const isSameYearIllegalDate = year[left] === year[right] && currentMonth[left] > currentMonth[right];
+            if ((panelType === left && item.value > year[right]) || (panelType === left && isSameYearIllegalDate)) {
+                // 1. select left year and left year > right year
+                // 2. select left year, left year = right year, but left date > right date
+                year[right] = item.value + 1;
+            } else if (panelType === right && isSameYearIllegalDate) {
+                // 1. select right year, left year = right year, but left date > right date
+                year[left] = item.value - 1;
+            }
+        }
+
+        this._adapter.setCurrentYear(year, () => this.autoSelectMonth(item, panelType, year));
         this._adapter.notifySelectYear(year);
     }
 
-    selectMonth(item: MonthScrollItem) {
-        const { month } = item;
+    selectMonth(item: MonthScrollItem, panelType?: PanelType) {
+        const { currentMonth, currentYear } = this.getStates();
+        const { type } = this.getProps();
+        const left = strings.PANEL_TYPE_LEFT;
+        const right = strings.PANEL_TYPE_RIGHT;
+
+        const month = cloneDeep(currentMonth);
+        month[panelType] = item.month;
+
+        // make sure the right panel time is always less than the left panel time
+        if (type === 'monthRange' && panelType === left && currentYear[left] === currentYear[right] && item.value > month[right]) {
+            month[right] = item.month + 1;
+        } 
+
         this._adapter.setCurrentMonth(month);
         this._adapter.notifySelectMonth(month);
     }
@@ -75,14 +117,14 @@ export default class YearAndMonthFoundation extends BaseFoundation<YearAndMonthA
     /**
      * After selecting a year, if the currentMonth is disabled, automatically select a non-disabled month
      */
-    autoSelectMonth(item: YearScrollItem) {
+    autoSelectMonth(item: YearScrollItem, panelType: PanelType, year: { left: number; right: number }) {
         const { disabledDate, locale } = this._adapter.getProps();
         const { months, currentMonth } = this._adapter.getStates();
 
         const currentDate = setYear(Date.now(), item.year);
-        const isCurrentMonthDisabled = disabledDate(setMonth(currentDate, currentMonth - 1));
+        const isCurrentMonthDisabled = disabledDate(setMonth(currentDate, currentMonth[panelType] - 1));
         if (isCurrentMonthDisabled) {
-            const currentIndex = months.findIndex(({ month }) => month === currentMonth);
+            const currentIndex = months.findIndex(({ month }) => month === currentMonth[panelType]);
             let validMonth: typeof months[number];
             // First look in the back, if you can't find it in the back, then look in the front
             validMonth = months.slice(currentIndex).find(({ month }) => !disabledDate(setMonth(currentDate, month - 1)));
@@ -90,7 +132,12 @@ export default class YearAndMonthFoundation extends BaseFoundation<YearAndMonthA
                 validMonth = months.slice(0, currentIndex).find(({ month }) => !disabledDate(setMonth(currentDate, month - 1)));
             }
             if (validMonth) {
-                this.selectMonth({ month: validMonth.month, value: locale.fullMonths[validMonth.month], disabled: false });
+                const month = cloneDeep(currentMonth);
+                month[panelType] = validMonth.month;
+
+                // change year and month same time
+                this._adapter.setCurrentYearAndMonth(year, month);
+                this._adapter.notifySelectYearAndMonth(year, month);
             }
         }
     }

+ 1 - 0
packages/semi-foundation/modal/modal.scss

@@ -76,6 +76,7 @@ $module: #{$prefix}-modal;
     &-content-fullScreen {
         border-radius: $radius-modal_content_fullscreen;
         border: none;
+        top: $spacing-modal_content_fullscreen-top;
     }
 
     // &-close {

+ 1 - 0
packages/semi-foundation/modal/variables.scss

@@ -43,6 +43,7 @@ $spacing-modal_footer_button-marginLeft: $spacing-base-tight; // 模态框 foote
 $spacing-modal_footer_button-marginRight: 0; // 模态框 footer 按钮右侧外边距
 $spacing-modal_confirm_header-marginBottom: 8px; // 命令式调用模态框 header 底部外边距
 $spacing-modal_confirm_icon_wrapper-marginRight: $spacing-base-tight; // 命令式调用模态框图标右侧外边距
+$spacing-modal_content_fullscreen-top: 0px; // 模态框内容全屏顶部位置
 
 // Width/Height
 $width-modal_title: 100%; // 模态框标题宽度

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-foundation",
-    "version": "2.31.4",
+    "version": "2.32.0-beta.0",
     "description": "",
     "scripts": {
         "build:lib": "node ./scripts/compileLib.js",

+ 15 - 0
packages/semi-foundation/sideSheet/sideSheet.scss

@@ -95,6 +95,7 @@ $module: #{$prefix}-sidesheet;
         align-items: flex-start;
         padding: $spacing-sideSheet_header-padding;
         padding-bottom: $spacing-sideSheet_header-paddingBottom;
+        border-bottom: $width-sideSheet_header-borderBottom solid $color-sideSheet_header-borderBottom;
     }
 
     &-body {
@@ -103,6 +104,20 @@ $module: #{$prefix}-sidesheet;
         overflow: auto;
     }
 
+    &-size-small {
+        width: $width-sideSheet_size-small;
+    }
+
+    &-size-medium {
+        width: $width-sideSheet_size-medium;
+    }
+
+    &-size-large {
+        width: $width-sideSheet_size-large;
+    }
+
+
+
     &-content {
         height: 100%;
         display: flex;

+ 6 - 0
packages/semi-foundation/sideSheet/variables.scss

@@ -2,6 +2,7 @@
 $color-sideSheet_mask-bg: var(--semi-color-overlay-bg); // 侧边栏打开后蒙层颜色
 $color-sideSheet-bg: var(--semi-color-bg-2); // 侧边栏背景颜色
 $color-sideSheet_main-text: var(--semi-color-text-0); // 侧边栏默认文本颜色
+$color-sideSheet_header-borderBottom: var(--semi-color-border); // 侧边栏 header 底部边框颜色
 
 // Spacing
 $spacing-sideSheet-margin: 0; // 侧边栏整体外边距
@@ -12,6 +13,11 @@ $spacing-sideSheet_body-paddingY: 0; // 侧边栏 body 垂直内边距
 $spacing-sideSheet_body-paddingX: $spacing-loose; // 侧边栏 body 水平内边距
 $spacing-sideSheet_footer-padding: $spacing-loose; // 侧边栏 footer 内边距
 
+$width-sideSheet_header-borderBottom: 0px; // 侧边栏 header 底部边框宽度
+$width-sideSheet_size-small: 448px; // 小尺寸侧边栏宽度
+$width-sideSheet_size-medium: 684px; // 中尺寸侧边栏宽度
+$width-sideSheet_size-large: 920px; // 大尺寸侧边栏宽度
+
 // Font
 $font-sideSheet_title-fontWeight: $font-weight-bold; // 侧边栏标题文本字重
 $font-sideSheet_title-fontSize: $font-size-header-5; // 侧边栏标题文本字号

+ 6 - 3
packages/semi-foundation/treeSelect/foundation.ts

@@ -47,7 +47,9 @@ export interface BasicTriggerRenderProps {
     inputValue: string;
     placeholder: string;
     value: BasicTreeNodeData[];
-    onClear: (e: any) => void
+    onClear: (e: any) => void;
+    onSearch: (inputValue: string) => void;
+    onRemove: (key: string) => void
 }
 
 export type BasicOnChangeWithObject = (node: BasicTreeNodeData[] | BasicTreeNodeData, e: any) => void;
@@ -128,6 +130,7 @@ export interface BasicTreeSelectProps extends Pick<BasicTreeProps,
     loadedKeys?: string[];
     showRestTagsPopover?: boolean;
     restTagsPopoverProps?: any;
+    clickTriggerToHide?: boolean;
     loadData?: (data: BasicTreeNodeData) => Promise<void>;
     onSelect?: (selectedKeys: string, selected: boolean, selectedNode: BasicTreeNodeData) => void;
     searchRender?: (inputProps: any) => any;
@@ -481,7 +484,7 @@ export default class TreeSelectFoundation<P = Record<string, any>, S = Record<st
     handleClick(e: any) {
         const isDisabled = this._isDisabled();
         const { isOpen, inputValue, isFocus } = this.getStates();
-        const { searchPosition } = this.getProps();
+        const { searchPosition, clickTriggerToHide } = this.getProps();
         if (isDisabled) {
             return;
         } else {
@@ -492,7 +495,7 @@ export default class TreeSelectFoundation<P = Record<string, any>, S = Record<st
                 if (searchPosition === 'trigger' && inputValue) {
                     return;
                 }
-                this.close(e);
+                clickTriggerToHide && this.close(e);
             } else {
                 this.open();
             }

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-icons",
-    "version": "2.31.4",
+    "version": "2.32.0-beta.0",
     "description": "semi icons",
     "keywords": [
         "semi",

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-illustrations",
-    "version": "2.31.4",
+    "version": "2.32.0-beta.0",
     "description": "semi illustrations",
     "keywords": [
         "semi",

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-next",
-    "version": "2.31.4",
+    "version": "2.32.0-beta.0",
     "description": "Plugin that support Semi Design in Next.js",
     "author": "伍浩威 <[email protected]>",
     "homepage": "",

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-scss-compile",
-    "version": "2.31.4",
+    "version": "2.32.0-beta.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.31.4",
+    "version": "2.32.0-beta.0",
     "description": "semi-theme-default",
     "keywords": [
         "semi-theme",

+ 106 - 2
packages/semi-ui/cascader/_story/cascader.stories.jsx

@@ -1,6 +1,7 @@
-import React, { useState, useCallback, useEffect, useRef } from 'react';
+import React, { useState, useCallback, useEffect, useRef, useMemo } from 'react';
 import CustomTrigger from './CustomTrigger';
-import { Button, Typography, Toast, Cascader, Checkbox } from '../../index';
+import { IconChevronDown, IconClose } from '@douyinfe/semi-icons';
+import { Button, Typography, Toast, Cascader, Checkbox, Input, Tag, TagInput } from '../../index';
 
 const { Text } = Typography;
 
@@ -1981,3 +1982,106 @@ export const setValueInSearch = () => {
       </div>
   );
 }
+
+export const TriggerAddMethods = () => {
+  const treeData = useMemo(() => [
+      {
+          label: '浙江省',
+          value: 'zhejiang',
+          children: [
+              {
+                  label: '杭州市',
+                  value: 'hangzhou',
+                  children: [
+                      {
+                          label: '西湖区',
+                          value: 'xihu',
+                      },
+                      {
+                          label: '萧山区',
+                          value: 'xiaoshan',
+                      },
+                      {
+                          label: '临安区',
+                          value: 'linan',
+                      },
+                  ],
+              },
+              {
+                  label: '宁波市',
+                  value: 'ningbo',
+                  children: [
+                      {
+                          label: '海曙区',
+                          value: 'haishu',
+                      },
+                      {
+                          label: '江北区',
+                          value: 'jiangbei',
+                      }
+                  ]
+              },
+          ],
+      }
+  ], []);
+
+  const closeIcon = useCallback((value, onClear) => {
+      return value ? <IconClose onClick={onClear} /> : <IconChevronDown />;
+  }, []);
+
+  const triggerRenderSingle = ({ value, placeholder, onClear, ...rest }) => {
+      return (
+          <Button theme={'light'} icon={closeIcon(value, onClear)} iconPosition={'right'}>
+              {value && value.length > 0 ? getLabelFromValue(value) : placeholder}
+          </Button>
+      );
+  };
+
+  const getLabelFromValue = useCallback((value) => {
+      const valueArr = value.split('-').map(item => Number(item));
+      let resultData = treeData;
+      valueArr.forEach((item, index) => {
+          resultData = index === 0 ? resultData[item] : resultData.children[item];
+      });
+      return resultData.label;
+  }, [treeData]);
+
+  const triggerRenderMultiple = useCallback((props) => {
+      const { value, onSearch, onRemove } = props;
+      const onCloseTag = (value, e, tagKey) => {
+          onRemove(tagKey);
+      };
+
+      const renderTagItem = (value) => {
+          const label = getLabelFromValue(value);
+          return <Tag tagKey={value} key={value} closable onClose={onCloseTag} style={{ marginLeft: 2 }}>{label}</Tag>
+      };
+      
+      return (
+          <TagInput
+              value={Array.from(value)}
+              onInputChange={onSearch}
+              renderTagItem={renderTagItem}
+          />
+      );
+  }, []);
+
+  return (
+      <>
+          <Cascader
+              treeData={treeData}
+              placeholder='Custom Trigger'
+              triggerRender={triggerRenderSingle}
+          />
+          <br />
+          <Cascader
+              triggerRender={triggerRenderMultiple}
+              multiple
+              filterTreeNode
+              treeData={treeData}
+              style={{ width: 300 }}
+              placeholder='Custom Trigger'
+          />
+      </>
+  );
+}

+ 7 - 0
packages/semi-ui/cascader/index.tsx

@@ -499,6 +499,11 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
         this.foundation.handleTagRemove(e, tagValuePath);
     };
 
+    handleRemoveByKey = (key) => {
+        const { keyEntities } = this.state;
+        this.handleTagRemove(null, keyEntities[key].valuePath);
+    }
+
     renderTagItem = (value: string | Array<string>, idx: number, type: string) => {
         const { keyEntities, disabledKeys } = this.state;
         const { size, disabled, displayProp, displayRender, disableStrictly } = this.props;
@@ -829,6 +834,8 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
                 triggerRender={triggerRender}
                 componentName={'Cascader'}
                 componentProps={{ ...this.props }}
+                onSearch={this.handleInputChange}
+                onRemove={this.handleRemoveByKey}
             />
         );
     };

+ 122 - 1
packages/semi-ui/datePicker/_story/datePicker.stories.jsx

@@ -1,4 +1,4 @@
-import React, { useState, useRef } from 'react';
+import React, { useState, useRef, useMemo, useCallback } from 'react';
 import {
   addDays,
   addWeeks,
@@ -37,7 +37,11 @@ import DatePickerTimeZone from './DatePickerTimeZone';
 import BetterRangePicker from './BetterRangePicker';
 import SyncSwitchMonth from './SyncSwitchMonth';
 import { Checkbox } from '../../checkbox';
+import Typography from '../../typography/typography';
+import { IconClose, IconChevronDown } from '@douyinfe/semi-icons';
 export * from './v2';
+import * as dateFns from 'date-fns';
+
 
 export default {
   title: 'DatePicker',
@@ -311,6 +315,20 @@ export const DatePickerWithPresets = () => {
         onPresetClick={onPresetClick}
         onChange={(...args) => console.log(...args)}
       />
+      {/* <br/>
+      <br/>
+      <div>type="monthRange"</div>
+      <DatePicker
+        type="monthRange"
+        insetInput={insetInput}
+        presetPosition={presetPosition}
+        presets={presetArr.map(preset => ({
+          text: preset.text,
+          start: preset.start,
+        }))}
+        onPresetClick={onPresetClick}
+        onChange={(...args) => console.log(...args)}
+      /> */}
       <br/>
       <br/>
       <div>type="date" density="compact"</div>
@@ -479,6 +497,109 @@ export const MonthPicker = () => {
   return <Demo />;
 };
 
+export const MonthRangePicker = () => {
+    const { Text } = Typography;
+    const formatToken = 'yyyy-MM';
+    const [controlledValue, setControlledValue] = useState(['2023-03', '2023-04']);
+    const [triggerValue, setTriggerValue] = useState();
+
+    const _setControlledValue = value => setControlledValue(value);
+
+    const onChange = useCallback(date => {
+      setTriggerValue(date);
+    }, []);
+
+    const onClear = useCallback(e => {
+        e && e.stopPropagation();
+        setTriggerValue(null);
+    }, []);
+
+    const closeIcon = useMemo(() => {
+        return triggerValue ? <IconClose onClick={onClear} /> : <IconChevronDown />;
+    }, [triggerValue]);
+
+    const triggerContent = (placeholder) => {
+        if (Array.isArray(triggerValue) && triggerValue.length) {
+            return `${dateFns.format(triggerValue[0], formatToken)} ~ ${dateFns.format(triggerValue[1], formatToken)}`;
+        } else {
+            return '请选择年月时间范围';
+        }
+    };
+
+    const TopSlot = function(props) {
+      const { style } = props;
+      return (
+          <Space style={{ padding: '12px 20px', ...style }}>
+              <Text strong style={{ color: 'var(--semi-color-text-2)' }}>
+                  请选择月份范围
+              </Text>
+          </Space>
+      );
+    };
+
+    const BottomSlot = function(props) {
+      const { style } = props;
+      return (
+          <Space style={{ padding: '12px 20px', ...style }}>
+              <Text strong style={{ color: 'var(--semi-color-text-2)' }}>
+                  定版前请阅读
+              </Text>
+              <Text link={{ href: 'https://semi.design/', target: '_blank' }}>发版须知</Text>
+          </Space>
+      );
+    };
+
+    const disabledDate = date => {
+        const deadDate = new Date('2023/3/1 00:00:00');
+        return date.getTime() < deadDate.getTime(); 
+    };
+
+    return (
+      <>
+        <div>
+          <div>default</div>
+          <DatePicker type="monthRange" />
+          <br />
+          <br />
+          <div>rangeSeparator 与 placeholder</div>
+          <DatePicker type="monthRange" rangeSeparator={'➡️'} placeholder={['开始', '结束']} insetLabel='月份范围'/>
+          <br />
+          <br />
+          <div>受控</div>
+          <DatePicker type="monthRange" bottomSlot={<BottomSlot />} topSlot={<TopSlot />} value={controlledValue} onChange={_setControlledValue}/>
+          <br />
+          <br />
+          <div>insetInput ➕ format</div>
+          <div data-cy="monthRange">
+            <DatePicker type="monthRange" insetInput format={'yyyy年MM月'} rangeSeparator={'到'} defaultValue={['2023-03', '2023-04']} style={{ width: 400 }}/>
+          </div>
+          <br />
+          <div>triggerRender</div>
+          <DatePicker 
+            type="monthRange" 
+            value={triggerValue} 
+            onChange={onChange}
+            triggerRender={({ placeholder }) => (
+                <Button theme={'light'} icon={closeIcon} iconPosition='right'>
+                    {triggerContent(placeholder)}
+                </Button>
+            )}
+          />
+          <br />
+          <br />
+          <div>年月禁用:禁用2023年3月前的所有年月</div>
+          <DatePicker type="monthRange" disabledDate={disabledDate}/>
+          <br />
+          <br />
+          <div>validateStatus</div>
+          <DatePicker type="monthRange" validateStatus='warning' />
+          <br />
+          <DatePicker type="monthRange" validateStatus='error' />
+        </div>
+      </>
+    );
+};
+
 export const PropTypesAndDefaultProps = () => (
   <div>
     <article>

+ 24 - 0
packages/semi-ui/datePicker/_story/v2/FeatOnClickOutside.tsx

@@ -0,0 +1,24 @@
+import React, { useRef } from 'react';
+import type { BaseDatePicker } from '../../index';
+import { DatePicker, Space } from '../../../index';
+
+/**
+ * test in cypress
+ */
+export default function Demo() {
+    const ref = useRef<BaseDatePicker>();
+
+    const handleClickOutside = () => {
+        console.log('click outside');
+        ref.current && ref.current.close();
+    };
+
+    return (
+        <DatePicker motion={false} type="dateTime" needConfirm ref={ref} onClickOutSide={handleClickOutside} />
+    );
+}
+
+Demo.storyName = 'onClickOutside';
+Demo.parameters = {
+    chromatic: { disableSnapshot: false },
+};

+ 39 - 0
packages/semi-ui/datePicker/_story/v2/FeatRefClass.tsx

@@ -0,0 +1,39 @@
+import React from 'react';
+import { DatePicker, Space, Button } from '../../../index';
+import type { BaseDatePicker } from '../../index';
+
+class FeatRefClass extends React.Component {
+    ref: React.RefObject<BaseDatePicker>;
+    constructor(props) {
+        super(props);
+        this.ref = React.createRef();
+    }
+
+    handleFocus() {
+        console.log('focus');
+    }
+
+    render() {
+        return (
+            <Space vertical align={'start'}>
+                <Space>
+                    <Button onClick={() => this.ref.current.open()}>open</Button>
+                    <Button onClick={() => this.ref.current.close()}>close</Button>
+                </Space>
+                <div>
+                    <DatePicker motion={false} type="dateTime" needConfirm ref={this.ref} />
+                </div>
+            </Space>
+        );
+    }
+}
+
+Demo.storyName = 'ref class 写法';
+Demo.parameters = {
+    chromatic: { disableSnapshot: false },
+};
+export default function Demo() {
+    return (
+        <FeatRefClass />
+    );
+}

+ 64 - 0
packages/semi-ui/datePicker/_story/v2/FeatRefFocus.tsx

@@ -0,0 +1,64 @@
+import React, { useRef } from 'react';
+import type { BaseDatePicker } from '../../index';
+import { DatePicker, Space, Button } from '../../../index';
+
+/**
+ * test in cypress
+ */
+export default function Demo() {
+    const ref = useRef<BaseDatePicker>();
+    const rangeRef = useRef<BaseDatePicker>();
+    const insetRef = useRef<BaseDatePicker>();
+
+    const handleFocusInset = (focusType) => {
+        insetRef.current.focus(focusType);
+        insetRef.current.open();
+    };
+
+    const handleBlurInset = () => {
+        insetRef.current.blur();
+        insetRef.current.close();
+    };
+
+    return (
+        <Space vertical align={'start'}>
+            <Space vertical align={'start'} data-cy="single">
+                <h4>单个输入框</h4>
+                <Space>
+                    <Button onClick={() => ref.current.focus()}>focus</Button>
+                    <Button onClick={() => ref.current.blur()}>blur</Button>
+                </Space>
+                <div>
+                    <DatePicker motion={false} type="dateTime" ref={ref} />
+                </div>
+            </Space>
+            <Space vertical align={'start'} data-cy="range">
+                <h4>两个输入框</h4>
+                <Space>
+                    <Button onClick={() => rangeRef.current.focus()}>focus default</Button>
+                    <Button onClick={() => rangeRef.current.focus('rangeEnd')}>focus end</Button>
+                    <Button onClick={() => rangeRef.current.blur()}>blur</Button>
+                </Space>
+                <div>
+                    <DatePicker motion={false} type="dateRange" ref={rangeRef} />
+                </div>
+            </Space>
+            <Space vertical align={'start'} data-cy="inset">
+                <h4>内嵌输入框</h4>
+                <Space>
+                    <Button onClick={handleFocusInset}>focus start + open</Button>
+                    <Button onClick={() => handleFocusInset('rangeEnd')}>focus end + open</Button>
+                    <Button onClick={handleBlurInset}>blur + close</Button>
+                </Space>
+                <div>
+                    <DatePicker motion={false} insetInput type="dateRange" ref={insetRef} />
+                </div>
+            </Space>
+        </Space>
+    );
+}
+
+Demo.storyName = 'ref focus & blur';
+Demo.parameters = {
+    chromatic: { disableSnapshot: false },
+};

+ 31 - 0
packages/semi-ui/datePicker/_story/v2/FeatRefOpen.tsx

@@ -0,0 +1,31 @@
+import React, { useRef } from 'react';
+import type { BaseDatePicker } from '../../index';
+import { DatePicker, Space, Button } from '../../../index';
+
+/**
+ * test in cypress
+ */
+export default function Demo() {
+    const ref = useRef<BaseDatePicker>();
+
+    const handleClickOutside = () => {
+        console.log('click outside');
+    };
+
+    return (
+        <Space vertical align={'start'}>
+            <Space>
+                <Button onClick={() => ref.current.open()}>open</Button>
+                <Button onClick={() => ref.current.close()}>close</Button>
+            </Space>
+            <div>
+                <DatePicker motion={false} type="dateTime" needConfirm ref={ref} onClickOutSide={handleClickOutside} />
+            </div>
+        </Space>
+    );
+}
+
+Demo.storyName = 'ref open & close';
+Demo.parameters = {
+    chromatic: { disableSnapshot: false },
+};

+ 46 - 0
packages/semi-ui/datePicker/_story/v2/FixNeedConfirmInTabs.tsx

@@ -0,0 +1,46 @@
+import React, { useRef } from 'react';
+import type { BaseDatePicker } from '../../index';
+import { DatePicker, Tabs, Space, Button } from '../../../index';
+
+/**
+ * test in cypress
+ */
+export default function Demo() {
+    const ref = useRef<BaseDatePicker>();
+    const ref2 = useRef<BaseDatePicker>();
+    const TabPane = Tabs.TabPane;
+
+    const handleClickOutside = () => {
+        console.log('click outside');
+    };
+
+    const handleTabChange = (activeKey: string) => {
+        if (activeKey === '1') {
+            ref2.current?.close();
+        }
+        if (activeKey === '2') {
+            ref.current?.close();
+        }
+    };
+
+    return (
+        <Space vertical align="start">
+            <Space>
+                <Button onClick={() => ref.current.close()}>close</Button>
+            </Space>
+            <Tabs type="line" onChange={handleTabChange}>
+                <TabPane tab="文档" itemKey="1">
+                    <DatePicker motion={false} type="dateTime" needConfirm ref={ref} onClickOutSide={handleClickOutside} />
+                </TabPane>
+                <TabPane tab="快速起步" itemKey="2">
+                    <DatePicker motion={false} type="dateTimeRange" needConfirm ref={ref2} onClickOutSide={handleClickOutside} />
+                </TabPane>
+            </Tabs>
+        </Space>
+    );
+}
+
+Demo.storyName = 'fix needConfirm in Tabs';
+Demo.parameters = {
+    chromatic: { disableSnapshot: false },
+};

+ 5 - 0
packages/semi-ui/datePicker/_story/v2/index.js

@@ -16,3 +16,8 @@ export { default as InsetInputControlled } from './InsetInputControlled';
 export { default as FeatInsetInputProps } from './FeatInsetInputProps';
 export { default as FixMultiplePanelShift } from './FixMultiplePanelShift';
 export { default as FixTimeZone } from './FixTimeZone';
+export { default as FeatRefOpen } from './FeatRefOpen';
+export { default as FeatRefFocus } from './FeatRefFocus';
+export { default as FeatOnClickOutside } from './FeatOnClickOutside';
+export { default as FeatRefClass } from './FeatRefClass';
+export { default as FixNeedConfirmInTabs } from './FixNeedConfirmInTabs';

+ 16 - 6
packages/semi-ui/datePicker/dateInput.tsx

@@ -35,7 +35,10 @@ export interface DateInputProps extends DateInputFoundationProps, BaseProps {
     onFocus?: (e: React.MouseEvent<HTMLInputElement>, rangeType?: RangeType) => void;
     onClear?: (e: React.MouseEvent<HTMLDivElement>) => void;
     onInsetInputChange?: (options: InsetInputChangeProps) => void;
-    value?: Date[]
+    value?: Date[];
+    inputRef?: React.RefObject<HTMLInputElement>;
+    rangeInputStartRef?: React.RefObject<HTMLInputElement>;
+    rangeInputEndRef?: React.RefObject<HTMLInputElement>
 }
 
 // eslint-disable-next-line @typescript-eslint/ban-types
@@ -304,6 +307,12 @@ export default class DateInput extends BaseComponent<DateInputProps, {}> {
         );
     }
 
+    isRenderMultipleInputs() {
+        const { type } = this.props;
+        // isRange and not monthRange render multiple inputs
+        return type.includes('Range') && type !== 'monthRange';
+    }
+
     renderInputInset() {
         const {
             type,
@@ -318,7 +327,6 @@ export default class DateInput extends BaseComponent<DateInputProps, {}> {
             insetInput,
         } = this.props;
 
-        const _isRangeType = type.includes('Range');
         const newInsetInputValue = this.foundation.getInsetInputValue({ value, insetInputValue });
         const { dateStart, dateEnd, timeStart, timeEnd } = get(insetInput, 'placeholder', {}) as InsetInputProps['placeholder'];
         const { datePlaceholder, timePlaceholder } = this.foundation.getInsetInputPlaceholder();
@@ -345,7 +353,7 @@ export default class DateInput extends BaseComponent<DateInputProps, {}> {
                     onChange={this.handleInsetInputChange}
                     onFocus={handleInsetTimeFocus}
                 />
-                {_isRangeType && (
+                { this.isRenderMultipleInputs() && (
                     <>
                         <div className={separatorCls}>{density === 'compact' ? null : '-'}</div>
                         <InsetDateInput
@@ -393,6 +401,7 @@ export default class DateInput extends BaseComponent<DateInputProps, {}> {
             prefix,
             autofocus,
             size,
+            inputRef,
             // range input support props, no need passing to not range type
             rangeInputStartRef,
             rangeInputEndRef,
@@ -419,23 +428,24 @@ export default class DateInput extends BaseComponent<DateInputProps, {}> {
 
         const inputCls = cls({
             [`${prefixCls}-input-readonly`]: inputReadOnly,
+            [`${prefixCls}-monthRange-input`]: type === 'monthRange',
         });
 
-        const isRangeType = /range/i.test(type);
         const rangeProps = { ...this.props, text, suffix, inputCls };
 
-        return isRangeType ? (
+        return this.isRenderMultipleInputs() ? (
             this.renderRangeInput(rangeProps)
         ) : (
             <Input
                 {...rest}
+                ref={inputRef}
                 insetLabel={insetLabel}
                 disabled={disabled}
                 readonly={inputReadOnly}
                 className={inputCls}
                 style={inputStyle}
                 hideSuffix={showClear}
-                placeholder={placeholder}
+                placeholder={type === 'monthRange' && Array.isArray(placeholder) ? placeholder[0] + rangeSeparator + placeholder[1] : placeholder}
                 onEnterPress={this.handleEnterPress}
                 onChange={this.handleChange}
                 onClear={this.handleInputClear}

+ 97 - 22
packages/semi-ui/datePicker/datePicker.tsx

@@ -40,10 +40,21 @@ export interface DatePickerProps extends DatePickerFoundationProps {
     renderDate?: (dayNumber?: number, fullDate?: string) => React.ReactNode;
     renderFullDate?: (dayNumber?: number, fullDate?: string, dayStatus?: DayStatusType) => React.ReactNode;
     triggerRender?: (props: DatePickerProps) => React.ReactNode;
+    /**
+     * There are multiple input boxes when selecting a range, and the input boxes will be out of focus multiple times. 
+     * 
+     * Use `onOpenChange` or `onClickOutSide` instead
+     */
     onBlur?: React.MouseEventHandler<HTMLInputElement>;
     onClear?: React.MouseEventHandler<HTMLDivElement>;
+    /**
+     * There are multiple input boxes when selecting a range, and the input boxes will be focused multiple times.
+     * 
+     * Use `onOpenChange` or `triggerRender` instead
+     */
     onFocus?: (e: React.MouseEvent, rangeType: RangeType) => void;
     onPresetClick?: (item: PresetType, e: React.MouseEvent<HTMLDivElement>) => void;
+    onClickOutSide?: () => void;
     locale?: Locale['DatePicker'];
     dateFnsLocale?: Locale['dateFnsLocale'];
     yearAndMonthOpts?: ScrollItemProps<any>;
@@ -133,7 +144,8 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
         onPanelChange: PropTypes.func,
         rangeSeparator: PropTypes.string,
         preventScroll: PropTypes.bool,
-        yearAndMonthOpts: PropTypes.object
+        yearAndMonthOpts: PropTypes.object,
+        onClickOutSide: PropTypes.func,
     };
 
     static defaultProps = {
@@ -172,13 +184,15 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
         syncSwitchMonth: false,
         rangeSeparator: strings.DEFAULT_SEPARATOR_RANGE,
         insetInput: false,
+        onClickOutSide: noop,
     };
 
     triggerElRef: React.MutableRefObject<HTMLElement>;
     panelRef: React.RefObject<HTMLDivElement>;
     monthGrid: React.RefObject<MonthsGrid>;
-    rangeInputStartRef: React.RefObject<HTMLElement>;
-    rangeInputEndRef: React.RefObject<HTMLElement>;
+    inputRef: DateInputProps['inputRef'];
+    rangeInputStartRef: DateInputProps['rangeInputStartRef'];
+    rangeInputEndRef: DateInputProps['rangeInputEndRef'];
     focusRecordsRef: React.RefObject<{ rangeStart: boolean; rangeEnd: boolean }>;
     clickOutSideHandler: (e: MouseEvent) => void;
     _mounted: boolean;
@@ -205,6 +219,7 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
         this.triggerElRef = React.createRef();
         this.panelRef = React.createRef();
         this.monthGrid = React.createRef();
+        this.inputRef = React.createRef();
         this.rangeInputStartRef = React.createRef();
         this.rangeInputEndRef = React.createRef();
         this.focusRecordsRef = React.createRef();
@@ -220,8 +235,8 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
     get adapter(): DatePickerAdapter {
         return {
             ...super.adapter,
-            togglePanel: panelShow => {
-                this.setState({ panelShow });
+            togglePanel: (panelShow, cb) => {
+                this.setState({ panelShow }, cb);
                 if (!panelShow) {
                     this.focusRecordsRef.current.rangeEnd = false;
                     this.focusRecordsRef.current.rangeStart = false;
@@ -233,15 +248,19 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
                     this.clickOutSideHandler = null;
                 }
                 this.clickOutSideHandler = e => {
-                    if (this.adapter.needConfirm()) {
-                        return;
-                    }
                     const triggerEl = this.triggerElRef && this.triggerElRef.current;
                     const panelEl = this.panelRef && this.panelRef.current;
                     const isInTrigger = triggerEl && triggerEl.contains(e.target as Node);
                     const isInPanel = panelEl && panelEl.contains(e.target as Node);
-                    if (!isInTrigger && !isInPanel && this._mounted) {
-                        this.foundation.closePanel(e);
+                    const clickOutSide = !isInTrigger && !isInPanel && this._mounted;
+                    if (this.adapter.needConfirm()) {
+                        clickOutSide && this.props.onClickOutSide();
+                        return;
+                    } else {
+                        if (clickOutSide) {
+                            this.props.onClickOutSide();
+                            this.foundation.closePanel(e);
+                        }
                     }
                 };
                 document.addEventListener('mousedown', this.clickOutSideHandler);
@@ -277,7 +296,7 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
             },
             needConfirm: () =>
                 ['dateTime', 'dateTimeRange'].includes(this.props.type) && this.props.needConfirm === true,
-            typeIsYearOrMonth: () => ['month', 'year'].includes(this.props.type),
+            typeIsYearOrMonth: () => ['month', 'year', 'monthRange'].includes(this.props.type),
             setRangeInputFocus: rangeInputFocus => {
                 const { preventScroll } = this.props;
                 if (rangeInputFocus !== this.state.rangeInputFocus) {
@@ -349,6 +368,26 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
                         break;
                 }
             },
+            setInputFocus: () => {
+                const { preventScroll } = this.props;
+                const inputNode = get(this, 'inputRef.current');
+                inputNode && inputNode.focus({ preventScroll });
+            },
+            setInputBlur: () => {
+                const inputNode = get(this, 'inputRef.current');
+                inputNode && inputNode.blur();
+            },
+            setRangeInputBlur: () => {
+                const { rangeInputFocus } = this.state;
+                if (rangeInputFocus === 'rangeStart') {
+                    const inputStartNode = get(this, 'rangeInputStartRef.current');
+                    inputStartNode && inputStartNode.blur();
+                } else if (rangeInputFocus === 'rangeEnd') {
+                    const inputEndNode = get(this, 'rangeInputEndRef.current');
+                    inputEndNode && inputEndNode.blur();
+                }
+                this.adapter.setRangeInputFocus(false);
+            },
             setTriggerDisabled: (disabled: boolean) => {
                 this.setState({ triggerDisabled: disabled });
             }
@@ -390,6 +429,32 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
         super.componentWillUnmount();
     }
 
+    open() {
+        this.foundation.open();
+    }
+
+    close() {
+        this.foundation.close();
+    }
+
+    /**
+     *
+     * When selecting a range, the default focus is on the start input box, passing in `rangeEnd` can focus on the end input box
+     *
+     * When `insetInput` is `true`, due to trigger disabled, the cursor will focus on the input box of the popup layer panel
+     *
+     * 范围选择时,默认聚焦在开始输入框,传入 `rangeEnd` 可以聚焦在结束输入框
+     *
+     * `insetInput` 打开时,由于 trigger 禁用,会把焦点放在弹出面板的输入框上
+     */
+    focus(focusType?: Exclude<RangeType, false>) {
+        this.foundation.focus(focusType);
+    }
+
+    blur() {
+        this.foundation.blur();
+    }
+
     setTriggerRef = (node: HTMLDivElement) => (this.triggerElRef.current = node);
 
     // Called when changes are selected by clicking on the selected date
@@ -547,7 +612,7 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
         this.foundation.handlePanelVisibleChange(visible);
     }
 
-    renderInner(extraProps?: Partial<DatePickerProps>) {
+    renderInner() {
         const {
             clearIcon,
             type,
@@ -584,7 +649,6 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
         const phText = placeholder || locale.placeholder[type]; // i18n
         // These values should be passed to triggerRender, do not delete any key if it is not necessary
         const props = {
-            ...extraProps,
             placeholder: phText,
             clearIcon,
             disabled: inputDisabled,
@@ -619,6 +683,7 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
             onRangeEndTabPress: this.handleRangeEndTabPress,
             rangeInputStartRef: insetInput ? null : this.rangeInputStartRef,
             rangeInputEndRef: insetInput ? null : this.rangeInputEndRef,
+            inputRef: this.inputRef,
         };
 
         return (
@@ -665,7 +730,7 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
     };
 
     renderPanel = (locale: Locale['DatePicker'], localeCode: string, dateFnsLocale: Locale['dateFnsLocale']) => {
-        const { dropdownClassName, dropdownStyle, density, topSlot, bottomSlot, presetPosition } = this.props;
+        const { dropdownClassName, dropdownStyle, density, topSlot, bottomSlot, presetPosition, type } = this.props;
         const wrapCls = classnames(
             cssClasses.PREFIX,
             {
@@ -676,17 +741,18 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
         );
 
         return (
-            <div ref={this.panelRef} className={wrapCls} style={dropdownStyle} >
+            <div ref={this.panelRef} className={wrapCls} style={dropdownStyle} x-type={type}>
                 {topSlot && (
                     <div className={`${cssClasses.PREFIX}-topSlot`} x-semi-prop="topSlot">
                         {topSlot}
                     </div>
                 )}
-                {presetPosition === "top" && this.renderQuickControls()}
+                {/* todo: monthRange does not support presetPosition temporarily */}
+                {presetPosition === "top" && type !== 'monthRange' && this.renderQuickControls()}
                 {this.adapter.typeIsYearOrMonth()
                     ? this.renderYearMonthPanel(locale, localeCode)
                     : this.renderMonthGrid(locale, localeCode, dateFnsLocale)}
-                {presetPosition === "bottom" && this.renderQuickControls()}
+                {presetPosition === "bottom" && type !== 'monthRange' && this.renderQuickControls()}
                 {bottomSlot && (
                     <div className={`${cssClasses.PREFIX}-bottomSlot`} x-semi-prop="bottomSlot">
                         {bottomSlot}
@@ -698,15 +764,23 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
     };
 
     renderYearMonthPanel = (locale: Locale['DatePicker'], localeCode: string) => {
-        const { density, presetPosition, yearAndMonthOpts } = this.props;
+        const { density, presetPosition, yearAndMonthOpts, type } = this.props;
 
         const date = this.state.value[0];
-        let year = 0;
-        let month = 0;
+        const year = { left: 0, right: 0 };
+        const month = { left: 0, right: 0 };
 
         if (isDate(date)) {
-            year = date.getFullYear();
-            month = date.getMonth() + 1;
+            year.left = date.getFullYear();
+            month.left = date.getMonth() + 1;
+        }
+
+        if (type === 'monthRange') {
+            const dateRight = this.state.value[1];
+            if (isDate(dateRight)) {
+                year.right = dateRight.getFullYear();
+                month.right = dateRight.getMonth() + 1;
+            }
         }
 
         return (
@@ -723,6 +797,7 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
                 presetPosition={presetPosition}
                 renderQuickControls={this.renderQuickControls()}
                 renderDateInput={this.renderDateInput()}
+                type={type}
                 yearAndMonthOpts={yearAndMonthOpts}
             />
         );

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

@@ -28,6 +28,7 @@ export type { MonthsGridProps } from './monthsGrid';
 export type { QuickControlProps } from './quickControl';
 export type { YearAndMonthProps } from './yearAndMonth';
 export type { InsetInputProps } from '@douyinfe/semi-foundation/datePicker/inputFoundation';
+export type { DatePicker as BaseDatePicker };
 
 export default forwardStatics(
     React.forwardRef<DatePicker, DatePickerProps>((props, ref) => {

+ 5 - 3
packages/semi-ui/datePicker/monthsGrid.tsx

@@ -507,10 +507,12 @@ export default class MonthsGrid extends BaseComponent<MonthsGridProps, MonthsGri
                 ref={current => this.cacheRefCurrent(`yam-${panelType}`, current)}
                 locale={locale}
                 localeCode={localeCode}
-                currentYear={y}
-                currentMonth={m}
+                // currentYear={y}
+                // currentMonth={m}
+                currentYear={{ left: y, right: 0 }}
+                currentMonth={{ left: m, right: 0 }}
                 onSelect={item =>
-                    this.foundation.toYearMonth(panelType, new Date(item.currentYear, item.currentMonth - 1))
+                    this.foundation.toYearMonth(panelType, new Date(item.currentYear.left, item.currentMonth.left - 1))
                 }
                 onBackToMain={() => {
                     this.foundation.showDatePanel(panelType);

+ 100 - 43
packages/semi-ui/datePicker/yearAndMonth.tsx

@@ -6,16 +6,16 @@ import BaseComponent, { BaseProps } from '../_base/baseComponent';
 import ScrollList from '../scrollList/index';
 import ScrollItem from '../scrollList/scrollItem';
 import { getYears } from '@douyinfe/semi-foundation/datePicker/_utils/index';
-import isNullOrUndefined from '@douyinfe/semi-foundation/utils/isNullOrUndefined';
 
 import IconButton from '../iconButton';
 import { IconChevronLeft } from '@douyinfe/semi-icons';
 import { BASE_CLASS_PREFIX } from '@douyinfe/semi-foundation/base/constants';
 
-import { noop, stubFalse } from 'lodash';
+import { noop, stubFalse, isEqual } from 'lodash';
 import { setYear, setMonth, set } from 'date-fns';
 import { Locale } from '../locale/interface';
 import { strings } from '@douyinfe/semi-foundation/datePicker/constants';
+import { PanelType } from '@douyinfe/semi-foundation/datePicker/monthsGridFoundation';
 
 
 const prefixCls = `${BASE_CLASS_PREFIX}-datepicker`;
@@ -28,8 +28,8 @@ export type YearAndMonthState = YearAndMonthFoundationState;
 
 class YearAndMonth extends BaseComponent<YearAndMonthProps, YearAndMonthState> {
     static propTypes = {
-        currentYear: PropTypes.number,
-        currentMonth: PropTypes.number,
+        currentYear: PropTypes.object,
+        currentMonth: PropTypes.object,
         onSelect: PropTypes.func,
         locale: PropTypes.object,
         localeCode: PropTypes.string,
@@ -40,7 +40,8 @@ class YearAndMonth extends BaseComponent<YearAndMonthProps, YearAndMonthState> {
         density: PropTypes.string,
         presetPosition: PropTypes.oneOf(strings.PRESET_POSITION_SET),
         renderQuickControls: PropTypes.node,
-        renderDateInput: PropTypes.node
+        renderDateInput: PropTypes.node,
+        type: PropTypes.oneOf(strings.TYPE_SET),
     };
 
     static defaultProps = {
@@ -49,6 +50,7 @@ class YearAndMonth extends BaseComponent<YearAndMonthProps, YearAndMonthState> {
         yearCycled: false,
         noBackBtn: false,
         onSelect: noop,
+        type: 'month',
     };
     foundation: YearAndMonthFoundation;
     yearRef: React.RefObject<ScrollItem<YearScrollItem>>;
@@ -59,8 +61,12 @@ class YearAndMonth extends BaseComponent<YearAndMonthProps, YearAndMonthState> {
         const now = new Date();
 
         let { currentYear, currentMonth } = props;
-        currentYear = currentYear || now.getFullYear();
-        currentMonth = currentMonth || now.getMonth() + 1;
+
+        const currentLeftYear = currentYear.left || now.getFullYear();
+        const currentLeftMonth = currentMonth.left || now.getMonth() + 1;
+
+        currentYear = { left: currentLeftYear, right: currentLeftYear };
+        currentMonth = { left: currentLeftMonth, right: currentMonth.right || currentLeftMonth + 1 };
 
         this.state = {
             years: getYears().map(year => ({
@@ -89,47 +95,67 @@ class YearAndMonth extends BaseComponent<YearAndMonthProps, YearAndMonthState> {
             // updateMonths: months => this.setState({ months }),
             setCurrentYear: (currentYear, cb) => this.setState({ currentYear }, cb),
             setCurrentMonth: currentMonth => this.setState({ currentMonth }),
-            notifySelectYear: year =>
+            setCurrentYearAndMonth: (currentYear, currentMonth) => this.setState({ currentYear, currentMonth }),
+            notifySelectYear: (year) =>
                 this.props.onSelect({
                     currentMonth: this.state.currentMonth,
                     currentYear: year,
                 }),
-            notifySelectMonth: month =>
+            notifySelectMonth: (month) =>
                 this.props.onSelect({
                     currentYear: this.state.currentYear,
                     currentMonth: month,
                 }),
+            notifySelectYearAndMonth: (year, month) =>
+                this.props.onSelect({
+                    currentYear: year,
+                    currentMonth: month,
+                }),
             notifyBackToMain: () => this.props.onBackToMain(),
         };
     }
 
     static getDerivedStateFromProps(props: YearAndMonthProps, state: YearAndMonthState) {
         const willUpdateStates: Partial<YearAndMonthState> = {};
-        const now = new Date();
 
-        if (!isNullOrUndefined(props.currentMonth) && props.currentMonth !== state.currentMonth && props.currentMonth !== 0) {
-            willUpdateStates.currentMonth = props.currentMonth || now.getMonth() + 1;
+        if (!isEqual(props.currentYear, state.currentYear) && props.currentYear.left !== 0) {
+            willUpdateStates.currentYear = props.currentYear;
         }
 
-        if (isNullOrUndefined(props.currentYear) && props.currentYear !== state.currentYear && props.currentYear !== 0) {
-            willUpdateStates.currentYear = props.currentYear || now.getFullYear();
+        if (!isEqual(props.currentMonth, state.currentMonth) && props.currentMonth.left !== 0) {
+            willUpdateStates.currentMonth = props.currentMonth;
         }
 
         return willUpdateStates;
     }
 
-    renderColYear() {
+    renderColYear(panelType: PanelType) {
         const { years, currentYear, currentMonth, months } = this.state;
         const { disabledDate, localeCode, yearCycled, yearAndMonthOpts } = this.props;
-        const currentDate = setMonth(Date.now(), currentMonth - 1);
+        const currentDate = setMonth(Date.now(), currentMonth[panelType] - 1);
+        const left = strings.PANEL_TYPE_LEFT;
+        const right = strings.PANEL_TYPE_RIGHT;
+
+        const needDisabled = (year) => {
+            if (panelType === right && currentYear[left]) {
+                if (currentMonth[left] <= currentMonth[right]) {
+                    return currentYear[left] > year;
+                } else {
+                    return currentYear[left] >= year;
+                }
+            }
+            return false;
+        };
+
         const list: any[] = years.map(({ value, year }) => {
             const isAllMonthDisabled = months.every(({ month }) => {
                 return disabledDate(set(currentDate, { year, month: month - 1 }));
             });
+            const isRightPanelDisabled = needDisabled(year);
             return ({
                 year,
                 value, // Actual rendered text
-                disabled: isAllMonthDisabled,
+                disabled: isAllMonthDisabled || isRightPanelDisabled,
             });
         });
         let transform = (val: string) => val;
@@ -143,21 +169,21 @@ class YearAndMonth extends BaseComponent<YearAndMonthProps, YearAndMonthState> {
                 cycled={yearCycled}
                 list={list}
                 transform={transform}
-                selectedIndex={years.findIndex(item => item.value === currentYear)}
+                selectedIndex={years.findIndex(item => item.value === currentYear[panelType])}
                 type="year"
-                onSelect={this.selectYear}
+                onSelect={item => this.selectYear(item as YearScrollItem, panelType)}
                 mode="normal"
                 {...yearAndMonthOpts}
             />
         );
     }
 
-    selectYear = (item: YearScrollItem) => {
-        this.foundation.selectYear(item);
+    selectYear = (item: YearScrollItem, panelType?: PanelType) => {
+        this.foundation.selectYear(item, panelType);
     };
 
-    selectMonth = (item: MonthScrollItem) => {
-        this.foundation.selectMonth(item);
+    selectMonth = (item: MonthScrollItem, panelType?: PanelType) => {
+        this.foundation.selectMonth(item, panelType);
     };
 
     reselect = () => {
@@ -172,22 +198,29 @@ class YearAndMonth extends BaseComponent<YearAndMonthProps, YearAndMonthState> {
         });
     };
 
-    renderColMonth() {
+    renderColMonth(panelType: PanelType) {
         const { months, currentMonth, currentYear } = this.state;
         const { locale, localeCode, monthCycled, disabledDate, yearAndMonthOpts } = this.props;
         let transform = (val: string) => val;
-        const currentDate = setYear(Date.now(), currentYear);
+        const currentDate = setYear(Date.now(), currentYear[panelType]);
+        const left = strings.PANEL_TYPE_LEFT;
+        const right = strings.PANEL_TYPE_RIGHT;
+
         if (localeCode === 'zh-CN' || localeCode === 'zh-TW') {
             // Only Chinese needs to add [month] after the selected month
             transform = val => `${val}月`;
         }
         // i18n
-        const list: MonthScrollItem[] = months.map(({ value, month }) => ({
-            month,
-            disabled: disabledDate(setMonth(currentDate, month - 1)),
-            value: locale.fullMonths[value], // Actual rendered text
-        }));
-        const selectedIndex = list.findIndex(item => item.month === currentMonth);
+        const list: MonthScrollItem[] = months.map(({ value, month }) => {
+            const isRightPanelDisabled = panelType === right && currentMonth[left] && currentYear[left] === currentYear[right] && currentMonth[left] > month;
+
+            return ({
+                month,
+                disabled: disabledDate(setMonth(currentDate, month - 1)) || isRightPanelDisabled,
+                value: locale.fullMonths[value], // Actual rendered text
+            });
+        });
+        const selectedIndex = list.findIndex(item => item.month === currentMonth[panelType]);
         return (
             <ScrollItem
                 ref={this.monthRef}
@@ -196,7 +229,7 @@ class YearAndMonth extends BaseComponent<YearAndMonthProps, YearAndMonthState> {
                 transform={transform}
                 selectedIndex={selectedIndex}
                 type="month"
-                onSelect={this.selectMonth}
+                onSelect={item => this.selectMonth(item as MonthScrollItem, panelType)}
                 mode='normal'
                 {...yearAndMonthOpts}
             />
@@ -208,13 +241,41 @@ class YearAndMonth extends BaseComponent<YearAndMonthProps, YearAndMonthState> {
         this.foundation.backToMain();
     };
 
+    renderPanel(panelType: PanelType) {
+
+        return (
+            <>
+                <ScrollList>
+                    {this.renderColYear(panelType)}
+                    {this.renderColMonth(panelType)}
+                </ScrollList>
+            </>
+        );
+    }
+
     render() {
-        const { locale, noBackBtn, density, presetPosition, renderQuickControls, renderDateInput } = this.props;
+        const { locale, noBackBtn, density, presetPosition, renderQuickControls, renderDateInput, type } = this.props;
         const prefix = `${prefixCls}-yearmonth-header`;
+        const bodyCls = `${prefixCls}-yearmonth-body`;
+
         // i18n
         const selectDateText = locale.selectDate;
         const iconSize = density === 'compact' ? 'default' : 'large';
         const buttonSize = density === 'compact' ? 'small' : 'default';
+        const panelTypeLeft = strings.PANEL_TYPE_LEFT;
+        const panelTypeRight = strings.PANEL_TYPE_RIGHT;
+
+        let content = null;
+        if (type === 'month') {
+            content = this.renderPanel(panelTypeLeft);
+        } else {
+            content = (
+                <div className={bodyCls}>
+                    {this.renderPanel(panelTypeLeft)}
+                    {this.renderPanel(panelTypeRight)}
+                </div>
+            );
+        }
 
         return (
             <React.Fragment>
@@ -233,23 +294,19 @@ class YearAndMonth extends BaseComponent<YearAndMonthProps, YearAndMonthState> {
                 {
                     presetPosition ? (
                         <div style={{ display: 'flex' }}>
-                            {presetPosition === "left" && renderQuickControls}
+                            {/* todo: monthRange does not support presetPosition temporarily */}
+                            {presetPosition === "left" && type !== 'monthRange' && renderQuickControls}
                             <div>
                                 {renderDateInput}
-                                <ScrollList>
-                                    {this.renderColYear()}
-                                    {this.renderColMonth()}
-                                </ScrollList>
+                                {content} 
                             </div>
-                            {presetPosition === "right" && renderQuickControls}
+                            {/* todo: monthRange does not support presetPosition temporarily */}
+                            {presetPosition === "right" && type !== 'monthRange' && renderQuickControls}
                         </div>
                     ) :
                         <>
                             {renderDateInput}
-                            <ScrollList>
-                                {this.renderColYear()}
-                                {this.renderColMonth()}
-                            </ScrollList>
+                            {content} 
                         </>
                 }
             </React.Fragment>

+ 2 - 1
packages/semi-ui/locale/interface.ts

@@ -31,7 +31,8 @@ export interface Locale {
             date: string;
             dateTime: string;
             dateRange: [string, string];
-            dateTimeRange: [string, string]
+            dateTimeRange: [string, string];
+            monthRange: [string, string]
         };
         footer: {
             confirm: string;

+ 1 - 0
packages/semi-ui/locale/source/ar.ts

@@ -33,6 +33,7 @@ const local: Locale = {
             dateTime: 'حدد التاريخ والوقت',
             dateRange: ['تاريخ البدء', 'تاريخ النهاية'],
             dateTimeRange: ['تاريخ البدء', 'تاريخ النهاية'],
+            monthRange: ['الشهر الأول', 'الشهر الأخير'],
         },
         footer: {
             confirm: 'تؤكد',

+ 1 - 0
packages/semi-ui/locale/source/de.ts

@@ -33,6 +33,7 @@ const local: Locale = {
             dateTime: 'Datum und Uhrzeit auswählen',
             dateRange: ['Startdatum', 'Enddatum'],
             dateTimeRange: ['Startdatum', 'Enddatum'],
+            monthRange: ['Startmonat', 'Endmonat'],
         },
         footer: {
             confirm: 'Bestätigen',

+ 1 - 0
packages/semi-ui/locale/source/en_GB.ts

@@ -33,6 +33,7 @@ const local: Locale = {
             dateTime: 'Select date and time',
             dateRange: ['Start date', 'End date'],
             dateTimeRange: ['Start date', 'End date'],
+            monthRange: ['Start month', 'End month'],
         },
         footer: {
             confirm: 'Confirm',

+ 1 - 0
packages/semi-ui/locale/source/en_US.ts

@@ -33,6 +33,7 @@ const local: Locale = {
             dateTime: 'Select date and time',
             dateRange: ['Start date', 'End date'],
             dateTimeRange: ['Start date', 'End date'],
+            monthRange: ['Start month', 'End month'],
         },
         footer: {
             confirm: 'Confirm',

+ 1 - 0
packages/semi-ui/locale/source/es.ts

@@ -38,6 +38,7 @@ const locale: Locale = {
             dateTime: 'Seleccionar hora y fecha',
             dateRange: ['Fecha inicial', 'Fecha final'],
             dateTimeRange: ['Fecha inicial', 'Fecha final'],
+            monthRange: ['Mes inicial', 'Mes final'],
         },
         footer: {
             confirm: 'Aceptar',

+ 1 - 0
packages/semi-ui/locale/source/fr.ts

@@ -33,6 +33,7 @@ const local: Locale = {
             dateTime: 'Sélectionner date et temps',
             dateRange: ['Date de début', 'Date de fin'],
             dateTimeRange: ['Date de début', 'Date de fin'],
+            monthRange: ['Mois de début', 'Mois de fin'],
         },
         footer: {
             confirm: 'Confirmer',

+ 1 - 0
packages/semi-ui/locale/source/id_ID.ts

@@ -33,6 +33,7 @@ const local: Locale = {
             dateTime: 'Pilih tanggal dan waktu',
             dateRange: ['Tanggal mulai', 'Tanggal akhir'],
             dateTimeRange: ['Tanggal mulai', 'Tanggal akhir'],
+            monthRange: ['Bulan pertama', 'Bulan terakhir'],
         },
         footer: {
             confirm: 'Konfirmasi',

+ 1 - 0
packages/semi-ui/locale/source/it.ts

@@ -33,6 +33,7 @@ const local: Locale = {
             dateTime: 'Seleziona data e ora',
             dateRange: ['Data inizio', 'Data fine'],
             dateTimeRange: ['Data inizio', 'Data fine'],
+            monthRange: ['Mese inizio', 'Mese fine'],
         },
         footer: {
             confirm: 'Conferma',

+ 1 - 0
packages/semi-ui/locale/source/ja_JP.ts

@@ -33,6 +33,7 @@ const local: Locale = {
             dateTime: '日時を選択してください',
             dateRange: ['開始日', '終了日'],
             dateTimeRange: ['開始日', '終了日'],
+            monthRange: ['開始月', '終了月'],
         },
         footer: {
             confirm: '確認する',

+ 1 - 0
packages/semi-ui/locale/source/ko_KR.ts

@@ -34,6 +34,7 @@ const local: Locale = {
             dateTime: '날짜 및 시간 선택',
             dateRange: ['시작 날짜', '종료일'],
             dateTimeRange: ['시작 날짜', '종료일'],
+            monthRange: ['시작 월', '종료 월'],
         },
         footer: {
             confirm: '확인',

+ 1 - 0
packages/semi-ui/locale/source/ms_MY.ts

@@ -33,6 +33,7 @@ const local: Locale = {
             dateTime: 'Pilih tarikh dan masa',
             dateRange: ['Tarikh mula', 'Tarikh akhir'],
             dateTimeRange: ['Tarikh mula', 'Tarikh akhir'],
+            monthRange: ['Bulan mula', 'Bulan akhir'],
         },
         footer: {
             confirm: 'Sahkan',

+ 1 - 0
packages/semi-ui/locale/source/nl_NL.ts

@@ -40,6 +40,7 @@ const local: Locale = {
             dateTime: 'Datum en tijd selecteren',
             dateRange: ['Begindatum', 'Einddatum'],
             dateTimeRange: ['Begindatum', 'Einddatum'],
+            monthRange: ['Begindatum', 'Einddatum'],
         },
         footer: {
             confirm: 'Bevestigen',

+ 1 - 0
packages/semi-ui/locale/source/pl_PL.ts

@@ -41,6 +41,7 @@ const local: Locale = {
             dateTime: 'Wybierz datę i godzinę',
             dateRange: ['Data rozpoczęcia', 'Data zakończenia'],
             dateTimeRange: ['Data rozpoczęcia', 'Data zakończenia'],
+            monthRange: ['Miesiąc rozpoczęcia', 'Miesiąc zakończenia'],
         },
         footer: {
             confirm: 'Potwierdź',

+ 1 - 0
packages/semi-ui/locale/source/pt_BR.ts

@@ -33,6 +33,7 @@ const local: Locale = {
             dateTime: 'Selecione a data e hora',
             dateRange: ['Data de início', 'Data de fim'],
             dateTimeRange: ['Data de início', 'Data de fim'],
+            monthRange: ['Mês de início', 'Mês de fim'],
         },
         footer: {
             confirm: 'OK',

+ 1 - 0
packages/semi-ui/locale/source/ro.ts

@@ -35,6 +35,7 @@ export default {
             dateTime: 'Selectează data și ora',
             dateRange: ['Data de început', 'Data de sfârșit'],
             dateTimeRange: ['Data de început', 'Data de sfârșit'],
+            monthRange: ['Luna de început', 'Luna de sfârșit'],
         },
         footer: {
             confirm: 'Confirmă',

+ 1 - 0
packages/semi-ui/locale/source/ru_RU.ts

@@ -36,6 +36,7 @@ const local: Locale = {
             dateTime: 'Выбрать дату и время',
             dateRange: ['Дата начала', 'Дата окончания'],
             dateTimeRange: ['Дата начала', 'Дата окончания'],
+            monthRange: ['Начальный месяц', 'Конечный месяц'],
         },
         footer: {
             confirm: 'подтвердить',

+ 1 - 0
packages/semi-ui/locale/source/sv_SE.ts

@@ -38,6 +38,7 @@ const local: Locale = {
             dateTime: 'Välj datum och tid',
             dateRange: ['Startdatum', 'Slutdatum'],
             dateTimeRange: ['Startdatum', 'Slutdatum'],
+            monthRange: ['Startmånad', 'Slutmånad'],
         },
         footer: {
             confirm: 'Bekräfta',

+ 1 - 0
packages/semi-ui/locale/source/th_TH.ts

@@ -36,6 +36,7 @@ const local: Locale = {
             dateTime: 'โปรดเลือกวันที่และเวลา',
             dateRange: ['วันที่เริ่มต้น', 'วันที่สิ้นสุด'],
             dateTimeRange: ['วันที่เริ่มต้น', 'วันที่สิ้นสุด'],
+            monthRange: ['เดือนเริ่มต้น', 'เดือนสิ้นสุด'],
         },
         footer: {
             confirm: 'ตกลง',

+ 1 - 0
packages/semi-ui/locale/source/tr_TR.ts

@@ -36,6 +36,7 @@ const local: Locale = {
             dateTime: 'Lütfen bir tarih ve saat seçin',
             dateRange: ['Başlangıç tarihi', 'Bitiş tarihi'],
             dateTimeRange: ['Başlangıç tarihi', 'Bitiş tarihi'],
+            monthRange: ['Başlangıç ​​ayı', 'Bitiş ayı']
         },
         footer: {
             confirm: 'Tamam',

+ 1 - 0
packages/semi-ui/locale/source/vi_VN.ts

@@ -36,6 +36,7 @@ const local: Locale = {
             dateTime: 'Chọn ngày và giờ',
             dateRange: ['Ngày bắt đầu', 'Ngày kết thúc'],
             dateTimeRange: ['Ngày bắt đầu', 'Ngày kết thúc'],
+            monthRange: ['Tháng bắt đầu', 'Tháng kết thúc'],
         },
         footer: {
             confirm: 'Xác nhận',

+ 1 - 0
packages/semi-ui/locale/source/zh_CN.ts

@@ -33,6 +33,7 @@ const local: Locale = {
             dateTime: '请选择日期及时间',
             dateRange: ['开始日期', '结束日期'],
             dateTimeRange: ['开始日期', '结束日期'],
+            monthRange: ['开始月份', '结束月份'],
         },
         footer: {
             confirm: '确定',

+ 1 - 0
packages/semi-ui/locale/source/zh_TW.ts

@@ -33,6 +33,7 @@ const local: Locale = {
             dateTime: '請選擇日期及時間',
             dateRange: ['開始日期', '結束日期'],
             dateTimeRange: ['開始日期', '結束日期'],
+            monthRange: ['開始月份', '結束月份'],
         },
         footer: {
             confirm: '確定',

+ 40 - 42
packages/semi-ui/modal/Modal.tsx

@@ -243,7 +243,7 @@ class Modal extends BaseComponent<ModalReactProps, ModalState> {
             this.foundation.beforeShow();
         }
 
-        if (!prevState.displayNone && this.state.displayNone){
+        if (!prevState.displayNone && this.state.displayNone) {
             this.foundation.afterHide();
         }
     }
@@ -362,53 +362,51 @@ class Modal extends BaseComponent<ModalReactProps, ModalState> {
 
         const shouldRender = this.props.visible || (this.props.keepDOM && (!this.props.lazyRender || this._haveRendered)) || (this.props.motion && !this.state.displayNone /* When there is animation, we use displayNone to judge whether animation is ended and judge whether to unmount content */);
 
-        if (shouldRender){
+        if (shouldRender) {
             this._haveRendered = true;
         }
 
         return (
-            <Portal style={wrapperStyle} getPopupContainer={getPopupContainer}>
-                <CSSAnimation
-                    motion={this.props.motion}
-                    animationState={visible?'enter':'leave'}
-                    startClassName={visible?`${cssClasses.DIALOG}-content-animate-show`:`${cssClasses.DIALOG}-content-animate-hide`}
-                    onAnimationEnd={()=>{
-                        this.updateState();
-                    }}
-                >
-                    {
-                        ({ animationClassName, animationEventsNeedBind })=>{
-                            return <CSSAnimation motion={this.props.motion} animationState={visible?'enter':'leave'}
-                                startClassName={visible?`${cssClasses.DIALOG}-mask-animate-show`:`${cssClasses.DIALOG}-mask-animate-hide`}
-                                onAnimationEnd={()=>{
-                                    this.updateState();
-                                }}
-                            >
-                                {
-                                    ({ animationClassName: maskAnimationClassName, animationEventsNeedBind: maskAnimationEventsNeedBind })=>{
-                                        return shouldRender ? <ModalContent
-                                            {...restProps}
-                                            contentExtraProps={animationEventsNeedBind}
-                                            maskExtraProps={maskAnimationEventsNeedBind}
-                                            isFullScreen={this.state.isFullScreen}
-                                            contentClassName={animationClassName}
-                                            maskClassName={maskAnimationClassName}
-                                            className={classList}
-                                            getPopupContainer={getPopupContainer}
-                                            maskStyle={maskStyle}
-                                            style={style}
-                                            ref={this.modalRef}
-                                            footer={renderFooter}
-                                            onClose={this.handleCancel}
-
-                                        />:<></>;
-                                    }
+            <CSSAnimation
+                motion={this.props.motion}
+                animationState={visible?'enter':'leave'}
+                startClassName={visible?`${cssClasses.DIALOG}-content-animate-show`:`${cssClasses.DIALOG}-content-animate-hide`}
+                onAnimationEnd={()=>{
+                    this.updateState();
+                }}
+            >
+                {
+                    ({ animationClassName, animationEventsNeedBind })=>{
+                        return <CSSAnimation motion={this.props.motion} animationState={visible?'enter':'leave'}
+                            startClassName={visible?`${cssClasses.DIALOG}-mask-animate-show`:`${cssClasses.DIALOG}-mask-animate-hide`}
+                            onAnimationEnd={()=>{
+                                this.updateState();
+                            }}
+                        >
+                            {
+                                ({ animationClassName: maskAnimationClassName, animationEventsNeedBind: maskAnimationEventsNeedBind })=>{
+                                    return shouldRender ? <Portal style={wrapperStyle} getPopupContainer={getPopupContainer}> <ModalContent
+                                        {...restProps}
+                                        contentExtraProps={animationEventsNeedBind}
+                                        maskExtraProps={maskAnimationEventsNeedBind}
+                                        isFullScreen={this.state.isFullScreen}
+                                        contentClassName={animationClassName}
+                                        maskClassName={maskAnimationClassName}
+                                        className={classList}
+                                        getPopupContainer={getPopupContainer}
+                                        maskStyle={maskStyle}
+                                        style={style}
+                                        ref={this.modalRef}
+                                        footer={renderFooter}
+                                        onClose={this.handleCancel}
+
+                                    /></Portal>:<></>;
                                 }
-                            </CSSAnimation>;
-                        }
+                            }
+                        </CSSAnimation>;
                     }
-                </CSSAnimation>
-            </Portal>
+                }
+            </CSSAnimation>
         );
     };
 

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-ui",
-    "version": "2.31.4",
+    "version": "2.32.0-beta.0",
     "description": "",
     "main": "lib/cjs/index.js",
     "module": "lib/es/index.js",
@@ -17,12 +17,12 @@
         "lib/*"
     ],
     "dependencies": {
-        "@douyinfe/semi-animation": "2.31.4",
-        "@douyinfe/semi-animation-react": "2.31.4",
-        "@douyinfe/semi-foundation": "2.31.4",
-        "@douyinfe/semi-icons": "2.31.4",
-        "@douyinfe/semi-illustrations": "2.31.4",
-        "@douyinfe/semi-theme-default": "2.31.4",
+        "@douyinfe/semi-animation": "2.32.0-beta.0",
+        "@douyinfe/semi-animation-react": "2.32.0-beta.0",
+        "@douyinfe/semi-foundation": "2.32.0-beta.0",
+        "@douyinfe/semi-icons": "2.32.0-beta.0",
+        "@douyinfe/semi-illustrations": "2.32.0-beta.0",
+        "@douyinfe/semi-theme-default": "2.32.0-beta.0",
         "async-validator": "^3.5.0",
         "classnames": "^2.2.6",
         "copy-text-to-clipboard": "^2.1.1",

+ 23 - 2
packages/semi-ui/select/index.tsx

@@ -37,7 +37,6 @@ import type { Locale } from '../locale/interface';
 import type { Position, TooltipProps } from '../tooltip';
 import type { Subtract } from 'utility-types';
 
-
 export type { OptionProps } from './option';
 export type { OptionGroupProps } from './optionGroup';
 export type { VirtualRowProps } from './virtualRow';
@@ -68,6 +67,25 @@ export interface optionRenderProps {
     [x: string]: any
 }
 
+export interface SelectedItemProps {
+    value: OptionProps['value'];
+    label: OptionProps['label'];
+    _show?: boolean;
+    _selected: boolean;
+    _scrollIndex?: number
+}
+
+export interface TriggerRenderProps {
+    value: SelectedItemProps[];
+    inputValue: string;
+    onSearch: (inputValue: string) => void;
+    onClear: () => void;
+    onRemove: (option: OptionProps) => void;
+    disabled: boolean;
+    placeholder: string;
+    componentProps: Record<string, any>
+}
+
 export interface selectMethod {
     clearInput?: () => void;
     selectAll?: () => void;
@@ -153,7 +171,7 @@ export type SelectProps = {
     onDeselect?: (value: SelectProps['value'], option: Record<string, any>) => void;
     onSelect?: (value: SelectProps['value'], option: Record<string, any>) => void;
     allowCreate?: boolean;
-    triggerRender?: (props?: any) => React.ReactNode;
+    triggerRender?: (props?: TriggerRenderProps) => React.ReactNode;
     onClear?: () => void;
     virtualize?: virtualListProps;
     onFocus?: (e: React.FocusEvent) => void;
@@ -1320,11 +1338,14 @@ class Select extends BaseComponent<SelectProps, SelectState> {
 
         const clear = clearIcon ? clearIcon : <IconClear />;
 
+        // semantics of onSearch are more in line with behavior, onChange is alias of onSearch, will be deprecate next major version
         const inner = useCustomTrigger ? (
             <Trigger
                 value={Array.from(selections.values())}
                 inputValue={inputValue}
                 onChange={this.handleInputChange}
+                onSearch={this.handleInputChange}
+                onRemove={(item) => this.foundation.removeTag(item)}
                 onClear={this.onClear}
                 disabled={disabled}
                 triggerRender={triggerRender}

+ 8 - 5
packages/semi-ui/sideSheet/SideSheetContent.tsx

@@ -5,6 +5,7 @@ import { cssClasses } from '@douyinfe/semi-foundation/sideSheet/constants';
 import Button from '../iconButton';
 import { noop } from 'lodash';
 import { IconClose } from '@douyinfe/semi-icons';
+import { SideSheetProps } from "@douyinfe/semi-foundation/sideSheet/sideSheetFoundation";
 
 let uuid = 0;
 const prefixCls = cssClasses.PREFIX;
@@ -19,17 +20,18 @@ export interface SideSheetContentProps {
     title?: React.ReactNode;
     closable?: boolean;
     headerStyle?: CSSProperties;
-    width: CSSProperties['width'];
+    width?: CSSProperties['width'];
     height: CSSProperties['height'];
     style: CSSProperties;
+    size: SideSheetProps['size'];
     bodyStyle?: CSSProperties;
     className: string;
-    dialogClassName?:string;
+    dialogClassName?: string;
     children?: React.ReactNode;
     footer?: React.ReactNode;
     'aria-label'?: string;
-    maskExtraProps?: {[key:string]: any};
-    wrapperExtraProps?: {[key:string]: any}
+    maskExtraProps?: {[key: string]: any};
+    wrapperExtraProps?: {[key: string]: any}
 }
 
 export default class SideSheetContent extends React.PureComponent<SideSheetContentProps> {
@@ -138,7 +140,7 @@ export default class SideSheetContent extends React.PureComponent<SideSheetConte
                 key="dialog-element"
                 role="dialog"
                 tabIndex={-1}
-                className={cls(`${prefixCls}-inner`, `${prefixCls}-inner-wrap`, this.props.dialogClassName??"")}
+                className={cls(`${prefixCls}-inner`, `${prefixCls}-inner-wrap`, this.props.dialogClassName??"", `${prefixCls}-size-${props.size}`)}
                 // onMouseDown={this.onDialogMouseDown}
                 style={{ ...props.style, ...style }}
                 {...this.props.wrapperExtraProps}
@@ -168,6 +170,7 @@ export default class SideSheetContent extends React.PureComponent<SideSheetConte
         } = this.props;
         const wrapperCls = cls(className, {
             [`${prefixCls}-fixed`]: !mask,
+            [`${prefixCls}-size-${this.props.size}`]: !mask
         });
         const wrapperStyle: CSSProperties = {};
         if (!mask && width) {

+ 2 - 2
packages/semi-ui/sideSheet/index.tsx

@@ -206,7 +206,6 @@ export default class SideSheet extends BaseComponent<SideSheetReactProps, SideSh
         const { direction } = this.context;
         const isVertical = placement === 'left' || placement === 'right';
         const isHorizontal = placement === 'top' || placement === 'bottom';
-        const sheetWidth = isVertical ? (width ? width : defaultWidthList[size]) : '100%';
         const sheetHeight = isHorizontal ? (height ? height : defaultHeight) : '100%';
         const classList = cls(prefixCls, className, {
             [`${prefixCls}-${placement}`]: placement,
@@ -216,11 +215,12 @@ export default class SideSheet extends BaseComponent<SideSheetReactProps, SideSh
             [`${prefixCls}-hidden`]: keepDOM && this.state.displayNone,
         });
         const contentProps = {
+            ...( isVertical ? (width ? { width } : {}) : { width: "100%" }),
             ...props,
             visible,
             motion: false,
+            size,
             className: classList,
-            width: sheetWidth,
             height: sheetHeight,
             onClose: this.handleCancel,
         };

+ 3 - 1
packages/semi-ui/space/index.tsx

@@ -5,6 +5,7 @@ import { strings, cssClasses } from '@douyinfe/semi-foundation/space/constants';
 import '@douyinfe/semi-foundation/space/space.scss';
 import { isString, isArray, isNumber } from 'lodash';
 import { flatten } from './utils';
+import getDataAttr from '@douyinfe/semi-foundation/utils/getDataAttr';
 
 const prefixCls = cssClasses.PREFIX;
 
@@ -84,8 +85,9 @@ class Space extends PureComponent<SpaceProps> {
             [`${prefixCls}-loose-vertical`]: spacingVerticalType === strings.SPACING_LOOSE,
         });
         const childrenNodes = flatten(children);
+        const dataAttributes = getDataAttr(this.props);
         return (
-            <div className={classNames} style={realStyle} x-semi-prop="children">
+            <div {...dataAttributes} className={classNames} style={realStyle} x-semi-prop="children">
                 {childrenNodes}
             </div>
         );

+ 1 - 0
packages/semi-ui/table/_story/v2/fixedResizableWithForm.tsx

@@ -1,4 +1,5 @@
 import React from 'react';
+// eslint-disable-next-line semi-design/no-import
 import { Table } from '@douyinfe/semi-ui';
 
 App.storyName = 'fixed resizable table with form';

+ 147 - 3
packages/semi-ui/treeSelect/_story/treeSelect.stories.jsx

@@ -1,9 +1,9 @@
-import React, { useState, useMemo, useRef } from 'react';
-import { Icon, Input, Button, Form, Popover, Tag, Typography, CheckboxGroup } from '../../index';
+import React, { useState, useMemo, useRef, useCallback } from 'react';
+import { Icon, Input, Button, Form, Popover, Tag, Typography, CheckboxGroup, TagInput } from '../../index';
 import TreeSelect from '../index';
 import { flattenDeep } from 'lodash';
 import CustomTrigger from './CustomTrigger';
-import { IconCreditCard } from '@douyinfe/semi-icons';
+import { IconCreditCard, IconChevronDown, IconClose } from '@douyinfe/semi-icons';
 import { setFocusToPreviousMenuItem } from '@douyinfe/semi-foundation/utils/a11y';
 const TreeNode = TreeSelect.TreeNode;
 const { Title } = Typography;
@@ -2107,3 +2107,147 @@ export const searchPositionInTriggerAndVirtualize = () => {
       </>
   );
 };
+
+export const clickTriggerToHide = () => (
+  <>
+      <p>clickTriggerToHide 未设置,默认为 true</p>
+      <TreeSelect
+          style={{ width: 300 }}
+          dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
+          treeData={treeData2}
+          placeholder="单选"
+      />
+      <p>clickTriggerToHide 设置为 false</p>
+      <TreeSelect
+          style={{ width: 300 }}
+          dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
+          treeData={treeData2}
+          placeholder="单选"
+          clickTriggerToHide={false}
+      />
+  </>
+);
+export const triggerRenderAddMethod = () => {
+  const treeData = useMemo(() => [
+      {
+          label: '亚洲',
+          value: '亚洲',
+          key: '0',
+          children: [
+              {
+                  label: '中国',
+                  value: '中国',
+                  key: '0-0',
+                  children: [
+                      {
+                          label: '北京',
+                          value: '北京',
+                          key: '0-0-0',
+                      },
+                      {
+                          label: '上海',
+                          value: '上海',
+                          key: '0-0-1',
+                      },
+                  ],
+              },
+          ],
+      },
+      {
+          label: '北美洲',
+          value: '北美洲',
+          key: '1',
+      }
+  ], []);
+
+  const onValueChange = useCallback((value) => {
+      console.log('onChange', value);
+  });
+
+  const closeIcon = useCallback((value, onClear) => {
+      return value && value.length ? <IconClose onClick={onClear} /> : <IconChevronDown />;
+  }, []);
+
+  const renderTagItem = useCallback((item, onRemove) => (
+      <Tag closable key={item.key} onClose={() => { onRemove(item.key); }}>{item.label}</Tag>
+  ), []);
+
+  const renderTrigger1 = useCallback((props) => {
+    const { value, placeholder, onClear } = props;
+    return (
+      <Button theme={'light'} icon={closeIcon(value, onClear)} iconPosition={'right'}>
+          {value && value.length ? value.map(item => item.label).join(',') : placeholder}
+      </Button>
+    );
+  }, []);
+
+  const renderTrigger2 = useCallback((props) => {
+      const { value, onSearch, onRemove, onClear } = props;
+      return (
+          <div style={{ border: '1px solid grey', width: 'fit-content', padding: 5, borderRadius: 5 }}>
+              {value && value.length > 0 && 
+              <div style={{ width: 'fit-content', minWidth: 10, padding: 5 }}>
+                  {value.map(item => renderTagItem(item, onRemove))}
+              </div>
+              }
+              <Input style={{ width: 200 }} onChange={onSearch} />
+              {closeIcon(value, onClear)}
+          </div>
+      );
+  }, []);
+
+  const renderTrigger3 = useCallback((props) => {
+    const { value, onSearch, onRemove, inputValue } = props;
+    const tagInputValue = value.map(item => item.key);
+    const renderTagInMultiple = (key) => {
+      const label = value.find(item => item.key === key).label;
+      const onCloseTag = (value, e, tagKey) => {
+        onRemove(tagKey);
+      }
+      return <Tag style={{ marginLeft: 2 }} tagKey={key} key={key} onClose={onCloseTag} closable>{label}</Tag>
+    }
+    return (
+      <TagInput
+        inputValue={inputValue}
+        value={tagInputValue}
+        onInputChange={onSearch}
+        renderTagItem={renderTagInMultiple}
+      />
+    )
+  }, []);
+
+  return (
+    <>
+      <TreeSelect
+          triggerRender={renderTrigger1}
+          multiple
+          treeData={treeData}
+          placeholder='Custom Trigger'
+          onChange={onValueChange}
+          style={{ width: 300 }}
+      />
+      <br />
+      <TreeSelect
+          triggerRender={renderTrigger2}
+          filterTreeNode
+          searchPosition="trigger"
+          multiple
+          treeData={treeData}
+          placeholder='Custom Trigger'
+          onChange={onValueChange}
+          style={{ width: 300 }}
+      />
+      <br />
+      <TreeSelect
+          triggerRender={renderTrigger3}
+          filterTreeNode
+          searchPosition="trigger"
+          multiple
+          treeData={treeData}
+          placeholder='Custom Trigger'
+          onChange={onValueChange}
+          style={{ width: 300 }}
+      />
+    </>
+  );
+}

+ 4 - 0
packages/semi-ui/treeSelect/index.tsx

@@ -264,6 +264,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
         showRestTagsPopover: PropTypes.bool,
         restTagsPopoverProps: PropTypes.object,
         preventScroll: PropTypes.bool,
+        clickTriggerToHide: PropTypes.bool,
     };
 
     static defaultProps: Partial<TreeSelectProps> = {
@@ -295,6 +296,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
         'aria-label': 'TreeSelect',
         showRestTagsPopover: false,
         restTagsPopoverProps: {},
+        clickTriggerToHide: true,
     };
     inputRef: React.RefObject<typeof Input>;
     tagInputRef: React.RefObject<TagInput>;
@@ -1014,6 +1016,8 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
                 componentName={'TreeSelect'}
                 triggerRender={triggerRender}
                 componentProps={{ ...this.props }}
+                onSearch={this.search}
+                onRemove={this.removeTag}
             />
         ) : (
             [

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

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

+ 15 - 15
yarn.lock

@@ -12894,9 +12894,9 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, gl
     path-is-absolute "^1.0.0"
 
 glob@^9.2.0:
-  version "9.3.0"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-9.3.0.tgz#be6e50d172d025c3fcf87903ae25b36b787c0bb0"
-  integrity sha512-EAZejC7JvnQINayvB/7BJbpZpNOJ8Lrw2OZNEvQxe0vaLn1SuwMcfV7/MNaX8L/T0wmptBFI4YMtDvSBxYDc7w==
+  version "9.3.2"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-9.3.2.tgz#8528522e003819e63d11c979b30896e0eaf52eda"
+  integrity sha512-BTv/JhKXFEHsErMte/AnfiSv8yYOLLiyH2lTg8vn02O21zWFgHPTfxtgn1QRe7NRgggUhC8hacR2Re94svHqeA==
   dependencies:
     fs.realpath "^1.0.0"
     minimatch "^7.4.1"
@@ -17648,9 +17648,9 @@ minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@^3.1.2:
     brace-expansion "^1.1.7"
 
 minimatch@^7.4.1:
-  version "7.4.2"
-  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-7.4.2.tgz#157e847d79ca671054253b840656720cb733f10f"
-  integrity sha512-xy4q7wou3vUoC9k1xGTXc+awNdGaGVHtFUaey8tiX4H1QRc04DZ/rmDFwNm2EBsuYEhAZ6SgMmYf3InGY6OauA==
+  version "7.4.3"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-7.4.3.tgz#012cbf110a65134bb354ae9773b55256cdb045a2"
+  integrity sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==
   dependencies:
     brace-expansion "^2.0.1"
 
@@ -19258,9 +19258,9 @@ path-root@^0.1.1:
     path-root-regex "^0.1.0"
 
 path-scurry@^1.6.1:
-  version "1.6.1"
-  resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.6.1.tgz#dab45f7bb1d3f45a0e271ab258999f4ab7e23132"
-  integrity sha512-OW+5s+7cw6253Q4E+8qQ/u1fVvcJQCJo/VFD8pje+dbJCF1n5ZRMV2AEHbGp+5Q7jxQIYJxkHopnj6nzdGeZLA==
+  version "1.6.3"
+  resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.6.3.tgz#4eba7183d64ef88b63c7d330bddc3ba279dc6c40"
+  integrity sha512-RAmB+n30SlN+HnNx6EbcpoDy9nwdpcGPnEKrJnu6GZoDWBdIjo1UQMVtW2ybtC7LC2oKLcMq8y5g8WnKLiod9g==
   dependencies:
     lru-cache "^7.14.1"
     minipass "^4.0.2"
@@ -21865,9 +21865,9 @@ rimraf@^3.0.0, rimraf@^3.0.2:
     glob "^7.1.3"
 
 rimraf@^4.4.0:
-  version "4.4.0"
-  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-4.4.0.tgz#c7a9f45bb2ec058d2e60ef9aca5167974313d605"
-  integrity sha512-X36S+qpCUR0HjXlkDe4NAOhS//aHH0Z+h8Ckf2auGJk3PTnx5rLmrHkwNdbVQuCSUhOyFrlRvFEllZOYE+yZGQ==
+  version "4.4.1"
+  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-4.4.1.tgz#bd33364f67021c5b79e93d7f4fa0568c7c21b755"
+  integrity sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==
   dependencies:
     glob "^9.2.0"
 
@@ -25200,9 +25200,9 @@ webidl-conversions@^4.0.2:
   integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
 
 "webpack-5@npm:webpack@^5.0.0":
-  version "5.76.2"
-  resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.2.tgz#6f80d1c1d1e3bf704db571b2504a0461fac80230"
-  integrity sha512-Th05ggRm23rVzEOlX8y67NkYCHa9nTNcwHPBhdg+lKG+mtiW7XgggjAeeLnADAe7mLjJ6LUNfgHAuRRh+Z6J7w==
+  version "5.76.3"
+  resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.3.tgz#dffdc72c8950e5b032fddad9c4452e7787d2f489"
+  integrity sha512-18Qv7uGPU8b2vqGeEEObnfICyw2g39CHlDEK4I7NK13LOur1d0HGmGNKGT58Eluwddpn3oEejwvBPoP4M7/KSA==
   dependencies:
     "@types/eslint-scope" "^3.7.3"
     "@types/estree" "^0.0.51"