- Touch/pen tap opens the viewer; mouse click still selects, dblclick opens - Panel and rail emit a single transform utility so the panel actually slides in below 1280px (Tailwind v4 utility ordering made the base translate-x-full win over the conditional translate-x-0) - Add touch-action: manipulation to grid cells
5.6 KiB
Design
Context
См. proposal.md — Why. Текущее состояние:
AppShell.svelte: у правого<aside>в списке классов одновременно постоянныйtranslate-x-fullи условныйtranslate-x-0(приpanelOpen). В Tailwind v4 скомпилированный порядок ставит.translate-x-fullпосле.translate-x-0, поэтому постоянный класс побеждает и панель остаётся за экраном;panelOpenуправляет только оверлеем. У рейла база-translate-x-fullсортируется раньшеtranslate-x-0, поэтому рейл открывается случайно корректно.PhotoGrid.svelte:onclick -> onSelect(выделение),ondblclick -> onOpen(просмотр). Touch генерируетclick, но неdblclick, поэтому касание только выделяет.
Goals / Non-Goals
Goals:
- Панель на узком экране действительно выдвигается кнопкой «Панель».
- Исключить класс трансформации, который может перебивать условный (панель и рейл).
- Одиночное касание по фото открывает просмотр на сенсорных устройствах.
- Мышь и клавиатура сохраняют прежнее поведение.
Non-Goals:
- Новый UI выделения/мультивыбора на сенсорных экранах.
- Изменения клавиатурной навигации, ровинга
tabindexи режима просмотра. - Изменения бэкенда.
Decisions
1. Ровно один класс трансформации для панели/рейла
Эмитим единственное значение: translate-x-0 при открытии, иначе translate-x-full. Постоянный
класс из строки убирается (для рейла — -translate-x-full). На рабочем столе видимость задаётся
вариантом xl:translate-x-0 / lg:translate-x-0.
Альтернативы: инлайновый style:translate; arbitrary-value классы; data-open с собственным CSS.
Выбрано исключение конфликта минимальной правкой — совпадает с уже существующим намерением кода и
не тянет новый CSS-слой.
2. Детект сенсорного ввода — по pointerType события
Тип ввода определяется на pointerdown (touch/pen → сенсорный, иначе указатель) и
используется при обработке активации. Не используем ширину вьюпорта и matchMedia('(pointer:coarse)').
Альтернатива: matchMedia по грубому указателю. Отклонено: на гибридных устройствах (тач + мышь)
это статично и путает при вводе мышью. pointerType на конкретном событии точнее.
3. Тап открывает, не выделяет
При активации сенсорным вводом вызывается открытие просмотра (onOpen); выделение, cursorIndex и
сведения в панели не трогаются. Клик мышью — как прежде (onSelect), двойной — onOpen.
На сенсорных ячейках имеет смысл touch-action: manipulation, чтобы исключить двойной тап-зум.
4. Выделение на touch — долгое нажатие
Отдельный UI не добавляем: остаётся существующее контекстное меню ячейки (oncontextmenu), которое
содержит «Открыть (просмотр)», «Переместить», «Копировать», «Удалить».
Risks / Trade-offs
- [На части мобильных браузеров долгое нажатие не доходит до нашего
oncontextmenu(нативное меню или выделение текста)] → зафиксировать как известное ограничение; при необходимости добавитьtouch-action/user-selectв follow-up, не расширяя этот change. - [
pointerTypeпуст для части синтетических кликов] → при не-сенсорном/неизвестном типе сохраняем прежнее поведение (выделение), чтобы мышь не ломалась. - [Регрессия порядка утилит Tailwind] → оба места (панель и рейл) используют единственный класс, поэтому конфликт невозможен независимо от порядка генерации.
- [Двойной тап-зум на мобильном] → активация на первом касании делает его нерелевантным; при
необходимости подавляется
touch-action: manipulation.
Migration Plan
Обратной совместимости нарушать не требуется. Откат — возврат правки классов/обработчиков.
Open Questions
Нет.