@peter.naydenov/shortcuts 3.3.1 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Changelog.md +51 -1
- package/README.md +2 -0
- package/dist/main.d.ts +120 -0
- package/dist/methods/_normalizeWithPlugins.d.ts +2 -0
- package/dist/methods/_readShortcutWithPlugins.d.ts +2 -0
- package/dist/methods/_systemAction.d.ts +2 -0
- package/dist/methods/changeContext.d.ts +2 -0
- package/dist/methods/index.d.ts +17 -0
- package/dist/methods/listShortcuts.d.ts +17 -0
- package/dist/methods/load.d.ts +2 -0
- package/dist/methods/unload.d.ts +2 -0
- package/dist/plugins/click/_findTarget.d.ts +2 -0
- package/dist/plugins/click/_listenDOM.d.ts +5 -0
- package/dist/plugins/click/_normalizeShortcutName.d.ts +2 -0
- package/dist/plugins/click/_readClickEvent.d.ts +2 -0
- package/dist/plugins/click/_registerShortcutEvents.d.ts +2 -0
- package/dist/plugins/click/index.d.ts +15 -0
- package/dist/plugins/form/_defaults.d.ts +5 -0
- package/dist/plugins/form/_listenDOM.d.ts +5 -0
- package/dist/plugins/form/_normalizeShortcutName.d.ts +2 -0
- package/dist/plugins/form/_registerShortcutEvents.d.ts +2 -0
- package/dist/plugins/form/index.d.ts +10 -0
- package/dist/plugins/key/_listenDOM.d.ts +5 -0
- package/dist/plugins/key/_normalizeShortcutName.d.ts +2 -0
- package/dist/plugins/key/_readKeyEvent.d.ts +2 -0
- package/dist/plugins/key/_registerShortcutEvents.d.ts +2 -0
- package/dist/plugins/key/_specialChars.d.ts +32 -0
- package/dist/plugins/key/index.d.ts +15 -0
- package/dist/shortcuts.cjs +1 -1
- package/dist/shortcuts.esm.mjs +1 -1
- package/dist/shortcuts.umd.js +1 -1
- package/jsconfig.json +10 -0
- package/package.json +16 -7
- package/src/main.js +98 -28
- package/src/methods/_readShortcutWithPlugins.js +2 -1
- package/src/methods/changeContext.js +2 -1
- package/src/methods/listShortcuts.js +2 -1
- package/src/methods/load.js +10 -7
- package/src/methods/unload.js +3 -3
- package/src/plugins/click/_listenDOM.js +13 -3
- package/src/plugins/click/index.js +16 -6
- package/src/plugins/form/index.js +12 -17
- package/src/plugins/key/_listenDOM.js +13 -0
- package/src/plugins/key/index.js +11 -5
- package/test/01-general.test.js +158 -251
- package/test/02-key.test.js +272 -0
- package/test/03-click.test.js +352 -0
- package/test/04-form.test.js +90 -0
- package/test-helpers/setup.js +18 -0
- package/test-helpers/wait.js +8 -0
- package/tsconfig.json +23 -0
- package/vitest.config.js +21 -0
- package/vitest-example/HelloWorld.js +0 -9
- package/vitest-example/HelloWorld.test.js +0 -11
- package/vitest.workspace.js +0 -19
- /package/{test-components → test-helpers}/Block.jsx +0 -0
- /package/{test-components → test-helpers}/style.css +0 -0
package/dist/shortcuts.umd.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).shortcuts={})}(this,function(t){"use strict";var e={_normalizeWithPlugins:function(t,e){return function(t){const n=e.shortcuts;Object.keys(n).forEach(e=>{Object.entries(n[e]).forEach(([o,r])=>{const i=t(o);i!==o&&(delete n[e][o],n[e][i]=r)})})}},_readShortcutWithPlugins:function(t,e){return function(n){const{inAPI:o}=t,r=n.split(":")[0],i=o._systemAction(r,"none");let s=n;return-1!==i&&(s=e.plugins[i].shortcutName(n)),s}},_systemAction:function(t,e){return function(t,n,o=null){return e.plugins.findIndex(e=>e.getPrefix()===t&&(e[n]&&e[n](o),!0))}},changeContext:function(t,e){const{shortcuts:n,currentContext:o}=e,{ev:r}=t;return function(t=!1){const i=o.name;if(!t)return r.reset(),void(o.name=null);i!==t&&(n[t]?(n[i]&&r.reset(),o.name=t,e.plugins.forEach(e=>e.contextChange(t)),Object.entries(n[t]).forEach(([t,e])=>{e.forEach(e=>r.on(t,e))}),r.on("*",(...t)=>{e.exposeShortcut&&e.exposeShortcut(...t)})):r.emit("@shortcuts-error",`Context '${t}' does not exist`))}},listShortcuts:function(t,e){const n=e.shortcuts;return function(t=null){if(null!=t){let e=n[t];return null==e?null:Object.entries(e).map(([t,e])=>t)}return Object.keys(n).map(t=>{let e={};return e.context=t,e.shortcuts=Object.entries(n[t]).map(([t,e])=>t),e})}},load:function(t,e){const{shortcuts:n,plugins:o}=e,{API:{changeContext:r,getContext:i}}=t;return function(t){const e=i(),s=o.map(t=>t.getPrefix().toUpperCase());let c=!1;Object.entries(t).forEach(([t,r])=>{t===e&&(c=!0),n[t]={},Object.entries(r).forEach(([e,r])=>{let i=e,c=e.toUpperCase().trim(),u=s.map((t,e)=>c.startsWith(t)?e:null).filter(t=>null!==t);if(u.length){let t=u[0];i=o[t].shortcutName(e)}r instanceof Function&&(r=[r]),n[t][i]=r})}),c&&(r(),r(e))}},unload:function(t,e){const{currentContext:n,shortcuts:o}=e,{ev:r}=t;return function(t){n.name!==t?o[t]?delete o[t]:r.emit("shortcuts-error",`Context '${t}' does not exist`):r.emit("shortcuts-error",`Context '${t}' can't be removed during is current active context. Change the context first`)}}};function n(t){const e=t.toUpperCase(),n=/KEY\s*\:/i.test(e),o=e.indexOf(":");if(!n)return t;return`KEY:${e.slice(o+1).split(",").map(t=>t.trim()).map(t=>t.split("+").map(t=>t.trim()).sort().join("+")).join(",")}`}function o(t,e){let{shiftKey:n,altKey:o,ctrlKey:r}=t,i=e(),s=t.code.replace("Key","").replace("Digit",""),c=[];return r&&c.push("CTRL"),n&&c.push("SHIFT"),o&&c.push("ALT"),i.hasOwnProperty(s)?c.push(i[s].toUpperCase()):["ControlLeft","ControlRight","ShiftLeft","ShiftRight","AltLeft","AltRight","Meta"].includes(s)||c.push(s.toUpperCase()),c.sort()}function r(t,e){let n=0;const{regex:o}=t,{listenOptions:r,currentContext:{name:i},shortcuts:s}=e;return null==i?0:(Object.entries(s[i]).forEach(([t,e])=>{if(!o.test(t))return;n++;let i=t.slice(4).split(",").length;r.maxSequence<i&&(r.maxSequence=i)}),n)}function i(){return{ArrowLeft:"LEFT",ArrowUp:"UP",ArrowRight:"RIGHT",ArrowDown:"DOWN",Enter:"ENTER",NumpadEnter:"ENTER",Escape:"ESC",Backspace:"BACKSPACE",Space:"SPACE",Tab:"TAB",Backquote:"`",BracketLeft:"[",BracketRight:"]",Equal:"=",Slash:"/",Backslash:"\\",IntlBackslash:"`",F1:"F1",F2:"F2",F3:"F3",F4:"F4",F5:"F5",F6:"F6",F7:"F7",F8:"F8",F9:"F9",F10:"F10",F11:"F11",F12:"F12"}}function s(t,e,n){const{listenOptions:{clickTarget:o}}=e;let r=n;return r===document||r===document.body?null:r.dataset[o]||"A"===r.nodeName?r:s(t,e,r.parentNode)}function c(t){const e=t.toUpperCase(),n=/CLICK\s*\:/i.test(e),o=["LEFT","MIDDLE","RIGHT"],r=["ALT","SHIFT","CTRL"];let i=null,s=[],c=0,u=e.indexOf(":");return n?(e.slice(u+1).trim().split("-").map(t=>t.trim()).forEach(t=>{o.includes(t)?i=t:r.includes(t)?s.push(t):isNaN(t)||(c=t)}),`CLICK:${i}-${c}${s.length>0?"-":""}${s.sort().join("-")}`):t}function u(t,e){let{shiftKey:n,altKey:o,ctrlKey:r,key:i,button:s}=t,c=`CLICK:${["LEFT","MIDDLE","RIGHT"][s]}-${e}`,u=[];return r&&u.push("CTRL"),n&&u.push("SHIFT"),o&&u.push("ALT"),u.length>0?`${c}${u.length>0?"-":""}${u.sort().join("-")}`:`${c}`}function l(t,e){let n=0;const{regex:o}=t,{listenOptions:r,currentContext:{name:i},shortcuts:s}=e;return null==i?0:(Object.entries(s[i]).forEach(([t,e])=>{if(!o.test(t))return;n++;let[,i]=t.slice(6).split("-");r.maxClicks<i&&(r.maxClicks=i)}),n)}function a(t){const e=t.toUpperCase(),n=/FORM\s*\:/i.test(e),o=e.indexOf(":");if(!n)return t;return`FORM:${e.slice(o+1).trim()}`}function f(t,e){const{regex:n,_defaults:o,ev:r}=t,{currentContext:{name:i},shortcuts:s,callbacks:c}=e;let u=[],l=[],a=[];if(null==i)return!1;if(Object.entries(s[i]).forEach(([t,e])=>{n.test(t)&&("FORM:WATCH"===t&&(u=e),"FORM:DEFINE"===t&&(l=e),"FORM:ACTION"===t&&(a=e))}),0===a.length)return!1;let f=new Set;0===l.length&&(l=[o.define]),0===u.length&&(u=[o.watch]);let m=u.map(t=>t()).reduce((t,e)=>(t.push(e),t),[]);return e.watchList=document.querySelectorAll(m),e.watchList.forEach(t=>f.add(l[0](t))),e.typeFn=l[0]?l[0]:o.define,a.forEach(t=>{if(!(t instanceof Function))return console.warn("Warning: The 'form:action' should be a function."),!1;if(!(t()instanceof Array))return console.warn("Warning: The 'form:action' function should RETURN an array."),!1;t().forEach(({fn:t,type:n,timing:o,wait:i=0})=>{if(f.has(n)&&t instanceof Function){let e=`${n}/${o}`;const i=c.hasOwnProperty(e);i?c[e].push(t):c[e]=[t],i||r.on(e,(t,e)=>{e.forEach(e=>{e instanceof Function&&e(t)})})}"instant"===o&&(e.wait[`${n}`]=i)})}),Object.keys(e.callbacks).length>0}const m={watch:()=>"input, select, textarea, button, a",define:t=>"checkbox"===t.type||"radio"===t.type?"checkbox":"button"==t.type||"submit"==t.type?"button":"input"};t.pluginClick=function(t,e,n){let{currentContext:o,shortcuts:r}=e,{inAPI:i}=t,a={ev:t.ev,_findTarget:s,_readClickEvent:u,mainDependencies:t,regex:/CLICK\s*\:/i},f={currentContext:o,shortcuts:r,listenOptions:{mouseWait:n.mouseWait?n.mouseWait:320,maxClicks:1,clickTarget:n.clickTarget?n.clickTarget:"click"}};i._normalizeWithPlugins(c);let m=function(t,e){const{ev:n,_findTarget:o,_readClickEvent:r,mainDependencies:i}=t,{listenOptions:s,currentContext:c}=e,{mouseWait:u}=s;let l=null,a=null,f=null,m=null,p=0;function h(){const t=r(a,p),e={target:l,targetProps:l?l.getBoundingClientRect():null,x:a.clientX,y:a.clientY,context:c.name,note:c.note,event:a,dependencies:i.extra,type:"click"};n.emit(t,e),f=null,m=null,l=null,a=null,p=0}function d(n){let r=s.maxClicks;return clearTimeout(f),m?(clearTimeout(m),void(m=setTimeout(()=>m=null,u))):(l=o(t,e,n.target),l&&l.dataset.hasOwnProperty("quickClick")&&(r=1),l&&"A"===l.tagName&&(r=1),a=n,p++,p>=r?(h(),void(r>1&&(m=setTimeout(()=>m=null,u)))):void(f=setTimeout(h,u)))}function g(n){let r=s.maxClicks;return clearTimeout(f),m?(clearTimeout(m),void(m=setTimeout(()=>m=null,u))):(l=o(t,e,n.target),l&&l.dataset.hasOwnProperty("quickClick")&&(r=1),l&&"A"===l.tagName&&(r=1),a=n,p++,p>=r?(h(),void(r>1&&(m=setTimeout(()=>m=null,u)))):void(f=setTimeout(h,u)))}return{start:function(){e.active||(window.addEventListener("contextmenu",g),document.addEventListener("click",d),e.active=!0)},stop:function(){e.active&&(window.removeEventListener("contextmenu",g),document.removeEventListener("click",d),e.active=!1)}}}(a,f),p=l(a,f);p>0&&m.start();let h={getPrefix:()=>"click",shortcutName:t=>c(t),contextChange:()=>{p=l(a,f),p<1&&m.stop(),p>0&&m.start()},mute:()=>m.stop(),unmute:()=>m.start(),destroy:()=>{m.stop(),f=null,h=null}};return Object.freeze(h),h},t.pluginForm=function(t,e,n){let{currentContext:o,shortcuts:r}=e,{inAPI:i}=t,s={ev:t.ev,mainDependencies:t,regex:/FORM\s*\:/i,_defaults:m},c={currentContext:o,shortcuts:r,callbacks:{},typeFn:"",watchList:[],wait:{}};i._normalizeWithPlugins(a);let u=function(t,e){const{ev:n}=t;let o=null;function r(t,e,n,o){return{target:n.target,context:e.currentContext.name,note:e.currentContext.note,event:n,dependencies:t.mainDependencies.extra,type:o}}function i(o){const{callbacks:i,typeFn:s}=e,c=s(o.target),u=r(t,e,o,c),l=`${c}/in`;null!=i[l]&&n.emit(l,u,i[l])}function s(o){const{callbacks:i,typeFn:s}=e,c=r(t,e,o,"form-out"),u=`${s(o.target)}/out`;null!=i[u]&&n.emit(u,c,i[u])}function c(i){const{callbacks:s,typeFn:c}=e,u=r(t,e,i,"form-instant"),l=c(i.target),a=e.wait[`${l}`],f=`${l}/instant`;null!=s[f]&&(0!==a?(clearTimeout(o),o=setTimeout(()=>n.emit(f,u,s[f]),a)):n.emit(f,u,s[f]))}return{start:function(){e.active||(document.addEventListener("focusin",i),document.addEventListener("focusout",s),document.addEventListener("input",c),e.active=!0)},stop:function(){e.active&&(document.removeEventListener("focusin",i),document.removeEventListener("focusout",s),document.removeEventListener("input",c),e.active=!1)}}}(s,c);if(o.name){f(s,c)&&u.start()}let l={getPrefix:()=>"form",shortcutName:t=>a(t),contextChange:()=>{c.callbacks={},c.typeFn="",c.watchList=[],c.wait={},f(s,c)?u.start():u.stop()},mute:()=>u.stop(),unmute:()=>u.start(),destroy:()=>{u.stop(),c=null,l=null}};return Object.freeze(l),l},t.pluginKey=function(t,e,s={}){let{currentContext:c,shortcuts:u,exposeShortcut:l}=e,{inAPI:a}=t,f={ev:t.ev,_specialChars:i,_readKeyEvent:o,mainDependencies:t,regex:/KEY\s*\:/i},m={currentContext:c,shortcuts:u,active:!1,listenOptions:{keyWait:s.keyWait?s.keyWait:480,maxSequence:1,keyIgnore:null},streamKeys:!(!s.streamKeys||"function"!=typeof s.streamKeys)&&s.streamKeys};a._normalizeWithPlugins(n);let p=function(t,e){const{ev:n,_specialChars:o,_readKeyEvent:r,mainDependencies:i}=t,{currentContext:s,streamKeys:c,listenOptions:u}=e,{keyWait:l}=u;let a=[],f=null,m=!0,p=!1;const h=()=>m=!1,d=()=>m=!0,g=()=>p=!0,y=()=>!1===m;function x(){let t=a.map(t=>[t.join("+")]);const e={wait:h,end:d,ignore:g,isWaiting:y,note:s.note,context:s.name,dependencies:i.extra,type:"key"};if(!m){let o=t.at(-1);n.emit(o,e),p&&(t=t.slice(0,-1),p=!1)}if(m){const o=`KEY:${t.join(",")}`;n.emit(o,e),a=[],f=null}}function v(e){if(clearTimeout(f),o().hasOwnProperty(e.code))return a.push(r(e,o)),c&&c({key:e.key,context:s.name,note:s.note,dependencies:t.extra}),u.keyIgnore?(clearTimeout(u.keyIgnore),void(u.keyIgnore=setTimeout(()=>u.keyIgnore=null,l))):m&&a.length===u.maxSequence?(x(),void(u.keyIgnore=setTimeout(()=>u.keyIgnore=null,l))):void(m?f=setTimeout(x,l):x())}function C(e){if(!o().hasOwnProperty(e.code)){if(clearTimeout(f),c&&c({key:e.key,context:s.name,note:s.note,dependencies:t.extra}),u.keyIgnore)return clearTimeout(u.keyIgnore),void(u.keyIgnore=setTimeout(()=>u.keyIgnore=null,l));if(a.push(r(e,o)),m&&a.length===u.maxSequence)return x(),void(u.keyIgnore=setTimeout(()=>u.keyIgnore=null,l));m?f=setTimeout(x,l):x()}}return{start:function(){e.active||(document.addEventListener("keydown",v),document.addEventListener("keypress",C),e.active=!0)},stop:function(){e.active&&(document.removeEventListener("keydown",v),document.removeEventListener("keypress",C),e.active=!1)}}}(f,m),h=r(f,m);h>0&&p.start();let d={getPrefix:()=>"key",shortcutName:t=>n(t),contextChange:t=>{h=r(f,m),h<1&&p.stop(),h>0&&p.start()},mute:()=>p.stop(),unmute:()=>p.start(),destroy:()=>{p.stop(),m=null,d=null}};return Object.freeze(d),d},t.shortcuts=function(t={}){const n=function(){let t={"*":[]},e={},n=new Set,o=!1,r="";return{on:function(e,n){t[e]||(t[e]=[]),t[e].push(n)},once:function(t,n){"*"!==t&&(e[t]||(e[t]=[]),e[t].push(n))},off:function(n,o){if(o)return t[n]&&(t[n]=t[n].filter(t=>t!==o)),e[n]&&(e[n]=e[n].filter(t=>t!==o)),t[n]&&0===t[n].length&&delete t[n],void(e[n]&&0===e[n].length&&delete t[n]);e[n]&&delete e[n],t[n]&&delete t[n]},reset:function(){t={"*":[]},e={},n=new Set},emit:function(){const[i,...s]=arguments;function c(e){let o=!1;"*"!==e&&(n.has(e)||(t[e].every(t=>{const e=t(...s);return"string"!=typeof e||"STOP"!==e.toUpperCase()||(o=!0,!1)}),o||t["*"].forEach(t=>t(i,...s))))}if(o&&(console.log(`${r} Event "${i}" was triggered.`),s.length>0&&(console.log("Arguments:"),console.log(...s),console.log("^----"))),"*"!==i){if(e[i]){if(n.has(i))return;e[i].forEach(t=>t(...s)),delete e[i]}t[i]&&c(i)}else Object.keys(t).forEach(t=>c(t))},stop:function(o){if("*"===o){const o=Object.keys(t),r=Object.keys(e);return void(n=new Set([...r,...o]))}n.add(o)},start:function(t){"*"!==t?n.delete(t):n.clear()},debug:function(t,e){o=!!t,e&&"string"==typeof e&&(r=e)}}}(),o={},r={},i={currentContext:{name:null,note:null},shortcuts:{},plugins:[],exposeShortcut:!(!t.onShortcut||"function"!=typeof t.onShortcut)&&t.onShortcut},s={ev:n,inAPI:o,API:r,extra:{}};return r.enablePlugin=(t,e={})=>{const n=t.name;if(-1===o._systemAction(n,"none")){let n;n=t(s,i,e),i.plugins.push(n)}},r.disablePlugin=t=>{const e=o._systemAction(t,"destroy");-1!==e&&(i.plugins=i.plugins.filter((t,n)=>n!==e))},r.mutePlugin=t=>o._systemAction(t,"mute"),r.unmutePlugin=t=>o._systemAction(t,"unmute"),r.getContext=()=>i.currentContext.name,r.getNote=()=>i.currentContext.note,r.setNote=(t=null)=>{"string"!=typeof t&&null!=t||(i.currentContext.note=t)},r.pause=(t="*")=>{let e=o._readShortcutWithPlugins(t);n.stop(e)},r.resume=(t="*")=>{const e=o._readShortcutWithPlugins(t);n.start(e)},r.emit=(t,...e)=>n.emit(o._readShortcutWithPlugins(t),...e),r.listContexts=()=>Object.keys(i.shortcuts),r.setDependencies=t=>s.extra={...s.extra,...t},r.getDependencies=()=>s.extra,Object.entries(e).forEach(([t,e])=>{t.startsWith("_")?o[t]=e(s,i):r[t]=e(s,i)}),r}});
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).shortcuts={})}(this,function(e){"use strict";var t={_normalizeWithPlugins:function(e,t){return function(e){const n=t.shortcuts;Object.keys(n).forEach(t=>{Object.entries(n[t]).forEach(([o,r])=>{const i=e(o);i!==o&&(delete n[t][o],n[t][i]=r)})})}},_readShortcutWithPlugins:function(e,t){return function(n){const{inAPI:o}=e,r=n.split(":")[0].toLowerCase().trim(),i=o._systemAction(r,"none");let s=n;return-1!==i&&(s=t.plugins[i].shortcutName(n)),s}},_systemAction:function(e,t){return function(e,n,o=null){return t.plugins.findIndex(t=>t.getPrefix()===e&&(t[n]&&t[n](o),!0))}},changeContext:function(e,t){const{shortcuts:n,currentContext:o,ERROR_EVENT_NAME:r}=t,{ev:i}=e;return function(e=!1){const s=o.name;if(!e)return i.reset(),void(o.name=null);s!==e&&(n[e]?(n[s]&&i.reset(),o.name=e,t.plugins.forEach(t=>t.contextChange(e)),Object.entries(n[e]).forEach(([e,t])=>{t.forEach(t=>i.on(e,t))}),i.on("*",(...e)=>{t.exposeShortcut&&t.exposeShortcut(...e)})):i.emit(r,`Context '${e}' does not exist`))}},listShortcuts:function(e,t){const n=t.shortcuts;return function(e=null){if(null!=e){let t=n[e];return null==t?null:Object.entries(t).map(([e,t])=>e)}return Object.keys(n).map(e=>{let t={};return t.context=e,t.shortcuts=Object.entries(n[e]).map(([e,t])=>e),t})}},load:function(e,t){const{shortcuts:n,plugins:o}=t,{API:{changeContext:r,getContext:i}}=e;return function(e){const t=i(),s=o.map(e=>e.getPrefix().toUpperCase());let c=!1;Object.entries(e).forEach(([e,r])=>{e===t&&(c=!0),n[e]={},Object.entries(r).forEach(([t,r])=>{let i=t,c=t.toUpperCase().trim(),u=s.map((e,t)=>c.startsWith(e)?t:null).filter(e=>null!==e);if(u.length){let e=u[0];i=o[e].shortcutName(t)}r instanceof Function&&(r=[r]),n[e][i]=r})}),c&&(r(),r(t))}},unload:function(e,t){const{currentContext:n,shortcuts:o,ERROR_EVENT_NAME:r}=t,{ev:i}=e;return function(e){n.name!==e?o[e]?delete o[e]:i.emit(r,`Context '${e}' does not exist`):i.emit(r,`Context '${e}' can't be removed during is current active context. Change the context first`)}}};function n(e){const t=e.toUpperCase(),n=/KEY\s*\:/i.test(t),o=t.indexOf(":");if(!n)return e;return`KEY:${t.slice(o+1).split(",").map(e=>e.trim()).map(e=>e.split("+").map(e=>e.trim()).sort().join("+")).join(",")}`}function o(e,t){let{shiftKey:n,altKey:o,ctrlKey:r}=e,i=t(),s=e.code.replace("Key","").replace("Digit",""),c=[];return r&&c.push("CTRL"),n&&c.push("SHIFT"),o&&c.push("ALT"),i.hasOwnProperty(s)?c.push(i[s].toUpperCase()):["ControlLeft","ControlRight","ShiftLeft","ShiftRight","AltLeft","AltRight","Meta"].includes(s)||c.push(s.toUpperCase()),c.sort()}function r(e,t){let n=0;const{regex:o}=e,{listenOptions:r,currentContext:{name:i},shortcuts:s}=t;return null==i?0:(Object.entries(s[i]).forEach(([e,t])=>{if(!o.test(e))return;n++;let i=e.slice(4).split(",").length;r.maxSequence<i&&(r.maxSequence=i)}),n)}function i(){return{ArrowLeft:"LEFT",ArrowUp:"UP",ArrowRight:"RIGHT",ArrowDown:"DOWN",Enter:"ENTER",NumpadEnter:"ENTER",Escape:"ESC",Backspace:"BACKSPACE",Space:"SPACE",Tab:"TAB",Backquote:"`",BracketLeft:"[",BracketRight:"]",Equal:"=",Slash:"/",Backslash:"\\",IntlBackslash:"`",F1:"F1",F2:"F2",F3:"F3",F4:"F4",F5:"F5",F6:"F6",F7:"F7",F8:"F8",F9:"F9",F10:"F10",F11:"F11",F12:"F12"}}function s(e,t,n){const{listenOptions:{clickTarget:o}}=t;let r=n;return r===document||r===document.body?null:r.dataset[o]||"A"===r.nodeName?r:s(e,t,r.parentNode)}function c(e){const t=e.toUpperCase(),n=/CLICK\s*\:/i.test(t),o=["LEFT","MIDDLE","RIGHT"],r=["ALT","SHIFT","CTRL"];let i=null,s=[],c=0,u=t.indexOf(":");return n?(t.slice(u+1).trim().split("-").map(e=>e.trim()).forEach(e=>{o.includes(e)?i=e:r.includes(e)?s.push(e):isNaN(e)||(c=e)}),`CLICK:${i}-${c}${s.length>0?"-":""}${s.sort().join("-")}`):e}function u(e,t){let{shiftKey:n,altKey:o,ctrlKey:r,key:i,button:s}=e,c=`CLICK:${["LEFT","MIDDLE","RIGHT"][s]}-${t}`,u=[];return r&&u.push("CTRL"),n&&u.push("SHIFT"),o&&u.push("ALT"),u.length>0?`${c}${u.length>0?"-":""}${u.sort().join("-")}`:`${c}`}function l(e,t){let n=0;const{regex:o}=e,{listenOptions:r,currentContext:{name:i},shortcuts:s}=t;return null==i?0:(Object.entries(s[i]).forEach(([e,t])=>{if(!o.test(e))return;n++;let[,i]=e.slice(6).split("-");r.maxClicks<i&&(r.maxClicks=i)}),n)}function a(e){const t=e.toUpperCase(),n=/FORM\s*\:/i.test(t),o=t.indexOf(":");if(!n)return e;return`FORM:${t.slice(o+1).trim()}`}function f(e,t){const{regex:n,_defaults:o,ev:r}=e,{currentContext:{name:i},shortcuts:s,callbacks:c}=t;let u=[],l=[],a=[];if(null==i)return!1;if(Object.entries(s[i]).forEach(([e,t])=>{n.test(e)&&("FORM:WATCH"===e&&(u=t),"FORM:DEFINE"===e&&(l=t),"FORM:ACTION"===e&&(a=t))}),0===a.length)return!1;let f=new Set;0===l.length&&(l=[o.define]),0===u.length&&(u=[o.watch]);let m=u.map(e=>e()).reduce((e,t)=>(e.push(t),e),[]);return t.watchList=document.querySelectorAll(m),t.watchList.forEach(e=>f.add(l[0](e))),t.typeFn=l[0]?l[0]:o.define,a.forEach(e=>{if(!(e instanceof Function))return console.warn("Warning: The 'form:action' should be a function."),!1;if(!(e()instanceof Array))return console.warn("Warning: The 'form:action' function should RETURN an array."),!1;e().forEach(({fn:e,type:n,timing:o,wait:i=0})=>{if(f.has(n)&&e instanceof Function){let t=`${n}/${o}`;const i=c.hasOwnProperty(t);i?c[t].push(e):c[t]=[e],i||r.on(t,(e,t)=>{t.forEach(t=>{t instanceof Function&&t(e)})})}"instant"===o&&(t.wait[`${n}`]=i)})}),Object.keys(t.callbacks).length>0}const m={watch:()=>"input, select, textarea, button, a",define:e=>"checkbox"===e.type||"radio"===e.type?"checkbox":"button"==e.type||"submit"==e.type?"button":"input"};e.pluginClick=function(e,t,n){let{currentContext:o,shortcuts:r}=t,{inAPI:i}=e,a={ev:e.ev,_findTarget:s,_readClickEvent:u,mainDependencies:e,regex:/CLICK\s*\:/i},f={currentContext:o,active:!1,shortcuts:r,listenOptions:{mouseWait:n.mouseWait?n.mouseWait:320,maxClicks:1,clickTarget:n.clickTarget?n.clickTarget:"click"}};i._normalizeWithPlugins(c);let m=function(e,t){const{ev:n,_findTarget:o,_readClickEvent:r,mainDependencies:i}=e,{listenOptions:s,currentContext:c}=t,{mouseWait:u}=s;let l=null,a=null,f=null,m=null,p=0;function h(){const e=r(a,p),t={target:l,targetProps:l?l.getBoundingClientRect():null,x:a.clientX,y:a.clientY,context:c.name,note:c.note,event:a,dependencies:i.extra,type:"click"};n.emit(e,t),f=null,m=null,l=null,a=null,p=0}function d(n){let r=s.maxClicks;return clearTimeout(f),m?(clearTimeout(m),void(m=setTimeout(()=>m=null,u))):(l=o(e,t,n.target),l&&l.dataset.hasOwnProperty("quickClick")&&(r=1),l&&"A"===l.tagName&&(r=1),a=n,p++,p>=r?(h(),void(r>1&&(m=setTimeout(()=>m=null,u)))):void(f=setTimeout(h,u)))}function g(n){let r=s.maxClicks;return clearTimeout(f),m?(clearTimeout(m),void(m=setTimeout(()=>m=null,u))):(l=o(e,t,n.target),l&&l.dataset.hasOwnProperty("quickClick")&&(r=1),l&&"A"===l.tagName&&(r=1),a=n,p++,p>=r?(h(),void(r>1&&(m=setTimeout(()=>m=null,u)))):void(f=setTimeout(h,u)))}return{start:function(){t.active||(window.addEventListener("contextmenu",g),document.addEventListener("click",d),t.active=!0)},stop:function(){t.active&&(window.removeEventListener("contextmenu",g),document.removeEventListener("click",d),t.active=!1,f&&(clearTimeout(f),f=null),m&&(clearTimeout(m),m=null),p=0)}}}(a,f),p=l(a,f);p>0&&m.start();let h={getPrefix:()=>"click",shortcutName:e=>c(e),contextChange:()=>{p=l(a,f),p<1&&m.stop(),p>0&&m.start()},mute:()=>{m.stop()},unmute:()=>m.start(),destroy:()=>m.stop()};return Object.freeze(h),h},e.pluginForm=function(e,t,n){let{currentContext:o,shortcuts:r}=t,{inAPI:i}=e,s={ev:e.ev,mainDependencies:e,regex:/FORM\s*\:/i,_defaults:m},c={currentContext:o,shortcuts:r,callbacks:{},typeFn:"",watchList:[],wait:{}};i._normalizeWithPlugins(a);let u=function(e,t){const{ev:n}=e;let o=null;function r(e,t,n,o){return{target:n.target,context:t.currentContext.name,note:t.currentContext.note,event:n,dependencies:e.mainDependencies.extra,type:o}}function i(o){const{callbacks:i,typeFn:s}=t,c=s(o.target),u=r(e,t,o,c),l=`${c}/in`;null!=i[l]&&n.emit(l,u,i[l])}function s(o){const{callbacks:i,typeFn:s}=t,c=r(e,t,o,"form-out"),u=`${s(o.target)}/out`;null!=i[u]&&n.emit(u,c,i[u])}function c(i){const{callbacks:s,typeFn:c}=t,u=r(e,t,i,"form-instant"),l=c(i.target),a=t.wait[`${l}`],f=`${l}/instant`;null!=s[f]&&(0!==a?(clearTimeout(o),o=setTimeout(()=>n.emit(f,u,s[f]),a)):n.emit(f,u,s[f]))}return{start:function(){t.active||(document.addEventListener("focusin",i),document.addEventListener("focusout",s),document.addEventListener("input",c),t.active=!0)},stop:function(){t.active&&(document.removeEventListener("focusin",i),document.removeEventListener("focusout",s),document.removeEventListener("input",c),t.active=!1)}}}(s,c);if(o.name){f(s,c)&&u.start()}let l={getPrefix:()=>"form",shortcutName:e=>a(e),contextChange:()=>{c.callbacks={},c.typeFn="",c.watchList=[],c.wait={},f(s,c)?u.start():u.stop()},mute:()=>u.stop(),unmute:()=>u.start(),destroy:()=>{u.stop()}};return Object.freeze(l),l},e.pluginKey=function(e,t,s={}){let{currentContext:c,shortcuts:u,exposeShortcut:l}=t,{inAPI:a}=e,f={ev:e.ev,_specialChars:i,_readKeyEvent:o,mainDependencies:e,regex:/KEY\s*\:/i},m={currentContext:c,shortcuts:u,active:!1,listenOptions:{keyWait:s.keyWait?s.keyWait:480,maxSequence:1,keyIgnore:null},streamKeys:!(!s.streamKeys||"function"!=typeof s.streamKeys)&&s.streamKeys};a._normalizeWithPlugins(n);let p=function(e,t){const{ev:n,_specialChars:o,_readKeyEvent:r,mainDependencies:i}=e,{currentContext:s,streamKeys:c,listenOptions:u}=t,{keyWait:l}=u;let a=[],f=null,m=!0,p=!1;const h=()=>m=!1,d=()=>m=!0,g=()=>p=!0,y=()=>!1===m;function x(){let e=a.map(e=>[e.join("+")]);const t={wait:h,end:d,ignore:g,isWaiting:y,note:s.note,context:s.name,dependencies:i.extra,type:"key"};if(!m){let o=e.at(-1);n.emit(o,t),p&&(e=e.slice(0,-1),p=!1)}if(m){const o=`KEY:${e.join(",")}`;n.emit(o,t),a=[],f=null}}function E(t){if(clearTimeout(f),o().hasOwnProperty(t.code))return a.push(r(t,o)),c&&c({key:t.key,context:s.name,note:s.note,dependencies:e.extra}),u.keyIgnore?(clearTimeout(u.keyIgnore),void(u.keyIgnore=setTimeout(()=>u.keyIgnore=null,l))):m&&a.length===u.maxSequence?(x(),void(u.keyIgnore=setTimeout(()=>u.keyIgnore=null,l))):void(m?f=setTimeout(x,l):x())}function v(t){if(!o().hasOwnProperty(t.code)){if(clearTimeout(f),c&&c({key:t.key,context:s.name,note:s.note,dependencies:e.extra}),u.keyIgnore)return clearTimeout(u.keyIgnore),void(u.keyIgnore=setTimeout(()=>u.keyIgnore=null,l));if(a.push(r(t,o)),m&&a.length===u.maxSequence)return x(),void(u.keyIgnore=setTimeout(()=>u.keyIgnore=null,l));m?f=setTimeout(x,l):x()}}return{start:function(){t.active||(document.addEventListener("keydown",E),document.addEventListener("keypress",v),t.active=!0)},stop:function(){t.active&&(document.removeEventListener("keydown",E),document.removeEventListener("keypress",v),t.active=!1,f&&(clearTimeout(f),f=null),u.keyIgnore&&(clearTimeout(u.keyIgnore),u.keyIgnore=null),a=[],m=!0,p=!1)}}}(f,m),h=r(f,m);h>0&&p.start();let d={getPrefix:()=>"key",shortcutName:e=>n(e),contextChange:e=>{h=r(f,m),h<1&&p.stop(),h>0&&p.start()},mute:()=>p.stop(),unmute:()=>p.start(),destroy:()=>p.stop()};return Object.freeze(d),d},e.shortcuts=function(e={}){let n={},o={};const r=function(){let e={"*":[]},t={},n=new Set,o=!1,r="";return{on:function(t,n){e[t]||(e[t]=[]),e[t].push(n)},once:function(e,n){"*"!==e&&(t[e]||(t[e]=[]),t[e].push(n))},off:function(n,o){if(o)return e[n]&&(e[n]=e[n].filter(e=>e!==o)),t[n]&&(t[n]=t[n].filter(e=>e!==o)),e[n]&&0===e[n].length&&delete e[n],void(t[n]&&0===t[n].length&&delete e[n]);t[n]&&delete t[n],e[n]&&delete e[n]},reset:function(){e={"*":[]},t={},n=new Set},emit:function(){const[i,...s]=arguments;function c(t){let o=!1;"*"!==t&&(n.has(t)||(e[t].every(e=>{const t=e(...s);return"string"!=typeof t||"STOP"!==t.toUpperCase()||(o=!0,!1)}),o||e["*"].forEach(e=>e(i,...s))))}if(o&&(console.log(`${r} Event "${i}" was triggered.`),s.length>0&&(console.log("Arguments:"),console.log(...s),console.log("^----"))),"*"!==i){if(t[i]){if(n.has(i))return;t[i].forEach(e=>e(...s)),delete t[i]}e[i]&&c(i)}else Object.keys(e).forEach(e=>c(e))},stop:function(o){if("*"===o){const o=Object.keys(e),r=Object.keys(t);return void(n=new Set([...r,...o]))}n.add(o)},start:function(e){"*"!==e?n.delete(e):n.clear()},debug:function(e,t){o=!!e,t&&"string"==typeof t&&(r=t)}}}(),i={currentContext:{name:null,note:null},shortcuts:{},plugins:[],exposeShortcut:!(!e.onShortcut||"function"!=typeof e.onShortcut)&&e.onShortcut,ERROR_EVENT_NAME:e.errorEventName?e.errorEventName:"@shortcuts-error"};let s={ev:r,inAPI:n,API:o,extra:{}};return o.enablePlugin=(e,t={})=>{if("function"!=typeof e)return;let o=e(s,i,t);const r=o.getPrefix();-1===n._systemAction(r,"none")?i.plugins.push(o):o.destroy()},o.disablePlugin=e=>{const t=n._systemAction(e,"destroy");-1!==t&&(i.plugins=i.plugins.filter((e,n)=>n!==t))},o.mutePlugin=e=>n._systemAction(e,"mute"),o.unmutePlugin=e=>n._systemAction(e,"unmute"),o.listPlugins=()=>i.plugins.map(e=>e.getPrefix()),o.getContext=()=>i.currentContext.name,o.getNote=()=>i.currentContext.note,o.setNote=(e=null)=>{"string"!=typeof e&&null!=e||(i.currentContext.note=e)},o.pause=(e="*")=>{let t=n._readShortcutWithPlugins(e);r.stop(t)},o.resume=(e="*")=>{const t=n._readShortcutWithPlugins(e);r.start(t)},o.emit=(e,...t)=>r.emit(n._readShortcutWithPlugins(e),...t),o.listContexts=()=>Object.keys(i.shortcuts),o.setDependencies=e=>s.extra={...s.extra,...e},o.getDependencies=()=>s.extra,o.reset=function(){r.reset(),o.changeContext(),i.plugins.forEach(e=>e.destroy()),o.listContexts().map(e=>o.unload(e)),s.extra={},i.exposeShortcut=null},Object.entries(t).forEach(([e,t])=>{e.startsWith("_")?n[e]=t(s,i):o[e]=t(s,i)}),o}});
|
package/jsconfig.json
ADDED
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peter.naydenov/shortcuts",
|
|
3
3
|
"description": "Context control of shortcuts based on keyboard and mouse events",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.5.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Peter Naydenov",
|
|
7
7
|
"main": "./dist/shortcuts.umd.js",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
+
"types": "./dist/main.d.ts",
|
|
11
12
|
"import": "./dist/shortcuts.esm.mjs",
|
|
12
13
|
"require": "./dist/shortcuts.cjs",
|
|
13
14
|
"default": "./dist/shortcuts.umd.js"
|
|
@@ -18,8 +19,11 @@
|
|
|
18
19
|
},
|
|
19
20
|
"scripts": {
|
|
20
21
|
"dev": "vite",
|
|
21
|
-
"build": "rollup -c",
|
|
22
|
-
"test": "vitest
|
|
22
|
+
"build": "rollup -c && npm run types",
|
|
23
|
+
"test": "vitest",
|
|
24
|
+
"test:ui": "vitest --ui",
|
|
25
|
+
"cover": "vitest run --coverage",
|
|
26
|
+
"types": "tsc"
|
|
23
27
|
},
|
|
24
28
|
"repository": {
|
|
25
29
|
"type": "git",
|
|
@@ -30,18 +34,23 @@
|
|
|
30
34
|
},
|
|
31
35
|
"devDependencies": {
|
|
32
36
|
"@peter.naydenov/visual-controller-for-react": "^3.0.1",
|
|
33
|
-
"@rollup/plugin-commonjs": "^28.0.
|
|
34
|
-
"@rollup/plugin-node-resolve": "^16.0.
|
|
37
|
+
"@rollup/plugin-commonjs": "^28.0.8",
|
|
38
|
+
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
35
39
|
"@rollup/plugin-terser": "^0.4.4",
|
|
40
|
+
"@testing-library/dom": "^10.4.1",
|
|
36
41
|
"@vitejs/plugin-react": "^5.0.4",
|
|
37
42
|
"@vitest/browser": "^3.2.4",
|
|
43
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
44
|
+
"@vitest/ui": "^3.2.4",
|
|
38
45
|
"ask-for-promise": "^3.0.2",
|
|
39
46
|
"chai": "^6.2.0",
|
|
40
47
|
"mocha": "^11.7.4",
|
|
41
|
-
"playwright": "^1.56.
|
|
48
|
+
"playwright": "^1.56.1",
|
|
42
49
|
"react": "^19.2.0",
|
|
43
50
|
"react-dom": "^19.2.0",
|
|
44
|
-
"
|
|
51
|
+
"typescript": "^5.9.3",
|
|
52
|
+
"vite": "^7.1.10",
|
|
53
|
+
"vitest": "^3.2.4"
|
|
45
54
|
},
|
|
46
55
|
"keywords": [
|
|
47
56
|
"shortcut",
|
package/src/main.js
CHANGED
|
@@ -1,19 +1,52 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
4
|
+
* @typedef {Object} PluginAPI
|
|
5
|
+
* @property {function(): string} getPrefix - Get plugin prefix
|
|
6
|
+
* @property {function(string): string} shortcutName - Format shortcut name
|
|
7
|
+
* @property {function(string): void} contextChange - Handle context change
|
|
8
|
+
* @property {function(): void} mute - Mute the plugin
|
|
9
|
+
* @property {function(): void} unmute - Unmute the plugin
|
|
10
|
+
* @property {function(): void} destroy - Destroy the plugin
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {Object} ShortcutsAPI
|
|
15
|
+
* @property {function(function, Object): void} enablePlugin - Enable a plugin
|
|
16
|
+
* @property {function(string): void} disablePlugin - Disable a plugin
|
|
17
|
+
* @property {function(string): number} mutePlugin - Mute a plugin
|
|
18
|
+
* @property {function(string): number} unmutePlugin - Unmute a plugin
|
|
19
|
+
* @property {function(): string[]} listPlugins - List enabled plugins
|
|
20
|
+
* @property {function(): string|null} getContext - Get current context name
|
|
21
|
+
* @property {function(): string|null} getNote - Get current context note
|
|
22
|
+
* @property {function(string|null): void} setNote - Set current context note
|
|
23
|
+
* @property {function(string): void} pause - Pause shortcuts in current context
|
|
24
|
+
* @property {function(string): void} resume - Resume shortcuts in current context
|
|
25
|
+
* @property {function(string, ...any): void} emit - Emit event for shortcut
|
|
26
|
+
* @property {function(): string[]} listContexts - List all context names
|
|
27
|
+
* @property {function(Object): void} setDependencies - Set external dependencies
|
|
28
|
+
* @property {function(): Object} getDependencies - Get external dependencies
|
|
29
|
+
* @property {function(): void} reset - Reset the library instance
|
|
30
|
+
* @property {function(string|boolean): void} changeContext - Change current context
|
|
31
|
+
* @property {function(string|null): string[]|Object[]} listShortcuts - List shortcuts
|
|
32
|
+
* @property {function(Object): void} load - Load shortcuts into contexts
|
|
33
|
+
* @property {function(string): void} unload - Unload a context
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Shortcuts
|
|
38
|
+
* =========
|
|
39
|
+
*
|
|
40
|
+
* Create shortcuts for your web application based on keyboard and mouse and DOM events.
|
|
41
|
+
* Repository: https://github.com/PeterNaydenov/shortcuts
|
|
42
|
+
*
|
|
43
|
+
* History notes:
|
|
44
|
+
* - Development was started on June 21st, 2023
|
|
45
|
+
* - First version was published on August 14th, 2023
|
|
46
|
+
* - Method 'emit' was added on September 30st, 2023
|
|
47
|
+
* - Version 2.0.0 was published on October 16th, 2023
|
|
48
|
+
* - Version 3.0.0. Plugin system. Published on March 5th, 2024
|
|
49
|
+
* - Version 3.2.0. Added plugin 'form'. Published on August 15th, 2025
|
|
17
50
|
*/
|
|
18
51
|
|
|
19
52
|
|
|
@@ -30,25 +63,35 @@ import pluginForm from './plugins/form/index.js'
|
|
|
30
63
|
|
|
31
64
|
|
|
32
65
|
|
|
66
|
+
/**
|
|
67
|
+
* @function shortcuts
|
|
68
|
+
* @description Create a shortcuts instance
|
|
69
|
+
* @param {Object} [options={}] - Configuration options
|
|
70
|
+
* @param {function} [options.onShortcut] - Function to log shortcut events
|
|
71
|
+
* @param {string} [options.errorEventName='@shortcuts-error'] - Name for error events
|
|
72
|
+
* @returns {ShortcutsAPI} The shortcuts API
|
|
73
|
+
*/
|
|
33
74
|
function main ( options = {} ) {
|
|
34
|
-
|
|
75
|
+
let
|
|
76
|
+
inAPI = {} // API for internal methods
|
|
77
|
+
, API = {} // API for public methods
|
|
78
|
+
;
|
|
35
79
|
const
|
|
36
80
|
ev = notice () // Event emitter instance
|
|
37
|
-
, inAPI = {} // API for internal methods
|
|
38
|
-
, API = {} // API for public methods
|
|
39
81
|
, state = {
|
|
40
|
-
currentContext
|
|
41
|
-
, shortcuts
|
|
42
|
-
, plugins
|
|
43
|
-
, exposeShortcut
|
|
82
|
+
currentContext : { name: null, note: null } // Context data container
|
|
83
|
+
, shortcuts : {} // shortcuts = { contextName : { shortcut : callback[] } }
|
|
84
|
+
, plugins : [] // Array of active plugins
|
|
85
|
+
, exposeShortcut : (options.onShortcut && ( typeof options.onShortcut === 'function')) ? options.onShortcut : false // Keyboard shortcut log function
|
|
86
|
+
, ERROR_EVENT_NAME : ( options.errorEventName ) ? options.errorEventName : '@shortcuts-error'
|
|
44
87
|
} // state
|
|
45
|
-
|
|
88
|
+
;
|
|
89
|
+
let dependencies = {
|
|
46
90
|
ev
|
|
47
91
|
, inAPI
|
|
48
92
|
, API
|
|
49
93
|
, extra : {}
|
|
50
|
-
}
|
|
51
|
-
;
|
|
94
|
+
};
|
|
52
95
|
|
|
53
96
|
|
|
54
97
|
|
|
@@ -58,17 +101,21 @@ function main ( options = {} ) {
|
|
|
58
101
|
* @description Enable a plugin
|
|
59
102
|
* @returns {void}
|
|
60
103
|
*/
|
|
61
|
-
API.enablePlugin = ( plugin,options={}) => {
|
|
104
|
+
API.enablePlugin = ( plugin, options={}) => {
|
|
105
|
+
if ( typeof plugin !== 'function' ) return
|
|
106
|
+
let plugApp = plugin ( dependencies, state, options )
|
|
62
107
|
const
|
|
63
|
-
name =
|
|
108
|
+
name = plugApp.getPrefix ()
|
|
64
109
|
, ix = inAPI._systemAction ( name, 'none' )
|
|
65
110
|
;
|
|
66
111
|
|
|
67
112
|
if ( ix === -1 ) { // If plugin is not registered
|
|
68
|
-
|
|
69
|
-
plugApp = plugin ( dependencies, state, options )
|
|
113
|
+
// Started instance of the plugin
|
|
70
114
|
state.plugins.push ( plugApp )
|
|
71
115
|
}
|
|
116
|
+
else {
|
|
117
|
+
plugApp.destroy ()
|
|
118
|
+
}
|
|
72
119
|
} // enable func.
|
|
73
120
|
|
|
74
121
|
|
|
@@ -99,8 +146,15 @@ function main ( options = {} ) {
|
|
|
99
146
|
API.unmutePlugin = pluginName => inAPI._systemAction ( pluginName, 'unmute' )
|
|
100
147
|
|
|
101
148
|
|
|
102
|
-
|
|
149
|
+
/**
|
|
150
|
+
* @function listPlugins
|
|
151
|
+
* @description List all enabled plugins
|
|
152
|
+
* @returns {string[]} - Array of plugin names
|
|
153
|
+
*/
|
|
154
|
+
API.listPlugins = () => state.plugins.map ( plugin => plugin.getPrefix() )
|
|
155
|
+
|
|
103
156
|
|
|
157
|
+
|
|
104
158
|
// ---------------------- > PUBLIC METHODS < ---------------------- //
|
|
105
159
|
/**
|
|
106
160
|
* @function getContext
|
|
@@ -181,6 +235,21 @@ function main ( options = {} ) {
|
|
|
181
235
|
API.getDependencies = () => dependencies.extra
|
|
182
236
|
|
|
183
237
|
|
|
238
|
+
/**
|
|
239
|
+
* @function reset
|
|
240
|
+
* @description Reset the library instance
|
|
241
|
+
* @returns {void}
|
|
242
|
+
*/
|
|
243
|
+
API.reset = function reset () {
|
|
244
|
+
ev.reset ()
|
|
245
|
+
API.changeContext ()
|
|
246
|
+
state.plugins.forEach ( plugin => plugin.destroy () )
|
|
247
|
+
API.listContexts ().map ( cx => API.unload ( cx ))
|
|
248
|
+
dependencies.extra = {}
|
|
249
|
+
state.exposeShortcut = null
|
|
250
|
+
} // reset func.
|
|
251
|
+
|
|
252
|
+
|
|
184
253
|
|
|
185
254
|
Object.entries ( methods ).forEach ( ([ name, method ]) => {
|
|
186
255
|
if ( name.startsWith('_') ) inAPI [ name ] = method ( dependencies, state )
|
|
@@ -192,6 +261,7 @@ function main ( options = {} ) {
|
|
|
192
261
|
|
|
193
262
|
|
|
194
263
|
|
|
264
|
+
/** @type {function} */
|
|
195
265
|
export { main as shortcuts }
|
|
196
266
|
export {
|
|
197
267
|
pluginKey
|
|
@@ -9,9 +9,10 @@ function _readShortcutWithPlugins ( dependencies, state ) {
|
|
|
9
9
|
return function _readShortcutWithPlugins ( shortcut ) {
|
|
10
10
|
const
|
|
11
11
|
{ inAPI } = dependencies
|
|
12
|
-
, pluginName = shortcut.split(':')[0]
|
|
12
|
+
, pluginName = shortcut.split(':')[0].toLowerCase().trim()
|
|
13
13
|
, ix = inAPI._systemAction ( pluginName, 'none' ) // Find a index. Don't call any method.
|
|
14
14
|
;
|
|
15
|
+
|
|
15
16
|
let pausedEvent = shortcut;
|
|
16
17
|
if ( ix !== -1 ) pausedEvent = state.plugins[ix].shortcutName ( shortcut )
|
|
17
18
|
return pausedEvent
|
|
@@ -5,6 +5,7 @@ const
|
|
|
5
5
|
{
|
|
6
6
|
shortcuts
|
|
7
7
|
, currentContext
|
|
8
|
+
, ERROR_EVENT_NAME
|
|
8
9
|
} = state
|
|
9
10
|
, { ev } = dependencies
|
|
10
11
|
;
|
|
@@ -37,7 +38,7 @@ return function changeContext ( contextName = false ) {
|
|
|
37
38
|
}
|
|
38
39
|
if ( current === contextName ) return // Do nothing if contextName is the same as current
|
|
39
40
|
if ( !shortcuts [ contextName ] ) { // If contextName is not defined
|
|
40
|
-
ev.emit (
|
|
41
|
+
ev.emit ( ERROR_EVENT_NAME, `Context '${ contextName }' does not exist` )
|
|
41
42
|
return
|
|
42
43
|
}
|
|
43
44
|
if ( shortcuts[current] ) {
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
function listShortcuts ( dependencies, state ) {
|
|
11
11
|
const shortcuts = state.shortcuts;
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* @function listShortcuts
|
|
14
|
+
* @description List all shortcuts in all contexts or in a specific context.
|
|
14
15
|
* @param {string}[ contextName=null] - List of shortcuts for provided context name. (optional)
|
|
15
16
|
* @returns {string[]|contextShortcuts[]} - List of shortcuts for a specified context or list of contextShortcuts for all contexts.
|
|
16
17
|
*/
|
package/src/methods/load.js
CHANGED
|
@@ -7,9 +7,10 @@ const
|
|
|
7
7
|
API : { changeContext, getContext }
|
|
8
8
|
} = dependencies;
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* @function load
|
|
11
|
+
* @description Load a context with shortcuts object
|
|
12
|
+
* @param {Object} shortcutsUpdate - Context description object: { contextName : { shortcut : callback[] } }
|
|
13
|
+
* @returns {void}
|
|
13
14
|
*/
|
|
14
15
|
return function load ( shortcutsUpdate ) {
|
|
15
16
|
const
|
|
@@ -19,9 +20,11 @@ return function load ( shortcutsUpdate ) {
|
|
|
19
20
|
let hasChanges = false;
|
|
20
21
|
|
|
21
22
|
Object.entries ( shortcutsUpdate ).forEach ( ([contextName, contextShortcuts]) => {
|
|
22
|
-
|
|
23
|
+
// If changes in current context will need to reload it
|
|
24
|
+
if ( contextName === currentContextName ) hasChanges = true;
|
|
23
25
|
shortcuts [ contextName ] = {}
|
|
24
|
-
|
|
26
|
+
|
|
27
|
+
Object.entries ( contextShortcuts ).forEach ( ([ title, payload ]) => {
|
|
25
28
|
let
|
|
26
29
|
name = title
|
|
27
30
|
, test = title.toUpperCase().trim()
|
|
@@ -31,8 +34,8 @@ return function load ( shortcutsUpdate ) {
|
|
|
31
34
|
let id = pluginIndexList[0];
|
|
32
35
|
name = plugins[id].shortcutName ( title )
|
|
33
36
|
}
|
|
34
|
-
if (
|
|
35
|
-
shortcuts [contextName][ name ] =
|
|
37
|
+
if ( payload instanceof Function ) payload = [ payload ]
|
|
38
|
+
shortcuts [contextName][ name ] = payload
|
|
36
39
|
}) // contextShortcuts.forEach
|
|
37
40
|
}) // shortcutsUpdate.forEach
|
|
38
41
|
if ( hasChanges ) { // Reload context shortcuts after loading process if context was active
|
package/src/methods/unload.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
function unload ( dependencies, state ) {
|
|
4
4
|
const
|
|
5
|
-
{ currentContext, shortcuts } = state
|
|
5
|
+
{ currentContext, shortcuts, ERROR_EVENT_NAME } = state
|
|
6
6
|
, { ev } = dependencies
|
|
7
7
|
;
|
|
8
8
|
/**
|
|
@@ -14,11 +14,11 @@ const
|
|
|
14
14
|
return function unload ( contextName ) {
|
|
15
15
|
const current = currentContext.name;
|
|
16
16
|
if ( current === contextName ) {
|
|
17
|
-
ev.emit (
|
|
17
|
+
ev.emit ( ERROR_EVENT_NAME, `Context '${ contextName }' can't be removed during is current active context. Change the context first` )
|
|
18
18
|
return
|
|
19
19
|
}
|
|
20
20
|
if ( !shortcuts [ contextName ] ) {
|
|
21
|
-
ev.emit (
|
|
21
|
+
ev.emit ( ERROR_EVENT_NAME, `Context '${ contextName }' does not exist` )
|
|
22
22
|
return
|
|
23
23
|
}
|
|
24
24
|
delete shortcuts [ contextName ]
|
|
@@ -38,7 +38,7 @@ function _listenDOM ( dependencies, state ) {
|
|
|
38
38
|
ev.emit ( mouseEvent, data )
|
|
39
39
|
// Reset:
|
|
40
40
|
mouseTimer = null
|
|
41
|
-
mouseIgnore = null
|
|
41
|
+
mouseIgnore = null // Timeout timer or null. Ignore mouse clicks until timer expires
|
|
42
42
|
mouseTarget = null
|
|
43
43
|
mouseDomEvent = null
|
|
44
44
|
count = 0
|
|
@@ -95,16 +95,26 @@ function _listenDOM ( dependencies, state ) {
|
|
|
95
95
|
|
|
96
96
|
function start () {
|
|
97
97
|
if ( state.active ) return
|
|
98
|
-
|
|
98
|
+
window.addEventListener ( 'contextmenu', listenRightClick )
|
|
99
99
|
document.addEventListener ( 'click' , listenLeftClick )
|
|
100
100
|
state.active = true
|
|
101
101
|
} // start func.
|
|
102
102
|
|
|
103
103
|
function stop () {
|
|
104
104
|
if ( !state.active ) return
|
|
105
|
-
|
|
105
|
+
window.removeEventListener ( 'contextmenu', listenRightClick )
|
|
106
106
|
document.removeEventListener ( 'click' , listenLeftClick )
|
|
107
107
|
state.active = false
|
|
108
|
+
// Clear any pending timers to prevent state pollution between tests
|
|
109
|
+
if ( mouseTimer ) {
|
|
110
|
+
clearTimeout ( mouseTimer )
|
|
111
|
+
mouseTimer = null
|
|
112
|
+
}
|
|
113
|
+
if ( mouseIgnore ) {
|
|
114
|
+
clearTimeout ( mouseIgnore )
|
|
115
|
+
mouseIgnore = null
|
|
116
|
+
}
|
|
117
|
+
count = 0
|
|
108
118
|
} // stop func.
|
|
109
119
|
|
|
110
120
|
return { start, stop }
|
|
@@ -9,6 +9,16 @@ import _registerShortcutEvents from "./_registerShortcutEvents"
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* @function pluginClick
|
|
14
|
+
* @description Plugin for mouse click shortcuts
|
|
15
|
+
* @param {Object} dependencies - Internal dependencies
|
|
16
|
+
* @param {Object} state - Library state
|
|
17
|
+
* @param {Object} [options={}] - Plugin options
|
|
18
|
+
* @param {number} [options.mouseWait=320] - Time to wait for click sequence in ms
|
|
19
|
+
* @param {string} [options.clickTarget='click'] - Data attribute name for click targets
|
|
20
|
+
* @returns {PluginAPI} Plugin API
|
|
21
|
+
*/
|
|
12
22
|
function pluginClick ( dependencies, state, options ) {
|
|
13
23
|
let
|
|
14
24
|
{ currentContext, shortcuts } = state
|
|
@@ -22,6 +32,7 @@ function pluginClick ( dependencies, state, options ) {
|
|
|
22
32
|
}
|
|
23
33
|
, pluginState = {
|
|
24
34
|
currentContext
|
|
35
|
+
, active : false
|
|
25
36
|
, shortcuts
|
|
26
37
|
, listenOptions : {
|
|
27
38
|
mouseWait : options.mouseWait ? options.mouseWait : 320 // 320 ms
|
|
@@ -55,13 +66,12 @@ function pluginClick ( dependencies, state, options ) {
|
|
|
55
66
|
mouseListener.start ()
|
|
56
67
|
}
|
|
57
68
|
}
|
|
58
|
-
, mute : () =>
|
|
69
|
+
, mute : () => {
|
|
70
|
+
|
|
71
|
+
mouseListener.stop ()
|
|
72
|
+
}
|
|
59
73
|
, unmute : () => mouseListener.start ()
|
|
60
|
-
, destroy : () =>
|
|
61
|
-
mouseListener.stop ()
|
|
62
|
-
pluginState = null
|
|
63
|
-
pluginAPI = null
|
|
64
|
-
}
|
|
74
|
+
, destroy : () => mouseListener.stop ()
|
|
65
75
|
}; // pluginAPI
|
|
66
76
|
Object.freeze ( pluginAPI )
|
|
67
77
|
return pluginAPI
|
|
@@ -8,20 +8,15 @@ import _defaults from './_defaults.js'
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* @function pluginForm
|
|
13
|
+
* @description Plugin for form element shortcuts
|
|
14
|
+
* @param {Object} dependencies - Internal dependencies
|
|
15
|
+
* @param {Object} state - Library state
|
|
16
|
+
* @param {Object} [options={}] - Plugin options
|
|
17
|
+
* @returns {PluginAPI} Plugin API
|
|
18
|
+
*/
|
|
11
19
|
function pluginForm ( dependencies, state, options ) {
|
|
12
|
-
/**
|
|
13
|
-
* 'form: watch' - A function. Should return a string. Define a selection that will be watched for changes. example: 'input, select.color, textarea, #name'
|
|
14
|
-
* 'form: define' - A function that receives every watched element. Should return text value that represents the type of the
|
|
15
|
-
* element according custom specification. Types could be specific for every single form.
|
|
16
|
-
* 'form:action' - List of Callback objects.
|
|
17
|
-
* Callback definition object:
|
|
18
|
-
* {
|
|
19
|
-
* fn: function to be called
|
|
20
|
-
* type: fn should be executed on type of the element
|
|
21
|
-
* timing: 'in' | 'out' | 'instant' - when to execute the function
|
|
22
|
-
* wait: time in milliseconds to wait before executing the function again. Works only with mode 'instant'.
|
|
23
|
-
* }
|
|
24
|
-
*/
|
|
25
20
|
|
|
26
21
|
let
|
|
27
22
|
{ currentContext, shortcuts } = state
|
|
@@ -71,10 +66,10 @@ function pluginForm ( dependencies, state, options ) {
|
|
|
71
66
|
, mute : () => formListener.stop ()
|
|
72
67
|
, unmute : () => formListener.start ()
|
|
73
68
|
, destroy : () => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
formListener.stop ()
|
|
70
|
+
// TODO: Clean up state of the plugin
|
|
71
|
+
}
|
|
72
|
+
|
|
78
73
|
}; // pluginAPI
|
|
79
74
|
Object.freeze ( pluginAPI )
|
|
80
75
|
return pluginAPI
|
|
@@ -126,6 +126,19 @@ function _listenDOM ( dependencies, state ) {
|
|
|
126
126
|
document.removeEventListener ( 'keydown' , listenForSpecialKeys )
|
|
127
127
|
document.removeEventListener ( 'keypress', listenForRegularKeys )
|
|
128
128
|
state.active = false
|
|
129
|
+
// Clear any pending timers to prevent state pollution between tests
|
|
130
|
+
if ( keyTimer ) {
|
|
131
|
+
clearTimeout ( keyTimer )
|
|
132
|
+
keyTimer = null
|
|
133
|
+
}
|
|
134
|
+
if ( listenOptions.keyIgnore ) {
|
|
135
|
+
clearTimeout ( listenOptions.keyIgnore )
|
|
136
|
+
listenOptions.keyIgnore = null
|
|
137
|
+
}
|
|
138
|
+
// Reset all state variables to prevent interference between tests
|
|
139
|
+
r = []
|
|
140
|
+
sequence = true
|
|
141
|
+
ignore = false
|
|
129
142
|
}
|
|
130
143
|
|
|
131
144
|
return { start, stop }
|
package/src/plugins/key/index.js
CHANGED
|
@@ -9,6 +9,16 @@ import _specialChars from './_specialChars.js'
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* @function pluginKey
|
|
14
|
+
* @description Plugin for keyboard shortcuts
|
|
15
|
+
* @param {Object} dependencies - Internal dependencies
|
|
16
|
+
* @param {Object} state - Library state
|
|
17
|
+
* @param {Object} [options={}] - Plugin options
|
|
18
|
+
* @param {number} [options.keyWait=480] - Time to wait for key sequence in ms
|
|
19
|
+
* @param {function} [options.streamKeys] - Function to stream key presses
|
|
20
|
+
* @returns {PluginAPI} Plugin API
|
|
21
|
+
*/
|
|
12
22
|
function pluginKey ( dependencies, state, options={} ) {
|
|
13
23
|
let
|
|
14
24
|
{ currentContext, shortcuts, exposeShortcut } = state
|
|
@@ -59,11 +69,7 @@ function pluginKey ( dependencies, state, options={} ) {
|
|
|
59
69
|
}
|
|
60
70
|
, mute : () => keysListener.stop ()
|
|
61
71
|
, unmute : () => keysListener.start ()
|
|
62
|
-
, destroy : () =>
|
|
63
|
-
keysListener.stop ()
|
|
64
|
-
pluginState = null
|
|
65
|
-
pluginAPI = null
|
|
66
|
-
}
|
|
72
|
+
, destroy : () => keysListener.stop ()
|
|
67
73
|
};
|
|
68
74
|
Object.freeze ( pluginAPI )
|
|
69
75
|
return pluginAPI
|