diff --git a/src/frontend/web/von_interface/static/js/components/taskPanel.js b/src/frontend/web/von_interface/static/js/components/taskPanel.js index 603b6dd9..03aa87d0 100644 --- a/src/frontend/web/von_interface/static/js/components/taskPanel.js +++ b/src/frontend/web/von_interface/static/js/components/taskPanel.js @@ -30,6 +30,9 @@ let _filterTaskSourceId = 'all'; let _isGlobalTabMode = false; // True when rendering into global tasks tab let _taskDetailState = {}; // taskId -> detail panel state let _selectedTaskId = ''; +let _pendingTaskNavigation = null; +let _referencedGlobalTaskId = ''; +let _panelReturnFocus = null; let _panelTaskIds = null; let _panelLoadGeneration = 0; let _bulkTaskVisibility = 'exclude'; @@ -182,6 +185,8 @@ function resetTaskPanelScopedState(activeOrganisationConceptId, { actorChanged = _taskQueueActivityGeneration += 1; stopTaskQueueActivityPolling(); _taskQueueActivityLoadPromise = null; + _pendingTaskNavigation = null; + _referencedGlobalTaskId = ''; _activeOrganisationConceptId = activeOrganisationConceptId; _globalTaskLoadGeneration += 1; _isLoading = false; @@ -757,6 +762,20 @@ export function initializeTaskPanel() { ensureTaskPanelAuthStatusListener(); renderTaskGroupFilterControls(); + _panelEl.addEventListener('keydown', (event) => { + if (event.key === 'Escape') { + event.preventDefault(); + event.stopPropagation(); + hideTaskPanel(); + } + }); + document.addEventListener('pointerdown', (event) => { + if (_isVisible && !_panelEl.contains(event.target) + && !event.target.closest('#taskPanelToggleBtn')) { + hideTaskPanel({ restoreFocus: false }); + } + }); + // Set up close button const closeBtn = document.getElementById('closeTaskPanel'); if (closeBtn) { @@ -823,12 +842,14 @@ export function showTaskPanel(options = {}) { _isGlobalTabMode = false; syncTaskQueueActivityPolling(); _taskListEl = document.getElementById('taskList') || _taskListEl; + if (!_isVisible) _panelReturnFocus = document.activeElement; _panelEl.classList.remove('hidden'); _panelEl.setAttribute('aria-hidden', 'false'); _panelEl.querySelectorAll('.task-create-form, .task-filter-row').forEach(element => { element.classList.toggle('hidden', _panelTaskIds !== null); }); _isVisible = true; + document.getElementById('closeTaskPanel')?.focus(); // Auto-refresh tasks when panel becomes visible return refreshTasks(); } @@ -870,11 +891,12 @@ async function loadReferencedPanelTasks() { /** * Hide the task panel. */ -export function hideTaskPanel() { +export function hideTaskPanel({ restoreFocus = true } = {}) { if (_panelEl) { _panelEl.classList.add('hidden'); _panelEl.setAttribute('aria-hidden', 'true'); _isVisible = false; + if (restoreFocus && _panelReturnFocus?.isConnected) _panelReturnFocus.focus(); } } @@ -909,6 +931,7 @@ async function openGlobalTasks() { _tasks = []; _taskDetailState = {}; _selectedTaskId = ''; + _referencedGlobalTaskId = ''; ensureTaskPanelOrganisationSwitchListener(); ensureTaskPanelAuthStatusListener(); ensureTaskQueueActivityPollingListeners(); @@ -939,6 +962,17 @@ async function openGlobalTasks() { loadGlobalTasks({ telemetry }), loadTaskQueueActivity(), ]); + if (_pendingTaskNavigation) { + const task = _pendingTaskNavigation; + _pendingTaskNavigation = null; + const taskId = getTaskId(task); + if (!_tasks.some(item => getTaskId(item) === taskId)) _tasks.push(task); + _referencedGlobalTaskId = taskId; + await selectTask(taskId); + const inspector = _globalTasksContainer.querySelector('#globalTaskInspector'); + inspector?.setAttribute('tabindex', '-1'); + inspector?.focus(); + } syncTaskQueueActivityPolling(); } @@ -1508,6 +1542,7 @@ function getFilteredTasks() { // Exact references remain reachable regardless of saved list filters. if (!_isGlobalTabMode && _panelTaskIds !== null) return _tasks; return _tasks.filter((task) => { + if (_isGlobalTabMode && getTaskId(task) === _referencedGlobalTaskId) return true; if (!taskMatchesGlobalScope(task)) { return false; } @@ -3440,9 +3475,10 @@ function renderTaskItem(task) {