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
39 changes: 39 additions & 0 deletions e2e/history-layout.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,42 @@ for (const [adapter, query, mail] of [
});
}
}

// Header markup copied from the Yii3 History grid: `SortState::header()` sets the order class on the link.
for (const [direction, order, arrow] of [
["ascending", "asc", "↑"],
["descending", "desc", "↓"],
]) {
test(`Yii3 History marks the ${direction} sorted column with an arrow`, async ({
page,
}) => {
await page.setContent(`
<!doctype html>
<html lang="en">
<body class="yii-debug">
<table class="yii-debug-table">
<thead>
<tr>
<th scope="col" aria-sort="${direction}"><a class="${order}" href="/debug?sort=time">Time</a></th>
<th scope="col"><a href="/debug?sort=method">Method</a></th>
</tr>
</thead>
</table>
</body>
</html>
`);
await page.addStyleTag({ path: stylesheet.pathname });

const marker = (selector) =>
page
.locator(selector)
.evaluate((link) => getComputedStyle(link, "::after").content);

expect(await marker("th[aria-sort] a"), "Sorted column arrow").toBe(
`"${arrow}"`,
);
expect(await marker("th:not([aria-sort]) a"), "Unsorted column").toBe(
"none",
);
});
}
2 changes: 1 addition & 1 deletion resources/assets/dist/css/debug.min.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion resources/assets/dist/js/debug.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion resources/assets/dist/js/focus.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 1 addition & 10 deletions resources/src/core/debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import "./history-cursor.js";
import { bindCopyControls } from "./clipboard.js";
import { initSectionPermalinks } from "./deep-links.js";
import {
dismissDropdowns,
focusDropdownItem,
onDisclosureClick,
onRevealClick,
prepareCellMoreControls,
Expand Down Expand Up @@ -67,16 +65,9 @@ import { requestParentToolbarDrawerClose } from "../toolbar/focus.js";
return;
}

/* The open menu answers first, so the drawer behind it keeps its Escape. */
if (focusDropdownItem(event)) {
return;
}

if (event.key === "Escape") {
var dropdownWasOpen = dismissDropdowns();

window.setTimeout(function () {
requestParentToolbarDrawerClose(event, window, dropdownWasOpen);
requestParentToolbarDrawerClose(event, window);
}, 0);
}
});
Expand Down
167 changes: 10 additions & 157 deletions resources/src/core/disclosure.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import { closest } from "./shared.js";
import { dropdownNavigationIndex } from "./dropdown.js";

/**
* Click-driven disclosure layers of the debugger page: dropdown menus,
* collapsible sections, the "Show more" cell boxes and the click-to-reveal
* controls guarding sensitive values.
*
* Keyboard handling stays with the page bootstrap, which owns the precedence
* between the layers; this module exposes the two steps that handler needs
* ({@link focusDropdownItem} and {@link dismissDropdowns}).
* Click-driven disclosure layers of the debugger page: the "Show more" cell
* boxes and the click-to-reveal controls guarding sensitive values.
*
* Usage example:
*
Expand All @@ -18,27 +12,10 @@ import { dropdownNavigationIndex } from "./dropdown.js";
* ```
*/

/** Selector of the trigger opening a dropdown menu. */
const DROPDOWN_TRIGGER = '[data-yii-debug-toggle="dropdown"]';

/** Focusable menu items; the browser activates them from the keyboard. */
const DROPDOWN_ITEMS = "a[href], button:not([disabled])";

/** Keys the dropdown menu answers with a roving focus move. */
const DROPDOWN_NAVIGATION_KEYS = ["ArrowDown", "ArrowUp", "Home", "End"];

function findToggle(node, kind) {
return closest(node, '[data-yii-debug-toggle="' + kind + '"]');
}

function dropdownItems(menu) {
return Array.from(menu.querySelectorAll(DROPDOWN_ITEMS)).filter(
function (item) {
return !item.hidden && item.getAttribute("aria-hidden") !== "true";
},
);
}

/**
* Pairs every "Show more" control with the body it controls and labels it after
* the state that body is rendered in.
Expand Down Expand Up @@ -69,148 +46,24 @@ export function prepareCellMoreControls() {
}
}

/** Closes every open dropdown but the one owning `except`. */
function hideDropdowns(except) {
var wrappers = document.querySelectorAll(".yii-debug-dropdown.is-open");
for (var i = 0; i < wrappers.length; i++) {
var menu = wrappers[i].querySelector(".yii-debug-dropdown-menu");
if (except && menu === except) {
continue;
}
wrappers[i].classList.remove("is-open");
var trigger = wrappers[i].querySelector(DROPDOWN_TRIGGER);
if (trigger) {
trigger.setAttribute("aria-expanded", "false");
}
}
}

/**
* Moves focus inside the dropdown the event started in.
*
* @returns {boolean} `true` when the key belonged to an open-able dropdown, so
* the caller stops before the layers behind it answer the same key.
*/
export function focusDropdownItem(event) {
var dropdownWrapper = closest(event.target, ".yii-debug-dropdown");
var dropdownTrigger = dropdownWrapper
? dropdownWrapper.querySelector(DROPDOWN_TRIGGER)
: null;
var dropdownMenu = dropdownWrapper
? dropdownWrapper.querySelector(".yii-debug-dropdown-menu")
: null;

if (
dropdownWrapper &&
dropdownTrigger &&
dropdownMenu &&
DROPDOWN_NAVIGATION_KEYS.indexOf(event.key) !== -1
) {
var items = dropdownItems(dropdownMenu);
var currentItem = items.indexOf(event.target);
var nextItem = dropdownNavigationIndex(
items.length,
currentItem,
event.key,
event.target === dropdownTrigger,
);

if (items.length === 0) {
return true;
}

event.preventDefault();
hideDropdowns(dropdownMenu);
dropdownWrapper.classList.add("is-open");
dropdownTrigger.setAttribute("aria-expanded", "true");

items[nextItem].focus();

return true;
}

return false;
}

/**
* Closes every dropdown and returns focus to the trigger of the one that was
* open.
*
* @returns {boolean} `true` when a dropdown absorbed the dismissal, so the
* layer behind it keeps its own Escape.
*/
export function dismissDropdowns() {
var openDropdown = document.querySelector(".yii-debug-dropdown.is-open");
var dropdownWasOpen = Boolean(openDropdown);
var openDropdownTrigger = openDropdown
? openDropdown.querySelector(DROPDOWN_TRIGGER)
: null;

hideDropdowns(null);

if (openDropdownTrigger) {
openDropdownTrigger.focus();
}

return dropdownWasOpen;
}

/** Toggles the disclosure layer a click landed on, closing the menus behind it. */
/** Toggles the "Show more" box a click landed on. */
export function onDisclosureClick(event) {
var dropdown = findToggle(event.target, "dropdown");
var collapse = findToggle(event.target, "collapse");
var cellMore = findToggle(event.target, "cell-more");

if (cellMore) {
var moreBox = closest(cellMore, ".yii-debug-cell-more");
event.preventDefault();

if (!moreBox) {
return;
}

var moreOpen = moreBox.classList.toggle("is-open");
cellMore.setAttribute("aria-expanded", moreOpen ? "true" : "false");
cellMore.textContent = moreOpen ? "Show less" : "Show more";
if (!cellMore) {
return;
}

if (collapse) {
var targetSelector =
collapse.getAttribute("data-target") || collapse.getAttribute("href");
var target = targetSelector ? document.querySelector(targetSelector) : null;
event.preventDefault();

if (!target) {
return;
}

var isShown = target.classList.contains("is-open");
target.classList.toggle("is-open", !isShown);
collapse.setAttribute("aria-expanded", isShown ? "false" : "true");
return;
}

if (dropdown) {
var wrapper = closest(dropdown, ".yii-debug-dropdown");
var menu = wrapper
? wrapper.querySelector(".yii-debug-dropdown-menu")
: null;
event.preventDefault();
event.stopPropagation();

if (!wrapper || !menu) {
return;
}
var moreBox = closest(cellMore, ".yii-debug-cell-more");
event.preventDefault();

var isOpen = wrapper.classList.contains("is-open");
hideDropdowns(menu);
wrapper.classList.toggle("is-open", !isOpen);
dropdown.setAttribute("aria-expanded", isOpen ? "false" : "true");
if (!moreBox) {
return;
}

hideDropdowns(null);
var moreOpen = moreBox.classList.toggle("is-open");
cellMore.setAttribute("aria-expanded", moreOpen ? "true" : "false");
cellMore.textContent = moreOpen ? "Show less" : "Show more";
}

/** Click-to-reveal toggle for sensitive User-panel fields. */
Expand Down
Loading
Loading