fix(arr): make the provider reorder honest

`visibility: hidden` on the disabled reorder button left a control in the
DOM that rendered as nothing. `disabled` already blocks the click and drops
it out of the tab order, so the hiding was decoration; the shared
`.control:disabled` colour says the same thing on screen, and the row does
not reflow because the hidden button reserved its box anyway.

Exposing it surfaced a second problem: `.control-quiet:hover` sits after
`.control:disabled` at the same specificity, so a disabled quiet control
lit accent-bright under the cursor and invited a click that does nothing.
Guarded with `:not(:disabled)` in the shared rule rather than on this one
control.

`refreshProviderEdges` found the buttons by `button:nth-of-type(1|2)`.
Position in the row is not a contract — a third button would have quietly
moved the edge logic onto the wrong control. They carry `data-move` now.

Closes #255
This commit is contained in:
Miguel Palhas
2026-08-25 22:33:40 +01:00
parent 20a3b2847b
commit 7798cbc3e6
2 changed files with 15 additions and 11 deletions
+11 -6
View File
@@ -722,13 +722,18 @@ export function settingsMain(views: { hide: () => void }[], goHome: () => void):
* A reorder control: the same `control control-quiet control-icon` the
* sibling rows in this view use (#231), with a chevron off the shared
* glyph set (#251) rather than the words "up" and "down".
*
* `data-move` is how `refreshProviderEdges` finds it (#255). Position in
* the row is not a contract — the day a third button joins the row, an
* ordinal selector would quietly disable the wrong control.
*/
function moveButton(glyph: "chevronUp" | "chevronDown", label: string): HTMLButtonElement {
function moveButton(direction: "up" | "down", label: string): HTMLButtonElement {
const button = el("button", "control control-quiet control-icon provider-move");
button.type = "button";
button.dataset.move = direction;
button.setAttribute("aria-label", label);
button.title = label;
button.append(icon(glyph));
button.append(icon(direction === "up" ? "chevronUp" : "chevronDown"));
return button;
}
@@ -758,8 +763,8 @@ export function settingsMain(views: { hide: () => void }[], goHome: () => void):
budgetInput.classList.add("provider-budget");
budgetInput.setAttribute("aria-label", `${provider} daily budget`);
const up = moveButton("chevronUp", `move ${provider} up`);
const down = moveButton("chevronDown", `move ${provider} down`);
const up = moveButton("up", `move ${provider} up`);
const down = moveButton("down", `move ${provider} down`);
up.addEventListener("click", () => {
const previous = item.previousElementSibling;
@@ -865,8 +870,8 @@ export function settingsMain(views: { hide: () => void }[], goHome: () => void):
refreshProviderEdges = () => {
const items = [...list.children];
items.forEach((item, index) => {
const up = item.querySelector<HTMLButtonElement>("button:nth-of-type(1)");
const down = item.querySelector<HTMLButtonElement>("button:nth-of-type(2)");
const up = item.querySelector<HTMLButtonElement>('[data-move="up"]');
const down = item.querySelector<HTMLButtonElement>('[data-move="down"]');
if (up) up.disabled = index === 0;
if (down) down.disabled = index === items.length - 1;
});
+4 -5
View File
@@ -1093,7 +1093,10 @@ body {
border-color: var(--line);
}
.control-quiet:hover,
/* `:not(:disabled)` because this rule sits after `.control:disabled` at the
same specificity: without it a disabled quiet control lights accent-bright
under the cursor and invites a click that does nothing (#255). */
.control-quiet:hover:not(:disabled),
.control-quiet[aria-expanded="true"] {
color: var(--accent-bright);
border-color: var(--accent);
@@ -2407,7 +2410,3 @@ select.form-input {
.provider-budget {
width: 5.5rem;
}
.provider-move:disabled {
visibility: hidden;
}