feat: complete legacy parity and modernize operator UI

This commit is contained in:
2026-07-22 12:34:46 +09:00
parent fc4007d676
commit 939c252d23
149 changed files with 26515 additions and 1736 deletions

View File

@@ -101,7 +101,7 @@
return true;
}
function syncMarket(tabId) {
function syncMarket(tabId, options) {
const button = marketButton(tabId);
if (!button) return false;
activeMarketTabId = tabId;
@@ -110,20 +110,42 @@
refreshCurrentMenu();
title.textContent = marketLabel(button);
}
if (pendingKeyboardMarketTabId && !button.disabled) {
const pendingButton = marketButton(pendingKeyboardMarketTabId);
const focusWasLost = !document.activeElement ||
document.activeElement === document.body ||
document.activeElement === document.documentElement;
if (activeWorkspace === "catalog" && pendingButton &&
!pendingButton.disabled && focusWasLost) {
pendingButton.focus();
}
const authoritativeNativeRender = options &&
options.authoritativeNativeRender === true;
if (authoritativeNativeRender && pendingKeyboardMarketTabId &&
pendingKeyboardMarketTabId !== tabId) {
// The native operation completed on a different tab. Do not carry a
// failed or superseded keyboard request into a later render.
pendingKeyboardMarketTabId = null;
}
if (authoritativeNativeRender && pendingKeyboardMarketTabId === tabId &&
!button.disabled) {
const pendingTabId = pendingKeyboardMarketTabId;
// Native loading temporarily disables the selected menu button, which
// can move focus to <body>. Only the final, non-busy native render may
// consume this request; the optimistic capture-phase click must not.
window.requestAnimationFrame(function () {
if (pendingKeyboardMarketTabId !== pendingTabId) return;
pendingKeyboardMarketTabId = null;
const pendingButton = marketButton(pendingTabId);
const active = document.activeElement;
const focusWasLost = !active ||
active === document.body ||
active === document.documentElement;
if (activeWorkspace === "catalog" && pendingButton &&
!pendingButton.disabled && focusWasLost) {
pendingButton.focus();
}
});
}
return true;
}
function hasPendingKeyboardMarketFocus(tabId) {
return pendingKeyboardMarketTabId !== null &&
(tabId === undefined || tabId === pendingKeyboardMarketTabId);
}
function select(name) {
if (views[name]) return setWorkspace(name);
if (!syncMarket(name)) return false;
@@ -154,8 +176,12 @@
event.preventDefault();
event.stopPropagation();
const keyboardMarketTabId = menuButton.dataset.tabId || null;
pendingKeyboardMarketTabId = keyboardMarketTabId;
menuButton.click();
if (keyboardMarketTabId) pendingKeyboardMarketTabId = keyboardMarketTabId;
return;
}
if (event.key === "Tab") {
pendingKeyboardMarketTabId = null;
return;
}
if (event.key !== "ArrowUp" && event.key !== "ArrowDown") return;
@@ -164,6 +190,12 @@
focusAdjacentItem(event.key === "ArrowUp" ? -1 : 1);
});
document.addEventListener("pointerdown", function () {
// A real pointer action transfers focus ownership to the user. A delayed
// native response must never pull it back to the keyboard-selected menu.
pendingKeyboardMarketTabId = null;
}, true);
// Reveal the selected catalog screen before app.js dispatches its native
// select-tab intent. The native state response remains authoritative and
// is reconciled through syncMarket after every render.
@@ -180,6 +212,7 @@
window.LegacyWorkspaceNavigation = Object.freeze({
select: select,
syncMarket: syncMarket,
hasPendingKeyboardMarketFocus: hasPendingKeyboardMarketFocus,
setExpanded: setNavigationExpanded,
active: function () { return activeWorkspace; },
activeMarket: function () { return activeMarketTabId; },