UNPKG

@blueprintjs/core

Version:
191 lines 8.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Collapse = exports.AnimationStates = void 0; const tslib_1 = require("tslib"); const jsx_runtime_1 = require("react/jsx-runtime"); /* * Copyright 2015 Palantir Technologies, Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const classnames_1 = tslib_1.__importDefault(require("classnames")); const react_1 = require("react"); const common_1 = require("../../common"); const props_1 = require("../../common/props"); /** * `Collapse` can be in one of six states, enumerated here. * When changing the `isOpen` prop, the following happens to the states: * isOpen={true} : CLOSED -> OPEN_START -> OPENING -> OPEN * isOpen={false} : OPEN -> CLOSING_START -> CLOSING -> CLOSED */ var AnimationStates; (function (AnimationStates) { /** * The body is re-rendered, height is set to the measured body height and * the body Y is set to 0. */ AnimationStates[AnimationStates["OPEN_START"] = 0] = "OPEN_START"; /** * Animation begins, height is set to auto. This is all animated, and on * complete, the state changes to OPEN. */ AnimationStates[AnimationStates["OPENING"] = 1] = "OPENING"; /** * The collapse height is set to auto, and the body Y is set to 0 (so the * element can be seen as normal). */ AnimationStates[AnimationStates["OPEN"] = 2] = "OPEN"; /** * Height has been changed from auto to the measured height of the body to * prepare for the closing animation in CLOSING. */ AnimationStates[AnimationStates["CLOSING_START"] = 3] = "CLOSING_START"; /** * Height is set to 0 and the body Y is at -height. Both of these properties * are transformed, and then after the animation is complete, the state * changes to CLOSED. */ AnimationStates[AnimationStates["CLOSING"] = 4] = "CLOSING"; /** * The contents of the collapse is not rendered, the collapse height is 0, * and the body Y is at -height (so that the bottom of the body is at Y=0). */ AnimationStates[AnimationStates["CLOSED"] = 5] = "CLOSED"; })(AnimationStates || (exports.AnimationStates = AnimationStates = {})); /** * Collapse component. * * @see https://blueprintjs.com/docs/#core/components/collapse */ const Collapse = ({ children, className, component = "div", isOpen = false, keepChildrenMounted = false, transitionDuration = 200, }) => { const [animationState, setAnimationState] = (0, react_1.useState)(isOpen ? AnimationStates.OPEN : AnimationStates.CLOSED); const [height, setHeight] = (0, react_1.useState)(isOpen ? "auto" : "0px"); const [heightWhenOpen, setHeightWhenOpen] = (0, react_1.useState)(undefined); const [prevIsOpen, setPrevIsOpen] = (0, react_1.useState)(isOpen); const isOpenRef = (0, react_1.useRef)(isOpen); isOpenRef.current = isOpen; const contents = (0, react_1.useRef)(null); const animationStateRef = (0, react_1.useRef)(animationState); animationStateRef.current = animationState; const delayedTimerRef = (0, react_1.useRef)(); const onDelayedStateChange = (0, react_1.useCallback)(() => { switch (animationStateRef.current) { case AnimationStates.OPENING: setAnimationState(AnimationStates.OPEN); setHeight("auto"); break; case AnimationStates.CLOSING: setAnimationState(AnimationStates.CLOSED); break; default: break; } }, []); // Synchronize animationState with the isOpen prop during render. if (isOpen !== prevIsOpen) { setPrevIsOpen(isOpen); if (isOpen) { switch (animationState) { case AnimationStates.OPEN: case AnimationStates.OPENING: break; default: setAnimationState(AnimationStates.OPEN_START); } } else { switch (animationState) { case AnimationStates.CLOSED: case AnimationStates.CLOSING: break; default: setAnimationState(AnimationStates.CLOSING_START); setHeight(`${heightWhenOpen}px`); } } } // Clean up delayed timer on unmount only (mirrors AbstractPureComponent.setTimeout behavior) (0, react_1.useEffect)(() => { return () => { if (delayedTimerRef.current != null) { clearTimeout(delayedTimerRef.current); } }; }, []); // Handle animation state transitions (0, react_1.useEffect)(() => { if (!contents.current) return undefined; switch (animationState) { case AnimationStates.OPEN_START: { const clientHeight = contents.current.clientHeight; setAnimationState(AnimationStates.OPENING); setHeight(`${clientHeight}px`); setHeightWhenOpen(clientHeight); clearTimeout(delayedTimerRef.current); delayedTimerRef.current = setTimeout(onDelayedStateChange, transitionDuration); break; } case AnimationStates.CLOSING_START: { const clientHeight = contents.current.clientHeight; const immediateTimer = setTimeout(() => { setAnimationState(AnimationStates.CLOSING); setHeight("0px"); setHeightWhenOpen(clientHeight); }); clearTimeout(delayedTimerRef.current); delayedTimerRef.current = setTimeout(onDelayedStateChange, transitionDuration); return () => clearTimeout(immediateTimer); } default: break; } return undefined; }, [animationState, transitionDuration, onDelayedStateChange]); const contentsRefHandler = (0, react_1.useCallback)((element) => { contents.current = element; if (contents.current != null) { const contentHeight = contents.current.clientHeight; setAnimationState(isOpenRef.current ? AnimationStates.OPEN : AnimationStates.CLOSED); setHeight(contentHeight === 0 ? undefined : `${contentHeight}px`); setHeightWhenOpen(contentHeight === 0 ? undefined : contentHeight); } }, []); const isContentVisible = animationState !== AnimationStates.CLOSED; const shouldRenderChildren = isContentVisible || keepChildrenMounted; const displayWithTransform = isContentVisible && animationState !== AnimationStates.CLOSING; // When fully open, always use "auto" height so content is never clipped — the ref // callback may have stored a measured pixel value, but that's only needed for close // animations, not for the resting open state. const effectiveHeight = animationState === AnimationStates.OPEN ? "auto" : height; const isAutoHeight = effectiveHeight === "auto"; const containerStyle = { height: isContentVisible ? effectiveHeight : undefined, overflowY: isAutoHeight ? "visible" : undefined, // transitions don't work with height: auto transition: isAutoHeight ? "none" : undefined, }; const contentsStyle = { // only use heightWhenOpen while closing transform: displayWithTransform ? "translateY(0)" : `translateY(-${heightWhenOpen}px)`, // transitions don't work with height: auto transition: isAutoHeight ? "none" : undefined, }; return (0, react_1.createElement)(component, { className: (0, classnames_1.default)(common_1.Classes.COLLAPSE, className), style: containerStyle, }, (0, jsx_runtime_1.jsx)("div", { className: common_1.Classes.COLLAPSE_BODY, ref: contentsRefHandler, style: contentsStyle, "aria-hidden": !isContentVisible, children: shouldRenderChildren ? children : null })); }; exports.Collapse = Collapse; exports.Collapse.displayName = `${props_1.DISPLAYNAME_PREFIX}.Collapse`; //# sourceMappingURL=collapse.js.map