@boxcustodia/library 2.0.0-alpha.19 → 2.0.0-alpha.20
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/dist/components/button/button.cjs.js +1 -1
- package/dist/components/button/button.es.js +19 -18
- package/dist/components/button/components/base-button.cjs.js +1 -1
- package/dist/components/button/components/base-button.es.js +20 -20
- package/dist/components/calendar/calendar.cjs.js +1 -1
- package/dist/components/calendar/calendar.es.js +1 -0
- package/dist/components/date-picker/date-input.cjs.js +1 -1
- package/dist/components/date-picker/date-input.es.js +92 -75
- package/dist/components/date-picker/date-picker.cjs.js +1 -1
- package/dist/components/date-picker/date-picker.es.js +104 -95
- package/dist/components/date-picker/date-picker.utils.cjs.js +1 -1
- package/dist/components/date-picker/date-picker.utils.es.js +51 -43
- package/dist/components/date-picker/use-hidden-field-value.cjs.js +1 -0
- package/dist/components/date-picker/use-hidden-field-value.es.js +11 -0
- package/dist/components/menu/menu.es.js +1 -9
- package/dist/components/otp/otp.cjs.js +2 -0
- package/dist/components/otp/otp.es.js +93 -0
- package/dist/components/password/password.cjs.js +1 -1
- package/dist/components/password/password.es.js +2 -2
- package/dist/components/select/select.cjs.js +1 -1
- package/dist/components/select/select.es.js +68 -60
- package/dist/hooks/internal/is-apple-device.cjs.js +1 -0
- package/dist/hooks/internal/is-apple-device.es.js +9 -0
- package/dist/hooks/internal/use-latest-ref.cjs.js +1 -0
- package/dist/hooks/internal/use-latest-ref.es.js +11 -0
- package/dist/hooks/use-array/use-array.cjs.js +1 -1
- package/dist/hooks/use-array/use-array.es.js +54 -42
- package/dist/hooks/use-async/use-async.cjs.js +1 -1
- package/dist/hooks/use-async/use-async.es.js +53 -20
- package/dist/hooks/use-boolean/use-boolean.cjs.js +1 -0
- package/dist/hooks/use-boolean/use-boolean.es.js +25 -0
- package/dist/hooks/use-click-outside/use-click-outside.cjs.js +1 -1
- package/dist/hooks/use-click-outside/use-click-outside.es.js +26 -12
- package/dist/hooks/use-debounce-callback/use-debounced-callback.cjs.js +1 -1
- package/dist/hooks/use-debounce-callback/use-debounced-callback.es.js +27 -10
- package/dist/hooks/use-debounce-value/use-debounced-value.cjs.js +1 -1
- package/dist/hooks/use-debounce-value/use-debounced-value.es.js +7 -9
- package/dist/hooks/use-disclosure/use-disclosure.cjs.js +1 -1
- package/dist/hooks/use-disclosure/use-disclosure.es.js +21 -11
- package/dist/hooks/use-document-title/use-document-title.cjs.js +1 -1
- package/dist/hooks/use-document-title/use-document-title.es.js +14 -12
- package/dist/hooks/use-event-listener/use-event-listener.cjs.js +1 -1
- package/dist/hooks/use-event-listener/use-event-listener.es.js +17 -9
- package/dist/hooks/use-hotkey/use-hotkey.cjs.js +1 -1
- package/dist/hooks/use-hotkey/use-hotkey.es.js +30 -14
- package/dist/hooks/use-hotkey/utils/is-input-field.cjs.js +1 -1
- package/dist/hooks/use-hotkey/utils/is-input-field.es.js +4 -2
- package/dist/hooks/use-hotkey/utils/match-and-run.cjs.js +1 -0
- package/dist/hooks/use-hotkey/utils/match-and-run.es.js +12 -0
- package/dist/hooks/use-hotkey/utils/match-key-modifiers.cjs.js +1 -1
- package/dist/hooks/use-hotkey/utils/match-key-modifiers.es.js +13 -12
- package/dist/hooks/use-hover/use-hover.cjs.js +1 -1
- package/dist/hooks/use-hover/use-hover.es.js +32 -17
- package/dist/hooks/use-is-visible/use-is-visible.cjs.js +1 -1
- package/dist/hooks/use-is-visible/use-is-visible.es.js +31 -27
- package/dist/hooks/use-local-storage/use-local-storage.cjs.js +1 -1
- package/dist/hooks/use-local-storage/use-local-storage.es.js +52 -20
- package/dist/hooks/use-media-query/use-media-query.cjs.js +1 -1
- package/dist/hooks/use-media-query/use-media-query.es.js +21 -11
- package/dist/hooks/use-mutation/use-mutation.cjs.js +1 -1
- package/dist/hooks/use-mutation/use-mutation.es.js +36 -22
- package/dist/hooks/use-object/use-object.cjs.js +1 -1
- package/dist/hooks/use-object/use-object.es.js +26 -22
- package/dist/hooks/use-prevent-page-close/use-prevent-page-close.cjs.js +1 -0
- package/dist/hooks/use-prevent-page-close/use-prevent-page-close.es.js +14 -0
- package/dist/hooks/use-step/use-step.cjs.js +1 -1
- package/dist/hooks/use-step/use-step.es.js +25 -24
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +308 -300
- package/dist/src/components/date-picker/date-picker.utils.d.ts +17 -0
- package/dist/src/components/date-picker/use-hidden-field-value.d.ts +12 -0
- package/dist/src/components/index.d.ts +1 -0
- package/dist/src/hooks/index.d.ts +2 -2
- package/dist/src/hooks/internal/index.d.ts +2 -0
- package/dist/src/hooks/internal/is-apple-device.d.ts +12 -0
- package/dist/src/hooks/internal/use-latest-ref.d.ts +12 -0
- package/dist/src/hooks/use-array/use-array.d.ts +24 -11
- package/dist/src/hooks/use-async/use-async.d.ts +16 -13
- package/dist/src/hooks/use-boolean/index.d.ts +1 -0
- package/dist/src/hooks/use-boolean/use-boolean.d.ts +15 -0
- package/dist/src/hooks/use-boolean/use-boolean.test.d.ts +1 -0
- package/dist/src/hooks/use-click-outside/use-click-outside.d.ts +23 -1
- package/dist/src/hooks/use-debounce-callback/use-debounced-callback.d.ts +19 -1
- package/dist/src/hooks/use-debounce-value/use-debounced-value.d.ts +10 -1
- package/dist/src/hooks/use-disclosure/use-disclosure.d.ts +17 -8
- package/dist/src/hooks/use-document-title/use-document-title.d.ts +11 -0
- package/dist/src/hooks/use-event-listener/use-event-listener.d.ts +18 -1
- package/dist/src/hooks/use-hotkey/index.d.ts +2 -1
- package/dist/src/hooks/use-hotkey/use-hotkey.d.ts +62 -5
- package/dist/src/hooks/use-hotkey/utils/index.d.ts +4 -3
- package/dist/src/hooks/use-hotkey/utils/is-input-field.d.ts +12 -2
- package/dist/src/hooks/use-hotkey/utils/is-input-field.test.d.ts +1 -0
- package/dist/src/hooks/use-hotkey/utils/match-and-run.d.ts +36 -0
- package/dist/src/hooks/use-hotkey/utils/match-and-run.test.d.ts +1 -0
- package/dist/src/hooks/use-hotkey/utils/match-key-modifiers.d.ts +20 -6
- package/dist/src/hooks/use-hotkey/utils/match-key-modifiers.test.d.ts +1 -0
- package/dist/src/hooks/use-hover/use-hover.d.ts +8 -4
- package/dist/src/hooks/use-is-visible/use-is-visible.d.ts +28 -4
- package/dist/src/hooks/use-local-storage/use-local-storage.d.ts +13 -2
- package/dist/src/hooks/use-media-query/use-media-query.d.ts +10 -1
- package/dist/src/hooks/use-media-query/use-media-query.test.d.ts +1 -0
- package/dist/src/hooks/use-mutation/use-mutation.d.ts +18 -11
- package/dist/src/hooks/use-object/use-object.d.ts +15 -6
- package/dist/src/hooks/use-prevent-page-close/index.d.ts +1 -0
- package/dist/src/hooks/use-prevent-page-close/use-prevent-page-close.d.ts +10 -0
- package/dist/src/hooks/use-prevent-page-close/use-prevent-page-close.test.d.ts +1 -0
- package/dist/src/hooks/use-step/use-step.d.ts +18 -11
- package/dist/src/utils/form.d.ts +10 -0
- package/package.json +1 -1
- package/src/components/alert-dialog/alert-dialog.test.tsx +13 -9
- package/src/components/auto-complete/auto-complete.test.tsx +4 -14
- package/src/components/avatar/avatar.test.tsx +7 -12
- package/src/components/button/button.test.tsx +10 -15
- package/src/components/button/button.tsx +14 -9
- package/src/components/button/components/base-button.tsx +2 -4
- package/src/components/calendar/calendar.test.tsx +12 -19
- package/src/components/calendar/calendar.tsx +4 -0
- package/src/components/card/card.test.tsx +4 -6
- package/src/components/checkbox/checkbox.test.tsx +12 -8
- package/src/components/checkbox-group/checkbox-group.test.tsx +7 -8
- package/src/components/combobox/combobox.test.tsx +24 -21
- package/src/components/date-picker/date-input-form.test.tsx +77 -0
- package/src/components/date-picker/date-input.stories.tsx +30 -18
- package/src/components/date-picker/date-input.tsx +77 -44
- package/src/components/date-picker/date-picker.stories.tsx +31 -1
- package/src/components/date-picker/date-picker.test.tsx +3 -13
- package/src/components/date-picker/date-picker.tsx +35 -16
- package/src/components/date-picker/date-picker.utils.test.ts +32 -14
- package/src/components/date-picker/date-picker.utils.ts +33 -0
- package/src/components/date-picker/use-date-input-popover.test.ts +3 -1
- package/src/components/date-picker/use-hidden-field-value.ts +23 -0
- package/src/components/dialog/dialog.test.tsx +10 -8
- package/src/components/dropzone/dropzone.test.tsx +11 -13
- package/src/components/empty/empty.test.tsx +4 -3
- package/src/components/field/field.test.tsx +12 -13
- package/src/components/form/form.stories.tsx +16 -1
- package/src/components/index.ts +1 -0
- package/src/components/label/label.test.tsx +3 -3
- package/src/components/menu/menu.tsx +1 -5
- package/src/components/number-input/number-input.test.tsx +6 -2
- package/src/components/password/password.test.tsx +20 -6
- package/src/components/password/password.tsx +2 -2
- package/src/components/popover/popover.test.tsx +4 -4
- package/src/components/progress/progress.test.tsx +7 -8
- package/src/components/radio-group/radio-group.test.tsx +17 -11
- package/src/components/select/select.test.tsx +10 -10
- package/src/components/select/select.tsx +9 -1
- package/src/components/stepper/stepper.stories.tsx +11 -15
- package/src/components/stepper/stepper.test.tsx +6 -4
- package/src/components/switch/switch.test.tsx +3 -3
- package/src/components/table/table.test.tsx +9 -3
- package/src/components/tabs/tabs.test.tsx +6 -2
- package/src/components/tag/tag.test.tsx +1 -3
- package/src/components/textarea/textarea.test.tsx +4 -1
- package/src/components/timeline/timeline.test.tsx +10 -5
- package/src/components/toast/toast.test.tsx +11 -14
- package/src/components/tooltip/tooltip.test.tsx +1 -5
- package/src/components/tree/tree.test.tsx +3 -1
- package/src/hooks/index.ts +2 -2
- package/src/hooks/internal/index.ts +2 -0
- package/src/hooks/internal/is-apple-device.test.ts +41 -0
- package/src/hooks/internal/is-apple-device.ts +33 -0
- package/src/hooks/internal/use-isomorphic-layout-effect.ts +3 -1
- package/src/hooks/internal/use-latest-ref.ts +21 -0
- package/src/hooks/use-array/use-array.stories.tsx +435 -64
- package/src/hooks/use-array/use-array.test.tsx +398 -15
- package/src/hooks/use-array/use-array.ts +105 -66
- package/src/hooks/use-async/use-async.stories.tsx +255 -131
- package/src/hooks/use-async/use-async.test.ts +397 -0
- package/src/hooks/use-async/use-async.ts +117 -39
- package/src/hooks/use-boolean/index.ts +1 -0
- package/src/hooks/use-boolean/use-boolean.stories.tsx +377 -0
- package/src/hooks/use-boolean/use-boolean.test.tsx +177 -0
- package/src/hooks/use-boolean/use-boolean.ts +50 -0
- package/src/hooks/use-click-outside/use-click-outside.stories.tsx +188 -18
- package/src/hooks/use-click-outside/use-click-outside.test.tsx +89 -10
- package/src/hooks/use-click-outside/use-click-outside.ts +62 -16
- package/src/hooks/use-debounce-callback/use-debounced-callback.stories.tsx +141 -41
- package/src/hooks/use-debounce-callback/use-debounced-callback.test.ts +217 -9
- package/src/hooks/use-debounce-callback/use-debounced-callback.ts +71 -11
- package/src/hooks/use-debounce-value/use-debounced-value.stories.tsx +247 -47
- package/src/hooks/use-debounce-value/use-debounced-value.test.ts +105 -10
- package/src/hooks/use-debounce-value/use-debounced-value.ts +19 -10
- package/src/hooks/use-disclosure/use-disclosure.stories.tsx +305 -14
- package/src/hooks/use-disclosure/use-disclosure.test.ts +198 -50
- package/src/hooks/use-disclosure/use-disclosure.ts +49 -29
- package/src/hooks/use-document-title/use-document-title.stories.tsx +54 -0
- package/src/hooks/use-document-title/use-document-title.test.tsx +26 -0
- package/src/hooks/use-document-title/{use-document-title.tsx → use-document-title.ts} +17 -3
- package/src/hooks/use-event-listener/use-event-listener.stories.tsx +105 -9
- package/src/hooks/use-event-listener/use-event-listener.test.tsx +77 -10
- package/src/hooks/use-event-listener/use-event-listener.ts +71 -11
- package/src/hooks/use-focus-trap/use-focus-trap.test.ts +31 -6
- package/src/hooks/use-focus-trap/use-focus-trap.ts +3 -2
- package/src/hooks/use-hotkey/index.ts +9 -1
- package/src/hooks/use-hotkey/use-hotkey.stories.tsx +279 -74
- package/src/hooks/use-hotkey/use-hotkey.test.tsx +286 -34
- package/src/hooks/use-hotkey/use-hotkey.ts +141 -17
- package/src/hooks/use-hotkey/utils/index.ts +8 -3
- package/src/hooks/use-hotkey/utils/is-input-field.test.ts +78 -0
- package/src/hooks/use-hotkey/utils/is-input-field.ts +31 -10
- package/src/hooks/use-hotkey/utils/match-and-run.test.ts +203 -0
- package/src/hooks/use-hotkey/utils/match-and-run.ts +62 -0
- package/src/hooks/use-hotkey/utils/match-key-modifiers.test.ts +65 -0
- package/src/hooks/use-hotkey/utils/match-key-modifiers.ts +39 -12
- package/src/hooks/use-hover/use-hover.stories.tsx +258 -80
- package/src/hooks/use-hover/use-hover.test.tsx +266 -26
- package/src/hooks/use-hover/use-hover.tsx +93 -28
- package/src/hooks/use-is-visible/use-is-visible.stories.tsx +193 -46
- package/src/hooks/use-is-visible/use-is-visible.test.tsx +235 -7
- package/src/hooks/use-is-visible/use-is-visible.ts +114 -0
- package/src/hooks/use-local-storage/use-local-storage.stories.tsx +129 -29
- package/src/hooks/use-local-storage/use-local-storage.test.ts +106 -41
- package/src/hooks/use-local-storage/use-local-storage.ts +100 -31
- package/src/hooks/use-media-query/use-media-query.stories.tsx +86 -26
- package/src/hooks/use-media-query/use-media-query.test.ts +132 -0
- package/src/hooks/use-media-query/use-media-query.ts +39 -14
- package/src/hooks/use-memoized-fn/use-memoized-fn.ts +0 -1
- package/src/hooks/use-mutation/use-mutation.stories.tsx +260 -94
- package/src/hooks/use-mutation/use-mutation.test.ts +359 -0
- package/src/hooks/use-mutation/use-mutation.ts +97 -0
- package/src/hooks/use-object/use-object.stories.tsx +310 -79
- package/src/hooks/use-object/use-object.test.tsx +235 -56
- package/src/hooks/use-object/use-object.ts +59 -0
- package/src/hooks/use-pagination/use-pagination.tsx +0 -1
- package/src/hooks/use-prevent-page-close/index.ts +1 -0
- package/src/hooks/use-prevent-page-close/use-prevent-page-close.stories.tsx +39 -0
- package/src/hooks/use-prevent-page-close/use-prevent-page-close.test.ts +89 -0
- package/src/hooks/use-prevent-page-close/use-prevent-page-close.ts +27 -0
- package/src/hooks/use-range-pagination/use-range-pagination.test.tsx +1 -1
- package/src/hooks/use-range-pagination/use-range-pagination.tsx +1 -1
- package/src/hooks/use-selection/use-selection.ts +0 -1
- package/src/hooks/use-step/use-step.stories.tsx +178 -65
- package/src/hooks/use-step/use-step.test.ts +178 -53
- package/src/hooks/use-step/use-step.ts +57 -49
- package/src/utils/form.test.tsx +13 -8
- package/src/utils/form.tsx +10 -0
- package/src/utils/functions/getFormData.test.ts +1 -1
- package/dist/hooks/use-hotkey/utils/create-hotkey-listener.cjs.js +0 -1
- package/dist/hooks/use-hotkey/utils/create-hotkey-listener.es.js +0 -10
- package/dist/hooks/use-prevent-close-window/use-prevent-close-window.cjs.js +0 -1
- package/dist/hooks/use-prevent-close-window/use-prevent-close-window.es.js +0 -15
- package/dist/hooks/use-toggle/use-toggle.cjs.js +0 -1
- package/dist/hooks/use-toggle/use-toggle.es.js +0 -10
- package/dist/src/hooks/use-hotkey/utils/create-hotkey-listener.d.ts +0 -1
- package/dist/src/hooks/use-prevent-close-window/index.d.ts +0 -1
- package/dist/src/hooks/use-prevent-close-window/use-prevent-close-window.d.ts +0 -13
- package/dist/src/hooks/use-toggle/index.d.ts +0 -1
- package/dist/src/hooks/use-toggle/use-toggle.d.ts +0 -3
- package/src/hooks/use-async/use-async.test.tsx +0 -68
- package/src/hooks/use-hotkey/utils/create-hotkey-listener.ts +0 -25
- package/src/hooks/use-is-visible/use-is-visible.tsx +0 -49
- package/src/hooks/use-mutation/use-mutation.test.tsx +0 -83
- package/src/hooks/use-mutation/use-mutation.tsx +0 -59
- package/src/hooks/use-object/use-object.tsx +0 -46
- package/src/hooks/use-prevent-close-window/index.ts +0 -1
- package/src/hooks/use-prevent-close-window/use-prevent-close-window.stories.tsx +0 -32
- package/src/hooks/use-prevent-close-window/use-prevent-close-window.test.ts +0 -79
- package/src/hooks/use-prevent-close-window/use-prevent-close-window.ts +0 -33
- package/src/hooks/use-toggle/index.ts +0 -1
- package/src/hooks/use-toggle/use-toggle.stories.tsx +0 -25
- package/src/hooks/use-toggle/use-toggle.test.tsx +0 -64
- package/src/hooks/use-toggle/use-toggle.ts +0 -14
- /package/dist/src/{hooks/use-prevent-close-window/use-prevent-close-window.test.d.ts → components/date-picker/date-input-form.test.d.ts} +0 -0
- /package/dist/src/hooks/{use-toggle/use-toggle.test.d.ts → internal/is-apple-device.test.d.ts} +0 -0
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { render, screen, waitFor } from "@testing-library/react";
|
|
2
|
-
import { describe, expect, it, vi } from "vitest";
|
|
1
|
+
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
3
3
|
import { keyboard } from "../../utils/tests/keyboard";
|
|
4
|
-
import {
|
|
4
|
+
import { getHotkeyHandler, type HotkeyOptions, useHotkey } from "../use-hotkey";
|
|
5
|
+
|
|
6
|
+
afterEach(() => {
|
|
7
|
+
vi.unstubAllGlobals();
|
|
8
|
+
});
|
|
5
9
|
|
|
6
10
|
describe("useHotkey hook", () => {
|
|
7
11
|
it("should be defined", () => {
|
|
8
12
|
expect(useHotkey).toBeDefined();
|
|
9
13
|
});
|
|
10
14
|
|
|
11
|
-
it("
|
|
15
|
+
it("calls handler on a matching keydown event", async () => {
|
|
12
16
|
const handler = vi.fn();
|
|
13
17
|
const TestComponent = () => {
|
|
14
18
|
useHotkey("a", handler);
|
|
@@ -16,17 +20,14 @@ describe("useHotkey hook", () => {
|
|
|
16
20
|
};
|
|
17
21
|
|
|
18
22
|
render(<TestComponent />);
|
|
19
|
-
|
|
20
|
-
keyboard(document, {
|
|
21
|
-
key: "a",
|
|
22
|
-
});
|
|
23
|
+
keyboard(document, { key: "a" });
|
|
23
24
|
|
|
24
25
|
await waitFor(() => {
|
|
25
|
-
expect(handler).
|
|
26
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
26
27
|
});
|
|
27
28
|
});
|
|
28
29
|
|
|
29
|
-
it("
|
|
30
|
+
it("matches an exact key combination", async () => {
|
|
30
31
|
const handler = vi.fn();
|
|
31
32
|
const TestComponent = () => {
|
|
32
33
|
useHotkey("ctrl+a", handler);
|
|
@@ -34,18 +35,29 @@ describe("useHotkey hook", () => {
|
|
|
34
35
|
};
|
|
35
36
|
|
|
36
37
|
render(<TestComponent />);
|
|
38
|
+
keyboard(document, { key: "a", ctrlKey: true });
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
ctrlKey: true,
|
|
40
|
+
await waitFor(() => {
|
|
41
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
41
42
|
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("rejects when an extra modifier is pressed", async () => {
|
|
46
|
+
const handler = vi.fn();
|
|
47
|
+
const TestComponent = () => {
|
|
48
|
+
useHotkey("ctrl+a", handler);
|
|
49
|
+
return null;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
render(<TestComponent />);
|
|
53
|
+
keyboard(document, { key: "a", ctrlKey: true, shiftKey: true });
|
|
42
54
|
|
|
43
55
|
await waitFor(() => {
|
|
44
|
-
expect(handler).toHaveBeenCalled();
|
|
56
|
+
expect(handler).not.toHaveBeenCalled();
|
|
45
57
|
});
|
|
46
58
|
});
|
|
47
59
|
|
|
48
|
-
it("
|
|
60
|
+
it("calls preventDefault by default", async () => {
|
|
49
61
|
const handler = vi.fn();
|
|
50
62
|
const TestComponent = () => {
|
|
51
63
|
useHotkey("b", handler);
|
|
@@ -53,7 +65,6 @@ describe("useHotkey hook", () => {
|
|
|
53
65
|
};
|
|
54
66
|
|
|
55
67
|
render(<TestComponent />);
|
|
56
|
-
|
|
57
68
|
const event = new KeyboardEvent("keydown", { key: "b" });
|
|
58
69
|
Object.defineProperty(event, "preventDefault", { value: vi.fn() });
|
|
59
70
|
document.dispatchEvent(event);
|
|
@@ -63,7 +74,7 @@ describe("useHotkey hook", () => {
|
|
|
63
74
|
});
|
|
64
75
|
});
|
|
65
76
|
|
|
66
|
-
it("
|
|
77
|
+
it("does not call preventDefault when preventDefault is false", async () => {
|
|
67
78
|
const handler = vi.fn();
|
|
68
79
|
const options: HotkeyOptions = { preventDefault: false };
|
|
69
80
|
const TestComponent = () => {
|
|
@@ -81,7 +92,7 @@ describe("useHotkey hook", () => {
|
|
|
81
92
|
});
|
|
82
93
|
});
|
|
83
94
|
|
|
84
|
-
it("
|
|
95
|
+
it("shares one handler across an array of keys", async () => {
|
|
85
96
|
const handler = vi.fn();
|
|
86
97
|
const TestComponent = () => {
|
|
87
98
|
useHotkey(["d", "e"], handler);
|
|
@@ -89,48 +100,289 @@ describe("useHotkey hook", () => {
|
|
|
89
100
|
};
|
|
90
101
|
|
|
91
102
|
render(<TestComponent />);
|
|
92
|
-
|
|
93
|
-
keyboard(document, {
|
|
94
|
-
key: "d",
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
keyboard(document, {
|
|
98
|
-
key: "e",
|
|
99
|
-
});
|
|
103
|
+
keyboard(document, { key: "d" });
|
|
104
|
+
keyboard(document, { key: "e" });
|
|
100
105
|
|
|
101
106
|
await waitFor(() => {
|
|
102
107
|
expect(handler).toHaveBeenCalledTimes(2);
|
|
103
108
|
});
|
|
104
109
|
});
|
|
105
110
|
|
|
106
|
-
it("does not call handler when watch
|
|
111
|
+
it("does not call handler when watch is false", async () => {
|
|
107
112
|
const handler = vi.fn();
|
|
108
113
|
const TestComponent = () => {
|
|
109
114
|
useHotkey("f", handler, { watch: false });
|
|
110
115
|
return null;
|
|
111
116
|
};
|
|
117
|
+
|
|
112
118
|
render(<TestComponent />);
|
|
113
119
|
keyboard(document, { key: "f" });
|
|
120
|
+
|
|
114
121
|
await waitFor(() => {
|
|
115
122
|
expect(handler).not.toHaveBeenCalled();
|
|
116
123
|
});
|
|
117
124
|
});
|
|
118
125
|
|
|
119
|
-
it("
|
|
126
|
+
it("listens on keyup when eventName is 'keyup'", async () => {
|
|
120
127
|
const handler = vi.fn();
|
|
121
128
|
const TestComponent = () => {
|
|
122
|
-
useHotkey("
|
|
123
|
-
return
|
|
129
|
+
useHotkey("g", handler, { eventName: "keyup" });
|
|
130
|
+
return null;
|
|
124
131
|
};
|
|
132
|
+
|
|
125
133
|
render(<TestComponent />);
|
|
126
134
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
135
|
+
keyboard(document, { key: "g" }, "keydown");
|
|
136
|
+
await waitFor(() => {
|
|
137
|
+
expect(handler).not.toHaveBeenCalled();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
keyboard(document, { key: "g" }, "keyup");
|
|
141
|
+
await waitFor(() => {
|
|
142
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe("array-of-tuples form", () => {
|
|
147
|
+
it("dispatches each binding independently", async () => {
|
|
148
|
+
const handlerA = vi.fn();
|
|
149
|
+
const handlerB = vi.fn();
|
|
150
|
+
const TestComponent = () => {
|
|
151
|
+
useHotkey([
|
|
152
|
+
["a", handlerA],
|
|
153
|
+
["b", handlerB],
|
|
154
|
+
]);
|
|
155
|
+
return null;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
render(<TestComponent />);
|
|
159
|
+
keyboard(document, { key: "b" });
|
|
160
|
+
|
|
161
|
+
await waitFor(() => {
|
|
162
|
+
expect(handlerB).toHaveBeenCalledTimes(1);
|
|
163
|
+
expect(handlerA).not.toHaveBeenCalled();
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("honors per-binding preventDefault override", async () => {
|
|
168
|
+
const handler = vi.fn();
|
|
169
|
+
const TestComponent = () => {
|
|
170
|
+
useHotkey([["a", handler, { preventDefault: false }]]);
|
|
171
|
+
return null;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
render(<TestComponent />);
|
|
175
|
+
const event = new KeyboardEvent("keydown", { key: "a" });
|
|
176
|
+
Object.defineProperty(event, "preventDefault", { value: vi.fn() });
|
|
177
|
+
document.dispatchEvent(event);
|
|
178
|
+
|
|
179
|
+
await waitFor(() => {
|
|
180
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
181
|
+
expect(event.preventDefault).not.toHaveBeenCalled();
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe("platform-aware mod", () => {
|
|
187
|
+
it("matches meta (⌘) and rejects ctrl on Apple", async () => {
|
|
188
|
+
vi.stubGlobal("navigator", { platform: "MacIntel" });
|
|
189
|
+
const handler = vi.fn();
|
|
190
|
+
const TestComponent = () => {
|
|
191
|
+
useHotkey("mod+k", handler);
|
|
192
|
+
return null;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
render(<TestComponent />);
|
|
196
|
+
|
|
197
|
+
keyboard(document, { key: "k", ctrlKey: true });
|
|
198
|
+
await waitFor(() => {
|
|
199
|
+
expect(handler).not.toHaveBeenCalled();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
keyboard(document, { key: "k", metaKey: true });
|
|
203
|
+
await waitFor(() => {
|
|
204
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("matches ctrl and rejects meta on non-Apple", async () => {
|
|
209
|
+
vi.stubGlobal("navigator", { platform: "Win32" });
|
|
210
|
+
const handler = vi.fn();
|
|
211
|
+
const TestComponent = () => {
|
|
212
|
+
useHotkey("mod+k", handler);
|
|
213
|
+
return null;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
render(<TestComponent />);
|
|
217
|
+
|
|
218
|
+
keyboard(document, { key: "k", metaKey: true });
|
|
219
|
+
await waitFor(() => {
|
|
220
|
+
expect(handler).not.toHaveBeenCalled();
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
keyboard(document, { key: "k", ctrlKey: true });
|
|
224
|
+
await waitFor(() => {
|
|
225
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
describe("tag and contentEditable suppression", () => {
|
|
231
|
+
it("does not fire when the event originates from an input field", async () => {
|
|
232
|
+
const handler = vi.fn();
|
|
233
|
+
const TestComponent = () => {
|
|
234
|
+
useHotkey("z", handler);
|
|
235
|
+
return <input data-testid="input" />;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
render(<TestComponent />);
|
|
239
|
+
const input = screen.getByTestId("input");
|
|
240
|
+
input.dispatchEvent(
|
|
241
|
+
new KeyboardEvent("keydown", { key: "z", bubbles: true }),
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
await waitFor(() => {
|
|
245
|
+
expect(handler).not.toHaveBeenCalled();
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("fires from an input when tagsToIgnore is empty", async () => {
|
|
250
|
+
const handler = vi.fn();
|
|
251
|
+
const TestComponent = () => {
|
|
252
|
+
useHotkey("z", handler, { tagsToIgnore: [] });
|
|
253
|
+
return <input data-testid="input" />;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
render(<TestComponent />);
|
|
257
|
+
const input = screen.getByTestId("input");
|
|
258
|
+
input.dispatchEvent(
|
|
259
|
+
new KeyboardEvent("keydown", { key: "z", bubbles: true }),
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
await waitFor(() => {
|
|
263
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("fires from a contentEditable target when triggerOnContentEditable is true", async () => {
|
|
268
|
+
const handler = vi.fn();
|
|
269
|
+
const TestComponent = () => {
|
|
270
|
+
useHotkey("z", handler, { triggerOnContentEditable: true });
|
|
271
|
+
return (
|
|
272
|
+
<div
|
|
273
|
+
contentEditable
|
|
274
|
+
data-testid="editable"
|
|
275
|
+
suppressContentEditableWarning
|
|
276
|
+
/>
|
|
277
|
+
);
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
render(<TestComponent />);
|
|
281
|
+
const editable = screen.getByTestId("editable");
|
|
282
|
+
editable.dispatchEvent(
|
|
283
|
+
new KeyboardEvent("keydown", { key: "z", bubbles: true }),
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
await waitFor(() => {
|
|
287
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
describe("lifecycle", () => {
|
|
293
|
+
it("removes the document listener on unmount", async () => {
|
|
294
|
+
const handler = vi.fn();
|
|
295
|
+
const TestComponent = () => {
|
|
296
|
+
useHotkey("u", handler);
|
|
297
|
+
return null;
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const { unmount } = render(<TestComponent />);
|
|
301
|
+
unmount();
|
|
302
|
+
keyboard(document, { key: "u" });
|
|
303
|
+
|
|
304
|
+
await waitFor(() => {
|
|
305
|
+
expect(handler).not.toHaveBeenCalled();
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it("invokes the latest handler after a re-render without re-binding", async () => {
|
|
310
|
+
const first = vi.fn();
|
|
311
|
+
const second = vi.fn();
|
|
312
|
+
const TestComponent = ({ handler }: { handler: () => void }) => {
|
|
313
|
+
useHotkey("r", handler);
|
|
314
|
+
return null;
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const { rerender } = render(<TestComponent handler={first} />);
|
|
318
|
+
rerender(<TestComponent handler={second} />);
|
|
319
|
+
|
|
320
|
+
keyboard(document, { key: "r" });
|
|
321
|
+
|
|
322
|
+
await waitFor(() => {
|
|
323
|
+
expect(second).toHaveBeenCalledTimes(1);
|
|
324
|
+
expect(first).not.toHaveBeenCalled();
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
describe("getHotkeyHandler", () => {
|
|
331
|
+
it("returns a handler that fires on a matching key", async () => {
|
|
332
|
+
const handler = vi.fn();
|
|
333
|
+
const TestComponent = () => (
|
|
334
|
+
<input
|
|
335
|
+
data-testid="scoped"
|
|
336
|
+
onKeyDown={getHotkeyHandler([["a", handler]])}
|
|
337
|
+
/>
|
|
130
338
|
);
|
|
131
339
|
|
|
340
|
+
render(<TestComponent />);
|
|
341
|
+
fireEvent.keyDown(screen.getByTestId("scoped"), { key: "a" });
|
|
342
|
+
|
|
132
343
|
await waitFor(() => {
|
|
133
|
-
expect(handler).
|
|
344
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it("shares mod behavior (Ctrl+K on non-Apple)", async () => {
|
|
349
|
+
vi.stubGlobal("navigator", { platform: "Win32" });
|
|
350
|
+
const handler = vi.fn();
|
|
351
|
+
const TestComponent = () => (
|
|
352
|
+
<input
|
|
353
|
+
data-testid="scoped"
|
|
354
|
+
onKeyDown={getHotkeyHandler([["mod+k", handler]])}
|
|
355
|
+
/>
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
render(<TestComponent />);
|
|
359
|
+
fireEvent.keyDown(screen.getByTestId("scoped"), {
|
|
360
|
+
key: "k",
|
|
361
|
+
ctrlKey: true,
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
await waitFor(() => {
|
|
365
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it("does NOT suppress the input tag it is attached to", async () => {
|
|
370
|
+
const handler = vi.fn();
|
|
371
|
+
const TestComponent = () => (
|
|
372
|
+
<input
|
|
373
|
+
data-testid="scoped"
|
|
374
|
+
onKeyDown={getHotkeyHandler([["mod+enter", handler]])}
|
|
375
|
+
/>
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
render(<TestComponent />);
|
|
379
|
+
fireEvent.keyDown(screen.getByTestId("scoped"), {
|
|
380
|
+
key: "Enter",
|
|
381
|
+
ctrlKey: true,
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
await waitFor(() => {
|
|
385
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
134
386
|
});
|
|
135
387
|
});
|
|
136
388
|
});
|
|
@@ -1,33 +1,157 @@
|
|
|
1
|
-
import { useCallback } from "react";
|
|
2
1
|
import { useEventListener } from "../use-event-listener";
|
|
3
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type HotkeyItem,
|
|
4
|
+
type HotkeyItemOptions,
|
|
5
|
+
type KeyboardEventLike,
|
|
6
|
+
matchAndRun,
|
|
7
|
+
} from "./utils";
|
|
4
8
|
|
|
9
|
+
export type { HotkeyItem, HotkeyItemOptions, KeyboardEventLike };
|
|
10
|
+
|
|
11
|
+
/** A single key string or a list of key strings sharing one handler. */
|
|
5
12
|
export type Keys = string | string[];
|
|
6
|
-
|
|
7
|
-
|
|
13
|
+
|
|
14
|
+
/** Supported listener event names. `"keypress"` is removed in v2. */
|
|
15
|
+
export type HotkeyEventName = "keydown" | "keyup";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Options for the single-binding `useHotkey(keys, handler, options)` form.
|
|
19
|
+
* Combines hook-level concerns (`watch`, `eventName`, `tagsToIgnore`) with the
|
|
20
|
+
* per-binding behavior (`preventDefault`, `triggerOnContentEditable`).
|
|
21
|
+
*/
|
|
22
|
+
export type HotkeyOptions = HotkeyItemOptions & {
|
|
23
|
+
/** Listen while `true` (default). When `false`, no handler fires. */
|
|
8
24
|
watch?: boolean;
|
|
25
|
+
/** DOM event to listen for. Default `"keydown"`. */
|
|
9
26
|
eventName?: HotkeyEventName;
|
|
10
|
-
|
|
11
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Tag names whose events are ignored. Default
|
|
29
|
+
* `["INPUT", "TEXTAREA", "SELECT"]`. Pass `[]` to ignore nothing.
|
|
30
|
+
*/
|
|
31
|
+
tagsToIgnore?: string[];
|
|
12
32
|
};
|
|
13
33
|
|
|
34
|
+
const DEFAULT_TAGS_TO_IGNORE = ["INPUT", "TEXTAREA", "SELECT"];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Bind one or more keyboard shortcuts at the `document` level.
|
|
38
|
+
*
|
|
39
|
+
* Two forms are supported:
|
|
40
|
+
*
|
|
41
|
+
* 1. Single binding — `useHotkey(keys, handler, options?)` where `keys` is a
|
|
42
|
+
* string or string array sharing one handler.
|
|
43
|
+
* 2. Multiple bindings — `useHotkey(bindings)` where `bindings` is a list of
|
|
44
|
+
* `[key, handler, options?]` tuples, each dispatched independently.
|
|
45
|
+
*
|
|
46
|
+
* Matching is strict: every required modifier (`shift`/`ctrl`/`alt`/`meta`)
|
|
47
|
+
* must be pressed and no other modifier may be. `mod` resolves to `meta` (⌘)
|
|
48
|
+
* on Apple platforms and `ctrl` elsewhere. Events originating in `tagsToIgnore`
|
|
49
|
+
* tags (or contentEditable regions) are suppressed unless opted out.
|
|
50
|
+
*
|
|
51
|
+
* The handler is read through a ref, so passing an inline function never
|
|
52
|
+
* re-attaches the underlying listener. SSR-safe: no DOM access on the server.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* useHotkey("mod+k", () => openPalette());
|
|
56
|
+
* useHotkey([["a", onA], ["b", onB, { preventDefault: false }]]);
|
|
57
|
+
*/
|
|
14
58
|
export function useHotkey(
|
|
15
|
-
keys:
|
|
16
|
-
handler: (event:
|
|
17
|
-
options
|
|
18
|
-
)
|
|
59
|
+
keys: Keys,
|
|
60
|
+
handler: (event: KeyboardEventLike) => void,
|
|
61
|
+
options?: HotkeyOptions,
|
|
62
|
+
): void;
|
|
63
|
+
export function useHotkey(
|
|
64
|
+
bindings: HotkeyItem[],
|
|
65
|
+
options?: HotkeyOptions,
|
|
66
|
+
): void;
|
|
67
|
+
export function useHotkey(
|
|
68
|
+
keysOrBindings: Keys | HotkeyItem[],
|
|
69
|
+
handlerOrOptions?: ((event: KeyboardEventLike) => void) | HotkeyOptions,
|
|
70
|
+
maybeOptions?: HotkeyOptions,
|
|
71
|
+
): void {
|
|
72
|
+
const isArrayForm =
|
|
73
|
+
Array.isArray(keysOrBindings) && typeof handlerOrOptions !== "function";
|
|
74
|
+
|
|
75
|
+
const options: HotkeyOptions = isArrayForm
|
|
76
|
+
? ((handlerOrOptions as HotkeyOptions | undefined) ?? {})
|
|
77
|
+
: (maybeOptions ?? {});
|
|
78
|
+
|
|
19
79
|
const {
|
|
20
80
|
eventName = "keydown",
|
|
21
|
-
preventDefault = true,
|
|
22
|
-
ignoreInputFields = true,
|
|
23
81
|
watch = true,
|
|
82
|
+
preventDefault = true,
|
|
83
|
+
triggerOnContentEditable = false,
|
|
84
|
+
tagsToIgnore = DEFAULT_TAGS_TO_IGNORE,
|
|
24
85
|
} = options;
|
|
25
86
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
87
|
+
const items: HotkeyItem[] = isArrayForm
|
|
88
|
+
? (keysOrBindings as HotkeyItem[])
|
|
89
|
+
: normalizeSingle(
|
|
90
|
+
keysOrBindings as Keys,
|
|
91
|
+
handlerOrOptions as (event: KeyboardEventLike) => void,
|
|
92
|
+
{ preventDefault, triggerOnContentEditable },
|
|
93
|
+
);
|
|
30
94
|
|
|
31
|
-
|
|
95
|
+
const listener = (event: KeyboardEvent) => {
|
|
96
|
+
if (!watch) return;
|
|
97
|
+
matchAndRun(event, items, tagsToIgnore);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
useEventListener(eventName, listener, document);
|
|
32
101
|
}
|
|
33
102
|
|
|
103
|
+
const normalizeSingle = (
|
|
104
|
+
keys: Keys,
|
|
105
|
+
handler: (event: KeyboardEventLike) => void,
|
|
106
|
+
perItemOptions: HotkeyItemOptions,
|
|
107
|
+
): HotkeyItem[] => {
|
|
108
|
+
const keyList = Array.isArray(keys) ? keys : [keys];
|
|
109
|
+
return keyList.map((key) => [key, handler, perItemOptions]);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Build an element-scoped `onKeyDown` handler. Shares the same
|
|
114
|
+
* strict-modifier and `mod` behavior as {@link useHotkey}.
|
|
115
|
+
*
|
|
116
|
+
* Two forms are supported:
|
|
117
|
+
*
|
|
118
|
+
* 1. Single binding — `getHotkeyHandler(keys, handler, options?)`.
|
|
119
|
+
* 2. Multiple bindings — `getHotkeyHandler(bindings)` where `bindings` is a
|
|
120
|
+
* list of `[key, handler, options?]` tuples.
|
|
121
|
+
*
|
|
122
|
+
* Because it is attached to an element's `onKeyDown`, it applies NO tag
|
|
123
|
+
* suppression — it has no `tagsToIgnore` and matches regardless of the target
|
|
124
|
+
* tag (including the `<input>` it is attached to).
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* <input onKeyDown={getHotkeyHandler("mod+enter", submit)} />
|
|
128
|
+
* <input onKeyDown={getHotkeyHandler([["mod+enter", submit], ["escape", cancel]])} />
|
|
129
|
+
*/
|
|
130
|
+
export function getHotkeyHandler(
|
|
131
|
+
keys: Keys,
|
|
132
|
+
handler: (event: KeyboardEventLike) => void,
|
|
133
|
+
options?: HotkeyItemOptions,
|
|
134
|
+
): (event: KeyboardEventLike) => void;
|
|
135
|
+
export function getHotkeyHandler(
|
|
136
|
+
bindings: HotkeyItem[],
|
|
137
|
+
): (event: KeyboardEventLike) => void;
|
|
138
|
+
export function getHotkeyHandler(
|
|
139
|
+
keysOrBindings: Keys | HotkeyItem[],
|
|
140
|
+
handler?: (event: KeyboardEventLike) => void,
|
|
141
|
+
options?: HotkeyItemOptions,
|
|
142
|
+
): (event: KeyboardEventLike) => void {
|
|
143
|
+
const isArrayForm =
|
|
144
|
+
Array.isArray(keysOrBindings) && typeof handler !== "function";
|
|
145
|
+
|
|
146
|
+
const bindings: HotkeyItem[] = isArrayForm
|
|
147
|
+
? (keysOrBindings as HotkeyItem[])
|
|
148
|
+
: normalizeSingle(
|
|
149
|
+
keysOrBindings as Keys,
|
|
150
|
+
handler as (event: KeyboardEventLike) => void,
|
|
151
|
+
options ?? {},
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
return (event: KeyboardEventLike) => {
|
|
155
|
+
matchAndRun(event, bindings, []);
|
|
156
|
+
};
|
|
157
|
+
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
|
|
1
|
+
export { shouldIgnoreEvent } from "./is-input-field";
|
|
2
|
+
export type {
|
|
3
|
+
HotkeyItem,
|
|
4
|
+
HotkeyItemOptions,
|
|
5
|
+
KeyboardEventLike,
|
|
6
|
+
} from "./match-and-run";
|
|
7
|
+
export { matchAndRun } from "./match-and-run";
|
|
8
|
+
export { getKeyModifiers, matchKeyModifiers } from "./match-key-modifiers";
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { shouldIgnoreEvent } from "./is-input-field";
|
|
3
|
+
import type { KeyboardEventLike } from "./match-and-run";
|
|
4
|
+
|
|
5
|
+
const DEFAULT_IGNORE = ["INPUT", "TEXTAREA", "SELECT"];
|
|
6
|
+
|
|
7
|
+
const makeEvent = (target: EventTarget | null): KeyboardEventLike => ({
|
|
8
|
+
key: "k",
|
|
9
|
+
shiftKey: false,
|
|
10
|
+
ctrlKey: false,
|
|
11
|
+
altKey: false,
|
|
12
|
+
metaKey: false,
|
|
13
|
+
target,
|
|
14
|
+
preventDefault: vi.fn(),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const makeTarget = (props: {
|
|
18
|
+
tagName?: string;
|
|
19
|
+
isContentEditable?: boolean;
|
|
20
|
+
}): EventTarget =>
|
|
21
|
+
({
|
|
22
|
+
tagName: props.tagName,
|
|
23
|
+
isContentEditable: props.isContentEditable ?? false,
|
|
24
|
+
}) as unknown as EventTarget;
|
|
25
|
+
|
|
26
|
+
describe("shouldIgnoreEvent", () => {
|
|
27
|
+
it("ignores targets whose tag is in the list", () => {
|
|
28
|
+
const event = makeEvent(makeTarget({ tagName: "INPUT" }));
|
|
29
|
+
expect(shouldIgnoreEvent(event, DEFAULT_IGNORE, false)).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("ignores SELECT and TEXTAREA by default", () => {
|
|
33
|
+
expect(
|
|
34
|
+
shouldIgnoreEvent(
|
|
35
|
+
makeEvent(makeTarget({ tagName: "SELECT" })),
|
|
36
|
+
DEFAULT_IGNORE,
|
|
37
|
+
false,
|
|
38
|
+
),
|
|
39
|
+
).toBe(true);
|
|
40
|
+
expect(
|
|
41
|
+
shouldIgnoreEvent(
|
|
42
|
+
makeEvent(makeTarget({ tagName: "TEXTAREA" })),
|
|
43
|
+
DEFAULT_IGNORE,
|
|
44
|
+
false,
|
|
45
|
+
),
|
|
46
|
+
).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("does not ignore tags outside the list", () => {
|
|
50
|
+
const event = makeEvent(makeTarget({ tagName: "BUTTON" }));
|
|
51
|
+
expect(shouldIgnoreEvent(event, DEFAULT_IGNORE, false)).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("ignores contentEditable when triggerOnContentEditable is false", () => {
|
|
55
|
+
const event = makeEvent(
|
|
56
|
+
makeTarget({ tagName: "DIV", isContentEditable: true }),
|
|
57
|
+
);
|
|
58
|
+
expect(shouldIgnoreEvent(event, DEFAULT_IGNORE, false)).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("does not ignore contentEditable when triggerOnContentEditable is true", () => {
|
|
62
|
+
const event = makeEvent(
|
|
63
|
+
makeTarget({ tagName: "DIV", isContentEditable: true }),
|
|
64
|
+
);
|
|
65
|
+
expect(shouldIgnoreEvent(event, DEFAULT_IGNORE, true)).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("ignores nothing when the list is empty and not contentEditable", () => {
|
|
69
|
+
const event = makeEvent(makeTarget({ tagName: "INPUT" }));
|
|
70
|
+
expect(shouldIgnoreEvent(event, [], false)).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("returns false when there is no target", () => {
|
|
74
|
+
expect(shouldIgnoreEvent(makeEvent(null), DEFAULT_IGNORE, false)).toBe(
|
|
75
|
+
false,
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
});
|