Browse Source

feat: recode reCollapsible

代强 3 years ago
parent
commit
37745f6e9a

+ 6 - 0
packages/semi-foundation/collapsible/animation.scss

@@ -0,0 +1,6 @@
+$transition_duration-collapsible-height: 0.25s;//折叠-高度-动画持续时间
+$transition_function-collapsible-height: var(--semi-transition_function-easeIn);//折叠-高度-过渡曲线
+$transition_delay-collapsible-height: var(--semi-transition_delay-fastest);//折叠-高度-延迟时间
+
+
+

+ 7 - 0
packages/semi-foundation/collapsible/collapsible.scss

@@ -1,3 +1,10 @@
 import "./animation.scss";
 import "./variables.scss";
 
+
+$module: #{$prefix}-collapsible;
+
+
+.#{$module}-transition{
+    transition: height $transition_duration-collapsible-height $transition_function-collapsible-height $transition_delay-collapsible-height;
+}

+ 20 - 7
packages/semi-foundation/collapsible/foundation.ts

@@ -1,8 +1,4 @@
 import BaseFoundation, {DefaultAdapter} from "../base/foundation";
-import {CollapseAdapter} from "../collapse/foundation";
-import {Motion} from "@douyinfe/semi-ui/_base/base";
-import React from "react";
-
 
 export interface CollapsibleFoundationProps{
     isOpen?: boolean;
@@ -21,16 +17,33 @@ export interface CollapsibleFoundationState{
 
 
 export interface CollapsibleAdapter<P = Record<string, any>, S = Record<string, any>>  extends DefaultAdapter<P, S>{
-
+    setDOMInRenderTree: (isInRenderTree:boolean) => void;
+    setDOMHeight: (domHeight:number) => void;
+    setVisible: (visible:boolean) => void;
 }
 
-class CollapsibleFoundation extends BaseFoundation<CollapsibleAdapter>{
+class CollapsibleFoundation extends BaseFoundation<CollapsibleAdapter,CollapsibleFoundationProps,CollapsibleFoundationState>{
 
-    constructor(adapter: CollapseAdapter) {
+    constructor(adapter: CollapsibleAdapter) {
         super({
             ...adapter
         });
     }
+
+
+    updateDOMInRenderTree = (isInRenderTree) => {
+        this._adapter.setDOMInRenderTree(isInRenderTree);
+    }
+
+    updateDOMHeight = (domHeight:number) => {
+        this._adapter.setDOMHeight(domHeight);
+    }
+
+    updateVisible = (visible:boolean) => {
+        this._adapter.setVisible(visible);
+    }
+
+
 }
 
 

+ 89 - 47
packages/semi-ui/collapsible/index.tsx

@@ -1,14 +1,18 @@
 import React from 'react';
-import type {CollapsibleAdapter, CollapsibleFoundationProps, CollapsibleFoundationState} from "@douyinfe/semi-foundation/collapsible/foundation";
-import BaseComponent from "@douyinfe/semi-ui/_base/baseComponent";
-import {Motion} from "@douyinfe/semi-ui/_base/base";
+import type {
+    CollapsibleAdapter,
+    CollapsibleFoundationProps,
+    CollapsibleFoundationState
+} from "@douyinfe/semi-foundation/collapsible/foundation";
+import BaseComponent from "../_base/baseComponent";
 import PropTypes from "prop-types";
 import cls from "classnames";
 import {cssClasses} from "@douyinfe/semi-foundation/collapsible/constants";
-import CollapsibleOld from "@douyinfe/semi-ui/collapsible/index.old";
 import {isEqual} from "lodash";
-interface CollapsibleProps extends CollapsibleFoundationProps{
-    motion?: Motion;
+import CollapsibleFoundation from "@douyinfe/semi-foundation/collapsible/foundation";
+
+interface CollapsibleProps extends CollapsibleFoundationProps {
+    motion?: boolean;
     children?: React.ReactNode;
     isOpen?: boolean;
     duration?: number;
@@ -17,12 +21,13 @@ interface CollapsibleProps extends CollapsibleFoundationProps{
     style?: React.CSSProperties;
     collapseHeight?: number;
     reCalcKey?: number | string;
-    id?:string,
+    id?: string,
 }
 
-interface CollapsibleState extends CollapsibleFoundationState{
-    domInRenderTree:boolean
-    domHeight:number
+interface CollapsibleState extends CollapsibleFoundationState {
+    domInRenderTree: boolean
+    domHeight: number
+    visible: boolean
 }
 
 class Collapsible extends BaseComponent<CollapsibleProps, CollapsibleState> {
@@ -35,19 +40,37 @@ class Collapsible extends BaseComponent<CollapsibleProps, CollapsibleState> {
     };
 
     private domRef = React.createRef<HTMLDivElement>();
-    private wrapperRef = React.createRef<HTMLDivElement>();
     private resizeObserver: ResizeObserver | null;
+    public foundation: CollapsibleFoundation;
+
     constructor(props: CollapsibleProps) {
         super(props);
         this.state = {
-            domInRenderTree:false,
+            domInRenderTree: false,
             domHeight: 0,
+            visible: this.props.isOpen
         }
+        this.foundation = new CollapsibleFoundation(this.adapter);
     }
 
-    get adapter(): CollapsibleAdapter<CollapsibleProps,CollapsibleState> {
+    get adapter(): CollapsibleAdapter<CollapsibleProps, CollapsibleState> {
         return {
             ...super.adapter,
+            setDOMInRenderTree: (domInRenderTree) => {
+                if (this.state.domInRenderTree !== domInRenderTree) {
+                    this.setState({domInRenderTree})
+                }
+            },
+            setDOMHeight: (domHeight) => {
+                if (this.state.domHeight !== domHeight) {
+                    this.setState({domHeight})
+                }
+            },
+            setVisible: (visible) => {
+                if (this.state.visible !== visible) {
+                    this.setState({visible})
+                }
+            }
         };
     }
 
@@ -56,85 +79,104 @@ class Collapsible extends BaseComponent<CollapsibleProps, CollapsibleState> {
         this.resizeObserver = new ResizeObserver(this.handleResize);
         this.resizeObserver.observe(this.domRef.current);
         const domInRenderTree = this.isChildrenInRenderTree();
-        this.setState({domInRenderTree})
-        if(domInRenderTree){
-            this.setState({domHeight:this.domRef.current.scrollHeight})
+        this.foundation.updateDOMInRenderTree(domInRenderTree)
+        if (domInRenderTree) {
+            this.foundation.updateDOMHeight(this.domRef.current.scrollHeight)
         }
+
     }
 
     componentDidUpdate(prevProps: Readonly<CollapsibleProps>, prevState: Readonly<CollapsibleState>, snapshot?: any) {
         const changedPropKeys = Object.keys(this.props).filter(key => !isEqual(this.props[key], prevProps[key]));
         const changedStateKeys = Object.keys(this.state).filter(key => !isEqual(this.state[key], prevState[key]));
-        if(changedPropKeys.includes("reCalcKey")){
-            this.setState({domHeight:this.domRef.current.scrollHeight})
+        if (changedPropKeys.includes("reCalcKey")) {
+            this.foundation.updateDOMHeight(this.domRef.current.scrollHeight)
+        }
+        if (changedStateKeys.includes("domInRenderTree") && this.state.domInRenderTree) {
+            this.foundation.updateDOMHeight(this.domRef.current.scrollHeight)
         }
-        if(changedStateKeys.includes("domInRenderTree") && this.state.domInRenderTree){
-            this.setState({domHeight:this.domRef.current.scrollHeight})
+        if (changedPropKeys.includes("isOpen")) {
+            if (this.props.isOpen || !this.props.motion) {
+                this.foundation.updateVisible(this.props.isOpen)
+            }
         }
     }
 
-    handleResize = (entryList:ResizeObserverEntry[]) => {
+    componentWillUnmount() {
+        super.componentWillUnmount()
+        this.resizeObserver.disconnect();
+    }
+
+    handleResize = (entryList: ResizeObserverEntry[]) => {
         const entry = entryList[0];
-        if(entry){
-            const entryInfo  = Collapsible.getEntryInfo(entry);
-            this.setState({domInRenderTree:entryInfo.isShown,domHeight:entryInfo.height})
+        if (entry) {
+            const entryInfo = Collapsible.getEntryInfo(entry);
+            this.foundation.updateDOMHeight(entryInfo.height);
+            this.foundation.updateDOMInRenderTree(entryInfo.isShown);
         }
     }
 
-    static getEntryInfo = (entry:ResizeObserverEntry)=>{
+    static getEntryInfo = (entry: ResizeObserverEntry) => {
         //judge whether parent or self display none
-        let inRenderTree:boolean;
-        if(entry.borderBoxSize){
-            inRenderTree= !(entry.borderBoxSize[0].blockSize===0 && entry.borderBoxSize[0].inlineSize===0);
-        }else {
-            inRenderTree = !(entry.contentRect.height===0 && entry.contentRect.width===0);
+        let inRenderTree: boolean;
+        if (entry.borderBoxSize) {
+            inRenderTree = !(entry.borderBoxSize[0].blockSize === 0 && entry.borderBoxSize[0].inlineSize === 0);
+        } else {
+            inRenderTree = !(entry.contentRect.height === 0 && entry.contentRect.width === 0);
         }
 
         let height = 0;
-        if(entry.borderBoxSize){
+        if (entry.borderBoxSize) {
             height = Math.ceil(entry.borderBoxSize[0].blockSize);
-        }else{
+        } else {
             const target = entry.target as HTMLElement;
             height = target.clientHeight;
         }
 
         return {
-            isShown: inRenderTree,height
+            isShown: inRenderTree, height
         }
-
-
     }
 
-    isChildrenInRenderTree = ()=>{
-        if(this.domRef.current){
-            return this.domRef.current.offsetHeight >0
-        }else{
+    isChildrenInRenderTree = () => {
+        if (this.domRef.current) {
+            return this.domRef.current.offsetHeight > 0
+        } else {
             return false
         }
     }
 
     render() {
-        const wrapperStyle:React.CSSProperties = {
+        const wrapperStyle: React.CSSProperties = {
             overflow: 'hidden',
-            height: this.props.isOpen ? this.state.domHeight : 0,
-            transition: `height ${this.props.duration}ms ease-in-out`,
+            height: this.props.isOpen ? (this.props.collapseHeight || this.state.domHeight) : 0,
+            transitionDuration: `${this.props.motion ? this.props.duration : 0}ms`,
             ...this.props.style
         }
-        const wrapperCls = cls(`${cssClasses.PREFIX}-wrapper`, this.props.className);
-        return <div ref={this.wrapperRef} className={wrapperCls} style={wrapperStyle} >
+        const wrapperCls = cls(`${cssClasses.PREFIX}-wrapper`, {
+            [`${cssClasses.PREFIX}-transition`]: this.props.motion
+        }, this.props.className);
+        return <div className={wrapperCls} style={wrapperStyle} onTransitionEnd={() => {
+            if (!this.props.isOpen) {
+                this.foundation.updateVisible(false)
+            }
+        }}>
             <div
                 x-semi-prop="children"
                 ref={this.domRef}
-                style={{ overflow: 'hidden' }}
+                style={{overflow: 'hidden'}}
+                id={this.props.id}
             >
-                {(this.props.keepDOM || this.props.isOpen ) && this.props.children}
+                {
+                    (this.props.keepDOM || this.state.visible || this.props.isOpen) && this.props.children
+                }
             </div>
         </div>
     }
 }
 
 Collapsible.propTypes = {
-    motion: PropTypes.oneOfType([PropTypes.bool, PropTypes.func, PropTypes.object]),
+    motion: PropTypes.bool,
     children: PropTypes.node,
     isOpen: PropTypes.bool,
     duration: PropTypes.number,