@supermousejs/core 2.3.0 → 2.4.1

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.
@@ -0,0 +1,235 @@
1
+ import { describe, it, expect, afterEach, beforeEach, vi } from "vitest";
2
+ import { Stage } from "../Supermouse";
3
+
4
+ describe("Supermouse Stage", () => {
5
+ let container: HTMLElement;
6
+ let stage: Stage;
7
+
8
+ beforeEach(() => {
9
+ container = document.createElement("div");
10
+ document.body.appendChild(container);
11
+ });
12
+
13
+ afterEach(() => {
14
+ stage?.destroy();
15
+ document.body.innerHTML = "";
16
+ document.head.innerHTML = "";
17
+ vi.restoreAllMocks();
18
+ });
19
+
20
+ const getStyleTag = () =>
21
+ document.querySelector('style[id^="supermouse-style-"]') as HTMLStyleElement | null;
22
+
23
+ const hasClassPrefix = (el: HTMLElement, prefix: string) =>
24
+ Array.from(el.classList).some((c) => c.startsWith(prefix));
25
+
26
+ describe("Element creation & positioning", () => {
27
+ it("creates stage element with default styles", () => {
28
+ stage = new Stage(container, 9999);
29
+ const el = stage.element;
30
+ expect(el).toBeInstanceOf(HTMLDivElement);
31
+ expect(el.style.position).toBe("absolute");
32
+ expect(el.style.inset).toBe("0px");
33
+ expect(el.style.pointerEvents).toBe("none");
34
+ expect(el.style.zIndex).toBe("9999");
35
+ expect(el.style.opacity).toBe("1");
36
+ expect(el.style.transition).toBe("opacity 0.15s ease");
37
+ expect(container.contains(el)).toBe(true);
38
+ });
39
+
40
+ it("uses fixed positioning when container is body", () => {
41
+ stage = new Stage(document.body, 9999);
42
+ expect(stage.element.style.position).toBe("fixed");
43
+ });
44
+
45
+ it("throws if container is not an HTMLElement", () => {
46
+ expect(() => new Stage(null as any, 9999)).toThrowError(
47
+ "[Supermouse] Invalid container: null. Must be an HTMLElement."
48
+ );
49
+ });
50
+
51
+ it("warns if container is not connected", () => {
52
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
53
+ const detached = document.createElement("div");
54
+ new Stage(detached, 9999).destroy();
55
+ expect(warn).toHaveBeenCalledWith(
56
+ expect.stringContaining("container is not attached to the document")
57
+ );
58
+ });
59
+ });
60
+
61
+ describe("Stylesheet & selectors", () => {
62
+ it("injects style tag with cursor suppression rules on creation", () => {
63
+ stage = new Stage(container, 9999);
64
+ const styleTag = getStyleTag();
65
+ expect(styleTag).toBeInstanceOf(HTMLStyleElement);
66
+ expect(styleTag!.parentNode).toBe(document.head);
67
+ expect(styleTag!.innerText).toContain("cursor: none !important");
68
+ });
69
+
70
+ it("adds supermouse scope classes to container", () => {
71
+ stage = new Stage(container, 9999);
72
+ expect(container.classList.contains("supermouse-scope")).toBe(true);
73
+ expect(hasClassPrefix(container, "supermouse-scope-")).toBe(true);
74
+ });
75
+
76
+ it("addSelector updates the stylesheet", () => {
77
+ stage = new Stage(container, 9999);
78
+ const styleTag = getStyleTag()!;
79
+ const before = styleTag.innerText;
80
+
81
+ stage.addSelector(".my-selector");
82
+ const after = styleTag.innerText;
83
+
84
+ expect(before).not.toBe(after);
85
+ expect(after).toContain(".my-selector");
86
+ });
87
+
88
+ it("addSelectors batch updates stylesheet only once", () => {
89
+ stage = new Stage(container, 9999);
90
+ const styleTag = getStyleTag()!;
91
+ const originalInnerText = styleTag.innerText;
92
+
93
+ stage.addSelectors([".a", ".b", ".c"]);
94
+ const finalInnerText = styleTag.innerText;
95
+
96
+ expect(originalInnerText).not.toBe(finalInnerText);
97
+ expect(finalInnerText).toContain(".a");
98
+ expect(finalInnerText).toContain(".b");
99
+ expect(finalInnerText).toContain(".c");
100
+ });
101
+
102
+ it("scopes all hover selectors correctly, splitting comma groups", () => {
103
+ const container = document.createElement("div");
104
+ document.body.appendChild(container);
105
+ const stage = new Stage(container, 9999);
106
+
107
+ stage.addSelector("p, span, h1, h2");
108
+
109
+ const styleTag = document.querySelector("style[id^='supermouse-style-']")!;
110
+ const styleText = styleTag.innerText;
111
+
112
+ const scopeClass = Array.from(container.classList).find((c) =>
113
+ c.startsWith("supermouse-scope-")
114
+ );
115
+ const hideClass = `supermouse-hide-${scopeClass!.split("-").pop()}`;
116
+
117
+ expect(styleText).toContain(`.${scopeClass}.${hideClass} p`);
118
+ expect(styleText).toContain(`.${scopeClass}.${hideClass} span`);
119
+ expect(styleText).toContain(`.${scopeClass}.${hideClass} h1`);
120
+ expect(styleText).toContain(`.${scopeClass}.${hideClass} h2`);
121
+
122
+ expect(styleText).not.toMatch(/(^|,)\s*span\s*{/);
123
+ expect(styleText).not.toMatch(/(^|,)\s*h1\s*{/);
124
+
125
+ stage.destroy();
126
+ container.remove();
127
+ });
128
+ });
129
+
130
+ describe("Native cursor suppression", () => {
131
+ it("setNativeCursor toggles hide class and inline cursor style", () => {
132
+ stage = new Stage(container, 9999);
133
+ stage.setNativeCursor("none");
134
+ expect(container.style.cursor).toBe("none");
135
+ expect(hasClassPrefix(container, "supermouse-hide-")).toBe(true);
136
+
137
+ stage.setNativeCursor("auto");
138
+ expect(container.style.cursor).toBe("");
139
+ expect(hasClassPrefix(container, "supermouse-hide-")).toBe(false);
140
+ });
141
+
142
+ it("restores original container cursor when showing native", () => {
143
+ container.style.cursor = "pointer";
144
+ stage = new Stage(container, 9999);
145
+ stage.setNativeCursor("none");
146
+ expect(container.style.cursor).toBe("none"); // suppressed
147
+ expect(hasClassPrefix(container, "supermouse-hide-")).toBe(true);
148
+
149
+ stage.setNativeCursor("auto");
150
+ expect(container.style.cursor).toBe("pointer"); // restored
151
+ expect(hasClassPrefix(container, "supermouse-hide-")).toBe(false);
152
+ });
153
+
154
+ it("keeps original container cursor even if empty", () => {
155
+ container.style.cursor = "";
156
+ stage = new Stage(container, 9999);
157
+ stage.setNativeCursor("auto");
158
+ expect(container.style.cursor).toBe("");
159
+ });
160
+ });
161
+
162
+ describe("Visibility & layout", () => {
163
+ it("setVisibility toggles opacity", () => {
164
+ stage = new Stage(container, 9999);
165
+ stage.setVisibility(false);
166
+ expect(stage.element.style.opacity).toBe("0");
167
+ stage.setVisibility(true);
168
+ expect(stage.element.style.opacity).toBe("1");
169
+ });
170
+
171
+ it("sets container position to relative if static", () => {
172
+ container.style.position = "static";
173
+ stage = new Stage(container, 9999);
174
+ expect(container.style.position).toBe("relative");
175
+ stage.destroy();
176
+ expect(container.style.position).toBe("");
177
+ });
178
+
179
+ it("does not modify container position if already non-static", () => {
180
+ container.style.position = "absolute";
181
+ stage = new Stage(container, 9999);
182
+ expect(container.style.position).toBe("absolute");
183
+ stage.destroy();
184
+ expect(container.style.position).toBe("absolute");
185
+ });
186
+
187
+ it("does not modify position if container is body", () => {
188
+ stage = new Stage(document.body, 9999);
189
+ expect(document.body.style.position).toBe("");
190
+ stage.destroy();
191
+ });
192
+ });
193
+
194
+ describe("Cleanup", () => {
195
+ it("destroy removes stage element, style tag, and classes", () => {
196
+ stage = new Stage(container, 9999);
197
+ const el = stage.element;
198
+ const styleTag = getStyleTag()!;
199
+ expect(document.body.contains(el)).toBe(true);
200
+ expect(document.head.contains(styleTag)).toBe(true);
201
+
202
+ stage.destroy();
203
+
204
+ expect(document.body.contains(el)).toBe(false);
205
+ expect(document.head.contains(styleTag)).toBe(false);
206
+ expect(container.classList.contains("supermouse-scope")).toBe(false);
207
+ expect(hasClassPrefix(container, "supermouse-scope-")).toBe(false);
208
+ expect(hasClassPrefix(container, "supermouse-hide-")).toBe(false);
209
+ expect(container.style.cursor).toBe("");
210
+ });
211
+
212
+ it("destroy restores original container cursor", () => {
213
+ container.style.cursor = "crosshair";
214
+ stage = new Stage(container, 9999);
215
+ stage.destroy();
216
+ expect(container.style.cursor).toBe("crosshair");
217
+ });
218
+
219
+ it("creates unique style IDs for multiple instances", () => {
220
+ const stage1 = new Stage(container, 9999);
221
+ const stage2Container = document.createElement("div");
222
+ document.body.appendChild(stage2Container);
223
+ const stage2 = new Stage(stage2Container, 9999);
224
+
225
+ const styleTags = document.querySelectorAll('style[id^="supermouse-style-"]');
226
+ expect(styleTags.length).toBe(2);
227
+ const ids = Array.from(styleTags).map((tag) => tag.id);
228
+ expect(ids[0]).not.toBe(ids[1]);
229
+
230
+ stage1.destroy();
231
+ stage2.destroy();
232
+ stage2Container.remove();
233
+ });
234
+ });
235
+ });
@@ -0,0 +1,50 @@
1
+ import { describe, it, expect, afterEach, vi } from "vitest";
2
+ import { Supermouse } from "../Supermouse";
3
+
4
+ describe("Suspend/Resume cursor state", () => {
5
+ let app: Supermouse;
6
+ let container: HTMLElement;
7
+
8
+ const hasClassPrefix = (el: HTMLElement, prefix: string) =>
9
+ Array.from(el.classList).some((c) => c.startsWith(prefix));
10
+
11
+ afterEach(() => {
12
+ app?.destroy();
13
+ document.body.innerHTML = "";
14
+ document.head.innerHTML = "";
15
+ vi.restoreAllMocks();
16
+ });
17
+
18
+ function createApp(cursor: "auto" | "custom" | "native" | "both" = "auto") {
19
+ container = document.createElement("div");
20
+ document.body.appendChild(container);
21
+ app = new Supermouse({ container, cursor, autoStart: false });
22
+ window.dispatchEvent(
23
+ new PointerEvent("pointermove", { clientX: 100, clientY: 100, pointerType: "mouse" })
24
+ );
25
+ app.start();
26
+ app.step(performance.now() + 16);
27
+ }
28
+
29
+ it("resume() restores native cursor suppression synchronously", () => {
30
+ createApp("auto");
31
+ expect(hasClassPrefix(container, "supermouse-hide-")).toBe(true);
32
+ app.suspend();
33
+ expect(app.stage.style.opacity).toBe("0");
34
+ expect(hasClassPrefix(container, "supermouse-hide-")).toBe(true);
35
+
36
+ app.resume();
37
+ // Immediately after resume, hide class should still be present (native cursor hidden)
38
+ expect(hasClassPrefix(container, "supermouse-hide-")).toBe(true);
39
+ expect(container.style.cursor).toBe("none");
40
+ });
41
+
42
+ it("suspend() hides stage but preserves native cursor suppression state", () => {
43
+ createApp("auto");
44
+ expect(hasClassPrefix(container, "supermouse-hide-")).toBe(true);
45
+
46
+ app.suspend();
47
+ expect(app.stage.style.opacity).toBe("0");
48
+ expect(hasClassPrefix(container, "supermouse-hide-")).toBe(true);
49
+ });
50
+ });
package/src/types.ts CHANGED
@@ -1,183 +1,165 @@
1
- import type { Supermouse } from "./Supermouse";
2
-
3
- export interface MousePosition {
4
- x: number;
5
- y: number;
6
- }
7
-
8
- export interface ShapeState {
9
- width: number;
10
- height: number;
11
- borderRadius: number;
12
- }
13
-
14
- /**
15
- * The Interface for interaction state.
16
- * Plugins should use Module Augmentation to add their specific properties to this interface.
17
- *
18
- * @example
19
- * declare module '@supermousejs/core' {
20
- * interface InteractionState {
21
- * magnetic: boolean | number;
22
- * text: string;
23
- * }
24
- * }
25
- */
26
- export interface InteractionState {
27
- /**
28
- * Allow arbitrary keys for rapid prototyping.
29
- * For type safety, use module augmentation to define expected keys.
30
- */
31
- [key: string]: any;
32
- }
33
-
34
- export interface MouseState {
35
- /** The raw position from the latest pointer event, before smoothing is applied. */
36
- pointer: MousePosition;
37
- /** The current goal position that the core loop is driving toward. */
38
- target: MousePosition;
39
- /** The smoothed/interpolated position used for rendering. */
40
- smooth: MousePosition;
41
- /** The current movement vector derived from the smoothed state. */
42
- velocity: MousePosition;
43
- /** The current movement angle in degrees, derived from velocity. */
44
- angle: number;
45
- /** Whether the pointer is currently pressed down. */
46
- isDown: boolean;
47
- /** Whether the pointer is currently hovering over a registered interactive element. */
48
- isHover: boolean;
49
- /** Whether the runtime has temporarily restored the native cursor due to native-input heuristics. */
50
- isNative: boolean;
51
- /**
52
- * If set, this overrides all auto-detection logic.
53
- * 'auto' = Force Native Cursor (Show)
54
- * 'none' = Force Custom Cursor (Hide Native)
55
- * null = Let the Core decide based on isNative/isHover
56
- */
57
- forcedCursor: "auto" | "none" | null;
58
- /** The DOM element currently being hovered, if any. */
59
- hoverTarget: HTMLElement | null;
60
- /** Whether the user has `prefers-reduced-motion` enabled. */
61
- reducedMotion: boolean;
62
- /** Whether the system has received valid input coordinates at least once. */
63
- hasReceivedInput: boolean;
64
- /** Defines a specific geometric shape the cursor should conform to. */
65
- shape: ShapeState | null;
66
- /** Centralized store for hover metadata from data attributes. */
67
- interaction: InteractionState;
68
- }
69
-
70
- export type NativeIgnoreStrategy = "auto" | "tag" | "css";
71
-
72
- /**
73
- * Configuration options passed to the Supermouse constructor.
74
- */
75
- export interface SupermouseOptions {
76
- /**
77
- * The interpolation factor (0 to 1). Lower is smoother/slower.
78
- * @default 0.15
79
- */
80
- smoothness?: number;
81
- /**
82
- * List of CSS selectors that trigger the "Hover" state.
83
- * Overrides the default set if provided.
84
- */
85
- hoverSelectors?: string[];
86
- /**
87
- * Whether to enable custom cursor effects on touch devices.
88
- * @default false
89
- */
90
- enableTouch?: boolean;
91
- /**
92
- * Whether to automatically disable the custom cursor on devices with coarse pointers.
93
- * @default true
94
- */
95
- autoDisableOnMobile?: boolean;
96
- /**
97
- * Strategy for detecting when to fallback to the native cursor.
98
- * - `'auto'`: Checks both HTML tags and CSS cursor styles (Accurate but slower).
99
- * - `'tag'`: Checks only semantic tags like <input>, <textarea> (Fastest, prevents layout thrashing).
100
- * - `'css'`: Checks only computed CSS cursor styles (Slow, triggers reflow).
101
- * - `null`: Never fallback to native cursor.
102
- *
103
- * @default 'auto'
104
- */
105
- ignoreOnNative?: NativeIgnoreStrategy | null;
106
- /**
107
- * Whether to hide the native cursor via scoped CSS injection on the container.
108
- * When enabled, the stage toggles `cursor: none` on the configured container and
109
- * on registered hover targets.
110
- * @default true
111
- */
112
- hideCursor?: boolean;
113
- /**
114
- * Whether to hide the custom cursor when the pointer leaves the browser viewport.
115
- * When enabled the runtime clears the cursor back to an off-screen position to avoid stale hover state.
116
- * @default true
117
- */
118
- hideOnLeave?: boolean;
119
- /**
120
- * List of plugins to initialize with the instance.
121
- */
122
- plugins?: SupermousePlugin[];
123
- /**
124
- * The DOM element to append the cursor stage to.
125
- * @default document.body
126
- */
127
- container?: HTMLElement;
128
- /**
129
- * Whether to start the internal animation loop automatically.
130
- * @default true
131
- */
132
- autoStart?: boolean;
133
- /**
134
- * Semantic rules mapping CSS selectors to interaction state.
135
- * Rules are evaluated against hovered elements and merged into `state.interaction`.
136
- * Matching data attributes on the same element are also read and can override or enrich the final object.
137
- * @example { 'button': { icon: 'pointer' } }
138
- */
139
- rules?: Record<string, InteractionState>;
140
- /**
141
- * Custom strategy to resolve interaction state from a hovered element.
142
- * When provided, this callback bypasses the default `rules` + `data-[prefix]-*` scraping and
143
- * returns the interaction payload directly for the current hover target.
144
- */
145
- resolveInteraction?: (target: HTMLElement) => InteractionState;
146
- /**
147
- * The prefix used for data attributes to store hover metadata.
148
- * For example, if dataPrefix is "supermouse", then the attribute would be "data-supermouse-*".
149
- * This allows for multiple instances of Supermouse to coexist without conflicting data attributes.
150
- * @default "supermouse"
151
- */
152
- dataPrefix?: string;
153
- }
154
-
155
- /**
156
- * Allows a property to be a static value or a function that returns the value based on state.
157
- * This is primarily useful for plugin option definitions that should react to the current runtime state.
158
- */
159
- export type ValueOrGetter<T> = T | ((state: MouseState) => T);
160
-
161
- /**
162
- * Interface for defining a Supermouse Plugin.
163
- */
164
- export interface SupermousePlugin {
165
- /** Unique name for the plugin. Used for toggling/retrieval. */
166
- name: string;
167
- /** Execution priority. Lower numbers run first. */
168
- priority?: number;
169
- /** If false, update() will not be called. */
170
- isEnabled?: boolean;
171
-
172
- /** Called when `app.use()` is executed. */
173
- install?: (instance: Supermouse) => void;
174
- /** Called on every animation frame with the frame delta time in milliseconds. */
175
- update?: (instance: Supermouse, deltaTime: number) => void;
176
- /** Called when the plugin is removed or the app is destroyed. */
177
- destroy?: (instance: Supermouse) => void;
178
-
179
- /** Called when the plugin is enabled via .enablePlugin() */
180
- onEnable?: (instance: Supermouse) => void;
181
- /** Called when the plugin is disabled via .disablePlugin() */
182
- onDisable?: (instance: Supermouse) => void;
183
- }
1
+ import type { SupermouseInstance } from "./Supermouse";
2
+
3
+ export interface MousePosition {
4
+ x: number;
5
+ y: number;
6
+ }
7
+
8
+ export interface ShapeState {
9
+ width: number;
10
+ height: number;
11
+ borderRadius: number;
12
+ }
13
+
14
+ export type RuleValue = string | boolean | number;
15
+ export type RuleSet = Record<string, RuleValue | ((el: HTMLElement) => RuleValue)>;
16
+ export type RuleDefinition = RuleSet | ((el: HTMLElement) => RuleSet);
17
+
18
+ /**
19
+ * Interaction state consumed by plugins.
20
+ * Extend via module augmentation for type safety.
21
+ *
22
+ * @example
23
+ * declare module '@supermousejs/core' {
24
+ * interface InteractionState {
25
+ * magnetic: boolean | number;
26
+ * text: string;
27
+ * }
28
+ * }
29
+ */
30
+ export interface InteractionState {
31
+ /** Arbitrary keys allowed for quick prototyping. */
32
+ [key: string]: any;
33
+ }
34
+
35
+ export interface MouseState {
36
+ /** Raw pointer position from the latest event (before smoothing). */
37
+ pointer: MousePosition;
38
+ /** Goal position the core loop drives toward. */
39
+ target: MousePosition;
40
+ /** Smoothed position used for rendering. */
41
+ smooth: MousePosition;
42
+ /** Movement vector derived from smoothed state. */
43
+ velocity: MousePosition;
44
+ /** Remaining distance to target. */
45
+ displacement: MousePosition;
46
+ /** Movement angle in degrees. */
47
+ angle: number;
48
+ /** Pointer is pressed down. */
49
+ isDown: boolean;
50
+ /** Hovering over a registered interactive element. */
51
+ isHover: boolean;
52
+ /** Native cursor temporarily restored due to native-input heuristics. */
53
+ isNative: boolean;
54
+ /** Current cursor mode: auto, custom, native, or both. */
55
+ cursorMode: "auto" | "custom" | "native" | "both";
56
+ /** Currently hovered DOM element, if any. */
57
+ hoverTarget: HTMLElement | null;
58
+ /** User has `prefers-reduced-motion` enabled. */
59
+ reducedMotion: boolean;
60
+ /** At least one valid input coordinate received. */
61
+ hasReceivedInput: boolean;
62
+ /** Geometric shape the cursor should conform to. */
63
+ shape: ShapeState | null;
64
+ /** Centralized store for hover metadata from data attributes and rules. */
65
+ interaction: InteractionState;
66
+ }
67
+
68
+ /** Configuration options for the Supermouse constructor. */
69
+ export interface SupermouseOptions {
70
+ /**
71
+ * The interpolation factor (0 to 1). Lower is smoother/slower.
72
+ * @default 0.15
73
+ */
74
+ smoothness?: number;
75
+ /**
76
+ * List of CSS selectors that trigger the "Hover" state.
77
+ * Overrides the default set if provided.
78
+ */
79
+ hoverSelectors?: string[];
80
+ /**
81
+ * Whether to enable custom cursor effects on touch devices.
82
+ * @default false
83
+ */
84
+ enableTouch?: boolean;
85
+ /**
86
+ * Whether to automatically disable the custom cursor on devices with coarse pointers.
87
+ * @default true
88
+ */
89
+ autoDisableOnMobile?: boolean;
90
+ /**
91
+ * Overall cursor mode.
92
+ * - `"auto"`: use built-in heuristic to decide per element.
93
+ * - `"custom"`: always show custom cursor, hide native.
94
+ * - `"native"`: always show native cursor, hide custom.
95
+ * - `"both"`: always show custom cursor **and** native cursor together (no suppression).
96
+ * @default "auto"
97
+ */
98
+ cursor?: "auto" | "custom" | "native" | "both";
99
+ /**
100
+ * Whether to hide the custom cursor when the pointer leaves the browser viewport.
101
+ * @default true
102
+ */
103
+ hideOnLeave?: boolean;
104
+ /**
105
+ * List of plugins to initialize with the instance.
106
+ */
107
+ plugins?: SupermousePlugin[];
108
+ /**
109
+ * The DOM element to append the cursor stage to.
110
+ * @default document.body
111
+ */
112
+ container?: HTMLElement;
113
+ /**
114
+ * Whether to start the internal animation loop automatically.
115
+ * @default true
116
+ */
117
+ autoStart?: boolean;
118
+ /**
119
+ * Semantic rules mapping CSS selectors to interaction state.
120
+ * Rules are evaluated against hovered elements and merged into `state.interaction`.
121
+ * @example { 'button': { icon: 'pointer' } }
122
+ */
123
+ rules?: Record<string, RuleDefinition>;
124
+ /**
125
+ * The prefix used for data attributes to store hover metadata.
126
+ * @default "supermouse"
127
+ */
128
+ dataPrefix?: string;
129
+ /**
130
+ * The `z-index` applied to the cursor stage element.
131
+ * @default 9999
132
+ */
133
+ zIndex?: number;
134
+ }
135
+
136
+ /** Static value or function that returns a value based on state. */
137
+ export type ValueOrGetter<T> = T | ((state: MouseState) => T);
138
+
139
+ /** Interface for defining a Supermouse plugin. */
140
+ export interface SupermousePlugin {
141
+ /** Unique plugin name. */
142
+ name: string;
143
+ /** Execution priority; lower runs first. */
144
+ priority?: number;
145
+ /** If false, `update()` will not be called. */
146
+ isEnabled?: boolean;
147
+ /** Root DOM element, if any. Hidden when plugin disabled. */
148
+ element?: HTMLElement | SVGElement;
149
+
150
+ /** Called when `app.use()` is executed. */
151
+ install?: (instance: SupermouseInstance) => void;
152
+ /** Called every animation frame with delta time in ms. */
153
+ update?: (instance: SupermouseInstance, deltaTime: number) => void;
154
+ /** Called when plugin removed or instance destroyed. */
155
+ destroy?: (instance: SupermouseInstance) => void;
156
+
157
+ /** Called when plugin enabled via `.enablePlugin()`. */
158
+ onEnable?: (instance: SupermouseInstance) => void;
159
+ /** Called when plugin disabled via `.disablePlugin()`. */
160
+ onDisable?: (instance: SupermouseInstance) => void;
161
+ /**
162
+ * Called before the plugin is disabled.
163
+ */
164
+ onBeforeDisable?(instance: SupermouseInstance): void | Promise<void>;
165
+ }
package/vitest.config.ts CHANGED
@@ -5,6 +5,6 @@ export default defineConfig({
5
5
  environment: "jsdom",
6
6
  globals: true,
7
7
  setupFiles: ["src/__tests__/setup.ts"],
8
- include: ["src/**/*.test.ts"]
8
+ include: ["src/**/*.test.ts", "src/**/*.spec.ts"]
9
9
  }
10
10
  });