From 14a1bca23dfb73f4eec54250627d38d90847008a Mon Sep 17 00:00:00 2001 From: Hristo Terezov Date: Wed, 5 Nov 2025 13:27:42 -0600 Subject: [PATCH] feat(draggable-panels): Enable touch-screen support Adds touch-screen support for resizing filmstrip and chat panels to enable tablet and touch-laptop users to adjust panel widths. Previously, drag handles only worked with mouse hover, making panels non-resizable on touch devices. Changes: - Implement Pointer Events API for unified mouse/touch handling - Add touch device detection with screen size threshold - Make drag handles always visible on touch devices with padding for easier tapping - Maintain identical visual layout between touch and non-touch versions Touch devices with sufficiently large screens now have fully functional drag handles with appropriate hit targets while smaller devices remain disabled to preserve mobile UX. --- globals.native.d.ts | 3 + react/features/base/environment/utils.ts | 49 ++++++ react/features/chat/components/web/Chat.tsx | 101 ++++++++---- react/features/chat/constants.ts | 17 ++ .../filmstrip/components/web/Filmstrip.tsx | 148 +++++++++++++----- react/features/filmstrip/constants.ts | 19 +++ react/features/filmstrip/functions.web.ts | 16 +- 7 files changed, 281 insertions(+), 72 deletions(-) diff --git a/globals.native.d.ts b/globals.native.d.ts index 8e7d1bcff2..c8096264e2 100644 --- a/globals.native.d.ts +++ b/globals.native.d.ts @@ -25,6 +25,8 @@ interface IWindow { self: any; top: any; + matchMedia: (query: string) => { matches: boolean; }; + onerror: (event: string, source: any, lineno: any, colno: any, e: Error) => void; onunhandledrejection: (event: any) => void; @@ -41,6 +43,7 @@ interface IWindow { interface INavigator { product: string; userAgent: string; + maxTouchPoints: number; } declare global { diff --git a/react/features/base/environment/utils.ts b/react/features/base/environment/utils.ts index 6314180571..8bd156280e 100644 --- a/react/features/base/environment/utils.ts +++ b/react/features/base/environment/utils.ts @@ -1,3 +1,4 @@ +import { MIN_FILMSTRIP_RESIZE_WIDTH } from '../../filmstrip/constants'; import Platform from '../react/Platform'; /** @@ -30,3 +31,51 @@ export function isIpadMobileBrowser() { return isIosMobileBrowser() && Platform.isPad; } +/** + * Detects if the current device has touch capability. + * This includes smartphones, tablets, and laptops with touch screens. + * + * @returns {boolean} True if the device supports touch events. + */ +export function isTouchDevice(): boolean { + // Check for touch support using multiple methods for better reliability + if (typeof window === 'undefined') { + return false; + } + + // Check maxTouchPoints (most reliable for modern browsers) + if ('maxTouchPoints' in navigator) { + return navigator.maxTouchPoints > 0; + } + + // Fallback to older touch events check + if ('ontouchstart' in window) { + return true; + } + + // Check for touch-specific media query + if (window.matchMedia && window.matchMedia('(pointer: coarse)').matches) { + return true; + } + + return false; +} + +/** + * Determines if resize functionality should be enabled based on device capabilities + * and screen size. On touch devices, resize is only enabled for larger screens (tablets+). + * On non-touch devices (desktop), resize is always enabled. + * + * @returns {boolean} True if resize functionality should be available to the user. + */ +export function shouldEnableResize(): boolean { + const hasTouch = isTouchDevice(); + + // On non-touch devices (desktop), always enable resize + if (!hasTouch) { + return true; + } + + // On touch devices, only enable if screen is large enough (tablets, not phones) + return window?.innerWidth >= MIN_FILMSTRIP_RESIZE_WIDTH; +} diff --git a/react/features/chat/components/web/Chat.tsx b/react/features/chat/components/web/Chat.tsx index 821a5e9a8d..ca844ba03c 100644 --- a/react/features/chat/components/web/Chat.tsx +++ b/react/features/chat/components/web/Chat.tsx @@ -4,6 +4,7 @@ import { connect, useSelector } from 'react-redux'; import { makeStyles } from 'tss-react/mui'; import { IReduxState } from '../../../app/types'; +import { isTouchDevice, shouldEnableResize } from '../../../base/environment/utils'; import { translate } from '../../../base/i18n/functions'; import { IconInfo, IconMessage, IconShareDoc, IconSubtitles } from '../../../base/icons/svg'; import { getLocalParticipant, getRemoteParticipants, isPrivateChatEnabledSelf } from '../../../base/participants/functions'; @@ -23,7 +24,16 @@ import { setUserChatWidth, toggleChat } from '../../actions.web'; -import { CHAT_SIZE, ChatTabs, OPTION_GROUPCHAT, SMALL_WIDTH_THRESHOLD } from '../../constants'; +import { + CHAT_DRAG_HANDLE_HEIGHT, + CHAT_DRAG_HANDLE_OFFSET, + CHAT_DRAG_HANDLE_WIDTH, + CHAT_SIZE, + CHAT_TOUCH_HANDLE_SIZE, + ChatTabs, + OPTION_GROUPCHAT, + SMALL_WIDTH_THRESHOLD +} from '../../constants'; import { getChatMaxSize, getFocusedTab, isChatDisabled } from '../../functions'; import { IChatProps as AbstractProps } from '../../types'; @@ -99,7 +109,12 @@ interface IProps extends AbstractProps { _width: number; } -const useStyles = makeStyles<{ _isResizing: boolean; width: number; }>()((theme, { _isResizing, width }) => { +const useStyles = makeStyles<{ + _isResizing: boolean; + isTouch: boolean; + resizeEnabled: boolean; + width: number; +}>()((theme, { _isResizing, isTouch, resizeEnabled, width }) => { return { container: { backgroundColor: theme.palette.ui01, @@ -110,11 +125,15 @@ const useStyles = makeStyles<{ _isResizing: boolean; width: number; }>()((theme, width: `${width}px`, zIndex: 300, - '&:hover, &:focus-within': { - '& .dragHandleContainer': { - visibility: 'visible' + // On non-touch devices (desktop), show handle on hover + // On touch devices, handle is always visible if resize is enabled + ...(!isTouch && { + '&:hover, &:focus-within': { + '& .dragHandleContainer': { + visibility: 'visible' + } } - }, + }), '@media (max-width: 580px)': { height: '100dvh', @@ -178,16 +197,23 @@ const useStyles = makeStyles<{ _isResizing: boolean; width: number; }>()((theme, dragHandleContainer: { height: '100%', - width: '9px', + // Touch devices need larger hit target but positioned to not take extra space + width: isTouch ? `${CHAT_TOUCH_HANDLE_SIZE}px` : `${CHAT_DRAG_HANDLE_WIDTH}px`, backgroundColor: 'transparent', position: 'absolute', cursor: 'col-resize', - display: 'flex', + display: resizeEnabled ? 'flex' : 'none', // Hide if resize not enabled alignItems: 'center', justifyContent: 'center', - visibility: 'hidden', - right: '4px', + // On touch devices, always visible if resize enabled. On desktop, hidden by default + visibility: (isTouch && resizeEnabled) ? 'visible' : 'hidden', + // Position touch handle centered on offset from edge, maintaining same gap as non-touch + right: isTouch + ? `${CHAT_DRAG_HANDLE_OFFSET - Math.floor((CHAT_TOUCH_HANDLE_SIZE - CHAT_DRAG_HANDLE_WIDTH) / 2)}px` + : `${CHAT_DRAG_HANDLE_OFFSET}px`, top: 0, + // Prevent touch scrolling while dragging + touchAction: 'none', '&:hover': { '& .dragHandle': { @@ -205,10 +231,15 @@ const useStyles = makeStyles<{ _isResizing: boolean; width: number; }>()((theme, }, dragHandle: { + // Keep the same visual appearance on all devices backgroundColor: theme.palette.icon02, - height: '100px', - width: '3px', - borderRadius: '1px' + height: `${CHAT_DRAG_HANDLE_HEIGHT}px`, + width: `${CHAT_DRAG_HANDLE_WIDTH / 3}px`, + borderRadius: '1px', + // Make more visible when actively shown + ...(isTouch && resizeEnabled && { + backgroundColor: theme.palette.icon01 + }) }, privateMessageRecipientsList: { @@ -240,7 +271,10 @@ const Chat = ({ return null; } - const { classes, cx } = useStyles({ _isResizing, width: _width }); + // Detect touch capability and screen size for resize functionality + const isTouch = isTouchDevice(); + const resizeEnabled = shouldEnableResize(); + const { classes, cx } = useStyles({ _isResizing, width: _width, isTouch, resizeEnabled }); const [ isMouseDown, setIsMouseDown ] = useState(false); const [ mousePosition, setMousePosition ] = useState(null); const [ dragChatWidth, setDragChatWidth ] = useState(null); @@ -276,16 +310,21 @@ const Chat = ({ }, [ participants, defaultRemoteDisplayName, t, notifyTimestamp ]); /** - * Handles mouse down on the drag handle. + * Handles pointer down on the drag handle. + * Supports both mouse and touch events via Pointer Events API. * - * @param {MouseEvent} e - The mouse down event. + * @param {React.PointerEvent} e - The pointer down event. * @returns {void} */ - const onDragHandleMouseDown = useCallback((e: React.MouseEvent) => { + const onDragHandlePointerDown = useCallback((e: React.PointerEvent) => { e.preventDefault(); e.stopPropagation(); - // Store the initial mouse position and chat width + // Capture the pointer to ensure we receive all pointer events + // even if the pointer moves outside the element + (e.target as HTMLElement).setPointerCapture(e.pointerId); + + // Store the initial pointer position and chat width setIsMouseDown(true); setMousePosition(e.clientX); setDragChatWidth(_width); @@ -293,7 +332,7 @@ const Chat = ({ // Indicate that resizing is in progress dispatch(setChatIsResizing(true)); - // Add visual feedback that we're dragging + // Add visual feedback that we're dragging (cursor for mouse, not visible on touch) document.body.style.cursor = 'col-resize'; // Disable text selection during resize @@ -301,11 +340,12 @@ const Chat = ({ }, [ _width, dispatch ]); /** - * Drag handle mouse up handler. + * Drag handle pointer up handler. + * Supports both mouse and touch events via Pointer Events API. * * @returns {void} */ - const onDragMouseUp = useCallback(() => { + const onDragPointerUp = useCallback(() => { if (isMouseDown) { setIsMouseDown(false); dispatch(setChatIsResizing(false)); @@ -317,12 +357,13 @@ const Chat = ({ }, [ isMouseDown, dispatch ]); /** - * Handles drag handle mouse move. + * Handles drag handle pointer move. + * Supports both mouse and touch events via Pointer Events API. * - * @param {MouseEvent} e - The mousemove event. + * @param {PointerEvent} e - The pointermove event. * @returns {void} */ - const onChatResize = useCallback(throttle((e: MouseEvent) => { + const onChatResize = useCallback(throttle((e: PointerEvent) => { if (isMouseDown && mousePosition !== null && dragChatWidth !== null) { // For chat panel resizing on the left edge: // - Dragging left (decreasing X coordinate) should make the panel wider @@ -346,14 +387,14 @@ const Chat = ({ // Set up event listeners when component mounts useEffect(() => { - document.addEventListener('mouseup', onDragMouseUp); - document.addEventListener('mousemove', onChatResize); + document.addEventListener('pointerup', onDragPointerUp); + document.addEventListener('pointermove', onChatResize); return () => { - document.removeEventListener('mouseup', onDragMouseUp); - document.removeEventListener('mousemove', onChatResize); + document.removeEventListener('pointerup', onDragPointerUp); + document.removeEventListener('pointermove', onChatResize); }; - }, [ onDragMouseUp, onChatResize ]); + }, [ onDragPointerUp, onChatResize ]); /** * Sends a text message. @@ -590,7 +631,7 @@ const Chat = ({ (isMouseDown || _isResizing) && 'visible', 'dragHandleContainer' ) } - onMouseDown = { onDragHandleMouseDown }> + onPointerDown = { onDragHandlePointerDown }>
: null diff --git a/react/features/chat/constants.ts b/react/features/chat/constants.ts index f1285c0b1c..f222267531 100644 --- a/react/features/chat/constants.ts +++ b/react/features/chat/constants.ts @@ -33,6 +33,23 @@ export const MESSAGE_TYPE_REMOTE = 'remote'; export const SMALL_WIDTH_THRESHOLD = 580; +/** + * Drag handle dimensions for resizable chat. + */ +export const CHAT_DRAG_HANDLE_WIDTH = 9; +export const CHAT_DRAG_HANDLE_HEIGHT = 100; + +/** + * Touch target size for chat drag handle on touch devices. + * Provides adequate hit area (44px) for comfortable tapping. + */ +export const CHAT_TOUCH_HANDLE_SIZE = 44; + +/** + * Offset from edge for positioning the chat drag handle. + */ +export const CHAT_DRAG_HANDLE_OFFSET = 4; + /** * Lobby message type. diff --git a/react/features/filmstrip/components/web/Filmstrip.tsx b/react/features/filmstrip/components/web/Filmstrip.tsx index e0474f1a35..002df7a6e9 100644 --- a/react/features/filmstrip/components/web/Filmstrip.tsx +++ b/react/features/filmstrip/components/web/Filmstrip.tsx @@ -10,7 +10,7 @@ import { withStyles } from 'tss-react/mui'; import { ACTION_SHORTCUT_TRIGGERED, createShortcutEvent, createToolbarEvent } from '../../../analytics/AnalyticsEvents'; import { sendAnalytics } from '../../../analytics/functions'; import { IReduxState, IStore } from '../../../app/types'; -import { isMobileBrowser } from '../../../base/environment/utils'; +import { isMobileBrowser, isTouchDevice, shouldEnableResize } from '../../../base/environment/utils'; import { translate } from '../../../base/i18n/functions'; import Icon from '../../../base/icons/components/Icon'; import { IconArrowDown, IconArrowUp } from '../../../base/icons/svg'; @@ -32,12 +32,17 @@ import { import { ASPECT_RATIO_BREAKPOINT, DEFAULT_FILMSTRIP_WIDTH, + DRAG_HANDLE_HEIGHT, + DRAG_HANDLE_TOP_PANEL_HEIGHT, + DRAG_HANDLE_TOP_PANEL_WIDTH, + DRAG_HANDLE_WIDTH, FILMSTRIP_TYPE, MIN_STAGE_VIEW_HEIGHT, MIN_STAGE_VIEW_WIDTH, TILE_HORIZONTAL_MARGIN, TILE_VERTICAL_MARGIN, - TOP_FILMSTRIP_HEIGHT + TOP_FILMSTRIP_HEIGHT, + TOUCH_DRAG_HANDLE_PADDING } from '../../constants'; import { getVerticalViewMaxWidth, @@ -61,6 +66,11 @@ const BACKGROUND_COLOR = 'rgba(51, 51, 51, .5)'; * @returns {Object} */ function styles(theme: Theme, props: IProps) { + const { _topPanelFilmstrip: isTopPanel } = props; + + const _isTouchDevice = isTouchDevice(); + const resizeEnabled = shouldEnableResize(); + const result = { toggleFilmstripContainer: { display: 'flex', @@ -122,23 +132,27 @@ function styles(theme: Theme, props: IProps) { right: 0, bottom: 0, - '&:hover, &:focus-within': { - '& .resizable-filmstrip': { - backgroundColor: BACKGROUND_COLOR - }, + // On touch devices, handle is always visible via base styles, so no hover needed. + // On desktop, show handle on hover/focus. + ...(!_isTouchDevice && { + '&:hover, &:focus-within': { + '& .resizable-filmstrip': { + backgroundColor: BACKGROUND_COLOR + }, - '& .filmstrip-hover': { - backgroundColor: BACKGROUND_COLOR - }, + '& .filmstrip-hover': { + backgroundColor: BACKGROUND_COLOR + }, - '& .toggleFilmstripContainer': { - opacity: 1 - }, + '& .toggleFilmstripContainer': { + opacity: 1 + }, - '& .dragHandleContainer': { - visibility: 'visible' as const + '& .dragHandleContainer': { + visibility: 'visible' as const + } } - }, + }), '.horizontal-filmstrip &.hidden': { bottom: '-50px', @@ -187,14 +201,25 @@ function styles(theme: Theme, props: IProps) { dragHandleContainer: { height: '100%', - width: '9px', + width: `${DRAG_HANDLE_WIDTH}px`, backgroundColor: 'transparent', position: 'relative' as const, cursor: 'col-resize', display: 'flex', alignItems: 'center', justifyContent: 'center', - visibility: 'hidden' as const, + // On touch devices, always visible if resize enabled. On desktop, hidden by default + visibility: (_isTouchDevice && resizeEnabled) ? 'visible' as const : 'hidden' as const, + marginLeft: 0, + marginTop: 0, + // Touch devices get padding for easier tapping + // Vertical filmstrip: left/right padding. Top panel: top/bottom padding. + paddingLeft: _isTouchDevice && !isTopPanel ? `${TOUCH_DRAG_HANDLE_PADDING}px` : 0, + paddingRight: _isTouchDevice && !isTopPanel ? `${TOUCH_DRAG_HANDLE_PADDING}px` : 0, + paddingTop: _isTouchDevice && isTopPanel ? `${TOUCH_DRAG_HANDLE_PADDING}px` : 0, + paddingBottom: _isTouchDevice && isTopPanel ? `${TOUCH_DRAG_HANDLE_PADDING}px` : 0, + // Prevent touch scrolling while dragging + touchAction: 'none', '&:hover': { '& .dragHandle': { @@ -213,20 +238,21 @@ function styles(theme: Theme, props: IProps) { '&.top-panel': { order: 2, width: '100%', - height: '9px', + height: `${DRAG_HANDLE_WIDTH}px`, cursor: 'row-resize', '& .dragHandle': { - height: '3px', - width: '100px' + height: `${DRAG_HANDLE_TOP_PANEL_HEIGHT}px`, + width: `${DRAG_HANDLE_TOP_PANEL_WIDTH}px` } } }, dragHandle: { + // Keep the same visual appearance on all devices backgroundColor: theme.palette.icon02, - height: '100px', - width: '3px', + height: `${DRAG_HANDLE_HEIGHT}px`, + width: `${DRAG_HANDLE_WIDTH / 3}px`, borderRadius: '1px' } }; @@ -313,6 +339,11 @@ export interface IProps extends WithTranslation { */ _isToolboxVisible: Boolean; + /** + * Whether the device has touch capability. + */ + _isTouchDevice?: boolean; + /** * Whether or not the current layout is vertical filmstrip. */ @@ -353,6 +384,11 @@ export interface IProps extends WithTranslation { */ _resizableFilmstrip: boolean; + /** + * Whether resize functionality should be enabled based on device and screen size. + */ + _resizeEnabled?: boolean; + /** * The number of rows in tile view. */ @@ -486,8 +522,10 @@ class Filmstrip extends PureComponent { this._onGridItemsRendered = this._onGridItemsRendered.bind(this); this._onListItemsRendered = this._onListItemsRendered.bind(this); this._onToggleButtonTouch = this._onToggleButtonTouch.bind(this); - this._onDragHandleMouseDown = this._onDragHandleMouseDown.bind(this); - this._onDragMouseUp = this._onDragMouseUp.bind(this); + this._onDragHandlePointerDown = this._onDragHandlePointerDown.bind(this); + this._onDragHandleClick = this._onDragHandleClick.bind(this); + this._onDragHandleTouchStart = this._onDragHandleTouchStart.bind(this); + this._onDragPointerUp = this._onDragPointerUp.bind(this); this._onFilmstripResize = this._onFilmstripResize.bind(this); this._throttledResize = throttle( @@ -511,10 +549,10 @@ class Filmstrip extends PureComponent { handler: this._onShortcutToggleFilmstrip })); - document.addEventListener('mouseup', this._onDragMouseUp); + document.addEventListener('pointerup', this._onDragPointerUp); // @ts-ignore - document.addEventListener('mousemove', this._throttledResize); + document.addEventListener('pointermove', this._throttledResize); } /** @@ -525,10 +563,10 @@ class Filmstrip extends PureComponent { override componentWillUnmount() { this.props.dispatch(unregisterShortcut('F')); - document.removeEventListener('mouseup', this._onDragMouseUp); + document.removeEventListener('pointerup', this._onDragPointerUp); // @ts-ignore - document.removeEventListener('mousemove', this._throttledResize); + document.removeEventListener('pointermove', this._throttledResize); } /** @@ -665,7 +703,9 @@ class Filmstrip extends PureComponent { (isMouseDown || _alwaysShowResizeBar) && 'visible', _topPanelFilmstrip && 'top-panel') } - onMouseDown = { this._onDragHandleMouseDown }> + onClick = { this._onDragHandleClick } + onPointerDown = { this._onDragHandlePointerDown } + onTouchStart = { this._onDragHandleTouchStart }>
{filmstrip} @@ -678,14 +718,23 @@ class Filmstrip extends PureComponent { } /** - * Handles mouse down on the drag handle. + * Handles pointer down on the drag handle. + * Supports both mouse and touch events via Pointer Events API. * - * @param {MouseEvent} e - The mouse down event. + * @param {React.PointerEvent} e - The pointer down event. * @returns {void} */ - _onDragHandleMouseDown(e: React.MouseEvent) { + _onDragHandlePointerDown(e: React.PointerEvent) { const { _topPanelFilmstrip, _topPanelHeight, _verticalFilmstripWidth } = this.props; + // Prevent toolbar from appearing and stop event propagation + e.preventDefault(); + e.stopPropagation(); + + // Capture the pointer to ensure we receive all pointer events + // even if the pointer moves outside the element + (e.target as HTMLElement).setPointerCapture(e.pointerId); + this.setState({ isMouseDown: true, mousePosition: _topPanelFilmstrip ? e.clientY : e.clientX, @@ -696,11 +745,33 @@ class Filmstrip extends PureComponent { } /** - * Drag handle mouse up handler. + * Prevents click events on drag handle from triggering toolbar. + * + * @param {React.MouseEvent} e - The click event. + * @returns {void} + */ + _onDragHandleClick(e: React.MouseEvent) { + e.preventDefault(); + e.stopPropagation(); + } + + /** + * Prevents touch start events on drag handle from triggering toolbar. + * + * @param {React.TouchEvent} e - The touch start event. + * @returns {void} + */ + _onDragHandleTouchStart(e: React.TouchEvent) { + e.stopPropagation(); + } + + /** + * Drag handle pointer up handler. + * Supports both mouse and touch events via Pointer Events API. * * @returns {void} */ - _onDragMouseUp() { + _onDragPointerUp() { if (this.state.isMouseDown) { this.setState({ isMouseDown: false @@ -710,12 +781,13 @@ class Filmstrip extends PureComponent { } /** - * Handles drag handle mouse move. + * Handles drag handle pointer move. + * Supports both mouse and touch events via Pointer Events API. * - * @param {MouseEvent} e - The mousemove event. + * @param {PointerEvent} e - The pointermove event. * @returns {void} */ - _onFilmstripResize(e: React.MouseEvent) { + _onFilmstripResize(e: PointerEvent) { if (this.state.isMouseDown) { const { dispatch, @@ -1148,4 +1220,4 @@ function _mapStateToProps(state: IReduxState, ownProps: any) { }; } -export default withStyles(translate(connect(_mapStateToProps)(Filmstrip)), styles); +export default translate(connect(_mapStateToProps)(withStyles(Filmstrip, styles))); diff --git a/react/features/filmstrip/constants.ts b/react/features/filmstrip/constants.ts index 77484b998b..d1ed8da33b 100644 --- a/react/features/filmstrip/constants.ts +++ b/react/features/filmstrip/constants.ts @@ -269,6 +269,19 @@ export const MIN_STAGE_VIEW_HEIGHT = 700; */ export const MIN_STAGE_VIEW_WIDTH = 800; +/** + * Drag handle dimensions for resizable filmstrip. + */ +export const DRAG_HANDLE_WIDTH = 9; +export const DRAG_HANDLE_HEIGHT = 100; +export const DRAG_HANDLE_TOP_PANEL_HEIGHT = 3; +export const DRAG_HANDLE_TOP_PANEL_WIDTH = 100; + +/** + * Touch padding added to each side of drag handle for easier tapping on touch devices. + */ +export const TOUCH_DRAG_HANDLE_PADDING = 6; + /** * Horizontal margin used for the vertical filmstrip. */ @@ -298,3 +311,9 @@ export const MAX_ACTIVE_PARTICIPANTS = 6; * Top filmstrip default height. */ export const TOP_FILMSTRIP_HEIGHT = 180; + +/** + * Minimum screen width needed for functional filmstrip resizing. + * Calculated as stage minimum + filmstrip minimum (800px + 120px = 920px). + */ +export const MIN_FILMSTRIP_RESIZE_WIDTH = MIN_STAGE_VIEW_WIDTH + DEFAULT_FILMSTRIP_WIDTH; diff --git a/react/features/filmstrip/functions.web.ts b/react/features/filmstrip/functions.web.ts index 61ae85b1b6..50f85c932e 100644 --- a/react/features/filmstrip/functions.web.ts +++ b/react/features/filmstrip/functions.web.ts @@ -2,7 +2,7 @@ import { Theme } from '@mui/material/styles'; import { IReduxState } from '../app/types'; import { IStateful } from '../base/app/types'; -import { isMobileBrowser } from '../base/environment/utils'; +import { isTouchDevice, shouldEnableResize } from '../base/environment/utils'; import { MEDIA_TYPE } from '../base/media/constants'; import { getLocalParticipant, @@ -30,6 +30,7 @@ import { DEFAULT_LOCAL_TILE_ASPECT_RATIO, DISPLAY_AVATAR, DISPLAY_VIDEO, + DRAG_HANDLE_WIDTH, FILMSTRIP_GRID_BREAKPOINT, FILMSTRIP_TYPE, INDICATORS_TOOLTIP_POSITION, @@ -45,6 +46,7 @@ import { TILE_VIEW_DEFAULT_NUMBER_OF_VISIBLE_TILES, TILE_VIEW_GRID_HORIZONTAL_MARGIN, TILE_VIEW_GRID_VERTICAL_MARGIN, + TOUCH_DRAG_HANDLE_PADDING, VERTICAL_VIEW_HORIZONTAL_MARGIN } from './constants'; @@ -621,6 +623,7 @@ export function getIndicatorsTooltipPosition(thumbnailType?: string) { /** * Returns whether or not the filmstrip is resizable. + * On touch devices, resize is only enabled for larger screens (tablets, not phones). * * @param {Object} state - Redux state. * @returns {boolean} @@ -629,7 +632,7 @@ export function isFilmstripResizable(state: IReduxState) { const { filmstrip } = state['features/base/config']; const _currentLayout = getCurrentLayout(state); - return !filmstrip?.disableResizable && !isMobileBrowser() + return !filmstrip?.disableResizable && shouldEnableResize() && (_currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW || _currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW); } @@ -662,8 +665,13 @@ export function getVerticalViewMaxWidth(state: IReduxState) { // Adding 4px for the border-right and margin-right. // On non-resizable filmstrip add 4px for the left margin and border. - // Also adding 7px for the scrollbar. Also adding 9px for the drag handle. - maxWidth += (_verticalViewGrid ? 0 : 11) + (_resizableFilmstrip ? 9 : 4); + // Also adding 7px for the scrollbar. + // Drag handle: DRAG_HANDLE_WIDTH + padding (TOUCH_DRAG_HANDLE_PADDING on each side for touch) + const dragHandleWidth = isTouchDevice() + ? DRAG_HANDLE_WIDTH + (TOUCH_DRAG_HANDLE_PADDING * 2) + : DRAG_HANDLE_WIDTH; + + maxWidth += (_verticalViewGrid ? 0 : 11) + (_resizableFilmstrip ? dragHandleWidth : 4); return maxWidth; }