Forráskód Böngészése

fix: [cascader] Fix the display error of single selection when setting filterTreeNode & displayRender at the same time (#957)

YyumeiZhang 3 éve
szülő
commit
bbde70429f

+ 11 - 30
cypress/integration/cascader.spec.js

@@ -2,39 +2,27 @@ describe('cascader', () => {
     it('clear when single choose', () => {
         cy.visit('http://127.0.0.1:6006/iframe.html?id=cascader--show-clear&args=&viewMode=story');
         cy.viewport(1500, 1000);
-
-        cy.get('input').eq(0).click();
-        cy.get('span').contains('Node1').click();
-        cy.get('span').contains('Child Node2').click();
-        cy.get('input').should('have.value', 'Node1 / Child Node2');
-        cy.get('input').eq(0).trigger('mouseover');
-        cy.get('.semi-cascader-clearbtn').click();
-        cy.get('input').should('have.value', '');
-
-        // clear when search
-        cy.get('input').eq(0).click();
-        // wait the panel open
-        cy.wait(100);
+        cy.get('.semi-cascader-selection').eq(0).click();
         cy.get('span').contains('Node1').click();
         cy.get('span').contains('Child Node2').click();
-        cy.get('input').eq(0).type('Node1');
-        cy.get('input').eq(0).trigger('mouseover');
+        cy.get('.semi-cascader-search-wrapper span').eq(0).contains('Child Node2').should('exist');
+        cy.get('.semi-cascader-selection').eq(0).trigger('mouseover');
         cy.get('.semi-cascader-clearbtn').click();
-        cy.get('input').should('have.attr', 'placeholder', 'Node1 / Child Node2');
+        cy.get('.semi-cascader-search-wrapper span').eq(0).contains('Child Node2').should('not.exist');
     });
 
     it('clear by key press', () => {
         cy.visit('http://127.0.0.1:6006/iframe.html?id=cascader--show-clear&args=&viewMode=story');
         cy.viewport(1500, 1000);
 
-        cy.get('input').eq(0).click();
+        cy.get('.semi-cascader-selection').eq(0).click();
         cy.get('span').contains('Node1').click();
         cy.get('span').contains('Child Node2').click();
-        cy.get('input').should('have.value', 'Node1 / Child Node2');
-        cy.get('input').eq(0).trigger('mouseover');
+        cy.get('.semi-cascader-search-wrapper span').eq(0).contains('Child Node2').should('exist');
+        cy.get('.semi-cascader-selection').eq(0).trigger('mouseover');
         cy.get(".semi-cascader-clearbtn").focus();
         cy.get('.semi-cascader-clearbtn').type('{enter}');
-        cy.get('input').should('have.value', '');
+        cy.get('.semi-cascader-search-wrapper span').eq(0).contains('Child Node2').should('not.exist');;
         cy.get('#root').click('right');
 
         cy.get('.semi-cascader').eq(1).click();
@@ -68,14 +56,7 @@ describe('cascader', () => {
         cy.visit('http://127.0.0.1:6006/iframe.html?id=cascader--dynamic-placeholder&args=&viewMode=story');
         cy.get('.semi-cascader-selection-placeholder').contains('Please select');
         cy.get('.semi-button-content').contains('Toggle').click();
-        cy.get('.semi-input-default').should('have.attr', 'placeholder', 'Search something');
-    });
-
-    it('placeholder change', () => {
-        cy.visit('http://127.0.0.1:6006/iframe.html?id=cascader--dynamic-placeholder&args=&viewMode=story');
-        cy.get('.semi-cascader-selection-placeholder').contains('Please select');
-        cy.get('.semi-button-content').contains('Toggle').click();
-        cy.get('.semi-input-default').should('have.attr', 'placeholder', 'Search something');
+        cy.get('.semi-cascader-search-wrapper span').eq(0).contains('Search something').should('exist');
     });
 
     it('load data', () => {
@@ -112,7 +93,7 @@ describe('cascader', () => {
     it('not exit default value', () => {
         cy.visit('http://127.0.0.1:6006/iframe.html?id=cascader--default-value-not-exist&args=&viewMode=story');
         
-        cy.get('input').should('have.value', 'yazhou not exist');
+        cy.get('.semi-cascader-search-wrapper span').eq(0).contains('yazhou not exist').should('exist');
     });
    
     it('multiple onChangeWithObject value=undefined', () => {
@@ -121,5 +102,5 @@ describe('cascader', () => {
         cy.get('.semi-cascader').click();
         cy.get('.semi-checkbox').eq(0).click();
         cy.get('.semi-tag-content').contains('亚洲');
-    })
+    });
 });

+ 20 - 0
packages/semi-foundation/cascader/cascader.scss

@@ -175,6 +175,17 @@ $module: #{$prefix}-cascader;
                 height: $height-cascader_selection_tagInput_input_large;
             }
         }
+
+        &-text {
+
+            &-inactive {
+                color: $color-cascader_selection_text_inactive;
+            }
+
+            &-hide {
+                display: none;
+            }
+        }
     }
 
     &-arrow,
@@ -294,8 +305,17 @@ $module: #{$prefix}-cascader;
     .#{$module}-selection {
         .#{$module}-search-wrapper {
             width: 100%;
+            height: $height-cascader_selection_wrapper;
+            display: flex;
+            align-items: center;
+            position: relative;
 
             .#{$prefix}-input-wrapper {
+                position: absolute;
+                top: 0;
+                left: 0;
+                border: none;
+                background-color: transparent;
                 height: 100%;
                 width: 100%;
                 border: $color-cascader_input-border-default;

+ 20 - 0
packages/semi-foundation/cascader/foundation.ts

@@ -183,6 +183,7 @@ export interface BasicCascaderInnerData {
     isFocus?: boolean;
     isInput?: boolean;
     disabledKeys?: Set<string>;
+    showInput?: boolean;
 }
 
 export interface CascaderAdapter extends DefaultAdapter<BasicCascaderProps, BasicCascaderInnerData> {
@@ -208,6 +209,8 @@ export interface CascaderAdapter extends DefaultAdapter<BasicCascaderProps, Basi
     notifyOnLoad: (newLoadedKeys: Set<string>, data: BasicCascaderData) => void;
     notifyListScroll: (e: any, panel: BasicScrollPanelProps) => void;
     notifyOnExceed: (data: BasicEntity[]) => void;
+    toggleInputShow: (show: boolean, cb: () => void) => void;
+    updateFocusState: (focus: boolean) => void,
 }
 
 // eslint-disable-next-line max-len
@@ -484,9 +487,11 @@ export default class CascaderFoundation extends BaseFoundation<CascaderAdapter,
 
     open() {
         const filterable = this._isFilterable();
+        const { multiple } = this.getProps();
         this._adapter.openMenu();
         if (filterable) {
             this._clearInput();
+            !multiple && this.toggle2SearchInput(true);
         }
         if (this._isControlledComponent()) {
             this.reCalcActiveKeys();
@@ -524,10 +529,25 @@ export default class CascaderFoundation extends BaseFoundation<CascaderAdapter,
                 inputValue = this.renderDisplayText([...selectedKeys][0]);
             }
             this._adapter.updateStates({ inputValue });
+            !multiple && this.toggle2SearchInput(false);
+            !multiple && this._adapter.updateFocusState(false);
         }
         this._notifyBlur(e);
     }
 
+    toggle2SearchInput(isShow: boolean) {
+        if (isShow) {
+            this._adapter.toggleInputShow(isShow, () => this.focusInput());
+        } else {
+            this._adapter.toggleInputShow(isShow, () => undefined);
+        }
+    }
+
+    focusInput() {
+        this._adapter.focusInput();
+        this._adapter.updateFocusState(true);
+    }
+
     getMergedMotion = () => {
         const { motion } = this.getProps();
         const { isSearching } = this.getStates();

+ 2 - 0
packages/semi-foundation/cascader/variables.scss

@@ -54,6 +54,7 @@ $spacing-cascader_clearBtn-marginRight: 12px; // 级联选择触发器清空按
 
 $color-cascader_selection_n-text-default: var(--semi-color-text-0); // 超出 maxTagCount 后,+n 的文字默认颜色
 $color-cascader_selection_n-text-disabled: var(--semi-color-disabled-text); // 超出 maxTagCount 后,+n 的文字disabled颜色
+$color-cascader_selection_text_inactive: var(--semi-color-text-2); // 级联选择单选inpu输入框和text并存时,text颜色
 $color-cascader_selection-text-default: var(--semi-color-text-0); // 级联选择选中项文字颜色
 $color-cascader_placeholder-text-default: var(--semi-color-text-2); // 级联选择未选中项文字颜色
 $color-cascader-icon-default: var(--semi-color-text-2); // 级联选择图标颜色 - 默认
@@ -94,6 +95,7 @@ $height-cascader_option_list: 180px; // 级联选择菜单高度
 $height-cascader_selection_tagInput_input_small: 22px;
 $height-cascader_selection_tagInput_input_default: 30px;
 $height-cascader_selection_tagInput_input_large: 38px;
+$height-cascader_selection_wrapper: 30px;
 
 $spacing-cascader_text-marginX: $spacing-base-tight; // 级联选择 prefix/suffix 文字水平内间距
 $spacing-cascader_icon-marginX: $spacing-tight; // 级联选择 prefix/suffix 图标水平内间距

+ 13 - 10
packages/semi-ui/cascader/__test__/cascader.test.js

@@ -427,10 +427,10 @@ describe('Cascader', () => {
         });
         expect(
             cascaderWithSingleFilter
-            .find(`.${BASE_CLASS_PREFIX}-cascader-search-wrapper .${BASE_CLASS_PREFIX}-input`)
+            .find(`.${BASE_CLASS_PREFIX}-cascader-search-wrapper span`)
             .getDOMNode()
-            .getAttribute('value')
-        ).toEqual('亚洲 / 中国');
+            .textContent
+            ).toEqual('亚洲 / 中国');
         cascaderWithSingleFilter.unmount();
 
         const cascaderWithSingleControlled = render({
@@ -550,10 +550,10 @@ describe('Cascader', () => {
         expect(searchWrapper.exists()).toEqual(true);
         expect(
             searchWrapper
-                .find('input')
-                .instance()
-                .getAttribute('placeholder')
-        ).toEqual('placeholder');
+                .find('span')
+                .getDOMNode()
+                .textContent
+            ).toEqual('placeholder');
     });
 
     it('onSearch', () => {
@@ -563,10 +563,11 @@ describe('Cascader', () => {
             filterTreeNode: true,
             onSearch: spyOnSearch,
         });
-        const searchWrapper = cascader.find(`.${BASE_CLASS_PREFIX}-cascader-search-wrapper`);
         let searchValue = '${BASE_CLASS_PREFIX}';
         let event = { target: { value: searchValue } };
-        searchWrapper.find('input').simulate('change', event);
+        cascader.simulate('click');
+        const input = cascader.find(`.${BASE_CLASS_PREFIX}-cascader-selection input`);
+        input.simulate('change', event);
         expect(spyOnSearch.calledOnce).toBe(true);
         expect(spyOnSearch.calledWithMatch(searchValue)).toBe(true);
     });
@@ -1228,8 +1229,10 @@ describe('Cascader', () => {
             separator: ' > ',
             defaultOpen: true,
         });
+        const span = cascader.find(`.${BASE_CLASS_PREFIX}-cascader-selection span`);
+        expect(span.getDOMNode().textContent).toEqual('亚洲 > 中国 > 北京'); 
+        cascader.simulate('click');
         const input = cascader.find(`.${BASE_CLASS_PREFIX}-cascader-selection input`);
-        expect(input.props().placeholder).toEqual('亚洲 > 中国 > 北京'); 
         const event = { target: { value: '中国' } };
         input.simulate('change', event);
         expect(

+ 22 - 0
packages/semi-ui/cascader/_story/cascader.stories.js

@@ -396,6 +396,28 @@ Searchable.parameters = {
   chromatic: { disableSnapshot: false },
 }
 
+export const filterTreeNodeAndDisplayRender = () => {
+  return (
+    <>
+      <div>
+          filterTreeNode=true,配合displayRender 使用,回显到input的内容也是符合预期
+      </div>
+      <Cascader
+        filterTreeNode
+        style={{ width: 300 }}
+        treeData={treeData4}
+        placeholder="自定义回填时显示数据的格式"
+        defaultValue={['zhejiang', 'ningbo', 'jiangbei']}
+        displayRender={(item) => {
+          console.log('item', item);
+          return <div>
+              {'已选择:' + item.join(' -> ')}
+          </div>;}}
+      />
+    </>
+  );
+};
+
 export const Disabled = () => {
   return (
     <div>

+ 50 - 33
packages/semi-ui/cascader/index.tsx

@@ -247,6 +247,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
             loadingKeys: new Set(),
             /* Mark whether this rendering has triggered asynchronous loading of data */
             loading: false,
+            showInput: false,
         };
         this.options = {};
         this.isEmpty = false;
@@ -274,8 +275,8 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
             },
         };
         const cascaderAdapter: Pick<
-            CascaderAdapter,
-            'registerClickOutsideHandler' | 'unregisterClickOutsideHandler' | 'rePositionDropdown'
+        CascaderAdapter,
+        'registerClickOutsideHandler' | 'unregisterClickOutsideHandler' | 'rePositionDropdown'
         > = {
             registerClickOutsideHandler: cb => {
                 const clickOutsideHandler = (e: Event) => {
@@ -362,6 +363,14 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
             },
             notifyOnExceed: data => this.props.onExceed(data),
             notifyClear: () => this.props.onClear(),
+            toggleInputShow: (showInput: boolean, cb: (...args: any) => void) => {
+                this.setState({ showInput }, () => {
+                    cb();
+                });
+            },
+            updateFocusState: (isFocus: boolean) => {
+                this.setState({ isFocus });
+            },
         };
     }
 
@@ -541,20 +550,28 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
     renderInput() {
         const { size, disabled } = this.props;
         const inputcls = cls(`${prefixcls}-input`);
-        const { inputValue, inputPlaceHolder } = this.state;
+        const { inputValue, inputPlaceHolder, showInput } = this.state;
         const inputProps = {
             disabled,
             value: inputValue,
             className: inputcls,
             onChange: this.handleInputChange,
-            placeholder: inputPlaceHolder,
         };
         const wrappercls = cls({
             [`${prefixcls}-search-wrapper`]: true,
         });
+
+        const displayText = this.renderDisplayText();
+        const spanCls = cls({
+            [`${prefixcls}-selection-placeholder`]: !displayText,
+            [`${prefixcls}-selection-text-hide`]: showInput && inputValue,
+            [`${prefixcls}-selection-text-inactive`]: showInput && !inputValue,
+        });
+
         return (
             <div className={wrappercls}>
-                <Input ref={this.inputRef as any} size={size} {...inputProps} />
+                <span className={spanCls}>{displayText ? displayText : inputPlaceHolder}</span>
+                {showInput && <Input ref={this.inputRef as any} size={size} {...inputProps} />}
             </div>
         );
     }
@@ -865,40 +882,40 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
         const { isOpen, isFocus, isInput, checkedKeys } = this.state;
         const filterable = Boolean(filterTreeNode);
         const useCustomTrigger = typeof triggerRender === 'function';
-        const classNames = useCustomTrigger
-            ? cls(className)
-            : cls(prefixcls, className, {
-                  [`${prefixcls}-focus`]: isFocus || (isOpen && !isInput),
-                  [`${prefixcls}-disabled`]: disabled,
-                  [`${prefixcls}-single`]: true,
-                  [`${prefixcls}-filterable`]: filterable,
-                  [`${prefixcls}-error`]: validateStatus === 'error',
-                  [`${prefixcls}-warning`]: validateStatus === 'warning',
-                  [`${prefixcls}-small`]: size === 'small',
-                  [`${prefixcls}-large`]: size === 'large',
-                  [`${prefixcls}-with-prefix`]: prefix || insetLabel,
-                  [`${prefixcls}-with-suffix`]: suffix,
-              });
-        const mouseEvent = showClear
-            ? {
-                  onMouseEnter: () => this.handleMouseOver(),
-                  onMouseLeave: () => this.handleMouseLeave(),
-              }
-            : {};
+        const classNames = useCustomTrigger ?
+            cls(className) :
+            cls(prefixcls, className, {
+                [`${prefixcls}-focus`]: isFocus || (isOpen && !isInput),
+                [`${prefixcls}-disabled`]: disabled,
+                [`${prefixcls}-single`]: true,
+                [`${prefixcls}-filterable`]: filterable,
+                [`${prefixcls}-error`]: validateStatus === 'error',
+                [`${prefixcls}-warning`]: validateStatus === 'warning',
+                [`${prefixcls}-small`]: size === 'small',
+                [`${prefixcls}-large`]: size === 'large',
+                [`${prefixcls}-with-prefix`]: prefix || insetLabel,
+                [`${prefixcls}-with-suffix`]: suffix,
+            });
+        const mouseEvent = showClear ?
+            {
+                onMouseEnter: () => this.handleMouseOver(),
+                onMouseLeave: () => this.handleMouseLeave(),
+            } :
+            {};
         const sectionCls = cls(`${prefixcls}-selection`, {
             [`${prefixcls}-selection-multiple`]: multiple && !isEmpty(checkedKeys),
         });
         const inner = useCustomTrigger
             ? this.renderCustomTrigger()
             : [
-                  <Fragment key={'prefix'}>{prefix || insetLabel ? this.renderPrefix() : null}</Fragment>,
-                  <Fragment key={'selection'}>
-                      <div className={sectionCls}>{this.renderSelectContent()}</div>
-                  </Fragment>,
-                  <Fragment key={'clearbtn'}>{this.renderClearBtn()}</Fragment>,
-                  <Fragment key={'suffix'}>{suffix ? this.renderSuffix() : null}</Fragment>,
-                  <Fragment key={'arrow'}>{this.renderArrow()}</Fragment>,
-              ];
+                <Fragment key={'prefix'}>{prefix || insetLabel ? this.renderPrefix() : null}</Fragment>,
+                <Fragment key={'selection'}>
+                    <div className={sectionCls}>{this.renderSelectContent()}</div>
+                </Fragment>,
+                <Fragment key={'clearbtn'}>{this.renderClearBtn()}</Fragment>,
+                <Fragment key={'suffix'}>{suffix ? this.renderSuffix() : null}</Fragment>,
+                <Fragment key={'arrow'}>{this.renderArrow()}</Fragment>,
+            ];
         /**
          * Reasons for disabling the a11y eslint rule:
          * The following attributes(aria-controls,aria-expanded) will be automatically added by Tooltip, no need to declare here