93 lines
3.1 KiB
JavaScript
93 lines
3.1 KiB
JavaScript
(function (root, factory) {
|
|
if (typeof module === 'object' && module.exports) module.exports = factory;
|
|
else root.createUpdateTriageGesture = factory;
|
|
})(typeof self !== 'undefined' ? self : this, function createUpdateTriageGesture(options) {
|
|
const viewportWidth = options.viewportWidth || (() => root.innerWidth);
|
|
const surface = options.surface;
|
|
const threshold = options.threshold || 72;
|
|
const edgeInset = options.edgeInset || 24;
|
|
const interactive = 'a,button,input,textarea,select,label,summary,[contenteditable="true"],[role="button"]';
|
|
let active = null;
|
|
let busy = false;
|
|
|
|
function horizontalScroller(target) {
|
|
for (let node = target; node && node !== surface; node = node.parentElement) {
|
|
if (Number(node.scrollWidth) > Number(node.clientWidth) + 1) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function ignored(event) {
|
|
return event.pointerType === 'mouse' || event.clientX <= edgeInset ||
|
|
event.clientX >= viewportWidth() - edgeInset ||
|
|
Boolean(event.target?.closest?.(interactive)) || horizontalScroller(event.target);
|
|
}
|
|
|
|
function reset() {
|
|
active = null;
|
|
delete surface.dataset.triageGesture;
|
|
}
|
|
|
|
function start(event) {
|
|
if (busy || !options.enabled() || viewportWidth() > 700 || ignored(event) || active) return;
|
|
active = {id:event.pointerId, x:event.clientX, y:event.clientY, cancelled:false};
|
|
surface.setPointerCapture?.(event.pointerId);
|
|
}
|
|
|
|
function move(event) {
|
|
if (!active || active.id !== event.pointerId) return;
|
|
const dx = event.clientX - active.x;
|
|
const dy = event.clientY - active.y;
|
|
if (Math.abs(dy) > Math.abs(dx) || Math.abs(dy) > 36) {
|
|
active.cancelled = true;
|
|
delete surface.dataset.triageGesture;
|
|
return;
|
|
}
|
|
if (Math.abs(dx) < 12) return;
|
|
event.preventDefault?.();
|
|
surface.dataset.triageGesture = dx > 0 ? 'keep' : 'read';
|
|
}
|
|
|
|
function decide(callback) {
|
|
busy = true;
|
|
let outcome;
|
|
try {
|
|
outcome = callback();
|
|
} catch (error) {
|
|
busy = false;
|
|
throw error;
|
|
}
|
|
if (outcome && typeof outcome.then === 'function') {
|
|
Promise.resolve(outcome).finally(() => { busy = false; });
|
|
} else {
|
|
busy = false;
|
|
}
|
|
}
|
|
|
|
function finish(event) {
|
|
if (!active || active.id !== event.pointerId) return;
|
|
const gesture = active;
|
|
const dx = event.clientX - gesture.x;
|
|
const dy = event.clientY - gesture.y;
|
|
surface.releasePointerCapture?.(event.pointerId);
|
|
reset();
|
|
if (gesture.cancelled || Math.abs(dx) < threshold || Math.abs(dx) <= Math.abs(dy)) return;
|
|
decide(dx > 0 ? options.keepUnread : options.markRead);
|
|
}
|
|
|
|
surface.addEventListener('pointerdown', start);
|
|
surface.addEventListener('pointermove', move);
|
|
surface.addEventListener('pointerup', finish);
|
|
surface.addEventListener('pointercancel', reset);
|
|
|
|
function destroy() {
|
|
surface.removeEventListener('pointerdown', start);
|
|
surface.removeEventListener('pointermove', move);
|
|
surface.removeEventListener('pointerup', finish);
|
|
surface.removeEventListener('pointercancel', reset);
|
|
reset();
|
|
}
|
|
|
|
return {destroy};
|
|
});
|