coherent-gameface-interaction-manager
Version:
Library for the most common UI interactions
7 lines (6 loc) • 14.2 kB
JavaScript
(function(v,E){typeof exports=="object"&&typeof module<"u"?module.exports=E():typeof define=="function"&&define.amd?define(E):(v=typeof globalThis<"u"?globalThis:v||self,v.dropzone=E())})(this,(function(){"use strict";class v{constructor(e){this.options=e,this.draggableElements=[],this.draggedElement=null,this.enabled=!1,this.offset={x:0,y:0}}get draggedItemIndex(){return[...this.draggableElements].indexOf(this.draggedElement)}get bodyScrollOffset(){return{x:document.body.scrollLeft,y:document.body.scrollTop}}setPointerOffset(e,t,n){const{x:i,y:s}=n.getBoundingClientRect();this.offset={x:e-i,y:t-s}}}function E(a){return a*180/Math.PI}function S(){return(Math.random()+1).toString(36).substring(7)}function M(a,e,t,n){const i=a-t,s=e-n;return Math.hypot(i,s)}function L(a,e,t,n){return{x:(a+t)/2,y:(e+n)/2}}class b{constructor(){this.actions=[],this.keyboardFunctions=[],this.gamepadFunctions=[]}init(){window._IM||(window._IM=new b)}getKeys(e){return _IM.keyboardFunctions.filter(t=>t.keys.every(n=>e.includes(n)))}getKeysIndex(e){return _IM.keyboardFunctions.findIndex(t=>t.keys.every(n=>e.includes(n)))}getGamepadAction({actions:e,type:t}){return _IM.gamepadFunctions.find(n=>n.actions.every(i=>e.includes(i))&&n.type===t&&n.actions.length===e.length)}getGamepadActions(e,t=!0){return _IM.gamepadFunctions.filter(n=>n.actions.every(i=>e.includes(i))&&(t?n.actions.length===e.length:!0))}getGamepadActionIndex(e){return _IM.gamepadFunctions.findIndex(t=>t.actions.every(n=>e.includes(n)))}getAction(e){return _IM.actions.find(t=>t.name===e)}getActionIndex(e){return _IM.actions.findIndex(t=>t.name===e)}hasDuplicateCallback(e,t){return e.callbacks.some(n=>n===t)}addCallbackToEntry(e,t,n){if(this.hasDuplicateCallback(e,t)){const i=typeof t=="string"?"action":"function",s=typeof t=="string"?t:"(anonymous function)";return console.error(`Duplicate callback detected!
${n.identifier}
Type: '${n.type}'
Callback: ${s}
This ${i} is already registered for this combination and type. To update it, first remove with the .off() method.`)}return e.callbacks.push(t)}removeKeyboardFunction(e){const t=_IM.keyboardFunctions.indexOf(e);t!==-1&&_IM.keyboardFunctions.splice(t,1)}removeGamepadFunction(e){const t=_IM.gamepadFunctions.indexOf(e);t!==-1&&_IM.gamepadFunctions.splice(t,1)}}const f=new b;class _{register(e,t){if(f.getAction(e))return console.error(`The following action "${e}" is already registered!`);_IM.actions.push({name:e,callback:t})}remove(e){const t=f.getActionIndex(e);if(t===-1)return console.error(`${e} is not a registered action!`);_IM.actions.splice(t,1)}execute(e,t){const n=f.getAction(e);if(!n)return console.error(`${e} is not a registered action!`);n.callback(t)}}const u=new _;function O(a,e){if(e<0&&a>-200&&a<200)return"top";if(e>0&&a>-200&&a<200)return"bottom";if(a<0&&e>-200&&e<200)return"left";if(a>0&&e>-200&&e<200)return"right";if(a<=-200&&e<=-200)return"top-left";if(a>=200&&e<=-200)return"top-right";if(a<=-200&&e>=200)return"bottom-left";if(a>=200&&e>=200)return"bottom-right"}function m(a){return a instanceof HTMLElement?a:document.querySelector(a)}const p=2;class D{constructor(){this.activeTouches=new Map}hold(e){if(!e)return console.error("Options not provided for hold!");let t;const n=m(e.element);if(!n)return console.error("Element not found!");const i=({touches:o})=>{this.activeTouches.set(o[0].identifier,o[0]),t=setTimeout(()=>{e.callback&&e.callback()},e.time||1e3)},s=({touches:o})=>{this.activeTouches.delete(o[0].identifier),clearTimeout(t)};return n.addEventListener("touchstart",i),n.addEventListener("touchend",s),{remove(){n.removeEventListener("touchstart",i),n.removeEventListener("touchend",s)}}}tap(e){if(!e)return console.error("Options not provided for tap!");let t,n,i=!0,s=e.tapsNumber||1;const o=m(e.element);if(!o)return console.error("Element not found!");const d=({touches:r})=>{this.activeTouches.set(r[0].identifier,r[0]),clearTimeout(n),i=!0,t=setTimeout(()=>{i=!1},e.tapTime||200)},c=({touches:r})=>{this.activeTouches.delete(r[0].identifier),clearTimeout(t),i&&(s--,n=setTimeout(()=>{s=e.tapsNumber||1,clearTimeout(n)},e.betweenTapsTime||500),!(s!==0||!e.callback)&&(e.callback(),i=!0,clearTimeout(t),s=e.tapsNumber||1))};return o.addEventListener("touchstart",d),o.addEventListener("touchend",c),{remove(){o.removeEventListener("touchstart",d),o.removeEventListener("touchend",c)}}}drag(e){if(!e)return console.error("Options not provided for drag!");const t=m(e.element);if(!t)return console.error("Element not found!");const n=({touches:o,target:d,currentTarget:c})=>{this.activeTouches.set(o[0].identifier,o[0]),document.addEventListener("touchmove",i),document.addEventListener("touchend",s),e.onDragStart&&e.onDragStart({x:o[0].clientX,y:o[0].clientY,target:d,currentTarget:c})},i=({touches:o})=>{this.activeTouches.has(o[0].identifier)&&e.onDrag&&e.onDrag({x:o[0].clientX,y:o[0].clientY})},s=({touches:o})=>{this.activeTouches.delete(o[0].identifier),document.removeEventListener("touchmove",i),document.removeEventListener("touchend",s),e.onDragEnd&&e.onDragEnd({x:o[0].clientX,y:o[0].clientY})};return t.addEventListener("touchstart",n),{remove(){t.removeEventListener("touchstart",n)}}}swipe(e){if(!e)return console.error("Options not provided for swipe!");let t,n,i,s=!0;const o=100;e.touchNumber||=1;const d=m(e.element);if(!d)return console.error("Element not found!");const c=({touches:h})=>{this.activeTouches.set(h[0].identifier,h[0]),this.activeTouches.size>e.touchNumber&&(document.removeEventListener("touchmove",r),document.removeEventListener("touchend",l)),this.activeTouches.size===e.touchNumber&&(t=setTimeout(()=>{s=!1,clearTimeout(t),t=null},1e3),document.addEventListener("touchmove",r),document.addEventListener("touchend",l))},r=({touches:h})=>{if(!this.activeTouches.has(h[0].identifier))return;const T=this.activeTouches.get(h[0].identifier);if(!T)return;const{clientX:y,clientY:I}=T,N=h[0].clientX-y,z=h[0].clientY-I;n=O(N,z),i=M(y,I,h[0].clientX,h[0].clientY)},l=({touches:h})=>{this.activeTouches.delete(h[0].identifier),this.activeTouches.size===0&&(document.removeEventListener("touchmove",r),document.removeEventListener("touchend",l),g()&&e.callback(n),clearTimeout(t),s=!0,t=null)},g=()=>s&&n&&i&&i>o;return d.addEventListener("touchstart",c),{remove(){d.removeEventListener("touchstart",c)}}}pinch(e){if(!e)return console.error("Options not provided for pinch!");let t;const n=40,i=m(e.element);if(!i)return console.error("Element not found!");const s=({touches:c})=>{if(this.activeTouches.set(c[0].identifier,c[0]),this.activeTouches.size<p)return;const r=this.activeTouches.get(0),l=this.activeTouches.get(1);!r||!l||(document.addEventListener("touchmove",o),document.addEventListener("touchend",d),t=M(r.clientX,r.clientY,l.clientX,l.clientY))},o=({touches:c})=>{if(this.activeTouches.size!==p)return;this.activeTouches.set(c[0].identifier,c[0]);const r=this.activeTouches.get(0),l=this.activeTouches.get(1);if(!r||!l)return;const g=M(r.clientX,r.clientY,l.clientX,l.clientY),h=Math.sign(g-t)*n;t=g;const T=L(r.clientX,r.clientY,l.clientX,l.clientY);e.callback&&e.callback({pinchDelta:h,midpoint:T})},d=({touches:c})=>{this.activeTouches.delete(c[0].identifier),this.activeTouches.size===0&&(document.removeEventListener("touchmove",o),document.removeEventListener("touchend",d))};return i.addEventListener("touchstart",s),{remove(){i.removeEventListener("touchstart",s)}}}rotate(e){if(!e)return console.error("Options not provided for rotate!");let t=0,n=0;const i=m(e.element);if(!i)return console.error("Element not found!");const s=({touches:r})=>{this.activeTouches.size<p||(this.activeTouches.set(r[0].identifier,r[0]),t=c()-n,e.callback&&e.callback(t))},o=({touches:r})=>{this.activeTouches.set(r[0].identifier,r[0]),this.activeTouches.size===p&&(n=c()-t,document.addEventListener("touchmove",s),document.addEventListener("touchend",d))},d=({touches:r})=>{this.activeTouches.delete(r[0].identifier),document.removeEventListener("touchmove",s),document.removeEventListener("touchend",d)};i.addEventListener("touchstart",o);const c=()=>{const g=this.activeTouches.get(0).clientY-this.activeTouches.get(1).clientY,h=this.activeTouches.get(0).clientX-this.activeTouches.get(1).clientX;return(E(Math.atan2(g,h))+360+90)%360};return{remove(){i.removeEventListener("touchstart",o)}}}}const F=new D;class x extends v{constructor(e){super(e),this.dropzones=[],this.draggedOver=null,this.touchEvents=[],this._touchEnabled=!1,this.options=e;const t=S();this.actionName=`drag-and-drop-${t}`,this.automaticAction=`move-to-${t}`,this.onMouseDown=this.onMouseDown.bind(this),this.onMouseMove=this.onMouseMove.bind(this),this.onMouseUp=this.onMouseUp.bind(this),this.onMouseEnter=this.onMouseEnter.bind(this),this.onMouseLeave=this.onMouseLeave.bind(this),this.init()}set touchEnabled(e){this._touchEnabled!==e&&(this._touchEnabled=e,this._touchEnabled?this.addTouchEvents():this.removeTouchEvents())}init(){if(!this.enabled){if(this.draggableElements=document.querySelectorAll(this.options.element),this.draggableElements.length===0)return console.error(`${this.options.element} is not a valid element selector.`);this.draggableElements.forEach(e=>e.addEventListener("mousedown",this.onMouseDown)),this.createDropzones(),this.dropzones.length!==0&&(this.dropzones.forEach(e=>{e.addEventListener("mouseenter",this.onMouseEnter),e.addEventListener("mouseleave",this.onMouseLeave)}),this.registerDragActions(),this.enabled=!0)}}deinit(){this.enabled&&(this.draggableElements.forEach(e=>e.removeEventListener("mousedown",this.onMouseDown)),this.dropzones.forEach(e=>{e.removeEventListener("mouseenter",this.onMouseEnter),e.removeEventListener("mouseleave",this.onMouseLeave)}),this.removeActions(),this.enabled=!1)}onMouseDown(e){this.draggedElement=e.currentTarget,this.draggedElement.style.position="absolute",this.draggedElement.style.pointerEvents="none",this.setPointerOffset(e.clientX,e.clientY,this.draggedElement),u.execute(this.actionName,{x:e.clientX+this.bodyScrollOffset.x-this.offset.x,y:e.clientY+this.bodyScrollOffset.y-this.offset.y,index:this.draggedItemIndex}),this.options.dragStyle&&this.draggedElement.classList.add(this.options.dragStyle),this.options.onDragStart&&this.options.onDragStart(this.draggedElement),document.addEventListener("mousemove",this.onMouseMove),document.addEventListener("mouseup",this.onMouseUp)}onMouseMove(e){u.execute(this.actionName,{x:e.clientX+this.bodyScrollOffset.x-this.offset.x,y:e.clientY+this.bodyScrollOffset.y-this.offset.y,index:this.draggedItemIndex})}onMouseUp(){document.removeEventListener("mousemove",this.onMouseMove),document.removeEventListener("mouseup",this.onMouseUp),this.draggedElement&&(this.draggedElement.style.position="",this.draggedElement.style.pointerEvents="",this.draggedElement.style.left="",this.draggedElement.style.top="",this.options.onDragEnd&&this.options.onDragEnd(this.draggedElement),this.options.dragStyle&&this.draggedElement.classList.remove(this.options.dragStyle)),this.draggedOver&&this.handleDrop(),this.draggedElement=null}onMouseEnter(e){this.draggedElement&&(this.draggedOver=e.currentTarget,this.options.dropzoneActiveClass&&this.draggedOver.classList.add(this.options.dropzoneActiveClass))}onMouseLeave(){this.draggedElement&&(this.options.dropzoneActiveClass&&this.draggedOver&&this.draggedOver.classList.remove(this.options.dropzoneActiveClass),this.draggedOver=null)}registerDragActions(){u.register(this.actionName,({x:e,y:t,index:n})=>{if(!this.draggableElements[n])return console.error(`There is no draggable element at index ${n}`);this.draggableElements[n].style.left=`${e}px`,this.draggableElements[n].style.top=`${t}px`,this.options.onDragMove&&this.options.onDragMove({x:e,y:t})}),u.register(this.automaticAction,this.automaticMove.bind(this))}removeActions(){u.remove(this.actionName),u.remove(this.automaticAction)}addTouchEvents(){this.draggableElements.forEach(e=>{this.touchEvents.push(F.drag({element:e,onDragStart:t=>{this.onMouseDown({currentTarget:t.currentTarget,clientX:t.x,clientY:t.y})},onDrag:({x:t,y:n})=>{this.onMouseMove({clientX:t,clientY:n});const i=document.elementFromPoint(t,n);let s=this.options.dropzones.reduce((o,d)=>o||!i?o:o=i.closest(d),null);if(s||(s=this.dropzones.includes(i)?i:null),s){this.onMouseEnter({currentTarget:s});return}this.onMouseLeave()},onDragEnd:()=>{this.onMouseUp()}}))})}removeTouchEvents(){this.touchEvents.forEach(e=>e?.remove()),this.touchEvents=[]}automaticMove({elementIndex:e,dropzoneIndex:t}){const{x:n,y:i}=this.dropzones[t].getBoundingClientRect(),{x:s,y:o}=this.draggableElements[e].getBoundingClientRect(),d={x:Math.sign(n-s),y:Math.sign(i-o)};this.draggableElements[e].style.position="absolute";const c=(r,l)=>{if(r===n&&l===i){this.dropzones[t].appendChild(this.draggableElements[e]),this.draggableElements[e].style.position="";return}r=r!==n?d.x+r:r,l=l!==i?d.y+l:l,this.draggableElements[e].style.left=`${r}px`,this.draggableElements[e].style.top=`${l}px`,(r!==n||l!==i)&&requestAnimationFrame(()=>c(r,l))};c(s,o)}createDropzones(){this.dropzones=this.options.dropzones.reduce((e,t)=>{const n=[...document.querySelectorAll(t)];return n.length===0&&console.error(`${t} is not a valid html element to be used as a dropzone`),e.concat(n)},[])}handleDrop(){let e=this.options.dropType;const t={preventDefault:()=>{e="ignore"},target:this.draggedElement,dropzone:this.draggedOver};if(this.options.onDrop&&this.options.onDrop(t),!(e==="ignore"||!this.draggedOver)){switch(this.draggedOver.children.length===0&&(e="add"),e){case"add":this.draggedOver.appendChild(this.draggedElement);break;case"shift":this.shiftElements();break;case"switch":this.draggedElement?.parentNode.appendChild(this.draggedOver.children[0]),this.draggedOver.appendChild(this.draggedElement);break}this.onMouseLeave()}}shiftElements(){const e=this.dropzones.findIndex(o=>o===this.draggedOver),t=this.dropzones.map(o=>o.children[0]===this.draggedElement?[]:[...o.children]),n=t.reduce((o,d,c)=>(d.length===0&&(o=Math.abs(e-c)<Math.abs(e-o)?c:o),o),1e5),i=Math.sign(e-n)>0?0:1,s=Math.sign(e-n)<0?0:1;t.splice(e+s,0,[this.draggedElement]),t.splice(n+i,1),t.forEach((o,d)=>{o.forEach(c=>this.dropzones[d].appendChild(c))})}}return f.init(),x}));
//# sourceMappingURL=dropzone.min.js.map