Jake Vanderwerf
2025-10-18 b0194e10a87e16797a568d8a30d53ebecd27d8a4
1
window.jvbDragHandler=class{constructor(t){this.draggableSelector=t.draggableSelector,this.dropTargetSelector=t.dropTargetSelector,this.getItemId=t.getItemId,this.getSelectedItems=t.getSelectedItems,this.validateDrop=t.validateDrop,this.onDrop=t.onDrop,this.handleSelector=t.handleSelector||null,this.ignoreSelector=t.ignoreSelector||"input, button, label, select, textarea, a",this.onDragStart=t.onDragStart||null,this.onDragEnd=t.onDragEnd||null,this.previewElement=t.previewElement||"img, video, .icon",this.previewOptions={offset:{x:-30,y:-40},showCount:!0,...t.previewOptions},this.dragThreshold={time:90,distance:5},this.state=this.getInitialState(),this.onPointerDown=this.handlePointerDown.bind(this),this.onPointerMove=this.handlePointerMove.bind(this),this.onPointerUp=this.handlePointerUp.bind(this),this.onPointerCancel=this.handlePointerCancel.bind(this),this.init()}getInitialState(){return{active:!1,itemIds:[],startPos:null,currentPos:null,targetElement:null,previewElement:null,startTime:null,holdTimer:null,pointerId:null,pointerTarget:null}}init(){document.addEventListener("pointerdown",this.onPointerDown)}handlePointerDown(t){if(this.shouldIgnoreElement(t.target))return;const e=t.target.closest(this.draggableSelector);if(!e)return;if(this.handleSelector){const i=t.target.closest(this.handleSelector);if(!i||!e.contains(i))return}const i=this.getItemId(e);if(!i)return;const n=this.getSelectedItems(e),r=n.includes(i)?n:[i];t.preventDefault(),this.state={active:!1,itemIds:r,startPos:{x:t.clientX,y:t.clientY},currentPos:{x:t.clientX,y:t.clientY},targetElement:null,previewElement:null,startTime:Date.now(),holdTimer:null,draggableElement:e,pointerId:t.pointerId,pointerTarget:e},document.addEventListener("pointermove",this.onPointerMove),document.addEventListener("pointerup",this.onPointerUp),document.addEventListener("pointercancel",this.onPointerCancel),this.state.holdTimer=setTimeout((()=>{this.state&&!this.state.active&&this.startDrag()}),this.dragThreshold.time)}startDrag(){if(this.state&&!this.state.active){if(this.state.holdTimer=null,this.state.active=!0,this.state.pointerTarget&&null!==this.state.pointerId)try{this.state.pointerTarget.setPointerCapture(this.state.pointerId)}catch(t){console.warn("Could not capture pointer:",t)}if(this.state.previewElement=this.createPreview(this.state.draggableElement,this.state.itemIds),this.applyDraggingState(!0),this.updatePreview(),this.onDragStart&&this.onDragStart(this.state.itemIds,this.state.draggableElement),window.jvbA11y){const t=this.state.itemIds.length>1?`Started dragging ${this.state.itemIds.length} items`:"Started dragging item";window.jvbA11y.announce(t)}}}handlePointerMove(t){if(!this.state)return;if(this.state.currentPos={x:t.clientX,y:t.clientY},!this.state.active&&this.state.holdTimer){const e=t.clientX-this.state.startPos.x,i=t.clientY-this.state.startPos.y;Math.sqrt(e*e+i*i)>2*this.dragThreshold.distance&&(clearTimeout(this.state.holdTimer),this.startDrag())}if(!this.state.active)return;t.preventDefault(),this.updatePreview();const e=document.elementFromPoint(t.clientX,t.clientY),i=this.findDropTarget(e);i!==this.state.targetElement&&(this.clearTargetHighlight(),this.state.targetElement=i,i&&this.validateDrop(this.state.itemIds,i)&&this.highlightTarget(i))}handlePointerUp(t){if(!this.state)return;if(this.state.holdTimer&&!this.state.active)return void this.cancelDragStart();if(!this.state.active)return void this.cleanup();document.removeEventListener("pointermove",this.onPointerMove),document.removeEventListener("pointerup",this.onPointerUp),document.removeEventListener("pointercancel",this.onPointerCancel);const{itemIds:e,targetElement:i}=this.state;let n=!1;if(i&&this.validateDrop(e,i)&&(this.onDrop(e,i),n=!0),this.cleanup(),this.onDragEnd&&this.onDragEnd(e,n),window.jvbA11y){const t=n?e.length>1?`Moved ${e.length} items`:"Item moved":"Drag cancelled";window.jvbA11y.announce(t)}}handlePointerCancel(t){this.state&&(document.removeEventListener("pointermove",this.onPointerMove),document.removeEventListener("pointerup",this.onPointerUp),document.removeEventListener("pointercancel",this.onPointerCancel),this.state.holdTimer&&(clearTimeout(this.state.holdTimer),this.state.holdTimer=null),this.cleanup(),window.jvbA11y&&window.jvbA11y.announce("Drag cancelled"))}cancelDragStart(){this.state&&this.state.holdTimer&&(clearTimeout(this.state.holdTimer),this.state.holdTimer=null),document.removeEventListener("pointermove",this.onPointerMove),document.removeEventListener("pointerup",this.onPointerUp),document.removeEventListener("pointercancel",this.onPointerCancel),this.cleanup()}createPreview(t,e){let i=window.getTemplate("dragPreview");i||(i=this.createPreviewElement());let n=i.querySelector(".drag-items"),r=n.querySelector(".drag-item");e.forEach(((t,e)=>{let i=this.findElementByItemId(t);if(i){let t=i.querySelector(this.previewElement)?.cloneNode(!0);if(t){let e=r.cloneNode(!0);e.appendChild(t),n.append(e)}}})),r.remove();let s=i.querySelector(".drag-count");return s&&(s.textContent="{"+e.length+"}",s.hidden=e.length<=1),document.body.appendChild(i),i}createPreviewElement(){const t=document.createElement("div");t.className="drag-preview";let e=t.cloneNode(!0),i=t.cloneNode(!0),n=t.cloneNode(!0);return n.className="drag-item",e.className="drag-items",e.append(n),i.className="drag-count",t.append(e),t.append(i),t}updatePreview(){this.state.previewElement&&this.state.currentPos&&(this.state.previewElement.style.left=`${this.state.currentPos.x+this.previewOptions.offset.x}px`,this.state.previewElement.style.top=`${this.state.currentPos.y+this.previewOptions.offset.y}px`)}findDropTarget(t){return t?t.closest(this.dropTargetSelector):null}highlightTarget(t){if(t&&(t.classList.add("dragover"),this.state.isMultiDrag&&(t.classList.add("multi-drop"),t.setAttribute("data-item-count",this.state.itemIds.length)),navigator.vibrate)){const t=this.state.isMultiDrag?[25,10,25]:[25];navigator.vibrate(t)}}clearTargetHighlight(){document.querySelectorAll(".dragover").forEach((t=>{t.classList.remove("dragover","multi-drop"),t.removeAttribute("data-item-count")}))}applyDraggingState(t){this.state.itemIds.forEach((e=>{const i=this.findElementByItemId(e);i&&i.classList.toggle("dragging",t)}))}findElementByItemId(t){const e=document.querySelectorAll(this.draggableSelector);for(const i of e)if(this.getItemId(i)===t)return i;return null}shouldIgnoreElement(t){return t.matches(this.ignoreSelector)}cleanup(){if(this.state&&this.state.holdTimer&&clearTimeout(this.state.holdTimer),this.state&&this.state.pointerTarget&&null!==this.state.pointerId)try{this.state.pointerTarget.releasePointerCapture(this.state.pointerId)}catch(t){}this.clearTargetHighlight(),this.applyDraggingState(!1),this.state&&this.state.previewElement&&this.state.previewElement.remove(),this.state=this.getInitialState()}destroy(){this.cleanup(),document.removeEventListener("pointerdown",this.onPointerDown),document.removeEventListener("pointermove",this.onPointerMove),document.removeEventListener("pointerup",this.onPointerUp),document.removeEventListener("pointercancel",this.onPointerCancel)}};