(function (root, factory) { const api = factory(); if (typeof module === 'object' && module.exports) module.exports = api; else root.createMobileFindWorkNavigation = api; })(typeof globalThis !== 'undefined' ? globalThis : this, function () { 'use strict'; const stages = ['discover', 'review', 'fit']; return function createMobileFindWorkNavigation(options) { const document = options.document; const buttons = options.buttons || Object.fromEntries(stages.map(name => [name, document?.querySelector('[data-find-work-stage="' + name + '"]')] )); const sections = options.sections || Object.fromEntries(stages.map(name => [name, document?.getElementById('find-work-' + name + '-stage')] )); const history = options.history; const eventTarget = options.eventTarget; const onStageChange = options.onStageChange || (() => {}); const listeners = new Map(); let current = 'discover'; let selectedCount = 0; let recovery = false; let started = false; function escape(value) { return String(value || '').replace(/[&<>"']/g, character => ({ '&':'&', '<':'<', '>':'>', '"':'"', "'":''' })[character] ); } function renderReview() { const reviewList = options.reviewList || document?.getElementById('find-work-review-list'); const reviewSummary = options.reviewSummary || document?.getElementById('find-work-review-summary'); if (!reviewList) return; const items = options.controller?.selectedItems?.() || options.getSelectedItems?.() || []; const planning = options.todayWork?.planning?.(); const today = options.todayWork ? { limit:options.todayWork.limit, count:options.todayWork.read().length, capacityMinutes:planning.capacity_minutes, estimates:planning.estimates, } : options.getTodayState?.() || {}; const remainingSlots = Math.max(0, (Number(today.limit) || 0) - (Number(today.count) || 0)); const planned = Object.values(today.estimates || {}).reduce((sum, minutes) => sum + minutes, 0); const remainingMinutes = today.capacityMinutes === null || today.capacityMinutes === undefined ? null : Math.max(0, today.capacityMinutes - planned); const formatMinutes = minutes => [Math.floor(minutes / 60) ? Math.floor(minutes / 60) + 'h' : '', minutes % 60 ? minutes % 60 + 'm' : ''].filter(Boolean).join(' ') || '0m'; if (reviewSummary) reviewSummary.textContent = items.length + ' selected · ' + remainingSlots + ' Today slot' + (remainingSlots === 1 ? '' : 's') + ' open' + (remainingMinutes === null ? '' : ' · ' + formatMinutes(remainingMinutes) + ' available') + '.'; reviewList.innerHTML = items.map(item => { const id = String(item.repository || '') + '#' + String(item.number || ''); return '
' + escape(id) + '' + escape(item.title || 'Untitled work') + '
' + '
'; }).join(''); reviewList.querySelectorAll('[data-find-work-review-remove]').forEach(button => button.addEventListener('click', () => { const item = items.find(candidate => String(candidate.repository || '') + '#' + String(candidate.number || '') === button.dataset.findWorkReviewRemove); if (item) (options.controller?.toggleSelection || options.toggleSelection)?.(item); }) ); } function allowed(name) { return stages.includes(name) && (name === 'discover' || selectedCount > 0 || recovery); } function paint(name, focus = false, notify = true) { if (!allowed(name)) name = 'discover'; current = name; stages.forEach(stage => { const button = buttons[stage]; const section = sections[stage]; if (button) { button.disabled = stage !== 'discover' && selectedCount === 0 && !recovery; if (stage === name) button.setAttribute('aria-current', 'step'); else button.removeAttribute('aria-current'); } if (section) section.hidden = stage !== name; }); if (name === 'review') renderReview(); if (name === 'fit') options.openFit?.(options.controller?.selectedItems?.() || []); if (notify) onStageChange(name); if (focus) sections[name]?.focus?.(); return name; } function stateFor(name) { const state = { ...(history?.state || {}) }; if (name === 'discover') delete state.findWorkStage; else state.findWorkStage = name; return state; } function go(name) { if (!allowed(name) || name === current) return false; const currentIndex = stages.indexOf(current); const nextIndex = stages.indexOf(name); if (nextIndex < currentIndex && history?.go) { history.go(nextIndex - currentIndex); return true; } if (nextIndex === currentIndex - 1 && history?.back) { history.back(); return true; } history?.pushState?.(stateFor(name), ''); paint(name, true); return true; } function onPopState(event) { if (event.state?.taskOverlay !== 'find') return; paint(event.state?.findWorkStage || 'discover', true); } function sync(state = {}) { selectedCount = Math.max(0, Number(state.selectedCount) || 0); recovery = state.recovery === true; if (!allowed(current)) paint('discover'); else paint(current, false, false); } function open(hasRecovery) { if (options.controller) { selectedCount = options.controller.selection().count; recovery = hasRecovery === true; } const restored = history?.state?.taskOverlay === 'find' ? history.state.findWorkStage : null; return paint(allowed(restored) ? restored : 'discover'); } function complete() { selectedCount = 0; recovery = false; history?.replaceState?.(stateFor('discover'), ''); return paint('discover'); } function back() { if (current === 'discover') return false; history?.back?.(); return true; } return { start() { if (started) return; started = true; stages.forEach(name => { const button = buttons[name]; if (!button) return; const listener = event => { event.preventDefault(); go(name); }; listeners.set(button, listener); button.addEventListener('click', listener); }); if (document) { const bind = (id, action) => { const button = document.getElementById(id); if (!button) return; const listener = event => { event.preventDefault(); action(); }; listeners.set(button, listener); button.addEventListener('click', listener); }; bind('select-find-work', () => { complete(); options.controller?.startSelection?.(); }); bind('cancel-find-work-selection', () => { complete(); options.controller?.cancelSelection?.(); }); bind('claim-selected-work', () => go('review')); bind('continue-find-work-fit', () => go('fit')); bind('back-find-work-discover', back); bind('cancel-find-work-estimates', back); bind('close-find-work', () => history?.go?.(-(stages.indexOf(current) + 1))); } eventTarget?.addEventListener?.('popstate', onPopState); paint('discover'); }, stop() { listeners.forEach((listener, button) => button.removeEventListener('click', listener)); listeners.clear(); eventTarget?.removeEventListener?.('popstate', onPopState); started = false; }, sync, open, go, back, complete, renderReview, stage: () => current, }; }; });