fix(ui): release the scroll lock on closed drawers, add stepper wizard controls, slide tabs
The scroll lock was mine, and it broke every page carrying a Drawer or Modal. Making dialog visibility testable, I replaced a size check with a data-show check -- but a Drawer animates open, so its panel cannot be hidden with data-show at all: display:none is not transitionable. Every closed Drawer therefore looked open, took the body scroll lock and never released it, and the page could not be scrolled. Both components publish data-open, which is the signal that actually means open, and that is what is read now. Stepper gains the wizard surface: showPanel renders each step body and shows only the active one, the same contract Tabs uses, and controls adds Back, Skip and Next, which becomes Finish on the last step. nextDisabled lets a form hold the step; the component never validates anything itself, since the page owns the form. Stepper also gets a single root. The panels and controls were siblings of the list, so the component had several roots and anything scoped to data-ui-component missed most of it. Tabs panels now slide in the direction of travel rather than fading upward. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2953,6 +2953,15 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
if (!dialog || !dialog.isConnected) return false;
|
||||
if (dialog.hasAttribute("hidden")) return false;
|
||||
if (dialog.closest('[data-show="false"]')) return false;
|
||||
/*
|
||||
* data-open is the signal Modal and Drawer actually publish, and it is the
|
||||
* only one Drawer can publish: its panel animates open, so it cannot be
|
||||
* toggled with data-show, which sets display:none. Treating every dialog
|
||||
* without a data-show="false" ancestor as open meant a closed Drawer held
|
||||
* the body scroll lock forever and the page could not be scrolled.
|
||||
*/
|
||||
var owner = dialog.closest("[data-open]");
|
||||
if (owner) return owner.getAttribute("data-open") === "true";
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1067,3 +1067,46 @@ test("json inside a textarea is left alone, not read as mustaches", () => {
|
||||
// evaluated {"id":"a-1","count":3} away and left it empty.
|
||||
expect(doc.querySelector("#editor")!.textContent).toBe('{"id":"a-1","count":3}');
|
||||
});
|
||||
|
||||
test("a closed drawer does not hold the body scroll lock", () => {
|
||||
const win = mount(
|
||||
`<div data-ui-component="Drawer" data-open="false">
|
||||
<div class="layer">
|
||||
<section role="dialog" aria-modal="true" tabindex="-1">
|
||||
<button id="inside">Inside</button>
|
||||
</section>
|
||||
</div>
|
||||
</div>`,
|
||||
);
|
||||
const doc = win.document;
|
||||
/*
|
||||
* A drawer animates open, so its panel cannot be hidden with data-show --
|
||||
* display:none is not transitionable. It publishes data-open instead. Reading
|
||||
* only data-show made every closed drawer look open, which locked the body
|
||||
* and left the page unscrollable.
|
||||
*/
|
||||
expect(doc.body.style.overflow).not.toBe("hidden");
|
||||
|
||||
const outside = doc.querySelector("#inside") as unknown as HTMLElement;
|
||||
outside.focus();
|
||||
outside.dispatchEvent(keydown(win, "Tab"));
|
||||
// No trap either: focus is free to leave a closed dialog.
|
||||
expect(doc.activeElement!.id).toBe("inside");
|
||||
});
|
||||
|
||||
test("an open drawer locks the body scroll and traps Tab", () => {
|
||||
const win = mount(
|
||||
`<div data-ui-component="Drawer" data-open="true">
|
||||
<section role="dialog" aria-modal="true" tabindex="-1">
|
||||
<button id="first">First</button>
|
||||
<button id="last">Last</button>
|
||||
</section>
|
||||
</div>`,
|
||||
);
|
||||
const doc = win.document;
|
||||
expect(doc.body.style.overflow).toBe("hidden");
|
||||
const last = doc.querySelector("#last") as unknown as HTMLElement;
|
||||
last.focus();
|
||||
last.dispatchEvent(keydown(win, "Tab"));
|
||||
expect(doc.activeElement!.id).toBe("first");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user