(function (root, factory) { if (typeof module === 'object' && module.exports) module.exports = factory; else root.createMobileTaskDock = factory; })(typeof self !== 'undefined' ? self : this, function createMobileTaskDock(options) { const nav = options.nav; const buttons = options.buttons; const overlays = options.overlays || []; let launcher = null; let wasHidden = false; function select(name) { if (!buttons[name]) return false; Object.entries(buttons).forEach(([key, button]) => { if (key === name) button.setAttribute('aria-current', 'page'); else button.removeAttribute('aria-current'); }); return true; } function refreshVisibility() { const hidden = overlays.some(overlay => overlay.open || overlay.classList.contains('open')); nav.hidden = hidden; if (options.sessionHud) { if (hidden) options.sessionHud.setAttribute('data-overlay-hidden', 'true'); else options.sessionHud.removeAttribute('data-overlay-hidden'); } if (wasHidden && !hidden && launcher) launcher.focus(); wasHidden = hidden; } function openQueues() { if (!options.queueSheet || options.queueSheet.open) return; options.queueSheet.showModal(); } function closeQueues() { options.queueSheet?.open && options.queueSheet.close(); } function start() { Object.entries(buttons).forEach(([name, button]) => { button.addEventListener('click', event => { launcher = event.currentTarget; select(name); name === 'queues' && options.queueSheet ? openQueues() : options.actions[name](); }); }); if (options.queueSheet) { options.queueClose?.addEventListener('click', closeQueues); options.queueSheet.addEventListener('cancel', event => { event.preventDefault(); closeQueues(); }); options.queueSheet.addEventListener('close', () => buttons.queues?.focus()); Object.entries(options.queueRows || {}).forEach(([name, row]) => { row.addEventListener('click', () => { closeQueues(); options.onSelectQueue?.(name, row); }); }); } options.observe(refreshVisibility, overlays); refreshVisibility(); } function updateWork(mode) { const text = { continue:'Continue', resume:'Resume', start:'Start', plan:'Plan', find:'Find', attention:'Attention', update:'Updates', agenda:'Agenda', later:'Later', draft:'Drafts', }[mode] || 'Work'; const queue = ['Attention', 'Updates', 'Agenda', 'Later', 'Drafts'].includes(text); if (options.workLabel) options.workLabel.textContent = text; if (buttons.work) buttons.work.setAttribute('aria-label', queue ? (mode === 'update' ? 'Resume ' : 'Open ') + text : mode === 'find' ? 'Find work' : text + (mode === 'work' ? '' : ' Today')); } function updateAttention(count) { const total = Math.max(0, Number(count) || 0); const visible = total > 0; if (options.attentionBadge) { options.attentionBadge.textContent = String(total); options.attentionBadge.hidden = !visible; } if (buttons.attention) { buttons.attention.hidden = !visible; buttons.attention.setAttribute('aria-label', 'Attention, ' + total + ' items'); } if (visible) nav.setAttribute('data-attention', 'true'); else nav.removeAttribute('data-attention'); } function updateQueues(counts) { const names = 'today agenda attention update later draft'.split(' '); const normalized = Object.fromEntries(names.map(name => [name, Math.max(0, Number(counts?.[name]) || 0)])); const actionableNames = ['today', 'attention', 'update', 'later', 'draft']; const active = actionableNames.reduce((total, name) => total + (normalized[name] > 0 ? 1 : 0), 0); Object.entries(options.queueCounts || {}).forEach(([name, element]) => { element.textContent = String(normalized[name] || 0); }); options.queueRows?.update?.setAttribute( 'aria-label', 'Updates, ' + normalized.update + ' unread conversations' ); if (options.queueBadge) { options.queueBadge.textContent = active + ' active'; options.queueBadge.hidden = active === 0; } if (options.deadlineBadge) { options.deadlineBadge.textContent = normalized.agenda + ' due'; options.deadlineBadge.hidden = normalized.agenda === 0; } if (options.queueRows?.agenda) { if (normalized.agenda > 0) options.queueRows.agenda.setAttribute('data-deadlines', 'true'); else options.queueRows.agenda.removeAttribute('data-deadlines'); } const label = active === 0 && normalized.agenda === 0 ? 'Queues: no active queues; no upcoming deadlines' : 'Queues: Today ' + normalized.today + ', Agenda ' + normalized.agenda + ' due' + ', Attention ' + normalized.attention + ', Updates ' + normalized.update + ', Later ' + normalized.later + ', Drafts ' + normalized.draft + '; ' + active + ' active ' + (active === 1 ? 'queue' : 'queues'); buttons.queues?.setAttribute('aria-label', label); } return {start, select, refreshVisibility, updateAttention, updateQueues, updateWork}; });