UNPKG

@amaui/ui-react

Version:
403 lines (400 loc) 14.9 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, 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;