Версия: 1.5 — Р-2 и Р-5 уточнены под @tanstack/virtual-core (padding-режим, без абсолютного позиционирования; вычисление окна не меняет разметку Р-1 и пул Р-5 по существу); Р-6 пересмотрен: движок табличной модели (@tanstack/table-core, manual/server-side режим) теперь принят для сортировки/фильтров/настроек колонок — вывод «отдельный движок не нужен» снят для данных и подтверждён для режимов (FSM); Р-7 без изменений. Подробности размещения — NOVA-Grid-Architecture.md (версия 0.2+).
Статус: утверждено к реализации. Требования (§1–§9) и принятые архитектурные решения (§11) зафиксированы. Раскрытие развилки Р-13 (транспорт обновлений) и оставшейся части Р-11 (структура пакета и точки расширения) ведётся в отдельном документе — NOVA-Grid-Architecture.md.
Живой wire и клиент: HTTP-контракт и статус реализации — в
NOVA-Grid-API-Spec.md/NOVA-Grid-API-Spec-Backend.md/NOVA-Grid-HTTP-Client.md(не в этом ТЗ). ТЗ фиксирует что должен делать Grid; расхождения wire с ранними формулировками (например HTTPlimitvs TSpageSize, authx-api-key) разрешаются в пользу Spec + кода.
Документ отвечает на вопрос что компонент должен делать и при каких ограничениях (§1–§9). Вопрос как зафиксирован по принятым решениям в §11; развилки, оставшиеся открытыми (§10), раскрываются в документе архитектуры.
NOVA Table.dc.html,components.css,tokens.css— визуальный прототип. Ни разметка, ни имена токенов, ни селекторы эталоном не являются и могут измениться.
Готовность разделов
| Раздел | Статус | |
|---|---|---|
§1–§3 контекст, сценарии, границы |
зафиксировано |
|
§4 функциональные требования (~120 ФТ) |
зафиксировано |
|
§5 нефункциональные требования |
зафиксировано |
|
§6 контракт с источником данных (К-1…К-14) |
зафиксировано |
|
§7 конфигурация инстанса |
зафиксировано |
|
§8 критерии приёмки |
зафиксировано |
|
§9 открытые вопросы |
закрыты |
|
§10 реестр решений |
Р-1…Р-10, Р-12 приняты; Р-11 структура пакета решена ( |
|
§11 принятые решения |
зафиксировано |
Журнал версий (сводка)
-
0.1–0.2 — первичное ТЗ, затем сверка с прототипом дизайн-системы.
-
0.3 — переписано на уровень требований: убраны решения по разметке и CSS, добавлены лица, сценарии, нумерованные ФТ/НФТ, критерии приёмки, реестр отложенных решений.
-
0.4 — модель фильтрации: поле ≠ колонка, именованные условия, сохранённые наборы, схема с сервера.
-
0.5–0.6 — приняты Р-1, Р-2, Р-4, Р-5, Р-7; введён §11.
-
0.7 — закрыты открытые вопросы §9 (размер набора, значения фильтров, права, версии, закрепление, реалтайм).
-
0.8–0.9 — приняты Р-3, Р-6, Р-9, Р-10, Р-12.
-
0.10 — §4.16 индикаторы загрузки.
-
0.11 — Р-6 уточнён: Zustand — стор приложения; подсистемы-автоматы.
-
0.12–0.14 — WebTransport в Р-13; порог индикаторов разведён на подмену содержимого и наложенный индикатор.
-
1.0 — финальная редакция требований.
-
1.1 — добавлен §4.17 Клавиатурная навигация; уточнены НФТ-3.1, ФТ-9.11, П-5 и Р-5.
-
1.2 — уточнения: ФТ-2.4 (служебная колонка «выбор + номер»), ФТ-4.28а (сброс условий и применённый набор), ФТ-6.4 (исключение после «выбрать всё»), ФТ-9.9 (публичные методы прокрутки), ФТ-12.14 (приоритет пользователя до конца сессии), ФТ-13.8 / П-3а (тест ветки деградации), НФТ-2 (раздельные метрики смещения), §7.3 (поле колонки отсутствует в схеме).
-
1.3 — уточнения по служебной колонке номера, сбросу фильтров при наборах, режиму «выбрать всё», публичным методам прокрутки, адаптивному скрытию, деградации размера набора, метрикам layout и связи колонки со схемой фильтрации.
-
1.4 → 1.5 — Р-6 пересмотрен повторно (как уже бывало в 0.8–0.9, 0.11, 1.1): движок табличной модели (
@tanstack/table-core, manual/server-side режим) принят для сортировки, фильтров и настроек колонок вместо вывода «отдельный движок не нужен»; семь macro-режимов не затронуты, остаются на zag-машине. Р-2 уточнён: диапазон видимых строк и высота распорок вычисляются@tanstack/virtual-core(padding-режим, не absolute-position) вместо ручного алгоритма; сам приём распорок и Р-1 не меняются. Р-5 уточнён без изменения стратегии — пул строк подбирает диапазон изvirtual-core, а не из ручного расчёта. Р-7 оставлен без изменений. Подготовлено вместе сNOVA-Grid-Architecture.md→ версия 0.2.
1. Контекст и назначение
NOVAGrid — компонент ui-kit для отображения и редактирования табличных данных в веб-приложении NOVA.
Один компонент инстанцируется многократно: разные экраны, разные наборы колонок, разные типы записей, разные источники данных. Компонент не знает ни про домен, ни про транспорт — всё доменное приходит снаружи через конфигурацию.
Проверка на переиспользуемость: внутри компонента не должно быть ни одной ветки, зависящей от конкретного имени колонки или поля записи.
1.1 Глоссарий
| Термин | Значение | |
|---|---|---|
Запись |
Единица данных, отображаемая одной строкой |
|
Набор |
Все записи, удовлетворяющие текущим фильтру и поиску, в текущем порядке сортировки. Может быть больше, чем загружено |
|
Окно |
Диапазон записей, загруженных в память клиента |
|
Детали |
Дополнительная информация записи, скрытая до раскрытия строки |
|
Поле фильтрации |
Атрибут, по которому источник умеет фильтровать. Не то же самое, что колонка: поле может не иметь колонки, колонка может не иметь поля |
|
Условие |
Одно правило фильтрации. Бывает двух видов: по полю (поле + оператор + значение) и именованное |
|
Именованное условие |
Предикат с собственным смыслом, не разбираемый на поле и оператор: «только с активными авариями», «изменённые за сутки». Может иметь параметры |
|
Сохранённый набор |
Именованная комбинация условий и поиска, применяемая одним действием |
|
Схема фильтрации |
Описание доступных полей, их типов, операторов и именованных условий |
|
Инстанс |
Конкретное применение компонента на конкретном экране |
2. Действующие лица и сценарии
2.1 Лица
| Лицо | Что делает с таблицей | |
|---|---|---|
Оператор |
Наблюдает за большим набором, ищет нужное, следит за статусами. Читает, почти не пишет. Работает подолгу, таблица открыта весь день |
|
Инженер |
Правит записи, добавляет и удаляет, выполняет действия над выборкой. Пишет часто |
|
Разработчик-потребитель |
Конфигурирует инстанс: колонки, источник, действия, редакторы. Не имеет права трогать внутренности компонента |
2.2 Ключевые сценарии
С-1. Найти запись в большом наборе. Оператор вводит фрагмент в поиск, получает сокращённый набор, уточняет условием по колонке, находит запись, раскрывает детали.
С-2. Групповое действие над выборкой. Инженер фильтрует набор по условию, выделяет все результаты (не перечисляя вручную), выполняет действие над всей выборкой — включая записи, которые физически не загружены в браузер.
С-3. Точечная правка. Инженер находит запись, переводит строку в режим редактирования, правит два-три поля, сохраняет. Соседние строки при этом не смещаются.
С-4. Настройка под себя. Оператор скрывает ненужные колонки, меняет ширины, закрепляет ключевую колонку слева. Настройки переживают перезагрузку страницы.
С-5. Долгий просмотр. Оператор прокручивает набор от начала к концу. Данные подгружаются по мере скролла. Ширины колонок за всё время не изменились ни разу.
С-6. Разбор инцидента. Оператор фильтрует по критическому статусу, раскрывает несколько строк одновременно, сравнивает их детали, копирует значения.
3. Границы
3.1 Входит в v1
Отображение больших наборов, сортировка (в т.ч. по нескольким колонкам), фильтрация двух уровней с именованными условиями, сохранённые наборы фильтров, полнотекстовый поиск, изменение ширин колонок, скрытие и закрепление колонок, раскрытие строки с деталями, выбор строк и групповые действия, построчное редактирование, добавление и удаление записей, клавиатурная навигация, переключение темы и плотности на лету, сохранение пользовательских настроек.
4. Функциональные требования
Обозначения: ФТ-n.m. Требования сформулированы проверяемо — каждое должно превращаться в тест.
4.1 Отображение
-
ФТ-1.1. Набор колонок задаётся конфигурацией инстанса. Число колонок не ограничено; при превышении ширины вьюпорта появляется горизонтальный скролл.
-
ФТ-1.2. Содержимое ячейки может быть произвольным: текст, число, дата, индикатор, прогресс-бар, ссылка, набор элементов. Способ отрисовки задаётся конфигурацией колонки.
-
ФТ-1.3. Текст, не помещающийся в ячейку, усекается с многоточием. Колонка может быть помечена как переносящая текст. возможен tooltip с полным текстом.
-
ФТ-1.4. Компонент поддерживает наборы до 500 000 записей без деградации отклика (см. НФТ-2).
-
ФТ-1.5. Строка может нести визуальный признак состояния записи (норма / предупреждение / авария / приглушена). Признак вычисляется из записи функцией из конфигурации.
-
ФТ-1.6. Признак состояния строки и признак выбранности строки различимы одновременно. Выбор аварийной строки не должен скрывать её аварийность.
4.2 Порядковый номер
-
ФТ-2.1. Каждая строка несёт порядковый номер.
-
ФТ-2.2. Номер — позиция записи в текущем наборе, а не в загруженном окне и не идентификатор записи. При смене сортировки или фильтра номера пересчитываются.
-
ФТ-2.3. Если продукту нужен постоянный номер записи, он подаётся как обычная колонка данных, а не как служебный номер.
-
ФТ-2.4. Номер не должен занимать отдельную колонку, если это можно совместить с колонкой выбора. Допускается одна служебная колонка, совмещающая выбор и порядковый номер (единый столбец в колоночной группе). Отдельная колонка только под номер не требуется. Ширина этой колонки подстраивается под число цифр
totalпри первой успешной загрузке набора (--nova-grid-select-digits+ density-токен--nova-grid-select-w) и не пересчитывается при смене фильтра/поиска/reload — без запекания px, смена плотности без reload сохраняется.
4.3 Сортировка
-
ФТ-3.1. Клик по заголовку колонки циклически переключает: по возрастанию → по убыванию → без сортировки.
-
ФТ-3.2. Поддерживается сортировка по нескольким колонкам одновременно, не более трёх. Порядок применения колонок виден пользователю.
-
ФТ-3.3. Колонка может быть помечена как несортируемая.
-
ФТ-3.4. Направление сортировки видно в заголовке в любой момент, без наведения.
-
ФТ-3.5. Сортировка выполняется источником данных, не клиентом (см. §6).
-
ФТ-3.6. Смена сортировки не сбрасывает выбор строк и не сворачивает раскрытые строки.
-
ФТ-3.7. Сортировка возможна только по колонкам. Поля, не имеющие колонки, для сортировки недоступны — в отличие от фильтрации (ФТ-4.2). Отдельного элемента управления сортировкой в панели нет.
-
ФТ-3.8. Колонка может сортироваться по полю, отличному от отображаемого значения: например, статус — по весу («авария» выше «нормы»), а не по алфавиту. Поле сортировки задаётся конфигурацией колонки.
4.4 Фильтрация
Модель
-
ФТ-4.1. Все фильтры — независимо от способа ввода — образуют единый набор условий. Двух параллельных состояний фильтрации не существует.
-
ФТ-4.2. Условие ссылается на поле фильтрации, а не на колонку. Поле и колонка — разные сущности; допустимы все три сочетания:
| Есть колонка | Нет колонки | ||
|---|---|---|---|
Есть поле |
условие видно на обоих уровнях |
условие живёт только в панели |
|
Нет поля |
колонка отображает, но не фильтруется |
— |
-
ФТ-4.3. Условия бывают двух видов:
-
по полю — поле, оператор, значение;
-
именованное — предикат с собственным смыслом, без оператора; может иметь параметры («изменённые за N часов»).
-
-
ФТ-4.4. Условия соединяются по «И». Внутри условия по перечислению допустимо «ИЛИ» по значениям.
-
ФТ-4.5. Набор доступных операторов определяется типом поля. Минимальный набор типов: строка, число, дата, логический, перечисление.
Схема фильтрации
-
ФТ-4.6. Описание доступных полей, их типов, операторов и именованных условий приходит от источника данных. При отсутствии — берётся из конфигурации инстанса. При отсутствии в обоих местах панель фильтров (уровень 1) недоступна, а уровень 2 работает только по колонкам с явно объявленным типом фильтра.
-
ФТ-4.7. Ожидание схемы не блокирует загрузку данных: ранее сохранённые условия применяются немедленно, подписи в панели уточняются по приходе схемы.
-
ФТ-4.8. Условие, ссылающееся на поле, отсутствующее в полученной схеме, не отбрасывается молча: оно помечается как нераспознанное и отключается, пользователь видит его и может удалить. Молчаливое отбрасывание недопустимо — пользователь не поймёт, почему выдача изменилась.
-
ФТ-4.9. Компонент значения условий не интерпретирует и не проверяет по существу: словарём полей и семантикой значений владеет источник.
Уровень 1 — панель над таблицей
-
ФТ-4.10. Активные условия показаны компактным списком; каждое удаляется по отдельности.
-
ФТ-4.11. Условие по полю редактируется на месте. Именованное условие переключается; при наличии параметров редактируется собственным способом.
-
ФТ-4.12. Добавление условия — выбор из перечня фильтруемых полей и именованных условий. Перечень может быть длиннее списка колонок и должен быть пригоден для поиска по нему.
-
ФТ-4.13. Полнотекстовый поиск (§4.5) располагается в той же панели и является частью состояния фильтрации, но отдельным от условий.
Уровень 2 — фильтр в заголовке колонки
-
ФТ-4.14. Доступен только для колонок, связанных с фильтруемым полем. Включается и выключается пользователем; в выключенном состоянии не занимает места.
-
ФТ-4.15. Условие по полю, имеющему видимую колонку, отображается на обоих уровнях; изменение на одном немедленно отражается на другом.
-
ФТ-4.16. Условие по полю без колонки и любое именованное условие существуют только на уровне 1.
-
ФТ-4.17. Скрытие колонки не снимает условие по её полю. Условие продолжает действовать и остаётся видимым в панели. Иначе пользователь теряет фильтр, просто убрав колонку с глаз, и не понимает, почему выдача изменилась.
Перечисления
-
ФТ-4.18. Для полей-перечислений предлагается список фактических значений с множественным выбором. Значения приходят вместе с первой порцией данных (§6.4) и учитывают остальные активные условия.
-
ФТ-4.19. Способ ввода значения выбирается по мощности перечня, пришедшего от источника:
| Пришло значений | Элемент управления | |
|---|---|---|
0 (перечень не пришёл) |
текстовое поле — свободный ввод |
|
1…10 |
простой список выбора (select) |
|
больше 10 |
список выбора с поиском (combobox) |
Элементы управления берутся из nova-ui-kit (на zag.js), не изобретаются в таблице (см. Р-7). * *ФТ-4.20.* Применяется явным действием, а не по каждому переключению значения.
Сохранённые наборы
-
ФТ-4.21. Сохранённый набор объединяет условия фильтрации, поиск, сортировку и настройки колонок (ширины, порядок, видимость, закрепление) под одним именем.
-
ФТ-4.22. Сохранённый набор применяется одним действием и замещает текущее состояние по всем перечисленным в ФТ-4.21 составляющим целиком, а не дополняет его.
-
ФТ-4.23. Применение набора и автосохранение настроек колонок (ФТ-15.1) действуют на одно состояние. При применении набора текущие автосохранённые настройки колонок замещаются настройками из набора; дальнейшие изменения колонок снова автосохраняются как обычно. Пользователь видит, что применён именно набор, и что состояние отклонилось от него после правок.
-
ФТ-4.23а. Когда применённый набор изменён (состояние отклонилось от сохранённого), компонент предлагает сохранить правки в набор. Изменения записываются в набор только по утвердительному ответу пользователя; иначе набор остаётся прежним, а правки живут лишь в текущем состоянии. Молчаливой перезаписи сохранённого набора не происходит.
-
ФТ-4.24. Наборы можно переименовать и удалить. Пользователь видит, какой набор сейчас применён и был ли он изменён после применения.
-
ФТ-4.25. Область видимости набора — личная либо общая. Право на создание и правку общих наборов проверяется на стороне источника; компонент право не вычисляет, а узнаёт по статусу ответа.
-
ФТ-4.26. Наборы приходят вместе с первой порцией данных (§6.4). Архитектура обязана допускать их получение и отдельным запросом — источник вправе отдавать их асинхронно.
-
ФТ-4.27. Набор, содержащий нераспознанные поля (ФТ-4.8), применяется частично, с явным сообщением о том, какие условия не применились.
Прочее
-
ФТ-4.28. Есть способ сбросить все условия и поиск одним действием. Сброс не затрагивает сортировку и настройки колонок.
-
ФТ-4.28а. Сброс условий и поиска (ФТ-4.28) не сбрасывает признак «применён набор» и не возвращает настройки колонок к значениям набора. Статус «набор изменён» обновляется: после сброса условий состояние считается отклонившимся от сохранённого набора. Пользователь по-прежнему видит, какой набор был применён, и может сохранить правки в него (ФТ-4.23а) либо выбрать другой набор.
-
ФТ-4.29. Поле может определить собственный способ ввода и собственный формат значения. Компонент значение не интерпретирует.
-
ФТ-4.30. Фильтрация выполняется источником данных, не клиентом.
4.5 Поиск
-
ФТ-5.1. Одно поле поиска над таблицей. Ввод фильтрует набор по всем текстовым полям записи.
-
ФТ-5.2. Запрос отправляется не на каждое нажатие клавиши: задержка после окончания ввода, минимальная длина запроса.
-
ФТ-5.3. Какие поля участвуют в поиске, решает источник данных; конфигурация колонок передаёт ему подсказку.
-
ФТ-5.4. Совпадения подсвечиваются в ячейках, если колонка это разрешает.
-
ФТ-5.5. Поиск и условия фильтрации применяются совместно, по «И».
4.6 Выбор строк и групповые действия
-
ФТ-6.1. Строку можно выбрать и снять выбор индивидуально.
-
ФТ-6.2. Поддерживается выбор диапазона строк.
-
ФТ-6.3. Поддерживается действие «выбрать всё». Оно означает «все записи набора», а не «все загруженные записи» — включая записи, отсутствующие в браузере. На сервер отправляем все фильтры и этот момент мы уже знаем количество записей, индикатор total, если больше 1000, делаем предупреждение, что операция может занять много времени и пишем сколько записей будет обработано, и две кнопки продолжить и отменить.
-
ФТ-6.4. После «выбрать всё» режим выбора — «весь набор» (exclude-пустой). Первое снятие выбора с отдельной записи переводит режим в поимённый (include по оставшимся видимым / ранее выбранным) и показывает предупреждение: выбор больше не означает «весь набор», а только перечисленные записи. Сохранить смысл «все, кроме этих» после ручного исключения нельзя — для этого режима отдельного API исключения нет. Повторное «выбрать всё» возвращает режим «весь набор».
-
ФТ-6.5. Счётчик выбранного показывает мощность набора, а не число загруженных строк. Если размер набора неизвестен — формулировка не должна вводить в заблуждение числом.
-
ФТ-6.6. Групповое действие получает описание выборки (режим, перечень исключений или включений, действующие условия и поиск), а не список идентификаторов. Требование обязательно: перечень на 1 000 записей клиент собрать не может.
-
ФТ-6.7. Смена сортировки выбор сохраняет.
-
ФТ-6.8. Смена фильтра или поиска: выбор вида «все, кроме» сбрасывается с уведомлением, поскольку условие перестаёт быть определённым. Поимённый выбор сохраняется с предупреждением, что часть выбранного вне текущего набора.
-
ФТ-6.9. Панель групповых действий не должна смещать таблицу при появлении. При
enableRowSelectionselection-bar монтируется сразу под тулбаром фильтров; без выделения панель видна в состоянииdata-disabled(кнопки disabled), при выборе строк становится активной. -
ФТ-6.10. Удаление записи убирает её из выборки.
4.7 Раскрытие строки
-
ФТ-7.1. У строки есть управляющий элемент раскрытия в её начале. При раскрытии показывается дополнительная информация записи.
-
ФТ-7.2. Элемент раскрытия не должен занимать отдельную колонку.
-
ФТ-7.3. Запись может не иметь деталей — тогда элемент раскрытия отсутствует, но выравнивание соседних строк не нарушается.
-
ФТ-7.4. По умолчанию раскрытых строк может быть несколько. Режим «только одна» — опция инстанса.
-
ФТ-7.5. Состояние раскрытия привязано к записи, а не к позиции. Смена сортировки раскрытые строки не сворачивает.
-
ФТ-7.6. Содержимое деталей задаётся конфигурацией инстанса и может быть произвольным.
-
ФТ-7.7. Высота деталей заранее не фиксирована и зависит от содержимого, но ограничена сверху: при превышении потолка (по умолчанию высота трёх строк) детали прокручиваются внутри собственной области, а не растягивают строку дальше. Потолок нужен сценарию С-6 — сравнению нескольких раскрытых строк одновременно.
-
ФТ-7.8. Раскрытие строки не меняет ширины колонок и не смещает содержимое соседних строк по горизонтали (см. НФТ-1). Область деталей занимает ширину видимой области таблицы и по горизонтали не прокручивается; от ширин колонок она не зависит.
-
ФТ-7.9. Раскрытие и сворачивание недоступны, пока строка редактируется.
4.8 Действия строки
-
ФТ-8.1. В конце строки доступны действия над записью. Состав действий задаётся конфигурацией инстанса.
-
ФТ-8.2. Действия проявляются при наведении на строку и обязательно при получении фокуса клавиатурой.
-
ФТ-8.3. Появление и исчезновение действий не меняет ширину колонок и не смещает содержимое строки.
-
ФТ-8.4. Действие может быть недоступно или скрыто для конкретной записи. Скрытое действие не должно сдвигать остальные иконки относительно соседних строк.
-
ФТ-8.5. При числе действий больше трёх избыточные убираются в раскрывающееся меню.
-
ФТ-8.6. Деструктивные действия визуально отличимы и требуют подтверждения либо предоставляют отмену.
-
ФТ-8.7. На устройствах без наведения действия доступны без него.
4.9 Редактирование
-
ФТ-9.1. Основной режим — редактирование строки целиком: поля появляются на местах ячеек, применение и отмена — явными действиями.
-
ФТ-9.2. Вход в режим редактирования не меняет высоту строки и не смещает соседние строки.
-
ФТ-9.3. Колонка может быть нередактируемой; её ячейка остаётся отображающей.
-
ФТ-9.4. Изменённое поле визуально отличимо от неизменённого.
-
ФТ-9.5. Валидация выполняется до сохранения; ошибка привязана к конкретному полю и объяснена текстом. Сохранение при ошибке заблокировано.
-
ФТ-9.6. Сообщение об ошибке остаётся видимым независимо от положения строки в области прокрутки.
-
ФТ-9.7. Одновременно редактируется одна строка. Переключение при несохранённых изменениях требует подтверждения.
-
ФТ-9.8. Изменения применяются оптимистично: результат виден сразу, при ошибке сервера — откат, объяснение и возврат в режим редактирования.
-
ФТ-9.8а. Право на редактирование записи заранее на клиенте не проверяется: словарь прав компоненту не передаётся. Редактирование доступно для всех записей, а отказ по правам возвращается статусом ответа при сохранении и обрабатывается как ошибка по ФТ-9.8 — с понятным сообщением «нет прав», а не общим текстом. Если продукту нужно скрывать действие заранее, он выражает это доменным предикатом доступности действия (ФТ-8.4), а не правами.
-
ФТ-9.9. Редактируемая строка не может незаметно покинуть видимую область. Любой перевод прокрутки или фокуса, при котором редактируемая строка покинула бы видимую область, откладывается до ответа в модальном диалоге о несохранённых изменениях; при отказе — не выполняется, при согласии — редактирование сбрасывается и действие выполняется. Правило действует для всех источников перевода:
-
колесо мыши и жест трекпада;
-
перетаскивание ползунка полосы прокрутки;
-
клавиатурная прокрутка (
PageUp/PageDown, стрелки на краевой строке,Home/End,Ctrl+Home/Ctrl+End); -
клавиатурный перевод фокуса на ячейку вне видимой области;
-
программный вызов (
scrollToRow,scrollToIndex, автопрокрутка к результату поиска или к добавленной записи); -
автопрокрутка при подгрузке данных.
*Все публичные методы прокрутки* API инстанса (§7.5) обязаны соблюдать то же правило: вызов, при котором редактируемая строка целиком покинула бы видимую область, откладывается до ответа в диалоге. Обход правила через публичный API недопустим.
Порог — полный выход: диалог возникает, когда строка вышла бы целиком; мелкая прокрутка, оставляющая строку частично видимой, проходит без диалога. * *ФТ-9.10.* Дополнительный режим — форма в боковой панели, для записей, у которых полей существенно больше, чем колонок, либо есть поля, не вмещающиеся в строку. Вызывается отдельным действием. * *ФТ-9.11.* Клавиатура: вход в режим, переход между полями строки, применение, отмена — все без мыши (см. §4.17, ФТ-17.4).
-
4.10 Добавление
-
ФТ-10.1. Есть действие добавления новой записи.
-
ФТ-10.2. Два режима на выбор инстанса: черновая строка в начале таблицы либо форма в боковой панели.
-
ФТ-10.3. Черновая строка видна независимо от положения прокрутки, пока не сохранена или не отменена.
-
ФТ-10.4. После сохранения запись занимает место согласно текущей сортировке и кратковременно выделяется.
4.11 Удаление
-
ФТ-11.1. Удаление одной записи сопровождается подтверждением либо возможностью отмены в течение нескольких секунд.
-
ФТ-11.2. Групповое удаление требует подтверждения с явным указанием числа записей.
-
ФТ-11.3. После удаления размер набора и порядковые номера последующих строк корректируются.
-
ФТ-11.4. Операция удаления одиночная/группа показывает прогресс с соответствии с ФТ-16.15.
4.12 Колонки
-
ФТ-12.1. Ширину колонки можно изменить перетаскиванием границы заголовка.
-
ФТ-12.2. Ширина ограничена минимумом и максимумом из конфигурации колонки.
-
ФТ-12.3. Есть способ вернуть колонке исходную ширину.
-
ФТ-12.4. Изменение ширины не вызывает перезапрос данных и не сбрасывает прокрутку.
-
ФТ-12.5. Изменение ширины доступно с клавиатуры.
-
ФТ-12.6. Колонку можно скрыть и вернуть. Скрыть последнюю видимую колонку нельзя.
-
ФТ-12.7. Возврат скрытой колонки доступен из постоянного элемента управления, а не только из меню самой колонки.
-
ФТ-12.13. Колонка может объявлять порог ширины области, ниже которого она автоматически скрывается (адаптивное скрытие). Порог задаётся конфигурацией колонки.
-
ФТ-12.14. Автоматическое и пользовательское скрытие — единый механизм видимости с одним состоянием, не два независимых. Явное действие пользователя (показать или скрыть колонку) имеет приоритет над порогом до конца сессии инстанса: колонку, которую пользователь вернул вручную, порог не прячет снова. При следующем монтировании инстанса (перезагрузка страницы, уничтожение и создание заново) приоритет пользователя сбрасывается; видимость снова определяется порогом и сохранёнными настройками (ФТ-15.1). «Конец сессии» = жизненный цикл текущего инстанса, а не вкладка браузера.
-
ФТ-12.15. Адаптивное скрытие не является отдельным режимом отображения: таблица остаётся таблицей, меняется лишь состав видимых колонок. Перестроения разметки в карточный или иной вид не происходит (см. §3.2).
-
ФТ-12.8. Колонку можно закрепить слева, чтобы она оставалась видимой при горизонтальной прокрутке.
-
ФТ-12.9. Служебные колонки (выбор, действия) закреплены всегда и пользователем не настраиваются.
-
ФТ-12.10. Граница между закреплённой и прокручиваемой областью визуально обозначена только при фактической прокрутке.
-
ФТ-12.16. Если суммарная ширина закреплённых слева колонок превысит ширину видимой области, закреплённая зона прокручивается внутри себя по горизонтали. Таблица остаётся работоспособной: запрета закрепления сверх лимита и автоматического открепления нет.
-
ФТ-12.11. Порядок колонок можно изменить (перетаскиванием заголовков либо иным способом).
-
ФТ-12.12. «Автоширина по содержимому» не поддерживается: при частичной загрузке набора результат зависел бы от позиции прокрутки. Вместо неё — возврат к исходной ширине (ФТ-12.3).
4.13 Загрузка данных и постраничность
-
ФТ-13.1. Поддерживаются два режима, выбираемых инстансом: непрерывная подгрузка при прокрутке и постраничная навигация.
-
ФТ-13.2. В режиме непрерывной подгрузки размер полосы прокрутки соответствует всему набору с первого кадра, а не растёт по мере загрузки.
-
ФТ-13.3. Незагруженные записи в видимой области показываются заполнителями той же высоты. Появление реальных данных не смещает layout.
-
ФТ-13.4. Подгрузка начинается заранее, до достижения границы загруженного диапазона.
-
ФТ-13.5. Одновременных запросов на одно направление не более одного. Ответы на устаревшие запросы отбрасываются.
-
ФТ-13.6. Смена сортировки, условий или поиска отменяет незавершённые запросы, сбрасывает загруженное окно и возвращает прокрутку в начало.
-
ФТ-13.7. Ошибка загрузки не приводит к потере уже загруженных данных; предлагается повтор.
-
ФТ-13.8. Если источник не сообщает размер набора, компонент деградирует корректно: постраничная навигация недоступна, счётчик показывает загруженное, полоса прокрутки растёт по мере загрузки. В штатном режиме размер набора точен и гарантирован (§6.3, К-3а), поэтому это ветка деградации, а не основной путь. Ветка деградации покрывается отдельным приёмочным сценарием (П-3а).
4.13а Обновление в реальном времени
-
ФТ-13.9. Запись может измениться на стороне источника, пока пользователь смотрит на таблицу. Компонент предоставляет точку расширения для приёма обновлений, не завязанную на транспорт (§6.7).
-
ФТ-13.10. Поступившее обновление применяется к окну кэша: изменённая видимая строка перерисовывается, размер набора и порядковые номера корректируются при добавлении и удалении записей на стороне источника.
-
ФТ-13.11. Обновление не сбрасывает пользовательский контекст: прокрутку, выбор, раскрытие, незавершённое редактирование. Обновление записи, которая сейчас редактируется, не затирает черновик; способ разрешения (уведомить, отложить, показать расхождение) определяется на шаге реализации транспорта.
-
ФТ-13.12. Конкретный транспорт (SSE, WebSocket, WebTransport или их сочетание) в v1 не фиксируется; требование v1 — наличие точки расширения и корректное применение событий (Р-13).
4.14 Состояния таблицы
-
ФТ-14.1. Первичная загрузка — заполнители вместо строк, заголовок и панель доступны.
-
ФТ-14.2. Пустой результат — объяснение причины (какие условия дали пустое пересечение) и действие для сброса.
-
ФТ-14.3. Пустой набор данных как таковой — отличается по формулировке от пустого результата фильтрации.
-
ФТ-14.4. Ошибка — объяснение и повтор, с сохранением загруженного.
-
ФТ-14.5. Подгрузка следующей порции — индикация, не блокирующая работу с уже загруженным.
4.15 Сохранение настроек
-
ФТ-15.1. Ширины, порядок, видимость и закрепление колонок сохраняются между сессиями отдельно для каждого инстанса.
-
ФТ-15.2. Сортировка, условия и поиск дополнительно могут отражаться в адресе страницы, чтобы состоянием можно было поделиться ссылкой.
-
ФТ-15.3. Изменение состава колонок в коде не должно ломать восстановление: настройки для исчезнувших колонок отбрасываются молча.
-
ФТ-15.4. Формат сохранённого состояния версионируется; при несовместимости состояние отбрасывается без ошибки.
4.16 Индикаторы загрузки и хода операций
Принцип. У каждой асинхронной операции есть видимое состояние. Индикатор соответствует области, которую операция затрагивает: операция над всей таблицей индицируется на уровне таблицы, над строкой — на строке, внутри всплывающего элемента — внутри него. Индикатор не блокирует работу с тем, что операции не касается, и не смещает разметку (НФТ-1): место под индикатор либо зарезервировано, либо он накладывается поверх.
-
ФТ-16.1. Первичная загрузка данных — строки-заполнители той же высоты (ФТ-14.1, ФТ-13.3); заголовок, панель и переключатели доступны сразу.
-
ФТ-16.2. Незагруженные записи в окне — заполнители на месте конкретных строк; при въезде реальных данных заполнитель заменяется без сдвига (ФТ-13.3).
-
ФТ-16.3. Подгрузка следующей порции (бесконечный скролл) — неблокирующий индикатор у края загруженного диапазона; уже загруженное остаётся интерактивным (ФТ-14.5).
-
ФТ-16.4. Переход по страницам (постраничный режим) — индикатор на области таблицы; предыдущая страница может гаснуть до прихода новой, элементы навигации остаются на месте.
-
ФТ-16.5. Применение сортировки, условий, поиска — переход набора в состояние загрузки: заполнители либо приглушение прежних строк, счётчик в состоянии «пересчитывается». Прокрутка сброшена в начало (ФТ-13.6), контекст фильтров и панель доступны.
-
ФТ-16.6. Поиск в процессе — в поле поиска признак активного запроса, отличный от состояния покоя; поле остаётся доступным для ввода.
-
ФТ-16.7. Загрузка перечня значений фильтра (перечисления) — индикатор внутри всплывающего элемента фильтра, не блокирующий остальную таблицу; до прихода значений выбор недоступен (К-6).
-
ФТ-16.8. Ожидание схемы фильтрации — данные не блокируются (ФТ-4.7); подписи и доступность полей в панели уточняются по приходе схемы, с промежуточным нейтральным состоянием, а не пустотой, воспринимаемой как «фильтров нет».
-
ФТ-16.9. Загрузка сохранённых наборов — индикатор в элементе выбора набора; сам элемент до прихода наборов показывает состояние загрузки, а не пустой список.
-
ФТ-16.10. Оптимистичное сохранение строки — строка сразу показывает новые значения с признаком «сохраняется»; при успехе признак снимается, при ошибке — откат и объяснение (ФТ-9.8). Признак не меняет высоту строки (НФТ-1, ФТ-9.2).
-
ФТ-16.11. Оптимистичное добавление и удаление — новая либо удаляемая строка несёт переходный признак (создаётся / удаляется) до подтверждения источником; отмена удаления возможна в отведённое время (ФТ-11.1).
-
ФТ-16.12. Групповая операция — прогресс операции над выборкой; для длительной — с долей выполнения (ФТ-11.4); панель действий отражает, что операция идёт, и не допускает её повторного запуска.
-
ФТ-16.13. Поступление обновления в реальном времени — изменённая строка отмечается ненавязчивым переходным признаком, чтобы изменение под курсором не прошло незамеченным (ФТ-13.10); признак не смещает разметку.
-
ФТ-16.14. Доступность. Область в состоянии загрузки помечается программно как занятая, чтобы вспомогательные технологии не читали заполнители как данные. Завершение значимых операций — загрузки набора, применения фильтра, групповой операции — объявляется неинтрузивно (НФТ-3.4).
-
ФТ-16.15. Порог видимости — объективный, не по субъективной оценке «долго». Правило зависит от рода индикатора:
-
Подмена содержимого — заглушки на месте ещё не пришедших данных (скелетоны первичной загрузки ФТ-16.1 и незагруженных строк в окне ФТ-16.2). Показываются сразу: их альтернатива — не «отсутствие мелькания», а пустая область, читаемая как поломка.
-
Индикатор поверх уже показанного — крутилка при сохранении строки, признак пересчёта у счётчика, индикатор применения фильтра и подобные. Включается, только если операция не завершилась за 300 мс с момента старта; завершившаяся раньше проходит без индикатора, не давая мелькания.
Порог 300 мс единый для всех индикаторов второго рода и задаётся одним параметром. * *ФТ-16.16. Уменьшенное движение.* Анимированные индикаторы (мерцание заполнителей, вращение) в режиме уменьшенного движения заменяются статичной индикацией (НФТ-3.7).
-
4.17 Клавиатурная навигация
-
ФТ-17.1. Вся функциональность, доступная мышью, доступна с клавиатуры без потери контекста (прокрутки, выбора, раскрытия, редактирования).
-
ФТ-17.2. Фокус всегда визуально различим и не теряется при подгрузке данных, смене сортировки/фильтра, перерисовке и смене плотности/темы (см. НФТ-3.5).
-
ФТ-17.3. Навигация по ячейкам и строкам использует roving tabindex (или эквивалент): в каждый момент в таблице фокусируем максимум одну ячейку/управляющий элемент.
Tab/Shift+Tabвыводят фокус из таблицы в соседние элементы страницы. -
ФТ-17.4. Базовая карта клавиш (минимальный обязательный набор):
| Клавиша / комбинация | Действие | Контекст | |
|---|---|---|---|
|
Переход к следующей / предыдущей ячейке в строке |
Обычный режим, редактирование |
|
|
Переход к следующей / предыдущей строке (та же колонка) |
Обычный режим |
|
|
Первая / последняя ячейка текущей строки |
Обычный режим |
|
|
Первая / последняя строка набора (та же колонка) |
Обычный режим |
|
|
Прокрутка на высоту видимой области |
Обычный режим |
|
|
Переключить выбор текущей строки |
Обычный режим |
|
|
Выбор диапазона от якорной строки |
Обычный режим |
|
|
«Выбрать всё» (весь набор, ФТ-6.3) |
Обычный режим |
|
|
Войти в режим редактирования строки |
Обычный режим, если строка редактируема |
|
|
Переход между редактируемыми полями строки |
Режим редактирования |
|
|
Применить изменения (сохранить) |
Режим редактирования |
|
|
Отменить изменения и выйти из редактирования |
Режим редактирования |
|
|
Раскрыть / свернуть строку |
На элементе раскрытия |
|
|
Активировать действие строки |
На иконке/кнопке действия |
|
|
Закрыть всплывающий элемент (меню колонки, фильтр, подтверждение) |
Любой поповер/диалог |
|
Стрелки + |
Изменение ширины колонки |
Заголовок колонки (режим изменения размера) |
-
ФТ-17.5. При получении фокуса строкой действия строки (ФТ-8.2) становятся доступны с клавиатуры так же, как при наведении.
-
ФТ-17.6. Если строка находится в режиме редактирования, любой перевод фокуса или прокрутки, при котором редактируемая строка целиком покинула бы видимую область, обрабатывается по ФТ-9.9 (диалог о несохранённых изменениях).
-
ФТ-17.7. Фокус на ячейке/строке, которая вышла из окна виртуализации, переносится на контейнер таблицы; координата (идентификатор записи + колонка) запоминается и восстанавливается при возврате строки в окно (см. Р-5).
-
ФТ-17.8. Сортировка по колонке, скрытие/показ, закрепление и изменение ширины доступны с клавиатуры через меню заголовка колонки или прямые сочетания (конкретные сочетания — на усмотрение реализации, но должны быть документированы и тестируемы).
-
ФТ-17.9. В режиме уменьшенного движения (НФТ-3.7) фокусные кольца и индикаторы остаются статичными, без анимации появления.
-
ФТ-17.10. Карта клавиш и поведение не зависят от конкретной колонки или поля записи (§1).
Следствия для приёмки. П-5 (сценарии С-1…С-6 полностью с клавиатуры) проверяется по таблице ФТ-17.4. Автоматический аудит доступности (П-4) включает проверку, что все интерактивные элементы достижимы с клавиатуры и имеют видимый фокус.
5. Нефункциональные требования
НФТ-1. Стабильность разметки — ключевое требование
Ни одно из перечисленных событий не должно изменять ширину колонок или вызывать горизонтальное смещение содержимого:
| Событие | Допустимое смещение | |
|---|---|---|
Подгрузка следующей порции данных при прокрутке |
0 px |
|
Замена заполнителей реальными данными |
0 px |
|
Раскрытие или сворачивание строки |
0 px |
|
Вход в режим редактирования и выход из него |
0 px |
|
Появление и исчезновение действий строки при наведении |
0 px |
|
Появление панели групповых действий |
0 px по вертикали для таблицы |
|
Прокрутка через записи с разной длиной содержимого |
0 px |
Следствие: ширины колонок не могут зависеть от содержимого ячеек ни в какой момент. Это ограничивает выбор техники разметки (§10) и делает невозможной автоширину по содержимому (ФТ-12.12).
Вертикальное смещение содержимого ниже раскрытой строки при раскрытии — ожидаемое поведение, не нарушение.
НФТ-2. Производительность
Бюджеты проверяются автоматически и блокируют сборку при нарушении.
| Метрика | Бюджет | |
|---|---|---|
Первичный рендер: 20 колонок, полный экран строк |
≤ 120 мс |
|
Кадр при прокрутке |
≤ 8 мс |
|
Кадр при изменении ширины колонки |
≤ 4 мс |
|
Отклик поля поиска до отправки запроса |
≤ 16 мс |
|
Горизонтальное смещение layout (ширины/позиции колонок) при прокрутке 10 000 записей |
0 px (см. НФТ-1) |
|
Вертикальный скачок при прокрутке мимо ранее раскрытых строк и замене заполнителей |
0 px (см. Р-4, ФТ-13.3) |
|
Узлов в DOM при наборе в 100 000 записей |
≤ 2 000 |
|
Рост потребления памяти за час непрерывной прокрутки |
≤ 10% |
«Совокупное смещение layout» разделено на горизонтальное (стабильность колонок) и вертикальное (стабильность высот распорок и кэша деталей). Последняя строка адресует сценарий С-5: таблица открыта весь рабочий день.
НФТ-3. Доступность
-
НФТ-3.1. Вся функциональность доступна с клавиатуры без мыши (см. §4.17): навигация по ячейкам и строкам, выбор, раскрытие, сортировка, изменение ширины, вход в редактирование, вызов действий.
-
НФТ-3.2. Вспомогательные технологии получают корректный полный размер набора и корректную позицию каждой строки в нём, несмотря на то что в документе присутствует лишь часть строк.
-
НФТ-3.3. Состояния — сортировка, выбор, раскрытие, недоступность — передаются программно, а не только визуально.
-
НФТ-3.4. Значимые изменения — число найденных записей, результат групповой операции, завершение подгрузки — объявляются вслух неинтрузивно.
-
НФТ-3.5. Фокус всегда видим и не теряется при подгрузке, пересортировке и перерисовке.
-
НФТ-3.6. Соответствие WCAG 2.1 AA по контрасту и размеру целей нажатия.
-
НФТ-3.7. Режим уменьшенного движения отключает анимации.
-
НФТ-3.8. Цвет не является единственным носителем смысла (в частности, для состояния записи по ФТ-1.5).
НФТ-4. Темизация и плотность
-
НФТ-4.1. Компонент поддерживает не менее двух цветовых тем и не менее трёх уровней плотности.
-
НФТ-4.2. Тема и плотность переключаются на лету, без пересоздания компонента и без потери состояния: прокрутки, выбора, раскрытия, незавершённого редактирования.
-
НФТ-4.3. Смена плотности меняет высоту строки. Компонент обязан узнать новую высоту и пересчитать расчёты, зависящие от неё, а не полагаться на зашитое значение. Значение высоты строки единственный источник истины из CSS.
-
НФТ-4.4. Смена плотности не сбрасывает пользовательские ширины колонок.
-
НФТ-4.5. Компонент не поставляет собственных стилей и не задаёт цвета и размеры напрямую — он потребляет их из дизайн-системы. Новое визуальное состояние заводится в дизайн-системе, а не в коде компонента.
НФТ-5. Переиспользуемость
-
НФТ-5.1. На одной странице может работать несколько инстансов одновременно, с независимыми состояниями и настройками.
-
НФТ-5.2. Возможности включаются и выключаются по отдельности. Инстанс, которому нужны только отображение и сортировка, не платит за фильтры, выбор и редактирование ни кодом, ни производительностью.
-
НФТ-5.3. Компонент не зависит от UI-фреймворка.
-
НФТ-5.4. Компонент корректно освобождает ресурсы при уничтожении: обработчики, наблюдатели, незавершённые запросы, состояния примитивов.
НФТ-6. Совместимость
-
НФТ-6.1. Последние две мажорные версии Chrome, Firefox, Safari, Edge.
-
НФТ-6.2. Тексты интерфейса выносятся из кода; форматирование чисел и дат подчиняется локали.
-
НФТ-6.3. Поддержка направления письма справа налево — целевая, но не блокирующая v1: техники разметки не должны делать её невозможной.
6. Контракт с источником данных
Требование к архитектуре, а не к реализации: без него бэкенд спроектировать нельзя, а ряд функциональных требований (ФТ-6.3, ФТ-6.6, ФТ-13.2) невыполним.
6.1 Принципы
-
К-1. Сортировка, фильтрация, поиск и выборка порций выполняются на стороне источника. Клиент не сортирует и не фильтрует данные.
-
К-2. Компонент обращается к источнику через единый интерфейс и не знает, что за ним: HTTP, WebSocket, локальный массив в памяти. Реализация «всё в памяти» должна быть возможна тем же интерфейсом.
-
К-3. Клиент не хранит весь набор. Хранится окно записей и размер набора.
6.2 Что источник обязан принимать
Позицию или курсор, размер порции, список правил сортировки, список условий фильтрации (обоих видов по ФТ-4.3), строку поиска, признак отмены запроса.
6.3 Что источник обязан возвращать
Порцию записей, признак наличия следующей порции, размер набора при текущих условиях фильтрации. Ответ разбит на страницы (курсорная или позиционная постраничность).
-
К-3а. Размер набора считается гарантированным и точным для любых условий фильтрации. Режим приблизительной оценки в v1 не требуется; полоса прокрутки строится по точному размеру с первого кадра (ФТ-13.2).
6.4 Первая порция данных: схема, значения, наборы
Вместе с первой порцией данных источник отдаёт сопутствующие сведения. Каждое из них компонент обязан уметь принять и отдельным запросом — источник вправе отдавать их асинхронно, и ожидание не блокирует показ данных (ФТ-4.7, ФТ-4.26).
-
К-4. Источник владеет словарём фильтруемых полей. Клиент его не выводит из описания колонок и не проверяет условия по существу (ФТ-4.9).
-
К-5. Схема фильтрации: перечень полей с типами и допустимыми операторами, перечень именованных условий с их параметрами, человекочитаемые подписи. Опционально: при отсутствии берётся из конфигурации инстанса (ФТ-4.6).
-
К-5а. Подсказки представления колонок, в том числе исходные ширины. Опционально: при отсутствии ширина берётся из конфигурации инстанса. Приоритет применения — §11, Р-3.
-
К-6. Для полей-перечислений — перечень фактических значений по полю с учётом остальных условий. По мощности перечня компонент выбирает элемент ввода (ФТ-4.19): пусто → текстовое поле, до 10 → select, свыше → combobox.
-
К-7. Сохранённые наборы (условия, поиск, сортировка, настройки колонок — ФТ-4.21), с разделением на личные и общие.
6.5 Права
-
К-8. Права проверяются на стороне источника. Признак «можно ли редактировать запись», «можно ли править общий набор» и подобные компонент не вычисляет и заранее не запрашивает — он узнаёт результат по статусу ответа на соответствующую операцию (ФТ-9.8а, ФТ-4.25).
6.6 Дополнительно
-
К-9. Для групповых операций — приём описания выборки вместо перечня идентификаторов (ФТ-6.6).
-
К-10. Операции создания, изменения и удаления — опциональны; их отсутствие отключает соответствующие возможности интерфейса.
-
К-11. Хранение сохранённых наборов — на стороне источника; при отсутствии поддержки наборы хранятся локально и не разделяются между пользователями (ФТ-4.26).
-
К-12. Версии записей и оптимистические блокировки в v1 не используются: сохранение работает по принципу «последний выигрывает». Обновление в реальном времени (§6.7) может изменить запись под пользователем; конфликт при сохранении отдельно не разрешается.
6.7 Обновление в реальном времени
-
К-13. Записи могут меняться на стороне источника, пока пользователь смотрит на таблицу. Компонент предоставляет точку расширения (hook) для приёма таких обновлений и применения их к окну кэша, не завязанную на конкретный транспорт.
-
К-14. Транспорт обновлений выбирается на следующем шаге; рассматриваются SSE, WebSocket и WebTransport (браузерный API, Baseline 2026) либо их сочетание. Требование v1 — наличие самой точки расширения и корректное обновление видимых строк и размера набора при поступлении события (см. Р-13). Точка расширения транспортно-нейтральна: добавление WebTransport-реализации не должно затрагивать ядро (НФТ-5.3).
7. Требования к конфигурации инстанса
Уровень контракта, не сигнатур. Конкретный вид API определяется на этапе проектирования.
7.1 Инстанс обязан задавать
Корневой элемент, идентификатор (для раздельного сохранения настроек), описание колонок, источник данных, способ получения устойчивого идентификатора записи.
7.2 Инстанс может задавать
Режим постраничности; схему фильтрации как запасной вариант на случай, если источник её не отдаёт (ФТ-4.6); состав действий строки и групповых действий; наличие и содержимое деталей; потолок высоты деталей и оценку высоты (§11, Р-4); режим редактирования; режим добавления; политику удаления; источник обновлений в реальном времени как точку расширения (§6.7); набор включённых возможностей; начальное состояние; политику сохранения настроек.
7.3 Описание колонки обязано покрывать
Идентификатор; заголовок; способ извлечения значения из записи; способ отрисовки значения; исходная, минимальная и максимальная ширина; выравнивание; моноширинный набор (meta.mono → data-mono), отдельно от выравнивания; участие в сортировке, изменении ширины, скрытии, закреплении, поиске; поле сортировки, если оно отличается от идентификатора колонки (ФТ-3.8); ссылку на фильтруемое поле, если колонка с ним связана (ФТ-4.2); способ редактирования либо его отсутствие.
Тип и операторы фильтра описанием колонки не задаются — они принадлежат схеме фильтрации (§6.4). Колонка лишь указывает, с каким полем она связана.
Если колонка ссылается на фильтруемое поле, а в полученной схеме этого поля нет, условие по нему обрабатывается как нераспознанное по ФТ-4.8: помечается, отключается, остаётся видимым для удаления. Молчаливое игнорирование связи колонка→поле недопустимо.
7.4 Описание фильтруемого поля обязано покрывать
Идентификатор; человекочитаемую подпись; тип; допустимые операторы; для перечислений — источник значений (готовый список либо запрос к источнику данных); опционально — собственный способ ввода и формат значения (ФТ-4.28).
Для именованного условия: идентификатор; подпись; описание смысла; перечень параметров, если они есть.
7.5 Компонент обязан предоставлять
Программное управление: обновить данные, сбросить состояние, прочитать и записать состояние, прочитать и очистить выборку, прокрутить к записи, начать редактирование записи, сменить тему и плотность, уничтожить инстанс.
Уведомления о: изменении состояния, изменении выборки, действии по строке, раскрытии, начале и завершении редактирования, ошибке.
8. Критерии приёмки
-
П-1. Все ФТ покрыты автоматическими тестами.
-
П-2. НФТ-1 проверяется инструментально: снимок геометрии всех заголовков до и после каждого события из таблицы НФТ-1; расхождение нулевое. Дополнительно — после 50 циклов «раскрыть/свернуть + изменение ширины колонки + смена плотности» геометрия заголовков идентична исходному снимку.
-
П-3. Бюджеты НФТ-2 проверяются в CI на эталонном наборе в 100 000 записей.
-
П-3а. Ветка деградации при неизвестном размере набора (ФТ-13.8) покрыта отдельным сценарием: постраничная навигация недоступна, счётчик и полоса прокрутки ведут себя по ФТ-13.8, остальные возможности (сортировка, фильтры, выбор, редактирование) работоспособны.
-
П-4. Автоматический аудит доступности проходит на всех состояниях (§4.14), в обеих темах, во всех плотностях.
-
П-5. Сценарии С-1…С-6 проходятся полностью с клавиатуры (проверка по таблице ФТ-17.4).
-
П-6. Существуют три эталонных инстанса: минимальный (5 колонок, только чтение и сортировка), полный (20 колонок, горизонтальная прокрутка, все возможности), нестандартный (собственные типы фильтров и рендереры). Что нельзя выразить этими тремя конфигурациями — считается дырой в API.
-
П-7. Ни одно требование не выполнено ценой ветвления по имени конкретной колонки или поля (§1).
9. Открытые вопросы
Все вопросы этого раздела закрыты. Открытым остаётся только выбор транспорта обновлений (см. Р-13, §10) — он вынесен на отдельный шаг реализации.
-
~~Ширина области деталей.~~ Решено (§11, Р-4): ширина видимой области, без горизонтальной прокрутки.
-
~~Размер набора от бэкенда.~~ Решено (К-3а): точный размер гарантирован при любых условиях, ответ разбит на страницы. Режим приблизительной оценки не нужен.
-
~~Перечни значений для фильтров.~~ Решено (К-6, ФТ-4.19): приходят с первой порцией с учётом остальных условий; по мощности перечня — текстовое поле (пусто) / select (≤10) / combobox (>10). 3а. ~~Схема фильтрации.~~ Решено (К-5, §6.4): приходит с первой порцией, допускается отдельный запрос; ожидание не блокирует данные. 3б. ~~Сохранённые наборы.~~ Решено (ФТ-4.21…4.26): включают фильтры, поиск, сортировку и настройки колонок; приходят с первой порцией, допускается отдельный запрос; хранятся на источнике. Взаимодействие с автосохранением колонок разрешено в ФТ-4.23. 3в. ~~Общие наборы.~~ Решено (ФТ-4.25, К-8): право проверяется на источнике, компонент узнаёт по статусу ответа.
-
~~Права на запись.~~ Решено (ФТ-9.8а, К-8): заранее не проверяются; отказ возвращается статусом ответа при сохранении.
-
~~Конкурентное редактирование.~~ Решено (К-12): версии записей не используются, «последний выигрывает».
-
~~Узкие экраны.~~ Решено (§11, Р-1): горизонтальная прокрутка с закреплённой первой колонкой плюс адаптивное скрытие низкоприоритетных колонок (ФТ-12.13). Карточный режим отклонён.
-
~~Предел закрепления колонок.~~ Решено (ФТ-12.16): закреплённая зона прокручивается внутри себя; запрета и автооткрепления нет.
-
~~Обновление в реальном времени.~~ Решено частично (К-13, ФТ-13.9…13.12): обновления нужны, компонент даёт точку расширения; выбор транспорта (SSE / WebSocket / WebTransport / сочетание) вынесен в Р-13.
10. Отложенные решения
Принимаются на этапе проектирования реализации, после утверждения требований. Здесь зафиксированы сами развилки и критерии выбора — чтобы решения не принимались походя.
| № | Развилка | Статус | Чем определяется | |
|---|---|---|---|---|
Р-1 |
Техника разметки таблицы: нативная табличная разметка либо сеточная на произвольных элементах |
Решено → §11 |
НФТ-1 (ширины не зависят от содержимого), НФТ-3.2 (структура для вспомогательных технологий), НФТ-2 (бюджет кадра), стоимость поддержки в дизайн-системе |
|
Р-2 |
Техника отображения только видимых строк и совместимость выбранного способа с Р-1 |
Решено → §11 |
НФТ-2, ФТ-13.2, ФТ-13.3 |
|
Р-3 |
Источник и применение ширин колонок |
Решено → §11 |
НФТ-1, ФТ-12.1, бюджет кадра при перетаскивании (НФТ-2) |
|
Р-4 |
Обработка переменной высоты деталей (ФТ-7.7) при частичном рендере |
Решено → §11 |
НФТ-1, НФТ-2, ФТ-7.6 |
|
Р-5 |
Стратегия повторного использования узлов при прокрутке и её совместимость с состояниями строки (редактирование, раскрытие, фокус) |
Решено → §11 |
НФТ-2, ФТ-9.9 |
|
Р-6 |
Состояние инстанса и связь с хранилищем приложения (Zustand — стор приложения, не компонента); подсистемы-автоматы |
Решено → §11 |
НФТ-5.3, К-1, ФТ-15 |
|
Р-7 |
Библиотека интерактивных примитивов и правило их жизненного цикла при частичном рендере |
Решено → §11 |
НФТ-2 (число живых объектов не должно зависеть от числа строк), НФТ-3, НФТ-5.4 |
|
Р-8 |
Способ доставки стилей и граница между компонентом и дизайн-системой |
Решено → §11 |
НФТ-4.5, НФТ-4.1 |
|
Р-9 |
Механизм реакции на смену темы и плотности |
Решено → §11 (CSS custom properties) |
НФТ-4.2, НФТ-4.3 |
|
Р-10 |
Способ хранения пользовательских настроек и формат в адресе страницы |
Решено → §11 |
ФТ-15 |
|
Р-11 |
Структура пакета и точки расширения для отключаемых возможностей |
Решено → §11 (роль движка табличной модели: нужен для данных, не нужен для режимов — версия 1.5) + |
НФТ-5.2 |
|
Р-12 |
Форма представления условий обоих видов (ФТ-4.3) в едином наборе и в адресе страницы |
Решено → §11 (в составе Р-10) |
ФТ-4.1, ФТ-4.8, ФТ-15.2, ФТ-15.4 |
|
Р-13 |
Транспорт обновлений в реальном времени: SSE, WebSocket, WebTransport (Baseline 2026) либо их сочетание, за общей точкой расширения (§6.7) |
Открыто (следующий шаг) |
ФТ-13.9…13.12, К-13, К-14 |
Приняты и зафиксированы в §11: Р-1, Р-2, Р-3, Р-4, Р-5, Р-6, Р-7, Р-8, Р-9, Р-10, Р-12 (в составе Р-10).
Открыта одна развилка полностью и одна частично:
* Р-11 (частично) — роль движка табличной модели решена вместе с Р-6 (версия 1.5): движок нужен для данных (табличная модель — @tanstack/table-core), не нужен для режимов (FSM — по-прежнему собственная zag-машина). Открыта только структура пакета (размещение файлов, порядок инициализации) и форма точек расширения для отключаемых возможностей (НФТ-5.2) — решаются на этапе архитектурного проектирования.
* Р-13 — транспорт обновлений; в v1 фиксируется только транспортно-нейтральная точка расширения, выбор транспорта (SSE / WebSocket / WebTransport / сочетание) — отдельный шаг.
До написания функциональности принятые решения подтверждаются прототипом, проверяющим НФТ-1 и бюджет кадра из НФТ-2 на эталонном наборе.
11. Принятые архитектурные решения
Раздел документирует как, в отличие от §4–§7. Он производен от требований и обновляется, если требования меняются. Каждое решение сопровождается следствиями, которые становятся проверяемыми наравне с ФТ.
Р-1. Разметка — нативная таблица
Решение. Нативная табличная разметка (table / thead / tbody / th / td) с фиксированным табличным раскладом, при котором ширины колонок берутся из явной колоночной группы, а не из содержимого ячеек. Сеточная разметка на произвольных элементах отклонена.
Обоснование. * Фиксированный табличный раскладка делает НФТ-1 свойством браузера, а не дисциплины кода: ширина колонки не зависит от содержимого ни в один момент. Подгрузка данных, замена заполнителей, длинный текст (усекается), строка деталей на всю ширину — ничто не двигает колонки. * Структура таблицы даёт вспомогательным технологиям роли строк, заголовков и ячеек без ручной разметки ролей (НФТ-3.2). * Строка деталей на всю ширину выражается штатным объединением ячеек. * Единая колоночная группа — единственная точка записи ширин, к которой обращается изменение размера (Р-3).
Следствия (проверяемы). * С-1.1. Ни одно событие из таблицы НФТ-1 не меняет геометрию заголовков (тест П-2). * С-1.2. Роли строк и ячеек доступны вспомогательным технологиям без переопределения (аудит П-4).
Узкие экраны. Отдельного режима отображения нет. Применяются: горизонтальная прокрутка с закреплённой первой колонкой (ФТ-12.8) и адаптивное скрытие низкоприоритетных колонок по порогу ширины (ФТ-12.13…12.15). Скрытие управляется программно из наблюдателя за шириной области и пишет в тот же механизм видимости, что и пользовательская настройка, — двух источников правды о видимости колонки не существует. Перестроение в карточный вид отклонено: оно ломает НФТ-1, лишает фиксированный раскладку предмета и убирает роли таблицы из дерева доступности.
Р-2. Показ только видимых строк — распорки
Решение (уточнено версией 1.5). Видимое окно строк реализуется двумя строками-распорками (сверху и снизу от пула отрисованных строк), высота которых равна суммарной высоте непоказанных строк соответствующей стороны. Числа для распорок и диапазон индексов отрисовки считает virtualizer.getVirtualItems()/getTotalSize() из @tanstack/virtual-core 3.17.7 в padding-режиме: верхняя распорка = items[0].start, нижняя = getTotalSize() - items[last].end, диапазон отрисовки = items[0].index … items[last].index. Абсолютное позиционирование строк по-прежнему отклонено как несовместимое с нативным табличным потоком (Р-1) — это дефолтный режим virtual-core, но не используемый здесь.
Обоснование пересмотра. virtual-core не диктует разметку — он только считает диапазон видимых индексов и офсеты по высоте контейнера, позиции скролла и (переменной, Р-4) высоте строки. Padding-режим — документированный паттерн использования библиотеки, буквально совпадающий с уже принятым приёмом распорок, и ничего не меняет в Р-1. Переход не вызван конфликтом с Р-1 (его нет) — это перенос ранее ручной, слабо покрытой тестами арифметики в общий протестированный движок. Пул переиспользуемых узлов и стратегия работы со строками с активным состоянием (Р-5) virtual-core не затрагивает — он не заводит и не переиспользует DOM-узлы.
Следствия.
* С-2.1. Распорки не участвуют в чередовании фона, наведении и нумерации и невидимы для вспомогательных технологий.
* С-2.2. Число строк в документе ограничено размером пула независимо от размера набора (НФТ-2, бюджет узлов DOM).
* С-2.3. Полная высота полотна и, следовательно, размер полосы прокрутки известны с первого кадра из размера набора (ФТ-13.2).
* С-2.4. У высоты распорок и диапазона отрисовки ровно один источник истины (virtualizer); параллельного ручного расчёта того же диапазона/офсетов в кодовой базе не существует.
Р-4. Переменная высота деталей
Решение. Высота деталей измеряется по факту отрисовки и кэшируется по идентификатору записи. Опорные параметры: * ширина деталей — ширина видимой области, от ширин колонок не зависит; * оценка высоты — одна на инстанс, задаётся в конфигурации (число и состав полей деталей на этом этапе известны); * потолок высоты — высота трёх строк; сверх него детали прокручиваются внутри себя (ФТ-7.7).
Почему скачок прокрутки практически исключён. * Переменную высоту имеют только раскрытые строки; обычные фиксированы токеном высоты строки. * Раскрытие — всегда явное действие пользователя, никогда не побочный эффект прокрутки: в момент измерения строка уже в видимой области, прокрутка не корректируется. * Измеренная высота кэшируется по идентификатору записи; при повторном входе строки в окно высота известна точно — прокрутка мимо ранее раскрытой строки не дёргается. * Промах оценки ограничен сверху потолком (высота двух строк), поэтому «плыть» прокрутке не на что.
Инвалидация кэша высот: обновление данных строки, смена плотности, смена темы. Изменение ширин колонок кэш не инвалидирует — ширина деталей от них не зависит.
Следствия. * С-4.1. Вертикальный скачок при прокрутке мимо ранее раскрытой строки отсутствует (тест на нулевой сдвиг, П-2, расширенный на вертикаль). * С-4.2. Горизонтальное изменение ширины колонки не пересчитывает высоту деталей.
Р-5. Переиспользование узлов и строки с состоянием
Решение. Пул переиспользуемых узлов обслуживает только строки-отображения. Строки, несущие живое состояние — редактируемая, раскрытые, сфокусированная, — исключаются из пула и отрисовываются выделенными узлами, привязанными к идентификатору записи. Узел вынесенной строки в пул не возвращается, пока состояние активно.
Уточнение (версия 1.5). Диапазон видимых индексов, из которого пул подбирает строки для показа, начиная с этой редакции — вывод @tanstack/virtual-core (Р-2), а не результат ручного расчёта. virtual-core не заводит и не переиспользует DOM-узлы, не знает ни о пуле, ни о строках с активным состоянием — reconciliation между диапазоном индексов и тем, какой записи какой узел (пул/выделенный) достаётся, остаётся кодом @nova/kit, как и раньше.
Редактируемая строка. Реализует ФТ-9.9. Любой перевод прокрутки или фокуса, при котором строка покинула бы видимую область целиком, откладывается до ответа в модальном диалоге; при отказе — не выполняется, при согласии — редактирование сбрасывается и действие выполняется. Как следствие редактируемая строка всегда остаётся в видимой области, а механизм её удержания вне окна не требуется.
Сфокусированная строка. При выходе из окна фокус переносится на контейнер таблицы, координата фокуса (запись, колонка) запоминается; при возврате строки в окно фокус восстанавливается на ту же ячейку (НФТ-3.5, ФТ-17.7).
Раскрытая строка. Переиспользованию не подлежит; при сворачивании вызывается очистка отрисовщика деталей (обработчики, наблюдатели), после чего узел возвращается в пул (НФТ-5.4).
Следствия. * С-5.1. Узел строки с активным состоянием не оказывается в пуле переиспользования (модульный тест). * С-5.2. Данные редактирования не теряются при попытке прокрутки (тест сценария по всем источникам из ФТ-9.9). * С-5.3. Свёрнутая строка не оставляет висящих обработчиков и наблюдателей (тест на утечки).
Р-7. Примитивы — выделенная библиотека поверх zag.js
Решение. Интерактивные примитивы (меню, поповер, диалог, всплывающая подсказка, поля выбора, выбор даты, уведомления, переключатели) поставляются выделенной библиотекой ui-kit, построенной на zag.js. Таблица потребляет примитивы через неё, а не инстанцирует машины напрямую.
Правило жизненного цикла — ключевое для НФТ-2. Число живых машин не зависит от числа строк. Машина на строку при частичном рендере означала бы создание и уничтожение тысяч объектов в секунду при прокрутке. Поэтому: * элементы управления внутри строк-отображений, повторяющиеся на каждой строке (переключатель выбора), реализуются без машины на строку; их поведение и разметка доступности задаются напрямую; * разделяемые оверлейные примитивы (меню колонки, поповер фильтра, меню действий, всплывающая подсказка) существуют в одном экземпляре на таблицу и переносят точку привязки к активному элементу; * машины полей редактирования создаются только для строки в режиме редактирования и уничтожаются при выходе из него; * диалог подтверждения и уведомления — по одному экземпляру, создаются лениво.
Отличие от собственного macro-автомата таблицы (Р-6). Примитивы выше — то, что таблица потребляет из общей библиотеки готовых zag-машин. Отдельно от них таблица авторит одну собственную zag-машину для своих семи macro-режимов (Р-6) — готовой машины для этого набора состояний в zag.js нет. Оба вида подчиняются одному и тому же правилу «число живых машин не зависит от числа строк»: разделяемые примитивы — по одному экземпляру на таблицу, собственная macro-машина — тоже по одному экземпляру на инстанс.
Следствия. * С-7.1. Число живых машин примитивов и собственной macro-машины на инстанс не превышает малой константы независимо от размера набора (тест: снять счётчик машин при наборе в 100 000 записей). * С-7.2. Уничтожение инстанса освобождает все машины, обработчики и наблюдатели (тест на утечки, НФТ-5.4).
Р-6. Состояние инстанса и связь с хранилищем приложения
Разграничение. Zustand — хранилище приложения, в которое встраивается таблица, а не собственное хранилище компонента. Компонент на Zustand не завязан: приложение-потребитель может использовать любой стор, и таблица обязана в него встраиваться (НФТ-5.3, НФТ-5.1). Поэтому состояние делится на два слоя.
Внутреннее состояние инстанса. Инстанс владеет своим состоянием сам, не завися от внешнего стора, но с версии 1.5 — через два хранилища, а не одно. @tanstack/table-core (см. ниже) владеет табличной моделью: сортировка, условия фильтрации уровня колонки, настройки колонок (ширины, порядок, видимость, закрепление). Собственное лёгкое реактивное ядро компонента, как и раньше, владеет остальным: выбор, раскрытие, окно кэша и его метаданные (размер набора, статусы загрузки), состояние подсистем-автоматов (ниже). Подписка на срезы обоих хранилищ даёт точечную перерисовку (НФТ-2); несколько инстансов на странице не делят состояние (НФТ-5.1); уничтожение инстанса освобождает состояние в обоих хранилищах (НФТ-5.4).
Связь с хранилищем приложения. Компонент публикует своё состояние и события через API (§7.5: чтение и запись состояния, подписка, события). Приложение связывает нужные срезы состояния инстанса со своим Zustand — двусторонним адаптером поверх этого API, а не прямым доступом к внутренностям. Адаптер — тонкий слой на стороне приложения либо опциональный пакет-мост; в ядре компонента зависимости от Zustand нет. Источник истины по внутреннему состоянию — инстанс; приложение зеркалирует и управляет им через публичный контракт.
Граница с движком табличной модели (пересмотрено версией 1.5). Прежний вывод этого раздела — «отдельный headless-движок табличной модели не требуется» — снимается для модели данных и подтверждается для режимов (FSM). @tanstack/table-core 9.1.2 владеет ColumnDef<Row>[], SortingState, ColumnFiltersState (только для фильтров уровня колонки, не для именованных условий — граница описана в NOVA-Grid-Architecture.md §7), видимостью/порядком/закреплением/шириной колонок и row model, сконфигурированной в manual/server-side режиме: getCoreRowModel() без клиентской сортировки/фильтрации/пагинации (manualSorting, manualFiltering, manualPagination: true) — реальные сортировка, фильтрация и постраничная загрузка остаются на сервере через DataSource (К-1 не нарушается). table-core не имеет понятия FSM и не затрагивает семь macro-режимов ниже — переходы режимов по-прежнему целиком покрывает zag-машина. Пример границы: изменение SortingState вызывает onSortingChange table-core → это триггерит переход подсистемы «применение фильтра/сортировки/поиска» (settled → applying, отмена незавершённых запросов, ФТ-13.6, сама таблица переходов не меняется) → адаптер (NOVA-Grid-Architecture.md §7) строит запрос страницы данных из состояния table-core → ответ переводит подсистему applying → settled. Технической структуре пакета (размещение файлов, порядок инициализации, форма точек расширения для отключаемых возможностей, НФТ-5.2) это не мешает — она по-прежнему решается на этапе архитектурного проектирования. Риск конкурирующих хранилищ снимается тем же способом, что и раньше: единственный источник истины на каждый срез состояния — либо table-core, либо реактивное ядро, никогда оба сразу.
Подсистемы-автоматы (FSM). Часть внутреннего состояния имеет форму конечного автомата — набор режимов с запрещёнными переходами. Начиная с версии 1.4 эти автоматы авторятся как обычные zag-машины (@zag-js/core createMachine) и исполняются через тот же VanillaMachine/PartResolver из packages/core, которым пользуются остальные компоненты кита (menu, combobox, tree-view, date-picker, dialog, splitter) — тот же connect()/normalizeProps-контракт и детерминированный id (см. Р-8). Готовой машины для таблиц в самом zag.js нет, поэтому машина для этих семи режимов авторится в этом пакете как собственная, а не берётся из официального набора @zag-js/* — в отличие от разделяемых оверлейных примитивов Р-7.
Экземпляр этой машины — один на инстанс таблицы, независимо от размера набора и числа отрисованных строк: правило Р-7 «число живых машин не зависит от числа строк» распространяется и на неё. Разметка и интерактивность строк/ячеек (Р-1, Р-2, Р-5) по-прежнему не управляются частями ([data-part]) этой или любой другой машины и остаются нативной table-разметкой с пулом узлов — это не смягчается, поскольку продиктовано бюджетом НФТ-2 (≤2000 DOM-узлов, ≤8 мс кадр при прокрутке на 100 000 записей), который машина на строку или на ячейку нарушила бы так же, как нарушила бы у tree-view или listbox-части combobox в этом же ките.
Правило: автомат — для режимов, обычное значение — для данных. Сортировка, фильтры, ширины и т.п. — значения, не состояния автомата; с версии 1.5 эти значения физически живут в @tanstack/table-core, а не в реактивном ядре (см. выше). Автоматами моделируются:
| Подсистема | Состояния (эскиз) | Ключевые guard’ы и эффекты | ||
|---|---|---|---|---|
Редактирование строки |
`idle → editing → validating → saving → (saved → idle \ |
error → editing)` |
нельзя сохранить при невалидном поле (ФТ-9.5); нельзя начать правку другой строки с несохранённым черновиком без подтверждения (ФТ-9.7); увод строки из окна перехватывается диалогом (ФТ-9.9, Р-5); откат при ошибке (ФТ-9.8) |
|
Загрузка окна данных |
`idle → loading → (loaded \ |
error) ; loaded → loadingMore` |
один запрос на направление, отмена предыдущего, отбрасывание устаревшего ответа (ФТ-13.5, ФТ-13.6) |
|
Групповая операция |
`idle → confirming → running → (done \ |
error)` |
подтверждение с числом записей (ФТ-11.2); запрет повторного запуска во время выполнения (ФТ-16.12); прогресс (ФТ-11.4) |
|
Изменение ширины (drag) |
`idle → dragging → (committed \ |
cancelled)` |
ограничения min/max во время перетаскивания (ФТ-12.2); отмена по Esc; запись в единую точку ширин (Р-3) |
|
Режим выбора |
|
«выбрать всё» переводит в |
||
Добавление записи |
`idle → drafting → saving → (added \ |
error)` |
черновая строка вне виртуализации и видна независимо от прокрутки (ФТ-10.3) |
|
Применение фильтра/сортировки/поиска |
|
сброс окна и прокрутки, отмена незавершённых запросов (ФТ-13.6); сцепление с автоматом загрузки |
Остальные подсистемы (позиция прокрутки, содержимое кэша, наборы значений фильтров) автоматами не являются и живут как данные.
Следствия.
* С-6.1. В ядре компонента нет зависимости от Zustand (хотя есть зависимость от @zag-js/core/packages/core и, с версии 1.5, от @tanstack/table-core, как у остальных компонентов кита с их зависимостями от @zag-js/*); таблица встраивается в приложение с произвольным стором через публичный API (тест: инстанс работает без Zustand в окружении).
* С-6.2. Состояние инстанса не пересекается с состоянием других инстансов (тест: два инстанса, изменение в одном не влияет на другой).
* С-6.3. Изменение среза состояния не вызывает перерисовку не подписанных на него участков (тест счётчиков перерисовки).
* С-6.4. Каждый автомат из перечня проверяется изолированно, без DOM: допустимые переходы проходят, недопустимые отклоняются, недопустимые состояния непредставимы (модульные тесты переходов).
* С-6.5. Двусторонний адаптер синхронизирует срез состояния инстанса с внешним стором в обе стороны без расхождения (тест round-trip через публичный API).
* С-6.6. Число живых экземпляров macro-машины равно числу инстансов таблицы на странице и не растёт с размером набора или числом отрисованных строк (тест: снять счётчик машин при наборе в 100 000 записей, ср. С-7.1).
* С-6.7. Состояние table-core проходит через тот же публичный API (§7.5), что и остальное внутреннее состояние: снаружи нет разницы между чтением/записью сортировки и чтением/записью выбора или раскрытия (тест round-trip через публичный API, ср. С-6.5).
Р-8. Форма компонента и контракт стилей
Решение. NOVAGrid — не custom element и не отдельный рантайм: light DOM компонент на общем базовом классе packages/core (VanillaMachine + PartResolver), как и остальные компоненты кита. Стилевой контракт — тот же, что у всех: атрибуты [data-scope="grid"] / [data-part="…"] / [data-state="…"], @scope с корнем на содержательный узел и пределом to ([data-nova-slot]), слой nova.components. Собственных классов на частях компонент не заводит.
Обоснование. Форма компонента оставалась открытой, пока не было решено, авторится ли macro-автомат (Р-6) как zag-машина. После этого решения вопрос снимается естественным образом: раз ядро компонента — обычный потребитель packages/core, как menu/combobox/tree-view/date-picker/dialog/splitter, у него нет причин иметь другую форму компонента или другой стилевой контракт, чем у соседей по киту. Ранее написанный table.css со скоупом на классе .nova-table переносится в grid.css со скоупом [data-scope="grid"] на этапе реализации.
Следствия.
* С-8.1. Стили таблицы проходят тот же Definition of Done, что и остальные компоненты кита: слой nova.components, @scope на содержательном узле, отсутствие собственных классов на частях.
* С-8.2. Таблица не инлайнит значения цветов/размеров и не заводит собственные CSS-переменные с именами дизайн-системы (НФТ-4.5).
Р-9. Реакция на тему и плотность — через CSS custom properties
Решение. Тема и плотность выражены дизайн-токенами в CSS custom properties. Смена темы или плотности — это смена значений переменных на предке (переключение атрибута/класса), после чего внешний вид меняется силами каскада, без перестроения разметки и без пересоздания компонента (НФТ-4.2). Единственный источник истины из CSS.
Что остаётся на компоненте. Смена плотности меняет высоту строки (НФТ-4.3). Само значение приходит из переменной, но расчёты, зависящие от высоты, живут в JS (виртуализатор, Р-2/Р-4). Поэтому компонент наблюдает за сменой темы/плотности и, при ней: перечитывает высоту строки и высоту заголовка из вычисленных стилей, пересчитывает виртуализатор, сбрасывает кэш измеренных высот деталей (Р-4), пересчитывает смещения закреплённых колонок. Пользовательские ширины колонок при этом не сбрасываются (НФТ-4.4).
Следствия. * С-9.1. Смена темы не меняет геометрию (тест на нулевой сдвиг заголовков до/после, П-2). * С-9.2. Смена плотности сохраняет прокрутку, выбор, раскрытие и незавершённое редактирование (тест сценария), а пользовательские ширины остаются (НФТ-4.4). * С-9.3. Компонент не задаёт цвета и размеры напрямую — только через токены (аудит: отсутствие захардкоженных значений, НФТ-4.5).
Р-10. Хранение настроек и формат в адресе страницы
Решение. Разделяемая часть состояния (условия фильтрации, поиск, сортировка, настройки колонок — состав по ФТ-4.21) сериализуется в адрес страницы, чтобы состоянием можно было поделиться ссылкой (ФТ-15.2). Именованный сохранённый набор транслируется в тот же адрес. Долговременное хранение наборов — на стороне источника (сервере); локальное хранилище — запасной вариант, когда источник наборы не хранит (К-11).
Разбор двух путей. * По ссылке (URL). Несёт применённое состояние целиком либо ссылку на именованный набор. Открытие такой ссылки другим пользователем воспроизводит тот же вид, если у него есть доступ к набору (право проверяет источник, Р-8/К-8). * На сервере. Именованные наборы и, при поддержке, персональные настройки колонок. Переживают смену устройства и браузера.
Совместимость. Формат сериализации версионируется; несовместимое состояние из адреса или хранилища отбрасывается без ошибки (ФТ-15.4). Настройки для колонок, которых больше нет в конфигурации, отбрасываются молча (ФТ-15.3).
Сериализация условий обоих видов (поглощает Р-12). Единый набор содержит условия двух видов — по полю (поле, оператор, значение) и именованные (идентификатор, параметры). Оба кодируются одной версионируемой формой в составе этого же URL/хранилища, без отдельного механизма. Нераспознанный вид или поле при разборе отключается видимо (ФТ-4.8, ФТ-4.27), а не роняет разбор. Отдельного решения Р-12 не требуется — форма определяется здесь.
Следствия. * С-10.1. Ссылка с сериализованным состоянием, открытая заново, воспроизводит тот же набор, сортировку и настройки колонок (тест round-trip). * С-10.2. Устаревший или повреждённый формат в адресе не роняет компонент (тест деградации).
Р-3. Ширины колонок — источник и применение
Решение. Значение ширины колонки применяется через единую точку записи, предопределённую разметкой (колоночная группа, Р-1): изменение размера пишет ширину туда, без чтения геометрии из JS — этим держится бюджет кадра при перетаскивании (НФТ-2) и стабильность разметки (НФТ-1).
Источник исходной ширины — по приоритету, сверху вниз:
| Приоритет | Источник | Когда | |
|---|---|---|---|
1 |
Пользовательская настройка |
сохранённая ширина (ФТ-15.1) или ширина из применённого набора (ФТ-4.21) |
|
2 |
Источник данных |
ширина приходит с backend вместе со схемой/первой порцией (§6.4) |
|
3 |
Конфигурация инстанса |
значение по умолчанию из описания колонки (§7.3) |
Пользовательское изменение ширины всегда перекрывает и backend, и конфигурацию и сохраняется (ФТ-15.1). «Сбросить ширину» (ФТ-12.3) возвращает к ширине уровня 2, если она есть, иначе — уровня 3. Минимум и максимум (ФТ-12.2) из конфигурации применяются к значению из любого источника. Смена плотности пользовательские ширины не трогает (НФТ-4.4).
Следствия. * С-3.1. Ширина, пришедшая с backend, применяется при отсутствии пользовательской настройки и перекрывается ею после ручного изменения (тест приоритетов). * С-3.2. Перетаскивание границы не вызывает чтения геометрии из JS и укладывается в бюджет кадра (НФТ-2, тест).