mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 04:09:17 +00:00
@@ -0,0 +1,292 @@
|
||||
# Issue #57 — единый выбор цвета и прозрачности за один клик
|
||||
|
||||
- **Issue:** https://github.com/Matysh/houseplan-card/issues/57
|
||||
- **Статус документа:** готово к будущей реализации; issue остаётся на `S3-spec`
|
||||
- **Приоритет:** P3
|
||||
- **Тип:** polish/tech-debt, обычный трек
|
||||
- **Пользовательское изменение:** да
|
||||
|
||||
## 1. Сценарий
|
||||
|
||||
Администратор настраивает цвет комнаты, контура, текста, мебели, decor fill либо
|
||||
Glow в одном из редакторских диалогов. Он нажимает образец цвета и ожидает сразу
|
||||
получить все относящиеся к значению controls на одной поверхности.
|
||||
|
||||
## 2. Что человек увидит до и после
|
||||
|
||||
До изменения первый клик открывает строку с нативным color input и прозрачностью,
|
||||
а второй клик по цвету открывает отдельный системный picker без прозрачности;
|
||||
после изменения один клик по образцу открывает единый House Plan picker, где цвет
|
||||
и прозрачность редактируются вместе.
|
||||
|
||||
## 3. Проблема
|
||||
|
||||
Текущий `hp-color-opacity` уже объединяет swatch и alpha slider внешне, но сам
|
||||
цвет делегирует `<input type="color">`. Получается вложенный workflow: popover
|
||||
House Plan → системный picker → возврат к отдельной прозрачности. На touch это
|
||||
особенно неудобно, а системная поверхность отличается между браузерами и не
|
||||
следует theme/accessibility contract карточки.
|
||||
|
||||
Главное решение владельца: убрать именно эту вложенность. Отдельная сложная
|
||||
профессиональная палитра, история цветов и второй dialog не нужны.
|
||||
|
||||
## 4. Scope
|
||||
|
||||
В задачу входят:
|
||||
|
||||
- замена нативного nested color input внутри `hp-color-opacity` единым встроенным
|
||||
picker UI;
|
||||
- цветовое поле, hue, точное текстовое значение и alpha на одной поверхности;
|
||||
- сохранение действующего public API и всех call sites;
|
||||
- keyboard, screen reader, touch/pointer capture и обе темы;
|
||||
- общий floating-surface/overlay lifecycle из #68;
|
||||
- pure color conversion/validation helpers;
|
||||
- bundle-size отчёт и targeted unit/smoke/golden coverage.
|
||||
|
||||
## 5. Не входит в задачу
|
||||
|
||||
- palette history, eyedropper, gradients, RGB/CMYK/Lab panels;
|
||||
- сохранённые пользовательские presets или синхронизация между карточками;
|
||||
- отдельная полноэкранная/mobile страница;
|
||||
- runtime dependency либо загрузка кода/палитры по сети;
|
||||
- изменение формата сохраняемого цвета/opacity;
|
||||
- массовый redesign диалогов и editor touch parity;
|
||||
- добавление alpha в consumers, где `showOpacity=false` имеет предметный смысл.
|
||||
|
||||
## 6. Технический выбор
|
||||
|
||||
Внутри существующего `hp-color-opacity` реализуется небольшой in-repo HSV picker:
|
||||
|
||||
- saturation/value field;
|
||||
- отдельный hue range;
|
||||
- alpha range + точное значение в процентах, если `showOpacity=true`;
|
||||
- редактируемое hex-поле `#RRGGBB`;
|
||||
- preview swatch на checkerboard.
|
||||
|
||||
Почему выбран этот путь:
|
||||
|
||||
| Вариант | Итог |
|
||||
| --- | --- |
|
||||
| Нативный `<input type=color>` | отклонён: сохраняет второй вложенный picker и не объединяет alpha |
|
||||
| Vendored `iro.js`/`vanilla-picker`/аналог | отклонён по умолчанию: чужой lifecycle/theme/a11y и риск превысить budget ради малого surface |
|
||||
| In-repo HSV controls | принят: сохраняет API, использует native ranges/floating controller и добавляет только нужный контракт |
|
||||
|
||||
Новых runtime dependencies в `package.json` нет. Production minified+gzip bundle
|
||||
delta этой функции не превышает 15 KiB; в handoff публикуются raw и gzip значения
|
||||
до/после на exact build.
|
||||
|
||||
Если автор докажет, что vendored dependency меньше и лучше выполняет все AC, это
|
||||
допустимая техническая замена только при: локально vendored source/license,
|
||||
нулевых runtime fetch, том же API, ≤15 KiB gzip delta и полном тестовом контракте.
|
||||
Продуктовая поверхность от выбора реализации не меняется.
|
||||
|
||||
## 7. Единый interaction contract
|
||||
|
||||
Один click/tap/Enter/Space по swatch открывает одну surface. В момент первого
|
||||
paint на ней одновременно доступны:
|
||||
|
||||
- saturation/value field;
|
||||
- hue;
|
||||
- hex value;
|
||||
- opacity slider и процент для opacity-capable consumer.
|
||||
|
||||
Ни один control внутри не открывает системный color dialog. Изменения применяются
|
||||
к draft в реальном времени через существующее событие
|
||||
`hp-color-opacity-change`; сохранение/отмена принадлежит родительскому диалогу и
|
||||
остаётся прежним.
|
||||
|
||||
Повторное нажатие trigger, click/tap вне поверхности, Escape, открытие другой
|
||||
exclusive transient surface, закрытие родительского диалога, mode change или
|
||||
disconnect закрывают picker. Escape сначала закрывает picker и не закрывает
|
||||
dialog; второй Escape работает по обычному контракту `hp-dialog`.
|
||||
|
||||
Открытие не меняет высоту/scroll родительского dialog. Popover API, portal
|
||||
fallback, flip/shift, visual viewport, resize/orientation и focus traversal
|
||||
переиспользуют `FloatingSurfaceController`/`hp-dialog.registerOverlay` из #68.
|
||||
|
||||
## 8. Значения и преобразования
|
||||
|
||||
- Canonical external color остаётся lowercase либо normalized `#RRGGBB` по
|
||||
действующему `safeStoredColor` contract.
|
||||
- Opacity наружу остаётся числом `[0,1]`; UI показывает целые `0–100%`.
|
||||
- HSV существует только как transient representation и не сохраняется.
|
||||
- Hex input принимает `RGB`, `RRGGBB`, с необязательным `#`; после commit
|
||||
нормализуется в `#rrggbb`/действующий canonical case.
|
||||
- Невалидный незавершённый hex остаётся draft до blur/Enter и не отправляет
|
||||
испорченное значение. На commit восстанавливается последнее валидное значение.
|
||||
- Изменение hue у achromatic цвета сохраняет transient hue в открытой session,
|
||||
чтобы последующее повышение saturation давало выбранный hue.
|
||||
- Все conversions конечны, clamped и имеют round-trip tolerance ≤1 RGB channel.
|
||||
|
||||
`showOpacity=false` полностью скрывает alpha row, не меняет входной opacity и
|
||||
эмитит его существующее значение вместе с новым цветом для API compatibility.
|
||||
|
||||
## 9. Pointer и touch
|
||||
|
||||
- Touch по saturation/value field использует Pointer Events и capture; движение
|
||||
за пределами поля продолжает текущую drag-session до up/cancel.
|
||||
- Второй pointer отменяет изменение первого либо игнорируется безопасно; pinch
|
||||
родительского canvas не начинается через открытую picker surface.
|
||||
- Touch targets hue/alpha/close-independent controls не меньше 40×40 CSS px.
|
||||
- `touch-action` задаётся только интерактивному полю/slider, не всему dialog.
|
||||
- `pointercancel`/lost capture сохраняют последнее уже эмитированное валидное
|
||||
draft, очищают drag state и не создают дополнительное событие.
|
||||
- Compatibility mouse после tap не открывает/закрывает surface второй раз.
|
||||
|
||||
Сам picker получает явную touch-поддержку. Остальные операции редакторов остаются
|
||||
best effort согласно `docs/TOUCH-SUPPORT.md`.
|
||||
|
||||
## 10. Keyboard и accessibility
|
||||
|
||||
- Trigger остаётся настоящей кнопкой с `aria-haspopup="dialog"` и
|
||||
`aria-expanded`.
|
||||
- Surface имеет локализованное accessible name и участвует в composed focus trap.
|
||||
- Hue, saturation, value и opacity доступны как отдельные native range inputs
|
||||
либо эквивалентные sliders с `aria-valuemin/max/now/text`.
|
||||
- Pointer-only saturation/value field не является единственным способом: рядом
|
||||
существуют keyboard-доступные saturation и value controls.
|
||||
- Arrow меняет значение на 1, `Shift+Arrow` — на 10; Home/End устанавливают
|
||||
границы, где это соответствует native range semantics.
|
||||
- Hex — подписанный text input с сообщаемой validation ошибкой без live-region
|
||||
spam на каждый символ.
|
||||
- Focus-visible не смешивается с hover; при закрытии Escape focus возвращается
|
||||
на trigger, при outside click не переносится.
|
||||
- При 200% zoom и viewport 390 CSS px поверхность не имеет горизонтального
|
||||
overflow; допустим внутренний вертикальный scroll.
|
||||
|
||||
## 11. Theme и visual contract
|
||||
|
||||
- Surface использует HA/House Plan theme tokens, не hardcoded light palette.
|
||||
- Saturation/value и hue tracks остаются читаемыми в light/dark themes.
|
||||
- Checkerboard явно показывает opacity, включая 0%.
|
||||
- Trigger продолжает показывать итоговую пару color+opacity.
|
||||
- Disabled component не открывается и не меняет draft.
|
||||
- `prefers-reduced-motion` отключает только open/close transition; color drag
|
||||
остаётся немедленным.
|
||||
|
||||
## 12. API, модель данных и migration
|
||||
|
||||
Сохраняется существующий contract:
|
||||
|
||||
```ts
|
||||
color: string
|
||||
opacity: number
|
||||
showOpacity: boolean
|
||||
disabled: boolean
|
||||
hp-color-opacity-change: { color: string; opacity: number }
|
||||
```
|
||||
|
||||
Существующие call sites не получают адаптеров и охватывают как минимум decor
|
||||
stroke/fill/text/furniture, room/space custom fill и marker Glow override.
|
||||
Config schema, backend, storage и migration не меняются; старые colors/alpha
|
||||
открываются и сохраняются losslessly в прежнем формате.
|
||||
|
||||
## 13. i18n
|
||||
|
||||
Добавляются пары en/ru для общих controls: Color picker, Hue, Saturation, Value/
|
||||
Brightness, Hex color и Opacity (существующий `space.opacity` можно переиспользовать
|
||||
только если семантика и scanner допускают это без связности с space dialog).
|
||||
|
||||
`hp-color-opacity` остаётся presentation-only и получает уже локализованные labels
|
||||
properties от House Plan; он не читает глобальный словарь или `hass.locale`.
|
||||
Две карточки с разными `config.language` не влияют друг на друга.
|
||||
|
||||
## 14. Acceptance criteria
|
||||
|
||||
1. **AC1 — один уровень.** Первый click/tap/keyboard activation открывает surface,
|
||||
где сразу доступны color и opacity; внутри нет `<input type=color>` или второго
|
||||
picker dialog. **Доказательство:** DOM unit + desktop/touch smoke.
|
||||
2. **AC2 — все consumers.** Existing call sites продолжают работать через тот же
|
||||
API; `showOpacity=false` скрывает только alpha. **Доказательство:** source-contract
|
||||
unit + smoke matrix.
|
||||
3. **AC3 — точные значения.** HSV/RGB, hex и alpha round-trip без повреждения,
|
||||
invalid draft не эмитится. **Доказательство:** table/property unit tests.
|
||||
4. **AC4 — touch.** Drag поля/hue/alpha, cancel и multi-touch безопасны на 390px
|
||||
viewport; nested/system picker не появляется. **Доказательство:** real-touch smoke.
|
||||
5. **AC5 — keyboard/a11y.** Все четыре dimensions и hex доступны с клавиатуры,
|
||||
labels/roles/values корректны, Escape/focus trap работают. **Доказательство:**
|
||||
accessibility/browser smoke.
|
||||
6. **AC6 — floating lifecycle.** Native/fallback surface не обрезается, корректно
|
||||
flip/shift-ится и конкурирует с `hp-help` по #68. **Доказательство:** forced-
|
||||
fallback smoke.
|
||||
7. **AC7 — visual parity.** Light/dark/200% zoom читаемы, checkerboard отражает
|
||||
alpha, dialog layout не прыгает. **Доказательство:** reviewed golden.
|
||||
8. **AC8 — budget.** Нет runtime dependency/fetch; minified+gzip delta ≤15 KiB,
|
||||
raw/gzip числа записаны. **Доказательство:** exact production build artifact.
|
||||
9. **AC9 — compatibility.** Config/backend schema и serialized values не меняются.
|
||||
**Доказательство:** round-trip unit + code review.
|
||||
|
||||
## 15. План автотестов
|
||||
|
||||
### Unit
|
||||
|
||||
- RGB↔HSV round trips, hue wrap, grayscale remembered hue;
|
||||
- safe hex parsing/commit/cancel and alpha clamping;
|
||||
- single event payload and no event for invalid draft;
|
||||
- `showOpacity`, disabled and existing property/event API;
|
||||
- source scan prohibits native `input[type=color]` in component;
|
||||
- en/ru key parity and presentation-only labels.
|
||||
|
||||
### Browser smoke
|
||||
|
||||
- each representative consumer: decor stroke/fill, room fill, Glow color-only;
|
||||
- pointer field/hue/alpha/hex, live draft and parent Cancel/Save;
|
||||
- keyboard traversal/ranges/hex/Escape and dialog focus wrap;
|
||||
- real touch, multi-touch/pointercancel, 390px/200% zoom;
|
||||
- native Popover and forced portal fallback, viewport edges/rotation;
|
||||
- `hp-help` exclusive overlay interaction and two-card language isolation.
|
||||
|
||||
### Golden
|
||||
|
||||
- open picker in light/dark desktop;
|
||||
- mobile 390px with non-100% alpha checkerboard;
|
||||
- color-only Glow variant without alpha row;
|
||||
- golden baseline updates only from reviewed full Linux artifact.
|
||||
|
||||
### Performance and size
|
||||
|
||||
- production build size before/after: raw and gzip;
|
||||
- pointermove updates only local component/draft, without parent full render per
|
||||
raw event (rAF/coalescing allowed);
|
||||
- no new long task in targeted editor smoke.
|
||||
|
||||
## 16. Затронутые поверхности
|
||||
|
||||
- `src/hp-color-opacity.ts`, `src/color.ts` либо новый small pure color helper;
|
||||
- `src/houseplan-card.ts` only for localized properties/call-site parity;
|
||||
- `src/i18n/en.json`, `src/i18n/ru.json`;
|
||||
- component unit tests, dialog/touch smoke and golden fixtures;
|
||||
- `docs/USER-GUIDE.ru.md`, `docs/TESTING.md`.
|
||||
|
||||
## 17. Риски и откат
|
||||
|
||||
| Риск | Мера |
|
||||
| --- | --- |
|
||||
| Custom field недоступен keyboard | отдельные native range alternatives |
|
||||
| Pointer drag перегружает render | local state + rAF/coalesced emission |
|
||||
| Цвет round-trip дрейфует | property/table tests и canonical external RGB |
|
||||
| Surface обрезается dialog | общий floating controller #68 |
|
||||
| Bundle непропорционально растёт | ≤15 KiB gzip hard budget |
|
||||
|
||||
Откат возвращает прежнее внутреннее содержимое `hp-color-opacity`; public API и
|
||||
сохранённые данные не меняются, поэтому migration rollback не нужен.
|
||||
|
||||
## 18. Release-артефакты
|
||||
|
||||
Implementation commit имеет `User-Visible: yes` и одновременно обновляет:
|
||||
|
||||
- `docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md`;
|
||||
- `docs/USER-GUIDE.ru.md` — единый one-click color+opacity workflow;
|
||||
- `docs/TESTING.md` — component/touch/fallback/keyboard coverage.
|
||||
|
||||
Нужны reviewed desktop/mobile golden artifacts, targeted smoke report и bundle
|
||||
size note с exact build SHA. Новые i18n keys добавляются синхронно en+ru.
|
||||
|
||||
## 19. Принятые технические предположения
|
||||
|
||||
- базовый implementation — in-repo HSV field + native ranges, без dependency;
|
||||
- external model остаётся `#RRGGBB` + opacity `[0,1]`;
|
||||
- live change API сохраняется, Save/Cancel принадлежит parent dialog;
|
||||
- pointer field дополняется, а не заменяет keyboard-доступные ranges;
|
||||
- exact easing/layout допустимо уточнять на реализации без изменения one-click
|
||||
и accessibility contracts.
|
||||
@@ -106,6 +106,12 @@ GitHub Issues и GitHub Projects (v2) остаются единственным
|
||||
|---|---|
|
||||
| [#103](https://github.com/Matysh/houseplan-card/issues/103) Состояния в Toggle confirmation | [103-toggle-confirmation-state.md](103-toggle-confirmation-state.md) |
|
||||
|
||||
## P3
|
||||
|
||||
| Issue | ТЗ |
|
||||
|---|---|
|
||||
| [#57](https://github.com/Matysh/houseplan-card/issues/57) Единый выбор цвета и прозрачности | [057-color-opacity-picker.md](057-color-opacity-picker.md) |
|
||||
|
||||
## Правило актуализации
|
||||
|
||||
При изменении продуктового решения сначала обновляется соответствующее issue, затем ТЗ. Реализация не считается завершённой только по наличию кода: нужны выполненные acceptance criteria, предусмотренная ТЗ проверка и актуальный статус Project v2.
|
||||
|
||||
Reference in New Issue
Block a user