mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
File diff suppressed because one or more lines are too long
@@ -667,6 +667,52 @@ export async function prepareGoldenScenario(page, scenario) {
|
||||
if (!trigger) throw new Error('golden decor color trigger missing');
|
||||
trigger.click();
|
||||
await picker.updateComplete;
|
||||
} else if (scenario.dialog === 'general-color') {
|
||||
card._openSettingsDialog();
|
||||
await card.updateComplete;
|
||||
const dialog = card.renderRoot.querySelector('hp-dialog');
|
||||
const picker = [...(dialog?.querySelectorAll('hp-color-opacity') || [])]
|
||||
.find((item) => item.label === card._t('gs.light_on'));
|
||||
await picker?.updateComplete;
|
||||
const trigger = picker?.renderRoot?.querySelector('.trigger');
|
||||
if (!trigger) throw new Error('golden general-settings color trigger missing');
|
||||
trigger.scrollIntoView({ block: 'center' });
|
||||
await frame();
|
||||
trigger.click();
|
||||
await picker.updateComplete;
|
||||
} else if (scenario.dialog === 'device-ripple-color') {
|
||||
card._setMode('devices');
|
||||
await card.updateComplete;
|
||||
await settleMode(card);
|
||||
const device = card._devices.find((item) => item.id === scenario.deviceId);
|
||||
if (!device) throw new Error(`golden ripple device missing: ${scenario.deviceId}`);
|
||||
card._openMarkerDialog(device);
|
||||
card._markerDialog = { ...card._markerDialog, display: 'icon_ripple' };
|
||||
card.requestUpdate();
|
||||
await card.updateComplete;
|
||||
const dialog = card.renderRoot.querySelector('hp-dialog');
|
||||
const picker = [...(dialog?.querySelectorAll('hp-color-opacity') || [])]
|
||||
.find((item) => item.label === card._t('marker.activity_color'));
|
||||
await picker?.updateComplete;
|
||||
const trigger = picker?.renderRoot?.querySelector('.trigger');
|
||||
if (!trigger) throw new Error('golden ripple color trigger missing');
|
||||
trigger.scrollIntoView({ block: 'center' });
|
||||
await frame();
|
||||
trigger.click();
|
||||
await picker.updateComplete;
|
||||
} else if (scenario.dialog === 'space-room-color') {
|
||||
card._openSpaceDialog('edit', scenario.space);
|
||||
await card.updateComplete;
|
||||
const dialog = card.renderRoot.querySelector('hp-dialog');
|
||||
const picker = [...(dialog?.querySelectorAll('hp-color-opacity') || [])]
|
||||
.find((item) => item.label === card._t('space.room_color'));
|
||||
await picker?.updateComplete;
|
||||
const trigger = picker?.renderRoot?.querySelector('.trigger');
|
||||
if (!trigger) throw new Error('golden space room-color trigger missing');
|
||||
trigger.scrollIntoView({ block: 'center' });
|
||||
await frame();
|
||||
trigger.click();
|
||||
await picker.updateComplete;
|
||||
}
|
||||
if (scenario.dayCycle) {
|
||||
const environment = card.renderRoot.querySelector('.hp-day-cycle-env');
|
||||
|
||||
+10
-1
@@ -1,7 +1,7 @@
|
||||
import { fixtureWallKey } from '../fixtures/visual-matrix.mjs';
|
||||
|
||||
/** Data-only HP-QA-01 capture matrix. Bump when framing or scenarios change. */
|
||||
export const GOLDEN_MATRIX_VERSION = 28;
|
||||
export const GOLDEN_MATRIX_VERSION = 29;
|
||||
|
||||
const stage = { capture: 'stage', threshold: { maxChannelDelta: 10, maxDiffRatio: 0.0005 } };
|
||||
const page = { capture: 'page', threshold: { maxChannelDelta: 10, maxDiffRatio: 0.0008 } };
|
||||
@@ -280,6 +280,15 @@ export const GOLDEN_SCENARIOS = Object.freeze([
|
||||
{ id: 'decor-color-popover-desktop-en', fixture: 'visual', space: 'golden-geometry',
|
||||
dialog: 'decor-color', language: 'en', theme: 'light',
|
||||
viewport: { width: 760, height: 820 }, ...page },
|
||||
{ id: 'general-color-popover-desktop-en', fixture: 'visual', space: 'golden-geometry',
|
||||
dialog: 'general-color', language: 'en', theme: 'light',
|
||||
viewport: { width: 900, height: 900 }, ...page },
|
||||
{ id: 'device-ripple-color-popover-mobile-ru', fixture: 'visual', space: 'golden-lighting',
|
||||
dialog: 'device-ripple-color', deviceId: 'golden-light-two',
|
||||
language: 'ru', theme: 'dark', viewport: { width: 390, height: 1000 }, ...page },
|
||||
{ id: 'space-room-color-popover-desktop-ru', fixture: 'visual', space: 'golden-geometry',
|
||||
dialog: 'space-room-color', language: 'ru', theme: 'dark',
|
||||
viewport: { width: 900, height: 1000 }, ...page },
|
||||
{ id: 'backup-full-preview-desktop-en', fixture: 'visual', space: 'golden-geometry',
|
||||
dialog: 'backup-full', language: 'en', theme: 'dark',
|
||||
viewport: { width: 1000, height: 900 }, ...page },
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import { launch, checkAll, finish } from './serve.mjs';
|
||||
|
||||
const { page, browser } = await launch({ width: 760, height: 920 });
|
||||
|
||||
const result = await page.evaluate(async () => {
|
||||
const out = {};
|
||||
const card = window.__card;
|
||||
const root = () => card.shadowRoot || card.renderRoot;
|
||||
const frame = () => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
||||
const change = (picker, detail) => picker.dispatchEvent(new CustomEvent(
|
||||
'hp-color-opacity-change', { detail, bubbles: true, composed: true },
|
||||
));
|
||||
const pickerByLabel = (dialog, label) => [...dialog.querySelectorAll('hp-color-opacity')]
|
||||
.find((picker) => picker.label === label);
|
||||
const nativeColors = () => root().querySelectorAll('input[type="color"]').length;
|
||||
|
||||
card._openSettingsDialog();
|
||||
card._settingsDialog = { ...card._settingsDialog, bgMode: 'static', bgColor: null };
|
||||
card.requestUpdate();
|
||||
await card.updateComplete;
|
||||
let dialog = root().querySelector('hp-dialog');
|
||||
const generalPickers = [...dialog.querySelectorAll('hp-color-opacity')];
|
||||
const lightOn = pickerByLabel(dialog, card._t('gs.light_on'));
|
||||
const tempCold = pickerByLabel(dialog, card._t('gs.temp_cold'));
|
||||
const globalBg = pickerByLabel(dialog, card._t('gs.bg_color'));
|
||||
out.generalSettingsUsesTwelveUnifiedPickers = generalPickers.length === 12
|
||||
&& generalPickers.filter((picker) => picker.showOpacity).length === 11
|
||||
&& globalBg?.showOpacity === false
|
||||
&& nativeColors() === 0;
|
||||
const previousTemp = { ...card._settingsDialog.colors.temp_cold };
|
||||
change(lightOn, { color: '#123456', opacity: 0.37 });
|
||||
await card.updateComplete;
|
||||
out.generalColorAndOpacityStayAtomic = card._settingsDialog.colors.light_on.c === '#123456'
|
||||
&& card._settingsDialog.colors.light_on.a === 0.37
|
||||
&& JSON.stringify(card._settingsDialog.colors.temp_cold) === JSON.stringify(previousTemp);
|
||||
|
||||
lightOn.renderRoot.querySelector('.trigger').click();
|
||||
await lightOn.updateComplete;
|
||||
await frame();
|
||||
tempCold.renderRoot.querySelector('.trigger').click();
|
||||
await tempCold.updateComplete;
|
||||
await frame();
|
||||
out.generalPickersAreExclusive = lightOn.renderRoot.querySelector('.trigger')
|
||||
?.getAttribute('aria-expanded') === 'false'
|
||||
&& tempCold.renderRoot.querySelector('.trigger')?.getAttribute('aria-expanded') === 'true';
|
||||
tempCold.renderRoot.querySelector('.trigger').click();
|
||||
await tempCold.updateComplete;
|
||||
|
||||
const originalGlobalBg = card._settingsDialog.bgColor;
|
||||
globalBg.renderRoot.querySelector('.trigger').click();
|
||||
await globalBg.updateComplete;
|
||||
globalBg.renderRoot.querySelector('.trigger').click();
|
||||
await globalBg.updateComplete;
|
||||
out.globalBackgroundOpenCloseDoesNotMaterialize = originalGlobalBg === null
|
||||
&& card._settingsDialog.bgColor === null;
|
||||
change(globalBg, { color: '#0a2a4a', opacity: 0.12 });
|
||||
await card.updateComplete;
|
||||
dialog = root().querySelector('hp-dialog');
|
||||
const defaultButton = [...dialog.querySelectorAll('button')]
|
||||
.find((button) => button.textContent.trim() === card._t('gs.bg_default'));
|
||||
const explicitGlobalBg = card._settingsDialog.bgColor;
|
||||
defaultButton?.click();
|
||||
await card.updateComplete;
|
||||
out.globalBackgroundIgnoresAlphaAndResets = explicitGlobalBg === '#0a2a4a'
|
||||
&& card._settingsDialog.bgColor === null;
|
||||
|
||||
card._settingsDialog = null;
|
||||
card._setMode('devices');
|
||||
card._openMarkerDialog();
|
||||
card._markerDialog = { ...card._markerDialog, display: 'icon_ripple', rippleColor: '', rippleSize: 4.5 };
|
||||
card.requestUpdate();
|
||||
await card.updateComplete;
|
||||
dialog = root().querySelector('hp-dialog');
|
||||
const ripple = pickerByLabel(dialog, card._t('marker.activity_color'));
|
||||
out.rippleUsesColorOnlyPicker = !!ripple && ripple.showOpacity === false
|
||||
&& ripple.color === '#3ea6ff' && nativeColors() === 0;
|
||||
const rippleSize = card._markerDialog.rippleSize;
|
||||
change(ripple, { color: '#abcdef', opacity: 0.05 });
|
||||
await card.updateComplete;
|
||||
out.rippleChangeLeavesSizeAndAlphaModelAlone = card._markerDialog.rippleColor === '#abcdef'
|
||||
&& card._markerDialog.rippleSize === rippleSize
|
||||
&& !Object.hasOwn(card._markerDialog, 'rippleOpacity');
|
||||
|
||||
card._markerDialog = null;
|
||||
card._setMode('view');
|
||||
card._openSpaceDialog('edit', card._space);
|
||||
card._spaceDialog = { ...card._spaceDialog, bgMode: 'static', bgColor: null };
|
||||
card.requestUpdate();
|
||||
await card.updateComplete;
|
||||
dialog = root().querySelector('hp-dialog');
|
||||
const roomColor = pickerByLabel(dialog, card._t('space.room_color'));
|
||||
let spaceBg = pickerByLabel(dialog, card._t('space.bg_color'));
|
||||
out.spaceUsesOpacityAndColorOnlyModes = roomColor?.showOpacity === true
|
||||
&& spaceBg?.showOpacity === false && nativeColors() === 0;
|
||||
change(roomColor, { color: '#fedcba', opacity: 0.63 });
|
||||
await card.updateComplete;
|
||||
out.roomColorAndOpacityStayAtomic = card._spaceDialog.roomColor === '#fedcba'
|
||||
&& card._spaceDialog.roomOpacity === 0.63;
|
||||
const serverSpaceBefore = JSON.stringify(card._serverCfg.spaces.find((space) => space.id === card._space));
|
||||
spaceBg = pickerByLabel(root().querySelector('hp-dialog'), card._t('space.bg_color'));
|
||||
change(spaceBg, { color: '#102030', opacity: 0.2 });
|
||||
await card.updateComplete;
|
||||
dialog = root().querySelector('hp-dialog');
|
||||
const inheritButton = [...dialog.querySelectorAll('button')]
|
||||
.find((button) => button.textContent.trim() === card._t('space.bg_inherit'));
|
||||
const explicitSpaceBg = card._spaceDialog.bgColor;
|
||||
inheritButton?.click();
|
||||
await card.updateComplete;
|
||||
card._spaceDialog = null;
|
||||
await card.updateComplete;
|
||||
out.spaceBackgroundIgnoresAlphaAndCancelPersistsNothing = explicitSpaceBg === '#102030'
|
||||
&& JSON.stringify(card._serverCfg.spaces.find((space) => space.id === card._space)) === serverSpaceBefore;
|
||||
|
||||
out.noNativeColorInputInAnyMigratedDialog = nativeColors() === 0;
|
||||
return out;
|
||||
});
|
||||
|
||||
checkAll(result);
|
||||
await finish(browser, result);
|
||||
File diff suppressed because one or more lines are too long
Vendored
+87
-88
File diff suppressed because one or more lines are too long
@@ -1,5 +1,14 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Every remaining colour control now uses the same House Plan picker, including
|
||||
the global light/temperature/LQI/Glow/wall palettes, plan and space
|
||||
backgrounds, room colour and device activity ripple. Existing opacity appears
|
||||
inside the same surface, while colour-only fields and Default/Inherited
|
||||
behaviour keep their previous data model
|
||||
([#180](https://github.com/Matysh/houseplan-card/issues/180)).
|
||||
|
||||
## v1.65.0-beta.3 — 2026-08-19
|
||||
|
||||
- Open-passage placement now previews the exact future wall cut before the
|
||||
|
||||
@@ -6,6 +6,15 @@
|
||||
> **Правило проекта:** оба файла пополняются в одном коммите с самим
|
||||
> изменением — как и остальная документация (см. docs/STATUS.md).
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Все оставшиеся настройки цвета теперь используют одну палитру House Plan:
|
||||
общие цвета света, температуры, LQI, Glow и стен, фон плана и пространства,
|
||||
цвет комнат и пульсации активности устройства. Существующая прозрачность
|
||||
находится в той же поверхности, а поля только с цветом и поведение
|
||||
«По умолчанию / Наследуется» сохраняют прежнюю модель данных
|
||||
([#180](https://github.com/Matysh/houseplan-card/issues/180)).
|
||||
|
||||
## v1.65.0-beta.3 — 2026-08-19
|
||||
|
||||
- Перед размещением открытого проёма теперь виден точный будущий разрыв стены:
|
||||
|
||||
@@ -95,6 +95,28 @@
|
||||
the implementation loop [golden: `decor-color-popover-mobile-ru`,
|
||||
`decor-color-popover-desktop-en`].
|
||||
|
||||
## Unified picker coverage for every color field (#180)
|
||||
|
||||
- [ ] A recursive source contract rejects every native `input[type=color]` in
|
||||
product TypeScript and fixes the complete shared-component inventory at
|
||||
13 template instances [unit: `color-picker.test.mjs`].
|
||||
- [ ] The 11 general light/temperature/LQI/Glow/wall palettes and the space room
|
||||
colour move their existing opacity into the unified picker; color and
|
||||
alpha update one parent draft atomically [unit: `color-picker.test.mjs`,
|
||||
auto: `smoke_color_picker_consumers.mjs`].
|
||||
- [ ] Global background, space background and activity ripple are color-only;
|
||||
opening/closing does not materialize an inherited/default value, and
|
||||
Default/Inherit restore `null` without adding alpha
|
||||
[auto: `smoke_color_picker_consumers.mjs`].
|
||||
- [ ] General settings keep one exclusive picker open among 12 swatches; marker
|
||||
ripple size remains independent, and cancelling the space dialog writes
|
||||
no color draft [auto: `smoke_color_picker_consumers.mjs`].
|
||||
- [ ] The three new dialog families are reviewed from the complete Linux
|
||||
artifact before a beta; implementation does not accept their baselines
|
||||
[golden: `general-color-popover-desktop-en`,
|
||||
`device-ripple-color-popover-mobile-ru`,
|
||||
`space-room-color-popover-desktop-ru`].
|
||||
|
||||
|
||||
## Open passage (#157)
|
||||
|
||||
|
||||
@@ -184,6 +184,13 @@ dialog. The Hue track shows the full colour spectrum at a glance, with a
|
||||
contrasting ring keeping its slider visible. Changes remain a draft until the
|
||||
owning properties dialog is saved.
|
||||
|
||||
This is the same control everywhere: decor and custom fills, the global
|
||||
light/temperature/LQI/Glow/wall palettes, global and per-space backgrounds,
|
||||
room colour, marker Glow and the device activity ripple. Settings that already
|
||||
have opacity show it in the picker; colour-only settings do not acquire one.
|
||||
**Default** and **Inherited** background actions remain beside the colour
|
||||
sample and do not save the displayed fallback unless a colour is changed.
|
||||
|
||||
<!-- docs-section: input -->
|
||||
|
||||
## 6. Navigation, zoom and input
|
||||
|
||||
@@ -193,6 +193,14 @@ title: План дома
|
||||
контур сохраняет видимость ползунка. Изменения остаются черновиком до сохранения
|
||||
родительского диалога свойств.
|
||||
|
||||
Один и тот же элемент выбора используется везде: для декора и своих заливок, общих
|
||||
палитр света, температуры, LQI, Glow и стен, общего и локального фона
|
||||
пространства, цвета комнат, Glow маркера и пульсации активности устройства. Если
|
||||
у настройки уже есть прозрачность, она показывается внутри палитры; поля только
|
||||
с цветом новую прозрачность не получают. Действия **«По умолчанию»** и
|
||||
**«Наследовать»** остаются рядом с образцом фона и не сохраняют показанное
|
||||
значение по умолчанию, пока пользователь не изменит цвет.
|
||||
|
||||
<!-- docs-section: input -->
|
||||
|
||||
## 6. Навигация, масштаб и жесты
|
||||
|
||||
+37
-22
@@ -14499,11 +14499,13 @@ class HouseplanCard extends LitElement {
|
||||
const d = this._settingsDialog!;
|
||||
const v = d.colors[key];
|
||||
return html`<div class="colorrow gsrow">
|
||||
<span class="gsl">${this._t(labelKey as any)}</span>
|
||||
<input type="color" .value=${v.c}
|
||||
@input=${(e: Event) => this._setFillColor(key, { c: (e.target as HTMLInputElement).value })} />
|
||||
${this._rangeInput(0, 100, 1, Math.round(v.a * 100), (n) => this._setFillColor(key, { a: n / 100 }))}
|
||||
<span class="opv">${Math.round(v.a * 100)}%</span>
|
||||
<hp-color-opacity .label=${this._t(labelKey as any)}
|
||||
.opacityLabel=${this._t('space.opacity')}
|
||||
.pickerLabels=${this._colorPickerLabels}
|
||||
.color=${v.c} .opacity=${v.a} .showOpacity=${true}
|
||||
@hp-color-opacity-change=${(e: CustomEvent<{ color: string; opacity: number }>) => {
|
||||
this._setFillColor(key, { c: e.detail.color, a: e.detail.opacity });
|
||||
}}></hp-color-opacity>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@@ -15081,10 +15083,13 @@ class HouseplanCard extends LitElement {
|
||||
</div>
|
||||
${this._settingsDialog!.bgMode === 'static'
|
||||
? html`<div class="colorrow gsrow">
|
||||
<span class="gsl">${this._t('gs.bg_color')}</span>
|
||||
<input type="color" .value=${this._settingsDialog!.bgColor || this._stageBgHex()}
|
||||
@input=${(e: Event) =>
|
||||
(this._settingsDialog = { ...this._settingsDialog!, bgColor: (e.target as HTMLInputElement).value })} />
|
||||
<hp-color-opacity .label=${this._t('gs.bg_color')}
|
||||
.pickerLabels=${this._colorPickerLabels}
|
||||
.color=${this._settingsDialog!.bgColor || this._stageBgHex()}
|
||||
.opacity=${1} .showOpacity=${false}
|
||||
@hp-color-opacity-change=${(e: CustomEvent<{ color: string }>) => {
|
||||
this._settingsDialog = { ...this._settingsDialog!, bgColor: e.detail.color };
|
||||
}}></hp-color-opacity>
|
||||
${this._settingsDialog!.bgColor
|
||||
? html`<button class="btn ghost" @click=${() =>
|
||||
(this._settingsDialog = { ...this._settingsDialog!, bgColor: null })}>${this._t('gs.bg_default')}</button>`
|
||||
@@ -19328,9 +19333,12 @@ class HouseplanCard extends LitElement {
|
||||
</div>`}
|
||||
${d.display === 'icon_ripple'
|
||||
? html`<div class="colorrow">
|
||||
<span class="opl">${this._t('marker.activity_color')}</span>
|
||||
<input type="color" .value=${d.rippleColor || '#3ea6ff'}
|
||||
@input=${(e: Event) => (this._markerDialog = { ...d, rippleColor: (e.target as HTMLInputElement).value })} />
|
||||
<hp-color-opacity .label=${this._t('marker.activity_color')}
|
||||
.pickerLabels=${this._colorPickerLabels}
|
||||
.color=${d.rippleColor || '#3ea6ff'} .opacity=${1} .showOpacity=${false}
|
||||
@hp-color-opacity-change=${(e: CustomEvent<{ color: string }>) => {
|
||||
this._markerDialog = { ...d, rippleColor: e.detail.color };
|
||||
}}></hp-color-opacity>
|
||||
<span class="opl">${this._t('marker.ripple_size')}</span>
|
||||
${this._rangeInput(2, 8, 0.5, d.rippleSize, (n) => (this._markerDialog = { ...d, rippleSize: n }))}
|
||||
<span class="opv">×${d.rippleSize}</span>
|
||||
@@ -19524,13 +19532,16 @@ class HouseplanCard extends LitElement {
|
||||
<span class="opv">${Math.round(d.cardFontScale * 100)}%</span>
|
||||
</div>
|
||||
${this._renderCardPreview(d.cardFontScale, 1, 1)}
|
||||
<label>${this._t('space.room_color')}</label>
|
||||
<div class="colorrow">
|
||||
<input type="color" .value=${d.roomColor}
|
||||
@input=${(e: Event) => (this._spaceDialog = { ...d, roomColor: (e.target as HTMLInputElement).value })} />
|
||||
<span class="opl">${this._t('space.opacity')}</span>
|
||||
${this._rangeInput(0, 100, 1, Math.round(d.roomOpacity * 100), (n) => (this._spaceDialog = { ...d, roomOpacity: n / 100 }))}
|
||||
<span class="opv">${Math.round(d.roomOpacity * 100)}%</span>
|
||||
<hp-color-opacity .label=${this._t('space.room_color')}
|
||||
.opacityLabel=${this._t('space.opacity')}
|
||||
.pickerLabels=${this._colorPickerLabels}
|
||||
.color=${d.roomColor} .opacity=${d.roomOpacity} .showOpacity=${true}
|
||||
@hp-color-opacity-change=${(e: CustomEvent<{ color: string; opacity: number }>) => {
|
||||
this._spaceDialog = {
|
||||
...d, roomColor: e.detail.color, roomOpacity: e.detail.opacity,
|
||||
};
|
||||
}}></hp-color-opacity>
|
||||
</div>
|
||||
<label>${this._t('space.bg_mode')}</label>
|
||||
<select class="areasel"
|
||||
@@ -19543,10 +19554,14 @@ class HouseplanCard extends LitElement {
|
||||
<option value="daynight" ?selected=${d.bgMode === 'daynight'}>${this._t('gs.bg_daynight')}</option>
|
||||
</select>
|
||||
${(d.bgMode ?? bgModeOf(this._settings, {})) === 'static'
|
||||
? html`<label>${this._t('space.bg_color')}</label>
|
||||
<div class="colorrow">
|
||||
<input type="color" .value=${d.bgColor || stageBgOf(this._settings, { bgColor: null }) || this._stageBgHex()}
|
||||
@input=${(e: Event) => (this._spaceDialog = { ...d, bgColor: (e.target as HTMLInputElement).value })} />
|
||||
? html`<div class="colorrow">
|
||||
<hp-color-opacity .label=${this._t('space.bg_color')}
|
||||
.pickerLabels=${this._colorPickerLabels}
|
||||
.color=${d.bgColor || stageBgOf(this._settings, { bgColor: null }) || this._stageBgHex()}
|
||||
.opacity=${1} .showOpacity=${false}
|
||||
@hp-color-opacity-change=${(e: CustomEvent<{ color: string }>) => {
|
||||
this._spaceDialog = { ...d, bgColor: e.detail.color };
|
||||
}}></hp-color-opacity>
|
||||
${d.bgColor
|
||||
? html`<button class="btn ghost" @click=${() => (this._spaceDialog = { ...d, bgColor: null })}>
|
||||
${this._t('space.bg_inherit')}</button>`
|
||||
|
||||
+3
-8
@@ -2159,14 +2159,9 @@ export const cardStyles = css`
|
||||
align-items: center;
|
||||
gap: var(--sp-4);
|
||||
}
|
||||
.colorrow input[type='color'] {
|
||||
width: 42px;
|
||||
height: 28px;
|
||||
border: 1px solid var(--hp-line);
|
||||
border-radius: var(--rad-s);
|
||||
background: transparent;
|
||||
padding: 1px;
|
||||
cursor: pointer;
|
||||
.gsrow > hp-color-opacity {
|
||||
min-width: min(100%, 210px);
|
||||
justify-content: space-between;
|
||||
}
|
||||
.colorrow input[type='range'] { flex: 1; }
|
||||
.colorrow .tempin { width: 70px; flex: none; }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { readFileSync, readdirSync } from 'node:fs';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
hexToRgb, hsvToHex, hsvToRgb, normalizeHexColor, normalizeHue, rgbToHex, rgbToHsv,
|
||||
@@ -40,13 +40,24 @@ test('HSV helpers wrap hue, clamp finite dimensions and keep grayscale achromati
|
||||
test('the shared component keeps its API and contains no nested native color picker', () => {
|
||||
const component = readFileSync(new URL('../src/hp-color-opacity.ts', import.meta.url), 'utf8');
|
||||
const card = readFileSync(new URL('../src/houseplan-card.ts', import.meta.url), 'utf8');
|
||||
assert.doesNotMatch(component, /type=["']color["']/);
|
||||
const sourceFiles = (directory) => readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
||||
const target = new URL(entry.name + (entry.isDirectory() ? '/' : ''), directory);
|
||||
return entry.isDirectory() ? sourceFiles(target) : entry.name.endsWith('.ts') ? [target] : [];
|
||||
});
|
||||
for (const source of sourceFiles(new URL('../src/', import.meta.url))) {
|
||||
assert.doesNotMatch(readFileSync(source, 'utf8'), /type\s*=\s*["']color["']/,
|
||||
`native color input remains in ${source.pathname}`);
|
||||
}
|
||||
for (const token of [
|
||||
'public color', 'public opacity', 'public disabled', 'public showOpacity',
|
||||
'hp-color-opacity-change', 'detail: { color: normalized, opacity: clamped }',
|
||||
]) assert.match(component, new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
||||
assert.equal((card.match(/<hp-color-opacity/g) || []).length, 8);
|
||||
assert.equal((card.match(/\.pickerLabels=\$\{this\._colorPickerLabels\}/g) || []).length, 8);
|
||||
assert.equal((card.match(/<hp-color-opacity/g) || []).length, 13);
|
||||
assert.equal((card.match(/\.pickerLabels=\$\{this\._colorPickerLabels\}/g) || []).length, 13);
|
||||
assert.equal((card.match(/\.showOpacity=\$\{false\}/g) || []).length, 4,
|
||||
'Glow, ripple and both background pickers stay color-only');
|
||||
assert.equal((card.match(/\.showOpacity=\$\{true\}/g) || []).length, 2,
|
||||
'general fill rows and room color explicitly keep their existing opacity');
|
||||
});
|
||||
|
||||
test('the hue range exposes one cyclic spectrum without restyling other ranges', () => {
|
||||
|
||||
@@ -211,7 +211,7 @@ test('sun-ray golden requires browser-painted light from a state-only sun entity
|
||||
assert.ok(scenario);
|
||||
const fixture = prepareGoldenFixture(scenario);
|
||||
const space = fixture.config.spaces.find((item) => item.id === scenario.space);
|
||||
assert.equal(GOLDEN_MATRIX_VERSION, 28);
|
||||
assert.equal(GOLDEN_MATRIX_VERSION, 29);
|
||||
assert.equal(space.settings.sun_rays, true);
|
||||
assert.equal(scenario.northDeg, 90,
|
||||
'the sign-sensitive golden must keep a non-zero north direction');
|
||||
@@ -239,6 +239,19 @@ test('the open color picker golden covers dark mobile and light desktop themes',
|
||||
]);
|
||||
});
|
||||
|
||||
test('all newly unified color-picker dialog families have reviewed-golden candidates', () => {
|
||||
const scenarios = GOLDEN_SCENARIOS.filter((item) => [
|
||||
'general-color', 'device-ripple-color', 'space-room-color',
|
||||
].includes(item.dialog));
|
||||
assert.deepEqual(scenarios.map(({ id, dialog, theme, viewport }) => ({
|
||||
id, dialog, theme, width: viewport.width,
|
||||
})), [
|
||||
{ id: 'general-color-popover-desktop-en', dialog: 'general-color', theme: 'light', width: 900 },
|
||||
{ id: 'device-ripple-color-popover-mobile-ru', dialog: 'device-ripple-color', theme: 'dark', width: 390 },
|
||||
{ id: 'space-room-color-popover-desktop-ru', dialog: 'space-room-color', theme: 'dark', width: 900 },
|
||||
]);
|
||||
});
|
||||
|
||||
test('value badge golden covers four positions and bottom badge with separate LQI', () => {
|
||||
const scenario = GOLDEN_SCENARIOS.find((item) => item.id === 'device-value-badge-positions-dark');
|
||||
assert.ok(scenario);
|
||||
|
||||
Reference in New Issue
Block a user