@amaui/ui-react
Version:
UI for React
403 lines (400 loc) • 14.9 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, getID, debounce, isEnvironment } from '@amaui/utils';
import { classNames, style as styleMethod, useAmauiTheme } from '@amaui/style-react';
import { staticClassName, Transition, Transitions, useMediaQuery } from '..';
const other = {
pointerEvents: 'none',
borderRadius: 'inherit',
position: 'absolute',
inset: '0',
width: '100%',
height: '100%'
};
const useStyle = styleMethod(theme => ({
'@keyframes pulse': {
'0%': {
transform: 'scale(0.77)'
},
'50%': {
transform: 'scale(0.7)'
},
'100%': {
transform: 'scale(0.77)'
}
},
root: _objectSpread(_objectSpread({}, other), {}, {
overflow: 'hidden',
// Bug fix for overflow in mobile Safari
zIndex: '0'
}),
background: _objectSpread(_objectSpread({}, other), {}, {
opacity: theme.palette.visual_contrast.default.opacity.hover,
transition: theme.methods.transitions.make(['opacity', 'background'], {
duration: 'rg',
timing_function: 'standard'
})
}),
hovered: {
background: 'currentColor'
},
selected: {
opacity: theme.palette.visual_contrast.default.opacity.selected,
background: 'currentColor'
},
dragged: {
opacity: theme.palette.visual_contrast.default.opacity.drag,
background: 'currentColor'
},
border: _objectSpread(_objectSpread({}, other), {}, {
opacity: '0',
boxShadow: 'inset 0 0 0 2px currentColor'
}),
wave: _objectSpread(_objectSpread({}, other), {}, {
inset: 'unset',
opacity: '0.1',
transform: 'scale(0)',
backgroundColor: 'currentColor',
transition: theme.methods.transitions.make(['opacity', 'transform'], {
duration: 'complex',
timing_function: 'standard'
}),
borderRadius: theme.methods.shape.radius.value(4e4, 'px'),
'&.entering': {
opacity: theme.palette.visual_contrast.default.opacity.quaternary,
transform: 'scale(1)'
},
'&.entered': {
opacity: theme.palette.visual_contrast.default.opacity.quaternary,
transform: 'scale(1)'
},
'&.exit': {
opacity: theme.palette.visual_contrast.default.opacity.quaternary,
transform: 'scale(1)'
},
'&.exiting': {
opacity: '0',
transform: 'scale(1)'
},
'&.exited': {
opacity: '0',
transform: 'scale(1)'
}
}),
pulse: {
'&.entering': {
opacity: theme.palette.visual_contrast.default.opacity.quaternary,
transform: 'scale(0.77)'
},
'&.entered': {
opacity: theme.palette.visual_contrast.default.opacity.quaternary,
transform: 'scale(0.77)',
animation: `$pulse 2400ms ${theme.transitions.timing_function.emphasized} 240ms infinite`
},
'&.exit': {
opacity: theme.palette.visual_contrast.default.opacity.quaternary,
transform: 'scale(0.7)'
},
'&.exiting': {
opacity: '0',
transform: 'scale(0.7)'
},
'&.exited': {
opacity: '0',
transform: 'scale(0.7)'
}
},
waveSimple: _objectSpread(_objectSpread({}, other), {}, {
opacity: '0.1',
backgroundColor: 'currentcolor',
transition: theme.methods.transitions.make(['opacity'], {
duration: 'complex',
timing_function: 'standard'
}),
'&.entering': {
opacity: theme.palette.visual_contrast.default.opacity.quaternary
},
'&.entered': {
opacity: theme.palette.visual_contrast.default.opacity.quaternary
},
'&.exit': {
opacity: theme.palette.visual_contrast.default.opacity.quaternary
},
'&.exiting': {
opacity: '0'
},
'&.exited': {
opacity: '0'
}
})
}), {
name: 'amaui-Interaction'
});
const Interaction = /*#__PURE__*/React.forwardRef((props_, ref) => {
const theme = useAmauiTheme();
const props = React.useMemo(() => _objectSpread(_objectSpread(_objectSpread({}, theme?.ui?.elements?.all?.props?.default), theme?.ui?.elements?.amauiInteraction?.props?.default), props_), [props_]);
const {
wave = true,
background = true,
border: border_,
pulse,
origin: origin_,
preselected,
selected,
dragged,
wave_version,
subscription,
clear,
disabled,
className
} = props;
const {
classes
} = useStyle();
const [init, setInit] = React.useState(false);
const [interactions, setInteractions] = React.useState([]);
const [border, setBorder] = React.useState(false);
const [waves, setWaves] = React.useState([]);
const refs = {
root: React.useRef(undefined),
mouse: React.useRef({
down: 0,
up: 0,
press: 0
}),
wave: React.useRef(undefined),
pulse: React.useRef(undefined),
touch: React.useRef(undefined),
interactions: React.useRef(undefined),
props: React.useRef(undefined)
};
const touch = useMediaQuery('(pointer: coarse)', {
element: refs.root.current
});
refs.props.current = props;
refs.wave.current = wave;
refs.pulse.current = pulse;
refs.touch.current = touch;
refs.interactions.current = interactions;
React.useEffect(() => {
const parent = refs.root.current.parentNode;
const onMouseIn = event => {
if (refs.touch.current) return;
add('mouse-in');
removeWaves();
};
const onMouseOut = event => {
if (refs.touch.current) return;
refs.mouse.current.up = new Date().getTime();
refs.mouse.current.press = refs.mouse.current.down ? Math.round(refs.mouse.current.up - refs.mouse.current.down) : 0;
remove('mouse-in');
removeWaves();
};
const onMouseDown = event => {
refs.mouse.current.down = new Date().getTime();
refs.mouse.current.up = 0;
refs.mouse.current.press = 0;
add('mouse-down');
addWave(event);
};
const updateBorder = debounce(() => setBorder(false), theme.transitions.duration.sm);
const onMouseUp = () => {
refs.mouse.current.up = new Date().getTime();
refs.mouse.current.press = refs.mouse.current.down ? Math.round(refs.mouse.current.up - refs.mouse.current.down) : 0;
setInteractions(items => {
if (items.indexOf('mouse-down') > -1) {
// Border
setBorder(true);
updateBorder();
}
return items;
});
remove('mouse-down');
removeWaves();
};
const rootDocument = isEnvironment('browser') ? refs.root.current?.ownerDocument || window.document : undefined;
if (parent) {
parent.addEventListener('mousedown', onMouseDown);
parent.addEventListener('touchstart', onMouseDown, {
passive: true
});
parent.addEventListener('mouseup', onMouseUp);
rootDocument.addEventListener('mouseup', onMouseUp);
parent.addEventListener('touchend', onMouseUp, {
passive: true
});
rootDocument.addEventListener('touchend', onMouseUp, {
passive: true
});
parent.addEventListener('mouseenter', onMouseIn);
// parent.addEventListener('touchstart', onMouseIn, { passive: true });
parent.addEventListener('mouseleave', onMouseOut);
// parent.addEventListener('touchend', onMouseOut, { passive: true });
}
setInit(true);
return () => {
if (parent) {
parent.removeEventListener('mousedown', onMouseDown);
parent.removeEventListener('touchstart', onMouseDown);
parent.removeEventListener('mouseup', onMouseUp);
rootDocument.removeEventListener('mouseup', onMouseUp);
parent.removeEventListener('touchend', onMouseUp);
rootDocument.removeEventListener('touchend', onMouseUp);
parent.removeEventListener('mouseenter', onMouseIn);
parent.removeEventListener('touchstart', onMouseIn);
parent.removeEventListener('mouseleave', onMouseOut);
parent.removeEventListener('touchend', onMouseOut);
}
};
}, []);
React.useEffect(() => {
if (init) {
setInteractions([]);
removeWaves();
}
}, [clear]);
React.useEffect(() => {
if (pulse) {
if (!refs.props.current.pulseOnMouseDown && has('mouse-down')) return;
addWavePulse();
} else removeWaves();
}, [pulse]);
const methods = React.useCallback(version => {
if (version === 'add') addWave(undefined, {
origin: 'center'
});else if (version === 'pulse') addWavePulse();else if (version === 'remove') removeWaves();
}, []);
React.useEffect(() => {
let subscribed;
if (subscription?.subscribe) subscribed = subscription.subscribe(methods);
// Clean up
return () => {
if (subscribed?.unsubscribe) subscribed.unsubscribe();
};
}, [subscription]);
React.useEffect(() => {
if (disabled) setInteractions([]);
}, [disabled]);
const addWave = React.useCallback(function (event) {
let overrides = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const origin = overrides.origin !== undefined ? overrides.origin : origin_;
if (refs.wave.current && !refs.props.current.disabled) {
let top = 0;
let left = 0;
let width = '100%';
if (wave_version !== 'simple') {
const rect = event?.currentTarget?.getBoundingClientRect();
const root = (event?.currentTarget || refs.root.current.parentNode)?.getBoundingClientRect();
// Mouse or touch event
const valuesEvent = !event?.touches ? event : event.touches[0];
const values = {
x: valuesEvent.x !== undefined ? valuesEvent.x : valuesEvent.clientX,
y: valuesEvent.y !== undefined ? valuesEvent.y : valuesEvent.clientY
};
values.offsetX = valuesEvent.offsetX !== undefined ? valuesEvent.offsetX : values.x - rect.x;
values.offsetY = valuesEvent.offsetY !== undefined ? valuesEvent.offsetY : values.y - rect.y;
const w = root.width;
const h = root.height;
const x = origin === 'center' || !event ? w / 2 : rect ? values.x - rect.left : values.offsetX;
const y = origin === 'center' || !event ? h / 2 : rect ? values.y - rect.top : values.offsetY;
width = Math.round(Math.sqrt(
// Largest sub rectangle in value
Math.max(x ** 2 + y ** 2, Math.abs(w - x) ** 2 + y ** 2, x ** 2 + Math.abs(h - y) ** 2, Math.abs(w - x) ** 2 + Math.abs(h - y) ** 2)) * 2);
top = y - width / 2;
left = x - width / 2;
}
setWaves(items => [...items, /*#__PURE__*/React.createElement(Transition, {
key: getID(),
duration: "complex",
enterOnAdd: true,
className: true
}, /*#__PURE__*/React.createElement("span", {
className: wave_version === 'simple' ? classes.waveSimple : classes.wave,
style: {
top: `${top}px`,
left: `${left}px`,
width: `${width}px`,
height: `${width}px`
}
}))]);
}
}, []);
const addWavePulse = React.useCallback(() => {
if (refs.pulse.current && !refs.props.current.disabled) {
// Remove previous wave
// if there is one
if (waves.length) removeWaves();
const root = refs.root.current.parentNode.getBoundingClientRect();
const w = root.width;
const h = root.height;
const x = w / 2;
const y = h / 2;
const width = Math.round(Math.sqrt(x ** 2 + y ** 2) * 2);
const top = y - width / 2;
const left = x - width / 2;
setWaves(items => [...items, /*#__PURE__*/React.createElement(Transition, {
key: getID(),
duration: "complex",
enterOnAdd: true,
className: true
}, /*#__PURE__*/React.createElement("span", {
className: classNames([classes.wave, classes.pulse]),
style: {
top: `${top}px`,
left: `${left}px`,
width: `${width}px`,
height: `${width}px`
}
}))]);
}
}, []);
const removeWaves = React.useCallback(() => setWaves([]), []);
const add = React.useCallback(value => {
if (!refs.props.current.disabled) {
setInteractions(items => {
const newItems = [...items];
if (newItems.indexOf(value) === -1) newItems.push(value);
return newItems;
});
}
}, []);
const has = React.useCallback(value => refs.interactions.current.includes(value), []);
const remove = React.useCallback(value => setInteractions(items => [...items].filter(item => item !== value)), []);
return /*#__PURE__*/React.createElement("span", {
ref: item => {
if (ref) {
if (is('function', ref)) ref(item);else ref.current = item;
}
refs.root.current = item;
},
className: classNames([staticClassName('Interaction', theme) && ['amaui-Interaction-root', disabled && `amaui-Interaction-disabled`], className, classes.root])
}, background && /*#__PURE__*/React.createElement("span", {
className: classNames([staticClassName('Interaction', theme) && ['amaui-Interaction-background'], classes.background, (preselected || has('mouse-in')) && classes.hovered, selected && classes.selected, dragged && classes.dragged])
}), /*#__PURE__*/React.createElement(Transitions, {
TransitionProps: {
noAbruption: true,
duration: {
enter: 'complex',
exit: refs.mouse.current.press < 500 ? 350 : 500
},
style: {
transitionDuration: refs.mouse.current.press < 500 ? 340 : 500
}
}
}, waves), border_ && /*#__PURE__*/React.createElement(Transition, {
in: border
}, status => /*#__PURE__*/React.createElement("span", {
className: classNames([staticClassName('Interaction', theme) && ['amaui-Interaction-border'], classes.border]),
style: {
opacity: status?.indexOf('enter') > -1 ? theme.palette.visual_contrast.default.opacity.quaternary : 0,
transition: status?.indexOf('exit') > -1 ? theme.methods.transitions.make('opacity', {
duration: 'complex',
timing_function: 'standard'
}) : undefined
}
})));
});
Interaction.displayName = 'amaui-Interaction';
export default Interaction;