mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-06 06:38:57 +00:00
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:
@@ -0,0 +1,81 @@
|
||||
.hpf-card {
|
||||
min-width: 0;
|
||||
padding: 14px;
|
||||
border: 1px solid var(--hpf-line);
|
||||
border-radius: 11px;
|
||||
background: var(--hpf-surface);
|
||||
}
|
||||
.hpf-form input:focus-visible,
|
||||
.hpf-form select:focus-visible,
|
||||
.hpf-form button:focus-visible,
|
||||
.hpf-footer button:focus-visible {
|
||||
outline: 2px solid var(--hpf-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
.hpf-form button:disabled,
|
||||
.hpf-form input:disabled,
|
||||
.hpf-form select:disabled { opacity: .4; cursor: default; }
|
||||
.hpf-form .hpf-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;
|
||||
}
|
||||
.hpf-switch-caption { display: grid; min-width: 0; gap: 3px; }
|
||||
.hpf-switch-caption strong { font-size: .875rem; font-weight: 600; overflow-wrap: anywhere; }
|
||||
.hpf-switch-caption small { font-size: .8125rem; color: var(--secondary-text-color); overflow-wrap: anywhere; }
|
||||
.hpf-form { display: grid; gap: 12px; }
|
||||
.hpf-head {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
.hpf-head h3 { margin: 0; font-size: 1rem; line-height: 1.3; font-weight: 600; }
|
||||
.hpf-row {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
min-height: 44px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
.hpf-row > .hpf-label { min-width: 0; overflow-wrap: anywhere; }
|
||||
.hpf-seg {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
border: 1px solid var(--hpf-line);
|
||||
border-radius: 9px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.hpf-seg label {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
min-height: 44px;
|
||||
flex: 1 1 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 0 12px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
}
|
||||
.hpf-seg label + label { border-left: 1px solid var(--hpf-line); }
|
||||
.hpf-seg input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
|
||||
.hpf-seg label:has(input:checked) {
|
||||
background: var(--hpf-accent);
|
||||
color: var(--text-primary-color, #fff);
|
||||
}
|
||||
.hpf-seg label:has(input:focus-visible) {
|
||||
outline: 2px solid var(--hpf-accent);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
.hpf-color { display: flex; min-width: 0; align-items: center; gap: 12px; }
|
||||
.hpf-color hp-color-opacity { flex: 0 0 auto; }
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
.hpf-card {
|
||||
min-width: 0;
|
||||
padding: 14px;
|
||||
border: 1px solid var(--hpf-line);
|
||||
border-radius: 11px;
|
||||
background: var(--hpf-surface);
|
||||
}
|
||||
.hpf-form input:focus-visible,
|
||||
.hpf-form select:focus-visible,
|
||||
.hpf-form button:focus-visible,
|
||||
.hpf-footer button:focus-visible {
|
||||
outline: 2px solid var(--hpf-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
.hpf-form button:disabled,
|
||||
.hpf-form input:disabled,
|
||||
.hpf-form select:disabled { opacity: .4; cursor: default; }
|
||||
.hpf-form { display: grid; gap: 12px; }
|
||||
.hpf-head {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
.hpf-head h3 { margin: 0; font-size: 1rem; line-height: 1.3; font-weight: 600; }
|
||||
.hpf-row {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
min-height: 44px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
.hpf-row > .hpf-label { min-width: 0; overflow-wrap: anywhere; }
|
||||
.hpf-seg {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
border: 1px solid var(--hpf-line);
|
||||
border-radius: 9px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.hpf-seg label {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
min-height: 44px;
|
||||
flex: 1 1 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 0 12px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
}
|
||||
.hpf-seg label + label { border-left: 1px solid var(--hpf-line); }
|
||||
.hpf-seg input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
|
||||
.hpf-seg label:has(input:checked) {
|
||||
background: var(--hpf-accent);
|
||||
color: var(--text-primary-color, #fff);
|
||||
}
|
||||
.hpf-seg label:has(input:focus-visible) {
|
||||
outline: 2px solid var(--hpf-accent);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
.hpf-color { display: flex; min-width: 0; align-items: center; gap: 12px; }
|
||||
.hpf-color hp-color-opacity { flex: 0 0 auto; }
|
||||
+408
@@ -0,0 +1,408 @@
|
||||
|
||||
hp-dialog[data-kind='summary'] {
|
||||
--hp-dialog-wide-width: 920px;
|
||||
--ha-dialog-width-md: 920px;
|
||||
--summary-editor-surface: var(--card-background-color, #fff);
|
||||
--summary-editor-muted: color-mix(in srgb, var(--summary-editor-surface) 96%, var(--primary-text-color, #303638));
|
||||
--summary-editor-line: var(--divider-color, #dce5e7);
|
||||
--summary-editor-accent: var(--primary-color, var(--hp-accent));
|
||||
--summary-editor-accent-soft: color-mix(in srgb, var(--summary-editor-surface) 94%, var(--summary-editor-accent));
|
||||
}
|
||||
hp-dialog[data-kind='summary'] .summary-editor {
|
||||
box-sizing: border-box;
|
||||
container: summary-editor / inline-size;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
max-height: none;
|
||||
padding: 14px 17px 18px;
|
||||
gap: 12px;
|
||||
flex: 1 1 auto;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-width: thin;
|
||||
color: var(--primary-text-color);
|
||||
background: var(--summary-editor-muted);
|
||||
font-size: .875rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.summary-editor *,
|
||||
.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);
|
||||
}
|
||||
.summary-general-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px 18px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.summary-general-grid:has(> .summary-switch:only-child) { grid-template-columns: minmax(0, 1fr); }
|
||||
.summary-editor .summary-field { min-width: 0; }
|
||||
.summary-editor .summary-field > label {
|
||||
display: block;
|
||||
margin: 0 0 5px;
|
||||
color: var(--secondary-text-color);
|
||||
font-size: .8125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.summary-editor input[type='text'],
|
||||
.summary-editor input[type='search'],
|
||||
.summary-editor select {
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
min-height: 44px;
|
||||
margin: 0;
|
||||
padding: 8px 10px;
|
||||
color: var(--primary-text-color);
|
||||
background: var(--summary-editor-surface);
|
||||
border: 1px solid var(--summary-editor-line);
|
||||
border-radius: 7px;
|
||||
font: inherit;
|
||||
font-size: .875rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.summary-editor select { cursor: pointer; }
|
||||
.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;
|
||||
}
|
||||
.summary-editor button { font: inherit; }
|
||||
.summary-editor button:disabled,
|
||||
.summary-editor input:disabled,
|
||||
.summary-editor select:disabled { opacity: .4; cursor: default; }
|
||||
.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; }
|
||||
/* 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 {
|
||||
appearance: none;
|
||||
position: relative;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
flex: 0 0 44px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
.summary-switch input::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 36px;
|
||||
height: 22px;
|
||||
inset: 11px 4px;
|
||||
border: 1px solid var(--summary-editor-line);
|
||||
border-radius: 12px;
|
||||
background: color-mix(in srgb, var(--summary-editor-surface) 65%, var(--secondary-text-color));
|
||||
}
|
||||
.summary-switch input::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
top: 14px;
|
||||
left: 7px;
|
||||
border-radius: 50%;
|
||||
background: var(--text-primary-color, #fff);
|
||||
box-shadow: 0 1px 2px rgb(0 0 0 / 20%);
|
||||
}
|
||||
.summary-switch input:checked::before {
|
||||
background: var(--summary-editor-accent);
|
||||
border-color: var(--summary-editor-accent);
|
||||
}
|
||||
.summary-switch input:checked::after { left: 21px; }
|
||||
.summary-switch:has(input:disabled) { cursor: default; }
|
||||
.summary-switch:has(input:disabled) .summary-switch-caption { opacity: .6; }
|
||||
.summary-blocks-heading {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.summary-blocks-heading > div { min-width: 0; flex: 1 1 220px; }
|
||||
.summary-blocks-heading p { margin-top: 4px; color: var(--secondary-text-color); font-size: .8125rem; }
|
||||
.summary-block-count {
|
||||
padding: 4px 8px;
|
||||
border-radius: 10px;
|
||||
color: var(--primary-text-color);
|
||||
background: var(--summary-editor-accent-soft);
|
||||
font-size: .75rem;
|
||||
font-weight: 600;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.summary-editor-blocks { display: grid; min-width: 0; gap: 10px; }
|
||||
.summary-editor-block {
|
||||
min-width: 0;
|
||||
border: 1px solid var(--summary-editor-line);
|
||||
border-radius: 10px;
|
||||
background: var(--summary-editor-muted);
|
||||
}
|
||||
.summary-block-head {
|
||||
display: grid;
|
||||
grid-template-columns: 44px 92px minmax(0, 1fr) 44px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid var(--summary-editor-line);
|
||||
border-radius: 10px 10px 0 0;
|
||||
background: var(--summary-editor-surface);
|
||||
}
|
||||
.summary-editor .summary-block-title { font-weight: 700; }
|
||||
.summary-order { display: grid; grid-template-columns: repeat(2, 44px); gap: 4px; }
|
||||
.summary-icon-button,
|
||||
.summary-drag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 44px;
|
||||
min-width: 44px;
|
||||
height: 44px;
|
||||
padding: 0;
|
||||
color: var(--secondary-text-color);
|
||||
border: 1px solid var(--summary-editor-line);
|
||||
border-radius: 7px;
|
||||
background: var(--summary-editor-surface);
|
||||
cursor: pointer;
|
||||
}
|
||||
.summary-drag { border: 0; background: transparent; cursor: grab; user-select: none; }
|
||||
.summary-drag:active { cursor: grabbing; }
|
||||
.summary-drag[draggable='false'] { cursor: default; }
|
||||
.summary-icon-button:hover:not(:disabled) {
|
||||
color: var(--summary-editor-accent);
|
||||
border-color: var(--summary-editor-accent);
|
||||
background: var(--summary-editor-accent-soft);
|
||||
}
|
||||
.summary-visibility[aria-pressed='true'] {
|
||||
color: var(--summary-editor-accent);
|
||||
border-color: color-mix(in srgb, var(--summary-editor-line) 45%, var(--summary-editor-accent));
|
||||
background: var(--summary-editor-accent-soft);
|
||||
}
|
||||
.summary-scope {
|
||||
display: grid;
|
||||
grid-column: 3 / -1;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
.summary-scope > select:only-child { grid-column: 1 / -1; }
|
||||
.summary-block-title-error,
|
||||
.summary-scope-problem { grid-column: 3 / -1; }
|
||||
.summary-block-body { min-width: 0; padding: 10px; }
|
||||
.summary-editor-values { display: grid; min-width: 0; gap: 7px; }
|
||||
.summary-editor-value {
|
||||
display: grid;
|
||||
grid-template-columns: 44px 92px minmax(0, .9fr) minmax(0, 1.2fr) 44px;
|
||||
align-items: start;
|
||||
min-width: 0;
|
||||
padding: 5px;
|
||||
gap: 7px;
|
||||
border: 1px solid var(--summary-editor-line);
|
||||
border-radius: 8px;
|
||||
background: var(--summary-editor-surface);
|
||||
}
|
||||
.summary-value-label,
|
||||
.summary-source-field { min-width: 0; }
|
||||
.summary-source {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 44px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 7px 9px;
|
||||
color: var(--primary-text-color);
|
||||
text-align: left;
|
||||
border: 1px solid var(--summary-editor-line);
|
||||
border-radius: 7px;
|
||||
background: var(--summary-editor-surface);
|
||||
cursor: pointer;
|
||||
}
|
||||
.summary-source-caption { display: grid; min-width: 0; gap: 2px; }
|
||||
.summary-source-caption strong { font-size: .875rem; font-weight: 600; overflow-wrap: anywhere; }
|
||||
.summary-source-caption small { color: var(--secondary-text-color); font-size: .75rem; overflow-wrap: anywhere; }
|
||||
.summary-editor .summary-value-remove {
|
||||
color: var(--error-color, #db4437);
|
||||
border-color: transparent;
|
||||
background: color-mix(in srgb, var(--summary-editor-surface) 94%, var(--error-color, #db4437));
|
||||
}
|
||||
.summary-editor .summary-value-remove:hover:not(:disabled) { border-color: var(--error-color, #db4437); }
|
||||
.summary-source-picker {
|
||||
min-width: 0;
|
||||
margin-top: 7px;
|
||||
padding: 10px;
|
||||
border: 1px solid var(--summary-editor-line);
|
||||
border-radius: 10px;
|
||||
background: var(--summary-editor-surface);
|
||||
box-shadow: 0 6px 18px rgb(0 0 0 / 18%);
|
||||
}
|
||||
.summary-editor .summary-source-picker > label {
|
||||
display: block;
|
||||
margin: 0 0 5px;
|
||||
font-size: .8125rem;
|
||||
font-weight: 600;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.summary-source-results {
|
||||
display: grid;
|
||||
max-height: min(360px, 48vh);
|
||||
margin-top: 8px;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
.summary-source-results button {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
min-height: 44px;
|
||||
padding: 7px 9px;
|
||||
align-content: center;
|
||||
color: var(--primary-text-color);
|
||||
text-align: left;
|
||||
overflow-wrap: anywhere;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: 7px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.summary-source-results button:hover:not(:disabled),
|
||||
.summary-source-results button[aria-selected='true'] { background: var(--summary-editor-accent-soft); }
|
||||
.summary-source-results button.broken { color: var(--warning-color, #f59e0b); }
|
||||
.summary-source-results small { color: var(--secondary-text-color); font-size: .75rem; overflow-wrap: anywhere; }
|
||||
.summary-source-group {
|
||||
padding: 9px 8px 4px;
|
||||
color: var(--secondary-text-color);
|
||||
font-size: .8125rem;
|
||||
font-weight: 600;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.summary-refine { padding: 9px; color: var(--secondary-text-color); overflow-wrap: anywhere; }
|
||||
.summary-problem { margin: 5px 0; font-size: .8125rem; overflow-wrap: anywhere; }
|
||||
.summary-problem.error { color: var(--error-color, #db4437); }
|
||||
.summary-problem.warning { color: var(--warning-color, #f59e0b); }
|
||||
.summary-local-hint { color: var(--secondary-text-color); }
|
||||
.summary-block-footer { display: flex; justify-content: flex-end; margin-top: 10px; padding-top: 9px; border-top: 1px solid var(--summary-editor-line); }
|
||||
.summary-delete-block {
|
||||
min-width: 44px;
|
||||
min-height: 44px;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid color-mix(in srgb, var(--summary-editor-line) 35%, var(--error-color, #db4437));
|
||||
border-radius: 7px;
|
||||
color: var(--error-color, #db4437);
|
||||
background: var(--summary-editor-surface);
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
cursor: pointer;
|
||||
}
|
||||
.summary-add {
|
||||
display: inline-flex;
|
||||
min-width: 44px;
|
||||
min-height: 44px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 7px;
|
||||
padding: 8px 12px;
|
||||
border: 1px dashed color-mix(in srgb, var(--summary-editor-line) 40%, var(--summary-editor-accent));
|
||||
border-radius: 8px;
|
||||
color: var(--summary-editor-accent);
|
||||
background: var(--summary-editor-accent-soft);
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
cursor: pointer;
|
||||
}
|
||||
.summary-editor .summary-add,
|
||||
.summary-editor .summary-delete-block { font-weight: 600; }
|
||||
.summary-add-value { width: 100%; }
|
||||
.summary-add-block { margin-top: 10px; }
|
||||
.summary-add:hover:not(:disabled) { border-style: solid; }
|
||||
.summary-delete-block:hover:not(:disabled) { background: color-mix(in srgb, var(--summary-editor-surface) 94%, var(--error-color, #db4437)); }
|
||||
.summary-reload { margin-top: 8px; min-height: 44px; white-space: normal; }
|
||||
hp-dialog[data-kind='summary'] .summary-editor-footer {
|
||||
min-height: 66px;
|
||||
padding: 11px 17px;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
border-top: 1px solid var(--summary-editor-line);
|
||||
background: var(--summary-editor-surface);
|
||||
}
|
||||
hp-dialog[data-kind='summary'] .summary-editor-footer button {
|
||||
min-width: 44px;
|
||||
min-height: 44px;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
padding: 9px 14px;
|
||||
border: 1px solid var(--summary-editor-line);
|
||||
border-radius: 8px;
|
||||
color: var(--primary-text-color);
|
||||
background: var(--summary-editor-surface);
|
||||
font-size: .875rem;
|
||||
line-height: 1.4;
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
hp-dialog[data-kind='summary'] .summary-editor-footer button.on {
|
||||
border-color: var(--summary-editor-accent);
|
||||
color: var(--text-primary-color, #fff);
|
||||
background: var(--summary-editor-accent);
|
||||
}
|
||||
hp-dialog[data-kind='summary'] .summary-editor-footer button:disabled { opacity: .4; cursor: default; }
|
||||
@container summary-editor (max-width: 800px) {
|
||||
.summary-general-grid { grid-template-columns: minmax(0, 1fr); }
|
||||
.summary-editor-value { grid-template-columns: 44px 92px minmax(0, 1fr) 44px; }
|
||||
.summary-value-label { grid-column: 3; }
|
||||
.summary-source-field { grid-column: 3; }
|
||||
.summary-value-remove { grid-column: 4; grid-row: 1; }
|
||||
.summary-scope { grid-template-columns: minmax(0, 1fr); }
|
||||
}
|
||||
@container summary-editor (max-width: 480px) {
|
||||
.summary-general,
|
||||
.summary-blocks-card { padding: 10px; }
|
||||
.summary-block-head { grid-template-columns: 44px minmax(0, 1fr) 44px; }
|
||||
.summary-block-head > .summary-order { grid-column: 2; }
|
||||
.summary-block-title { grid-column: 1 / 3; grid-row: 2; }
|
||||
.summary-visibility { grid-column: 3; grid-row: 2; }
|
||||
.summary-scope,
|
||||
.summary-block-title-error,
|
||||
.summary-scope-problem { grid-column: 1 / -1; }
|
||||
.summary-editor-value { grid-template-columns: 44px minmax(0, 1fr) 44px; }
|
||||
.summary-editor-value > .summary-order { grid-column: 2; }
|
||||
.summary-value-label { grid-column: 1 / -1; grid-row: 2; }
|
||||
.summary-source-field { grid-column: 1 / -1; grid-row: 3; }
|
||||
.summary-value-remove { grid-column: 3; grid-row: 1; }
|
||||
}
|
||||
+67
-10
@@ -5,21 +5,33 @@ import { readFileSync } from 'node:fs';
|
||||
import {
|
||||
CARD_DIALOG_FORM_KIT, SUMMARY_PANEL_FORM_KIT, formKitCss,
|
||||
} from '../test-build/styles/form-kit.styles.js';
|
||||
import { summaryPanelEditorCss } from '../test-build/summary-panel-editor-style.js';
|
||||
|
||||
/**
|
||||
* #594. Набор контролов поднят из редактора боковой панели, и главный вопрос к
|
||||
* нему один: общий ли он на самом деле. Ответ обязан быть машинным — с
|
||||
* параметрами панели генератор должен выдавать её нынешние правила ДОСЛОВНО.
|
||||
* Тогда переезд панели на общий источник (следующий шаг эпика #591) окажется
|
||||
* подстановкой, а не редизайном, и её пиксели не дрогнут.
|
||||
*
|
||||
* Фрагменты ниже скопированы из `src/summary-panel-editor-style.ts` как есть.
|
||||
* Тест читает тот же файл и проверяет, что фрагмент всё ещё в нём: иначе
|
||||
* «замороженная» фикстура тихо разошлась бы с панелью и сравнивала генератор
|
||||
* сама с собой.
|
||||
* #597 довёл это до конца: панель больше не описывает эти правила у себя, она
|
||||
* подставляет фрагменты набора. Поэтому доказательство переехало с «фрагмент
|
||||
* есть в исходнике панели» на «СОБРАННЫЙ лист панели совпадает с замороженным
|
||||
* побайтово» — оно сильнее и ловит в том числе перестановку правил.
|
||||
*/
|
||||
const PANEL_STYLE = readFileSync(
|
||||
new URL('../src/summary-panel-editor-style.ts', import.meta.url), 'utf8',
|
||||
const FROZEN_PANEL_CSS = readFileSync(
|
||||
new URL('./fixtures/summary-panel-editor.css', import.meta.url), 'utf8',
|
||||
);
|
||||
/**
|
||||
* #597 M2 ревью ТЗ. К3 обещает, что разрез генератора на фрагменты не меняет
|
||||
* ни байта в листе диалогов, а сослаться было не на что: существующие тесты
|
||||
* набора проверяют подстроки и счётчики, но не полный текст. Лишний пробел на
|
||||
* стыке фрагментов прошёл бы незамеченным — в CSS он безвреден, но обещание
|
||||
* «байт в байт» без свидетеля остаётся обещанием.
|
||||
*/
|
||||
const FROZEN_CARD_KIT_CSS = readFileSync(
|
||||
new URL('./fixtures/form-kit-card-dialog.css', import.meta.url), 'utf8',
|
||||
);
|
||||
const FROZEN_CARD_KIT_WITH_SWITCH_CSS = readFileSync(
|
||||
new URL('./fixtures/form-kit-card-dialog-with-switch.css', import.meta.url), 'utf8',
|
||||
);
|
||||
|
||||
const PANEL_FRAGMENTS = {
|
||||
@@ -60,13 +72,58 @@ const PANEL_FRAGMENTS = {
|
||||
test('#594 AC11 генератор воспроизводит правила панели дословно', () => {
|
||||
const generated = formKitCss(SUMMARY_PANEL_FORM_KIT);
|
||||
for (const [name, fragment] of Object.entries(PANEL_FRAGMENTS)) {
|
||||
assert.ok(PANEL_STYLE.includes(fragment),
|
||||
`${name}: фикстура разошлась с листом панели — сверьте src/summary-panel-editor-style.ts`);
|
||||
assert.ok(FROZEN_PANEL_CSS.includes(fragment),
|
||||
`${name}: фикстура разошлась с замороженным листом панели`);
|
||||
assert.ok(generated.includes(fragment),
|
||||
`${name}: генератор с параметрами панели больше не выдаёт её правило дословно`);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* #597. Главное доказательство шага и единственное, которое ловит перестановку
|
||||
* правил: у настроек панели нет ни одного golden-кадра (в матрице нет сцены
|
||||
* `dialog: 'summary'`), поэтому «панель не изменилась» нельзя показать
|
||||
* пикселями. Сравнение собранного листа с замороженным — замена эталону.
|
||||
*
|
||||
* Фикстура `test/fixtures/summary-panel-editor.css` снята с `origin/dev` до
|
||||
* правки. Она живёт ровно до того шага эпика #591, который законно меняет вид
|
||||
* панели: там её обновляют вместе с кадрами и объясняют расхождение.
|
||||
*/
|
||||
test('#597 собранный лист панели совпадает с замороженным побайтово', () => {
|
||||
assert.equal(summaryPanelEditorCss.length, FROZEN_PANEL_CSS.length,
|
||||
'длина листа панели изменилась — значит изменился и он сам');
|
||||
assert.equal(summaryPanelEditorCss, FROZEN_PANEL_CSS);
|
||||
// И фрагменты действительно пришли из набора, а не остались литералами:
|
||||
// иначе тест выше сравнивал бы панель сама с собой.
|
||||
const source = readFileSync(
|
||||
new URL('../src/summary-panel-editor-style.ts', import.meta.url), 'utf8',
|
||||
);
|
||||
for (const fn of ['formKitCardsCss', 'formKitFocusCss', 'formKitDisabledCss',
|
||||
'formKitSwitchRowCss', 'formKitSwitchCaptionCss']) {
|
||||
assert.ok(source.includes(`\${${fn}(SUMMARY_PANEL_FORM_KIT)}`),
|
||||
`${fn}: панель не подставляет фрагмент набора`);
|
||||
}
|
||||
// #597 M1 ревью ТЗ: проверять надо отсутствие ВСЕХ пяти фрагментов, а не одной
|
||||
// характерной подстроки. `min-height: 54px` встречается только в ряду-
|
||||
// переключателе, поэтому литерал, оставленный «на всякий случай» для любого из
|
||||
// четырёх остальных, прошёл бы и это правило, и побайтовое сравнение выше:
|
||||
// на собранный вывод лишний литерал не влияет, он просто вторая копия.
|
||||
for (const [name, fragment] of Object.entries(PANEL_FRAGMENTS)) {
|
||||
assert.ok(!source.includes(fragment),
|
||||
`${name}: правило осталось литералом в листе панели — копия не устранена`);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* #597 M2. Разрез `sharedCss` на пять функций обязан быть механическим: лист
|
||||
* диалогов карточки собирается из тех же фрагментов и не имеет права измениться
|
||||
* ни на байт. Фикстуры сняты с `origin/dev` до правки.
|
||||
*/
|
||||
test('#597 лист диалогов карточки не изменился ни на байт', () => {
|
||||
assert.equal(formKitCss(CARD_DIALOG_FORM_KIT, { withSwitch: false }), FROZEN_CARD_KIT_CSS);
|
||||
assert.equal(formKitCss(CARD_DIALOG_FORM_KIT), FROZEN_CARD_KIT_WITH_SWITCH_CSS);
|
||||
});
|
||||
|
||||
test('#594 имена параметризованы, а не зашиты', () => {
|
||||
const card = formKitCss(CARD_DIALOG_FORM_KIT, { withSwitch: false });
|
||||
assert.ok(card.includes('.hpf-card {'), 'карточка набора не переименовалась под префикс карточки');
|
||||
|
||||
Reference in New Issue
Block a user