diff --git a/README.md b/README.md index 5fda607..1b1fca8 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

⌨️ reakeys


- React Hook for Mousetrap Hotkeys + React Hook for Ctrl-keys Hotkeys

@@ -12,7 +12,7 @@ - + @@ -85,11 +85,11 @@ const hotkeys = useHotkeys(); This is useful for creating a dialog to present the user with all the options. Below is an example of how to make -a dialog using [realayers](https://github.com/reaviz/realayers): +a dialog using [reablocks](https://github.com/reaviz/reablocks): ```jsx import React, { useState, FC, useCallback, useMemo } from 'react'; -import { Dialog } from 'realayers'; +import { Dialog } from 'reablocks'; import { useHotkeys, getHotkeyText } from 'reakeys'; import groupBy from 'lodash/groupBy'; import sortBy from 'lodash/sortBy'; diff --git a/package.json b/package.json index e27cb51..62004ef 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ }, "homepage": "https://github.com/reaviz/reakeys#readme", "dependencies": { - "mousetrap": "^1.6.5" + "ctrl-keys": "^1.0.1" }, "peerDependencies": { "react": ">=16", @@ -64,7 +64,6 @@ "@storybook/react-vite": "^7.0.20", "@storybook/theming": "7.0.20", "@types/classnames": "^2.3.1", - "@types/mousetrap": "^1.6.11", "@types/react": "^18.2.12", "@types/react-dom": "^18.2.5", "@typescript-eslint/eslint-plugin": "^5.59.11", diff --git a/src/Hotkey.story.tsx b/src/Hotkey.story.tsx index 9ce92a4..40ed4a7 100644 --- a/src/Hotkey.story.tsx +++ b/src/Hotkey.story.tsx @@ -1,4 +1,3 @@ -import Mousetrap from 'mousetrap'; import React, { useEffect, useRef, useState } from 'react'; import { useHotkeys } from './useHotkeys'; @@ -7,9 +6,7 @@ export default { }; export const Simple = () => { - const hotkeys = useHotkeys([ - { name: 'Test', keys: 'SHIFT+A', callback: () => alert('holla') }, - ]); + const hotkeys = useHotkeys([{ name: 'Simple', keys: 'SHIFT+A', callback: () => alert('SHIFT + A pressed') }]); return (
@@ -25,12 +22,51 @@ export const Simple = () => { ); }; +export const Input = () => { + const hotkeys = useHotkeys([{ name: 'Input', keys: 'SHIFT+A', callback: () => alert('SHIFT + A pressed') }]); + + return ( +
+ Press SHIFT + A (shouldn't trigger if input is focused) +
+ +
+        {JSON.stringify(
+          hotkeys.map(({ ref: element, ...rest }) => rest),
+          null,
+          2
+        )}
+      
+
+ ); +}; + +export const Disable = () => { + const [disabled, setDisabled] = useState(false); + + const hotkeys = useHotkeys([{ name: 'Disable', keys: 'SHIFT+A', callback: () => alert('SHIFT + A pressed'), disabled }]); + + return ( +
+ Press SHIFT + A
+ +
+        {JSON.stringify(
+          hotkeys.map(({ ref: element, disabled, ...rest }) => rest),
+          null,
+          2
+        )}
+      
+
+ ); +}; + export const Refs = () => { const [color, setColor] = useState('blue'); const hotkeys = useHotkeys([ { - name: 'Test', + name: 'Refs', keys: 'SHIFT+A', callback: () => { alert(`color: ${color}`); @@ -59,17 +95,15 @@ export const Refs = () => { export const Multiple = () => { const hotkeys = useHotkeys([ - { name: 'Test', keys: 'SHIFT+A', callback: () => alert('holla') }, - ]); - - useHotkeys([ - { name: 'Test 2', keys: 'mod+b', callback: () => alert('baller') }, + { name: 'Nested A', keys: ['SHIFT+A'], callback: () => alert('SHIFT + A pressed') }, + { name: 'Nested B', keys: ['META+B'], callback: () => alert('META + B pressed') }, ]); return (
- Press SHIFT + A
- Press MOD + b
+ Press SHIFT + A +
+ Press META + B
         {JSON.stringify(
@@ -83,24 +117,19 @@ export const Multiple = () => {
 };
 
 const NestedComponent = () => {
-  useHotkeys([
-    { name: 'Test 2', keys: 'mod+b', callback: () => alert('baller') },
-  ]);
+  useHotkeys([{ name: 'Child', keys: ['META+B'], callback: () => alert('META + B (child)') }]);
 
-  return 

Press MOD + b

; + return

Press META + B

; }; export const Nested = () => { - const hotkeys = useHotkeys([ - { name: 'Test', keys: 'SHIFT+A', callback: () => alert('holla') }, - ]); + const hotkeys = useHotkeys([{ name: 'Parent', keys: ['SHIFT+A'], callback: () => alert('SHIFT + A (parent)') }]); return (
Press SHIFT + A

-
         {JSON.stringify(
           hotkeys.map(({ ref: element, ...rest }) => rest),
@@ -119,17 +148,14 @@ export const Focus = () => {
 
   const hotkeys = useHotkeys([
     {
-      name: 'Test 3',
-      keys: 'SHIFT+C',
+      name: 'Focus A',
+      keys: ['SHIFT+C'],
       callback: () => alert(`first, counter: ${counter}`),
       ref: elmRef,
     },
-  ]);
-
-  useHotkeys([
     {
-      name: 'Test 3',
-      keys: 'SHIFT+C',
+      name: 'Focus b',
+      keys: ['SHIFT+C'],
       callback: () => alert(`second, counter: ${counter}`),
       ref: elmRef2,
     },
@@ -137,17 +163,11 @@ export const Focus = () => {
 
   return (
     
- {counter} -
@@ -174,16 +194,16 @@ export const Focus = () => { export const Action = () => { const hotkeys = useHotkeys([ { - name: 'Pay respects', - keys: 'f', + name: 'Action', + keys: ['F'], callback: () => alert("You've been promoted!"), action: 'keyup', }, ]); return ( -
- Press f to pay respects + <> +
Press "f" to pay respects

         {JSON.stringify(
@@ -192,17 +212,17 @@ export const Action = () => {
           2
         )}
       
-
+ ); }; export const Asynchronous = () => { const elmRef = useRef(null); const [loaded, setLoaded] = useState(false); - const hotkeys = useHotkeys([ + useHotkeys([ { - name: 'Loaded', - keys: 'l', + name: 'Asynchronous', + keys: ['L'], callback: () => alert('Hey!'), action: 'keyup', ref: elmRef, @@ -223,35 +243,28 @@ export const Asynchronous = () => { return (
- {loaded - ? 'Loaded' - : 'Loading (pressing "l" is disabled until the element is shown and focused)...'} + {loaded ? 'Loaded' : 'Loading (pressing "L" is disabled until the element is shown and focused)...'} +

{loaded && (
- Click me and press "l`" + Click me and press "l"
)} -
-        {JSON.stringify(
-          hotkeys.map(({ ref: element, ...rest }) => rest),
-          null,
-          2
-        )}
-      
); }; const Counter = () => { const [counter, setCounter] = useState(0); - const hotkeys = useHotkeys([ + + useHotkeys([ { - name: 'Generate a random number', - keys: 'g', + name: 'Counter', + keys: ['G'], callback: () => setCounter(Math.random()), }, ]); @@ -261,22 +274,17 @@ const Counter = () => {
  1. Press "g" to generate a random number: {counter}
  2. Open the modal, press "g" and close the modal
  3. -
  4. - Press "g" once the modal is closed, it should generate - random number -
  5. +
  6. Press "g" once the modal is closed, it should generate random number
-
-
{JSON.stringify(hotkeys, null, 2)}
); }; const ModalComponent = ({ onClose }: { onClose: () => void }) => { - const hotkeys = useHotkeys([ + useHotkeys([ { - name: 'Modal shortcut', - keys: 'g', + name: 'ModalComponent', + keys: ['G'], callback: () => alert('This shortcut is bound through the modal'), }, ]); @@ -297,8 +305,6 @@ const ModalComponent = ({ onClose }: { onClose: () => void }) => {

Press g

-
-
{JSON.stringify(hotkeys, null, 2)}
); @@ -318,25 +324,11 @@ const ModalToggle = () => { }; export const Modal = () => { + const hotkeys = useHotkeys(); return (
-
- ); -}; - -export const Trigger = () => { - const hotkeys = useHotkeys([ - { name: 'Test', keys: 'SHIFT+A', callback: () => alert('holla') }, - ]); - - return ( -
- - Press SHIFT + A
         {JSON.stringify(
           hotkeys.map(({ ref: element, ...rest }) => rest),
diff --git a/src/index.ts b/src/index.ts
index d1bf4c7..0031f23 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,3 +1,2 @@
 export * from './useHotkeys';
-export * from './useHotkeyState';
 export * from './utils';
diff --git a/src/useHotkeyState.ts b/src/useHotkeyState.ts
deleted file mode 100644
index 9443409..0000000
--- a/src/useHotkeyState.ts
+++ /dev/null
@@ -1,113 +0,0 @@
-import { RefObject, useEffect, useState } from 'react';
-import Mousetrap, {
-  ExtendedKeyboardEvent,
-  MousetrapInstance,
-  MousetrapStatic,
-} from 'mousetrap';
-
-export interface HotkeyShortcuts {
-  name: string;
-  category?: string;
-  description?: string;
-  keys: string | string[];
-  ref?: RefObject;
-  hidden?: boolean;
-  disabled?: boolean;
-  callback: (e: ExtendedKeyboardEvent, combo: string) => void;
-  action?: 'keypress' | 'keydown' | 'keyup';
-}
-
-/**
- * Creates a global state singleton.
- */
-const createStateHook = () => {
-  const mousetraps = new Map<
-    HTMLElement | undefined,
-    MousetrapStatic | MousetrapInstance
-  >();
-  let keys: HotkeyShortcuts[] = [];
-
-  const bindKeys = (nextKeys: HotkeyShortcuts[]) => {
-    nextKeys.forEach((k) => {
-      if (k.disabled) {
-        return;
-      }
-
-      if (k.ref) {
-        if (!k.ref.current) {
-          // exit early if ref is provided but null
-          // we do not want to attach global event handlers in this case
-          return;
-        }
-
-        const element = k.ref.current;
-
-        if (!mousetraps.has(element)) {
-          mousetraps.set(element, new Mousetrap(element));
-        }
-
-        mousetraps.get(element)!.bind(k.keys, k.callback, k.action);
-      } else {
-        if (!mousetraps.get(undefined)) {
-          mousetraps.set(undefined, Mousetrap);
-        }
-
-        mousetraps.get(undefined)!.bind(k.keys, k.callback, k.action);
-      }
-    });
-  };
-
-  const addKeys = (nextKeys: HotkeyShortcuts[]) => {
-    keys = [...keys, ...nextKeys];
-
-    bindKeys(nextKeys);
-  };
-
-  const removeKeys = (nextKeys: HotkeyShortcuts[]) => {
-    // remove keys from the array
-    keys = keys.filter((k) => !nextKeys.includes(k));
-
-    // unbind mousetrap events
-    nextKeys.forEach((k) => {
-      if (k.ref) {
-        if (!k.ref.current) {
-          return;
-        }
-
-        mousetraps.get(k.ref.current)?.unbind(k.keys, k.action);
-      } else {
-        mousetraps.get(undefined)?.unbind(k.keys, k.action);
-      }
-    });
-
-    // drop mousetrap instances that have no keys attached anymore
-    for (const [element] of mousetraps) {
-      if (element === undefined) {
-        if (keys.some((k) => k.ref === undefined)) {
-          continue;
-        }
-      } else {
-        if (keys.some((k) => k.ref?.current === element)) {
-          continue;
-        }
-      }
-
-      mousetraps.delete(element);
-    }
-
-    // re-bind keys to restore listeners that were overwritten by the ones we just removed
-    bindKeys(keys);
-  };
-
-  return () => {
-    const [state, setState] = useState([]);
-
-    useEffect(() => {
-      setState(keys);
-    }, []);
-
-    return [state, addKeys, removeKeys] as const;
-  };
-};
-
-export const useHotkeyState = createStateHook();
diff --git a/src/useHotkeys.ts b/src/useHotkeys.ts
index 47c1cc5..2635b83 100644
--- a/src/useHotkeys.ts
+++ b/src/useHotkeys.ts
@@ -1,29 +1,140 @@
-import { useLayoutEffect, useMemo } from 'react';
-import { HotkeyShortcuts, useHotkeyState } from './useHotkeyState';
+import { RefObject, useEffect, useLayoutEffect, useState } from 'react';
+import keys, { Callback, Handler, Key } from 'ctrl-keys';
 
-export const useHotkeys = (shortcuts?: HotkeyShortcuts[]) => {
-  const [keys, addKeys, removeKeys] = useHotkeyState();
+type Keys = [Key] | [Key, Key] | [Key, Key, Key] | [Key, Key, Key, Key];
 
+export interface HotkeyShortcuts {
+  name: string;
+  keys: string | string[];
+  ref?: RefObject;
+  disabled?: boolean;
+  callback: Callback;
+  action?: 'keypress' | 'keydown' | 'keyup';
+  description?: string;
+  category?: string;
+  hidden?: boolean;
+}
+
+let isGlobalListenersBinded = false;
+
+const keypressGlobalHandler = keys();
+const keyupGlobalHandler = keys();
+const keydownGlobalHandler = keys();
+
+/**
+ * Map of specific elements handlers
+ */
+const handlers = new Map();
+let hotkeys: HotkeyShortcuts[] = [];
+
+const extractKeys = (keys: string | string[]): Keys => {
+  return (Array.isArray(keys) ? keys.map((key) => key.toLowerCase()) : [keys.toLowerCase()]) as Keys;
+};
+
+const focusInputWrapper = (callback: Callback) => (event: any) => {
+  const target = event.target;
+
+  const isInput = target.tagName === 'INPUT' && !['checkbox', 'radio', 'range', 'button', 'file', 'reset', 'submit', 'color'].includes(target.type);
+  if (target.isContentEditable || ((isInput || target.tagName === 'TEXTAREA' || target.tagName === 'SELECT') && !target.readOnly)) {
+    return;
+  }
+
+  return callback(event);
+};
+
+const registerGlobalShortcut = (shortcut: HotkeyShortcuts) => {
+  if (!shortcut.action || shortcut.action === 'keypress') {
+    keypressGlobalHandler.add(...extractKeys(shortcut.keys), shortcut.callback);
+  }
+  if (shortcut.action === 'keyup') {
+    keyupGlobalHandler.add(...extractKeys(shortcut.keys), shortcut.callback);
+  }
+  if (shortcut.action === 'keydown') {
+    keydownGlobalHandler.add(...extractKeys(shortcut.keys), shortcut.callback);
+  }
+};
+
+const removeGlobalShortcut = (shortcut: HotkeyShortcuts) => {
+  if (!shortcut.action || shortcut.action === 'keypress') {
+    keypressGlobalHandler.remove(...extractKeys(shortcut.keys), shortcut.callback);
+  }
+  if (shortcut.action === 'keyup') {
+    keyupGlobalHandler.remove(...extractKeys(shortcut.keys), shortcut.callback);
+  }
+  if (shortcut.action === 'keydown') {
+    keydownGlobalHandler.remove(...extractKeys(shortcut.keys), shortcut.callback);
+  }
+};
+
+const registerElementShortcut = (shortcut: HotkeyShortcuts) => {
+  const handler = keys();
+
+  handler.add(...extractKeys(shortcut.keys), shortcut.callback);
+
+  shortcut.ref?.current?.addEventListener(shortcut.action ?? 'keypress', handler.handle);
+
+  handlers.set(shortcut.ref?.current as HTMLElement, handler);
+};
+
+const removeElementShortcut = (shortcut: HotkeyShortcuts) => {
+  if (shortcut.ref?.current && !shortcut.disabled) {
+    const handler = handlers.get(shortcut.ref?.current) as Handler;
+
+    handler.remove(...extractKeys(shortcut.keys), shortcut.callback);
+
+    shortcut.ref?.current?.removeEventListener(shortcut.action ?? 'keypress', handler.handle);
+  }
+};
+
+export const useHotkeys = (shortcuts: HotkeyShortcuts[] = []) => {
+  const [registered, setRegistered] = useState([]);
+  /**
+   * Register global listeners for "keypress", "keyup" and "keydown" events
+   */
   useLayoutEffect(() => {
-    if (shortcuts) {
-      addKeys(shortcuts);
+    if (!isGlobalListenersBinded && window !== undefined) {
+      window.addEventListener('keypress', keypressGlobalHandler.handle);
+      window.addEventListener('keyup', keyupGlobalHandler.handle);
+      window.addEventListener('keydown', keydownGlobalHandler.handle);
+
+      isGlobalListenersBinded = true;
     }
+  }, []);
 
-    return () => {
-      if (shortcuts) {
-        removeKeys(shortcuts);
+  /**
+   * Register shortcuts
+   */
+  useLayoutEffect(() => {
+    shortcuts.map((shortcut) => {
+      if (shortcut.disabled) {
+        return;
+      }
+
+      // Wrap callback in input focus wrapper to avoid trigger shortcut for input
+      shortcut.callback = focusInputWrapper(shortcut.callback);
+
+      if (shortcut.ref?.current) {
+        registerElementShortcut(shortcut);
+        hotkeys = [...hotkeys, shortcut];
+      } else if (!shortcut.ref) {
+        registerGlobalShortcut(shortcut);
+        hotkeys = [...hotkeys, shortcut];
       }
+    });
+
+    // Remove all shortcuts on destroy
+    return () => {
+      shortcuts.map((shortcut) => {
+        removeElementShortcut(shortcut);
+        removeGlobalShortcut(shortcut);
+        hotkeys = hotkeys.filter((hotkey) => shortcut !== hotkey);
+      });
     };
-  }, [addKeys, removeKeys, shortcuts]);
-
-  return useMemo(
-    () =>
-      keys.reduce((agg, cur) => {
-        if (!agg.find((a) => a.keys === cur.keys && a.ref && cur.ref)) {
-          agg.push(cur);
-        }
-        return agg;
-      }, []),
-    [keys]
-  );
+  }, [shortcuts]);
+
+  useEffect(() => {
+    setRegistered(hotkeys);
+  }, []);
+
+  return registered;
 };
diff --git a/yarn.lock b/yarn.lock
index 3796bb1..fc2fb06 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3148,11 +3148,6 @@
   resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca"
   integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==
 
-"@types/mousetrap@^1.6.11":
-  version "1.6.11"
-  resolved "https://registry.yarnpkg.com/@types/mousetrap/-/mousetrap-1.6.11.tgz#ef9620160fdcefcb85bccda8aaa3e84d7429376d"
-  integrity sha512-F0oAily9Q9QQpv9JKxKn0zMKfOo36KHCW7myYsmUyf2t0g+sBTbG3UleTPoguHdE1z3GLFr3p7/wiOio52QFjQ==
-
 "@types/ms@*":
   version "0.7.31"
   resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
@@ -3176,6 +3171,13 @@
   resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.34.tgz#62d2099b30339dec4b1b04a14c96266459d7c8b2"
   integrity sha512-VmVm7gXwhkUimRfBwVI1CHhwp86jDWR04B5FGebMMyxV90SlCmFujwUHrxTD4oO+SOYU86SoxvhgeRQJY7iXFg==
 
+"@types/node@^18.15.11":
+  version "18.19.21"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.21.tgz#f4ca1ac8ffb05ee4b89163c2d6fac9a1a59ee149"
+  integrity sha512-2Q2NeB6BmiTFQi4DHBzncSoq/cJMLDdhPaAoJFnFCyD9a8VPZRf7a1GAwp1Edb7ROaZc5Jz/tnZyL6EsWMRaqw==
+  dependencies:
+    undici-types "~5.26.4"
+
 "@types/normalize-package-data@^2.4.0":
   version "2.4.0"
   resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
@@ -4485,6 +4487,13 @@ csstype@^3.0.2:
   resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.3.tgz#2b410bbeba38ba9633353aff34b05d9755d065f8"
   integrity sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag==
 
+ctrl-keys@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/ctrl-keys/-/ctrl-keys-1.0.1.tgz#7d59a84a8175c11dd590371c1430e328630271a3"
+  integrity sha512-0cfxE8pQDSAl9pkkbksf0RlHJDcreK1b/wUuPtmDGtbVF8z39mNb6+UcXxSjrdYTAqncjShEz8KgtDX+EiRN7A==
+  dependencies:
+    just-types "^2.0.0-alpha.2"
+
 date-time@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/date-time/-/date-time-3.1.0.tgz#0d1e934d170579f481ed8df1e2b8ff70ee845e1e"
@@ -6511,6 +6520,15 @@ jsonfile@^6.0.1:
     array-includes "^3.1.5"
     object.assign "^4.1.3"
 
+just-types@^2.0.0-alpha.2:
+  version "2.0.0-alpha.2"
+  resolved "https://registry.yarnpkg.com/just-types/-/just-types-2.0.0-alpha.2.tgz#672d4ab31b4124afd43f7f9f74b278133979403e"
+  integrity sha512-RvudbWEC0Eo8uijI3HUb0EVehSPxrxmWVUPzU/KPZDXoQosnDo0A9ZeynBFGOJbI1QqYvz4ZC4XXL4yAexrtvw==
+  dependencies:
+    "@types/node" "^18.15.11"
+    tslib "^2.5.0"
+    typescript "^5.0.3"
+
 kind-of@^6.0.2:
   version "6.0.3"
   resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
@@ -7369,11 +7387,6 @@ mlly@^1.2.0:
     pkg-types "^1.0.3"
     ufo "^1.1.2"
 
-mousetrap@^1.6.5:
-  version "1.6.5"
-  resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.5.tgz#8a766d8c272b08393d5f56074e0b5ec183485bf9"
-  integrity sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA==
-
 mri@^1.1.0, mri@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
@@ -9195,6 +9208,11 @@ tslib@^2.1.0, tslib@^2.4.0:
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.2.tgz#1b6f07185c881557b0ffa84b111a0106989e8338"
   integrity sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==
 
+tslib@^2.5.0:
+  version "2.6.2"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
+  integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
+
 tsutils@^3.21.0:
   version "3.21.0"
   resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
@@ -9278,6 +9296,11 @@ typescript@*, typescript@~5.0.4:
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b"
   integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==
 
+typescript@^5.0.3:
+  version "5.3.3"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37"
+  integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==
+
 ufo@^1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.1.2.tgz#d0d9e0fa09dece0c31ffd57bd363f030a35cfe76"
@@ -9298,6 +9321,11 @@ unbox-primitive@^1.0.2:
     has-symbols "^1.0.3"
     which-boxed-primitive "^1.0.2"
 
+undici-types@~5.26.4:
+  version "5.26.5"
+  resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
+  integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
+
 unfetch@^4.2.0:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be"