UNPKG

@appbuckets/react-ui

Version:
417 lines (410 loc) 11.2 kB
'use strict'; var tslib = require('tslib'); var React = require('react'); var clsx = require('clsx'); var reactUiCore = require('@appbuckets/react-ui-core'); var customHook = require('../utils/customHook.js'); require('../BucketTheme/BucketTheme.js'); var BucketContext = require('../BucketTheme/BucketContext.js'); function _interopDefaultLegacy(e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; } function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty( n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; }, } ); } }); } n['default'] = e; return Object.freeze(n); } var React__namespace = /*#__PURE__*/ _interopNamespace(React); var clsx__default = /*#__PURE__*/ _interopDefaultLegacy(clsx); /* -------- * Internal Function * -------- */ function nextFrame(callback) { requestAnimationFrame(function () { requestAnimationFrame(callback); }); } /* -------- * Component Render * -------- */ var Collapsable = function (receivedProps) { var props = BucketContext.useWithDefaultProps('collapsable', receivedProps); var _a = customHook.useSharedClassName(props), className = _a.className, _b = _a.rest, children = _b.children, collapsedHeight = _b.collapsedHeight, content = _b.content, userDefinedDefaultOpen = _b.defaultOpen, disabled = _b.disabled, onChange = _b.onChange, onClose = _b.onClose, onOpen = _b.onOpen, userDefinedOpen = _b.open, skipAnimation = _b.skipAnimation, trigger = _b.trigger, userDefinedTriggerRef = _b.triggerRef, rest = tslib.__rest(_b, [ 'children', 'collapsedHeight', 'content', 'defaultOpen', 'disabled', 'onChange', 'onClose', 'onOpen', 'open', 'skipAnimation', 'trigger', 'triggerRef', ]); // ---- // Memoized Props and Internal Hooks // ---- var contentRef = React__namespace.useRef(null); var triggerRef = React__namespace.useRef(); var _c = tslib.__read( React__namespace.useReducer(function (val) { return val + 1; }, 0), 2 ), forceUpdate = _c[1]; var _d = tslib.__read(React__namespace.useState(0), 2), callbackTick = _d[0], setCallbackTick = _d[1]; var _e = tslib.__read( reactUiCore.useAutoControlledValue(false, { defaultProp: userDefinedDefaultOpen, prop: userDefinedOpen, }), 2 ), isOpen = _e[0], trySetOpen = _e[1]; var collapsedVisibility = React__namespace.useMemo( function () { return collapsedHeight === 0 ? 'hidden' : 'unset'; }, [collapsedHeight] ); // ---- // Internal State // ---- var state = React__namespace.useRef({ collapse: isOpen ? 'expanded' : 'collapsed', style: { height: isOpen ? '' : collapsedHeight, visibility: isOpen ? 'unset' : collapsedVisibility, }, }).current; // ---- // Handlers and Callbacks // ---- var onCallback = React__namespace.useCallback(function (callback, params) { if (typeof callback === 'function') { if (Array.isArray(params)) { callback.apply( void 0, tslib.__spreadArray([], tslib.__read(params), false) ); } else { callback(); } } }, []); var getElementHeight = React__namespace.useCallback(function () { var _a; return ''.concat( ((_a = contentRef.current) === null || _a === void 0 ? void 0 : _a.scrollHeight) || 0, 'px' ); }, []); var setCollapsed = React__namespace.useCallback( function () { /** If no ref, return */ if (!contentRef.current) { return; } /** Update the State */ state.collapse = 'collapsed'; state.style = { height: collapsedHeight, visibility: collapsedVisibility, }; forceUpdate(); setTimeout(function () { setCallbackTick(Date.now()); }, 0); }, [collapsedHeight, collapsedVisibility, state] ); var setCollapsing = React__namespace.useCallback( function () { /** If no ref, return */ if (!contentRef.current) { return; } /** If must avoid animation, skip */ if (skipAnimation) { return setCollapsed(); } /** Update the State */ state.collapse = 'collapsing'; state.style = { height: getElementHeight(), visibility: 'unset', }; forceUpdate(); nextFrame(function () { if (!contentRef.current) { return; } if (state.collapse !== 'collapsing') { return; } state.style = { height: collapsedHeight, visibility: 'unset', }; setCallbackTick(Date.now()); }); }, [collapsedHeight, getElementHeight, setCollapsed, skipAnimation, state] ); var setExpanded = React__namespace.useCallback( function () { /** If no ref, return */ if (!contentRef.current) { return; } /** Update the State */ state.collapse = 'expanded'; state.style = { height: '', visibility: 'unset', }; forceUpdate(); setTimeout(function () { setCallbackTick(Date.now()); }, 0); }, [state] ); var setExpanding = React__namespace.useCallback( function () { /** If no ref, return */ if (!contentRef.current) { return; } /** If must avoid animation, skip */ if (skipAnimation) { return setExpanded(); } /** Update state */ state.collapse = 'expanding'; nextFrame(function () { /** If no ref, return */ if (!contentRef.current) { return; } if (state.collapse !== 'expanding') { return; } state.style = { height: getElementHeight(), visibility: 'unset', }; setCallbackTick(Date.now()); }); }, [getElementHeight, setExpanded, skipAnimation, state] ); var handleTransitionEnd = React__namespace.useCallback( function (_a) { var target = _a.target, propertyName = _a.propertyName; /** Skip other transition */ if (target !== contentRef.current || propertyName !== 'height') { return; } var height = target.style.height; /** Properly continue transition */ switch (state.collapse) { case 'expanding': if (!(height === '' || height === ''.concat(collapsedHeight, 'px'))) { setExpanded(); } break; case 'collapsing': if (!(height === '' || height !== ''.concat(collapsedHeight, 'px'))) { setCollapsed(); } break; } }, [collapsedHeight, setExpanded, setCollapsed, state.collapse] ); var handleTriggerRef = React__namespace.useCallback( function (component) { triggerRef.current = component; if (userDefinedTriggerRef !== undefined) { reactUiCore.handleRef(userDefinedTriggerRef, component); } }, [userDefinedTriggerRef] ); var handleCollapsableToggle = React__namespace.useCallback( function () { /** Abort if Disabled */ if (disabled) { return; } if (state.collapse === 'collapsed' || state.collapse === 'collapsing') { /** Invoke user defined callback */ if (typeof onOpen === 'function') { onOpen(state); } /** Try setting State */ trySetOpen(true); } else if ( state.collapse === 'expanded' || state.collapse === 'expanding' ) { /** Invoke user defined callback */ if (typeof onClose === 'function') { onClose(state); } /** Try setting State */ trySetOpen(false); } }, [disabled, onClose, onOpen, state, trySetOpen] ); // ---- // LifeCycle Events // ---- /** Simulate getDerivedStateFromProps */ var didOpen = state.collapse === 'expanding' || state.collapse === 'expanded'; React__namespace.useEffect( function () { if (callbackTick) { onCallback(onChange, [state]); } }, // This effect is used to auto invoke on state change // only once the callbackTick will be update. // Callback Tick must be the only dependencies // eslint-disable-next-line react-hooks/exhaustive-deps [callbackTick] ); React__namespace.useEffect( function () { if (isOpen && !didOpen && !disabled) { setExpanding(); } else if (!isOpen && didOpen && !disabled) { setCollapsing(); } }, // This effect is used to set the animation start // while opening or closing the collapsable element // In this case, the only dependencies of the Effect // must be the open state and the internal didOpen state // eslint-disable-next-line react-hooks/exhaustive-deps [didOpen, isOpen] ); // ---- // Class List Building // ---- var classes = clsx__default['default']( { disabled: disabled, opening: state.collapse === 'expanding', opened: state.collapse === 'expanded' || state.collapse === 'expanding', closing: state.collapse === 'collapsing', closed: state.collapse === 'collapsed' || state.collapse === 'collapsing', }, 'collapsable', className ); // ---- // Trigger Element // ---- var triggerElement = React__namespace.useMemo( function () { if (!trigger) { return null; } return React__namespace.createElement( reactUiCore.Ref, { innerRef: handleTriggerRef }, React__namespace.cloneElement(trigger, { onClick: handleCollapsableToggle, }) ); }, [handleCollapsableToggle, handleTriggerRef, trigger] ); // ---- // Component Render // ---- var collapsableContent = reactUiCore.childrenUtils.isNil(children) ? content : children; var contentStyle = tslib.__assign( { overflow: state.collapse === 'expanded' ? '' : 'hidden' }, state.style ); return React__namespace.createElement( 'div', tslib.__assign({}, rest, { className: classes }), triggerElement && React__namespace.createElement( 'div', { className: 'trigger' }, triggerElement ), React__namespace.createElement( 'div', { ref: contentRef, className: 'content', style: contentStyle, onTransitionEnd: handleTransitionEnd, }, collapsableContent ) ); }; Collapsable.displayName = 'Collapsable'; Collapsable.create = reactUiCore.createShorthandFactory( Collapsable, function (content) { return { content: content, }; } ); module.exports = Collapsable;