@amaui/ui-react
Version:
UI for React
315 lines (299 loc) • 11.6 kB
JavaScript
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;