Show spectrum on hue slider

Issue: #192
User-Visible: yes
This commit is contained in:
Sergey Matyunin
2026-08-19 09:54:44 +03:00
parent 1c3404fdd0
commit 508d38ca71
13 changed files with 322 additions and 143 deletions
File diff suppressed because one or more lines are too long
+4 -1
View File
@@ -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 = 26;
export const GOLDEN_MATRIX_VERSION = 27;
const stage = { capture: 'stage', threshold: { maxChannelDelta: 10, maxDiffRatio: 0.0005 } };
const page = { capture: 'page', threshold: { maxChannelDelta: 10, maxDiffRatio: 0.0008 } };
@@ -269,6 +269,9 @@ export const GOLDEN_SCENARIOS = Object.freeze([
{ id: 'decor-color-popover-mobile-ru', fixture: 'visual', space: 'golden-geometry',
dialog: 'decor-color', language: 'ru', theme: 'dark',
viewport: { width: 390, height: 760 }, ...page },
{ 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: 'backup-full-preview-desktop-en', fixture: 'visual', space: 'golden-geometry',
dialog: 'backup-full', language: 'en', theme: 'dark',
viewport: { width: 1000, height: 900 }, ...page },
+5
View File
@@ -48,6 +48,11 @@ const result = await page.evaluate(async () => {
&& surface().scrollWidth <= surface().clientWidth;
const hue = ranges()[0];
const hueStyle = getComputedStyle(hue);
out.hueTrackContract = hue?.classList.contains('hue-range')
&& hue.min === '0' && hue.max === '359' && hue.step === '1'
&& hueStyle.getPropertyValue('--hp-picker-hue-track').includes('linear-gradient')
&& hue.getBoundingClientRect().height >= 40;
ranges()[1].value = '100';
ranges()[1].dispatchEvent(new Event('input', { bubbles: true, composed: true }));
ranges()[2].value = '100';
File diff suppressed because one or more lines are too long
+78 -45
View File
File diff suppressed because one or more lines are too long
+4
View File
@@ -2,6 +2,10 @@
## v1.65.0-beta.3 — 2026-08-19
- The House Plan color picker now draws the full spectrum directly on the Hue
track, so the target colour family is visible before moving the slider. Mouse,
touch and keyboard values remain unchanged
([#192](https://github.com/Matysh/houseplan-card/issues/192)).
- Editor color controls now open a single House Plan picker with saturation,
brightness, hue, exact HEX and opacity together. The nested browser color
dialog is gone, while existing room, space, decor and Glow values keep their
+4
View File
@@ -8,6 +8,10 @@
## v1.65.0-beta.3 — 2026-08-19
- Палитра House Plan теперь показывает полный спектр прямо на шкале «Оттенок»,
поэтому нужную группу цветов видно ещё до движения ползунка. Значения и
управление мышью, касанием и клавиатурой не изменились
([#192](https://github.com/Matysh/houseplan-card/issues/192)).
- Выбор цвета в редакторах теперь сразу открывает единую палитру House Plan с
насыщенностью, яркостью, оттенком, точным HEX и прозрачностью. Вложенный
системный диалог браузера больше не нужен, а сохранённый формат цветов комнат,
+8 -3
View File
@@ -85,9 +85,14 @@
- [ ] The color-only Glow consumer keeps the same picker without an opacity row,
and native/fallback floating surfaces remain mutually exclusive with help
[auto: `smoke_help_affordance.mjs`].
- [ ] The existing dark mobile decor color golden is reviewed from the complete
Linux artifact before a beta; baseline acceptance is not part of the
implementation loop [golden: `decor-color-popover-mobile-ru`].
- [ ] The Hue range keeps `0…359`, step 1 and its existing input events while its
WebKit/Blink and Gecko tracks expose the same cyclic spectrum; forced-colors
falls back to a system track [unit: `color-picker.test.mjs`, auto:
`smoke_color_picker.mjs`].
- [ ] The dark mobile and light desktop open-picker goldens are reviewed from the
complete Linux artifact before a beta; baseline acceptance is not part of
the implementation loop [golden: `decor-color-popover-mobile-ru`,
`decor-color-popover-desktop-en`].
## Open passage (#157)
+2 -1
View File
@@ -180,7 +180,8 @@ the tray scrolls horizontally instead of shrinking the plan.
Where an editor offers a color sample, one click opens the House Plan color
picker. Hue, saturation, brightness, an exact HEX value and opacity (when the
setting supports it) are available together; there is no second browser color
dialog. Changes remain a draft until the owning properties dialog is saved.
dialog. The Hue track shows the full colour spectrum at a glance. Changes remain
a draft until the owning properties dialog is saved.
<!-- docs-section: input -->
+2 -2
View File
@@ -189,8 +189,8 @@ title: План дома
Если редактор показывает образец цвета, один клик открывает палитру House Plan.
Оттенок, насыщенность, яркость, точное значение HEX и прозрачность (если она
поддерживается этой настройкой) доступны вместе, без второго системного диалога
браузера. Изменения остаются черновиком до сохранения родительского диалога
свойств.
браузера. Шкала «Оттенок» сразу показывает полный цветовой спектр. Изменения
остаются черновиком до сохранения родительского диалога свойств.
<!-- docs-section: input -->
+33
View File
@@ -233,6 +233,29 @@ export class HpColorOpacity extends LitElement {
}
.hue-range {
accent-color: var(--hp-picker-hue, #f00);
--hp-picker-hue-track: linear-gradient(to right,
#f00 0%, #ff0 16.667%, #0f0 33.333%, #0ff 50%,
#00f 66.667%, #f0f 83.333%, #f00 100%);
}
.hue-range::-webkit-slider-runnable-track {
height: 10px;
box-sizing: border-box;
border: 1px solid var(--divider-color, #666);
border-radius: 999px;
background: var(--hp-picker-hue-track);
}
.hue-range::-moz-range-track {
height: 10px;
box-sizing: border-box;
border: 1px solid var(--divider-color, #666);
border-radius: 999px;
background: var(--hp-picker-hue-track);
}
.hue-range::-moz-range-progress {
height: 10px;
border: 0;
border-radius: 999px;
background: transparent;
}
input[type='text'],
input[type='number'] {
@@ -276,6 +299,16 @@ export class HpColorOpacity extends LitElement {
@media (prefers-reduced-motion: reduce) {
.picker { transition: none !important; }
}
@media (forced-colors: active) {
.hue-range::-webkit-slider-runnable-track {
border-color: ButtonText;
background: Canvas;
}
.hue-range::-moz-range-track {
border-color: ButtonText;
background: Canvas;
}
}
`;
connectedCallback(): void {
+15
View File
@@ -48,3 +48,18 @@ test('the shared component keeps its API and contains no nested native color pic
assert.equal((card.match(/<hp-color-opacity/g) || []).length, 8);
assert.equal((card.match(/\.pickerLabels=\$\{this\._colorPickerLabels\}/g) || []).length, 8);
});
test('the hue range exposes one cyclic spectrum without restyling other ranges', () => {
const component = readFileSync(new URL('../src/hp-color-opacity.ts', import.meta.url), 'utf8');
const spectrum = component.match(/\.hue-range\s*\{[\s\S]*?\n\s*\}/)?.[0] || '';
assert.match(spectrum, /--hp-picker-hue-track:\s*linear-gradient\(to right/);
for (const stop of ['#f00 0%', '#ff0 16.667%', '#0f0 33.333%', '#0ff 50%',
'#00f 66.667%', '#f0f 83.333%', '#f00 100%']) assert.ok(spectrum.includes(stop), stop);
assert.match(component, /\.hue-range::\-webkit-slider-runnable-track\s*\{[\s\S]*?height:\s*10px;[\s\S]*?background:\s*var\(--hp-picker-hue-track\);/);
assert.match(component, /\.hue-range::\-moz-range-track\s*\{[\s\S]*?height:\s*10px;[\s\S]*?background:\s*var\(--hp-picker-hue-track\);/);
assert.match(component, /\.hue-range::\-moz-range-progress\s*\{[\s\S]*?background:\s*transparent;/);
assert.match(component, /@media \(forced-colors:\s*active\)[\s\S]*?background:\s*Canvas;/);
assert.match(component, /class="hue-range" type="range" min="0" max="359" step="1"/);
const commonRange = component.match(/input\[type='range'\]\s*\{[\s\S]*?\n\s*\}/)?.[0] || '';
assert.doesNotMatch(commonRange, /linear-gradient|hp-picker-hue-track/);
});
+11 -1
View File
@@ -209,7 +209,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, 26);
assert.equal(GOLDEN_MATRIX_VERSION, 27);
assert.equal(space.settings.sun_rays, true);
assert.equal(scenario.northDeg, 90,
'the sign-sensitive golden must keep a non-zero north direction');
@@ -227,6 +227,16 @@ test('sun-ray golden requires browser-painted light from a state-only sun entity
assert.equal(scenario.sunRayPixels.minChannelDelta >= 4, true);
});
test('the open color picker golden covers dark mobile and light desktop themes', () => {
const scenarios = GOLDEN_SCENARIOS.filter((item) => item.dialog === 'decor-color');
assert.deepEqual(scenarios.map(({ id, language, theme, viewport }) => ({
id, language, theme, width: viewport.width,
})), [
{ id: 'decor-color-popover-mobile-ru', language: 'ru', theme: 'dark', width: 390 },
{ id: 'decor-color-popover-desktop-en', language: 'en', theme: 'light', width: 760 },
]);
});
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);