Przeglądaj źródła

Merge branch 'release'

zhangyumei.0319 3 lat temu
rodzic
commit
9450f134e9
33 zmienionych plików z 561 dodań i 176 usunięć
  1. 4 0
      .eslintrc.js
  2. 2 2
      content/basic/icon/index-en-US.md
  3. 2 2
      content/basic/icon/index.md
  4. 60 59
      content/input/cascader/index.md
  5. 35 13
      content/input/slider/index-en-US.md
  6. 37 14
      content/input/slider/index.md
  7. 1 1
      content/show/table/index-en-US.md
  8. 1 1
      content/show/table/index.md
  9. 12 0
      content/start/changelog/index-en-US.md
  10. 13 0
      content/start/changelog/index.md
  11. 65 0
      cypress/integration/slider.spec.js
  12. 1 1
      lerna.json
  13. 2 2
      packages/semi-animation-react/package.json
  14. 1 1
      packages/semi-animation-styled/package.json
  15. 1 1
      packages/semi-animation/package.json
  16. 1 1
      packages/semi-eslint-plugin/package.json
  17. 1 1
      packages/semi-foundation/package.json
  18. 141 9
      packages/semi-foundation/slider/foundation.ts
  19. 7 5
      packages/semi-foundation/slider/slider.scss
  20. 4 4
      packages/semi-foundation/slider/variables.scss
  21. 2 0
      packages/semi-foundation/treeSelect/treeSelect.scss
  22. 2 2
      packages/semi-foundation/utils/a11y.ts
  23. 2 2
      packages/semi-icons/package.json
  24. 1 1
      packages/semi-illustrations/package.json
  25. 2 2
      packages/semi-next/package.json
  26. 1 1
      packages/semi-scss-compile/package.json
  27. 1 1
      packages/semi-theme-default/package.json
  28. 5 1
      packages/semi-ui/cascader/index.tsx
  29. 11 11
      packages/semi-ui/package.json
  30. 4 2
      packages/semi-ui/slider/_story/slider.stories.js
  31. 63 33
      packages/semi-ui/slider/index.tsx
  32. 1 1
      packages/semi-webpack/package.json
  33. 75 2
      yarn.lock

+ 4 - 0
.eslintrc.js

@@ -20,6 +20,8 @@ module.exports = {
             rules: {
                 // 因为历史原因,现有项目基本全部是4个空格
                 indent: ['error', 4, { 'SwitchCase': 1 }],
+                'comma-spacing': ["error", { "before": false, "after": true }],
+                'no-multi-spaces': ["error", { ignoreEOLComments: true }],
                 'react/display-name': 'off',
                 'react/jsx-indent': ['error', 4],
                 'react/jsx-indent-props': ['error', 4],
@@ -52,6 +54,8 @@ module.exports = {
             rules: {
                 // 因为历史原因,现有项目基本全部是4个空格
                 indent: 'off',
+                'comma-spacing': ["error", { "before": false, "after": true }],
+                'no-multi-spaces': ["error", { ignoreEOLComments: true }],
                 '@typescript-eslint/indent': ['error', 4],
                 'react/display-name': 'off',
                 'react/jsx-indent': ['error', 4],

+ 2 - 2
content/basic/icon/index-en-US.md

@@ -97,7 +97,7 @@ import { IconLikeHeart, IconFlag, IconLock, IconUnlock } from '@douyinfe/semi-ic
 
 ### Custom icon
 You can use custom icons to pass in Icon components
-Icon component supports size, rotate, spinning and other attributes
+Icon component supports size, rotate, spin and other attributes
 
 ```jsx live=true
 import React from 'react';
@@ -105,7 +105,7 @@ import { Icon } from '@douyinfe/semi-ui';
 
 () => {
     function CustomIcon(){
-        return <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+        return <svg width="1em" height="1em" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
             <circle cx="12" cy="12" r="11" fill="#FBCD2C"/>
             <mask id="mask0" maskType="alpha" maskUnits="userSpaceOnUse" x="1" y="1" width="22" height="22">
                 <circle cx="12" cy="12" r="11" fill="#A2845E"/>

+ 2 - 2
content/basic/icon/index.md

@@ -96,7 +96,7 @@ import { IconLikeHeart, IconFlag, IconLock, IconUnlock } from '@douyinfe/semi-ic
 
 ### 自定义图标
 可以使用自定义图标传入Icon组件
-Icon组件支持size、rotate、spining等属性
+Icon组件支持size、rotate、spin等属性
 
 ```jsx live=true
 import React from 'react';
@@ -104,7 +104,7 @@ import { Icon } from '@douyinfe/semi-ui';
 
 () => {
     function CustomIcon(){
-        return <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+        return <svg width="1em" height="1em" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
             <circle cx="12" cy="12" r="11" fill="#FBCD2C"/>
             <mask id="mask0" maskType="alpha" maskUnits="userSpaceOnUse" x="1" y="1" width="22" height="22">
                 <circle cx="12" cy="12" r="11" fill="#A2845E"/>

+ 60 - 59
content/input/cascader/index.md

@@ -1506,67 +1506,68 @@ function Demo() {
 
 ### Cascader
 
-| 属性               | 说明                                                                                 | 类型                                                                             | 默认值                           | 版本   |
-| ------------------ | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- | -------------------------------- | ------ |
-| arrowIcon     |   自定义右侧下拉箭头 Icon,当 showClear 开关打开且当前有选中值时,hover 会优先显示 clear icon                                                              | ReactNode                                                                          |                             | 1.15.0      |
-| autoAdjustOverflow | 是否自动调整下拉框展开方向,用于边缘遮挡时自动调整展开方向 | boolean | true | - |
-| autoMergeValue | 设置自动合并 value。具体而言是,开启后,当某个父节点被选中时,value 将不包括该节点的子孙节点。不支持动态切换 | boolean | true |  1.28.0 |
-| bottomSlot | 底部插槽 | ReactNode | - |  1.27.0 |
-| changeOnSelect     | 是否允许选择非叶子节点                                                                   | boolean                                                                          | false                            | -      |
-| className          | 选择框的 className 属性                                                              | string                                                                           | -                                | -      |
-| defaultOpen       | 设置是否默认打开下拉菜单              | boolean   | false                                | -      |
-| defaultValue       | 指定默认选中的条目                                                                   | string\|number\|TreeNode\|(string\|number\|TreeNode)[]                                                                           | -                                | -      |
-| disabled           | 是否禁用                                                                             | boolean                                                                          | false                            | -      |
-| displayProp        | 设置回填选项显示的属性值                                                                 | string                                                                           | `label`                          | -      |
+| 属性               | 说明                                                                                 | 类型                                                                             | 默认值                           | 版本                               |
+| ------------------ | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- | -------------------------------- |----------------------------------|
+| arrowIcon     |   自定义右侧下拉箭头 Icon,当 showClear 开关打开且当前有选中值时,hover 会优先显示 clear icon                                                              | ReactNode                                                                          |                             | 1.15.0                           |
+| autoAdjustOverflow | 是否自动调整下拉框展开方向,用于边缘遮挡时自动调整展开方向 | boolean | true | -                                |
+| autoMergeValue | 设置自动合并 value。具体而言是,开启后,当某个父节点被选中时,value 将不包括该节点的子孙节点。不支持动态切换 | boolean | true | 1.28.0                           |
+| bottomSlot | 底部插槽 | ReactNode | - | 1.27.0                           |
+| changeOnSelect     | 是否允许选择非叶子节点                                                                   | boolean                                                                          | false                            | -                                |
+| className          | 选择框的 className 属性                                                              | string                                                                           | -                                | -                                |
+| defaultOpen       | 设置是否默认打开下拉菜单              | boolean   | false                                | -                                |
+| defaultValue       | 指定默认选中的条目                                                                   | string\|number\| TreeNode\                        |(string\|number\|TreeNode)[]                                                                           | -                                | -      |
+| disabled           | 是否禁用                                                                             | boolean                                                                          | false                            | -                                |
+| displayProp        | 设置回填选项显示的属性值                                                                 | string                                                                           | `label`                          | -                                |
 | displayRender      | 设置回填格式                                                                 | (selected: string[] \| Entity, idx?: number) => ReactNode                                                        | selected => selected.join(' / ') | -      |
-| dropdownClassName  | 下拉菜单的 className 属性                                                            | string                                                                           | -                                | -      |
-| dropdownStyle      | 下拉菜单的样式                                                                       | object                                                                           | -                                | -      |
-| emptyContent       | 当搜索无结果时展示的内容                                                             | ReactNode                                                                        | `暂无数据`                       | -      |
-| filterLeafOnly       |  搜索结果是否只展示叶子结点路径   | boolean    | true    | 1.26.0    |
+| dropdownClassName  | 下拉菜单的 className 属性                                                            | string                                                                           | -                                | -                                |
+| dropdownStyle      | 下拉菜单的样式                                                                       | object                                                                           | -                                | -                                |
+| emptyContent       | 当搜索无结果时展示的内容                                                             | ReactNode                                                                        | `暂无数据`                       | -                                |
+| filterLeafOnly       |  搜索结果是否只展示叶子结点路径   | boolean    | true    | 1.26.0                           |
 | filterTreeNode     | 设置筛选,默认用 treeNodeFilterProp 的值作为要筛选的 TreeNode 的属性值 | ((inputValue: string, treeNodeString: string) => boolean) \| boolean | false                            | -      |
-| getPopupContainer | 指定父级 DOM,下拉框将会渲染至该 DOM 中,自定义需要设置 position: relative |() => HTMLElement|() => document.body|-|
-| insetLabel         | 前缀标签别名,主要用于 Form                                                          | ReactNode                                                                        | -                                | 0.28.0 |
-| leafOnly | 多选时设置 value 只包含叶子节点,即显示的 Tag 和 onChange 的 value 参数只包含叶子节点。不支持动态切换 | boolean | false |  2.2.0|
-| loadData | 异步加载数据,需要返回一个Promise | (selectOptions: TreeNode[]) => Promise< void > |- |  1.8.0|
-| max| 多选时,限制多选选中的数量,超出 max 后将触发 onExceed 回调 | number |-|1.28.0|
-| maxTagCount| 多选时,标签的最大展示数量,超出后将以 +N 形式展示| number |-|1.28.0|
-| motion | 设置下拉框弹出的动画 |boolean\|object|true|-|
-| mouseEnterDelay | 鼠标移入后,延迟显示下拉框的时间,单位毫秒 | number | 50 | - |
-| mouseLeaveDelay | 鼠标移出后,延迟消失下拉框的时间,单位毫秒 | number | 50 | - |
-| multiple | 设置多选 | boolean | false |  1.28.0 |
-| placeholder        | 选择框默认文字                                                                       | string                                                                           | -                                | -      |
-| prefix             | 前缀标签                                                                             | ReactNode                                                                        | -                                | 0.28.0 |
-| preventScroll | 指示浏览器是否应滚动文档以显示新聚焦的元素,作用于组件内的 focus 方法 | boolean |  |  |
-|restTagsPopoverProps |Popover 的配置属性,可以控制 position、zIndex、trigger 等,具体参考[Popover](/zh-CN/show/popover#API%20%E5%8F%82%E8%80%83)           |PopoverProps     | {}        |1.28.0|
-| searchPlaceholder  | 搜索框默认文字                                                                       | string                                                                           | -                                | -      |
-| separator  | 自定义分隔符,包括:搜索时显示在下拉框的内容以及单选时回显到 Trigger 的内容的分隔符    | string                                                                           | ' / '                                | 2.2.0      |
-| showClear       |  是否展示清除按钮   | boolean    | false    | 0.35.0    |
-| showNext| 设置展开 Dropdown 子菜单的方式,可选: `click`、`hover` | string |`click`|1.29.0|
-| showRestTagsPopover| 当超过 maxTagCount,hover 到 +N 时,是否通过 Popover 显示剩余内容| boolean |false|1.28.0|
-| size               | 选择框大小,可选 `large`,`small`,`default`                                         | string                                                                           | `default`                        | -      |
-| stopPropagation | 是否阻止下拉框上的点击事件冒泡 | boolean | true | - |
-| disableStrictly | 设置是否开启严格禁用。开启后,当节点是 disabled 的时候,则不能通过子级或者父级的关系改变选中状态 | boolean | false | 1.32.0|
-| style              | 选择框的样式                                                                         | CSSProperties                                                                           | -                                | -      |
-| suffix             | 后缀标签                                                                             | ReactNode                                                                        | -                                | 0.28.0 |
-| topSlot | 顶部插槽 | ReactNode | - |  1.27.0 |
-| treeData           | 展示数据,具体属性参考 [TreeNode](#TreeNode)              | TreeNode[]                                                                  | \[]                              | -      |
-| treeNodeFilterProp | 搜索时输入项过滤对应的 treeNode 属性                                                 | string                                                                           | `label`                          | -      |
-| triggerRender | 自定义触发器渲染方法  | (triggerRenderData: object) => ReactNode | - | 0.34.0 |
-| validateStatus | trigger 的校验状态,仅影响展示样式。可选: default、error、warning | string | `default` | - |
-| value       | (受控)选中的条目                                                                   | string\|number\|TreeNode\|(string\|number\|TreeNode)[]                                                                           | -                                | -      |
-| zIndex | 下拉菜单的 zIndex | number | 1030 | - |
-| enableLeafClick | 多选时,是否启动点击叶子节点选项触发勾选 | boolean | false | 2.2.0 |
-| onBlur | 失焦 Cascader 的回调 | (e: MouseEvent) => void | - | - |
-| onChange           | 选中树节点时调用此函数,默认返回选中项 path 的 value 数组                            | (value: string\|number\|TreeNode\|(string\|number\|TreeNode)[]) => void                                                                         | -                                | -      |
-| onChangeWithObject | 是否将选中项 option 的其他属性作为回调。设为 true 时,onChange 的入参类型会从 string/number 变为 TreeNode。此时如果是受控,也需要把 value 设置成 TreeNode 类型,且必须含有 value 的键值,defaultValue 同理 | boolean | false | 1.16.0 |
-| onClear| showClear 为 true 时,点击清空按钮触发的回调 | () => void |-|1.29.0|
-| onDropdownVisibleChange       | 下拉框切换时的回调   | (visible: boolean) => void        | -                                | 0.35.0    |
-| onExceed| 多选时,超出 max 后触发的回调 | (checkedItem: Entity[]) => void |-|1.28.0|
-| onFocus| 聚焦 Cascader 的回调 | (e: MouseEvent) => void | - | - |
-| onListScroll | 下拉面板滚动的回调 | (e: React.Event, panel: { panelIndex: number; activeNode: TreeNode; } ) => void | - | 1.15.0 |
-| onLoad | 节点加载完毕时触发的回调 | (newLoadedKeys: Set< string >, data: TreeNode) => void |- |  1.8.0|
-| onSearch           | 文本框值变化时回调                                                                   | (value: string) => void                                                                         | -                                | -      |
-| onSelect           | 被选中时调用,返回选中项的 value                                                     | (value: string \| number \| (string \| number)[]) => void                                                                         | -                                | -      |
+| getPopupContainer | 指定父级 DOM,下拉框将会渲染至该 DOM 中,自定义需要设置 position: relative |() => HTMLElement|() => document.body| -                                |
+| insetLabel         | 前缀标签别名,主要用于 Form                                                          | ReactNode                                                                        | -                                | 0.28.0                           |
+| leafOnly | 多选时设置 value 只包含叶子节点,即显示的 Tag 和 onChange 的 value 参数只包含叶子节点。不支持动态切换 | boolean | false | 2.2.0                            |
+| loadData | 异步加载数据,需要返回一个Promise | (selectOptions: TreeNode[]) => Promise< void > |- | 1.8.0                            |
+| max| 多选时,限制多选选中的数量,超出 max 后将触发 onExceed 回调 | number |-| 1.28.0                           |
+| maxTagCount| 多选时,标签的最大展示数量,超出后将以 +N 形式展示| number |-| 1.28.0                           |
+| motion | 设置下拉框弹出的动画 |boolean\|object| true                             |-|
+| mouseEnterDelay | 鼠标移入后,延迟显示下拉框的时间,单位毫秒 | number | 50 | -                                |
+| mouseLeaveDelay | 鼠标移出后,延迟消失下拉框的时间,单位毫秒 | number | 50 | -                                |
+| multiple | 设置多选 | boolean | false | 1.28.0                           |
+| placeholder        | 选择框默认文字                                                                       | string                                                                           | -                                | -                                |
+| position           | 方向,可选值:`top`,`topLeft`,`topRight`,`left`,`leftTop`,`leftBottom`,`right`,`rightTop`,`rightBottom`,`bottom`,`bottomLeft`,`bottomRight` | string                     | "bottom"                                    | 2.16.0-beta.0                    |
+| prefix             | 前缀标签                                                                             | ReactNode                                                                        | -                                | 0.28.0                           |
+| preventScroll | 指示浏览器是否应滚动文档以显示新聚焦的元素,作用于组件内的 focus 方法 | boolean |  |                                  |
+|restTagsPopoverProps |Popover 的配置属性,可以控制 position、zIndex、trigger 等,具体参考[Popover](/zh-CN/show/popover#API%20%E5%8F%82%E8%80%83)           |PopoverProps     | {}        | 1.28.0                           |
+| searchPlaceholder  | 搜索框默认文字                                                                       | string                                                                           | -                                | -                                |
+| separator  | 自定义分隔符,包括:搜索时显示在下拉框的内容以及单选时回显到 Trigger 的内容的分隔符    | string                                                                           | ' / '                                | 2.2.0                            |
+| showClear       |  是否展示清除按钮   | boolean    | false    | 0.35.0                           |
+| showNext| 设置展开 Dropdown 子菜单的方式,可选: `click`、`hover` | string |`click`| 1.29.0                           |
+| showRestTagsPopover| 当超过 maxTagCount,hover 到 +N 时,是否通过 Popover 显示剩余内容| boolean |false| 1.28.0                           |
+| size               | 选择框大小,可选 `large`,`small`,`default`                                         | string                                                                           | `default`                        | -                                |
+| stopPropagation | 是否阻止下拉框上的点击事件冒泡 | boolean | true | -                                |
+| disableStrictly | 设置是否开启严格禁用。开启后,当节点是 disabled 的时候,则不能通过子级或者父级的关系改变选中状态 | boolean | false | 1.32.0                           |
+| style              | 选择框的样式                                                                         | CSSProperties                                                                           | -                                | -                                |
+| suffix             | 后缀标签                                                                             | ReactNode                                                                        | -                                | 0.28.0                           |
+| topSlot | 顶部插槽 | ReactNode | - | 1.27.0                           |
+| treeData           | 展示数据,具体属性参考 [TreeNode](#TreeNode)              | TreeNode[]                                                                  | \[]                              | -                                |
+| treeNodeFilterProp | 搜索时输入项过滤对应的 treeNode 属性                                                 | string                                                                           | `label`                          | -                                |
+| triggerRender | 自定义触发器渲染方法  | (triggerRenderData: object) => ReactNode | - | 0.34.0                           |
+| validateStatus | trigger 的校验状态,仅影响展示样式。可选: default、error、warning | string | `default` | -                                |
+| value       | (受控)选中的条目                                                                   | string\|number\| TreeNode\                        |(string\|number\|TreeNode)[]                                                                           | -                                | -      |
+| zIndex | 下拉菜单的 zIndex | number | 1030 | -                                |
+| enableLeafClick | 多选时,是否启动点击叶子节点选项触发勾选 | boolean | false | 2.2.0                            |
+| onBlur | 失焦 Cascader 的回调 | (e: MouseEvent) => void | - | -                                |
+| onChange           | 选中树节点时调用此函数,默认返回选中项 path 的 value 数组                            | (value: string\|number\| TreeNode\                        |(string\|number\|TreeNode)[]) => void                                                                         | -                                | -      |
+| onChangeWithObject | 是否将选中项 option 的其他属性作为回调。设为 true 时,onChange 的入参类型会从 string/number 变为 TreeNode。此时如果是受控,也需要把 value 设置成 TreeNode 类型,且必须含有 value 的键值,defaultValue 同理 | boolean | false | 1.16.0                           |
+| onClear| showClear 为 true 时,点击清空按钮触发的回调 | () => void |-| 1.29.0                           |
+| onDropdownVisibleChange       | 下拉框切换时的回调   | (visible: boolean) => void        | -                                | 0.35.0                           |
+| onExceed| 多选时,超出 max 后触发的回调 | (checkedItem: Entity[]) => void |-| 1.28.0                           |
+| onFocus| 聚焦 Cascader 的回调 | (e: MouseEvent) => void | - | -                                |
+| onListScroll | 下拉面板滚动的回调 | (e: React.Event, panel: { panelIndex: number; activeNode: TreeNode; } ) => void | - | 1.15.0                           |
+| onLoad | 节点加载完毕时触发的回调 | (newLoadedKeys: Set< string >, data: TreeNode) => void |- | 1.8.0                            |
+| onSearch           | 文本框值变化时回调                                                                   | (value: string) => void                                                                         | -                                | -                                |
+| onSelect           | 被选中时调用,返回选中项的 value                                                     | (value: string \| number \| (string \                        | number)[]) => void                                                                         | -                                | -      |
 
 ### TreeNode
 

+ 35 - 13
content/input/slider/index-en-US.md

@@ -75,7 +75,7 @@ class InputSlider extends React.Component {
                 <div style={{ width: 320, marginRight: 15 }}>
                     <Slider step={1} value={value} onChange={(value) => (this.getSliderValue(value))} ></Slider>
                 </div>
-                <InputNumber onChange={(v) => this.getSliderValue(v)} style={{width: 100}} value={value} min={0} max={100} />
+                <InputNumber onChange={(v) => this.getSliderValue(v)} style={{ width: 100 }} value={value} min={0} max={100} />
             </div>
         );
     }
@@ -119,11 +119,11 @@ import { Slider } from '@douyinfe/semi-ui';
         <br/>
         <br/>
         <div>Marks</div>
-        <Slider marks={{ 20: '20c', 40: '40c' }} defaultValue={[0, 100]} range={true} ></Slider>
+        <Slider marks={{ 20: '20°C', 40: '40°C' }} defaultValue={[0, 100]} range={true} tipFormatter={v => (`${v}°C`)} getAriaValueText={(value) => `${value}°C`}></Slider>
         <br/>
         <br/>
         <div>Inclued</div>
-        <Slider marks={{ 20: '20c', 40: '40c' }} included={false} defaultValue={[0, 100]} range={true}></Slider>
+        <Slider marks={{ 20: '20°C', 40: '40°C' }} included={false} defaultValue={[0, 100]} range={true} tipFormatter={v => (`${v}°C`)} getAriaValueText={(value) => `${value}°C`}></Slider>
     </div>
 );
 ```
@@ -216,23 +216,23 @@ import { Slider } from '@douyinfe/semi-ui';
 
 () => (
     <div>
-        <div style={{height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block'}}>
+        <div style={{ height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block' }}>
             <Slider vertical></Slider>
         </div>
-        <div style={{height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block'}}>
+        <div style={{ height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block' }}>
             <Slider vertical verticalReverse></Slider>
         </div>
-        <div style={{height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block'}}>
+        <div style={{ height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block' }}>
             <Slider vertical range defaultValue={[20, 60]}></Slider>
         </div>
-        <div style={{height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block'}}>
+        <div style={{ height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block' }}>
             <Slider vertical verticalReverse range defaultValue={[20, 60]}></Slider>
         </div>
-        <div style={{height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block'}}>
-            <Slider vertical range marks={{ 20: '20c', 40: '40c' }} step={10} defaultValue={[20, 60]}></Slider>
+        <div style={{ height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block' }}>
+            <Slider vertical range marks={{ 20: '20°C', 40: '40°C' }} step={10} defaultValue={[20, 60]}></Slider>
         </div>
-        <div style={{height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block'}}>
-            <Slider vertical verticalReverse range marks={{ 20: '20c', 40: '40c' }} step={10} defaultValue={[20, 60]}></Slider>
+        <div style={{ height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block' }}>
+            <Slider vertical verticalReverse range marks={{ 20: '20°C', 40: '40°C' }} step={10} defaultValue={[20, 60]}></Slider>
         </div>
     </div>
 );
@@ -242,6 +242,9 @@ import { Slider } from '@douyinfe/semi-ui';
 
 | Property       | Instructions                                                                               | type          | Default | Version | 
 | -------------- | ------------------------------------------------------------------------------------------ | ------------- | ------- |------ |
+| aria-label| [aria-label](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-label) used to define a string that labels the current element. Use it in cases where a text label is not visible on the screen | string |-|-|
+| aria-labelledby | [aria-labelledby](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-labelledby) attribute establishes relationships between objects and their label(s), and its value should be one or more element IDs, which refer to elements that have the text needed for labeling | string |-|-|
+| aria-valuetext| [aria-valuetext](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-valuetext) used to provide a user-friendly name for the current value of the slider | string |-|-|
 | defaultValue   | Default value                                                                              | number \| number[] | 0       |- |
 | disabled       | Disable slider                                                                             | boolean       | false   |- |
 | included       | Takes effect when `marks` is not null, true means containment and false means coordination | boolean       | true    |- |
@@ -259,7 +262,7 @@ import { Slider } from '@douyinfe/semi-ui';
 | verticalReverse | Vertical but reverse direction >=1.29.0| boolean | false |-|
 | onAfterChange  | Triggered when onmouseup is invoked, passed in current value as params                     | (value: number \| number[]) => void      | -       |- |
 | onChange       | Callback function when slider value changes                                                | (value: number \| number[]) => void      | -       |- |
-
+| getAriaValueText | Used to provide a user-friendly name for the current value of the slider, important for screen reader users,  The parameters value and index are the current slider value, order | (value: number, index?: number) => string |-|-|
 ## Accessibility
 
 ### ARIA
@@ -269,8 +272,27 @@ import { Slider } from '@douyinfe/semi-ui';
 - The slider element has the `aria-valuemin` property set to a decimal value representing the minimum allowed value of the slider.
 - The slider element has the `aria-valuemax` property set to a decimal value representing the maximum allowed value of the slider.
 - If the slider is vertically oriented, it has `aria-orientation` set to vertical.
-- If the value of `aria-valuenow` is not user-friendly, e.g., the day of the week is represented by a number, support setting API `aria-valuetext` property to a string that makes the slider value understandable, e.g., "Monday". And you can use API `getAriaValueText(value)` to specify `aria-valuetext`.
+- If the value of `aria-valuenow` is not user-friendly, e.g., the day of the week is represented by a number, support setting API `aria-valuetext` property to a string that makes the slider value understandable, e.g., "Monday". And you can use API `getAriaValueText(value, index)` to specify `aria-valuetext`.
 - Supporting API `aria-label` `aria-labelledby` to specify Slider label.
 
+### Keyboard and Focus
+
+- The slider of Slider can get the focus and display the prompt information of the current slider, and this information needs to be read by assistive technology.
+- When the user uses the `range` API, you can use `Tab` and `Shift` + `Tab` to switch the focus of the left and right sliders.
+- Keyboard users can use `Up Arrow` or `Right Arrow` to increase the slider value, `Down Arrow` or `Left Arrow` to decrease the slider value.
+- If you want the slider to change more than the step size, Slider supports 10*step changes:
+  - Windows users: `Page Up` for increasing, `Page Down` for decreasing;
+  - Mac users:`Fn` + `Up Arrow` for increasing, `Fn` + `Down Arrow` for decreasing;
+  - When the user uses the `range` property, the Page Up key of the previous slider is only supported until it meets the next slider, and then using the Page Up key on the previous slider will not respond. The same is true for the latter slider. After encountering, there is no response to the Page Down key.
+- To move the slider to the minimum value of the slider:
+  - Windows users: `Home`;
+  - Mac users: `Fn` + `left arrow`;
+  - When the user uses the `range` property, the `Home`(`Fn` + `left arrow`) button of the latter slider only supports until it meets the previous slider, and the `Home`(`Fn` + `left arrow`) button is unresponsive after the overlap.
+- To move the slider to the maximum value of the slider:
+  - Windows users: `End`;
+  - Mac users: `Fn` + `right arrow`;
+  - When the user uses the `range` property, the `End`(`Fn` + `right arrow`) key of the previous slider is only supported until it meets the next slider, and the `End`(`Fn` + `right arrow`) key is unresponsive after the overlap.
+
+
 ## Design Tokens
 <DesignToken/>

+ 37 - 14
content/input/slider/index.md

@@ -70,7 +70,7 @@ class InputSlider extends React.Component {
                 <div style={{ width: 320, marginRight: 15 }}>
                     <Slider step={1} value={value} onChange={(value) => (this.getSliderValue(value))} ></Slider>
                 </div>
-                <InputNumber onChange={(v) => this.getSliderValue(v)} style={{width: 100}} value={value} min={0} max={100} />
+                <InputNumber onChange={(v) => this.getSliderValue(v)} style={{ width: 100 }} value={value} min={0} max={100} />
             </div>
         );
     }
@@ -78,14 +78,14 @@ class InputSlider extends React.Component {
 ```
 
 ### 自定义提示
-使用 `tipFormatter` 可以设置 Tooltip 的显示的格式。设置 `tipFormatter={null}`,则隐藏 Tooltip。
+使用 `tipFormatter` 可以设置 Tooltip 的显示的格式。设置 `tipFormatter={null}`,则隐藏 Tooltip。`getAriaValueText`用于给滑块的当前值提供一个用户友好的名称,对屏幕阅读器用户很重要。
 ```jsx live=true
 import React from 'react';
 import { Slider } from '@douyinfe/semi-ui';
 
 () => (
     <div>
-        <Slider tipFormatter={v => (`${v}%`)} />
+        <Slider tipFormatter={v => (`${v}%`)} getAriaValueText={v => (`${v}%`)}/>
         <br/>
         <br/>
         <Slider tipFormatter={null} />
@@ -110,11 +110,11 @@ import { Slider } from '@douyinfe/semi-ui';
         <br/>
         <br/>
         <div>Marks</div>
-        <Slider marks={{ 20: '20c', 40: '40c' }} defaultValue={[0, 100]} range={true} ></Slider>
+        <Slider marks={{ 20: '20°C', 40: '40°C' }} defaultValue={[0, 100]} tipFormatter={v => (`${v}°C`)} range={true} getAriaValueText={(value) => `${value}°C`}></Slider>
         <br/>
         <br/>
         <div>Inclued</div>
-        <Slider marks={{ 20: '20c', 40: '40c' }} included={false} defaultValue={[0, 100]} range={true}></Slider>
+        <Slider marks={{ 20: '20°C', 40: '40°C' }} included={false} defaultValue={[0, 100]} range={true} tipFormatter={v => (`${v}°C`)} getAriaValueText={(value) => `${value}°C`}></Slider>
     </div>
 );
 ```
@@ -203,23 +203,23 @@ import { Slider } from '@douyinfe/semi-ui';
 
 () => (
     <div>
-        <div style={{height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block'}}>
+        <div style={{ height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block' }}>
             <Slider vertical></Slider>
         </div>
-        <div style={{height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block'}}>
+        <div style={{ height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block' }}>
             <Slider vertical verticalReverse></Slider>
         </div>
-        <div style={{height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block'}}>
+        <div style={{ height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block' }}>
             <Slider vertical range defaultValue={[20, 60]}></Slider>
         </div>
-        <div style={{height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block'}}>
+        <div style={{ height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block' }}>
             <Slider vertical verticalReverse range defaultValue={[20, 60]}></Slider>
         </div>
-        <div style={{height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block'}}>
-            <Slider vertical range marks={{ 20: '20c', 40: '40c' }} step={10} defaultValue={[20, 60]}></Slider>
+        <div style={{ height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block' }}>
+            <Slider vertical range marks={{ 20: '20°C', 40: '40°C' }} step={10} defaultValue={[20, 60]}></Slider>
         </div>
-        <div style={{height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block'}}>
-            <Slider vertical verticalReverse range marks={{ 20: '20c', 40: '40c' }} step={10} defaultValue={[20, 60]}></Slider>
+        <div style={{ height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block' }}>
+            <Slider vertical verticalReverse range marks={{ 20: '20°C', 40: '40°C' }} step={10} defaultValue={[20, 60]}></Slider>
         </div>
     </div>
 );
@@ -229,6 +229,9 @@ import { Slider } from '@douyinfe/semi-ui';
 ## API参考
 | 属性  | 说明        | 类型   | 默认值 | 版本 | 
 |-------|-------------|-----------------|--------|-------|
+| aria-label| [aria-label](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-label)属性,用来给当前元素加上的标签描述, 提升可访问性 | string |-|-|
+| aria-labelledby | [aria-labelledby](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-labelledby)属性,表明某些元素的 id 是某一对象的标签。它被用来确定控件或控件组与它们标签之间的联系, 提升可访问性 | string |-|-|
+| aria-valuetext| [aria-valuetext](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-valuetext)属性,为滑块的当前值提供用户友好的名称。| string |-|-|
 | defaultValue | 设置初始取值 | number \| number[] | 0 |-|
 | disabled | 滑块是否禁用 | boolean | false |-|
 | included | `marks` 不为空对象时有效,值为 true 时表示值为包含关系,false 表示并列 | boolean | true |-|
@@ -246,6 +249,7 @@ import { Slider } from '@douyinfe/semi-ui';
 | verticalReverse | 反转垂直方向,即上大下小 >=1.29.0| boolean | false |-|
 | onAfterChange | 与 `onmouseup` 触发时机一致,把当前值作为参数传入 | (value: number \| number[]) => void | 无 |-|
 | onChange | 当 Slider 的值发生改变时的回调 | (value: number \| number[]) => void | 无 |-|
+| getAriaValueText | 用于给滑块的当前值提供一个用户友好的名称,对屏幕阅读器用户很重要,参数value为当前滑块的值,index为当前滑块的顺序 | (value: number, index?: number) => string |-|-|
 
 ## Accessibility
 
@@ -256,9 +260,28 @@ import { Slider } from '@douyinfe/semi-ui';
 - 元素的 `aria-valuemin` 属性为最小允许值的十进制数值。
 - 元素的 `aria-valuemax` 属性为最大允许值的十进制数值。
 - 当 Slider 为纵向时,元素的 `aria-orientation` 属性为 'vertical'。
-- 当 `aria-valuenow` 的值不容易被理解时,支持通过 API `aria-valuetext` 传递一个字符串使其更友好。也可以通过 API `geAriaValueText(value)` 方法得到 `aria-valuetext` 的值。
+- 当 `aria-valuenow` 的值不容易被理解时,支持通过 API `aria-valuetext` 传递一个字符串使其更友好。也可以通过 API `getAriaValueText(value, index)` 方法得到 `aria-valuetext` 的值。
 - 支持通过 API `aria-label` 或者 `aria-labelledby` 确定 slider 的标签。
 
+### 键盘和焦点
+
+- Slider 的滑块可被获取到焦点,并展示当前滑块的提示信息,且这些信息需要被辅助技术读取到。
+- 当用户使用 `range` 属性时,可以使用 `Tab` 及 `Shift`  + `Tab` 切换左右两个滑块的焦点。
+- 键盘用户可以通过 `上箭头` 或 `右箭头` 来增加滑块值,`下箭头` 或 `左箭头` 来减少滑块值。
+- 若想要滑块高于步长的变化量时, slider支持 10*step 的变化量:
+  - Windows 用户: `Page Up` 用于增加,`Page Down` 用于减少;
+  - Mac 用户使用: `Fn` + `上箭头` 用于增加,`Fn` + `下箭头` 用于按键;
+  - 当用户使用 `range` 属性时,前一个滑块的  `Page Up`(`Fn` + `上箭头`) 键仅支持到与后一个滑块相遇,重合后再对前一个滑块使用  Page Up 键则无响应。后一个滑块同理,相遇后,对`Page Down`(`Fn` + `下箭头`) 键无响应。
+- 若想将滑块移动到滑杆的最小值处:
+  - Windows 用户: `Home` ;
+  - Mac 用户: `Fn` + `左箭头`;
+  - 当用户使用 `range` 属性时,后一个滑块的 `Home`(`Fn` + `左箭头`) 键仅支持到与前一个滑块相遇,重合后再次使用 `Home`(`Fn` + `左箭头`) 键无响应。
+- 若想将滑块移动到滑杆的最大值处:
+  - Windows 用户:`End` ;
+  - Mac 用户:`Fn` + `右箭头`;
+  - 当用户使用 `range` 属性时,前一个滑块的 `End`(`Fn` + `右箭头`) 键仅支持到与后一个滑块相遇,重合后再次使用 `End`(`Fn` + `右箭头`) 键无响应。
+
+
 
 ## 设计变量
 <DesignToken/>

+ 1 - 1
content/show/table/index-en-US.md

@@ -4378,7 +4378,7 @@ render(App);
 | empty                   | Content displayed when there is no data                                                                                   | ReactNode                                                                                                          | ReactNode  | 'No data yet. '                                                   |
 | expandCellFixed         | Whether the column of the expansion icon is fixed or not, the same value as the fixed value in Column                     | boolean\|string                                                                                                 | false      |
 | expandIcon              | Custom expansion icon, hidden when it is `false`                                                                          | boolean <br/>\|ReactNode <br/>\| (expanded: boolean) => ReactNode                                               |            |
-| expandedRowKeys         | Expanded rows, the row expansion function will be controlled when this parameter is introduced.                           | (string | number)[]                                                                                                 |            |
+| expandedRowKeys         | Expanded rows, the row expansion function will be controlled when this parameter is introduced.                           | (string \| number)[]                                                                                                 |            |
 | expandedRowRender       | Extra unfolding lines                                                                                                     | (record: object, index: number, expanded: boolean) => ReactNode                                                 |            |
 | expandAllRows           | All rows are expanded                                                           | boolean                                                                                                         | false      | **1.30.0**
 | expandAllGroupRows      | All grouped rows are expanded                                                           | boolean                                                                                                         | false      | **1.30.0**

+ 1 - 1
content/show/table/index.md

@@ -4384,7 +4384,7 @@ render(App);
 | empty                     | 无数据时展示的内容                                                                                             | ReactNode                                                                                                       | '暂无数据' |
 | expandCellFixed           | 展开图标所在列是否固定,与 Column 中的 fixed 取值相同                                                          | boolean\|string                                                                                                 | false      |
 | expandIcon                | 自定义展开按钮,传 `false` 关闭默认的渲染                                                                      | boolean \| ReactNode<br/> \| (expanded: boolean) => ReactNode                                                   |            |
-| expandedRowKeys           | 展开的行,传入此参数时行展开功能将受控                                                                         | (string                                                                                                         | number)[]  |                                         |
+| expandedRowKeys           | 展开的行,传入此参数时行展开功能将受控                                                                         | (string \| number)[]  |                                         |
 | expandedRowRender         | 额外的展开行                                                                                                   | (record: object, index: number, expanded: boolean) => ReactNode                                                 |            |
 | expandAllRows             | 是否展开所有行                                                                                                 | boolean                                                                                                         | false      | **1.30.0**                              |
 | expandAllGroupRows        | 是否展开分组行                                                                                                 | boolean                                                                                                         | false      | **1.30.0**                              |

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

@@ -17,6 +17,18 @@ Version:Major.Minor.Patch (follow the **Semver** specification)
 ---
 
 
+#### 🎉 2.16.0-beta.0 (2022-07-25)
+- 【Fix】
+    - fix the problem that it does not take effect when the noHorizontalPadding parameter type of Button is string
+    - fix the issue that the item in the right panel can still be deleted and cannot be dragged after the item is disabled in the draggable Transfer
+    - fix the problem that the icon and text are not aligned when the Text component sets the icon through the icon API
+    - allows users to customize the type and theme of the Button individually through the parameters of the Button in the ButtonGroup
+    - fix the problem that the width of TreeSelect arrows is not uniform after wrapping when multiple selections are made
+- 【Feat】
+    - Cascader adds position API to control the direction of the bullet layer
+    - Slider adds A11y focus and keyboard adaptation
+- 【Docs】
+    - Icon update custom icon example
 #### 🎉 2.15.1 (2022-07-19)
 - 【Fix】
     - Fix the problem that @douyinfe/semi-illustrations failed to shaking [#961](https://github.com/DouyinFE/semi-design/issues/961)

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

@@ -16,6 +16,19 @@ Semi 版本号遵循 **Semver** 规范(主版本号-次版本号-修订版本
 
 ---
 
+#### 🎉 2.16.0-beta.0 (2022-07-25)
+- 【Fix】
+    - 修复当Button的noHorizontalPadding参数类型为string时不生效问题
+    - 修复可拖拽的Transfer禁用item后右侧面板中的item仍然能够删除且不可拖动的问题
+    - 修复Text组件通过icon API设置icon时icon和文字不对齐问题
+    - 允许用户通过ButtonGroup中的Button的参数单独定制Button的type和theme
+    - 修复多选时,换行后TreeSelect 箭头宽度不统一问题
+- 【Feat】
+    - Cascader 新增 position API 控制弹层方向
+    - Slider 新增 A11y 焦点及键盘适配
+- 【Docs】
+    - Icon 更新自定义图标示例
+
 #### 🎉 2.15.1 (2022-07-19)
 - 【Fix】
     - 修复 @douyinfe/semi-illustrations 插画按需加载失效的问题 [#961](https://github.com/DouyinFE/semi-design/issues/961)

+ 65 - 0
cypress/integration/slider.spec.js

@@ -141,4 +141,69 @@ describe('slider', () => {
             expect($button.position()).deep.equal(handleInitialPos);
         });
     });
+
+    it('keyboard', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=slider--horizontal-slider&args=&viewMode=story');
+        cy.get('.semi-slider-handle').eq(0).click();
+        // test keyboard event: upArrow
+        cy.get('.semi-slider-handle').eq(0).type('{upArrow}');
+        cy.get('.semi-slider-handle').eq(0).should('have.attr', 'aria-valuenow', '1');
+        // test keyboard event: rightArrow
+        cy.get('.semi-slider-handle').eq(0).type('{rightArrow}');
+        cy.get('.semi-slider-handle').eq(0).should('have.attr', 'aria-valuenow', '2');
+        // test keyboard event: downArrow
+        cy.get('.semi-slider-handle').eq(0).type('{downArrow}');
+        cy.get('.semi-slider-handle').eq(0).should('have.attr', 'aria-valuenow', '1');
+        // test keyboard event: leftArrow
+        cy.get('.semi-slider-handle').eq(0).type('{leftArrow}');
+        cy.get('.semi-slider-handle').eq(0).should('have.attr', 'aria-valuenow', '0');
+        // test keyboard event: pageup
+        cy.get('.semi-slider-handle').eq(0).type('{pageup}');
+        cy.get('.semi-slider-handle').eq(0).should('have.attr', 'aria-valuenow', '10');
+        // test keyboard event: pagedown
+        cy.get('.semi-slider-handle').eq(0).type('{pagedown}');
+        cy.get('.semi-slider-handle').eq(0).should('have.attr', 'aria-valuenow', '0');
+        // test keyboard event: End
+        cy.get('.semi-slider-handle').eq(0).type('{end}');
+        cy.get('.semi-slider-handle').eq(0).should('have.attr', 'aria-valuenow', '100');
+        // test keyboard event: Home
+        cy.get('.semi-slider-handle').eq(0).type('{home}');
+        cy.get('.semi-slider-handle').eq(0).should('have.attr', 'aria-valuenow', '0');
+        // test keyboard event: tab
+        cy.get('.semi-slider-handle').eq(0).tab();
+        cy.get('.semi-slider-handle').eq(1).should('be.focused');
+    });
+
+    it('range', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=slider--horizontal-slider&args=&viewMode=story');
+        // click range slider right dot
+        cy.get('.semi-slider-handle').eq(2).click();
+        cy.get('.semi-slider-handle').eq(2).type('{pageup}').type('{pageup}').type('{pageup}').type('{pageup}');
+        cy.get('.semi-slider-handle').eq(2).should('have.attr', 'aria-valuenow', '60');
+        // The value of the left slider cannot exceed the value of the right slider
+        cy.get('.semi-slider-handle').eq(2).type('{pageup}');
+        cy.get('.semi-slider-handle').eq(2).should('have.attr', 'aria-valuenow', '60');
+        cy.get('.semi-slider-handle').eq(2).type('{rightArrow}');
+        cy.get('.semi-slider-handle').eq(2).should('have.attr', 'aria-valuenow', '60');
+        cy.get('.semi-slider-handle').eq(2).type('{End}');
+        cy.get('.semi-slider-handle').eq(2).should('have.attr', 'aria-valuenow', '60');
+        // The value of the right slider cannot be lower than the value of the left slider
+        cy.get('.semi-slider-handle').eq(2).tab();
+        cy.get('.semi-slider-handle').eq(3).type('{pagedown}');
+        cy.get('.semi-slider-handle').eq(3).should('have.attr', 'aria-valuenow', '60');
+        cy.get('.semi-slider-handle').eq(3).type('{leftArrow}');
+        cy.get('.semi-slider-handle').eq(3).should('have.attr', 'aria-valuenow', '60');
+        cy.get('.semi-slider-handle').eq(3).type('{Home}');
+        cy.get('.semi-slider-handle').eq(3).should('have.attr', 'aria-valuenow', '60');
+        cy.get('.semi-slider-handle').eq(3).type('{pageup}');
+        cy.get('.semi-slider-handle').eq(3).should('have.attr', 'aria-valuenow', '70');
+        cy.get('.semi-slider-handle').eq(3).type('{End}');
+        cy.get('.semi-slider-handle').eq(3).should('have.attr', 'aria-valuenow', '100');
+
+        cy.get('.semi-slider-handle').eq(2).click();
+        cy.get('.semi-slider-handle').eq(2).type('{pagedown}');
+        cy.get('.semi-slider-handle').eq(2).should('have.attr', 'aria-valuenow', '50');
+        cy.get('.semi-slider-handle').eq(2).type('{Home}');
+        cy.get('.semi-slider-handle').eq(2).should('have.attr', 'aria-valuenow', '0');
+    });  
 });

+ 1 - 1
lerna.json

@@ -1,5 +1,5 @@
 {
     "useWorkspaces": true,
     "npmClient": "yarn",
-    "version": "2.15.1"
+    "version": "2.17.0-alpha.1"
 }

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

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-animation-react",
-  "version": "2.15.1",
+  "version": "2.17.0-alpha.1",
   "description": "motion library for semi-ui-react",
   "keywords": [
     "motion",
@@ -27,7 +27,7 @@
   "dependencies": {
     "@babel/runtime-corejs3": "^7.15.4",
     "@douyinfe/semi-animation": "2.12.0",
-    "@douyinfe/semi-animation-styled": "2.15.1",
+    "@douyinfe/semi-animation-styled": "2.17.0-alpha.1",
     "classnames": "^2.2.6"
   },
   "peerDependencies": {

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

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-animation-styled",
-  "version": "2.15.1",
+  "version": "2.17.0-alpha.1",
   "description": "semi styled animation",
   "keywords": [
     "semi",

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

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-animation",
-  "version": "2.15.1",
+  "version": "2.17.0-alpha.1",
   "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.15.1",
+  "version": "2.17.0-alpha.1",
   "description": "semi ui eslint plugin",
   "keywords": [
     "semi",

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

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

+ 141 - 9
packages/semi-foundation/slider/foundation.ts

@@ -3,7 +3,8 @@
 /* eslint-disable no-nested-ternary */
 import BaseFoundation, { DefaultAdapter } from '../base/foundation';
 import touchEventPolyfill from '../utils/touchPolyfill';
-
+import warning from '../utils/warning';
+import { handlePrevent } from '../utils/a11y';
 
 export interface Marks{
     [key: number]: string;
@@ -34,7 +35,7 @@ export interface SliderProps{
     'aria-label'?: string;
     'aria-labelledby'?: string;
     'aria-valuetext'?: string;
-    getAriaValueText?: (value: number) => string;
+    getAriaValueText?: (value: number, index?: number) => string;
 }
 
 export interface SliderState {
@@ -49,6 +50,8 @@ export interface SliderState {
     clickValue: 0;
     showBoundary: boolean;
     isInRenderTree: boolean;
+    firstDotFocusVisible: boolean;
+    secondDotFocusVisible: boolean;
 }
 
 export interface SliderLengths{
@@ -65,7 +68,6 @@ export interface ScrollParentVal{
 
 export interface OverallVars{
     dragging: boolean[];
-    chooseMovePos: 'min' | 'max';
 }
 
 export interface SliderAdapter extends DefaultAdapter<SliderProps, SliderState>{
@@ -514,8 +516,7 @@ export default class SliderFoundation extends BaseFoundation<SliderAdapter> {
         const handleMinDom = this._adapter.getMinHandleEl().current;
         const handleMaxDom = this._adapter.getMaxHandleEl().current;
         if (e.target === handleMinDom || e.target === handleMaxDom) {
-            e.preventDefault();
-            e.stopPropagation();
+            handlePrevent(e);
             const touch = touchEventPolyfill(e.touches[0], e);
             this.onHandleDown(touch, handler);
         }
@@ -564,14 +565,147 @@ export default class SliderFoundation extends BaseFoundation<SliderAdapter> {
             this._adapter.setDragging([dragging[0], false]);
         }
         this._adapter.setStateVal('isDrag', false);
-        // this._adapter.setStateVal('chooseMovePos', '');
         this._adapter.onHandleLeave();
         this._adapter.onHandleUpAfter();
         return true;
     };
 
+    _handleValueDecreaseWithKeyBoard = (step: number, handler: 'min'| 'max') => {
+        const { min, currentValue } = this.getStates();
+        const { range } = this.getProps();
+        if (handler === 'min') {
+            if (range) {
+                let newMinValue = currentValue[0] - step;
+                newMinValue = newMinValue < min ? min : newMinValue;
+                return [newMinValue, currentValue[1]];
+            } else {
+                let newMinValue = currentValue - step;
+                newMinValue = newMinValue < min ? min : newMinValue;
+                return newMinValue;
+            }
+        } else {
+            let newMaxValue = currentValue[1] - step;
+            newMaxValue = newMaxValue < currentValue[0] ? currentValue[0] : newMaxValue;
+            return [currentValue[0], newMaxValue];
+        }
+    }
+
+    _handleValueIncreaseWithKeyBoard = (step: number, handler: 'min'| 'max') => {
+        const { max, currentValue } = this.getStates();
+        const { range } = this.getProps();
+        if (handler === 'min') {
+            if (range) {
+                let newMinValue = currentValue[0] + step;
+                newMinValue = newMinValue > currentValue[1] ? currentValue[1] : newMinValue;
+                return [newMinValue, currentValue[1]];
+            } else {
+                let newMinValue = currentValue + step;
+                newMinValue = newMinValue > max ? max : newMinValue;
+                return newMinValue;
+            }
+        } else {
+            let newMaxValue = currentValue[1] + step;
+            newMaxValue = newMaxValue > max ? max : newMaxValue;
+            return [currentValue[0], newMaxValue];
+        }
+    }
+
+    _handleHomeKey = (handler: 'min'| 'max') => {
+        const { min, currentValue } = this.getStates();
+        const { range } = this.getProps();
+        if (handler === 'min') {
+            if (range) {
+                return [min, currentValue[1]];
+            } else {
+                return min;
+            }
+        } else {
+            return [currentValue[0], currentValue[0]];
+        }
+    }
+
+    _handleEndKey = (handler: 'min'| 'max') => {
+        const { max, currentValue } = this.getStates();
+        const { range } = this.getProps();
+        if (handler === 'min') {
+            if (range) {
+                return [currentValue[1], currentValue[1]];
+            } else {
+                return max;
+            }
+        } else {
+            return [currentValue[0], max];
+        }
+    }
+
+    handleKeyDown = (event: any, handler: 'min'| 'max') => {
+        const { min, max, currentValue } = this.getStates();
+        const { step, range } = this.getProps();
+        let outputValue;
+        switch (event.key) {
+            case "ArrowLeft":
+            case "ArrowDown":
+                outputValue = this._handleValueDecreaseWithKeyBoard(step, handler);
+                break;
+            case "ArrowRight":
+            case "ArrowUp":
+                outputValue = this._handleValueIncreaseWithKeyBoard(step, handler);
+                break;
+            case "PageUp":
+                outputValue = this._handleValueIncreaseWithKeyBoard(10 * step, handler);
+                break;
+            case "PageDown":
+                outputValue = this._handleValueDecreaseWithKeyBoard(10 * step, handler);
+                break;
+            case "Home":
+                outputValue = this._handleHomeKey(handler);
+                break;
+            case "End":
+                outputValue = this._handleEndKey(handler);
+                break;
+            case 'default':
+                break;
+        }
+        if (["ArrowLeft", "ArrowDown", "ArrowRight", "ArrowUp", "PageUp", "PageDown", "Home", "End"].includes(event.key)) {
+            let update = true;
+            if (Array.isArray(currentValue)) {
+                update = !(currentValue[0] === outputValue[0] && currentValue[1] === outputValue[1]);
+            } else {
+                update = currentValue !== outputValue;
+            }
+            if (update) {
+                this._adapter.updateCurrentValue(outputValue);
+                this._adapter.notifyChange(outputValue);
+            }
+            handlePrevent(event);
+        }
+    }
+
     // eslint-disable-next-line @typescript-eslint/no-empty-function
-    onFocus = (e:any, handler: 'min'| 'max') => {}
+    onFocus = (e: any, handler: 'min'| 'max') => {
+        handlePrevent(e);
+        const { target } = e;
+        try {
+            if (target.matches(':focus-visible')) {
+                if (handler === 'min') {
+                    this._adapter.setStateVal('firstDotFocusVisible', true);
+                } else {
+                    this._adapter.setStateVal('secondDotFocusVisible', true);
+                }
+            }
+        } catch (error) {
+            warning(true, 'Warning: [Semi Slider] The current browser does not support the focus-visible'); 
+        }
+    }
+
+    onBlur = (e: any, handler: 'min'| 'max') => {
+        const { firstDotFocusVisible, secondDotFocusVisible } = this.getStates();
+        if (handler === 'min') {
+            firstDotFocusVisible && this._adapter.setStateVal('firstDotFocusVisible', false);
+        } else {
+            secondDotFocusVisible && this._adapter.setStateVal('secondDotFocusVisible', false);
+        }
+    }
 
     handleWrapClick = (e: any) => {
         const { disabled, isDrag } = this._adapter.getStates();
@@ -648,6 +782,4 @@ export default class SliderFoundation extends BaseFoundation<SliderAdapter> {
         return vertical ? y : x;
     }
 
-
-
 }

+ 7 - 5
packages/semi-foundation/slider/slider.scss

@@ -1,5 +1,5 @@
 //@import '../theme/variables.scss';
-@import "./variables.scss";
+@import './variables.scss';
 
 $module: #{$prefix}-slider;
 
@@ -29,7 +29,7 @@ $module: #{$prefix}-slider;
         font-variant: tabular-nums;
         line-height: $font-slider_rail-lineHeight;
         list-style: none;
-        font-feature-settings: "tnum";
+        font-feature-settings: 'tnum';
         position: absolute;
         height: $height-slider_rail;
         cursor: pointer;
@@ -54,7 +54,11 @@ $module: #{$prefix}-slider;
         border: none;
         border-radius: 50%;
         cursor: pointer;
-        transition: #fff .3s;
+        transition: #fff 0.3s;
+
+        &:focus-visible {
+            outline: $width-slider_handle-focus solid $color-slider_handle-focus;
+        }
     }
 
     &-handle:hover {
@@ -133,10 +137,8 @@ $module: #{$prefix}-slider;
         text-align: center;
         cursor: pointer;
         transform: translate(-50%, 0) rotate(-180deg);
-
     }
 
-
     &-boundary {
         position: relative;
         font-size: $font-size-small;

+ 4 - 4
packages/semi-foundation/slider/variables.scss

@@ -11,9 +11,10 @@ $color-slider_handle_disabled-border-hover: var(--semi-color-white); // 禁用
 $color-slider_handle_disabled-border: var(--semi-color-border); // 禁用滑动条圆形描边颜色 - 默认态
 $color-slider_mark-text-default: var(--semi-color-text-2); // 滑动条刻度文字颜色
 $color-slider_rail-bg-default: var(--semi-color-fill-0); // 滑动条轨道颜色 - 未填充
-$color-slider_rail: rgba(0, 0, 0, .65);
+$color-slider_rail: rgba(0, 0, 0, 0.65);
 $color-slider_track-bg-default: var(--semi-color-primary); // 滑动条轨道颜色 - 已填充
 $color-slider_track_disabled-bg: var(--semi-color-primary-disabled); // 禁用滑动条轨道颜色 - 已填充
+$color-slider_handle-focus: var(--semi-color-primary-light-active); // 圆形按钮轮廓 - 聚焦
 
 // Spacing
 $spacing-slider-paddingX: 13px; // 滑动条整体水平内边距
@@ -33,7 +34,7 @@ $spacing-slider_boundary_min-left: 0;
 $spacing-slider_boundary_max-right: 0;
 $spacing-slider_vertical_marks-marginTop: -30px; // 垂直滑动条刻度标签顶部外边距
 $spacing-slider_vertical_marks-marginLeft: 29px; // 垂直滑动条刻度标签左侧外边距
-$spacing-slider_vertical_marks-reverse-marginLeft: -26px;// 垂直滑动条刻度标签左侧外边距(标签在左侧时)
+$spacing-slider_vertical_marks-reverse-marginLeft: -26px; // 垂直滑动条刻度标签左侧外边距(标签在左侧时)
 $spacing-slider_vertical_rail-top: 0; // 垂直滑动条轨道顶部距离
 $spacing-slider_vertical_handle-marginTop: 0; // 垂直滑动条原型按钮顶部外边距
 $spacing-slider_vertical_handle-marginLeft: -10px; // 垂直滑动条原型按钮左侧外边距
@@ -42,7 +43,6 @@ $spacing-slider_vertical_handle-marginLeft: -10px; // 垂直滑动条原型按
 $radius-slider_rail: var(--semi-border-radius-small); // 滚动条未填充轨道圆角
 $radius-slider_track: var(--semi-border-radius-small); // 滚动条已填充轨道圆角
 
-
 // Width/Height
 $height-slider_wrapper: 32px; // 滚动条容器整体高度
 $height-slider_vertical_wrapper: 4px; // 垂直滚动条整体宽度
@@ -52,7 +52,7 @@ $width-slider_handle_clicked: 1px; // 滚动条圆形按钮按下后描边宽度
 $height-slider_track: 4px; // 滚动条已填充轨道高度
 $width-slider_dot: 4px; // 滚动条圆形刻度点宽度
 $width-slider_handle_border_disabled: 1px; // 禁用滚动条圆形按钮按下后描边宽度
-
+$width-slider_handle-focus: 2px; // 圆形按钮轮廓 - 聚焦
 
 // Font
 $font-slider_rail-fontSize: 14px; // 滚动条轨道文本字号

+ 2 - 0
packages/semi-foundation/treeSelect/treeSelect.scss

@@ -239,6 +239,7 @@ $module: #{$prefix}-tree-select;
         // right: 0;
         display: inline-flex;
         align-items: center;
+        flex-shrink: 0;
         height: 100%;
         justify-content: center;
         width: $width-treeSelect_arrow;
@@ -265,6 +266,7 @@ $module: #{$prefix}-tree-select;
         display: inline-flex;
         align-items: center;
         height: 100%;
+        flex-shrink: 0;
         justify-content: center;
         width: $width-treeSelect_arrow;
         color: $color-treeSelect_default-icon-default;

+ 2 - 2
packages/semi-foundation/utils/a11y.ts

@@ -1,6 +1,6 @@
 import { get } from "lodash";
 
-export function handlePrevent(event: any)  {
+export function handlePrevent(event: any) {
     event.stopPropagation();
     event.preventDefault();
 }
@@ -47,7 +47,7 @@ export function setFocusToPreviousMenuItem (itemNodes: HTMLElement[], currentIte
 }
 
 // set focus to the next item in item list
-export function  setFocusToNextMenuitem (itemNodes: HTMLElement[], currentItem: HTMLElement): void {
+export function setFocusToNextMenuitem (itemNodes: HTMLElement[], currentItem: HTMLElement): void {
     let newMenuItem: HTMLElement, index: number;
 
     if (itemNodes.length > 0){

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

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-icons",
-  "version": "2.15.1",
+  "version": "2.17.0-alpha.1",
   "description": "semi icons",
   "keywords": [
     "semi",
@@ -38,7 +38,7 @@
     "@babel/plugin-transform-runtime": "^7.15.8",
     "@babel/preset-env": "^7.15.8",
     "@babel/preset-react": "^7.14.5",
-    "@douyinfe/semi-webpack-plugin": "2.15.1",
+    "@douyinfe/semi-webpack-plugin": "2.17.0-alpha.1",
     "babel-loader": "^8.2.2",
     "css-loader": "4.3.0",
     "del": "^6.0.0",

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

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-illustrations",
-  "version": "2.15.1",
+  "version": "2.17.0-alpha.1",
   "description": "semi illustrations",
   "keywords": [
     "semi",

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

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

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

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-scss-compile",
-  "version": "2.15.1",
+  "version": "2.17.0-alpha.1",
   "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.15.1",
+    "version": "2.17.0-alpha.1",
     "description": "semi-theme-default",
     "keywords": [
         "semi-theme",

+ 5 - 1
packages/semi-ui/cascader/index.tsx

@@ -29,6 +29,7 @@ import Tag from '../tag';
 import TagInput from '../tagInput';
 import { Motion } from '../_base/base';
 import { isSemiIcon } from '../_utils';
+import { Position } from '../tooltip/index';
 
 export { CascaderType, ShowNextType } from '@douyinfe/semi-foundation/cascader/foundation';
 export { CascaderData, Entity, Data, CascaderItemProps } from './item';
@@ -82,6 +83,7 @@ export interface CascaderProps extends BasicCascaderProps {
     onBlur?: (e: MouseEvent) => void;
     onFocus?: (e: MouseEvent) => void;
     validateStatus?: ValidateStatus;
+    position?: Position;
 }
 
 export interface CascaderState extends BasicCascaderInnerData {
@@ -169,6 +171,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
         leafOnly: PropTypes.bool,
         enableLeafClick: PropTypes.bool,
         preventScroll: PropTypes.bool,
+        position:PropTypes.string
     };
 
     static defaultProps = {
@@ -954,12 +957,13 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
             stopPropagation,
             mouseLeaveDelay,
             mouseEnterDelay,
+            position
         } = this.props;
         const { isOpen, rePosKey } = this.state;
         const { direction } = this.context;
         const content = this.renderContent();
         const selection = this.renderSelection();
-        const pos = direction === 'rtl' ? 'bottomRight' : 'bottomLeft';
+        const pos = position ?? (direction === 'rtl' ? 'bottomRight' : 'bottomLeft');
         const mergedMotion: Motion = this.foundation.getMergedMotion();
         return (
             <Popover

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-ui",
-    "version": "2.15.1",
+    "version": "2.17.0-alpha.1",
     "description": "",
     "main": "lib/cjs/index.js",
     "module": "lib/es/index.js",
@@ -15,18 +15,18 @@
     "dependencies": {
         "@babel/runtime-corejs3": "^7.15.4",
         "@douyinfe/semi-animation": "2.12.0",
-        "@douyinfe/semi-animation-react": "2.15.1",
-        "@douyinfe/semi-foundation": "2.15.1",
-        "@douyinfe/semi-icons": "2.15.1",
+        "@douyinfe/semi-animation-react": "2.17.0-alpha.1",
+        "@douyinfe/semi-foundation": "2.17.0-alpha.1",
+        "@douyinfe/semi-icons": "2.17.0-alpha.1",
         "@douyinfe/semi-illustrations": "2.15.0",
-        "@douyinfe/semi-theme-default": "2.15.1",
-        "@types/react-window": "^1.8.2",
+        "@douyinfe/semi-theme-default": "2.17.0-alpha.1",
         "async-validator": "^3.5.0",
         "classnames": "^2.2.6",
         "copy-text-to-clipboard": "^2.1.1",
         "date-fns": "^2.9.0",
         "date-fns-tz": "^1.0.10",
         "lodash": "^4.17.21",
+        "prop-types": "^15.7.2",
         "react-resizable": "^1.8.0",
         "react-sortable-hoc": "^2.0.0",
         "react-window": "^1.8.2",
@@ -35,9 +35,6 @@
         "utility-types": "^3.10.0"
     },
     "peerDependencies": {
-        "@types/react": ">=16.0.0",
-        "@types/react-dom": ">=16.0.0",
-        "prop-types": "15.7.2",
         "react": ">=16.0.0",
         "react-dom": ">=16.0.0"
     },
@@ -69,15 +66,18 @@
     ],
     "author": "",
     "license": "MIT",
-    "gitHead": "eb34a4f25f002bb4cbcfa51f3df93bed868c831a",
+    "gitHead": "e78045febe3e4645c8afe6d42cfbcb4e97a67923",
     "devDependencies": {
         "@babel/plugin-proposal-decorators": "^7.15.8",
         "@babel/plugin-transform-runtime": "^7.15.8",
         "@babel/preset-env": "^7.15.8",
         "@babel/preset-react": "^7.14.5",
-        "@douyinfe/semi-scss-compile": "2.15.1",
+        "@douyinfe/semi-scss-compile": "2.17.0-alpha.1",
         "@storybook/addon-knobs": "^6.3.1",
         "@types/lodash": "^4.14.176",
+        "@types/react": ">=16.0.0",
+        "@types/react-dom": ">=16.0.0",
+        "@types/react-window": "^1.8.2",
         "babel-loader": "^8.2.2",
         "babel-plugin-lodash": "^3.3.4",
         "case-sensitive-paths-webpack-plugin": "^2.4.0",

+ 4 - 2
packages/semi-ui/slider/_story/slider.stories.js

@@ -113,7 +113,8 @@ export const HorizontalSlider = () => (
     <div style={divStyle}>
       <div>marks</div>
       <Slider
-        marks={{ 20: '20c', 40: '40c' }}
+        marks={{ 20: '20°C', 40: '40°C' }}
+        getAriaValueText={(value) => `${value}°C`}
         defaultValue={[0, 100]}
         range={true}
         onChange={value => {
@@ -124,7 +125,8 @@ export const HorizontalSlider = () => (
     <div style={divStyle}>
       <div>inclued</div>
       <Slider
-        marks={{ 20: '20c', 40: '40c' }}
+        marks={{ 20: '20°C', 40: '40°C' }}
+        getAriaValueText={(value) => `${value}°C`}
         included={false}
         defaultValue={[0, 100]}
         range={true}

+ 63 - 33
packages/semi-ui/slider/index.tsx

@@ -51,6 +51,7 @@ export default class Slider extends BaseComponent<SliderProps, SliderState> {
         showBoundary: PropTypes.bool,
         railStyle: PropTypes.object,
         verticalReverse: PropTypes.bool,
+        getAriaValueText: PropTypes.func,
     } as any;
 
     static defaultProps: Partial<SliderProps> = {
@@ -77,7 +78,6 @@ export default class Slider extends BaseComponent<SliderProps, SliderState> {
     private maxHanleEl: React.RefObject<HTMLDivElement>;
     private dragging: boolean[];
     private eventListenerSet: Set<() => void>;
-    private chooseMovePos: 'min' | 'max';
     foundation: SliderFoundation;
 
     constructor(props: SliderProps) {
@@ -98,14 +98,14 @@ export default class Slider extends BaseComponent<SliderProps, SliderState> {
             isDrag: false,
             clickValue: 0,
             showBoundary: false,
-            isInRenderTree: true
+            isInRenderTree: true,
+            firstDotFocusVisible: false,
+            secondDotFocusVisible: false,
         };
         this.sliderEl = React.createRef();
         this.minHanleEl = React.createRef();
         this.maxHanleEl = React.createRef();
         this.dragging = [false, false];
-        // this.chooseMovePos = 'min';
-        // this.isDrag = false;
         this.foundation = new SliderFoundation(this.adapter);
         this.eventListenerSet = new Set();
     }
@@ -165,7 +165,6 @@ export default class Slider extends BaseComponent<SliderProps, SliderState> {
             },
             getOverallVars: () => ({
                 dragging: this.dragging,
-                chooseMovePos: this.chooseMovePos,
             }),
             updateDisabled: (disabled: boolean) => {
                 this.setState({ disabled });
@@ -189,8 +188,6 @@ export default class Slider extends BaseComponent<SliderProps, SliderState> {
             getMinHandleEl: () => this.minHanleEl,
             getMaxHandleEl: () => this.maxHanleEl,
             onHandleDown: (e: React.MouseEvent) => {
-                e.stopPropagation();
-                e.preventDefault();
                 this._addEventListener(document.body, 'mousemove', this.foundation.onHandleMove, false);
                 this._addEventListener(document.body, 'mouseup', this.foundation.onHandleUp, false);
                 this._addEventListener(document.body, 'touchmove', this.foundation.onHandleTouchMove, false);
@@ -287,7 +284,7 @@ export default class Slider extends BaseComponent<SliderProps, SliderState> {
 
     renderHandle = () => {
         const { vertical, range, tooltipVisible, tipFormatter, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, 'aria-valuetext': ariaValueText, getAriaValueText, disabled } = this.props;
-        const { chooseMovePos, isDrag, isInRenderTree } = this.state;
+        const { chooseMovePos, isDrag, isInRenderTree, firstDotFocusVisible, secondDotFocusVisible } = this.state;
         const stylePos = vertical ? 'top' : 'left';
         const percentInfo = this.foundation.getMinAndMaxPercent(this.state.currentValue);
         const minPercent = percentInfo.min;
@@ -307,7 +304,7 @@ export default class Slider extends BaseComponent<SliderProps, SliderState> {
         const { min, max, currentValue } = this.state;
 
         const commonAria = {
-            'aria-label': ariaLabel,
+            'aria-label': ariaLabel ?? (disabled ? 'Disabled Slider' : undefined),
             'aria-labelledby': ariaLabelledby,
             'aria-disabled': disabled
         };
@@ -319,7 +316,7 @@ export default class Slider extends BaseComponent<SliderProps, SliderState> {
                 position="top"
                 trigger="custom"
                 rePosKey={minPercent}
-                visible={isInRenderTree && tipVisible.min}
+                visible={isInRenderTree && (tipVisible.min || firstDotFocusVisible)}
                 className={`${cssClasses.HANDLE}-tooltip`}
             >
                 <span
@@ -352,24 +349,32 @@ export default class Slider extends BaseComponent<SliderProps, SliderState> {
                     onTouchEnd={e => {
                         this.foundation.onHandleUp(e);
                     }}
-                    onFocus={e => this.foundation.onFocus(e, 'min')}
+                    onKeyDown={(e)=>{
+                        this.foundation.handleKeyDown(e, 'min');
+                    }}
+                    onFocus={e => {
+                        this.foundation.onFocus(e, 'min');
+                    }}
+                    onBlur={(e) => { 
+                        this.foundation.onBlur(e, 'min');
+                    }}
                     role="slider"
-                    tabIndex={0}
+                    aria-valuetext={getAriaValueText ? getAriaValueText(currentValue as number, 0) : ariaValueText}
+                    tabIndex={disabled ? -1 : 0}
                     {...commonAria}
                     aria-valuenow={currentValue as number}
                     aria-valuemax={max}
                     aria-valuemin={min}
-                    aria-valuetext={getAriaValueText ? getAriaValueText(currentValue as number) : ariaValueText}
                 />
             </Tooltip>
         ) : (
             <React.Fragment>
-                <Tooltip
+                <Tooltip    
                     content={tipChildren.min}
                     position="top"
                     trigger="custom"
                     rePosKey={minPercent}
-                    visible={isInRenderTree && tipVisible.min}
+                    visible={isInRenderTree && (tipVisible.min || firstDotFocusVisible)}
                     className={`${cssClasses.HANDLE}-tooltip`}
                 >
                     <span
@@ -401,12 +406,20 @@ export default class Slider extends BaseComponent<SliderProps, SliderState> {
                         onTouchEnd={e => {
                             this.foundation.onHandleUp(e);
                         }}
-                        onFocus={e => this.foundation.onFocus(e, 'min')}
+                        onKeyDown={(e)=>{
+                            this.foundation.handleKeyDown(e, 'min');
+                        }}
+                        onFocus={e => {
+                            this.foundation.onFocus(e, 'min');
+                        }}
+                        onBlur={(e) => { 
+                            this.foundation.onBlur(e, 'min');
+                        }}
                         role="slider"
-                        tabIndex={0}
+                        tabIndex={disabled ? -1 : 0}
                         {...commonAria}
+                        aria-valuetext={getAriaValueText ? getAriaValueText(currentValue[0], 0) : ariaValueText}
                         aria-valuenow={currentValue[0]}
-                        aria-valuetext={getAriaValueText ? getAriaValueText(currentValue[0]) : ariaValueText}
                         aria-valuemax={currentValue[1]}
                         aria-valuemin={min}
                     />
@@ -416,7 +429,7 @@ export default class Slider extends BaseComponent<SliderProps, SliderState> {
                     position="top"
                     trigger="custom"
                     rePosKey={maxPercent}
-                    visible={isInRenderTree && tipVisible.max}
+                    visible={isInRenderTree && (tipVisible.max || secondDotFocusVisible)}
                     className={`${cssClasses.HANDLE}-tooltip`}
                 >
                     <span
@@ -448,12 +461,20 @@ export default class Slider extends BaseComponent<SliderProps, SliderState> {
                         onTouchEnd={e => {
                             this.foundation.onHandleUp(e);
                         }}
-                        onFocus={e => this.foundation.onFocus(e, 'min')}
+                        onKeyDown={e =>{
+                            this.foundation.handleKeyDown(e, 'max');
+                        }}
+                        onFocus={e =>  {
+                            this.foundation.onFocus(e, 'max');
+                        }}
+                        onBlur={(e) => {
+                            this.foundation.onBlur(e, 'max');
+                        }}
                         role="slider"
-                        tabIndex={0}
+                        tabIndex={disabled ? -1 : 0}
                         {...commonAria}
+                        aria-valuetext={getAriaValueText ? getAriaValueText(currentValue[1], 1) : ariaValueText}
                         aria-valuenow={currentValue[1]}
-                        aria-valuetext={getAriaValueText ? getAriaValueText(currentValue[1]) : ariaValueText}
                         aria-valuemax={max}
                         aria-valuemin={currentValue[0]}
                     />
@@ -538,29 +559,38 @@ export default class Slider extends BaseComponent<SliderProps, SliderState> {
         return labelContent;
     };
 
+    _getAriaValueText = (value: number, index?: number) => {
+        const { getAriaValueText } = this.props;
+        return getAriaValueText ? getAriaValueText(value, index) : value;
+    }
+
 
     render() {
+        const { disabled, currentValue, min, max  } = this.state;
+        const { vertical, verticalReverse, style, railStyle, range, className } = this.props;
         const wrapperClass = cls(
             `${prefixCls}-wrapper`,
             {
-                [`${prefixCls}-disabled`]: this.state.disabled,
-                [`${cssClasses.VERTICAL}-wrapper`]: this.props.vertical,
-                [`${prefixCls}-reverse`]: this.props.vertical && this.props.verticalReverse
+                [`${prefixCls}-disabled`]: disabled,
+                [`${cssClasses.VERTICAL}-wrapper`]: vertical,
+                [`${prefixCls}-reverse`]: vertical && verticalReverse
             },
-            this.props.className
+            className
         );
         const boundaryClass = cls(`${prefixCls}-boundary`, {
             [`${prefixCls}-boundary-show`]: this.props.showBoundary && this.state.showBoundary,
         });
         const sliderCls = cls({
-            [`${prefixCls}`]: !this.props.vertical,
-            [cssClasses.VERTICAL]: this.props.vertical,
+            [`${prefixCls}`]: !vertical,
+            [cssClasses.VERTICAL]: vertical,
         });
+        const ariaLabel = range ? `Range: ${this._getAriaValueText(currentValue[0], 0)} to ${this._getAriaValueText(currentValue[1], 1)}` : undefined;
         const slider = (
             <div
                 className={wrapperClass}
-                style={this.props.style}
+                style={style}
                 ref={this.sliderEl}
+                aria-label={ariaLabel}
                 onMouseEnter={() => this.foundation.handleWrapperEnter()}
                 onMouseLeave={() => this.foundation.handleWrapperLeave()}
             >
@@ -568,7 +598,7 @@ export default class Slider extends BaseComponent<SliderProps, SliderState> {
                     <div
                         className={`${prefixCls}-rail`}
                         onClick={this.foundation.handleWrapClick}
-                        style={this.props.railStyle}
+                        style={railStyle}
                     />
                 }
                 {this.renderTrack()}
@@ -576,12 +606,12 @@ export default class Slider extends BaseComponent<SliderProps, SliderState> {
                 <div>{this.renderHandle()}</div>
                 {this.renderLabel()}
                 <div className={boundaryClass}>
-                    <span className={`${prefixCls}-boundary-min`}>{this.state.min}</span>
-                    <span className={`${prefixCls}-boundary-max`}>{this.state.max}</span>
+                    <span className={`${prefixCls}-boundary-min`}>{min}</span>
+                    <span className={`${prefixCls}-boundary-max`}>{max}</span>
                 </div>
             </div>
         );
-        if (!this.props.vertical) {
+        if (!vertical) {
             return <div className={sliderCls}>{slider}</div>;
         }
         return slider;

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

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

+ 75 - 2
yarn.lock

@@ -1460,6 +1460,16 @@
   resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
   integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
 
+"@douyinfe/[email protected]":
+  version "2.15.1"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-react/-/semi-animation-react-2.15.1.tgz#e6a7010eba304c437510ff30e7210302864c46da"
+  integrity sha512-wgMdJ6j5oAfNefw8y+C691jyjVz9HKJ8zimshLy/vkGeFhfYe7ezq6/RMAYmw10J7YN2djZyBL5FqkfWqATqyA==
+  dependencies:
+    "@babel/runtime-corejs3" "^7.15.4"
+    "@douyinfe/semi-animation" "2.12.0"
+    "@douyinfe/semi-animation-styled" "2.15.1"
+    classnames "^2.2.6"
+
 "@douyinfe/[email protected]":
   version "2.9.1"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-react/-/semi-animation-react-2.9.1.tgz#f2e4c6ef7899729ee6145edf0579598ba195bfdd"
@@ -1470,6 +1480,13 @@
     "@douyinfe/semi-animation-styled" "2.9.1"
     classnames "^2.2.6"
 
+"@douyinfe/[email protected]":
+  version "2.15.1"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-styled/-/semi-animation-styled-2.15.1.tgz#dd67077c2f31db42d562c3f4dbd021261c3ee027"
+  integrity sha512-8cNY6068DTzVyz+B4HBHoRszsWrEQ2F5ySUKLCpooV+rnlJar0mE/JfqQsPrNanfwZBrc1EKqSsm+ggOVA5uIQ==
+  dependencies:
+    "@babel/runtime-corejs3" "^7.15.4"
+
 "@douyinfe/[email protected]":
   version "2.9.1"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-styled/-/semi-animation-styled-2.9.1.tgz#0a4a3c521626118b209604b2d6447fbcaa4839a4"
@@ -1493,6 +1510,21 @@
     "@babel/runtime-corejs3" "^7.15.4"
     bezier-easing "^2.1.0"
 
+"@douyinfe/[email protected]":
+  version "2.15.1"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-foundation/-/semi-foundation-2.15.1.tgz#75c8ed3f22ce53227f06c1d5ce8ad026651600f6"
+  integrity sha512-p7ynMsPBwVrNJZf7tmnl/m9iwF0XlIRmq5GWufUN1AoBmx6xEGY8B35tLpnvyEnkFzSAEQ074FbFb3e86yCJDg==
+  dependencies:
+    "@babel/runtime-corejs3" "^7.15.4"
+    "@douyinfe/semi-animation" "2.12.0"
+    async-validator "^3.5.0"
+    classnames "^2.2.6"
+    date-fns "^2.9.0"
+    date-fns-tz "^1.0.10"
+    lodash "^4.17.21"
+    memoize-one "^5.2.1"
+    scroll-into-view-if-needed "^2.2.24"
+
 "@douyinfe/[email protected]":
   version "2.9.1"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-foundation/-/semi-foundation-2.9.1.tgz#1300bb97d6ceb92274ca4c9e6c66c5c16dc284ea"
@@ -1508,6 +1540,14 @@
     memoize-one "^5.2.1"
     scroll-into-view-if-needed "^2.2.24"
 
+"@douyinfe/[email protected]", "@douyinfe/semi-icons@^2.0.0":
+  version "2.15.1"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-icons/-/semi-icons-2.15.1.tgz#9345b5d3b3260dde2f0f270ad84c8cc482a0598a"
+  integrity sha512-2t4ba46KRaLVrbhBqtMMq1YLjRdxBGUY7hZhmP8iV9TNt2b/5qzWOYbNITA1oshH+5cYbZan26kPVVayq0mCMw==
+  dependencies:
+    "@babel/runtime-corejs3" "^7.15.4"
+    classnames "^2.2.6"
+
 "@douyinfe/[email protected]", "@douyinfe/semi-icons@latest":
   version "2.9.1"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-icons/-/semi-icons-2.9.1.tgz#7a04e1a77070220b04f63e6f65aac30155ed8ddd"
@@ -1586,6 +1626,13 @@
     monaco-themes "^0.3.3"
     react-live "^2.2.2"
 
+"@douyinfe/[email protected]":
+  version "2.15.1"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-theme-default/-/semi-theme-default-2.15.1.tgz#78ea1ef30dbab06c19824430e22902046abe6adb"
+  integrity sha512-kV68xhV3xPQKJGgragzJlXnLRRxzzeaDq6I5l/MkTsjMGkfvyKuwKY+ob0NbaI7FBk8WRi18G3tInHYukia/AQ==
+  dependencies:
+    glob "^7.1.6"
+
 "@douyinfe/[email protected]":
   version "2.9.1"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-theme-default/-/semi-theme-default-2.9.1.tgz#734113e9783ca58b69afe1769005e7e57e5a4da7"
@@ -1593,6 +1640,32 @@
   dependencies:
     glob "^7.1.6"
 
+"@douyinfe/semi-ui@^2.0.0":
+  version "2.15.1"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-ui/-/semi-ui-2.15.1.tgz#2fa2e1a22ced6a0299211ab736edf09d3e162201"
+  integrity sha512-0aaU4LnbGOAw79CxLrKuwmGykmQUCNKISdEAHmleiFK+6ZAdoxWF1I0YzbQK78QLKSKmuJpzHIBHOteBVjf1gQ==
+  dependencies:
+    "@babel/runtime-corejs3" "^7.15.4"
+    "@douyinfe/semi-animation" "2.12.0"
+    "@douyinfe/semi-animation-react" "2.15.1"
+    "@douyinfe/semi-foundation" "2.15.1"
+    "@douyinfe/semi-icons" "2.15.1"
+    "@douyinfe/semi-illustrations" "2.15.0"
+    "@douyinfe/semi-theme-default" "2.15.1"
+    "@types/react-window" "^1.8.2"
+    async-validator "^3.5.0"
+    classnames "^2.2.6"
+    copy-text-to-clipboard "^2.1.1"
+    date-fns "^2.9.0"
+    date-fns-tz "^1.0.10"
+    lodash "^4.17.21"
+    react-resizable "^1.8.0"
+    react-sortable-hoc "^2.0.0"
+    react-window "^1.8.2"
+    resize-observer-polyfill "^1.5.1"
+    scroll-into-view-if-needed "^2.2.24"
+    utility-types "^3.10.0"
+
 "@douyinfe/semi-ui@latest":
   version "2.9.1"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-ui/-/semi-ui-2.9.1.tgz#505d4783ea1fa73d307b75f62091030f1fee9332"
@@ -5182,7 +5255,7 @@
   dependencies:
     "@types/react" "*"
 
-"@types/react-dom@^18.0.1":
+"@types/react-dom@>=16.0.0", "@types/react-dom@^18.0.1":
   version "18.0.3"
   resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.3.tgz#a022ea08c75a476fe5e96b675c3e673363853831"
   integrity sha512-1RRW9kst+67gveJRYPxGmVy8eVJ05O43hg77G2j5m76/RFJtMbcfAs2viQ2UNsvvDg8F7OfQZx8qQcl6ymygaQ==
@@ -5210,7 +5283,7 @@
   dependencies:
     "@types/react" "*"
 
-"@types/react@*", "@types/react@^18.0.5":
+"@types/react@*", "@types/react@>=16.0.0", "@types/react@^18.0.5":
   version "18.0.8"
   resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.8.tgz#a051eb380a9fbcaa404550543c58e1cf5ce4ab87"
   integrity sha512-+j2hk9BzCOrrOSJASi5XiOyBbERk9jG5O73Ya4M0env5Ixi6vUNli4qy994AINcEF+1IEHISYFfIT4zwr++LKw==