From d738898d1dc1da6368101ad85dfab43c09df82c9 Mon Sep 17 00:00:00 2001 From: jsakamoto Date: Thu, 19 Dec 2024 17:52:41 +0900 Subject: [PATCH] Improve: excluding didn't work well if an element is inside a shadow DOM. --- HotKeys2/script.js | 15 ++++++++------- HotKeys2/script.ts | 18 ++++++++++-------- HotKeys2/wwwroot/script.min.js | 2 +- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/HotKeys2/script.js b/HotKeys2/script.js index 83b9b85..4431d7e 100644 --- a/HotKeys2/script.js +++ b/HotKeys2/script.js @@ -64,10 +64,10 @@ export var Toolbelt; (ev.metaKey ? 8 : 0); const key = convertToKeyName(ev); const code = ev.code; - const targetElement = ev.target; - const tagName = targetElement.tagName; - const type = targetElement.getAttribute('type'); - const preventDefault = callback(modifiers, key, code, targetElement, tagName, type); + const targets = [ev.target, ev.composedPath()[0]] + .filter(e => e) + .map(e => [e, e.tagName, e.getAttribute('type')]); + const preventDefault = callback(modifiers, key, code, targets); if (preventDefault) ev.preventDefault(); }; @@ -75,7 +75,7 @@ export var Toolbelt; HotKeys2.createContext = () => { let idSeq = 0; const hotKeyEntries = new Map(); - const onKeyDown = (modifiers, key, code, targetElement, tagName, type) => { + const onKeyDown = (modifiers, key, code, targets) => { let preventDefault = false; hotKeyEntries.forEach(entry => { if (!entry.isDisabled) { @@ -96,7 +96,7 @@ export var Toolbelt; entryModKeys |= 8; if (eventModkeys !== entryModKeys) return; - if (isExcludeTarget(entry, targetElement, tagName, type)) + if (targets.some(([targetElement, tagName, type]) => isExcludeTarget(entry, targetElement, tagName, type))) return; preventDefault = true; entry.action(); @@ -128,7 +128,8 @@ export var Toolbelt; }; }; HotKeys2.handleKeyEvent = (hotKeysWrapper, isWasm) => { - const onKeyDown = (modifiers, key, code, targetElement, tagName, type) => { + const onKeyDown = (modifiers, key, code, targets) => { + const [, tagName, type] = targets[0]; if (isWasm) { return hotKeysWrapper.invokeMethod(OnKeyDownMethodName, modifiers, tagName, type, key, code); } diff --git a/HotKeys2/script.ts b/HotKeys2/script.ts index 8fca7d1..6f9d033 100644 --- a/HotKeys2/script.ts +++ b/HotKeys2/script.ts @@ -86,7 +86,8 @@ return false; } - type KeyEventHandler = (modifiers: ModCodes, key: string, code: string, targetElement: HTMLElement, tagName: string, type: string | null) => boolean; + type KeyEventTarget = [HTMLElement, string, string | null]; + type KeyEventHandler = (modifiers: ModCodes, key: string, code: string, targets: KeyEventTarget[]) => boolean; const createKeydownHandler = (callback: KeyEventHandler) => { return (ev: KeyboardEvent) => { @@ -99,11 +100,11 @@ const key = convertToKeyName(ev); const code = ev.code; - const targetElement = ev.target as HTMLElement; - const tagName = targetElement.tagName; - const type = targetElement.getAttribute('type'); + const targets = [ev.target as HTMLElement, ev.composedPath()[0] as HTMLElement | undefined] + .filter(e => e) + .map(e => [e!, e!.tagName, e!.getAttribute('type')]); - const preventDefault = callback(modifiers, key, code, targetElement, tagName, type); + const preventDefault = callback(modifiers, key, code, targets); if (preventDefault) ev.preventDefault(); } } @@ -112,7 +113,7 @@ let idSeq: number = 0; const hotKeyEntries = new Map(); - const onKeyDown = (modifiers: ModCodes, key: string, code: string, targetElement: HTMLElement, tagName: string, type: string | null): boolean => { + const onKeyDown: KeyEventHandler = (modifiers, key, code, targets) => { let preventDefault = false; hotKeyEntries.forEach(entry => { @@ -132,7 +133,7 @@ if (startsWith(keyEntry, "Meta")) entryModKeys |= ModCodes.Meta; if (eventModkeys !== entryModKeys) return; - if (isExcludeTarget(entry, targetElement, tagName, type)) return; + if (targets.some(([targetElement, tagName, type]) => isExcludeTarget(entry, targetElement, tagName, type))) return; preventDefault = true; entry.action(); @@ -171,7 +172,8 @@ export const handleKeyEvent = (hotKeysWrapper: any, isWasm: boolean) => { - const onKeyDown = (modifiers: ModCodes, key: string, code: string, targetElement: HTMLElement, tagName: string, type: string | null): boolean => { + const onKeyDown: KeyEventHandler = (modifiers, key, code, targets: KeyEventTarget[]) => { + const [, tagName, type] = targets[0]; if (isWasm) { return hotKeysWrapper.invokeMethod(OnKeyDownMethodName, modifiers, tagName, type, key, code); } else { diff --git a/HotKeys2/wwwroot/script.min.js b/HotKeys2/wwwroot/script.min.js index 1d1b06f..240ab3c 100644 --- a/HotKeys2/wwwroot/script.min.js +++ b/HotKeys2/wwwroot/script.min.js @@ -1 +1 @@ -export var Toolbelt;(function(n){var t;(function(n){var t;(function(n){const i=document,r="OnKeyDown",u=["button","checkbox","color","file","image","radio","range","reset","submit",],f="INPUT",e="keydown";class c{constructor(n,t,i,r,u,f,e){this.dotNetObj=n;this.mode=t;this.modifiers=i;this.keyEntry=r;this.exclude=u;this.excludeSelector=f;this.isDisabled=e}action(){this.dotNetObj.invokeMethodAsync("InvokeAction")}}const o=n=>i.addEventListener(e,n),s=n=>i.removeEventListener(e,n),l=n=>{return{OS:"Meta",Decimal:"Period"}[n.key]||n.key},t=(n,t)=>n.startsWith(t),a=(n,t,i,r)=>(n.exclude&1)!=0&&i===f&&u.every(n=>n!==r)?!0:(n.exclude&2)!=0&&i===f&&u.some(n=>n===r)?!0:(n.exclude&4)!=0&&i==="TEXTAREA"?!0:(n.exclude&8)!=0&&t.isContentEditable?!0:n.excludeSelector!==""&&t.matches(n.excludeSelector)?!0:!1,h=n=>t=>{if(typeof t.altKey!="undefined"){const r=(t.shiftKey?1:0)+(t.ctrlKey?2:0)+(t.altKey?4:0)+(t.metaKey?8:0),u=l(t),f=t.code,i=t.target,e=i.tagName,o=i.getAttribute("type"),s=n(r,u,f,i,e,o);s&&t.preventDefault()}};n.createContext=()=>{let r=0;const n=new Map,u=(i,r,u,f,e,o)=>{let s=!1;return n.forEach(n=>{if(!n.isDisabled){const l=n.mode===1,v=l?u:r,h=n.keyEntry;if(h!==v)return;const y=l?i:i&65534;let c=l?n.modifiers:n.modifiers&65534;if(t(h,"Shift")&&l&&(c|=1),t(h,"Control")&&(c|=2),t(h,"Alt")&&(c|=4),t(h,"Meta")&&(c|=8),y!==c)return;if(a(n,f,e,o))return;s=!0;n.action()}}),s},i=h(u);return o(i),{register:(t,i,u,f,e,o,s)=>{const h=r++,l=new c(t,i,u,f,e,o,s);return n.set(h,l),h},update:(t,i)=>{const r=n.get(t);r&&(r.isDisabled=i)},unregister:t=>{t!==-1&&n.delete(t)},dispose:()=>{s(i)}}};n.handleKeyEvent=(n,t)=>{const u=(i,u,f,e,o,s)=>t?n.invokeMethod(r,i,o,s,u,f):(n.invokeMethodAsync(r,i,o,s,u,f),!1),i=h(u);return o(i),{dispose:()=>{s(i)}}}})(t=n.HotKeys2||(n.HotKeys2={}))})(t=n.Blazor||(n.Blazor={}))})(Toolbelt||(Toolbelt={})); \ No newline at end of file +export var Toolbelt;(function(n){var t;(function(n){var t;(function(n){const i=document,r="OnKeyDown",u=["button","checkbox","color","file","image","radio","range","reset","submit",],f="INPUT",e="keydown";class c{constructor(n,t,i,r,u,f,e){this.dotNetObj=n;this.mode=t;this.modifiers=i;this.keyEntry=r;this.exclude=u;this.excludeSelector=f;this.isDisabled=e}action(){this.dotNetObj.invokeMethodAsync("InvokeAction")}}const o=n=>i.addEventListener(e,n),s=n=>i.removeEventListener(e,n),l=n=>{return{OS:"Meta",Decimal:"Period"}[n.key]||n.key},t=(n,t)=>n.startsWith(t),a=(n,t,i,r)=>(n.exclude&1)!=0&&i===f&&u.every(n=>n!==r)?!0:(n.exclude&2)!=0&&i===f&&u.some(n=>n===r)?!0:(n.exclude&4)!=0&&i==="TEXTAREA"?!0:(n.exclude&8)!=0&&t.isContentEditable?!0:n.excludeSelector!==""&&t.matches(n.excludeSelector)?!0:!1,h=n=>t=>{if(typeof t.altKey!="undefined"){const i=(t.shiftKey?1:0)+(t.ctrlKey?2:0)+(t.altKey?4:0)+(t.metaKey?8:0),r=l(t),u=t.code,f=[t.target,t.composedPath()[0]].filter(n=>n).map(n=>[n,n.tagName,n.getAttribute("type")]),e=n(i,r,u,f);e&&t.preventDefault()}};n.createContext=()=>{let r=0;const n=new Map,u=(i,r,u,f)=>{let e=!1;return n.forEach(n=>{if(!n.isDisabled){const h=n.mode===1,c=h?u:r,o=n.keyEntry;if(o!==c)return;const l=h?i:i&65534;let s=h?n.modifiers:n.modifiers&65534;if(t(o,"Shift")&&h&&(s|=1),t(o,"Control")&&(s|=2),t(o,"Alt")&&(s|=4),t(o,"Meta")&&(s|=8),l!==s)return;if(f.some(([i,t,r])=>a(n,i,t,r)))return;e=!0;n.action()}}),e},i=h(u);return o(i),{register:(t,i,u,f,e,o,s)=>{const h=r++,l=new c(t,i,u,f,e,o,s);return n.set(h,l),h},update:(t,i)=>{const r=n.get(t);r&&(r.isDisabled=i)},unregister:t=>{t!==-1&&n.delete(t)},dispose:()=>{s(i)}}};n.handleKeyEvent=(n,t)=>{const u=(i,u,f,e)=>{const[,o,s]=e[0];return t?n.invokeMethod(r,i,o,s,u,f):(n.invokeMethodAsync(r,i,o,s,u,f),!1)},i=h(u);return o(i),{dispose:()=>{s(i)}}}})(t=n.HotKeys2||(n.HotKeys2={}))})(t=n.Blazor||(n.Blazor={}))})(Toolbelt||(Toolbelt={})); \ No newline at end of file