(function (root, factory) { if (typeof module === 'object' && module.exports) module.exports = factory; else root.createMobileDeviceSetup = factory; })(typeof self !== 'undefined' ? self : this, function createMobileDeviceSetup(options) { const promptDismissKey = 'stackchain.device-setup-prompt-dismissed-until'; const promptDismissMs = 7 * 24 * 60 * 60 * 1000; const steps = [ ['install', options.installButton, options.installStatus, options.install], ['offline', options.offlineButton, options.offlineStatus, options.enableOffline], ['protection', options.protectionButton, options.protectionStatus, options.protectStorage], ['push', options.pushButton, options.pushStatus, options.enablePush], ['deadline', options.deadlineButton, options.deadlineStatus, options.enableDeadline], ]; let trigger = options.launcher; let ownsDetour = false; function readinessCounts(readiness) { const available = steps.filter(([name]) => readiness[name].state !== 'unavailable').length; const complete = steps.filter(([name]) => readiness[name].state === 'complete').length; return {available, complete}; } function renderPrompt(readiness) { if (!options.promptCard) return; const {available, complete} = readinessCounts(readiness); let dismissed = false; try { dismissed = Number(options.promptStorage?.getItem(promptDismissKey) || 0) > (options.now?.() ?? Date.now()); } catch (_error) {} options.promptSummary.textContent = `${complete} of ${available} steps complete`; options.promptCard.hidden = dismissed || available === 0 || complete === available; } async function render() { const readiness = await options.getReadiness(); let available = 0; let complete = 0; steps.forEach(([name, button, status]) => { const step = readiness[name]; status.textContent = step.detail; button.hidden = step.state === 'complete' || step.state === 'unavailable'; button.disabled = step.state === 'pending'; if (step.state !== 'unavailable') available += 1; if (step.state === 'complete') complete += 1; }); options.readyStatus.textContent = complete === available ? 'This device is ready.' : `${complete} of ${available} available steps ready.`; renderPrompt(readiness); return readiness; } async function open(event) { trigger = event?.currentTarget || options.launcher; if (options.isMobile?.()) { ownsDetour = options.timerView?.beginDetour?.('device-setup')?.reason === 'device-setup'; } await render(); options.sheet.hidden = false; options.closeButton.focus(); } function close(resume = true) { options.sheet.hidden = true; if (resume && ownsDetour) options.timerView?.finishDetour?.(); ownsDetour = false; trigger?.focus?.(); } async function start() { options.launcher.addEventListener('click', open); options.promptLauncher?.addEventListener('click', open); options.promptDismiss?.addEventListener('click', () => { try { options.promptStorage?.setItem(promptDismissKey, String((options.now?.() ?? Date.now()) + promptDismissMs)); } catch (_error) {} options.promptCard.hidden = true; }); options.closeButton.addEventListener('click', close); options.returnButton?.addEventListener('click', () => close(false)); options.sheet.addEventListener('click', event => { if (event.target === options.sheet) close(); }); options.escapeTarget.addEventListener('keydown', event => { if (event.key === 'Escape' && !options.sheet.hidden) close(); }); steps.forEach(([_name, button, _status, action]) => { button.addEventListener('click', async () => { button.disabled = true; try { await action(); } finally { await render(); } }); }); renderPrompt(await options.getReadiness()); } return {start, open, close, render}; }); if (typeof module === 'object' && module.exports) { module.exports.mount = mountMobileDeviceSetup; } else { self.createMobileDeviceSetup.mount = mountMobileDeviceSetup; } function mountMobileDeviceSetup(options) { const qs = selector => options.document.querySelector(selector); return createMobileDeviceSetup({ launcher:qs('#open-device-setup'), closeButton:qs('#close-device-setup'), sheet:qs('#device-setup-sheet'), installButton:qs('#device-setup-install'), offlineButton:qs('#device-setup-offline'), pushButton:qs('#device-setup-push'), protectionButton:qs('#device-setup-protection'), deadlineButton:qs('#device-setup-deadline'), installStatus:qs('#device-setup-install-status'), offlineStatus:qs('#device-setup-offline-status'), protectionStatus:qs('#device-setup-protection-status'), pushStatus:qs('#device-setup-push-status'), deadlineStatus:qs('#device-setup-deadline-status'), readyStatus:qs('#device-setup-ready-status'), promptCard:qs('#device-readiness-card'), promptSummary:qs('#device-readiness-summary'), promptLauncher:qs('#finish-device-setup'), promptDismiss:qs('#dismiss-device-readiness'), promptStorage:options.promptStorage, returnButton:qs('#return-from-device-setup'), timerView:options.timerView, isMobile:options.isMobile || (() => innerWidth <= 600), escapeTarget:options.document, getReadiness:() => ({ install:options.installApp.state(), offline:!options.offlineAvailable() ? {state:'unavailable', detail:'Offline saving is unavailable in this browser.'} : options.offlineEnabled() ? {state:'complete', detail:qs('#offline-work-status').textContent || 'Offline work is saved.'} : {state:'incomplete', detail:'Private My Work and Today data are not saved offline.'}, protection:options.storageProtectionReadiness(), push:qs('#push-updates').disabled ? {state:'unavailable', detail:qs('#push-update-status').textContent || 'Update notifications are unavailable.'} : qs('#push-updates').checked ? {state:'complete', detail:'New update notifications are enabled.'} : {state:'incomplete', detail:qs('#push-update-status').textContent || 'New update notifications are off.'}, deadline:options.deadlineReadiness(), }), install:() => options.installApp.install(), enableOffline:options.enableOffline, protectStorage:options.protectStorage, enablePush:options.enablePush, enableDeadline:options.enableDeadline, }); }