stackchain-dashboard/frontend/mobile-create-issue-nav.js
timmy fe95b4e32a
All checks were successful
CI / lint (pull_request) Successful in 2m12s
CI / build-release (pull_request) Successful in 6s
CI / browser-journey (pull_request) Successful in 1m1s
CI / release-candidate (pull_request) Has been skipped
fix: keep mobile filing navigation selected
2026-08-16 15:12:02 +00:00

123 lines
4.5 KiB
JavaScript

function createMobileCreateIssueNavigation(options) {
const buttons = options.buttons || {};
const targets = options.targets || {};
const listeners = new Map();
const prefersReducedMotion = options.prefersReducedMotion || (() => false);
const targetNames = new Map(Object.entries(targets).map(([name, target]) => [target, name]));
let filingAvailable = !options.filing?.hidden;
let observer = null;
let navigationLockUntil = 0;
function select(name) {
Object.entries(buttons).forEach(([key, button]) => {
if (!button) return;
if (key === name) button.setAttribute('aria-current', 'location');
else button.removeAttribute('aria-current');
});
}
function setFilingAvailable(available) {
filingAvailable = Boolean(available);
const button = buttons.file;
if (!button) return;
if (filingAvailable) button.removeAttribute('aria-disabled');
else button.setAttribute('aria-disabled', 'true');
}
function navigate(name) {
if (name === 'file') setFilingAvailable(!options.filing?.hidden);
if (name === 'file' && !filingAvailable) return false;
const target = targets[name];
if (!target) return false;
target.scrollIntoView({
block: 'start',
behavior: prefersReducedMotion() ? 'auto' : 'smooth',
});
navigationLockUntil = Date.now() + 500;
select(name);
return true;
}
function reset(available = !options.filing?.hidden) {
setFilingAvailable(available);
select('describe');
}
return {
start() {
Object.entries(buttons).forEach(([name, button]) => {
if (!button || listeners.has(button)) return;
const listener = event => {
event.preventDefault();
navigate(name);
};
listeners.set(button, listener);
button.addEventListener('click', listener);
});
reset();
const observe = options.observe || ((handler, observedTargets, root) => {
if (typeof IntersectionObserver === 'undefined') return null;
const instance = new IntersectionObserver(handler, {
root,
rootMargin: '-20% 0px -60% 0px',
threshold: [0, 0.25, 0.5, 0.75, 1],
});
observedTargets.forEach(target => instance.observe(target));
return instance;
});
observer = observe(entries => {
if (Date.now() < navigationLockUntil) return;
const visible = entries
.filter(entry => entry.isIntersecting && targetNames.has(entry.target))
.filter(entry => targetNames.get(entry.target) !== 'file' || filingAvailable)
.sort((left, right) => right.intersectionRatio - left.intersectionRatio)[0];
if (visible) select(targetNames.get(visible.target));
}, Array.from(targetNames.keys()).filter(Boolean), options.root || null);
},
stop() {
listeners.forEach((listener, button) => button.removeEventListener('click', listener));
listeners.clear();
if (observer) observer.disconnect();
observer = null;
},
navigate,
reset,
select,
setFilingAvailable,
};
}
function attachMobileCreateIssueNavigation({ document, window }) {
const bySection = name => document.querySelector('[data-create-issue-section="' + name + '"]');
const filing = document.getElementById('create-issue-filing');
const sheet = document.getElementById('create-issue-sheet');
const navigation = createMobileCreateIssueNavigation({
root:document.querySelector('.create-issue-panel'),
buttons:{describe:bySection('describe'), evidence:bySection('evidence'), file:bySection('file')},
targets:{
describe:document.getElementById('create-issue-describe'),
evidence:document.getElementById('create-issue-evidence'),
file:filing,
},
filing,
prefersReducedMotion:() => window.matchMedia('(prefers-reduced-motion: reduce)').matches,
});
navigation.start();
document.getElementById('file-new-issue').addEventListener('click', () => {
setTimeout(() => navigation.navigate('file'), 0);
});
new MutationObserver(() => {
navigation.setFilingAvailable(!filing.hidden);
if (!filing.hidden && sheet.classList.contains('open')) navigation.navigate('file');
}).observe(filing, {attributes:true, attributeFilter:['hidden']});
new MutationObserver(() => {
if (sheet.classList.contains('open')) navigation.reset(!filing.hidden);
}).observe(sheet, {
attributes:true, attributeFilter:['class'],
});
return navigation;
}
if (typeof module !== 'undefined') module.exports = createMobileCreateIssueNavigation;
else attachMobileCreateIssueNavigation({document, window});