diff --git a/package-lock.json b/package-lock.json index a2aaa3b51..12f0161d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,7 +37,8 @@ "react-is": "18.2.0", "spatial-navigation-polyfill": "https://github.com/Stremio/spatial-navigation.git#64871b1422466f5f45d24ebc8bbd315b2ebab6a6", "stremio-translations": "https://github.com/Stremio/stremio-translations.git#92675658de92113c5888cf5e57003e468e8b8c9c", - "url": "0.11.0" + "url": "0.11.0", + "use-long-press": "^3.1.5" }, "devDependencies": { "@babel/core": "7.16.0", @@ -13664,6 +13665,14 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, + "node_modules/use-long-press": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/use-long-press/-/use-long-press-3.1.5.tgz", + "integrity": "sha512-bnwk2SlvLLpeJPkNYSGkc59q5YNV9V/fLDkSOAF2p7Xt0zw3iYHEmgEGkNYkK7zEIEyRFi5CczKsT7MN99UzVQ==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/use-sidecar": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", @@ -25104,6 +25113,12 @@ } } }, + "use-long-press": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/use-long-press/-/use-long-press-3.1.5.tgz", + "integrity": "sha512-bnwk2SlvLLpeJPkNYSGkc59q5YNV9V/fLDkSOAF2p7Xt0zw3iYHEmgEGkNYkK7zEIEyRFi5CczKsT7MN99UzVQ==", + "requires": {} + }, "use-sidecar": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", diff --git a/package.json b/package.json index 771453a81..a6b2a9a28 100755 --- a/package.json +++ b/package.json @@ -40,7 +40,8 @@ "react-is": "18.2.0", "spatial-navigation-polyfill": "https://github.com/Stremio/spatial-navigation.git#64871b1422466f5f45d24ebc8bbd315b2ebab6a6", "stremio-translations": "https://github.com/Stremio/stremio-translations.git#92675658de92113c5888cf5e57003e468e8b8c9c", - "url": "0.11.0" + "url": "0.11.0", + "use-long-press": "^3.1.5" }, "devDependencies": { "@babel/core": "7.16.0", diff --git a/src/common/Button/Button.js b/src/common/Button/Button.js index 7235860a8..9d5ef7c1e 100644 --- a/src/common/Button/Button.js +++ b/src/common/Button/Button.js @@ -4,15 +4,20 @@ const React = require('react'); const PropTypes = require('prop-types'); const classnames = require('classnames'); const styles = require('./styles'); +const { useLongPress } = require('use-long-press'); -const Button = React.forwardRef(({ className, href, disabled, children, ...props }, ref) => { +const Button = React.forwardRef(({ className, href, disabled, children, onLongPress, ...props }, ref) => { + const longPress = useLongPress(onLongPress, { detect: 'pointer' }); const onKeyDown = React.useCallback((event) => { if (typeof props.onKeyDown === 'function') { props.onKeyDown(event); } - if (event.key === 'Enter' && !event.nativeEvent.buttonClickPrevented) { - event.currentTarget.click(); + if (event.key === 'Enter') { + event.preventDefault(); + if (!event.nativeEvent.buttonClickPrevented) { + event.currentTarget.click(); + } } }, [props.onKeyDown]); const onMouseDown = React.useCallback((event) => { @@ -36,7 +41,8 @@ const Button = React.forwardRef(({ className, href, disabled, children, ...props className: classnames(className, styles['button-container'], { 'disabled': disabled }), href, onKeyDown, - onMouseDown + onMouseDown, + ...longPress() }, children ); @@ -50,7 +56,8 @@ Button.propTypes = { disabled: PropTypes.bool, children: PropTypes.node, onKeyDown: PropTypes.func, - onMouseDown: PropTypes.func + onMouseDown: PropTypes.func, + onLongPress: PropTypes.func, }; module.exports = Button; diff --git a/src/common/Popup/Popup.js b/src/common/Popup/Popup.js index 7c694e6d5..aad02bdf3 100644 --- a/src/common/Popup/Popup.js +++ b/src/common/Popup/Popup.js @@ -47,16 +47,23 @@ const Popup = ({ open, direction, renderLabel, renderMenu, dataset, onCloseReque onCloseRequest(closeEvent); } break; + case 'pointerdown': + if (event.target !== document.documentElement && !labelRef.current.contains(event.target)) { + onCloseRequest(closeEvent); + } + break; } } }; if (routeFocused && open) { window.addEventListener('keydown', onCloseEvent); window.addEventListener('mousedown', onCloseEvent); + window.addEventListener('pointerdown', onCloseEvent); } return () => { window.removeEventListener('keydown', onCloseEvent); window.removeEventListener('mousedown', onCloseEvent); + window.removeEventListener('pointerdown', onCloseEvent); }; }, [routeFocused, open, onCloseRequest, dataset]); React.useLayoutEffect(() => { diff --git a/src/common/Popup/styles.less b/src/common/Popup/styles.less index d906f31ee..902fd550f 100644 --- a/src/common/Popup/styles.less +++ b/src/common/Popup/styles.less @@ -3,6 +3,10 @@ @import (reference) '~@stremio/stremio-colors/less/stremio-colors.less'; .label-container { + // IOS specific + // prevents showing the default context-menu when long pressing an anchor in safari. + -webkit-touch-callout: none !important; + position: relative; overflow: visible; diff --git a/src/routes/MetaDetails/VideosList/Video/Video.js b/src/routes/MetaDetails/VideosList/Video/Video.js index b36bb5aa8..6ce900537 100644 --- a/src/routes/MetaDetails/VideosList/Video/Video.js +++ b/src/routes/MetaDetails/VideosList/Video/Video.js @@ -3,6 +3,7 @@ const React = require('react'); const PropTypes = require('prop-types'); const classnames = require('classnames'); +const { t } = require('i18next'); const { useServices } = require('stremio/services'); const { useRouteFocused } = require('stremio-router'); const Icon = require('@stremio/stremio-icons/dom'); @@ -20,21 +21,31 @@ const Video = ({ className, id, title, thumbnail, episode, released, upcoming, w toggleMenu(); } }, []); - const popupLabelOnKeyDown = React.useCallback((event) => { - event.nativeEvent.buttonClickPrevented = true; - }, []); const popupLabelOnContextMenu = React.useCallback((event) => { if (!event.nativeEvent.togglePopupPrevented && !event.nativeEvent.ctrlKey) { event.preventDefault(); + if (event.nativeEvent.pointerType === 'mouse') { + toggleMenu(); + } + } + }, [toggleMenu]); + const popupLabelOnLongPress = React.useCallback((event) => { + if (event.nativeEvent.pointerType !== 'mouse' && !event.nativeEvent.togglePopupPrevented) { toggleMenu(); } }, [toggleMenu]); + const popupMenuOnPointerDown = React.useCallback((event) => { + event.nativeEvent.togglePopupPrevented = true; + }, []); const popupMenuOnContextMenu = React.useCallback((event) => { event.nativeEvent.togglePopupPrevented = true; }, []); const popupMenuOnClick = React.useCallback((event) => { event.nativeEvent.togglePopupPrevented = true; }, []); + const popupMenuOnKeyDown = React.useCallback((event) => { + event.nativeEvent.buttonClickPrevented = true; + }, []); const toggleWatchedOnClick = React.useCallback((event) => { event.preventDefault(); closeMenu(); @@ -132,12 +143,12 @@ const Video = ({ className, id, title, thumbnail, episode, released, upcoming, w }, []); const renderMenu = React.useMemo(() => function renderMenu() { return ( -