@expofp/debug 3.0.0-alpha.3 → 3.0.0-alpha.4

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.
Files changed (41) hide show
  1. package/package.json +1 -1
  2. package/dist/lib/debugUi/DebugOverlay.d.ts +0 -7
  3. package/dist/lib/debugUi/DebugOverlay.js +0 -7
  4. package/dist/lib/debugUi/DebugTabs.d.ts +0 -6
  5. package/dist/lib/debugUi/DebugTabs.js +0 -6
  6. package/dist/lib/debugUi/DebugUi.d.ts +0 -5
  7. package/dist/lib/debugUi/DebugUi.js +0 -31
  8. package/dist/lib/debugUi/LogTab.d.ts +0 -2
  9. package/dist/lib/debugUi/LogTab.js +0 -6
  10. package/dist/lib/debugUi/Settings.d.ts +0 -2
  11. package/dist/lib/debugUi/Settings.js +0 -7
  12. package/dist/lib/debugUi/SettingsItem.d.ts +0 -6
  13. package/dist/lib/debugUi/SettingsItem.js +0 -5
  14. package/dist/lib/debugUi/SettingsTab.d.ts +0 -2
  15. package/dist/lib/debugUi/SettingsTab.js +0 -13
  16. package/dist/lib/debugUi/SideButton.d.ts +0 -4
  17. package/dist/lib/debugUi/SideButton.js +0 -37
  18. package/dist/lib/debugUi/index.d.ts +0 -2
  19. package/dist/lib/debugUi/index.js +0 -1
  20. package/dist/lib/debugUi/renderDebugUi.d.ts +0 -2
  21. package/dist/lib/debugUi/renderDebugUi.js +0 -24
  22. package/dist/lib/editors/BooleanEditor.d.ts +0 -3
  23. package/dist/lib/editors/BooleanEditor.js +0 -6
  24. package/dist/lib/editors/EditorProps.d.ts +0 -7
  25. package/dist/lib/editors/EditorProps.js +0 -1
  26. package/dist/lib/editors/EnumEditor.d.ts +0 -5
  27. package/dist/lib/editors/EnumEditor.js +0 -16
  28. package/dist/lib/initDebug.d.ts +0 -2
  29. package/dist/lib/initDebug.js +0 -15
  30. package/dist/lib/settings/registerBooleanSetting.d.ts +0 -3
  31. package/dist/lib/settings/registerBooleanSetting.js +0 -8
  32. package/dist/lib/settings/registerEnumSetting.d.ts +0 -3
  33. package/dist/lib/settings/registerEnumSetting.js +0 -10
  34. package/dist/lib/settings/registerSetting.d.ts +0 -9
  35. package/dist/lib/settings/registerSetting.js +0 -64
  36. package/dist/lib/settings/registeredSettings.d.ts +0 -3
  37. package/dist/lib/settings/registeredSettings.js +0 -2
  38. package/dist/lib/settings/resetAllSettings.d.ts +0 -2
  39. package/dist/lib/settings/resetAllSettings.js +0 -4
  40. package/dist/lib/utils/addDebugSecretListener.d.ts +0 -2
  41. package/dist/lib/utils/addDebugSecretListener.js +0 -31
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expofp/debug",
3
- "version": "3.0.0-alpha.3",
3
+ "version": "3.0.0-alpha.4",
4
4
  "type": "module",
5
5
  "description": "ExpoFP SDK internal: debug utilities",
6
6
  "license": "MIT",
@@ -1,7 +0,0 @@
1
- import '@radix-ui/themes/styles.css';
2
- interface DebugPanelProps {
3
- onClose?: () => void;
4
- }
5
- export declare const DebugOverlay: ({ onClose }: DebugPanelProps) => import("react/jsx-runtime").JSX.Element;
6
- export {};
7
- //# sourceMappingURL=DebugOverlay.d.ts.map
@@ -1,7 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import '@radix-ui/themes/styles.css';
3
- import { Box, Container, Theme } from '@radix-ui/themes';
4
- import { DebugTabs } from './DebugTabs.js';
5
- export const DebugOverlay = ({ onClose }) => {
6
- return (_jsx(Theme, { appearance: "dark", children: _jsx(Box, { position: "fixed", top: "0", right: "0", width: "100vw", height: "100dvh", overflowY: "auto", style: { backgroundColor: 'black' }, children: _jsx(Container, { size: "4", px: "2", children: _jsx(DebugTabs, { onClose: onClose }) }) }) }));
7
- };
@@ -1,6 +0,0 @@
1
- interface DebugTabsProps {
2
- onClose?: () => void;
3
- }
4
- export declare const DebugTabs: ({ onClose }: DebugTabsProps) => import("react/jsx-runtime").JSX.Element;
5
- export {};
6
- //# sourceMappingURL=DebugTabs.d.ts.map
@@ -1,6 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Cross1Icon } from '@radix-ui/react-icons';
3
- import { Button, Tabs } from '@radix-ui/themes';
4
- import { LogTab } from './LogTab.js';
5
- import { SettingsTab } from './SettingsTab.js';
6
- export const DebugTabs = ({ onClose }) => (_jsxs(Tabs.Root, { defaultValue: "settingsTab", children: [_jsxs(Tabs.List, { children: [_jsx(Tabs.Trigger, { value: "settingsTab", children: "Settings" }), _jsx(Tabs.Trigger, { value: "logTab", children: "Log" }), _jsxs(Button, { ml: "auto", variant: "soft", mt: "3px", color: "gray", onClick: onClose, children: [_jsx(Cross1Icon, { width: "16", height: "16" }), " Close"] })] }), _jsx(Tabs.Content, { value: "settingsTab", children: _jsx(SettingsTab, {}) }), _jsx(Tabs.Content, { value: "logTab", children: _jsx(LogTab, {}) })] }));
@@ -1,5 +0,0 @@
1
- export declare const DebugUi: React.FC<{
2
- open: boolean;
3
- useDebugButtonState: () => [boolean, (n: boolean) => void];
4
- }>;
5
- //# sourceMappingURL=DebugUi.d.ts.map
@@ -1,31 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { lazy, Suspense, useEffect, useState } from 'react';
3
- import { addDebugSecretListener } from '../utils/addDebugSecretListener.js';
4
- import { SideButton } from './SideButton.js';
5
- const LazyDebugOverlay = lazy(async () => ({
6
- default: (await import('./DebugOverlay.js')).DebugOverlay,
7
- }));
8
- export const DebugUi = (props) => {
9
- const [isOpen, setIsOpen] = useState(props.open);
10
- const [debugButtonEnabled, setDebugButtonEnabled] = props.useDebugButtonState();
11
- useEffect(() => {
12
- return addDebugSecretListener(() => {
13
- setDebugButtonEnabled(true);
14
- setIsOpen(true);
15
- });
16
- }, []);
17
- // set visibility of efp floorplan to none when debug ui is open
18
- // otherwise, z-index issues occur
19
- useEffect(() => {
20
- const efpFloorplan = document.getElementsByClassName('expofp-floorplan-default')[0];
21
- if (efpFloorplan) {
22
- efpFloorplan.style.visibility = isOpen ? 'hidden' : 'visible';
23
- }
24
- }, [isOpen]);
25
- if (isOpen) {
26
- return (_jsx(Suspense, { fallback: null, children: _jsx(LazyDebugOverlay, { onClose: () => {
27
- setIsOpen(false);
28
- } }) }));
29
- }
30
- return debugButtonEnabled ? _jsx(SideButton, { onClick: () => setIsOpen(true) }) : null;
31
- };
@@ -1,2 +0,0 @@
1
- export declare const LogTab: () => import("react/jsx-runtime").JSX.Element;
2
- //# sourceMappingURL=LogTab.d.ts.map
@@ -1,6 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { InfoCircledIcon } from '@radix-ui/react-icons';
3
- import { Box, Callout } from '@radix-ui/themes';
4
- export const LogTab = () => {
5
- return (_jsx(Box, { pt: "20px", children: _jsx(Box, { mb: "20px", children: _jsxs(Callout.Root, { children: [_jsx(Callout.Icon, { children: _jsx(InfoCircledIcon, {}) }), _jsx(Callout.Text, { children: "Implement to intercept console.info, console.warn and console.error and output them here when logging enabled." })] }) }) }));
6
- };
@@ -1,2 +0,0 @@
1
- export declare const Settings: React.FC;
2
- //# sourceMappingURL=Settings.d.ts.map
@@ -1,7 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { Skeleton } from '@radix-ui/themes';
3
- import { Suspense } from 'react';
4
- import { editors } from '../settings/registeredSettings.js';
5
- export const Settings = () => {
6
- return editors.map((Editor, index) => (_jsx(Suspense, { fallback: _jsx(Skeleton, { height: "60px" }), children: _jsx(Editor, {}) }, index)));
7
- };
@@ -1,6 +0,0 @@
1
- interface SettingsItemProps extends React.PropsWithChildren {
2
- label: string;
3
- }
4
- export declare const SettingsItem: ({ label, children }: SettingsItemProps) => import("react/jsx-runtime").JSX.Element;
5
- export {};
6
- //# sourceMappingURL=SettingsItem.d.ts.map
@@ -1,5 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { Box, Heading, Separator } from '@radix-ui/themes';
3
- export const SettingsItem = ({ label, children }) => {
4
- return (_jsxs(_Fragment, { children: [_jsxs(Box, { children: [_jsx(Heading, { size: "4", children: label }), _jsx(Box, { mt: "10px", children: children })] }), _jsx(Separator, { size: "4", my: "20px" })] }));
5
- };
@@ -1,2 +0,0 @@
1
- export declare const SettingsTab: () => import("react/jsx-runtime").JSX.Element;
2
- //# sourceMappingURL=SettingsTab.d.ts.map
@@ -1,13 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { InfoCircledIcon } from '@radix-ui/react-icons';
3
- import { AlertDialog, Box, Button, Callout, Flex } from '@radix-ui/themes';
4
- import { resetAllSettings } from '../settings/resetAllSettings.js';
5
- import { Settings } from './Settings.js';
6
- export const SettingsTab = () => {
7
- return (_jsxs(Box, { pt: "20px", children: [_jsx(Box, { mb: "20px", children: _jsxs(Callout.Root, { children: [_jsx(Callout.Icon, { children: _jsx(InfoCircledIcon, {}) }), _jsx(Callout.Text, { children: "These settings will persist across page reloads in your browser." })] }) }), _jsx(Settings, {}), _jsxs(Flex, { gap: "3", mt: "40px", justify: "center", children: [_jsx(Button, { onClick: () => {
8
- window.location.reload();
9
- }, children: "Reload with the applied changes" }), _jsxs(AlertDialog.Root, { children: [_jsx(AlertDialog.Trigger, { children: _jsx(Button, { color: "red", children: "Reset all and reload" }) }), _jsxs(AlertDialog.Content, { maxWidth: "450px", children: [_jsx(AlertDialog.Title, { children: "Reset all and reload" }), _jsx(AlertDialog.Description, { size: "2", children: "This will reset all settings, disable debug mode, and reload the page." }), _jsxs(Flex, { gap: "3", mt: "4", justify: "end", children: [_jsx(AlertDialog.Cancel, { children: _jsx(Button, { variant: "soft", color: "gray", children: "Cancel" }) }), _jsx(AlertDialog.Action, { children: _jsx(Button, { variant: "solid", color: "red", onClick: () => {
10
- resetAllSettings();
11
- window.location.reload();
12
- }, children: "Reset all and reload" }) })] })] })] })] })] }));
13
- };
@@ -1,4 +0,0 @@
1
- export declare const SideButton: ({ onClick }: {
2
- onClick?: () => void;
3
- }) => import("react/jsx-runtime").JSX.Element;
4
- //# sourceMappingURL=SideButton.d.ts.map
@@ -1,37 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- export const SideButton = ({ onClick }) => {
3
- const size = 64;
4
- const width = 20;
5
- const radius = 14;
6
- return (_jsx("button", { type: "button", style: {
7
- position: 'fixed',
8
- right: 0,
9
- top: '50%',
10
- transform: 'translateY(-50%)',
11
- padding: 0,
12
- border: 'none',
13
- background: 'transparent',
14
- cursor: 'pointer',
15
- zIndex: 9999,
16
- boxSizing: 'border-box',
17
- }, onClick: onClick, children: _jsx("div", { style: {
18
- width,
19
- height: size,
20
- paddingLeft: 4,
21
- display: 'flex',
22
- alignItems: 'center',
23
- justifyContent: 'center',
24
- background: 'var(--side-tab-bg, rgba(0,0,0,0.5))',
25
- color: 'white',
26
- borderRadius: `${radius}px 0 0 ${radius}px`,
27
- fontSize: Math.round(size * 0.25),
28
- lineHeight: 1,
29
- userSelect: 'none',
30
- cursor: 'pointer',
31
- filter: 'grayscale(0.5)',
32
- boxSizing: 'border-box',
33
- }, children: _jsx(ChevronLeftSvg, { size: size * 0.7 }) }) }));
34
- };
35
- function ChevronLeftSvg({ size = 18, strokeWidth = 3, ...props }) {
36
- return (_jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", ...props, children: _jsx("path", { fill: "none", d: "M14.5 5.5L8.5 12l6 6.5", stroke: "currentColor", strokeWidth: strokeWidth, strokeLinecap: "round", strokeLinejoin: "round" }) }));
37
- }
@@ -1,2 +0,0 @@
1
- export { renderDebugUi } from './renderDebugUi.js';
2
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- export { renderDebugUi } from './renderDebugUi.js';
@@ -1,2 +0,0 @@
1
- export declare function renderDebugUi(): void;
2
- //# sourceMappingURL=renderDebugUi.d.ts.map
@@ -1,24 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { registerBooleanSetting } from '../settings/index.js';
3
- import { addDebugSecretListener } from '../utils/addDebugSecretListener.js';
4
- export function renderDebugUi() {
5
- const showDebugButtonSetting = registerBooleanSetting('Show Debug Button Overlay');
6
- const render = async (open) => {
7
- const { DebugUi } = await import('./DebugUi.js');
8
- const { createRoot } = await import('react-dom/client');
9
- const rootElement = document.createElement('div');
10
- document.body.appendChild(rootElement);
11
- const root = createRoot(rootElement);
12
- root.render(_jsx(DebugUi, { useDebugButtonState: showDebugButtonSetting.useState, open: open }));
13
- };
14
- if (showDebugButtonSetting.get()) {
15
- render(false);
16
- }
17
- else {
18
- const unsuscribe = addDebugSecretListener(() => {
19
- unsuscribe();
20
- showDebugButtonSetting.set(true);
21
- render(true);
22
- });
23
- }
24
- }
@@ -1,3 +0,0 @@
1
- import type { EditorProps } from './EditorProps.js';
2
- export declare const BooleanEditor: React.FC<EditorProps<boolean>>;
3
- //# sourceMappingURL=BooleanEditor.d.ts.map
@@ -1,6 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { Flex, Switch } from '@radix-ui/themes';
3
- import { SettingsItem } from '../debugUi/SettingsItem.js';
4
- export const BooleanEditor = (props) => {
5
- return (_jsx(SettingsItem, { label: props.label, children: _jsx(Flex, { children: _jsx(Switch, { checked: props.value, onCheckedChange: (checked) => props.onChange(checked) }) }) }));
6
- };
@@ -1,7 +0,0 @@
1
- export type EditorProps<T, K = void> = {
2
- label: string;
3
- value: T;
4
- context: K;
5
- onChange: (value: T) => void;
6
- };
7
- //# sourceMappingURL=EditorProps.d.ts.map
@@ -1 +0,0 @@
1
- export {};
@@ -1,5 +0,0 @@
1
- import type { EditorProps } from './EditorProps.js';
2
- export declare function EnumEditor<T>(props: EditorProps<T, {
3
- values: readonly T[];
4
- }>): import("react/jsx-runtime").JSX.Element;
5
- //# sourceMappingURL=EnumEditor.d.ts.map
@@ -1,16 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Select } from '@radix-ui/themes';
3
- import { SettingsItem } from '../debugUi/SettingsItem.js';
4
- export function EnumEditor(props) {
5
- const stringMode = props.context.values.every((v) => typeof v === 'string');
6
- const stringify = stringMode ? (v) => v : (v) => JSON.stringify(v);
7
- const parse = stringMode ? (v) => v : (v) => JSON.parse(v);
8
- const value = stringify(props.value);
9
- const values = props.context.values.map((v) => ({
10
- value: stringify(v),
11
- label: stringify(v),
12
- })) || [];
13
- return (_jsx(SettingsItem, { label: "Data.js validation", children: _jsxs(Select.Root, { value: value, onValueChange: (value) => {
14
- props.onChange(parse(value));
15
- }, children: [_jsx(Select.Trigger, {}), _jsx(Select.Content, { children: values.map((v) => (_jsx(Select.Item, { value: v.value, children: v.label }, v.value))) })] }) }));
16
- }
@@ -1,2 +0,0 @@
1
- export declare function initDebug(): void;
2
- //# sourceMappingURL=initDebug.d.ts.map
@@ -1,15 +0,0 @@
1
- let debugInitialized = false;
2
- export function initDebug() {
3
- if (debugInitialized) {
4
- return;
5
- }
6
- debugInitialized = true;
7
- // if not browser, exit
8
- if (typeof window === 'undefined' && typeof document === 'undefined') {
9
- return;
10
- }
11
- // add a simple debug button to the page (right, center, fixed position)
12
- import('./debugUi/index.js').then(({ renderDebugUi }) => {
13
- renderDebugUi();
14
- });
15
- }
@@ -1,3 +0,0 @@
1
- import { type DebugSetting } from './registerSetting.js';
2
- export declare function registerBooleanSetting(label: string): DebugSetting<boolean>;
3
- //# sourceMappingURL=registerBooleanSetting.d.ts.map
@@ -1,8 +0,0 @@
1
- import React from 'react';
2
- import { registerSetting } from './registerSetting.js';
3
- export function registerBooleanSetting(label) {
4
- const BooleanEditorLazy = React.lazy(() => import('../editors/BooleanEditor.js').then((mod) => ({
5
- default: mod.BooleanEditor,
6
- })));
7
- return registerSetting(label, false, BooleanEditorLazy);
8
- }
@@ -1,3 +0,0 @@
1
- import { type DebugSetting } from './registerSetting.js';
2
- export declare function registerEnumSetting<T>(label: string, values: readonly T[], defaultValue: T): DebugSetting<T>;
3
- //# sourceMappingURL=registerEnumSetting.d.ts.map
@@ -1,10 +0,0 @@
1
- import React from 'react';
2
- import { registerSetting } from './registerSetting.js';
3
- export function registerEnumSetting(label, values, defaultValue) {
4
- const EnumEditorLazy = React.lazy(() => import('../editors/EnumEditor.js').then((mod) => ({
5
- default: mod.EnumEditor,
6
- })));
7
- return registerSetting(label, defaultValue, EnumEditorLazy, {
8
- values,
9
- });
10
- }
@@ -1,9 +0,0 @@
1
- import type { EditorProps } from '../editors/EditorProps.js';
2
- export interface DebugSetting<T> {
3
- get: () => T;
4
- set: (value: T) => void;
5
- reset: () => void;
6
- useState: () => [T, (value: T) => void];
7
- }
8
- export declare function registerSetting<T, K = void>(label: string, defaultValue: T, Editor: React.FC<EditorProps<T, K>>, context?: K): DebugSetting<T>;
9
- //# sourceMappingURL=registerSetting.d.ts.map
@@ -1,64 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useState } from 'react';
3
- import { editors, resets } from './registeredSettings.js';
4
- const LOCAL_STORAGE_EVENT = 'efp-local-storage';
5
- export function registerSetting(label, defaultValue, Editor, context) {
6
- const key = `efp-debug-setting-"${label}"`;
7
- const get = () => tryJsonParse(localStorage.getItem(key), defaultValue);
8
- const set = (value) => {
9
- localStorage.setItem(key, JSON.stringify(value));
10
- emitLocalStorageChange(key);
11
- };
12
- const reset = () => {
13
- localStorage.removeItem(key);
14
- emitLocalStorageChange(key);
15
- };
16
- resets.push(reset);
17
- function useLocalStorageState() {
18
- const [state, setState] = useState(get());
19
- useEffect(() => {
20
- const update = () => setState(get());
21
- const onStorage = (e) => {
22
- if (e.key === key)
23
- update();
24
- };
25
- const onLocal = (e) => {
26
- const { detail } = e;
27
- if (detail?.key === key)
28
- update();
29
- };
30
- window.addEventListener('storage', onStorage);
31
- window.addEventListener(LOCAL_STORAGE_EVENT, onLocal);
32
- return () => {
33
- window.removeEventListener('storage', onStorage);
34
- window.removeEventListener(LOCAL_STORAGE_EVENT, onLocal);
35
- };
36
- }, []);
37
- const setValueCallback = useCallback(set, []);
38
- return [state, setValueCallback];
39
- }
40
- const EditorImpl = () => {
41
- const [state, setState] = useLocalStorageState();
42
- return _jsx(Editor, { label: label, value: state, context: context, onChange: setState });
43
- };
44
- editors.push(EditorImpl);
45
- return {
46
- get,
47
- set,
48
- reset,
49
- useState: useLocalStorageState,
50
- };
51
- }
52
- function tryJsonParse(str, fallback) {
53
- if (str === null)
54
- return fallback;
55
- try {
56
- return JSON.parse(str);
57
- }
58
- catch {
59
- return fallback;
60
- }
61
- }
62
- function emitLocalStorageChange(key) {
63
- window.dispatchEvent(new CustomEvent(LOCAL_STORAGE_EVENT, { detail: { key } }));
64
- }
@@ -1,3 +0,0 @@
1
- export declare const editors: React.FC[];
2
- export declare const resets: (() => void)[];
3
- //# sourceMappingURL=registeredSettings.d.ts.map
@@ -1,2 +0,0 @@
1
- export const editors = [];
2
- export const resets = [];
@@ -1,2 +0,0 @@
1
- export declare function resetAllSettings(): void;
2
- //# sourceMappingURL=resetAllSettings.d.ts.map
@@ -1,4 +0,0 @@
1
- import { resets } from './registeredSettings.js';
2
- export function resetAllSettings() {
3
- resets.forEach((reset) => reset());
4
- }
@@ -1,2 +0,0 @@
1
- export declare function addDebugSecretListener(callback: () => void): () => void;
2
- //# sourceMappingURL=addDebugSecretListener.d.ts.map
@@ -1,31 +0,0 @@
1
- const SECRET_PHRASE = 'dbg123';
2
- const callbacks = [];
3
- let initialized = false;
4
- export function addDebugSecretListener(callback) {
5
- callbacks.push(callback);
6
- if (!initialized) {
7
- initialized = true;
8
- let buffer = '';
9
- function onKeyDown(e) {
10
- // Ignore modifier keys
11
- if (e.ctrlKey || e.metaKey || e.altKey)
12
- return;
13
- if (e.key.length !== 1)
14
- return;
15
- buffer += e.key.toLowerCase();
16
- // Keep buffer length sane
17
- buffer = buffer.slice(-SECRET_PHRASE.length);
18
- if (buffer === SECRET_PHRASE) {
19
- buffer = '';
20
- callbacks.forEach((cb) => cb());
21
- }
22
- }
23
- document.addEventListener('keydown', onKeyDown);
24
- }
25
- return () => {
26
- const index = callbacks.indexOf(callback);
27
- if (index !== -1) {
28
- callbacks.splice(index, 1);
29
- }
30
- };
31
- }