Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions e2e/control-names.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,24 +88,33 @@ test('Settings group titles are headings', async ({ page }) => {
await expect(page.locator('#sec-general').getByRole('heading', { level: 2, name: 'Sprache' })).toBeVisible();
});

test('the selected colour swatch is announced as pressed', async ({ page }) => {
test('the icon colour Style is a named drop-down', async ({ page }) => {
await openDashboardList(page);
await rowByName(page, 'Alpha')
.getByRole('button', { name: /^Bearbeiten/ })
.click();
const style = page.getByRole('button', { name: /^Stil/ });
await expect(style).toHaveAccessibleName('Stil Dunkel');
const pick = async v => {
await style.click();
await page.locator(`#icon-col-style-list [data-val="${v}"]`).click();
};
const swatch = v => page.locator(`#icon-color-slot .cc-swatch[data-v="${v}"]`);
await expect(swatch('dark')).toHaveAttribute('aria-pressed', 'true');
await expect(swatch('light')).toHaveAttribute('aria-pressed', 'false');
await swatch('light').click();
await expect(swatch('light')).toHaveAttribute('aria-pressed', 'true');
await expect(swatch('dark')).toHaveAttribute('aria-pressed', 'false');
await swatch('custom').click();
await expect(swatch('custom')).toHaveAttribute('aria-expanded', 'true');
await expect(swatch('custom')).toHaveAttribute('aria-pressed', 'true');
await expect(swatch('red')).toBeHidden();
await pick('color');
await expect(style).toHaveAccessibleName('Stil Farbe');
await expect(swatch('blue')).toHaveAttribute('aria-pressed', 'true');
await swatch('red').click();
await expect(swatch('red')).toHaveAttribute('aria-pressed', 'true');
await expect(swatch('blue')).toHaveAttribute('aria-pressed', 'false');
await expect(swatch('custom')).toHaveAttribute('aria-pressed', 'false');
await expect(swatch('custom')).toHaveAttribute('aria-expanded', 'true');
await pick('light');
await expect(swatch('red')).toBeHidden();
await pick('color');
await expect(swatch('red')).toHaveAttribute('aria-pressed', 'true');
await swatch('custom').click();
await expect(swatch('custom')).toHaveAttribute('aria-pressed', 'true');
await expect(swatch('red')).toHaveAttribute('aria-pressed', 'false');
});

test('the Test result is announced', async ({ page }) => {
Expand Down
6 changes: 4 additions & 2 deletions e2e/reflow.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ test.describe('on a touch screen', () => {
await page.setViewportSize({ width: 320, height: 700 });
await page.goto('/admin/#dashboard');
await page.locator('#al [data-act="edit"]').first().click();
await page.locator('#icon-color-slot .cc-swatch[data-v="custom"]').click();
await page.locator('#icon-col-style-btn').click();
await page.locator('#icon-col-style-list [data-val="color"]').click();
const picker = page.locator('#icon-color-slot');
await picker.locator('.cc-hues').waitFor({ state: 'visible' });
await animationsDone(page);
Expand Down Expand Up @@ -104,7 +105,8 @@ test('the colour sliders share one length', async ({ page, request }) => {
await page.setViewportSize({ width: 1280, height: 900 });
await openDashboardList(page);
await page.locator('#al [data-act="edit"]').first().click();
await page.locator('#icon-color-slot .cc-swatch[data-v="custom"]').click();
await page.locator('#icon-col-style-btn').click();
await page.locator('#icon-col-style-list [data-val="color"]').click();
const sliders = page.locator('#icon-color-slot .hsb-range');
await expect(sliders).toHaveCount(3);
await animationsDone(page);
Expand Down
4 changes: 2 additions & 2 deletions ui/admin/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<script src="/js/mobile-class.js?v=fe95da0d"></script>
<script src="/js/admin-theme.js?v=66565254"></script>
<link rel="stylesheet" href="/css/tokens.css?v=0f4ba0b8">
<link rel="stylesheet" href="/css/admin.css?v=4eb9474e">
<link rel="stylesheet" href="/css/admin.css?v=dc70919b">
<link rel="stylesheet" href="/css/widget-config-form.css?v=53b91d5f">
<link rel="stylesheet" href="/css/config-recovery.css?v=faa8f109">
</head>
Expand Down Expand Up @@ -302,6 +302,6 @@ <h1 class="sec-title" id="ev-title">General</h1>
</div></div>
</div>

<script type="module" src="/js/admin.js?v=4b6830bd"></script>
<script type="module" src="/js/admin.js?v=02ebf569"></script>
</body>
</html>
21 changes: 2 additions & 19 deletions ui/css/admin.css
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
/* admin.css v33 */
:root{
/* The two plate previews in the colour control. Fixed in both themes: each
shows the plate it selects, not the theme the page is in. */
--cc-plate-dark:#1C1C1E;
--cc-plate-light:#F2F2F7;

/* Surfaces and borders, darkest first, from the system greys. */
--bg-outer:var(--sy-gray6);
--sb:#141517;
Expand Down Expand Up @@ -353,9 +348,7 @@ button.rnm{font-family:inherit;color:inherit;background:none;border:0;padding:0;

/* ── Color control: swatch row + HSB sliders + code (PSD Icon/Label/Activity) ── */
.cc-row{padding-top:13px;padding-bottom:13px}
/* Grid, not a wrapping flex row. space-between spreads whatever wraps to the
far edges. */
.cc-sw{display:grid;grid-template-columns:repeat(auto-fit,26px);gap:10px;margin-inline-start:auto;flex:2;max-width:none;justify-content:space-between}
.cc-sw{display:flex;flex-wrap:wrap;gap:10px;justify-content:flex-end;margin-inline-start:auto;flex:0 1 auto}
.cc-swatch{width:26px;height:26px;border-radius:50%;cursor:pointer;border:none;padding:0;outline:2px solid transparent;outline-offset:2px;transition:outline-color var(--t);flex-shrink:0}
.cc-swatch.on{outline-color:var(--swatch-ring)}
@media (forced-colors: active){
Expand Down Expand Up @@ -424,12 +417,6 @@ input::placeholder,textarea::placeholder{color:var(--dm-strong)}
.dash-load-fail{padding:32px;text-align:center;color:var(--dm);font-size:14px}
.dash-load-fail .retry-btn{padding:8px 20px;border-radius:16px;background:var(--btn-fill);border:0;color:var(--tx);cursor:pointer;font-size:14px;font-family:inherit}
.wcf-add-label{color:var(--ac2)}
.cc-kw-auto{background:linear-gradient(135deg,var(--cc-plate-dark) 50%,var(--cc-plate-light) 50%);box-shadow:inset 0 0 0 .5px var(--separator)}
.cc-kw-light,.cc-kw-dark{box-shadow:inset 0 0 0 1px var(--separator)}
.cc-kw-clear{background:repeating-conic-gradient(var(--sy-gray2) 0 25%,var(--sy-gray4) 0 50%) 50% 50%/8px 8px;box-shadow:inset 0 0 0 1px var(--separator)}
html[data-theme="light"] .cc-kw-clear{background-image:repeating-conic-gradient(var(--sy-gray4) 0 25%,#fff 0 50%)}
.cc-hues .cc-sw{grid-template-columns:repeat(auto-fill,22px);gap:8px;justify-content:end}
.cc-hues .cc-swatch{width:22px;height:22px}
/* App form: icon source row, poll, value list */
.icon-src-row{gap:12px}
.icon-prev{box-shadow:inset 0 0 0 1px var(--separator);width:34px;height:34px;border-radius:9px;display:flex;align-items:center;justify-content:center;color:var(--on-tint);font-size:13px;font-weight:600;flex-shrink:0;overflow:hidden}
Expand Down Expand Up @@ -736,10 +723,6 @@ a:focus-visible,button:focus-visible,[role="button"]:focus-visible{outline:2px s
.adm-range { height:44px; padding-block:19px; background-clip:content-box; border-radius:3px / 22px; }
.hsb-range { height:44px; padding-block:19px; background-clip:content-box; border-radius:3px / 22px; }
.cc-swatch { width:34px; height:34px; }
.cc-sw { grid-template-columns:repeat(auto-fit,34px); }
.cc-kw-clear { background-size:10px 10px; }
.cc-hues .cc-sw { grid-template-columns:repeat(7,34px); }
.cc-hues .cc-swatch { width:34px; height:34px; }
}

/* ─────────────── Mobile: phone settings + bottom tab bar ───────────────
Expand All @@ -758,7 +741,7 @@ html.is-mobile .sec{padding-block:16px calc(132px + env(safe-area-inset-bottom))
html.is-mobile .sec-title{font-size:var(--fs-large-title);line-height:var(--lh-large-title);letter-spacing:var(--tr-large-title)}
html.is-mobile .cc-row{flex-wrap:wrap;row-gap:10px}
html.is-mobile .cc-row .rl{flex:0 0 100%}
html.is-mobile .cc-sw{flex:1 1 100%;grid-template-columns:repeat(7,auto);column-gap:0;justify-content:space-between}
html.is-mobile .cc-sw{display:grid;flex:1 1 100%;grid-template-columns:repeat(7,auto);column-gap:0;justify-content:space-between}
html.is-mobile .sec-hdr{padding:10px 0 10px;flex-wrap:wrap;row-gap:6px}
html.is-mobile .sec-title{order:1;flex-basis:100%;min-width:0;overflow-wrap:break-word}
html.is-mobile .sec-hdr-actions{margin-inline-start:auto}
Expand Down
3 changes: 2 additions & 1 deletion ui/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -538,9 +538,10 @@
"hidden": "Ausgeblendet"
},
"color": {
"style": "Stil",
"dark": "Dunkel",
"light": "Weiß",
"auto": "Automatisch (Dunkel oder Weiß)",
"auto": "Automatisch",
"clear": "Klar",
"system": "System",
"accent": "Akzent",
Expand Down
3 changes: 2 additions & 1 deletion ui/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -538,9 +538,10 @@
"hidden": "Hidden"
},
"color": {
"style": "Style",
"dark": "Dark",
"light": "White",
"auto": "Auto (Dark or White)",
"auto": "Auto",
"clear": "Clear",
"system": "System",
"accent": "Accent",
Expand Down
3 changes: 2 additions & 1 deletion ui/i18n/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -550,9 +550,10 @@
"hidden": "Oculto"
},
"color": {
"style": "Estilo",
"dark": "Oscuro",
"light": "Blanco",
"auto": "Automático (oscuro o blanco)",
"auto": "Automático",
"clear": "Transparente",
"system": "Sistema",
"accent": "Acento",
Expand Down
3 changes: 2 additions & 1 deletion ui/i18n/fa.json
Original file line number Diff line number Diff line change
Expand Up @@ -538,9 +538,10 @@
"hidden": "پنهان"
},
"color": {
"style": "سبک",
"dark": "تیره",
"light": "سفید",
"auto": "خودکار (تیره یا سفید)",
"auto": "خودکار",
"clear": "شفاف",
"system": "سیستم",
"accent": "رنگ اصلی",
Expand Down
3 changes: 2 additions & 1 deletion ui/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -550,9 +550,10 @@
"hidden": "Masqué"
},
"color": {
"style": "Style",
"dark": "Sombre",
"light": "Blanc",
"auto": "Automatique (sombre ou blanc)",
"auto": "Automatique",
"clear": "Transparent",
"system": "Système",
"accent": "Accent",
Expand Down
3 changes: 2 additions & 1 deletion ui/i18n/zh-Hans.json
Original file line number Diff line number Diff line change
Expand Up @@ -526,9 +526,10 @@
"hidden": "已隐藏"
},
"color": {
"style": "样式",
"dark": "深色",
"light": "白色",
"auto": "自动(深色或白色)",
"auto": "自动",
"clear": "透明",
"system": "系统",
"accent": "强调色",
Expand Down
2 changes: 1 addition & 1 deletion ui/js/admin-app-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
} from '/js/admin-shared.js?v=1c01be9a';
import { createListbox } from '/js/listbox.js?v=f814170f';
import { MAX_LABELS } from '/js/badge-logic.js?v=5c72bd4c';
import { renderColorControl, BADGE_DEFAULT } from '/js/admin-color-control.js?v=ef504f96';
import { renderColorControl, BADGE_DEFAULT } from '/js/admin-color-control.js?v=4083ea3b';
import { badgeErrorAdvice, TONE } from '/js/admin-error.js?v=b0881957';
import { fluidHoverClear, fluidHoverKb } from '/js/fluid-hover.js?v=cb886e86';
import { iconSvg } from '/js/icon-set.js?v=628341d4';
Expand Down
64 changes: 53 additions & 11 deletions ui/js/admin-color-control.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { t } from '/js/i18n.js?v=ed7c5565';
import { html, raw, setHtml } from '/js/html.js?v=c71f8903';
import { qa, q } from '/js/utils.js?v=705ca896';
import { iconSvg } from '/js/icon-set.js?v=628341d4';
import { createListbox } from '/js/listbox.js?v=f814170f';
import { HUE_NAMES, ROLE_NAMES, TILE_KEYWORDS, pageTheme, tileColor } from '/js/palette.js?v=3fb8ae43';

const CC_SWATCHES = ['#1c1c1e', '#8e8e93', '#f2f2f7', '#ff393c', '#ffcd00', '#35c759', '#0289ff', '#cb30df'];
Expand Down Expand Up @@ -147,9 +148,11 @@ export function renderColorControl(
container,
{ value = '#0289ff', idPrefix, onChange, variant, swatchColors = CC_SWATCHES, label = t('common.color') } = {},
) {
const top = variant === 'tile' ? TILE_KEYWORDS : variant === 'badge' ? ROLE_NAMES : [];
const styled = variant === 'tile';
const styles = styled ? TILE_KEYWORDS : [];
const top = variant === 'badge' ? ROLE_NAMES : [];
const hues = variant ? HUE_NAMES : [];
const isKw = v => typeof v === 'string' && (top.includes(v) || hues.includes(v));
const isKw = v => typeof v === 'string' && (styles.includes(v) || top.includes(v) || hues.includes(v));
const kwName = v => t(COLOR_KEYS[v]);
const init = _hexToHsv(isKw(value) ? _swatchFill(value) || '#0289ff' : value) || { h: 212, s: 99, v: 100 };
const kwSwatch = v =>
Expand All @@ -160,13 +163,18 @@ export function renderColorControl(
const wrap = document.createElement('div');
const slider = (name, cls, id, max, val, lo, hi) => html`
<div class="row hsb-row"><span class="rl">${name}</span><div class="hsb-track"><span class="hsb-ico">${raw(lo)}</span><input type="range" class="${cls}" id="${id}" min="0" max="${max}" value="${val}" aria-label="${name}"><span class="hsb-ico">${raw(hi)}</span></div></div>`;
const hueRow = hues.length
? html`<div class="row cc-row cc-hues"><span class="rl">${t('color.system')}</span><div class="cc-sw">${hues.map(kwSwatch)}</div></div>`
: '';
const hueRow = !hues.length
? ''
: styled
? html`<div class="row cc-row cc-hues"><span class="rl">${label}</span><div class="cc-sw">${hues.map(kwSwatch)}${rainbow}</div></div>`
: html`<div class="row cc-row cc-hues"><span class="rl">${t('color.system')}</span><div class="cc-sw">${hues.map(kwSwatch)}</div></div>`;
const topRow = styled
? html`<div class="row cc-style-row"><span class="rl">${t('color.style')}</span></div>`
: html`<div class="row cc-row"><span class="rl">${label}</span><div class="cc-sw">${swatches}</div></div>`;
setHtml(
wrap,
html`
<div class="row cc-row"><span class="rl">${label}</span><div class="cc-sw">${swatches}</div></div>
${topRow}
<div class="row-wrap reveal cc-tune"><div class="reveal-in">
${hueRow}
${slider(t('appearance.hue'), 'hsb-range hsb-hue', `${idPrefix}-h`, 360, init.h, _ccIco.hueLo, _ccIco.hueHi)}
Expand All @@ -179,7 +187,7 @@ export function renderColorControl(
'unsafe-inline'. A swatch paints itself from its own value. */
for (const b of wrap.querySelectorAll('.cc-swatch[data-v]')) {
const v = b.getAttribute('data-v') || '';
const fill = v[0] === '#' ? v : v === 'auto' || v === 'clear' ? '' : _swatchFill(v);
const fill = v[0] === '#' ? v : _swatchFill(v);
if (fill) /** @type {HTMLElement} */ (b).style.background = fill;
}
const rows = /** @type {HTMLElement[]} */ ([...wrap.children]);
Expand All @@ -196,6 +204,33 @@ export function renderColorControl(
container.appendChild(hidden);
let mode = isKw(value) ? value : 'color';
let showTune = hues.includes(mode);
/* The colour a Style other than Color replaced, so choosing Color brings it back. */
let resume = null;
const styleOf = () => (styles.includes(mode) ? mode : 'color');
const styleBox = styled
? createListbox({
id: `${idPrefix}-style`,
label: t('color.style'),
options: [...styles, 'color'].map(v => ({ value: v, label: v === 'color' ? label : kwName(v) })),
value: styleOf(),
onChange: next => {
if (next !== 'color') {
if (!styles.includes(mode)) resume = { mode, hex: curHex(), pristine };
mode = next;
showTune = false;
commit();
return;
}
if (styles.includes(mode)) {
mode = resume?.mode ?? 'blue';
setSliders(resume?.hex ?? _swatchFill('blue'));
}
showTune = true;
commit(resume?.pristine);
},
})
: null;
if (styleBox) rows[0].appendChild(styleBox.el);
/* The first paint restores stored state, so it must not animate. */
let _painted = false;
/* The value as it was stored. The sliders are integers, so reading a colour
Expand Down Expand Up @@ -235,13 +270,15 @@ export function renderColorControl(
b.classList.toggle('on', on);
});
const rb = q('.cc-rainbow', container);
if (rb) rb.classList.toggle('on', showTune && (mode === 'color' || hues.includes(mode)));
if (rb)
rb.classList.toggle('on', styled ? mode === 'color' : showTune && (mode === 'color' || hues.includes(mode)));
const sw = qa('.cc-swatch', container);
const preset = sw.some(b => b !== rb && b.classList.contains('on'));
sw.forEach(b =>
b.setAttribute('aria-pressed', String(b === rb ? mode === 'color' && !preset : b.classList.contains('on'))),
);
rb?.setAttribute('aria-expanded', String(showTune));
if (!styled) rb?.setAttribute('aria-expanded', String(showTune));
styleBox?.setValue(styleOf());
reveal(tune, showTune, !_painted);
if (!codeRv.closest('.editing')) {
codeRv.textContent = mode === 'color' ? hex : kwName(mode);
Expand All @@ -250,8 +287,8 @@ export function renderColorControl(
hidden.value = mode === 'color' ? (pristine ?? hex) : mode;
_painted = true;
}
const commit = () => {
pristine = null;
const commit = (keep = null) => {
pristine = keep;
paint();
onChange?.(hidden.value);
};
Expand All @@ -265,6 +302,11 @@ export function renderColorControl(
qa('.cc-swatch', container).forEach(b =>
b.addEventListener('click', () => {
const v = b.dataset.v || '';
if (v === 'custom' && styled) {
mode = 'color';
commit();
return;
}
if (v === 'custom') {
showTune = !(showTune && (mode === 'color' || hues.includes(mode)));
if (showTune && !hues.includes(mode)) mode = 'color';
Expand Down
2 changes: 1 addition & 1 deletion ui/js/admin-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { paintIcon } from '/js/admin-shared.js?v=1c01be9a';
import { clr, el, focusFirst, initial, qa, setUserText } from '/js/utils.js?v=705ca896';
import { html, raw, setHtml } from '/js/html.js?v=c71f8903';
import { t } from '/js/i18n.js?v=ed7c5565';
import { sizeLabel } from '/js/admin-widget-form.js?v=b76d944f';
import { sizeLabel } from '/js/admin-widget-form.js?v=1cb745cf';
import { widgetGlyph } from '/js/widget-glyphs.js?v=b383b7c8';
import { iconSvg } from '/js/icon-set.js?v=628341d4';

Expand Down
2 changes: 1 addition & 1 deletion ui/js/admin-settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { confirmText, promptModal } from '/js/modal.js?v=6b0320bd';
import { el, inp, setUserText } from '/js/utils.js?v=705ca896';
import { formatNumber } from '/js/format-number.js?v=abcd4ae4';
import { serialWrites } from '/js/admin-save-logic.js?v=5613a9fa';
import { renderColorControl } from '/js/admin-color-control.js?v=ef504f96';
import { renderColorControl } from '/js/admin-color-control.js?v=4083ea3b';
import { BACKDROP } from '/js/background.js?v=25428bf2';
import {
ALLOWED_HOSTS_MAX,
Expand Down
2 changes: 1 addition & 1 deletion ui/js/admin-widget-form.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { state } from '/js/admin-state.js?v=af772a1b';
import { PE_SVG, initInlineEdit, swapContent } from '/js/admin-shared.js?v=1c01be9a';
import { createListbox } from '/js/listbox.js?v=f814170f';
import { renderWidgetConfigForm } from '/js/widget-config-form.js?v=355b495e';
import { renderWidgetConfigForm } from '/js/widget-config-form.js?v=1807c371';
import { html, raw, setHtml } from '/js/html.js?v=c71f8903';
import { sizesForView, widgetConfigMode, rejectionLines, carriesTypedValues } from '/js/admin-logic.js?v=1cca36f5';
import { t } from '/js/i18n.js?v=ed7c5565';
Expand Down
Loading
Loading