(function (root, factory) { if (typeof module !== 'undefined' && module.exports) module.exports = factory; else root.createMobilePullRefresh = factory; })(typeof self !== 'undefined' ? self : this, function createMobilePullRefresh(options) { const surface = options.surface || document.querySelector('#my-work'); const indicator = options.indicator || document.querySelector('#mobile-pull-refresh'); const threshold = options.threshold || 64; const isMobile = options.isMobile || (() => window.matchMedia('(max-width: 600px)').matches); const getScrollY = options.getScrollY || (() => window.scrollY); const isBlocked = options.isBlocked || (() => Array.from(options.overlays || document.querySelectorAll('[role="dialog"], #whiteboard-modal, #markdown-modal')).some(overlay => (overlay.checkVisibility ? overlay.checkVisibility() : !overlay.closest('[hidden]')) && (overlay.classList.contains('open') || overlay.getAttribute('aria-modal') === 'true'))); let gesture = null; let inFlight = null; function show(state, text) { indicator.hidden = false; indicator.dataset.state = state; indicator.textContent = text; } function resetGesture() { gesture = null; } function startsOnExcludedTarget(target) { if (!target) return false; if (target.closest?.('button, a, input, textarea, select, summary, [contenteditable="true"], [role="button"]')) return true; for (let node = target; node && node !== surface; node = node.parentElement) { if ((node.scrollWidth || 0) > (node.clientWidth || 0)) return true; } return false; } function onPointerDown(event) { if (inFlight || event.pointerType !== 'touch' || !isMobile() || getScrollY() > 0 || isBlocked() || startsOnExcludedTarget(event.target)) return; gesture = {id:event.pointerId, x:event.clientX, y:event.clientY, ready:false}; surface.setPointerCapture?.(event.pointerId); } function onPointerMove(event) { if (!gesture || gesture.id !== event.pointerId) return; const dx = event.clientX - gesture.x; const dy = event.clientY - gesture.y; if (dy <= 0 || Math.abs(dx) > dy) return resetGesture(); event.preventDefault(); gesture.ready = dy >= threshold; show(gesture.ready ? 'ready' : 'pulling', gesture.ready ? 'Release to refresh' : 'Pull to refresh'); } async function onPointerUp(event) { if (!gesture || gesture.id !== event.pointerId) return; const shouldRefresh = gesture.ready; resetGesture(); surface.releasePointerCapture?.(event.pointerId); if (!shouldRefresh || inFlight) return; show('refreshing', 'Refreshing My Work…'); inFlight = Promise.resolve().then(options.refresh); try { await inFlight; show('success', 'My Work is up to date'); } catch (_) { show('error', 'Could not refresh · pull to retry'); } finally { inFlight = null; } } function onPointerCancel() { resetGesture(); } return { start() { surface.addEventListener('pointerdown', onPointerDown); surface.addEventListener('pointermove', onPointerMove); surface.addEventListener('pointerup', onPointerUp); surface.addEventListener('pointercancel', onPointerCancel); }, stop() { surface.removeEventListener('pointerdown', onPointerDown); surface.removeEventListener('pointermove', onPointerMove); surface.removeEventListener('pointerup', onPointerUp); surface.removeEventListener('pointercancel', onPointerCancel); resetGesture(); }, }; });