feat: contain mobile issue capture focus (Closes #1046)

This commit is contained in:
timmy committed 2026-08-17 22:55:04 +00:00
1 parent c655cf5861
commit be10dba4bd
4 files changed
+185 -2

No files matched your search

+52
View File
@@ -8,6 +8,57 @@ function newIssueOperationId() {
return String(Date.now()) + '-' + Math.random().toString(16).slice(2);
}
function createIssueModalLifecycle({ root, background = [], document, requestClose }) {
let launcher = null;
let active = false;
let backgroundState = [];
const focusableSelector = [
'a[href]', 'button:not([disabled])', 'input:not([disabled])',
'select:not([disabled])', 'textarea:not([disabled])',
'[tabindex]:not([tabindex="-1"])',
].join(',');
const focusable = () => Array.from(root.querySelectorAll(focusableSelector)).filter(element =>
!element.hidden && !element.disabled && element.getClientRects().length > 0
);
function keydown(event) {
if (!active) return;
if (event.key === 'Escape') {
event.preventDefault();
requestClose();
return;
}
if (event.key !== 'Tab') return;
const controls = focusable();
if (!controls.length) return;
const first = controls[0];
const last = controls[controls.length - 1];
if (event.shiftKey && document.activeElement === first) {
event.preventDefault();
last.focus();
} else if (!event.shiftKey && document.activeElement === last) {
event.preventDefault();
first.focus();
}
}
root.addEventListener('keydown', keydown);
return {
open(trigger = null) {
launcher = trigger?.isConnected ? trigger : document.activeElement;
backgroundState = background.map(element => [element, element.inert]);
backgroundState.forEach(([element]) => { element.inert = true; });
active = true;
(root.querySelector('#cancel-new-issue') || focusable()[0])?.focus();
},
close({ restore = true } = {}) {
active = false;
backgroundState.forEach(([element, inert]) => { element.inert = inert; });
backgroundState = [];
if (restore && launcher?.isConnected) launcher.focus();
launcher = null;
},
};
}
function normalizeSharedContent(value = {}) {
const clean = input => String(input || '').replace(/\s+/g, ' ').trim();
const text = String(value.text || '').trim().slice(0, 9500);
@@ -840,5 +891,6 @@ createIssueCapture.buildRelatedDraft = buildRelatedDraft;
createIssueCapture.relatedChecklistDraft = relatedChecklistDraft;
createIssueCapture.linkChecklistTask = linkChecklistTask;
createIssueCapture.createChecklistPromotion = createChecklistPromotion;
createIssueCapture.createModalLifecycle = createIssueModalLifecycle;
if (typeof module !== 'undefined' && module.exports) module.exports = createIssueCapture;
+21 -1
View File
@@ -4692,7 +4692,25 @@
sharedLaunchState = null;
}
let createIssueModalLifecycle = null;
let createIssueLauncher = null;
function issueCaptureModal() {
if (createIssueModalLifecycle) return createIssueModalLifecycle;
const root = qs('#create-issue-sheet');
const background = Array.from(document.body.children).filter(element =>
element !== root && element.tagName !== 'SCRIPT'
);
createIssueModalLifecycle = createIssueCapture.createModalLifecycle({
root, background, document, requestClose:() => closeCreateIssueSheet(),
});
return createIssueModalLifecycle;
}
async function openCreateIssueSheet(navigate = true) {
const root = qs('#create-issue-sheet');
if (!root.classList.contains('open') && document.activeElement && !root.contains(document.activeElement)) {
createIssueLauncher = document.activeElement;
}
if (!issueCapture && !await ensureIssueCapture()) return;
if (navigate) {
taskOverlayHistory.open('new');
@@ -4751,6 +4769,7 @@
updateIssueCreateActions();
qs('#create-issue-sheet').classList.add('open');
creatingIssue = true;
issueCaptureModal().open(createIssueLauncher);
if (!sharedImageHandled && sharedImageMarker) {
sharedImageHandled = true;
if (createIssueAttachmentController.state()) {
@@ -4816,13 +4835,14 @@
clearTimeout(duplicateCheckTimer);
qs('#create-issue-duplicates').hidden = true;
creatingIssue = false;
issueCaptureModal().close({restore:!followUpSourceUpdate});
createIssueLauncher = null;
if (followUpSourceUpdate) {
const source = followUpSourceUpdate;
followUpSourceUpdate = null;
notificationReader.open(source.item, source.detail);
return;
}
qs('#new-issue').focus();
}
function applyOutboxResult(result, openCreated = false, startCreated = false) {