d-pad
Version:
easy keyboard navigation without having to change your html
1 lines • 5.54 kB
JavaScript
"use strict";var dpad=function(){class t{constructor(t,e){this.x=t,this.y=e}length2(){return this.x*this.x+this.y*this.y}length(){return Math.sqrt(this.length2())}abs(){return this.x=Math.abs(this.x),this.y=Math.abs(this.y),this}transpose(){const t=this.x;return this.x=this.y,this.y=t,this}invert(){return this.x=-this.x,this.y=-this.y,this}scale(t){return this.x*=t,this.y*=t,this}div(t){return this.x/=t,this.y/=t,this}to(t){return this.x-=t.x,this.y-=t.y,this}equal(t){return this.x===t.x&&this.y===t.y}dot(t){return this.x*t.x+this.y*t.y}copy(){return new t(this.x,this.y)}toString(){return`vec2{${this.x}, ${this.y}}`}}class e{constructor(e){e.isConnected||console.warn("D-Pad GetBounds: Element might not be rendered or connected to the DOM.",e);const n=e.getBoundingClientRect();this.tl=new t(n.left,n.top),this.br=new t(n.right,n.bottom)}overlaps(t){const e=this.tl.x<t.br.x&&t.tl.x<this.br.x,n=this.tl.y<t.br.y&&t.tl.y<this.br.y;return e&&n}center(){const e=this.size();return e.scale(.5),new t(this.tl.x+e.x,this.tl.y+e.x)}size(){return new t(this.br.x-this.tl.x,this.br.y-this.tl.y)}vector(e){let n=0;e.br.x<this.tl.x?n=e.br.x-this.tl.x:this.br.x<e.tl.x&&(n=e.tl.x-this.br.x);let r=0;return e.br.y<this.tl.y?r=e.br.y-this.tl.y:this.br.y<e.tl.y&&(r=e.tl.y-this.br.y),0===n&&0===r?e.center().to(this.center()):new t(n,r)}toString(){return`rect{ ${this.tl}, ${this.br} }`}}let n=null;function r(t,r,s=!1){const i=o(t),l=function(t,n,r){const s=new e(t),i=s.center(),o={alignment:0,distance:1/0,element:null,skew:1/0};for(const t of r){const r=new e(t);if(r.overlaps(s))continue;const c=s.vector(r),l=c.length2();c.div(l);const u=n.dot(c);if(0===u)continue;if(u<o.alignment)continue;if(l>o.distance)continue;const a=r.center().to(i),h=n.copy().transpose().abs().dot(a);u===o.alignment&&l==o.distance&&h>o.skew||(o.element=t,o.alignment=u,o.distance=l,o.skew=h)}return null==o?void 0:o.element}(t,r,c(i,t)),u=new e(t);return l?(n=l.classList.contains("dp-group")?{element:l,dir:r,from:u}:null,l.focus(),!0):!s&&(i!=document.body?(n={element:i,dir:r.invert(),from:u},i.focus(),!0):(n&&(n.dir=r),!1))}function s(t,e){if(!l(t,"dp-flow-auto","dp-flow-strict"))return!1;for(const n of e)if(r(t,n.copy(),!0))return!0;return!1}function i(t){return setTimeout((()=>{0===t.value.length&&s(t,h.back)}),0),!1}function o(t){var e;const n=null===(e=t.parentElement)||void 0===e?void 0:e.closest(".dp-group");return n instanceof HTMLElement?n:document.body}function*c(t,e){const n=t.querySelectorAll("input, textarea, select, button, a, *[tabindex], audio, video, summary");for(const r of n){if(r===t)continue;if(r===e)continue;if(!(r instanceof HTMLElement))continue;if(r.classList.contains("dp-ignore"))continue;if(r.classList.contains("dp-disable"))continue;if(o(r)!=t)continue;if(r.hasAttribute("disabled"))continue;const n=window.getComputedStyle(r);"none"!==n.display&&("hidden"!==n.visibility&&"contents"!==n.display&&(yield r))}}function l(t,e,n){if(t.classList.contains(e))return!0;if(t.classList.contains(n))return!1;const r=t.closest(`.${e}, .${n}`);return!!r&&(!!r.classList.contains(e)||(r.classList.contains(n),!1))}function u(t){let e=0,n=0;for(let r=0;r<t.selectionStart;r++)"\n"!==t.value[r]?e++:(e=0,n++);let r=n;for(let e=t.selectionStart;e<t.value.length;e++)"\n"===t.value[e]&&r++;let s=t.value.indexOf("\n",t.selectionStart);return s=-1===s?t.value.length-t.selectionStart+e:s-t.selectionStart+e,{line:n,col:e,cols:s,lines:r}}document.addEventListener("keydown",(t=>{const o=document.activeElement;if(!(o&&o instanceof HTMLElement))return;if(!l(o,"dp-enable","dp-disable"))return!1;const f=function(t,o){if(n&&" "===t.key)return function(t){const n={distance:1/0,element:null};for(const r of c(t.element)){const s=new e(r).vector(t.from).length2();s>n.distance||(n.element=r,n.distance=s)}return!!n.element&&(n.element.focus(),!0)}(n);if(o instanceof HTMLInputElement){switch(t.key){case"ArrowRight":return o.selectionStart==o.value.length&&r(o,a.right.copy());case"ArrowLeft":return 0==o.selectionStart&&r(o,a.left.copy());case"ArrowUp":return r(o,a.up.copy());case"ArrowDown":return r(o,a.down.copy());case"Backspace":return i(o);default:if(1===t.key.length&&!t.ctrlKey&&!t.metaKey&&!t.altKey)return function(t){return setTimeout((()=>!(!t.maxLength||-1===t.maxLength)&&!(t.value.length<t.maxLength)&&void s(t,h.next)),0),!1}(o)}return!1}if(o instanceof HTMLSelectElement){switch(t.key){case"ArrowRight":return r(o,a.right.copy());case"ArrowLeft":return r(o,a.left.copy());case"ArrowUp":return 0==o.selectedIndex&&r(o,a.up.copy());case"ArrowDown":return o.selectedIndex==o.options.length-1&&r(o,a.down.copy())}return!1}if(o instanceof HTMLTextAreaElement){switch(t.key){case"ArrowRight":{const t=u(o);return t.col===t.cols&&r(o,a.right.copy())}case"ArrowLeft":return 0===u(o).col&&r(o,a.left.copy());case"ArrowUp":return 0===u(o).line&&r(o,a.up.copy());case"ArrowDown":{const t=u(o);return t.line==t.lines&&r(o,a.down.copy())}case"Backspace":return i(o)}return!1}const l=function(t){if(!t.startsWith("Arrow"))return null;const e=a[t.slice(5).toLowerCase()];return e?e.copy():null}(t.key);return!!l&&r(o,l)}(t,o);return f&&t.preventDefault()," "===t.key&&(o instanceof HTMLDivElement||o instanceof HTMLLabelElement)?(t.preventDefault(),o.click(),!1):void 0}));const a={up:new t(0,-1),down:new t(0,1),left:new t(-1,0),right:new t(1,0)};Object.freeze(a),Object.freeze(a.up),Object.freeze(a.down),Object.freeze(a.left),Object.freeze(a.right);const h={next:[a.right,a.down],back:[a.left,a.up]};return{direction:a,autoFlow:h}}();