refactor(styles): редактор панели переезжает на общий набор контролов

Набор контролов был поднят из редактора боковой панели в #594, но сама панель
осталась на своей копии тех же правил. Две копии расходятся молча: правка в
наборе не доедет до панели, и наоборот.

Просто вызвать `formKitCss` из панели нельзя. Общие правила в её листе не идут
подряд: между карточкой-группой и обводкой фокуса стоит сетка `.summary-general-grid`,
между фокусом и выключенным состоянием — `button { font: inherit }`, между ним и
рядом-переключателем — `svg`. Вставить один общий кусок значило бы переставить её
объявления, а перестановка правил равной специфичности — уже не подстановка:
проверять пришлось бы каждое правило по отдельности.

Поэтому генератор разрезан на пять именованных фрагментов — карточка-группа,
обводка фокуса, выключенное состояние, ряд-переключатель, подпись ряда, — и
панель подставляет каждый на своё место. `formKitCss` остаётся их композицией,
чтобы у диалогов не появилось пяти вызовов вместо одного.

Доказательство вместо обещания. У настроек панели нет ни одного golden-кадра: в
матрице нет сцены `dialog: 'summary'`, и «панель не изменилась» показать
пикселями нечем. Замена эталону — побайтовое сравнение собранного листа с
замороженным снимком с `origin/dev`:

    длина было 14585 стало 14585 — ПОБАЙТОВО СОВПАЛО

Две находки ревью ТЗ закрыты тестами, а не формулировками:

- проверка «копии не осталось» ловила один фрагмент из пяти (`min-height: 54px`
  встречается только в ряду-переключателе). Теперь цикл по всем пяти текстам;
  проба — фрагмент карточки-группы, оставленный в комментарии, — краснит именно
  этот цикл, а прежняя проверка была бы зелёной;
- «выход `formKitCss` остаётся байт в байт» опиралось на тесты, сравнивающие
  подстроки и счётчики. Добавлены две фикстуры с полным текстом обеих форм листа
  диалогов — с рядом-переключателем и без. Форма с переключателем сегодня не
  используется никем, и её расхождение не поймал бы никто до первого потребителя.

Три замороженные фикстуры — осознанный долг: они удаляются на том шаге эпика,
который законно меняет вид панели. Записано рядом с фикстурой.

Issue: #597
User-Visible: no
This commit is contained in:
Claude
2026-09-19 14:19:54 +00:00
committed by claude[bot]
parent 48c198ff26
commit b28907f63d
51 changed files with 1242 additions and 1388 deletions
+53 -24
View File
@@ -52,11 +52,47 @@ export const CARD_DIALOG_FORM_KIT: FormKitCssOptions = {
},
};
/** Правила, общие с панелью. Текст обязан совпадать с её листом дословно. */
function sharedCss(options: FormKitCssOptions, withSwitch: boolean): string {
const { form, prefix, cards, footer, tokens } = options;
const switchRules = !withSwitch ? '' : `
${form} .${prefix}-switch {
/**
* Правила, общие с панелью, — по одному фрагменту на правило (#597).
*
* Разрезано не ради красоты: в листе панели эти правила НЕ идут подряд, между
* ними стоят её собственные (`button { font: inherit }`, `svg { … }`). Пока
* генератор отдавал их одним куском, переезд панели на общий источник требовал
* бы переставить её объявления — то есть переписать каскад под инструмент.
* Отдельные фрагменты позволяют подставить каждый ровно туда, где он стоит
* сейчас, и собранный лист совпадает с прежним побайтово.
*/
export function formKitCardsCss({ cards, tokens }: FormKitCssOptions): string {
return ` ${cards.join(',\n ')} {
min-width: 0;
padding: 14px;
border: 1px solid var(${tokens.line});
border-radius: 11px;
background: var(${tokens.surface});
}`;
}
/** Обводка фокуса формы и её футера. */
export function formKitFocusCss({ form, footer, tokens }: FormKitCssOptions): string {
return ` ${form} input:focus-visible,
${form} select:focus-visible,
${form} button:focus-visible,
${footer} button:focus-visible {
outline: 2px solid var(${tokens.accent});
outline-offset: 2px;
}`;
}
/** Выключенные контролы. */
export function formKitDisabledCss({ form }: FormKitCssOptions): string {
return ` ${form} button:disabled,
${form} input:disabled,
${form} select:disabled { opacity: .4; cursor: default; }`;
}
/** Ряд-переключатель: подпись слева, переключатель справа. */
export function formKitSwitchRowCss({ form, prefix }: FormKitCssOptions): string {
return ` ${form} .${prefix}-switch {
display: flex;
min-width: 0;
min-height: 54px;
@@ -66,27 +102,20 @@ function sharedCss(options: FormKitCssOptions, withSwitch: boolean): string {
margin: 0;
color: var(--primary-text-color);
cursor: pointer;
}
.${prefix}-switch-caption { display: grid; min-width: 0; gap: 3px; }
}`;
}
/** Подпись ряда-переключателя: заголовок и пояснение мелким шрифтом. */
export function formKitSwitchCaptionCss({ prefix }: FormKitCssOptions): string {
return ` .${prefix}-switch-caption { display: grid; min-width: 0; gap: 3px; }
.${prefix}-switch-caption strong { font-size: .875rem; font-weight: 600; overflow-wrap: anywhere; }
.${prefix}-switch-caption small { font-size: .8125rem; color: var(--secondary-text-color); overflow-wrap: anywhere; }`;
return ` ${cards.join(',\n ')} {
min-width: 0;
padding: 14px;
border: 1px solid var(${tokens.line});
border-radius: 11px;
background: var(${tokens.surface});
}
${form} input:focus-visible,
${form} select:focus-visible,
${form} button:focus-visible,
${footer} button:focus-visible {
outline: 2px solid var(${tokens.accent});
outline-offset: 2px;
}
${form} button:disabled,
${form} input:disabled,
${form} select:disabled { opacity: .4; cursor: default; }${switchRules}`;
}
/** Все общие правила подряд — так их берут диалоги карточки. */
function sharedCss(options: FormKitCssOptions, withSwitch: boolean): string {
const switchRules = !withSwitch ? '' : `\n${formKitSwitchRowCss(options)}\n${formKitSwitchCaptionCss(options)}`;
return `${formKitCardsCss(options)}\n${formKitFocusCss(options)}\n${formKitDisabledCss(options)}${switchRules}`;
}
/**
+19 -34
View File
@@ -1,5 +1,17 @@
/** The #505 settings composition; included only in the lazy summary sheet. */
export const summaryPanelEditorCss = String.raw`
import {
SUMMARY_PANEL_FORM_KIT, formKitCardsCss, formKitDisabledCss, formKitFocusCss,
formKitSwitchCaptionCss, formKitSwitchRowCss,
} from './styles/form-kit.styles';
/**
* The #505 settings composition; included only in the lazy summary sheet.
*
* #597: карточка-группа, обводка фокуса, выключенное состояние и ряд-переключатель
* больше не описаны здесь — они приходят из общего набора (`form-kit.styles.ts`),
* который был поднят из ЭТОГО файла в #594. Подстановка, а не редизайн: собранный
* лист обязан совпасть с прежним побайтово (`test/form-kit.test.mjs`).
*/
export const summaryPanelEditorCss = `
hp-dialog[data-kind='summary'] {
--hp-dialog-wide-width: 920px;
--ha-dialog-width-md: 920px;
@@ -31,14 +43,7 @@ export const summaryPanelEditorCss = String.raw`
.summary-editor-footer * { box-sizing: border-box; }
.summary-editor h3 { margin: 0; font-size: 1rem; line-height: 1.3; }
.summary-editor p { margin: 0; overflow-wrap: anywhere; }
.summary-general,
.summary-blocks-card {
min-width: 0;
padding: 14px;
border: 1px solid var(--summary-editor-line);
border-radius: 11px;
background: var(--summary-editor-surface);
}
${formKitCardsCss(SUMMARY_PANEL_FORM_KIT)}
.summary-general-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -76,32 +81,12 @@ export const summaryPanelEditorCss = String.raw`
.summary-editor [aria-invalid='true'] {
border-color: var(--error-color, #db4437);
}
.summary-editor input:focus-visible,
.summary-editor select:focus-visible,
.summary-editor button:focus-visible,
.summary-editor-footer button:focus-visible {
outline: 2px solid var(--summary-editor-accent);
outline-offset: 2px;
}
${formKitFocusCss(SUMMARY_PANEL_FORM_KIT)}
.summary-editor button { font: inherit; }
.summary-editor button:disabled,
.summary-editor input:disabled,
.summary-editor select:disabled { opacity: .4; cursor: default; }
${formKitDisabledCss(SUMMARY_PANEL_FORM_KIT)}
.summary-editor svg { display: block; flex: 0 0 auto; width: 20px; height: 20px; }
.summary-editor .summary-switch {
display: flex;
min-width: 0;
min-height: 54px;
align-items: center;
justify-content: space-between;
gap: 12px;
margin: 0;
color: var(--primary-text-color);
cursor: pointer;
}
.summary-switch-caption { display: grid; min-width: 0; gap: 3px; }
.summary-switch-caption strong { font-size: .875rem; font-weight: 600; overflow-wrap: anywhere; }
.summary-switch-caption small { font-size: .8125rem; color: var(--secondary-text-color); overflow-wrap: anywhere; }
${formKitSwitchRowCss(SUMMARY_PANEL_FORM_KIT)}
${formKitSwitchCaptionCss(SUMMARY_PANEL_FORM_KIT)}
/* The native checkbox itself is the visible 44px hit target. No hidden
duplicate control or optimistic storage write is needed for the switch. */
.summary-editor .summary-switch input {