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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,17 @@ mosaic and tree list modes, global search, the tab menu and four dashboard block

### Fixed

- A multiple select showed what was chosen only while it had focus, in the dark theme
(2026-10-26). `.dark .adm-select option` paints every option of a dark select gray-900, and in
a list box (`<select multiple>`) that rule also covered the colour the browser gives a selected
option while the box has no focus: a plain declaration in the browser's own stylesheet
(Chromium's `select:-internal-list-box option:checked`; only the focused highlight is
`!important`). In Chromium and Firefox the chosen options looked like all the others whenever
the box had no focus. The selected options of a dark multiple select now get the browser's own
colours back (`revert`), as the light theme always had them; the unselected ones and every
option of a single select keep gray-900. WebKit paints a list box's selection over the CSS and
never had the bug. `tests-adminata/Visual/select.spec.js` checks both themes in Chromium and
Firefox. Reported in recomaty-panel#663, which drops its own override for it.
- The document title was escaped twice (2026-09-29). `standard_layout` captures the
`adminata_head_title` block — already escaped where it printed the page's `title` block and the
crumbs — to collapse its whitespace, and `u.collapseWhitespace` hands back a plain string that
Expand Down
15 changes: 15 additions & 0 deletions assets/css/components/select.css
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,19 @@
background-color: var(--color-gray-900);
color: rgb(255 255 255 / 90%);
}

/*
* In a list box (`<select multiple>`) the rule above also covers the colour the browser gives
* a selected option while the box has no focus: a plain declaration in the browser's own
* stylesheet (Chromium's `select:-internal-list-box option:checked`; only the focused
* highlight is `!important`). In Chromium and Firefox a dark multiple select therefore looked
* as if nothing were chosen whenever it had no focus (recomaty-panel#663). `revert` hands the
* selected options back to the browser, as the light theme always had them: its own unfocused
* selection colour under `color-scheme: dark`, with its hover and disabled variants. The
* options of a single select keep the rule above. tests-adminata/Visual/select.spec.js.
*/
.dark .adm-select[multiple] option:checked {
background-color: revert;
color: revert;
}
}
2 changes: 1 addition & 1 deletion src/Resources/public/app.css

Large diffs are not rendered by default.

107 changes: 107 additions & 0 deletions tests-adminata/Visual/select.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* This file is part of the adminata package.
*
* (c) IDCT Bartosz Pachołek <bartosz@idct.tech>
*
* Forked from the Sonata Project
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { expect, test } from '@playwright/test';

import { THEMES, open, useTheme } from './support/demo.js';

/*
* A multiple select shows what is chosen while it has no focus, in both themes
* (recomaty-panel#663).
*
* The product list's Tags filter with one tag chosen: a native `<select multiple>`
* (`adm-select adm-select-multiple`) with "New" selected and the other three not, beside the
* filter's single select of operators. The dark theme paints every option of an `adm-select`
* gray-900, and in Chromium and Firefox that rule used to cover the colour the browser gives the
* selected options of a list box without focus as well, so "New" looked like the rest until the
* box was clicked. What colour a browser picks for a selection is its own business; what is
* tested is that a selected option never looks like an unselected one, and that the options
* adminata paints keep their gray-900.
*
* The colours are computed styles, so one viewport per engine is enough. WebKit is left out: it
* paints a list box's selection itself, over whatever the CSS says, so its computed style never
* shows the selection, and the bug never reached it.
*/

const PATH = '/admin/tests/app/product/list?filter%5Btags%5D%5Bvalue%5D%5B%5D=1';

/** The background of every option of a select, as the browser computes it. */
async function optionBackgrounds(page, selector) {
return page.locator(selector).evaluate((select) =>
Array.from(select.options, (option) => ({
label: option.textContent.trim(),
selected: option.selected,
background: getComputedStyle(option).backgroundColor,
})),
);
}

/** `--color-gray-900` as the browser computes a background of it. */
async function gray900(page) {
return page.evaluate(() => {
const probe = document.createElement('div');
probe.style.backgroundColor = 'var(--color-gray-900)';
document.body.append(probe);
const colour = getComputedStyle(probe).backgroundColor;
probe.remove();

return colour;
});
}

test.describe('select', () => {
test.beforeEach(async ({ browserName }, testInfo) => {
test.skip(!testInfo.project.name.endsWith('-wide'), 'Computed colours: one viewport per engine.');
test.skip('webkit' === browserName, 'WebKit paints the selection of a list box over its CSS.');
});

for (const theme of THEMES) {
test(`a multiple select shows its selection without focus in ${theme} mode`, async ({ page }) => {
await useTheme(page, theme);
await open(page, PATH);

const select = page.locator('#filter_tags_value');
await expect(select).toBeVisible();
await expect(select).not.toBeFocused();

const options = await optionBackgrounds(page, '#filter_tags_value');
const selected = options.filter((option) => option.selected);
const unselected = options.filter((option) => !option.selected);

expect(selected.map((option) => option.label)).toEqual(['New']);
expect(unselected).toHaveLength(3);

for (const option of unselected) {
expect(
option.background,
`"${selected[0].label}" (selected) and "${option.label}" (not) have the same background`,
).not.toBe(selected[0].background);
}

if ('dark' === theme) {
const paint = await gray900(page);

// adminata's own paint stays on what is not selected…
for (const option of unselected) {
expect(option.background, `"${option.label}" lost adminata's gray-900`).toBe(paint);
}

// …and on every option of a single select, the selected one included.
for (const option of await optionBackgrounds(page, '#filter_tags_type')) {
expect(option.background, `the operator "${option.label}" lost adminata's gray-900`).toBe(
paint,
);
}
}
});
}
});
Loading