浏览代码

chore: beta merge main

走鹃 3 年之前
父节点
当前提交
b3bbd15e07

+ 2 - 0
.eslintrc.js

@@ -34,6 +34,7 @@ module.exports = {
                 'jsx-a11y/click-events-have-key-events': ['warn'],
                 'jsx-a11y/no-noninteractive-element-interactions': ['warn'],
                 'jsx-a11y/no-autofocus': ['warn'],
+                'object-curly-spacing': ['error', 'always'],
             },
             globals: {
                 "sinon": "readonly",
@@ -77,6 +78,7 @@ module.exports = {
                 'jsx-a11y/click-events-have-key-events': ['warn'],
                 'jsx-a11y/no-noninteractive-element-interactions': ['warn'],
                 'jsx-a11y/no-autofocus': ['warn'],
+                'object-curly-spacing': ['error', 'always'],
             }
         },
     ],

+ 1 - 1
.github/workflows/chromatic.yml

@@ -26,7 +26,7 @@ jobs:
   chromatic-deployment:
     # Operating System
     runs-on: ubuntu-latest
-    if: ${{ github.repository_owner == 'DouyinFE' }}
+    if: github.event_name == 'push' && github.repository_owner == 'DouyinFE' || github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'DouyinFE/semi-design'
     # Job steps
     steps:
       - uses: actions/checkout@v1

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

@@ -16,6 +16,14 @@ Version:Major.Minor.Patch
 
 ---
 
+#### 🎉 2.5.0 (2022-2-24)
+- 【Fix】
+    - Fixed resizable `Table` columns width bug when update `columns`  [#650](https://github.com/DouyinFE/semi-design/issues/650)
+    - `Select` component automatically scrolls when the keyboard up and down keys are used to adjust the relative position of the focused option  [#607](https://github.com/DouyinFE/semi-design/issues/607) [@chenzn1](https://github.com/chenzn1)
+    - Fixed the problem that the configuration of `webpack.resolve.alias` is invalid after the `@douyinfe/semi-next` plugin is used in the `next.js` project  [#630](https://github.com/DouyinFE/semi-design/issues/630)
+    - Fixed the issue that the focus state of the input box was not cleared after closing the panel when `DatePicker` open was controlled  [#528](https://github.com/DouyinFE/semi-design/issues/528)
+    - Fixed `Tooltip` in React17 if the parent prevents the click event from bubbling and the pop-up layer is collapsed will fail  [#593](https://github.com/DouyinFE/semi-design/issues/593) [@chenc041](https://github.com/chenc041)
+
 #### 🎉 2.5.0-beta.0 (2022-02-18)
 - 【Fix】
     - fix slider throw error in shadowDOM or other DocumentFragment env
@@ -31,7 +39,7 @@ Version:Major.Minor.Patch
     - Tree supports parent-child node selection relationship detachment  [#522](https://github.com/DouyinFE/semi-design/issues/522)
     - Tooltip `leftTopOver` and `rightTopOver` position supports `autoAdjustOverflow`
 - 【Style】
-    - Update hover Sass token in Cascader component [@Carlosfengv](https://github.com/Carlosfengv)
+    - Update hover Sass token in Cascader component [@Carlosfengv](https://github.com/Carlosfengv
 
 #### 🎉 2.4.1 (2022-02-16)
 - 【Fix】

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

@@ -14,6 +14,15 @@ Semi 版本号遵循**Semver**规范(主版本号-次版本号-修订版本号
 -   修订版本号(patch):bugfix
 
 ---
+
+#### 🎉 2.5.0 (2022-2-24)
+- 【Fix】
+    - 修复 resizable Table 动态删除列时列宽计算错误问题  [#650](https://github.com/DouyinFE/semi-design/issues/650)
+    - Select 组件当用键盘上下键操作时,增加自动滚动交互,调整聚焦 option 的相对位置  [#607](https://github.com/DouyinFE/semi-design/issues/607) [@chenzn1](https://github.com/chenzn1)
+    - 修复 next.js 项目使用 @douyinfe/semi-next 插件后,webpack.resolve.alias配置失效的问题  [#630](https://github.com/DouyinFE/semi-design/issues/630)
+    - 修复 DatePicker open 受控时关闭面板后输入框聚焦态没有清空问题  [#528](https://github.com/DouyinFE/semi-design/issues/528)
+    - 修复 Tooltip 在 React17 里如果父级有阻止点击事件冒泡弹出层收起会失效 [#593](https://github.com/DouyinFE/semi-design/issues/593) [@chenc041](https://github.com/chenc041)
+
 #### 🎉 2.5.0-beta.0 (2022-02-18)
 - 【Fix】
     - 修复 slider 在 shadowRoot、WebComponent 或其他 DocumentFragment 下报错的问题

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

@@ -98,4 +98,26 @@ describe('DatePicker', () => {
         cy.get('[data-cy=3] .semi-datepicker-range-input-wrapper-start .semi-input').should('have.value', '2021-12-15 10:37:13');
         cy.get('[data-cy=3] .semi-datepicker-range-input-wrapper-end .semi-input').should('have.value', '2022-01-20 10:37:13');
     });
+
+    /**
+     * 测试 open 受控时,点击面板内按钮关闭面板后,输入框应该清除 focus 状态
+     */
+    it('input range focus when open is controlled', () => {
+        cy.visit('http://localhost:6009/iframe.html?id=datepicker--fix-input-range-focus&args=&viewMode=story');
+        cy.get('.semi-datepicker-range-input-wrapper-start > .semi-input-wrapper').click();
+        cy.get('.semi-datepicker-day').contains('10')
+            .then($btn => {
+                $btn.trigger('click');
+            });
+        cy.get('.semi-datepicker-day').contains('15')
+            .then($btn => {
+                $btn.trigger('click');
+            });
+        cy.get('.semi-datepicker-bottomSlot .semi-button')
+            .then($btn => {
+                $btn.trigger('click');
+                cy.get('.semi-datepicker-range-input-wrapper-start').should('not.have.class', 'semi-datepicker-range-input-wrapper-active');
+                cy.get('.semi-datepicker-range-input-wrapper-end').should('not.have.class', 'semi-datepicker-range-input-wrapper-active');
+            });
+    });
 });

+ 12 - 0
packages/semi-foundation/datePicker/foundation.ts

@@ -384,6 +384,18 @@ export default class DatePickerFoundation extends BaseFoundation<DatePickerAdapt
         this._adapter.notifyBlur(e);
     }
 
+    /**
+     * clear range input focus when open is controlled
+     * fixed github 1375
+     */
+    clearRangeInputFocus = () => {
+        const { type } = this._adapter.getProps();
+        const { rangeInputFocus } = this._adapter.getStates();
+        if (type === 'dateTimeRange' && rangeInputFocus) {
+            this._adapter.setRangeInputFocus(false);
+        }
+    }
+
     /**
      * Callback when the content of the input box changes
      * Update the date panel if the changed value is a legal date, otherwise only update the input box

+ 3 - 2
packages/semi-foundation/select/foundation.ts

@@ -44,7 +44,7 @@ export interface SelectAdapter<P = Record<string, any>, S = Record<string, any>>
     notifyMouseLeave(event: any): void;
     notifyMouseEnter(event: any): void;
     updateHovering(isHover: boolean): void;
-    updateScrollTop(): void;
+    updateScrollTop(index?: number): void;
 }
 
 type LabelValue = string | number;
@@ -704,7 +704,7 @@ export default class SelectFoundation extends BaseFoundation<SelectAdapter> {
         }
         // console.log('new:' + index);
         this._adapter.updateFocusIndex(index);
-        // TODO requires scrollIntoView
+        this._adapter.updateScrollTop(index);
     }
 
     _handleArrowKeyDown(offset: number) {
@@ -954,4 +954,5 @@ export default class SelectFoundation extends BaseFoundation<SelectAdapter> {
     updateScrollTop() {
         this._adapter.updateScrollTop();
     }
+    
 }

+ 3 - 1
packages/semi-foundation/table/utils.ts

@@ -281,7 +281,9 @@ export function assignColumnKeys(columns: Record<string, any>[], childrenColumnN
     const sameLevelCols: Record<string, any>[] = [];
     each(columns, (column, index) => {
         if (column.key == null) {
-            column.key = `${level}-${index}`;
+            // if user give column a dataIndex, use it for backup
+            const _index = column.dataIndex || index;
+            column.key = `${level}-${_index}`;
         }
         if (Array.isArray(column[childrenColumnName]) && column[childrenColumnName].length) {
             sameLevelCols.push(...column[childrenColumnName]);

+ 4 - 1
packages/semi-next/src/index.ts

@@ -9,7 +9,7 @@ export default function(options: SemiNextOptions = {}) {
     return (nextConfig: NextConfig = {}) => {
         const actualConfig: NextConfig = {
             ...nextConfig,
-            webpack(config, { isServer, webpack }) {
+            webpack(config, { isServer, webpack, ...rest }) {
                 config.plugins.push(new SemiWebpackPlugin({
                     omitCss: options.omitCss === undefined ? true : options.omitCss,
                     webpackContext: {
@@ -39,6 +39,9 @@ export default function(options: SemiNextOptions = {}) {
                         ];
                     }
                 }
+                if (typeof nextConfig.webpack === 'function') {
+                    return nextConfig.webpack(config, { isServer, webpack, ...rest });
+                }
                 return config;
             }
         };

+ 25 - 0
packages/semi-ui/datePicker/_story/v2/FixInputRangeFocus.jsx

@@ -0,0 +1,25 @@
+import React from 'react';
+import { DatePicker, Button } from '../../../index';
+
+/**
+ * fix gitlab #1375
+ */
+App.storyName = 'fixed input range focus';
+export default function App() {
+    const [visible, setVisible] = React.useState(false);
+    return (
+        <div>
+            {/* <Button onClick={() => { setVisible(false); }}>关闭</Button> */}
+            <DatePicker
+                type="dateTimeRange"
+                bottomSlot={<Button onClick={() => { setVisible(false); }}>关闭</Button>}
+                onFocus={() => {
+                    console.log('focus');
+                    setVisible(true); 
+                }}
+                open={visible}
+                showClear
+            />
+        </div>
+    );
+}

+ 2 - 1
packages/semi-ui/datePicker/_story/v2/index.js

@@ -1,2 +1,3 @@
 export { default as YearButton } from './YearButton';
-export { default as PanelOpen  } from './PanelOpen';
+export { default as PanelOpen  } from './PanelOpen';
+export { default as FixInputRangeFocus } from './FixInputRangeFocus';

+ 3 - 0
packages/semi-ui/datePicker/datePicker.tsx

@@ -331,6 +331,9 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
 
         if (prevProps.open !== this.props.open) {
             this.foundation.initPanelOpenStatus();
+            if (!this.props.open) {
+                this.foundation.clearRangeInputFocus();
+            }
         }
     }
 

+ 18 - 19
packages/semi-ui/select/index.tsx

@@ -530,9 +530,13 @@ class Select extends BaseComponent<SelectProps, SelectState> {
 
                 }
             },
-            updateScrollTop: () => {
+            updateScrollTop: (index?: number) => {
                 // eslint-disable-next-line max-len
-                let destNode = document.querySelector(`#${prefixcls}-${this.selectOptionListID} .${prefixcls}-option-selected`) as HTMLDivElement;
+                let optionClassName = `.${prefixcls}-option-selected`;
+                if (index !== undefined) {
+                    optionClassName = `.${prefixcls}-option:nth-child(${index})`;
+                }
+                let destNode = document.querySelector(`#${prefixcls}-${this.selectOptionListID} ${optionClassName}`) as HTMLDivElement;
                 if (Array.isArray(destNode)) {
                     // eslint-disable-next-line prefer-destructuring
                     destNode = destNode[0];
@@ -754,31 +758,26 @@ class Select extends BaseComponent<SelectProps, SelectState> {
         this.foundation.handleOptionMouseEnter(optionIndex);
     }
 
-    renderWithGroup(visibileOptions: OptionProps[]) {
+    renderWithGroup(visibleOptions: OptionProps[]) {
         const content: JSX.Element[] = [];
         const groupStatus = new Map();
 
-        visibileOptions.forEach((option, optionIndex) => {
+        visibleOptions.forEach((option, optionIndex) => {
             const parentGroup = option._parentGroup;
             const optionContent = this.renderOption(option, optionIndex);
-            if (parentGroup && groupStatus.has(parentGroup.label)) {
-                // group content already insert
-                content.push(optionContent);
-            } else if (parentGroup) {
+            if (parentGroup && !groupStatus.has(parentGroup.label)) {
+                // when use with OptionGroup and group content not already insert
                 const groupContent = <OptionGroup {...parentGroup} key={parentGroup.label} />;
                 groupStatus.set(parentGroup.label, true);
                 content.push(groupContent);
-                content.push(optionContent);
-            } else {
-                // when not use with OptionGroup
-                content.push(optionContent);
-            }
+            } 
+            content.push(optionContent);
         });
 
         return content;
     }
 
-    renderVirtualizeList(visibileOptions: OptionProps[]) {
+    renderVirtualizeList(visibleOptions: OptionProps[]) {
         const { virtualize } = this.props;
         const { direction } = this.context;
         const { height, width, itemSize } = virtualize;
@@ -787,9 +786,9 @@ class Select extends BaseComponent<SelectProps, SelectState> {
             <List
                 ref={this.virtualizeListRef}
                 height={height || numbers.LIST_HEIGHT}
-                itemCount={visibileOptions.length}
+                itemCount={visibleOptions.length}
                 itemSize={itemSize}
-                itemData={{ visibileOptions, renderOption: this.renderOption }}
+                itemData={{ visibleOptions, renderOption: this.renderOption }}
                 width={width || '100%'}
                 style={{ direction }}
             >
@@ -814,11 +813,11 @@ class Select extends BaseComponent<SelectProps, SelectState> {
         } = this.props;
 
         // Do a filter first, instead of directly judging in forEach, so that the focusIndex can correspond to
-        const visibileOptions = options.filter(item => item._show);
+        const visibleOptions = options.filter(item => item._show);
 
-        let listContent: JSX.Element | JSX.Element[] = this.renderWithGroup(visibileOptions);
+        let listContent: JSX.Element | JSX.Element[] = this.renderWithGroup(visibleOptions);
         if (virtualize) {
-            listContent = this.renderVirtualizeList(visibileOptions);
+            listContent = this.renderVirtualizeList(visibleOptions);
         }
 
         const style = { minWidth: dropdownMinWidth, ...dropdownStyle };

+ 2 - 2
packages/semi-ui/select/virtualRow.tsx

@@ -5,8 +5,8 @@ export interface VirtualRowProps{
     style?: React.CSSProperties;
 }
 const VirtualRow = ({ index, data, style }: VirtualRowProps) => {
-    const { visibileOptions } = data;
-    const option = visibileOptions[index];
+    const { visibleOptions } = data;
+    const option = visibleOptions[index];
     return data.renderOption(option, index, style);
 };
 

+ 114 - 0
packages/semi-ui/table/_story/v2/FixedResizable/index.jsx

@@ -0,0 +1,114 @@
+import React, { useState } from 'react';
+import { Table, Avatar, Button, Space } from '@douyinfe/semi-ui';
+import { IconMore } from '@douyinfe/semi-icons';
+
+const columns = [
+    {
+        title: '标题',
+        dataIndex: 'name',
+        width: 300,
+        key: 'name',
+        render: (text, record, index) => {
+            return (
+                <div>
+                    <Avatar size="small" shape="square" src={record.nameIconSrc} style={{ marginRight: 12 }}></Avatar>
+                    {text}
+                </div>
+            );
+        },
+    },
+    {
+        title: '大小',
+        dataIndex: 'size',
+        key: 'size',
+        width: 200,
+    },
+    {
+        title: '所有者',
+        dataIndex: 'owner',
+        key: 'owner',
+        width: 200,
+        render: (text, record, index) => {
+            return (
+                <div>
+                    <Avatar size="small" color={record.avatarBg} style={{ marginRight: 4 }}>
+                        {typeof text === 'string' && text.slice(0, 1)}
+                    </Avatar>
+                    {text}
+                </div>
+            );
+        },
+    },
+    {
+        title: '更新日期',
+        dataIndex: 'updateTime',
+        key: 'updateTime',
+        width: 200,
+    },
+    {
+        title: '',
+        dataIndex: 'operate',
+        key: 'operate',
+        render: () => {
+            return <IconMore />;
+        },
+    },
+];
+
+/**
+ * fix https://github.com/DouyinFE/semi-design/issues/650
+ */
+App.storyName = 'fixed resizable column width bug';
+App.parameters = { chromatic: { disableSnapshot: true } };
+function App() {
+    const [cols, setCols] = useState(columns);
+
+    const onClickHandle = () => {
+        const localCols = [...cols].filter((i, index) => index !== 1);
+        setCols(localCols);
+    };
+
+    const data = [
+        {
+            key: '1',
+            name: 'Semi Design 设计稿.fig',
+            nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png',
+            size: '2M',
+            owner: '姜鹏志',
+            updateTime: '2020-02-02 05:13',
+            avatarBg: 'grey',
+        },
+        {
+            key: '2',
+            name: 'Semi Design 分享演示文稿',
+            nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
+            size: '2M',
+            owner: '郝宣',
+            updateTime: '2020-01-17 05:31',
+            avatarBg: 'red',
+        },
+        {
+            key: '3',
+            name: '设计文档',
+            nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
+            size: '34KB',
+            owner: 'Zoey Edwards',
+            updateTime: '2020-01-26 11:01',
+            avatarBg: 'light-blue',
+        },
+    ];
+
+    return (
+        <>
+            <Space>
+                <Button onClick={onClickHandle}>减少一列</Button>
+                <Button theme="solid" onClick={() => setCols(columns)}>
+                    reset
+                </Button>
+            </Space>
+            <Table columns={cols} dataSource={data} pagination={false} resizable />
+        </>
+    );
+}
+
+export default App;

+ 2 - 1
packages/semi-ui/table/_story/v2/index.js

@@ -1,4 +1,5 @@
 export { default as DefaultFilteredValue } from './defaultFilteredValue';
 export { default as FixedColumnsChange } from './FixedColumnsChange';
 export { default as FixedZIndex } from './FixedZIndex';
-export { default as FixedHeaderMerge } from './FixedHeaderMerge';
+export { default as FixedHeaderMerge } from './FixedHeaderMerge';
+export { default as FixedResizable } from './FixedResizable';

+ 48 - 4
packages/semi-ui/tooltip/__test__/tooltip.test.js

@@ -72,8 +72,8 @@ describe(`Tooltip`, () => {
         expect(elem.state(`visible`)).toBe(true);
 
         // click outside
-        document.body.click();
-        // document.dispatchEvent(new Event('mousedown', { bubbles: true, cancelable: true }));
+        // document.body.click();
+        document.dispatchEvent(new Event('mousedown', { bubbles: true, cancelable: true }));
         // demo.find(`#${triggerId}`)
         //     .at(0)
         //     .simulate(`mouseDown`);
@@ -88,7 +88,8 @@ describe(`Tooltip`, () => {
         // unmount elem
         demo.unmount();
         await sleep(100);
-        document.body.click();
+        // document.body.click();
+        document.dispatchEvent(new Event('mousedown', { bubbles: true, cancelable: true }));
         expect(document.getElementsByClassName(`${BASE_CLASS_PREFIX}-tooltip-wrapper`).length).toBe(0);
     });
 
@@ -165,7 +166,8 @@ describe(`Tooltip`, () => {
         expect(refFn.called).toBeTruthy();
 
         // click outside
-        document.body.click();
+        // document.body.click();
+        document.dispatchEvent(new Event('mousedown', { bubbles: true, cancelable: true }));
         await sleep(100);
         expect(
             demo
@@ -290,6 +292,48 @@ describe(`Tooltip`, () => {
             expect(document.querySelector(`.${BASE_CLASS_PREFIX}-tooltip-wrapper`).getAttribute('x-placement')).toBe(position);
         }
     });
+
+  it(`test click outside handler`, async () => {
+    const containerId = `container`;
+
+    const demo = mount(
+      <div style={{ height: 480, width: 320 }}>
+        <div id={containerId}>Hello Semi</div>
+        <Tooltip
+          content='Content'
+          trigger='click'
+        >
+          <Button >Click here</Button>
+        </Tooltip>
+      </div>
+    );
+
+    const toolTipElem = demo.find(Tooltip);
+    const buttonElem = demo.find(Button);
+    // click inside
+    buttonElem.simulate('click');
+    toolTipElem.update();
+    await sleep(100);
+    expect(toolTipElem.state(`visible`)).toBe(true);
+
+    // click outside
+    // document.body.click();
+    document.dispatchEvent(new Event('mousedown', { bubbles: true, cancelable: true }));
+    toolTipElem.update();
+    await sleep(100);
+    expect(toolTipElem.state('visible')).toBe(false);
+
+    // click button to show tooltip
+    buttonElem.simulate('click');
+    toolTipElem.update();
+    await sleep(100);
+    expect(toolTipElem.state('visible')).toBe(true);
+
+    document.getElementById(containerId).dispatchEvent(new Event('mousedown', { bubbles: true, cancelable: true }));
+    toolTipElem.update();
+    await sleep(100);
+    expect(toolTipElem.state('visible')).toBe(false);
+  });
 });
 
 it('wrapperClassName', () => {

+ 4 - 4
packages/semi-ui/tooltip/index.tsx

@@ -280,7 +280,7 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
             getDocumentElementBounding: () => document.documentElement.getBoundingClientRect(),
             setPosition: ({ position, ...style }: { position: Position }) => {
                 this.setState(
-                    { 
+                    {
                         containerStyle: { ...this.state.containerStyle, ...style },
                         placement: position,
                         isPositionUpdated: true
@@ -326,11 +326,11 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
                         cb();
                     }
                 };
-                document.addEventListener('click', this.clickOutsideHandler, false);
+                document.addEventListener('mousedown', this.clickOutsideHandler, { capture: true });
             },
             unregisterClickOutsideHandler: () => {
                 if (this.clickOutsideHandler) {
-                    document.removeEventListener('click', this.clickOutsideHandler, false);
+                    document.removeEventListener('mousedown', this.clickOutsideHandler, { capture: true });
                     this.clickOutsideHandler = null;
                 }
             },
@@ -661,4 +661,4 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
     }
 }
 
-export { Position };
+export { Position };