Список микрорешений, которые рождаются при сборке таблицы с редактируемыми ячейками. Иллюстрация к тезису: между макетом и продуктом — серая зона из сотен таких решений.
Вход в режим редактирования
- клик — курсор встает точно в позицию клика
- двойной клик — выделяется слово
- тройной клик — выделяется абзац/вся строка
- нажатие enter/f2 на выделенной ячейке — курсор в конец текста
- начало набора символов на выделенной ячейке — содержимое заменяется
- backspace на выделенной ячейке — очищает содержимое
Выход из режима редактирования
- enter — сохранить, перейти на ячейку ниже
- escape — отменить изменения, вернуться в режим навигации
- tab — сохранить, перейти на следующую ячейку
- shift+tab — сохранить, перейти на предыдущую ячейку
- клик за пределами ячейки (blur) — сохранить
- сохранение через debounce (1 сек после последнего изменения)
Навигация внутри текста (режим редактирования)
- стрелки — перемещение курсора по тексту (не по таблице!)
- cmd+left/right — начало/конец строки
- option+left/right — перемещение по словам
- shift+стрелки — выделение текста
- shift+cmd+left/right — выделение до начала/конца строки
- home/end — начало/конец текста
Навигация по таблице (режим навигации, ячейка выделена но не редактируется)
- стрелки — перемещение между ячейками
- tab/shift+tab — следующая/предыдущая ячейка
- enter — войти в режим редактирования
- escape — снять выделение
- home/end — первая/последняя ячейка в строке
- ctrl+home/end — первая/последняя ячейка в таблице
Перенос строки внутри ячейки
- shift+enter или option+enter — новая строка внутри ячейки
- прямая установка value ломает cmd+z (undo внутри ячейки)
Выделение текста
- двойной клик — выделение слова
- тройной клик — выделение абзаца
- react controlled textarea уничтожает выделение при re-render (setState в onFocus вызывает re-render, react переприсваивает value в dom, браузер сбрасывает текущее выделение)
- решение: не вызывать setState в onFocus, использовать uncontrolled textarea
- contenteditable в firefox имеет баги с двойным/тройным кликом — textarea таких багов не имеет
Фокус и визуал
- фокус-ринг (оранжевый в нашем случае) — визуальная обратная связь что ячейка активна
- фокус-ринг должен появляться мгновенно при клике
- textarea стилизуется как обычный текст (невидимые границы), выглядит как часть таблицы
- авторесайз textarea под содержимое (css field-sizing: content или js)
Propagation событий
- stopPropagation на mousedown/pointerdown/keydown в режиме редактирования — универсальный паттерн всех production-таблиц
- без этого: клик в ячейку триггерит выделение строки, drag, навигацию по таблице
- ag grid, handsontable, glide data grid — все используют stopPropagation
- при выходе из режима редактирования ячейка переэмитирует клавишу (tab, enter) чтобы таблица могла навигировать
Два лагеря ux-паттернов
- spreadsheet-like (google sheets, airtable, ag grid): клик выделяет ячейку, двойной клик/enter — редактирование. два явных режима. лучше для data-heavy таблиц
- document-like (notion, linear): клик сразу редактирует. нет режима выделения. лучше для content-focused ui с малым числом редактируемых полей на строку
Как это делают production-приложения
- Google sheets: canvas + textarea overlay, два режима
- Notion: contenteditable div, всегда в режиме редактирования
- Aitable: input/textarea, два режима (selected vs editing)
- AG-grid: input/textarea, suppressKeyboardEvent callback
- Handsontable: один textarea на всю таблицу, переиспользуется между ячейками
- Glide data grid: canvas + overlay textarea, нет dom-элементов на ячейку
- ни одна production data table не использует contenteditable — только input/textarea