Skip to content

Commit 3a62cc5

Browse files
authored
Merge pull request #129 from LennartvdM/codex/implement-delegated-root-tab-handler-with-logging
Implement delegated root tab handling with logging
2 parents 75b1f59 + 3ed6265 commit 3a62cc5

3 files changed

Lines changed: 91 additions & 83 deletions

File tree

web/app.js

Lines changed: 78 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { DEBUG } from './debug.js';
22
import { createRadialUrchin } from './ui/visuals/RadialUrchin.js';
33

4+
console.info('[app] app.js loaded');
5+
46
const tabList = document.querySelector('.tab-bar[role="tablist"]');
57
if (tabList instanceof HTMLElement && !tabList.hasAttribute('aria-orientation')) {
68
tabList.setAttribute('aria-orientation', 'horizontal');
@@ -1045,11 +1047,16 @@ let consoleIndicator;
10451047
let pendingAutoSwitch = false;
10461048

10471049
function focusTabPanel(targetTab) {
1048-
const panel = tabPanelMap.get(targetTab);
1050+
if (typeof targetTab !== 'string') {
1051+
return;
1052+
}
1053+
const normalized = targetTab.toLowerCase();
1054+
collectRootTabElements();
1055+
const panel = tabPanelMap.get(normalized);
10491056
if (!panel) {
10501057
return;
10511058
}
1052-
const heading = tabHeadings.get(targetTab);
1059+
const heading = tabHeadings.get(normalized);
10531060
const focusTarget = heading instanceof HTMLElement ? heading : panel;
10541061
if (focusTarget && typeof focusTarget.focus === 'function') {
10551062
try {
@@ -1060,28 +1067,32 @@ function focusTabPanel(targetTab) {
10601067
}
10611068
}
10621069

1063-
function goToRootTab(targetTab, options = {}) {
1064-
console.info('[root-tabs] switching to', targetTab);
1065-
const { focusPanel = false } = options;
1066-
if (typeof targetTab !== 'string') {
1070+
function goToRootTab(id) {
1071+
if (typeof id !== 'string' || !id) {
10671072
return;
10681073
}
1069-
const normalizedTarget = targetTab.toLowerCase();
1074+
console.info('[root-tabs] switching to', id);
1075+
const normalizedTarget = id.toLowerCase();
10701076
if (!tabOrder.includes(normalizedTarget)) {
10711077
return;
10721078
}
10731079

1074-
tabButtons.forEach((button) => {
1075-
const buttonId = (button.dataset.rootTab || button.dataset.tab || '').toLowerCase();
1080+
collectRootTabElements();
1081+
1082+
const tabs = document.querySelectorAll('[data-root-tab]');
1083+
const panels = document.querySelectorAll('[data-root-panel]');
1084+
1085+
tabs.forEach((button) => {
1086+
const buttonId = (button.dataset.rootTab || '').toLowerCase();
10761087
const isActive = buttonId === normalizedTarget;
10771088
button.classList.toggle('active', isActive);
10781089
button.classList.toggle('root-tab--active', isActive);
10791090
button.setAttribute('aria-selected', isActive ? 'true' : 'false');
10801091
button.setAttribute('tabindex', isActive ? '0' : '-1');
10811092
});
10821093

1083-
tabPanels.forEach((panel) => {
1084-
const panelId = (panel.dataset.rootPanel || panel.dataset.tab || '').toLowerCase();
1094+
panels.forEach((panel) => {
1095+
const panelId = (panel.dataset.rootPanel || '').toLowerCase();
10851096
const isActive = panelId === normalizedTarget;
10861097
panel.classList.toggle('active', isActive);
10871098
panel.classList.toggle('root-panel--active', isActive);
@@ -1101,61 +1112,68 @@ function goToRootTab(targetTab, options = {}) {
11011112
});
11021113
pendingAutoSwitch = false;
11031114
}
1104-
if (focusPanel) {
1105-
focusTabPanel(normalizedTarget);
1106-
}
11071115
}
11081116

11091117
function initRootTabs() {
1110-
if (!document.body || document.body.dataset.rootTabsHydrated === '1') {
1118+
if (!document.body) {
1119+
return;
1120+
}
1121+
if (document.body.dataset.rootTabsHydrated === '1') {
1122+
console.info('[root-tabs] already hydrated');
11111123
return;
11121124
}
1113-
1114-
collectRootTabElements();
1115-
1116-
const tabs = tabButtons;
1117-
const panels = tabPanels;
1118-
1119-
console.info(
1120-
'[root-tabs] found tabs:',
1121-
tabs.map((tab) => tab.dataset.rootTab || tab.dataset.tab || '')
1122-
);
1123-
console.info(
1124-
'[root-tabs] found panels:',
1125-
panels.map((panel) => panel.dataset.rootPanel || panel.dataset.tab || '')
1126-
);
1127-
11281125
document.body.dataset.rootTabsHydrated = '1';
11291126

1130-
if (tabs.length === 0 || panels.length === 0) {
1127+
const bar = document.querySelector('[data-root-tabs]');
1128+
if (!bar) {
1129+
console.warn('[root-tabs] no [data-root-tabs] container found');
11311130
return;
11321131
}
11331132

1134-
tabs.forEach((tab) => {
1135-
tab.addEventListener('click', (event) => {
1136-
console.info('[root-tabs] click on', tab.dataset.rootTab || tab.dataset.tab);
1137-
event.preventDefault();
1138-
const targetId = tab.dataset.rootTab || tab.dataset.tab;
1139-
if (targetId) {
1140-
goToRootTab(targetId, { focusPanel: true });
1141-
}
1142-
});
1133+
console.info('[root-tabs] hydrating');
1134+
1135+
collectRootTabElements();
1136+
1137+
bar.addEventListener('click', (event) => {
1138+
const btn = event.target.closest('[data-root-tab]');
1139+
if (!btn) {
1140+
return;
1141+
}
1142+
event.preventDefault();
1143+
const id = btn.dataset.rootTab;
1144+
if (!id) {
1145+
return;
1146+
}
1147+
console.info('[root-tabs] click on', id);
1148+
goToRootTab(id);
1149+
focusTabPanel(id);
11431150
});
11441151

1145-
const defaultTabButton =
1146-
tabs.find((button) => button.classList.contains('root-tab--active')) ||
1147-
tabs.find((button) => button.classList.contains('active')) ||
1148-
tabs[0];
1152+
tabButtons.forEach((button) => {
1153+
button.addEventListener('keydown', handleRootTabKeydown);
1154+
});
1155+
1156+
if (consoleTabButton) {
1157+
consoleTabButton.style.display = 'inline-flex';
1158+
consoleTabButton.style.alignItems = 'center';
1159+
consoleTabButton.style.justifyContent = 'center';
11491160

1150-
const defaultTarget =
1151-
defaultTabButton?.dataset.rootTab ||
1152-
defaultTabButton?.dataset.tab ||
1153-
tabOrder[0];
1161+
consoleIndicator = document.createElement('span');
1162+
consoleIndicator.textContent = '•';
1163+
consoleIndicator.setAttribute('aria-hidden', 'true');
1164+
consoleIndicator.style.marginLeft = '4px';
1165+
consoleIndicator.style.fontSize = '10px';
1166+
consoleIndicator.style.color = '#22d3ee';
1167+
consoleIndicator.style.opacity = '0';
1168+
consoleIndicator.style.transition = 'opacity 0.2s ease';
1169+
consoleIndicator.style.pointerEvents = 'none';
1170+
consoleTabButton.append(consoleIndicator);
1171+
}
11541172

1155-
if (defaultTarget) {
1156-
goToRootTab(defaultTarget, { focusPanel: false });
1157-
} else if (tabOrder.length > 0) {
1158-
goToRootTab(tabOrder[0], { focusPanel: false });
1173+
const firstActive = document.querySelector('[data-root-tab].root-tab--active');
1174+
const first = firstActive || document.querySelector('[data-root-tab]');
1175+
if (first && first.dataset.rootTab) {
1176+
goToRootTab(first.dataset.rootTab);
11591177
}
11601178
}
11611179

@@ -1164,28 +1182,16 @@ registerIntentHandler(INTENT_TYPES.NAVIGATE_TAB, (payload = {}) => {
11641182
if (!target) {
11651183
return;
11661184
}
1167-
const shouldFocus = payload.focusPanel === true;
1168-
goToRootTab(target, { focusPanel: shouldFocus });
1185+
goToRootTab(target);
1186+
if (payload.focusPanel === true) {
1187+
focusTabPanel(target);
1188+
}
11691189
});
11701190

1171-
initRootTabs();
1172-
1173-
if (consoleTabButton) {
1174-
consoleTabButton.style.display = 'inline-flex';
1175-
consoleTabButton.style.alignItems = 'center';
1176-
consoleTabButton.style.justifyContent = 'center';
1177-
1178-
consoleIndicator = document.createElement('span');
1179-
consoleIndicator.textContent = '•';
1180-
consoleIndicator.setAttribute('aria-hidden', 'true');
1181-
consoleIndicator.style.marginLeft = '4px';
1182-
consoleIndicator.style.fontSize = '10px';
1183-
consoleIndicator.style.color = '#22d3ee';
1184-
consoleIndicator.style.opacity = '0';
1185-
consoleIndicator.style.transition = 'opacity 0.2s ease';
1186-
consoleIndicator.style.pointerEvents = 'none';
1187-
consoleTabButton.append(consoleIndicator);
1188-
}
1191+
document.addEventListener('DOMContentLoaded', () => {
1192+
console.info('[app] DOMContentLoaded');
1193+
initRootTabs();
1194+
});
11891195

11901196
function handleRootTabKeydown(event) {
11911197
const { key } = event;
@@ -1241,10 +1247,6 @@ function handleRootTabKeydown(event) {
12411247
}
12421248
}
12431249

1244-
tabButtons.forEach((button) => {
1245-
button.addEventListener('keydown', handleRootTabKeydown);
1246-
});
1247-
12481250
function tabFromShortcutKey(key) {
12491251
const numericKey = Number.parseInt(key, 10);
12501252
if (Number.isNaN(numericKey)) {

web/index.html

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,15 @@
88
</head>
99
<body>
1010
<div class="workbench">
11-
<nav class="tab-bar" role="tablist" aria-label="Workbench tabs">
11+
<nav
12+
id="root-tab-bar"
13+
class="tab-bar"
14+
role="tablist"
15+
aria-label="Workbench tabs"
16+
data-root-tabs
17+
>
1218
<button
13-
class="tab root-tab root-tab--active active"
19+
class="tab root-tab root-tab--active"
1420
type="button"
1521
id="tab-visuals"
1622
role="tab"
@@ -107,7 +113,6 @@ <h2 class="tab-panel-title" data-tab-heading tabindex="-1">Visuals</h2>
107113
aria-labelledby="tab-config"
108114
data-tab="config"
109115
data-root-panel="config"
110-
hidden
111116
tabindex="-1"
112117
>
113118
<h2 class="tab-panel-title" data-tab-heading tabindex="-1">Config</h2>
@@ -241,7 +246,6 @@ <h2 class="tab-panel-title" data-tab-heading tabindex="-1">Config</h2>
241246
aria-labelledby="tab-console"
242247
data-tab="console"
243248
data-root-panel="console"
244-
hidden
245249
tabindex="-1"
246250
>
247251
<h2 class="tab-panel-title" data-tab-heading tabindex="-1">Console</h2>
@@ -253,7 +257,6 @@ <h2 class="tab-panel-title" data-tab-heading tabindex="-1">Console</h2>
253257
aria-labelledby="tab-json"
254258
data-tab="json"
255259
data-root-panel="json"
256-
hidden
257260
tabindex="-1"
258261
>
259262
<h2 class="tab-panel-title" data-tab-heading tabindex="-1">JSON</h2>
@@ -265,7 +268,6 @@ <h2 class="tab-panel-title" data-tab-heading tabindex="-1">JSON</h2>
265268
aria-labelledby="tab-fixtures"
266269
data-tab="fixtures"
267270
data-root-panel="fixtures"
268-
hidden
269271
tabindex="-1"
270272
>
271273
<h2 class="tab-panel-title" data-tab-heading tabindex="-1">Fixtures</h2>
@@ -277,7 +279,6 @@ <h2 class="tab-panel-title" data-tab-heading tabindex="-1">Fixtures</h2>
277279
aria-labelledby="tab-logs"
278280
data-tab="logs"
279281
data-root-panel="logs"
280-
hidden
281282
tabindex="-1"
282283
>
283284
<h2 class="tab-panel-title" data-tab-heading tabindex="-1">Logs</h2>

web/style.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,14 @@ body {
9494
color: #ffffff;
9595
}
9696

97+
.root-tab {
98+
opacity: 0.7;
99+
}
100+
97101
.root-tab--active {
98102
background: #3a3f4f;
99103
color: #ffffff;
104+
opacity: 1;
100105
}
101106

102107
.tab-content {

0 commit comments

Comments
 (0)