⟨/⟩ 40_Arch/Tech/Angular/ADR_UI_009_Forms.cs

164 строк · в начало

using System;
using Ban.Sdaid.Notation.Documents;
using Ban.Sdaid.Icd;

namespace Ban.Sdaid.Icd.Arch.Tech.Angular
{
    /// <summary>
    /// ADR-UI-009: Формы. Единый механизм — Signal Forms; базовое поле с плавающей меткой,
    /// состояниями и под-блоком описания (пояснение / ошибка). Валидация и submit — единым стилем.
    /// </summary>
    public class ADR_UI_009_Forms : IAdrDocument, IFolder<ArchAngularFolder>
    {
        public string Name => "ADR-UI-009. Формы";

        public string Description =>
            @"Приложению нужны формы — ввод идентификаторов, поля значений, фильтры. Фиксируем один способ:
формы на **Signal Forms** (signals-first), единое базовое поле с **плавающей меткой**, единый стиль
валидации, показа ошибок и связки со значением.";

        public string Version => "1.0";
        public string Status => "accepted";
        public string[] Comments => new[]
        {
            "2026-08-11. Принято перед реализацией форм страницы «Ввод данных».",
        };

        public Type? Supersedes => null;

        public static string S1_Context = """
            ## Контекст и постановка задачи

            Нужен **один способ** реализации форм, чтобы стиль валидации, показа ошибок и связки со
            значением был единым, а разработчик и ИИ-ассистент не выбирали подход в каждой задаче.

            Фронт строится signals-first (ADR-UI-001), поэтому формы делаем на **Signal Forms**
            (`@angular/forms/signals`) — значения и валидность и так сигналы, без моста к реактивным
            потокам. Базовое поле и его поведение берём по единому визуальному образцу (плавающая метка).
            """;

        public static string S2_DecisionDrivers = """
            ## Драйверы решения

            1. **Signals-first** — форма выражена сигналами; не заводим параллельный поток и подписки ради
               отображения.
            2. **Единый стиль** — одинаковые валидация, показ ошибок, submit во всех формах.
            3. **Типизация** — значения и валидаторы типизированы, без `any`.
            4. **Кросс-полевая и асинхронная валидация** — поддерживаются из коробки подхода.
            """;

        public static string S3_ConsideredOptions = """
            ## Рассмотренные варианты

            - **A. Template-driven** (`ngModel`) — недостаточно для составных форм и сложной валидации.
            - **B. Reactive Forms + мост в сигналы** (`toSignal(valueChanges)`) — стабильный API, но
              вводит двойной слой (реактивная форма + сигналы-отображение).
            - **C. Signal Forms** (`@angular/forms/signals`) — signal-native, без моста; стабильный
              публичный API в актуальной версии Angular.
            """;

        public static string S4_DecisionOutcome = """
            ## Решение

            Выбран **Вариант C**: формы на **Signal Forms**. Значения, валидность и состояния — сигналы;
            шаблон использует их напрямую (`@if`, `[disabled]`) без `async`-моста. Самодельных оболочек
            поверх (`createForm()` и т. п.) не вводим — используем нативный API (`form`, `field`, `Control`,
            `validate`, правила `required`/`min`/`max`/`minLength`/`email`/`pattern`, `validateAsync`,
            `submit`).

            ### Базовое поле — `Field` (плавающая метка)

            Общий атом всех полей ввода: рамка-капсула (скругление, `padding`, фон/обводка по состоянию)
            с **плавающей меткой**:

            - **пусто и не в фокусе** — метка крупным плейсхолдером внутри контрола;
            - **в фокусе или есть значение** — метка поднимается к верхней кромке, шрифт мельче, под ней —
              значение.

            **Состояния** (различаются фоном/обводкой, геометрия одна): `default`, `hover`, `focus`
            (кольцо), `error`, `error-focus`, `disable`, `autofill`.

            **Под контролом — под-блок `description`**: сюда идут **пояснение** к полю (helper, опц.) и
            **сообщение об ошибке**. То есть подпись-пояснение и ошибка — **ниже** контрола.

            **Слоты** `prefix` / `suffix`; суффикс задаёт вариант поля: `Input`, `Select` (▾),
            `Date` (календарь) — всё на одной рамке. Поле — base-компонент набора `ui/`.

            ### Раскладка форм

            - **Дефолт — поля с плавающей меткой**, в столбец. Отдельные представления (метка слева/сверху)
              пока не вводим — по необходимости (открытый вопрос).
            - **Интервалы — только токенами** (поле↔поле, поле↔футер), без «магических» px (ADR-UI-004).
            - **Футер кнопок — сразу под полями формы**, не приклеен к низу; порядок/типы кнопок — по ADR
              о кнопках.

            ### Валидация

            - **Стандартные** правила (`required`/`min`/`max`/`minLength`/`email`/`pattern`) + **кастомные**
              валидаторы-функции в `src/app/validators/` (имена глагольные, чистые функции с явным
              контрактом ошибок).
            - **Кросс-полевые** — на уровне формы; **асинхронные** — через per-page service
              (`validateAsync`/`validateHttp`), API в валидатор напрямую не тащим.
            - **Сообщение об ошибке** — рядом с полем, в его `description`; маппинг кодов ошибок в русский
              текст — в компоненте поля / общем помощнике.

            ### Submit

            1. показать ошибки (пометить поля «тронутыми» / submitted);
            2. если форма невалидна — выйти (без «тихого» отказа);
            3. вызвать метод per-page service со значением формы (или после маппинга в API-модель).

            На время отправки — сигнал `submitting` (блокировка формы), по завершении — снять.

            ### Что не используем

            - **Reactive Forms** и **Template-driven** (`ngModel`) — кроме тривиального одиночного контрола
              вне формы (например, строка поиска над списком).
            - **Самодельные оболочки** поверх форм-API.
            """;

        public static string S5_PositiveConsequences = """
            ## Положительные следствия

            - Форма — сигналы; шаблон работает с ними напрямую, без `async`-моста и лишних подписок.
            - Единое базовое поле (плавающая метка + состояния + описание) — одинаковый вид и поведение
              во всех формах.
            - Ошибки и пояснения — предсказуемо под полем.
            """;

        public static string S6_NegativeConsequences = """
            ## Отрицательные следствия и компромиссы

            - Базовое поле с плавающей меткой и всеми состояниями — заметный объём разовой работы
              (следствие собственного набора `ui/`, а не самих форм).
            - Signal Forms — молодой API: для нестандартных сценариев (динамические/вложенные формы,
              сложная кросс-полевая валидация) готовых примеров меньше, чем у зрелого Reactive, — иногда
              сверяемся с документацией, а не копируем готовый рецепт.
            """;

        public static string S7_Validation = """
            ## Проверка

            - Все формы используют Signal Forms; `ngModel` — только для одиночных контролов вне формы.
            - Тип значения формы выводится без `any`.
            - При отправке невалидной формы пользователь видит ошибки под полями — нет «тихого» отказа.
            - Интервалы полей/футера — из токенов (нет «магических» px).
            """;

        public static string S8_OpenQuestions = """
            ## Открытые вопросы / отложено

            - **Альтернативные представления форм** (метка слева/сверху) — вводим по необходимости.
            - **Помощник serverErrors** (привязка ошибок API к полям после submit) — появится с первой
              формой, обращающейся к API.
            """;

        public static string S99_Related = $"""
            ## Связанные артефакты

            - {nameof(Ban.Sdaid.Icd.Arch.Tech.Angular.ADR_UI_001_Stack)} — Signal Forms в стеке.
            - {nameof(Ban.Sdaid.Icd.Arch.Tech.Angular.ADR_UI_004_Styling)} — токены интервалов и состояний поля.
            """;
    }
}