fix(room): шторка настроек не закрывалась свайпом вниз
Обработчики висели только на ручке `.room-sheet-handle` (40×4px) — попасть в неё пальцем практически невозможно, и палец почти всегда приземлялся на панель, где обработчиков не было вовсе. Свайп теперь закрывает шторку при жесте по любому месту панели, но только когда содержимое проскроллено в самый верх — иначе палец должен листать список устройств, как в любом стандартном bottom sheet.
This commit is contained in:
@@ -11,7 +11,7 @@ interface DeviceSettingsDialogProps extends StageViewProps {
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
/** Свайп ручки шторки вниз дальше этого порога (px) закрывает панель, меньше — она возвращается на место. */
|
||||
/** Свайп панели вниз дальше этого порога (px) закрывает шторку, меньше — она возвращается на место. */
|
||||
const SHEET_DISMISS_THRESHOLD_PX = 80
|
||||
|
||||
/** Человекочитаемая подпись пункта списка устройств — `label` пуст, пока нет разрешения на медиа. */
|
||||
@@ -30,7 +30,13 @@ function deviceLabel(device: MediaDeviceInfo, index: number, fallback: string):
|
||||
* `deviceId` при следующем подключении.
|
||||
*
|
||||
* На мобильной ширине (`useIsCompactViewport`) рендерится шторкой снизу вместо
|
||||
* модалки — по клику вне, Escape (`useModalDismiss`) и свайпу вниз за ручку.
|
||||
* модалки — по клику вне, Escape (`useModalDismiss`) и свайпу вниз по ЛЮБОМУ
|
||||
* месту панели (не только за ручку-волосок `.room-sheet-handle` — та её не
|
||||
* пережила бы: 40×4px, попасть пальцем почти нереально). Свайп срабатывает,
|
||||
* только если содержимое панели проскроллено в самый верх (`scrollTop === 0`
|
||||
* на touchstart, см. `dragEligibleRef`) — иначе палец должен листать список
|
||||
* устройств, а не закрывать окно; решение фиксируется на весь жест, а не
|
||||
* пересчитывается на каждый touchmove, как в любом стандартном bottom sheet.
|
||||
* Десктоп не меняется.
|
||||
*
|
||||
* Там же, и только там, первой секцией идёт «Вид» (режим показа участников и
|
||||
@@ -61,6 +67,12 @@ export function DeviceSettingsDialog({
|
||||
useModalDismiss(onClose)
|
||||
|
||||
const dragStartYRef = useRef<number | null>(null)
|
||||
// Жест начат, когда контент панели был проскроллен в самый верх — свайп по
|
||||
// панели, у которой ещё есть что скроллить, должен листать содержимое, а не
|
||||
// закрывать шторку. Решается ОДИН раз в touchstart и держится весь жест
|
||||
// (даже если внутри него направление сменится) — так же, как в любом
|
||||
// стандартном bottom sheet.
|
||||
const dragEligibleRef = useRef(false)
|
||||
const [dragOffset, setDragOffset] = useState(0)
|
||||
const [isDragging, setIsDragging] = useState(false)
|
||||
|
||||
@@ -92,18 +104,21 @@ export function DeviceSettingsDialog({
|
||||
}
|
||||
}
|
||||
|
||||
function handleHandleTouchStart(e: React.TouchEvent<HTMLDivElement>) {
|
||||
function handlePanelTouchStart(e: React.TouchEvent<HTMLDivElement>) {
|
||||
dragEligibleRef.current = e.currentTarget.scrollTop === 0
|
||||
if (!dragEligibleRef.current) return
|
||||
dragStartYRef.current = e.touches[0].clientY
|
||||
setIsDragging(true)
|
||||
}
|
||||
|
||||
function handleHandleTouchMove(e: React.TouchEvent<HTMLDivElement>) {
|
||||
if (dragStartYRef.current === null) return
|
||||
function handlePanelTouchMove(e: React.TouchEvent<HTMLDivElement>) {
|
||||
if (!dragEligibleRef.current || dragStartYRef.current === null) return
|
||||
const delta = e.touches[0].clientY - dragStartYRef.current
|
||||
if (delta > 0) setDragOffset(delta)
|
||||
}
|
||||
|
||||
function handleHandleTouchEnd() {
|
||||
function handlePanelTouchEnd() {
|
||||
if (!dragEligibleRef.current) return
|
||||
if (dragOffset > SHEET_DISMISS_THRESHOLD_PX) {
|
||||
onClose()
|
||||
return
|
||||
@@ -124,20 +139,16 @@ export function DeviceSettingsDialog({
|
||||
<div
|
||||
className={isCompact ? 'room-sheet-panel' : 'room-modal-panel'}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onTouchStart={isCompact ? handlePanelTouchStart : undefined}
|
||||
onTouchMove={isCompact ? handlePanelTouchMove : undefined}
|
||||
onTouchEnd={isCompact ? handlePanelTouchEnd : undefined}
|
||||
style={
|
||||
isCompact && dragOffset
|
||||
? { transform: `translateY(${dragOffset}px)`, transition: isDragging ? 'none' : undefined }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{isCompact && (
|
||||
<div
|
||||
className="room-sheet-handle"
|
||||
onTouchStart={handleHandleTouchStart}
|
||||
onTouchMove={handleHandleTouchMove}
|
||||
onTouchEnd={handleHandleTouchEnd}
|
||||
/>
|
||||
)}
|
||||
{isCompact && <div className="room-sheet-handle" />}
|
||||
|
||||
<div className="room-modal-head">
|
||||
<h2 id="device-settings-title">{isCompact ? 'Настройки' : 'Настройки устройств'}</h2>
|
||||
|
||||
@@ -803,10 +803,16 @@ video[data-lk-source='screen_share'] { object-fit: contain; background: #000; }
|
||||
* Тот же диалог (`DeviceSettingsDialog`), что модалка выше, — только контейнер
|
||||
* снизу вместо центра экрана: `.room-sheet-overlay`/`.room-sheet-panel` вместо
|
||||
* `.room-modal-overlay`/`.room-modal-panel`, разметка полей (`.room-field` и
|
||||
* ниже) общая. Ручка `.room-sheet-handle` — свайп вниз для закрытия (JS считает
|
||||
* смещение пальца и сам решает, закрывать или вернуть панель на место;
|
||||
* `transition` тут — только пружина возврата, во время самого драга
|
||||
* компонент подставляет инлайновый `transition: none`).
|
||||
* ниже) общая. Свайп вниз для закрытия — JS на самой панели (не на ручке —
|
||||
* см. докстринг `DeviceSettingsDialog`) считает смещение пальца и сам решает,
|
||||
* закрывать или вернуть панель на место; `transition` тут — только пружина
|
||||
* возврата, во время самого драга компонент подставляет инлайновый
|
||||
* `transition: none`. `overscroll-behavior-y: contain` — чтобы при свайпе
|
||||
* вниз от самого верха списка устройств iOS/Chrome не показывали заодно
|
||||
* ещё и нативный эффект растяжения скролла поверх нашей анимации панели.
|
||||
* Ручка `.room-sheet-handle` — теперь чисто визуальная подсказка (сама
|
||||
* панель ловит жест где угодно), `touch-action: none` на ней оставлен —
|
||||
* безвредно и снимает системные жесты с этой узкой полоски.
|
||||
*/
|
||||
.room-sheet-overlay {
|
||||
position: fixed;
|
||||
@@ -827,6 +833,7 @@ video[data-lk-source='screen_share'] { object-fit: contain; background: #000; }
|
||||
width: 100%;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior-y: contain;
|
||||
transition: transform 160ms ease-out;
|
||||
}
|
||||
.room-sheet-handle {
|
||||
|
||||
Reference in New Issue
Block a user