Files
ds-galo/openspec/changes/archive/2026-10-11-mobile-tap-open-and-panel/design.md
T

5.6 KiB
Raw Blame History

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

Нет.