UNPKG

@amaui/ui-react

Version:
315 lines (299 loc) 11.6 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } import React from 'react'; import { is, wait } from '@amaui/utils'; import AmauiSubscription from '@amaui/subscription'; import { classNames, useAmauiTheme } from '@amaui/style-react'; import TransitionContext from './Context'; import { reflow } from '../utils'; export const STATUS = { appended: 'appended', add: 'add', added: 'added', adding: 'adding', enter: 'enter', entering: 'entering', entered: 'entered', exit: 'exit', exiting: 'exiting', exited: 'exited', removed: 'removed' }; const Transition = props_ => { const theme = useAmauiTheme(); const props = React.useMemo(() => _objectSpread(_objectSpread(_objectSpread({}, theme?.ui?.elements?.all?.props?.default), theme?.ui?.elements?.amauiTransition?.props?.default), props_), [props_]); const { in: inProp_, name, prefix, run, append, add: add_, enter: enter_ = true, exit: exit_ = true, enterOnAdd = props.add || props.enter, exitOnAdd, noAbruption, removeOnExited, preEnterAppendTimeout = 14, delay: delay_, duration: duration_, // An all in one method onTransition, onInit, onAppended, onAdd, onAdding, onAdded, onEnter, onEntering, onEntered: onEntered_, onExit, onExiting, onExited, onRemoved, className, children, ref } = props; const [init, setInit] = React.useState(false); const [inProp, setInProp] = React.useState(inProp_); const [status, setStatus] = React.useState(() => { let statusNew = ''; if (inProp) { statusNew = add_ ? STATUS.add : STATUS.entered; if (enterOnAdd) statusNew = STATUS.enter; } else { statusNew = enterOnAdd || removeOnExited ? STATUS.removed : STATUS.exited; if (exitOnAdd) statusNew = STATUS.exit; } if (append) statusNew = 'appended'; return statusNew; }); const subs = React.useRef({ status: new AmauiSubscription() }); const refs = { root: React.useRef(undefined), status: React.useRef(undefined), inProp: React.useRef(undefined), prefix: React.useRef(undefined) }; refs.status.current = status; refs.inProp.current = inProp; refs.prefix.current = prefix; React.useEffect(() => { let statusNew; if (status === 'appended') { if (is('function', onTransition)) onTransition(refs.root.current, 'appended'); if (is('function', onAppended)) onAppended(refs.root.current); } if (inProp) { statusNew = add_ ? STATUS.add : STATUS.entered; if (enterOnAdd) statusNew = STATUS.enter; } else { statusNew = enterOnAdd || removeOnExited ? STATUS.removed : STATUS.exited; if (exitOnAdd) statusNew = STATUS.exit; } if (is('function', onInit)) onInit(refs.root.current); // Update update(statusNew); setInit(true); }, []); React.useEffect(() => { if (init) { let statusNew; if (inProp) { if ([STATUS.enter, STATUS.entered].indexOf(status) === -1) statusNew = STATUS.enter; } else { if ([STATUS.exit, STATUS.exited].indexOf(status) === -1) statusNew = STATUS.exit; } // Added const classNameValue = refs.root.current?.className?.baseVal || refs.root.current?.className; if (inProp && (!refs.root.current || classNameValue?.indexOf('removed') > -1)) { // We add the element and get the ref value // for update below to use it for enter update('appended'); // Prevent update batches setTimeout(() => update(statusNew), preEnterAppendTimeout); } else update(statusNew); } }, [inProp]); React.useEffect(() => { if (status === STATUS.exited && removeOnExited) { updateStatus('removed'); // Subscriptions subs.current.status.emit('removed'); } }, [status]); React.useEffect(() => { if (!inProp_ && noAbruption && ['enter', 'entering'].indexOf(subs.current.status.value) > -1) { const sub = item => { if (item === 'entered') { setInProp(inProp_); subs.current.status.unsubscribe(sub); } }; subs.current.status.subscribe(sub); } else if (inProp_ && noAbruption && ['exit', 'exiting'].indexOf(subs.current.status.value) > -1) { const sub = item => { if (item === 'exited') { setInProp(inProp_); subs.current.status.unsubscribe(sub); } }; subs.current.status.subscribe(sub); } else if (!inProp_ && noAbruption && subs.current.status.value === 'entered' || inProp_ && noAbruption && subs.current.status.value === 'exited' || inProp_ !== inProp) setInProp(inProp_); }, [inProp_]); const update = async (status_, pause) => { if (pause !== undefined) await wait(pause); refs.status.current = status_; switch (status_) { case 'add': return await add(status_); case 'enter': return await enter(status_); case 'exit': return await exit(status_); case 'exited': return updateStatus(status_); default: return updateStatus(status_); } }; const delay = React.useCallback(async status_ => { let value = delay_; if (is('string', value) && theme.transitions.duration[value] !== undefined) value = theme.transitions.duration[value]; if (is('object', value)) value = value[status_] !== undefined ? value[status_] : value.default; if (!(is('number', value) && value > 0)) return; await wait(value); }, [delay_, theme]); const duration = React.useCallback(async status_ => { let value = duration_; if (is('string', value) && theme.transitions.duration[value] !== undefined) value = theme.transitions.duration[value]; if (is('object', value)) value = value[status_] !== undefined ? value[status_] : value.default; if (!is('number', value)) value = theme.transitions.duration.rg; if (!(is('number', value) && value > 0)) return; await wait(value); }, [duration_, theme]); const add = async status_ => { if (!add_ || refs.status.current !== status_) return; updateStatus('add'); // Reflow reflow(refs.root.current); await delay('add'); // Prevent update batches await wait(14); updateStatus('adding'); // await duration('add'); if (refs.status.current === status_) updateStatus('added'); }; const enter = async status_ => { if (!enter_ || status_ !== 'appended' && (refs.status.current !== status_ || !refs.inProp.current)) return; updateStatus('enter'); // Reflow reflow(refs.root.current); await delay('enter'); // Prevent update batches await wait(14); updateStatus('entering'); await duration('enter'); if (refs.status.current?.indexOf('exit') === -1) updateStatus('entered'); }; const exit = async status_ => { if (!exit_ || refs.status.current !== status_ || refs.inProp.current) return; updateStatus('exit'); // Reflow reflow(refs.root.current); await delay('exit'); // Prevent update batches await wait(14); updateStatus('exiting'); await duration('exit'); if (refs.status.current?.indexOf('enter') === -1) updateStatus('exited'); }; const onEntered = element => { if (is('function', onEntered_)) onEntered_(element); if (run && inProp) setInProp(false); }; const updateStatus = status_ => { setStatus(status_); // Subscriptions subs.current.status.emit(status_); switch (status_) { case 'appended': if (is('function', onTransition)) onTransition(refs.root.current, status_); if (is('function', onAppended)) onAppended(refs.root.current); break; case 'add': if (is('function', onTransition)) onTransition(refs.root.current, status_); if (is('function', onAdd)) onAdd(refs.root.current); break; case 'adding': if (is('function', onTransition)) onTransition(refs.root.current, status_); if (is('function', onAdding)) onAdding(refs.root.current); break; case 'added': if (is('function', onTransition)) onTransition(refs.root.current, status_); if (is('function', onAdded)) onAdded(refs.root.current); break; case 'enter': if (is('function', onTransition)) onTransition(refs.root.current, status_); if (is('function', onEnter)) onEnter(refs.root.current); break; case 'entering': if (is('function', onTransition)) onTransition(refs.root.current, status_); if (is('function', onEntering)) onEntering(refs.root.current); break; case 'entered': if (is('function', onTransition)) onTransition(refs.root.current, status_); if (is('function', onEntered)) onEntered(refs.root.current); break; case 'exit': if (is('function', onTransition)) onTransition(refs.root.current, status_); if (is('function', onExit)) onExit(refs.root.current); break; case 'exiting': if (is('function', onTransition)) onTransition(refs.root.current, status_); if (is('function', onExiting)) onExiting(refs.root.current); break; case 'exited': if (is('function', onTransition)) onTransition(refs.root.current, status_); if (is('function', onExited)) onExited(refs.root.current); break; case 'removed': if (is('function', onTransition)) onTransition(refs.root.current, status_); if (is('function', onRemoved)) onRemoved(refs.root.current); break; default: break; } // Add className if (className && is('element', refs.root.current)) { let className_ = classNames([(refs.root.current.className?.baseVal || refs.root.current.className)?.split(' ')]); // Remove all previous classes className_ = className_.replace(new RegExp(`(^| )${refs.prefix.current || ''}(add|enter|exit)(ed|ing)?($| )`, 'g'), ' '); // Add className_ += ` ${refs.prefix.current || ''}${status_}`; className_ = className_.replace(/ +/g, ' ').trim(); if (refs.root.current.className?.baseVal) refs.root.current.className.baseVal = className_;else refs.root.current.className = className_; } }; if (status === 'removed') return null; const value_ = { status }; return /*#__PURE__*/React.createElement(TransitionContext.Provider, { value: value_ }, is('function', children) ? children(status, refs.root) : /*#__PURE__*/React.cloneElement(children, { ref: item => { if (ref) { if (is('function', ref)) ref(item);else ref.current = item; } refs.root.current = item; } })); }; Transition.displayName = 'amaui-Transition'; export default Transition;